diff --git a/AGENTS.md b/AGENTS.md index 99a46481c..09bfc9be5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,7 +116,7 @@ their header; edit `scripts/gstack2/` inputs and regenerate instead. ## Platform contract Portable means the canonical skill tree follows the Agent Skills specification. -Verified must name its layer. The six-host project/global/selection/removal +Verified must name its layer. The seven-host project/global/selection/removal matrix is **Verified — installer**; host UI/process execution is still pending. Native means a necessary host API is covered while consuming the same canonical judgment. These labels require evidence; see diff --git a/README.md b/README.md index 6eb80d3aa..ed00ef336 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,10 @@ collects anonymous command-usage telemetry by default; set `DISABLE_TELEMETRY=1` or `DO_NOT_TRACK=1` before invoking it to opt out. GStack does not proxy or add fields to that telemetry. -The candidate installer matrix passes project/global placement for Claude -Code, Codex, Cursor, Pi, OpenClaw, and GitHub Copilot. This verifies files and -canonical hashes; host UI execution remains a separate release gate. +The candidate installer matrix covers project/global placement for Claude +Code, Codex, Kimi Code CLI, Cursor, Pi, OpenClaw, and GitHub Copilot. This +verifies files and canonical hashes; host UI execution remains a separate +release gate. Start with `/plan`, or invoke the skill syntax your host displays. Pure judgment modes work without a shared executable. Capability-dependent modes @@ -71,6 +72,27 @@ Approved installs fetch the pinned official GitHub Release artifact, verify its declared byte count and SHA-256, and use Sigstore metadata when Cosign is already available. Cosign is not an end-user prerequisite. +Browser setup is provider-aware and optional. During onboarding, a user can ask +to configure it immediately; otherwise GStack waits until a workflow first +needs interactive browser evidence. It then detects the active host from its +actual tool surface, explains the required user-controlled setup, and runs a +local navigation/read/click readiness fixture. Supported host-native paths +include Claude in Chrome, the Codex desktop browser, Gemini CLI's experimental +browser agent, VS Code/GitHub Copilot's integrated browser, OpenClaw's browser +plugin, and any interactive Cursor provider actually exposed to the current +session. Kimi Code has fetch/search but no native interactive browser, while Pi +requires an explicitly installed extension; neither is silently upgraded. +Inherited host environment variables never count as provider detection. No +extension, connector, MCP, settings entry, profile, alternate host, or browser +runtime is configured without consent, and `./setup` is never a GStack 2 +browser setup command. + +Install through `npx skills add time-attack/gstack`; do not clone the repository +under `.agents/skills/gstack`. A standards installation exposes exactly one +canonical QA skill at `.agents/skills/qa/SKILL.md`. The cloned compatibility +tree contains legacy GStack 1 entry points and is not the GStack 2 install +surface. + The npm package is deliberately not the skill installer and does not contain the six skill tree or compiled browser/design/PDF payloads. It is the small host-neutral runtime control/bootstrap surface used by release tooling. New diff --git a/browse/src/cli.ts b/browse/src/cli.ts index ad2dc5333..dee7b642f 100644 --- a/browse/src/cli.ts +++ b/browse/src/cli.ts @@ -55,8 +55,6 @@ export function resolveServerScript( ); } -const SERVER_SCRIPT = resolveServerScript(); - /** * On Windows, resolve the Node.js-compatible server bundle. * Falls back to null if not found (server will use Bun instead). @@ -80,17 +78,37 @@ export function resolveNodeServerScript( return null; } -const NODE_SERVER_SCRIPT = resolveNodeServerScript(); -const IS_COMPILED = import.meta.dir.includes('$bunfs'); +export function resolveServerLaunchTarget( + env: Record = process.env, + metaDir: string = import.meta.dir, + execPath: string = process.execPath +): { isCompiled: boolean; nodeServerScript: string | null; sourceServerScript: string | null } { + const isCompiled = metaDir.includes('$bunfs'); + const nodeServerScript = resolveNodeServerScript(metaDir, execPath); + if (isCompiled) { + if (!nodeServerScript) { + throw new Error( + 'server-node.mjs not found. Rebuild the managed browser runtime and run `gstack doctor --skill-api 2.0`.' + ); + } + return { isCompiled, nodeServerScript, sourceServerScript: null }; + } + return { + isCompiled, + nodeServerScript, + sourceServerScript: resolveServerScript(env, metaDir, execPath), + }; +} + +const { + isCompiled: IS_COMPILED, + nodeServerScript: NODE_SERVER_SCRIPT, + sourceServerScript: SERVER_SCRIPT, +} = resolveServerLaunchTarget(); // Every installed/compiled client must use the adjacent Node-compatible daemon. // Source development may fall back to `bun run server.ts` when dist has not // been built yet, but an installed capability must never require host-global Bun. -if (IS_COMPILED && !NODE_SERVER_SCRIPT) { - throw new Error( - 'server-node.mjs not found. Rebuild the managed browser runtime and run `gstack doctor --skill-api 2.0`.' - ); -} interface ServerState { pid: number; @@ -331,6 +349,7 @@ async function startServer(extraEnv?: Record): Promise { }); }); +describe('resolveServerLaunchTarget', () => { + const { resolveServerLaunchTarget } = require('../src/cli'); + + test('compiled clients select adjacent server-node.mjs without requiring source server.ts', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'browse-compiled-launch-')); + const distDir = path.join(root, 'browse', 'dist'); + fs.mkdirSync(distDir, { recursive: true }); + const executable = path.join(distDir, 'browse'); + const nodeServer = path.join(distDir, 'server-node.mjs'); + fs.writeFileSync(executable, 'fixture'); + fs.writeFileSync(nodeServer, 'fixture'); + try { + expect(resolveServerLaunchTarget({}, '/$bunfs/root', executable)).toEqual({ + isCompiled: true, + nodeServerScript: nodeServer, + sourceServerScript: null, + }); + expect(fs.existsSync(path.join(root, 'browse', 'src', 'server.ts'))).toBe(false); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); + + test('compiled clients fail with the managed-runtime remedy when the Node daemon is absent', () => { + expect(() => resolveServerLaunchTarget({}, '/$bunfs/root', '/nonexistent/browse')) + .toThrow('server-node.mjs not found'); + }); +}); + describe('version mismatch detection', () => { test('detects when versions differ', () => { const stateVersion = 'abc123'; diff --git a/docs/gstack-2/ARCHITECTURE.md b/docs/gstack-2/ARCHITECTURE.md index 94c2177ee..f6956c18e 100644 --- a/docs/gstack-2/ARCHITECTURE.md +++ b/docs/gstack-2/ARCHITECTURE.md @@ -95,8 +95,8 @@ host-placement behavior of `./setup` and ten-host generated trees is historical compatibility/development machinery, not the 2.0 installation architecture; the current `./setup` installs only the optional runtime described below. -The standards installer matrix passed 470/470 checks with CLI 1.5.19. It -verified project/global copies for Claude Code, Codex, Cursor, Pi, OpenClaw, and +The standards installer matrix passed 510/510 checks with CLI 1.5.19. It +verified project/global copies for Claude Code, Codex, Kimi Code CLI, Cursor, Pi, OpenClaw, and GitHub Copilot plus selected-skill and opt-in compatibility-alias cases, removal, spaces, source symlink, copy mode, and canonical hashes across 16 installs and two removals. The committed artifact is @@ -233,8 +233,8 @@ means the replacement still lacks its required release evidence. | # | Legacy defect | GStack 2 disposition / replacement | Replacement evidence | |---:|---|---|---| -| 1 | Ten registered hosts; setup fully installs five | **Contained:** placement is delegated to the standard installer. | 470/470 checks across six hosts, 16 installs, two removals, project/global scopes, and selections. Passing live v3/host UI launch remains separate. | -| 2 | Kiro rewrites Codex output | **Contained:** one canonical standards tree; no Kiro rewrite in the 2.0 path. | Six-host matrix installs byte-matching canonical copies without host rewrites. | +| 1 | Ten registered hosts; setup fully installs five | **Contained:** placement is delegated to the standard installer. | 510/510 checks across seven hosts, 18 installs, two removals, project/global scopes, and selections. Passing live v3/host UI launch remains separate. | +| 2 | Kiro rewrites Codex output | **Contained:** one canonical standards tree; no Kiro rewrite in the 2.0 path. | Seven-host matrix installs byte-matching canonical copies without host rewrites. | | 3 | Gitignored external trees defeat freshness CI | **Implemented:** canonical `skills/`, `compat/`, and parity fixtures are committed. | 4,681 parity checks plus installed-file hash equality. | | 4 | External `--dry-run` mutates files | **Contained:** external host generation/dry-run is not used for 2.0 distribution. | Canonical regeneration/parity check exists; a non-mutating canonical check mode is not yet present. | | 5 | Single-host generation failures only warn | **Contained:** no per-host generation in the canonical path; canonical generation throws on failure. | Generator/parity suite and final build rerun are green. | @@ -248,9 +248,9 @@ means the replacement still lacks its required release evidence. | 13 | Production model benchmark imports test helpers | **Implemented:** the runner, pricing, providers, and optional judge live under `lib/model-benchmark/`; the production CLI imports only production modules. | `benchmark-production-boundary.test.ts` rejects imports from `test/` across `bin/` and `lib/`; focused runner and CLI tests exercise the relocated implementation. | | 14 | Default tests omit `design/test` | **Implemented:** package default and free-test roots include `design/test`. | Design is green at 101/0/381 and is included in the uninterrupted 384-file broad pass. | | 15 | Default tests omit `ios-qa/daemon/test` | **Implemented:** package default and free-test roots include the daemon tests. | Focused daemon run: 95 pass / 0 fail / 229 assertions; daemon tests are also included in the uninterrupted broad pass. | -| 16 | Host setup contradicts config-driven claim | **Contained:** host setup is no longer a 2.0 responsibility. | Standard installer CLI 1.5.19 passed all six configured host targets. | +| 16 | Host setup contradicts config-driven claim | **Contained:** host setup is no longer a 2.0 responsibility. | Standard installer CLI 1.5.19 passed all seven configured host targets. | | 17 | Host-generated judgment copies drift | **Implemented:** one canonical module corpus with source-blob/render hashes. | 4,681 parity checks and installed-copy hashes pass. | -| 18 | Updating one host leaves another stale | **Contained:** one tree is installed by each host's standard installer. | Project/global copies across six hosts matched canonical hashes; remote update flow remains installer-owned. | +| 18 | Updating one host leaves another stale | **Contained:** one tree is installed by each host's standard installer. | Project/global copies across seven hosts matched canonical hashes; remote update flow remains installer-owned. | | 19 | State identity crosses worktrees | **Implemented:** repo plus stable worktree identity selects state. | Linked-worktree core test passes. | | 20 | Partial ship failures are not reliably idempotent | **Implemented at runtime primitive:** claimed effects become uncertain and are not automatically repeated. | Crash/resume and completed-effect tests pass. End-to-end ship resume remains pending. | | 21 | Parser failures become empty success | **Implemented in iOS device discovery:** parse/tool failures are typed errors. | `tunnel-bootstrap.test.ts` malformed-JSON regression passes in the focused daemon suite. | diff --git a/docs/gstack-2/HOST-COMPATIBILITY.md b/docs/gstack-2/HOST-COMPATIBILITY.md index 13d4b547c..01135207f 100644 --- a/docs/gstack-2/HOST-COMPATIBILITY.md +++ b/docs/gstack-2/HOST-COMPATIBILITY.md @@ -63,8 +63,8 @@ installed-skill count. ## Candidate installer matrix -The isolated standard-installer matrix passed 470/470 checks with `skills` CLI -1.5.19: 16 install cases and two removal cases. Default discovery projected +The isolated standard-installer matrix passed 510/510 checks with `skills` CLI +1.5.19: 18 install cases and two removal cases. Default discovery projected only canonical `skills/`; separate explicit-selection cases covered a single canonical skill and an opt-in legacy alias. The matrix used symlinked and spaced source paths matching a clean checkout where ignored legacy host trees are @@ -77,6 +77,7 @@ does not install the optional runtime. The committed evidence artifact is |---|---|---|---|---|---|---| | Claude Code | yes | pass | pass | no separate subset case | **Verified — installer** | pending | | OpenAI Codex | yes | pass | pass | global `qa`, `review`, `ship` pass + removal pass; actual selected `qa` runtime-absent run; opt-in alias covered | **Verified — installer**; runtime-absent invocation passed | live v1/v2/v3 failed; v3 was 3/4 | +| Kimi Code CLI | yes | pass | pass | no separate subset case | **Verified — installer** | browser automation uses the consented GStack local-browser fallback; host skill invocation pending | | Cursor | yes | pass | pass | project `qa`, `review`, `ship` pass + removal pass | **Verified — installer** | pending | | Pi | yes | pass | pass | no separate subset case | **Verified — installer** | pending | | OpenClaw | yes | pass | pass | project `ship` single-skill pass | **Verified — installer** | pending | @@ -89,7 +90,7 @@ OpenClaw and explicitly selected the `office-hours` compatibility alias for Codex; neither alias nor unselected canonical skill was silently enrolled. `--copy` was advertised and used. -This is filesystem/installer verification, not a claim that six host UIs loaded +This is filesystem/installer verification, not a claim that seven host UIs loaded or executed the skills. A separate actual Codex invocation installed only `qa` from `time-attack/gstack/skills`, with the optional runtime absent, and passed the judgment/setup-gate behavior without changing its workspace or creating a @@ -134,7 +135,7 @@ bun run scripts/gstack2/test-install-matrix.ts --full \ --output /tmp/gstack2-install-matrix.json ``` -The current matrix passed 470/470 installer CLI checks across 16 installs and two +The current matrix passed 510/510 installer CLI checks across 18 installs and two removals; its JSON artifact is committed at [`evals/installation/install-matrix.json`](../../evals/installation/install-matrix.json). Steps 8–9 passed for the recorded Codex runtime-absent invocation; actual UI diff --git a/docs/gstack-2/JUDGMENT-PARITY.md b/docs/gstack-2/JUDGMENT-PARITY.md index 56d55cbb2..85a92591e 100644 --- a/docs/gstack-2/JUDGMENT-PARITY.md +++ b/docs/gstack-2/JUDGMENT-PARITY.md @@ -2,7 +2,7 @@ Parity is executable, not a prose claim. Run `bun run scripts/gstack2/run-parity.ts` or the dedicated Bun tests. -The pinned release inventory passes **4,697 checks** across 55 specialist sources, 16 carved sections, 25 routing scenarios, 16 regression ports, and **78 assets**. +The pinned release inventory passes **4,833 checks** across 55 specialist sources, 16 carved sections, 25 routing scenarios, 16 regression ports, and **78 assets**. The suite verifies: diff --git a/docs/gstack-2/JUDGMENT-PROVENANCE.json b/docs/gstack-2/JUDGMENT-PROVENANCE.json index d2999fa5c..89043877e 100644 --- a/docs/gstack-2/JUDGMENT-PROVENANCE.json +++ b/docs/gstack-2/JUDGMENT-PROVENANCE.json @@ -20,7 +20,7 @@ "bug_fix_ports": 16, "assets": 78, "dependency_copies": 4, - "runtime_helpers": 40 + "runtime_helpers": 41 }, "sources": [ { @@ -83,7 +83,7 @@ "source_path": "office-hours/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "8568fe73cca76a80805fab3092cacd10db7e1d7f", - "normalized_render_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "normalized_render_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "target": "skills/plan/references/legacy/office-hours.md", "disposition": "BUG_FIX", "overlays": [ @@ -197,7 +197,7 @@ "source_path": "plan-ceo-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c43cfe64146fe79df74a974f1673fe36defcce00", - "normalized_render_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "normalized_render_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "target": "skills/plan/references/legacy/plan-ceo-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -337,7 +337,7 @@ "source_path": "plan-eng-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1d5be0e6f47f0896ee831b53c56818850b1dcfe4", - "normalized_render_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "normalized_render_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "target": "skills/plan/references/legacy/plan-eng-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -457,7 +457,7 @@ "source_path": "plan-devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1ef723c10997a08ef87940daceb08bf8d60dd810", - "normalized_render_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "normalized_render_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "target": "skills/plan/references/legacy/plan-devex-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -590,7 +590,7 @@ "source_path": "autoplan/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b2eaca9fde8f46001bea9961b8ed651d0f6f9e13", - "normalized_render_sha256": "b11bef3e060400cd59a16e41c9b6f8395ace065867a1c2a70329319153a2c4dc", + "normalized_render_sha256": "dffedf321083151a10e7377dba6dcfd5507b678c5279f5d47a1b0d91bfe53e09", "target": "skills/plan/references/legacy/autoplan.md", "disposition": "BUG_FIX", "overlays": [ @@ -681,7 +681,7 @@ "source_path": "spec/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "6c0c14e1b37e1e56d85427e9c8d080a6df908992", - "normalized_render_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "normalized_render_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "target": "skills/plan/references/legacy/spec.md", "disposition": "BUG_FIX", "overlays": [ @@ -814,7 +814,7 @@ "source_path": "plan-tune/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "dc1214d4c023ed2b126aca8aedb4153b222e73c5", - "normalized_render_sha256": "306bbf971a2867e1f39cce82d64289d6c457bceb07439ddd6f4a3ff8f8ae5e9a", + "normalized_render_sha256": "f2204078e497a347b48a2b0899400baf3b6ab54f097bbd57d06deb5f71126ce9", "target": "skills/plan/references/legacy/plan-tune.md", "disposition": "BUG_FIX", "overlays": [ @@ -893,7 +893,7 @@ "source_path": "context-save/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "a3702bc95439cddd87841aba836708bf511ef55d", - "normalized_render_sha256": "348b840c60cecd8818d75f63ebb8e3effae659437137ce75c1ba6124177120b6", + "normalized_render_sha256": "f457da8b2cc1f9a4ed8ba82f4ea570a69460ce0147e171b5023d66324b6dc899", "target": "skills/plan/references/legacy/context-save.md", "disposition": "BUG_FIX", "overlays": [ @@ -968,7 +968,7 @@ "source_path": "context-restore/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "55889f6e06d3ba896f33a16969959c700bc24f1e", - "normalized_render_sha256": "c14726232c67616485c115dd7ca0898992bab9c7990629b40dc3b6f1c0dec0ee", + "normalized_render_sha256": "44c64ae4a63d0ed0fbc427ee2e4f0330d5074c14cdc589baa660dd7a22dbcdf9", "target": "skills/plan/references/legacy/context-restore.md", "disposition": "BUG_FIX", "overlays": [ @@ -1040,7 +1040,7 @@ "source_path": "learn/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "90d08d2298cccd0d5090f194a2cd76a5361b4323", - "normalized_render_sha256": "0d1bbdf1bba2eaae206a350d47fb25ab50d49611262cb376861e83f78822f762", + "normalized_render_sha256": "b56cfcec0690c47124cf62e40aea16658933b61d44adbe821449f8c65a96a7c4", "target": "skills/plan/references/legacy/learn.md", "disposition": "BUG_FIX", "overlays": [ @@ -1113,7 +1113,7 @@ "source_path": "retro/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b0819c8a6b736baf489179ba587442cf9973b9d2", - "normalized_render_sha256": "c92e69da5c62e4bb5a426f4fb5623b41215e538315e2faebece8ba118cacaf71", + "normalized_render_sha256": "d2dd2fe80fbeb5573266700052e0ce5e781dcd31ed986cd9203880bc98c9d9c8", "target": "skills/plan/references/legacy/retro.md", "disposition": "BUG_FIX", "overlays": [ @@ -1240,7 +1240,7 @@ "source_path": "setup-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f48581543b46ecf889f4f86fb7a0d9e75d0bd6ca", - "normalized_render_sha256": "244ebee93e8871d59c1b0eac040bed362783e48a9b134171d743750ce9a59b29", + "normalized_render_sha256": "ea0759f957601c05a6e131ee3d1d9e07c732b7852d8a7b10c1fdc259e6e12c3b", "target": "skills/plan/references/legacy/setup-gbrain.md", "disposition": "BUG_FIX", "overlays": [ @@ -1340,7 +1340,7 @@ "source_path": "sync-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "2ec065472e263a07f3818ed87ee9a6a2e13ca3ae", - "normalized_render_sha256": "175e7d0d2998bd4c8ddcd9a84228c41ba4a5cf3aca3a8bc09ddfc5a303c1df30", + "normalized_render_sha256": "973d3d126e97f2c720bc048e6a124e51c0195f9bc7a714ffc41fd57af37f4e73", "target": "skills/plan/references/legacy/sync-gbrain.md", "disposition": "BUG_FIX", "overlays": [ @@ -1416,7 +1416,7 @@ "source_path": "design-consultation/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "64af56ecdbd132cb7c28344e8e4ecb2e5dacf811", - "normalized_render_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "normalized_render_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "target": "skills/design/references/legacy/design-consultation.md", "disposition": "BUG_FIX", "overlays": [ @@ -1504,7 +1504,7 @@ "source_path": "design-shotgun/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "230dbc2922f05bf272bf5168a958a12604fac1bc", - "normalized_render_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "normalized_render_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "target": "skills/design/references/legacy/design-shotgun.md", "disposition": "BUG_FIX", "overlays": [ @@ -1591,7 +1591,7 @@ "source_path": "design-html/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3cdec9a14d62d2e046ed924c972efc30a7d43aca", - "normalized_render_sha256": "cbb1b4357bedbb0fffe23c3e0ad31ee8c2a198a31443edb660e5c836e67e94e6", + "normalized_render_sha256": "40682d97ac83aa9178487348d5abf176334fd439e2d12f8e5cda1f8b20cd2c30", "target": "skills/design/references/legacy/design-html.md", "disposition": "BUG_FIX", "overlays": [ @@ -1687,7 +1687,7 @@ "source_path": "plan-design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7178c991e41277410de500298cf81372543861af", - "normalized_render_sha256": "2f7a5d113c466d75ca4524e424c072044d92ae7ab93009f1fbe3ae5965e8aa34", + "normalized_render_sha256": "1ff3644728bee14a4a002b91bada44b1c95a20ee5779ed458dabca2485a19e78", "target": "skills/design/references/legacy/plan-design-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -1820,7 +1820,7 @@ "source_path": "design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "bdcda48e29b489a1cc49faa333922412251d4b41", - "normalized_render_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "normalized_render_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "target": "skills/design/references/legacy/design-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -2022,7 +2022,7 @@ "source_path": "diagram/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9e19a52c6b7f727ce4faf5c1f9c14514ecb52f50", - "normalized_render_sha256": "21f18fa4957b79bf5941abfebd1b222dee3eb960520e0deac1c32d0b6fd62a38", + "normalized_render_sha256": "18b1db5eb50d85a6da26b60ac72a19c144e95bb4a2d92b2d23a87086c49c01b7", "target": "skills/design/references/legacy/diagram.md", "disposition": "BUG_FIX", "overlays": [ @@ -2095,7 +2095,7 @@ "source_path": "make-pdf/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9133a711d4f3d056a21f790e8ec3b98f13fbaa50", - "normalized_render_sha256": "c092bc644ff9b8929d9cbdfedb6f2761b40ce8e1fca53d13ad350ba91195104d", + "normalized_render_sha256": "ece1cfa64bb3cbb3979ce06890e09bbc2ccab4e1d49310c57dc28e70e1236ee9", "target": "skills/design/references/legacy/make-pdf.md", "disposition": "BUG_FIX", "overlays": [ @@ -2162,7 +2162,7 @@ "source_path": "qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "11997f7b878282c34b6bfd3d4b7a8131f9ad4da8", - "normalized_render_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "normalized_render_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "target": "skills/qa/references/legacy/qa.md", "disposition": "BUG_FIX", "overlays": [ @@ -2288,7 +2288,7 @@ "source_path": "qa-only/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75c4123cc5c406ffdd36c71a094335c137135b1e", - "normalized_render_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "normalized_render_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "target": "skills/qa/references/legacy/qa-only.md", "disposition": "BUG_FIX", "overlays": [ @@ -2389,7 +2389,7 @@ "source_path": "ios-qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "e93d2831a31df65ec8e4e8693919ef5bf148457f", - "normalized_render_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "normalized_render_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "target": "skills/qa/references/legacy/ios-qa.md", "disposition": "BUG_FIX", "overlays": [ @@ -2466,7 +2466,7 @@ "source_path": "devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "081d4f35bbdec0c6b3da8ae71615ec4d41a84551", - "normalized_render_sha256": "9fbc11b3fe252c281581512d67f6915c72e34bb2fdc7bc795e3077ee0e48d34e", + "normalized_render_sha256": "4a907c759b6cf4202fbacaea504b1eb601a53dd35b206109d6c5105168ade7e1", "target": "skills/qa/references/legacy/devex-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -2565,7 +2565,7 @@ "source_path": "benchmark/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "038f16f5fba4ae4e9eae922e3276bba8ef88149e", - "normalized_render_sha256": "a4554b8b139ee95c9f25c2e747f9e2214b2f0db64401057529e5ec775be323cd", + "normalized_render_sha256": "05ac1b123a605201546a7e95899a5b55708ca7fbb7569c58b4d755c52b45a92d", "target": "skills/qa/references/legacy/benchmark.md", "disposition": "BUG_FIX", "overlays": [ @@ -2631,7 +2631,7 @@ "source_path": "canary/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d1eb2950aba2fa2b09d90f13143492c60d46793c", - "normalized_render_sha256": "329b61120f60893d023533cbc493389d1c7476819fb785422de0e7d34e0c2c0b", + "normalized_render_sha256": "89be5f218da2bd812303c87b8c177081727727e5e0d2dc74eb7a73299794d5ef", "target": "skills/qa/references/legacy/canary.md", "disposition": "BUG_FIX", "overlays": [ @@ -2710,7 +2710,7 @@ "source_path": "browse/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9a159e4c9820172c229e2174d4a62a8f9668ab93", - "normalized_render_sha256": "b7fd526a00444003cad654abb15c2d17606cc5295f38533754e95108038d3467", + "normalized_render_sha256": "1b532bd904b1fa1686113e8c96b70015ea6b2e6df7319a72c299de901fe5e81b", "target": "skills/qa/references/legacy/browse.md", "disposition": "BUG_FIX", "overlays": [ @@ -2798,7 +2798,7 @@ "source_path": "open-gstack-browser/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ef91a527890a3ac3622cc7dc84bad1ff7b64443b", - "normalized_render_sha256": "9e4a08db3e17badfc67703f7c5e66f80a7f05e0b96fad31fc48b8216964d7449", + "normalized_render_sha256": "df626d71b8cea4a02d2fb7aef3169563dd132bf17a9d6d84f287894cad84d2cf", "target": "skills/qa/references/legacy/open-gstack-browser.md", "disposition": "BUG_FIX", "overlays": [ @@ -2873,7 +2873,7 @@ "source_path": "setup-browser-cookies/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f812d9f56f27c32fb5f102083bbe418344c1a652", - "normalized_render_sha256": "8ab5dfb05136a637ce41067d9e50ade76c4a8b957587e4eab134eaa260b29ac3", + "normalized_render_sha256": "04c161a58c1a9010efe38095b383b0e1d445a2b678e5bf931a1281d45196940d", "target": "skills/qa/references/legacy/setup-browser-cookies.md", "disposition": "BUG_FIX", "overlays": [ @@ -2934,7 +2934,7 @@ "source_path": "pair-agent/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75ed42d590f99c46cd0883c37bb1f2f9f499211c", - "normalized_render_sha256": "6882bc549c689ff50fb06d5e72a597939aa651926c50067330d1e6fd86cd6b58", + "normalized_render_sha256": "8557ca390d0b6548f956d2c0e9316f1cf137689d4dc17d40a4525d19f22bc457", "target": "skills/qa/references/legacy/pair-agent.md", "disposition": "BUG_FIX", "overlays": [ @@ -3017,7 +3017,7 @@ "source_path": "scrape/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "4cb4f17c074edcdce0bc8d133f19a6a739964851", - "normalized_render_sha256": "03ef708a4c9a1f3de961c48767617502c564690faa7240b3ca39b20496a17f98", + "normalized_render_sha256": "82b7214021c997000822b2b4adcae19443490198556a7e6af54d8937ae504943", "target": "skills/qa/references/legacy/scrape.md", "disposition": "BUG_FIX", "overlays": [ @@ -3094,7 +3094,7 @@ "source_path": "skillify/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "21fb2f503e3d40683fa782b21abf05a8f0fef69b", - "normalized_render_sha256": "b0743c957157e19bd90b457fd3ac9d6924c3aedce969c10af8d7973c2bcd6c9f", + "normalized_render_sha256": "8743671a165c951e852d1f37418aa6c0bd7eeb4f32866dc25f55f7d8eb26f1d5", "target": "skills/qa/references/legacy/skillify.md", "disposition": "BUG_FIX", "overlays": [ @@ -3177,7 +3177,7 @@ "source_path": "benchmark-models/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "034cda182406dc04a82c4336ac3ebc36b5fc41b1", - "normalized_render_sha256": "2ef0679d45f21bacc09cd774ff96bb3b82853a8e89d8606847c4f9416a47a48b", + "normalized_render_sha256": "3ea3c6cd3542350d4130ee0d5e170c905d0c5c3da096b1fc0e805e095af9321c", "target": "skills/qa/references/legacy/benchmark-models.md", "disposition": "BUG_FIX", "overlays": [ @@ -3237,7 +3237,7 @@ "source_path": "investigate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "67e254d743ffb9060f48e3f6d4b715c077ee688d", - "normalized_render_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "normalized_render_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "target": "skills/debug/references/legacy/investigate.md", "disposition": "BUG_FIX", "overlays": [ @@ -3433,7 +3433,7 @@ "source_path": "freeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c0b31aa7f9f216fc5a351d91f4bcff68c828d090", - "normalized_render_sha256": "433bb7c1909852c83978ae282c582590c5136abe56020e7d0be39749822c345b", + "normalized_render_sha256": "036fe2c7d5c154982ba3509e9b9ab1f5867f86c1d71beb569802d7f7be46ed7c", "target": "skills/debug/references/legacy/freeze.md", "disposition": "BUG_FIX", "overlays": [ @@ -3477,7 +3477,7 @@ "source_path": "guard/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3d34ee0c181ec7b263bf6092ba8f384619c5efb6", - "normalized_render_sha256": "ff8170babcc9ad20f6de292db838d2c4545f0ed98dddd184ab5e7c52c073dc7e", + "normalized_render_sha256": "36072a06a2a2ab6c1beec2a888417fb96eb1a50f284cb26ee3796b7c69857be8", "target": "skills/debug/references/legacy/guard.md", "disposition": "BUG_FIX", "overlays": [ @@ -3520,7 +3520,7 @@ "source_path": "unfreeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "88e413fe5a49a45d46d8867b2d80ace30b3b45aa", - "normalized_render_sha256": "51d6183901e866697382b7e900e2154e8bfb6a9ebf4f3e1bb3ad29925fad0e20", + "normalized_render_sha256": "19cd17084af59cb912f9507bf8ffdc81e38aa1fbef14605a838efbc6521e9534", "target": "skills/debug/references/legacy/unfreeze.md", "disposition": "BUG_FIX", "overlays": [ @@ -3562,7 +3562,7 @@ "source_path": "review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ba1ea88068de4b09cf717eb4ae42aa247d198314", - "normalized_render_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "normalized_render_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "target": "skills/review/references/legacy/review.md", "disposition": "BUG_FIX", "overlays": [ @@ -3667,7 +3667,7 @@ "source_path": "cso/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "413fb099597b55dadca116e45a202aa693a94b74", - "normalized_render_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "normalized_render_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "target": "skills/review/references/legacy/cso.md", "disposition": "BUG_FIX", "overlays": [ @@ -3769,7 +3769,7 @@ "source_path": "health/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f92eb7347ec4f77dd8dbf464d63fcdf6a3459908", - "normalized_render_sha256": "ff664221e7de57e66c1b45d719982d90f38e0288914b34731ba4dd73541347e8", + "normalized_render_sha256": "514ff165b393a25707c9ed336869ac0b50c63a00d90a21b1656276e1b97842d9", "target": "skills/review/references/legacy/health.md", "disposition": "BUG_FIX", "overlays": [ @@ -3842,7 +3842,7 @@ "source_path": "codex/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "333de7d8d590cc78184b0e2371171f6121408f73", - "normalized_render_sha256": "0df6ff685d230f87763b4c43f957e20854fe1319d78247d683e5250e2accb188", + "normalized_render_sha256": "94ebbe955a1063f88712c51723023765519eab8986b029bf1222d0ec19b79e12", "target": "skills/review/references/legacy/codex.md", "disposition": "BUG_FIX", "overlays": [ @@ -4001,7 +4001,7 @@ "source_path": "ship/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "068ac4fe54bcb46572295d48263b11bd38fcde18", - "normalized_render_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "normalized_render_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "target": "skills/ship/references/legacy/ship.md", "disposition": "BUG_FIX", "overlays": [ @@ -4136,7 +4136,7 @@ "source_path": "land-and-deploy/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "98976ad020d541d251cc7e34802a13458ddc88e2", - "normalized_render_sha256": "3fab0678a9a7a76664330c74db2bd25b56c335ac72ce4e691c2f2564a7ede819", + "normalized_render_sha256": "6920f3d97ce474b8f20c8b3e38ca9d3c03973e47af33103a60bab7c02eb867bd", "target": "skills/ship/references/legacy/land-and-deploy.md", "disposition": "BUG_FIX", "overlays": [ @@ -4244,7 +4244,7 @@ "source_path": "landing-report/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "32a8cc1ab036b1ff8692f0400b68d8b56224a251", - "normalized_render_sha256": "4f9512bafcfd995c4c35cdff869ed60f8a9a9ef12f20bc18bf7b898cbd2faa53", + "normalized_render_sha256": "ae5050b0558b5324f58ee9181dc83ac0189b63da70d94f86792c08e112765fe9", "target": "skills/ship/references/legacy/landing-report.md", "disposition": "BUG_FIX", "overlays": [ @@ -4318,7 +4318,7 @@ "source_path": "document-release/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7621cb31290217b5c7cc8fb000e910b3dd38128f", - "normalized_render_sha256": "e5aa85ac93b36b638d075d152f9d7108c0108727056f61331b25fe33de569a59", + "normalized_render_sha256": "21de7096aa788e3c4206587761f3122016f4cfc3080ec07f07cd7698075307a5", "target": "skills/ship/references/legacy/document-release.md", "disposition": "BUG_FIX", "overlays": [ @@ -4475,7 +4475,7 @@ "source_path": "document-generate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d3ef0cbc3786c4010b3c692fb94246f56a7e1d7b", - "normalized_render_sha256": "71726ef3082b6c00a5cad19d28da4d830f8e9b261d213a7938c4e1ce3b8c54bb", + "normalized_render_sha256": "d91adb16f1749c24bc7a05aa6cff3ff00bb0db005872751f417e66de2057e620", "target": "skills/ship/references/legacy/document-generate.md", "disposition": "BUG_FIX", "overlays": [ @@ -4677,7 +4677,7 @@ "source_path": "ios-sync/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "156a33c4c35d3bad804e44c93ae4c931878489f3", - "normalized_render_sha256": "c51e8865876efae833e92d54b37780afd4fa6240c13231dfb7d4635948fbc73f", + "normalized_render_sha256": "88f9f20aa406c13098ebd2709afc6487bb42c5466ac9f757bf4d9baff7f028d0", "target": "skills/ship/references/legacy/ios-sync.md", "disposition": "BUG_FIX", "overlays": [ @@ -5759,7 +5759,7 @@ "owner_tree": "design", "consumer_tree": "plan", "target": "skills/plan/references/legacy/plan-design-review.md", - "sha256": "0c9930db5c50e873318cbbf00b9147edff82a210fdbdf2aacb2a8edfdf1f6463", + "sha256": "5ff3595a7a6e9136d68678e2810001033beb0b92c3f139b1c749e73398ed4b9a", "disposition": "SHARED_MODULE" }, { @@ -5767,7 +5767,7 @@ "owner_tree": "debug", "consumer_tree": "qa", "target": "skills/qa/references/legacy/investigate.md", - "sha256": "abc5ef0411163a0959605f259e54623033f0936942a347e74ed82f13af001a3c", + "sha256": "ebd663fead64cc7d92969742d06bbb18be6f21be7010bba82fc82a27f84efc5e", "disposition": "SHARED_MODULE" }, { @@ -5775,7 +5775,7 @@ "owner_tree": "qa", "consumer_tree": "ship", "target": "skills/ship/references/legacy/canary.md", - "sha256": "21252c073bfa2c90f5ad6883feb8ee63d893febbd981a842ec5f7611b8807f2e", + "sha256": "9fcf4fdea7d52113c8f5b5cc81c1fb36df591cf9e07bcc38c4ee106b15245997", "disposition": "SHARED_MODULE" }, { @@ -5783,7 +5783,7 @@ "owner_tree": "plan", "consumer_tree": "ship", "target": "skills/ship/references/legacy/context-restore.md", - "sha256": "8658c29bfbe2097aa9e3cc4e6b04fd0c15f77fd3c256c238415dffa93e120982", + "sha256": "1b1bd416815b759ea2f60565f0959c129d62e88991118680aab45669e54da82f", "disposition": "SHARED_MODULE" } ], @@ -6494,6 +6494,7 @@ "runtime_helpers": [ { "name": "browse", + "delivery": "repository-payload", "source_path": "browse/dist/browse", "platform_source_paths": { "posix": "browse/dist/browse", @@ -6550,8 +6551,66 @@ ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/browse" }, + { + "name": "bun", + "delivery": "managed-runtime-artifact", + "component": "core", + "build_step": "managed-bun", + "source_path": ".gstack-runtime-tools/bun", + "consumer_modules": [ + "autoplan", + "benchmark", + "benchmark-models", + "browse", + "canary", + "codex", + "context-restore", + "context-save", + "cso", + "design-consultation", + "design-html", + "design-review", + "design-shotgun", + "devex-review", + "diagram", + "document-generate", + "document-release", + "freeze", + "guard", + "health", + "investigate", + "ios-qa", + "ios-sync", + "land-and-deploy", + "landing-report", + "learn", + "make-pdf", + "office-hours", + "open-gstack-browser", + "pair-agent", + "plan-ceo-review", + "plan-design-review", + "plan-devex-review", + "plan-eng-review", + "plan-tune", + "qa", + "qa-only", + "retro", + "review", + "scrape", + "setup-browser-cookies", + "setup-gbrain", + "ship", + "skillify", + "spec", + "sync-gbrain", + "unfreeze" + ], + "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/bun" + }, { "name": "gstack", + "delivery": "repository-payload", "source_path": "bin/gstack", "consumer_modules": [ "diagram", @@ -6564,6 +6623,7 @@ }, { "name": "gstack-artifacts-init", + "delivery": "repository-payload", "source_path": "bin/gstack-artifacts-init", "consumer_modules": [ "setup-gbrain" @@ -6572,6 +6632,7 @@ }, { "name": "gstack-brain-cache", + "delivery": "repository-payload", "source_path": "bin/gstack-brain-cache", "consumer_modules": [ "office-hours", @@ -6584,6 +6645,7 @@ }, { "name": "gstack-codex-probe", + "delivery": "repository-payload", "source_path": "bin/gstack-codex-probe", "consumer_modules": [ "autoplan", @@ -6593,6 +6655,7 @@ }, { "name": "gstack-config", + "delivery": "repository-payload", "source_path": "bin/gstack-config", "consumer_modules": [ "autoplan", @@ -6607,6 +6670,7 @@ }, { "name": "gstack-decision-log", + "delivery": "repository-payload", "source_path": "bin/gstack-decision-log", "consumer_modules": [ "ship", @@ -6616,6 +6680,7 @@ }, { "name": "gstack-design", + "delivery": "repository-payload", "source_path": "design/dist/design", "platform_source_paths": { "posix": "design/dist/design", @@ -6674,6 +6739,7 @@ }, { "name": "gstack-developer-profile", + "delivery": "repository-payload", "source_path": "bin/gstack-developer-profile", "consumer_modules": [ "office-hours", @@ -6683,6 +6749,7 @@ }, { "name": "gstack-diff-scope", + "delivery": "repository-payload", "source_path": "bin/gstack-diff-scope", "consumer_modules": [ "land-and-deploy", @@ -6692,6 +6759,7 @@ }, { "name": "gstack-distill-apply", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-apply", "consumer_modules": [ "plan-tune" @@ -6700,6 +6768,7 @@ }, { "name": "gstack-distill-free-text", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-free-text", "consumer_modules": [ "plan-tune" @@ -6708,6 +6777,7 @@ }, { "name": "gstack-gbrain-detect", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-detect", "consumer_modules": [ "setup-gbrain", @@ -6717,6 +6787,7 @@ }, { "name": "gstack-gbrain-install", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-install", "consumer_modules": [ "setup-gbrain" @@ -6725,6 +6796,7 @@ }, { "name": "gstack-gbrain-lib.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-lib.sh", "consumer_modules": [ "setup-gbrain" @@ -6733,6 +6805,7 @@ }, { "name": "gstack-gbrain-mcp-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-mcp-verify", "consumer_modules": [ "setup-gbrain" @@ -6741,6 +6814,7 @@ }, { "name": "gstack-gbrain-repo-policy", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-repo-policy", "consumer_modules": [ "setup-gbrain" @@ -6749,6 +6823,7 @@ }, { "name": "gstack-gbrain-source-wireup", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-source-wireup", "consumer_modules": [ "setup-gbrain" @@ -6757,6 +6832,7 @@ }, { "name": "gstack-gbrain-supabase-provision", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-provision", "consumer_modules": [ "setup-gbrain" @@ -6765,6 +6841,7 @@ }, { "name": "gstack-gbrain-supabase-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-verify", "consumer_modules": [ "setup-gbrain" @@ -6773,14 +6850,17 @@ }, { "name": "gstack-gbrain-sync", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ - "setup-gbrain" + "setup-gbrain", + "sync-gbrain" ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/gstack-gbrain-sync" }, { "name": "gstack-gbrain-sync.ts", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ "sync-gbrain" @@ -6789,6 +6869,7 @@ }, { "name": "gstack-global-discover", + "delivery": "repository-payload", "source_path": "bin/gstack-global-discover.ts", "consumer_modules": [ "retro" @@ -6797,6 +6878,7 @@ }, { "name": "gstack-learnings-log", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-log", "consumer_modules": [ "cso", @@ -6817,6 +6899,7 @@ }, { "name": "gstack-learnings-search", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-search", "consumer_modules": [ "cso", @@ -6835,6 +6918,7 @@ }, { "name": "gstack-memory-ingest", + "delivery": "repository-payload", "source_path": "bin/gstack-memory-ingest.ts", "consumer_modules": [ "setup-gbrain" @@ -6843,6 +6927,7 @@ }, { "name": "gstack-model-benchmark", + "delivery": "repository-payload", "source_path": "bin/gstack-model-benchmark", "consumer_modules": [ "benchmark-models" @@ -6851,6 +6936,7 @@ }, { "name": "gstack-next-version", + "delivery": "repository-payload", "source_path": "bin/gstack-next-version", "consumer_modules": [ "land-and-deploy", @@ -6862,6 +6948,7 @@ }, { "name": "gstack-paths", + "delivery": "repository-payload", "source_path": "bin/gstack-paths", "consumer_modules": [ "codex", @@ -6879,6 +6966,7 @@ }, { "name": "gstack-pr-title-rewrite.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-pr-title-rewrite.sh", "consumer_modules": [ "ship" @@ -6887,6 +6975,7 @@ }, { "name": "gstack-question-preference", + "delivery": "repository-payload", "source_path": "bin/gstack-question-preference", "consumer_modules": [ "plan-tune" @@ -6895,6 +6984,7 @@ }, { "name": "gstack-redact", + "delivery": "repository-payload", "source_path": "bin/gstack-redact", "consumer_modules": [ "document-generate", @@ -6905,6 +6995,7 @@ }, { "name": "gstack-redact-audit-log", + "delivery": "repository-payload", "source_path": "bin/gstack-redact-audit-log", "consumer_modules": [ "spec" @@ -6913,6 +7004,7 @@ }, { "name": "gstack-review-log", + "delivery": "repository-payload", "source_path": "bin/gstack-review-log", "consumer_modules": [ "autoplan", @@ -6924,6 +7016,7 @@ }, { "name": "gstack-review-read", + "delivery": "repository-payload", "source_path": "bin/gstack-review-read", "consumer_modules": [ "devex-review", @@ -6935,6 +7028,7 @@ }, { "name": "gstack-slug", + "delivery": "repository-payload", "source_path": "bin/gstack-slug", "consumer_modules": [ "autoplan", @@ -6966,6 +7060,7 @@ }, { "name": "gstack-taste-update", + "delivery": "repository-payload", "source_path": "bin/gstack-taste-update", "consumer_modules": [ "design-consultation", @@ -6975,6 +7070,7 @@ }, { "name": "gstack-version-bump", + "delivery": "repository-payload", "source_path": "bin/gstack-version-bump", "consumer_modules": [ "ship" @@ -6983,6 +7079,7 @@ }, { "name": "make-pdf", + "delivery": "repository-payload", "source_path": "make-pdf/dist/pdf", "platform_source_paths": { "posix": "make-pdf/dist/pdf", @@ -7041,6 +7138,7 @@ }, { "name": "remote-slug", + "delivery": "repository-payload", "source_path": "browse/bin/remote-slug", "consumer_modules": [ "autoplan", diff --git a/docs/gstack-2/STATUS.md b/docs/gstack-2/STATUS.md index 99340175b..dfea931c5 100644 --- a/docs/gstack-2/STATUS.md +++ b/docs/gstack-2/STATUS.md @@ -22,9 +22,9 @@ PR, or PR-ready claim is authorized by this status. `debug`, `review`, and `ship`. - [x] The six `/plan` top-level modes are exactly **Discovery, Product, Engineering, DX, Specification, and Full chain**. -- [x] Standard installer matrix is green: 470/470 checks, 16 installs and two +- [x] Standard installer matrix is green: 510/510 checks, 18 installs and two removals with CLI 1.5.19. Project/global installs passed for Claude Code, - Codex, Cursor, Pi, OpenClaw, and GitHub Copilot; selected-skill and opt-in + Codex, Kimi Code CLI, Cursor, Pi, OpenClaw, and GitHub Copilot; selected-skill and opt-in compatibility-alias cases, paths with spaces, source symlink, physical copies, and canonical hashes passed. The committed artifact is [`evals/installation/install-matrix.json`](../../evals/installation/install-matrix.json). @@ -154,7 +154,7 @@ PR, or PR-ready claim is authorized by this status. - [ ] The paid live v3 installed-host adversarial gate is failed, not pending: the immutable one-shot result is **3/4**, with review failing compound - inspection. Do not retry or relabel it. The six hosts remain **Verified at + inspection. Do not retry or relabel it. The seven hosts remain **Verified at the installer layer only**; representative UI/process coverage is incomplete. - [ ] Finish final evidence-linked disposition for every infrastructure item; see the 25-row table in [ARCHITECTURE.md](./ARCHITECTURE.md). Current focused @@ -175,7 +175,7 @@ PR, or PR-ready claim is authorized by this status. | Structured scenarios | [SCENARIOS.md](./SCENARIOS.md) | 25/25 structured routing fixtures green | | Backlog traceability | [BACKLOG-MAP.json](./BACKLOG-MAP.json) | 755 unique items mapped | | Context integration | [CONTEXT-DEV.md](./CONTEXT-DEV.md) | Automated contract 22/139 green; verified-key official-endpoint live smoke passed | -| Host matrix | [HOST-COMPATIBILITY.md](./HOST-COMPATIBILITY.md) | 470/470 checks; Codex runtime-absent run passed; live v3 failed; other UI launches pending | +| Host matrix | [HOST-COMPATIBILITY.md](./HOST-COMPATIBILITY.md) | 510/510 checks; Codex runtime-absent run passed; live v3 failed; other UI launches pending | | Privacy boundary | [PRIVACY.md](./PRIVACY.md) | Implemented contract; full retained-tool egress audit pending | | Physical iOS | [IOS-PHYSICAL-DEVICE.md](./IOS-PHYSICAL-DEVICE.md), [live artifact](./evidence/ios-physical-device-2026-07-20T17-49-19-302Z.json) | 12/12 harness tests and five-of-five live iterations passed on a wired paired iPhone | | Upgrade/recovery | [UPGRADE-AND-ROLLBACK.md](./UPGRADE-AND-ROLLBACK.md) | Runtime installer 25 pass / 341 assertions; deterministic clean macOS arm64 bundle audit recorded | diff --git a/docs/gstack-2/TEST-EVIDENCE.md b/docs/gstack-2/TEST-EVIDENCE.md index dc46ccee4..d1f8efbed 100644 --- a/docs/gstack-2/TEST-EVIDENCE.md +++ b/docs/gstack-2/TEST-EVIDENCE.md @@ -28,8 +28,8 @@ pass from deterministic, offline, or filesystem-only evidence. | Physical-device smoke, 2026-07-20 | **PASS:** 12/12 harness checks and all five required live iterations. Release symbols were absent; signing, safe in-place install, launch, CoreDevice bootstrap, boot-token rotation, five session acquire/release cycles, ten screenshots, accessibility reads, coordinate taps, bundle checks, state cleanup, tunnel shutdown, and temporary-workspace cleanup passed. | Closes the physical-iPhone evidence layer for the tested wired paired `iPhone17,1`; it is not a claim about every iPhone or iOS release. Redacted artifact: [`ios-physical-device-2026-07-20T17-49-19-302Z.json`](./evidence/ios-physical-device-2026-07-20T17-49-19-302Z.json). | | Context.dev contract (`gstack2-runtime-context.test.ts`) | **22 pass / 0 fail**, 139 assertions. | Persists explicit host/local-browser/none choices without consent, rejects private/credential URLs and request material plus private DNS, proves zero lookup/fetch before mode+consent, validates documented endpoint paths and exact failure taxonomy, and makes search typed unsupported without network. | | `gstack context smoke --url https://www.context.dev --json` | **PASS:** the official `/web/scrape/markdown` endpoint returned `ok: true` and credit metadata. | The verified key entered through protected stdin/environment worked. The temporary secrets file was mode `0600`, the isolated home was removed, and the safe output did not contain the key. Committed redacted artifact: [`evals/context-dev/live-smoke-2026-07-17.json`](../../evals/context-dev/live-smoke-2026-07-17.json). | -| Standard installer matrix | **PASS: 470/470 checks**, 16 install cases, two removal cases, `skills` CLI 1.5.19. | Project/global installs pass for six hosts, selected-skill and opt-in compatibility-alias cases, copies, and hashes. This remains installer/filesystem evidence. Committed artifact: [`evals/installation/install-matrix.json`](../../evals/installation/install-matrix.json). | -| Standard Agent Skills install + actual runtime-absent Codex invocation | **PASS.** Root `--list` returned exactly six. The selected source was `time-attack/gstack/skills --skill qa`; despite `skills` 1.5.19's pre-filter display counting hidden aliases, exactly one skill (`qa`) installed byte-identically. Codex exited 0 with `gstack` absent, reported `NEEDS_SETUP` and one approval prompt, changed no files, and created no runtime/browser/external-service activity. | Closes the actual-host runtime-absent judgment gate for Codex without overstating six-host UI coverage. Artifact: [`standard-codex-runtime-absent-2026-07-17.json`](../../evals/installation/standard-codex-runtime-absent-2026-07-17.json). | +| Standard installer matrix | **PASS: 510/510 checks**, 18 install cases, two removal cases, `skills` CLI 1.5.19. | Project/global installs pass for seven hosts, including Kimi Code CLI through the standard `.agents/skills` path; selected-skill and opt-in compatibility-alias cases, copies, and hashes pass. This remains installer/filesystem evidence. Committed artifact: [`evals/installation/install-matrix.json`](../../evals/installation/install-matrix.json). | +| Standard Agent Skills install + actual runtime-absent Codex invocation | **PASS.** Root `--list` returned exactly six. The selected source was `time-attack/gstack/skills --skill qa`; despite `skills` 1.5.19's pre-filter display counting hidden aliases, exactly one skill (`qa`) installed byte-identically. Codex exited 0 with `gstack` absent, reported `NEEDS_SETUP` and one approval prompt, changed no files, and created no runtime/browser/external-service activity. | Closes the actual-host runtime-absent judgment gate for Codex without overstating seven-host UI coverage. Artifact: [`standard-codex-runtime-absent-2026-07-17.json`](../../evals/installation/standard-codex-runtime-absent-2026-07-17.json). | | `bun test test/gstack2-runtime-install.test.ts` | **Exit 0: 25 pass / 0 fail**, 341 assertions. | Managed allowlist, hashes, spaces, source/internal-link rejection, production-only frozen dependencies, deterministic exact Sharp/ngrok platform closure, native-load rollback smoke, rollback/recovery, stable launchers, wrapper neutrality, and state-preserving uninstall pass. | | Deterministic clean macOS arm64 managed runtime bundle audit | **110 components, 1,829 files, 450,044,315 bytes, 50 capability launchers.** | This is a platform-specific bundle measurement, not a universal byte count; platform-native package payloads differ. Setup includes the Sharp/ngrok closure and excludes the development-only Claude Agent SDK. The Hugging Face sidecar is outside the bundle and its package is development-only, so production setup installs neither its inference runtime nor model weights; the L4 capability reports unavailable. The standard skill installer remains Markdown-only. Committed artifact: [`evals/runtime-bundle/darwin-arm64.json`](../../evals/runtime-bundle/darwin-arm64.json); reproduce with `bun run scripts/gstack2/audit-runtime-bundle.ts --output evals/runtime-bundle/darwin-arm64.json`. | | Earlier declared Linux Dev Container plus `bun run test:gstack2` inside it | **Exit 0: 136 pass / 0 fail, 1,127 assertions across 15 files.** Log: `/tmp/gstack2-devcontainer-gate-candidate-final4.log`. | Historical container checkpoint retained rather than overwritten; the current 150-test container result is recorded in the native-CI row below. | diff --git a/docs/gstack-2/UPGRADE-AND-ROLLBACK.md b/docs/gstack-2/UPGRADE-AND-ROLLBACK.md index 2aa00fa4d..3382e70c7 100644 --- a/docs/gstack-2/UPGRADE-AND-ROLLBACK.md +++ b/docs/gstack-2/UPGRADE-AND-ROLLBACK.md @@ -200,7 +200,7 @@ or iPhone data. Focused candidate tests cover the managed installer plus atomic activation, failed-health rollback, interrupted-pointer recovery, manual rollback, newer-schema refusal, and non-mutating cleanup preview. The standard installer -matrix separately passed 470/470 checks across six hosts, 16 installs, scopes, +matrix separately passed 510/510 checks across seven hosts, 18 installs, scopes, selections, and two removals; its artifact is [`evals/installation/install-matrix.json`](../../evals/installation/install-matrix.json). The runtime installer is green at 25 pass / 0 fail and 341 assertions, and the diff --git a/evals/browser-provider-evals/2026-07-20-browser-provider-eval.md b/evals/browser-provider-evals/2026-07-20-browser-provider-eval.md new file mode 100644 index 000000000..145dbda4f --- /dev/null +++ b/evals/browser-provider-evals/2026-07-20-browser-provider-eval.md @@ -0,0 +1,156 @@ +# Browser-provider routing eval — 2026-07-20 + +## Scope + +Report-only evaluation of GStack 2 just-in-time browser-provider selection on +macOS. No product files, host settings, extensions, browser profiles, or managed +runtime components were changed. No `./setup` command was run. + +The inventory covers the seven installer-verified GStack 2 hosts plus Gemini +CLI: Claude Code, OpenAI Codex, Kimi Code CLI, Cursor, Pi, OpenClaw, GitHub +Copilot/VS Code, and Gemini CLI. + +## Installation-path reproduction + +The reported loaded path was: + +```text +.agents/skills/gstack/qa/SKILL.md +``` + +That is the cloned GStack 1 compatibility tree. Its `qa/SKILL.md` still contains +an instruction to run `cd && ./setup`. The canonical GStack 2 path is: + +```text +.agents/skills/qa/SKILL.md +``` + +The canonical source is `skills/qa/SKILL.md`; it routes browser setup through +`references/BROWSER-PROVIDERS.md` and explicitly forbids `./setup` in a +standards-installed skill directory. + +## Official provider inventory + +| Host | Interactive browser capability | Classification | Setup/readiness constraint | +|---|---|---|---| +| Claude Code | Claude in Chrome | Native extension | Chrome extension, paid direct Anthropic plan, site permission, and live connector discovery are required. | +| OpenAI Codex Desktop | In-app Browser; optional Codex Chrome connection | Native in-app | The in-app browser must be opened and exposed to the active task; CLI presence is not readiness. | +| Gemini CLI | Experimental bundled browser agent | Native experimental agent | Disabled by default, requires Chrome 144+, one-time consent, and can use isolated, persistent, or existing-browser modes. | +| Cursor | Chrome DevTools provider was live in the evaluated CLI session | Host-provided MCP | Must be discovered from the current tool surface and pass the common readiness journey. Current public Cursor tool docs do not establish universal availability. | +| GitHub Copilot / VS Code | Integrated browser agent tools | Native in-app | Browser tools must be enabled in the tool picker and may be controlled by organization policy. Agent-created pages use isolated in-memory state. | +| OpenClaw | Bundled browser plugin | Native bundled plugin | Plugin/tool policy must expose it; the default managed profile is isolated from the personal profile. | +| Kimi Code CLI | WebSearch and FetchURL only | No native interactive automation | `kimi web` is the session UI, not an agent-controlled browser. | +| Pi | No core browser; optional community packages exist | Extension-only | A third-party package install is required and must never be inferred or performed silently. | + +Primary documentation: + +- Claude: https://code.claude.com/docs/en/chrome +- Codex: https://help.openai.com/en/articles/20001277-using-the-built-in-browser-in-the-chatgpt-desktop-app +- Gemini: https://github.com/google-gemini/gemini-cli/blob/main/docs/core/subagents.md +- GitHub Copilot / VS Code: https://code.visualstudio.com/docs/agents/guides/browser-agent-testing-guide +- OpenClaw: https://docs.openclaw.ai/browser +- Kimi: https://www.kimi.com/code/docs/en/kimi-code-cli/reference/tools.html +- Pi: https://pi.dev/docs/latest + +## Provider-selection eval + +All runnable agents received the same clean-room prompt, canonical project-local +QA skill, and fresh `GSTACK_HOME`. The prompt prohibited installation, settings +changes, extension enablement, profile attachment, consent acceptance, and +`./setup`. + +| Host run | Version | Result | Key evidence | +|---|---:|---|---| +| Claude Code | 2.1.214 | PASS | Correctly found no Claude-in-Chrome tool surface, rejected unrelated installed binaries as readiness, and offered native setup, GStack preview/install, evidence-free continuation, or defer. | +| Codex CLI | 0.144.5 | PASS | Live provider discovery returned no browser in the CLI task; it correctly offered activation of Codex Desktop Browser and the other three choices without installing. | +| Gemini CLI | 0.51.0 | FAIL | Twice misidentified itself as Codex by reading inherited `CODEX_*`/desktop environment signals. It never inspected a callable Gemini browser tool and omitted Gemini's own experimental browser agent. | +| Cursor Agent | 2026.07.16-899851b | PASS WITH CONTRACT AMBIGUITY | Discovered a live Chrome DevTools peer and completed functional tests, but initially emitted `currently_usable: true` before the readiness journey had passed. | +| Pi | 0.80.9 | PASS | Correctly classified the read-only session as no native automation and offered all four choices. | +| Kimi | 1.49.0 | BLOCKED | Existing credential returned HTTP 401 before the agent could evaluate the skill. | +| OpenClaw | not installed | UNTESTED LIVE | Official documentation establishes a bundled browser plugin; no local executable was available. | +| GitHub Copilot / VS Code | VS Code 3.12.17 | UNTESTED LIVE | Official documentation establishes integrated browser tools; the active task did not expose a Copilot agent session. | + +The cross-model harness dry-run recognized Claude and Codex but incorrectly +reported Gemini OAuth as unavailable. The Claude/Codex selection run completed +without a paid quality judge: + +| Model | Latency | Input → output tokens | Estimated cost | Tool calls | +|---|---:|---:|---:|---:| +| Claude Opus 4.7 | 69.8 s | 8 → 4,801 | $0.52 | 5 | +| GPT-5.4 | 43.1 s | 190,996 → 1,398 | $0.49 | 9 | + +## Functional public-web eval + +Two providers were already available without installation or profile attachment: +Codex In-app Browser and Cursor's Chrome DevTools provider. Both passed the same +four journeys: + +1. TodoMVC: add a todo, mark it complete, open the Completed filter, and verify + retained state plus zero active items. +2. Selenium Web Form: fill text, choose option Two, check the default checkbox, + submit, and verify `Form submitted` plus `Received!`. +3. The Internet JavaScript Error: capture the deliberate `TypeError` from the + console with its source URL. +4. TodoMVC responsive: emulate 375×812 and capture screenshot evidence. + +Both providers also passed the local common-readiness fixture: exact loopback +navigation, heading read, button interaction, `READY` state, console marker, +successful `POST /proof`, fixture shutdown, and released listener. + +Evidence: + +- Codex TodoMVC: `/tmp/gstack-browser-provider-evals/codex-in-app/todomvc-completed.png` +- Codex Selenium form: `/tmp/gstack-browser-provider-evals/codex-in-app/selenium-form-submitted.png` +- Codex console-error page: `/tmp/gstack-browser-provider-evals/codex-in-app/javascript-error.png` +- Codex mobile: `/tmp/gstack-browser-provider-evals/codex-in-app/todomvc-mobile.png` +- Cursor mobile: `/tmp/gstack-browser-provider-evals/cursor/todomvc-375x812.png` + +## Findings + +1. **P0 onboarding path ambiguity:** cloning the repository under + `.agents/skills/gstack` exposes the legacy compatibility skill and its + `./setup` instruction. A clean GStack 2 evaluation must use the standards + installer and load `.agents/skills/qa/SKILL.md`. +2. **P1 provider registry is incomplete:** `BROWSER-PROVIDERS.md` names only + Claude, Codex, and Kimi. It is missing Gemini, Cursor/runtime-discovered MCP, + GitHub Copilot/VS Code, OpenClaw, and Pi's explicit no-core-browser case. +3. **P1 host inference is unsafe:** Gemini demonstrated that environment + variables inherited from the parent application can identify the wrong host. + Provider selection must use active callable tool discovery first. +4. **P1 readiness state is underspecified:** `available`, `needs-user-action`, + and `ready` must be separate states. Cursor called the provider usable before + the readiness fixture had run. +5. **P1 benchmark detection misses Gemini OAuth:** the model benchmark's auth + detector disagreed with a successful direct Gemini run. +6. **P2 no cross-host setup copy:** the user-facing decision should be generated + from a common state machine, with host-specific setup text supplied by an + internal registry rather than hand-authored branching in each specialist. + +## Recommended decision contract + +```text +QA requires an interactive browser. + +Detected: + +A) Use the detected agent browser +B) Preview the isolated GStack browser download, then ask separately to install +C) Continue without browser evidence (interactive claims will be unverified) +D) Defer +``` + +Selection is not readiness. Readiness is granted only after the common fixture +proves navigation, page reading, interaction, console/network evidence when +supported, and clean shutdown. GStack local browser preview and installation +remain two separate approvals. `./setup` is never a valid GStack 2 response. + +## Untested surfaces + +- Claude in Chrome functional automation: no extension peer was attached and no + permission was granted to attach a signed-in profile. +- Gemini browser-agent functional automation: enabling it and accepting its + first-run consent would change isolated host settings and requires explicit + approval. +- GitHub Copilot/VS Code and OpenClaw live journeys: their agent tool surfaces + were not active on this machine. +- Kimi provider-selection judgment: authentication failed before inference. diff --git a/evals/installation/install-matrix.json b/evals/installation/install-matrix.json index 514c1fd9a..cc62862b5 100644 --- a/evals/installation/install-matrix.json +++ b/evals/installation/install-matrix.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "mode": "full", - "generatedAt": "2026-07-17T03:32:07.629Z", + "generatedAt": "2026-07-20T21:38:49.315Z", "platform": "darwin", "architecture": "arm64", "repositoryRoot": "", @@ -20,7 +20,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1695, + "durationMs": 743, "stdout": "1.5.19", "stderr": "" }, @@ -33,7 +33,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1027, + "durationMs": 585, "stdout": "Usage: skills [options]\n\nManage Skills:\n add Add a skill package (alias: a)\n e.g. vercel-labs/agent-skills\n https://github.com/vercel-labs/agent-skills\n use @\n Generate a prompt for using one skill without installing it\n remove [skills] Remove installed skills\n list, ls List installed skills\n find [query] Search for skills interactively\n\nFind Options:\n --owner Search only repositories from a GitHub owner\n\nUpdates:\n update [skills...] Update skills to latest versions (alias: upgrade)\n\nUpdate Options:\n -g, --global Update global skills only\n -p, --project Update project skills only\n -y, --yes Skip scope prompt (auto-detect: project if in a project, else global)\n\nProject:\n experimental_install Restore skills from skills-lock.json\n init [name] Initialize a skill (creates /SKILL.md or ./SKILL.md)\n experimental_sync Sync skills from node_modules into agent directories\n\nAdd Options:\n -g, --global Install skill globally (user-level) instead of project-level\n -a, --agent Specify agents to install to (use '*' for all agents)\n -s, --skill Specify skill names to install (use '*' for all skills)\n -l, --list List available skills in the repository without installing\n -y, --yes Skip confirmation prompts\n --copy Copy files instead of symlinking to agent directories\n --metadata Attach valid JSON to the install telemetry event\n --subagent Install to Eve subagents (use 'root' for the root agent)\n --all Shorthand for --skill '*' --agent '*' -y\n --full-depth Search all subdirectories even when a root SKILL.md exists\n\nUse Options:\n -s, --skill Specify the skill to use\n -a, --agent Start one supported agent interactively\n --full-depth Search all subdirectories even when a root SKILL.md exists\n --dangerously-accept-openclaw-risks\n Allow unverified OpenClaw community skills\n\nRemove Options:\n -g, --global Remove from global scope\n -a, --agent Remove from specific agents (use '*' for all agents)\n -s, --skill Specify skills to remove (use '*' for all skills)\n -y, --yes Skip confirmation prompts\n --all Shorthand for --skill '*' --agent '*' -y\n \nExperimental Sync Options:\n -a, --agent Specify agents to install to (use '*' for all agents)\n -y, --yes Skip confirmation prompts\n\nList Options:\n -g, --global List global skills (default: project)\n -a, --agent Filter by specific agents\n --json Output as JSON (machine-readable, no ANSI codes)\n\nOptions:\n --help, -h Show this help message\n --version, -v Show version number\n\nExamples:\n $ skills add vercel-labs/agent-skills\n $ skills use vercel-labs/agent-skills@vercel-optimize | claude\n $ skills use vercel-labs/agent-skills --skill vercel-optimize --agent claude-code\n $ skills add vercel-labs/agent-skills -g\n $ skills add vercel-labs/agent-skills --agent claude-code cursor\n $ skills add vercel-labs/agent-skills --skill pr-review commit\n $ skills remove # interactive remove\n $ skills remove web-design # remove by name\n $ skills rm --global frontend-design\n $ skills list # list project skills\n $ skills ls -g # list global skills\n $ skills ls -a claude-code # filter by agent\n $ skills ls --json # JSON output\n $ skills find # interactive search\n $ skills find typescript # search by keyword\n $ skills find react --owner vercel # search within an owner\n $ skills update\n $ skills update my-skill # update a single skill\n $ skills update -g # update global skills only\n $ skills experimental_install # restore from skills-lock.json\n $ skills init my-skill\n $ skills experimental_sync # sync from node_modules\n $ skills experimental_sync -y # sync without prompts\n\nDiscover more skills at https://skills.sh/", "stderr": "" } @@ -84,7 +84,7 @@ { "id": "repository.debug.preserved-modules", "passed": true, - "detail": "7 preserved module files" + "detail": "6 preserved module files" }, { "id": "repository.design.exists", @@ -104,7 +104,7 @@ { "id": "repository.design.preserved-modules", "passed": true, - "detail": "9 preserved module files" + "detail": "8 preserved module files" }, { "id": "repository.plan.exists", @@ -124,7 +124,7 @@ { "id": "repository.plan.preserved-modules", "passed": true, - "detail": "16 preserved module files" + "detail": "15 preserved module files" }, { "id": "repository.qa.exists", @@ -144,7 +144,7 @@ { "id": "repository.qa.preserved-modules", "passed": true, - "detail": "15 preserved module files" + "detail": "14 preserved module files" }, { "id": "repository.review.exists", @@ -164,7 +164,7 @@ { "id": "repository.review.preserved-modules", "passed": true, - "detail": "6 preserved module files" + "detail": "5 preserved module files" }, { "id": "repository.ship.exists", @@ -184,7 +184,7 @@ { "id": "repository.ship.preserved-modules", "passed": true, - "detail": "12 preserved module files" + "detail": "11 preserved module files" }, { "id": "repository.collision.qa.canonical", @@ -1032,8 +1032,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1132, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: \n│\n◇ Local path validated\n│\n◒ Discovering skills…◇ Found 6 skills\n\n│\n◇ Available Skills\n│\n│ debug\n│\n│ Diagnose root causes before changing code, or fix a reproduced defect. Use for failures, regressions, flaky behavior, and iOS repair.\n│\n│ design\n│\n│ Explore, generate, critique, or implement product design. Use for design systems, visual alternatives, HTML, live web UI, accessibility, or iOS HIG review.\n│\n│ plan\n│\n│ Plan products, scope, architecture, developer experience, or executable specs before implementation. Use for ideas, strategic or engineering reviews, autoplan, and planning preferences.\n│\n│ qa\n│\n│ Report on or fix validated product defects. Use for web/browser QA, real-device iOS, developer journeys, accessibility, performance baselines, or production canaries.\n│\n│ review\n│\n│ Review code with validated evidence. Use for normal, security, performance, or deep audits of diffs, architecture, data, tests, dependencies, docs, and code health.\n│\n│ ship\n│\n│ Prepare, land, deploy, monitor, or resume a release. Use for checks, versioning, docs, commits, PRs, merge gates, production verification, and rollback.\n\n│\n└ Use --skill to install specific skills", + "durationMs": 609, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: \n│\n◇ Local path validated\n│\n◇ Found 6 skills\n\n│\n◇ Available Skills\n│\n│ debug\n│\n│ Diagnose root causes before changing code, or fix a reproduced defect. Use for failures, regressions, flaky behavior, and iOS repair.\n│\n│ design\n│\n│ Explore, generate, critique, or implement product design. Use for design systems, visual alternatives, HTML, live web UI, accessibility, or iOS HIG review.\n│\n│ plan\n│\n│ Plan products, scope, architecture, developer experience, or executable specs before implementation. Use for ideas, strategic or engineering reviews, autoplan, and planning preferences.\n│\n│ qa\n│\n│ Report on or fix validated product defects. Use for web/browser QA, real-device iOS, developer journeys, accessibility, performance baselines, or production canaries.\n│\n│ review\n│\n│ Review code with validated evidence. Use for normal, security, performance, or deep audits of diffs, architecture, data, tests, dependencies, docs, and code health.\n│\n│ ship\n│\n│ Prepare, land, deploy, monitor, or resume a release. Use for checks, versioning, docs, commits, PRs, merge gates, production verification, and rollback.\n\n│\n└ Use --skill to install specific skills", "stderr": "" }, "passed": true @@ -1076,7 +1076,7 @@ { "id": "claude-code-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "claude-code-project-default.debug.copy", @@ -1091,7 +1091,7 @@ { "id": "claude-code-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "claude-code-project-default.design.copy", @@ -1106,7 +1106,7 @@ { "id": "claude-code-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "claude-code-project-default.plan.copy", @@ -1121,7 +1121,7 @@ { "id": "claude-code-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "claude-code-project-default.qa.copy", @@ -1136,7 +1136,7 @@ { "id": "claude-code-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "claude-code-project-default.review.copy", @@ -1151,7 +1151,7 @@ { "id": "claude-code-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "claude-code-project-default.ship.copy", @@ -1178,8 +1178,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1071, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◒ Discovering skills…◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/design │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/review │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Claude Code │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.claude/skills/debug │\n│ ✓ design (copied) │\n│ → ./.claude/skills/design │\n│ ✓ plan (copied) │\n│ → ./.claude/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.claude/skills/qa │\n│ ✓ review (copied) │\n│ → ./.claude/skills/review │\n│ ✓ ship (copied) │\n│ → ./.claude/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 684, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/design │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/review │\n│ copy → Claude Code │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Claude Code │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.claude/skills/debug │\n│ ✓ design (copied) │\n│ → ./.claude/skills/design │\n│ ✓ plan (copied) │\n│ → ./.claude/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.claude/skills/qa │\n│ ✓ review (copied) │\n│ → ./.claude/skills/review │\n│ ✓ ship (copied) │\n│ → ./.claude/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -1221,7 +1221,7 @@ { "id": "claude-code-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "claude-code-global-default.debug.copy", @@ -1236,7 +1236,7 @@ { "id": "claude-code-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "claude-code-global-default.design.copy", @@ -1251,7 +1251,7 @@ { "id": "claude-code-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "claude-code-global-default.plan.copy", @@ -1266,7 +1266,7 @@ { "id": "claude-code-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "claude-code-global-default.qa.copy", @@ -1281,7 +1281,7 @@ { "id": "claude-code-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "claude-code-global-default.review.copy", @@ -1296,7 +1296,7 @@ { "id": "claude-code-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "claude-code-global-default.ship.copy", @@ -1324,7 +1324,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1029, + "durationMs": 724, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Claude Code │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Claude Code │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Claude Code │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Claude Code │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Claude Code │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Claude Code │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.claude/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.claude/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.claude/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.claude/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.claude/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.claude/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -1367,7 +1367,7 @@ { "id": "codex-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "codex-project-default.debug.copy", @@ -1382,7 +1382,7 @@ { "id": "codex-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "codex-project-default.design.copy", @@ -1397,7 +1397,7 @@ { "id": "codex-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "codex-project-default.plan.copy", @@ -1412,7 +1412,7 @@ { "id": "codex-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "codex-project-default.qa.copy", @@ -1427,7 +1427,7 @@ { "id": "codex-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "codex-project-default.review.copy", @@ -1442,7 +1442,7 @@ { "id": "codex-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "codex-project-default.ship.copy", @@ -1469,7 +1469,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1047, + "durationMs": 693, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Codex │\n│ │\n│ ./.agents/skills/design │\n│ copy → Codex │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Codex │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Codex │\n│ │\n│ ./.agents/skills/review │\n│ copy → Codex │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Codex │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -1512,7 +1512,7 @@ { "id": "codex-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "codex-global-default.debug.copy", @@ -1527,7 +1527,7 @@ { "id": "codex-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "codex-global-default.design.copy", @@ -1542,7 +1542,7 @@ { "id": "codex-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "codex-global-default.plan.copy", @@ -1557,7 +1557,7 @@ { "id": "codex-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "codex-global-default.qa.copy", @@ -1572,7 +1572,7 @@ { "id": "codex-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "codex-global-default.review.copy", @@ -1587,7 +1587,7 @@ { "id": "codex-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "codex-global-default.ship.copy", @@ -1615,18 +1615,309 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1029, + "durationMs": 939, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Codex │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true }, + { + "id": "kimi-code-cli-project-default", + "agent": "kimi-code-cli", + "agentLabel": "Kimi Code CLI", + "scope": "project", + "sourceKind": "path-with-spaces", + "expectedRoot": "/cases/kimi-code-cli-project-default/project with spaces/.agents/skills", + "expectedSkills": [ + "debug", + "design", + "plan", + "qa", + "review", + "ship" + ], + "installedSkills": [ + "debug", + "design", + "plan", + "qa", + "review", + "ship" + ], + "checks": [ + { + "id": "kimi-code-cli-project-default.command", + "passed": true, + "detail": "exit=0; signal=none" + }, + { + "id": "kimi-code-cli-project-default.selected-skills", + "passed": true, + "detail": "expected debug, design, plan, qa, review, ship; found debug, design, plan, qa, review, ship" + }, + { + "id": "kimi-code-cli-project-default.debug.content", + "passed": true, + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" + }, + { + "id": "kimi-code-cli-project-default.debug.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.debug.canonical-name", + "passed": true, + "detail": "expected debug; found debug" + }, + { + "id": "kimi-code-cli-project-default.design.content", + "passed": true, + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" + }, + { + "id": "kimi-code-cli-project-default.design.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.design.canonical-name", + "passed": true, + "detail": "expected design; found design" + }, + { + "id": "kimi-code-cli-project-default.plan.content", + "passed": true, + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" + }, + { + "id": "kimi-code-cli-project-default.plan.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.plan.canonical-name", + "passed": true, + "detail": "expected plan; found plan" + }, + { + "id": "kimi-code-cli-project-default.qa.content", + "passed": true, + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" + }, + { + "id": "kimi-code-cli-project-default.qa.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.qa.canonical-name", + "passed": true, + "detail": "expected qa; found qa" + }, + { + "id": "kimi-code-cli-project-default.review.content", + "passed": true, + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" + }, + { + "id": "kimi-code-cli-project-default.review.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.review.canonical-name", + "passed": true, + "detail": "expected review; found review" + }, + { + "id": "kimi-code-cli-project-default.ship.content", + "passed": true, + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" + }, + { + "id": "kimi-code-cli-project-default.ship.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-project-default.ship.canonical-name", + "passed": true, + "detail": "expected ship; found ship" + } + ], + "command": { + "argv": [ + "npx", + "--yes", + "skills", + "add", + "/canonical package/source with spaces", + "--agent", + "kimi-code-cli", + "--copy", + "--yes" + ], + "exitCode": 0, + "signal": null, + "durationMs": 679, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Kimi Code CLI │\n│ │\n│ ./.agents/skills/design │\n│ copy → Kimi Code CLI │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Kimi Code CLI │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Kimi Code CLI │\n│ │\n│ ./.agents/skills/review │\n│ copy → Kimi Code CLI │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Kimi Code CLI │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "stderr": "" + }, + "passed": true + }, + { + "id": "kimi-code-cli-global-default", + "agent": "kimi-code-cli", + "agentLabel": "Kimi Code CLI", + "scope": "global", + "sourceKind": "source-symlink", + "expectedRoot": "/cases/kimi-code-cli-global-default/home with spaces/.agents/skills", + "expectedSkills": [ + "debug", + "design", + "plan", + "qa", + "review", + "ship" + ], + "installedSkills": [ + "debug", + "design", + "plan", + "qa", + "review", + "ship" + ], + "checks": [ + { + "id": "kimi-code-cli-global-default.command", + "passed": true, + "detail": "exit=0; signal=none" + }, + { + "id": "kimi-code-cli-global-default.selected-skills", + "passed": true, + "detail": "expected debug, design, plan, qa, review, ship; found debug, design, plan, qa, review, ship" + }, + { + "id": "kimi-code-cli-global-default.debug.content", + "passed": true, + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" + }, + { + "id": "kimi-code-cli-global-default.debug.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.debug.canonical-name", + "passed": true, + "detail": "expected debug; found debug" + }, + { + "id": "kimi-code-cli-global-default.design.content", + "passed": true, + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" + }, + { + "id": "kimi-code-cli-global-default.design.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.design.canonical-name", + "passed": true, + "detail": "expected design; found design" + }, + { + "id": "kimi-code-cli-global-default.plan.content", + "passed": true, + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" + }, + { + "id": "kimi-code-cli-global-default.plan.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.plan.canonical-name", + "passed": true, + "detail": "expected plan; found plan" + }, + { + "id": "kimi-code-cli-global-default.qa.content", + "passed": true, + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" + }, + { + "id": "kimi-code-cli-global-default.qa.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.qa.canonical-name", + "passed": true, + "detail": "expected qa; found qa" + }, + { + "id": "kimi-code-cli-global-default.review.content", + "passed": true, + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" + }, + { + "id": "kimi-code-cli-global-default.review.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.review.canonical-name", + "passed": true, + "detail": "expected review; found review" + }, + { + "id": "kimi-code-cli-global-default.ship.content", + "passed": true, + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" + }, + { + "id": "kimi-code-cli-global-default.ship.copy", + "passed": true, + "detail": "directory and SKILL.md are physical copies" + }, + { + "id": "kimi-code-cli-global-default.ship.canonical-name", + "passed": true, + "detail": "expected ship; found ship" + } + ], + "command": { + "argv": [ + "npx", + "--yes", + "skills", + "add", + "/linked canonical source", + "--agent", + "kimi-code-cli", + "--copy", + "--yes", + "--global" + ], + "exitCode": 0, + "signal": null, + "durationMs": 618, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Kimi Code CLI │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Kimi Code CLI │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Kimi Code CLI │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Kimi Code CLI │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Kimi Code CLI │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Kimi Code CLI │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "stderr": "" + }, + "passed": true + }, { "id": "cursor-project-default", "agent": "cursor", "agentLabel": "Cursor", "scope": "project", - "sourceKind": "path-with-spaces", + "sourceKind": "source-symlink", "expectedRoot": "/cases/cursor-project-default/project with spaces/.agents/skills", "expectedSkills": [ "debug", @@ -1658,7 +1949,7 @@ { "id": "cursor-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "cursor-project-default.debug.copy", @@ -1673,7 +1964,7 @@ { "id": "cursor-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "cursor-project-default.design.copy", @@ -1688,7 +1979,7 @@ { "id": "cursor-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "cursor-project-default.plan.copy", @@ -1703,7 +1994,7 @@ { "id": "cursor-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "cursor-project-default.qa.copy", @@ -1718,7 +2009,7 @@ { "id": "cursor-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "cursor-project-default.review.copy", @@ -1733,7 +2024,7 @@ { "id": "cursor-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "cursor-project-default.ship.copy", @@ -1752,7 +2043,7 @@ "--yes", "skills", "add", - "/canonical package/source with spaces", + "/linked canonical source", "--agent", "cursor", "--copy", @@ -1760,8 +2051,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1034, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/design │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/review │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Cursor │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 729, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/design │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/review │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Cursor │\n│ │\n├───────────────────────────╯\n│\n◒ Installing skills…◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -1771,7 +2062,7 @@ "agent": "cursor", "agentLabel": "Cursor", "scope": "global", - "sourceKind": "source-symlink", + "sourceKind": "path-with-spaces", "expectedRoot": "/cases/cursor-global-default/home with spaces/.agents/skills", "expectedSkills": [ "debug", @@ -1803,7 +2094,7 @@ { "id": "cursor-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "cursor-global-default.debug.copy", @@ -1818,7 +2109,7 @@ { "id": "cursor-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "cursor-global-default.design.copy", @@ -1833,7 +2124,7 @@ { "id": "cursor-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "cursor-global-default.plan.copy", @@ -1848,7 +2139,7 @@ { "id": "cursor-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "cursor-global-default.qa.copy", @@ -1863,7 +2154,7 @@ { "id": "cursor-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "cursor-global-default.review.copy", @@ -1878,7 +2169,7 @@ { "id": "cursor-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "cursor-global-default.ship.copy", @@ -1897,7 +2188,7 @@ "--yes", "skills", "add", - "/linked canonical source", + "/canonical package/source with spaces", "--agent", "cursor", "--copy", @@ -1906,8 +2197,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1037, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Cursor │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 893, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Cursor │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Cursor │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -1917,7 +2208,7 @@ "agent": "pi", "agentLabel": "Pi", "scope": "project", - "sourceKind": "source-symlink", + "sourceKind": "path-with-spaces", "expectedRoot": "/cases/pi-project-default/project with spaces/.pi/skills", "expectedSkills": [ "debug", @@ -1949,7 +2240,7 @@ { "id": "pi-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "pi-project-default.debug.copy", @@ -1964,7 +2255,7 @@ { "id": "pi-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "pi-project-default.design.copy", @@ -1979,7 +2270,7 @@ { "id": "pi-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "pi-project-default.plan.copy", @@ -1994,7 +2285,7 @@ { "id": "pi-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "pi-project-default.qa.copy", @@ -2009,7 +2300,7 @@ { "id": "pi-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "pi-project-default.review.copy", @@ -2024,7 +2315,7 @@ { "id": "pi-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "pi-project-default.ship.copy", @@ -2043,7 +2334,7 @@ "--yes", "skills", "add", - "/linked canonical source", + "/canonical package/source with spaces", "--agent", "pi", "--copy", @@ -2051,8 +2342,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1038, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/design │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/review │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Pi │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.pi/skills/debug │\n│ ✓ design (copied) │\n│ → ./.pi/skills/design │\n│ ✓ plan (copied) │\n│ → ./.pi/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.pi/skills/qa │\n│ ✓ review (copied) │\n│ → ./.pi/skills/review │\n│ ✓ ship (copied) │\n│ → ./.pi/skills/ship │\n│ │\n├───────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 1324, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◒ Discovering skills…◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/design │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/plan │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/qa │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/review │\n│ copy → Pi │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Pi │\n│ │\n├───────────────────────────╯\n│\n◒ Installing skills…◐ Installing skills…◇ Installation complete\n\n│\n◇ Installed 6 skills ──────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.pi/skills/debug │\n│ ✓ design (copied) │\n│ → ./.pi/skills/design │\n│ ✓ plan (copied) │\n│ → ./.pi/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.pi/skills/qa │\n│ ✓ review (copied) │\n│ → ./.pi/skills/review │\n│ ✓ ship (copied) │\n│ → ./.pi/skills/ship │\n│ │\n├───────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2062,7 +2353,7 @@ "agent": "pi", "agentLabel": "Pi", "scope": "global", - "sourceKind": "path-with-spaces", + "sourceKind": "source-symlink", "expectedRoot": "/cases/pi-global-default/home with spaces/.pi/agent/skills", "expectedSkills": [ "debug", @@ -2094,7 +2385,7 @@ { "id": "pi-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "pi-global-default.debug.copy", @@ -2109,7 +2400,7 @@ { "id": "pi-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "pi-global-default.design.copy", @@ -2124,7 +2415,7 @@ { "id": "pi-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "pi-global-default.plan.copy", @@ -2139,7 +2430,7 @@ { "id": "pi-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "pi-global-default.qa.copy", @@ -2154,7 +2445,7 @@ { "id": "pi-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "pi-global-default.review.copy", @@ -2169,7 +2460,7 @@ { "id": "pi-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "pi-global-default.ship.copy", @@ -2188,7 +2479,7 @@ "--yes", "skills", "add", - "/canonical package/source with spaces", + "/linked canonical source", "--agent", "pi", "--copy", @@ -2197,8 +2488,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1171, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Pi │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ────────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.pi/agent/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.pi/agent/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.pi/agent/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.pi/agent/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.pi/agent/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.pi/agent/skills/ship │\n│ │\n├─────────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 846, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/design │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Pi │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Pi │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ────────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.pi/agent/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.pi/agent/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.pi/agent/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.pi/agent/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.pi/agent/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.pi/agent/skills/ship │\n│ │\n├─────────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2208,7 +2499,7 @@ "agent": "openclaw", "agentLabel": "OpenClaw", "scope": "project", - "sourceKind": "path-with-spaces", + "sourceKind": "source-symlink", "expectedRoot": "/cases/openclaw-project-default/project with spaces/skills", "expectedSkills": [ "debug", @@ -2240,7 +2531,7 @@ { "id": "openclaw-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "openclaw-project-default.debug.copy", @@ -2255,7 +2546,7 @@ { "id": "openclaw-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "openclaw-project-default.design.copy", @@ -2270,7 +2561,7 @@ { "id": "openclaw-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "openclaw-project-default.plan.copy", @@ -2285,7 +2576,7 @@ { "id": "openclaw-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "openclaw-project-default.qa.copy", @@ -2300,7 +2591,7 @@ { "id": "openclaw-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "openclaw-project-default.review.copy", @@ -2315,7 +2606,7 @@ { "id": "openclaw-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "openclaw-project-default.ship.copy", @@ -2334,7 +2625,7 @@ "--yes", "skills", "add", - "/canonical package/source with spaces", + "/linked canonical source", "--agent", "openclaw", "--copy", @@ -2342,8 +2633,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1128, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/design │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/plan │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/qa │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/review │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/ship │\n│ copy → OpenClaw │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──╮\n│ │\n│ ✓ debug (copied) │\n│ → ./skills/debug │\n│ ✓ design (copied) │\n│ → ./skills/design │\n│ ✓ plan (copied) │\n│ → ./skills/plan │\n│ ✓ qa (copied) │\n│ → ./skills/qa │\n│ ✓ review (copied) │\n│ → ./skills/review │\n│ ✓ ship (copied) │\n│ → ./skills/ship │\n│ │\n├───────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 754, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/design │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/plan │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/qa │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/review │\n│ copy → OpenClaw │\n│ │\n│ ./.agents/skills/ship │\n│ copy → OpenClaw │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──╮\n│ │\n│ ✓ debug (copied) │\n│ → ./skills/debug │\n│ ✓ design (copied) │\n│ → ./skills/design │\n│ ✓ plan (copied) │\n│ → ./skills/plan │\n│ ✓ qa (copied) │\n│ → ./skills/qa │\n│ ✓ review (copied) │\n│ → ./skills/review │\n│ ✓ ship (copied) │\n│ → ./skills/ship │\n│ │\n├───────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2353,7 +2644,7 @@ "agent": "openclaw", "agentLabel": "OpenClaw", "scope": "global", - "sourceKind": "source-symlink", + "sourceKind": "path-with-spaces", "expectedRoot": "/cases/openclaw-global-default/home with spaces/.openclaw/skills", "expectedSkills": [ "debug", @@ -2385,7 +2676,7 @@ { "id": "openclaw-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "openclaw-global-default.debug.copy", @@ -2400,7 +2691,7 @@ { "id": "openclaw-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "openclaw-global-default.design.copy", @@ -2415,7 +2706,7 @@ { "id": "openclaw-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "openclaw-global-default.plan.copy", @@ -2430,7 +2721,7 @@ { "id": "openclaw-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "openclaw-global-default.qa.copy", @@ -2445,7 +2736,7 @@ { "id": "openclaw-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "openclaw-global-default.review.copy", @@ -2460,7 +2751,7 @@ { "id": "openclaw-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "openclaw-global-default.ship.copy", @@ -2479,7 +2770,7 @@ "--yes", "skills", "add", - "/linked canonical source", + "/canonical package/source with spaces", "--agent", "openclaw", "--copy", @@ -2488,8 +2779,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1211, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/design │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/review │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → OpenClaw │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ────────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.openclaw/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.openclaw/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.openclaw/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.openclaw/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.openclaw/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.openclaw/skills/ship │\n│ │\n├─────────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 903, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/design │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/review │\n│ copy → OpenClaw │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → OpenClaw │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ────────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.openclaw/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.openclaw/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.openclaw/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.openclaw/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.openclaw/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.openclaw/skills/ship │\n│ │\n├─────────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2499,7 +2790,7 @@ "agent": "github-copilot", "agentLabel": "GitHub Copilot", "scope": "project", - "sourceKind": "source-symlink", + "sourceKind": "path-with-spaces", "expectedRoot": "/cases/github-copilot-project-default/project with spaces/.agents/skills", "expectedSkills": [ "debug", @@ -2531,7 +2822,7 @@ { "id": "github-copilot-project-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "github-copilot-project-default.debug.copy", @@ -2546,7 +2837,7 @@ { "id": "github-copilot-project-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "github-copilot-project-default.design.copy", @@ -2561,7 +2852,7 @@ { "id": "github-copilot-project-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "github-copilot-project-default.plan.copy", @@ -2576,7 +2867,7 @@ { "id": "github-copilot-project-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "github-copilot-project-default.qa.copy", @@ -2591,7 +2882,7 @@ { "id": "github-copilot-project-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "github-copilot-project-default.review.copy", @@ -2606,7 +2897,7 @@ { "id": "github-copilot-project-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "github-copilot-project-default.ship.copy", @@ -2625,7 +2916,7 @@ "--yes", "skills", "add", - "/linked canonical source", + "/canonical package/source with spaces", "--agent", "github-copilot", "--copy", @@ -2633,8 +2924,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1177, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/design │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/plan │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/qa │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/review │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/ship │\n│ copy → GitHub Copilot │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 878, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/debug │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/design │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/plan │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/qa │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/review │\n│ copy → GitHub Copilot │\n│ │\n│ ./.agents/skills/ship │\n│ copy → GitHub Copilot │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ./.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ./.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ./.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2644,7 +2935,7 @@ "agent": "github-copilot", "agentLabel": "GitHub Copilot", "scope": "global", - "sourceKind": "path-with-spaces", + "sourceKind": "source-symlink", "expectedRoot": "/cases/github-copilot-global-default/home with spaces/.agents/skills", "expectedSkills": [ "debug", @@ -2676,7 +2967,7 @@ { "id": "github-copilot-global-default.debug.content", "passed": true, - "detail": "source=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59; installed=8900ccd55bc62260eb9e7e3a7a2d0e6288d7771102f7a8b3b5655a741de80f59" + "detail": "source=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2; installed=f351222a3e7840bb8f9c6ea37e7fe9571164fb66591a27c9538c37c9f3fcfdc2" }, { "id": "github-copilot-global-default.debug.copy", @@ -2691,7 +2982,7 @@ { "id": "github-copilot-global-default.design.content", "passed": true, - "detail": "source=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085; installed=4d171487d24fb6742f82f75d724c84989a4b0fb6b7ff0dc1dba433ca4c528085" + "detail": "source=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d; installed=ab7e7b7a404004602e5aa80e919bdc4a301e2da7eb5232acb799fa27702fb48d" }, { "id": "github-copilot-global-default.design.copy", @@ -2706,7 +2997,7 @@ { "id": "github-copilot-global-default.plan.content", "passed": true, - "detail": "source=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f; installed=8fb64e9ac13541aaca65cafbfcf97ce3c6dfabcc781d83cfdd4f2b15c31b7e6f" + "detail": "source=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6; installed=4f951aee4525ac358833486c28339c706e9b372282c22c258577d20cf36a57c6" }, { "id": "github-copilot-global-default.plan.copy", @@ -2721,7 +3012,7 @@ { "id": "github-copilot-global-default.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "github-copilot-global-default.qa.copy", @@ -2736,7 +3027,7 @@ { "id": "github-copilot-global-default.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "github-copilot-global-default.review.copy", @@ -2751,7 +3042,7 @@ { "id": "github-copilot-global-default.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "github-copilot-global-default.ship.copy", @@ -2770,7 +3061,7 @@ "--yes", "skills", "add", - "/canonical package/source with spaces", + "/linked canonical source", "--agent", "github-copilot", "--copy", @@ -2779,8 +3070,8 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1091, - "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/design │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/review │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → GitHub Copilot │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", + "durationMs": 905, + "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /linked canonical source\n│\n◇ Local path validated\n│\n◇ Found 6 skills\n│\n● Installing all 6 skills\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/debug │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/design │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/plan │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/qa │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/review │\n│ copy → GitHub Copilot │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → GitHub Copilot │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 6 skills ──────────╮\n│ │\n│ ✓ debug (copied) │\n│ → ~/.agents/skills/debug │\n│ ✓ design (copied) │\n│ → ~/.agents/skills/design │\n│ ✓ plan (copied) │\n│ → ~/.agents/skills/plan │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, "passed": true @@ -2816,7 +3107,7 @@ { "id": "collision-selection-project.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "collision-selection-project.qa.copy", @@ -2831,7 +3122,7 @@ { "id": "collision-selection-project.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "collision-selection-project.review.copy", @@ -2846,7 +3137,7 @@ { "id": "collision-selection-project.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "collision-selection-project.ship.copy", @@ -2877,7 +3168,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1197, + "durationMs": 954, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: \n│\n◇ Local path validated\n│\n◒ Discovering skills…◇ Found 113 skills\n│\n● Selected 3 skills: qa, review, ship\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ./.agents/skills/qa │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/review │\n│ copy → Cursor │\n│ │\n│ ./.agents/skills/ship │\n│ copy → Cursor │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 3 skills ──────────╮\n│ │\n│ ✓ qa (copied) │\n│ → ./.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ./.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ./.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -2914,7 +3205,7 @@ { "id": "collision-selection-global.qa.content", "passed": true, - "detail": "source=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0; installed=0ec2e2b726c872c1ccaf8a72cac987f694f6aa85ded7ece5f52bcfef31e5a8d0" + "detail": "source=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef; installed=773e53fef2af6b948402a5fe36be7c18022734277b2c95a4609395339a5d95ef" }, { "id": "collision-selection-global.qa.copy", @@ -2929,7 +3220,7 @@ { "id": "collision-selection-global.review.content", "passed": true, - "detail": "source=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551; installed=b68b7f15eb0ef866a29e8fada17223ace2ebf73d66fc1c60f6fd776c762a4551" + "detail": "source=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6; installed=f6b28db16774cbeebff99ab7f968eb35fa3d4076201eec075389b7ab5a5107e6" }, { "id": "collision-selection-global.review.copy", @@ -2944,7 +3235,7 @@ { "id": "collision-selection-global.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "collision-selection-global.ship.copy", @@ -2976,7 +3267,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1114, + "durationMs": 853, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 111 skills\n│\n● Selected 3 skills: qa, review, ship\n\n│\n◇ Installation Summary ────╮\n│ │\n│ ~/.agents/skills/qa │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/review │\n│ copy → Codex │\n│ │\n│ ~/.agents/skills/ship │\n│ copy → Codex │\n│ │\n├───────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 3 skills ──────────╮\n│ │\n│ ✓ qa (copied) │\n│ → ~/.agents/skills/qa │\n│ ✓ review (copied) │\n│ → ~/.agents/skills/review │\n│ ✓ ship (copied) │\n│ → ~/.agents/skills/ship │\n│ │\n├───────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -3009,7 +3300,7 @@ { "id": "single-skill-ship-project.ship.content", "passed": true, - "detail": "source=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242; installed=2d43c0634596f7c3a48b1b82e23f099ada6b27f6f2f2d538964fad6eb1f4b242" + "detail": "source=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202; installed=45d26c47b04cdff6c9fa2cf055e4f4f30af3ef331a3b18b52711be1394291202" }, { "id": "single-skill-ship-project.ship.copy", @@ -3038,7 +3329,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1374, + "durationMs": 760, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: /canonical package/source with spaces\n│\n◇ Local path validated\n│\n◇ Found 111 skills\n│\n● Selected 1 skill: ship\n\n│\n◇ Installation Summary ──╮\n│ │\n│ ./.agents/skills/ship │\n│ copy → OpenClaw │\n│ │\n├─────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 1 skill ─╮\n│ │\n│ ✓ ship (copied) │\n│ → ./skills/ship │\n│ │\n├─────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -3100,7 +3391,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 1089, + "durationMs": 758, "stdout": "│\n● codex Agent detected — installing non-interactively\n│\n◇ Source: \n│\n◇ Local path validated\n│\n◒ Discovering skills…◇ Found 113 skills\n│\n● Selected 1 skill: office-hours\n\n│\n◇ Installation Summary ──────────╮\n│ │\n│ ./.agents/skills/office-hours │\n│ copy → Codex │\n│ │\n├─────────────────────────────────╯\n│\n◇ Installation complete\n\n│\n◇ Installed 1 skill ─────────────────╮\n│ │\n│ ✓ office-hours (copied) │\n│ → ./.agents/skills/office-hours │\n│ │\n├─────────────────────────────────────╯\n\n│\n└ Done! Review skills before use; they run with full agent permissions.", "stderr": "" }, @@ -3156,7 +3447,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 936, + "durationMs": 761, "stdout": "│\n● codex Agent detected — removing non-interactively\n│\n◇ Found 3 unique installed skill(s)\n│\n◇ Removal process complete\n│\n◆ Successfully removed 3 skill(s)\n\n│\n└ Done!", "stderr": "" }, @@ -3211,7 +3502,7 @@ ], "exitCode": 0, "signal": null, - "durationMs": 901, + "durationMs": 859, "stdout": "│\n● codex Agent detected — removing non-interactively\n│\n◇ Found 3 unique installed skill(s)\n│\n◇ Removal process complete\n│\n◆ Successfully removed 3 skill(s)\n\n│\n└ Done!", "stderr": "" }, @@ -3220,10 +3511,10 @@ ], "summary": { "passed": true, - "checks": 470, - "passedChecks": 470, + "checks": 510, + "passedChecks": 510, "failedChecks": 0, - "installCases": 16, + "installCases": 18, "removalCases": 2 }, "limitations": [ diff --git a/evals/parity/contracts/autoplan.json b/evals/parity/contracts/autoplan.json index cad2ab0a0..1282d343b 100644 --- a/evals/parity/contracts/autoplan.json +++ b/evals/parity/contracts/autoplan.json @@ -9,7 +9,7 @@ "source_path": "autoplan/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b2eaca9fde8f46001bea9961b8ed651d0f6f9e13", - "normalized_render_sha256": "b11bef3e060400cd59a16e41c9b6f8395ace065867a1c2a70329319153a2c4dc", + "normalized_render_sha256": "dffedf321083151a10e7377dba6dcfd5507b678c5279f5d47a1b0d91bfe53e09", "target": "skills/plan/references/legacy/autoplan.md", "overlays": [ 679, diff --git a/evals/parity/contracts/benchmark-models.json b/evals/parity/contracts/benchmark-models.json index fc36f977c..952eafecb 100644 --- a/evals/parity/contracts/benchmark-models.json +++ b/evals/parity/contracts/benchmark-models.json @@ -9,7 +9,7 @@ "source_path": "benchmark-models/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "034cda182406dc04a82c4336ac3ebc36b5fc41b1", - "normalized_render_sha256": "2ef0679d45f21bacc09cd774ff96bb3b82853a8e89d8606847c4f9416a47a48b", + "normalized_render_sha256": "3ea3c6cd3542350d4130ee0d5e170c905d0c5c3da096b1fc0e805e095af9321c", "target": "skills/qa/references/legacy/benchmark-models.md", "overlays": [ 679 diff --git a/evals/parity/contracts/benchmark.json b/evals/parity/contracts/benchmark.json index c69be08e5..16996315e 100644 --- a/evals/parity/contracts/benchmark.json +++ b/evals/parity/contracts/benchmark.json @@ -9,7 +9,7 @@ "source_path": "benchmark/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "038f16f5fba4ae4e9eae922e3276bba8ef88149e", - "normalized_render_sha256": "a4554b8b139ee95c9f25c2e747f9e2214b2f0db64401057529e5ec775be323cd", + "normalized_render_sha256": "05ac1b123a605201546a7e95899a5b55708ca7fbb7569c58b4d755c52b45a92d", "target": "skills/qa/references/legacy/benchmark.md", "overlays": [ 679 diff --git a/evals/parity/contracts/browse.json b/evals/parity/contracts/browse.json index 0eef9753d..e80e4ce29 100644 --- a/evals/parity/contracts/browse.json +++ b/evals/parity/contracts/browse.json @@ -9,7 +9,7 @@ "source_path": "browse/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9a159e4c9820172c229e2174d4a62a8f9668ab93", - "normalized_render_sha256": "b7fd526a00444003cad654abb15c2d17606cc5295f38533754e95108038d3467", + "normalized_render_sha256": "1b532bd904b1fa1686113e8c96b70015ea6b2e6df7319a72c299de901fe5e81b", "target": "skills/qa/references/legacy/browse.md", "overlays": [ 679, diff --git a/evals/parity/contracts/canary.json b/evals/parity/contracts/canary.json index 602a57153..5579bf385 100644 --- a/evals/parity/contracts/canary.json +++ b/evals/parity/contracts/canary.json @@ -9,7 +9,7 @@ "source_path": "canary/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d1eb2950aba2fa2b09d90f13143492c60d46793c", - "normalized_render_sha256": "329b61120f60893d023533cbc493389d1c7476819fb785422de0e7d34e0c2c0b", + "normalized_render_sha256": "89be5f218da2bd812303c87b8c177081727727e5e0d2dc74eb7a73299794d5ef", "target": "skills/qa/references/legacy/canary.md", "overlays": [ 679, diff --git a/evals/parity/contracts/codex.json b/evals/parity/contracts/codex.json index 6990d3f1e..9112c7a8c 100644 --- a/evals/parity/contracts/codex.json +++ b/evals/parity/contracts/codex.json @@ -9,7 +9,7 @@ "source_path": "codex/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "333de7d8d590cc78184b0e2371171f6121408f73", - "normalized_render_sha256": "0df6ff685d230f87763b4c43f957e20854fe1319d78247d683e5250e2accb188", + "normalized_render_sha256": "94ebbe955a1063f88712c51723023765519eab8986b029bf1222d0ec19b79e12", "target": "skills/review/references/legacy/codex.md", "overlays": [ 679 diff --git a/evals/parity/contracts/context-restore.json b/evals/parity/contracts/context-restore.json index 221d1a92e..d1f24aabc 100644 --- a/evals/parity/contracts/context-restore.json +++ b/evals/parity/contracts/context-restore.json @@ -9,7 +9,7 @@ "source_path": "context-restore/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "55889f6e06d3ba896f33a16969959c700bc24f1e", - "normalized_render_sha256": "c14726232c67616485c115dd7ca0898992bab9c7990629b40dc3b6f1c0dec0ee", + "normalized_render_sha256": "44c64ae4a63d0ed0fbc427ee2e4f0330d5074c14cdc589baa660dd7a22dbcdf9", "target": "skills/plan/references/legacy/context-restore.md", "overlays": [ 679 diff --git a/evals/parity/contracts/context-save.json b/evals/parity/contracts/context-save.json index 5707bd677..ba0900ea9 100644 --- a/evals/parity/contracts/context-save.json +++ b/evals/parity/contracts/context-save.json @@ -9,7 +9,7 @@ "source_path": "context-save/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "a3702bc95439cddd87841aba836708bf511ef55d", - "normalized_render_sha256": "348b840c60cecd8818d75f63ebb8e3effae659437137ce75c1ba6124177120b6", + "normalized_render_sha256": "f457da8b2cc1f9a4ed8ba82f4ea570a69460ce0147e171b5023d66324b6dc899", "target": "skills/plan/references/legacy/context-save.md", "overlays": [ 679 diff --git a/evals/parity/contracts/cso.json b/evals/parity/contracts/cso.json index 9e4642474..ad76ff4cd 100644 --- a/evals/parity/contracts/cso.json +++ b/evals/parity/contracts/cso.json @@ -9,7 +9,7 @@ "source_path": "cso/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "413fb099597b55dadca116e45a202aa693a94b74", - "normalized_render_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "normalized_render_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "target": "skills/review/references/legacy/cso.md", "overlays": [ 679, diff --git a/evals/parity/contracts/design-consultation.json b/evals/parity/contracts/design-consultation.json index ab4a7f8c8..606f77ba9 100644 --- a/evals/parity/contracts/design-consultation.json +++ b/evals/parity/contracts/design-consultation.json @@ -9,7 +9,7 @@ "source_path": "design-consultation/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "64af56ecdbd132cb7c28344e8e4ecb2e5dacf811", - "normalized_render_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "normalized_render_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "target": "skills/design/references/legacy/design-consultation.md", "overlays": [ 679, diff --git a/evals/parity/contracts/design-html.json b/evals/parity/contracts/design-html.json index 455ef6bb4..c05165088 100644 --- a/evals/parity/contracts/design-html.json +++ b/evals/parity/contracts/design-html.json @@ -9,7 +9,7 @@ "source_path": "design-html/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3cdec9a14d62d2e046ed924c972efc30a7d43aca", - "normalized_render_sha256": "cbb1b4357bedbb0fffe23c3e0ad31ee8c2a198a31443edb660e5c836e67e94e6", + "normalized_render_sha256": "40682d97ac83aa9178487348d5abf176334fd439e2d12f8e5cda1f8b20cd2c30", "target": "skills/design/references/legacy/design-html.md", "overlays": [ 679 diff --git a/evals/parity/contracts/design-review.json b/evals/parity/contracts/design-review.json index f150b887f..fef3393ab 100644 --- a/evals/parity/contracts/design-review.json +++ b/evals/parity/contracts/design-review.json @@ -9,7 +9,7 @@ "source_path": "design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "bdcda48e29b489a1cc49faa333922412251d4b41", - "normalized_render_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "normalized_render_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "target": "skills/design/references/legacy/design-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/design-shotgun.json b/evals/parity/contracts/design-shotgun.json index ae9d59f9e..acccf6f3e 100644 --- a/evals/parity/contracts/design-shotgun.json +++ b/evals/parity/contracts/design-shotgun.json @@ -9,7 +9,7 @@ "source_path": "design-shotgun/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "230dbc2922f05bf272bf5168a958a12604fac1bc", - "normalized_render_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "normalized_render_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "target": "skills/design/references/legacy/design-shotgun.md", "overlays": [ 679, diff --git a/evals/parity/contracts/devex-review.json b/evals/parity/contracts/devex-review.json index 87ff4e95d..a262e3669 100644 --- a/evals/parity/contracts/devex-review.json +++ b/evals/parity/contracts/devex-review.json @@ -9,7 +9,7 @@ "source_path": "devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "081d4f35bbdec0c6b3da8ae71615ec4d41a84551", - "normalized_render_sha256": "9fbc11b3fe252c281581512d67f6915c72e34bb2fdc7bc795e3077ee0e48d34e", + "normalized_render_sha256": "4a907c759b6cf4202fbacaea504b1eb601a53dd35b206109d6c5105168ade7e1", "target": "skills/qa/references/legacy/devex-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/diagram.json b/evals/parity/contracts/diagram.json index 333247a52..778bbe07e 100644 --- a/evals/parity/contracts/diagram.json +++ b/evals/parity/contracts/diagram.json @@ -9,7 +9,7 @@ "source_path": "diagram/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9e19a52c6b7f727ce4faf5c1f9c14514ecb52f50", - "normalized_render_sha256": "21f18fa4957b79bf5941abfebd1b222dee3eb960520e0deac1c32d0b6fd62a38", + "normalized_render_sha256": "18b1db5eb50d85a6da26b60ac72a19c144e95bb4a2d92b2d23a87086c49c01b7", "target": "skills/design/references/legacy/diagram.md", "overlays": [ 679 diff --git a/evals/parity/contracts/document-generate.json b/evals/parity/contracts/document-generate.json index 544f9cd50..99ec0b840 100644 --- a/evals/parity/contracts/document-generate.json +++ b/evals/parity/contracts/document-generate.json @@ -9,7 +9,7 @@ "source_path": "document-generate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d3ef0cbc3786c4010b3c692fb94246f56a7e1d7b", - "normalized_render_sha256": "71726ef3082b6c00a5cad19d28da4d830f8e9b261d213a7938c4e1ce3b8c54bb", + "normalized_render_sha256": "d91adb16f1749c24bc7a05aa6cff3ff00bb0db005872751f417e66de2057e620", "target": "skills/ship/references/legacy/document-generate.md", "overlays": [ 679 diff --git a/evals/parity/contracts/document-release.json b/evals/parity/contracts/document-release.json index 8f9ac8572..f01a49b79 100644 --- a/evals/parity/contracts/document-release.json +++ b/evals/parity/contracts/document-release.json @@ -9,7 +9,7 @@ "source_path": "document-release/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7621cb31290217b5c7cc8fb000e910b3dd38128f", - "normalized_render_sha256": "e5aa85ac93b36b638d075d152f9d7108c0108727056f61331b25fe33de569a59", + "normalized_render_sha256": "21de7096aa788e3c4206587761f3122016f4cfc3080ec07f07cd7698075307a5", "target": "skills/ship/references/legacy/document-release.md", "overlays": [ 679 diff --git a/evals/parity/contracts/freeze.json b/evals/parity/contracts/freeze.json index ebeaa0b87..f43789191 100644 --- a/evals/parity/contracts/freeze.json +++ b/evals/parity/contracts/freeze.json @@ -9,7 +9,7 @@ "source_path": "freeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c0b31aa7f9f216fc5a351d91f4bcff68c828d090", - "normalized_render_sha256": "433bb7c1909852c83978ae282c582590c5136abe56020e7d0be39749822c345b", + "normalized_render_sha256": "036fe2c7d5c154982ba3509e9b9ab1f5867f86c1d71beb569802d7f7be46ed7c", "target": "skills/debug/references/legacy/freeze.md", "overlays": [ 679 diff --git a/evals/parity/contracts/guard.json b/evals/parity/contracts/guard.json index df53b47e4..d6c192e73 100644 --- a/evals/parity/contracts/guard.json +++ b/evals/parity/contracts/guard.json @@ -9,7 +9,7 @@ "source_path": "guard/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3d34ee0c181ec7b263bf6092ba8f384619c5efb6", - "normalized_render_sha256": "ff8170babcc9ad20f6de292db838d2c4545f0ed98dddd184ab5e7c52c073dc7e", + "normalized_render_sha256": "36072a06a2a2ab6c1beec2a888417fb96eb1a50f284cb26ee3796b7c69857be8", "target": "skills/debug/references/legacy/guard.md", "overlays": [ 679 diff --git a/evals/parity/contracts/health.json b/evals/parity/contracts/health.json index 0c82c14d4..96c3ba5e8 100644 --- a/evals/parity/contracts/health.json +++ b/evals/parity/contracts/health.json @@ -9,7 +9,7 @@ "source_path": "health/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f92eb7347ec4f77dd8dbf464d63fcdf6a3459908", - "normalized_render_sha256": "ff664221e7de57e66c1b45d719982d90f38e0288914b34731ba4dd73541347e8", + "normalized_render_sha256": "514ff165b393a25707c9ed336869ac0b50c63a00d90a21b1656276e1b97842d9", "target": "skills/review/references/legacy/health.md", "overlays": [ 679 diff --git a/evals/parity/contracts/investigate.json b/evals/parity/contracts/investigate.json index 305c3c185..6cc288d99 100644 --- a/evals/parity/contracts/investigate.json +++ b/evals/parity/contracts/investigate.json @@ -9,7 +9,7 @@ "source_path": "investigate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "67e254d743ffb9060f48e3f6d4b715c077ee688d", - "normalized_render_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "normalized_render_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "target": "skills/debug/references/legacy/investigate.md", "overlays": [ 679, diff --git a/evals/parity/contracts/ios-qa.json b/evals/parity/contracts/ios-qa.json index 88b17f567..d66ac9cc6 100644 --- a/evals/parity/contracts/ios-qa.json +++ b/evals/parity/contracts/ios-qa.json @@ -9,7 +9,7 @@ "source_path": "ios-qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "e93d2831a31df65ec8e4e8693919ef5bf148457f", - "normalized_render_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "normalized_render_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "target": "skills/qa/references/legacy/ios-qa.md", "overlays": [ 679 diff --git a/evals/parity/contracts/ios-sync.json b/evals/parity/contracts/ios-sync.json index 2cb4f8235..cd6b89432 100644 --- a/evals/parity/contracts/ios-sync.json +++ b/evals/parity/contracts/ios-sync.json @@ -9,7 +9,7 @@ "source_path": "ios-sync/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "156a33c4c35d3bad804e44c93ae4c931878489f3", - "normalized_render_sha256": "c51e8865876efae833e92d54b37780afd4fa6240c13231dfb7d4635948fbc73f", + "normalized_render_sha256": "88f9f20aa406c13098ebd2709afc6487bb42c5466ac9f757bf4d9baff7f028d0", "target": "skills/ship/references/legacy/ios-sync.md", "overlays": [ 679 diff --git a/evals/parity/contracts/land-and-deploy.json b/evals/parity/contracts/land-and-deploy.json index a552d64b3..31b6785fa 100644 --- a/evals/parity/contracts/land-and-deploy.json +++ b/evals/parity/contracts/land-and-deploy.json @@ -9,7 +9,7 @@ "source_path": "land-and-deploy/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "98976ad020d541d251cc7e34802a13458ddc88e2", - "normalized_render_sha256": "3fab0678a9a7a76664330c74db2bd25b56c335ac72ce4e691c2f2564a7ede819", + "normalized_render_sha256": "6920f3d97ce474b8f20c8b3e38ca9d3c03973e47af33103a60bab7c02eb867bd", "target": "skills/ship/references/legacy/land-and-deploy.md", "overlays": [ 679, diff --git a/evals/parity/contracts/landing-report.json b/evals/parity/contracts/landing-report.json index f59a3b6bf..8644773ef 100644 --- a/evals/parity/contracts/landing-report.json +++ b/evals/parity/contracts/landing-report.json @@ -9,7 +9,7 @@ "source_path": "landing-report/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "32a8cc1ab036b1ff8692f0400b68d8b56224a251", - "normalized_render_sha256": "4f9512bafcfd995c4c35cdff869ed60f8a9a9ef12f20bc18bf7b898cbd2faa53", + "normalized_render_sha256": "ae5050b0558b5324f58ee9181dc83ac0189b63da70d94f86792c08e112765fe9", "target": "skills/ship/references/legacy/landing-report.md", "overlays": [ 679 diff --git a/evals/parity/contracts/learn.json b/evals/parity/contracts/learn.json index 8d6960984..340643a36 100644 --- a/evals/parity/contracts/learn.json +++ b/evals/parity/contracts/learn.json @@ -9,7 +9,7 @@ "source_path": "learn/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "90d08d2298cccd0d5090f194a2cd76a5361b4323", - "normalized_render_sha256": "0d1bbdf1bba2eaae206a350d47fb25ab50d49611262cb376861e83f78822f762", + "normalized_render_sha256": "b56cfcec0690c47124cf62e40aea16658933b61d44adbe821449f8c65a96a7c4", "target": "skills/plan/references/legacy/learn.md", "overlays": [ 679, diff --git a/evals/parity/contracts/make-pdf.json b/evals/parity/contracts/make-pdf.json index fac92d46c..bc9017246 100644 --- a/evals/parity/contracts/make-pdf.json +++ b/evals/parity/contracts/make-pdf.json @@ -9,7 +9,7 @@ "source_path": "make-pdf/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9133a711d4f3d056a21f790e8ec3b98f13fbaa50", - "normalized_render_sha256": "c092bc644ff9b8929d9cbdfedb6f2761b40ce8e1fca53d13ad350ba91195104d", + "normalized_render_sha256": "ece1cfa64bb3cbb3979ce06890e09bbc2ccab4e1d49310c57dc28e70e1236ee9", "target": "skills/design/references/legacy/make-pdf.md", "overlays": [ 679 diff --git a/evals/parity/contracts/office-hours.json b/evals/parity/contracts/office-hours.json index 309388904..c425dddd4 100644 --- a/evals/parity/contracts/office-hours.json +++ b/evals/parity/contracts/office-hours.json @@ -9,7 +9,7 @@ "source_path": "office-hours/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "8568fe73cca76a80805fab3092cacd10db7e1d7f", - "normalized_render_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "normalized_render_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "target": "skills/plan/references/legacy/office-hours.md", "overlays": [ 679, diff --git a/evals/parity/contracts/open-gstack-browser.json b/evals/parity/contracts/open-gstack-browser.json index 6d77dfe28..935315d89 100644 --- a/evals/parity/contracts/open-gstack-browser.json +++ b/evals/parity/contracts/open-gstack-browser.json @@ -9,7 +9,7 @@ "source_path": "open-gstack-browser/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ef91a527890a3ac3622cc7dc84bad1ff7b64443b", - "normalized_render_sha256": "9e4a08db3e17badfc67703f7c5e66f80a7f05e0b96fad31fc48b8216964d7449", + "normalized_render_sha256": "df626d71b8cea4a02d2fb7aef3169563dd132bf17a9d6d84f287894cad84d2cf", "target": "skills/qa/references/legacy/open-gstack-browser.md", "overlays": [ 679 diff --git a/evals/parity/contracts/pair-agent.json b/evals/parity/contracts/pair-agent.json index 82e8bfc59..41db63c13 100644 --- a/evals/parity/contracts/pair-agent.json +++ b/evals/parity/contracts/pair-agent.json @@ -9,7 +9,7 @@ "source_path": "pair-agent/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75ed42d590f99c46cd0883c37bb1f2f9f499211c", - "normalized_render_sha256": "6882bc549c689ff50fb06d5e72a597939aa651926c50067330d1e6fd86cd6b58", + "normalized_render_sha256": "8557ca390d0b6548f956d2c0e9316f1cf137689d4dc17d40a4525d19f22bc457", "target": "skills/qa/references/legacy/pair-agent.md", "overlays": [ 679 diff --git a/evals/parity/contracts/plan-ceo-review.json b/evals/parity/contracts/plan-ceo-review.json index b7ca90be9..700a85d23 100644 --- a/evals/parity/contracts/plan-ceo-review.json +++ b/evals/parity/contracts/plan-ceo-review.json @@ -9,7 +9,7 @@ "source_path": "plan-ceo-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c43cfe64146fe79df74a974f1673fe36defcce00", - "normalized_render_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "normalized_render_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "target": "skills/plan/references/legacy/plan-ceo-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/plan-design-review.json b/evals/parity/contracts/plan-design-review.json index eb8c96ccd..e31173b91 100644 --- a/evals/parity/contracts/plan-design-review.json +++ b/evals/parity/contracts/plan-design-review.json @@ -9,7 +9,7 @@ "source_path": "plan-design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7178c991e41277410de500298cf81372543861af", - "normalized_render_sha256": "2f7a5d113c466d75ca4524e424c072044d92ae7ab93009f1fbe3ae5965e8aa34", + "normalized_render_sha256": "1ff3644728bee14a4a002b91bada44b1c95a20ee5779ed458dabca2485a19e78", "target": "skills/design/references/legacy/plan-design-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/plan-devex-review.json b/evals/parity/contracts/plan-devex-review.json index 340fa643b..d8624cbb6 100644 --- a/evals/parity/contracts/plan-devex-review.json +++ b/evals/parity/contracts/plan-devex-review.json @@ -9,7 +9,7 @@ "source_path": "plan-devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1ef723c10997a08ef87940daceb08bf8d60dd810", - "normalized_render_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "normalized_render_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "target": "skills/plan/references/legacy/plan-devex-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/plan-eng-review.json b/evals/parity/contracts/plan-eng-review.json index 90c78ae5f..3c0cc03cb 100644 --- a/evals/parity/contracts/plan-eng-review.json +++ b/evals/parity/contracts/plan-eng-review.json @@ -9,7 +9,7 @@ "source_path": "plan-eng-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1d5be0e6f47f0896ee831b53c56818850b1dcfe4", - "normalized_render_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "normalized_render_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "target": "skills/plan/references/legacy/plan-eng-review.md", "overlays": [ 679, diff --git a/evals/parity/contracts/plan-tune.json b/evals/parity/contracts/plan-tune.json index fbf88ac97..5c5581596 100644 --- a/evals/parity/contracts/plan-tune.json +++ b/evals/parity/contracts/plan-tune.json @@ -9,7 +9,7 @@ "source_path": "plan-tune/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "dc1214d4c023ed2b126aca8aedb4153b222e73c5", - "normalized_render_sha256": "306bbf971a2867e1f39cce82d64289d6c457bceb07439ddd6f4a3ff8f8ae5e9a", + "normalized_render_sha256": "f2204078e497a347b48a2b0899400baf3b6ab54f097bbd57d06deb5f71126ce9", "target": "skills/plan/references/legacy/plan-tune.md", "overlays": [ 679 diff --git a/evals/parity/contracts/qa-only.json b/evals/parity/contracts/qa-only.json index 3d2a99061..6221b0169 100644 --- a/evals/parity/contracts/qa-only.json +++ b/evals/parity/contracts/qa-only.json @@ -9,7 +9,7 @@ "source_path": "qa-only/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75c4123cc5c406ffdd36c71a094335c137135b1e", - "normalized_render_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "normalized_render_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "target": "skills/qa/references/legacy/qa-only.md", "overlays": [ 679, diff --git a/evals/parity/contracts/qa.json b/evals/parity/contracts/qa.json index 2ea05cbc5..e00d3e760 100644 --- a/evals/parity/contracts/qa.json +++ b/evals/parity/contracts/qa.json @@ -9,7 +9,7 @@ "source_path": "qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "11997f7b878282c34b6bfd3d4b7a8131f9ad4da8", - "normalized_render_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "normalized_render_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "target": "skills/qa/references/legacy/qa.md", "overlays": [ 679, diff --git a/evals/parity/contracts/retro.json b/evals/parity/contracts/retro.json index 144bfe5a6..f6e70230d 100644 --- a/evals/parity/contracts/retro.json +++ b/evals/parity/contracts/retro.json @@ -9,7 +9,7 @@ "source_path": "retro/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b0819c8a6b736baf489179ba587442cf9973b9d2", - "normalized_render_sha256": "c92e69da5c62e4bb5a426f4fb5623b41215e538315e2faebece8ba118cacaf71", + "normalized_render_sha256": "d2dd2fe80fbeb5573266700052e0ce5e781dcd31ed986cd9203880bc98c9d9c8", "target": "skills/plan/references/legacy/retro.md", "overlays": [ 679, diff --git a/evals/parity/contracts/review.json b/evals/parity/contracts/review.json index 5c75cd791..e4f087fdc 100644 --- a/evals/parity/contracts/review.json +++ b/evals/parity/contracts/review.json @@ -9,7 +9,7 @@ "source_path": "review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ba1ea88068de4b09cf717eb4ae42aa247d198314", - "normalized_render_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "normalized_render_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "target": "skills/review/references/legacy/review.md", "overlays": [ 610, diff --git a/evals/parity/contracts/scrape.json b/evals/parity/contracts/scrape.json index ea2e3cb0e..ace34ff58 100644 --- a/evals/parity/contracts/scrape.json +++ b/evals/parity/contracts/scrape.json @@ -9,7 +9,7 @@ "source_path": "scrape/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "4cb4f17c074edcdce0bc8d133f19a6a739964851", - "normalized_render_sha256": "03ef708a4c9a1f3de961c48767617502c564690faa7240b3ca39b20496a17f98", + "normalized_render_sha256": "82b7214021c997000822b2b4adcae19443490198556a7e6af54d8937ae504943", "target": "skills/qa/references/legacy/scrape.md", "overlays": [ 679, diff --git a/evals/parity/contracts/setup-browser-cookies.json b/evals/parity/contracts/setup-browser-cookies.json index ae7d12a96..f86f14c0a 100644 --- a/evals/parity/contracts/setup-browser-cookies.json +++ b/evals/parity/contracts/setup-browser-cookies.json @@ -9,7 +9,7 @@ "source_path": "setup-browser-cookies/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f812d9f56f27c32fb5f102083bbe418344c1a652", - "normalized_render_sha256": "8ab5dfb05136a637ce41067d9e50ade76c4a8b957587e4eab134eaa260b29ac3", + "normalized_render_sha256": "04c161a58c1a9010efe38095b383b0e1d445a2b678e5bf931a1281d45196940d", "target": "skills/qa/references/legacy/setup-browser-cookies.md", "overlays": [ 679 diff --git a/evals/parity/contracts/setup-gbrain.json b/evals/parity/contracts/setup-gbrain.json index 7a0a0dc4c..8cfba658c 100644 --- a/evals/parity/contracts/setup-gbrain.json +++ b/evals/parity/contracts/setup-gbrain.json @@ -9,7 +9,7 @@ "source_path": "setup-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f48581543b46ecf889f4f86fb7a0d9e75d0bd6ca", - "normalized_render_sha256": "244ebee93e8871d59c1b0eac040bed362783e48a9b134171d743750ce9a59b29", + "normalized_render_sha256": "ea0759f957601c05a6e131ee3d1d9e07c732b7852d8a7b10c1fdc259e6e12c3b", "target": "skills/plan/references/legacy/setup-gbrain.md", "overlays": [ 679 diff --git a/evals/parity/contracts/ship.json b/evals/parity/contracts/ship.json index 7ab717d27..a8ade9005 100644 --- a/evals/parity/contracts/ship.json +++ b/evals/parity/contracts/ship.json @@ -9,7 +9,7 @@ "source_path": "ship/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "068ac4fe54bcb46572295d48263b11bd38fcde18", - "normalized_render_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "normalized_render_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "target": "skills/ship/references/legacy/ship.md", "overlays": [ 679, diff --git a/evals/parity/contracts/skillify.json b/evals/parity/contracts/skillify.json index 08d7685c0..2be350a34 100644 --- a/evals/parity/contracts/skillify.json +++ b/evals/parity/contracts/skillify.json @@ -9,7 +9,7 @@ "source_path": "skillify/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "21fb2f503e3d40683fa782b21abf05a8f0fef69b", - "normalized_render_sha256": "b0743c957157e19bd90b457fd3ac9d6924c3aedce969c10af8d7973c2bcd6c9f", + "normalized_render_sha256": "8743671a165c951e852d1f37418aa6c0bd7eeb4f32866dc25f55f7d8eb26f1d5", "target": "skills/qa/references/legacy/skillify.md", "overlays": [ 679, diff --git a/evals/parity/contracts/spec.json b/evals/parity/contracts/spec.json index 2e86a6c3a..7eeb62e50 100644 --- a/evals/parity/contracts/spec.json +++ b/evals/parity/contracts/spec.json @@ -9,7 +9,7 @@ "source_path": "spec/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "6c0c14e1b37e1e56d85427e9c8d080a6df908992", - "normalized_render_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "normalized_render_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "target": "skills/plan/references/legacy/spec.md", "overlays": [ 679 diff --git a/evals/parity/contracts/sync-gbrain.json b/evals/parity/contracts/sync-gbrain.json index f53a53a95..6946ea3af 100644 --- a/evals/parity/contracts/sync-gbrain.json +++ b/evals/parity/contracts/sync-gbrain.json @@ -9,7 +9,7 @@ "source_path": "sync-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "2ec065472e263a07f3818ed87ee9a6a2e13ca3ae", - "normalized_render_sha256": "175e7d0d2998bd4c8ddcd9a84228c41ba4a5cf3aca3a8bc09ddfc5a303c1df30", + "normalized_render_sha256": "973d3d126e97f2c720bc048e6a124e51c0195f9bc7a714ffc41fd57af37f4e73", "target": "skills/plan/references/legacy/sync-gbrain.md", "overlays": [ 679 diff --git a/evals/parity/contracts/unfreeze.json b/evals/parity/contracts/unfreeze.json index e8c770377..df736b8a9 100644 --- a/evals/parity/contracts/unfreeze.json +++ b/evals/parity/contracts/unfreeze.json @@ -9,7 +9,7 @@ "source_path": "unfreeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "88e413fe5a49a45d46d8867b2d80ace30b3b45aa", - "normalized_render_sha256": "51d6183901e866697382b7e900e2154e8bfb6a9ebf4f3e1bb3ad29925fad0e20", + "normalized_render_sha256": "19cd17084af59cb912f9507bf8ffdc81e38aa1fbef14605a838efbc6521e9534", "target": "skills/debug/references/legacy/unfreeze.md", "overlays": [ 679 diff --git a/evals/parity/manifest.json b/evals/parity/manifest.json index d2999fa5c..89043877e 100644 --- a/evals/parity/manifest.json +++ b/evals/parity/manifest.json @@ -20,7 +20,7 @@ "bug_fix_ports": 16, "assets": 78, "dependency_copies": 4, - "runtime_helpers": 40 + "runtime_helpers": 41 }, "sources": [ { @@ -83,7 +83,7 @@ "source_path": "office-hours/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "8568fe73cca76a80805fab3092cacd10db7e1d7f", - "normalized_render_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "normalized_render_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "target": "skills/plan/references/legacy/office-hours.md", "disposition": "BUG_FIX", "overlays": [ @@ -197,7 +197,7 @@ "source_path": "plan-ceo-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c43cfe64146fe79df74a974f1673fe36defcce00", - "normalized_render_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "normalized_render_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "target": "skills/plan/references/legacy/plan-ceo-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -337,7 +337,7 @@ "source_path": "plan-eng-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1d5be0e6f47f0896ee831b53c56818850b1dcfe4", - "normalized_render_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "normalized_render_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "target": "skills/plan/references/legacy/plan-eng-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -457,7 +457,7 @@ "source_path": "plan-devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "1ef723c10997a08ef87940daceb08bf8d60dd810", - "normalized_render_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "normalized_render_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "target": "skills/plan/references/legacy/plan-devex-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -590,7 +590,7 @@ "source_path": "autoplan/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b2eaca9fde8f46001bea9961b8ed651d0f6f9e13", - "normalized_render_sha256": "b11bef3e060400cd59a16e41c9b6f8395ace065867a1c2a70329319153a2c4dc", + "normalized_render_sha256": "dffedf321083151a10e7377dba6dcfd5507b678c5279f5d47a1b0d91bfe53e09", "target": "skills/plan/references/legacy/autoplan.md", "disposition": "BUG_FIX", "overlays": [ @@ -681,7 +681,7 @@ "source_path": "spec/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "6c0c14e1b37e1e56d85427e9c8d080a6df908992", - "normalized_render_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "normalized_render_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "target": "skills/plan/references/legacy/spec.md", "disposition": "BUG_FIX", "overlays": [ @@ -814,7 +814,7 @@ "source_path": "plan-tune/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "dc1214d4c023ed2b126aca8aedb4153b222e73c5", - "normalized_render_sha256": "306bbf971a2867e1f39cce82d64289d6c457bceb07439ddd6f4a3ff8f8ae5e9a", + "normalized_render_sha256": "f2204078e497a347b48a2b0899400baf3b6ab54f097bbd57d06deb5f71126ce9", "target": "skills/plan/references/legacy/plan-tune.md", "disposition": "BUG_FIX", "overlays": [ @@ -893,7 +893,7 @@ "source_path": "context-save/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "a3702bc95439cddd87841aba836708bf511ef55d", - "normalized_render_sha256": "348b840c60cecd8818d75f63ebb8e3effae659437137ce75c1ba6124177120b6", + "normalized_render_sha256": "f457da8b2cc1f9a4ed8ba82f4ea570a69460ce0147e171b5023d66324b6dc899", "target": "skills/plan/references/legacy/context-save.md", "disposition": "BUG_FIX", "overlays": [ @@ -968,7 +968,7 @@ "source_path": "context-restore/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "55889f6e06d3ba896f33a16969959c700bc24f1e", - "normalized_render_sha256": "c14726232c67616485c115dd7ca0898992bab9c7990629b40dc3b6f1c0dec0ee", + "normalized_render_sha256": "44c64ae4a63d0ed0fbc427ee2e4f0330d5074c14cdc589baa660dd7a22dbcdf9", "target": "skills/plan/references/legacy/context-restore.md", "disposition": "BUG_FIX", "overlays": [ @@ -1040,7 +1040,7 @@ "source_path": "learn/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "90d08d2298cccd0d5090f194a2cd76a5361b4323", - "normalized_render_sha256": "0d1bbdf1bba2eaae206a350d47fb25ab50d49611262cb376861e83f78822f762", + "normalized_render_sha256": "b56cfcec0690c47124cf62e40aea16658933b61d44adbe821449f8c65a96a7c4", "target": "skills/plan/references/legacy/learn.md", "disposition": "BUG_FIX", "overlays": [ @@ -1113,7 +1113,7 @@ "source_path": "retro/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "b0819c8a6b736baf489179ba587442cf9973b9d2", - "normalized_render_sha256": "c92e69da5c62e4bb5a426f4fb5623b41215e538315e2faebece8ba118cacaf71", + "normalized_render_sha256": "d2dd2fe80fbeb5573266700052e0ce5e781dcd31ed986cd9203880bc98c9d9c8", "target": "skills/plan/references/legacy/retro.md", "disposition": "BUG_FIX", "overlays": [ @@ -1240,7 +1240,7 @@ "source_path": "setup-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f48581543b46ecf889f4f86fb7a0d9e75d0bd6ca", - "normalized_render_sha256": "244ebee93e8871d59c1b0eac040bed362783e48a9b134171d743750ce9a59b29", + "normalized_render_sha256": "ea0759f957601c05a6e131ee3d1d9e07c732b7852d8a7b10c1fdc259e6e12c3b", "target": "skills/plan/references/legacy/setup-gbrain.md", "disposition": "BUG_FIX", "overlays": [ @@ -1340,7 +1340,7 @@ "source_path": "sync-gbrain/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "2ec065472e263a07f3818ed87ee9a6a2e13ca3ae", - "normalized_render_sha256": "175e7d0d2998bd4c8ddcd9a84228c41ba4a5cf3aca3a8bc09ddfc5a303c1df30", + "normalized_render_sha256": "973d3d126e97f2c720bc048e6a124e51c0195f9bc7a714ffc41fd57af37f4e73", "target": "skills/plan/references/legacy/sync-gbrain.md", "disposition": "BUG_FIX", "overlays": [ @@ -1416,7 +1416,7 @@ "source_path": "design-consultation/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "64af56ecdbd132cb7c28344e8e4ecb2e5dacf811", - "normalized_render_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "normalized_render_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "target": "skills/design/references/legacy/design-consultation.md", "disposition": "BUG_FIX", "overlays": [ @@ -1504,7 +1504,7 @@ "source_path": "design-shotgun/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "230dbc2922f05bf272bf5168a958a12604fac1bc", - "normalized_render_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "normalized_render_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "target": "skills/design/references/legacy/design-shotgun.md", "disposition": "BUG_FIX", "overlays": [ @@ -1591,7 +1591,7 @@ "source_path": "design-html/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3cdec9a14d62d2e046ed924c972efc30a7d43aca", - "normalized_render_sha256": "cbb1b4357bedbb0fffe23c3e0ad31ee8c2a198a31443edb660e5c836e67e94e6", + "normalized_render_sha256": "40682d97ac83aa9178487348d5abf176334fd439e2d12f8e5cda1f8b20cd2c30", "target": "skills/design/references/legacy/design-html.md", "disposition": "BUG_FIX", "overlays": [ @@ -1687,7 +1687,7 @@ "source_path": "plan-design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7178c991e41277410de500298cf81372543861af", - "normalized_render_sha256": "2f7a5d113c466d75ca4524e424c072044d92ae7ab93009f1fbe3ae5965e8aa34", + "normalized_render_sha256": "1ff3644728bee14a4a002b91bada44b1c95a20ee5779ed458dabca2485a19e78", "target": "skills/design/references/legacy/plan-design-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -1820,7 +1820,7 @@ "source_path": "design-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "bdcda48e29b489a1cc49faa333922412251d4b41", - "normalized_render_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "normalized_render_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "target": "skills/design/references/legacy/design-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -2022,7 +2022,7 @@ "source_path": "diagram/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9e19a52c6b7f727ce4faf5c1f9c14514ecb52f50", - "normalized_render_sha256": "21f18fa4957b79bf5941abfebd1b222dee3eb960520e0deac1c32d0b6fd62a38", + "normalized_render_sha256": "18b1db5eb50d85a6da26b60ac72a19c144e95bb4a2d92b2d23a87086c49c01b7", "target": "skills/design/references/legacy/diagram.md", "disposition": "BUG_FIX", "overlays": [ @@ -2095,7 +2095,7 @@ "source_path": "make-pdf/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9133a711d4f3d056a21f790e8ec3b98f13fbaa50", - "normalized_render_sha256": "c092bc644ff9b8929d9cbdfedb6f2761b40ce8e1fca53d13ad350ba91195104d", + "normalized_render_sha256": "ece1cfa64bb3cbb3979ce06890e09bbc2ccab4e1d49310c57dc28e70e1236ee9", "target": "skills/design/references/legacy/make-pdf.md", "disposition": "BUG_FIX", "overlays": [ @@ -2162,7 +2162,7 @@ "source_path": "qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "11997f7b878282c34b6bfd3d4b7a8131f9ad4da8", - "normalized_render_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "normalized_render_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "target": "skills/qa/references/legacy/qa.md", "disposition": "BUG_FIX", "overlays": [ @@ -2288,7 +2288,7 @@ "source_path": "qa-only/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75c4123cc5c406ffdd36c71a094335c137135b1e", - "normalized_render_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "normalized_render_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "target": "skills/qa/references/legacy/qa-only.md", "disposition": "BUG_FIX", "overlays": [ @@ -2389,7 +2389,7 @@ "source_path": "ios-qa/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "e93d2831a31df65ec8e4e8693919ef5bf148457f", - "normalized_render_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "normalized_render_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "target": "skills/qa/references/legacy/ios-qa.md", "disposition": "BUG_FIX", "overlays": [ @@ -2466,7 +2466,7 @@ "source_path": "devex-review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "081d4f35bbdec0c6b3da8ae71615ec4d41a84551", - "normalized_render_sha256": "9fbc11b3fe252c281581512d67f6915c72e34bb2fdc7bc795e3077ee0e48d34e", + "normalized_render_sha256": "4a907c759b6cf4202fbacaea504b1eb601a53dd35b206109d6c5105168ade7e1", "target": "skills/qa/references/legacy/devex-review.md", "disposition": "BUG_FIX", "overlays": [ @@ -2565,7 +2565,7 @@ "source_path": "benchmark/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "038f16f5fba4ae4e9eae922e3276bba8ef88149e", - "normalized_render_sha256": "a4554b8b139ee95c9f25c2e747f9e2214b2f0db64401057529e5ec775be323cd", + "normalized_render_sha256": "05ac1b123a605201546a7e95899a5b55708ca7fbb7569c58b4d755c52b45a92d", "target": "skills/qa/references/legacy/benchmark.md", "disposition": "BUG_FIX", "overlays": [ @@ -2631,7 +2631,7 @@ "source_path": "canary/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d1eb2950aba2fa2b09d90f13143492c60d46793c", - "normalized_render_sha256": "329b61120f60893d023533cbc493389d1c7476819fb785422de0e7d34e0c2c0b", + "normalized_render_sha256": "89be5f218da2bd812303c87b8c177081727727e5e0d2dc74eb7a73299794d5ef", "target": "skills/qa/references/legacy/canary.md", "disposition": "BUG_FIX", "overlays": [ @@ -2710,7 +2710,7 @@ "source_path": "browse/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "9a159e4c9820172c229e2174d4a62a8f9668ab93", - "normalized_render_sha256": "b7fd526a00444003cad654abb15c2d17606cc5295f38533754e95108038d3467", + "normalized_render_sha256": "1b532bd904b1fa1686113e8c96b70015ea6b2e6df7319a72c299de901fe5e81b", "target": "skills/qa/references/legacy/browse.md", "disposition": "BUG_FIX", "overlays": [ @@ -2798,7 +2798,7 @@ "source_path": "open-gstack-browser/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ef91a527890a3ac3622cc7dc84bad1ff7b64443b", - "normalized_render_sha256": "9e4a08db3e17badfc67703f7c5e66f80a7f05e0b96fad31fc48b8216964d7449", + "normalized_render_sha256": "df626d71b8cea4a02d2fb7aef3169563dd132bf17a9d6d84f287894cad84d2cf", "target": "skills/qa/references/legacy/open-gstack-browser.md", "disposition": "BUG_FIX", "overlays": [ @@ -2873,7 +2873,7 @@ "source_path": "setup-browser-cookies/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f812d9f56f27c32fb5f102083bbe418344c1a652", - "normalized_render_sha256": "8ab5dfb05136a637ce41067d9e50ade76c4a8b957587e4eab134eaa260b29ac3", + "normalized_render_sha256": "04c161a58c1a9010efe38095b383b0e1d445a2b678e5bf931a1281d45196940d", "target": "skills/qa/references/legacy/setup-browser-cookies.md", "disposition": "BUG_FIX", "overlays": [ @@ -2934,7 +2934,7 @@ "source_path": "pair-agent/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "75ed42d590f99c46cd0883c37bb1f2f9f499211c", - "normalized_render_sha256": "6882bc549c689ff50fb06d5e72a597939aa651926c50067330d1e6fd86cd6b58", + "normalized_render_sha256": "8557ca390d0b6548f956d2c0e9316f1cf137689d4dc17d40a4525d19f22bc457", "target": "skills/qa/references/legacy/pair-agent.md", "disposition": "BUG_FIX", "overlays": [ @@ -3017,7 +3017,7 @@ "source_path": "scrape/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "4cb4f17c074edcdce0bc8d133f19a6a739964851", - "normalized_render_sha256": "03ef708a4c9a1f3de961c48767617502c564690faa7240b3ca39b20496a17f98", + "normalized_render_sha256": "82b7214021c997000822b2b4adcae19443490198556a7e6af54d8937ae504943", "target": "skills/qa/references/legacy/scrape.md", "disposition": "BUG_FIX", "overlays": [ @@ -3094,7 +3094,7 @@ "source_path": "skillify/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "21fb2f503e3d40683fa782b21abf05a8f0fef69b", - "normalized_render_sha256": "b0743c957157e19bd90b457fd3ac9d6924c3aedce969c10af8d7973c2bcd6c9f", + "normalized_render_sha256": "8743671a165c951e852d1f37418aa6c0bd7eeb4f32866dc25f55f7d8eb26f1d5", "target": "skills/qa/references/legacy/skillify.md", "disposition": "BUG_FIX", "overlays": [ @@ -3177,7 +3177,7 @@ "source_path": "benchmark-models/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "034cda182406dc04a82c4336ac3ebc36b5fc41b1", - "normalized_render_sha256": "2ef0679d45f21bacc09cd774ff96bb3b82853a8e89d8606847c4f9416a47a48b", + "normalized_render_sha256": "3ea3c6cd3542350d4130ee0d5e170c905d0c5c3da096b1fc0e805e095af9321c", "target": "skills/qa/references/legacy/benchmark-models.md", "disposition": "BUG_FIX", "overlays": [ @@ -3237,7 +3237,7 @@ "source_path": "investigate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "67e254d743ffb9060f48e3f6d4b715c077ee688d", - "normalized_render_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "normalized_render_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "target": "skills/debug/references/legacy/investigate.md", "disposition": "BUG_FIX", "overlays": [ @@ -3433,7 +3433,7 @@ "source_path": "freeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "c0b31aa7f9f216fc5a351d91f4bcff68c828d090", - "normalized_render_sha256": "433bb7c1909852c83978ae282c582590c5136abe56020e7d0be39749822c345b", + "normalized_render_sha256": "036fe2c7d5c154982ba3509e9b9ab1f5867f86c1d71beb569802d7f7be46ed7c", "target": "skills/debug/references/legacy/freeze.md", "disposition": "BUG_FIX", "overlays": [ @@ -3477,7 +3477,7 @@ "source_path": "guard/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "3d34ee0c181ec7b263bf6092ba8f384619c5efb6", - "normalized_render_sha256": "ff8170babcc9ad20f6de292db838d2c4545f0ed98dddd184ab5e7c52c073dc7e", + "normalized_render_sha256": "36072a06a2a2ab6c1beec2a888417fb96eb1a50f284cb26ee3796b7c69857be8", "target": "skills/debug/references/legacy/guard.md", "disposition": "BUG_FIX", "overlays": [ @@ -3520,7 +3520,7 @@ "source_path": "unfreeze/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "88e413fe5a49a45d46d8867b2d80ace30b3b45aa", - "normalized_render_sha256": "51d6183901e866697382b7e900e2154e8bfb6a9ebf4f3e1bb3ad29925fad0e20", + "normalized_render_sha256": "19cd17084af59cb912f9507bf8ffdc81e38aa1fbef14605a838efbc6521e9534", "target": "skills/debug/references/legacy/unfreeze.md", "disposition": "BUG_FIX", "overlays": [ @@ -3562,7 +3562,7 @@ "source_path": "review/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "ba1ea88068de4b09cf717eb4ae42aa247d198314", - "normalized_render_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "normalized_render_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "target": "skills/review/references/legacy/review.md", "disposition": "BUG_FIX", "overlays": [ @@ -3667,7 +3667,7 @@ "source_path": "cso/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "413fb099597b55dadca116e45a202aa693a94b74", - "normalized_render_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "normalized_render_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "target": "skills/review/references/legacy/cso.md", "disposition": "BUG_FIX", "overlays": [ @@ -3769,7 +3769,7 @@ "source_path": "health/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "f92eb7347ec4f77dd8dbf464d63fcdf6a3459908", - "normalized_render_sha256": "ff664221e7de57e66c1b45d719982d90f38e0288914b34731ba4dd73541347e8", + "normalized_render_sha256": "514ff165b393a25707c9ed336869ac0b50c63a00d90a21b1656276e1b97842d9", "target": "skills/review/references/legacy/health.md", "disposition": "BUG_FIX", "overlays": [ @@ -3842,7 +3842,7 @@ "source_path": "codex/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "333de7d8d590cc78184b0e2371171f6121408f73", - "normalized_render_sha256": "0df6ff685d230f87763b4c43f957e20854fe1319d78247d683e5250e2accb188", + "normalized_render_sha256": "94ebbe955a1063f88712c51723023765519eab8986b029bf1222d0ec19b79e12", "target": "skills/review/references/legacy/codex.md", "disposition": "BUG_FIX", "overlays": [ @@ -4001,7 +4001,7 @@ "source_path": "ship/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "068ac4fe54bcb46572295d48263b11bd38fcde18", - "normalized_render_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "normalized_render_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "target": "skills/ship/references/legacy/ship.md", "disposition": "BUG_FIX", "overlays": [ @@ -4136,7 +4136,7 @@ "source_path": "land-and-deploy/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "98976ad020d541d251cc7e34802a13458ddc88e2", - "normalized_render_sha256": "3fab0678a9a7a76664330c74db2bd25b56c335ac72ce4e691c2f2564a7ede819", + "normalized_render_sha256": "6920f3d97ce474b8f20c8b3e38ca9d3c03973e47af33103a60bab7c02eb867bd", "target": "skills/ship/references/legacy/land-and-deploy.md", "disposition": "BUG_FIX", "overlays": [ @@ -4244,7 +4244,7 @@ "source_path": "landing-report/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "32a8cc1ab036b1ff8692f0400b68d8b56224a251", - "normalized_render_sha256": "4f9512bafcfd995c4c35cdff869ed60f8a9a9ef12f20bc18bf7b898cbd2faa53", + "normalized_render_sha256": "ae5050b0558b5324f58ee9181dc83ac0189b63da70d94f86792c08e112765fe9", "target": "skills/ship/references/legacy/landing-report.md", "disposition": "BUG_FIX", "overlays": [ @@ -4318,7 +4318,7 @@ "source_path": "document-release/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "7621cb31290217b5c7cc8fb000e910b3dd38128f", - "normalized_render_sha256": "e5aa85ac93b36b638d075d152f9d7108c0108727056f61331b25fe33de569a59", + "normalized_render_sha256": "21de7096aa788e3c4206587761f3122016f4cfc3080ec07f07cd7698075307a5", "target": "skills/ship/references/legacy/document-release.md", "disposition": "BUG_FIX", "overlays": [ @@ -4475,7 +4475,7 @@ "source_path": "document-generate/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "d3ef0cbc3786c4010b3c692fb94246f56a7e1d7b", - "normalized_render_sha256": "71726ef3082b6c00a5cad19d28da4d830f8e9b261d213a7938c4e1ce3b8c54bb", + "normalized_render_sha256": "d91adb16f1749c24bc7a05aa6cff3ff00bb0db005872751f417e66de2057e620", "target": "skills/ship/references/legacy/document-generate.md", "disposition": "BUG_FIX", "overlays": [ @@ -4677,7 +4677,7 @@ "source_path": "ios-sync/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "156a33c4c35d3bad804e44c93ae4c931878489f3", - "normalized_render_sha256": "c51e8865876efae833e92d54b37780afd4fa6240c13231dfb7d4635948fbc73f", + "normalized_render_sha256": "88f9f20aa406c13098ebd2709afc6487bb42c5466ac9f757bf4d9baff7f028d0", "target": "skills/ship/references/legacy/ios-sync.md", "disposition": "BUG_FIX", "overlays": [ @@ -5759,7 +5759,7 @@ "owner_tree": "design", "consumer_tree": "plan", "target": "skills/plan/references/legacy/plan-design-review.md", - "sha256": "0c9930db5c50e873318cbbf00b9147edff82a210fdbdf2aacb2a8edfdf1f6463", + "sha256": "5ff3595a7a6e9136d68678e2810001033beb0b92c3f139b1c749e73398ed4b9a", "disposition": "SHARED_MODULE" }, { @@ -5767,7 +5767,7 @@ "owner_tree": "debug", "consumer_tree": "qa", "target": "skills/qa/references/legacy/investigate.md", - "sha256": "abc5ef0411163a0959605f259e54623033f0936942a347e74ed82f13af001a3c", + "sha256": "ebd663fead64cc7d92969742d06bbb18be6f21be7010bba82fc82a27f84efc5e", "disposition": "SHARED_MODULE" }, { @@ -5775,7 +5775,7 @@ "owner_tree": "qa", "consumer_tree": "ship", "target": "skills/ship/references/legacy/canary.md", - "sha256": "21252c073bfa2c90f5ad6883feb8ee63d893febbd981a842ec5f7611b8807f2e", + "sha256": "9fcf4fdea7d52113c8f5b5cc81c1fb36df591cf9e07bcc38c4ee106b15245997", "disposition": "SHARED_MODULE" }, { @@ -5783,7 +5783,7 @@ "owner_tree": "plan", "consumer_tree": "ship", "target": "skills/ship/references/legacy/context-restore.md", - "sha256": "8658c29bfbe2097aa9e3cc4e6b04fd0c15f77fd3c256c238415dffa93e120982", + "sha256": "1b1bd416815b759ea2f60565f0959c129d62e88991118680aab45669e54da82f", "disposition": "SHARED_MODULE" } ], @@ -6494,6 +6494,7 @@ "runtime_helpers": [ { "name": "browse", + "delivery": "repository-payload", "source_path": "browse/dist/browse", "platform_source_paths": { "posix": "browse/dist/browse", @@ -6550,8 +6551,66 @@ ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/browse" }, + { + "name": "bun", + "delivery": "managed-runtime-artifact", + "component": "core", + "build_step": "managed-bun", + "source_path": ".gstack-runtime-tools/bun", + "consumer_modules": [ + "autoplan", + "benchmark", + "benchmark-models", + "browse", + "canary", + "codex", + "context-restore", + "context-save", + "cso", + "design-consultation", + "design-html", + "design-review", + "design-shotgun", + "devex-review", + "diagram", + "document-generate", + "document-release", + "freeze", + "guard", + "health", + "investigate", + "ios-qa", + "ios-sync", + "land-and-deploy", + "landing-report", + "learn", + "make-pdf", + "office-hours", + "open-gstack-browser", + "pair-agent", + "plan-ceo-review", + "plan-design-review", + "plan-devex-review", + "plan-eng-review", + "plan-tune", + "qa", + "qa-only", + "retro", + "review", + "scrape", + "setup-browser-cookies", + "setup-gbrain", + "ship", + "skillify", + "spec", + "sync-gbrain", + "unfreeze" + ], + "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/bun" + }, { "name": "gstack", + "delivery": "repository-payload", "source_path": "bin/gstack", "consumer_modules": [ "diagram", @@ -6564,6 +6623,7 @@ }, { "name": "gstack-artifacts-init", + "delivery": "repository-payload", "source_path": "bin/gstack-artifacts-init", "consumer_modules": [ "setup-gbrain" @@ -6572,6 +6632,7 @@ }, { "name": "gstack-brain-cache", + "delivery": "repository-payload", "source_path": "bin/gstack-brain-cache", "consumer_modules": [ "office-hours", @@ -6584,6 +6645,7 @@ }, { "name": "gstack-codex-probe", + "delivery": "repository-payload", "source_path": "bin/gstack-codex-probe", "consumer_modules": [ "autoplan", @@ -6593,6 +6655,7 @@ }, { "name": "gstack-config", + "delivery": "repository-payload", "source_path": "bin/gstack-config", "consumer_modules": [ "autoplan", @@ -6607,6 +6670,7 @@ }, { "name": "gstack-decision-log", + "delivery": "repository-payload", "source_path": "bin/gstack-decision-log", "consumer_modules": [ "ship", @@ -6616,6 +6680,7 @@ }, { "name": "gstack-design", + "delivery": "repository-payload", "source_path": "design/dist/design", "platform_source_paths": { "posix": "design/dist/design", @@ -6674,6 +6739,7 @@ }, { "name": "gstack-developer-profile", + "delivery": "repository-payload", "source_path": "bin/gstack-developer-profile", "consumer_modules": [ "office-hours", @@ -6683,6 +6749,7 @@ }, { "name": "gstack-diff-scope", + "delivery": "repository-payload", "source_path": "bin/gstack-diff-scope", "consumer_modules": [ "land-and-deploy", @@ -6692,6 +6759,7 @@ }, { "name": "gstack-distill-apply", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-apply", "consumer_modules": [ "plan-tune" @@ -6700,6 +6768,7 @@ }, { "name": "gstack-distill-free-text", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-free-text", "consumer_modules": [ "plan-tune" @@ -6708,6 +6777,7 @@ }, { "name": "gstack-gbrain-detect", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-detect", "consumer_modules": [ "setup-gbrain", @@ -6717,6 +6787,7 @@ }, { "name": "gstack-gbrain-install", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-install", "consumer_modules": [ "setup-gbrain" @@ -6725,6 +6796,7 @@ }, { "name": "gstack-gbrain-lib.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-lib.sh", "consumer_modules": [ "setup-gbrain" @@ -6733,6 +6805,7 @@ }, { "name": "gstack-gbrain-mcp-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-mcp-verify", "consumer_modules": [ "setup-gbrain" @@ -6741,6 +6814,7 @@ }, { "name": "gstack-gbrain-repo-policy", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-repo-policy", "consumer_modules": [ "setup-gbrain" @@ -6749,6 +6823,7 @@ }, { "name": "gstack-gbrain-source-wireup", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-source-wireup", "consumer_modules": [ "setup-gbrain" @@ -6757,6 +6832,7 @@ }, { "name": "gstack-gbrain-supabase-provision", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-provision", "consumer_modules": [ "setup-gbrain" @@ -6765,6 +6841,7 @@ }, { "name": "gstack-gbrain-supabase-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-verify", "consumer_modules": [ "setup-gbrain" @@ -6773,14 +6850,17 @@ }, { "name": "gstack-gbrain-sync", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ - "setup-gbrain" + "setup-gbrain", + "sync-gbrain" ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/gstack-gbrain-sync" }, { "name": "gstack-gbrain-sync.ts", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ "sync-gbrain" @@ -6789,6 +6869,7 @@ }, { "name": "gstack-global-discover", + "delivery": "repository-payload", "source_path": "bin/gstack-global-discover.ts", "consumer_modules": [ "retro" @@ -6797,6 +6878,7 @@ }, { "name": "gstack-learnings-log", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-log", "consumer_modules": [ "cso", @@ -6817,6 +6899,7 @@ }, { "name": "gstack-learnings-search", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-search", "consumer_modules": [ "cso", @@ -6835,6 +6918,7 @@ }, { "name": "gstack-memory-ingest", + "delivery": "repository-payload", "source_path": "bin/gstack-memory-ingest.ts", "consumer_modules": [ "setup-gbrain" @@ -6843,6 +6927,7 @@ }, { "name": "gstack-model-benchmark", + "delivery": "repository-payload", "source_path": "bin/gstack-model-benchmark", "consumer_modules": [ "benchmark-models" @@ -6851,6 +6936,7 @@ }, { "name": "gstack-next-version", + "delivery": "repository-payload", "source_path": "bin/gstack-next-version", "consumer_modules": [ "land-and-deploy", @@ -6862,6 +6948,7 @@ }, { "name": "gstack-paths", + "delivery": "repository-payload", "source_path": "bin/gstack-paths", "consumer_modules": [ "codex", @@ -6879,6 +6966,7 @@ }, { "name": "gstack-pr-title-rewrite.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-pr-title-rewrite.sh", "consumer_modules": [ "ship" @@ -6887,6 +6975,7 @@ }, { "name": "gstack-question-preference", + "delivery": "repository-payload", "source_path": "bin/gstack-question-preference", "consumer_modules": [ "plan-tune" @@ -6895,6 +6984,7 @@ }, { "name": "gstack-redact", + "delivery": "repository-payload", "source_path": "bin/gstack-redact", "consumer_modules": [ "document-generate", @@ -6905,6 +6995,7 @@ }, { "name": "gstack-redact-audit-log", + "delivery": "repository-payload", "source_path": "bin/gstack-redact-audit-log", "consumer_modules": [ "spec" @@ -6913,6 +7004,7 @@ }, { "name": "gstack-review-log", + "delivery": "repository-payload", "source_path": "bin/gstack-review-log", "consumer_modules": [ "autoplan", @@ -6924,6 +7016,7 @@ }, { "name": "gstack-review-read", + "delivery": "repository-payload", "source_path": "bin/gstack-review-read", "consumer_modules": [ "devex-review", @@ -6935,6 +7028,7 @@ }, { "name": "gstack-slug", + "delivery": "repository-payload", "source_path": "bin/gstack-slug", "consumer_modules": [ "autoplan", @@ -6966,6 +7060,7 @@ }, { "name": "gstack-taste-update", + "delivery": "repository-payload", "source_path": "bin/gstack-taste-update", "consumer_modules": [ "design-consultation", @@ -6975,6 +7070,7 @@ }, { "name": "gstack-version-bump", + "delivery": "repository-payload", "source_path": "bin/gstack-version-bump", "consumer_modules": [ "ship" @@ -6983,6 +7079,7 @@ }, { "name": "make-pdf", + "delivery": "repository-payload", "source_path": "make-pdf/dist/pdf", "platform_source_paths": { "posix": "make-pdf/dist/pdf", @@ -7041,6 +7138,7 @@ }, { "name": "remote-slug", + "delivery": "repository-payload", "source_path": "browse/bin/remote-slug", "consumer_modules": [ "autoplan", diff --git a/evals/parity/runtime-helper-closure.json b/evals/parity/runtime-helper-closure.json index 42ca3c579..882bb780f 100644 --- a/evals/parity/runtime-helper-closure.json +++ b/evals/parity/runtime-helper-closure.json @@ -5,6 +5,7 @@ "helpers": [ { "name": "browse", + "delivery": "repository-payload", "source_path": "browse/dist/browse", "platform_source_paths": { "posix": "browse/dist/browse", @@ -61,8 +62,66 @@ ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/browse" }, + { + "name": "bun", + "delivery": "managed-runtime-artifact", + "component": "core", + "build_step": "managed-bun", + "source_path": ".gstack-runtime-tools/bun", + "consumer_modules": [ + "autoplan", + "benchmark", + "benchmark-models", + "browse", + "canary", + "codex", + "context-restore", + "context-save", + "cso", + "design-consultation", + "design-html", + "design-review", + "design-shotgun", + "devex-review", + "diagram", + "document-generate", + "document-release", + "freeze", + "guard", + "health", + "investigate", + "ios-qa", + "ios-sync", + "land-and-deploy", + "landing-report", + "learn", + "make-pdf", + "office-hours", + "open-gstack-browser", + "pair-agent", + "plan-ceo-review", + "plan-design-review", + "plan-devex-review", + "plan-eng-review", + "plan-tune", + "qa", + "qa-only", + "retro", + "review", + "scrape", + "setup-browser-cookies", + "setup-gbrain", + "ship", + "skillify", + "spec", + "sync-gbrain", + "unfreeze" + ], + "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/bun" + }, { "name": "gstack", + "delivery": "repository-payload", "source_path": "bin/gstack", "consumer_modules": [ "diagram", @@ -75,6 +134,7 @@ }, { "name": "gstack-artifacts-init", + "delivery": "repository-payload", "source_path": "bin/gstack-artifacts-init", "consumer_modules": [ "setup-gbrain" @@ -83,6 +143,7 @@ }, { "name": "gstack-brain-cache", + "delivery": "repository-payload", "source_path": "bin/gstack-brain-cache", "consumer_modules": [ "office-hours", @@ -95,6 +156,7 @@ }, { "name": "gstack-codex-probe", + "delivery": "repository-payload", "source_path": "bin/gstack-codex-probe", "consumer_modules": [ "autoplan", @@ -104,6 +166,7 @@ }, { "name": "gstack-config", + "delivery": "repository-payload", "source_path": "bin/gstack-config", "consumer_modules": [ "autoplan", @@ -118,6 +181,7 @@ }, { "name": "gstack-decision-log", + "delivery": "repository-payload", "source_path": "bin/gstack-decision-log", "consumer_modules": [ "ship", @@ -127,6 +191,7 @@ }, { "name": "gstack-design", + "delivery": "repository-payload", "source_path": "design/dist/design", "platform_source_paths": { "posix": "design/dist/design", @@ -185,6 +250,7 @@ }, { "name": "gstack-developer-profile", + "delivery": "repository-payload", "source_path": "bin/gstack-developer-profile", "consumer_modules": [ "office-hours", @@ -194,6 +260,7 @@ }, { "name": "gstack-diff-scope", + "delivery": "repository-payload", "source_path": "bin/gstack-diff-scope", "consumer_modules": [ "land-and-deploy", @@ -203,6 +270,7 @@ }, { "name": "gstack-distill-apply", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-apply", "consumer_modules": [ "plan-tune" @@ -211,6 +279,7 @@ }, { "name": "gstack-distill-free-text", + "delivery": "repository-payload", "source_path": "bin/gstack-distill-free-text", "consumer_modules": [ "plan-tune" @@ -219,6 +288,7 @@ }, { "name": "gstack-gbrain-detect", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-detect", "consumer_modules": [ "setup-gbrain", @@ -228,6 +298,7 @@ }, { "name": "gstack-gbrain-install", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-install", "consumer_modules": [ "setup-gbrain" @@ -236,6 +307,7 @@ }, { "name": "gstack-gbrain-lib.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-lib.sh", "consumer_modules": [ "setup-gbrain" @@ -244,6 +316,7 @@ }, { "name": "gstack-gbrain-mcp-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-mcp-verify", "consumer_modules": [ "setup-gbrain" @@ -252,6 +325,7 @@ }, { "name": "gstack-gbrain-repo-policy", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-repo-policy", "consumer_modules": [ "setup-gbrain" @@ -260,6 +334,7 @@ }, { "name": "gstack-gbrain-source-wireup", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-source-wireup", "consumer_modules": [ "setup-gbrain" @@ -268,6 +343,7 @@ }, { "name": "gstack-gbrain-supabase-provision", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-provision", "consumer_modules": [ "setup-gbrain" @@ -276,6 +352,7 @@ }, { "name": "gstack-gbrain-supabase-verify", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-supabase-verify", "consumer_modules": [ "setup-gbrain" @@ -284,14 +361,17 @@ }, { "name": "gstack-gbrain-sync", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ - "setup-gbrain" + "setup-gbrain", + "sync-gbrain" ], "stable_path": "${GSTACK_HOME:-$HOME/.gstack}/bin/gstack-gbrain-sync" }, { "name": "gstack-gbrain-sync.ts", + "delivery": "repository-payload", "source_path": "bin/gstack-gbrain-sync.ts", "consumer_modules": [ "sync-gbrain" @@ -300,6 +380,7 @@ }, { "name": "gstack-global-discover", + "delivery": "repository-payload", "source_path": "bin/gstack-global-discover.ts", "consumer_modules": [ "retro" @@ -308,6 +389,7 @@ }, { "name": "gstack-learnings-log", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-log", "consumer_modules": [ "cso", @@ -328,6 +410,7 @@ }, { "name": "gstack-learnings-search", + "delivery": "repository-payload", "source_path": "bin/gstack-learnings-search", "consumer_modules": [ "cso", @@ -346,6 +429,7 @@ }, { "name": "gstack-memory-ingest", + "delivery": "repository-payload", "source_path": "bin/gstack-memory-ingest.ts", "consumer_modules": [ "setup-gbrain" @@ -354,6 +438,7 @@ }, { "name": "gstack-model-benchmark", + "delivery": "repository-payload", "source_path": "bin/gstack-model-benchmark", "consumer_modules": [ "benchmark-models" @@ -362,6 +447,7 @@ }, { "name": "gstack-next-version", + "delivery": "repository-payload", "source_path": "bin/gstack-next-version", "consumer_modules": [ "land-and-deploy", @@ -373,6 +459,7 @@ }, { "name": "gstack-paths", + "delivery": "repository-payload", "source_path": "bin/gstack-paths", "consumer_modules": [ "codex", @@ -390,6 +477,7 @@ }, { "name": "gstack-pr-title-rewrite.sh", + "delivery": "repository-payload", "source_path": "bin/gstack-pr-title-rewrite.sh", "consumer_modules": [ "ship" @@ -398,6 +486,7 @@ }, { "name": "gstack-question-preference", + "delivery": "repository-payload", "source_path": "bin/gstack-question-preference", "consumer_modules": [ "plan-tune" @@ -406,6 +495,7 @@ }, { "name": "gstack-redact", + "delivery": "repository-payload", "source_path": "bin/gstack-redact", "consumer_modules": [ "document-generate", @@ -416,6 +506,7 @@ }, { "name": "gstack-redact-audit-log", + "delivery": "repository-payload", "source_path": "bin/gstack-redact-audit-log", "consumer_modules": [ "spec" @@ -424,6 +515,7 @@ }, { "name": "gstack-review-log", + "delivery": "repository-payload", "source_path": "bin/gstack-review-log", "consumer_modules": [ "autoplan", @@ -435,6 +527,7 @@ }, { "name": "gstack-review-read", + "delivery": "repository-payload", "source_path": "bin/gstack-review-read", "consumer_modules": [ "devex-review", @@ -446,6 +539,7 @@ }, { "name": "gstack-slug", + "delivery": "repository-payload", "source_path": "bin/gstack-slug", "consumer_modules": [ "autoplan", @@ -477,6 +571,7 @@ }, { "name": "gstack-taste-update", + "delivery": "repository-payload", "source_path": "bin/gstack-taste-update", "consumer_modules": [ "design-consultation", @@ -486,6 +581,7 @@ }, { "name": "gstack-version-bump", + "delivery": "repository-payload", "source_path": "bin/gstack-version-bump", "consumer_modules": [ "ship" @@ -494,6 +590,7 @@ }, { "name": "make-pdf", + "delivery": "repository-payload", "source_path": "make-pdf/dist/pdf", "platform_source_paths": { "posix": "make-pdf/dist/pdf", @@ -552,6 +649,7 @@ }, { "name": "remote-slug", + "delivery": "repository-payload", "source_path": "browse/bin/remote-slug", "consumer_modules": [ "autoplan", diff --git a/evals/parity/transcripts/deterministic/ceo-review.json b/evals/parity/transcripts/deterministic/ceo-review.json index 738633920..dbb54530c 100644 --- a/evals/parity/transcripts/deterministic/ceo-review.json +++ b/evals/parity/transcripts/deterministic/ceo-review.json @@ -58,15 +58,15 @@ } }, "mechanical_port": { - "rendered_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "rendered_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/plan/references/legacy/plan-ceo-review.md", - "rendered_legacy_body_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "rendered_legacy_body_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "semantic_signature": { - "normalized_sha256": "b2f811568920dfdbd500372df13855041ecb2b8b73e3629f3c71d87c5a78e034", + "normalized_sha256": "811b50b2cf2e20768138c50ef67471402ad1e361bd07958c63107c5081cf21b7", "headings_sha256": "aee8af5dbf376874037f52d130bc6a804d4f27c989afd787b769ef53a9bbc56b", "questions_sha256": "442f02fed4d7a2e9e868795fe12aa5df3fb74a1dd8e538fbc76b8b1907345a81", "obligations_sha256": "305f7bea6815e4aa1d968c20c0823e0f98f34bf475065f0091f918c8eaa5ba85", diff --git a/evals/parity/transcripts/deterministic/code-review.json b/evals/parity/transcripts/deterministic/code-review.json index cb72c88ad..74b41bf7e 100644 --- a/evals/parity/transcripts/deterministic/code-review.json +++ b/evals/parity/transcripts/deterministic/code-review.json @@ -60,15 +60,15 @@ } }, "mechanical_port": { - "rendered_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "rendered_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/review/references/legacy/review.md", - "rendered_legacy_body_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "rendered_legacy_body_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "semantic_signature": { - "normalized_sha256": "9d6398fe0d2900fcce8ae1eb0b390e21f05c94d6ae3a4e21c911025cdc1fd963", + "normalized_sha256": "0afa76278ec061e56a36048eb9034a2cda6e56ea70bba321a0414bc279ddf84b", "headings_sha256": "8ed11544f49943c2974265c86ad258f53207aaed6f289ea2e6afd1239eee26d8", "questions_sha256": "477b52941fada9cd9a3c3863c90310e198fca3dead264006f1431b0fde20f807", "obligations_sha256": "2e21e383f16f209a665d66710ca6d0ebae5a7fea3a200fc23506cfc584ca74b9", diff --git a/evals/parity/transcripts/deterministic/debug.json b/evals/parity/transcripts/deterministic/debug.json index de79d2bae..bf4220d9a 100644 --- a/evals/parity/transcripts/deterministic/debug.json +++ b/evals/parity/transcripts/deterministic/debug.json @@ -53,15 +53,15 @@ } }, "mechanical_port": { - "rendered_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "rendered_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/debug/references/legacy/investigate.md", - "rendered_legacy_body_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "rendered_legacy_body_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "semantic_signature": { - "normalized_sha256": "91f19c8d736a9a6077941f63f52853634325059e5ac4d8808c4ba6dbceca1a30", + "normalized_sha256": "ee817d27e0b303b1e173ab2c572c70f55ef2211225d366a166d2e26cbbe3b2ab", "headings_sha256": "aae491f6430a9d113ece7954490bd9db9d546e55ee5f10f2ac6cd67dc4d4e284", "questions_sha256": "9e058bbca5f57ed0c9c4724b67d640e15872a0ad283d496b8913d2dd6cfe897d", "obligations_sha256": "d832c32c233beb080da54a3ebdc627da9535a158f9772dce4abee15742996a1c", diff --git a/evals/parity/transcripts/deterministic/design-alternatives.json b/evals/parity/transcripts/deterministic/design-alternatives.json index cf0dbe6ac..e634bc6eb 100644 --- a/evals/parity/transcripts/deterministic/design-alternatives.json +++ b/evals/parity/transcripts/deterministic/design-alternatives.json @@ -57,15 +57,15 @@ } }, "mechanical_port": { - "rendered_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "rendered_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/design/references/legacy/design-shotgun.md", - "rendered_legacy_body_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "rendered_legacy_body_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "semantic_signature": { - "normalized_sha256": "1db4cd23ee115ce841d2db898cc442141588635d43018393703aa28ce40d48f2", + "normalized_sha256": "e27c29760f1edffffee08f131b2c0ee76720eb447d3bb05eee4278467d785620", "headings_sha256": "54756bba54ab9183163992eb243290a41b99f896b5993d7f7a0242a5434e0ffe", "questions_sha256": "2492e14286f1029d529a7ecb1fe28cffc94f2f4c922521e73b26a23147a60e29", "obligations_sha256": "8d595f621cb352052f374d49204597142f011694db5953fb826d46c68946e94e", diff --git a/evals/parity/transcripts/deterministic/design-consultation.json b/evals/parity/transcripts/deterministic/design-consultation.json index 787ece308..02c572f2e 100644 --- a/evals/parity/transcripts/deterministic/design-consultation.json +++ b/evals/parity/transcripts/deterministic/design-consultation.json @@ -57,21 +57,21 @@ } }, "mechanical_port": { - "rendered_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "rendered_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/design/references/legacy/design-consultation.md", - "rendered_legacy_body_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "rendered_legacy_body_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "semantic_signature": { - "normalized_sha256": "afa1f2b58d22d9f0f9064336ce7e9836430a298e1deb56f5330390f91df3f680", + "normalized_sha256": "d323457820291635bc4c46e4559ce6f4d194b940607b76208e95df0c86ffcb0b", "headings_sha256": "78cdfd5aa0c0264964542d45190c919c7adb8c01ea68a22d67dd0fad538cc643", "questions_sha256": "91adedef9aa8000a9aa3385381227d149dbc52cbf2d127656bf0697510b62906", - "obligations_sha256": "bca9ced676d3f62fad557554c5268ff0b339d39ab028b1022ddff1a930bd24f7", + "obligations_sha256": "c36974d06c0f66d0a0f2ea3d27282ed785c7cc956cce40a8c8db35492680c972", "heading_count": 12, "question_count": 2, - "obligation_count": 17 + "obligation_count": 18 } }, "deterministic_comparison": { diff --git a/evals/parity/transcripts/deterministic/design-review.json b/evals/parity/transcripts/deterministic/design-review.json index 8ef96a7e4..521141563 100644 --- a/evals/parity/transcripts/deterministic/design-review.json +++ b/evals/parity/transcripts/deterministic/design-review.json @@ -57,21 +57,21 @@ } }, "mechanical_port": { - "rendered_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "rendered_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/design/references/legacy/design-review.md", - "rendered_legacy_body_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "rendered_legacy_body_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "semantic_signature": { - "normalized_sha256": "33584047a11aa46a1b6a2bef5bf97a4d0b443bb6c9542685732e7d4dff795a2e", + "normalized_sha256": "fe15a4fae62fba41432ae18bbf4ef5620058b784b7bf9768304d0d1dd17bf45b", "headings_sha256": "739abb73da8446c6133835f56ea0c862a7c1fd57e6702a6afe1ed1fd2fb2b2c8", "questions_sha256": "57579e50936d62734df12299638bdcf8ce412f24b68688b071be156312aafcbc", - "obligations_sha256": "e7b864611d4940a37f553642dec6bf9ba3c4eec94f0ecbe2cab19699751cacb8", + "obligations_sha256": "e4fe261ecc3956834dfac15cac60fc4b8057ad0c3b37ede06ee4667d61852655", "heading_count": 62, "question_count": 24, - "obligation_count": 62 + "obligation_count": 63 } }, "deterministic_comparison": { diff --git a/evals/parity/transcripts/deterministic/dx-review.json b/evals/parity/transcripts/deterministic/dx-review.json index b7810ae6e..8b380468a 100644 --- a/evals/parity/transcripts/deterministic/dx-review.json +++ b/evals/parity/transcripts/deterministic/dx-review.json @@ -59,15 +59,15 @@ } }, "mechanical_port": { - "rendered_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "rendered_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/plan/references/legacy/plan-devex-review.md", - "rendered_legacy_body_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "rendered_legacy_body_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "semantic_signature": { - "normalized_sha256": "fff989dcab1f37d0c219378caef6b1e627c23c2537a717564be0d8567229bccc", + "normalized_sha256": "2d39acb36fdc7f4f4b3492ff7dd21081fad98b256fa40d00aeee67bd41343f15", "headings_sha256": "7aebb4be8ec2a9ba9034b835bb07a244053a62156eabec252e0d78b3523f248a", "questions_sha256": "5d2d6b27d20c553887edf16ec79a25c0ecab599418052d8e5fc52a172ceb8afe", "obligations_sha256": "44fe82d36ea1aca78b396ef65052cff618b4f444fcb6f9ace283c116d0add332", diff --git a/evals/parity/transcripts/deterministic/engineering-review.json b/evals/parity/transcripts/deterministic/engineering-review.json index d30496e50..bb5e22275 100644 --- a/evals/parity/transcripts/deterministic/engineering-review.json +++ b/evals/parity/transcripts/deterministic/engineering-review.json @@ -58,15 +58,15 @@ } }, "mechanical_port": { - "rendered_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "rendered_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/plan/references/legacy/plan-eng-review.md", - "rendered_legacy_body_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "rendered_legacy_body_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "semantic_signature": { - "normalized_sha256": "ee268f1d24769509b89b7db998372a773d9e7bc376306a68319af4f74c6fe713", + "normalized_sha256": "0ed7d4a6ec9dadf363ef5d9311392067a3d90bddac9c1ec781d0f25db55e13ae", "headings_sha256": "0200c83cdde0bdc56f79527a4e38884bf075c09b1d829a4244e979b324dc52fc", "questions_sha256": "b74758da3f085a457fc0f9cf1b5c34dfe849fd713a5882849da0abb2edc1a73f", "obligations_sha256": "186b9e3bb712898fa6e8544ba2f6fc932a161e9bb388cb7b8f7bdecb782b57f9", diff --git a/evals/parity/transcripts/deterministic/office-hours.json b/evals/parity/transcripts/deterministic/office-hours.json index 46b339d57..4eee6edca 100644 --- a/evals/parity/transcripts/deterministic/office-hours.json +++ b/evals/parity/transcripts/deterministic/office-hours.json @@ -58,21 +58,21 @@ } }, "mechanical_port": { - "rendered_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "rendered_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/plan/references/legacy/office-hours.md", - "rendered_legacy_body_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "rendered_legacy_body_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "semantic_signature": { - "normalized_sha256": "b633a6ef87928ca2434f3a2b1ce88946cbe528a9e3659469e0f9cbbe8a7b146b", + "normalized_sha256": "1a5c9dbda769631df4c3e909fde6b97917780f6a7e9eca5a4edc8c2d0f302052", "headings_sha256": "f50aa767e26cdfc3c8fa4c6bdcabd10e061ca0ab59e2aff4c204eaf68bfd1d56", "questions_sha256": "92d854d47b2f92f30cc34a59cfb6f3e18449beddcb8ebd5490162545bc8384c7", - "obligations_sha256": "ec3076674f71ffad877bfcd6d147fc77c218831ac13329f5c80442e5f2e7e1f0", + "obligations_sha256": "c6f9f220f431b50b7721ac9baa3ae3d7be5de88b489c80a45350028c96a9db84", "heading_count": 34, "question_count": 13, - "obligation_count": 42 + "obligation_count": 43 } }, "deterministic_comparison": { diff --git a/evals/parity/transcripts/deterministic/physical-ios-qa.json b/evals/parity/transcripts/deterministic/physical-ios-qa.json index 78b5f2da4..173b8dd36 100644 --- a/evals/parity/transcripts/deterministic/physical-ios-qa.json +++ b/evals/parity/transcripts/deterministic/physical-ios-qa.json @@ -57,15 +57,15 @@ } }, "mechanical_port": { - "rendered_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "rendered_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/qa/references/legacy/ios-qa.md", - "rendered_legacy_body_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "rendered_legacy_body_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "semantic_signature": { - "normalized_sha256": "a9353a4d837240f85c0e13144e66575f9e267926090cfc0f344a08b5fb09b98c", + "normalized_sha256": "73ea0aabaa9e7c8fdd4ae279f519303a78682540b2340816f97e239718908e62", "headings_sha256": "79304f733fa401249340540c659f15a9da4e7a89a3d505d39437f6bcbcaf4ce5", "questions_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "obligations_sha256": "1d8b9f6b93b844f1a9ad43f1434c7b0a4b891a6e5e0ad8827f41c7eefbef239f", diff --git a/evals/parity/transcripts/deterministic/qa-fix-verify.json b/evals/parity/transcripts/deterministic/qa-fix-verify.json index 1b892219d..bd102a76b 100644 --- a/evals/parity/transcripts/deterministic/qa-fix-verify.json +++ b/evals/parity/transcripts/deterministic/qa-fix-verify.json @@ -57,21 +57,21 @@ } }, "mechanical_port": { - "rendered_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "rendered_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/qa/references/legacy/qa.md", - "rendered_legacy_body_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "rendered_legacy_body_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "semantic_signature": { - "normalized_sha256": "b1387290067842593297c8d64dc578a890a336ce5a8b60085e51b52c55ebaade", + "normalized_sha256": "e7cd5615adaf54413daa97838cb364810317dd7d661cec5cc4ed40eb48192e55", "headings_sha256": "dd0b56f79cee31c4c3c71f32fb0a4438f686c6ae517625a7d59475deecd4c75e", "questions_sha256": "8fece376011d8a8606bcd05d52332f77b56de551b7f1abde94bff1f33ef0a9c1", - "obligations_sha256": "e2f2395744c3e2b66bb69fd4d0acea685ab3a76b86796dc8a8f3e23bc9fb29ec", + "obligations_sha256": "c9a3696553c10b1338568d728f86f47c9a7a06b2defa37bd557c662d4a9086d8", "heading_count": 58, "question_count": 6, - "obligation_count": 64 + "obligation_count": 65 } }, "deterministic_comparison": { diff --git a/evals/parity/transcripts/deterministic/qa-report-only.json b/evals/parity/transcripts/deterministic/qa-report-only.json index 4d324faaf..bb2c224b3 100644 --- a/evals/parity/transcripts/deterministic/qa-report-only.json +++ b/evals/parity/transcripts/deterministic/qa-report-only.json @@ -57,21 +57,21 @@ } }, "mechanical_port": { - "rendered_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "rendered_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/qa/references/legacy/qa-only.md", - "rendered_legacy_body_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "rendered_legacy_body_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "semantic_signature": { - "normalized_sha256": "e360ea826399287e00b030c9aa0bcda16e06ab004f5dcdb14db309bad0ccea25", + "normalized_sha256": "376eff42459f5b8755bd95934cce615db0fca16504c8e82f84b2704c63f62af3", "headings_sha256": "0241f7efa9ffcaef764bd6517099b03394e09f90c8d1f3f015c7c7a135f78201", "questions_sha256": "8fece376011d8a8606bcd05d52332f77b56de551b7f1abde94bff1f33ef0a9c1", - "obligations_sha256": "e876a6573bd7cdd581cbab5dd913c86d78e7dba1532bd9e60b8b324f56fe11d1", + "obligations_sha256": "9ea973707574aa3750e38a2920e65072f804fe7cddf789f1710e08b0a44d92d3", "heading_count": 34, "question_count": 6, - "obligation_count": 39 + "obligation_count": 40 } }, "deterministic_comparison": { diff --git a/evals/parity/transcripts/deterministic/security-review.json b/evals/parity/transcripts/deterministic/security-review.json index f60f84671..9e36d7ca0 100644 --- a/evals/parity/transcripts/deterministic/security-review.json +++ b/evals/parity/transcripts/deterministic/security-review.json @@ -56,15 +56,15 @@ } }, "mechanical_port": { - "rendered_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "rendered_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/review/references/legacy/cso.md", - "rendered_legacy_body_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "rendered_legacy_body_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "semantic_signature": { - "normalized_sha256": "2cecaf39e950a0ec848f85c11f9678b6a7644c0251c07be42cd52d4b2e6b61a6", + "normalized_sha256": "d79cf702cef33e878da8569c63936e3bdf811b194d8abbce925b6bb759f05cf7", "headings_sha256": "45365014bc4ff67be80c8747098a368c439104ea76ad4d1c36000fe65192e291", "questions_sha256": "e7e23a9ccc1e3677812d260b4f38b16ef8c438e0b2346c045c1334b7045006de", "obligations_sha256": "2f83f56fa932b0937d9f968a7e27c974f17772ad8062b9577504dfae600622f6", diff --git a/evals/parity/transcripts/deterministic/ship.json b/evals/parity/transcripts/deterministic/ship.json index 8a9c761ec..4a47be8c4 100644 --- a/evals/parity/transcripts/deterministic/ship.json +++ b/evals/parity/transcripts/deterministic/ship.json @@ -56,15 +56,15 @@ } }, "mechanical_port": { - "rendered_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "rendered_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/ship/references/legacy/ship.md", - "rendered_legacy_body_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "rendered_legacy_body_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "semantic_signature": { - "normalized_sha256": "ab7a7219653b0fc5cdad0bb2959394798bc50ae4db4dc24b76c0454dd1b1397a", + "normalized_sha256": "ca55c56483c524c732cf4cd1a7b7ef36f379666577549ef989823b98bce8c5b5", "headings_sha256": "42c12606506cfd2cfd31dc678c91d63b0212aa4e424730f5b9e257ab033c4958", "questions_sha256": "943ac3bc0b28becb558873810d1884e4311865a10cbd44a2b8c8ae0184fece84", "obligations_sha256": "134233894787ff09edb0506fdede185d24a756fc3278fe2e81790296bd48ca8c", diff --git a/evals/parity/transcripts/deterministic/specification.json b/evals/parity/transcripts/deterministic/specification.json index 9517efa32..9c47b2d08 100644 --- a/evals/parity/transcripts/deterministic/specification.json +++ b/evals/parity/transcripts/deterministic/specification.json @@ -58,15 +58,15 @@ } }, "mechanical_port": { - "rendered_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "rendered_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "differs_from_baseline": true, "allowed_difference": "Canonical GStack 2 carve: exclude the retired shared onboarding wrapper and host hook advisory; resolve retired invocations to six public routes; relocate host/runtime paths; lazy-load pinned carved sections from package-local references." }, "candidate": { "target_path": "skills/plan/references/legacy/spec.md", - "rendered_legacy_body_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "rendered_legacy_body_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "semantic_signature": { - "normalized_sha256": "059ec7a95791f6fc559f4e2321d7340ef5ae7e4ba89949df401f74d91411bfd3", + "normalized_sha256": "76b7c03e6d8e7b852beda0a58409d97153771dd8f7b870bbdda23e996ac23fc1", "headings_sha256": "339ccc4b1c0d870b7c696ef28b1116ee22742a81d7b8713040a2766a3e36c3c3", "questions_sha256": "d98a7e322b838c543487c19d59332501a4393513b5f1a58b2ba4ac6cf911aa67", "obligations_sha256": "7f6a3078d1b84288b26cf37943d7a036d8f7a50d7a1dcea6aefe96c297662a6b", diff --git a/runtime/browser-provider-smoke.mjs b/runtime/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/runtime/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/runtime/doctor.js b/runtime/doctor.js index 65f452e72..1f9601bad 100644 --- a/runtime/doctor.js +++ b/runtime/doctor.js @@ -245,13 +245,11 @@ async function inspectManagedChromium(activeRoot, nodeCommand) { const result = await captureCommand(nodeCommand, [ "--input-type=module", "--eval", - `const { chromium } = await import(${JSON.stringify(moduleUrl)}); process.stdout.write(chromium.executablePath());`, + `const { chromium } = await import(${JSON.stringify(moduleUrl)}); const browser = await chromium.launch({ headless: true }); try { process.stdout.write(browser.version()); } finally { await browser.close(); }`, ], { env: { ...process.env, PLAYWRIGHT_BROWSERS_PATH: browserRoot } }); - const executable = result.stdout.trim(); - const stat = await fs.lstat(executable).catch(() => null); - if (!stat?.isFile() || stat.isSymbolicLink()) return { ok: false, message: "Playwright could not resolve a safe managed Chromium executable" }; - if (process.platform !== "win32") await fs.access(executable, fsConstants.X_OK); - return { ok: true, message: "managed Chromium executable is present", details: { executable } }; + const version = result.stdout.trim(); + if (!version) return { ok: false, message: "managed Chromium launched without reporting a browser version" }; + return { ok: true, message: `managed headless Chromium ${version} launches and exits cleanly`, details: { browserRoot, version } }; } catch (error) { return { ok: false, message: `managed Chromium is not runnable: ${error.message}` }; } diff --git a/runtime/runtime-bootstrap.mjs b/runtime/runtime-bootstrap.mjs index dda8cac07..84370d441 100644 --- a/runtime/runtime-bootstrap.mjs +++ b/runtime/runtime-bootstrap.mjs @@ -7,7 +7,7 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; -import { createReadStream } from "node:fs"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; @@ -104,7 +104,7 @@ export async function main(argv = process.argv.slice(2), options = {}) { const root = path.join(temporary, "merged", "gstack"); await fs.mkdir(root, { recursive: true, mode: 0o700 }); const claimedFiles = new Set(); - if (reusable) await seedReusableRuntime(reusable.root, root, claimedFiles); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); for (const item of plan.downloads) { const archive = path.join(temporary, `${item.component}.tar.gz`); await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); @@ -206,6 +206,100 @@ function sameGraph(actual, expected) { return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); } +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); +} + async function fetchJson(fetch_, url) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); @@ -340,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/scripts/gstack2/browser-provider-contract.ts b/scripts/gstack2/browser-provider-contract.ts new file mode 100644 index 000000000..dca3e9395 --- /dev/null +++ b/scripts/gstack2/browser-provider-contract.ts @@ -0,0 +1,211 @@ +export type BrowserProviderKind = + | 'native-extension' + | 'native-in-app' + | 'native-agent' + | 'native-plugin' + | 'native-mcp' + | 'extension-only' + | 'no-native-automation'; + +export interface BrowserProviderContract { + id: 'claude' | 'codex' | 'gemini' | 'cursor' | 'github-copilot' | 'openclaw' | 'kimi' | 'pi'; + label: string; + kind: BrowserProviderKind; + setup: readonly string[]; + readiness: readonly string[]; + unavailable: string; +} + +export const BROWSER_PROVIDER_CONTRACTS: readonly BrowserProviderContract[] = [ + { + id: 'claude', + label: 'Claude in Chrome', + kind: 'native-extension', + setup: [ + 'Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector.', + 'Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them.', + 'After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness.', + ], + readiness: [ + 'The Claude in Chrome tool surface is visible to the active Claude Code session.', + 'Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error.', + 'The common local readiness journey completes through the Claude browser tools.', + ], + unavailable: 'Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser.', + }, + { + id: 'codex', + label: 'Codex built-in browser', + kind: 'native-in-app', + setup: [ + 'Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state.', + 'Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted.', + 'Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider.', + ], + readiness: [ + 'Active browser discovery returns the in-app browser provider instead of an empty provider list.', + 'A tab can be created or selected after the user opens the browser surface.', + 'The common local readiness journey completes through the Codex browser tools.', + ], + unavailable: 'Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability.', + }, + { + id: 'gemini', + label: 'Gemini CLI browser agent', + kind: 'native-agent', + setup: [ + 'Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog.', + 'Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable.', + 'Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session.', + ], + readiness: [ + 'The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`.', + 'The user has completed Gemini\'s own enablement and one-time consent without GStack acting on their behalf.', + 'The common local readiness journey completes through the Gemini browser agent.', + ], + unavailable: 'Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent.', + }, + { + id: 'cursor', + label: 'Cursor interactive browser provider', + kind: 'native-mcp', + setup: [ + 'Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence.', + 'If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user.', + 'If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools.', + ], + readiness: [ + 'The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer.', + 'The selected browser session does not require silently attaching the user\'s personal profile.', + 'The common local readiness journey completes through the discovered Cursor provider.', + ], + unavailable: 'Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine\'s configured Chrome DevTools MCP into a universal Cursor capability claim.', + }, + { + id: 'github-copilot', + label: 'GitHub Copilot and VS Code integrated browser', + kind: 'native-in-app', + setup: [ + 'Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user\'s active tool selection.', + 'Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them.', + 'Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user\'s explicit action.', + ], + readiness: [ + 'The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction.', + 'An isolated browser page can be opened without silently sharing an existing tab or cookie store.', + 'The common local readiness journey completes through the VS Code integrated browser tools.', + ], + unavailable: 'Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab.', + }, + { + id: 'openclaw', + label: 'OpenClaw browser plugin', + kind: 'native-plugin', + setup: [ + 'Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session.', + 'Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action.', + 'Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it.', + ], + readiness: [ + 'The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks.', + 'Browser doctor/status and tab discovery succeed for the explicitly selected profile.', + 'The common local readiness journey completes through OpenClaw browser actions.', + ], + unavailable: 'Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile.', + }, + { + id: 'kimi', + label: 'Kimi Code', + kind: 'no-native-automation', + setup: [ + 'Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness.', + 'Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session.', + 'Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required.', + ], + readiness: [ + 'Kimi discovers the canonical GStack skills through its standard Agent Skills directories.', + 'Fetch/search-only work may use Kimi host tools when their limitations satisfy the task.', + 'Interactive browser readiness is tested against GStack local browser, not `kimi web`.', + ], + unavailable: 'Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend.', + }, + { + id: 'pi', + label: 'Pi coding agent', + kind: 'extension-only', + setup: [ + 'Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool.', + 'Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer.', + 'Offer GStack local browser when no already-configured interactive provider is callable.', + ], + readiness: [ + 'An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session.', + 'The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment.', + 'The common local readiness journey completes through that active tool.', + ], + unavailable: 'Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically.', + }, +] as const; + +export function renderBrowserProviderContract(): string { + const sections = BROWSER_PROVIDER_CONTRACTS.map((provider) => `## ${provider.label} + +Classification: \`${provider.kind}\` + +Setup: + +${provider.setup.map((step, index) => `${index + 1}. ${step}`).join('\n')} + +Readiness evidence: + +${provider.readiness.map((step) => `- ${step}`).join('\n')} + +If unavailable: ${provider.unavailable}`).join('\n\n'); + + return `# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited \`CODEX_*\`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report \`needs-user-action\`, \`unavailable\`, or \`failed\` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- \`available\`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- \`needs-user-action\`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- \`ready\`: the selected provider passed the common local readiness journey in this session. +- \`unavailable\`: no suitable interactive tool is exposed in the current session. +- \`failed\`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report \`ready\` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run \`./setup\`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +\`node references/support/browser-provider-smoke.mjs\` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading \`GStack browser readiness\`. +3. Click \`Complete readiness check\`. +4. Verify the page status becomes \`READY\`. +5. When supported, confirm the console message \`gstack-browser-readiness:ready\` and the successful \`POST /proof\` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider \`ready\` only after navigation, page reading, and interaction all succeed. The fixture binds to \`127.0.0.1\`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +${sections} + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow \`RUNTIME.md\` for preview and separate install consent, run \`gstack doctor\`, then run the same readiness journey with the \`browse\` launcher. Do not run \`./setup\`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. +`; +} diff --git a/scripts/gstack2/generate-skill-tree.ts b/scripts/gstack2/generate-skill-tree.ts index 30139edb8..90159925e 100644 --- a/scripts/gstack2/generate-skill-tree.ts +++ b/scripts/gstack2/generate-skill-tree.ts @@ -3,6 +3,7 @@ import { createHash } from 'node:crypto'; import * as fs from 'fs'; import * as path from 'path'; import { BUG_FIX_OVERLAYS, overlaysForSource } from './bug-fix-overlays'; +import { renderBrowserProviderContract } from './browser-provider-contract'; import { contractFor, DISPATCHERS, SOURCE_ASSIGNMENTS } from './assignments'; import { SCENARIOS } from './scenarios'; import { runDeterministicSemanticParity } from './semantic-parity'; @@ -493,6 +494,8 @@ function runtimeContract(): string { The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. +Before interactive browser work, read \`references/BROWSER-PROVIDERS.md\` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. + When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. Only after the user approves that metadata check, run the non-mutating preview from this skill root: \`node references/support/runtime-bootstrap.mjs preview --capability \` (repeat \`--capability\` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. @@ -559,12 +562,15 @@ Do not put secrets in run IDs, effect keys, or command arguments. Existing appro function writeSharedContracts(): void { const bootstrap = fs.readFileSync(path.join(ROOT, 'runtime', 'runtime-bootstrap.mjs')); + const browserSmoke = fs.readFileSync(path.join(ROOT, 'runtime', 'browser-provider-smoke.mjs')); for (const tree of TREE_NAMES) { write(path.join(ROOT, 'skills', tree, 'references', 'SHARED-JUDGMENT.md'), sharedJudgmentContract()); write(path.join(ROOT, 'skills', tree, 'references', 'AUTHORITY-POLICY.md'), authorityPolicyContract()); write(path.join(ROOT, 'skills', tree, 'references', 'WEB-CONTEXT.md'), webContextContract()); write(path.join(ROOT, 'skills', tree, 'references', 'RUNTIME.md'), runtimeContract()); + write(path.join(ROOT, 'skills', tree, 'references', 'BROWSER-PROVIDERS.md'), `${GENERATED}\n${renderBrowserProviderContract()}`); write(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-bootstrap.mjs'), bootstrap); + write(path.join(ROOT, 'skills', tree, 'references', 'support', 'browser-provider-smoke.mjs'), browserSmoke); writeJson(path.join(ROOT, 'skills', tree, 'references', 'support', 'runtime-contract.json'), RUNTIME_SKILL_CONTRACT); } write(path.join(ROOT, 'skills', 'qa', 'references', 'SYSTEM-FUNCTIONAL.md'), systemFunctionalContract()); @@ -644,9 +650,20 @@ function runtimeHelperClosure(rendered: Map): Arra }; return [...consumers].sort(([left], [right]) => left.localeCompare(right)).map(([name, sources]) => { const platformPaths = platformSourceOverrides[name]; + const managedRuntimeArtifact = name === 'bun'; return { name, - source_path: platformPaths?.posix ?? sourceOverrides[name] ?? `bin/${name}`, + ...(managedRuntimeArtifact + ? { + delivery: 'managed-runtime-artifact', + component: 'core', + build_step: 'managed-bun', + source_path: '.gstack-runtime-tools/bun', + } + : { + delivery: 'repository-payload', + source_path: platformPaths?.posix ?? sourceOverrides[name] ?? `bin/${name}`, + }), ...(platformPaths ? { platform_source_paths: platformPaths } : {}), consumer_modules: [...sources].sort(), stable_path: `\${GSTACK_HOME:-$HOME/.gstack}/bin/${name}`, diff --git a/scripts/gstack2/run-parity.ts b/scripts/gstack2/run-parity.ts index e353da6c7..4d3f85cc2 100644 --- a/scripts/gstack2/run-parity.ts +++ b/scripts/gstack2/run-parity.ts @@ -12,7 +12,11 @@ import { GSTACK2_BASE_SHA, TREE_NAMES } from './types'; const CONTRACT_KEYS = ['question_order', 'pressure', 'smart_skips', 'stop_approval_gates', 'evidence', 'artifacts', 'mutation', 'exit', 'voice']; const PROVENANCE_KEYS = ['original_source_file', 'original_line_range', 'purpose', 'invocation_conditions', 'modes', 'question_sequence', 'follow_up_behavior', 'smart_skip_rules', 'pushback_rules', 'stop_gates', 'approval_gates', 'rubrics_and_scoring', 'cognitive_frameworks', 'evidence_requirements', 'artifacts_produced', 'mutation_authority', 'exit_states', 'voice', 'response_posture', 'new_location', 'parity_test']; const ALLOWED_DISPOSITIONS = new Set(['VERBATIM_PORT', 'MECHANICAL_PORT', 'JUDGMENT_PRESERVING_CARVE', 'SHARED_MODULE', 'BUG_FIX', 'DUPLICATE_INFRASTRUCTURE', 'REMOVE_WITH_USER_APPROVAL']); -export const EXPECTED_PARITY_CHECKS = 4697; +// Inventory history: the componentized-runtime parity expansion added 152 +// checks to the previously verified 4,681-check corpus. The first update only +// accounted for the 16 lazy-section checks; the remaining 136 cover runtime +// contracts, retired-invocation guards, and generated package closure. +export const EXPECTED_PARITY_CHECKS = 4833; function sha256(value: string | Uint8Array): string { return createHash('sha256').update(value).digest('hex'); @@ -266,9 +270,20 @@ export function runParity(): ParityResult { const helperClosure = json(path.join(ROOT, 'evals', 'parity', 'runtime-helper-closure.json')); check(JSON.stringify(helperClosure.helpers) === JSON.stringify(manifest.runtime_helpers), 'Runtime helper closure and provenance differ'); for (const helper of helperClosure.helpers) { - const sourcePath = helper.platform_source_paths?.[process.platform === 'win32' ? 'win32' : 'posix'] - ?? helper.source_path; - check(fs.existsSync(path.join(ROOT, sourcePath)), `Preserved helper ${helper.name} has no source payload at ${sourcePath}`); + if (helper.delivery === 'managed-runtime-artifact') { + check( + helper.name === 'bun' + && helper.component === 'core' + && helper.build_step === 'managed-bun' + && helper.source_path === '.gstack-runtime-tools/bun' + && helper.stable_path.endsWith('/bin/bun'), + `Managed runtime helper ${helper.name} lacks core-component provenance`, + ); + } else { + const sourcePath = helper.platform_source_paths?.[process.platform === 'win32' ? 'win32' : 'posix'] + ?? helper.source_path; + check(fs.existsSync(path.join(ROOT, sourcePath)), `Preserved helper ${helper.name} has no source payload at ${sourcePath}`); + } check(Array.isArray(helper.consumer_modules) && helper.consumer_modules.length > 0, `Preserved helper ${helper.name} has no consumer provenance`); } for (const record of [...manifest.sources, ...manifest.sections]) { diff --git a/scripts/gstack2/test-install-matrix.ts b/scripts/gstack2/test-install-matrix.ts index 5f3a609ac..fa2977f46 100644 --- a/scripts/gstack2/test-install-matrix.ts +++ b/scripts/gstack2/test-install-matrix.ts @@ -39,6 +39,12 @@ export const AGENT_MATRIX: readonly AgentMatrixEntry[] = [ projectPath: ['.agents', 'skills'], globalPath: ['.agents', 'skills'], }, + { + agent: 'kimi-code-cli', + label: 'Kimi Code CLI', + projectPath: ['.agents', 'skills'], + globalPath: ['.agents', 'skills'], + }, { agent: 'cursor', label: 'Cursor', diff --git a/skills/debug/references/BROWSER-PROVIDERS.md b/skills/debug/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/debug/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/debug/references/RUNTIME.md b/skills/debug/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/debug/references/RUNTIME.md +++ b/skills/debug/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/debug/references/legacy/freeze.md b/skills/debug/references/legacy/freeze.md index e4b16525d..103935bc3 100644 --- a/skills/debug/references/legacy/freeze.md +++ b/skills/debug/references/legacy/freeze.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/debug/references/legacy/guard.md b/skills/debug/references/legacy/guard.md index f9d302d5d..c3f579b8c 100644 --- a/skills/debug/references/legacy/guard.md +++ b/skills/debug/references/legacy/guard.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/debug/references/legacy/investigate.md b/skills/debug/references/legacy/investigate.md index 5c93fbe5f..4bb89c3f1 100644 --- a/skills/debug/references/legacy/investigate.md +++ b/skills/debug/references/legacy/investigate.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/debug/references/legacy/unfreeze.md b/skills/debug/references/legacy/unfreeze.md index 2035cb05a..4d30c8a3d 100644 --- a/skills/debug/references/legacy/unfreeze.md +++ b/skills/debug/references/legacy/unfreeze.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/debug/references/support/browser-provider-smoke.mjs b/skills/debug/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/debug/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/debug/references/support/runtime-bootstrap.mjs b/skills/debug/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/debug/references/support/runtime-bootstrap.mjs +++ b/skills/debug/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/skills/design/references/BROWSER-PROVIDERS.md b/skills/design/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/design/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/design/references/RUNTIME.md b/skills/design/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/design/references/RUNTIME.md +++ b/skills/design/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/design/references/legacy/design-consultation.md b/skills/design/references/legacy/design-consultation.md index 66bd3df8c..71f98752d 100644 --- a/skills/design/references/legacy/design-consultation.md +++ b/skills/design/references/legacy/design-consultation.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -73,26 +74,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. If browse is not available, that's fine — visual research is optional. The skill works without it using WebSearch and your built-in design knowledge. diff --git a/skills/design/references/legacy/design-html.md b/skills/design/references/legacy/design-html.md index cc322c471..2ff8e1b2f 100644 --- a/skills/design/references/legacy/design-html.md +++ b/skills/design/references/legacy/design-html.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -166,26 +167,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. --- diff --git a/skills/design/references/legacy/design-review.md b/skills/design/references/legacy/design-review.md index 76de9688b..6cbedc465 100644 --- a/skills/design/references/legacy/design-review.md +++ b/skills/design/references/legacy/design-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -80,26 +81,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. **Check test framework (bootstrap if needed):** diff --git a/skills/design/references/legacy/design-shotgun.md b/skills/design/references/legacy/design-shotgun.md index c4131402b..fd1dc9568 100644 --- a/skills/design/references/legacy/design-shotgun.md +++ b/skills/design/references/legacy/design-shotgun.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/design/references/legacy/diagram.md b/skills/design/references/legacy/diagram.md index ea1a3ab6f..24bfc635f 100644 --- a/skills/design/references/legacy/diagram.md +++ b/skills/design/references/legacy/diagram.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/design/references/legacy/make-pdf.md b/skills/design/references/legacy/make-pdf.md index c02457b78..b92e74e7e 100644 --- a/skills/design/references/legacy/make-pdf.md +++ b/skills/design/references/legacy/make-pdf.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/design/references/legacy/plan-design-review.md b/skills/design/references/legacy/plan-design-review.md index ab40aa8c8..3d448f5d6 100644 --- a/skills/design/references/legacy/plan-design-review.md +++ b/skills/design/references/legacy/plan-design-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/design/references/support/browser-provider-smoke.mjs b/skills/design/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/design/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/design/references/support/runtime-bootstrap.mjs b/skills/design/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/design/references/support/runtime-bootstrap.mjs +++ b/skills/design/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/skills/plan/references/BROWSER-PROVIDERS.md b/skills/plan/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/plan/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/plan/references/RUNTIME.md b/skills/plan/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/plan/references/RUNTIME.md +++ b/skills/plan/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/plan/references/legacy/autoplan.md b/skills/plan/references/legacy/autoplan.md index 0402d1cb0..d99331bc2 100644 --- a/skills/plan/references/legacy/autoplan.md +++ b/skills/plan/references/legacy/autoplan.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/context-restore.md b/skills/plan/references/legacy/context-restore.md index 6dcacf534..3eb0e8c9a 100644 --- a/skills/plan/references/legacy/context-restore.md +++ b/skills/plan/references/legacy/context-restore.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/context-save.md b/skills/plan/references/legacy/context-save.md index 211e1e261..d4d27baca 100644 --- a/skills/plan/references/legacy/context-save.md +++ b/skills/plan/references/legacy/context-save.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/learn.md b/skills/plan/references/legacy/learn.md index b78348ad9..1a04ee089 100644 --- a/skills/plan/references/legacy/learn.md +++ b/skills/plan/references/legacy/learn.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/office-hours.md b/skills/plan/references/legacy/office-hours.md index 159c81f51..3b4d5fedc 100644 --- a/skills/plan/references/legacy/office-hours.md +++ b/skills/plan/references/legacy/office-hours.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -31,26 +32,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. # YC Office Hours diff --git a/skills/plan/references/legacy/plan-ceo-review.md b/skills/plan/references/legacy/plan-ceo-review.md index 4af309c78..6be012379 100644 --- a/skills/plan/references/legacy/plan-ceo-review.md +++ b/skills/plan/references/legacy/plan-ceo-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/plan-design-review.md b/skills/plan/references/legacy/plan-design-review.md index ab40aa8c8..3d448f5d6 100644 --- a/skills/plan/references/legacy/plan-design-review.md +++ b/skills/plan/references/legacy/plan-design-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/plan-devex-review.md b/skills/plan/references/legacy/plan-devex-review.md index ddb1e3ace..0ab57a6df 100644 --- a/skills/plan/references/legacy/plan-devex-review.md +++ b/skills/plan/references/legacy/plan-devex-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/plan-eng-review.md b/skills/plan/references/legacy/plan-eng-review.md index 22383142a..54506a93b 100644 --- a/skills/plan/references/legacy/plan-eng-review.md +++ b/skills/plan/references/legacy/plan-eng-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/plan-tune.md b/skills/plan/references/legacy/plan-tune.md index cb9a91349..605bcb14a 100644 --- a/skills/plan/references/legacy/plan-tune.md +++ b/skills/plan/references/legacy/plan-tune.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/retro.md b/skills/plan/references/legacy/retro.md index a1b74d8b0..8e7214275 100644 --- a/skills/plan/references/legacy/retro.md +++ b/skills/plan/references/legacy/retro.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -696,7 +697,7 @@ DISCOVER_BIN="" [ -x $GSTACK_BIN/gstack-global-discover ] && DISCOVER_BIN=$GSTACK_BIN/gstack-global-discover [ -z "$DISCOVER_BIN" ] && [ -x "$GSTACK_BIN/gstack-global-discover" ] && DISCOVER_BIN="$GSTACK_BIN/gstack-global-discover" [ -z "$DISCOVER_BIN" ] && which gstack-global-discover >/dev/null 2>&1 && DISCOVER_BIN=$(which gstack-global-discover) -[ -z "$DISCOVER_BIN" ] && [ -f $GSTACK_BIN/gstack-global-discover ] && DISCOVER_BIN="bun run $GSTACK_BIN/gstack-global-discover" +[ -z "$DISCOVER_BIN" ] && [ -f $GSTACK_BIN/gstack-global-discover ] && DISCOVER_BIN="$GSTACK_BIN/gstack-global-discover" echo "DISCOVER_BIN: $DISCOVER_BIN" ``` diff --git a/skills/plan/references/legacy/setup-gbrain.md b/skills/plan/references/legacy/setup-gbrain.md index 5f326737b..9ce3c496a 100644 --- a/skills/plan/references/legacy/setup-gbrain.md +++ b/skills/plan/references/legacy/setup-gbrain.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/plan/references/legacy/spec.md b/skills/plan/references/legacy/spec.md index 9272d8cf4..076210d94 100644 --- a/skills/plan/references/legacy/spec.md +++ b/skills/plan/references/legacy/spec.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -209,7 +210,6 @@ Scan-at-sink on the EXACT bytes that will be sent: write to a temp file, scan th file, pass the SAME file downstream. Never scan a string then re-render it. ```bash -command -v bun >/dev/null 2>&1 || echo "redaction scan skipped — bun not on PATH" # Resolve visibility once; cache + reuse. Order: local config ("${GSTACK_HOME:-$HOME/.gstack}", never # committed) → gh → glab → unknown(=public-strict). REDACT_VIS=$($GSTACK_BIN/gstack-config get redact_repo_visibility 2>/dev/null) diff --git a/skills/plan/references/legacy/sync-gbrain.md b/skills/plan/references/legacy/sync-gbrain.md index c4b89e831..e5da50d72 100644 --- a/skills/plan/references/legacy/sync-gbrain.md +++ b/skills/plan/references/legacy/sync-gbrain.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -170,7 +171,7 @@ Pass user args to the orchestrator. Do not paraphrase them — pass through as-is. ```bash -bun run $GSTACK_BIN/gstack-gbrain-sync.ts +$GSTACK_BIN/gstack-gbrain-sync ``` The orchestrator runs three stages: code → memory → brain-sync (per the diff --git a/skills/plan/references/support/browser-provider-smoke.mjs b/skills/plan/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/plan/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/plan/references/support/runtime-bootstrap.mjs b/skills/plan/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/plan/references/support/runtime-bootstrap.mjs +++ b/skills/plan/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/skills/qa/references/BROWSER-PROVIDERS.md b/skills/qa/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/qa/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/qa/references/RUNTIME.md b/skills/qa/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/qa/references/RUNTIME.md +++ b/skills/qa/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/qa/references/legacy/benchmark-models.md b/skills/qa/references/legacy/benchmark-models.md index 012457b29..13cdeaf6f 100644 --- a/skills/qa/references/legacy/benchmark-models.md +++ b/skills/qa/references/legacy/benchmark-models.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/qa/references/legacy/benchmark.md b/skills/qa/references/legacy/benchmark.md index edf1dc46a..d2ff2e499 100644 --- a/skills/qa/references/legacy/benchmark.md +++ b/skills/qa/references/legacy/benchmark.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -31,26 +32,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. # $qa --mode Report --module benchmark — Performance Regression Detection diff --git a/skills/qa/references/legacy/browse.md b/skills/qa/references/legacy/browse.md index 566001e24..f4c7eadc7 100644 --- a/skills/qa/references/legacy/browse.md +++ b/skills/qa/references/legacy/browse.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -36,26 +37,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Core QA Patterns diff --git a/skills/qa/references/legacy/canary.md b/skills/qa/references/legacy/canary.md index 6fd411a8f..2d649c96b 100644 --- a/skills/qa/references/legacy/canary.md +++ b/skills/qa/references/legacy/canary.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -31,26 +32,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Step 0: Detect platform and base branch diff --git a/skills/qa/references/legacy/devex-review.md b/skills/qa/references/legacy/devex-review.md index 466e73c91..0a0c74d33 100644 --- a/skills/qa/references/legacy/devex-review.md +++ b/skills/qa/references/legacy/devex-review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -70,26 +71,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. # $qa --mode Report --module devex-review: Live Developer Experience Audit diff --git a/skills/qa/references/legacy/investigate.md b/skills/qa/references/legacy/investigate.md index 5c93fbe5f..4bb89c3f1 100644 --- a/skills/qa/references/legacy/investigate.md +++ b/skills/qa/references/legacy/investigate.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/qa/references/legacy/ios-qa.md b/skills/qa/references/legacy/ios-qa.md index 5e7a5788e..2041d0964 100644 --- a/skills/qa/references/legacy/ios-qa.md +++ b/skills/qa/references/legacy/ios-qa.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/qa/references/legacy/open-gstack-browser.md b/skills/qa/references/legacy/open-gstack-browser.md index da5702f89..1b27b2734 100644 --- a/skills/qa/references/legacy/open-gstack-browser.md +++ b/skills/qa/references/legacy/open-gstack-browser.md @@ -1,5 +1,5 @@ - + @@ -12,10 +12,17 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" ``` +## Visible-browser point-of-use gate + +This workflow may require internal `browser-visible` because it reaches a headed browser, extension, interactive cookie picker, or browser handoff. Do not offer visible Chromium during ordinary headless QA. + +At the first actual visible-browser step, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub signed-manifest request and sends no repository/private data, then STOP. Only after that approval run `node references/support/runtime-bootstrap.mjs preview --capability browser-visible`. It expands to `core + browser-code + browser-visible` for a first install, but an existing verified headless runtime downloads only missing `browser-visible`; it never requires `browser-headless`. Show the exact missing components and summed incremental compressed bytes, then STOP again for separate install approval. Only after install approval run `node references/support/runtime-bootstrap.mjs install --capability browser-visible --yes`, recheck readiness, and resume the interrupted step. + # $qa --mode Report --module open-gstack-browser — Launch GStack Browser Launch GStack Browser — AI-controlled Chromium with the sidebar extension, @@ -36,26 +43,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Step 0: Pre-flight cleanup diff --git a/skills/qa/references/legacy/pair-agent.md b/skills/qa/references/legacy/pair-agent.md index 12397db86..f6ee806a8 100644 --- a/skills/qa/references/legacy/pair-agent.md +++ b/skills/qa/references/legacy/pair-agent.md @@ -1,5 +1,5 @@ - + @@ -12,10 +12,17 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" ``` +## Visible-browser point-of-use gate + +This workflow may require internal `browser-visible` because it reaches a headed browser, extension, interactive cookie picker, or browser handoff. Do not offer visible Chromium during ordinary headless QA. + +At the first actual visible-browser step, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub signed-manifest request and sends no repository/private data, then STOP. Only after that approval run `node references/support/runtime-bootstrap.mjs preview --capability browser-visible`. It expands to `core + browser-code + browser-visible` for a first install, but an existing verified headless runtime downloads only missing `browser-visible`; it never requires `browser-headless`. Show the exact missing components and summed incremental compressed bytes, then STOP again for separate install approval. Only after install approval run `node references/support/runtime-bootstrap.mjs install --capability browser-visible --yes`, recheck readiness, and resume the interrupted step. + # $qa --mode Report --module pair-agent — Share Your Browser With Another AI Agent You're sitting in Claude Code with a browser running. You also have another AI agent @@ -54,26 +61,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Step 1: Check prerequisites diff --git a/skills/qa/references/legacy/qa-only.md b/skills/qa/references/legacy/qa-only.md index 4011eb5e4..c42cebe4a 100644 --- a/skills/qa/references/legacy/qa-only.md +++ b/skills/qa/references/legacy/qa-only.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -51,26 +52,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. **Create output directories:** diff --git a/skills/qa/references/legacy/qa.md b/skills/qa/references/legacy/qa.md index c22afcd48..84da354dc 100644 --- a/skills/qa/references/legacy/qa.md +++ b/skills/qa/references/legacy/qa.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -122,26 +123,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. **Check test framework (bootstrap if needed):** diff --git a/skills/qa/references/legacy/scrape.md b/skills/qa/references/legacy/scrape.md index 714e9c870..c8bb0a40b 100644 --- a/skills/qa/references/legacy/scrape.md +++ b/skills/qa/references/legacy/scrape.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/qa/references/legacy/setup-browser-cookies.md b/skills/qa/references/legacy/setup-browser-cookies.md index 5bb63f07b..0407f317f 100644 --- a/skills/qa/references/legacy/setup-browser-cookies.md +++ b/skills/qa/references/legacy/setup-browser-cookies.md @@ -1,5 +1,5 @@ - + @@ -12,10 +12,17 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" ``` +## Visible-browser point-of-use gate + +This workflow may require internal `browser-visible` because it reaches a headed browser, extension, interactive cookie picker, or browser handoff. Do not offer visible Chromium during ordinary headless QA. + +At the first actual visible-browser step, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub signed-manifest request and sends no repository/private data, then STOP. Only after that approval run `node references/support/runtime-bootstrap.mjs preview --capability browser-visible`. It expands to `core + browser-code + browser-visible` for a first install, but an existing verified headless runtime downloads only missing `browser-visible`; it never requires `browser-headless`. Show the exact missing components and summed incremental compressed bytes, then STOP again for separate install approval. Only after install approval run `node references/support/runtime-bootstrap.mjs install --capability browser-visible --yes`, recheck readiness, and resume the interrupted step. + # Setup Browser Cookies Import logged-in sessions from your real Chromium browser into the headless browse session. @@ -54,26 +61,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ### 2. Open the cookie picker diff --git a/skills/qa/references/legacy/skillify.md b/skills/qa/references/legacy/skillify.md index ce7c0087a..315f0d0fd 100644 --- a/skills/qa/references/legacy/skillify.md +++ b/skills/qa/references/legacy/skillify.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/qa/references/support/browser-provider-smoke.mjs b/skills/qa/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/qa/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/qa/references/support/runtime-bootstrap.mjs b/skills/qa/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/qa/references/support/runtime-bootstrap.mjs +++ b/skills/qa/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/skills/review/references/BROWSER-PROVIDERS.md b/skills/review/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/review/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/review/references/RUNTIME.md b/skills/review/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/review/references/RUNTIME.md +++ b/skills/review/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/review/references/legacy/codex.md b/skills/review/references/legacy/codex.md index 431fb9457..a939b74a3 100644 --- a/skills/review/references/legacy/codex.md +++ b/skills/review/references/legacy/codex.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/review/references/legacy/cso.md b/skills/review/references/legacy/cso.md index afe081e12..5d7b8540b 100644 --- a/skills/review/references/legacy/cso.md +++ b/skills/review/references/legacy/cso.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/review/references/legacy/health.md b/skills/review/references/legacy/health.md index 3fbabfeb2..43aed4df2 100644 --- a/skills/review/references/legacy/health.md +++ b/skills/review/references/legacy/health.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/review/references/legacy/review.md b/skills/review/references/legacy/review.md index 83a9f0ed8..81ab399d0 100644 --- a/skills/review/references/legacy/review.md +++ b/skills/review/references/legacy/review.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -349,7 +350,7 @@ Check whether this PR's claimed VERSION still points at a free slot in the queue BRANCH_VERSION=$(git show HEAD:VERSION 2>/dev/null | tr -d '\r\n[:space:]' || echo "") BASE_BRANCH=$(gh pr view --json baseRefName -q .baseRefName 2>/dev/null || echo main) BASE_VERSION=$(git show origin/$BASE_BRANCH:VERSION 2>/dev/null | tr -d '\r\n[:space:]' || echo "") -QUEUE_JSON=$(bun run $GSTACK_BIN/gstack-next-version \ +QUEUE_JSON=$($GSTACK_BIN/gstack-next-version \ --base "$BASE_BRANCH" \ --bump patch \ --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}') diff --git a/skills/review/references/support/browser-provider-smoke.mjs b/skills/review/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/review/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/review/references/support/runtime-bootstrap.mjs b/skills/review/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/review/references/support/runtime-bootstrap.mjs +++ b/skills/review/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/skills/ship/references/BROWSER-PROVIDERS.md b/skills/ship/references/BROWSER-PROVIDERS.md new file mode 100644 index 000000000..9f0e998c2 --- /dev/null +++ b/skills/ship/references/BROWSER-PROVIDERS.md @@ -0,0 +1,188 @@ + +# Browser provider setup and readiness + +Browser setup is optional and consented. The Agent Skills installer owns skill placement; this flow never enrolls another host. At onboarding, offer this flow only when the user asks to configure browser capabilities now. Otherwise run it just in time when a selected workflow first needs interactive browser evidence. + +## Routing flow + +1. Identify the active host and provider from callable tools in the current session, not from unrelated installed binaries, parent-application environment variables, documentation, or guessed host names. Inherited `CODEX_*`, bundle identifiers, and similar process metadata never override the actual agent/tool surface. +2. Show the detected provider, what user-controlled setup it requires, and the GStack local-browser fallback. +3. Ask whether to use the host-native provider, set up GStack local browser, continue without browser evidence, or defer. A selection is not permission to install another product or attach private browser state. +4. For a host-native selection, follow the matching provider section below. Never install an extension, grant site access, sign in, add an MCP server, attach an existing profile, or change host settings without the user's explicit action. +5. After setup, run the common readiness journey. Tool names or metadata alone are insufficient evidence. +6. If the journey fails, report `needs-user-action`, `unavailable`, or `failed` with the exact observed cause and offer the local GStack browser. Do not silently fall back. + +## Provider states + +- `available`: the current session exposes callable navigation, reading, and interaction tools, but readiness has not yet been proven. +- `needs-user-action`: the host has a documented provider, but the user must enable, open, approve, or connect it. +- `ready`: the selected provider passed the common local readiness journey in this session. +- `unavailable`: no suitable interactive tool is exposed in the current session. +- `failed`: a callable provider attempted the readiness journey and failed; include the exact failing step. + +Never report `ready` merely because a browser binary, CLI, plugin, extension, MCP name, settings entry, or documentation exists. Present one provider decision, then continue through the selected path. Do not run `./setup`; it is not a GStack 2 browser setup command. + +## Common local readiness journey + +Start the dependency-free fixture from this skill root with: + +`node references/support/browser-provider-smoke.mjs` + +The process prints one JSON line containing a loopback URL and remains alive. Through the selected browser provider: + +1. Open that exact URL. +2. Verify the heading `GStack browser readiness`. +3. Click `Complete readiness check`. +4. Verify the page status becomes `READY`. +5. When supported, confirm the console message `gstack-browser-readiness:ready` and the successful `POST /proof` request. +6. Stop the fixture process and confirm it releases its listener. + +Mark the provider `ready` only after navigation, page reading, and interaction all succeed. The fixture binds to `127.0.0.1`, uses a per-run random token, sends no repository data, and does not persist cookies or credentials. + +## Claude in Chrome + +Classification: `native-extension` + +Setup: + +1. Explain that Claude in Chrome requires Google Chrome, a paid Claude plan, the user-installed extension, the same signed-in Claude account, granted site permissions, and an enabled connector. +2. Ask whether the user wants to configure it now. Never install the extension, grant permissions, enable a connector, sign in, or attach a Chrome profile for them. +3. After the user completes setup, retry the active host tab/context discovery rather than treating the presence of the Claude CLI as browser readiness. + +Readiness evidence: + +- The Claude in Chrome tool surface is visible to the active Claude Code session. +- Tab/context discovery returns an attached browser peer instead of a missing-extension or no-peer error. +- The common local readiness journey completes through the Claude browser tools. + +If unavailable: Offer GStack local browser or continue without browser evidence; do not substitute a different extension or remote browser. + +## Codex built-in browser + +Classification: `native-in-app` + +Setup: + +1. Explain that the built-in browser is a ChatGPT desktop-app surface on macOS and Windows and uses its own browser state. +2. Ask the user to open it from the Work/Codex toolbar (Command-Shift-B on macOS or Control-Shift-B on Windows) and approve the test site when prompted. +3. Do not infer readiness from the Codex CLI or skill installation; the active session must expose an in-app browser provider. + +Readiness evidence: + +- Active browser discovery returns the in-app browser provider instead of an empty provider list. +- A tab can be created or selected after the user opens the browser surface. +- The common local readiness journey completes through the Codex browser tools. + +If unavailable: Offer GStack local browser or defer browser work; do not claim that restarting a browser-less session will create the missing host capability. + +## Gemini CLI browser agent + +Classification: `native-agent` + +Setup: + +1. Explain that Gemini CLI has an experimental bundled `browser_agent`, disabled by default, which requires a recent local Chrome and displays a first-run consent dialog. +2. Ask whether the user wants to enable it themselves. Never edit Gemini settings, accept its consent dialog, attach an existing Chrome session, or infer readiness from the Gemini executable. +3. Prefer Gemini isolated browser mode for public or localhost QA unless the user explicitly needs and approves an existing signed-in browser session. + +Readiness evidence: + +- The active Gemini session exposes the callable `browser_agent` tool rather than only `google_web_search` or `web_fetch`. +- The user has completed Gemini's own enablement and one-time consent without GStack acting on their behalf. +- The common local readiness journey completes through the Gemini browser agent. + +If unavailable: Offer the consented GStack local browser, evidence-limited fetch/search, or deferral; do not install a browser MCP because Gemini already bundles its experimental browser agent. + +## Cursor interactive browser provider + +Classification: `native-mcp` + +Setup: + +1. Inspect the active Cursor tool surface for an interactive provider such as Chrome DevTools; Cursor CLI presence and parent-process environment variables are not provider evidence. +2. If an interactive provider is exposed, explain its current session/profile boundary and ask whether to use it. Never add or approve an MCP server, extension, or browser profile for the user. +3. If no interactive provider is exposed in this Cursor session, offer GStack local browser rather than assuming every Cursor installation has the same browser tools. + +Readiness evidence: + +- The active Cursor session exposes callable navigation, page-reading, and interaction tools and returns a live browser peer. +- The selected browser session does not require silently attaching the user's personal profile. +- The common local readiness journey completes through the discovered Cursor provider. + +If unavailable: Offer GStack local browser, evidence-limited web access, or deferral; do not turn one machine's configured Chrome DevTools MCP into a universal Cursor capability claim. + +## GitHub Copilot and VS Code integrated browser + +Classification: `native-in-app` + +Setup: + +1. Explain that current VS Code can expose built-in browser agent tools, subject to the `workbench.browser.enableChatTools` organization setting and the user's active tool selection. +2. Ask the user to enable the Built-in > Browser tools in the active agent session when they want to use them. Never change VS Code or organization settings for them. +3. Prefer an agent-created isolated browser page; sharing an existing browser page or its signed-in state requires the user's explicit action. + +Readiness evidence: + +- The active Copilot/VS Code agent session exposes callable browser tools such as page navigation, reading, screenshot, and interaction. +- An isolated browser page can be opened without silently sharing an existing tab or cookie store. +- The common local readiness journey completes through the VS Code integrated browser tools. + +If unavailable: Offer GStack local browser or defer; distinguish organization-policy disablement from a transient missing browser tab. + +## OpenClaw browser plugin + +Classification: `native-plugin` + +Setup: + +1. Explain that OpenClaw includes a browser plugin with a dedicated managed profile, while its `user` profile can attach to an existing browser session. +2. Inspect the active OpenClaw tool policy for the callable browser tool. Never edit `plugins.allow`, enable the plugin, or attach the `user` profile without explicit user action. +3. Prefer the isolated managed profile for public and localhost QA; use an existing signed-in profile only when the user explicitly selects it. + +Readiness evidence: + +- The active OpenClaw agent exposes the browser tool after its plugin and tool-policy checks. +- Browser doctor/status and tab discovery succeed for the explicitly selected profile. +- The common local readiness journey completes through OpenClaw browser actions. + +If unavailable: Offer GStack local browser or defer; do not silently repair OpenClaw plugin policy or attach a personal browser profile. + +## Kimi Code + +Classification: `no-native-automation` + +Setup: + +1. Explain that Kimi Code loads standard Agent Skills and provides WebSearch/FetchURL when configured, but does not currently document a native interactive browser-automation harness. +2. Do not describe `kimi web` as browser automation: it is the browser-based user interface for the Kimi session. +3. Offer GStack local browser when interactive navigation, clicking, screenshots, console, or network evidence is required. + +Readiness evidence: + +- Kimi discovers the canonical GStack skills through its standard Agent Skills directories. +- Fetch/search-only work may use Kimi host tools when their limitations satisfy the task. +- Interactive browser readiness is tested against GStack local browser, not `kimi web`. + +If unavailable: Continue with fetch/search-only evidence or offer the consented GStack local browser; never silently add a browser MCP or alternate backend. + +## Pi coding agent + +Classification: `extension-only` + +Setup: + +1. Explain that Pi intentionally has no core interactive browser tool; browser automation is available only through optional third-party packages or an already exposed host tool. +2. Inspect the active Pi tool surface. Never install a Pi package, run its browser installer, or treat a package listing as a live browser peer. +3. Offer GStack local browser when no already-configured interactive provider is callable. + +Readiness evidence: + +- An explicitly user-installed Pi browser extension exposes a callable browser tool in the active session. +- The extension returns a live isolated session without triggering an unapproved Chromium download or profile attachment. +- The common local readiness journey completes through that active tool. + +If unavailable: Offer GStack local browser, evidence-limited continuation, or deferral; never recommend or install a third-party Pi browser package automatically. + +## GStack local browser fallback + +GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend. Follow `RUNTIME.md` for preview and separate install consent, run `gstack doctor`, then run the same readiness journey with the `browse` launcher. Do not run `./setup`, add a cloud browser, remote provider, alternate local backend, or personal-profile attachment as a fallback. diff --git a/skills/ship/references/RUNTIME.md b/skills/ship/references/RUNTIME.md index 5d66a7733..6ce746ba0 100644 --- a/skills/ship/references/RUNTIME.md +++ b/skills/ship/references/RUNTIME.md @@ -3,14 +3,22 @@ The six Agent Skills are useful without a GStack runtime. Never install, download, build, select, update, or remove runtime capabilities merely because a skill was invoked. -When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Installation requires the user's explicit approval after disclosing the selected capabilities, that an official versioned release artifact will be downloaded and SHA-256 verified, and any exact byte/build information available from the bootstrap manifest. Never call the bootstrap before approval. +Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full. It owns provider detection, consented host setup, the common readiness journey, and the explicit GStack fallback. Skill installation never proves browser readiness. -Capability names are `browser`, `design`, `pdf`, `diagram`, and `ios`; the core state/runtime CLI is managed separately. After approval, from this skill root run `node references/support/runtime-bootstrap.mjs install --capability ` (repeat `--capability` for additional approved capabilities). The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates the fixed manifest schema/version/skill API/platform artifact and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. +When an active specialist first reaches a capability it cannot use, name the exact capability and why it is needed. Offer to continue without it when the judgment-only or host-native path remains valid. Before any network preview, ask whether the user wants to check official setup options and exact sizes. Disclose that an uncached preview makes one public GitHub request for signed manifest metadata and sends no repository content, private URL, file, cookie, token, or credential; then STOP. A cached already-verified manifest may preview offline, but never silently fetch. -Resolve capability dependencies before preview and approval: `diagram` implies `browser`; `pdf` implies both `diagram` and `browser` transitively. `design` and `ios` are standalone. The disclosure and approval must name the complete expanded set, not only the capability first requested. +Only after the user approves that metadata check, run the non-mutating preview from this skill root: `node references/support/runtime-bootstrap.mjs preview --capability ` (repeat `--capability` for additional requested capabilities). It dependency-expands, reports already verified local components, exact missing components, and their summed compressed bytes. It never downloads components or mutates runtime state. Preview consent is not install consent. -After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. +User-facing setup capabilities are exactly `browser`, `design`, `diagram`, `pdf`, and `ios`. `all` means those five and intentionally excludes visible Chromium. The internal `browser-visible` capability is additive and is offered only when a workflow actually reaches a headed browser, extension, or browser-handoff step. Never offer it during ordinary headless QA. + +After showing the complete preview, STOP for explicit approval. Only after approval run `node references/support/runtime-bootstrap.mjs install --capability --yes`; install must reprint the identical dependency-closed plan before downloading. Signed internal components are `core`, `browser-code` (browse code and dependencies), `browser-headless` (Playwright headless shell and FFmpeg), `browser-visible` (full Chromium), `design`, `diagram`, `pdf`, and `ios`. Logical `browser` expands to `browser-code + browser-headless`; internal `browser-visible` expands to `browser-code + browser-visible` and does not require headless. Component dependencies are `browser-code → core`, `browser-headless → browser-code`, and `browser-visible → browser-code`. `diagram` depends on logical `browser`; `pdf` depends on `diagram`; `ios` is Darwin-only. Therefore a first-time headed flow previews `core + browser-code + browser-visible`, while an existing verified headless runtime downloads only missing `browser-visible`. The manifest schema is v2 with global `capabilityComponents` and `componentDependencies`, plus `targets[target].components[id]` carrying signed exact-byte artifacts. + +The dependency-free Node 18+ bootstrap accepts only official GStack GitHub Release hosts, validates manifest/runtime/skill API/platform/component metadata and SHA-256, verifies a declared Cosign bundle when Cosign is available, rejects unsafe archive paths/links, and invokes the atomic managed installer. Never run `./setup` inside a standard-installed skill directory, enroll a host, or treat skill installation as runtime consent. + +After doctor confirms readiness, canonical launcher bindings are `GSTACK_HOME=${GSTACK_HOME:-$HOME/.gstack}`, `GSTACK_BIN=$GSTACK_HOME/bin`, `BUN_CMD=$GSTACK_BIN/bun`, `B=$GSTACK_BIN/browse`, `D=$GSTACK_BIN/gstack-design`, and `P=$GSTACK_BIN/make-pdf`. Specialist modules may bind only the launchers they use. These assignments do not install or grant consent. Never download or install a second Bun for GStack helpers; the managed runtime owns its pinned Bun executable in the active slot. + +Some retained helpers are shell scripts. `gstack doctor` verifies Bash and, on Windows, discovers Git for Windows Bash; disclose and stop at the affected helper if that prerequisite is unavailable. Python is not a global GStack prerequisite: only a specialist flow explicitly labeled as Python-dependent may request it, at the point of use and with the user's approval. The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.0.0`, and `skillApi=2.0`; the machine-readable copy is `references/support/runtime-contract.json`. An incompatible active runtime is unavailable, not permission to upgrade it. -The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability `; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. +The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source --capability --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment. diff --git a/skills/ship/references/legacy/canary.md b/skills/ship/references/legacy/canary.md index 6fd411a8f..2d649c96b 100644 --- a/skills/ship/references/legacy/canary.md +++ b/skills/ship/references/legacy/canary.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -31,26 +32,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Step 0: Detect platform and base branch diff --git a/skills/ship/references/legacy/context-restore.md b/skills/ship/references/legacy/context-restore.md index 6dcacf534..3eb0e8c9a 100644 --- a/skills/ship/references/legacy/context-restore.md +++ b/skills/ship/references/legacy/context-restore.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/ship/references/legacy/document-generate.md b/skills/ship/references/legacy/document-generate.md index 8c1772434..b6b247846 100644 --- a/skills/ship/references/legacy/document-generate.md +++ b/skills/ship/references/legacy/document-generate.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/ship/references/legacy/document-release.md b/skills/ship/references/legacy/document-release.md index 56a723519..791603f9a 100644 --- a/skills/ship/references/legacy/document-release.md +++ b/skills/ship/references/legacy/document-release.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/ship/references/legacy/ios-sync.md b/skills/ship/references/legacy/ios-sync.md index 276e118bc..d96d02d4a 100644 --- a/skills/ship/references/legacy/ios-sync.md +++ b/skills/ship/references/legacy/ios-sync.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" diff --git a/skills/ship/references/legacy/land-and-deploy.md b/skills/ship/references/legacy/land-and-deploy.md index 4c307d48f..4ababccae 100644 --- a/skills/ship/references/legacy/land-and-deploy.md +++ b/skills/ship/references/legacy/land-and-deploy.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -31,26 +32,9 @@ fi ``` If `NEEDS_SETUP`: -1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait. +1. Tell the user: "The optional managed headless browser capability is missing. Do you want to preview its exact dependency-closed component plan and compressed bytes now?" Then STOP and wait. 2. Read `references/RUNTIME.md` and follow its explicit capability bootstrap. Never assume a standard-installed skill directory contains `./setup`. -3. If `bun` is not installed: - ```bash - if ! command -v bun >/dev/null 2>&1; then - BUN_VERSION="1.3.10" - BUN_INSTALL_SHA="bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd" - tmpfile=$(mktemp) - curl -fsSL "https://bun.sh/install" -o "$tmpfile" - actual_sha=$(shasum -a 256 "$tmpfile" | awk '{print $1}') - if [ "$actual_sha" != "$BUN_INSTALL_SHA" ]; then - echo "ERROR: bun install script checksum mismatch" >&2 - echo " expected: $BUN_INSTALL_SHA" >&2 - echo " got: $actual_sha" >&2 - rm "$tmpfile"; exit 1 - fi - BUN_VERSION="$BUN_VERSION" bash "$tmpfile" - rm "$tmpfile" - fi - ``` +3. The approved managed runtime includes its own pinned Bun at `$GSTACK_BIN/bun`; never download or install another Bun from a skill workflow. ## Step 0: Detect platform and base branch @@ -439,7 +423,7 @@ BASE_VERSION=$(git show origin/$BASE_BRANCH:VERSION 2>/dev/null | tr -d '\r\n[:s # We don't need the exact original level — we just need "a level" that passes to the util. # If the minor digit advanced, call it minor; patch digit, patch; etc. If base > branch, skip (not ours to land). # For simplicity: use "patch" as a conservative default; util handles collision-past regardless of input level. -QUEUE_JSON=$(bun run $GSTACK_BIN/gstack-next-version \ +QUEUE_JSON=$($GSTACK_BIN/gstack-next-version \ --base "$BASE_BRANCH" \ --bump patch \ --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}') diff --git a/skills/ship/references/legacy/landing-report.md b/skills/ship/references/legacy/landing-report.md index 33ca9c62e..6476864e6 100644 --- a/skills/ship/references/legacy/landing-report.md +++ b/skills/ship/references/legacy/landing-report.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -64,7 +65,7 @@ they'd claim for micro/patch/minor/major. Cheap (same gh call cached by bun). ```bash for LEVEL in micro patch minor major; do - bun run $GSTACK_BIN/gstack-next-version \ + $GSTACK_BIN/gstack-next-version \ --base "$BASE_BRANCH" \ --bump "$LEVEL" \ --current-version "$BASE_VERSION" \ diff --git a/skills/ship/references/legacy/ship.md b/skills/ship/references/legacy/ship.md index 741683fdd..cf4a0ac12 100644 --- a/skills/ship/references/legacy/ship.md +++ b/skills/ship/references/legacy/ship.md @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" GSTACK_ROOT="$GSTACK_HOME" GSTACK_STATE_ROOT="$GSTACK_HOME" GSTACK_BIN="$GSTACK_HOME/bin" +BUN_CMD="$GSTACK_BIN/bun" B="$GSTACK_BIN/browse" D="$GSTACK_BIN/gstack-design" P="$GSTACK_BIN/make-pdf" @@ -251,7 +252,7 @@ stay agent judgment; the slot pick stays `gstack-next-version`. 1. **Classify state** — pure reader, never writes: ```bash - bun run $GSTACK_BIN/gstack-version-bump classify --base + $GSTACK_BIN/gstack-version-bump classify --base ``` Read the JSON `state` and dispatch: - **FRESH** → do the bump (steps 2-4). @@ -266,14 +267,14 @@ stay agent judgment; the slot pick stays `gstack-next-version`. 3. **Queue-aware pick** (workspace-aware ship): ```bash - QUEUE_JSON=$(bun run $GSTACK_BIN/gstack-next-version --base --bump "$BUMP_LEVEL" --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}') + QUEUE_JSON=$($GSTACK_BIN/gstack-next-version --base --bump "$BUMP_LEVEL" --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}') NEW_VERSION=$(echo "$QUEUE_JSON" | jq -r '.version // empty') ``` If `offline`/util fails: fall back to local `BUMP_LEVEL` arithmetic and print `⚠ workspace-aware ship offline — using local bump only`. If `claimed` is non-empty, render the queue table so the user sees landing order. If an active sibling workspace holds a version `>= NEW_VERSION`, **AskUserQuestion**: advance past (unrelated work) or abort and sync with the sibling. 4. **Write the bump** (FRESH, or an approved rebump): ```bash - bun run $GSTACK_BIN/gstack-version-bump write --version "$NEW_VERSION" + $GSTACK_BIN/gstack-version-bump write --version "$NEW_VERSION" ``` The CLI validates the 4-digit `MAJOR.MINOR.PATCH.MICRO` pattern and writes **both** VERSION and package.json. On a half-write (VERSION written, package.json failed) it exits 3 — re-run, and classify will report DRIFT_STALE_PKG for `repair` to fix. diff --git a/skills/ship/references/support/browser-provider-smoke.mjs b/skills/ship/references/support/browser-provider-smoke.mjs new file mode 100644 index 000000000..e1a13c72b --- /dev/null +++ b/skills/ship/references/support/browser-provider-smoke.mjs @@ -0,0 +1,126 @@ +#!/usr/bin/env node +import { randomBytes } from "node:crypto"; +import http from "node:http"; +import { pathToFileURL } from "node:url"; + +const HOST = "127.0.0.1"; + +export function createReadinessServer(options = {}) { + const token = options.token ?? randomBytes(24).toString("hex"); + if (!/^[a-f0-9]{32,128}$/.test(token)) throw new TypeError("Readiness token must be 32-128 lowercase hex characters"); + let completed = false; + let baseUrl = null; + + const server = http.createServer((request, response) => { + const url = new URL(request.url ?? "/", baseUrl ?? `http://${HOST}`); + const supplied = url.searchParams.get("token"); + const headers = { + "Cache-Control": "no-store", + "Content-Security-Policy": "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", + "Referrer-Policy": "no-referrer", + "X-Content-Type-Options": "nosniff", + }; + + if (url.pathname === "/" && request.method === "GET") { + response.writeHead(200, { ...headers, "Content-Type": "text/html; charset=utf-8" }); + response.end(renderPage(token)); + return; + } + if (url.pathname === "/proof") { + if (request.method !== "POST") { + response.writeHead(405, { ...headers, Allow: "POST" }); + response.end(); + return; + } + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + completed = true; + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, status: "READY" })); + return; + } + if (url.pathname === "/status" && request.method === "GET") { + if (supplied !== token) { + response.writeHead(403, headers); + response.end(); + return; + } + response.writeHead(200, { ...headers, "Content-Type": "application/json" }); + response.end(JSON.stringify({ ok: true, completed })); + return; + } + response.writeHead(404, headers); + response.end(); + }); + + return { + server, + token, + get completed() { return completed; }, + async start() { + if (baseUrl) return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(options.port ?? 0, HOST, resolve); + }); + const address = server.address(); + if (!address || typeof address === "string") throw new Error("Readiness fixture did not acquire a TCP port"); + baseUrl = `http://${HOST}:${address.port}`; + return { url: `${baseUrl}/?token=${token}`, baseUrl, token }; + }, + async stop() { + if (!server.listening) return; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + }, + }; +} + +function renderPage(token) { + return ` + + + + + GStack browser readiness + + + +
+

