From 8aac62486f4e2aebd2b9e07998c058fc2a0ba04c Mon Sep 17 00:00:00 2001 From: 3Mad Date: Mon, 24 Aug 2026 20:13:41 +0800 Subject: [PATCH 1/3] fix(setup): install scripts in slim runtime roots Keep bin, lib, and scripts together across Agents, Codex, Factory, OpenCode, Cursor, and Kiro installs. Add Unix symlink and Windows copy regression coverage for commands that load runtime assets from scripts. --- docs/ADDING_A_HOST.md | 4 +- hosts/codex.ts | 5 +- hosts/define-host.ts | 2 +- hosts/opencode.ts | 2 +- scripts/test-free-shards.ts | 9 ++ setup | 26 +++- test/host-config.test.ts | 2 + test/setup-runtime-lib-command.test.ts | 198 +++++++++++++++++++++---- 8 files changed, 209 insertions(+), 39 deletions(-) diff --git a/docs/ADDING_A_HOST.md b/docs/ADDING_A_HOST.md index d699816df9..7569aa48d5 100644 --- a/docs/ADDING_A_HOST.md +++ b/docs/ADDING_A_HOST.md @@ -69,8 +69,8 @@ That expands to the full `HostConfig` with these defaults: (`~/.claude/skills/gstack` → `~/{globalRoot}`, `.claude/skills/gstack` → `{localSkillRoot}`, `.claude/skills` → `{hostSubdir}/skills`) - `suppressedResolvers`: the GBrain pair (`GBRAIN_CONTEXT_LOAD`, `GBRAIN_SAVE_RESULTS`) -- `runtimeRoot`: the shared asset list (`bin`, `browse/dist`, `browse/bin`, - `gstack-upgrade`, `ETHOS.md` + review checklist files) +- `runtimeRoot`: the shared asset list (`bin`, `lib`, `scripts`, `browse/dist`, + `browse/bin`, `gstack-upgrade`, `ETHOS.md` + review checklist files) - `install`: `{ linkingStrategy: 'symlink-generated' }` - `learningsMode: 'basic'` diff --git a/hosts/codex.ts b/hosts/codex.ts index b46e922249..f5962181d6 100644 --- a/hosts/codex.ts +++ b/hosts/codex.ts @@ -18,8 +18,9 @@ const codex = defineHost({ // generateMetadata emits agents/openai.yaml (the format is hardcoded in // gen-skill-docs.ts). Codex also gets a repo-local sidecar at - // .agents/skills/gstack (symlinked runtime assets: bin, browse, review, qa, - // ETHOS.md) — that behavior lives in setup's create_agents_sidecar, not here. + // .agents/skills/gstack (symlinked runtime assets: bin, lib, scripts, browse, + // review, qa, ETHOS.md) — that behavior lives in setup's + // create_agents_sidecar, not here. generation: { generateMetadata: true, skipSkills: ['codex'], // Codex skill is a Claude wrapper around codex exec diff --git a/hosts/define-host.ts b/hosts/define-host.ts index 0ed190dd00..bcf1dd2f3f 100644 --- a/hosts/define-host.ts +++ b/hosts/define-host.ts @@ -105,7 +105,7 @@ export function defineHost(overrides: HostOverrides): toolRewrites, suppressedResolvers = [...GBRAIN_RESOLVERS], runtimeRoot = { - globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], + globalSymlinks: ['bin', 'lib', 'scripts', 'browse/dist', 'browse/bin', 'gstack-upgrade', 'ETHOS.md'], globalFiles: { 'review': ['checklist.md', 'TODOS-format.md'], }, diff --git a/hosts/opencode.ts b/hosts/opencode.ts index fcba39d91f..c74cec7cec 100644 --- a/hosts/opencode.ts +++ b/hosts/opencode.ts @@ -9,7 +9,7 @@ const opencode = defineHost({ // OpenCode links a wider runtime asset set than the shared default // (design binary, review specialists, qa templates/references, DX hall of fame). runtimeRoot: { - globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], + globalSymlinks: ['bin', 'lib', 'scripts', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'], globalFiles: { 'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'], }, diff --git a/scripts/test-free-shards.ts b/scripts/test-free-shards.ts index 140eac3615..a39033f2a2 100755 --- a/scripts/test-free-shards.ts +++ b/scripts/test-free-shards.ts @@ -259,6 +259,15 @@ export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }> // pattern hit is a false positive — the point of these files is Windows // coverage, so auto-excluding them defeats the regression tests they carry. const KNOWN_WINDOWS_SAFE: Array<{ file: string; reason: string }> = [ + { + file: 'test/setup-runtime-lib-command.test.ts', + // Trips the "spawns bin/ shebang script" pattern via installed fixture + // paths, but shell commands are invoked through spawnSync('bash', ...), and + // the TypeScript command through Bun. The Unix symlink cells self-skip on + // win32; the IS_WINDOWS=1 cells must run on windows-latest to cover copied + // lib/ and scripts/ runtime assets. + reason: 'installed bin/ fixture paths are launched explicitly with bash or Bun; Windows copy-install coverage must run on windows-latest', + }, { file: 'test/setup-windows-rerun-refresh.test.ts', // Trips the "spawns bin/ shebang script" pattern via path.join(..., 'bin', diff --git a/setup b/setup index 8c5d6c0772..d0aac60448 100755 --- a/setup +++ b/setup @@ -1130,8 +1130,9 @@ create_agents_sidecar() { mkdir -p "$agents_gstack" # Sidecar directories that skills reference at runtime. bin scripts import - # shared modules via ../lib, so bin and lib must always travel together. - for asset in bin lib browse review qa; do + # shared modules via ../lib and load runtime classifiers/profile data from + # ../scripts, so bin, lib, and scripts must always travel together. + for asset in bin lib scripts browse review qa; do local src="$SOURCE_GSTACK_DIR/$asset" local dst="$agents_gstack/$asset" if [ -d "$src" ] || [ -f "$src" ]; then @@ -1192,6 +1193,9 @@ create_codex_runtime_root() { if [ -d "$gstack_dir/lib" ]; then _link_or_copy "$gstack_dir/lib" "$codex_gstack/lib" fi + if [ -d "$gstack_dir/scripts" ]; then + _link_or_copy "$gstack_dir/scripts" "$codex_gstack/scripts" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$codex_gstack/browse/dist" fi @@ -1247,6 +1251,9 @@ create_factory_runtime_root() { if [ -d "$gstack_dir/lib" ]; then _link_or_copy "$gstack_dir/lib" "$factory_gstack/lib" fi + if [ -d "$gstack_dir/scripts" ]; then + _link_or_copy "$gstack_dir/scripts" "$factory_gstack/scripts" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$factory_gstack/browse/dist" fi @@ -1300,6 +1307,9 @@ create_opencode_runtime_root() { if [ -d "$gstack_dir/lib" ]; then _link_or_copy "$gstack_dir/lib" "$opencode_gstack/lib" fi + if [ -d "$gstack_dir/scripts" ]; then + _link_or_copy "$gstack_dir/scripts" "$opencode_gstack/scripts" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$opencode_gstack/browse/dist" fi @@ -1456,13 +1466,17 @@ create_cursor_runtime_root() { if [ -f "$cursor_dir/gstack/SKILL.md" ]; then _link_or_copy "$cursor_dir/gstack/SKILL.md" "$cursor_gstack/SKILL.md" fi - # bin scripts import shared modules via ../lib — bin and lib travel together. + # bin scripts import shared modules via ../lib and load runtime data from + # ../scripts — bin, lib, and scripts travel together. if [ -d "$gstack_dir/bin" ]; then _link_or_copy "$gstack_dir/bin" "$cursor_gstack/bin" fi if [ -d "$gstack_dir/lib" ]; then _link_or_copy "$gstack_dir/lib" "$cursor_gstack/lib" fi + if [ -d "$gstack_dir/scripts" ]; then + _link_or_copy "$gstack_dir/scripts" "$cursor_gstack/scripts" + fi if [ -d "$gstack_dir/browse/dist" ]; then _link_or_copy "$gstack_dir/browse/dist" "$cursor_gstack/browse/dist" fi @@ -1516,6 +1530,11 @@ create_cursor_sidecar() { _link_or_copy "$repo_root/lib" "$cursor_gstack/lib" fi fi + if [ -d "$repo_root/scripts" ]; then + if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/scripts" ] || [ ! -e "$cursor_gstack/scripts" ]; then + _link_or_copy "$repo_root/scripts" "$cursor_gstack/scripts" + fi + fi if [ -d "$repo_root/browse/dist" ]; then if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$cursor_gstack/browse/dist" ] || [ ! -e "$cursor_gstack/browse/dist" ]; then _link_or_copy "$repo_root/browse/dist" "$cursor_gstack/browse/dist" @@ -1753,6 +1772,7 @@ if [ "$INSTALL_KIRO" -eq 1 ]; then 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/scripts" "$KIRO_GSTACK/scripts" _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/host-config.test.ts b/test/host-config.test.ts index 0a167f409e..0bff826aa6 100644 --- a/test/host-config.test.ts +++ b/test/host-config.test.ts @@ -586,6 +586,8 @@ describe('host config correctness', () => { for (const config of ALL_HOST_CONFIGS) { expect(config.runtimeRoot.globalSymlinks.length).toBeGreaterThan(0); expect(config.runtimeRoot.globalSymlinks).toContain('bin'); + expect(config.runtimeRoot.globalSymlinks).toContain('lib'); + expect(config.runtimeRoot.globalSymlinks).toContain('scripts'); expect(config.runtimeRoot.globalSymlinks).toContain('ETHOS.md'); } }); diff --git a/test/setup-runtime-lib-command.test.ts b/test/setup-runtime-lib-command.test.ts index 1105f91529..8cc4611d0c 100644 --- a/test/setup-runtime-lib-command.test.ts +++ b/test/setup-runtime-lib-command.test.ts @@ -7,10 +7,11 @@ import * as os from 'os'; const ROOT = path.resolve(import.meta.dir, '..'); const SETUP_SRC = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8'); -// gstack-learnings-log is the command from the original bug report: bin scripts -// import shared modules via `$SCRIPT_DIR/../lib`, so a runtime root that -// exposes bin/ without lib/ fails with "Cannot find module .../lib/jsonl-store.ts". -// Running it end-to-end from each installed root proves bin and lib travel together. +// gstack-learnings-log and gstack-question-preference cover both runtime dependency +// classes: bin scripts import shared modules via `$SCRIPT_DIR/../lib`, while +// preference/profile commands load classifiers and registry data from +// `$ROOT_DIR/scripts`. Running them end-to-end from each installed root proves +// bin, lib, and scripts travel together. const PAYLOAD = JSON.stringify({ skill: 'review', type: 'pattern', @@ -48,6 +49,19 @@ interface CommandResult { runStderr: string; learningsWritten: boolean; libIsSymlink: boolean | null; + scriptsIsSymlink: boolean | null; + preferenceStatus: number | null; + preferenceStdout: string; + preferenceStderr: string; + deriveStatus: number | null; + deriveStdout: string; + deriveStderr: string; + vibeStatus: number | null; + vibeStdout: string; + vibeStderr: string; + brainCacheStatus: number | null; + brainCacheStdout: string; + brainCacheStderr: string; supabaseConfigPresent: boolean; } @@ -73,12 +87,46 @@ function buildRootAndRunCommand( ); const libLst = fs.lstatSync(path.join(rootDir, 'lib'), { throwIfNoEntry: false }); + const scriptsLst = fs.lstatSync(path.join(rootDir, 'scripts'), { throwIfNoEntry: false }); const run = spawnSync('bash', [path.join(rootDir, 'bin', 'gstack-learnings-log'), PAYLOAD], { cwd: project, encoding: 'utf-8', timeout: 30000, env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') }, }); + const preference = spawnSync( + 'bash', + [path.join(rootDir, 'bin', 'gstack-question-preference'), '--check', 'runtime-root-probe', '--summary-stdin'], + { + cwd: project, + encoding: 'utf-8', + input: 'ordinary two-way question', + timeout: 30000, + env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') }, + }, + ); + const derive = spawnSync('bash', [path.join(rootDir, 'bin', 'gstack-developer-profile'), '--derive'], { + cwd: project, + encoding: 'utf-8', + timeout: 30000, + env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') }, + }); + const vibe = spawnSync('bash', [path.join(rootDir, 'bin', 'gstack-developer-profile'), '--vibe'], { + cwd: project, + encoding: 'utf-8', + timeout: 30000, + env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') }, + }); + const brainCache = spawnSync( + process.execPath, + [path.join(rootDir, 'bin', 'gstack-brain-cache'), 'meta', '--project', 'runtime-root-probe'], + { + cwd: project, + encoding: 'utf-8', + timeout: 30000, + env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') }, + }, + ); const projectsDir = path.join(home, '.gstack', 'projects'); const learningsWritten = fs.existsSync(projectsDir) @@ -94,6 +142,19 @@ function buildRootAndRunCommand( runStderr: run.stderr, learningsWritten, libIsSymlink: libLst ? libLst.isSymbolicLink() : null, + scriptsIsSymlink: scriptsLst ? scriptsLst.isSymbolicLink() : null, + preferenceStatus: preference.status, + preferenceStdout: preference.stdout, + preferenceStderr: preference.stderr, + deriveStatus: derive.status, + deriveStdout: derive.stdout, + deriveStderr: derive.stderr, + vibeStatus: vibe.status, + vibeStdout: vibe.stdout, + vibeStderr: vibe.stderr, + brainCacheStatus: brainCache.status, + brainCacheStdout: brainCache.stdout, + brainCacheStderr: brainCache.stderr, // Distinct defect (#2215): telemetry-class bin scripts source // $GSTACK_DIR/supabase/config.sh to resolve GSTACK_SUPABASE_URL. The // [ -f ... ] guard means a missing file degrades SILENTLY, so only a @@ -138,6 +199,14 @@ const HOST_ROOTS: Record { script: string; rootDir: ].join('\n'), rootDir: path.join(sandbox, 'home', '.opencode', 'skills', 'gstack'), }), + cursor: (sandbox) => ({ + script: [ + extractFunction('_sidecar_root_user_owned'), + extractFunction('create_cursor_runtime_root'), + `create_cursor_runtime_root "${ROOT}" "${sandbox}/home/.cursor/skills/gstack"`, + ].join('\n'), + rootDir: path.join(sandbox, 'home', '.cursor', 'skills', 'gstack'), + }), kiro: (sandbox) => ({ script: [ `HOME="${sandbox}/home"`, @@ -152,38 +221,85 @@ const HOST_ROOTS: Record { script: string; rootDir: // The IS_WINDOWS=0 cells rely on Unix `ln -snf` semantics; on a real Windows // runner without Developer Mode that silently degrades to a copy — the exact -// bug _link_or_copy works around — so skip there, matching the behavior-matrix -// precedent in setup-windows-fallback.test.ts. The IS_WINDOWS=1 cells exercise -// the Windows copy branch itself, which is plain `cp -R` and portable. -describe.skipIf(process.platform === 'win32')('setup: bin commands resolve sibling lib from every host root', () => { +// bug _link_or_copy works around — so only those cells skip there. The +// IS_WINDOWS=1 cells run on every platform and are force-included in +// test:windows so windows-latest exercises the real copy-install behavior. +describe('setup: runtime commands resolve lib and scripts from every host root', () => { + function assertRuntimeCommands(r: CommandResult): void { + expect(r.runStderr).not.toContain('lib/jsonl-store.ts'); + expect(r.runStatus).toBe(0); + expect(r.learningsWritten).toBe(true); + expect(r.preferenceStatus).toBe(0); + expect(r.preferenceStdout).toContain('ASK_NORMALLY'); + expect(r.preferenceStderr).not.toContain('one-way-doors.ts'); + expect(r.deriveStatus).toBe(0); + expect(r.deriveStdout).toContain('DERIVE: ok'); + expect(r.deriveStderr).not.toContain('registry or signals file missing'); + expect(r.vibeStatus).toBe(0); + expect(r.vibeStdout).toContain('Builder-Coach'); + expect(r.vibeStderr).not.toContain('archetypes.ts'); + expect(r.brainCacheStatus).toBe(0); + expect(r.brainCacheStdout).toContain('"schema_version"'); + expect(r.brainCacheStderr).not.toContain('brain-cache-spec'); + } + for (const [host, buildScript] of Object.entries(HOST_ROOTS)) { - test(`${host} root (symlink install): gstack-learnings-log imports ../lib and writes the learning`, () => { - const r = buildRootAndRunCommand('0', buildScript); - expect(r.buildStatus).toBe(0); - expect(r.libIsSymlink).toBe(true); - expect(r.runStderr).not.toContain('lib/jsonl-store.ts'); - expect(r.runStatus).toBe(0); - expect(r.learningsWritten).toBe(true); - expect(r.supabaseConfigPresent).toBe(true); - }); + test.skipIf(process.platform === 'win32')( + `${host} root (symlink install) runs commands that import ../lib and ../scripts`, + () => { + const r = buildRootAndRunCommand('0', buildScript); + expect(r.buildStatus).toBe(0); + expect(r.libIsSymlink).toBe(true); + expect(r.scriptsIsSymlink).toBe(true); + assertRuntimeCommands(r); + expect(r.supabaseConfigPresent).toBe(true); + }, + 10000, + ); - test(`${host} root (Windows copy install): gstack-learnings-log imports ../lib and writes the learning`, () => { - const r = buildRootAndRunCommand('1', buildScript); - expect(r.buildStatus).toBe(0); - // Windows branch copies: lib must be a real directory, not a symlink. - expect(r.libIsSymlink).toBe(false); - expect(r.runStderr).not.toContain('lib/jsonl-store.ts'); - expect(r.runStatus).toBe(0); - expect(r.learningsWritten).toBe(true); - expect(r.supabaseConfigPresent).toBe(true); - }); + test( + `${host} root (Windows copy install) runs commands that import ../lib and ../scripts`, + () => { + const r = buildRootAndRunCommand('1', buildScript); + expect(r.buildStatus).toBe(0); + expect(r.libIsSymlink).toBe(false); + expect(r.scriptsIsSymlink).toBe(false); + assertRuntimeCommands(r); + expect(r.supabaseConfigPresent).toBe(true); + }, + 10000, + ); + } + + for (const isWindows of ['0', '1'] as const) { + const installMode = isWindows === '0' ? 'symlink' : 'Windows copy'; + test.skipIf(process.platform === 'win32' && isWindows === '0')( + `cursor sidecar (${installMode} install) includes runtime scripts`, + () => { + const r = buildRootAndRunCommand(isWindows, (sandbox) => ({ + script: [ + extractFunction('_sidecar_root_user_owned'), + extractFunction('create_cursor_sidecar'), + `mkdir -p "${sandbox}/repo"`, + `_link_or_copy "${ROOT}/bin" "${sandbox}/repo/bin"`, + `_link_or_copy "${ROOT}/lib" "${sandbox}/repo/lib"`, + `_link_or_copy "${ROOT}/scripts" "${sandbox}/repo/scripts"`, + `create_cursor_sidecar "${sandbox}/repo"`, + ].join('\n'), + rootDir: path.join(sandbox, 'repo', '.cursor', 'skills', 'gstack'), + })); + expect(r.buildStatus).toBe(0); + expect(r.scriptsIsSymlink).toBe(isWindows === '0'); + assertRuntimeCommands(r); + }, + 10000, + ); } // Negative control: a root with bin/ but no lib/ (the pre-fix layout) must - // fail on the ../lib import. Proves the positive cells actually detect the - // regression rather than passing vacuously. + // fail on the ../lib import. Proves the positive cells detect the regression. test('a root missing lib/ beside bin/ fails the ../lib import (pre-fix layout)', () => { - const r = buildRootAndRunCommand('0', (sandbox) => ({ + const r = buildRootAndRunCommand('1', (sandbox) => ({ script: [ `mkdir -p "${sandbox}/broken"`, `_link_or_copy "${ROOT}/bin" "${sandbox}/broken/bin"`, @@ -195,4 +311,26 @@ describe.skipIf(process.platform === 'win32')('setup: bin commands resolve sibli expect(r.runStderr).toContain('lib/jsonl-store.ts'); expect(r.learningsWritten).toBe(false); }); + + test('a root missing scripts/ beside bin/ fails runtime imports (pre-fix layout)', () => { + const r = buildRootAndRunCommand('1', (sandbox) => ({ + script: [ + `mkdir -p "${sandbox}/broken"`, + `_link_or_copy "${ROOT}/bin" "${sandbox}/broken/bin"`, + `_link_or_copy "${ROOT}/lib" "${sandbox}/broken/lib"`, + ].join('\n'), + rootDir: path.join(sandbox, 'broken'), + })); + expect(r.buildStatus).toBe(0); + expect(r.runStatus).toBe(0); + expect(r.learningsWritten).toBe(true); + expect(r.preferenceStatus).not.toBe(0); + expect(r.preferenceStderr).toContain('one-way-doors.ts'); + expect(r.deriveStatus).not.toBe(0); + expect(r.deriveStderr).toContain('registry or signals file missing'); + expect(r.vibeStatus).not.toBe(0); + expect(r.vibeStderr).toContain('archetypes.ts'); + expect(r.brainCacheStatus).not.toBe(0); + expect(r.brainCacheStderr).toContain('brain-cache-spec'); + }); }); From 7cbe7e870c611b1039a3dca44fe67ce231a0cc03 Mon Sep 17 00:00:00 2001 From: 3Mad Date: Mon, 24 Aug 2026 20:13:41 +0800 Subject: [PATCH 2/3] chore: bump version and changelog (v1.69.1.0) Co-Authored-By: OpenAI Codex --- CHANGELOG.md | 16 ++++++++++++++++ VERSION | 2 +- package.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5598a00696..1ee2b76d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.69.1.0] - 2026-08-24 + +**Slim runtime installs now carry every asset their commands load.** + +Setup now keeps `bin/`, `lib/`, and `scripts/` together in Agents sidecars and +the Codex, Factory, OpenCode, Cursor, and Kiro runtime roots. Commands such as +`gstack-question-preference`, `gstack-developer-profile`, and +`gstack-brain-cache` no longer fail because classifiers, registries, profile +data, or cache schema files were left behind. + +Regression coverage exercises real commands through Unix symlink installs and +Windows copy installs, including Cursor's repository sidecar and negative +controls for both missing `lib/` and missing `scripts/` layouts. The declarative +host defaults and host-authoring documentation now describe the same runtime +asset contract. + ## [1.68.3.0] - 2026-08-20 **Re-pairing a browser agent to narrow its access now revokes the old access on** diff --git a/VERSION b/VERSION index f6cb3fdd41..2c3f8d5338 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.68.3.0 +1.69.1.0 diff --git a/package.json b/package.json index fa7526cc13..67ba3e978f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.68.3", + "version": "1.69.1", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", From a154c2647e43dfcb4729929b49f434147d857366 Mon Sep 17 00:00:00 2001 From: 3Mad Date: Sat, 29 Aug 2026 12:55:54 +0800 Subject: [PATCH 3/3] docs: clarify host runtime config boundaries --- docs/ADDING_A_HOST.md | 20 +++++++++++++------- docs/PROJECT_STRUCTURE.md | 5 +++-- scripts/host-config.ts | 6 +++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/ADDING_A_HOST.md b/docs/ADDING_A_HOST.md index 7569aa48d5..da68ed0676 100644 --- a/docs/ADDING_A_HOST.md +++ b/docs/ADDING_A_HOST.md @@ -4,7 +4,9 @@ gstack uses a declarative host config system. Each supported AI coding agent (Claude, Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw, Hermes, GBrain) is defined as a typed TypeScript config object built by the `defineHost()` factory. Adding a new host means creating one file and -re-exporting it. Zero code changes to the generator, setup, or tooling. +re-exporting it for generation and shared tooling. `setup` still has explicit +per-host dispatch and runtime-root implementations, so a host that needs a +slim runtime root must add or update its corresponding setup branch as well. ## How it works @@ -25,16 +27,20 @@ hosts/ ``` Each config file calls `defineHost()` and exports the resulting `HostConfig` -object, which tells the generator: +object, which centralizes: - Where to put generated skills (paths) - How to transform frontmatter (allowlist/denylist fields) - What Claude-specific references to rewrite (paths, tool names) -- What binary to detect for auto-install +- What CLI binaries the inspection/export tooling can detect - What resolver sections to suppress -- What assets to symlink at install time - -The generator, setup script, platform-detect, uninstall, health checks, worktree -copy, and tests all read from these configs. None of them have per-host code. +- What assets a slim runtime root is expected to contain + +The generator, template resolvers, skill checks, worktree copy, config +inspection/export CLI, and tests read from these configs. The setup and +uninstall scripts do not yet consume every corresponding field; in particular, +host configs and setup's explicit runtime-root implementations are maintained +separately. Keep both layers in sync until setup becomes fully table-driven +(tracked in `TODOS.md`). ## Step-by-step: add a new host diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index b20c45a3f8..3eb3ef3e1c 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -18,10 +18,11 @@ gstack/ │ ├── opencode.ts, slate.ts, cursor.ts, openclaw.ts # IDE hosts │ ├── hermes.ts, gbrain.ts # Agent runtime hosts │ └── index.ts # Registry: exports all, derives Host type -├── scripts/ # Build + DX tooling +├── scripts/ # Runtime data/modules + build and DX tooling +│ ├── one-way-doors.ts, archetypes.ts, brain-cache-spec.ts # Runtime dependencies │ ├── gen-skill-docs.ts # Template → SKILL.md generator (config-driven) │ ├── host-config.ts # HostConfig interface + validator -│ ├── host-config-export.ts # Shell bridge for setup script +│ ├── host-config-export.ts # Host config inspection/export CLI │ ├── resolvers/ # Template resolver modules (preamble, design, review, gbrain, etc.) │ ├── skill-check.ts # Health dashboard │ ├── test-paid-shards.ts # Sharded paid-tier runner (one Bun process per shard) diff --git a/scripts/host-config.ts b/scripts/host-config.ts index 47e13fbefe..fa7f648c21 100644 --- a/scripts/host-config.ts +++ b/scripts/host-config.ts @@ -9,9 +9,9 @@ * hosts/*.ts → hosts/index.ts → host-config.ts (this file) * │ │ * └── typed configs ──────────────────→ consumed by gen-skill-docs.ts, - * setup (via host-config-export.ts), - * skill-check.ts, worktree.ts, - * platform-detect, uninstall + * template resolvers, + * host-config-export.ts, + * skill-check.ts, worktree.ts */ import type { Model } from './models';