From 85f3bd7a707d99cb50bcb5bdde57bcc8522289a4 Mon Sep 17 00:00:00 2001 From: Greg Jackson Date: Sat, 1 Aug 2026 19:10:14 +0100 Subject: [PATCH] fix(setup): link lib/ beside bin/ in runtime sidecars (#2305) Fixes #2305. Co-Authored-By: Claude Opus 4.6 --- setup | 9 ++++++ test/setup-lib-sidecar.test.ts | 51 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/setup-lib-sidecar.test.ts diff --git a/setup b/setup index 275236cd36..ca0d1d39b7 100755 --- a/setup +++ b/setup @@ -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 diff --git a/test/setup-lib-sidecar.test.ts b/test/setup-lib-sidecar.test.ts new file mode 100644 index 0000000000..38ff0ddd0b --- /dev/null +++ b/test/setup-lib-sidecar.test.ts @@ -0,0 +1,51 @@ +/** + * Static invariant: runtime sidecar roots (codex, factory, opencode) that link + * bin/ MUST also link lib/, because bin/ scripts import from ../lib/ via + * relative paths (e.g. gstack-learnings-log imports lib/jsonl-store.ts). + * + * Without lib/ beside bin/ in the sidecar, those imports resolve to nothing and + * the script crashes at runtime. + * + * Issue: #2305 + */ + +import { describe, test, expect } from 'bun:test'; +import * as fs from 'fs'; +import * as path from 'path'; + +const SETUP = fs.readFileSync(path.join(import.meta.dir, '..', 'setup'), 'utf-8'); + +describe('setup links lib/ beside bin/ in runtime sidecars', () => { + const SIDECAR_VARS = ['codex_gstack', 'factory_gstack', 'opencode_gstack']; + + for (const sidecar of SIDECAR_VARS) { + test(`${sidecar} links lib/ when bin/ is linked`, () => { + const binPattern = new RegExp( + `_link_or_copy "\\$gstack_dir/bin" "\\$${sidecar}/bin"`, + ); + const libPattern = new RegExp( + `_link_or_copy "\\$gstack_dir/lib" "\\$${sidecar}/lib"`, + ); + + const hasBin = binPattern.test(SETUP); + const hasLib = libPattern.test(SETUP); + + expect(hasBin).toBe(true); + expect(hasLib).toBe(true); + }); + } + + test('lib/ link uses _link_or_copy (not raw ln)', () => { + const libLines = SETUP.split('\n').filter( + (l) => l.includes('/lib"') && /\bln\s+-/.test(l), + ); + expect(libLines).toEqual([]); + }); + + test('lib/ link is guarded by -d check', () => { + const libGuards = SETUP.split('\n').filter((l) => + l.includes('if [ -d "$gstack_dir/lib" ]'), + ); + expect(libGuards.length).toBeGreaterThanOrEqual(3); + }); +});