Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.**
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.60.1.0
1.60.2.0
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 11 additions & 1 deletion setup
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions test/setup-runtime-lib-linking.test.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
});
});
Loading