diff --git a/CHANGELOG.md b/CHANGELOG.md index 351e7cabde..8c8223ffe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.60.2.0] - 2026-07-31 + +## **Runtime roots now ship `lib/` beside `bin/`, so shared JSONL imports work from Codex sidecars.** + +`gstack-learnings-log` imports the shared `lib/jsonl-store.ts` sanitizer from a bash-launched Bun snippet. The source checkout had that file, but Codex/Factory/OpenCode/Kiro runtime roots exposed `bin/` without `lib/`, so invoking the logger through `.agents/skills/gstack/bin/...` failed with `Cannot find module '../lib/jsonl-store.ts'`. + +### Fixed + +- `setup`: installs or links `lib/` next to `bin/` for the Codex `.agents/skills/gstack` sidecar, Codex global runtime root, Factory runtime root, OpenCode runtime root, and Kiro runtime root. +- `test/setup-runtime-lib-linking.test.ts`: locks the installer invariant and reproduces the sidecar path end-to-end, proving `gstack-learnings-log` can import the shared JSONL module and append a learning. + +### Verified + +- `bun test test/setup-runtime-lib-linking.test.ts test/learnings.test.ts test/jsonl-store.test.ts test/gstack-question-log.test.ts test/setup-sections-linking.test.ts test/setup-windows-fallback.test.ts test/bin-windows-bun-import-paths.test.ts` — 75 pass, 0 fail. +- `bun run test` exited 0 on the current base. + ## [1.60.1.0] - 2026-07-09 ## **The /autoplan dual-voice eval is back on the board, catching real regressions.** diff --git a/VERSION b/VERSION index c4190e0048..762f175545 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.60.1.0 +1.60.2.0 diff --git a/package.json b/package.json index 846438a603..78b98a86f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.60.1.0", + "version": "1.60.2.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/setup b/setup index 275236cd36..8c52ce950d 100755 --- a/setup +++ b/setup @@ -749,7 +749,7 @@ create_agents_sidecar() { mkdir -p "$agents_gstack" # Sidecar directories that skills reference at runtime - for asset in bin browse review qa; do + for asset in bin browse review qa lib; do local src="$SOURCE_GSTACK_DIR/$asset" local dst="$agents_gstack/$asset" if [ -d "$src" ] || [ -f "$src" ]; then @@ -796,6 +796,9 @@ create_codex_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$codex_gstack/bin" fi + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$codex_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$codex_gstack/browse/dist" fi @@ -836,6 +839,9 @@ create_factory_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$factory_gstack/bin" fi + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$factory_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$factory_gstack/browse/dist" fi @@ -874,6 +880,9 @@ create_opencode_runtime_root() { if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$opencode_gstack/bin" fi + if [ -d "$gstack_dir/lib" ]; then + _link_or_copy "$gstack_dir/lib" "$opencode_gstack/lib" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$opencode_gstack/browse/dist" fi @@ -1115,6 +1124,7 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then [ -L "$KIRO_GSTACK" ] && rm -f "$KIRO_GSTACK" mkdir -p "$KIRO_GSTACK" "$KIRO_GSTACK/browse" "$KIRO_GSTACK/gstack-upgrade" "$KIRO_GSTACK/review" _link_or_copy "$SOURCE_GSTACK_DIR/bin" "$KIRO_GSTACK/bin" + _link_or_copy "$SOURCE_GSTACK_DIR/lib" "$KIRO_GSTACK/lib" _link_or_copy "$SOURCE_GSTACK_DIR/browse/dist" "$KIRO_GSTACK/browse/dist" _link_or_copy "$SOURCE_GSTACK_DIR/browse/bin" "$KIRO_GSTACK/browse/bin" # ETHOS.md — referenced by "Search Before Building" in all skill preambles diff --git a/test/setup-runtime-lib-linking.test.ts b/test/setup-runtime-lib-linking.test.ts new file mode 100644 index 0000000000..5b959dc831 --- /dev/null +++ b/test/setup-runtime-lib-linking.test.ts @@ -0,0 +1,77 @@ +import { describe, test, expect } from 'bun:test'; +import { spawnSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +const ROOT = path.resolve(import.meta.dir, '..'); +const SETUP = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8'); + +function fnBody(src: string, name: string): string { + const start = src.indexOf(`${name}() {`); + if (start === -1) return ''; + const end = src.indexOf('\n}', start); + return src.slice(start, end === -1 ? undefined : end); +} + +describe('setup runtime roots include lib/ beside bin/', () => { + test('Codex .agents sidecar links lib/ with runtime assets', () => { + const body = fnBody(SETUP, 'create_agents_sidecar'); + expect(body).toContain('for asset in bin browse review qa lib'); + }); + + test('Codex, Factory, OpenCode, and Kiro runtime roots install lib/', () => { + expect(fnBody(SETUP, 'create_codex_runtime_root')).toContain('_link_or_copy "$gstack_dir/lib" "$codex_gstack/lib"'); + expect(fnBody(SETUP, 'create_factory_runtime_root')).toContain('_link_or_copy "$gstack_dir/lib" "$factory_gstack/lib"'); + expect(fnBody(SETUP, 'create_opencode_runtime_root')).toContain('_link_or_copy "$gstack_dir/lib" "$opencode_gstack/lib"'); + expect(SETUP).toContain('_link_or_copy "$SOURCE_GSTACK_DIR/lib" "$KIRO_GSTACK/lib"'); + }); +}); + +describe.skipIf(process.platform === 'win32')('gstack-learnings-log sidecar runtime', () => { + test('runs from .agents/skills/gstack/bin when lib/ is linked beside bin/', () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-sidecar-lib-')); + try { + const project = path.join(tmp, 'project'); + const runtime = path.join(project, '.agents', 'skills', 'gstack'); + const gstackHome = path.join(tmp, 'gstack-home'); + fs.mkdirSync(runtime, { recursive: true }); + fs.mkdirSync(gstackHome, { recursive: true }); + fs.symlinkSync(path.join(ROOT, 'bin'), path.join(runtime, 'bin'), 'dir'); + fs.symlinkSync(path.join(ROOT, 'lib'), path.join(runtime, 'lib'), 'dir'); + + fs.mkdirSync(project, { recursive: true }); + spawnSync('git', ['init', '-b', 'main'], { cwd: project, stdio: 'ignore' }); + spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: project, stdio: 'ignore' }); + spawnSync('git', ['config', 'user.name', 'Test'], { cwd: project, stdio: 'ignore' }); + fs.writeFileSync(path.join(project, 'app.ts'), 'console.log("hello");\n'); + spawnSync('git', ['add', '.'], { cwd: project, stdio: 'ignore' }); + spawnSync('git', ['commit', '-m', 'initial'], { cwd: project, stdio: 'ignore' }); + + const learning = JSON.stringify({ + skill: 'ship', + type: 'operational', + key: 'sidecar-lib-import', + insight: 'sidecar runtime can import shared jsonl-store', + confidence: 9, + source: 'observed', + }); + const result = spawnSync(path.join(runtime, 'bin', 'gstack-learnings-log'), [learning], { + cwd: project, + env: { ...process.env, HOME: tmp, GSTACK_HOME: gstackHome }, + encoding: 'utf-8', + timeout: 15_000, + }); + + expect(result.status).toBe(0); + expect(result.stderr).not.toContain('Cannot find module'); + + const projectDirs = fs.readdirSync(path.join(gstackHome, 'projects')); + expect(projectDirs.length).toBeGreaterThan(0); + const learningsFile = path.join(gstackHome, 'projects', projectDirs[0], 'learnings.jsonl'); + expect(fs.readFileSync(learningsFile, 'utf-8')).toContain('sidecar-lib-import'); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); +});