GStack browser readiness

+

This local page verifies navigation, reading, interaction, console, and network access.

+ +

WAITING

+
+ + +`; +} + +async function main() { + const fixture = createReadinessServer(); + const started = await fixture.start(); + process.stdout.write(`${JSON.stringify({ ...started, pid: process.pid })}\n`); + const stop = async () => { + await fixture.stop(); + process.exitCode = fixture.completed ? 0 : 2; + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`gstack browser readiness: ${error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/skills/ship/references/support/runtime-bootstrap.mjs b/skills/ship/references/support/runtime-bootstrap.mjs index b78b1572e..84370d441 100644 --- a/skills/ship/references/support/runtime-bootstrap.mjs +++ b/skills/ship/references/support/runtime-bootstrap.mjs @@ -7,21 +7,41 @@ import os from "node:os"; import path from "node:path"; import process from "node:process"; import { createHash } from "node:crypto"; +import { constants as fsConstants, createReadStream } from "node:fs"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; -export const BOOTSTRAP_SCHEMA_VERSION = 1; +export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; -const CAPABILITIES = new Set(["browser", "design", "pdf", "diagram", "ios"]); +const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), + "browser-visible": Object.freeze([]), design: Object.freeze([]), pdf: Object.freeze(["browser", "diagram"]), diagram: Object.freeze(["browser"]), ios: Object.freeze([]), }); +export const COMPONENT_DEPENDENCIES = Object.freeze({ + core: Object.freeze([]), + "browser-code": Object.freeze(["core"]), + "browser-headless": Object.freeze(["browser-code"]), + "browser-visible": Object.freeze(["browser-code"]), + design: Object.freeze(["core"]), + diagram: Object.freeze(["browser-headless"]), + pdf: Object.freeze(["diagram"]), + ios: Object.freeze(["core"]), +}); +export const CAPABILITY_COMPONENTS = Object.freeze({ + browser: Object.freeze(["browser-code", "browser-headless"]), + "browser-visible": Object.freeze(["browser-code", "browser-visible"]), + design: Object.freeze(["design"]), + diagram: Object.freeze(["diagram"]), + pdf: Object.freeze(["pdf"]), + ios: Object.freeze(["ios"]), +}); const ALLOWED_DOWNLOAD_HOSTS = new Set([ "github.com", "objects.githubusercontent.com", @@ -43,18 +63,26 @@ export async function main(argv = process.argv.slice(2), options = {}) { io.stdout.write(usage()); return 0; } - if (parsed.action !== "install") throw bootstrapError("Expected `install`", "BOOTSTRAP_USAGE"); + if (!["preview", "install"].includes(parsed.action)) { + throw bootstrapError("Expected `preview` or `install`", "BOOTSTRAP_USAGE"); + } + + const platform = options.platform ?? process.platform; + if (parsed.capabilities.includes("ios") && platform !== "darwin") { + throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); + } if (parsed.source) { + if (parsed.action === "preview") { + io.stdout.write("Reviewed-source fallback has no signed compressed-byte manifest; the local installer can provide an on-disk preview only.\n"); + return 0; + } + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after review", "BOOTSTRAP_CONSENT_REQUIRED"); io.stderr.write("Developer-only source install: only continue with a checkout you reviewed and trust.\n"); return await installFromSource(parsed.source, parsed, { ...options, ...io, prepared: false }); } const fetch_ = options.fetch ?? globalThis.fetch; if (typeof fetch_ !== "function") throw bootstrapError("Node 18+ with fetch is required", "BOOTSTRAP_NODE_UNSUPPORTED"); - const platform = options.platform ?? process.platform; - if (parsed.capabilities.includes("ios") && platform !== "darwin") { - throw bootstrapError("The physical-iOS capability is available only on macOS", "BOOTSTRAP_PLATFORM_UNSUPPORTED"); - } const target = platformTarget( platform, options.arch ?? process.arch, @@ -63,19 +91,32 @@ export async function main(argv = process.argv.slice(2), options = {}) { const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); const manifest = await fetchJson(fetch_, manifestUrl); - const artifact = validateManifest(manifest, target); - io.stdout.write(`Official runtime artifact: ${formatBytes(artifact.bytes)} for ${target}.\n`); + validateManifest(manifest, target); + const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); + const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); + const plan = buildComponentPlan(manifest, target, parsed.capabilities, reusable); + if (parsed.json) io.stdout.write(`${JSON.stringify({ ok: true, action: parsed.action, ...plan }, null, 2)}\n`); + else printComponentPlan(io.stdout, plan); + if (parsed.action === "preview") return 0; + if (!parsed.yes) throw bootstrapError("Installation requires explicit --yes after reviewing this exact component plan", "BOOTSTRAP_CONSENT_REQUIRED"); const temporary = await fs.mkdtemp(path.join(options.tmpDir ?? os.tmpdir(), "gstack-bootstrap-")); try { - const archive = path.join(temporary, "runtime.tar.gz"); - await downloadVerified(fetch_, artifact.url, archive, artifact.sha256, artifact.bytes); - io.stdout.write(`Verified SHA-256 for GStack runtime ${manifest.version} (${target}).\n`); - await verifyCosignWhenAvailable(archive, artifact, temporary, { ...options, fetch: fetch_, ...io }); - const extracted = path.join(temporary, "extracted"); - await fs.mkdir(extracted, { mode: 0o700 }); - await extractTarSafely(archive, extracted, options); - const root = safeArtifactRoot(extracted, artifact.root ?? "gstack"); - await assertNoLinks(root); + const root = path.join(temporary, "merged", "gstack"); + await fs.mkdir(root, { recursive: true, mode: 0o700 }); + const claimedFiles = new Set(); + if (reusable) await seedReusableRuntime(reusable, root, claimedFiles); + for (const item of plan.downloads) { + const archive = path.join(temporary, `${item.component}.tar.gz`); + await downloadVerified(fetch_, item.artifact.url, archive, item.artifact.sha256, item.artifact.bytes); + io.stdout.write(`Verified SHA-256 for ${item.component} (${target}).\n`); + await verifyCosignWhenAvailable(archive, item.artifact, path.join(temporary, item.component), { ...options, fetch: fetch_, ...io }); + const extracted = path.join(temporary, "extracted", item.component); + await fs.mkdir(extracted, { recursive: true, mode: 0o700 }); + await extractTarSafely(archive, extracted, options); + const componentRoot = safeArtifactRoot(extracted, item.artifact.root ?? "gstack"); + await assertNoLinks(componentRoot); + await mergeComponentRoot(componentRoot, root, claimedFiles, item.component); + } return await installFromSource(root, parsed, { ...options, ...io, prepared: true, version: manifest.version }); } finally { await fs.rm(temporary, { recursive: true, force: true }); @@ -87,10 +128,12 @@ export async function main(argv = process.argv.slice(2), options = {}) { } function parseArgs(argv) { - const result = { action: null, capabilities: [], source: null, home: null, help: false }; + const result = { action: null, capabilities: [], source: null, home: null, yes: false, json: false, help: false }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (["-h", "--help"].includes(arg)) result.help = true; + else if (arg === "--yes") result.yes = true; + else if (arg === "--json") result.json = true; else if (!result.action && !arg.startsWith("-")) result.action = arg; else if (["--capability", "--source", "--home"].includes(arg)) { const value = argv[++index]; @@ -101,6 +144,7 @@ function parseArgs(argv) { } else throw bootstrapError(`Unknown option: ${arg}`, "BOOTSTRAP_USAGE"); } if (result.help) return result; + if (result.action === "preview" && result.yes) throw bootstrapError("preview cannot be combined with --yes", "BOOTSTRAP_USAGE"); if (!result.capabilities.length) throw bootstrapError("At least one --capability is required", "BOOTSTRAP_USAGE"); result.capabilities = [...new Set(result.capabilities)].sort(); for (const capability of result.capabilities) { @@ -122,25 +166,138 @@ function parseArgs(argv) { function validateManifest(manifest, target) { if (manifest?.schemaVersion !== BOOTSTRAP_SCHEMA_VERSION || manifest?.version !== BOOTSTRAP_RUNTIME_VERSION || - manifest?.skillApi !== "2.0" || typeof manifest?.artifacts !== "object") { + manifest?.skillApi !== "2.0" || typeof manifest?.targets !== "object" || + !sameGraph(manifest.capabilityComponents, CAPABILITY_COMPONENTS) || + !sameGraph(manifest.componentDependencies, COMPONENT_DEPENDENCIES)) { throw bootstrapError("Official runtime manifest is incompatible", "BOOTSTRAP_MANIFEST_INVALID"); } - const artifact = manifest.artifacts[target]; - if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || - !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + const targetRecord = manifest.targets[target]; + const expected = Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .sort(); + if (!targetRecord || typeof targetRecord.components !== "object" || + JSON.stringify(Object.keys(targetRecord.components).sort()) !== JSON.stringify(expected)) { throw bootstrapError(`No valid official runtime artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); } - assertOfficialReleaseAssetUrl(artifact.url); - if (artifact.cosignBundleUrl) { - assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); - if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || - artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { - throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + for (const [component, artifact] of Object.entries(targetRecord.components)) { + if (!artifact || artifact.format !== "tar.gz" || !/^[a-f0-9]{64}$/.test(artifact.sha256) || + !Number.isSafeInteger(artifact.bytes) || artifact.bytes < 1 || artifact.bytes > 2 * 1024 * 1024 * 1024) { + throw bootstrapError(`Invalid ${component} artifact for ${target}`, "BOOTSTRAP_ARTIFACT_UNAVAILABLE"); + } + assertOfficialReleaseAssetUrl(artifact.url); + if (artifact.cosignBundleUrl) { + assertOfficialReleaseAssetUrl(artifact.cosignBundleUrl); + if (artifact.certificateIdentity !== OFFICIAL_CERTIFICATE_IDENTITY || + artifact.certificateOidcIssuer !== GITHUB_OIDC_ISSUER) { + throw bootstrapError("Cosign metadata does not bind the official GStack release workflow", "BOOTSTRAP_MANIFEST_INVALID"); + } + } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { + throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - } else if (artifact.certificateIdentity || artifact.certificateOidcIssuer) { - throw bootstrapError("Cosign certificate metadata requires a bundle URL", "BOOTSTRAP_MANIFEST_INVALID"); } - return artifact; + return targetRecord; +} + +function sameGraph(actual, expected) { + if (!actual || typeof actual !== "object" || Array.isArray(actual)) return false; + const normalize = (graph) => Object.fromEntries(Object.entries(graph) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, values]) => [key, Array.isArray(values) ? [...values].sort() : values])); + return JSON.stringify(normalize(actual)) === JSON.stringify(normalize(expected)); +} + +function selectedComponents(capabilities) { + const selected = new Set(["core"]); + for (const capability of capabilities) { + for (const component of CAPABILITY_COMPONENTS[capability] ?? []) selected.add(component); + } + const pending = [...selected]; + while (pending.length) { + for (const dependency of COMPONENT_DEPENDENCIES[pending.pop()] ?? []) { + if (!selected.has(dependency)) { + selected.add(dependency); + pending.push(dependency); + } + } + } + return [...selected].sort(); +} + +function buildComponentPlan(manifest, target, capabilities, reusable) { + const components = selectedComponents(capabilities); + const retained = new Set(reusable?.components ?? []); + const downloads = components + .filter((component) => !retained.has(component)) + .map((component) => ({ component, artifact: manifest.targets[target].components[component] })); + const downloadBytes = downloads.reduce((total, item) => total + item.artifact.bytes, 0); + return { + target, + version: manifest.version, + capabilities, + components, + reusedComponents: components.filter((component) => retained.has(component)), + downloads, + downloadBytes, + }; +} + +function printComponentPlan(stdout, plan) { + stdout.write(`GStack optional runtime ${plan.version} for ${plan.target}\n`); + stdout.write(`Capabilities: ${plan.capabilities.join(", ")}\n`); + stdout.write(`Components: ${plan.components.join(", ")}\n`); + if (plan.reusedComponents.length) stdout.write(`Reusing: ${plan.reusedComponents.join(", ")}\n`); + stdout.write(`Download: ${plan.downloadBytes} bytes across ${plan.downloads.length} component(s)\n`); +} + +async function inspectReusableRuntime(home, version) { + const versions = path.join(home, "versions"); + const pointer = JSON.parse(await fs.readFile(path.join(versions, "current.json"), "utf8")); + if (pointer?.schemaVersion !== 2 || pointer?.status !== "active" || + typeof pointer.current !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(pointer.current)) return null; + const root = path.join(versions, pointer.current); + const stat = await fs.lstat(root); + if (!stat.isDirectory() || stat.isSymbolicLink()) return null; + const bundle = JSON.parse(await fs.readFile(path.join(root, ".gstack-bundle.json"), "utf8")); + if (bundle?.schemaVersion !== 2 || bundle?.version !== version || !Array.isArray(bundle.runtimeComponents) || + !Array.isArray(bundle.files)) return null; + const components = [...new Set(bundle.runtimeComponents)]; + if (!components.length || components.some((component) => !Object.hasOwn(COMPONENT_DEPENDENCIES, component))) return null; + await assertNoLinks(root); + const files = []; + const seen = new Set(); + for (const entry of bundle.files) { + const relative = entry?.path; + if (typeof relative !== "string" || !relative || relative.includes("\\") || path.posix.isAbsolute(relative) || + path.posix.normalize(relative) !== relative || relative.split("/").includes("..") || seen.has(relative) || + !Number.isSafeInteger(entry.size) || entry.size < 0 || !/^[a-f0-9]{64}$/.test(entry.sha256)) return null; + seen.add(relative); + const file = path.join(root, ...relative.split("/")); + const fileStat = await fs.lstat(file).catch(() => null); + if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.size !== entry.size || + await sha256File(file) !== entry.sha256) return null; + files.push(relative); + } + return { root, components, files }; +} + +async function seedReusableRuntime(reusable, destination, claimedFiles) { + for (const relative of reusable.files) { + if (claimedFiles.has(relative)) throw bootstrapError(`Runtime components overlap at ${relative}`, "BOOTSTRAP_MANIFEST_INVALID"); + claimedFiles.add(relative); + const target = path.join(destination, ...relative.split("/")); + await fs.mkdir(path.dirname(target), { recursive: true, mode: 0o700 }); + await fs.copyFile(path.join(reusable.root, ...relative.split("/")), target, fsConstants.COPYFILE_EXCL); + } +} + +function sha256File(file) { + return new Promise((resolve, reject) => { + const hash = createHash("sha256"); + const stream = createReadStream(file); + stream.on("error", reject); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("end", () => resolve(hash.digest("hex"))); + }); } async function fetchJson(fetch_, url) { @@ -277,6 +434,31 @@ function safeArtifactRoot(extracted, relative) { return target; } +async function mergeComponentRoot(source, destination, claimedFiles, component) { + async function visit(relative = "") { + for (const entry of await fs.readdir(path.join(source, relative), { withFileTypes: true })) { + const child = relative ? `${relative}/${entry.name}` : entry.name; + const from = path.join(source, ...child.split("/")); + const to = path.join(destination, ...child.split("/")); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + throw bootstrapError(`Runtime component ${component} contains a link or special file`, "BOOTSTRAP_ARCHIVE_UNSAFE"); + } + if (entry.isDirectory()) { + await fs.mkdir(to, { recursive: true, mode: 0o700 }); + await visit(child); + } else { + if (claimedFiles.has(child)) { + throw bootstrapError(`Runtime components overlap at ${child}`, "BOOTSTRAP_MANIFEST_INVALID"); + } + claimedFiles.add(child); + await fs.mkdir(path.dirname(to), { recursive: true, mode: 0o700 }); + await fs.copyFile(from, to, fsConstants.COPYFILE_EXCL); + } + } + } + await visit(); +} + async function assertNoLinks(root) { const pending = [root]; while (pending.length) { diff --git a/test/gstack2-browser-provider-setup.test.ts b/test/gstack2-browser-provider-setup.test.ts new file mode 100644 index 000000000..e1109b776 --- /dev/null +++ b/test/gstack2-browser-provider-setup.test.ts @@ -0,0 +1,94 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import fs from "node:fs"; +import path from "node:path"; +import { + BROWSER_PROVIDER_CONTRACTS, + renderBrowserProviderContract, +} from "../scripts/gstack2/browser-provider-contract"; +import { createReadinessServer } from "../runtime/browser-provider-smoke.mjs"; + +const ROOT = path.resolve(import.meta.dir, ".."); +const TREES = ["plan", "design", "qa", "debug", "review", "ship"]; +const fixtures: Array> = []; + +afterEach(async () => { + await Promise.all(fixtures.splice(0).map((fixture) => fixture.stop())); +}); + +describe("browser provider setup contract", () => { + test("defines every verified host plus Gemini without inventing Kimi or Pi core automation", () => { + expect(BROWSER_PROVIDER_CONTRACTS.map((provider) => provider.id)).toEqual([ + "claude", + "codex", + "gemini", + "cursor", + "github-copilot", + "openclaw", + "kimi", + "pi", + ]); + expect(BROWSER_PROVIDER_CONTRACTS.map((provider) => provider.kind)).toEqual([ + "native-extension", + "native-in-app", + "native-agent", + "native-mcp", + "native-in-app", + "native-plugin", + "no-native-automation", + "extension-only", + ]); + + const rendered = renderBrowserProviderContract(); + expect(rendered).toContain("Never install an extension"); + expect(rendered).toContain("Tool names or metadata alone are insufficient evidence"); + expect(rendered).toContain("Inherited `CODEX_*`"); + expect(rendered).toContain("Never report `ready` merely because"); + expect(rendered).toContain("Gemini CLI has an experimental bundled `browser_agent`"); + expect(rendered).toContain("current VS Code can expose built-in browser agent tools"); + expect(rendered).toContain("OpenClaw includes a browser plugin"); + expect(rendered).toContain("Do not describe `kimi web` as browser automation"); + expect(rendered).toContain("Pi intentionally has no core interactive browser tool"); + expect(rendered).toContain("never silently add a browser MCP or alternate backend"); + expect(rendered).toContain("GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend"); + expect(rendered).toContain("Do not run `./setup`"); + }); + + test("generates the same provider contract and dependency-free probe for all six skills", () => { + const expectedContract = `\n${renderBrowserProviderContract()}`; + const expectedProbe = fs.readFileSync(path.join(ROOT, "runtime", "browser-provider-smoke.mjs")); + for (const tree of TREES) { + expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "BROWSER-PROVIDERS.md"), "utf8")) + .toBe(expectedContract); + expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "support", "browser-provider-smoke.mjs"))) + .toEqual(expectedProbe); + expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "RUNTIME.md"), "utf8")) + .toContain("Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full"); + } + }); + + test("readiness fixture proves navigation, protected interaction, and durable completion state", async () => { + const token = "a".repeat(48); + const fixture = createReadinessServer({ token }); + fixtures.push(fixture); + const started = await fixture.start(); + + expect(started.baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/); + const page = await fetch(started.url); + expect(page.status).toBe(200); + const html = await page.text(); + expect(html).toContain("GStack browser readiness"); + expect(html).toContain('id="gstack-readiness-action"'); + expect(html).toContain('id="gstack-readiness-status"'); + + const denied = await fetch(`${started.baseUrl}/proof?token=${"b".repeat(48)}`, { method: "POST" }); + expect(denied.status).toBe(403); + const before = await fetch(`${started.baseUrl}/status?token=${token}`).then((response) => response.json()); + expect(before).toEqual({ ok: true, completed: false }); + + const proof = await fetch(`${started.baseUrl}/proof?token=${token}`, { method: "POST" }); + expect(proof.status).toBe(200); + expect(await proof.json()).toEqual({ ok: true, status: "READY" }); + const after = await fetch(`${started.baseUrl}/status?token=${token}`).then((response) => response.json()); + expect(after).toEqual({ ok: true, completed: true }); + }); +}); diff --git a/test/gstack2-installation.test.ts b/test/gstack2-installation.test.ts index 25d2eee38..5b72f96cc 100644 --- a/test/gstack2-installation.test.ts +++ b/test/gstack2-installation.test.ts @@ -62,6 +62,7 @@ describe('GStack 2 standard installer surface', () => { expect(AGENT_MATRIX.map((entry) => entry.agent)).toEqual([ 'claude-code', 'codex', + 'kimi-code-cli', 'cursor', 'pi', 'openclaw', @@ -105,7 +106,9 @@ describe('GStack 2 standard installer surface', () => { expect(result.summary.passed).toBe(true); expect(result.discovery).toMatchObject({ count: 6, names: [...PUBLIC_SKILLS], passed: true }); - expect(result.installs).toHaveLength(AGENT_MATRIX.length * 2 + 2); + // Two default scopes per host plus project/global collision selection, + // the OpenClaw single-skill case, and the opt-in compatibility alias. + expect(result.installs).toHaveLength(AGENT_MATRIX.length * 2 + 4); expect(result.removals).toHaveLength(2); expect(fs.existsSync(output)).toBe(true); }, 600_000); diff --git a/test/gstack2-runtime-install.test.ts b/test/gstack2-runtime-install.test.ts index f96b31dcd..fd2c71103 100644 --- a/test/gstack2-runtime-install.test.ts +++ b/test/gstack2-runtime-install.test.ts @@ -168,7 +168,7 @@ describe("GStack 2 managed runtime installer", () => { const phases: string[] = []; const run = async (_command: string, args: string[], options: { env?: Record; superviseTree?: boolean; timeoutMs?: number } = {}) => { if (!args[0]?.endsWith(path.join("node_modules", "playwright", "cli.js"))) return { code: 0, stdout: "", stderr: "" }; - expect(args.slice(1)).toEqual(["install", "--no-shell", "chromium"]); + expect(args.slice(1)).toEqual(["install", "--only-shell", "chromium"]); expect(options.superviseTree).toBe(true); expect(options.timeoutMs).toBe(15 * 60_000); const target = options.env?.PLAYWRIGHT_BROWSERS_PATH; @@ -234,7 +234,7 @@ describe("GStack 2 managed runtime installer", () => { capabilities: { browse: ".gstack-runtime-browsers/fresh/chrome" }, runCommand: async (_command: string, args: string[], options: { env?: Record } = {}) => { downloads += 1; - expect(args.slice(1)).toEqual(["install", "--no-shell", "chromium"]); + expect(args.slice(1)).toEqual(["install", "--only-shell", "chromium"]); const target = options.env?.PLAYWRIGHT_BROWSERS_PATH; if (!target) throw new Error("missing fixture browser destination"); await fs.mkdir(path.join(target, "fresh"), { recursive: true }); @@ -309,6 +309,43 @@ describe("GStack 2 managed runtime installer", () => { }); }); + test("browser-visible materializes full Chromium without also downloading the headless shell", async () => { + await withFixture(async ({ home }) => { + const installModes: string[] = []; + const result = await installManagedRuntime({ + sourceDir: REPO_ROOT, + home, + version: "browser-visible-only", + capabilityIds: ["browser-visible"], + bunCommand: process.execPath, + runCommand: async (command: string, args: string[], options: { env?: Record } = {}) => { + if (args[0] === "--eval" && args[1]?.includes("process.execPath")) { + return { code: 0, stdout: process.execPath, stderr: "" }; + } + if (args[0]?.endsWith(path.join("node_modules", "playwright", "cli.js"))) { + installModes.push(args[2]); + expect(args.slice(1)).toEqual(["install", "--no-shell", "chromium"]); + const browserRoot = options.env?.PLAYWRIGHT_BROWSERS_PATH; + if (!browserRoot) throw new Error("fixture browser root missing"); + await fs.mkdir(path.join(browserRoot, "chromium-fixture"), { recursive: true }); + await fs.writeFile(path.join(browserRoot, "chromium-fixture", "chrome"), "fixture\n", { mode: 0o755 }); + return { code: 0, stdout: "", stderr: "" }; + } + if (args[0] === "--version" && (command === process.execPath || command.includes(".gstack-runtime-tools"))) { + return { code: 0, stdout: "1.3.14\n", stderr: "" }; + } + return { code: 0, stdout: "", stderr: "" }; + }, + smokeTest: async () => {}, + }); + expect(installModes).toEqual(["--no-shell"]); + const bundleManifest = await readJson(path.join(result.path, ".gstack-bundle.json")); + expect(bundleManifest.selectedCapabilities).toEqual(["browser-visible"]); + expect(bundleManifest.runtimeComponents).toContain("browser-visible"); + expect(bundleManifest.runtimeComponents).not.toContain("browser-headless"); + }, { createDefaultSource: false }); + }, FULL_RUNTIME_TEST_TIMEOUT_MS); + test("default capability builds never regenerate the Agent Skills tree", async () => { const calls: Array<{ command: string; args: string[] }> = []; await defaultBunBuilder({ diff --git a/test/gstack2-runtime-setup-ux.test.ts b/test/gstack2-runtime-setup-ux.test.ts index 8cf99cc3f..b4880109b 100644 --- a/test/gstack2-runtime-setup-ux.test.ts +++ b/test/gstack2-runtime-setup-ux.test.ts @@ -9,7 +9,10 @@ import { resolveRuntimePaths } from "../runtime/paths.js"; import { setupRuntime } from "../runtime/setup.js"; import { bashCandidates, resolveBashCommand } from "../runtime/tooling.js"; import { + BOOTSTRAP_SCHEMA_VERSION, BOOTSTRAP_RUNTIME_VERSION, + CAPABILITY_COMPONENTS, + COMPONENT_DEPENDENCIES, OFFICIAL_MANIFEST_URL, main as bootstrapMain, } from "../runtime/runtime-bootstrap.mjs"; @@ -19,6 +22,29 @@ function capture() { return { stream: { write: (chunk: string) => { value += chunk; } }, value: () => value }; } +function officialManifestFixture(target: string, customize?: (component: string, artifact: Record) => void) { + const components = Object.fromEntries(Object.keys(COMPONENT_DEPENDENCIES) + .filter((component) => component !== "ios" || target.startsWith("darwin-")) + .map((component) => { + const artifact: Record = { + url: `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/${component}.tar.gz`, + sha256: "0".repeat(64), + bytes: 8, + format: "tar.gz", + }; + customize?.(component, artifact); + return [component, artifact]; + })); + return { + schemaVersion: BOOTSTRAP_SCHEMA_VERSION, + version: BOOTSTRAP_RUNTIME_VERSION, + skillApi: "2.0", + capabilityComponents: CAPABILITY_COMPONENTS, + componentDependencies: COMPONENT_DEPENDENCIES, + targets: { [target]: { components } }, + }; +} + describe("GStack runtime setup UX", () => { test("capability selection keeps the core and excludes unselected heavyweight surfaces", () => { const surface = runtimeSurfaceForCapabilities(["browser"]); @@ -221,7 +247,7 @@ describe("GStack runtime setup UX", () => { } }); - test("doctor resolves the exact Chromium executable from the managed slot", async () => { + test("doctor launches the managed headless Chromium slot instead of requiring full Chromium", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-doctor-browser-")); const home = path.join(root, "home"); try { @@ -231,15 +257,13 @@ describe("GStack runtime setup UX", () => { const browserRoot = path.join(active, ".gstack-runtime-browsers"); const managedBun = path.join(active, ".gstack-runtime-tools", process.platform === "win32" ? "bun.exe" : "bun"); const playwright = path.join(active, "node_modules", "playwright"); - const executable = path.join(browserRoot, "chromium-fixture", process.platform === "win32" ? "chrome.exe" : "chrome"); - await fs.mkdir(path.dirname(executable), { recursive: true }); + await fs.mkdir(path.join(browserRoot, "chromium-headless-shell-fixture"), { recursive: true }); await fs.mkdir(path.dirname(managedBun), { recursive: true }); await fs.mkdir(playwright, { recursive: true }); - await fs.writeFile(executable, "fixture\n", { mode: 0o755 }); await fs.copyFile(process.execPath, managedBun); if (process.platform !== "win32") await fs.chmod(managedBun, 0o755); await fs.writeFile(path.join(playwright, "index.mjs"), - `export const chromium = { executablePath: () => ${JSON.stringify(executable)} };\n`); + `export const chromium = { launch: async ({ headless }) => { if (headless !== true) throw new Error("expected headless"); return { version: () => "fixture-chromium", close: async () => {} }; } };\n`); await fs.writeFile(path.join(active, ".gstack-bundle.json"), JSON.stringify({ compatibility: { skillApi: "2.0" }, selectedCapabilities: ["browser"], @@ -252,7 +276,7 @@ describe("GStack runtime setup UX", () => { const report = await runDoctor({ home, cwd: root, nodeCommand: process.execPath }); expect(report.checks.find((check) => check.id === "capability:browser")).toMatchObject({ status: "pass", - details: { executable }, + details: { browserRoot, version: "fixture-chromium" }, }); expect(report.checks.find((check) => check.id === "runtime-tool:bun")).toMatchObject({ status: "pass" }); } finally { @@ -289,7 +313,6 @@ describe("GStack runtime setup UX", () => { test("bootstrap refuses an artifact whose SHA-256 does not match the official manifest", async () => { const output = capture(); const target = `${process.platform === "win32" ? "windows" : process.platform}-${process.arch}`; - const artifactUrl = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/fixture.tar.gz`; let calls = 0; const fetch_ = async (url: string) => { calls += 1; @@ -297,21 +320,16 @@ describe("GStack runtime setup UX", () => { return { ok: true, url, - json: async () => ({ - schemaVersion: 1, - version: BOOTSTRAP_RUNTIME_VERSION, - skillApi: "2.0", - artifacts: { [target]: { url: artifactUrl, sha256: "0".repeat(64), bytes: 8, format: "tar.gz" } }, - }), + json: async () => officialManifestFixture(target), }; } return { ok: true, - url: artifactUrl, + url, arrayBuffer: async () => new TextEncoder().encode("tampered").buffer, }; }; - expect(await bootstrapMain(["install", "--capability", "browser"], { + expect(await bootstrapMain(["install", "--capability", "browser", "--yes"], { stdout: output.stream, stderr: output.stream, fetch: fetch_, @@ -352,9 +370,8 @@ describe("GStack runtime setup UX", () => { test("declared Cosign bundles must bind the official release workflow identity", async () => { const output = capture(); const target = `${process.platform === "win32" ? "windows" : process.platform}-${process.arch}`; - const artifactUrl = `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/fixture.tar.gz`; let calls = 0; - expect(await bootstrapMain(["install", "--capability", "browser"], { + expect(await bootstrapMain(["install", "--capability", "browser", "--yes"], { stdout: output.stream, stderr: output.stream, fetch: async (url: string) => { @@ -362,19 +379,9 @@ describe("GStack runtime setup UX", () => { return { ok: true, url, - json: async () => ({ - schemaVersion: 1, - version: BOOTSTRAP_RUNTIME_VERSION, - skillApi: "2.0", - artifacts: { - [target]: { - url: artifactUrl, - sha256: "0".repeat(64), - bytes: 8, - format: "tar.gz", - cosignBundleUrl: `${artifactUrl}.sigstore.json`, - }, - }, + json: async () => officialManifestFixture(target, (component, artifact) => { + if (component !== "core") return; + artifact.cosignBundleUrl = `${artifact.url}.sigstore.json`; }), }; }, @@ -396,7 +403,7 @@ describe("GStack runtime setup UX", () => { process.env.BOOTSTRAP_TEST_LOG = log; try { expect(await bootstrapMain([ - "install", "--source", root, "--capability", "pdf", "--home", path.join(root, "home"), + "install", "--source", root, "--capability", "pdf", "--home", path.join(root, "home"), "--yes", ], { stdout: output.stream, stderr: output.stream })).toBe(0); } finally { if (previous == null) delete process.env.BOOTSTRAP_TEST_LOG;