chore: merge release v1.7.0 to main (#2277)

Reconciles main (1.6.1) with the released 1.7.0 tree. main adopts the
1.7.0 tree; both parents retained so the 1.6.1 hotfix line stays in
history. The 1.6.1 fixes (#1591/#1693/#1580/#1847) are already present
in 1.7.0 via their own next-line PRs.
This commit is contained in:
Tom Boucher
2026-07-14 21:52:53 -04:00
1282 changed files with 190279 additions and 82771 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "gsd-core",
"description": "Marketplace for GSD Core — meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
"owner": {
"name": "open-gsd",
"url": "https://github.com/open-gsd"
},
"plugins": [
{
"name": "gsd-core",
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
"version": "1.7.0",
"source": "./",
"author": {
"name": "open-gsd",
"url": "https://github.com/open-gsd"
}
}
]
}

View File

@@ -1,7 +1,7 @@
{
"name": "gsd-core",
"displayName": "GSD Core",
"version": "1.6.1",
"version": "1.7.0",
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
"author": {
"name": "open-gsd",

View File

@@ -37,7 +37,6 @@ body:
description: Which AI coding tool are you using GSD with?
options:
- Claude Code
- Gemini CLI
- OpenCode
- Codex
- Copilot
@@ -174,7 +173,6 @@ body:
**How to retrieve:**
- Claude Code: `cat ~/.claude/settings.json`
- Gemini CLI: `cat ~/.gemini/settings.json`
- OpenCode: `cat ~/.config/opencode/opencode.json` or `opencode.jsonc`
**⚠️ PII Warning:** This file may contain API keys, tokens, or custom paths. **Remove all API keys and tokens before pasting.** We recommend running through [presidio-anonymizer](https://microsoft.github.io/presidio/) or manually redacting any line containing "key", "token", or "secret".

View File

@@ -170,7 +170,6 @@ body:
description: Which runtimes must this work with? Check all that apply.
options:
- label: Claude Code
- label: Gemini CLI
- label: OpenCode
- label: Codex
- label: Copilot

View File

@@ -0,0 +1,105 @@
## Registry Entry PR
> **Using the wrong template?**
> — Bug fix: use [fix.md](?template=fix.md)
> — New feature (not a registry listing): use [feature.md](?template=feature.md)
> — Enhancement to existing behavior: use [enhancement.md](?template=enhancement.md)
Full schema and process: [docs/registries/README.md](../../docs/registries/README.md).
---
## Registry type
<!-- Check exactly one. -->
- [ ] Capability Registry entry — adds/updates one object in `docs/registries/capabilities.json`
- [ ] EoS Registry entry — adds/updates one object in `docs/registries/eos.json`
## The entry
<!-- Paste the exact JSON object you added, unmodified. -->
```json
{
"id": "",
"name": "",
"type": "",
"repo": "",
"description": "",
"author": "",
"license": "",
"enginesGsd": "",
"install": "",
"uninstall": "",
"interactions": {},
"discussion": ""
}
```
---
## Required-field checklist
- [ ] `id`, `name`, `type`, `repo`, `description`, `author`, `license`, `enginesGsd`, `install`, `uninstall`, `interactions`, `discussion` are all present and non-empty
- [ ] **(Capability entries only)** `interactions.loopExtensionPoints` is a non-empty subset of the 12 Loop Extension Points, `interactions.hookKinds` ⊆ `{step, contribution, gate}`, and `interactions.configKeys` / `requires` / `runtimeCompat` / `produces` / `consumes` are present (empty arrays are fine where nothing applies)
- [ ] **(EoS entries only)** `protocolVersion` is an integer ≥ 1, `interactions.interfacePoints` is a non-empty subset of the six interface points, `interactions.profile` is one of `programmatic-cli` / `declarative-cli` / `ide`, and `interactions.axes` has exactly the eight required axis keys
## Ownership & non-endorsement
- [ ] `repo` links to a repository **I own or am the primary maintainer of** — not a fork, mirror, or someone else's project
- [ ] I understand that inclusion in this registry means only that a maintainer merged this PR — it is **not** an endorsement, and GSD has not reviewed, tested, audited, or verified my solution or its claimed GSD interactions
- [ ] I understand this entry is removed only for illegal content, malware, spam, or a dead/non-functional link — never for quality — and a maintainer may remove it on that narrow basis without further notice
## One entry, one PR
- [ ] This PR adds or updates exactly **one** entry, in exactly one of `capabilities.json` / `eos.json`
- [ ] I have not bundled any other registry entry, code change, or unrelated docs change into this PR
## Generated file in sync
- [ ] I ran `npm run gen:registry` after editing the JSON source, and this PR includes the regenerated `docs/registries/capability-registry.md` or `docs/registries/eos-registry.md`
- [ ] I did **not** hand-edit the generated `.md` file directly — all edits were made to the JSON source
## Documentation
> CI enforces `lint:docs` for any changeset fragment typed `Added` / `Changed` / `Deprecated` / `Removed` — it must also touch a file under `docs/`. The JSON source and its regenerated markdown, both under `docs/registries/`, satisfy this.
- [ ] This PR includes both the JSON source file and the regenerated markdown file under `docs/registries/`
## Checklist
- [ ] `npm run validate:registry` passes locally against my entry
- [ ] `discussion` links to a GitHub Discussion in the `Registry` category (or notes that one will be created on merge, per [docs/registries/README.md](../../docs/registries/README.md))
- [ ] `.changeset/` fragment added with an `Added` type describing the new listing
---
## Example filled entry
<!-- Reference only — delete this section before submitting your PR. -->
```json
{
"id": "linear-issue-sync",
"name": "Linear Issue Sync",
"type": "capability",
"repo": "some-org/gsd-cap-linear-sync",
"description": "Mirrors ROADMAP.md items to Linear issues as a ship:post contribution.",
"author": "Some Org <hello@some-org.example>",
"license": "MIT",
"enginesGsd": ">=1.6.0",
"install": "gsd capability install https://github.com/some-org/gsd-cap-linear-sync.git#v1.0.0",
"uninstall": "gsd capability remove linear-issue-sync",
"interactions": {
"loopExtensionPoints": ["ship:post"],
"hookKinds": ["contribution"],
"configKeys": ["linear-issue-sync.enabled"],
"requires": [],
"runtimeCompat": ["all"],
"produces": ["linear-issue-links"],
"consumes": ["ROADMAP.md"]
},
"discussion": "https://github.com/open-gsd/gsd-core/discussions/1234"
}
```

View File

@@ -115,7 +115,7 @@ jobs:
# filter those out. A substantive change still parks: it leaves
# non-"version" lines (deps in package.json; resolved/integrity in the
# lockfile when a dependency actually changes).
VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json gemini-extension.json'
VERSION_STAMP_MANIFESTS='package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json'
DROPPED=$(printf '%s\n' "$DROPPED" | while IFS= read -r f; do
[ -n "$f" ] || continue
case " $VERSION_STAMP_MANIFESTS " in
@@ -141,11 +141,6 @@ jobs:
echo "dropped_oneline=" >> "$GITHUB_OUTPUT"
fi
- name: Install dependencies and build
if: steps.check.outputs.next_exists == 'true'
run: npm ci --silent && npm run build:lib
- name: Sync next's version to main's released version
if: steps.check.outputs.next_exists == 'true'
run: |

View File

@@ -5,7 +5,7 @@ on:
types: [opened]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:

View File

@@ -319,7 +319,7 @@ jobs:
needs: [validate-version, install-smoke-rc]
if: inputs.action == 'rc'
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 30
permissions:
contents: write
id-token: write
@@ -507,7 +507,10 @@ jobs:
needs: [validate-version, install-smoke-finalize]
if: inputs.action == 'finalize'
runs-on: ubuntu-latest
timeout-minutes: 10
# Matches the rc job's budget: `npm ci` + `npm run test:coverage:unit` now
# exceeds 10m as the unit suite grows, so a 10m cap cancels the job mid-test
# before tag/publish. 30m gives the same headroom rc already relies on.
timeout-minutes: 30
permissions:
contents: write
pull-requests: write

29
.gitignore vendored
View File

@@ -67,6 +67,22 @@ build/
# by `npm run build:lib`). Source of truth is src/; these are emitted, never edited.
# Published via prepublishOnly; built before test via pretest. Grows as modules migrate.
/tsconfig.build.tsbuildinfo
/gsd-core/bin/lib/host-integration.cjs
/gsd-core/bin/lib/host-integration-sdk.cjs
/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs
/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs
/gsd-core/bin/lib/handshake-serialized.cjs
/gsd-core/bin/lib/install-effort-resolver.cjs
/gsd-core/bin/lib/install-engine.cjs
/gsd-core/bin/lib/embedding-adapter.cjs
/gsd-core/bin/lib/adapter-declarative.cjs
/gsd-core/bin/lib/adapter-imperative.cjs
/gsd-core/bin/lib/model-adapter.cjs
/gsd-core/bin/lib/hook-bus.cjs
/gsd-core/bin/lib/state-io.cjs
/gsd-core/bin/lib/mcp-server.cjs
/gsd-core/bin/lib/external-descriptor-trust.cjs
/gsd-core/bin/lib/cli-skew-check.cjs
/gsd-core/bin/lib/capability-loader.cjs
/gsd-core/bin/lib/capability-source.cjs
/gsd-core/bin/lib/capability-ledger.cjs
@@ -75,6 +91,8 @@ build/
/gsd-core/bin/lib/capability-consent.cjs
/gsd-core/bin/lib/capability-lock.cjs
/gsd-core/bin/lib/markdown-sectionizer.cjs
/gsd-core/bin/lib/markdown-table.cjs
/gsd-core/bin/lib/write-set.cjs
/gsd-core/bin/lib/resolution.cjs
/gsd-core/bin/lib/research-store.cjs
/gsd-core/bin/lib/research-provider.cjs
@@ -83,7 +101,9 @@ build/
/gsd-core/bin/lib/plan-drift-guard.cjs
/gsd-core/bin/lib/edge-probe.cjs
/gsd-core/bin/lib/probe-core.cjs
/gsd-core/bin/lib/spec-section.cjs
/gsd-core/bin/lib/prohibition-enforcement.cjs
/gsd-core/bin/lib/ui-consideration-probe.cjs
/gsd-core/bin/lib/config-types.cjs
/gsd-core/bin/lib/cli-exit.cjs
/gsd-core/bin/lib/code-review-flags.cjs
@@ -99,6 +119,7 @@ build/
/gsd-core/bin/lib/installer-migration-report.cjs
/gsd-core/bin/lib/prompt-budget.cjs
/gsd-core/bin/lib/secrets.cjs
/gsd-core/bin/lib/smart-entry.cjs
/gsd-core/bin/lib/phase-lifecycle.cjs
/gsd-core/bin/lib/workstream-name-policy.cjs
/gsd-core/bin/lib/decisions.cjs
@@ -151,6 +172,7 @@ build/
/gsd-core/bin/lib/core-utils.cjs
/gsd-core/bin/lib/io.cjs
/gsd-core/bin/lib/phase-id.cjs
/gsd-core/bin/lib/normalize-test-command.cjs
/gsd-core/bin/lib/config-loader.cjs
/gsd-core/bin/lib/model-resolver.cjs
/gsd-core/bin/lib/loop-resolver.cjs
@@ -165,6 +187,7 @@ build/
/gsd-core/bin/lib/phase-command-router.cjs
/gsd-core/bin/lib/surface.cjs
/gsd-core/bin/lib/gap-checker.cjs
/gsd-core/bin/lib/gate-predicate-evaluator.cjs
/gsd-core/bin/lib/docs.cjs
/gsd-core/bin/lib/check-command-router.cjs
/gsd-core/bin/lib/frontmatter.cjs
@@ -177,6 +200,7 @@ build/
/gsd-core/bin/lib/eval.cjs
/gsd-core/bin/lib/eval-command-router.cjs
/gsd-core/bin/lib/init-command-router.cjs
/gsd-core/bin/lib/onboard-projection.cjs
/gsd-core/bin/lib/agent-command-router.cjs
/gsd-core/bin/lib/agent-install-check.cjs
/gsd-core/bin/lib/task-command-router.cjs
@@ -215,6 +239,7 @@ tmp/
# MemPalace per-project files (issue #185)
mempalace.yaml
entities.json
.planning/.mempalace-stage/
# Local scratch + Claude-test artifacts
.scratch/
@@ -231,3 +256,7 @@ reports/mutation/
# Local Crabbox machine configuration
.crabbox.yaml
.crabbox.local.yaml
# Memtrace local daemon/runtime state (per-machine; never committed)
.memdb/
.memtrace/

731
.kilo/plugins/gsd-core.js Normal file
View File

@@ -0,0 +1,731 @@
/**
* GSD plugin for OpenCode.ai (CommonJS)
*
* Architecture: SUBPROCESS REUSE. Instead of re-implementing hook logic inside
* the plugin, this file is a thin adapter that spawns the existing Claude Code
* hook scripts under hooks/ as child processes. The hooks speak a stable
* protocol (JSON on stdin, JSON + exit code on stdout); this adapter:
* 1. Translates OpenCode plugin events into Claude Code hook payloads
* 2. Spawns `node <HOOKS_DIR>/<hook>.js` with the payload on stdin
* 3. Translates hook output back into OpenCode semantics
* - block → throw Error (OpenCode returns the error to the model)
* - advisory → output.metadata + console.error (best-effort surfacing)
*
* Namespace conversion (/gsd:xxx → /gsd-xxx) reuses scripts/fix-slash-commands.cjs
* via require(), keeping the single source of truth.
*
* ── Two distribution shapes, one adapter (issue #1914) ─────────────────────
* This single file serves both distribution paths, distinguished at load time
* by REPO_ROOT (path.resolve(__dirname, "../..")):
*
* • Option 1 — file copy (the supported GSD path). `bin/install.js` copies
* this file to <opencodeConfigDir>/plugins/gsd-core.js, so REPO_ROOT is the
* OpenCode config dir. GSD's own install already stages `hooks/*.js` and
* `gsd-core/` there (ADR-857 skips hook *registration* for OpenCode, not the
* file copy), so the hook bridge and content rewriting resolve natively.
* Commands/agents/skills are ALREADY registered by GSD's native file copy in
* this mode, so the plugin's own config-hook registration is redundant and is
* SKIPPED (see IS_PACKAGE_TREE) to avoid double-registration.
*
* • Option 2 — package / git-spec. When loaded from the package tree (npm
* `main`, or an OpenCode git-spec install), REPO_ROOT is the package root and
* the source layout (commands/gsd/, agents/, skills/) is present. Here the
* plugin IS the sole registrar, so it registers commands/agents/skills too.
*
* IS_PACKAGE_TREE keys off the presence of the SOURCE command layout
* (commands/gsd/), which only exists in the package tree — never in an installed
* config dir (that uses the flattened command/ layout). The hook bridge and
* Read-time content rewriting run in BOTH modes; only the config-hook
* registration of commands/agents/skills is gated.
*
* Runtime-specific hooks are deliberately excluded:
* - gsd-statusline.js / gsd-update-banner.js (Claude Code statusline)
* - gsd-cursor-*.js (Cursor-specific)
* - *.sh scripts (invoked directly by commands/agents, not hook events)
*/
"use strict";
const path = require("path");
const fs = require("fs");
const os = require("os");
const { spawnSync } = require("child_process");
// Resolve REPO_ROOT to the directory that actually holds the GSD payload
// (hooks/ + gsd-core/). This must work across three physical layouts because a
// single adapter file serves both distribution shapes (see header):
// • package/git-spec tree: <root>/.opencode/plugins/gsd-core.js → <root>
// • global file-copy: ~/.config/opencode/plugins/gsd-core.js → ~/.config/opencode
// • local file-copy: <proj>/.opencode/plugins/gsd-core.js → <proj>/.opencode
// A fixed "../.." only works for the first; the copied layouts sit one level
// shallower. Walking up to the first ancestor containing BOTH payload markers
// resolves all three deterministically. Falls back to the package-tree
// assumption ("../..") if no ancestor matches (keeps graceful degradation).
function resolveRepoRoot(startDir) {
let dir = startDir;
for (let i = 0; i < 6; i++) {
if (
fs.existsSync(path.join(dir, "hooks")) &&
fs.existsSync(path.join(dir, "gsd-core"))
) {
return dir;
}
const parent = path.dirname(dir);
if (parent === dir) break; // filesystem root
dir = parent;
}
// No ancestor carried both markers (broken/partial layout — the plugin can't
// function regardless). Fall back to the package-tree assumption ("../.."),
// matching the historical fixed-depth behavior and the .opencode/plugins/
// source layout.
return path.resolve(startDir, "../..");
}
// CJS: __dirname is a global, no need to derive from import.meta.url
const REPO_ROOT = resolveRepoRoot(__dirname);
const HOOKS_DIR = path.join(REPO_ROOT, "hooks");
const COMMANDS = path.join(REPO_ROOT, "commands", "gsd");
const AGENTS = path.join(REPO_ROOT, "agents");
const SKILLS = path.join(REPO_ROOT, "skills");
const GSD_CORE = path.join(REPO_ROOT, "gsd-core");
// True only when loaded from the package/source tree (Option 2), detected by the
// presence of the SOURCE command layout (commands/gsd/). In an installed OpenCode
// config dir (Option 1) this directory is absent — the flattened command/ layout
// is used instead — so the plugin skips its own command/agent/skill registration
// and lets GSD's native file copy own that surface (avoids double-registration).
const IS_PACKAGE_TREE = fs.existsSync(COMMANDS);
// ---------------------------------------------------------------------------
// Namespace conversion — reuse the single source of truth
// ---------------------------------------------------------------------------
let _cmdNames = null;
let _transformFn = null;
/**
* Lazily load scripts/fix-slash-commands.cjs and cache the transform function
* + command name list. Returns null if the module is unavailable (the plugin
* still works, just without namespace conversion).
*/
function getNamespaceConverter() {
if (_transformFn) return _transformFn;
try {
const mod = require(
path.join(REPO_ROOT, "scripts", "fix-slash-commands.cjs"),
);
_cmdNames = mod.readCmdNames();
_transformFn = mod.transformContentToHyphen;
return _transformFn;
} catch {
return null;
}
}
// ---------------------------------------------------------------------------
// Session state — tracked across plugin hook invocations
// ---------------------------------------------------------------------------
let currentSessionId = null;
let currentCwd = process.cwd();
// ---------------------------------------------------------------------------
// Tool name / argument mapping (OpenCode ↔ Claude Code)
// ---------------------------------------------------------------------------
const TOOL_NAME_MAP = {
read: "Read",
write: "Write",
edit: "Edit",
apply_patch: "MultiEdit",
multi_edit: "MultiEdit",
bash: "Bash",
webfetch: "WebFetch",
web_search: "WebSearch",
websearch: "WebSearch",
task: "Task",
subagent: "Task",
};
function mapToolName(tool) {
if (!tool) return "";
return TOOL_NAME_MAP[String(tool).toLowerCase()] || tool;
}
// Build a Claude-style `tool_input` object from OpenCode's `output.args`.
function mapToolInput(args) {
const input = {};
if (!args || typeof args !== "object") return input;
// File-path keys (OpenCode uses filePath/path; Claude uses file_path)
const filePath = args.filePath || args.path || args.file_path;
if (filePath) input.file_path = filePath;
// Content for Write
if (args.content !== undefined) input.content = args.content;
// Edit patch fields
if (args.new_string !== undefined) input.new_string = args.new_string;
if (args.newString !== undefined) input.new_string = args.newString;
if (args.old_string !== undefined) input.old_string = args.old_string;
if (args.oldString !== undefined) input.old_string = args.oldString;
// Bash command
if (args.command !== undefined) input.command = args.command;
// Web
if (args.url !== undefined) input.url = args.url;
if (args.query !== undefined) input.query = args.query;
return input;
}
// ---------------------------------------------------------------------------
// Hook subprocess runner
// ---------------------------------------------------------------------------
/**
* Spawn a Claude Code hook script and pipe a JSON payload to its stdin.
*
* Hooks follow the convention:
* - stdout: JSON object (decision/advisory) or empty
* - exit 0: allow (with optional advisory JSON on stdout)
* - exit 2: block (Claude convention; reason in stdout JSON)
* - any error: exit 0 silently (hooks swallow their own errors)
*
* @param {string} hookFile filename under hooks/, e.g. "gsd-prompt-guard.js"
* @param {object} payload stdin JSON (hook_event_name, tool_name, ...)
* @param {object} [opts]
* @param {number} [opts.timeout=8000] spawn timeout in ms
* @param {string} [opts.cwd] working directory for the child
* @returns {{ stdout: string, exitCode: number, timedOut: boolean }}
*/
function runHook(hookFile, payload, opts = {}) {
const hookPath = path.join(HOOKS_DIR, hookFile);
if (!fs.existsSync(hookPath)) {
return { stdout: "", exitCode: 0, timedOut: false };
}
const timeout = opts.timeout ?? 8000;
let result;
try {
result = spawnSync(process.execPath, [hookPath], {
input: JSON.stringify(payload),
encoding: "utf8",
timeout,
cwd: opts.cwd || currentCwd,
windowsHide: true,
});
} catch {
// Spawn failure — never break the tool call
return { stdout: "", exitCode: 0, timedOut: false };
}
const stdout = (result.stdout || "").trim();
const exitCode = result.status == null ? 0 : result.status;
return { stdout, exitCode, timedOut: result.signal === "SIGTERM" };
}
// ---------------------------------------------------------------------------
// Hook output translation → OpenCode semantics
// ---------------------------------------------------------------------------
/**
* Parse a hook's stdout and apply its effect to the OpenCode output object.
*
* - Block → throw Error(parsed.reason) so OpenCode aborts the tool call
* - Advisory→ append to output.metadata._gsdAdvisory[] and log to stderr
* - Silent → no-op
*
* @param {{ stdout: string, exitCode: number }} hookResult
* @param {object} [output] OpenCode mutable output object (optional)
*/
function handleHookResult(hookResult, output) {
const { stdout, exitCode } = hookResult;
if (!stdout && exitCode !== 2) return; // silent allow
let parsed = null;
if (stdout) {
try {
parsed = JSON.parse(stdout);
} catch {
// Non-JSON stdout (e.g. a stray log) — treat exit 2 as hard block, else allow
}
}
// Block: explicit decision OR Claude exit-code-2 convention
const isBlock = exitCode === 2 || (parsed && parsed.decision === "block");
if (isBlock) {
const reason =
(parsed && parsed.reason) || "Blocked by GSD hook (no reason provided).";
throw new Error(reason);
}
// Advisory: inject additionalContext into metadata + log
const advisory =
parsed &&
parsed.hookSpecificOutput &&
parsed.hookSpecificOutput.additionalContext;
if (advisory) {
if (output) {
output.metadata = output.metadata || {};
// Accumulate: a single tool call can run several advisory hooks in
// sequence (prompt guard, read guard, worktree guard, workflow guard).
// Storing a scalar would let a later advisory clobber an earlier one, so
// collect them all.
if (!Array.isArray(output.metadata._gsdAdvisory)) {
output.metadata._gsdAdvisory = [];
}
output.metadata._gsdAdvisory.push(advisory);
}
// Best-effort visibility when metadata isn't surfaced to the model
console.error(advisory);
}
}
// ---------------------------------------------------------------------------
// Frontmatter helpers (for config registration)
// ---------------------------------------------------------------------------
function parseFrontmatter(content) {
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!m) return { frontmatter: {}, body: content };
const fm = {};
for (const line of m[1].split("\n")) {
const i = line.indexOf(":");
if (i > 0) {
let v = line.slice(i + 1).trim();
if (v.startsWith('"') && v.endsWith('"')) v = v.slice(1, -1);
fm[line.slice(0, i).trim()] = v;
}
}
return { frontmatter: fm, body: m[2] };
}
// Rewrite @~/.claude/ includes to point at the repo root.
// Also applies /gsd:xxx → /gsd-xxx namespace conversion via the shared
// transform from scripts/fix-slash-commands.cjs (single source of truth).
function rewriteRefs(content) {
let out = content.replace(/@~\/\.claude\//g, `@${REPO_ROOT}/`);
const transform = getNamespaceConverter();
if (transform && _cmdNames && _cmdNames.length) {
out = transform(out, _cmdNames);
}
return out;
}
function loadDir(dir, keyFn, valFn) {
const result = {};
if (!fs.existsSync(dir)) return result;
for (const f of fs.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
const raw = fs.readFileSync(path.join(dir, f), "utf8");
const { frontmatter, body } = parseFrontmatter(raw);
result[keyFn(f)] = valFn(body, frontmatter, f);
}
return result;
}
// ---------------------------------------------------------------------------
// Runtime content transform — for Read tool results on GSD-managed files
// ---------------------------------------------------------------------------
// Directories whose .md files may contain ~/.claude/ paths and gsd: namespace
// refs. When the model reads these via the Read tool, we transparently rewrite
// both so OpenCode sees correct paths and hyphen-form command names.
const GSD_MANAGED_DIRS = [
path.join(GSD_CORE, "workflows"),
path.join(GSD_CORE, "references"),
path.join(GSD_CORE, "templates"),
path.join(GSD_CORE, "contexts"),
COMMANDS,
AGENTS,
SKILLS,
];
function isGsdManagedFile(filePath) {
if (!filePath) return false;
const resolved = path.resolve(filePath);
return GSD_MANAGED_DIRS.some(
(dir) => resolved === dir || resolved.startsWith(dir + path.sep),
);
}
// Rewrite content for OpenCode consumption:
// 1. @-include paths: @~/.claude/ → @<REPO_ROOT>/
// 2. plain-text paths: ~/.claude/gsd-core/ → <GSD_CORE>/
// 3. namespace: gsd:xxx → gsd-xxx (via fix-slash-commands.cjs)
function rewriteContent(content) {
let out = content;
out = out.replace(/@~\/\.claude\//g, `@${REPO_ROOT}/`);
out = out.replace(/~\/\.claude\/gsd-core\//g, `${GSD_CORE}/`);
const transform = getNamespaceConverter();
if (transform && _cmdNames && _cmdNames.length) {
out = transform(out, _cmdNames);
}
return out;
}
// ---------------------------------------------------------------------------
// Skills cache — copy SKILL.md files with rewritten @-include paths
// ---------------------------------------------------------------------------
//
// OpenCode's skill loader reads SKILL.md files directly from disk and resolves
// @-includes internally — this bypasses our tool.execute hooks. To make
// @~/.claude/gsd-core/... includes resolve, we copy all SKILL.md files to a
// cache directory with paths rewritten to the actual GSD_CORE location.
//
// Only used in package-tree mode (Option 2). In an installed OpenCode config
// dir (Option 1) skills are already staged + registered by GSD's native file
// copy, so we never register skills from the plugin (see IS_PACKAGE_TREE).
const SKILLS_CACHE = path.join(
os.homedir(),
".cache",
"opencode",
"gsd-skills",
);
function prepareSkillsCache() {
if (!fs.existsSync(SKILLS)) return null;
fs.mkdirSync(SKILLS_CACHE, { recursive: true });
for (const dir of fs.readdirSync(SKILLS)) {
const srcFile = path.join(SKILLS, dir, "SKILL.md");
if (!fs.existsSync(srcFile)) continue;
const raw = fs.readFileSync(srcFile, "utf8");
// Rewrite @-include paths only; namespace conversion is handled at
// Read-time via tool.execute.after for workflow/reference files.
const rewritten = raw
.replace(/@~\/\.claude\/gsd-core\//g, `@${GSD_CORE}/`)
.replace(/~\/\.claude\/gsd-core\//g, `${GSD_CORE}/`);
const destDir = path.join(SKILLS_CACHE, dir);
fs.mkdirSync(destDir, { recursive: true });
fs.writeFileSync(path.join(destDir, "SKILL.md"), rewritten);
}
return SKILLS_CACHE;
}
// ===========================================================================
// Plugin entry
// ===========================================================================
const GsdCorePlugin = async ({ directory } = {}) => {
if (directory) currentCwd = directory;
return {
// ── Config: register commands / agents / skills paths ──────────────
// Only in package-tree mode (Option 2). In an installed config dir
// (Option 1) GSD's native file copy already registered these, so the
// plugin stays out of registration to avoid double-registering.
config: async (config) => {
if (!IS_PACKAGE_TREE) return;
// Commands (commands/gsd/*.md → gsd-<name>)
config.command = config.command || {};
const cmds = loadDir(
COMMANDS,
(f) => "gsd-" + f.slice(0, -3),
(body, fm, name) => ({
template: rewriteRefs(body.trim()),
description: fm.description || `GSD ${name.slice(0, -3)} command`,
}),
);
for (const [k, v] of Object.entries(cmds)) {
if (!config.command[k]) config.command[k] = v;
}
// Agents (agents/*.md)
config.agent = config.agent || {};
const agents = loadDir(
AGENTS,
(f) => f.slice(0, -3),
(body, fm, name) => ({
prompt: rewriteRefs(body.trim()),
description: fm.description || `GSD ${name.slice(0, -3)} agent`,
mode: fm.mode || "subagent",
}),
);
for (const [k, v] of Object.entries(agents)) {
if (!config.agent[k]) config.agent[k] = v;
}
// Skills — copy SKILL.md files to cache with rewritten @-include paths,
// then register the cache directory. OpenCode's skill loader reads
// SKILL.md from disk and resolves @-includes internally (bypassing our
// tool.execute hooks), so we must pre-process the files.
const skillsCache = prepareSkillsCache();
config.skills = config.skills || {};
config.skills.paths = config.skills.paths || [];
const skillsPath = skillsCache || SKILLS;
if (!config.skills.paths.includes(skillsPath)) {
config.skills.paths.push(skillsPath);
}
},
// ── shell.env ───────────────────────────────────────────────────────
"shell.env": async (_input, output) => {
output.env = output.env || {};
output.env.GSD_DIR = GSD_CORE;
},
// ── tool.execute.before — PreToolUse hooks ─────────────────────────
"tool.execute.before": async (input, output) => {
const claudeTool = mapToolName(input.tool);
const toolInput = mapToolInput(output.args || {});
const cwd = currentCwd;
// 0. Read path rewrite — redirect ~/.claude/gsd-core/ to actual GSD_CORE
// so the model can read workflow/reference/template files that SKILL.md
// and command templates reference via the canonical Claude path.
if (claudeTool === "Read" && toolInput.file_path) {
const original = toolInput.file_path;
const rewritten = original
.replace(/^~\/\.claude\/gsd-core\//, GSD_CORE + "/")
.replace(/(?:.*)\/\.claude\/gsd-core\//, GSD_CORE + "/");
if (rewritten !== original) {
const args = output.args || {};
if (args.filePath) args.filePath = rewritten;
else if (args.path) args.path = rewritten;
else if (args.file_path) args.file_path = rewritten;
else args.filePath = rewritten;
}
}
const basePayload = {
hook_event_name: "PreToolUse",
cwd,
};
// NOTE: session_id intentionally omitted for PreToolUse hooks.
// gsd-read-guard.js treats a non-empty session_id as a Claude Code
// session and skips its advisory. On OpenCode we WANT the advisory.
const prePayload = (overrides = {}) => ({
...basePayload,
tool_name: claudeTool,
tool_input: toolInput,
...overrides,
});
const isWriteLike = ["Write", "Edit", "MultiEdit"].includes(claudeTool);
// 1. gsd-prompt-guard.js — injection scan on .planning/ writes
if (claudeTool === "Write" || claudeTool === "Edit") {
const r = runHook("gsd-prompt-guard.js", prePayload());
handleHookResult(r, output);
}
// 2. gsd-read-guard.js — read-before-edit advisory
if (claudeTool === "Write" || claudeTool === "Edit") {
const r = runHook("gsd-read-guard.js", prePayload());
handleHookResult(r, output);
}
// 3. gsd-worktree-path-guard.js — hard-block edits outside worktree
if (isWriteLike) {
const r = runHook("gsd-worktree-path-guard.js", prePayload());
handleHookResult(r, output);
}
// 4. gsd-workflow-guard.js — workflow advisory + git-force-add block
// (covers Write/Edit/MultiEdit AND Bash force-add detection)
if (isWriteLike || claudeTool === "Bash") {
const r = runHook("gsd-workflow-guard.js", prePayload());
handleHookResult(r, output);
}
},
// ── tool.execute.after — PostToolUse hooks ─────────────────────────
"tool.execute.after": async (input, output) => {
const claudeTool = mapToolName(input.tool);
// NOTE: In the `after` hook, `args` lives on `input` (not `output`).
// The `output` object only has { title, output, metadata }.
const toolInput = mapToolInput(input.args || {});
const cwd = currentCwd;
// GSD content transform — rewrite paths + namespace in Read results
// BEFORE injection scanning so the scanner sees the final content.
if (
claudeTool === "Read" &&
output.output &&
isGsdManagedFile(toolInput.file_path)
) {
const content =
typeof output.output === "string"
? output.output
: String(output.output);
output.output = rewriteContent(content);
}
// gsd-read-injection-scanner.js — scan Read/WebFetch/WebSearch results
if (
claudeTool === "Read" ||
claudeTool === "WebFetch" ||
claudeTool === "WebSearch"
) {
const payload = {
hook_event_name: "PostToolUse",
tool_name: claudeTool,
tool_input: toolInput,
tool_response: output.output,
cwd,
};
const r = runHook("gsd-read-injection-scanner.js", payload);
handleHookResult(r, output);
return;
}
// gsd-context-monitor.js — context usage warnings (Bash/Edit/Write/Task/...)
// Only meaningful when a session_id is tracked (writes metrics sentinel).
if (currentSessionId) {
const payload = {
hook_event_name: "PostToolUse",
tool_name: claudeTool,
tool_input: toolInput,
session_id: currentSessionId,
cwd,
};
const r = runHook("gsd-context-monitor.js", payload);
handleHookResult(r, output);
}
},
// ── experimental.session.compacting — PreCompact ───────────────────
"experimental.session.compacting": async (_input, output) => {
if (!currentSessionId) return;
const payload = {
hook_event_name: "PreCompact",
session_id: currentSessionId,
cwd: currentCwd,
};
const r = runHook("gsd-context-monitor.js", payload);
handleHookResult(r, output);
// Also inject a GSD compaction breadcrumb (mirrors the original plugin)
output.context = output.context || [];
output.context.push(
`[GSD] Active session: ${currentSessionId}. Preserve any in-flight phase/plan state.`,
);
},
// ── General event subscriptions ─────────────────────────────────────
event: async ({ event }) => {
// session.created → SessionStart hooks
if (event.type === "session.created") {
// Track session for context-monitor payloads.
// SDK type EventSessionCreated: { properties: { info: Session } }
// Session has `id` and `directory` (not `cwd`).
const info = event.properties?.info;
currentSessionId =
info?.id || event.sessionID || event.session_id || null;
if (info?.directory) currentCwd = info.directory;
// gsd-ensure-canonical-path.js — no stdin dependency; silent
runHook("gsd-ensure-canonical-path.js", {
hook_event_name: "SessionStart",
session_id: currentSessionId,
cwd: currentCwd,
});
// gsd-check-update.js — spawns its own background worker; no stdin
runHook("gsd-check-update.js", {
hook_event_name: "SessionStart",
session_id: currentSessionId,
cwd: currentCwd,
});
return;
}
// file.edited → FileChanged hook (config.json reload)
if (event.type === "file.edited") {
// SDK type EventFileEdited: { properties: { file: string } }
const filePath = event.properties?.file || event.filePath || "";
if (!filePath.endsWith("config.json")) return;
const cwd = event.properties?.cwd || currentCwd;
const expected = path.join(cwd, ".planning", "config.json");
if (path.resolve(filePath) !== path.resolve(expected)) return;
const payload = {
hook_event_name: "FileChanged",
file_path: filePath,
event: "change",
cwd,
};
const r = runHook("gsd-config-reload.js", payload);
// Advisory-only (additionalContext); surface to logs
handleHookResult(r);
return;
}
// session.idle ↔ Claude Stop lifecycle point (#1682 Slice 1b/c).
// OpenCode fires session.idle when the run quiesces. GSD maps it to the
// Stop equivalent — the opencode-subset lifecycle peer of compaction
// (compaction preserves state across context-window summarization; idle
// marks end-of-turn). No-op sentinel today (GSD state is already
// persisted to .planning/), but it MUST be recognized so the declared
// opencode-subset surface is fully wired and a future Stop-class hook can
// attach without a plugin change.
if (event.type === "session.idle") {
return;
}
// permission.asked / permission.replied — OpenCode permission lifecycle
// (#2087, opencode.ai/docs/plugins). GSD gates tool INPUTS at
// tool.execute.before (read-guard, injection-scanner); the permission
// grant/deny decision itself carries no GSD workflow-phase contribution,
// so these are recognized sentinels — wired so a future permission-aware
// gate can attach without a plugin change (the engine owns phase
// sequencing; this host bus is session/tool/permission-scoped, never
// phase-scoped — ADR-1239 §OpenCode).
if (event.type === "permission.asked" || event.type === "permission.replied") {
return;
}
// session.error — OpenCode session-error lifecycle point (#2087). No GSD
// hook fires here today (loop state is already persisted to .planning/);
// recognized so the declared extension-event surface is fully wired and a
// future error-class hook can attach without a plugin change.
if (event.type === "session.error") {
return;
}
},
};
};
// Export shape — verified against OpenCode's plugin loader source
// (packages/opencode/src/plugin). The loader imports this module and runs
// `for (const entry of Object.values(mod)) { getServerPlugin(entry) }`, where
// `getServerPlugin` accepts a bare function OR an object exposing a `.server`
// function, and THROWS `TypeError("Plugin export is not a function")` for
// anything else. So EVERY enumerable value the loader iterates must be a
// function or an object with `.server`.
//
// The subtlety: depending on how OpenCode's runtime (Node or Bun) imports a
// CommonJS file, `mod` may be the raw `module.exports` OR an ESM namespace of
// the form `{ default: module.exports, ...syntheticNamedExports }`. A plain
// `module.exports = { id: "gsd-core", server }` literal risks a string `id`
// appearing in `Object.values(mod)` (as a raw property, or as a lexer-
// synthesized named export) — which would trip the throw. Two defenses:
// 1. `id` is defined NON-ENUMERABLE, so it never appears in Object.values yet
// stays readable (via property access) for the loader's identity/dedup.
// 2. `module.exports` is assigned from a VARIABLE (not an object literal), so
// cjs-module-lexer cannot statically synthesize named exports from it —
// only `default` is exposed under ESM/Bun interop.
// Result: raw-CJS `Object.values` = `[server]`; ESM `Object.values` =
// `[{server, <id non-enum>}]` — both fully extractable. Test-only helpers hang
// off the `server` FUNCTION (`server._internals`), never as a sibling export.
GsdCorePlugin._internals = {
REPO_ROOT,
IS_PACKAGE_TREE,
mapToolName,
mapToolInput,
parseFrontmatter,
rewriteContent,
isGsdManagedFile,
handleHookResult,
GsdCorePlugin,
};
const gsdCorePluginExport = { server: GsdCorePlugin };
Object.defineProperty(gsdCorePluginExport, "id", {
value: "gsd-core",
enumerable: false,
writable: false,
configurable: false,
});
module.exports = gsdCorePluginExport;

View File

@@ -0,0 +1,731 @@
/**
* GSD plugin for OpenCode.ai (CommonJS)
*
* Architecture: SUBPROCESS REUSE. Instead of re-implementing hook logic inside
* the plugin, this file is a thin adapter that spawns the existing Claude Code
* hook scripts under hooks/ as child processes. The hooks speak a stable
* protocol (JSON on stdin, JSON + exit code on stdout); this adapter:
* 1. Translates OpenCode plugin events into Claude Code hook payloads
* 2. Spawns `node <HOOKS_DIR>/<hook>.js` with the payload on stdin
* 3. Translates hook output back into OpenCode semantics
* - block → throw Error (OpenCode returns the error to the model)
* - advisory → output.metadata + console.error (best-effort surfacing)
*
* Namespace conversion (/gsd:xxx → /gsd-xxx) reuses scripts/fix-slash-commands.cjs
* via require(), keeping the single source of truth.
*
* ── Two distribution shapes, one adapter (issue #1914) ─────────────────────
* This single file serves both distribution paths, distinguished at load time
* by REPO_ROOT (path.resolve(__dirname, "../..")):
*
* • Option 1 — file copy (the supported GSD path). `bin/install.js` copies
* this file to <opencodeConfigDir>/plugins/gsd-core.js, so REPO_ROOT is the
* OpenCode config dir. GSD's own install already stages `hooks/*.js` and
* `gsd-core/` there (ADR-857 skips hook *registration* for OpenCode, not the
* file copy), so the hook bridge and content rewriting resolve natively.
* Commands/agents/skills are ALREADY registered by GSD's native file copy in
* this mode, so the plugin's own config-hook registration is redundant and is
* SKIPPED (see IS_PACKAGE_TREE) to avoid double-registration.
*
* • Option 2 — package / git-spec. When loaded from the package tree (npm
* `main`, or an OpenCode git-spec install), REPO_ROOT is the package root and
* the source layout (commands/gsd/, agents/, skills/) is present. Here the
* plugin IS the sole registrar, so it registers commands/agents/skills too.
*
* IS_PACKAGE_TREE keys off the presence of the SOURCE command layout
* (commands/gsd/), which only exists in the package tree — never in an installed
* config dir (that uses the flattened command/ layout). The hook bridge and
* Read-time content rewriting run in BOTH modes; only the config-hook
* registration of commands/agents/skills is gated.
*
* Runtime-specific hooks are deliberately excluded:
* - gsd-statusline.js / gsd-update-banner.js (Claude Code statusline)
* - gsd-cursor-*.js (Cursor-specific)
* - *.sh scripts (invoked directly by commands/agents, not hook events)
*/
"use strict";
const path = require("path");
const fs = require("fs");
const os = require("os");
const { spawnSync } = require("child_process");
// Resolve REPO_ROOT to the directory that actually holds the GSD payload
// (hooks/ + gsd-core/). This must work across three physical layouts because a
// single adapter file serves both distribution shapes (see header):
// • package/git-spec tree: <root>/.opencode/plugins/gsd-core.js → <root>
// • global file-copy: ~/.config/opencode/plugins/gsd-core.js → ~/.config/opencode
// • local file-copy: <proj>/.opencode/plugins/gsd-core.js → <proj>/.opencode
// A fixed "../.." only works for the first; the copied layouts sit one level
// shallower. Walking up to the first ancestor containing BOTH payload markers
// resolves all three deterministically. Falls back to the package-tree
// assumption ("../..") if no ancestor matches (keeps graceful degradation).
function resolveRepoRoot(startDir) {
let dir = startDir;
for (let i = 0; i < 6; i++) {
if (
fs.existsSync(path.join(dir, "hooks")) &&
fs.existsSync(path.join(dir, "gsd-core"))
) {
return dir;
}
const parent = path.dirname(dir);
if (parent === dir) break; // filesystem root
dir = parent;
}
// No ancestor carried both markers (broken/partial layout — the plugin can't
// function regardless). Fall back to the package-tree assumption ("../.."),
// matching the historical fixed-depth behavior and the .opencode/plugins/
// source layout.
return path.resolve(startDir, "../..");
}
// CJS: __dirname is a global, no need to derive from import.meta.url
const REPO_ROOT = resolveRepoRoot(__dirname);
const HOOKS_DIR = path.join(REPO_ROOT, "hooks");
const COMMANDS = path.join(REPO_ROOT, "commands", "gsd");
const AGENTS = path.join(REPO_ROOT, "agents");
const SKILLS = path.join(REPO_ROOT, "skills");
const GSD_CORE = path.join(REPO_ROOT, "gsd-core");
// True only when loaded from the package/source tree (Option 2), detected by the
// presence of the SOURCE command layout (commands/gsd/). In an installed OpenCode
// config dir (Option 1) this directory is absent — the flattened command/ layout
// is used instead — so the plugin skips its own command/agent/skill registration
// and lets GSD's native file copy own that surface (avoids double-registration).
const IS_PACKAGE_TREE = fs.existsSync(COMMANDS);
// ---------------------------------------------------------------------------
// Namespace conversion — reuse the single source of truth
// ---------------------------------------------------------------------------
let _cmdNames = null;
let _transformFn = null;
/**
* Lazily load scripts/fix-slash-commands.cjs and cache the transform function
* + command name list. Returns null if the module is unavailable (the plugin
* still works, just without namespace conversion).
*/
function getNamespaceConverter() {
if (_transformFn) return _transformFn;
try {
const mod = require(
path.join(REPO_ROOT, "scripts", "fix-slash-commands.cjs"),
);
_cmdNames = mod.readCmdNames();
_transformFn = mod.transformContentToHyphen;
return _transformFn;
} catch {
return null;
}
}
// ---------------------------------------------------------------------------
// Session state — tracked across plugin hook invocations
// ---------------------------------------------------------------------------
let currentSessionId = null;
let currentCwd = process.cwd();
// ---------------------------------------------------------------------------
// Tool name / argument mapping (OpenCode ↔ Claude Code)
// ---------------------------------------------------------------------------
const TOOL_NAME_MAP = {
read: "Read",
write: "Write",
edit: "Edit",
apply_patch: "MultiEdit",
multi_edit: "MultiEdit",
bash: "Bash",
webfetch: "WebFetch",
web_search: "WebSearch",
websearch: "WebSearch",
task: "Task",
subagent: "Task",
};
function mapToolName(tool) {
if (!tool) return "";
return TOOL_NAME_MAP[String(tool).toLowerCase()] || tool;
}
// Build a Claude-style `tool_input` object from OpenCode's `output.args`.
function mapToolInput(args) {
const input = {};
if (!args || typeof args !== "object") return input;
// File-path keys (OpenCode uses filePath/path; Claude uses file_path)
const filePath = args.filePath || args.path || args.file_path;
if (filePath) input.file_path = filePath;
// Content for Write
if (args.content !== undefined) input.content = args.content;
// Edit patch fields
if (args.new_string !== undefined) input.new_string = args.new_string;
if (args.newString !== undefined) input.new_string = args.newString;
if (args.old_string !== undefined) input.old_string = args.old_string;
if (args.oldString !== undefined) input.old_string = args.oldString;
// Bash command
if (args.command !== undefined) input.command = args.command;
// Web
if (args.url !== undefined) input.url = args.url;
if (args.query !== undefined) input.query = args.query;
return input;
}
// ---------------------------------------------------------------------------
// Hook subprocess runner
// ---------------------------------------------------------------------------
/**
* Spawn a Claude Code hook script and pipe a JSON payload to its stdin.
*
* Hooks follow the convention:
* - stdout: JSON object (decision/advisory) or empty
* - exit 0: allow (with optional advisory JSON on stdout)
* - exit 2: block (Claude convention; reason in stdout JSON)
* - any error: exit 0 silently (hooks swallow their own errors)
*
* @param {string} hookFile filename under hooks/, e.g. "gsd-prompt-guard.js"
* @param {object} payload stdin JSON (hook_event_name, tool_name, ...)
* @param {object} [opts]
* @param {number} [opts.timeout=8000] spawn timeout in ms
* @param {string} [opts.cwd] working directory for the child
* @returns {{ stdout: string, exitCode: number, timedOut: boolean }}
*/
function runHook(hookFile, payload, opts = {}) {
const hookPath = path.join(HOOKS_DIR, hookFile);
if (!fs.existsSync(hookPath)) {
return { stdout: "", exitCode: 0, timedOut: false };
}
const timeout = opts.timeout ?? 8000;
let result;
try {
result = spawnSync(process.execPath, [hookPath], {
input: JSON.stringify(payload),
encoding: "utf8",
timeout,
cwd: opts.cwd || currentCwd,
windowsHide: true,
});
} catch {
// Spawn failure — never break the tool call
return { stdout: "", exitCode: 0, timedOut: false };
}
const stdout = (result.stdout || "").trim();
const exitCode = result.status == null ? 0 : result.status;
return { stdout, exitCode, timedOut: result.signal === "SIGTERM" };
}
// ---------------------------------------------------------------------------
// Hook output translation → OpenCode semantics
// ---------------------------------------------------------------------------
/**
* Parse a hook's stdout and apply its effect to the OpenCode output object.
*
* - Block → throw Error(parsed.reason) so OpenCode aborts the tool call
* - Advisory→ append to output.metadata._gsdAdvisory[] and log to stderr
* - Silent → no-op
*
* @param {{ stdout: string, exitCode: number }} hookResult
* @param {object} [output] OpenCode mutable output object (optional)
*/
function handleHookResult(hookResult, output) {
const { stdout, exitCode } = hookResult;
if (!stdout && exitCode !== 2) return; // silent allow
let parsed = null;
if (stdout) {
try {
parsed = JSON.parse(stdout);
} catch {
// Non-JSON stdout (e.g. a stray log) — treat exit 2 as hard block, else allow
}
}
// Block: explicit decision OR Claude exit-code-2 convention
const isBlock = exitCode === 2 || (parsed && parsed.decision === "block");
if (isBlock) {
const reason =
(parsed && parsed.reason) || "Blocked by GSD hook (no reason provided).";
throw new Error(reason);
}
// Advisory: inject additionalContext into metadata + log
const advisory =
parsed &&
parsed.hookSpecificOutput &&
parsed.hookSpecificOutput.additionalContext;
if (advisory) {
if (output) {
output.metadata = output.metadata || {};
// Accumulate: a single tool call can run several advisory hooks in
// sequence (prompt guard, read guard, worktree guard, workflow guard).
// Storing a scalar would let a later advisory clobber an earlier one, so
// collect them all.
if (!Array.isArray(output.metadata._gsdAdvisory)) {
output.metadata._gsdAdvisory = [];
}
output.metadata._gsdAdvisory.push(advisory);
}
// Best-effort visibility when metadata isn't surfaced to the model
console.error(advisory);
}
}
// ---------------------------------------------------------------------------
// Frontmatter helpers (for config registration)
// ---------------------------------------------------------------------------
function parseFrontmatter(content) {
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!m) return { frontmatter: {}, body: content };
const fm = {};
for (const line of m[1].split("\n")) {
const i = line.indexOf(":");
if (i > 0) {
let v = line.slice(i + 1).trim();
if (v.startsWith('"') && v.endsWith('"')) v = v.slice(1, -1);
fm[line.slice(0, i).trim()] = v;
}
}
return { frontmatter: fm, body: m[2] };
}
// Rewrite @~/.claude/ includes to point at the repo root.
// Also applies /gsd:xxx → /gsd-xxx namespace conversion via the shared
// transform from scripts/fix-slash-commands.cjs (single source of truth).
function rewriteRefs(content) {
let out = content.replace(/@~\/\.claude\//g, `@${REPO_ROOT}/`);
const transform = getNamespaceConverter();
if (transform && _cmdNames && _cmdNames.length) {
out = transform(out, _cmdNames);
}
return out;
}
function loadDir(dir, keyFn, valFn) {
const result = {};
if (!fs.existsSync(dir)) return result;
for (const f of fs.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
const raw = fs.readFileSync(path.join(dir, f), "utf8");
const { frontmatter, body } = parseFrontmatter(raw);
result[keyFn(f)] = valFn(body, frontmatter, f);
}
return result;
}
// ---------------------------------------------------------------------------
// Runtime content transform — for Read tool results on GSD-managed files
// ---------------------------------------------------------------------------
// Directories whose .md files may contain ~/.claude/ paths and gsd: namespace
// refs. When the model reads these via the Read tool, we transparently rewrite
// both so OpenCode sees correct paths and hyphen-form command names.
const GSD_MANAGED_DIRS = [
path.join(GSD_CORE, "workflows"),
path.join(GSD_CORE, "references"),
path.join(GSD_CORE, "templates"),
path.join(GSD_CORE, "contexts"),
COMMANDS,
AGENTS,
SKILLS,
];
function isGsdManagedFile(filePath) {
if (!filePath) return false;
const resolved = path.resolve(filePath);
return GSD_MANAGED_DIRS.some(
(dir) => resolved === dir || resolved.startsWith(dir + path.sep),
);
}
// Rewrite content for OpenCode consumption:
// 1. @-include paths: @~/.claude/ → @<REPO_ROOT>/
// 2. plain-text paths: ~/.claude/gsd-core/ → <GSD_CORE>/
// 3. namespace: gsd:xxx → gsd-xxx (via fix-slash-commands.cjs)
function rewriteContent(content) {
let out = content;
out = out.replace(/@~\/\.claude\//g, `@${REPO_ROOT}/`);
out = out.replace(/~\/\.claude\/gsd-core\//g, `${GSD_CORE}/`);
const transform = getNamespaceConverter();
if (transform && _cmdNames && _cmdNames.length) {
out = transform(out, _cmdNames);
}
return out;
}
// ---------------------------------------------------------------------------
// Skills cache — copy SKILL.md files with rewritten @-include paths
// ---------------------------------------------------------------------------
//
// OpenCode's skill loader reads SKILL.md files directly from disk and resolves
// @-includes internally — this bypasses our tool.execute hooks. To make
// @~/.claude/gsd-core/... includes resolve, we copy all SKILL.md files to a
// cache directory with paths rewritten to the actual GSD_CORE location.
//
// Only used in package-tree mode (Option 2). In an installed OpenCode config
// dir (Option 1) skills are already staged + registered by GSD's native file
// copy, so we never register skills from the plugin (see IS_PACKAGE_TREE).
const SKILLS_CACHE = path.join(
os.homedir(),
".cache",
"opencode",
"gsd-skills",
);
function prepareSkillsCache() {
if (!fs.existsSync(SKILLS)) return null;
fs.mkdirSync(SKILLS_CACHE, { recursive: true });
for (const dir of fs.readdirSync(SKILLS)) {
const srcFile = path.join(SKILLS, dir, "SKILL.md");
if (!fs.existsSync(srcFile)) continue;
const raw = fs.readFileSync(srcFile, "utf8");
// Rewrite @-include paths only; namespace conversion is handled at
// Read-time via tool.execute.after for workflow/reference files.
const rewritten = raw
.replace(/@~\/\.claude\/gsd-core\//g, `@${GSD_CORE}/`)
.replace(/~\/\.claude\/gsd-core\//g, `${GSD_CORE}/`);
const destDir = path.join(SKILLS_CACHE, dir);
fs.mkdirSync(destDir, { recursive: true });
fs.writeFileSync(path.join(destDir, "SKILL.md"), rewritten);
}
return SKILLS_CACHE;
}
// ===========================================================================
// Plugin entry
// ===========================================================================
const GsdCorePlugin = async ({ directory } = {}) => {
if (directory) currentCwd = directory;
return {
// ── Config: register commands / agents / skills paths ──────────────
// Only in package-tree mode (Option 2). In an installed config dir
// (Option 1) GSD's native file copy already registered these, so the
// plugin stays out of registration to avoid double-registering.
config: async (config) => {
if (!IS_PACKAGE_TREE) return;
// Commands (commands/gsd/*.md → gsd-<name>)
config.command = config.command || {};
const cmds = loadDir(
COMMANDS,
(f) => "gsd-" + f.slice(0, -3),
(body, fm, name) => ({
template: rewriteRefs(body.trim()),
description: fm.description || `GSD ${name.slice(0, -3)} command`,
}),
);
for (const [k, v] of Object.entries(cmds)) {
if (!config.command[k]) config.command[k] = v;
}
// Agents (agents/*.md)
config.agent = config.agent || {};
const agents = loadDir(
AGENTS,
(f) => f.slice(0, -3),
(body, fm, name) => ({
prompt: rewriteRefs(body.trim()),
description: fm.description || `GSD ${name.slice(0, -3)} agent`,
mode: fm.mode || "subagent",
}),
);
for (const [k, v] of Object.entries(agents)) {
if (!config.agent[k]) config.agent[k] = v;
}
// Skills — copy SKILL.md files to cache with rewritten @-include paths,
// then register the cache directory. OpenCode's skill loader reads
// SKILL.md from disk and resolves @-includes internally (bypassing our
// tool.execute hooks), so we must pre-process the files.
const skillsCache = prepareSkillsCache();
config.skills = config.skills || {};
config.skills.paths = config.skills.paths || [];
const skillsPath = skillsCache || SKILLS;
if (!config.skills.paths.includes(skillsPath)) {
config.skills.paths.push(skillsPath);
}
},
// ── shell.env ───────────────────────────────────────────────────────
"shell.env": async (_input, output) => {
output.env = output.env || {};
output.env.GSD_DIR = GSD_CORE;
},
// ── tool.execute.before — PreToolUse hooks ─────────────────────────
"tool.execute.before": async (input, output) => {
const claudeTool = mapToolName(input.tool);
const toolInput = mapToolInput(output.args || {});
const cwd = currentCwd;
// 0. Read path rewrite — redirect ~/.claude/gsd-core/ to actual GSD_CORE
// so the model can read workflow/reference/template files that SKILL.md
// and command templates reference via the canonical Claude path.
if (claudeTool === "Read" && toolInput.file_path) {
const original = toolInput.file_path;
const rewritten = original
.replace(/^~\/\.claude\/gsd-core\//, GSD_CORE + "/")
.replace(/(?:.*)\/\.claude\/gsd-core\//, GSD_CORE + "/");
if (rewritten !== original) {
const args = output.args || {};
if (args.filePath) args.filePath = rewritten;
else if (args.path) args.path = rewritten;
else if (args.file_path) args.file_path = rewritten;
else args.filePath = rewritten;
}
}
const basePayload = {
hook_event_name: "PreToolUse",
cwd,
};
// NOTE: session_id intentionally omitted for PreToolUse hooks.
// gsd-read-guard.js treats a non-empty session_id as a Claude Code
// session and skips its advisory. On OpenCode we WANT the advisory.
const prePayload = (overrides = {}) => ({
...basePayload,
tool_name: claudeTool,
tool_input: toolInput,
...overrides,
});
const isWriteLike = ["Write", "Edit", "MultiEdit"].includes(claudeTool);
// 1. gsd-prompt-guard.js — injection scan on .planning/ writes
if (claudeTool === "Write" || claudeTool === "Edit") {
const r = runHook("gsd-prompt-guard.js", prePayload());
handleHookResult(r, output);
}
// 2. gsd-read-guard.js — read-before-edit advisory
if (claudeTool === "Write" || claudeTool === "Edit") {
const r = runHook("gsd-read-guard.js", prePayload());
handleHookResult(r, output);
}
// 3. gsd-worktree-path-guard.js — hard-block edits outside worktree
if (isWriteLike) {
const r = runHook("gsd-worktree-path-guard.js", prePayload());
handleHookResult(r, output);
}
// 4. gsd-workflow-guard.js — workflow advisory + git-force-add block
// (covers Write/Edit/MultiEdit AND Bash force-add detection)
if (isWriteLike || claudeTool === "Bash") {
const r = runHook("gsd-workflow-guard.js", prePayload());
handleHookResult(r, output);
}
},
// ── tool.execute.after — PostToolUse hooks ─────────────────────────
"tool.execute.after": async (input, output) => {
const claudeTool = mapToolName(input.tool);
// NOTE: In the `after` hook, `args` lives on `input` (not `output`).
// The `output` object only has { title, output, metadata }.
const toolInput = mapToolInput(input.args || {});
const cwd = currentCwd;
// GSD content transform — rewrite paths + namespace in Read results
// BEFORE injection scanning so the scanner sees the final content.
if (
claudeTool === "Read" &&
output.output &&
isGsdManagedFile(toolInput.file_path)
) {
const content =
typeof output.output === "string"
? output.output
: String(output.output);
output.output = rewriteContent(content);
}
// gsd-read-injection-scanner.js — scan Read/WebFetch/WebSearch results
if (
claudeTool === "Read" ||
claudeTool === "WebFetch" ||
claudeTool === "WebSearch"
) {
const payload = {
hook_event_name: "PostToolUse",
tool_name: claudeTool,
tool_input: toolInput,
tool_response: output.output,
cwd,
};
const r = runHook("gsd-read-injection-scanner.js", payload);
handleHookResult(r, output);
return;
}
// gsd-context-monitor.js — context usage warnings (Bash/Edit/Write/Task/...)
// Only meaningful when a session_id is tracked (writes metrics sentinel).
if (currentSessionId) {
const payload = {
hook_event_name: "PostToolUse",
tool_name: claudeTool,
tool_input: toolInput,
session_id: currentSessionId,
cwd,
};
const r = runHook("gsd-context-monitor.js", payload);
handleHookResult(r, output);
}
},
// ── experimental.session.compacting — PreCompact ───────────────────
"experimental.session.compacting": async (_input, output) => {
if (!currentSessionId) return;
const payload = {
hook_event_name: "PreCompact",
session_id: currentSessionId,
cwd: currentCwd,
};
const r = runHook("gsd-context-monitor.js", payload);
handleHookResult(r, output);
// Also inject a GSD compaction breadcrumb (mirrors the original plugin)
output.context = output.context || [];
output.context.push(
`[GSD] Active session: ${currentSessionId}. Preserve any in-flight phase/plan state.`,
);
},
// ── General event subscriptions ─────────────────────────────────────
event: async ({ event }) => {
// session.created → SessionStart hooks
if (event.type === "session.created") {
// Track session for context-monitor payloads.
// SDK type EventSessionCreated: { properties: { info: Session } }
// Session has `id` and `directory` (not `cwd`).
const info = event.properties?.info;
currentSessionId =
info?.id || event.sessionID || event.session_id || null;
if (info?.directory) currentCwd = info.directory;
// gsd-ensure-canonical-path.js — no stdin dependency; silent
runHook("gsd-ensure-canonical-path.js", {
hook_event_name: "SessionStart",
session_id: currentSessionId,
cwd: currentCwd,
});
// gsd-check-update.js — spawns its own background worker; no stdin
runHook("gsd-check-update.js", {
hook_event_name: "SessionStart",
session_id: currentSessionId,
cwd: currentCwd,
});
return;
}
// file.edited → FileChanged hook (config.json reload)
if (event.type === "file.edited") {
// SDK type EventFileEdited: { properties: { file: string } }
const filePath = event.properties?.file || event.filePath || "";
if (!filePath.endsWith("config.json")) return;
const cwd = event.properties?.cwd || currentCwd;
const expected = path.join(cwd, ".planning", "config.json");
if (path.resolve(filePath) !== path.resolve(expected)) return;
const payload = {
hook_event_name: "FileChanged",
file_path: filePath,
event: "change",
cwd,
};
const r = runHook("gsd-config-reload.js", payload);
// Advisory-only (additionalContext); surface to logs
handleHookResult(r);
return;
}
// session.idle ↔ Claude Stop lifecycle point (#1682 Slice 1b/c).
// OpenCode fires session.idle when the run quiesces. GSD maps it to the
// Stop equivalent — the opencode-subset lifecycle peer of compaction
// (compaction preserves state across context-window summarization; idle
// marks end-of-turn). No-op sentinel today (GSD state is already
// persisted to .planning/), but it MUST be recognized so the declared
// opencode-subset surface is fully wired and a future Stop-class hook can
// attach without a plugin change.
if (event.type === "session.idle") {
return;
}
// permission.asked / permission.replied — OpenCode permission lifecycle
// (#2087, opencode.ai/docs/plugins). GSD gates tool INPUTS at
// tool.execute.before (read-guard, injection-scanner); the permission
// grant/deny decision itself carries no GSD workflow-phase contribution,
// so these are recognized sentinels — wired so a future permission-aware
// gate can attach without a plugin change (the engine owns phase
// sequencing; this host bus is session/tool/permission-scoped, never
// phase-scoped — ADR-1239 §OpenCode).
if (event.type === "permission.asked" || event.type === "permission.replied") {
return;
}
// session.error — OpenCode session-error lifecycle point (#2087). No GSD
// hook fires here today (loop state is already persisted to .planning/);
// recognized so the declared extension-event surface is fully wired and a
// future error-class hook can attach without a plugin change.
if (event.type === "session.error") {
return;
}
},
};
};
// Export shape — verified against OpenCode's plugin loader source
// (packages/opencode/src/plugin). The loader imports this module and runs
// `for (const entry of Object.values(mod)) { getServerPlugin(entry) }`, where
// `getServerPlugin` accepts a bare function OR an object exposing a `.server`
// function, and THROWS `TypeError("Plugin export is not a function")` for
// anything else. So EVERY enumerable value the loader iterates must be a
// function or an object with `.server`.
//
// The subtlety: depending on how OpenCode's runtime (Node or Bun) imports a
// CommonJS file, `mod` may be the raw `module.exports` OR an ESM namespace of
// the form `{ default: module.exports, ...syntheticNamedExports }`. A plain
// `module.exports = { id: "gsd-core", server }` literal risks a string `id`
// appearing in `Object.values(mod)` (as a raw property, or as a lexer-
// synthesized named export) — which would trip the throw. Two defenses:
// 1. `id` is defined NON-ENUMERABLE, so it never appears in Object.values yet
// stays readable (via property access) for the loader's identity/dedup.
// 2. `module.exports` is assigned from a VARIABLE (not an object literal), so
// cjs-module-lexer cannot statically synthesize named exports from it —
// only `default` is exposed under ESM/Bun interop.
// Result: raw-CJS `Object.values` = `[server]`; ESM `Object.values` =
// `[{server, <id non-enum>}]` — both fully extractable. Test-only helpers hang
// off the `server` FUNCTION (`server._internals`), never as a sibling export.
GsdCorePlugin._internals = {
REPO_ROOT,
IS_PACKAGE_TREE,
mapToolName,
mapToolInput,
parseFrontmatter,
rewriteContent,
isGsdManagedFile,
handleHookResult,
GsdCorePlugin,
};
const gsdCorePluginExport = { server: GsdCorePlugin };
Object.defineProperty(gsdCorePluginExport, "id", {
value: "gsd-core",
enumerable: false,
writable: false,
configurable: false,
});
module.exports = gsdCorePluginExport;

View File

@@ -0,0 +1,42 @@
# Clone-Portable `<execution_context>` in Committed PLAN.md
GSD does not make the `<execution_context>` block in committed `PLAN.md` files
clone-portable — it does not rewrite the planner's install-relative
`@…/gsd-core/…` references into repository-relative or install-neutral paths so
that a committed plan reads identically across developers, machines, or runtimes.
## Why this is out of scope
PLAN.md is a **machine artifact**, not a human- or clone-facing document — the
same principle that governs [plan-md-human-rendering.md](./plan-md-human-rendering.md)
(from #2158). A committed PLAN.md is a per-run agent instruction set, produced by
`gsd-planner` and consumed in place by `gsd-executor`, `gsd-plan-checker`, and
`gsd-verifier`. There is no documented step in which a plan is read on another
machine after `git clone` without a local GSD install.
Under that model, `<execution_context>`'s `@` references point at the reader's
own local GSD install (Claude `~/.claude/gsd-core/…`, Cursor `.cursor/gsd-core/…`,
or an absolute path for a `--local` install). They are install-relative by design,
and the executor loads those workflows from its own installed copy — it never
consumes the paths a *different* machine wrote into a committed plan.
`/gsd-execute-phase` builds its own `<execution_context>` inline from the
orchestrator's installed workflow (`workflows/execute-phase.md`), so the block a
planner writes into a committed plan does not gate execution anywhere.
Making committed plans clone-portable would treat PLAN.md as a shared
cross-developer document — the boundary #2158 declined to cross — for a block that
no consumer reads across machines.
**Revisit if** GSD introduces a documented cross-developer / cross-machine contract
for committed PLAN.md — a human- or teammate-facing use where plans are read after
`git clone` without a local install — at which point `<execution_context>`
portability becomes in scope.
## Prior requests
- #2238 — "Planner embeds machine-specific gsd-core paths in committed PLAN.md execution_context"
## Related
- `.out-of-scope/plan-md-human-rendering.md` — #2158, the governing "PLAN.md is a machine artifact" precedent.
- `docs/reference/plan-md.md` — the `<execution_context>` reference, which describes the install-relative behaviour.

View File

@@ -0,0 +1,63 @@
# Human-Readable Rendering of PLAN.md
GSD does not change PLAN.md's structural tag convention (`<task>`, `<action>`,
`<tasks>`, `<files>`, `<verify>`, etc.) to improve how a PLAN.md renders when a
human opens the raw file in a markdown viewer on GitHub/GitLab.
## Why this is out of scope
PLAN.md is a **machine artifact**, not a human-facing document. The docs are
explicit:
- `docs/reference/plan-md.md` — a PLAN.md is *"an executable unit of work — a
structured document that tells an executor agent exactly what to build and how
to verify it was built correctly."*
- `agents/gsd-planner.md` — *"Produce PLAN.md files that Claude executors can
implement without interpretation. Plans are prompts, not documents that become
prompts."*
PLAN.md is produced by `gsd-planner` and consumed by `gsd-executor`,
`gsd-plan-checker`, `gsd-verifier`, and cross-AI review agents. There is no
documented step in which a human opens, reads, reviews, or signs off on a
PLAN.md — unlike `SUMMARY.md` / `VERIFICATION.md`, which are produced for human
validation.
The reported symptom — alphabet-only tags like `<task>` / `<action>` tripping
CommonMark's HTML-block rule so inner markdown renders as cramped run-on text —
only manifests when a human views the raw file in a markdown renderer. It does
**not** affect either machine consumer:
- Tag location/extraction is regex-based (`extractTaggedBlocks` in
`src/markdown-sectionizer.cts`), operating on raw text, not rendered HTML.
- Agents read the raw file content, not a rendered view.
```js
// The extraction contract is a raw-text regex, indifferent to CommonMark
// HTML-block folding:
new RegExp(`<${escapedTag}>([\\s\\S]*?)</${escapedTag}>`, 'g')
```
The proposed fixes (HTML-comment markers `<!-- task -->`, or underscored tag
names `<task_node>`) would change a load-bearing machine convention that is
duplicated across ~6 surfaces — the extractor regex, the `execute-plan` grep
counter, `verify.cjs`, `decisions.cjs`, and the planner/executor schema docs. A
drift between those surfaces silently breaks plan extraction (the executor finds
zero tasks), which is a far worse failure than cosmetic rendering. The
underscore option additionally increases token consumption on every plan read
(longer tag names, repeated across every PLAN.md, read in full by the executor)
and leaves the marker names visible as literal noise in any rendered view.
Incurring that cost and risk to improve a rendering path that is not a
documented use of PLAN.md does not align with the project's model of PLAN.md as
an agent instruction set.
The same reasoning covers the report's secondary point (unquoted `|` in PLAN.md
frontmatter breaking rendered markdown tables): that too is a human-render
concern for a machine artifact.
**Revisit if** GSD ever introduces a human-review gate for PLAN.md — a step
where a person reads and approves the plan before execution. At that point
PLAN.md gains a documented human audience and its rendering becomes in-scope.
## Prior requests
- #2158 — "PLAN.md XML task tags trigger CommonMark HTML-block rule — task content renders as cramped run-on text"

View File

@@ -0,0 +1,38 @@
# Statusline Account / Usage Segment (credential-reading, external API)
GSD's statusline does not read credentials or call external network APIs to
display account-level resource state (5-hour / 7-day rate-limit utilization,
usage windows, plan quotas).
## Why this is out of scope
The statusline draws its data boundary at **local, read-only** sources — see
[`docs/adr/2164-statusline-scope-boundary.md`](../docs/adr/2164-statusline-scope-boundary.md).
It refines the stdin payload Claude Code already sends (model, context meter,
GSD-state) and may add a new *local* source (e.g. `git`), but it does not:
- read Claude Code's OAuth credentials (`.credentials.json`, or the macOS login
Keychain via `security`), or
- make authenticated network calls (e.g. `https://api.anthropic.com/api/oauth/usage`)
to fetch data.
Reasons:
- **Trust surface.** A planning-workflow hook reading an OAuth token is a
materially larger trust surface than any rendering concern — even read-only,
never-logged, and opt-in. Credential custody belongs to the platform, not to
a markdown planning tool.
- **Unstable dependency.** The usage endpoint is undocumented; it can change or
disappear and silently rot the feature.
- **Scope.** Surfacing account/rate-limit state is a platform (Claude Code)
concern. This matches the prior in
[`temporal-context.md`](./temporal-context.md): *"Statusline / TUI re-entry is
platform-level, not GSD-level."*
**Revisit if** a documented, first-party usage API — or a platform-provided
value delivered to the hook without GSD reading credentials — becomes
available. That would move usage display out of the excluded tier.
## Prior requests
- #2164 — "enhancement(statusline): opt-in 5-hour/7-day account usage segment"

71
.pr-body-2100.md Normal file
View File

@@ -0,0 +1,71 @@
## Linked Issue
Closes #2100
The linked issue carries the `approved-feature` label.
---
## Feature summary
Migrates **Windsurf** onto the ADR-1239 Embeddable Orchestration System — the largest of the EoS migrations. Folds all 10 residual `isWindsurf` branches onto the capability descriptor **and** wires GSD's write/command safety guards into Windsurf/Cascade's native blocking hook bus.
## What changed (highlights)
| File | What changed |
|------|-------------|
| `bin/install.js` | Folded 10 `isWindsurf` sites onto `hostBehaviors` (skipSharedHooksInstall, legacyDevinSkillsCleanup, installsCommandBodiesForWorkflowDelegation [#1629], verificationStyle); dropped 2 dead destructures + the dead `else if (isWindsurf)` agent arm; wired the Cascade hook-bridge install/uninstall; corrected stale comments |
| `capabilities/windsurf/capability.json` | `hostBehaviors` block; `hooksSurface: "none"` → `"windsurf-hooks-json"` |
| `src/runtime-hooks-surface.cts` | `writeWindsurfHooksJson`/`reconcileWindsurfHooksJson`/`removeWindsurfHooksJson` (Cursor-templated, Cascade's flat `{hooks:{<event>:[{command}]}}` shape) + event/script constants |
| `hooks/gsd-windsurf-pre-write.js`, `gsd-windsurf-pre-command.js` | **New** Cascade-native blocking guard scripts (stdin JSON, exit-code-2 blocking) |
| `gsd-core/bin/lib/capability-validator.cjs`, `src/runtime-config-adapter-registry.cts` | `windsurf-hooks-json` added to `VALID_HOOKS_SURFACES`, GATE A's `profile-marker-only` allowlist, and the `HooksSurface` union |
| managed-hooks-registry / build-hooks / INVENTORY | registered the 2 new guard scripts |
| tests / docs / changeset | `declarative-reference-windsurf` + `windsurf-hooks-bridge` (live blocking); matrix hookBus delta; changeset (`Changed`) |
## Implementation notes
- **Byte-parity concretely verified** (via the review): a real windsurf install rebuilt through the golden-parity harness → 327 files, 0 drift; only `windsurf.json` gains the 2 new script hashes. cursor/trae re-verified 0 drift. The load-bearing #1629 command-body copy (`.windsurf/gsd-core/commands/gsd/*.md` for local installs) is intact.
- **The hook-bridge is faithful, not padding.** Cascade's `hooks.json` genuinely supports blocking via exit code 2 (confirmed against docs.windsurf.com / docs.devin.ai). Only **2 of GSD's 6 guards** faithfully map — the worktree-path guard (→ `pre_write_code`) and a destructive-command guard (→ `pre_run_command`). The 4 advisory guards + `pre_mcp_tool_use` + the 5 `post_*` logging events are **deliberately not wired**: Cascade's hook bus has no context-injection channel to carry GSD's advisory reminders faithfully, and GSD has no MCP-tool policy — porting them would be non-functional padding. This is the same faithful-subset pattern used for codebuddy #2098 / copilot #2099, and it satisfies AC4's testable requirement ("a real blocking hook rejecting a disallowed write/command").
- **Security (reviewed, clean).** The guard scripts parse untrusted stdin and spawn `git rev-parse` — command injection via `file_path` was **refuted** (argv array, no `shell:true`, PATH-resolved git). No traversal / prototype-pollution (frozen 2-event set, fixed script names). The pre-command guard was **hardened post-review**: a tokenize-based classifier (no catastrophic-backtracking regex — a 200k-char pathological input now completes in ~32ms via a 4096-char cap), catching prefixed `rm -rf` forms (`sudo`/`env`/`/bin/rm`) and refspec force-pushes (`HEAD:main`, `+main`), and a fail-closed false-positive fixed (a `feature/main-fix` branch or a trailing-`# ...main` comment no longer wrongly blocks a legit force-push). Guards fail-open (never wedge Cascade) by design.
- **Golden mechanics.** `.windsurf/hooks.json` is golden-excluded by basename (like settings.json); the 2 guard scripts under `hooks/` are windsurf-specific → only windsurf.json regenerates, additively.
## Spec compliance (acceptance criteria)
- [x] Golden parity: byte-identical for the folds across all 16 runtimes (windsurf.json regen is the additive hook-script delta only)
- [x] Driven through the descriptor — zero live `runtime==='windsurf'`/`isWindsurf` branches (AC2 guard over 4 files)
- [x] Every axis populated + `capability-validator`-clean (`runtime`/dispatch stay `undocumented` per the cited search trail)
- [x] UPGRADE implemented AND exercised by a test driving a real blocking hook (exit-2 on a disallowed write/command)
- [x] `negotiateHostCapabilities` fail-closes for windsurf (test)
- [x] `gsd-test` green (linux node22/24); no other-runtime regression (cursor.json byte-identical)
- [x] Docs (matrix hookBus delta) + changeset (`Changed`)
## Testing
- [x] macOS (real install byte-parity harness + live guard-script exit-2 probing + ReDoS timing)
- [x] Windows (backslash; Windows destructive-command forms handled) — GitHub CI
- [x] Linux (`gsd-test`)
- [x] Runtimes: Windsurf (primary) + all 16 golden fixtures (only windsurf's 2 new scripts)
---
## Scope confirmation
- [x] Windsurf only; other runtimes byte-identical. The hook-bridge's faithful 2-guard scope (vs. the AC's fuller event list) is disclosed above — the unbridged events have no faithful GSD logic / Cascade channel.
- [x] Cascade envelope/schema is best-effort per the official docs (guards fail-open if the live schema differs, never breaking Cascade); flagged for a live-Cascade schema confirmation follow-up.
## Documentation
- [x] matrix (## windsurf hookBus/hooksSurface delta + the not-ported-guards rationale); English
## Checklist
- [x] `Closes #2100`; issue has `approved-feature`
- [x] Acceptance criteria met (faithful hook-bridge scope disclosed)
- [x] `gsd-test` green
- [x] New tests cover the folds (AC2 guard) + the blocking hook bus (live exit-2) + fail-closed negotiation
- [x] `.changeset/` fragment (`Changed`)
- [x] No new dependencies
## Breaking changes
None at landing. New Windsurf install output is additive: 2 guard scripts + a `.windsurf/hooks.json` registering blocking pre-hooks. No skill, agent, workflow, or path is removed or altered; the guards fail-open.

View File

@@ -6,6 +6,214 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
## [1.7.0] - 2026-07-15
### Added
- **A default-off, BETA, claude-only "Claude orchestration" capability** — adopts Claude Code's Workflow tool (`/effort ultracode`, Agent SDK ≥ v0.3.149) as an optional parallel-execution backend for the GSD loop, restoring the wave parallelism + plan-checker + verifier that the #853 backgrounded-agent nesting limitation forces inline on Claude Code, and folding the existing `gsd-ultraplan-phase` plan-offload under the same runtime gate. When `claude_orchestration.enabled` is on AND the runtime is Claude AND the Workflow tool is detected AND the Agent SDK meets the floor (`claude_orchestration.min_agent_sdk_version`, default `0.3.149`), `execute-phase` emits a generated Workflow script (`waves → parallel() barriers`, `plans → agent({ agentType: 'gsd-executor', isolation: 'worktree' })`, `files_modified overlap → separate sequential stages`, `resumeFromRunId` wired to the phase run id, shared `budget` pool) that composes the SAME executor agent + worktree isolation the inline path uses, so artifacts/commits are produced identically. Detection is pure and fail-closed (any miss → inline), so on any runtime lacking the Workflow tool behaviour is byte-identical to today. Adds a pure module `gsd-core/bin/lib/claude-orchestration.cjs` (`detectWorkflowBackend`, `emitWorkflowScript`), the `capabilities/claude-orchestration/` declaration with two gated loop contributions (`execute:wave:post`, `plan:post`) and a `claude-orchestration` command family (`gsd-tools claude-orchestration detect-backend|emit-workflow`), federated config keys, and an ADR-1143 implementation amendment. (#1143) (#2044)
- **Phases that integrate an external API/SDK/service can no longer seal without a decided coverage matrix** — a new `api-coverage` gate on the `ai-integration` capability blocks `/gsd:verify-work` until the phase produces a `COVERAGE.md` enumerating the API's full capability surface, with every non-integrated capability an explicit, reasoned opt-out. Full coverage is the default; the matrix is the subtraction record, so "we integrated the API" can no longer silently mean "we integrated whatever the first use case exercised." Toggleable via `workflow.api_coverage_gate` (on by default). (#1562) (#2065)
-
**OpenCode installs now auto-register the GSD companion MCP server (`mcp.gsd`)** — `--opencode` install writes a `mcp.gsd` entry (local stdio → `gsd-mcp-server`) into `opencode.json`, so OpenCode drives GSD's command + planning-state surface over MCP with no bespoke plugin (ADR-1239 Phase D / #1682). Idempotent and non-clobbering; a user-defined `mcp.gsd` is preserved. (#1682) (#1929)
-
**OpenCode plugin handles `session.idle` + the `opencode-subset` hook dialect is implemented** — the GSD OpenCode plugin now recognizes `session.idle` (↔ Claude `Stop` lifecycle point), completing the compaction/idle pair (#1914 shipped compaction). The reserved `opencode-subset` dialect gains a consumer — `hookEventSurfaceFor()` in `host-integration.cts` — describing OpenCode's session/tool/file event subset (no workflow-phase events; the engine owns phase sequencing, ADR-1239 §OpenCode binding). Adds a Claude-parity test asserting the plugin covers the full declared subset. (#1682) (#1930)
- **GSD now warns when model config changed without re-running the installer on static-frontmatter runtimes** — on `codex` and `opencode`, editing `model_overrides` or `model_profile_overrides` or `model_policy.runtime_tiers` in `.planning/config.json` or `~/.gsd/defaults.json` previously had no effect until the user re-ran `gsd install <runtime>`, and the failure was silent: the sub-agent kept using the base model. Workflow entry points like `gsd-tools init *` now emit a one-line stderr warning naming the changed config file and the exact remediation command when they detect the config is newer than the baked agent files. The guard is read-only and warning-only by default, dedup'd per session, and skipped entirely on Claude Code because Claude Code resolves models at spawn time. Resolves #1688 as the structural follow-up to #1650. (#1692)
- **`gsd-tools state rebuild`** — new subcommand that re-derives STATE.md body structure from canonical sources (frontmatter + `.planning/phases/` disk scan), reconciling drifted `## Current Position` prose, dropping orphaned rows from the `**By Phase:**` table, clearing template-placeholder field values, and de-duplicating `## Session Continuity Archive` blocks. Every mutation is recorded in a `## Rebuild Log` audit section. Idempotent (running twice on a clean file is a no-op). Supports `--dry-run` (preview) and `--verbose` (tee log to stderr). Heavier, manual counterpart to the lightweight auto-triggered `state sync`. (#1830)
- **`graphify.graph_path` makes the knowledge-graph location configurable so one umbrella graph can serve multiple projects** — a new `.planning/config.json` key (path relative to project root, or absolute) overrides where `/gsd-graphify query|status|diff` read the graph, letting a single curated cross-repo umbrella graph serve every sibling sub-project without N drifting ~5 MB mirror copies. Previously the graph location was hardcoded to `<cwd>/.planning/graphs/` with no override; the only workaround was copying the umbrella `graph.json` into each project (which drifted, wasted disk, and could be silently overwritten by an in-project build). The diff snapshot travels with the configured graph; build stays project-scoped; unset → byte-identical default; a configured-but-missing file yields an actionable error naming the path. (#1825) (#2013)
- **Claude Sonnet 5 is now the `standard` (sonnet) tier model.** The model catalog and provider presets resolve the sonnet/standard tier to `claude-sonnet-5` (GA 2026-06-30) across the Anthropic-backed runtimes (`claude`, `copilot`, and the `anthropic`/`anthropic-fable` presets), plus the OpenRouter-style `anthropic/claude-sonnet-5` for `opencode`/`hermes`, replacing the superseded `claude-sonnet-4-6`. Opus and Haiku tier defaults are unchanged (the `haiku` high-effort preset's escalation slot tracks the current sonnet model). Shipped in 1.6.1. (#1847) (#1848)
- **Third-party capability gates now actually fire via a generic `command-exit-zero` predicate.** — a capability's declared `check.predicate` gate was rendered for display but never evaluated (only built-in `check.query` gates were enforced, and the `security` capability's gate worked solely via a hard-coded `ship.md` branch). A new generic evaluator (`gsd_run check predicate`) now evaluates `check.predicate` blocks by `kind`; the first built-in kind `command-exit-zero` runs a bounded `sh -c` command at the project root and blocks the loop on non-zero exit (timeout → block, fail-closed). The `execute:wave:post`, `execute:post`, and `plan:post` gate-dispatch sites route `predicate` gates to the new evaluator automatically. (#2008) (#2011)
- **GSD's lifecycle hooks now run under Kimi CLI** — installing GSD into Kimi wires its session-state, phase-boundary, graphify, and guard hooks into Kimi's own native `config.toml` `[[hooks]]` bus (Beta on Kimi's side) instead of silently no-op'ing, and GSD's Kimi subagents can now run in the background. Kimi's install is driven by its negotiated capability descriptor instead of hardcoded runtime special-cases. (#2095) (#2159)
- **GSD is now installable on pi** — `npx @opengsd/gsd-core --pi` installs the GSD extension to `~/.pi/agent/extensions/gsd.cjs`, and `/gsd <family> <subcommand>` now dispatches real commands through the embedded engine (the reference binding previously could only run `query help`). Drives pi through the negotiated imperative Host-Integration adapter, with active-model steering and the full pi lifecycle-event surface. (#2102) (#2205)
-
**GSD now ships a pi extension** — a real, jiti-loadable ExtensionAPI module (`pi/gsd.cjs`) that registers `/gsd` (dispatches through the GSD command-routing hub) + `gsd_invoke` tool + `tool_call` event, installable at `~/.pi/agent/extensions/`. A reachability test proves the `/gsd` handler dispatches through the engine (keystone wired, not just registered on a mock). (#1965) (#1965)
- `plan-phase` now authors edge and prohibition predicates into PLAN.md `must_haves` when a phase SPEC omits `## Edge Coverage` / `## Prohibitions`, so goal-backward verification still has predicates to check on a spec-less phase (ADR-857 Phase 6). Gated by the new default-on `workflow.specless_probe_fallback` toggle — disable it to skip the fallback (the skip is recorded visibly in the plan). Spec-less prohibitions are authored descriptor-less and disposed flagged/unverified (honest verifier #1154), never a silent pass. (#1835)
- **Discover third-party GSD Capabilities in a new Community Capability Registry.** — A non-endorsing discoverability catalog where authors register a Capability via a documentation PR; each entry carries a live latest-release badge and a per-entry GitHub Discussion for community ranking and comments. (#2188) (#2188)
- **GSD now warns when a stale global CLI (e.g. a retired @gsd-build/sdk canary) shadows your project-local install** — the gsd-tools CLI startup detects when the running binary is outside the project root while a project-local install exists, and prints a remediation warning to stderr (non-blocking). (#1754) (#1755)
- **`gsd-mcp-server` — companion MCP server (interface points 1 + 5)** — a new bin command (`npx @opengsd/gsd-core gsd-mcp-server`) runs a stdio JSON-RPC 2.0 MCP server exposing `gsd_invoke_command` (→ the GSD command-routing hub) + `gsd_read_state` / `gsd_write_state` (→ `.planning/` state), so any MCP-consuming host (Claude Code, Codex, OpenCode, VS Code, Gemini CLI, Cursor, Cline, Hermes) can drive GSD with no bespoke plugin (ADR-1239 Phase C-2 / #1681). Dependency-free (hand-rolled JSON-RPC). How-to: `docs/how-to/connect-gsd-mcp-server.md`. (#1810)
- **Opt-in absolute token count on the statusline context meter** — new `statusline.show_context_tokens` config (default `false`). When enabled, the meter shows the absolute context total after the percentage, e.g. "████░░░░░░ 46% (156k)", summing input, cache-creation, cache-read, and output tokens from the hook payload (a broader basis than the meter's percentage, which is derived from `used_percentage` and excludes output tokens — the two figures can diverge slightly). Default meter output is unchanged. (#2161) (#2174)
- **Long-running compute can now be externalized as async external jobs instead of blocking the agent turn** — a default-off external-job capability lets executors submit SLURM jobs, commit a .planning/async-jobs manifest, defer SUMMARY.md, and return external_job_waiting; the core loop already reconciles these manifests (#1165), so this adds the producer half (SLURM adapter, pure manifest module, planner/executor fragments, operation policy). (#1105) (#1998)
-
**GSD now ships a repo-local VS Code extension** — a buildable extension (`vscode/extension.js` + `vscode/package.json`) that registers `gsd.invoke` (dispatches through the GSD command-routing hub) in the VS Code command palette. A reachability test proves the handler dispatches through the engine (keystone wired). Not Marketplace-published; mirrors the OpenCode plugin's bar. (#1966) (#1966)
- **Discover third-party GSD Embeddable Orchestration System (EoS) integrations in a new EoS Registry.** — A non-endorsing discoverability catalog where host-integration authors register via a documentation PR; each entry declares its Host-Integration interface points, negotiated axes, and protocol version, with a live release badge and a per-entry GitHub Discussion for ranking and comments. (#2193) (#2193)
- GSD Core ships a `.claude-plugin/marketplace.json` marketplace manifest so Claude-plugin-compatible runtimes (ZCODE et al.) can discover and install gsd-core from a custom marketplace source. Additive — the existing `.claude-plugin/plugin.json` and the Claude Code install path are unchanged. The catalog version (`plugins[0].version`) tracks `package.json` via the release version-sync. (#1861)
- **GSD now drives VS Code through the Embeddable Orchestration System** — the VS Code extension is rewired through the negotiated imperative Host-Integration adapter (active `vscode.lm` model, engine hook bus, sandboxed storage), gains native Language Model Tools (GSD skills as `#gsd-*` tools) and `#runSubagent` dispatch, and runs as a Web Extension (no Node APIs). (#2103) (#2210)
- **`/gsd:next` smart-entry workflow** — adds a state-aware entry point that classifies the current project situation (no-project, blocked, verify-failed, planning, executing, verify-pending, complete, and more) and recommends the right next GSD command. The `gsd-tools smart-entry [--json]` classifier handles phase ordering including decimal phase IDs; the `/gsd:next` skill surfaces the workflow with tiered fallback behavior. (#1798)
- OpenCode now runs GSD's lifecycle safety hooks (prompt-injection guard, read-before-edit guard, injection scanner, worktree/workflow guards, context monitor) via a native plugin installed to `~/.config/opencode/plugins/gsd-core.js`. OpenCode declares `hooksSurface: 'none'`, so these hooks were previously inert; the plugin bridges OpenCode's event bus onto GSD's existing hook scripts. Installed automatically by `npx @opengsd/gsd-core --opencode` and removed on uninstall. (#1923)
- **Host-integration descriptors now carry an `extensionEvents` vocabulary** — the extension-system event surface (OpenCode, pi) is a separate descriptor field from managed `hookEvents`, so OpenCode declares `extensionEvents:opencode` without conflicting with the hooksSurface:none invariant. (#1946) (#1946)
- **`/gsd-review` now supports custom reviewer instances** — run one model-capable adapter (e.g. OpenCode) as several independent reviewer identities via a bounded `review.reviewer_instances` config, so two different models can review in a single pass without manually swapping config or hand-merging REVIEWS.md. (#1517) (#1766)
- **Opt-in git branch and working-state segment in the statusline** — the shell prompt's branch/dirty-state signal is hidden for the whole session under the Claude Code TUI, so wrong-branch commits and ship-time push rejections surface only after the fact. New `statusline.show_git` config (default `false`) renders the branch name plus staged/unstaged/untracked/ahead/behind markers (or ✓ when clean and in sync) after the directory segment. When disabled, no git subprocess is spawned and output is unchanged. (#2163) (#2183)
- **`/gsd:onboard` guides brownfield setup** — existing repos now have a top-level onboarding command that routes through codebase mapping, docs ingest, project initialization, and an onboarding summary without silently overwriting planning files. (#1994)
- **Plural/optional/chosen assumption-delta checkpoint during planning** — when a phase makes something plural, optional, or chosen that used to be singular, required, or derived, the planner is now prompted to re-ask whether the primary key / identity model still names the right thing, preventing silent architectural drift from accumulating into a later user-facing bug. Advisory (non-blocking); fires only on a detected signal. Toggle with workflow.assumption_delta. (#1561) (#1767)
- **`/gsd-ui-phase` now probes UI state coverage** — a new `ui-consideration-probe` (the third `probe-core` adapter) enumerates the shape-rooted UI states a UI-SPEC must resolve (empty/loading/error/populated/partial/overflow/zero-one-many/long-text). After the UI checker approves, the probe surfaces applicable considerations for each element, records a `## UI Considerations` section in the UI-SPEC, and plan-phase lifts each resolved consideration into `must_haves` — so a purely-visual state with no wired test routes to `insufficient_spec → human_needed` at verify rather than a silent pass. (#1979)
- **Host-Integration Interface (ADR-1239 Phase A)** — a versioned, negotiated capability contract (`runtime.hostIntegration`) over the six host-integration points (command, dispatch, model, hooks, state, artifact). Adds an in-process `negotiateHostCapabilities` handshake that fail-closes on undeclared/unknown/`undocumented` values (`effective ⊆ host-declared ∩ engine-known`), a typed degradation ladder, host-capability profiles, and a documentation-sourced per-CLI capability matrix for all 16 runtimes. Interface-definition only — no change to install behaviour. (#1690)
- **ZCode (Z.ai) is now an installable runtime** — a desktop Agentic Development Environment for the GLM-5.2 model can now be targeted with `--zcode`, landing GSD skills at `~/.zcode/skills/<name>/SKILL.md` plus slash commands and subagents. ZCode ships as a pure declarative capability descriptor (`capabilities/zcode/capability.json`) with zero hardcoded `runtime === 'zcode'` branches, reusing the Claude skill converter — the de-hardcoded, data-driven runtime path that 1.7.0 (ADR-1016 / ADR-1239) enables. (#1925) (#2039)
### Changed
-
**The GSD CLI now self-heals a missing runtime build.** The compiled `gsd-core/bin/lib/*.cjs` modules are gitignored build artifacts (ADR-457) that ship prebuilt in the npm tarball but are absent on a Claude Code plugin-marketplace / git-clone install, which never runs `npm run build:lib`. Previously every command died at load with `Cannot find module './lib/cli-exit.cjs'`. The `gsd-tools` entrypoint now detects the missing output and compiles it once, on demand (lock-guarded so parallel invocations don't race), then proceeds — a single no-op check on the already-built npm path. When TypeScript is genuinely unavailable it prints an actionable `npm install && npm run build:lib` message instead of crashing. (#2036)
- **Internal: Claude Code's installer is now driven through the public Host-Integration Interface (ADR-1239 / EoS).** `bin/install.js` routes `claude` install/uninstall through the imperative adapter (`createImperativeAdapter`) instead of calling the engine directly, and its 13 hardcoded `runtime === 'claude'` / `runtime !== 'claude'` branches are folded into descriptor-driven `runtime.hostBehaviors` on `capabilities/claude/capability.json` (permission schema, `settings.local.json` scope routing, `.gsd-source` marker, effort frontmatter, canonical-workflow authorship, and more). Install/uninstall output is **byte-identical** for both the global skills layout and the local legacy layout (golden-parity asserted for both scopes); no other runtime changes. Removes the "add-a-host tax" of scattered string-equality checks for the tier-1 reference host. No user-facing change. (#2086) (#2106)
- **OpenCode is now driven through the public Host-Integration Interface, with two capability upgrades (ADR-1239 / EoS).** OpenCode and its Kilo sibling previously installed via a bespoke `runtime === 'opencode'`/`isOpencode` branch in `bin/install.js`; its commands+skills+plugin install now runs through the imperative adapter → the engine's combined-family install path (`installRuntimeArtifacts`), and every hardcoded `runtime === 'opencode'` branch is folded into descriptor-driven `runtime.hostBehaviors`. Install/uninstall output is **byte-identical** (golden parity asserted for all 16 runtimes). Two Context7-verified upgrades land: (1) **background dispatch** — OpenCode shipped experimental background subagents in v1.15 and made them default-on in v1.17, so `dispatch.background`/`backgroundDispatch` flip to `true`; GSD no longer force-flattens OpenCode-hosted wave dispatch (`shouldFlattenDispatch` now returns `false`), letting agents run concurrently where the host supports it. (2) **expanded event surface** — the OpenCode plugin now subscribes to `permission.asked`, `permission.replied`, and `session.error` (added to `EXTENSION_EVENT_SURFACES.opencode`), wiring the declared surface for future permission/error-aware bindings. (#2087) (#2108)
- **Codex is now driven through the public Host-Integration Interface, with three capability upgrades (ADR-1239 / EoS).** Codex previously installed via hardcoded `runtime === 'codex'`/`isCodex` projection in `bin/install.js`; its `config.toml` / agent-`.toml` / `hooks.json` install now runs through the declarative embedding adapter and descriptor-driven `runtime.hostBehaviors`, with **zero** positive `isCodex` gates and **zero** `runtime === 'codex'` branches remaining (source-guarded). Install/uninstall output stays byte-parity-gated (`tests/fixtures/golden-install-parity/codex.json`). Three Context7-verified upgrades land, each with a test driving the user-reachable surface: (1) **skill root** — GSD skills now install to Codex's canonical `$HOME/.agents/skills` (via a skills-kind `home` override) instead of the deprecated `$CODEX_HOME/skills` fallback, and pre-move installs are migrated (stale `~/.codex/skills/gsd-*` cleaned on both install and uninstall, user-owned content preserved); (2) **hook events** — GSD registers the six documented Codex lifecycle events it previously skipped (`PreToolUse`, `PermissionRequest`, `PreCompact`, `PostCompact`, `SubagentStop`, `UserPromptSubmit`, in addition to the existing `SessionStart`/`SubagentStart`/`Stop`/`PostToolUse`) in `hooks.json`, so `gsd-context-monitor` fires at the same points as in Claude Code, and the descriptor `extendedHookEvents` is reconciled from `[]` to the schema-valid wired subset; (3) **dispatch tuning** — `[agents] max_depth = 1` is written explicitly into the managed `config.toml` block to pin the negotiated `dispatch.maxDepth: 1` axis (`degradationFor` flattens GSD-hosted waves to single-level), and `validateCodexConfigSchema` now permits a known-scalar-only `[agents]` AgentsToml table (coexisting with the flattened `[agents.gsd-*]` role sub-tables) while still rejecting the `[[agents]]` and unknown-key break-forms from #2760. (#2088) (#2110)
- **Cursor is now driven through the public Host-Integration Interface, with two capability upgrades (ADR-1239 / EoS).** Cursor previously installed via hardcoded `runtime === 'cursor'`/`isCursor` branches in `bin/install.js`; its install/uninstall now runs through the imperative adapter, and every hardcoded cursor branch is folded into descriptor-driven `runtime.hostBehaviors` (reapplyCommand, frontmatterDialect, hooksJsonSurface, skipSharedHooksInstall, reportCommandsDir, managedHookEvents). Install/uninstall output is **byte-identical** (golden parity asserted for all 16 runtimes). Two Context7-verified upgrades land: (1) **expanded hook-bus coverage** — GSD registers all 6 managed lifecycle events in Cursor's `hooks.json` (`preToolUse`, `stop`, `subagentStart`, `subagentStop` in addition to the original `sessionStart`/`postToolUse`), driven by a new descriptor-driven adapter module (`src/host-integration-adapters/imperative-hook-bus.cts`) that reads `hostBehaviors.managedHookEvents` instead of a hardcoded event pair; cite https://cursor.com/docs/hooks. (2) **named/background nested subagent dispatch** — Cursor's `dispatch.background`/`backgroundDispatch`/`nested` are all `true` with `maxDepth: 2`, so `shouldFlattenDispatch(cursor)` returns `false` and GSD's wave-based execution drives Cursor's native background + depth-2 nested subagent invocation instead of flattening to inline sequential calls; cite https://cursor.com/docs/subagents + https://cursor.com/docs/sdk/typescript. (#2089) (#2120)
- **Cline is now driven through the public Host-Integration Interface, with two capability upgrades (ADR-1239 / EoS).** Cline previously installed via hardcoded `runtime === 'cline'`/`isCline` branches in `bin/install.js`; its install/uninstall now runs through the imperative adapter, and every hardcoded cline branch is folded into descriptor-driven `runtime.hostBehaviors` (reapplyCommand, frontmatterDialect, skipSharedHooksInstall, localTargetIsProjectRoot, clineRulesSurface, localCommandsViaRules). Install/uninstall output is **byte-identical** (golden parity asserted for cline + claude/cursor/codex/opencode). Two Context7-verified upgrades land: (1) **`AgentPlugin.hooks.beforeTool` planning guard** — the `.clinerules/hooks/PreToolUse` file-convention hook (#787) is re-implemented as a real Cline SDK `AgentPlugin` that cancels write-class calls targeting `.planning/` (same fail-open semantics), driven by a new descriptor-driven adapter module (`src/host-integration-adapters/cline-sdk-binding.cts`); cite https://github.com/cline/cline/blob/main/docs/sdk/plugins.mdx. (2) **`createAgentModel` model overrides** — `DefaultGateway.createAgentModel({providerId, modelId})` is wired so GSD's per-subagent `model_overrides`/`model_profile_overrides` resolution applies to Cline subagents (`modelMode: active`); cite https://github.com/cline/cline/blob/main/docs/sdk/reference/gateway.mdx. Cline's dispatch deliberately stays **degraded/flat** (`maxDepth: 1`, read-only, no nested spawning) per the documented host restriction — never silently upgraded to full nested/background. (#2090) (#2132)
- **Hermes Agent is now driven through the public Host-Integration Interface, with three capability upgrades (ADR-1239 / EoS).** Hermes previously installed via hardcoded `runtime === 'hermes'`/`isHermes` branches in `bin/install.js`; its install/uninstall now runs through the imperative adapter, and every hardcoded hermes branch is folded into descriptor-driven `runtime.hostBehaviors`. Three upgrades land: (1) **real plugin hook vocabulary** — GSD registers a new `extensionEvents: "hermes"` dialect carrying the 13 documented Hermes plugin events (`pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`, `on_session_start`, `on_session_end`, `on_session_finalize`, `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`, `pre_approval_request`, `transform_tool_result`), replacing the borrowed `hookEvents: "claude"` 6-event surface that silently never fired; cite https://github.com/nousresearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md. (2) **dispatch posture** — Hermes' `dispatch.nested: true` with `maxDepth: 1` is correctly negotiated (not silently flattened). (3) **branding/category metadata** — `DESCRIPTION.md` category descriptions, `version:` frontmatter, and branding rewrites are now descriptor-driven rather than hardcoded. Install/uninstall output is byte-identical (golden parity asserted for all runtimes). (#2091) (#2134)
- **Qwen Code now projects GSD's specialist agents as native subagents** — installing GSD into Qwen Code writes `~/.qwen/agents/gsd-*.md` files you can invoke directly (planner, executor, code-reviewer, …) instead of reaching them only through skill prose, and a `SubagentStart` hook now fires alongside `SubagentStop`. Qwen's install is driven by its negotiated capability descriptor instead of hardcoded runtime special-cases. (#2092) (#2153)
- **Kilo Code now supports native hooks, active-model routing, and named subagent dispatch** — installing GSD into Kilo wires a lifecycle-hook plugin, keeps each agent's requested model instead of dropping it, projects GSD's specialist agents as invokable subagents, and documents the GSD MCP companion. Kilo's install is driven by its negotiated capability descriptor instead of hardcoded runtime special-cases. (#2093) (#2156)
- **GSD skills installed for Trae now carry SOLO stage metadata** — Trae's SOLO Agent can recognize GSD skills as workflow-stage skills for auto-invocation instead of requiring manual triggering. Several of Trae's install branches (shared-hooks gating, path rewrites) also move onto its capability descriptor. Note: the stage-metadata field is a best-effort/inferred shape — Trae publishes no formal schema. (#2094) (#2157)
- **Installing GSD into Antigravity now writes the `permissions.allow` rules its CLI documents** — so GSD's own reads and hooks aren't stuck on interactive prompts — and registers GSD's companion MCP server via a standalone `mcp_config.json` (best-effort: Antigravity's raw config schema isn't published, so this uses the Gemini-CLI-successor format). Antigravity's install is now driven by its negotiated capability descriptor instead of hardcoded runtime special-cases. (#2096) (#2165)
-
**Augment Code now installs through its capability descriptor, with a native MCP companion** — installing GSD into Augment registers the GSD companion server in Augment's `settings.json` `mcpServers` and drives command/skill/agent conversion from Augment's negotiated descriptor instead of hardcoded runtime special-cases. (#2097) (#2166)
-
**CodeBuddy now wires GSD's full extended lifecycle hook set and is driven by its capability descriptor** — installing GSD into CodeBuddy now registers `SubagentStart`, `SubagentStop`, `Stop`, and `PreCompact` hooks in its `settings.json` (it previously had none of these), matching the coverage Qwen/Kimi already ship, and CodeBuddy's install is fully descriptor-driven instead of via residual hardcoded runtime branches. (#2098) (#2169)
-
**GitHub Copilot now wires GSD's full lifecycle hook bus and is driven by its capability descriptor** — installing GSD into Copilot registers `preToolUse`, `postToolUse`, `userPromptSubmitted`, and `sessionEnd` handlers in its `hooks/gsd-session.json` (beyond today's `sessionStart`-only advisory), and Copilot's residual hardcoded runtime branches are folded onto descriptor-driven `hostBehaviors`. (#2099) (#2172)
- **Windsurf now enforces GSD's write/command safety guards through Cascade's native hook bus** — installing GSD into Windsurf registers blocking `pre_write_code`/`pre_run_command` hooks in `.windsurf/hooks.json` (exit-code-2 blocking) and drives Windsurf's install from its capability descriptor instead of hardcoded runtime branches. (#2100) (#2190)
- **ZCode's install is now driven and regression-tested through its capability descriptor** — ZCode joins the dogfooded declarative-adapter reference hosts with a byte-identical install, and its shared-hooks exclusion is folded onto `hostBehaviors` instead of a hardcoded runtime branch. (Hook-automation and MCP upgrades remain blocked on ZCode publishing its on-disk config formats.) (#2101) (#2195)
- **Codex/OpenAI default models advance to the GPT-5.6 family (Sol/Terra/Luna)** — the Codex runtime tier defaults and the `openai` provider preset now resolve to current-generation model IDs instead of the superseded GPT-5.4/5.5 line, so Codex users on default profiles get improved agentic coding (Sol) and lower costs (Terra/Luna) without changing any config. (#2122) (#2146)
- **Internal: the installer's `program` (display-name) + `command` (slash-invocation) chains are now single-source lookups** — the 14-line `program` chain (an exact duplicate of `runtimeLabel`) → `getRuntimeLabel`, and the 14-line `command` chain (the per-runtime `/gsd-new-project` syntax: gemini `/gsd:`, codex `$`, cursor skill-mention, kimi `/skill:`, default `/gsd-new-project`) → new `getRuntimeNewProjectCommand(runtime)` helper (ADR-1239 Phase B / #1679 AC2 slice 4). `runtime ===` count in `bin/install.js`: 53 → 25 (cumulative this session: 129 → 25). Stdout strings preserved byte-for-byte; no install-output change (golden-parity 16/16). No user-facing change. (#1813)
- **Internal: the installer's per-function `is<Runtime>` flag-declaration blocks are now a single `runtimeFlags` lookup** — the four duplicated `const isX = runtime === 'x'` blocks in `bin/install.js` (uninstall / writeManager / install / a fourth helper — 48 branches) are collapsed into one `runtimeFlags(runtime)` helper in `runtime-name-policy.cts` (ADR-1239 Phase B / #1679 AC2 slice 3). The add-a-host tax for flags is removed (one `RUNTIME_FLAG_IDS` entry, not four declaration blocks). Install output is byte-identical for all 16 runtimes (golden-parity asserted); `runtime ===` count in `bin/install.js`: 101 → 53. No user-facing change. (#1811)
- **Internal: third-party descriptor loader enforces `configHome` write-confinement at load time** — `loadRegistry({includeInstalled:true, configHome})` now rejects (skip + warn, fail-closed) any installed third-party host-plugin descriptor whose declared `destSubpath` resolves outside the supplied `configHome`, before it is composed into the registry (ADR-1239 Phase C-2 / #1681 slice 2). The `configHome` option is optional and backward-compatible (omitted → no load-time check; install-time gate still bounds writes). No user-facing change for existing flows. (#1808)
- **Internal: agent install for cursor/windsurf/augment/trae/codebuddy now flows through the descriptor path** — ADR-1235 step 1 routes the trivial-converter runtime group's agents off the inline install() loop onto the descriptor-driven `installRuntimeArtifacts` path, applying the cross-cutting steps uniformly (pre-converter, no workflow-stamp). Agent output is byte-identical for all 16 runtimes (golden-parity asserted, global + local verified); no user-facing change. (#1764)
- **gsd-ui-checker gains an adversarial FORCE stance (LLM-playbook principle 16)** — the only verdict-producing critic that lacked one now resists rubber-stamping UI-SPEC contracts, with BLOCK/FLAG/PASS classification. Based on arXiv 2505.23840 (third-person objective persona), 2506.04975 (objective-not-hostile persona). (#1584)
- **Internal: the declarative embedding adapter is now named + bound behind a minimal `HostIntegrationInterface`** — `createDeclarativeAdapter({runtime})` (new `src/adapter-declarative.cts`) delegates in-process to `install-engine`'s `installRuntimeArtifacts`/`uninstallRuntimeArtifacts`, formalizing today's projection path as one of the two embedding adapters behind a common contract (ADR-1239 Phase C-1 / #1680 AC1). Output is byte-identical to today's install (gated by `golden-install-parity`). The full 6-point interface binding surface is deferred until the imperative adapter (AC2) fixes the shape (ADR-1239 open wire-shape question). No user-facing change — the adapter is not yet wired to any runtime path. (#1802)
- **Internal: getDirName is now derived from a documented `runtime.localConfigDir` descriptor field** — each runtime's local content-rewrite directory (e.g. `cursor`→`.cursor`, `copilot`→`.github`) moved from a hand-maintained if-chain into its capability descriptor (ADR-1239 Phase B), so it can no longer drift from the registry. Install output is byte-identical for all 16 runtimes (golden-parity asserted); no user-facing change. (#1757)
- **Internal: copyWithPathReplacement converter selection is now data-driven** — the installer's back-compat content-copy path replaced its 13 hardcoded `runtime === 'x'` flag chains with a single per-runtime dispatch table (ADR-1239 Phase B). Install output is byte-identical for all 16 runtimes (golden-parity asserted); no user-facing change. (#1759)
- **Phase-completion now writes `Status: All phases complete` instead of the overloaded bare `Milestone complete`** — the phase-level completion verb (`completePhaseCore`) was writing the same bare 'Milestone complete' string that the milestone-close verb uses for terminal state, causing a phase-level verb to own a milestone-level field. Per ADR-2207, phase-completion now writes the existing intermediate value 'All phases complete' (already used in gsd2-import.cts); milestone termination ('<version> milestone complete' / 'Awaiting next milestone') remains solely with the milestone-close verb. (#2204) (#2259)
- **#853 dispatch-flatten is now data-driven (ADR-1239 Phase B)** — whether GSD backgrounds the plan/execute orchestrator is decided from a documentation-sourced `backgroundDispatch` capability per host (via `gsd_run query dispatch-should-flatten`) instead of a hardcoded `runtime === 'codex'` check. **Cursor now backgrounds the orchestrator** (its docs document backgrounded subagent nesting); codex unchanged; all other hosts run inline. Fail-closed to inline on any uncertainty. (#1719)
- **Internal: companion MCP server module (interface points 1 + 5)** — `handleMessage`/`runServer` (new `src/mcp-server.cts`) is a minimal, dependency-free stdio JSON-RPC 2.0 server exposing `gsd_invoke_command` (→ the command-routing hub) + `gsd_read_state`/`gsd_write_state` (→ the Phase 3 stateIO seam), so any MCP-consuming host can drive GSD with no bespoke plugin (ADR-1239 Phase C-2 / #1681 slice 3a). Bin entry / packaging deferred to slice 3b. No user-facing change — the server is not yet wired to a bin entry. (#1809)
- **`requirements mark-complete` reports a per-surface write-set** — the command now returns a per-requirement `write_set` (checkbox + traceability surfaces) and a `write_set_complete` that is true only when every surface of every requirement applied, so a partial (checkbox-only) reconcile can no longer masquerade as full success even inside a multi-ID batch. Introduces the reusable ADR-2143 §5/§6 `Result` / `WriteSet` contract. (#2251) (#2251)
- **Internal: the imperative embedding adapter now composes the capability registry behind the same `HostIntegrationInterface`** — `createImperativeAdapter({runtime})` (new `src/adapter-imperative.cts`) calls `loadRegistry({includeInstalled:true})` (first-party-wins + consent + fail-closed — identical trust semantics to the CLI) and binds the engine surface behind the same contract the declarative adapter (AC1) satisfies, plus a `registry` accessor for an in-process host to bind its primitives to (ADR-1239 Phase C-1 / #1680 AC2). Concrete host binding is deferred to Phase 5. No user-facing change — the adapter is not yet wired to any runtime path. (#1803)
- **Internal: the model adapter seam exposes `passive` + `active` adapters selected by `modelMode`** — `createModelAdapter({modelMode})` (new `src/model-adapter.cts`): `passive` formalizes today's tier routing (delegates to `model-resolver.resolveModelForTier`), `active` is a host-supplied `sendRequest` seam (VS Code `vscode.lm` / pi providers), fail-closed until Phase 5 binds a concrete provider (ADR-1239 Phase C-1 / #1680 AC3). No user-facing change — the seam is not yet wired to any runtime path. (#1804)
- **Internal: derive the non-Claude runtime list from the capability registry** — `NON_CLAUDE_RUNTIMES` is now computed from the capability registry instead of a hand-maintained literal, so it can no longer drift from the per-runtime descriptors. No user-visible behavior change (the list is identical). (#1728)
- **Honest verifier — verify-phase now abstains on non-inferable `backstop` truths instead of confidently false-passing them (#1154).** When the spec's edge-probe marks a truth non-inferable (`verification: backstop`) and the verifier cannot confirm it with explicit evidence (a passing wired held-out/property test, or a directly-observed behavior), it now reports `human_needed` with reason `insufficient_spec` ("unverified — held-out test recommended") rather than a silent `passed`. Autonomous runs complete with "N unverified non-inferable checks"; interactive runs route to the end-of-phase human checkpoint. Inferable truths are never abstained (over-abstention guard); abstention is exogenous (driven by the tag, not self-judgment). Truth-axis mirror of the prohibition judgment-tier (ADR-550 D4). (#1738)
- Document Claude Code's advisor-tool inheritance in the model-profiles reference: the session-level advisor is inherited by all GSD subagents and composes with per-agent tiering, with candidate executor/advisor pairings, when it is worth enabling, and the session-level (no per-agent control) constraint. (#1922)
- **Extraction discipline for strict-format agents (LLM-playbook principle 8)** — gsd-doc-classifier and gsd-doc-synthesizer apply taxonomy/precedence rules directly without inventing content, reducing reasoning-induced format drift. Based on arXiv 2504.05081 (few-shot beats CoT for pattern tasks), 2506.00069 (terminal instruction placement), 2505.14810, 2505.11423. (#1584)
- **Internal: extracted the runtime-artifact install engine from `bin/install.js`** — `installRuntimeArtifacts`/`uninstallRuntimeArtifacts`/`installOpencodeFamilySkills` and their helpers now live in a dedicated `gsd-core/bin/lib/install-engine.cjs` module (ADR-1239 Phase B), so adapters can import the install pipeline instead of reaching into the 12k-line installer. Install output is byte-identical for all 16 runtimes (golden-parity asserted); no user-facing behaviour change. (#1735)
- **MemPalace `memory_mode` `kg_backend` and `replace` are now functional** — selecting either mode now routes recall through the palace instead of silently behaving like `augment`: `kg_backend` treats the palace temporal KG as the primary knowledge-graph source (native `.planning/graphs/` as fallback), and `replace` resolves recall through the palace as the source of truth. Every mode stays default-resilient — an unreachable palace falls back to native memory and no memory is lost. (#2010) (#2010)
- **`/gsd:surface` and `--materialize` now produce byte-identical agent output to a fresh install** — surface-path agents for descriptor-driven runtimes (cursor, windsurf, augment, trae, codebuddy, copilot, antigravity) now receive the same path-prefix rewrite, Co-Authored-By attribution, runtime-specific conversion, and body normalization as the install path. Copilot and Antigravity agents are now installed via the descriptor-driven path (copilot agents get the `.agent.md` filename rename). Cline remains on the inline loop (rules-only local branch). (#1575) (#2040)
- **Internal: hook-bus + stateIO adapter seams** — `createHookBus({bus})` (new `src/hook-bus.cts`, `host`/`engine`/`none` — engine is in-process pub/sub, host fail-closed, none silent) + `createStateIO({io})` (new `src/state-io.cts`, `filesystem`/`sandboxed-storage`/`session-log-append` — filesystem delegates to fs, the rest are fail-closed seams) (ADR-1239 Phase C-1 / #1680 AC4). Completes the Phase 3 adapter seam layer; concrete host binding is Phase 5. No user-facing change. (#1805)
- **Long-context model names render compactly in the statusline** — the verbose " (1M context)" suffix Claude Code appends to the model display name now collapses to a compact " (1M)" badge (tolerant of future window sizes and the abbreviated "ctx" variant: "(500K context)" → "(500K)", "(1M ctx)" → "(1M)"). Lossless — the long-context signal stays, the 12 characters of width don't. (#2160) (#2173)
- **Lazy-split `plan-phase.md` into a `steps/` directory** — ~4.7 KB lighter eager context per `/gsd-plan-phase` call via byte-invariant progressive disclosure (ADR-1610). (#1852) (#1934)
- **GSD subagents now self-load configured agent_skills regardless of orchestrator bash** — projects that map skills via `.planning/config.json` `agent_skills.<agent-type>` no longer silently lose them on `/gsd-autonomous` or Cursor, where `Skill()`-delegated workflow bash init did not reliably run. Each of the 22 consumer agents queries its own type at init and reads the listed skills, with a dedup guard so runtimes that also inject orchestrator-side (Claude Code) never carry two copies. (#1866) (#1868)
- **Internal: install/uninstall runtime labels are now sourced from a single `getRuntimeLabel` lookup** — the two duplicated `runtimeLabel` assignment chains in `bin/install.js` (uninstall + install) are collapsed into one curated label table in `runtime-name-policy.cts`, sibling to the registry-derived `getDirName` (ADR-1239 Phase B, #1679). Install output is byte-identical for all 16 runtimes (golden-parity asserted). Two console-label inconsistencies are normalized as a side effect: `kimi` shows 'Kimi CLI' in both sites, and `cline` uninstall no longer falls through to 'Claude Code'. (#1800)
- **Internal: external-descriptor trust gate — load-time `configHome` confinement** — `assertDescriptorConfined(descriptor, configHome)` (new `src/external-descriptor-trust.cts`) fail-closed rejects any installed third-party host-plugin descriptor whose declared `destSubpath` resolves outside the user-approved `configHome`, before its install plan runs (ADR-1239 Phase C-2 / #1681 slice 1). Defense-in-depth load-time twin of Phase 2's install-time `assertDestWithinConfigHome`. Not yet wired into the loader (slice 2). No user-facing change. (#1806)
- **Internal: the installer's runtime → global-config-home hook-pathogen fragment is now a single `getGlobalConfigHomeFragment` lookup** — the 14-branch `if (runtime === 'x') return "'...'"` chain in `getConfigDirFromHome` (`bin/install.js`, the hook `path.join()` codegen mapping) is collapsed into one table in `runtime-name-policy.cts`, sibling to `getRuntimeLabel` (ADR-1239 Phase B, #1679 AC2 slice 2). Generated hook output is byte-identical for all 16 runtimes (golden-parity asserted); antigravity's dynamic env-overridable resolution is preserved in the caller. No user-facing change. (#1801)
### Removed
- **Removed the sunset Gemini CLI runtime — use Antigravity CLI instead** — Google discontinued Gemini CLI on 2026-06-18, so `npx gsd-core --gemini` now prints a deprecation notice and points you to Antigravity CLI (the official successor), which GSD already ships as a first-class runtime. (#1928) (#1996)
### Fixed
- The `verify-work` security-blocked presentation no longer offers next-phase planning. When security enforcement blocks phase advancement (no `SECURITY.md` produced), the workflow now routes only to the current-phase fix instead of competing `/gsd:plan-phase {next}` and `/gsd:execute-phase {next}` options. (#1687)
- `milestone complete` and `roadmap analyze` now exclude the Phase 0 / Phase 999 backlog sentinels. A milestone whose only directory-less ROADMAP heading is a backlog sentinel can be completed without `--force`, and `roadmap analyze` no longer counts the sentinel in `phase_count` or routes `next_phase` into it. Completes the `^999` exclusion #1445 added to the progress denominators. (#1691)
- **`config-set` no longer silently coerces values into something the disk never sees** — `Number.isFinite` replaced `!isNaN` in the value parser so `Infinity`/`-Infinity` are no longer coerced to non-finite numbers that `JSON.stringify` then renders as `null` on disk while the CLI echoes `Infinity` (output ≠ disk). `context_window` now has a per-key validator requiring a finite positive integer (rejects `Infinity`, `0`, negatives, non-integers with a non-zero exit), and `project_code` is always persisted as a string so a leading-zero code like `007` survives verbatim instead of collapsing to `7`. Numeric coercion for genuine numeric keys (e.g. `granularity 42`) is unchanged. (#1581) (#2023)
- **`phase.complete` no longer reports a false `is_last_phase` on a `<details>`-wrapped checkbox checklist (#1591, #1752)** — when the active milestone's phase checklist was written as `- [ ] Phase N:` checkbox items inside a `<details>` block and the next phase had no directory on disk yet (still in planning), `phase.complete`'s `isLastPhase` roadmap-enumeration fallback used a heading-only pattern (`/#{2,4}\s*Phase…/`) that never matched checkbox items. It returned `is_last_phase: true, next_phase: null` on a mid-milestone phase and — via the milestone-complete cascade — wrongly flipped STATE.md to `Milestone complete` and decremented `progress.total_phases` (e.g. 8 → 7). The pattern now matches heading-style (`### Phase N:`), plain checkbox-list phases (`- [ ] Phase N:` / `- [x] Phase N:`), and the canonical **bold** checklist form the roadmap template emits (`- [ ] **Phase N: Name**`); `extractCurrentMilestone` already surfaces the `<details>`-wrapped checklist correctly, so no parser change was needed. Only the reproduced `phase.complete` fallback is changed; the heading-only sibling patterns elsewhere in `phase.cts` are untouched.
(#1819)
- The `<agent_skills>` block emitted by `gsd init` no longer leaks backslash paths into `@`-reference skill paths on Windows. The global skill directory (a native `path.join` result) was interpolated into the generated markdown without POSIX normalization, producing references like `@C:\…\skills\name/SKILL.md`; the reference is now normalized at the emit site so skill references use forward slashes on every platform. (#1736)
- **`/gsd-settings` no longer warns about four search-provider keys on fresh projects (#1747)** — `buildNewProjectConfig` emits seven search-provider availability flags and `research-provider.cts` `providerAvailability()` consumes all seven, but only three were registered in `VALID_CONFIG_KEYS` (`config-schema.manifest.json`). Running `/gsd-settings` on a freshly generated `.planning/config.json` printed `unknown config key(s) … tavily_search, ref_search, perplexity, jina — these will be ignored` even though the user never hand-edited the config. The four missing keys are now registered alongside `brave_search`/`firecrawl`/`exa_search` and documented in `docs/CONFIGURATION.md`; a drift guard in `tests/bug-2530-valid-config-keys.test.cjs` now requires every config-driven research-provider flag to be in the schema, so a future provider addition cannot reintroduce the drift. (#1814)
- **`gsd-tools state json` no longer reports conflated progress for an unversioned milestone (#1761)** — the ADR-1769 Phase 7 fix (#1794) taught `state sync` to leave Progress untouched when a milestone version is asserted but the ROADMAP has no versioned heading for it, but the `state json` **read** path still rebuilt progress via `buildStateFrontmatter`, whose phase-heading count fell back to the whole document and summed sibling milestones. `state json` therefore reported a conflated `total_phases` (e.g. 8 = 4+4 across two milestones) plus a derived `percent`, contradicting the sync guard on the very same project. The read path now mirrors the sync guard: when the asserted milestone cannot be bounded to a versioned ROADMAP heading, `total_phases` falls back to the on-disk phase-dir count and `percent` is omitted. Bounded milestones (versioned ROADMAP, or no milestone asserted) are unchanged; the signal rides on the existing `_diskScanCache` so `extractCurrentMilestone`'s return contract and its other callers are untouched. (#1818)
- **`gsd-graphify-update.sh` now reads the full multi-line command in Gate 2 (#1772)** — the PostToolUse auto-update hook joined `tool_name` + `\n` + `tool_input.command` and extracted the command with `sed -n '2p'` (line 2 only). Agent runtimes (Claude Code's Bash tool among them) routinely emit HEAD-advancing commits as multi-line scripts (`cd /path`, then `git add`, then `git commit …`), so line 2 was the `cd`, Gate 2's `*"git commit"*` match failed, and the rebuild silently no-op'd on real commits even with `graphify.auto_update: true`. The failure was invisible in manual probes because a single-line `git commit -m x` passes line 2 verbatim. The hook now captures line 2 through EOF (`sed -n '2,$p'`) so the `case` glob sees the full command string; single-line behavior is unchanged and multi-line commands without a HEAD-advancing op still no-op cleanly. (#1815)
- **`/gsd-thread close|resume` now writes the thread status/updated frontmatter (#1778)** — the thread workflow's CLOSE and RESUME branches invoked `frontmatter.set` with the pre-1.6 fully-positional shape (`frontmatter.set <file> <field> <value>`), but since 1.6 the dispatcher parses the file positionally and reads `field`/`value` from the named flags `--field`/`--value` via `parseNamedArgs`. The positional form left `field`/`value` undefined, `cmdFrontmatterSet` errored `file, field, and value required`, and the writes were silently skipped — so closing a thread never marked it `status: resolved` and resuming never marked it `status: in_progress`, with the error scrolling past on every thread command. All four sites (CLOSE `status`+`updated`, RESUME `status`+`updated`) now use the 1.6 hybrid form that `verify-work.md` already uses (`frontmatter.set <file> --field <field> --value <value>`). (#1816)
- **The installer no longer copies dead lifecycle hook scripts for Kilo and ZCode** — both declare `hooksSurface: 'none'` and have no plugin surface, so the staged `hooks/*.js`, `hooks/*.sh`, `hooks/lib/` and the CommonJS `package.json` marker were dead weight in `~/.kilo/` and `~/.zcode/`. The two hook-copy guards in `install.js` now exclude Kilo and ZCode alongside the other no-hook runtimes. OpenCode, which also declares `hooksSurface: 'none'`, is deliberately kept: its native plugin adapter (#1914) spawns those staged hooks via OpenCode's event bus and needs both them and the marker. (#2057)
- **Test gates can no longer hang forever on a watch-mode test runner.** vitest defaults to watch mode in an interactive terminal (exactly where `gsd-execute-phase` runs), so a resolved `npm test` / `pnpm test` that maps to vitest never exited and the orchestrator waited indefinitely until the user manually intervened. Every GSD test-command gate — the regression gate, the post-merge gate, the audit-fix gate, and the verify-phase gate — now routes the resolved command through a shared `normalize-test-command` helper that rewrites it to a one-shot form (direct vitest → `vitest run`; jest `--watch` → `--watchAll=false`; a package-manager `test` script backed by watch-vitest → `CI=true` prefix; already-one-shot commands are left unchanged). The three gates that previously hung or silently continued — the regression, post-merge, and audit-fix gates — additionally bound execution with a configurable `workflow.test_gate_timeout` (default 600s), aborting or surfacing the cause on timeout instead of hanging; the verify-phase gate was already bounded (a fixed 5-minute limit) and keeps it, now naming watch mode on timeout. The normalizer only rewrites a runner named as a standalone command token (so paths/targets like `run-vitest.js` are never mangled), is length-capped and linear-time on adversarial input, and only reads a regular-file `package.json`. (#2060)
- **`settings-advanced.md` no longer has an orphan `</step>` around §8 Model Policy** — the §8 Model Policy block ended with a closing `</step>` but had no matching opening tag (5 opens / 6 closes), leaving its content as loose inter-step prose that could fail to execute reliably. Added the missing `<step name="model_policy">` opener so the section is a proper step. A new workflow `<step>`-tag-balance regression guard (fenced-code-stripped) now blocks any future orphan tag across all top-level workflows. (#1864) (#2014)
- **The runtime launcher now honors `CLAUDE_CONFIG_DIR`** — the `gsd_run` preamble embedded in every workflow/agent resolved the Claude global install only at `$HOME/.claude/gsd-core/bin/`, while the installer honored `CLAUDE_CONFIG_DIR`, so a global install redirected via `CLAUDE_CONFIG_DIR` was invisible to every `gsd_run` call (every GSD command failed with `gsd-tools.cjs not found`). The Claude resolver arm now uses `${CLAUDE_CONFIG_DIR:-$HOME/.claude}` — matching the installer and the other runtimes' `${VAR:-default}` pattern — so a custom `CLAUDE_CONFIG_DIR` is found and the default `$HOME/.claude` path is unchanged. Re-synced into all 95 workflows/agents; two capped workflows trimmed to stay under their byte budgets. (#1865) (#2024)
- **Node-test prohibition proofs now require a clean-fixture causation control** — a `node-test` prohibition's fail-first proof no longer accepts a deceptive content-independent negative test (one that reds merely because `GSD_PROHIB_SUBJECT` is *set*, ignoring the subject's content). The `check_clean_fixture` control is now **mandatory** for the `node-test` kind: a descriptor that omits it is un-provable and hard-gates, rather than greening on the violation alone. **Breaking (Hyrum):** a previously-green node-test prohibition with no clean fixture now hard-gates — blast radius is zero in-tree (no `node-test` prohibition ships today). The `lint-rule` kind is unchanged (its subject IS the linted file, no `GSD_PROHIB_SUBJECT` indirection). (#1906) (#2001)
- **Third-party capabilities now work on installed layouts.** `capability install` no longer rejects capabilities with a real `engines.gsd` range as "incompatible with GSD 0.0.0" — the host version is now read from the authoritative `gsd-core/VERSION` file across every runtime and the `capability install` CLI. The installer also now ships the registry generator scripts (`gen-capability-registry.cjs`, `gen-loop-host-contract.cjs`), so installed third-party capabilities actually compose into the loop instead of being silently discarded. (#1938)
- **`/gsd:verify-work` preserves verification state across gap-closure execution and no longer auto-promotes deferred follow-ups into blocking gaps** — resuming after `/gsd:execute-phase --gaps-only` used to lose the verification state: the UAT `## Gaps` still read `status: failed` even after their fix plans executed, so verify-work re-diagnosed them as fresh blockers, spawned a new gap plan, and reported only the new plan as verified. A state contract now links each gap to its fix plan: every UAT gap carries a stable `gap_id` (`G-{phase}-{N}`), gap-closure plans tag the ids they address in their frontmatter (`gap_ids: […]`), and a new `reconcile_gaps` step on resume marks a gap `status: resolved` when its plan has a matching `*-SUMMARY.md` — so fixed gaps aren't re-diagnosed and the phase can close. Separately, a deferred-follow-up branch captures future-work ideas (signals like "later", "next version", "out of scope") into a `## Deferred Follow-Ups` section instead of creating a blocking gap/plan. (#1921) (#2025)
- **`roadmap update-plan-progress` no longer counts stray non-plan `*-SUMMARY.md` files against phase completion** — remediation/gap-closure summaries (e.g. `30-FIX-CR02-SUMMARY.md`, `30-GAPCLOSURE-SUMMARY.md`) inflated `summary_count`, and once `summary_count >= plan_count` the phase silently flipped to `Complete` (checkbox checked, date stamped) even though several plans had no summary. A new `countMatchedSummaries` helper (core-utils) pairs summaries to plans via the `PLAN→SUMMARY` marker swap + the `<stem>-SUMMARY.md` form (layout-agnostic across root, bare, and nested layouts), so only a summary that corresponds to a real plan counts. Wired into `scanPhasePlans` (fixing roadmap listing, state sync, verification, workstream inventory at once) and `cmdRoadmapUpdatePlanProgress`. (#1988) (#2016)
- **`milestone complete --ws` requirements archive header now points at the workstream REQUIREMENTS.md** — the archive header string hardcoded the root path (`` `…see .planning/REQUIREMENTS.md` ``), so a workstream archive directed readers at the wrong file even though #1917 had already fixed the archive *locations* to land inside the workstream. The display path is now derived from the same workstream-aware `reqPath` the writer uses (`path.relative(cwd, reqPath)`), so root behavior is byte-identical and the workstream case correctly reads `.planning/workstreams/<ws>/REQUIREMENTS.md`. (#1993) (#2015)
- **Load-failed capability gates now fail open with a loud warning instead of blocking the whole project** — when an installed overlay (third-party) capability failed to load (e.g. an incompatible `engines.gsd` range) but had declared a `gate`-kind loop hook, the loop resolver injected a blocking synthetic gate (`blocking:true`, `onError:halt`) at every point where that capability declared a gate. A single incompatible capability therefore halted every `ship:pre` and `verify:post` in the project — unrelated to what the gate would have checked, and with no remediation surfaced. The resolver now injects no gate and instead emits a loud warning — to stderr and in the `loop render-hooks` envelope's `warnings` array — naming the load-failure reason and the exact `gsd capability remove <id>` remediation, and the loop proceeds (fail open). The capability id embedded in that remediation is validated against the canonical id shape first, so a malformed overlay directory name cannot inject shell metacharacters into the surfaced command. The loader still records `_overlay.blockedGates`; only the consequence changes from block to warn. `step`/`contribution` overlays were already skip-open. (#2009) (#2075)
- **`phase.complete` now updates the `## Progress` rollup row even when an earlier phase-numbered table precedes it** — the Progress-row writer used a non-global regex that matched *any* table row starting with the phase number, so it bound to the first such row (e.g. a `| Phase | Requirements | Count |` coverage table), no-op'd on the wrong 3-column row, and never reached the real Progress row. The regex is now scoped to the `## Progress` section so it binds to the correct table. The command still returned `roadmap_updated: true` (that field is `fs.existsSync(ROADMAP.md)`), masking the silent failure. (#2012) (#2032)
- **context7 now works for plugin-marketplace installs (8 agents regained doc lookup)** — the agents granted only `mcp__context7__*`, which matches a standalone context7 MCP server but not the official Claude Code plugin-marketplace install (`context7@claude-plugins-official`), whose tools are named `mcp__plugin_context7_context7__*`. The grant never matched, so advisor/ai/domain/phase/project/ui-researcher + planner + executor silently lost documentation lookup and fell back to WebSearch. All 8 agents now grant both forms, the researcher profile table is updated, and a parity guard asserts no agent grants the standalone form without the plugin form. (#2017) (#2029)
- **`applySurface` no longer deletes every `gsd-*` agent when the skills manifest resolves empty** — the agent-prune loop in `_syncGsdDir` deleted any `gsd-*.md` not in the staged set, and when the manifest was empty/unresolvable (null manifest, no array entries, no `files` key, or an unresolvable install source root), the staged set was empty → every agent was pruned. Skills were guarded by `pruneSkillDirs`'s manifest-membership check (conservative preservation on empty manifest); agents had no equivalent. The agent-prune loop is now skipped when the manifest is empty/absent, so agents are preserved while copy (adding genuinely new agents) still runs. (#2018) (#2031)
- **`planning-config.md` global-learnings path corrected to `~/.gsd/knowledge/`** — the `features.global_learnings` row directed users to `~/.gsd/learnings/`, but the implementation (`src/learnings.cts`, `execute-phase.md`) stores and reads global learnings from `~/.gsd/knowledge/`. Anyone following the docs to inspect, back up, or seed their global learnings looked in a directory the code never touches. (#2019) (#2026)
- **Removed dead SDK file references from runtime-loaded markdown that triggered an infinite `find.exe` storm on Windows** — `agents/gsd-executor.md` pointed at `sdk/src/query/QUERY-HANDLERS.md` and `gsd-core/workflows/reapply-patches.md` at `sdk/dist/cli.js`, both retired with the SDK package (ADR-0174). AI runtimes that resolve doc references by filesystem search ran `find / -iname …`; on Git Bash for Windows `/` maps to the drive root, so `find.exe` traversed the whole disk (14h+, orphaned processes, 4M+ open handles each, unkillable). The references now resolve to live paths, and a new regression guard asserts no `sdk/src|sdk/dist|sdk/handlers` file references remain in agents/workflows/references markdown. (#2020) (#2027)
- **`roadmap update-plan-progress` no longer checks the phase checkbox without verification** — the command stamped the phase-level ROADMAP checkbox and completion date the moment the last plan summary landed (called routinely after every wave and every plan), with **no verification gate** — unlike `phase.complete` which correctly requires `readVerificationStatus(...).status === 'passed'`. Now `isComplete` requires both all plan summaries AND a passed verification, matching the `cmdPhaseComplete` contract, so the checkbox only fires after `gsd-verifier` has confirmed the phase. (#2022) (#2030)
- **`phase complete` no longer marks a milestone done out of order, nor silently writes root state in workstream mode.** Completing the numerically-highest phase while an earlier phase was still outstanding wrongly flipped STATE.md to `Status: Milestone complete` (the milestone-end check only looked for higher-numbered phases, so an out-of-order completion — e.g. Phase 10 before Phase 9 — read as the end). It now reports milestone-end only when every lower-numbered phase in the milestone is checked complete. Separately, in workstream mode with no active workstream, `phase complete` previously fell back to root `.planning` and wrote STATE.md/ROADMAP.md (and the mislabel) into the shared root other workstreams read; it now fails safe — asking for `--ws <name>` or an active workstream — mirroring the existing `init progress` guard. (#2066) (#2066)
- **Phase directories whose slug begins with a single digit now resolve correctly.** A phase like `46-6-rs-pipeline-orchestrator` (roadmap name "6 Rs Pipeline Orchestrator") had its phase token over-collected as `46-6` instead of `46`, so `gsd-tools` phase-by-number lookups resolved `phase_dir=null` / `has_context=false` (breaking `init.plan-phase`, `init.phase-op`, and downstream execute/verify/ship). Numeric phase-token components must now be zero-padded (≥2 digits), so a single-digit slug word is no longer absorbed into the token. Fixed consistently across every same-class implementation — `extractPhaseToken`, `PHASE_TOKEN_FROM_DIR_RE` and `canonicalPlanStem` (health checks / plan pairing), `isDirInMilestone`'s numeric matcher (milestone filtering), and `extractCanonicalPlanId` — so the health-check and milestone-filter subsystems are fixed alongside phase resolution. (#2059)
- **`gsd-tools config-set <key> null` now clears (removes) the key instead of persisting the literal string `"null"`.** The documented "Clear" action previously fell through the value parser and stored `"null"` — a truthy value — so "cleared" keys stayed set and `config-get` returned `"null"`; for secret keys (`brave_search`/`firecrawl`/`exa_search`) a masked success line hid a truthy value on disk that integrations could pass along as a real credential. `config-set <key> null` now deletes the key (short-circuiting the typed per-key validators so clearing an enum/boolean/number key removes it rather than being rejected), making the "Clear" flows in `settings-integrations.md` / `settings-advanced.md` actually clear. (#2058)
- **`init plan-phase` no longer collapses foreign-prefixed task/workstream IDs into numeric phases** — a query like `MEM-01` (where `MEM` is not the configured `project_code`) used to have its prefix stripped and resolve to the unrelated numeric Phase 01; it now reports `phase_found: false` unless a phase directory or roadmap entry literally carries that prefix. The configured `project_code`'s own prefixed phases (e.g. `LKML-01` under `project_code: LKML`) continue to resolve as before. (#2056) (#2105)
- **`phase complete` no longer ticks the wrong phase's ROADMAP checkbox** — completing a phase whose number also appears in a later phase's description (e.g. an idempotent re-run of an already-complete phase) used to mark the *wrong* phase done, because the checkbox-matching regex greedily spanned from `]` to any later "Phase N" mention instead of only the immediately-following phase title. (#2067) (#2079)
- **`gsd-tools effort sync` no longer crashes in an installed runtime.** In any global install (e.g. `~/.claude/gsd-core/`), `effort sync` threw `Cannot find module '../../../bin/install.js'` — the command reached into the package-root `bin/install.js` for its install-time effort resolvers, but the installer only copies the `gsd-core/` subtree into a runtime home, so that file is never present there. As a result, `effort` config changes (`routing_tier_defaults` / `agent_overrides`) silently never reached installed agents without a full reinstall. The two resolvers (`readGsdEffectiveEffortConfig` + `resolveInstallTimeEffort`, with their helpers) are now extracted into a shipped `gsd-core/bin/lib/install-effort-resolver.cjs` that both `effort sync` and the installer import — a single source of truth that is always present in the installed tree. (#2076) (#2076)
- **`model_overrides` and per-phase-type models now actually apply to the assumptions-analyzer, code-reviewer, and code-fixer agents on Claude Code.** Previously `model_overrides["gsd-code-reviewer"]` / `["gsd-assumptions-analyzer"]` / `["gsd-code-fixer"]` (and `models.verification` / `models.discuss` / `models.execution`) were accepted and resolved but silently dropped — the workflows spawned these agents with no model, so they inherited the session model and the configured routing never took effect (no warning). Every spawn now threads its resolved model: `discuss-phase-assumptions`, `code-review`, and `code-review-fix` (both the re-review and the two fixer spawns) resolve it inline, and `quick`'s review step uses the code-reviewer's own resolved model instead of the executor's. The stale "`discuss` — reserved, no subagent" model-profile docs are corrected to list `gsd-assumptions-analyzer`, and the `verification` row now includes `gsd-code-reviewer`. (#2074) (#2074)
- **`/gsd-review`'s Antigravity CLI reviewer no longer fails silently on large prompts, unavailable pinned models, or pre-session stalls** — the `agy` invocation now uses a file-reference prompt to avoid exec arg-list overflow, is wrapped in an external wall-clock `timeout` paired with `--print-timeout` because `--print-timeout` cannot fire before `agy` creates a session, passes `--model` from `review.models.agy` when set as an escape hatch for a 404'd pinned model, and its empty-output stub now surfaces an `agy` cli.log diagnostic instead of a bare generic message. Supersedes the #687 "no external killer / inline `$(cat)`" contract, which predated `agy` gaining `--model` and predated its own guidance to pair `--print-timeout` with a terminal timeout. (#2073) (#2109)
- **`init execute-phase`, `init verify-work`, and `init phase-op` no longer collapse foreign-prefixed task IDs to numeric phases** — `MEM-01` under `project_code: LKML` was silently stripped to `01` and resolved to the unrelated numeric Phase 01, because the #2056 guard was applied only to `init plan-phase`. The guard is now extracted into shared helpers (`guardedFindPhase` / `guardedGetRoadmapPhase`) that delegate to the canonical `isForeignPrefixedPhaseQuery` from `phase-id.cts`, and all four init commands route through them. (#2104) (#2149)
- **`commit --files` now commits only the declared paths** — `gsd-tools commit --files A B` previously ran a bare `git commit` that absorbed the entire staged index, silently sweeping in unrelated files the caller never named. The commit now appends a pathspec (`-- <paths>`) so only the staged subset of `--files` lands in the commit; the no-`--files` default path is unchanged. Missing tracked files are still skipped (not committed as deletions, #2014), and when all declared files are missing the function short-circuits to `nothing_to_commit` instead of absorbing the index. (#2112) (#2148)
- **Fixed unresolvable bare `require('gsd-core/...')` in `gsd-surface` command doc** — the four `require()` examples now derive the engine path from `runtimeConfigDir` (resolvable at runtime), and the reinstall hint corrects `npm i -g gsd-core` to `npm i -g @opengsd/gsd-core`. (#2116) (#2213)
- **`milestone complete --dry-run` now prints a preview plan instead of silently mutating** — `gsd-tools milestone complete --dry-run` was neither parsed nor rejected, so a caller expecting a preview triggered the full destructive mutation (archive phases, move audit artifacts, rewrite STATE.md) with no way to back out. The `--dry-run` flag is now honored: it returns a JSON plan listing `would_archive` (roadmap, requirements, audit, phase dirs) and `would_update` (MILESTONES.md, STATE.md) targets with zero filesystem mutations. (#2118) (#2155)
- **`/gsd-secure-phase` now has a single SECURITY.md writer** — the `gsd-security-auditor` subagent previously held `Write`/`Edit` tools and was instructed to "write SECURITY.md" with no padded `<N>-` prefix and no template frontmatter, while the orchestrator's Step 6 also wrote the phase-scoped `<N>-SECURITY.md` from `templates/SECURITY.md`. The auditor is now return-only (drops `Write`/`Edit`, returns a structured verdict with `threats_open`); the orchestrator is the sole file writer. The workflow's Step 5 spawn constraints explicitly forbid the auditor from writing SECURITY.md. (#2119) (#2154)
- **Dead security scan exports removed; injection-scan docs corrected to match reality** — `scanEntropyAnomalies` and `shannonEntropy` were dead code with zero production callers (live hooks inline their own patterns for independence). REQ-SCAN-INJ-02/-03 now accurately describe what runs live (injection patterns, invisible Unicode) vs CI-only (base64-decode, codebase scan). (#2198) (#2211)
- **Custom STATE.md frontmatter keys are no longer dropped on every mutating verb** — syncStateFrontmatter rebuilt the frontmatter from a fixed schema, silently dropping any custom key. It now carries forward existing keys the schema does not own. (#2202) (#2233)
- **Non-frontend phases with `UI hint: no` are no longer blocked by the UI-SPEC gate** — the UI safety gate's token list included the bare token `UI`, which matched GSD's own `**UI hint**: no` metadata line and false-detected a UI, blocking backend/infra phases at /gsd-plan-phase. An explicit `UI hint: yes|no` is now authoritative and the hint line is no longer token-sniffed. (#2150) (#2222)
- **OpenCode reviewer no longer silently yields an empty review on large prompts** — `/gsd-review --opencode` now invokes `opencode run --format json` and reconstructs the review from the assistant text parts, so a large-prompt run where the default `build` agent ends its turn with zero output tokens no longer produces an empty stub. When the agent genuinely emits no text, the stub now reports the stop reason, output-token count, and captured stderr instead of a generic message. (#1936) (#1992)
- **`stale-bake-guard` hermeticity fix (test-isolation)** — the readGsdEffectiveModelOverrides subtest no longer reads the developer's real `~/.gsd/defaults.json`; the resolver now accepts a homedir seam so the test sandboxes HOME. (#2152) (#2223)
- **`/gsd-surface` (`list`/`status`) works on Claude Code global installs** — the installer now writes a `.gsd-source` marker pointing at its `commands/gsd` source, so `findInstallSourceRoot` resolves on the global skills layout (which ships no `commands/gsd` tree) instead of throwing `could not locate commands/gsd`. (#1487) (#1487)
- **`phase complete --phase N` now works alongside the positional form** — the phase verb family treated the first positional as the phase number, so `--phase 12` was passed as the literal phase name and failed with 'Phase --phase not found'. The phase family now accepts the --phase flag consistently with the state family, and unrecognized flags yield a usage error. (#2201) (#2231)
- **Third-party capability skills now surface correctly after install** — a skills-only `role: feature` capability installed `active` but its skills never reached the runtime surface, `capability enable`/`set` rejected it as `unknown capability`, and `capability list` disagreed with `capability state`. `resolveSurface` now unions the composed registry's `capabilityClusters` into the surfaced skill set (no on-disk linking), the writer validates against the composed overlay-aware registry, and `capability list` carries a `surfaced` field matching `capability state`. (#2054)
- Fixed: probe-core's runProbeCli now fails closed on per-item adapter garbage inside a well-shaped report envelope, matching its documented 'fails closed on adapter garbage' contract. (#1910)
- **`/gsd:verify-work` no longer silently terminates when all remaining UAT tests are blocked** — sessions with `blocked_count > 0` and `pending_count == 0` now route to `complete_session` as expected, enabling the zero-issues auto-transition path. (#1722)
- **state record-metric no longer appends per-plan rows into the By-Phase velocity table** — it now maintains its own Per-Plan Metrics table (self-created on first use), and its auto-create scaffold header is corrected. (#2253) (#2253)
- Codex reviewer now captures the review via codex's --output-last-message flag instead of redirecting stdout, so Windows process-teardown output no longer pollutes the review file and slips past the empty-output guard. (#1709)
- **`last_activity` now shows your local calendar day** — the clock seam derived the date by slicing a UTC instant, so in negative-UTC-offset zones during UTC's early evening the date-only `last_activity` field jumped a day ahead of the operator's actual date (and of `last_updated`'s local date). Operator-facing date fields now use a host-local calendar day while internal/cosmetic stamps stay UTC. (#2136) (#2216)
- **`milestone_name` is no longer clobbered with a delimiter-led fragment** — getMilestoneInfo's `##` heading regex was unanchored, so it matched a heading quoted inside backticks in the Milestones bullet and wrote garbage like `— Active Milestone` over the curated milestone name on every phase transition. Now consults the 🚧 marker first, anchors the regex to line start, strips the leading delimiter, and widens the preserve guard so a bad derive keeps the existing name. (#2135) (#2215)
- **`init milestone-op` now counts project_code-prefixed phase directories correctly** — fully shipped milestones using the standard prefixed directory layout no longer report `completed_phases: 0` or stay falsely incomplete. (#1844) (#1844)
- **`/gsd-quick` no longer halts with a stale-base worktree mismatch** — the worktree executor now degrades to sequential execution when its fork base has diverged from origin/HEAD, instead of spawning a worktree guaranteed to fail the base-mismatch guard. (#1991)
- **Setting `external_job.submit_timeout_ms` / `poll_timeout_ms` / `artifact_dir` in `.planning/config.json` now actually configures the SLURM adapter** — the keys were declared by the external-job capability but the adapter only read env vars, so config edits silently had no effect. The adapter now resolves them through the canonical capability-config seam (env override > config > registry default), surfaces the resolved `artifact_dir` in `submit` output, documents why the contribution registers at `execute:wave:post` (#1164 asks for `wave:pre`, which `execute-phase.md` does not dispatch today; wiring it is a core-loop change #1164 explicitly defers), and gains unit coverage for the CLI surface (`parseFlags`, `findPlanningDir`, `resolveExternalJobSettings`, `formatShowReport`). (#1164) (#2006)
- **The Antigravity reviewer in `/gsd-review` no longer reviews blind** — `agy -p` never granted the agent the repo under review, so it frequently anchored on its own scratch directory and returned plan-text-only verdicts counted at full consensus weight. The reviewer is now granted the repo (capability-probed `--add-dir`) and anchored to the absolute repo root; a review that still runs without repo access is stamped `[reviewed-without-repo-access]` and down-weighted in the Consensus Summary. The cursor-agent prompt gains the same absolute-root anchor. (#2176) (#2184)
- **Autonomous reruns now skip phases with deferred verification until you resume them explicitly** — if a prior `/gsd-autonomous` run recorded `verification_deferred_human` or `verification_deferred_gaps`, later reruns no longer drop back into the same prompt loop and instead point you at the saved resume command. (#1846) (#1846)
- **`requirements mark-complete` no longer reports silent success when the traceability row is missing** — it OR-ed its checkbox and table-row writes into one flag, so a checkbox-only reconcile returned a payload byte-identical to a full reconcile while the traceability row stayed Pending (and re-run masked it as already-complete). It now surfaces `table_unmatched` for IDs whose checkbox reconciled but whose table row is absent, and treats a checked box with no table row as partial rather than done. (#2140) (#2219)
- state prune now resolves the current phase from the canonical location — frontmatter current_phase, the Current Phase field, or the prose Phase: line scoped to the ## Current Position section — instead of extracting Phase over the whole document, where stateExtractField's pipe-table fallback could latch onto an unrelated | Phase | N | row (e.g. a historical verification table) and compute a wrong prune cutoff. (#1832)
- **`model_overrides` Claude model IDs now resolve to Agent-tool aliases on the claude runtime** — a full Claude model ID (e.g. `claude-sonnet-5`) in `model_overrides` was returned verbatim and silently dropped by the Claude Agent tool (whose `model` parameter documents only tier aliases), causing the spawned subagent to inherit the parent session model instead of the configured one. It now maps to the tier alias (`sonnet`/`opus`/`haiku`/`fable`), consistent with the `model_policy` path (#1144). Bare aliases, non-Claude values, and non-Claude runtimes are unchanged; a Claude ID with no alias warns once and falls through to tier resolution. (#2041) (#2048)
- Phase headers that place a parenthetical tag before the colon (`### Phase 26 (Cluster B): Title`) now resolve and enumerate the same as untagged headers. Previously the resolver returned not-found and `roadmap analyze`/listing silently dropped the phase (wrong phase_count, progress, and next_phase). Tag tolerance is applied at every phase-header read site; untagged and all existing header formats parse unchanged. (#1765)
- Executor and milestone-summary/forensics workflows now call state.* commands with named flags so the named-only router records metrics, decisions, blockers, and session continuity instead of silently dropping positional args. (#1873)
- **bug-1367 install test no longer fails on Windows CI when hooks/dist isn't pre-built** — the test ran install.js without building its hooks/dist precondition (a gitignored build artifact the unit lane doesn't build), so on a lane without pre-built hooks the installer hit "Failed to install hooks: directory is empty" and the before-hook threw. The test now builds hooks in its own before() (mirroring golden-install-parity). (#1926) (#1927)
- **`/gsd-fast` now appends Quick Task rows to STATE.md again** — the log_to_state column-count guard used an off-by-one awk formula (`NF-1`) that was always one too high, so the schema gate rejected the very table quick.md creates and silently skipped the STATE.md update. Also now supports the 6-column validate-mode table. (#2133) (#2214)
- Build the gitignored `hooks/dist/` artifact once upfront in `scripts/run-tests.cjs` (the same chokepoint as `ensureBuiltArtifacts`), before any concurrent install test spawns `install.js`. Closes the scoped-CI first-build empty-dir race that intermittently failed install tests with `Failed to install hooks: directory is empty` (e.g. `bug-3683-workflow-colon-namespace-leak`). (#1967) (#1968)
- **workstream progress no longer reports shipped milestones as `executing`** — `gsd-tools workstream progress` now derives each workstream's status from authoritative shipped signals (an archived milestone snapshot under milestones/, or a SHIPPED marker in the workstream ROADMAP) instead of trusting the mutable STATE.md `Status` field, so a stale field can never hide a shipped/archived milestone. The output adds `status_source` (`field` | `derived`) and `status_conflict` (true when the derived value disagrees with the stale field). (#1913) (#1916)
- **Windows install/upgrade/state-write operations no longer fail on transient antivirus/indexer file locks** — the fs.renameSync atomic-publish sites (install state, hooks config, capability ledger/lifecycle, phase/workstream/milestone dirs, roadmap, planning/state locks) now retry EPERM/EBUSY/EACCES via retryRenameSync instead of propagating the transient lock; enforced by the new local/require-fs-op-fallback lint rule (ADR-1703 Phase 6). (#1740) (#1742)
- reconstructFrontmatter now emits valid YAML for scalars and block-array items that were previously serialized unescaped. Values carrying a YAML indicator plus a literal quote/backslash, embedded control characters, the empty string, a leading YAML indicator, or leading/trailing whitespace are now routed through a properly escaped double-quoted form, so frontmatter round-trips through strict parsers (js-yaml, PyYAML) instead of corrupting the block on the next state sync. (#1807)
- **`phase remove` no longer destroys the Progress table when removing the last phase** — deleting a phase used a whole-document regex whose scan, on the final phase, ran past the section and swept away the `## Progress` heading and its entire tracking table; the deletion is now structurally bounded to the phase’s own section. (#2253) (#2253)
- **`phases clear` archives phase directories instead of destroying them** — at a milestone switch, committed phase directories were hard-deleted (`rmSync`) with no archive, silently losing browsable phase history (the #1447 dirty-tree guard was a no-op for the common committed case). Phase directories are now moved to `milestones/<version>-phases/` (collision-safe; timestamp fallback when no version resolves), so history survives the switch. The #1447 uncommitted-changes guard is retained as a secondary backstop. (#1871) (#1919)
- **Cross-AI review no longer silently drops the Codex/Claude/Gemini lanes on large plan sets** — the prompt-fed reviewer blocks in review.md invoked each CLI with no explicit timeout, so a slow source-grounded review was killed at the host default (~2 min) and the lane was silently lost. The workflow now directs a high Bash timeout and frames an empty output as a timeout (not the crash it was misdiagnosed as). (#2194) (#2226)
- **`/gsd-progress` no longer reports a stale root milestone in workstream mode** — in a multi-workstream project with no active workstream set, `gsd-tools query init.progress` silently fell back to root `.planning/STATE.md` (often stale) and reported it confidently. It now fails safe with an actionable error naming the available workstreams and the `--ws`/`workstream set` fix, so a stale root value is never reported. Flat mode and `--ws <name>` are unchanged. (#1912) (#1918)
- Windows: stop double-quoting $CLAUDE_PROJECT_DIR-anchored managed node hook paths during the #2979 legacy rewrite, which produced "\"$CLAUDE_PROJECT_DIR\"/..." and broke every node managed hook with MODULE_NOT_FOUND (PreToolUse-guard deadlock). (#1746)
- **phase complete now updates STATE progress on milestone-grouped roadmaps** — deriveProgressFromRoadmap parses the ## Progress table by header (column-by-name) instead of a fixed 4-column layout, so the 5-column milestone-grouped shape is no longer silently unparsed. (#2168)
- **Windows Claude Code hooks now work under PowerShell** — when Claude Code's hook runner resolves to PowerShell (not Git Bash), every GSD-installed hook failed with `Unexpected token` because the installer emitted bare quoted paths with no PowerShell call operator. The fix adds a `hookShell` parameter to the hook-command projection chain; when `hookShell='powershell'`, the `&` call operator is prepended. Default behavior (Git Bash, no prefix) is unchanged. (#2236) (#2261)
- **`capability state` and `loop render-hooks` now accept `--runtime` to override the auto-detected runtime** — previously both commands parsed only `--config-dir`, so the runtime config dir was derived from the persisted `.planning/config.json` runtime (precedence `GSD_RUNTIME` → `config.runtime` → `claude`). A repo that persisted `runtime:"codex"` resolved the config dir to `~/.codex`, where the Claude skill isn't installed, so every skill-bearing capability reported `surfaced:false` and `execute:post`/`verify:post` hooks silently no-op'd when the operator drove GSD from Claude Code. `--runtime <r>` (canonicalized, so aliases like `codex-app` work) now bypasses that fallback so the config dir resolves to the explicitly-named runtime's home. Behavior without the flag is unchanged. (#2003) (#2051)
- **`phase complete` no longer false-reports REQ-IDs as missing when the traceability table leads with a status column** — the parser required the REQ-ID in the first column, so a table shaped `| ☐ | REQ-01 | …` matched zero rows and every body REQ-ID was reported missing. It now matches REQ-IDs in any column. (#2203) (#2234)
- **`init milestone-op` now ignores backlog `999.x` headings when counting milestone phases** — parked backlog items no longer inflate `phase_count` or pin `all_phases_complete` false for an otherwise finished milestone. (#1843) (#1843)
- **Phase archival is now wired end-to-end across the milestone lifecycle** — finishes the #1871 follow-up: `phases archive` is now a real command (the half-wired alias is routed, no longer errors Unknown), `milestone complete` archives phase dirs by default (`--no-archive-phases` opts out), and `new-milestone` §6 stages the archive move + source removal in the same commit so history is preserved atomically rather than left as orphaned uncommitted deletions. (#1871) (#1924)
- **`state update-progress` no longer mangles the frontmatter and discards the progress suffix** — its Progress: regex matched the raw STATE.md including frontmatter, so the YAML `progress:` key was hit first (corrupting the frontmatter) while the body line stayed stale and was silently reverted on the next write, and any descriptive suffix after the progress bar was destroyed. It now targets the body line only and preserves the suffix. (#2177) (#2224)
- **`/gsd-ship` no longer silently drops the ship-status note from STATE on merge** — the track_shipping step committed the STATE ship-note after creating the PR but never pushed it, so on a fast merge the note stayed local-only and never reached the default branch. The ship-note is now pushed onto the PR branch with a `[ci skip]` trailer so it lands on merge without a redundant pipeline. (#2138) (#2217)
- **`/gsd-debug` no longer stalls on a phantom background handoff** — the orchestrator treated the foreground session-manager spawn as a background task and queried its agent ID via TaskOutput (which needs a task ID), then waited on a handoff that was never queryable. The workflow now states the spawn is foreground/blocking, forbids passing an agent ID to TaskOutput, and gives a lost-handoff recovery path. (#2196) (#2227)
- **Roadmap phase lookup now ignores fenced examples and the backlog sentinel lane** — `roadmap get-phase` and `init plan-phase` no longer return fenced sample headings as real phases or treat `999.x` backlog items as active milestone work. (#1845) (#1845)
- **`phase complete` no longer checks the wrong ROADMAP checkbox or writes the plan count into a shipped milestone** — the roadmap mutators ran unanchored and un-milestone-scoped, so they could flip a bullet inside a backticked prose literal or a Backlog entry instead of the closing phase's, and write the plan count into a same-numbered phase in a shipped milestone. The checkbox flip is now line-anchored and both writers are scoped to the current milestone. (#2200) (#2229)
- **`roadmap get-phase` resolves project-code-prefixed headings by bare number** — a bare-number query (e.g. `29`) now resolves a drifted `### Phase AB-29:` heading, matching the internal resolver used by `init.phase-op`; previously the CLI returned empty. A bare sibling (`### Phase 29:`) still takes precedence. A project-code-prefixed heading present only as a summary/checklist line (no matching detail section) now reports a `malformed_roadmap` diagnostic — for both prefixed and bare-number queries — instead of a silent empty result. (#2114) (#2139)
- **`milestone complete --ws` now archives into the workstream instead of root** — the archive paths (MILESTONES.md, the milestones/ archive dir, and the per-version MILESTONE-AUDIT.md) were hardcoded to root `.planning/`, so a workstream milestone close scattered its artifacts into root and never produced a workstream-local archive. They now derive from the workstream-aware planning base (`planningPaths(cwd).planning`); flat-mode (no --ws) is unchanged. (#1911) (#1917)
- **`phase complete` now reads milestone-grouped ROADMAP progress tables** — progress reported 0% on projects whose Progress table carries a Milestone column, because the reader assumed a fixed column position; it now resolves progress columns by name so both flat and milestone-grouped tables work (#2137). Quick Tasks logging via `/gsd:fast` also appends schema-correct, lock-safe rows instead of guessing the column count in shell (#2133). (#2248) (#2248)
- **Skill-bearing capabilities now surface correctly on flat command-layout installs** — on an install using the flat `commands/gsd-<stem>.md` source layout (e.g. a Claude Code local project install with no `commands/gsd/` subdir), every skill-bearing capability (`nyquist`, `code-review`, `security`, `ui`, `mempalace`, `ai-integration`, `profile-pipeline`) was silently reported `surfaced:false`/`enabled:false`/`active:false`, so their loop hooks (`verify:post`, `execute:post`, etc.) never fired even with the corresponding `workflow.*` toggle on. The skill-manifest resolver now detects the flat layout and produces the same stems the nested `commands/gsd/*.md` loader does. (#1858) (#2049)
- **Roadmap, requirements, and state table edits are confined to the right table** — the last ad-hoc table writers (phase completion updating roadmap progress, `requirements mark-complete`, and `state record-metric`/velocity) now route through the shared markdown-table seam, so a stray decoy table elsewhere in a document can no longer swallow a phase-progress update, a single ragged neighbouring row no longer silently aborts the whole edit, and per-plan metric recording no longer drops trailing section content or duplicates the section. (#2253) (#2253)
- **ROADMAP phase edits can no longer escape their section** — completing a phase updated its plan count and per-plan checkboxes with whole-document regexes that could bleed into a neighbouring phase; those per-phase writes are now structurally bounded to the phase own section via a new `withSection` / `withPhaseSection` seam (#2130, #2067, #2080). (#2250) (#2250)
- **STATE.md `## Session` fields now resolve on Windows** — the session-section reader used a `\n`-only heading regex that silently failed on a CRLF `## Session` heading, nulling all session state on Windows checkouts; it now reads through the CRLF-safe section seam. (#2253) (#2253)
- **Bullet/em-dash ROADMAP phases no longer resolve to `Phase null`** — the roadmap phase lookup matched only ATX headings with a colon, so a bullet entry like `- [ ] **Phase N — Name**` (which the roadmapper emits) failed to resolve and `Phase null` landed in STATE.md; a bullet-only ROADMAP also broke the milestone phase count. Phase lookup and the milestone filter now accept bullet/checkbox entries with an em-dash/en-dash/hyphen/colon separator. (#2199) (#2228)
- **Linuxbrew users no longer lose all GSD-managed hooks after `brew upgrade node`** — normalizeNodePath only recognized macOS Homebrew Cellar paths, so on Linux the version-pinned node path stayed baked into hook commands and 404'd after a node bump (and reinstall couldn't repair it). It now rewrites any Homebrew Cellar path — Intel, Apple Silicon, Linuxbrew, custom HOMEBREW_PREFIX — to the stable `<prefix>/bin/node` symlink. (#2185) (#2225)
- **`milestone complete` no longer corrupts the recorded phase** — closing a milestone (e.g. `v0.5`) previously overwrote `current_phase` in STATE.md with the version's minor digit, and a follow-up `state complete-phase` mined a bogus `0.5` token and rewrote the file; phase resolution is now anchored so the real phase is preserved and a milestone-closure line is rejected. (#2111) (#2131)
- **Headless MemPalace capture no longer fails silently** — the headless invocation `mempalace mine <path> --wing <wing> --room <room>` used a `--room` flag that does not exist on the `mine` subcommand (only `search` accepts `--room`), causing every headless/no-MCP capture run to fail with `unrecognized arguments: --room` and silently skip (onError: skip). The fix replaces the flag with MemPalace's documented room-assignment mechanism: stage the artifact under a room-named subfolder with a `mempalace.yaml` taxonomy so `detect_room()` assigns it via folder-path match. (#2220) (#2260)
- Fixed: a hand-authored non-inferable backstop truth with a stray trailing space or surrounding quotes no longer silently grades green — it correctly abstains (insufficient_spec), restoring the #1154 honest-verifier guarantee. (#1909)
- **`commit_docs` no longer silently disables on CRLF `.gitignore` repos** — git check-ignore falsely reports a trailing-slash path (e.g. `.planning/`) as ignored when the .gitignore has CRLF line endings with blank lines. isGitIgnored now strips trailing slashes before querying, so the false positive cannot occur. (#2206) (#2235)
- **Phase-directory resolution fails loud on cross-project collisions** — when two unrelated GSD projects share a `.planning/phases/` tree, a bare phase number silently resolved to the first `0N-*` directory found. The fix detects multiple matches and surfaces an `ambiguous_matches` result. (#2237) (#2262)
- **`scanPhasePlans` no longer counts PLAN-REVIEW artifacts as executable plans** — `*-PLAN-REVIEW.md` files were counted by the loose `/PLAN/i` fallback. The fix adds a `PLAN_REVIEW_RE` exclusion before the fallback. (#2252) (#2263)
- **Milestone audit no longer flags a not-yet-validated phase as a Nyquist failure** — a phase that was planned but never run through `validate-phase` now reports as NOT-VALIDATED (a "run validate-phase" TODO) instead of collapsing into PARTIAL alongside phases whose validation genuinely failed. (#2117) (#2209)
### Security
- **`gate="blocking-human"` checkpoints are no longer auto-approved by the execute-phase orchestrator** — the package-legitimacy gate (#2827) spans two layers: `gsd-executor` refuses to auto-approve a `gate="blocking-human"` checkpoint and escalates it via `checkpoint_return_format` so a human can vet the package, and `execute-phase`'s `checkpoint_handling` step decides what happens next. That step dispatched purely on checkpoint *type* and never read `gate`, so under `--auto` / `--chain` it immediately auto-approved the very checkpoint the executor had just refused to auto-approve (`human-verify → {user_response} = "approved"`). The slopsquatting defence was therefore inert in exactly the unattended mode where nobody is watching: an `[ASSUMED]`/`[SUS]` package reached install with no human ever seeing the verification prompt. `checkpoint_handling` now carves out `gate="blocking-human"` (and the package-legitimacy `what-built` markers) ahead of every auto-mode branch, routing those checkpoints to the standard present-to-user flow regardless of type. `references/checkpoints.md` documents the `gate` attribute and its two values for the first time — previously `blocking-human` appeared nowhere outside `agents/gsd-executor.md`, so no planner had a documented way to author a checkpoint that auto-mode could not bypass. The existing regression test asserted the executor half only; it now asserts the orchestrator half too, which is why it stayed green while the gate was open. (#2107) (#2113)
- **Hardened phase/roadmap/plan markdown parsing against quadratic-time (ReDoS) CPU exhaustion** — a crafted `ROADMAP.md`, `STATE.md`, or `PLAN.md` with large runs of unclosed `(`, `[`, `<tag>`, `<!--`, or `<details>` could drive the phase-header, Plans-count, `files_modified`, and `<tag>`-block parsers into O(n²) scans (tens of seconds on a ~1.5 MB file). Every affected regex is now linear: header tag/bracket clauses are length-bounded, the Plans-count scan is section-local, and all `<tag>…</tag>` extraction routes through a single ReDoS-safe seam. (#2128) (#2141)
- **Installer writes are now confined to the declared config home** — the workflow/skill emit path (`copyWithPathReplacement`) and the Codex config writer (`installCodexConfig`) now reject any destination that escapes the install root: crafted or absolute paths, path-separator agent names, and pre-existing symlinks are refused before any delete or write. Fail-closed: an install write with no declared root is rejected rather than written unconfined. (#1725)
- **Install write-confinement (ADR-1239 Phase B)** — the installer now rejects any runtime-descriptor `destSubpath` that would write or delete outside the user's config home (path traversal, the config root itself, NUL bytes) and refuses to follow a pre-existing symlink that escapes it. Hardening only; no change to legitimate installs. (#1706)
## [1.6.1] - 2026-07-01
### Added

View File

@@ -52,6 +52,12 @@ Module owning projection from dispatch results/errors to CLI `{ exitCode, stdout
### STATE.md Document Module
Module owning STATE.md parse, field extraction, field replacement, status normalization, and frontmatter reconstruction. It does not scan `.planning/phases` and does not own persistence or locking; phase/plan/summary counts arrive from inventory/progress Modules as inputs, and read-modify-write paths remain Adapters. Source of truth: `gsd-core/bin/lib/state-document.cjs`.
### STATE.md Transition Module
Module owning STATE.md lifecycle/maintenance transitions as intent-based methods (`beginPhase`, `advancePlan`, `completePhase`, `plannedPhase`, `milestoneSwitch`, `milestoneComplete`, `patch`, `sync`, `prune`, `update`, `rebuild`). Pure core `(content, intent, deps) → newContent` with injected I/O (file read/write, lock, disk scan); consults a field-classification table that names each STATE.md field's class (`derived-from-body` | `derived-from-disk` | `derived-from-external` | `curated` | `free`) and its preservation policy. Supersedes the 14 scattered RMW callbacks in `state.cts` and the direct `writeStateMd` callers in `milestone.cts:352` and `phase.cts:1770`; verify's `regenerateState` factory-reset primitive stays as a direct `writeStateMd` call. Absorbs `syncStateFrontmatter` + `readModifyWriteStateMd`'s post-sync preservation block; Encoding 3 (`cmdStateBuildFrontmatter`) stays separate — read path concern. Sibling/super-module of the STATE.md Document Module; consumes its `stateReplaceField`/`stateExtractField` primitives. Body section structure (`## Current Position`, `## Session`, etc.) lives as a constants block inside the Module. Append-only transitions (`addDecision`, `addBlocker`, etc.) stay on today's RMW seam for now. Targets the #1760/#1761/#1743/#1695/#1264/#1255/#1257/#3242 bug cluster. Migration per ADR-1372 §T6 sequenced as substrate + `beginPhase` first (PR1), then transition-by-transition with characterization tests first per transition. **ADR-1817 adds `rebuild` as the capstone 11th transition — the body-structure derivability contract.** Re-derives `## Current Position` prose from frontmatter and `## By-Phase Progress` table from phase dirs on disk; preserves `## Session` / `## Decisions` / unknown sections verbatim; de-duplicates `## Session Continuity Archive` (keep most-recent N, default 3); appends a structured audit entry to `## Rebuild Log` (`timestamp`, `kind`, `section`, `before`, `after`, `reason`) for every mutation. Hard idempotency guarantee: a no-mutation rebuild appends no log entry, so two successive invocations on a clean file are byte-identical. Non-overlapping with `sync` (3 lightweight frontmatter fields, auto-triggered) and orthogonal to `auto_prune_state` (age-based removal) — `rebuild` reconciles with current canonical sources, `prune` removes by retention policy, the two compose (rebuild first, then prune). Section ordering is invariant: rebuild rewrites content in place, never reorders. Targets the #1776/#1761/#1591 body-drift cluster that survived ADR-1769's per-field transitions. Phased per ADR-1817: Phase 0 = this ADR + predicates (closes #1817), Phase 1 = `rebuildCore` body + `rebuild` dispatch case + drift-class unit tests (#1827), Phase 2 = `cmdStateRebuild` CLI + `--dry-run`/`--verbose` + integration tests + docs + changeset (#1826). Source of truth: `gsd-core/bin/lib/state-transition.cjs` (generated from `src/state-transition.cts`).
### STATE.md Status Lifecycle (ADR-2207)
The `Status` field in STATE.md follows a strict lifecycle: `Ready to plan` → `All phases complete` (all phases done, milestone awaiting formal close) → `<version> milestone complete` (terminal, written only by the milestone-close verb `milestoneCompleteCore`) → `Awaiting next milestone` (archived). Phase-completion verbs write `All phases complete` on the last phase — never `Milestone complete` (the overloaded bare value was removed in #2204 per ADR-2207 to decouple phase-level writes from milestone termination). `normalizeStateStatus` maps any status containing "complete" → `completed`, so consumers using the normalized projection (workstream inventory's `status` field, statusline) recognize `All phases complete` without code changes. Note: `isCompletedInventory` (workstream-inventory-builder.cts) intentionally checks only for the terminal `\bmilestone\s+complete\b` / `\barchived\b` — `All phases complete` returns `false` (intermediate, not terminal).
### Query Execution Policy Module
Module owning query transport routing policy projection (`preferNative`, fallback policy, workstream subprocess forcing) at execution seam.
@@ -65,7 +71,7 @@ Canonical command normalization and resolution Interface (`query-command-resolut
Module owning command resolution, policy projection (`mutation`, `output_mode`), unknown-command diagnosis, and handler Adapter binding at one seam for query dispatch.
### Init Command Module
Module owning the `init.*` family of query handlers that compose atomic queries into the flat JSON bundles consumed by init workflows (`/gsd-execute-phase`, `/gsd-plan-phase`, `/gsd-verify-work`, `/gsd-new-project`, `/gsd-manager`, `/gsd-progress`, `/gsd-resume`, etc.). Source of truth: `gsd-core/bin/lib/init.cjs` — the basic handlers (plus `withProjectRoot` project-identity injection) and the 3 heavyweight handlers (`initNewProject`, `initProgress`, `initManager`). All handlers return `{ data: <flat JSON> }`. Test seams: `tests/init.test.cjs` and `tests/init-manager.test.cjs` (cover withProjectRoot precedence, progress/manager precedence regression #2674, workstream scoping regression #3196, and cross-milestone dependency regression #2267). (The SDK `handlers/init/*.ts` sources and the `init*.test.ts` seams were retired with the SDK package per ADR-0174.)
Module owning the `init.*` family of query handlers that compose atomic queries into the flat JSON bundles consumed by init workflows (`/gsd-execute-phase`, `/gsd-plan-phase`, `/gsd-verify-work`, `/gsd-new-project`, `/gsd-onboard`, `/gsd-manager`, `/gsd-progress`, `/gsd-resume`, etc.). Source of truth: `src/init.cts` and the compiled `gsd-core/bin/lib/init.cjs`; onboarding routing readiness lives in `src/onboard-projection.cts`. The basic handlers (plus `withProjectRoot` project-identity injection) and the heavyweight handlers (`initNewProject`, `initOnboard`, `initProgress`, `initManager`) return `{ data: <flat JSON> }`. Test seams: `tests/init.test.cjs`, `tests/onboard-command.test.cjs`, and `tests/init-manager.test.cjs` (cover withProjectRoot precedence, onboarding projection/rendering, progress/manager precedence regression #2674, workstream scoping regression #3196, and cross-milestone dependency regression #2267). (The SDK `handlers/init/*.ts` sources and the `init*.test.ts` seams were retired with the SDK package per ADR-0174.)
### Command Routing Hub
Single dispatch seam (`gsd-core/bin/lib/command-routing-hub.cjs`) that centralizes CJS routing, the no-throw pure-result contract, typed error variants, and dispatch-event emission for all command family adapters. Interface: `createHub({ cjsRegistry, manifest, logger }) → hub`; `hub.dispatch({ family, subcommand, args, cwd, raw, parentTraceId? }) → Result` where `Result = { ok: true, data } | { ok: false, kind, ...typedPayload }` and `kind ∈ { UnknownCommand, InvalidArgs, HandlerRefusal, HandlerFailure }`. The `InvalidArgs` variant carries an optional `exitReason?: string` field (amendment #1642 / #1644 Phase 1) holding the `ERROR_REASON` enum value, separate from `reason` (the explanation text); the `makeInvalidArgs(arg, reason, exitReason?)` factory omits the field when the third arg is absent, undefined, or empty — preserving the strict-keys invariant tested at `tests/command-routing-hub.test.cjs:444`. The Hub is single-runtime (no mode selection, no sdkLoader), never prints, never exits, never throws. Adapters call `createHub`, dispatch, then translate the pure Result to `output()`/`error()` calls; when an `InvalidArgs` Result carries `exitReason`, the adapter passes it as the second arg to `error(message, exitReason)` so the JSON-error envelope (`GSD_JSON_ERRORS=1`) preserves the typed reason. Source: `gsd-core/bin/lib/command-routing-hub.cjs`; ADR: `docs/adr/0174-retire-gsd-sdk-package-boundary.md` (§5 amended #1642).
@@ -118,20 +124,35 @@ Module owning bounded, never-throw git repository introspection — the single s
### Runtime Name Policy Module
Module owning runtime identity normalization at runtime-selection seams. Canonicalizes alias signals from env/config (`GSD_RUNTIME`, `.planning/config.json:runtime`) to supported runtime IDs so output emitters and query runtime gates stay consistent across naming variants (for example `codex-app`/`codex-cli` -> `codex`). Sources: `gsd-core/bin/lib/runtime-name-policy.cjs`, alias manifest `gsd-core/bin/shared/runtime-aliases.manifest.json`.
### Host-Integration Interface
Pure, additive, no-I/O Module owning the versioned, negotiated contract over the six host-integration interface points (command, dispatch, model, hooks, state, artifact) — ADR-1239 Phase A. Extends the ADR-1016 runtime descriptor with eight closed-vocabulary axes carried under `capability.json` `runtime.hostIntegration`: `embeddingMode` (`imperative|declarative`), `commandSurface` (`slash-file|slash-programmatic|slash-toml|palette|prose-only`), `dispatch` (`{namedDispatch,nested,maxDepth,background,backgroundDispatch,subagentToolkit}`), `modelMode` (`active|passive`), `hookBus` (`host|engine|none`), `stateIO` (`filesystem|sandboxed-storage|session-log-append`), `transport` (`mcp|native-extension`), `runtime` (`node|bun|sandboxed-web|python|go|rust|electron|other`). Interface: `negotiateHostCapabilities(host, engine?) → { protocolVersion, effective, points, warnings }` enforcing the trust-boundary invariant `effective ⊆ host-declared ∩ engine-known` (never augment with an undeclared or unknown/future-`protocolVersion` value — fail-closed via the most-restrictive-known `SAFE_DEFAULTS`); `degradationFor(point, axes) → { level, fallback }` (a pure Full/Degraded/Absent ladder table, never throws); `profileOf(axes) → 'programmatic-cli'|'declarative-cli'|'ide'|null`; plus `PROTOCOL_VERSION` (integer, starts at 1 — distinct from the package `version`/`engines.gsd` semver), `HOST_INTEGRATION_AXES` (the frozen closed vocabulary, single source of truth), `PROFILE_BASELINES`, and `shouldFlattenDispatch(dispatch) → boolean` (ADR-1239 Phase B / #1708 — graduates the #853 rule: returns `true` = run the orchestrator inline UNLESS the host is documented to background a nesting-capable orchestrator (`background === true && backgroundDispatch === true`); fail-closed to inline; exposed to the plan/execute workflows via the `gsd_run query dispatch-should-flatten --raw` CLI, which replaced the former scattered `RUNTIME === 'codex'` prose check). The runtime-descriptor validator (`gsd-core/bin/lib/capability-validator.cjs` `validateRuntimeBody`) mirrors the closed vocabulary inline (exported as `_HOST_INTEGRATION_VOCAB`) and is kept in lock-step by the parity guard `tests/host-integration-validator-parity.test.cjs`. Orthogonal axes (resolved explicitly per ADR-1239 Phase A): `commandStyle` (GSD emission style, retained) vs `commandSurface` (host surface type); `hookEvents` dialect vs `hookBus` ownership (a host with `hooksSurface:none` may still be `hookBus:host` — e.g. opencode); `runtimeCompat` (feature→host) vs these negotiated runtime→engine axes. Phase A defined the interface; Phase B (#1679) wires it incrementally — `destSubpath` write-confinement (#1704) and the typed documentation-sourced #853 dispatch-flatten (#1708, the first consumer of a negotiated `dispatch` axis); adapters/MCP/host-bindings remain Phases C–E. Source of truth: `gsd-core/bin/lib/host-integration.cjs` (generated from `src/host-integration.cts`). See ADR-1239 and ADR-1016.
### Statusline
Host-integration hook (`hooks/gsd-statusline.js`) that renders the session status line: model name, context-window meter, workspace directory, and the GSD-state segment (`formatGsdState()` projecting `.planning/` STATE.md). Opt-in segments are gated by `.planning/config.json` keys (`statusline.show_last_command`, `statusline.context_position`, plus the approved `statusline.show_context_tokens` and `statusline.state_format`), each registered across `gsd-core/bin/shared/config-schema.manifest.json` + `src/config.cts` + the `loadConfig` whitelist + `docs/CONFIGURATION.md`. The compact GSD-state format consumes the canonical status vocabulary from `normalizeStateStatus()` (STATE.md Document Module) rather than a parallel keyword list. **Data-source boundary (ADR-2164):** the statusline sources only local, read-only data — it refines the stdin payload Claude Code already sends and may add a new *local* source (e.g. `git`), but does not read credentials or call external/network APIs for data; account/usage/platform-level state is out of scope.
### Install Engine Module
Module owning the layout-driven runtime-artifact install pipeline — `installRuntimeArtifacts`, `uninstallRuntimeArtifacts`, `installOpencodeFamilySkills`, and their cluster helpers (`_copyStaged`, `_snapshotDir`/`_restoreDir`, legacy-migration + GSD-entry pruning, user-artifact preserve/restore). Extracted from the 12k-line `bin/install.js` (ADR-1239 Phase B, #1679) so adapters import the engine instead of reaching into the installer. Commit-attribution resolution stays in `bin/install.js` and is injected via a `resolveAttribution` parameter (the engine takes no config I/O). Source: `src/install-engine.cts` -> `gsd-core/bin/lib/install-engine.cjs`.
### Installer Migration Authoring Guard Module
Module owning validation for Installer Migration Module records and planned actions. It enforces migration metadata, explicit install scopes, ownership evidence for destructive/config actions, and runtime contract citations for runtime config rewrites before a migration can enter planning or apply.
### Installer Module
Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, gemini, hermes, kilo, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). The same module exposes `detectAntigravityDirAmbiguity(opts)` — a side-effect-free probe reporting whether multiple `~/.gemini/antigravity{,-ide,-cli}` dirs coexist and which one GSD's `gsd-core/VERSION` marker (the `dot-home-nested` `probeExists`) resolves to, for installer / `/gsd-update` operator guidance when a pre-#217 install landed in the wrong sibling dir (#1441). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Seven runtimes with non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity) use a nested router layout: 6 `gsd-ns-*` router bundles emitted as top-level skills, with concrete skills nested at `<router>/skills/<name>/SKILL.md` (hermes prefix='': `skills/gsd/ns-*/…`). The remaining skills-runtimes (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) use the flat `skills/gsd-<stem>/` layout unchanged. See Skill Surface Budget Module and Runtime Artifact Layout Module.
Primary installer for all runtimes. Single production file: `bin/install.js` (generated). Exports: `install(isGlobal, runtime[, configDir])` → typed result `{ runtime, configDir, settingsPath, settings, statuslineCommand, updateBannerCommand }`; `uninstall(isGlobal, runtime[, configDir])`; `installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile)`; `uninstallRuntimeArtifacts(runtime, configDir, scope)`; `writeManifest(configDir, runtime)`. Runtime enum: `allRuntimes` (15 values: claude, antigravity, augment, cline, codebuddy, codex, copilot, cursor, hermes, kilo, kimi, opencode, qwen, trae, windsurf). Directory helpers: `getDirName(runtime)` → local dir name; `getConfigDirFromHome(runtime, isGlobal)` → shell-quoted path fragment. Per-runtime global config-dir resolution is delegated to `gsd-core/bin/lib/runtime-homes.cjs:getGlobalConfigDir(runtime[, explicitDir])` — the canonical, env-var–aware projection (`explicitDir` override + opencode/kilo `*_CONFIG` file-path precedence); the legacy in-installer `getGlobalDir`/`getOpencodeGlobalDir`/`getKiloGlobalDir` were retired into it (#56). The same module exposes `detectAntigravityDirAmbiguity(opts)` — a side-effect-free probe reporting whether multiple `~/.gemini/antigravity{,-ide,-cli}` dirs coexist and which one GSD's `gsd-core/VERSION` marker (the `dot-home-nested` `probeExists`) resolves to, for installer / `/gsd-update` operator guidance when a pre-#217 install landed in the wrong sibling dir (#1441). Runtime-specific helpers: `resolveKiloConfigPath(configDir)`, `configureKiloPermissions(isGlobal[, explicitDir])`. Claude-specific permission helpers: `mergeClaudePermissions(settings)` — non-destructively appends GSD-owned allow/deny entries (see `GSD_CLAUDE_ALLOW_PERMISSIONS`, `GSD_CLAUDE_DENY_PERMISSIONS` constants) to a Claude Code settings object; called from `finishInstall` for `runtime === 'claude'` only; uninstall removes exactly these entries (#768). Layout-driven artifact copy/removal delegates to `gsd-core/bin/lib/runtime-artifact-layout.cjs:resolveRuntimeArtifactLayout` (throws `TypeError` for unknown runtimes). Seven runtimes with non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity) use a nested router layout: 6 `gsd-ns-*` router bundles emitted as top-level skills, with concrete skills nested at `<router>/skills/<name>/SKILL.md` (hermes prefix='': `skills/gsd/ns-*/…`). The remaining skills-runtimes (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) use the flat `skills/gsd-<stem>/` layout unchanged. See Skill Surface Budget Module and Runtime Artifact Layout Module.
### I/O Module
Module owning the tool's CLI I/O primitives: `output()` result emission (with large-payload temp-file spillover via `GSD_TEMP_DIR`/`ensureGsdTempDir`/`reapStaleTempFiles`), `error()` stderr emission with exit-code mapping, and the JSON-error-mode toggle (`setJsonErrorMode`/`getJsonErrorMode`, `ERROR_REASON`). Extracted from the Core module per ADR-857 rollout phase 1 (#859) so feature modules (`graphify`, `intel`, `audit`, `profile-pipeline`) depend on a small I/O seam instead of the core god-module; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/io.cjs` (generated from `src/io.cts`).
### Markdown Sectionizer
Canonical markdown-structure parsing seam (`gsd-core/bin/lib/markdown-sectionizer.cjs`, generated from `src/markdown-sectionizer.cts`). Pure functions, Node built-ins only. Exports: `stripFencedCode(content) → { text, unterminatedFence }` (CommonMark-correct state machine, CRLF-safe, signals unterminated fences); `tokenizeHeadings(content) → HeadingToken[]` (ATX headings outside fenced blocks, `{ level, text, line, offset }`); `collectSections(content, stopPredicate) → Section[]` (line-by-line section collection driven by a heading predicate); `collectSection(content, headingPredicate, { levelBounded, stripFences }) → Section | null` (single named section with level-bounded stop); `iterateBullets(sectionText) → BulletItem[]` (dash/checkbox/numbered markers with indented continuation); `extractTaggedBlocks(content, tagName) → string[]` (inner text of every `<tagName>…</tagName>` block in document order, tagName regex-escaped, caller decides fence-stripping — generalises `decisions.cts`'s bespoke extractor for T1); `replaceSection(content, section, newBody) → string` (pure character-offset splice using `Section.bodyStart`/`bodyEnd` for read-modify-write callers — eliminates T6 `state.cts`'s 7× inline `content.replace` pattern). `Section` carries `bodyStart`/`bodyEnd` offsets for `replaceSection`. ADR-1372 (epic #1372) establishes this seam and a tiered migration plan (T0–T7) to retire the 8+ ad-hoc markdown parsers and ~20 inline section-collects across `src/*.cts`. New `src/*.cts` modules must import this seam instead of hand-rolling fence strippers or heading-regex section walks (enforced by the `no-adhoc-markdown-parsing` ESLint rule landing in tier T7).
Canonical markdown-structure parsing seam (`gsd-core/bin/lib/markdown-sectionizer.cjs`, generated from `src/markdown-sectionizer.cts`). Pure functions, Node built-ins only. Exports: `stripFencedCode(content) → { text, unterminatedFence }` (CommonMark-correct state machine, CRLF-safe, signals unterminated fences); `tokenizeHeadings(content) → HeadingToken[]` (ATX headings outside fenced blocks, `{ level, text, line, offset }`); `collectSections(content, stopPredicate) → Section[]` (line-by-line section collection driven by a heading predicate); `collectSection(content, headingPredicate, { levelBounded, stripFences }) → Section | null` (single named section with level-bounded stop); `iterateBullets(sectionText) → BulletItem[]` (dash/checkbox/numbered markers with indented continuation); `extractTaggedBlocks(content, tagName) → string[]` (inner text of every `<tagName>…</tagName>` block in document order, tagName regex-escaped, caller decides fence-stripping — generalises `decisions.cts`'s bespoke extractor for T1); `replaceSection(content, section, newBody) → string` (pure character-offset splice using `Section.bodyStart`/`bodyEnd` for read-modify-write callers — eliminates T6 `state.cts`'s 7× inline `content.replace` pattern); `withSection(content, target, edit) → string` (resolve the section whose heading matches `target` — exact heading text or a `HeadingToken` predicate — and run `edit(body)` against ONLY that section's body before splicing the result back; bounded no-op when no heading matches or `edit` returns the same/non-string body; ADR-2143 §4 structurally retires the #2130/#2067/#2080 boundary-crossing class by confining any regex the caller runs to the matched section). `Section` carries `bodyStart`/`bodyEnd` offsets for `replaceSection`. ADR-1372 (epic #1372) establishes this seam and a tiered migration plan (T0–T7) to retire the 8+ ad-hoc markdown parsers and ~20 inline section-collects across `src/*.cts`. New `src/*.cts` modules must import this seam instead of hand-rolling fence strippers or heading-regex section walks (enforced by the `no-adhoc-markdown-parsing` ESLint rule landing in tier T7).
### Markdown Table Model
Canonical GFM table parsing + schema registry seam (`gsd-core/bin/lib/markdown-table.cjs`, generated from `src/markdown-table.cts`; ADR-2143, epic #2143). Pure functions, Node built-ins only, string-in/value-out, no I/O. Exports: `parseMarkdownTable(sectionText) → Result<MarkdownTable>` (parses the first GFM pipe table found; typed `{ok:false,reason}` parse errors for no-table, missing/misaligned delimiter row, and ragged data rows — never silently drops or coerces a malformed row); `MarkdownTable` (`{columns: string[], rows: Record<string,string>[]}`, rows addressed by column name, not position); `Result<T>` (`{ok:true,value}\|{ok:false,reason}` — re-exported from the Write-Set Module, the ADR-2143 §5 single source of truth for this shape, so existing importers of `Result` from `markdown-table.cjs` are unaffected; deliberately distinct from command-routing-hub's dispatch `Result` `{ok,data\|kind}`; the two never mix); `TABLE_SCHEMAS` (`Record<string, CanonicalTableVariant[]>` — the canonical column-header variants for every GFM table GSD parses or generates: `RoadmapProgress` flat/milestone-grouped, `RequirementsTraceability`, `QuickTasks` no-status/with-status, `Security` trust-boundaries/threat-register/accepted-risks/audit-trail); `matchTableSchema(columns) → {id,label}\|null` (resolves a parsed header back to its canonical schema by exact column-name/order match). This registry is the single source of truth for ROADMAP/STATE/SECURITY canonical tables — a parity test (`tests/markdown-table.test.cjs`) asserts every variant's header appears verbatim in the template/workflow file that generates it, so the registry and templates can never silently drift (ADR-2143 §3 Generative-Fix-Divergence guard). `phase-lifecycle.cts`'s `deriveProgressFromRoadmap` is the first consumer: it locates the Progress section via the Markdown Sectionizer's `collectSection` and reads cells by column NAME through this seam, fixing #2137 (the prior position-anchored regex assumed `Status` was always the 3rd cell, which broke for the 5-column milestone-grouped `Milestone` variant).
### Write-Set Module
Shared fail-loud `Result<T>` and per-surface write-set contracts (`gsd-core/bin/lib/write-set.cjs`, generated from `src/write-set.cts`; ADR-2143 §5/§6, epic #2143). Pure, Node built-ins only, no I/O. Exports: `Result<T>` (`{ok:true,value}\|{ok:false,reason}` — ADR-2143 §5 fail-loud parse shape, never a bare `null` a caller can mistake for "empty but fine"; the single source of truth `markdown-table.cjs` re-exports so its existing importers are unaffected; deliberately distinct from command-routing-hub's dispatch `Result` `{ok,data\|kind}`); `WriteOutcome` (`{surface: string, applied: boolean}` — one surface's outcome within a multi-surface write); `WriteSet` (`WriteOutcome[]`); `writeSetComplete(ws) → boolean` (true only when the set is non-empty AND every surface applied — ADR-2143 §6's "no OR-into-one-flag" rule: a command that mutates more than one surface must not collapse independent surface outcomes into a single boolean, the anti-pattern that let a checkbox-only partial write (#2140) report full success). `milestone.cts`'s `requirements mark-complete` handler is the first consumer: it reports a `write_set` (`checkbox`/`traceability` surfaces) and `write_set_complete` alongside its existing `updated`/`marked_complete`/`already_complete`/`not_found`/`table_unmatched` fields, which remain computed exactly as before — the write-set is additive, structured ADR-2143 documentation of the same per-surface facts #2140's tactical fix already exposed via `table_unmatched`.
### Roadmap Parser Module
Module owning ROADMAP.md parsing: shipped-milestone slicing, current-milestone extraction, milestone/phase lookups, and milestone-phase filtering (`stripShippedMilestones`, `extractCurrentMilestone`, `replaceInCurrentMilestone`, `getRoadmapPhaseInternal`, `getMilestoneInfo`, `getMilestonePhaseFilter`). Depends only on leaf modules (`phase-id`, `planning-workspace`, `shell-command-projection`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2b (#870), resolving the ROADMAP.md parse/write straddle so the Roadmap module (`roadmap.cjs`, which owns ROADMAP.md mutation) imports parsing directly instead of through Core; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/roadmap-parser.cjs` (generated from `src/roadmap-parser.cts`).
Module owning ROADMAP.md parsing: shipped-milestone slicing, current-milestone extraction, milestone/phase lookups, and milestone-phase filtering (`stripShippedMilestones`, `extractCurrentMilestone`, `replaceInCurrentMilestone`, `getRoadmapPhaseInternal`, `getMilestoneInfo`, `getMilestonePhaseFilter`, `withPhaseSection`). `withPhaseSection(content, phaseId, edit)` resolves a phase's `### Phase N` detail-section heading via the #2121 phase-id source (`phaseMarkdownRegexSource`) and delegates to the markdown-sectionizer seam's `withSection`, so a per-phase ROADMAP edit is bounded to that phase's own section (ADR-2143 §4). Depends only on leaf modules (`phase-id`, `planning-workspace`, `shell-command-projection`, `markdown-sectionizer`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2b (#870), resolving the ROADMAP.md parse/write straddle so the Roadmap module (`roadmap.cjs`, which owns ROADMAP.md mutation) imports parsing directly instead of through Core; the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/roadmap-parser.cjs` (generated from `src/roadmap-parser.cts`).
### Core Utilities Module
Module owning the shared low-level utility primitives extracted from Core: POSIX path normalization (`toPosixPath`), filesystem scanning (`detectSubRepos`, `readSubdirectories`, `getPhaseFileStats`, `pathExistsInternal`), and small pure helpers (`generateSlugInternal`, `extractOneLinerFromBody`, `filterPlanFiles`, `filterSummaryFiles`, `extractCanonicalPlanId`, `timeAgo`). Depends only on Node built-ins and already-leafed modules (`phase-id` for `comparePhaseNum`, `planning-workspace` for `findContextMdIn`) — no `loadConfig`, no other core dependency. Extracted from the Core module per ADR-857 rollout phase 2c (#877) as the shared leaf that unblocks the phase-locator fs-search extraction (2d); the `core.cjs` re-export spine was retired in epic #1267, so callers import this leaf directly. Source of truth: `gsd-core/bin/lib/core-utils.cjs` (generated from `src/core-utils.cts`).
@@ -155,13 +176,13 @@ Module owning install detection for `/gsd:update`. `resolveUpdateContext({ home,
Module owning which skills and agents are written to runtime config directories at install time (Phase 1) and at runtime via cluster-level toggles (Phase 2). Phase 1: `gsd-core/bin/lib/install-profiles.cjs` defines named profiles (`core`, `standard`, `full`), computes transitive closure over `requires:` frontmatter, stages skills/agents to runtime config dirs, and persists the chosen profile in a `.gsd-profile` marker. Profile resolution precedence: explicit `--profile=` flag > `.gsd-profile` marker > `full`. `--minimal`/`--core-only` are back-compat aliases for `--profile=core`. Phase 2: `gsd-core/bin/lib/surface.cjs` implements the `/gsd:surface` slash command for cluster-level enable/disable without reinstall; cluster definitions live in `gsd-core/bin/lib/clusters.cjs`; per-runtime state persists in `<runtimeConfigDir>/.gsd-surface.json` independent from the `.gsd-profile` marker. See ADR-0011.
### Runtime Artifact Layout Module
Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Owns the per-runtime `nested` skill-bundle decision (#69): a `skillsKind` flag in `src/runtime-artifact-layout.cts` drives whether a runtime receives the nested router layout (6 `gsd-ns-*` routers + concrete skills under `<router>/skills/<name>/`) or the flat `skills/gsd-<stem>/` layout; the evidence/doc-link matrix is recorded in a comment above `resolveRuntimeArtifactLayout`. Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. Per ADR-1508 / #1511 the former `getInstallExports`/`loadInstallExports` relay (a `GSD_TEST_MODE`-guarded `require('bin/install.js')` by which `surface.cjs` reached `computePathPrefix`/`applyRuntimeContentRewritesInPlace`) was DELETED from this module; content rewriting now lives in the Runtime Artifact Conversion Module and `surface.cjs:applySurface` calls its `rewriteStagedSkillBodies` directly. The resolved `scope` is still carried on the `Layout` object so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). See ADR-3660.
Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Owns the per-runtime `nested` skill-bundle decision (#69): a `skillsKind` flag in `src/runtime-artifact-layout.cts` drives whether a runtime receives the nested router layout (6 `gsd-ns-*` routers + concrete skills under `<router>/skills/<name>/`) or the flat `skills/gsd-<stem>/` layout; the evidence/doc-link matrix is recorded in a comment above `resolveRuntimeArtifactLayout`. Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. Per ADR-1508 / #1511 the former `getInstallExports`/`loadInstallExports` relay (a `GSD_TEST_MODE`-guarded `require('bin/install.js')` by which `surface.cjs` reached `computePathPrefix`/`applyRuntimeContentRewritesInPlace`) was DELETED from this module; content rewriting now lives in the Runtime Artifact Conversion Module and `surface.cjs:applySurface` calls its `rewriteStagedSkillBodies` directly. The resolved `scope` is still carried on the `Layout` object so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). The `.gsd-source` marker (#1477) is a two-party provisioning contract that lets source resolution succeed on the Claude global skills layout, which ships `gsd-core/{bin,contexts,references,templates,workflows}` but no `commands/gsd` source tree for `findInstallSourceRoot` to walk up to: the writer is `bin/install.js`, which writes `<configDir>/.gsd-source` (content: the absolute path to its own `commands/gsd`, terminated by a newline) when `runtime === 'claude' && isGlobal`, guarded by `fs.existsSync` so a half-published package never writes a dangling marker; the reader is `findInstallSourceRoot(configDir)`, which prefers the marker over its walk-up but falls through to the walk-up if the marker is absent, dangling, or empty/whitespace-only. See ADR-3660.
### Runtime Artifact Conversion Module
Sibling Module to Runtime Artifact Layout Module. Owns projection from canonical Claude-authored command/agent/skill markdown into runtime-specific artifact bodies, including converter selection, frontmatter/body normalization, runtime path rewrites, and staged artifact generation. Runtime Artifact Layout remains responsible for filesystem placement (`kind`, destination subpath, prefix, nesting); Runtime Artifact Conversion owns the content Implementation behind that placement seam so install, uninstall/surface parity, and future plugin/package projections stop reaching back through `bin/install.js` for converter functions or `GSD_TEST_MODE`-guarded installer exports. Chosen direction: sibling Module, not an expanded Layout Module, to preserve ADR-3660's narrow placement responsibility while deepening artifact content locality. First slice: relocate only the layout-reached conversion family (`convertClaudeCommandTo*Skill`, converted command-file emitters, `buildKimiAgentArtifacts`) plus the minimal helper closure they need; do not leave helper dependencies in `bin/install.js` because that would preserve the same shallow seam under a new filename. Installer integration decision: `bin/install.js` imports the conversion Module at top level and re-exports the moved names for compatibility; the conversion Module must not import `bin/install.js` or Runtime Artifact Layout, so the dependency direction becomes installer/layout Adapters -> conversion Module, never conversion -> installer. First-slice Interface decision: export the existing compatibility names only; do not introduce a grouped `convertRuntimeArtifact` Interface until after relocation proves byte-for-byte behavior. SHIPPED (ADR-1508): the converter family relocated in #1510 Phase 1 (`getDirName`→runtime-name-policy, `processAttribution` here); #1511 Phase 2 moved the content-rewrite engine here in full — `_applyRuntimeRewrites` (per-runtime switch, injected attribution), the staged-content walkers `applyRuntimeContentRewritesInPlace`/`applyRuntimeContentRewritesForCommandsInPlace`, `computePathPrefix` (private; `_computePathPrefix` for tests), and the deep public seam `rewriteStagedSkillBodies`/`rewriteStagedCommandBodies({runtime,configDir,scope,homedir?,platform?,resolveAttribution?})`. `bin/install.js` binds these back (single owner, exports preserved); `getCommitAttribution` stays in `bin/install.js` (impure install-time config I/O) and is injected. The `getInstallExports` relay in Runtime Artifact Layout Module was deleted; the dependency direction installer/layout → conversion (never upward) is now enforced. Exception: opencode and kilo path-prefix rewriting is a deliberate `bin/install.js`-owned pre-conversion step (`applyOpencodeFamilyPathPrefix`) per #784, not a violation of the single-owner rule. Source: `gsd-core/bin/lib/runtime-artifact-conversion.cjs` (generated from `src/runtime-artifact-conversion.cts`). Also exports `resolveVersionFrom(libDir)` — a lazy, defensive GSD-version resolver (installed-tree `gsd-core/VERSION` first, then the source/npm `package.json` three dirs up, both validated against the repo's shared semver-prefix shape, degrading to `''` on failure) that replaced a module-load-time `require('../../../package.json')` which crashed on runtimes whose root carries no `package.json` (e.g. Codex) (#1383).
### Runtime Artifact Install Plan Module
Module owning install-time staging and content-rewrite selection for a pre-resolved Runtime Artifact Layout. Interface: `createRuntimeArtifactInstallPlan({ layout, resolvedProfile, homedir?, platform?, resolveAttribution?, deps? }) -> { ok:true, plan:{ items, cleanupDirs } } | { ok:false, kind:'stage_failed'|'rewrite_failed', message, cleanupDirs, failedKind? }`. It iterates `layout.kinds` in order, calls each kind's `stage(resolvedProfile)`, delegates `commands` to Runtime Artifact Conversion `rewriteStagedCommandBodies`, delegates `skills` and `kimi-agents` to `rewriteStagedSkillBodies`, leaves non-rewritten kinds unchanged, and projects copy items as `{ kind, sourceDir, destDir }`. It deliberately does not prune, copy, run legacy migrations, print output, or execute cleanup; those remain Installer Module adapter responsibilities until later slices wire the plan into `bin/install.js`. Source: `gsd-core/bin/lib/runtime-artifact-install-plan.cjs` (generated from `src/runtime-artifact-install-plan.cts`). See Runtime Artifact Layout Module and Runtime Artifact Conversion Module.
Module owning install-time staging and content-rewrite selection for a pre-resolved Runtime Artifact Layout. Interface: `createRuntimeArtifactInstallPlan({ layout, resolvedProfile, homedir?, platform?, resolveAttribution?, deps? }) -> { ok:true, plan:{ items, cleanupDirs } } | { ok:false, kind:'stage_failed'|'rewrite_failed', message, cleanupDirs, failedKind? }`. It iterates `layout.kinds` in order, calls each kind's `stage(resolvedProfile)`, delegates `commands` to Runtime Artifact Conversion `rewriteStagedCommandBodies`, delegates `skills` and `kimi-agents` to `rewriteStagedSkillBodies`, leaves non-rewritten kinds unchanged, and projects copy items as `{ kind, sourceDir, destDir }`. It deliberately does not prune, copy, run legacy migrations, print output, or execute cleanup; those remain Installer Module adapter responsibilities until later slices wire the plan into `bin/install.js`. **Write-confinement (ADR-1239 Phase B / #1679):** the exported pure `assertDestWithinConfigHome(configDir, destSubpath) -> resolvedDest` is the security gate — every kind's `destDir` is computed through it on both the install and uninstall plan paths, so a `destSubpath` that escapes `configHome` (`../../etc`, a NUL byte, etc.) is rejected at plan-build time with a clear error; `surface.cjs:applySurface` and `bin/install.js:installOpencodeFamilySkills` route their joins through the same helper, and `_copyStaged` carries a defense-in-depth containment check. This is security-load-bearing for the Phase C third-party-descriptor loader (which is where an untrusted `destSubpath` could arrive). Source: `gsd-core/bin/lib/runtime-artifact-install-plan.cjs` (generated from `src/runtime-artifact-install-plan.cts`). See Runtime Artifact Layout Module and Runtime Artifact Conversion Module.
### Command Roster Module
Tiny read-only helper Module owning discovery of canonical `commands/gsd/*.md` command stems for artifact conversion and runtime projection. It is a sibling dependency of Runtime Artifact Conversion Module, not part of conversion itself: conversion consumes a roster to safely rewrite `gsd:` / `/gsd-` references, while roster discovery owns filesystem/catalog knowledge. First slice: extract existing `readGsdCommandNames` behavior behind this Module instead of moving it into Runtime Artifact Conversion Module or keeping it as installer-owned state.
@@ -175,6 +196,9 @@ A bundle delivering one optional GSD feature, toggled as a unit at install or af
### Loop Host Contract
Generated description of what the five-step loop (Discuss → Plan → Execute → Verify → Ship) exposes as extension points: per-step loop points, agent roles, and core artifacts. Sourced from structured `<!-- gsd:loop-host ... -->` HTML-comment markers embedded near the top of each of the five step workflow files (`discuss-phase.md`, `plan-phase.md`, `execute-phase.md`, `verify-work.md`, `ship.md`). Generated by `scripts/gen-loop-host-contract.cjs` → `gsd-core/bin/lib/loop-host-contract.cjs` (ADR-894 §3 phase 3a-impl-2). Covers exactly the 12 canonical points (discuss:pre/post, plan:pre/post, execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post). The generator enforces a drift guard: every declared non-orchestrator agent role must correspond to an actual agent reference in the workflow file. Consumed by `gen-capability-registry.cjs` (replaces the former inline `LOOP_HOST_CONTRACT` constant). Run `node scripts/gen-loop-host-contract.cjs --write` after editing a workflow step marker.
### Gate Predicate Evaluator Module
Pure, deps-injected evaluator for capability gate `check.predicate` blocks (#2008, ADR-2008). Prior to #2008 the registry validator accepted `check.predicate` (one of exactly-one-of `query`/`predicate`/`agentVerdict`) and the loop-resolver rendered it, but nothing EVALUATED a declared predicate — only `check.query` was enforced (dispatched via `gsd_run check <query>`), and built-in gates like `security` worked only via hard-coded `capId` prose branches in `ship.md`/`execute-phase.md`/`verify-work.md`. This module is the generic evaluation path: `evaluatePredicate(predicate, context, deps) → { block, message, details? }` dispatches by `predicate.kind` through a `KIND_TABLE`. Built-in kind (v1): `command-exit-zero` — runs a declared command in a bounded `sh -c` subprocess (production binding: `shell-command-projection.execTool`) at the project root, inheriting env; exit 0 ⇒ pass, non-zero ⇒ block, timeout (SIGTERM) ⇒ block; interpolates `${PHASE_NUMBER}`/`${PHASE_DIR}`/`${PHASE_REQ_IDS}` from gate context. A THROWN error (malformed predicate, non-positive/non-finite timeout, command >4096 chars, unknown kind) maps at the CLI seam to a non-zero check-command exit, which the workflow's two-step gate contract treats as a step-1 command failure routed per `onError` — so an evaluator bug is never conflated with a legitimate block decision. Leaf pure module (no fs/child_process/config — subprocess seam injected). CLI entry: `gsd_run check predicate --predicate '<json>' [--phase-dir …] [--phase-number …] [--phase-req-ids …] --raw`, wired into `check-command-router.cts:cmdCheckPredicate`; the three generic workflow gate-dispatch sites (`execute:wave:post`, `execute:post`, `plan:post`) branch on `check` shape (`query` vs `predicate`). Source of truth: `src/gate-predicate-evaluator.cts`. Docs: `docs/reference/gate-predicates.md`, `docs/how-to/command-exit-zero-gate.md`.
### Capability Registry
Generated central manifest projecting all co-located Capability declarations into one validated artifact for runtime resolution and for the install, surface, config, and loop-extension adapters. Mirrors the research-profiles / package-identity generation pattern (co-located source → generated central file). Generated by `scripts/gen-capability-registry.cjs` → `gsd-core/bin/lib/capability-registry.cjs` (ADR-894 §5 phase 3a-impl). Role-partitioned indexes: `bySkill`, `byAgent`, `byLoopPoint` (hook ordering materialized), `configKeys` (ownership map: key→capId), `configSchema` (full per-key schema: key→{ owner, type, default, description }), `runtimes`, `requiresClosure(id)`. Each feature capability's entry in `capabilities` now includes the optional `activationKey` field (the dotted config key that gates the whole capability, e.g. `"graphify.enabled"`; absent means no config gate). ADR-857 phase 3b adds `configSchema` with validated type/default/description per key, sourced from each capability's `.config` slice. ADR-857 phase 4a adds two derived views: `capabilityClusters` (`{ <capId>: [<skill stems>] }` — each cap's skills array, sorted, derived from the capability's `skills` declaration; consistency-gated against the hand-authored `CLUSTERS`) and `profileMembership` (`{ <capId>: { tier, profiles: [...] } }` — the tier-derived index: suffix of `PROFILE_RANK` starting at the capability's tier). Both views cover the same capability set: only capabilities that own skills (non-empty `skills` array). The generator enforces a HARD gate (throws) if a capId matching a `CLUSTERS` key has a mismatched skill set, and emits SOFT `⚠ pending-reconciliation` warnings to stderr (never to the file) for skills not yet in the hand-authored profile at the capability's tier. `install` and `surface` are UNTOUCHED (still read hand-authored constants; derived views are emitted and tested but unconsumed until cutover). Validated against the Loop Host Contract (12 points; generated by `gen-loop-host-contract.cjs` from workflow markers, phase 3a-impl-2). Run `node scripts/gen-capability-registry.cjs --write` after editing any `capabilities/<id>/capability.json`.
@@ -184,6 +208,12 @@ ADR-857 phase 3b seam that merges capability-declared config slices into the `lo
### Capability Registry Overlay
Runtime seam (`gsd-core/bin/lib/capability-loader.cjs`, ADR-1244 D2) that composes the frozen first-party Capability Registry (`capability-registry.cjs`) with a validated installed overlay of third-party capability manifests discovered at load time. Install roots are global (`$GSD_HOME/.gsd/capabilities/<id>/capability.json`, where `GSD_HOME` defaults to `~`) and project (`<projectRoot>/.gsd/capabilities/<id>/capability.json`). Primary interface: `loadRegistry({ includeInstalled }) → registry` — when `includeInstalled` is true the overlay is merged via the canonical `buildRegistry` so all derived views (bySkill, byAgent, byLoopPoint, configKeys) cover first-party and overlay entries identically. First-party always wins: any overlay entry whose id, owned skill/agent stem, or federated config key collides with first-party, or whose id uses a reserved `gsd-`/`gsd-core-`/`anthropic-` prefix, is rejected at load time. Load-time re-gate: an overlay failing schema validation or whose `engines.gsd` semver range does not satisfy the running GSD version is skipped with a warning and never crashes the load loop. Per-hook-kind policy: a skipped capability that declared a `gate`-kind hook fails CLOSED (the loop resolver injects a blocking gate); skipped `step` or `contribution` capabilities skip open. A capability dir whose co-located ledger entry carries an in-flight `_pending` intent (a crashed/uncommitted install or upgrade, ADR-1244 Phase 4) is skipped OPEN (never activated until reconciliation commits or rolls it back). #1459 user-owned consent gate: a PROJECT-scope overlay is activated (declarative surfaces AND command dispatch) ONLY when the user-owned Capability Consent Store holds a record for `(realpath(projectRoot), id)` whose stored `contentHash` equals the bundle content hash the loader RECOMPUTES at load (`bundleContentHash(capDir)` over the whole on-disk bundle) — NOT the repo-plantable ledger integrity nor the executable-only disclosure signature — otherwise the cap is DISCOVERED-BUT-INACTIVE (a warning carrying `kind:'unconsented'`, no surfaces, empty commandRoots), so a forged/cloned in-repo project ledger or any post-consent tamper no longer activates anything; GLOBAL scope (under the user's own home) is trusted without a record, and the global-vs-project root dedup/escalation is realpath-keyed so a symlinked `GSD_HOME` aliasing the project root cannot bypass the gate (finding 1). The consent lookup is wrapped to fail CLOSED (inactive); both the per-scope ledger AND the `capability.json` manifest are read via the shared bounded `readSmallRegularFile` (a repo-planted FIFO/oversized ledger or manifest can no longer hang or OOM the loader — finding 2). The loader reuses the ledger's shared `isValidLedgerEntry` for committed-entry parity. Consumers wired to the overlay-aware registry: `config-loader.cjs`, `config-schema.cjs`, `capability-state.cjs`, `loop-resolver.cjs`.
### Community Capability Registry
Human-facing discoverability catalog (`docs/registries/capability-registry.md`, generated from `docs/registries/capabilities.json`; issue #2182) listing third-party Feature Capabilities registered by a docs PR so a solo developer can find one before installing it. Distinct from **Capability Registry** (the generated runtime manifest compiled from first-party `capability.json` declarations, ADR-894) and **Capability Registry Overlay** (the runtime seam that merges an installed third-party manifest into that generated registry at load time, ADR-1244 D2): this registry is a static document rendered by `scripts/gen-registry.cjs`, not a runtime data structure or loader. Each entry enumerates the capability's Loop Extension Points and hook kinds so a reader can judge blast radius before running `gsd capability install`, and declares its `engines.gsd` range. Inclusion is an explicit non-endorsement — a maintainer merged a link, nothing more — per `docs/registries/README.md`.
### EoS Registry
Human-facing discoverability catalog (`docs/registries/eos-registry.md`, generated from `docs/registries/eos.json`; issue #2182) listing third-party Embeddable Orchestration System (EoS) host integrations — projects that embed GSD as an orchestration engine behind the ADR-1239 six-interface-point Host-Integration Interface. Entries are registered by the same docs-PR process, schema conventions, and non-endorsement stance as the **Community Capability Registry**, but enumerate the six interface points, the eight negotiated axes, and `protocolVersion` in place of Loop Extension Points and hook kinds. It has no generated-manifest or Capability Registry Overlay counterpart: an ADR-1239 host integration runs inside the third-party host, not inside GSD's own capability loader, so there is nothing for a runtime registry to merge. See `docs/registries/README.md` for the full entry schema.
### Capability Validator
Shared conformance validator (`gsd-core/bin/lib/capability-validator.cjs`, ADR-1244 D2) extracted from `scripts/gen-capability-registry.cjs` so the build-time generator and the runtime overlay loader share one validator implementation. Exports the same `validateCapability(manifest)` surface consumed by both the generator (build-time) and `capability-loader.cjs` (runtime). Generative-parity is CI-guarded: a drift between the generator's validation logic and the extracted module is a hard failure. Callers that previously inlined validation against the generator's internal helpers are migrated to import this module directly. Source of truth: `gsd-core/bin/lib/capability-validator.cjs`.
@@ -194,7 +224,7 @@ ADR-1244 D3 fetch-and-stage seam (`gsd-core/bin/lib/capability-source.cjs`). Pri
ADR-1244 D4 per-runtime install manifest (`gsd-core/bin/lib/capability-ledger.cjs`). Leaf module (only `node:fs`/`node:path` plus `shell-command-projection`'s `platformWriteSync`). Records `{ id, version, source, integrity, files[], sharedEdits[{file,marker}] }` per installed capability in `.gsd-capabilities.json` at the runtime config dir root. Exports: `readLedger` (structural-validated, never throws), `writeLedger` (atomic via `platformWriteSync`), `recordInstall` (idempotent, prototype-pollution-guarded), `removeEntry`, and `reconcile` (reports orphans whose `files[]` are missing on disk; hardened against non-string/`..` members; never mutates). Serves as the atomic commit point for Phase-4 upgrade/remove and the reconciliation basis for detecting stale entries after out-of-band deletions.
### Capability Consent Store
Issue #1459 user-owned consent seam (`gsd-core/bin/lib/capability-consent.cjs`, generated from `src/capability-consent.cts`). Leaf module (`node:fs`/`node:path`/`node:os`/`node:crypto` + the ledger's shared bounded `readSmallRegularFile`/`readSmallRegularFileBuffer` + the shared `capability-lock` primitive). Stores `{ version:"1", records: { "<JSON disk key {r:realpath(projectRoot),i:id}>": { projectRoot, id, scope:'project', integrity, disclosureSignature, contentHash, consentedAt } } }` at `${GSD_HOME||homedir()}/.gsd/consent.json` — a USER-OWNED file OUTSIDE any repository. Exports: `consentStorePath(gsdHome?)`, `readConsentStore(gsdHome?)` (bounded via `readSmallRegularFile` + 8 MiB cap, NON-THROWING — missing/corrupt/oversized/FIFO/wrong-shape → empty `{records:{}}`; caps records at `MAX_RECORDS=4096`), `bundleContentHash(capDir)` (THE security binding — a `sha512-<base64>` over a DETERMINISTIC, INJECTIVE, LOSSLESS serialization of EVERY regular file AND directory under the bundle: length-FRAMED entry COUNT + per-entry TYPE tag + uint32 path-byte-len + RAW path bytes from a `{encoding:'buffer'}` dir walk [finding 4] + for files uint64 content-byte-len + RAW content bytes via `readSmallRegularFileBuffer` [finding 1b], plus typed DIR markers binding empty directories [finding 2]; symlinks/non-regular rejected; size+count bounded), `hasProjectConsent({gsdHome,projectRoot,id,contentHash})` (true iff a record for `${realpath(projectRoot)}<id>` exists AND its stored `contentHash` equals the supplied recomputed hash — the binding is `contentHash`, NOT `integrity` and NOT `disclosureSignature` (those remain on the record purely for the human disclosure + re-consent-on-executable-change UX); unsafe ids → false; prototype-pollution-safe NUL-joined keys + `Object.prototype.hasOwnProperty`), `recordProjectConsent({gsdHome,projectRoot,id,integrity,disclosureSignature,contentHash})` (LOCKED, atomic+durable write — tmp `wx`/fsync/rename/dir-fsync mirroring `writeLedger`; enforces the record cap at write time) and `revokeProjectConsent({gsdHome,projectRoot,id})` (LOCKED atomic delete, no-op if absent) — BOTH **THROW** rather than perform an UNLOCKED read-modify-write when the consent-store lock cannot be acquired (finding 3; the lifecycle treats a consent-write failure as non-fatal, and the `trust revoke` CLI catches the throw and emits a clean error). This is the authoritative consent signal the loader recomputes (`bundleContentHash(capDir)`) and checks at load before activating a PROJECT-scope third-party overlay (declarative surfaces AND command dispatch): a forged/cloned in-repo project ledger, OR any post-consent tamper (swapped declarative manifest, edited hook script, empty-integrity local install — all change the recomputed hash), leaves the cap DISCOVERED-BUT-INACTIVE until the user consents on THIS machine to the EXACT bundle (the lifecycle records the consent on a consented project install/upgrade and revokes it on remove; install/lookup/revoke share one canonical `consentProjectRoot` root key). GLOBAL-scope overlays (under the user's own home) need no record; and when `GSD_HOME` resolves (via realpath, defeating symlink aliasing — finding 1) to a genuine project root the in-repo bundle still requires a record. The consent lock is the SHARED hardened primitive (below), so it never stale-steals a slow-but-live writer (finding 4). See `docs/explanation/capability-trust-model.md` "project-scope trust boundary".
Issue #1459 user-owned consent seam (`gsd-core/bin/lib/capability-consent.cjs`, generated from `src/capability-consent.cts`). Leaf module (`node:fs`/`node:path`/`node:os`/`node:crypto` + the ledger's shared bounded `readSmallRegularFile`/`readSmallRegularFileBuffer` + the shared `capability-lock` primitive). Stores `{ version:"1", records: { "<JSON disk key {r:realpath(projectRoot),i:id}>": { projectRoot, id, scope:'project', integrity, disclosureSignature, contentHash, consentedAt } } }` at `${GSD_HOME||homedir()}/.gsd/consent.json` — a USER-OWNED file OUTSIDE any repository. Exports: `consentStorePath(gsdHome?)`, `readConsentStore(gsdHome?)` (bounded via `readSmallRegularFile` + 8 MiB cap, NON-THROWING — missing/corrupt/oversized/FIFO/wrong-shape → empty `{records:{}}`; caps records at `MAX_RECORDS=4096`), `bundleContentHash(capDir)` (THE security binding — a `sha512-<base64>` over a DETERMINISTIC, INJECTIVE, LOSSLESS serialization of EVERY regular file AND directory under the bundle: length-FRAMED entry COUNT + per-entry TYPE tag + uint32 path-byte-len + RAW path bytes from a `{encoding:'buffer'}` dir walk [finding 4] + for files uint64 content-byte-len + RAW content bytes via `readSmallRegularFileBuffer` [finding 1b], plus typed DIR markers binding empty directories [finding 2]; symlinks/non-regular rejected; size+count bounded), `hasProjectConsent({gsdHome,projectRoot,id,contentHash})` (true iff a record for `${realpath(projectRoot)}\x00<id>` exists AND its stored `contentHash` equals the supplied recomputed hash — the binding is `contentHash`, NOT `integrity` and NOT `disclosureSignature` (those remain on the record purely for the human disclosure + re-consent-on-executable-change UX); unsafe ids → false; prototype-pollution-safe NUL-joined keys + `Object.prototype.hasOwnProperty`), `recordProjectConsent({gsdHome,projectRoot,id,integrity,disclosureSignature,contentHash})` (LOCKED, atomic+durable write — tmp `wx`/fsync/rename/dir-fsync mirroring `writeLedger`; enforces the record cap at write time) and `revokeProjectConsent({gsdHome,projectRoot,id})` (LOCKED atomic delete, no-op if absent) — BOTH **THROW** rather than perform an UNLOCKED read-modify-write when the consent-store lock cannot be acquired (finding 3; the lifecycle treats a consent-write failure as non-fatal, and the `trust revoke` CLI catches the throw and emits a clean error). This is the authoritative consent signal the loader recomputes (`bundleContentHash(capDir)`) and checks at load before activating a PROJECT-scope third-party overlay (declarative surfaces AND command dispatch): a forged/cloned in-repo project ledger, OR any post-consent tamper (swapped declarative manifest, edited hook script, empty-integrity local install — all change the recomputed hash), leaves the cap DISCOVERED-BUT-INACTIVE until the user consents on THIS machine to the EXACT bundle (the lifecycle records the consent on a consented project install/upgrade and revokes it on remove; install/lookup/revoke share one canonical `consentProjectRoot` root key). GLOBAL-scope overlays (under the user's own home) need no record; and when `GSD_HOME` resolves (via realpath, defeating symlink aliasing — finding 1) to a genuine project root the in-repo bundle still requires a record. The consent lock is the SHARED hardened primitive (below), so it never stale-steals a slow-but-live writer (finding 4). See `docs/explanation/capability-trust-model.md` "project-scope trust boundary".
### Capability Lock
Issue #1459 finding 4 shared cross-process lock primitive (`gsd-core/bin/lib/capability-lock.cjs`, generated from `src/capability-lock.cts`). Leaf module (`node:fs`/`node:path`/`node:os`/`node:crypto` + the ledger's bounded `readSmallRegularFile` + `shell-command-projection`'s `execTool` for the rare start-time shell-out). THE single hardened lockfile protocol shared by BOTH `capability-lifecycle` (the `.gsd/capabilities/.lock` mutation lock) and `capability-consent` (the consent-store `.consent.lock`) — extracted so the two locks cannot diverge (mirrors the shared-validator / shared bounded-reader lessons). Exports: `acquireLock(lockPath, opts?)` (O_EXCL create with a JSON `{token,pid,hostname,startTime,ts}` body; steal protocol binds age to the body's own `ts`, never stale-steals a VERIFIED-LIVE same-host holder — pid alive AND recorded start-time matches the pid's current start-time, defeating pid-reuse without ever stealing a live holder — and reclaims only a dead/unverifiable holder via the dead-pid fast path or the hard `LOCK_DEADMAN_MS` deadman; `opts.maxAttempts` raises the bounded retry budget and `opts.waitForFresh` makes a contended fresh/live holder be WAITED FOR rather than failed-fast so genuinely-racing consent writers serialize), `releaseLock(handle)` (token + inode owner-safe — never deletes a successor's lock), `getProcessStartTime`, and the `_setLockProbes`/`_resetLockProbes` test seams. Carries the #1462 lifecycle-lock invariants (process-start-time liveness, TOCTOU-safe pre-rename identity recheck, bounded iterative loop).
@@ -208,6 +238,8 @@ ADR-1244 Phase 4 (D5+D6) orchestration seam (`gsd-core/bin/lib/capability-lifecy
### Capability Command Dispatch
ADR-1244 Phase 5 (D7) registry-driven dispatch of capability command families. First-party families (`graphify`/`intel`/`audit`, shipped in `bin/lib/`) dispatch via `dispatchCapabilityCommand` (`gsd-core/bin/gsd-tools.cjs`) against the FROZEN `capability-registry.cjs` `commandFamilies` (confined to `bin/lib/`) — unchanged. Third-party (installed overlay) families dispatch via `dispatchOverlayCapabilityCommand`: after the first-party path returns false, it calls `loadRegistry({ includeInstalled, cwd })` and dispatches a family iff its `capId` is in `_overlay.commandRoots` — which `capability-loader.cjs` populates ONLY for accepted overlay capabilities that declare `commands` AND pass the loader's activation gate (a **committed** ledger entry, present and non-`_pending`, PLUS — for PROJECT scope — a matching user consent record in the Capability Consent Store; GLOBAL scope needs no consent record). A bundle dropped on disk with no install (no ledger entry) or no on-this-machine consent is NOT command-dispatchable. The router module is `require()`'d FROM the capability's install root via `defaultRequireFromInstallRoot` (bare-`.cjs` basename + `realpath` containment, rejecting `..` traversal and symlink escape); same own-property/function/sync-only guards as the first-party path. Wired into the `runCommand` default arm before "Unknown command". A repo-planted project ledger no longer activates anything on its own (#1459) — see `docs/explanation/capability-trust-model.md` "project-scope trust boundary".
### Claude Orchestration Capability
Default-off, BETA, claude-only Capability (`capabilities/claude-orchestration/`, `role: feature`, `runtimeCompat.supported: ["claude"]`, `tier: full`, `activationKey: claude_orchestration.enabled`) adopting Claude Code's Workflow tool (the engine behind `/effort ultracode`, Agent SDK ≥ v0.3.149) as an optional parallel-execution backend for the GSD loop, and folding the `gsd-ultraplan-phase` plan-offload under the same runtime gate (#1143; ADR-1143). Pure, fail-closed core in `gsd-core/bin/lib/claude-orchestration.cjs` (generated from `src/claude-orchestration.cts`): `detectWorkflowBackend({ runtimeId, hostIntegration, config, agentSdkVersion }) → { available, backend:'workflow'|'inline', reason }` (gate ladder: enabled → Claude → execution_backend ≠ inline → host dispatch nested+background → valid Agent SDK → SDK ≥ floor; every miss degrades to `inline`, never throws); `emitWorkflowScript({ phaseDir, waves, runId, budgetTokens? }) → { ok, script, summary }` mapping waves → `parallel()` stage barriers, plans → `agent({ agentType:'gsd-executor', isolation:'worktree' })`, `files_modified` overlap → separate sequential stages (greedy first-fit), `resumeFromRunId` wired to the run id, shared `budget(tokens)`; all interpolated identifiers validated script-safe (no `"`,`\`,control chars) and briefs JSON-quoted (review anti-injection). Registers two loop contributions at WIRED points only (execute:wave:pre/execute:pre are declared but not rendered, same constraint external-job documents): `execute:wave:post into:executor` (Workflow-backend guidance) and `plan:post into:planner` (ultraplan ownership declaration), both `when: claude_orchestration.enabled`, `onError: skip`. Federated config keys (`claude_orchestration.enabled` default false, `execution_backend` enum auto|workflow|inline default auto, `min_agent_sdk_version` string default "0.3.149") live only in the registry — uninstall removes them cleanly. Pre-release versions of the floor compare below GA (SemVer precedence). Restores the wave parallelism + plan-checker + verifier that #853 forces inline on Claude Code; on any runtime lacking the Workflow tool, behaviour is byte-identical to today. BETA v1 ships detection + emission + declarative ultraplan ownership + a `claude-orchestration` command family (`gsd-tools claude-orchestration detect-backend|emit-workflow`, router `gsd-core/bin/lib/claude-orchestration-command-router.cjs` from `src/claude-orchestration-command-router.cts`); full install-profile migration of the ultraplan skill into `skills[]` is a follow-up (CLUSTERS/profile gate). Test anchors: `tests/claude-orchestration.test.cjs`, `tests/claude-orchestration-command-router.test.cjs`.
### Loop Extension Point
A named, stable site on a host loop step (per-step `pre`/`post` plus per-wave in Execute; 12 total) where Capabilities register hooks. Three hook kinds: `step` (runs as its own sequenced unit), `contribution` (injects into the core step's prompt/context), and `gate` (checks and optionally blocks via a declared `blocking` flag). Each hook declares the artifacts it produces and consumes; hook order is derived by topological sort of that produces/consumes graph (capability-id tiebreak), which also defines data flow — file-artifact based, surviving `/clear` and fresh executor contexts. Hooks are surfaced by runtime resolution with concrete projection: the workflow calls a query that resolves the active hooks and returns fully-rendered, ordered markdown for the executor. Failure is default-resilient — a non-gate hook that errors is skipped with a warning; a hook may opt into `onError: halt`. Part of the Capability system. ADR-857 phase 3c ships the registry-consuming query layer: `gsd-core/bin/lib/loop-resolver.cjs` exposes `resolveLoopHooks({ point, registry, config })` (pure, no I/O), `renderLoopHooks(resolved)` (pure markdown renderer), and `cmdLoopRenderHooks(cwd, point, raw, opts)` (I/O entry point); activated via `gsd-tools loop render-hooks <point>` which emits `{ point, activeHooks[], rendered }`. Activation is driven by `when` (dotted config key resolved against `loadConfig`), with inline literal `__proto__`/`constructor`/`prototype` prototype-pollution guard. The first phase-6 cutovers wiring workflows to this query have landed — ui-phase at `plan:pre` and ui-review at `verify:post` (in `plan-phase.md`/`autonomous.md`); further per-feature cutovers are ongoing.
@@ -253,22 +285,19 @@ A cross-Wing knowledge connection created by `mempalace_create_tunnel`. GSD prop
A per-agent narrative entry written by `mempalace_diary_write`. GSD's `gsd-mempalace-curator` writes a diary entry at `ship:post` when `mempalace.diary_journal: true`, recording a session summary scoped to the project and agent role. MemPalace vocabulary — see Connected Capability.
### memory_mode
The `mempalace.memory_mode` config key controlling how tightly MemPalace couples to GSD's native memory. Three declared values: `augment` (default — **implemented**; palace is an additional write-mostly recall layer; lowest coupling), `kg_backend` (**declared; routing seam not yet implemented** — intended to route graphify KG queries through MemPalace's temporal graph; selecting today behaves as `augment`), `replace` (**declared; not yet functional** — intended to make the palace the durable store; selecting today behaves as `augment`). Only `augment` has effect in the current release; `kg_backend` and `replace` are forward-declared for a future release. Read at hook-render time; switching is a config change, not a reinstall. See MemPalace Settings in `docs/CONFIGURATION.md`.
The `mempalace.memory_mode` config key controlling how authoritative MemPalace is during recall/capture relative to GSD's native memory. Three wired values: `augment` (default — palace is an additive recall layer; native memory stays authoritative; lowest coupling), `kg_backend` (knowledge-graph queries resolve against MemPalace's temporal graph as the primary source, `.planning/graphs/` as fallback; non-KG drawer recall stays additive), `replace` (recall resolves through the palace as the source of truth, native artifacts as fallback). Every mode is `onError:skip` and default-resilient — an unreachable palace degrades to native memory and GSD keeps writing `.planning/graphs/`, so no mode loses memory. Read at hook-render time; switching is a config change, not a reinstall. Cross-mode migration of existing `.planning/graphs/` into the palace is a separate, not-yet-implemented concern (PRD/ADR §17 open question). See MemPalace Settings in `docs/CONFIGURATION.md`.
### Runtime Hooks Surface Module
Standalone hook-surface writer module extracted from `bin/install.js` as ADR-857 phase 5f-1 (behavior-preserving relocation, no logic change). Owns: Cline rules-body/agents-md/pre-tool-use hook generation (`buildClineRulesBody`, `buildClineAgentsMdBody`, `buildClinePreToolUseHook`, `mergeGsdAgentsMd`, `writeClineArtifacts`); Cursor `hooks.json` lifecycle (`buildCursorHookEntry`, `isManagedCursorHookEntry`, `reconcileCursorHooksJson`, `writeCursorHooksJson`, `removeCursorHooksJson`); Copilot session-hook config (`buildCopilotHookConfig`, `writeCopilotHookConfig`); Codex hook-block and event management (`buildCodexHookBlock`, `rewriteLegacyCodexHookBlock`, `reconcileCodexHooksJsonEvent`, `reconcileCodexHooksJsonSessionStart`, `ensureCodexHooksJsonSessionStart`, `ensureCodexHooksJsonEvent`, `removeCodexHooksJsonEvent`, `removeCodexHooksJsonSessionStart`, `buildCodexHookWindowsShimIR`); and shared hook command helpers (`buildHookCommand`, `rewriteLegacyManagedNodeHookCommands`, `normalizeNodePath`, `resolveNodeRunner`). `bin/install.js` delegates to this module via thin wrappers and re-exports its functions unchanged so existing tests require no modification. Source: `src/runtime-hooks-surface.cts`. Built output: `gsd-core/bin/lib/runtime-hooks-surface.cjs`.
Standalone hook-surface writer module extracted from `bin/install.js` as ADR-857 phase 5f-1 (behavior-preserving relocation, no logic change). Owns: Cline rules-body/agents-md/pre-tool-use hook generation (`buildClineRulesBody`, `buildClineAgentsMdBody`, `buildClinePreToolUseHook`, `mergeGsdAgentsMd`, `writeClineArtifacts`); Cursor `hooks.json` lifecycle (`buildCursorHookEntry`, `isManagedCursorHookEntry`, `reconcileCursorHooksJson`, `writeCursorHooksJson`, `removeCursorHooksJson`); Copilot session-hook config (`buildCopilotHookConfig`, `writeCopilotHookConfig`); Codex hook-block and event management (`buildCodexHookBlock`, `rewriteLegacyCodexHookBlock`, `reconcileCodexHooksJsonEvent`, `reconcileCodexHooksJsonSessionStart`, `ensureCodexHooksJsonSessionStart`, `ensureCodexHooksJsonEvent`, `removeCodexHooksJsonEvent`, `removeCodexHooksJsonSessionStart`, `buildCodexHookWindowsShimIR`); Kimi native config.toml `[[hooks]]` lifecycle (`buildKimiHooksTomlBlock`, `stripKimiHooksTomlBlock`, `writeKimiHooksToml`, `removeKimiHooksToml` — #2095 EoS/kimi Upgrade 1, the first genuinely NEW hook surface added post-relocation rather than a behavior-preserving move: kimi's `[[hooks]]` array lives in its own native `config.toml`, resolved by `resolveKimiHooksTomlDir` in Runtime Homes Module to a directory deliberately separate from kimi's GSD configDir, wrapped in `# GSD Hooks BEGIN`/`END` marker comments for idempotent reinstall); and shared hook command helpers (`buildHookCommand`, `rewriteLegacyManagedNodeHookCommands`, `normalizeNodePath`, `resolveNodeRunner`). `bin/install.js` delegates to this module via thin wrappers and re-exports its functions unchanged so existing tests require no modification. Source: `src/runtime-hooks-surface.cts`. Built output: `gsd-core/bin/lib/runtime-hooks-surface.cjs`.
### Runtime Config Adapter Registry
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`, `sandboxTier`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. `sandboxTier` (`none` | `codex-agent-sandbox`) gates per-agent `sandbox_mode` emission in the codex TOML path and fails loud on a missing/invalid value (#1151). The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
Module owning the explicit per-runtime config-mutation dispatch table for the installer. `resolveRuntimeConfigIntent(runtime)` projects a typed config intent — `installSurface` (`settings-json` | `codex-toml` | `copilot-instructions` | `cline-rules` | `cursor-hooks-json` | `profile-marker-only`), `writesSharedSettings` (the `finishInstall` shared-settings write gate), and `finishPermissionWriter` (`opencode` | `kilo` | `antigravity` | none) — that `bin/install.js` dispatches on instead of inline `runtime === '...'` branching. Owns adapter selection only: it performs no filesystem IO and does not execute config mutations (the install/finishInstall handlers and the per-runtime writers do that). Unknown runtimes fail loudly with a `TypeError`, guarded by an `Object.hasOwn` own-property check so prototype-chain keys (`__proto__`, `constructor`) also throw. Also exports `resolveInstallPlan(runtime)` — the ADR-58 `InstallPlan` capstone — which collects the install-level descriptor axes (`installSurface`, `writesSharedSettings`, `finishPermissionWriter`, `hookEvents`, `extendedHookEvents`, `hooksSurface`, `sandboxTier`) into one typed `InstallPlan` value consumed by `install()` and `finishInstall()` in `bin/install.js`. `sandboxTier` (`none` | `codex-agent-sandbox`) gates per-agent `sandbox_mode` emission in the codex TOML path and fails loud on a missing/invalid value (#1151). The spatial axes (`configHome`, `artifactLayout`, `commandStyle`) remain behind their self-resolving adapter modules and are not part of the plan; they are the execution adapters. Realizes both the adapter-selection and plan-collection halves of the Runtime Install Policy Module boundary. Source: `gsd-core/bin/lib/runtime-config-adapter-registry.cjs`. See ADR-58, #60.
### Claude Code Plugin Manifest Module
Module owning the projection of gsd-core's artifact surfaces (`commands`, `agents`, hooks) onto the Claude Code plugin contract (`.claude-plugin/plugin.json` + `hooks/hooks.json`) — the plugin-contract sibling of the Runtime Artifact Layout Module (which projects the same surfaces onto filesystem placements). Defined mapping: `name`=`binName` (drives the `/gsd-core:` command namespace), `repository`/`homepage`=`repoUrl` (Package Identity Module), `version`/`description`/`license` from `package.json` (`version` is required for `claude plugin validate --strict`), `commands`=`./commands/gsd/`, agents via Claude Code's default `agents/` discovery (the explicit string form is schema-rejected), `hooks`=`./hooks/hooks.json`. The hook projection carries ONLY the always-on subset of the Installer Module's Claude `settings.json` wiring (check-update, context-monitor, prompt-guard, read-guard, worktree-path-guard, read-injection-scanner) via `${CLAUDE_PLUGIN_ROOT}`; config-gated opt-in hooks are excluded because a static manifest cannot honor per-project config gates, and plugin-shipped agents cannot carry hook frontmatter (so all plugin-path hook wiring lives in hooks.json). `hooks.json` covers all seven Claude Code lifecycle events: SessionStart, PreToolUse, PostToolUse, SubagentStop, Stop, PreCompact (all wired to context-monitor for context-headroom awareness), and FileChanged (matcher: `config.json` → config-reload, injects `additionalContext` when `.planning/config.json` changes mid-session). Additive — the file-copy path (Runtime Artifact Layout / Install Policy / Installer Modules) is unchanged. Conformance is validated by `claude plugin validate --strict` plus the in-repo drift-guard `tests/issue-766-plugin-manifest.test.cjs`. _Avoid_: "the plugin API", "the plugin file" (when you mean the seam). See ADR-766 and Runtime Artifact Layout Module.
### Gemini Extension Package
The repo-root `gemini-extension.json` + `GEMINI.md` pair that projects gsd-core onto the Gemini CLI extension contract, enabling one-step lifecycle management via `gemini extensions install <git-url>` / `update` / `remove` (and `gemini extensions link <path>` for dev). The Gemini-CLI sibling of the Claude Code Plugin Manifest Module — same additive idea, different runtime package format. Defined mapping: `name`=`binName` (`gsd-core`; lowercase-dashes per Gemini's extension naming rule), `version` tracks `package.json` (Gemini's `gemini extensions update` keys off the manifest `version` field), `description` (required by the manifest schema), `contextFileName`=`GEMINI.md` (the extension's context payload, loaded into every Gemini session). Intentionally minimal: no `mcpServers` (gsd-core ships no MCP server). Slash-command / agent / hook projection into the extension (which would require committing the Gemini-format TOML/agent conversions the Installer Module produces at `--gemini` install time) is deferred — the manual `npx gsd-core --gemini` path remains the way to install the `/gsd:*` commands, and is unchanged (additive, no breaking change). Conformance is guarded by the in-repo drift test `tests/issue-775-gemini-extension.test.cjs` (manifest validity, `version`↔`package.json` parity, `contextFileName` existence, `files[]` publication). _Avoid_: "the Gemini plugin" (Gemini calls them extensions, not plugins). See #775, ADR-766, Claude Code Plugin Manifest Module, and Runtime Artifact Layout Module.
### Knowledge Graph Module
Module owning the graphify integration: tri-state capability gate (`isCapabilityActive('graphify', cwd)` from capability-state.cjs — requires installed AND surfaced AND config-enabled; replaces the former config-only `isGraphifyEnabled` gate, cutover in #1306), disabled response (`disabledResponse`), subprocess helper (`execGraphify`, typed `GRAPHIFY_REASON` enum), presence detection (`checkGraphifyInstalled`), version checking (`checkGraphifyVersion`), query surface (`graphifyQuery` — BFS seed-expand + budget trim), status surface (`graphifyStatus` — node/edge counts, mtime staleness, commit-staleness tri-state via `built_at_commit`/`commits_behind`/`commit_stale`), diff surface (`graphifyDiff` — added/removed/changed nodes+edges), build pre-flight (`graphifyBuild`), snapshot management (`writeSnapshot`). Config leg reads `.planning/config.json:graphify.enabled`; all three legs (install, surface, config) must be active; writes to `.planning/graphs/`. Auto-update hook (`hooks/gsd-graphify-update.sh`) triggers a detached background rebuild after HEAD-advancing git operations on the default branch when `graphify.auto_update=true`. Status file `.planning/graphs/.last-build-status.json` carries `{ ts, status, exit_code, duration_ms, head_at_build, graphify_version }`. Graph IR uses `nodes[]`, `edges[]` (or `links[]` for graphify ≥0.7 compat), `hyperedges[]`, `built_at_commit`. `commit_stale` is tri-state: `false` (known fresh), `true` (stale), `null` (unknown — no git or pre-v0.7 graph). Source: `gsd-core/bin/lib/graphify.cjs`. Skill: `commands/gsd/graphify.md`.
Module owning the graphify integration: tri-state capability gate (`isCapabilityActive('graphify', cwd)` from capability-state.cjs — requires installed AND surfaced AND config-enabled; replaces the former config-only `isGraphifyEnabled` gate, cutover in #1306), disabled response (`disabledResponse`), subprocess helper (`execGraphify`, typed `GRAPHIFY_REASON` enum), presence detection (`checkGraphifyInstalled`), version checking (`checkGraphifyVersion`), query surface (`graphifyQuery` — BFS seed-expand + budget trim), status surface (`graphifyStatus` — node/edge counts, mtime staleness, commit-staleness tri-state via `built_at_commit`/`commits_behind`/`commit_stale`), diff surface (`graphifyDiff` — added/removed/changed nodes+edges), build pre-flight (`graphifyBuild`), snapshot management (`writeSnapshot`). Config leg reads `.planning/config.json:graphify.enabled`; all three legs (install, surface, config) must be active; writes to `.planning/graphs/`. Graph location override (#1825): `graphify.graph_path` in `.planning/config.json` (a path relative to the project root, or absolute) redirects where `graphifyQuery`/`graphifyStatus`/`graphifyDiff` read `graph.json` — so one umbrella-level cross-repo graph serves multiple sibling projects without N drifting mirror copies; the diff snapshot (`.last-build-snapshot.json`) travels with the configured graph (same dir); the auto-update status sidecar stays project-local; `writeSnapshot` honors the key (reads the configured graph, writes the snapshot alongside it); build stays project-scoped (`.planning/graphs/`) since the build skill hardcodes that destination — the umbrella graph is built in the umbrella project and sub-projects only READ it. Unset/blank/non-string → byte-identical `.planning/graphs/graph.json` default; a configured-but-missing file yields an actionable error naming the path. The key is registered in `config-schema.manifest.json` `validKeys`. Auto-update hook (`hooks/gsd-graphify-update.sh`) triggers a detached background rebuild after HEAD-advancing git operations on the default branch when `graphify.auto_update=true`. Status file `.planning/graphs/.last-build-status.json` carries `{ ts, status, exit_code, duration_ms, head_at_build, graphify_version }`. Graph IR uses `nodes[]`, `edges[]` (or `links[]` for graphify ≥0.7 compat), `hyperedges[]`, `built_at_commit`. `commit_stale` is tri-state: `false` (known fresh), `true` (stale), `null` (unknown — no git or pre-v0.7 graph). Source: `gsd-core/bin/lib/graphify.cjs`. Skill: `commands/gsd/graphify.md`.
### Intel Module
Module owning the code-intelligence store: tri-state capability gate (`isCapabilityActive('intel', cwd)` from capability-state.cjs — honours installed+surfaced+config-enabled; replaces the former config-only `isIntelEnabled` gate, cutover in #1307; intel has `skills:[]` so installed/surfaced are vacuously true and the effective gate is `intel.enabled` in config), disabled response, query surface (`intelQuery` — full-text search across all intel JSON files), status surface (`intelStatus` — per-file freshness, 24-hour staleness threshold), diff surface (`intelDiff` — added/changed/removed files vs last-refresh snapshot), snapshot management (`saveRefreshSnapshot`/`intelSnapshot`), validation (`intelValidate` — existence, JSON validity, _meta.updated_at recency), api-surface render (`intelApiSurface` — generates `.planning/intel/API-SURFACE.md` from `api-map.json`), plus ungated utilities (`intelPatchMeta` — patches `_meta.updated_at` in any JSON file; `intelExtractExports` — extracts CJS/ESM exports from any JS file). Loop hook rendering gates on `state.active` (not `state.enabled`) so the `activationKey` config gate is honoured even without a per-hook `when` guard (Phase 4 tri-state alignment, #1307). Source: `gsd-core/bin/lib/intel.cjs` (generated from `src/intel.cts`). Router: `gsd-core/bin/lib/intel-command-router.cjs`. See Capability Command Family Module (ADR-959 4d-impl-4) and Loop Extension Point.
@@ -314,6 +343,9 @@ The ownership seam between the prohibition probe and security/compliance tooling
### Prohibition Probe Module
Second adapter of the Probe Core Module (ADR-550 Decision 7): the spec-phase prohibition-completeness probe wired into spec-phase Step 5.6, surfacing the unwritten *must-NOT* constraints (values/safety/ethics) the spec never forbids. Unlike the Edge Probe, recall is **prose-orchestrated, not a compiled engine** (ADR-550 D7b) — a two-stage pass per requirement: Stage 1 an adversarial recall question, Stage 2 a one-pass precision classifier (drop routine engineering, keep genuine prohibitions). The code surface is schema/projection only: `projectProhibitions()` (deterministic SPEC↔`must_haves.prohibitions` projection backing the `DEFECT.GENERATIVE-FIX` parity assertion), the `{test, judgment}` `PROHIBITION_VALIDATORS`, `validateProhibitionResolution`, and `dispositionForProhibition()` (the fail-closed default — an unwired `test`-tier item resolves to `unverified`/flagged, never green). **Deterministic test-tier locate (#1278, ADR-550 D3 addendum):** a resolved `test`-tier prohibition MAY carry an optional flat-scalar `check` descriptor — `check_kind` (`node-test` | `lint-rule`), `check_target`, and `check_rule` (lint-rule only) — that `projectProhibitions` emits into `must_haves.prohibitions` when well-formed, and `descriptorFromProjection()` (the read-back seam in the #1259 enforcement producer, `src/prohibition-enforcement.cts`) reconstructs into a `{kind, target, rule?}` `CheckDescriptor`, so verify-phase locates the wired check with zero LLM/author authoring. **Flat scalars, never a nested `check:{}` object** — so the round-trip rides the *unchanged* shared `parseMustHavesBlock` (the #644 no-parser-rewrite precedent); an absent/partial descriptor falls through to the producer's existing fail-closed locate, and `failFirst` stays caller-attested (machine-proof is #1279). No `proposeProhibitions()` — recall is LLM prose. `plan-phase` lifts every resolved prohibition from the SPEC `## Prohibitions (must-NOT)` section into the `must_haves.prohibitions` sibling block (never `truths`). Exports (locked surface): `projectProhibitions`, `PROHIBITION_VALIDATORS` (the `{test, judgment}` validators bundle injected into `probe-core`'s generic engine), `validateProhibitionResolution`, and `dispositionForProhibition` (the fail-closed disposition) — the prohibition adapter surface, shipped from `probe-core` alongside the generic engine. Source of truth: `gsd-core/bin/lib/probe-core.cjs` (the prohibition exports live in `src/probe-core.cts`, gitignored per ADR-457) + `gsd-core/references/prohibition-probe.md`. Tests: `tests/prohibition-probe.*.test.cjs`. See ADR-550, Probe Core Module, Edge Probe Module, Verification Tier, Bespoke vs Canon Prohibition.
### Spec-Section Helper Module
The SINGLE source of truth for "did the phase SPEC supply section X (with at least one resolved row)?" — the SPEC-section detection seam consumed by `plan-phase` Step 7.95 (the spec-less probe fallback) to decide, per section, whether to run the fallback. Replaces the ad-hoc `awk` that previously lived in the workflow body, which hard-coded the section header strings at the call site and hand-rolled markdown-table row counting — a brittleness that produced two bugs: an exact `^## Prohibitions$` anchor that missed the canonical `## Prohibitions (must-NOT)` heading, and a single-table row-counting assumption. **Suffix-tolerant header invariant:** `SECTION_HEADERS` regexes match a heading AND any parenthetical/whitespace suffix — `prohibitions` matches both `## Prohibitions` and `## Prohibitions (must-NOT)`; `edges` matches `## Edge Coverage` (and any future suffix); if spec-phase renames a heading, update HERE and the `templates/spec.md` heading together (the contract is pinned by `tests/spec-section.test.cjs`). **Supply rule:** `supplied = present AND dataRows > 0` — a present-but-empty section is NOT supplied (it triggers the fallback). **Multi-table robustness:** a blank or prose line resets the per-table state, so a section with multiple tables (or prose between them) counts every table's data rows without miscounting a second table's header row; the `|…|` line before a `|---|` separator is the table header row and is never counted. **Fail-safe:** a missing/unreadable SPEC file resolves to `present:false` / `supplied:false` (so the fallback fires) rather than throwing. Exports (locked surface): the `SpecSectionKey` type (`edges | prohibitions`), `SECTION_HEADERS` (the canonical header matchers), the `SectionStatus` shape (`{ key, present, dataRows, supplied }`), `countSectionDataRows` (pure `specText → { present, dataRows }`), and `specSectionStatus` (disk-reading wrapper). CLI: `node spec-section.cjs <specFile> <edges|prohibitions>` prints `SectionStatus` JSON — exit 0 on success (an absent file is a valid "not supplied" answer), exit 2 only on a usage error (missing args / bad key). Pure and dependency-free. Source of truth: `gsd-core/bin/lib/spec-section.cjs` (generated from `src/spec-section.cts`, gitignored per ADR-457). Tests: `tests/spec-section.test.cjs`. See Edge Probe Module, Prohibition Probe Module, and `references/specless-probe-fallback.md`.
### MVP Mode
Phase-level planning mode that frames work as a vertical slice (UI → API → DB) of one user-visible capability instead of horizontal layers. Resolved at workflow init via the precedence chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → `workflow.mvp_mode` config → false. All-or-nothing per phase (PRD #2826 Q1). Surfaced as `MVP_MODE=true|false` to the planner, executor, verifier, and discovery surfaces (progress, stats, graphify). Canonical parser: `roadmap.cjs` `**Mode:**` field; canonical resolution chain documented in `workflows/plan-phase.md`. Concept index: `references/mvp-concepts.md`.
@@ -353,11 +385,42 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint-
### External-job-waiting half-state
A legal deferred state of an Execute step (`external_job_waiting`): the executor has dispatched a long-running async external job and committed an async-job manifest at `.planning/async-jobs/<job>.json` instead of a SUMMARY.md. Distinct from the synchronous "mid-production-commits" half-state and from an illegal partial-plan state. The core loop's step-completion + safe-resume/pause contract treats a non-terminal manifest as legal and reconciles against it (never re-dispatching the plan, which would duplicate the external job); SUMMARY.md is deferred until the job reaches a terminal state and its `expected_artifacts` are verified. The manifest is a versioned stability contract (`docs/reference/planning-artifacts.md`); core *consumes* it while a default-off scheduler-adapter Capability (#1164) *produces* it at `execute:wave:post` — the contract-is-core / producer-is-capability seam mirrors ADR-857's verification-substrate decision. Status enum is closed and scheduler-agnostic: `submitted`, `running`, `completed-unverified`, `failed`, `cancelled`, `timeout`.
### External-job Capability
The producer half of the async external-job contract (#1164, part of #1105). Default-off Capability (`capabilities/external-job/capability.json`) that *writes* `.planning/async-jobs/<job>.json` manifests — the only thing that does; core never writes them. SLURM is the first backend (`sbatch --parsable` submit, `squeue` poll with `sacct` fallback, terminal-state mapping); the design stays scheduler-pluggable via the `backend` field (LSF/PBS/Kubernetes batch forward-declared, not built). Contributions inject at `execute:wave:post` into the executor (classify runtime budget → externalize `long_compute`, commit manifest + handoff, return `external_job_waiting`, defer SUMMARY.md) and at `plan:post` into the planner (emit `<runtime_budget>` quick|medium|unknown|long_compute per task). Activation key `external_job.enabled` (default `false`); sibling keys `external_job.backend`, `external_job.artifact_dir` (default `Artifacts/jobs`, per-job dirs — no fixed log paths, no hardcoded cluster/partition/account), `external_job.submit_timeout_ms` / `external_job.poll_timeout_ms` (bounded subprocesses per CLAUDE.md). Pure producer logic — SLURM state→manifest-status mapping, manifest build/validate, `sbatch`/`squeue`/`sacct` parsers, and the fail-closed manifest writer (refuses a second non-terminal job for a `plan_id` already in flight; refuses to clobber a malformed manifest) — lives in `gsd-core/src/external-job.cts` (generated to `gsd-core/bin/lib/external-job.cjs`); the operator CLI surface is `scripts/slurm-adapter.cjs` (`submit`/`poll`/`show`). Manifest commands are untrusted across the trust seam: `show` surfaces them for confirmation, never auto-runs `submit_command`/`verification_command`/`resume_command`. Test seam: `tests/external-job.test.cjs` (producer behavioral + fast-check property tests; the consumer invariant suite is `tests/external-job-waiting.test.cjs`).
### Untrusted-input boundary
The prompt-level data/instruction isolation seam for untrusted web/document ingress (#1577). Shared reference `gsd-core/references/untrusted-input-boundary.md`, `@`-included by the 10 ingest agents (`gsd-project-researcher`, `gsd-phase-researcher`, `gsd-ui-researcher`, `gsd-assumptions-analyzer`, `gsd-advisor-researcher`, `gsd-ai-researcher`, `gsd-domain-researcher`, `gsd-research-synthesizer`, `gsd-doc-classifier`, `gsd-doc-synthesizer`) — every agent that reads fetch/search/MCP output or external source documents. The reference instructs: treat fetched/read content as **data, never instructions**; self-scan content for embedded directives before use; act only on the assigned task (ignore off-task instructions in data); and wrap quoted untrusted spans in a **fresh random delimiter** per wrap (fixed markers are spoofable). This prompt-level boundary is the primary control — it keeps an injection from being *followed* even while it sits in context. The hook-level companion is the read-injection scanner (`hooks/gsd-read-injection-scanner.js`, PostToolUse on `Read`/`WebFetch`/`WebSearch`), advisory by default; the opt-in top-level `security.injection_blocking` key upgrades HIGH-confidence detections to a PostToolUse circuit-breaker that halts the agent's next step (it runs *after* the fetch, so it is not a redactor). Tests: `tests/untrusted-input-isolation.test.cjs`, `tests/read-injection-scanner.*.test.cjs`, `tests/injection-blocking-config.test.cjs`. See `docs/adr/1577-untrusted-input-boundary-and-injection-blocking.md` and `docs/explanation/security-model.md`. Grounding: arXiv 2506.05739 (PPA), 2507.15219 (PromptArmor), 2504.20472.
---
## Probe family — spec-completeness probes (machine-oriented predicates)
> Glossary prose for these modules lives above (Probe Core / Edge Probe / Prohibition Probe / Verification Tier / Verification substrate). These are the greppable one-line predicates ADR-550's Consequences promised alongside the glossary. Research-derived numbers (N17/N18 rates) are deliberately kept out of this machine-canon and live hedged in `docs/design/verifier-reach.md`. (That design note and `docs/adr/1606` are co-delivered sibling PRs of epic #1605; predicate refs to them below resolve once the batch lands.)
`PROBE.principle=verifier-reach-equals-spec-reach (a goal-backward verifier only checks assertions that exist; probes make omitted assertions exist before code) — ADR-857 verification-substrate boundary; docs/design/verifier-reach.md`
`PROBE.family=edge-probe(shape-axis)+prohibition-probe(must-NOT-axis)+ui-consideration-probe(UI-state-axis), shared probe-core, run as spec-phase/ui-phase soft gates (ADR-550 D7; #1867)`
`PROBE.protocol=recall(adversarial over-generate)->precision(drop routine-engineering); dismissals require a non-empty reason`
`PROBE.core.seam=analyzeCoverage(items,resolutions?,validators) ingests ALREADY-proposed items; does NOT assume deterministic propose (ADR-550 D7b)`
`PROBE.item.axes=status{resolved|dismissed|unresolved} x verification{<probe-defined>|null} — orthogonal; the lifecycle enum carries no verification fact (ADR-550 D7a)`
`PROBE.edge.verification=explicit|backstop`
`PROBE.prohib.verification=test|judgment`
`PROBE.ui.verification=explicit|backstop`
`PROBE.ui.axis=MIXED — closed compiled shape-rooted 8 (empty/loading/error/populated/partial/overflow/zero-one-many/long-text) via ui-consideration-probe adapter; open UX (real-time/a11y/i18n-RTL) prose-owned in references/domain-probes.md, NOT compiled (#1867)`
`PROBE.ui.seam=ui-phase Step 9.5 post-verification: element-cue classify -> propose-then-confirm (partial-cue mitigation, Goodhart) -> autoResolve --auto floor (never dismiss; unclassified stays unresolved #1110) -> ## UI Considerations write-back -> plan-phase `## UI Considerations` lift rule (#1867)`
`PROBE.ci.surface=the contract (parse/validate, projection round-trip, fail-closed guards), NEVER the LLM judgment (ADR-550 D5)`
`PROHIB.recall=LLM-prose; no compiled prohibition-probe recall engine (only the schema/projection layer is code, ADR-550 D7b)`
`PROHIB.canon-referral=OWASP/GDPR/fairness-canon are REFERRED to /gsd:secure-phase+eslint, never minted as prohibitions (ADR-550 D6)`
`PROHIB.enforce.green-rule=passed iff provenFailFirst===true && run.passed===true (runProhibitionEnforcement); every miss/fail/un-provable HARD-GATES both modes via dispositionForProhibition's fail-closed default`
`PROHIB.enforce.kinds=node-test (non-vacuous red via isNonVacuousNodeTestRed; pass-side vacuity via isNonVacuousNodeTestPass) | lint-rule (eslint --format json filtered by ruleId)`
`PROHIB.enforce.failfirst=MACHINE-PROVEN against an author-supplied violation fixture (#1279); caller failFirst attestation DEMOTED to a non-authoritative hint (FF-08)`
`PROHIB.enforce.causation=clean-fixture control proves the red is content-caused not env-var-set; MANDATORY for node-test (#1906 supersedes #1346 opt-in) — absent clean-fixture ⇒ node-test un-provable/fail-closed; lint-rule needs none (its subject IS the linted file)`
`PROHIB.descriptor.shape=5 FLAT scalars (check_kind,check_target,check_rule,check_violation_fixture,check_clean_fixture) — NEVER a nested check:{} (parseMustHavesBlock is a flat parser, src/frontmatter.cts)`
`PROHIB.rail=core verify rail, non-toggleable (ADR-857 verification-substrate boundary / decision #6); the verifier<->predicate contract is NOT an off-by-default capability`
`PROHIB.judgment-tier=never-silent / never-hard-halt soft gate; autonomous emits "unverified-prohibition — human review recommended" (exogenous grading, ADR-550 D4)`
`PROHIB.enforce.adr=docs/adr/1606 (verify-time enforcement seam) + docs/adr/550 (spec-phase contract)`
---
## Test rules and lint
`RULESET.TESTS.no-source-grep=scripts/lint-no-source-grep.cjs rejects readFileSync source + .includes()/.match()/.startsWith() on the bound var; CI hard-fail`
@@ -388,7 +451,7 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`RULESET.WORKFLOW_SIZE_BUDGET=workflow size enforcement (#1074; BYTES not lines per #717; LF-normalized per #683) = per-file baseline (PRIMARY anti-creep: tests/workflow-size-baseline.json pins each file's exact size) + loose tier hard caps (outer red lines, NEVER raised on approach: XL<=98304 / LARGE<=61440 / DEFAULT<=40960) + new-file cap (un-baselined files <32768, the Codex anchor) + discuss-phase<32000; a file that grew fails the baseline guard — fix with `npm run size:baseline`, commit the one-line diff, and justify the growth in the PR (or extract LAZILY-loaded content; eager @-imports don't reduce loaded context); crossing a hard cap means EXTRACT, not bump`
`RULESET.AGENT_SIZE_BUDGET=agent-size-budget (#1074; sibling of WORKFLOW_SIZE_BUDGET; BYTES not lines per #717/#683, rebased from lines in PR 3/3) = per-file baseline (PRIMARY anti-creep: tests/agent-size-baseline.json pins each agents/gsd-*.md exact byte size) + loose tier hard caps (red lines, never raised on approach: XL<=57344 / LARGE<=49152 / DEFAULT<=24576); net-new agents are DEFAULT-tier (no separate new-file cap). One 'npm run size:baseline' regenerates BOTH workflow and agent baselines via the shared scripts/workflow-size.cjs measureMdFiles(dir,predicate) counter. A grown agent fails the baseline guard — regenerate + justify, or extract LAZILY to gsd-core/references/. DISTINCT from DEFECT.AGENT-FILE-SIZE-CAP-BREACH (a separate 45K-CHAR extraction-evidence threshold on gsd-planner via planner-decomposition/reachability tests): that guard proves mode-sections were extracted; this one bounds total agent bytes. Two guards, two units (chars vs bytes), two purposes`
`RULESET.WORKFLOW_FILE_NAMES=workflow files use hyphens; <step name="..."> XML attributes must match (extract-learnings not extract_learnings); tests should pin exact hyphenated name`
`RULESET.WORKFLOW_EXECUTION_CONTEXT=@-ref in commands/gsd/*.md must resolve to an existing file on disk; regression test in tests/bug-3135-capture-backlog-workflow.test.cjs; INVENTORY.md row + INVENTORY-MANIFEST.json families.workflows must stay in sync; "Invoked by" attribution must move when a flag absorbs a micro-skill`
`RULESET.WORKFLOW_EXECUTION_CONTEXT=@-ref in commands/gsd/*.md must resolve to an existing file on disk; regression test in tests/docs-update.test.cjs (folds former \`bug-3135-capture-backlog-workflow\`, consolidation epic #1969); INVENTORY.md row + INVENTORY-MANIFEST.json families.workflows must stay in sync; "Invoked by" attribution must move when a flag absorbs a micro-skill`
`RULESET.WORKFLOW_EXECUTE_END_TO_END=ADR-0002 standard for single-workflow commands is "Execute end-to-end." (no bolded **Follow the X workflow** fragments); flag-dispatch routing uses "execute the X workflow end-to-end." in routing bullets`
`RULESET.WORKFLOW.COVERAGE-METADATA=#1602 SUMMARY frontmatter `coverage:` block (list of {id,description,requirement?,verification:[{kind∈unit|integration|e2e|automated_ui|manual_procedural|other, ref, status∈pass|fail|unknown}],human_judgment:bool,rationale?}) is the per-deliverable RTM consumed DETERMINISTICALLY by verify-work extract_tests via `gsd-tools uat classify-coverage --summary <f>` (src/coverage.cts → bin/lib/coverage.cjs). AUTHORING: execute-plan create_summary populates it from task <verify> results; every deliverable MUST be classified; fail-safe default = human_judgment:true + rationale. CLASSIFY CONTRACT: auto-pass (skip human) ONLY when human_judgment===false (strict boolean) AND verification non-empty AND every status==='pass' AND zero validation errors — else PRESENT to human. mode:legacy (no block) ⇒ byte-identical prose `## Accomplishments` fall-through; `coverage: []` ⇒ mode:coverage, zero entries (single-confirmation). Frozen IR: MODE/PRESENT_REASON/ERROR_CODE enums locked by tests/coverage-metadata-parser.test.cjs. extractFrontmatter CANNOT parse it (scalars-only `-` items) → dedicated parser, sibling of parseMustHavesBlock. Asymmetry by design: false-negative=redundant prompt (status quo); false-positive=shipped bug UAT existed to catch`
@@ -396,9 +459,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`RULESET.ARGUMENTS-SANITIZE=any workflow step constructing .planning/.../{SLUG}.md path from user input ($ARGUMENTS, parsed remainder) must sanitize inline ([a-z0-9-] only, reject ..//\\, max-length) — "(already sanitized)" must trace back to explicit guard; RESUME/fallback modes need own guards`
`RULESET.SHARED-HELPERS-LINT-VS-TEST=when a lint script and test suite both implement same constant (CANONICAL_TOOLS) or parser (parseFrontmatter, executionContextRefs), extract to scripts/*-helpers.cjs required by both — silent divergence otherwise`
`RULESET.GEMINI.TOOLS.ask_user=Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in body text`
`RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file`
`RULESET.ADR-HEADER=every docs/adr/NNNN-*.md must open with - **Status:** Accepted|Proposed|Deprecated + - **Date:** YYYY-MM-DD immediately after title`
`RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL SIX families.* arrays (agents/commands/workflows/references/cli_modules/hooks) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all six, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write`
@@ -428,9 +488,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`RULESET.CODERABBIT.GUARD.SCOPE=if a new @me open PR appears during final list, include it in the same guard pass before declaring all-open-PRs complete`
`RULESET.TESTS.CODERABBIT_FIX=prefer exported-function behavioral tests over source-grep; lint-no-source-grep rejects readFileSync source assertions without allow-test-rule`
`RULESET.WORKFLOW_MARKDOWN.FENCES=when editing shell snippets inside workflow markdown, preserve the opening language fence; malformed fence can create fresh CodeRabbit threads`
`RULESET.GEMINI.TOOLS.ask_user=Gemini CLI has no ask_user tool; filter both AskUserQuestion and lowercase ask_user from tools frontmatter and neutralize both names in Gemini body text`
`RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file`
`CI.GATE.issue-link-required=hard-fail if PR body lacks closes/fixes/resolves #<issue>`
`CI.GATE.changeset-lint=hard-fail for user-facing code diffs unless .changeset/* or PR has no-changelog label`
`CI.GATE.repair-sequence(PR)=create issue -> apply approval label -> edit PR body w/ closing keyword -> apply no-changelog if appropriate -> re-run checks`
@@ -453,7 +510,6 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`WORKTREE.SEAM.test-policy=cover all decision branches in policy module before changing prune behavior`
`WORKTREE.SEAM.test-anchors=[resolveWorktreeContext:has_local_planning|linked_worktree|not_git_repo|main_worktree, planWorktreePrune:git_list_failed|worktrees_present|no_worktrees|parser_throw_fallback, executeWorktreePrunePlan:missing_plan|skip_passthrough|unsupported_action|metadata_prune_only]`
`WORKTREE.SEAM.invariant=parser failure must degrade to metadata_prune_only and never escalate to destructive removal`
`WORKTREE.SEAM.execution-rule=prefer node --test tests/worktree-safety-policy.test.cjs for fast seam validation; avoid full npm test loop for seam-only changes`
`WORKTREE.SEAM.inventory-interface=[listLinkedWorktreePaths, inspectWorktreeHealth]`
`WORKTREE.SEAM.caller-rule=verify.cjs must consume inspectWorktreeHealth for W017 classification; no ad-hoc porcelain parsing in callers`
`WORKTREE.SEAM.test-anchor-w017=tests/orphan-worktree-detection.test.cjs + tests/worktree-safety-policy.test.cjs`
@@ -680,8 +736,8 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`DEFECT.WINDOWS-FS-OPS.symptom=fs.renameSync / fs.copyFileSync hits EPERM/EBUSY on Windows when antivirus or another process holds a transient handle on the target`
`DEFECT.WINDOWS-FS-OPS.examples=c47c2c5d build-hooks rename → copy fallback, d2412271 install Windows persistent SDK shim`
`DEFECT.WINDOWS-FS-OPS.detect=any rename/copy in build/install path without try/catch fallback`
`DEFECT.WINDOWS-FS-OPS.fix-forward=catch EPERM/EBUSY/EACCES, fall back to copy + unlink with retry, surface degraded-mode message; never silently swallow`
`DEFECT.WINDOWS-FS-OPS.detect=ADR-1703 Phase 6: enforced by local/require-fs-op-fallback (AST ESLint rule, error) over src/**/*.cts + bin/install.js + scripts/build-hooks.js — flags an unguarded fs.rename/fs.renameSync (the atomic-publish primitive named in .symptom) that lacks a transient-errno retry or a Windows platform guard; a catch that silently swallows or cleans-up-and-rethrows without an errno check does NOT satisfy the .fix-forward clause. copyFile/unlink are the fallback primitives (out of scope); delegated retry helpers (retryRenameSync from shell-command-projection) are the recognized compliant shape`
`DEFECT.WINDOWS-FS-OPS.fix-forward=catch EPERM/EBUSY/EACCES, fall back to copy + unlink with retry, surface degraded-mode message; never silently swallow; the canonical production cure is retryRenameSync (shell-command-projection.cjs) or a bounded RENAME_RETRY_ERRNOS = new Set(['EPERM','EBUSY','EACCES']) loop`
`DEFECT.UNBOUNDED-SUBPROCESS.symptom=git/npm subprocess shelled out without timeout; CLI hangs indefinitely on stuck remote, large repo, or missing network`
`DEFECT.UNBOUNDED-SUBPROCESS.examples=a33cbe72 worktree fix bound git subprocesses with timeout`
@@ -720,30 +776,36 @@ The prompt-level data/instruction isolation seam for untrusted web/document ingr
`DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.examples=#586/PR #650 ship.md verification gate — grep "^status:" also matched body status: lines, yielding passed+gaps_found+human_needed instead of passed and blocking a passed phase; the same broad-grep still lives in execute-phase.md (consolidation tracked by #651)`
`DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.detect=grep "^<key>:" on a *.md whose result is compared to exact tokens, with no frontmatter scoping and no -m1; one body line beginning <key>: is enough to break it`
`DEFECT.FRONTMATTER-SCALAR-BROAD-GREP.fix-forward=scope to the leading frontmatter block and take the first match: sed -n '/^---$/,/^---$/p' "$f" | grep -m1 "^<key>:" | cut -d: -f2 | tr -d ' '; fix every parallel copy in the same change or consolidate behind one queryable seam (#651)`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.symptom=a test that parses a workflow bash block out of a *.md and runs it via execFileSync('bash',...) breaks on Windows two ways: the fence regex uses a literal \n after the bash fence that will not match CRLF and trips windows-test-parity-guard (fenceRegexLiteralNewline); and git-bash exists so a bash-presence probe is true, but an os.tmpdir() Windows path (C:\...) is un-globbable in bash so the pipeline returns empty and assertions fail`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.symptom=a test that parses a workflow bash block out of a *.md and runs it via execFileSync('bash',...) breaks on Windows two ways: the fence regex uses a literal \n after the bash fence that will not match CRLF and is flagged by local/no-crlf-fragile-split (the windows-test-parity-guard ratchet it formerly tripped was deleted in ADR-1703 Phase 4 #1726); and git-bash exists so a bash-presence probe is true, but an os.tmpdir() Windows path (C:\...) is un-globbable in bash so the pipeline returns empty and assertions fail`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.examples=#586/PR #650 tests/ship-586-verification-routing.test.cjs — the fence \n offender failed ubuntu-24/macos/coverage, then the Windows tmpdir-path glob failed full test (windows-latest,22) at fail 3; both were invisible to file-scoped gsd-test-both runs because the parity guard is only scanned by the full suite`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.detect=test does readFileSync(md).match for a bash fence with literal \n, OR execFileSync('bash',...) gated only on a bash-presence probe; also verifying a new test with a file-scoped run instead of the full suite hides repo-wide static guards`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.detect=test does readFileSync(md).match for a bash fence with literal \n, OR execFileSync('bash',...) gated only on a bash-presence probe; also verifying a new test with a file-scoped run instead of the full suite hides repo-wide static guards; now enforced at write-time + CI by local/no-crlf-fragile-split (CRLF fence/frontmatter regex + readFileSync split-on-\n) and local/no-unguarded-nonportable-exec (bash+chmod), eslint, ADR-1703`
`DEFECT.TEST-SHELL-PIPELINE-NONPORTABLE.fix-forward=match the fence with \r?\n and normalize the captured block to LF; gate pipeline execution on process.platform !== 'win32' && hasBash since the extraction LOGIC is platform-independent and POSIX coverage suffices; run the full suite (or the parity/lint guards) before push when adding a test file`
`DEFECT.WINDOWS-TEST-PORTABILITY.symptom=local gsd-test runs Mac+Linux only (no Windows host); Windows-only test failures (chmod exec-bit not honored for PATH-executing extension-less scripts in Git Bash msys2; / vs \ path-separator in assertions; Git Bash msys2 shell semantics) surface ONLY in CI test (windows-latest,*) / full test (windows-latest,*) lanes, never locally`
`DEFECT.WINDOWS-TEST-PORTABILITY.examples=PR #1084 (chmod 0o755 + bare-command execution failed on windows lane); test files that assert path.join result without normalizing to forward slashes`
`DEFECT.WINDOWS-TEST-PORTABILITY.detect=npm run lint:windows-test-portability (tripwire: flags tests combining chmod exec-bit with sh/bash -c and no platform guard); watch CI windows matrix green before declaring a PR done`
`DEFECT.WINDOWS-TEST-PORTABILITY.fix-forward=gate platform-specific execution with if (process.platform !== 'win32'); normalize path expectations to forward slashes with .replace(/\\/g, '/'); invoke scripts via explicit interpreter (sh <path>) rather than relying on exec-bit; annotate // windows-portability-ok: <reason> when a bypass is intentional`
`DEFECT.WINDOWS-TEST-PORTABILITY.prevention=run lint:ci before opening a PR; treat the CI windows lane as the only true Windows signal — gsd-test (Mac/Linux only) cannot substitute for it`
`DEFECT.WINDOWS-TEST-PORTABILITY.examples=PR #1084 (chmod 0o755 + bare-command execution failed on windows lane); PR #1692 tests/stale-bake-guard.test.cjs resolveAgentDir assertions hardcoded '/H/.config/opencode/agent' forward-slash literals against a path.join return — passed macOS/linux/ubuntu CI (incl. gsd-test docker mirror), failed windows-latest,24 + full test windows-latest,22 shard 2/3; test files that assert path.join result without normalizing to forward slashes`
`DEFECT.WINDOWS-TEST-PORTABILITY.detect=npm run lint (eslint) runs the local/* AST portability rules (ADR-1703): local/no-unguarded-nonportable-exec flags a test that chmods an exec bit AND runs it via sh/bash -c without a process.platform !== 'win32' guard (the retired scripts/lint-windows-test-portability.cjs tripwire, migrated to AST in #1720); local/no-path-literal-in-assert + local/no-posix-mode-bit-assert cover the assertion shapes; local/no-crlf-fragile-split (CRLF file-content split/regex), local/no-hardcoded-tmp (/tmp literal → os.tmpdir()), local/no-bare-npm-exec (npm needs shell:true on Windows) and local/require-userprofile-with-home (set USERPROFILE alongside HOME) replace the deleted windows-test-parity-guard ratchet (#1726); all are platform-guard-aware with zero opt-out (tests/portability-rule-disable-ban.test.cjs); watch CI windows matrix green before declaring a PR done`
`DEFECT.WINDOWS-TEST-PORTABILITY.fix-forward=gate platform-specific execution with if (process.platform !== 'win32'); normalize path expectations to forward slashes with .replace(/\\/g, '/'); invoke scripts via explicit interpreter (sh <path>) rather than relying on exec-bit; there is NO opt-out for the local/* portability rules — structure platform-specific code behind a recognized process.platform !== 'win32' guard (ADR-1703 zero escape hatch)`
`DEFECT.WINDOWS-TEST-PORTABILITY.prevention=run npm run lint (the local/* AST portability rules, ADR-1703) before opening a PR; treat the CI windows lane as the only true Windows signal — gsd-test (Mac/Linux only) cannot substitute for it`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.symptom=a test writes a file with a POSIX mode (fs.writeFileSync(p, data, {mode: 0o644}) or fs.chmodSync) then asserts fs.statSync(p).mode & 0o777 === <that exact octal>; passes on macOS/Linux/ubuntu CI, FAILS on the windows-latest CI lane — Windows fs does NOT honor POSIX write modes, Node reports the mode derived from the DOS readonly attribute (0o666 for writable / 0o444 for readonly), never the requested 0o644/0o755`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.examples=#1634/PR #1638 tests/capability-lifecycle.test.cjs "a .cjs hook command is node-prefixed so it runs without the executable bit" failed windows-latest,24 on "precondition: file staged without +x" (expected 420/0o644, got 438/0o666); the node-prefix behavioral assertion was correct — only the mode-bit precondition was the POSIX-only fact`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.detect=grep tests for \`.mode & 0o777\` / \`.mode) === 0o\` / \`writeFileSync(...{ mode: 0o\` / \`chmodSync\` paired with a strict-equality assertion on the resulting mode; any such assertion is a POSIX-only fact that will diverge on Windows (write reads back as 0o666)`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.detect=grep tests for \`.mode & 0o777\` / \`.mode) === 0o\` / \`writeFileSync(...{ mode: 0o\` / \`chmodSync\` paired with a strict-equality assertion on the resulting mode; any such assertion is a POSIX-only fact that will diverge on Windows (write reads back as 0o666); NOW mechanically enforced by the AST ESLint rule local/no-posix-mode-bit-assert (eslint-rules/no-posix-mode-bit-assert.cjs, ADR-1703 Phase 2 #1711) — flags a .mode-vs-octal-literal equality assertion unless control-dependent on a process.platform !== 'win32' guard (eslint-rules/lib/platform-guard.cjs); zero opt-outs (tests/portability-rule-disable-ban.test.cjs)`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.fix-forward=gate the mode-bit precondition on if (process.platform !== 'win32') — the executable-bit/mode is a POSIX concept meaningless on Windows; KEEP the platform-independent behavioral assertion (the actual behavior under test) running on every OS; do NOT delete the precondition, scope it to POSIX`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.prevention=ref DEFECT.WINDOWS-TEST-PORTABILITY — gsd-test is Mac/Linux only (no Windows host), only the CI windows-latest lane catches this; run npm run lint:ci (lint-windows-test-portability) before push; prefer asserting the BEHAVIOR (command shape, runnability) over the filesystem mode bit`
`DEFECT.WINDOWS-POSIX-MODE-BIT-ASSERT.prevention=ref DEFECT.WINDOWS-TEST-PORTABILITY — gsd-test is Mac/Linux only (no Windows host), only the CI windows-latest lane catches this; enforced at write-time + CI by the AST ESLint rule local/no-posix-mode-bit-assert (eslint, error; ADR-1703 Phase 2 #1711); run npm run lint before push; prefer asserting the BEHAVIOR (command shape, runnability) over the filesystem mode bit`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.symptom=path.join() result on Windows (backslashes) substituted verbatim into markdown body (@-references, workflow files, generated docs); content gains mixed separators; cross-platform substring assertions fail on windows-latest CI lane only; macOS/Linux CI green so defect ships undetected`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.examples=PR #1622 computePathPrefix returned ${resolvedTarget}/ verbatim — rewrites of @~/.claude/gsd-core/commands/gsd/X.md wrote @C:\...\gsd-ial-windsurf-XXX\gsd-core/commands/gsd/help.md (trailing forward slashes from the original literal survived, prefix backslashes did not); tests/install-runtime-artifacts.test.cjs:318 + tests/install.test.cjs:1323 failed on windows-latest only`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.detect=any function returning a filesystem path that flows into markdown/text body substitution; grep for path.join/raw resolvedTarget/${configDir}/ in code paths writing workflow .md, agent .md, or generated docs; smoke pattern is ${resolvedTarget}/ or ${configDir}/... templates that bypass normalization`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.detect=any function returning a filesystem path that flows into markdown/text body substitution; grep for path.join/raw resolvedTarget/${configDir}/ in code paths writing workflow .md, agent .md, or generated docs; smoke pattern is ${resolvedTarget}/ or ${configDir}/... templates that bypass normalization; NOW enforced at write-time + CI by local/normalize-path-in-content (eslint, error, src/**/*.cts; ADR-1703 Phase 5 #1733) — flags a path-returning fn result (path.basename excluded — returns a separator-less filename) interpolated DIRECTLY into @-reference content (shape a: @~/, @$, @/) or into a template immediately followed by a /…\.md or /…\.json quasi (shape b); INDIRECT data-flow (path stored in a variable/object field then interpolated, e.g. ${entry.ref}) is NOT detected by the rule — normalize at the assignment source or at the emit site; one known indirect leak (src/init.cts cmdAgentSkills entry.ref) fixed in PR #1733 by normalizing at emit; zero opt-out (the out-of-band disable-ban scans src/**/*.cts too)`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.fix-forward=normalize at the SOURCE not the test: posixTarget=String(resolvedTarget).replace(/\\/g,'/'), posixHome=homeDir?String(homeDir).replace(/\\/g,'/'):homeDir; markdown body is POSIX-only; .replace(/\\/g,'/') is idempotent on POSIX (no backslashes present) so safe to apply unconditionally; isWindowsHost arg is a no-op tripwire (enh-1511) — do NOT branch on it, normalize always`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.prevention=RULESET.CONTENT-PATH-NORMALIZATION; tests are downstream signal, never the fix; ref DEFECT.WINDOWS-TEST-PORTABILITY for test-side parity (normalize expected substrings too: ${configDir}/foo.replace(/\\/g,'/'))`
`DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT.prevention=enforced by local/normalize-path-in-content (eslint, error; ADR-1703 Phase 5 #1733) per RULESET.CONTENT-PATH-NORMALIZATION; tests are downstream signal, never the fix; ref DEFECT.WINDOWS-TEST-PORTABILITY for test-side parity (normalize expected substrings too: ${configDir}/foo.replace(/\\/g,'/'))`
`RULESET.CONTENT-PATH-NORMALIZATION=filesystem paths substituted into markdown body text (@-references, workflow .md, agent .md, generated docs, command bodies) MUST be normalized to POSIX forward slashes via .replace(/\\/g,'/') at the production source BEFORE substitution; never push normalization to tests; cross-platform content is POSIX-only; applies to: computePathPrefix output, install-path rewrites, generated shim paths emitted into .md bodies; idempotent on POSIX so unconditional`
`RULESET.CONTENT-PATH-NORMALIZATION=filesystem paths substituted into markdown body text (@-references, workflow .md, agent .md, generated docs, command bodies) MUST be normalized to POSIX forward slashes via .replace(/\\/g,'/') at the production source BEFORE substitution; never push normalization to tests; cross-platform content is POSIX-only; applies to: computePathPrefix output, install-path rewrites, generated shim paths emitted into .md bodies; idempotent on POSIX so unconditional; mechanically enforced by local/normalize-path-in-content (eslint, src/**/*.cts; #1733)`
`DEFECT.WINDOWS-PATH-LITERAL-IN-ASSERT.symptom=an assertion compares the return value of a path-returning function (resolveAgentDir, path.join, path.resolve, getPathX, computePathPrefix, etc.) to a HARDCODED forward-slash string literal like '/H/.config/opencode/agent' or 'C:/Users/...' — passes on POSIX (macOS/linux/ubuntu CI incl. gsd-test docker mirror, where path.join emits forward slashes so literal == actual), FAILS on windows-latest CI lane where path.join emits backslashes so literal != actual`
`DEFECT.WINDOWS-PATH-LITERAL-IN-ASSERT.examples=PR #1692 tests/stale-bake-guard.test.cjs resolveAgentDir suite: assert.equal(resolveAgentDir('opencode',{homedir:()=>'/H'}), '/H/.config/opencode/agent') — green on macOS+ubuntu (docker gate PASS 21101/21101), red on test (windows-latest,24) + full test (windows-latest,22, shard 2/3); same root cause as DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT but on the TEST side against a function return, not the production-markdown side`
`DEFECT.WINDOWS-PATH-LITERAL-IN-ASSERT.detect=any assert*/expect call whose ACTUAL operand is a call to a path-returning fn (path.join, path.resolve, resolveAgentDir, getPathX, computePathPrefix, os.homedir(), path.dirname/basename) AND whose EXPECTED operand is a string literal containing '/' that does NOT first flow through .replace(/\\/g,'/'); the literal-vs-fnCall shape is the tripwire — assert.equal(pathFn(...), '/hardcoded/posix/path') is the violation; assert.equal(String(pathFn(...)).replace(/\\/g,'/'), '/hardcoded/posix/path') is the compliant form; NOW mechanically enforced by the AST ESLint rule local/no-path-literal-in-assert (eslint-rules/no-path-literal-in-assert.cjs, ADR-1703 Phase 1 #1707) — platform-guard-aware (won't flag an assertion control-dependent on a process.platform !== 'win32' guard; eslint-rules/lib/platform-guard.cjs), fn list single-sourced as eslint-rules/lib/portability-vocab.cjs PATH_RETURNING_FNS (drift-guarded vs src/runtime-homes.cts)`
`DEFECT.WINDOWS-PATH-LITERAL-IN-ASSERT.fix-forward=normalize the ACTUAL value to POSIX before comparing: assert.equal(String(pathFn(...)).replace(/\\/g,'/'), '/posix/literal'). Do NOT instead path.join the expected value to match the platform separator — that passes on every platform but masks a malformed backslash-on-POSIX return (both sides wrong together). The .replace is idempotent on POSIX so it is safe unconditionally. For values that are conceptually never paths (null/undefined/numbers), no normalization needed.`
`DEFECT.WINDOWS-PATH-LITERAL-IN-ASSERT.prevention=enforced at write-time (editor) and in CI by the AST ESLint rule local/no-path-literal-in-assert (error, scoped to tests/**/*.test.cjs in eslint.config.mjs; ADR-1703 Phase 1 #1707); inline suppression is banned out-of-band by tests/portability-rule-disable-ban.test.cjs (zero escape hatches — structure platform-specific code behind a recognized process.platform guard, never opt out); run npm run lint before push; treat the CI windows-latest lane as the only true Windows signal — gsd-test (Mac/Linux only) cannot substitute; ref umbrella DEFECT.WINDOWS-TEST-PORTABILITY and production-side analogue DEFECT.WINDOWS-PATH-LEAK-IN-MARKDOWN-CONTENT`
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.symptom=scripts/prompt-injection-scan.sh flags a NEW test file as a finding because the test contains real injection payloads as fixtures (strings that match one of the scanner's PATTERNS — see scripts/prompt-injection-scan.sh lines 18-64) to prove the validator under test rejects them; scanner cannot distinguish fixture from real injection; CI security lane fails on the test that ADDS the security validation`
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.examples=PR #1622 commit 4ed208e74 added convertClaudeCommandToWindsurfWorkflow commandName validation with 22 malicious-name fixtures; scanner matched an instruction-override phrase at tests/windsurf-conversion.test.cjs:122; CI security lane failed even though the test is the security control`
@@ -788,7 +850,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
`SESSION.2026-05-09=[8-PR triage wave, 7 merged + 1 subsumed; META.RULE.* introduced; WAVE.LESSON.* captured; k320/k322/k323/k326/k331 evidence; AI Ops Memory predicate format established]`
`SESSION.2026-05-10=[ai-ops memory consolidation; release-notes standard taxonomy + templates; RELEASE-NOTES.* predicates introduced]`
`SESSION.2026-05-13=[Shell Command Projection Module expansion (#3465-#3468); ADR-0009 superseded; new exports for subprocess dispatch and platform file I/O; phase-gated migration plan; PR #3464 three-gate invariant CI+CR+unresolved=0; PR #3470 stash-include-untracked rebase pattern]`
`SESSION.2026-05-14=[#3095/PR #3490 EXEC.CLASSIFY.* introduced (Anthropic/Copilot/Codex/Gemini cross-runtime rate-limit sentinel coverage); #3489/PR #3499 DEFECT.STATE-TRAMPLE.idempotency-oracle (STATE.md current_phase field is oracle for state.complete-phase); #3488/PR #3501 DAG resolver same-phase short-form depends_on (shortFormToId index added to sdk/src/query/phase.ts); #3491/PR #3502 DEFECT.NESTED-GIT-INIT (gitWorktreeInfoInternal helper); #3493/PR #3500 extractCurrentMilestone generic Phase Details continuation past planned-milestone siblings; #3503/PR #3504 DEFECT.PATH-SUBSTRING-CHECK (trailing-slash anchor for homedir checks); #3346/PR #3505 codex AoT TOML leaf-key via extractFlatHookEventName; #3506/PR #3507 label-scoped stale-bot sub-job pattern; multi-PR triage operational lessons folded into PROC.TRIAGE.*; #3508 DEFECT.AGENT-ISOLATION-SILENT-FAIL; gsd-test image-missing auto-build (locally-built image via embedded heredoc Dockerfile); refined PRED.k322 threshold to 3 PRs/<10min]`
`SESSION.2026-05-14=[#3095/PR #3490 EXEC.CLASSIFY.* introduced (Anthropic/Copilot/Codex/Gemini [runtime removed #1928] cross-runtime rate-limit sentinel coverage); #3489/PR #3499 DEFECT.STATE-TRAMPLE.idempotency-oracle (STATE.md current_phase field is oracle for state.complete-phase); #3488/PR #3501 DAG resolver same-phase short-form depends_on (shortFormToId index added to sdk/src/query/phase.ts); #3491/PR #3502 DEFECT.NESTED-GIT-INIT (gitWorktreeInfoInternal helper); #3493/PR #3500 extractCurrentMilestone generic Phase Details continuation past planned-milestone siblings; #3503/PR #3504 DEFECT.PATH-SUBSTRING-CHECK (trailing-slash anchor for homedir checks); #3346/PR #3505 codex AoT TOML leaf-key via extractFlatHookEventName; #3506/PR #3507 label-scoped stale-bot sub-job pattern; multi-PR triage operational lessons folded into PROC.TRIAGE.*; #3508 DEFECT.AGENT-ISOLATION-SILENT-FAIL; gsd-test image-missing auto-build (locally-built image via embedded heredoc Dockerfile); refined PRED.k322 threshold to 3 PRs/<10min]`
`SESSION.2026-05-15=[#3537/PR #3538 DEFECT.PHASE-REGEX-FANOUT — phaseMarkdownRegexSource promoted to core.cjs and wired to 7 sites; parity-style regression test established as DEFECT.GENERATIVE-FIX exemplar; trek-e/gsd-test-runner#1 filed for DEFECT.GSD-TEST-MIRROR-POISONED — chown-back-before-exec legacy gap (poisoned holodeck mirror unstuck via authorized docker chown to remote 1000:1000); RULESET.PR-FLOW.* codified from project CLAUDE.md load-bearing rule; first dispatch under run-tests-before-create held cleanly (PR #3520 worker stopped on Docker exit 12 infra failure, orchestrator opened PR after unblock); CONTEXT.md refactored from 882 lines of mixed prose+predicates into ~500 lines of pure-predicate format with chronological session log]`
`SESSION.2026-05-15.parallel-fix-dispatch=[#3542/PR #3546 prohibit git stash family in executor agents (shared refs/stash across worktrees); #3541/PR #3547 non-TTY resolution for installer prompt-user actions (default remove for SDK build artifacts, keep for skills/gsd-*/SKILL.md); #3545 filed for gsd-test-summary concurrent /tmp output collision; new predicates DEFECT.HOOK-OVER-ENFORCEMENT.read-tool-tracking, DEFECT.GSD-TEST-CONCURRENT-OUTPUT-COLLISION, DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL, DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT, PROC.PARALLEL-FIX-DISPATCH; agent-trust-but-verify caught /gsd-update retired-syntax comment slip in #3541 implementation before PR open]`
`SESSION.2026-05-16=[multi-PR triage wave (#3577/3581/3640/3641/3642/3648/3649/3637/3639). Established global PreToolUse hook ~/.claude/hooks/test-memory-guard.sh denying new node/test spawns when sum(RSS of node|vitest|jest|...) >= 4 GiB on the 24 GB Mac OR when a same-runner process is already in argv[0] — hard deny via hookSpecificOutput.permissionDecision=deny. PR #3577 fix: revert config-ensure-section dispatch to CJS cmdConfigEnsureSection (SDK author wrote single-section semantics under a name whose legacy callers expect full-default config init); plus 3 SDK parity carve-outs (configNewProject defaults align with sdk/shared/config-defaults.manifest.json, return relative .planning/config.json path, drop quotes from Unknown config key, lead malformed-JSON error with "Failed to read config.json:"). PR #3649 fix: chunk node --test spawn at 28K argv ceiling (Windows CreateProcess lpCommandLine cap 32,767 was instantly aborting unchunked spawn of 546 paths). Chunking fix surfaced 14 pre-existing Windows-only test bugs (4010 pass / 14 fail; vs 0/0 before — entire suite was un-runnable on Windows). PRs #3639 + #3637 confirmed unable to stand alone (legitimately depend on Phase 6 scaffolding only present on feat/3575-enforcement-hardening) — user decision: cherry-pick into #3577 and close. Five other PRs each had ≤1 unresolved CR thread of the changeset-pr-number / null-vs-throw / implicit-Claude-runtime / docs-stale-guidance / hardcoded-tests-path family — all quick wins. New predicates: DEFECT.SDK-PORT-NAME-COLLISION, DEFECT.WINDOWS-ARGV-OVERFLOW, DEFECT.STACKED-PR-CANNOT-STAND-ALONE, DEFECT.CANARY-VERSION-LEAK, DEFECT.GSD-TEST-HOST-MID-RUN-DEATH, RULESET.HARNESS.test-memory-guard, RULESET.PR-FLOW.docker-before-push, RULESET.PR-FLOW.templates-mandatory]`
@@ -804,12 +866,13 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
`DEFECT.WINDOWS-ARGV-OVERFLOW.detect=Windows CI job at "Run unit tests" exits with code 1 within seconds of starting, no node:test output between "run-tests: suite=… files=N: …" line and "Process completed with exit code 1"; same job on Linux/macOS runs full duration`
`DEFECT.WINDOWS-ARGV-OVERFLOW.fix-forward=chunk argv into batches whose total length stays under 28,000 chars (headroom under the 32,767 ceiling); run each chunk sequentially; aggregate exit codes (first non-zero wins). Expose RUN_TESTS_MAX_CMDLINE_CHARS env override so cross-platform regression tests can force chunking with short tmp paths`
`DEFECT.WINDOWS-ARGV-OVERFLOW.test-anchor=tests/run-tests-harness.test.cjs "Windows argv-overflow chunking (issue #3597)" — 30 long-named fixture files + RUN_TESTS_MAX_CMDLINE_CHARS=2000 → asserts run-tests: chunk N/M marker in stderr; pattern works on every platform`
`DEFECT.WINDOWS-ARGV-OVERFLOW.prevention=a RUNTIME argv-length property (args-array size not statically knowable) — NOT AST-lint-enforceable; addressed at the source by the production run-tests.cjs chunking under RUN_TESTS_MAX_CMDLINE_CHARS plus its test-anchor (tests/run-tests-harness.test.cjs). ADR-1703 Phase 3 (#1720) evaluated and dropped a no-oversized-test-argv lint rule as unsound (it could not detect the canonical execFileSync(node,[...paths]) array overflow)`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.symptom=a test deletes/rewrites a SHARED REAL build artifact or fixture (e.g. gsd-core/bin/lib/*.cjs, the build tsbuildinfo) that other test files require; node --test runs files concurrently, so innocent concurrent tests intermittently fail with "Cannot find module" / ENOENT while the racy test itself passes (victim-not-culprit, leg-asymmetric red); placing mutable build state inside a copied/shipped tree (gsd-core/bin/) additionally races install-test fs.cpSync copies → copyfile ENOENT`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.examples=#996/88e30d53 — bug-969 hardening tests fs.unlinkSync'd + restored the real gsd-core/bin/lib/core.cjs and set tsBuildInfoFile inside gsd-core/bin/ → next red across the full-test matrix (macOS/Windows) + ubuntu-24 coverage leg, ~40-50 MODULE_NOT_FOUND/ENOENT per leg; reproduced locally on iteration 1; fixed #1001/#1002`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.detect=grep tests for fs.unlinkSync|rmSync|writeFileSync|renameSync|cpSync targeting paths resolved from the repo root (join(__dirname,'..',...)) under gsd-core/bin/lib or a shared committed fixture, instead of a mkdtempSync temp dir; any build helper (e.g. ensureBuiltArtifacts) invoked with real-tree paths during the concurrent test phase; any tsBuildInfoFile / build-cache path that lands inside a copied/shipped dir (gsd-core/bin/)`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.fix-forward=tests mutate ONLY isolated mkdtempSync copies — never delete/rewrite shared real build outputs while node --test runs files concurrently; parameterize build helpers to accept {root,srcDir,outDir,tsBuildInfoPath,tsconfigPath} overrides and point the test at a throwaway temp project (precedent: #1002 ensureBuiltArtifacts(overrides)); keep mutable build state (tsbuildinfo) OUTSIDE copied/shipped trees (repo root, gitignored) + best-effort self-heal of stale bin-local copies; this is the concrete instance of the RULESET.TESTS.delete-bad-tests real-race class`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.test-anchor=tests/bug-969-test-infra-flake-hardening.test.cjs (hermetic temp-project rewrite); regression gate = 10x concurrent run of that suite + tests/state.test.cjs + tests/install.test.cjs must be clean (reproduces on iter 1 when racy)`
`DEFECT.SHARED-ARTIFACT-MUTATION-IN-CONCURRENT-TEST.test-anchor=tests/run-tests-harness.test.cjs (hermetic temp-project rewrite); regression gate = 10x concurrent run of that suite + tests/state.test.cjs + tests/install.test.cjs must be clean (reproduces on iter 1 when racy)`
`DEFECT.STACKED-PR-CANNOT-STAND-ALONE.symptom=patch PR was authored against scaffolding (handler files, lint scripts, generated modules) that exists only on an unmerged upstream feature branch; the PR's "base" on GitHub is the feature branch, not main; merging requires the upstream PR to land first`
`DEFECT.STACKED-PR-CANNOT-STAND-ALONE.examples=#3639 + #3637 both targeted base=feat/3575-enforcement-hardening (the Phase 6 PR #3577); #3639 modifies SDK-bridge calls in 6 family-router files that on main do NOT have any SDK-bridge call yet; #3637 patches scripts/lint-shared-module-handsync.cjs which does not exist on main at all`
@@ -829,7 +892,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
`RULESET.HARNESS.test-memory-guard=~/.claude/hooks/test-memory-guard.sh fires on every Bash PreToolUse; if argv[0]∈{node|vitest|jest|mocha|tsx|ts-node|tap|ava|playwright|cypress} OR matches (npm|pnpm|yarn|bun) (run )?(t|test|tests|vitest|jest); blocks via hookSpecificOutput.permissionDecision=deny when sum(RSS of running matching procs, excluding tsserver|*-mcp|claude|Electron|...) ≥ 4 GiB OR when argv[0] basename matches a running process's argv[0]. Exception: node --version|-v|--help|-h|-p|-e are trivial probes and skip the check. Designed for a 24 GB Mac where prior accidental fan-out exhausted RAM`
`RULESET.PR-FLOW.docker-before-push=before ANY git push of any fix to any PR, run gsd-test-summary (docker on the remote, mirrors ubuntu CI) and confirm exit 0. macOS-local node --test is NOT a substitute — many failures are platform-specific (path separators, case sensitivity, locale, fs semantics). Watchdog with Monitor on the output log; never set a sleep/timer and walk away. Source: user feedback 2026-05-16 — "we don't set a timer we actively watch and record results in real time as possible"`
`RULESET.PR-FLOW.docker-before-push=before ANY git push of any fix to any PR, run gsd-test (docker on the remote, mirrors ubuntu CI) and confirm exit 0. macOS-local node --test is NOT a substitute — many failures are platform-specific (path separators, case sensitivity, locale, fs semantics). Watchdog with Monitor on the output log; never set a sleep/timer and walk away. Source: user feedback 2026-05-16 — "we don't set a timer we actively watch and record results in real time as possible"`
`RULESET.PR-FLOW.templates-mandatory=every gh pr create|edit|gh issue create|edit MUST first invoke the gh-templates-first skill and Read (Read tool, not Bash cat — k321 read-tracking) the matching template in .github/. Apply ALL required sections; never write freeform bodies. Repo enforces this via gsd-pr-template-policy GitHub Action which flags any non-templated body — the bot allows the PR to stay open only because authors are contributors-or-higher, but the warning is a real complaint that must be cured. Source: user feedback 2026-05-16 (multi-message escalation) — "the whole reason i have that github action is because you fucking blow through and ignore using the templates"`
@@ -841,7 +904,7 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
`EXEC.CLASSIFY.workflow=gsd-core/workflows/execute-phase.md step 7; class-distinct prompts (quota-to-wait-for-reset; classify-handoff-bug-to-spot-check; unknown-to-continue/stop)`
`EXEC.CLASSIFY.classes={class:'quota-exceeded'|'classify-handoff-bug'|'unknown-failure', sentinel?, retryAfterSeconds?}`
`EXEC.CLASSIFY.sentinel-order=most specific first: 429 beats too-many-requests; quota beats resource_exhausted; case-insensitive; canonical sentinel value is lower-cased form`
`EXEC.CLASSIFY.cross-runtime=Anthropic/CC: usage limit|rate limit|quota|429|retry-after; Copilot CLI: rate_limit (stem); Codex CLI: 429|usage_limit_reached|too many requests; Gemini CLI: RESOURCE_EXHAUSTED|exceeded your`
`EXEC.CLASSIFY.cross-runtime=Anthropic/CC: usage limit|rate limit|quota|429|retry-after; Copilot CLI: rate_limit (stem); Codex CLI: 429|usage_limit_reached|too many requests`
`EXEC.CLASSIFY.precedence=quota sentinel wins over classifyHandoffIfNeeded bug when both appear`
`EXEC.CLASSIFY.retry-after-parser=\bretry[-_ ]after[:\s]+(\d+)\b avoids embedded-word false matches like noretry-after`
`EXEC.CLASSIFY.proactive-signal-not-usable=Anthropic exposes anthropic-ratelimit-* headers + Agent SDK RateLimitEvent; Claude Code subprocess does NOT forward to hooks/statusline today (upstream #33820, #22407, #32796)`
@@ -863,11 +926,11 @@ Migration plan: Phase 1 (#3465) seam additions complete; Phase 2 (#3466) targets
`DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.fix-forward=keep gsd-test-summary --both at the top-level orchestrator; sub-agents either run it foreground with timeout: 1500000 (25min) and block, OR delegate the test step back to the orchestrator (write commits + return); never have a sub-agent fire-and-await a backgrounded long task`
`DEFECT.SUBAGENT-LONG-RUNNING-BG-STALL.anchor=project CLAUDE.md "Top-level orchestrator (cross-turn notifications available) vs Sub-agent worker (no cross-turn notifications)" guidance — load-bearing for multi-worktree parallel fix dispatch`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.symptom=sub-agent writes /gsd-<cmd> (legacy hyphen syntax) in code comments or doc strings while implementing a fix; lands as part of the implementation diff`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.examples=#3541 implementation included a typical /gsd-update path comment in installer-migration-report.cjs; caught by tests/bug-2543-gsd-slash-namespace.test.cjs (#3443 invariant)`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.detect=tests/bug-2543-gsd-slash-namespace.test.cjs prints "Found N retired /gsd-<cmd> reference(s) — use /gsd:<cmd> instead" with line-number-precise violations`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.examples=#3541 implementation included a typical /gsd-update path comment in installer-migration-report.cjs; caught by tests/slash-command-namespace.test.cjs (#3443 invariant)`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.detect=tests/slash-command-namespace.test.cjs prints "Found N retired /gsd-<cmd> reference(s) — use /gsd:<cmd> instead" with line-number-precise violations`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.fix-forward=replace /gsd-<cmd> with /gsd:<cmd> at the cited file:line; healthy emergent property — project-wide invariant test catches drift agents would never self-correct`
`DEFECT.AGENT-RETIRED-SLASH-SYNTAX-DRIFT.lesson=agent-trust-but-verify is load-bearing — sub-agent reporting "done" is not a substitute for running the full suite; the invariant test surfaces drift even in doc-only changes`
`PROC.PARALLEL-FIX-DISPATCH.pattern=bot triage brief → worktree per branch → parallel sub-agents do rubber-duck/RCA/TDD implementation only → top-level orchestrator owns commit + gsd-test-summary --both + push + PR + changeset-pr-backfill`
`PROC.PARALLEL-FIX-DISPATCH.pattern=bot triage brief → worktree per branch → parallel sub-agents do rubber-duck/RCA/TDD implementation only → top-level orchestrator owns commit + gsd-test + push + PR + changeset-pr-backfill`
`PROC.PARALLEL-FIX-DISPATCH.rationale=long-running test runs need cross-turn notifications (orchestrator-only); CONTRIBUTING.md gh-templates-first hook requires session-scoped Read calls sub-agents wouldn't otherwise make; sequencing test runs avoids GSD-TEST-CONCURRENT-OUTPUT-COLLISION`
`PROC.PARALLEL-FIX-DISPATCH.observed=#3541 + #3542 dispatched simultaneously this session; PRs #3546 #3547 opened green; one syntax slip caught by AGENT-RETIRED-SLASH-SYNTAX-DRIFT and fixed before second PR opened`
@@ -891,7 +954,7 @@ Full detail in `~/.claude/skills/gsd-pr-fix-discipline/SKILL.md`. AI agents MUST
### Slash command two-tier confusion
- **Symptom:** `tests/bug-2543-gsd-slash-namespace.test.cjs` or `tests/bug-3584-runtime-slash-emitters.test.cjs` fails
- **Symptom:** `tests/slash-command-namespace.test.cjs` (folds former `bug-2543-gsd-slash-namespace`, consolidation epic #1969) or `tests/init-manager.test.cjs` (folds former `bug-3584-runtime-slash-emitters`, consolidation epic #1969) fails
- **Affected this session:** #154 (three passes), #164 (added the authoritative matrix)
- **Fix:** Consult `## Slash-command form` section of this file before touching any `/gsd-` or `/gsd:` token — colon for `agents/`/`commands/`, hyphen for runtime emitters

View File

@@ -817,6 +817,7 @@ The following checks run on every PR in addition to the test suite:
| Job | What it checks | How to pass |
|-----|----------------|-------------|
| `Lint — ESLint` | No source-grep tests (see above), via the `local/no-source-grep` rule | Replace with `runGsdTools()` behavioral tests, or add `// allow-test-rule: <reason>` |
| `Lint — cross-platform portability` | Windows-portability defects in tests, via `local/no-path-literal-in-assert` (more rules land per [ADR-1703](docs/adr/1703-portability-enforcement-architecture.md)) — e.g. a path-returning call asserted against a hardcoded `/`-literal | Normalize the actual: `String(pathFn(...)).replace(/\\/g, '/')`, or structure platform-specific code behind a `process.platform !== 'win32'` guard. **No `eslint-disable`** — see [cross-platform-portability-rules.md](docs/contributing/cross-platform-portability-rules.md) |
Run locally before pushing: `npm run lint` (or `npx eslint .`)

View File

@@ -1,9 +1,13 @@
# GSD Core — Gemini CLI context
# GSD Core — Antigravity CLI context
This context is loaded by the **gsd-core Gemini CLI extension**. It gives Gemini
the operating context for [GSD Core](https://github.com/open-gsd/gsd-core), a
meta-prompting, context-engineering, and spec-driven development system for AI
coding agents.
> **Gemini CLI was sunset by Google on 2026-06-18** and is no longer served for
> free/Pro/Ultra tiers. Antigravity CLI is its official successor, and this file
> is the context Antigravity reads automatically (its `contextFileName` is
> `GEMINI.md`, inherited from the shared Gemini 3 backend).
This context gives Antigravity the operating context for
[GSD Core](https://github.com/open-gsd/gsd-core), a meta-prompting,
context-engineering, and spec-driven development system for AI coding agents.
## What GSD is
@@ -16,30 +20,28 @@ files rather than in the conversation.
## The slash commands (installed separately)
> **This extension ships only the context above — not the slash commands.** It
> loads gsd's operating context into your Gemini sessions and is managed through
> `gemini extensions list / update / uninstall`. To install the `/gsd:*` command
> set, agents, and hooks into `~/.gemini/`, run the dedicated installer:
> **This file ships only the context above — not the slash commands.** To
> install the `/gsd-*` command set, agents, and hooks into `~/.gemini/antigravity/`,
> run the dedicated installer:
>
> ```bash
> npx gsd-core --gemini --global
> npx gsd-core --antigravity --global
> ```
>
> The two paths are complementary and the manual installer remains fully
> supported. The commands below are available only once that installer has run.
> The commands below are available only once that installer has run.
If you have installed the gsd commands, the workflow is driven by these `/gsd:*`
slash commands (Gemini registers gsd's commands under the `gsd` namespace, so the
colon form is canonical):
If you have installed the gsd commands, the workflow is driven by these `/gsd-*`
slash commands (Antigravity registers gsd's commands under a hyphenated
namespace):
- `/gsd:new-project` — initialise a project and gather deep context.
- `/gsd:progress` — the unified situational command: check progress, advance the
- `/gsd-new-project` — initialise a project and gather deep context.
- `/gsd-progress` — the unified situational command: check progress, advance the
workflow, or dispatch a freeform intent.
- `/gsd:plan-phase <N>` — produce a detailed phase plan with a verification loop.
- `/gsd:execute-phase <N>` — execute a phase's plans with wave-based parallelism.
- `/gsd:verify-work` — validate built features through conversational UAT.
- `/gsd:ship` — open a PR, run review, and prepare for merge.
- `/gsd:help` — list every available command.
- `/gsd-plan-phase <N>` — produce a detailed phase plan with a verification loop.
- `/gsd-execute-phase <N>` — execute a phase's plans with wave-based parallelism.
- `/gsd-verify-work` — validate built features through conversational UAT.
- `/gsd-ship` — open a PR, run review, and prepare for merge.
- `/gsd-help` — list every available command.
## Working with GSD
@@ -47,7 +49,7 @@ colon form is canonical):
acting, and keep it current as work progresses.
- Prefer the smallest change that satisfies the phase's verification criteria.
- Run the project's tests and linters before declaring a phase done.
- When unsure what to do next, and the gsd commands are installed, `/gsd:progress`
- When unsure what to do next, and the gsd commands are installed, `/gsd-progress`
is the situational entry point.
Learn more: <https://github.com/open-gsd/gsd-core>

View File

@@ -1,73 +0,0 @@
# Quick Wins: Confirmed-Bug Fixes
**Status**: Active
**Started**: 2026-05-16
**Owner**: Current session (Grok + user)
**Context**: Follow-up to `/gsd-inbox` triage on 2026-05-16
## Goal
Land 6 high-signal, confirmed-bug issues that currently have **zero open pull requests**. These are the cleanest quick-win opportunities available in the public GitHub inbox right now.
All six issues carry the `confirmed-bug` label, meaning the bug has been verified and a fix is explicitly welcome.
## The 6 Issues (Prioritized)
| # | Issue | Short Title | Type | Recommended Flow | Est. Effort | Status | Notes |
|---|-------|-------------|------|------------------|-------------|--------|-------|
| 1 | [#3583](https://github.com/open-gsd/gsd-core/issues/3583) | Claude skill install leaves `/gsd:<cmd>` in `SKILL.md` body | Installer / Command namespace | PR 3629 (our branch) + competing 3586 | Small (1 file + test) | PR opened / Review | **Leading PR: 3629** (cristianuibar) — reviewed + hardened with CodeRabbit feedback (left-boundary regex + body-scoped guard). Competing PR 3586 has "needs changes" + "ci: failing". Issue still carries `confirmed-bug`. |
| 2 | [#3579](https://github.com/open-gsd/gsd-core/issues/3579) | `build-hooks.js` + npm publish omit graphify auto-update hook | Packaging / Build | `/gsd-quick` | Small | Not started | Classic "new feature missed in release artifact". Easy local verification. |
| 3 | [#3496](https://github.com/open-gsd/gsd-core/issues/3496) | `/gsd:update` changelog extraction skips intermediate versions | Workflow / Update logic | `/gsd-quick` or lightweight plan | Medium-small | Not started | Needs deterministic version-range helper. |
| 4 | [#3588](https://github.com/open-gsd/gsd-core/issues/3588) | Production `npm audit` has 1 high + 5 moderate advisories | Security / Dependencies | Direct + careful review | Medium | Not started | Transitive via `@anthropic-ai/claude-agent-sdk`. May need overrides. |
| 5 | [#3584](https://github.com/open-gsd/gsd-core/issues/3584) | Runtime `bin/lib/*.cjs` still emit `/gsd:<cmd>` (larger piece deferred from #3583) | Runtime output / Slash formatter | Short plan first, then execute | Medium-Large | Not started | 16+ files. Design a centralized runtime-aware formatter. Do after #3583. |
| 6 | [#3340](https://github.com/open-gsd/gsd-core/issues/3340) | SDK publish lag — agent dir fix never shipped in `@opengsd/gsd-sdk@0.1.0` | Release / SDK publishing | Plan + coordination | Medium (release-focused) | Not started | Oldest. Mostly a publishing/versioning task. |
## Execution Rules for This Batch
- **Branch naming**: `fix/NNNN-short-description` (enforced by CI)
- **PR template**: Must use `.github/PULL_REQUEST_TEMPLATE/fix.md`
- **Linking**: `Fixes #NNNN` (or `Closes`) in the PR body
- **Changeset**: Required for all user-facing or security fixes
- **Testing**: All existing tests must pass + new coverage where the issue describes a gap
- **Clean context windows**: Each fix should preferably be driven from a fresh session using the prepared prompts (see session notes or ask for them)
- **GSD self-use**: For the small ones (#3583, #3579, #3496), using `/gsd-quick` (or `/gsd-fast`) inside the fix session is encouraged and appropriate. For #3584, a short planning step is recommended.
## Status Legend
- **Not started** — Issue claimed for this batch, no work begun
- **In progress** — Active work in a clean window
- **PR opened** — Pull request created and linked
- **Review** — Awaiting review / CI / merge fixes
- **Merged** — Landed on main
- **Blocked** — Needs input from maintainers or upstream
## Current Status
- [x] #3583 — **PR opened** (3629 leading after CodeRabbit review + hardening push; competing 3586 needs changes + CI failing)
- [ ] #3579 — Not started (cleanest next target — 0 PRs)
- [ ] #3496 — PR 3497 open (changes requested)
- [ ] #3588 — Not started
- [ ] #3584 — Not started (larger; deferred runtime cjs colon emissions)
- [ ] #3340 — Not started
**Progress**: 0 / 6 merged (1 in active review)
## Process Notes
- These issues were identified during a `/gsd-inbox` run on 2026-05-16.
- At the time of creation of this file, zero of the six had open PRs.
- 2026-05-16 Grok session: Reviewed PR 3629 (our #3583 fix) for CodeRabbit comments. 1 critical was false-positive (scripts/ *is* published per package.json "files" + npm pack). Applied the 2 valid suggestions (bidirectional word-boundary lookbehind in `buildColonPattern` + body-only scope for the colon-ref regression guard in the test). Tests pass. Pushed hardening commit to the fork branch. Competing PR 3586 exists but is behind on CI/review status.
- Work is intended to be done in **parallel clean context windows** (one issue per fresh Claude/Codex/Gemini session) using dedicated prompts.
- After each fix is complete in its window, the resulting branch + PR description should be brought back here for final review and opening.
- This file serves as the single source of truth for the current batch while execution is in progress. It can be deleted or moved to `docs/archive/` once all six PRs are merged.
## Related Artifacts
- Inbox triage report: `/tmp/GSD-INBOX-TRIAGE-2026-05-16.md` (from the `/gsd-inbox` run)
- Full issue list with `confirmed-bug` label: `gh issue list --state open --label confirmed-bug`
---
**Next action**: #3583 now has active PR(s) under review. Next clean quick win (0 PRs, small packaging effort, high value for recently-landed graphify feature): **#3579**. Validated via GitHub search: no PRs mention 3579. Ready for `/gsd-quick` or direct fix (update `scripts/build-hooks.js` HOOKS_TO_COPY + ensure `hooks/lib/` copy in installer + fix any publish filter).
This document will be updated as status changes.

View File

@@ -47,13 +47,14 @@ npx @opengsd/gsd-core@latest
別のランタイムをお使いの場合や Node.js がない場合は [ランタイムへのインストール](docs/ja-JP/how-to/install-on-your-runtime.md) を参照してください。
インストール後、最初のプロジェクトを開始します。
インストール後、新規プロジェクトを開始するか、既存リポジトリをオンボーディングします。
```bash
/gsd-new-project
/gsd-new-project # グリーンフィールドプロジェクト
/gsd-onboard # 既存コードベース
```
初めての方は [はじめてのプロジェクト](docs/ja-JP/tutorials/your-first-project.md) で、インストールから最初のフェーズ出荷までのガイド付きチュートリアルをご覧ください。
初めての方は [はじめてのプロジェクト](docs/ja-JP/tutorials/your-first-project.md) で、インストールから最初のフェーズ出荷までのガイド付きチュートリアルをご覧ください。既存リポジトリの場合は [既存コードベースのオンボーディング](docs/ja-JP/tutorials/onboarding-an-existing-codebase.md) を参照してください。
---

View File

@@ -47,13 +47,14 @@ npx @opengsd/gsd-core@latest
다른 런타임이나 Node.js가 없는 환경은 [런타임에 설치하기](docs/ko-KR/how-to/install-on-your-runtime.md)를 참조하세요.
설치 후 첫 번째 프로젝트를 시작합니다:
설치 후 새 프로젝트를 시작하거나 기존 저장소를 온보딩합니다:
```bash
/gsd-new-project
/gsd-new-project # 그린필드 프로젝트
/gsd-onboard # 기존 코드베이스
```
처음 사용하시나요? [첫 번째 프로젝트](docs/ko-KR/tutorials/your-first-project.md)를 따라 설치부터 첫 단계 출시까지 안내받으세요.
처음 사용하시나요? [첫 번째 프로젝트](docs/ko-KR/tutorials/your-first-project.md)를 따라 설치부터 첫 단계 출시까지 안내받으세요. 기존 저장소라면 [기존 코드베이스 온보딩](docs/ko-KR/tutorials/onboarding-an-existing-codebase.md)을 참고하세요.
---

View File

@@ -6,7 +6,7 @@
**English** · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md) · [한국어](README.ko-KR.md)
**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.**
**A light-weight meta-prompting, context engineering, and spec-driven development system for Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more.**
[![npm version](https://img.shields.io/npm/v/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core)
[![npm downloads](https://img.shields.io/npm/dm/%40opengsd%2Fgsd-core?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@opengsd/gsd-core)
@@ -21,7 +21,7 @@
## What is GSD Core
GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Gemini CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean.
GSD Core is a context-engineering and spec-driven development framework that drives AI coding agents (Claude Code, Codex, Antigravity CLI, Kimi CLI, Copilot, Cursor, and more) through a disciplined phase loop. It solves [context rot](docs/explanation/context-engineering.md) — the quality degradation that accumulates as an AI fills its context window — by running all heavy research, planning, and execution work in fresh-context subagents while keeping your main session lean.
---
@@ -43,17 +43,18 @@ Each milestone repeats the same five-step loop, one phase at a time:
npx @opengsd/gsd-core@latest
```
The installer prompts for your runtime (Claude Code, OpenCode, Gemini CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly.
The installer prompts for your runtime (Claude Code, OpenCode, Antigravity CLI, Kimi CLI, Kilo, Codex, Copilot, Cursor, Windsurf, and more) and whether to install globally or locally. The installer is required for cross-runtime compatibility — do not copy files from `agents/` or `commands/` directly.
On another runtime or without Node.js? See [Install on your runtime](docs/how-to/install-on-your-runtime.md).
Once installed, start your first project:
Once installed, start a new project or onboard an existing repo:
```bash
/gsd-new-project
/gsd-new-project # greenfield project
/gsd-onboard # existing codebase
```
New here? Follow [Your first project](docs/tutorials/your-first-project.md) for a guided walkthrough from install to first shipped phase.
New here? Follow [Your first project](docs/tutorials/your-first-project.md) for a guided walkthrough from install to first shipped phase, or [Onboarding an existing codebase](docs/tutorials/onboarding-an-existing-codebase.md) for brownfield setup.
---

View File

@@ -47,13 +47,14 @@ O instalador solicita seu ambiente de execução (Claude Code, OpenCode, Gemini
Em outro runtime ou sem Node.js? Consulte [Instalar no seu runtime](docs/pt-BR/how-to/install-on-your-runtime.md).
Após a instalação, inicie seu primeiro projeto:
Após a instalação, inicie um projeto novo ou integre um repositório existente:
```bash
/gsd-new-project
/gsd-new-project # projeto greenfield
/gsd-onboard # base de código existente
```
É a primeira vez? Siga [Seu primeiro projeto](docs/pt-BR/tutorials/your-first-project.md) para um passo a passo guiado, desde a instalação até a primeira fase entregue.
É a primeira vez? Siga [Seu primeiro projeto](docs/pt-BR/tutorials/your-first-project.md) para um passo a passo guiado, desde a instalação até a primeira fase entregue. Para um repositório existente, consulte [Integrar uma base de código existente](docs/pt-BR/tutorials/onboarding-an-existing-codebase.md).
---

View File

@@ -47,13 +47,14 @@ npx @opengsd/gsd-core@latest
使用其他运行时或没有 Node.js?请参阅[在你的运行时上安装](docs/zh-CN/how-to/install-on-your-runtime.md)。
安装完成后,启动你的第一个项目:
安装完成后,启动一个新项目或接入现有仓库:
```bash
/gsd-new-project
/gsd-new-project # 新建项目
/gsd-onboard # 现有代码库
```
初次使用?请按照[你的第一个项目](docs/zh-CN/tutorials/your-first-project.md)进行引导式操作,从安装到完成第一个交付阶段。
初次使用?请按照[你的第一个项目](docs/zh-CN/tutorials/your-first-project.md)进行引导式操作,从安装到完成第一个交付阶段。对于现有仓库,请参阅[接入现有代码库](docs/zh-CN/tutorials/onboarding-an-existing-codebase.md)。
---

View File

@@ -131,13 +131,17 @@ match `package.json`:
- `.claude-plugin/plugin.json` — Claude Code plugin manifest (issue #766)
- `gemini-extension.json` — Gemini CLI extension manifest (issue #775)
- `.claude-plugin/marketplace.json` — Claude plugin marketplace manifest; its
version lives at `plugins[0].version` and is stamped via a nested versionKey
descriptor (issue #1855)
The `version` npm lifecycle script (`scripts/sync-manifest-versions.cjs --stage`)
stamps these files automatically on every `npm version` call, and stages them so
they are included in the release commit alongside `package.json`.
To add a new manifest that must track the package version, register its path in
the `VERSIONED_MANIFESTS` array in `scripts/sync-manifest-versions.cjs`. A
To add a new manifest that must track the package version, register its path
(and, if its version field is not top-level, its dotted `versionKey`) in the
`VERSIONED_MANIFESTS` array in `scripts/sync-manifest-versions.cjs`. A
regression test (`tests/issue-844-manifest-version-sync.test.cjs`) enforces this:
it scans all committed JSON files for a matching `version` field and fails if any
are missing from the registry.

View File

@@ -1,7 +1,7 @@
---
name: gsd-advisor-researcher
description: Researches a single gray area decision and returns a structured comparison table with rationale. Spawned by discuss-phase advisor mode.
tools: Read, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*
tools: Read, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*
color: cyan
---
@@ -19,6 +19,8 @@ Spawned by `discuss-phase` via `Task()`. You do NOT present output directly to t
@~/.claude/gsd-core/references/untrusted-input-boundary.md
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
<documentation_lookup>
@~/.claude/gsd-core/references/research-documentation-lookup.md
</documentation_lookup>

View File

@@ -1,7 +1,7 @@
---
name: gsd-ai-researcher
description: Researches a chosen AI framework's official docs to produce implementation-ready guidance — best practices, syntax, core patterns, and pitfalls distilled for the specific use case. Writes the Framework Quick Reference and Implementation Guidance sections of AI-SPEC.md. Spawned by /gsd:ai-integration-phase orchestrator.
tools: Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, mcp__context7__*
tools: Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, mcp__context7__*, mcp__plugin_context7_context7__*
color: green
# hooks:
# PostToolUse:
@@ -72,7 +72,7 @@ Update AI-SPEC.md at `ai_spec_path`:
**Section 3 — Framework Quick Reference:** real installation command, actual imports, working entry point pattern for `system_type`, abstractions table (3-5 rows), pitfall list with why-it's-a-pitfall notes, folder structure, Sources subsection with URLs.
**Section 4 — Implementation Guidance:** specific model (e.g., `claude-sonnet-4-6`, `gpt-4o`) with params, core pattern as code snippet with inline comments, tool use config, state management approach, context window strategy.
**Section 4 — Implementation Guidance:** specific model (e.g., `claude-sonnet-5`, `gpt-4o`) with params, core pattern as code snippet with inline comments, tool use config, state management approach, context window strategy.
</step>
<step name="write_section_4b">

View File

@@ -20,6 +20,8 @@ Spawned by `discuss-phase-assumptions` via `Task()`. You do NOT present output d
@~/.claude/gsd-core/references/untrusted-input-boundary.md
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
<input>
Agent receives via prompt:

View File

@@ -24,6 +24,8 @@ Before fixing code, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions during fixes.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation
@@ -456,7 +458,7 @@ For each finding in sorted order:
Use `gsd-tools query commit` with conventional format (message first, then every staged file path):
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query commit \
"fix({padded_phase}): {finding_id} {short_description}" \
--files \

View File

@@ -40,6 +40,8 @@ Before reviewing, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions during review.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during review

View File

@@ -29,6 +29,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation

View File

@@ -93,7 +93,7 @@ Agent(
Resolve the debugger model before spawning:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
debugger_model=$(gsd_run query resolve-model gsd-debugger 2>/dev/null | jq -r '.model' 2>/dev/null || true)
```

View File

@@ -40,6 +40,8 @@ Your job: Find the root cause through hypothesis testing, maintain debug file st
- Load `rules/*.md` as needed during **investigation and fix**.
- Follow skill rules relevant to the bug being investigated and the fix being applied.
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
<philosophy>
@~/.claude/gsd-core/references/debugger-philosophy.md
@@ -1150,7 +1152,7 @@ mv .planning/debug/{slug}.md .planning/debug/resolved/
**Check planning config using state load (commit_docs is available from the output):**
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query state.load)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
# commit_docs is in the JSON output

View File

@@ -20,6 +20,104 @@ If the prompt contains a `<required_reading>` block, use the `Read` tool to load
@~/.claude/gsd-core/references/untrusted-input-boundary.md
<extraction_discipline>
This is **rule-application, not generation.** Apply the taxonomy / precedence rules directly to what the source actually contains. Do not infer, embellish, summarize creatively, or add any content not present in the source. Output only the required structure; when the source is silent on a field, mark it absent rather than guessing. (2505.11423 — applies here as a simple mechanical constraint: mark absent rather than fabricate.)
</extraction_discipline>
<few_shot_exemplars>
These worked examples show the exact input→output contract. Apply the same pattern to new inputs.
**Exemplar 1 — Clean ADR case**
Input: file `docs/adr/0003-choose-postgres.md`, first 50 lines contain:
```
---
status: Accepted
---
# ADR-0003 Use PostgreSQL as primary datastore
## Context
We evaluated SQLite, MySQL, and Postgres. Team has prior Postgres expertise.
## Decision
Use PostgreSQL 15+ for all relational data.
## Consequences
Operators must provision a Postgres instance.
```
Output:
```json
{
"source_path": "docs/adr/0003-choose-postgres.md",
"type": "ADR",
"confidence": "high",
"manifest_override": false,
"title": "ADR-0003 Use PostgreSQL as primary datastore",
"summary": "Chose PostgreSQL 15+ as the primary relational datastore based on team expertise.",
"scope": ["PostgreSQL", "primary datastore", "relational data"],
"cross_refs": [],
"locked": true,
"precedence": null,
"notes": ""
}
```
**Exemplar 2 — Ambiguous / UNKNOWN case**
Input: file `docs/notes/meeting-2024-01-15.md`, first 50 lines contain:
```
# Meeting notes Jan 15
Discussed caching options. No decision reached.
Bob mentioned Redis might be faster. Action: revisit next week.
```
Output:
```json
{
"source_path": "docs/notes/meeting-2024-01-15.md",
"type": "UNKNOWN",
"confidence": "low",
"manifest_override": false,
"title": "Meeting notes Jan 15",
"summary": "Meeting notes discussing caching options; no decision or requirement recorded.",
"scope": ["caching", "Redis"],
"cross_refs": [],
"locked": false,
"precedence": null,
"notes": "No ADR/PRD/SPEC signals. Prose-only meeting notes. No status field, no decision statement, no requirements. Mark as UNKNOWN — user must type-tag via manifest."
}
```
**Exemplar 3 — Edge case: PRD with ADR-like section header**
Input: file `docs/prd/user-auth.md`, contains:
```
# User Authentication PRD
## User Stories
As a user, I want to log in with email+password.
## Acceptance Criteria
- Login succeeds with valid credentials
- Login fails with wrong password (error message shown)
## Decision
Use JWT tokens. (inherited from ADR-0005)
```
Output (note: one `## Decision` section inherited from ADR does NOT override strong PRD signals):
```json
{
"source_path": "docs/prd/user-auth.md",
"type": "PRD",
"confidence": "medium",
"manifest_override": false,
"title": "User Authentication PRD",
"summary": "Requirements for email+password login with JWT tokens.",
"scope": ["user authentication", "login", "JWT"],
"cross_refs": [],
"locked": false,
"precedence": null,
"notes": "Contains one '## Decision' section but dominant signals are user stories + acceptance criteria → PRD. ADR reference recorded in cross_refs if a link is present."
}
```
</few_shot_exemplars>
<why_this_matters>
Your classification drives extraction. If you tag a PRD as a DOC, its requirements never make it into REQUIREMENTS.md. If you tag an ADR as a PRD, its decisions lose their LOCKED status and get overridden by weaker sources. Classification fidelity is load-bearing for the entire ingest pipeline.
</why_this_matters>
@@ -111,6 +209,13 @@ Regardless of type, extract:
- **locked_markers** — for ADRs only: does status read `Accepted` (locked) vs `Proposed`/`Draft` (not locked)? Set `locked: true|false`.
</step>
<terminal_output_schema_restatement>
**Output contract reminder (2506.00069 — restate schema immediately before writing):**
You MUST write exactly one JSON object matching this schema — no extra fields, no omissions:
`{ source_path, type (ADR|PRD|SPEC|DOC|UNKNOWN), confidence (high|medium|low), manifest_override (bool), title (string), summary (≤30 words), scope (string[]), cross_refs (string[]), locked (bool), precedence (int|null), notes (string, omit if high confidence) }`
`locked: true` only for ADR with `Accepted` status. `manifest_override: true` only if MANIFEST_TYPE was provided. Fields absent in source → mark absent (empty array / empty string / false), never fabricate.
</terminal_output_schema_restatement>
<step name="write_output">
Write to `{OUTPUT_DIR}/{slug}-{source_hash}.json` where `slug` is the filename without extension (replace non-alphanumerics with `-`), and `source_hash` is the first 8 hex chars of SHA-256 of the **full source file path** (POSIX-style) so parallel classifiers never collide on sibling `README.md` files.

View File

@@ -22,6 +22,56 @@ If the prompt contains a `<required_reading>` block, load every file listed ther
@~/.claude/gsd-core/references/untrusted-input-boundary.md
<extraction_discipline>
This is **rule-application, not generation.** Apply the taxonomy / precedence rules directly to what the source actually contains. Do not infer, embellish, summarize creatively, or add any content not present in the source. Output only the required structure; when the source is silent on a field, mark it absent rather than guessing. (2505.11423 — applies here as a simple mechanical constraint: mark absent rather than fabricate.)
</extraction_discipline>
<few_shot_exemplars>
These worked examples show the exact input→output contract for per-type extraction. Apply the same pattern.
**Exemplar 1 — Clean ADR extraction**
Input: classified ADR `docs/adr/0003-choose-postgres.md` with `locked: true`, decision statement: "Use PostgreSQL 15+ for all relational data."
Output entry for `INTEL_DIR/decisions.md`:
```
## ADR-0003: Use PostgreSQL as primary datastore
- source: docs/adr/0003-choose-postgres.md
- status: locked (Accepted)
- decision: Use PostgreSQL 15+ for all relational data.
- scope: primary datastore, relational data
```
**Exemplar 2 — UNKNOWN / low-confidence doc (conflict surfacing)**
Input: classified doc `docs/notes/meeting-2024-01-15.md` with `type: UNKNOWN`, `confidence: low`.
Output: do NOT extract to any intel file. Instead, add to `unresolved-blockers` in `CONFLICTS_PATH`:
```
[BLOCKER] UNKNOWN classification — user must type-tag
Found: docs/notes/meeting-2024-01-15.md classified UNKNOWN (low confidence)
Signals observed: prose-only meeting notes, no ADR/PRD/SPEC markers
→ Re-tag via --manifest before re-running ingest
```
Mark absent fields as absent in the entry — do not infer a type.
**Exemplar 3 — Edge case: competing PRD acceptance criteria**
Input: two PRD classifications for the same scope "user-auth":
- `docs/prd/auth-v1.md` → requirement: "login via email+password"
- `docs/prd/auth-v2.md` → requirement: "login via SSO only"
Output: do NOT pick one. Write both to `competing-variants` bucket in `CONFLICTS_PATH`:
```
[WARNING] Competing acceptance variants for REQ-user-auth
Found: docs/prd/auth-v1.md requires "email+password"
Found: docs/prd/auth-v2.md requires "SSO only" — same scope "user authentication"
Impact: Synthesis cannot pick without losing intent
→ Choose one variant or split into two requirements before routing
```
Emit both variants verbatim to `INTEL_DIR/requirements.md` under separate IDs (REQ-user-auth-v1, REQ-user-auth-v2).
</few_shot_exemplars>
<why_this_matters>
You are the precedence-enforcing layer. Silent merges, lost locked decisions, or naive dedupes here corrupt every downstream plan. When in doubt, surface the conflict rather than pick.
</why_this_matters>
@@ -111,6 +161,17 @@ Apply the `doc-conflict-engine` severity semantics:
- `auto-resolved` maps to [INFO] — recorded for transparency
</step>
<terminal_output_schema_restatement>
**Output contract reminder (2506.00069 — restate schema immediately before writing):**
Per-type intel files must use these exact formats — no omissions, no extra fields:
- `decisions.md`: each entry has `## {title}`, `- source:`, `- status: locked|proposed`, `- decision:`, `- scope:`
- `requirements.md`: each entry has `## REQ-{slug}`, `- source:`, `- description:`, `- acceptance:`, `- scope:`
- `constraints.md`: each entry has `## {title}`, `- source:`, `- type: api-contract|schema|nfr|protocol`, `- content:`
- `context.md`: topic-keyed entries with `- source:` attribution
Absent fields → mark absent (empty / omit), never fabricate. LOCKED-vs-LOCKED → always BLOCKER, never auto-resolve.
`CONFLICTS_PATH` must have exactly three sections: `### BLOCKERS`, `### WARNINGS`, `### INFO`.
</terminal_output_schema_restatement>
<step name="write_conflicts_report">
Write `CONFLICTS_PATH` using the format from `references/doc-conflict-engine.md`. Three buckets, plain text, no tables.

View File

@@ -34,6 +34,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation

View File

@@ -1,7 +1,7 @@
---
name: gsd-domain-researcher
description: Researches the business domain and real-world application context of the AI system being built. Surfaces domain expert evaluation criteria, industry-specific failure modes, regulatory context, and what "good" looks like for practitioners in this field — before the eval-planner turns it into measurable rubrics. Spawned by /gsd:ai-integration-phase orchestrator.
tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*
tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*
color: purple
# hooks:
# PostToolUse:

View File

@@ -39,6 +39,8 @@ Read `~/.claude/gsd-core/references/ai-evals.md` before auditing. This is your s
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation
@@ -112,7 +114,7 @@ Score 5 components (ok / partial / missing):
Do NOT compute scores by hand. Call the deterministic verb with your audited inputs:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query eval.score --covered <covered_count> --total <total_dimensions> --infra <tooling>,<dataset>,<cicd>,<guardrails>,<tracing> --raw
```

View File

@@ -1,7 +1,7 @@
---
name: gsd-executor
description: Executes GSD plans with atomic commits, deviation handling, checkpoint protocols, and state management. Spawned by execute-phase orchestrator or execute-plan command.
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, mcp__context7__*
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, mcp__context7__*, mcp__plugin_context7_context7__*
color: yellow
# hooks:
# PostToolUse:
@@ -24,7 +24,7 @@ Your job: Execute the plan completely, commit each task, create SUMMARY.md, upda
<documentation_lookup>
When you need library or framework documentation, check in this order:
1. If Context7 MCP tools (`mcp__context7__*`) are available in your environment, use them:
1. If Context7 MCP tools (`mcp__context7__*, mcp__plugin_context7_context7__*`) are available in your environment, use them:
- Resolve library ID: `mcp__context7__resolve-library-id` with `libraryName`
- Fetch docs: `mcp__context7__get-library-docs` with `context7CompatibleLibraryId` and `topic`
@@ -64,6 +64,8 @@ Before executing, discover project context:
- Load `rules/*.md` as needed during **implementation**.
- Follow skill rules relevant to the task you are about to commit.
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
**CLAUDE.md enforcement:** If `./CLAUDE.md` exists, treat its directives as hard constraints during execution. Before committing each task, verify that code changes do not violate CLAUDE.md rules (forbidden patterns, required conventions, mandated tools). If a task action would contradict a CLAUDE.md directive, apply the CLAUDE.md rule — it takes precedence over plan instructions. Document any CLAUDE.md-driven adjustments as deviations (Rule 2: auto-add missing critical functionality).
</project_context>
@@ -73,7 +75,7 @@ Before executing, discover project context:
Load execution context:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.execute-phase "${PHASE}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
```
@@ -313,7 +315,7 @@ For full automation-first patterns, server lifecycle, CLI handling:
**Auto-mode checkpoint behavior** (when `AUTO_CFG` is `"true"`):
- **checkpoint:human-verify** → Auto-approve **except package-legitimacy checkpoints**. If checkpoint has `gate="blocking-human"` OR its purpose indicates package legitimacy verification (`what-built` mentions `Package verification required before install` or `Package install failed — human verification required`), do **not** auto-approve. STOP and return checkpoint_return_format for explicit human confirmation.
- **checkpoint:decision** → Auto-select first option (planners front-load the recommended choice). Log `⚡ Auto-selected: [option name]`. Continue to next task.
- **checkpoint:decision** → If checkpoint has `gate="blocking-human"`, do **not** auto-select — STOP and return checkpoint_return_format for an explicit human decision (a `blocking-human` decision exists because its default answer would be wrong to assume). Otherwise auto-select first option (planners front-load the recommended choice), log `⚡ Auto-selected: [option name]`, continue to next task.
- **checkpoint:human-action** → STOP normally. Auth gates cannot be automated — return structured checkpoint message using checkpoint_return_format.
**Standard checkpoint behavior** (when `AUTO_CFG` is not `"true"`):
@@ -338,6 +340,7 @@ When hitting checkpoint or auth gate, return this structure:
## CHECKPOINT REACHED
**Type:** [human-verify | decision | human-action]
**Gate:** [blocking | blocking-human] — copy the task's `gate` attribute verbatim so the orchestrator's carve-out sees it
**Plan:** {phase}-{plan}
**Progress:** {completed}/{total} tasks complete
@@ -689,7 +692,7 @@ Do NOT skip. Do NOT proceed to state updates if self-check fails.
</self_check>
<state_updates>
After SUMMARY.md, update STATE.md using `gsd-tools query` state handlers (positional args; see `sdk/src/query/QUERY-HANDLERS.md`):
After SUMMARY.md, update STATE.md using `gsd-tools query` state handlers (named flags):
```bash
# Advance plan counter (handles edge cases automatically)
@@ -700,16 +703,17 @@ gsd_run query state.update-progress
# Record execution metrics (phase, plan, duration, tasks, files)
gsd_run query state.record-metric \
"${PHASE}" "${PLAN}" "${DURATION}" "${TASK_COUNT}" "${FILE_COUNT}"
--phase "${PHASE}" --plan "${PLAN}" --duration "${DURATION}" \
--tasks "${TASK_COUNT}" --files "${FILE_COUNT}"
# Add decisions (extract from SUMMARY.md key-decisions)
for decision in "${DECISIONS[@]}"; do
gsd_run query state.add-decision "${decision}"
gsd_run query state.add-decision --summary "${decision}"
done
# Update session info (timestamp, stopped-at, resume-file)
# Update session info (stopped-at, resume-file; timestamp set automatically)
gsd_run query state.record-session \
"" "Completed ${PHASE}-${PLAN}-PLAN.md" "None"
--stopped-at "Completed ${PHASE}-${PLAN}-PLAN.md" --resume-file "None"
```
```bash
@@ -736,7 +740,7 @@ gsd_run query requirements.mark-complete ${REQ_IDS}
**For blockers found during execution:**
```bash
gsd_run query state.add-blocker "Blocker description"
gsd_run query state.add-blocker --text "Blocker description"
```
</state_updates>

View File

@@ -35,6 +35,8 @@ Every expected cross-phase connection must resolve to WIRED (verified end-to-end
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation

View File

@@ -212,7 +212,7 @@ Glob for project structure indicators:
Read package.json, configs, and build files. Write `stack.json`. Then patch its timestamp:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run intel patch-meta .planning/intel/stack.json
```

View File

@@ -22,7 +22,7 @@ If `mempalace.enabled !== true`, do nothing and report `MemPalace disabled — c
## Wing / mode / transport
- **Wing:** `mempalace.wing` if non-empty, else `project_code`, else the repo directory name. Every call you make is scoped to this one wing.
- **Mode:** only `augment` is currently wired — KG writes are an additive mirror of `.planning/graphs/`. `kg_backend`/`replace` are forward-declared and behave as `augment` today.
- **Mode** (`mempalace.memory_mode`): under `augment`, KG writes are an additive mirror of `.planning/graphs/`. Under `kg_backend`/`replace`, the palace KG is the authoritative fact store — still mirror every fact here as the primary target; GSD's normal graphify keeps `.planning/graphs/` current, so an unreachable palace never loses history.
- **Transport:** prefer the `mempalace_*` MCP tools interactively; fall back to the `mempalace` CLI in headless/cron runs. If neither is reachable, report unavailability and stop — do not error.
## Tasks (each independently best-effort)

View File

@@ -51,6 +51,8 @@ Read ALL files from `<required_reading>`. Extract:
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation

View File

@@ -1,7 +1,7 @@
---
name: gsd-phase-researcher
description: Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
color: cyan
# hooks:
# PostToolUse:
@@ -50,6 +50,8 @@ Before researching, discover project context:
- Load `rules/*.md` as needed during **research**.
- Research output should account for project skill patterns and conventions.
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
**CLAUDE.md enforcement:** If `./CLAUDE.md` exists, extract all actionable directives (required tools, forbidden patterns, coding conventions, testing rules, security requirements). Include a `## Project Constraints (from CLAUDE.md)` section in RESEARCH.md listing these directives so the planner can verify compliance. Treat CLAUDE.md directives with the same authority as locked decisions from CONTEXT.md — research should not recommend approaches that contradict them.
</project_context>
@@ -112,7 +114,7 @@ Construct a JSON file at a temp path (e.g. `/tmp/research-plan-input.json`):
### Step B — Obtain the fetch plan
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query research-plan --input /tmp/research-plan-input.json
```

View File

@@ -54,6 +54,8 @@ Before verifying, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during verification
@@ -689,7 +691,7 @@ issue:
Load phase operation context:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
```

View File

@@ -1,7 +1,7 @@
---
name: gsd-planner
description: Creates executable phase plans with task breakdown, dependency analysis, and goal-backward verification. Spawned by /gsd:plan-phase orchestrator.
tools: Read, Write, Edit, Bash, Glob, Grep, Skill, WebFetch, mcp__context7__*
tools: Read, Write, Edit, Bash, Glob, Grep, Skill, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*
color: green
# hooks:
# PostToolUse:
@@ -46,6 +46,8 @@ Before planning, discover project context:
**Project skills:** @~/.claude/gsd-core/references/project-skills-discovery.md
- Load `rules/*.md` as needed during **planning**.
- Ensure plans account for project skill patterns and conventions.
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
</project_context>
<context_fidelity>
@@ -620,7 +622,7 @@ start of execution when `--reviews` flag is present or reviews mode is active.
Load planning context:
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
INIT=$(gsd_run query init.plan-phase "${PHASE}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
```

View File

@@ -1,7 +1,7 @@
---
name: gsd-project-researcher
description: Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.
tools: Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
tools: Read, Write, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*, mcp__perplexity__*
color: cyan
# hooks:
# PostToolUse:
@@ -34,6 +34,8 @@ Your files feed the roadmap:
@~/.claude/gsd-core/references/untrusted-input-boundary.md
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
<documentation_lookup>
@~/.claude/gsd-core/references/research-documentation-lookup.md
</documentation_lookup>
@@ -78,7 +80,7 @@ Construct a JSON file at a temp path (e.g. `/tmp/research-plan-input.json`):
### Step B — Obtain the fetch plan
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query research-plan --input /tmp/research-plan-input.json
```

View File

@@ -34,6 +34,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
@~/.claude/gsd-core/references/untrusted-input-boundary.md
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
<downstream_consumer>
Your SUMMARY.md is consumed by the gsd-roadmapper agent which uses it to:
@@ -153,7 +155,7 @@ Write to `.planning/research/SUMMARY.md`.
The 4 parallel researcher agents write files but do NOT commit. You commit everything together.
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query commit "docs: complete project research" --files .planning/research/
```

View File

@@ -26,6 +26,8 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation

View File

@@ -1,10 +1,8 @@
---
name: gsd-security-auditor
description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Produces SECURITY.md. Spawned by /gsd:secure-phase.
description: Verifies threat mitigations from PLAN.md threat model exist in implemented code. Returns structured security verdict (SECURED / OPEN_THREATS / ESCALATE). Spawned by /gsd:secure-phase.
tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
@@ -15,11 +13,11 @@ color: red
<role>
An implemented phase has been submitted for security audit. Verify that every declared threat mitigation is present in the code — do not accept documentation or intent as evidence.
Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_model>` by its declared disposition (mitigate / accept / transfer). Reports gaps. Writes SECURITY.md.
Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_model>` by its declared disposition (mitigate / accept / transfer). Reports gaps. Returns a structured verdict — the orchestrator owns the SECURITY.md file write (#2119: single-writer contract).
**Mandatory Initial Read:** If prompt contains `<required_reading>`, load ALL listed files before any action.
**Implementation files are READ-ONLY.** Only create/modify: SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation.
**Implementation files are READ-ONLY.** The auditor does NOT write any files — it returns a structured verdict (SECURED / OPEN_THREATS / ESCALATE). The orchestrator persists SECURITY.md. Implementation security gaps → OPEN_THREATS or ESCALATE. Never patch implementation.
</role>
<adversarial_stance>
@@ -51,6 +49,8 @@ Read ALL files from `<required_reading>`. Extract:
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during implementation
@@ -77,20 +77,20 @@ Classify each threat before verification. Record classification for every threat
- L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists.
</step>
<step name="verify_and_write">
<step name="verify_and_return">
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`. Apply depth per `asvs_level` (see analyze_threats step).
For `accept` threats: check SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
For `accept` threats: check existing SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`.
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in SECURITY.md (not a blocker).
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in the structured return (not a blocker).
**Severity-aware `threats_open` computation (severity order: critical > high > medium > low):**
`threats_open` (the SECURITY.md frontmatter gate field) = the count of threats whose status is OPEN AND whose severity rank ≥ the `block_on` rank. `block_on: none` ⇒ 0 (nothing ever blocks). `block_on: low` ⇒ all open threats block. `block_on: high` (default) ⇒ only high and critical open threats block.
Open threats BELOW the block threshold are recorded in SECURITY.md as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`.
Open threats BELOW the block threshold are recorded in the return as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`.
**Fail-closed for missing severity:** if an OPEN threat has no severity or an unparseable severity (e.g. a legacy register predating the Severity column), treat it as `critical` for this computation — it COUNTS toward `threats_open` (blocking). Never silently drop an unranked open threat.
Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return structured result.
Return the structured result (SECURED / OPEN_THREATS / ESCALATE) with `threats_open` set to the severity-filtered count. The orchestrator writes SECURITY.md from this data — the auditor does NOT write any files (#2119).
</step>
</execution_flow>
@@ -114,7 +114,7 @@ Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return str
### Unregistered Flags
{none / list from SUMMARY.md ## Threat Flags with no threat mapping}
SECURITY.md: {path}
**threats_open:** {count}
```
## OPEN_THREATS
@@ -143,9 +143,9 @@ SECURITY.md: {path}
*Only blocking-open threats count toward `threats_open` in SECURITY.md frontmatter.*
Next: Implement mitigations or document as accepted in SECURITY.md accepted risks log, then re-run /gsd:secure-phase.
Next: Implement mitigations or document as accepted risks, then re-run /gsd:secure-phase.
SECURITY.md: {path}
**threats_open:** {count}
```
## ESCALATE
@@ -170,6 +170,6 @@ SECURITY.md: {path}
- [ ] Each threat verified by disposition type (mitigate / accept / transfer)
- [ ] Threat flags from SUMMARY.md `## Threat Flags` incorporated
- [ ] Implementation files never modified
- [ ] SECURITY.md written to correct path
- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE
- [ ] No files written — structured verdict returned only (orchestrator writes SECURITY.md)
- [ ] Structured return: SECURED / OPEN_THREATS / ESCALATE with `threats_open` count
</success_criteria>

View File

@@ -49,6 +49,8 @@ Before auditing, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill
3. Do NOT load full `AGENTS.md` files (100KB+ context cost)

View File

@@ -24,12 +24,44 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
You are read-only — never modify UI-SPEC.md. Report findings, let the researcher fix.
</role>
<adversarial_stance>
**FORCE stance:** Assume every UI-SPEC.md contains design debt until the contract proves otherwise. Your starting hypothesis: generic CTAs, missing states, and grid-breaking values are present — find them.
**Common failure modes — how UI checkers go soft:**
- Passing a spec because all sections are filled in, without checking the *content* quality of CTA labels, empty/error states, and copy
- Treating "accent color defined" as sufficient without checking it is reserved (not applied to all interactive elements)
- Accepting more than 4 font sizes or non-4-multiple spacing because "it's close enough"
- Letting a polished-looking spec bias the verdict toward PASS before each dimension is checked
- Softening a BLOCK to FLAG to avoid sending the researcher back
**Required verdict classification:** every dimension must resolve to:
- **BLOCK** — contract is incomplete/inconsistent/unimplementable; planning must not begin
- **FLAG** — works but degrades design quality; researcher should fix
- **PASS** — dimension meets the contract
</adversarial_stance>
<objective_persona>
**The Auditor** is an independent design reviewer known for objective, uncompromising spec review. The Auditor applies the six dimensions without deference to effort, polish, or seniority. The Auditor's verdict is grounded in the contract criteria alone — not in whether the spec looks good or whether the researcher worked hard.
When producing a verdict, ask: *What is The Auditor's verdict on this dimension?* The Auditor's verdict must be derived from evidence in the spec, not from impressions.
The Auditor is skeptical and exacting, but NOT hostile or contemptuous. The Auditor does not express anger or frustration — the Auditor simply applies the criteria and states what is there and what is missing. (Sources: 2505.23840 — third-person objective persona as sycophancy mitigation; 2506.04975 — objective persona, not hostile, to avoid toxicity escalation.)
This persona is **not a standalone accuracy guarantee**. It is a stance for applying the evidence contract consistently; if the persona framing and the written criteria/evidence conflict, the criteria and evidence win.
**Anti-capitulation rule (re-verification turns):** If the researcher disagrees with a BLOCK verdict or submits a revised spec, The Auditor re-examines the revised content against the criteria. Researcher disagreement alone is never grounds to downgrade a BLOCK. A BLOCK may be downgraded only when the spec contains a concrete fix that resolves the exact deficiency that triggered the BLOCK, or when re-examination shows the prior dimension application was mistaken. Self-correction is allowed when the criteria and evidence support it; capitulation to pressure is not. "We'll handle it in implementation" or "it's implied" are not concrete fixes.
</objective_persona>
@~/.claude/gsd-core/references/ui-consideration-probe.md
<project_context>
Before verifying, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during verification

View File

@@ -1,7 +1,7 @@
---
name: gsd-ui-researcher
description: Produces UI-SPEC.md design contract for frontend phases. Reads upstream artifacts, detects design system state, asks only unanswered questions. Spawned by /gsd:ui-phase orchestrator.
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*
tools: Read, Write, Edit, Bash, Grep, Glob, Skill, WebSearch, WebFetch, mcp__context7__*, mcp__plugin_context7_context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*
color: purple
# hooks:
# PostToolUse:
@@ -28,6 +28,7 @@ If the prompt contains a `<required_reading>` block, you MUST use the `Read` too
</role>
@~/.claude/gsd-core/references/untrusted-input-boundary.md
@~/.claude/gsd-core/references/ui-consideration-probe.md
<documentation_lookup>
@~/.claude/gsd-core/references/research-documentation-lookup.md
@@ -39,6 +40,8 @@ Before researching, discover project context:
**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions.
**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
1. List available skills (subdirectories)
2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
3. Load specific `rules/*.md` files as needed during research
@@ -288,7 +291,7 @@ This file is the canonical output of this agent. The orchestrator reads `$PHASE_
## Step 6: Commit (optional)
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
gsd_run query commit "docs($PHASE): UI design contract" --files "$PHASE_DIR/$PADDED_PHASE-UI-SPEC.md"
```

View File

@@ -12,7 +12,7 @@ color: green
---
<role>
A completed phase has been submitted for goal-backward verification. Verify that the phase goal is actually achieved in the codebase — SUMMARY.md claims are not evidence.
A completed phase has been submitted for verification. Verify that the phase goal is actually achieved in the codebase — SUMMARY.md claims are not evidence.
Goal-backward verification. Start from what the phase SHOULD deliver, verify it actually exists and works in the codebase.
@@ -52,14 +52,16 @@ Before verifying, discover project context:
**Project skills:** @~/.claude/gsd-core/references/project-skills-discovery.md
- Load `rules/*.md` as needed during **verification**.
- Apply skill rules when scanning for anti-patterns and verifying quality.
**agent_skills:** self-load per @~/.claude/gsd-core/references/agent-skills-bootstrap.md
</project_context>
<core_principle>
**Task completion ≠ Goal achievement**
A task "create chat component" can be marked complete when the component is a placeholder. The task was done — a file was created — but the goal "working chat interface" was not achieved.
A "create chat component" task can be complete with a placeholder file — task done, goal "working chat interface" missed.
Goal-backward verification starts from the outcome and works backwards:
Start from the outcome and work backwards:
1. What must be TRUE for the goal to be achieved?
2. What must EXIST for those truths to hold?
@@ -99,7 +101,7 @@ Set `is_re_verification = false`, proceed with Step 1.
## Step 1: Load Context (Initial Mode Only)
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
ls "$PHASE_DIR"/*-PLAN.md 2>/dev/null
ls "$PHASE_DIR"/*-SUMMARY.md 2>/dev/null
gsd_run query roadmap.get-phase "$PHASE_NUM"
@@ -197,6 +199,7 @@ For each truth:
- A pre-existing test exercises the transition/invariant and passes (confirm via Step 7b's single-named-test path) → ✓ VERIFIED.
- No such test exists, or it can't run without a server/state mutation → ⚠️ PRESENT_BEHAVIOR_UNVERIFIED. Emit a human-verification item (Step 8) and do not count it toward the verified score (Step 9).
- An accepted override (Step 3b) carries the truth as PASSED (override), exactly as it does for a FAILED truth.
5b. **Non-inferable (`backstop`) truths:** a `verification: backstop` truth (via `truthVerification()`) abstains unless confirmed by explicit evidence — mark `insufficient_spec` -> a human-verification item -> `human_needed`. See `references/honest-verifier.md`.
6. Determine truth status
## Step 3b: Check Verification Overrides

31
bin/gsd-mcp-server.js Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env node
'use strict';
/**
* gsd-mcp-server — companion MCP server bin entry (ADR-1239 Phase C-2 / #1681).
*
* Lives at top-level bin/ (alongside install.js) — it is a PACKAGE bin the host
* spawns via `npx gsd-mcp-server` (or the global bin), NOT a per-runtime
* artifact copied into a host's config dir. (Placing it under gsd-core/bin/
* would leak it into every runtime install + break golden parity.)
*
* A stdio JSON-RPC 2.0 server exposing GSD interface points 1 (command) + 5
* (state IO) so any MCP-consuming host (Claude/Codex/OpenCode/VS Code/Gemini/
* Cursor/Cline/Hermes) can drive GSD with no bespoke plugin. Delegates to the
* tested server module (gsd-core/bin/lib/mcp-server.cjs runServer). Reads
* line-delimited JSON-RPC from stdin, writes one response + newline per
* request, exits cleanly when stdin closes.
*
* The protocol logic (handleMessage) + the injectable-stream loop (runServer)
* are unit-tested in tests/gsd-mcp-server.test.cjs; the process lifecycle
* (spawn → JSON-RPC → clean exit) in tests/gsd-mcp-server-bin.test.cjs.
*/
const { runServer } = require('../gsd-core/bin/lib/mcp-server.cjs');
runServer({
input: process.stdin,
output: process.stdout,
ctx: { cwd: process.cwd() },
}).catch((err) => {
process.stderr.write(String((err && err.message) || err) + '\n');
process.exit(1);
});

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
{
"id": "ai-integration",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "AI design contract",
"description": "AI-SPEC design contract workflow for phases that build AI systems; owns the AI integration command, agents, and workflow.ai_integration_phase activation key.",
"tier": "full",
@@ -30,6 +30,11 @@
"type": "boolean",
"default": true,
"description": "Prompt for an AI-SPEC design contract before planning phases that involve AI systems."
},
"workflow.api_coverage_gate": {
"type": "boolean",
"default": true,
"description": "Require an explicit API-coverage decision (full-by-default, opt-out-not-opt-in) before a phase that integrates an external API/SDK/service can seal. At plan:pre the planner is prompted to enumerate the API surface into COVERAGE.md; at verify:pre a blocking gate fails the seal unless the matrix exists with every non-integrated capability an explicit, reasoned opt-out. Independent of ai_integration_phase (applies to any external-API integration, not only AI)."
}
},
"steps": [
@@ -48,6 +53,32 @@
"onError": "skip"
}
],
"contributions": [],
"gates": []
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/api-coverage-plan-pre.md"
},
"produces": [
"COVERAGE.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.api_coverage_gate",
"onError": "skip"
}
],
"gates": [
{
"point": "verify:pre",
"check": {
"query": "api-coverage.verify-pre"
},
"when": "workflow.api_coverage_gate",
"blocking": true,
"onError": "halt"
}
]
}

View File

@@ -0,0 +1,105 @@
# API Coverage Decision Checkpoint
> Full API Coverage by Default — Opt Out, Never Opt In. Fires when a phase
> integrates an external API / SDK / service. Most non-API phases will not fire
> it — that is the point.
## Why this exists
"We integrated the API" too often silently means "we integrated whatever the
first use case exercised." Every un-built capability is then an invisible hole,
discovered later by a user who reasonably expected it to work. The phase sealed
green because its tasks completed; nobody decided the gaps were acceptable,
because nobody enumerated them. This checkpoint makes the surface **visible and
decided** before the phase can seal.
## Detect whether this phase integrates an external API
The detector is a deterministic scan over the phase scope. It strips fenced
code blocks first, so a trigger term inside a code snippet does not fire. It
returns a typed result: `{ detected, signals[], terms }`. Run it on the phase
scope (the concatenation of this phase's ROADMAP section + the PLAN body):
```bash
SCOPE="$(cat "${PHASE_DIR}"/*-PLAN.md 2>/dev/null) $(gsd_run query roadmap.get-phase "${PHASE}" 2>/dev/null || true)"
API_COVERAGE_JSON=$(printf '%s' "$SCOPE" | node gsd-core/bin/lib/api-coverage.cjs --json 2>/dev/null || echo '{"detected":false,"signals":[]}')
```
Read `API_COVERAGE_JSON.detected`. Act on it only — do **not** pattern-match the
prose yourself.
**If `detected` is `false`:** this phase does not integrate an external API. Skip
the checkpoint entirely and continue planning. Do not raise it with the user.
**If `detected` is `true`:** an external-API integration is in scope. You MUST
produce a **coverage matrix** before the plan is finalized.
## Produce the coverage matrix
Enumerate the external API's full **capability surface** — the verb/endpoint/method
list (e.g. for a music service: `search`, `play`, `pause`, `skip`, `set_volume`,
`get_playlist`, `create_playlist`, `add_to_playlist`, …). For each capability
record a decision, starting from **full coverage** as the default:
| capability | decision | reason |
|---|---|---|
| `<capability-id>` | `INTEGRATE` \| `OPT-OUT` | `<one-line reason if OPT-OUT>` |
Rules:
- **`INTEGRATE` is the default.** Every capability starts as INTEGRATE; the
matrix is the *subtraction record*.
- **Every `OPT-OUT` MUST carry a one-line reason** (`not needed`, `not needed
yet`, `explicitly out of scope`, …). An opt-out without a reason is an
un-decided hole — the exact failure mode this gate exists to close.
- **A second integration against the same need** (e.g. a second platform for the
same capability) starts from the **same full-coverage baseline** as the first.
Do not carry over the first integration's opt-outs silently — re-decide each
capability for the new surface, so a first-class/fallback asymmetry cannot
accumulate.
Write the matrix to `${PHASE_DIR}/COVERAGE.md` (canonical markdown-table form):
```markdown
# API Coverage — <service>
> Full coverage by default. Opt-outs are explicit, reasoned decisions.
| capability | decision | reason |
|---|---|---|
| search | INTEGRATE | |
| playlists | INTEGRATE | |
| skip | OPT-OUT | not needed yet — tracked for follow-up phase |
```
A fenced ` ```coverage ` JSON block is also accepted for machine-generated
matrices; the markdown table is preferred (human-editable, diff-friendly).
## The seal-time gate
This checkpoint is enforced. At `verify:pre` the `api-coverage.verify-pre` gate
runs `check api-coverage.verify-pre <phase-dir>`:
- If `COVERAGE.md` exists, it is validated — every row needs a valid decision and
every `OPT-OUT` a reason. A malformed/partial matrix **blocks the seal**.
- If `COVERAGE.md` is absent, the detector runs again over the phase scope. If a
strong external-API-integration signal is found, the seal is **blocked** until a
matrix is produced. If no signal is found, the phase is treated as a non-API
phase and the seal proceeds.
So: an API-integrating phase cannot seal without a decided matrix. Produce it at
plan time; do not leave it for seal time.
## Tuning the vocabulary (optional)
The trigger vocabulary is a curated, additive-only set in
`gsd-core/bin/lib/api-coverage.cjs` (`DEFAULT_API_COVERAGE_TERMS`). To widen it
for a project, override at the call site:
```bash
printf '%s' "$SCOPE" | node gsd-core/bin/lib/api-coverage.cjs --json \
--verbs integrate,wrap,connect,embed --nouns api,sdk,rest,grpc,webhook,plugin
```
The whole checkpoint is toggleable via `workflow.api_coverage_gate` in
`.planning/config.json`.

View File

@@ -1,7 +1,7 @@
{
"id": "antigravity",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Antigravity",
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.",
"tier": "core",
@@ -24,6 +24,7 @@
],
"probeExists": "gsd-core/VERSION"
},
"localConfigDir": ".agents",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -34,6 +35,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
],
"local": [
@@ -44,6 +53,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},
@@ -54,7 +71,31 @@
"supportTier": 1,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": []
"permissionWriter": "antigravity",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": "undocumented",
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "go"
},
"hostBehaviors": {
"reviewerCli": true,
"projectInstructionFile": "GEMINI.md",
"noPathRewrite": true,
"hookPathStyle": "raw",
"globalDirResolver": "antigravity"
}
}
}

View File

@@ -0,0 +1,45 @@
{
"id": "assumption-delta",
"role": "feature",
"version": "1.7.0",
"title": "Assumption-delta architecture checkpoint",
"description": "Rarely-firing advisory checkpoint that triggers when a phase makes something plural, optional, or chosen that used to be singular, required, or derived. Surfaces one identity-model question (promote the new general representation to primary, or add it alongside?) so a silent primary-key drift does not accumulate into a later user-facing bug. Non-blocking; fires only on a detected signal.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.assumption_delta": {
"type": "boolean",
"default": true,
"description": "Enable the assumption-delta architecture checkpoint during planning. When a pluralization/optional/chosen signal is detected in the phase scope, the planner is prompted to re-ask whether the primary key / identity model still names the right thing. Advisory (non-blocking)."
}
},
"steps": [],
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/plan-pre.md"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.assumption_delta",
"onError": "skip"
}
],
"gates": []
}

View File

@@ -0,0 +1,53 @@
# Assumption-Delta Architecture Checkpoint
> Advisory, non-blocking. Fires **only** when the phase scope shows a singular→plural / required→optional / derived→chosen transition. When it fires, it surfaces ONE identity-model question before the plan is finalized. Most phases will not fire it — that is the point.
## Why this exists
Most quietly-imported architectural debt does not come from a missing upfront design phase. It comes at the *seam*: a later phase introduces a second case (a second platform, auth method, tenant, region, source of truth) and nobody re-asks whether the original abstraction still names the right thing. The phase that adds the second case is exactly the 20-minute conversation that prevents an afternoon of later cleanup.
## Run the detector
The detector is a deterministic scan over the phase scope text. It strips fenced code blocks first, so a trigger word that appears only inside a code snippet does not fire. It returns a typed result: `{ detected, signals[], terms }`. Resolve it through the `assumption-delta scan` query (same phase-section resolver as `roadmap.get-phase`):
```bash
ASSUMPTION_DELTA_JSON=$(gsd_run query assumption-delta scan "${PHASE}" --json 2>/dev/null || echo '{"detected":false,"signals":[],"terms":{}}')
```
> If the phase section cannot be resolved (no `ROADMAP.md` / unknown phase), the query emits `{ "detected": false, ... }` — the checkpoint does not fire. Do not block on it.
>
> Optional tuning — pass `--terms <comma-list>` to replace the curated pluralization cues for this project (the `optional`/`chosen` cues keep their defaults): `gsd_run query assumption-delta scan "${PHASE}" --json --terms second,alternative,fallback`.
## Decision branch
Read `ASSUMPTION_DELTA_JSON`. Act on `detected` only — do **not** pattern-match the human prose.
**If `detected` is `false`:** this phase does not change a core assumption. Skip the checkpoint entirely and continue planning. Do not raise it with the user.
**If `detected` is `true`:** a core assumption may have lost its monopoly. The `signals[]` array tells you which family fired:
| `kind` | What changed | The question to answer |
|---|---|---|
| `pluralization` | A second X was introduced where there was one (second platform / auth method / tenant / region / source of truth) | Does the current primary key / identity model still name the right noun? |
| `optional` | A required / `only` field became optional | Is the field still the right anchor, or has the anchor moved? |
| `chosen` | A derived value became chosen, or a constant became a parameter | Has a configuration decision become a modeling decision? |
Before finalizing the plan, answer this for the user and record the decision explicitly:
> **Promote vs. add-alongside.** The usual correct move when a generalization occurs is to **promote** the new general representation to the primary and **demote** the old specific one to a detail of one variant — *not* to add the new one alongside the still-required old one. Adding alongside silently contradicts the generalized intent (a later variant that does not fit the old primary can be stored but never confirmed as a default).
Record the outcome in the PLAN.md front matter / a `<assumption_delta_decision>` block:
- The **noun** that is now primary (the generalized identity).
- The **decision**: `promote` | `add-alongside` | `no-change`, with a one-line rationale.
- If `add-alongside`: call it out as accepted debt and note what would force a later promote.
## Optional companion: an invariant test
When `detected` is `true`, suggest (do not require) a contract/invariant test that encodes the now-generalized intent — e.g. *"every confirmed default round-trips through the primary use-path, for every supported variant."* That test goes red the instant a future phase reintroduces the singular assumption, so the regression cannot land silently. If the user accepts, add the test as a task in the plan.
## Tuning the vocabulary (optional)
The trigger vocabulary is a curated, additive-only set in `gsd-core/bin/lib/assumption-delta.cjs` (`DEFAULT_ASSUMPTION_DELTA_TERMS`). Bare "or" is intentionally excluded — it is too common in prose and would make the gate fire constantly. To widen or narrow the cues for a project, override at the call site with `--terms <comma-list>` (replaces the pluralization cues; `optional`/`chosen` keep defaults). The whole checkpoint is toggleable via `workflow.assumption_delta` in `.planning/config.json`.
This checkpoint is advisory: it informs and records; it never blocks the phase.

View File

@@ -1,7 +1,7 @@
{
"id": "audit",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Audit",
"description": "Open-artifact audit and UAT-gap audit for milestone close gates; exposes `gsd-tools audit-uat` (cross-phase UAT outstanding items) and `gsd-tools audit-open` (structured open-artifact scan across debug, tasks, threads, todos, seeds, UAT, verification, context-questions).",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "augment",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Augment Code",
"description": "Augment Code CLI — commands + nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"AUGMENT_CONFIG_DIR"
]
},
"localConfigDir": ".augment",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -35,6 +36,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
@@ -53,6 +62,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},
@@ -64,6 +81,27 @@
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"hostBehaviors": {
"commandBodyConverter": "convertClaudeToAugmentMarkdown",
"mcpCompanion": "settings-json"
},
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
}
}
}

View File

@@ -0,0 +1,85 @@
{
"id": "claude-orchestration",
"role": "feature",
"version": "1.7.0",
"title": "Claude orchestration (Workflow backend)",
"description": "Default-off, BETA, claude-only capability that adopts Claude Code's Workflow tool (the engine behind /effort ultracode) as an optional parallel-execution backend for the GSD loop. When the runtime exposes the Workflow tool and claude_orchestration.execution_backend resolves to 'workflow', execute-phase emits a generated Workflow script (waves -> parallel() barriers, plans -> agent({ agentType: 'gsd-executor', isolation: 'worktree' }), files_modified overlap -> separate sequential stages, resumeFromRunId wired to the phase run id, shared token budget) that composes the SAME gsd-executor agent and worktree isolation the inline path uses, restoring the wave parallelism the #853 backgrounded-agent nesting limitation forces inline on Claude Code. (The plan-checker and verifier remain inline until separately wired — this capability delivers the parallel-execution backend, not those gates.) Also folds the ultraplan plan-offload under one runtime gate (plan:* surface). On any runtime lacking the Workflow tool, or when the capability is disabled, behaviour is byte-identical to today (inline/manual dispatch). Detection + emission live in gsd-core/bin/lib/claude-orchestration.cjs (pure, fail-closed). Mirrors the existing gsd-ultraplan-phase BETA-isolation posture.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtimeCompat": {
"supported": [
"claude"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"commands": [
{
"family": "claude-orchestration",
"module": "claude-orchestration-command-router.cjs",
"router": "routeClaudeOrchestrationCommand",
"subcommands": [
"detect-backend",
"emit-workflow"
]
}
],
"activationKey": "claude_orchestration.enabled",
"config": {
"claude_orchestration.enabled": {
"type": "boolean",
"default": false,
"description": "Master toggle for the Claude orchestration capability. Default-off + BETA: the Workflow-tool execution backend and the ultraplan plan-offload surface are inert unless this is true. When false, loop behaviour is byte-identical to a non-Claude runtime (inline/manual dispatch)."
},
"claude_orchestration.execution_backend": {
"type": "enum",
"values": [
"auto",
"workflow",
"inline"
],
"default": "auto",
"description": "Which execute-phase dispatch backend to use when the capability is enabled. 'auto' (default) activates the Workflow backend only when the runtime is Claude AND the Workflow tool is detected AND the Agent SDK meets claude_orchestration.min_agent_sdk_version; otherwise it falls back to inline. 'workflow' forces the Workflow backend when the tool is present AND the Agent SDK meets the floor (still fails closed to inline if the tool is absent or the SDK is too old — the floor applies in both modes). 'inline' forces today's manual one-agent-per-message dispatch regardless of tool availability."
},
"claude_orchestration.min_agent_sdk_version": {
"type": "string",
"default": "0.3.149",
"description": "Minimum Agent SDK version required to activate the Workflow backend under execution_backend='auto'. Defaults to 0.3.149 (the release that introduced the Workflow tool). Raise to pin a higher floor; the detection seam fails closed to inline for any runtime reporting an older or unknown version."
}
},
"steps": [],
"contributions": [
{
"point": "execute:wave:post",
"into": "executor",
"fragment": {
"path": "fragments/execute-wave-post.md"
},
"produces": [],
"consumes": [
"PLAN.md"
],
"when": "claude_orchestration.enabled",
"onError": "skip"
},
{
"point": "plan:post",
"into": "planner",
"fragment": {
"path": "fragments/plan-post.md"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "claude_orchestration.enabled",
"onError": "skip"
}
],
"gates": []
}

View File

@@ -0,0 +1,64 @@
# Claude orchestration — Workflow execution backend (BETA)
> Injected at `execute:wave:post` `into: executor` only when
> `claude_orchestration.enabled` is true. Default-off; `onError: skip`.
## When this contribution is active
The Claude orchestration capability is **default-off and BETA**. It activates only
when ALL of the following hold:
1. `claude_orchestration.enabled` is `true` in `.planning/config.json`, AND
2. the active runtime is **Claude Code** (the Workflow tool is Claude / Agent
SDK-specific), AND
3. `claude_orchestration.execution_backend` resolves to `workflow` — either
explicitly, or via `auto` — **and** the Agent SDK version is
`>= claude_orchestration.min_agent_sdk_version` (default `0.3.149`). The SDK
floor applies in both `auto` and `workflow` modes (fail-closed: a pre-release
or older SDK never activates the preview backend).
Detection is fail-closed: any miss degrades to **inline, manual, one-agent-per-
message dispatch** — exactly today's behaviour. On a non-Claude runtime this
contribution is a no-op.
## What the executor does when the Workflow backend is active
Instead of the orchestrator fanning out one `Agent(subagent_type=gsd-executor,
isolation=worktree, run_in_background=true)` per message (which on Claude Code
cannot nest further subagents — #853 — and so degrades to sequential inline
execution), execute-phase **emits a generated Workflow script** and lets the main
loop orchestrate it:
- **waves → one or more sequential `parallel()` barriers** — each wave is a
barrier group; when plans within a wave share `files_modified`, they are split
into separate sequential stages within that wave's barrier (the next wave
still waits for the previous wave to complete).
- **plans → `agent(brief, { agentType: 'gsd-executor', isolation: 'worktree' })`**
— the SAME executor agent and worktree isolation the inline path uses, so the
produced `SUMMARY.md` and commits are identical.
- **`files_modified` overlap → separate sequential stages** — two plans that
touch the same file are placed in different stages within the wave (the same
overlap rule execute-phase already applies inline).
- **`resumeFromRunId`** — wired to the phase run id, so an interrupted phase
resumes without re-running completed plans.
- **`budget(tokens)`** — a shared token pool across the whole phase when the
orchestrator passes a `budgetTokens` value to `emitWorkflowScript` (it is a
function parameter, not a config key; the orchestrator decides the budget).
The emitter is a pure function exposed through the capability command surface:
`gsd-tools claude-orchestration emit-workflow --waves <manifest.json> --run-id <id>
[--phase-dir <dir>] [--budget <n>]` (or `require('gsd-core/bin/lib/claude-orchestration.cjs').emitWorkflowScript`
directly). It maps the phase's wave/plan manifest to the Workflow script string
and never invokes the Workflow tool itself; the orchestrator runs the emitted
script. Detection is resolved by the orchestrator calling the pure
`detectWorkflowBackend` with the LIVE host descriptor (the CLI
`gsd-tools claude-orchestration detect-backend` is a simulation harness that
assumes a capable host unless `--no-nested-dispatch` is passed — it does not probe
the real runtime; the orchestrator supplies the real descriptor).
## Fallback contract
If detection resolves to `inline` (tool absent, SDK too old, runtime not Claude,
or the capability disabled), execute-phase MUST proceed with the standard inline
wave dispatch. The executor MUST NOT assume parallelism, a shared budget, or
resume-from-run-id semantics in that mode.

View File

@@ -0,0 +1,28 @@
# Claude orchestration — ultraplan plan-offload ownership (BETA)
> Injected at `plan:post` `into: planner` only when
> `claude_orchestration.enabled` is true. Default-off; `onError: skip`.
## Ownership declaration
The `gsd-ultraplan-phase` plan-offload surface (offloading GSD's plan phase to
Claude Code's ultraplan cloud) is **owned by this capability**, not by a
standalone BETA skill. Both surfaces share one runtime gate
(`claude_orchestration.enabled`), one BETA boundary, and one Claude-Code-only
detection seam.
## When the planner should consider ultraplan offload
When this contribution is active (capability enabled, Claude Code runtime), the
planner MAY offer the `/gsd-ultraplan-phase` path as an alternative to local
`/gsd-plan-phase` for phases where cloud-assisted planning adds value. This is
advisory, not mandatory — the stable local planner remains the default.
## Fallback contract
If the capability is disabled, or the runtime is not Claude Code, ultraplan
offload is **not surfaced** and the planner proceeds with the standard local
`/gsd-plan-phase`. The `gsd-ultraplan-phase` command itself remains installed
(its own runtime gate already no-ops on non-Claude runtimes); this contribution
only governs whether the capability manifest advertises it as part of the
orchestration surface.

View File

@@ -1,7 +1,7 @@
{
"id": "claude",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Claude Code",
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
"tier": "core",
@@ -17,6 +17,7 @@
"CLAUDE_CONFIG_DIR"
]
},
"localConfigDir": ".claude",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -61,6 +62,44 @@
"Stop",
"PreCompact",
"FileChanged"
]
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"attributionSource": "settings-json-commit",
"authorsCanonicalWorkflow": true,
"localInstallStyle": "legacy-flat",
"permissionsSchema": "claude",
"settingsFileByScope": {
"local": "settings.local.json",
"global": "settings.json"
},
"sourceMarkerFile": ".gsd-source",
"agentFrontmatterExtensions": [
"effort"
],
"ownsClaudePaths": true,
"nativeModelAliases": true,
"skillsGlobalOnboarding": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": "global",
"hyphenNameAgentBody": true,
"reviewerCli": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "cline",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Cline",
"description": "Cline (VS Code extension) — global-only nested-skill layout; cline-rules hook surface (.clinerules); no hook events emitted; tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"CLINE_CONFIG_DIR"
]
},
"localConfigDir": ".cline",
"configFormat": "markdown-dir",
"artifactLayout": {
"global": [
@@ -38,6 +39,31 @@
"installSurface": "cline-rules",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"frontmatterDialect": "cline",
"skipSharedHooksInstall": true,
"localTargetIsProjectRoot": true,
"clineRulesSurface": true,
"localCommandsViaRules": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "code-review",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Code review",
"description": "Source-file code review and review-fix workflow support for completed execution work.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "codebuddy",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"CODEBUDDY_CONFIG_DIR"
]
},
"localConfigDir": ".codebuddy",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -35,6 +36,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
@@ -53,6 +62,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},
@@ -64,6 +81,31 @@
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"reportCommandsDir": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "codex",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "OpenAI Codex CLI",
"description": "OpenAI Codex CLI — shell-var command style; per-agent sandbox tiers; config.toml + hooks.json hook surface; tier-1 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"CODEX_HOME"
]
},
"localConfigDir": ".codex",
"configFormat": "toml",
"artifactLayout": {
"global": [
@@ -26,7 +27,8 @@
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill"
"converter": "convertClaudeCommandToCodexSkill",
"home": ".agents"
}
],
"local": [
@@ -36,7 +38,8 @@
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill"
"converter": "convertClaudeCommandToCodexSkill",
"home": ".agents"
}
]
},
@@ -48,6 +51,35 @@
"installSurface": "codex-toml",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"reapplyCommand": "$gsd-update --reapply",
"tomlConfigInstall": true,
"cleanupSkillSidecars": true,
"agentTomlFiles": true,
"frontmatterDialect": "codex",
"reviewerCli": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "copilot",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "GitHub Copilot",
"description": "GitHub Copilot (VS Code) — markdown config format; copilot-inline hook surface; no hook events emitted; flat skill nesting (unconfirmed recursive loader); tier-2 support.",
"tier": "core",
@@ -18,6 +18,7 @@
"COPILOT_HOME"
]
},
"localConfigDir": ".github",
"configFormat": "markdown",
"artifactLayout": {
"global": [
@@ -28,6 +29,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
@@ -38,6 +47,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},
@@ -48,6 +65,29 @@
"installSurface": "copilot-instructions",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "undocumented"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"agentFileExtension": ".agent.md",
"skipSharedHooksInstall": true,
"noPathRewrite": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "cursor",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Cursor",
"description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"CURSOR_CONFIG_DIR"
]
},
"localConfigDir": ".cursor",
"configFormat": "none",
"artifactLayout": {
"global": [
@@ -35,6 +36,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
@@ -53,6 +62,14 @@
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCursorCommand"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},
@@ -64,6 +81,41 @@
"installSurface": "cursor-hooks-json",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 2,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"reapplyCommand": "gsd-update --reapply (mention the skill name)",
"frontmatterDialect": "cursor",
"hooksJsonSurface": true,
"skipSharedHooksInstall": true,
"reportCommandsDir": true,
"skipUpdateBannerCommand": true,
"skipSettingsUi": true,
"managedHookEvents": [
"sessionStart",
"postToolUse",
"preToolUse",
"stop",
"subagentStart",
"subagentStop"
],
"reviewerCli": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "drift",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Drift detection gates",
"description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.",
"tier": "full",

View File

@@ -0,0 +1,81 @@
{
"id": "external-job",
"role": "feature",
"version": "1.7.0",
"title": "Async external-job scheduler adapter",
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies execute:wave:pre, but execute-phase.md only dispatches execute:wave:post today (wave:pre is declared in the loop host contract but not rendered); wiring wave:pre dispatch is a core-loop change #1164 explicitly puts out of scope, so this capability registers at wave:post and the executor honors the runtime_budget classification guidance before running any tagged task. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"external_job.enabled": {
"type": "boolean",
"default": false,
"description": "Master toggle for the async external-job producer capability. Default-off: the core loop consumes manifests whether or not this is on, but no manifest is ever written unless an executor opts in here."
},
"external_job.backend": {
"type": "enum",
"values": [
"slurm"
],
"default": "slurm",
"description": "Scheduler backend. SLURM is the first adapter; the field is the pluggability seam for future backends (LSF, PBS, Kubernetes batch). Core never interprets this value."
},
"external_job.artifact_dir": {
"type": "string",
"default": "Artifacts/jobs",
"description": "Root for per-job artifact directories (e.g. Artifacts/jobs/<jobid>/). Avoids fixed log paths and hardcoding a cluster/project layout. Surfaced by the adapter (`slurm-adapter.cjs submit` prints the resolved value); override via GSD_EXTERNAL_JOB_ARTIFACT_DIR."
},
"external_job.submit_timeout_ms": {
"type": "number",
"default": 30000,
"description": "Hard timeout (ms) for the scheduler submit subprocess (e.g. sbatch). Bounded per CLAUDE.md unbounded-subprocess policy. Read by the adapter (env GSD_SLURM_SUBMIT_TIMEOUT_MS overrides)."
},
"external_job.poll_timeout_ms": {
"type": "number",
"default": 15000,
"description": "Hard timeout (ms) for the scheduler poll subprocess (squeue, with sacct fallback). Read by the adapter (env GSD_SLURM_POLL_TIMEOUT_MS overrides)."
}
},
"steps": [],
"contributions": [
{
"point": "execute:wave:post",
"into": "executor",
"fragment": {
"path": "fragments/execute-wave-post.md"
},
"produces": [
".planning/async-jobs/<job>.json"
],
"consumes": [
"PLAN.md"
],
"when": "external_job.enabled",
"onError": "skip"
},
{
"point": "plan:post",
"into": "planner",
"fragment": {
"path": "fragments/plan-post.md"
},
"produces": [],
"consumes": [],
"when": "external_job.enabled",
"onError": "skip"
}
],
"gates": []
}

View File

@@ -0,0 +1,44 @@
<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).
Why wave:post, not wave:pre (#1164 refinement A): execute-phase.md only
dispatches execute:wave:post today — wave:pre is declared in the loop host
contract but not rendered. Wiring wave:pre dispatch is a core-loop change
#1164 puts out of scope. The executor therefore honors this classification
guidance BEFORE running any task tagged <runtime_budget>long_compute</runtime_budget>,
whether in the current or a subsequent wave, and externalizes rather than
blocking the turn. -->
## Externalize long-running compute (async external job)
If the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`
(see the plan-phase fragment), do **not** run it in the foreground — it would
block the agent turn for hours. Instead externalize it and record a durable
half-state:
1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run
normally. `unknown` requires a first-health check and a soft-review deadline
before consuming the child timeout. `long_compute` (>30–60 min) is
externalized.
2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):
```bash
node scripts/slurm-adapter.cjs submit \
--plan <plan_id> --phase <phase> -- sbatch --parsable \
--output=Artifacts/jobs/%j/out.log ./run.sh
```
The helper writes `.planning/async-jobs/<job>.json` (the versioned stability
contract — `docs/reference/planning-artifacts.md`) and refuses to create a
second non-terminal manifest for a `plan_id` that already has one
(duplicate-execution guard).
3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**
and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job
reaches a terminal state and its `expected_artifacts` are verified.
4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and
`pause-work` reconcile against the manifest and never re-dispatch the plan.
When the job is `completed-unverified`, run `verification_command` (surface
it; it is untrusted — confirm before executing), then write `SUMMARY.md` and
close the plan.
Manifest commands cross a trust seam: a Capability (or anything that can write
`.planning/`) produces them; the core loop consumes them. Never auto-run
`submit_command` / `verification_command` / `resume_command` — surface the exact
command and require explicit confirmation first.

View File

@@ -0,0 +1,24 @@
<!-- external-job capability — plan:post fragment, injected into the planner (#1164). -->
## Tag runtime budgets on long tasks
For every `<task>` likely to exceed ~2 minutes of real compute, emit a
`<runtime_budget>` child element so execute can classify it:
- `<runtime_budget>quick</runtime_budget>` — under ~2 min; runs normally.
- `<runtime_budget>medium</runtime_budget>` — ~2–30 min; foreground, but with
progress expectations.
- `<runtime_budget>unknown</runtime_budget>` — runtime not yet characterized;
execute must run a first-health check and set a soft-review deadline before
trusting the child timeout. Define a progress signal and an abort condition.
- `<runtime_budget>long_compute</runtime_budget>` — legitimately over ~30–60 min
(HPC solver, model training, large simulations). Execute must **externalize**
this as an async external job (see the execute:wave:post fragment) rather than
blocking the agent turn.
For any `long_compute` task, also declare the async contract the executor will
need: the `submit_command`, the `expected_artifacts` the job must produce, and
the `verification_command` that proves the output before the plan can close.
Do not hardcode a cluster account, partition, or project path — the planner
never knows the scheduler layout; it declares the contract, the executor's
adapter fills the backend specifics.

View File

@@ -1,7 +1,7 @@
{
"id": "gap-analysis",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Post-planning gap analysis",
"description": "Proactive, non-blocking post-planning coverage report. After all PLAN.md files are generated, cross-references every REQ-ID and D-ID from REQUIREMENTS.md and CONTEXT.md against plan bodies. Emits a Source | Item | Status table. Does not block phase advancement.",
"tier": "standard",

View File

@@ -1,57 +0,0 @@
{
"id": "gemini",
"role": "runtime",
"version": "1.6.1",
"title": "Gemini CLI",
"description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".gemini",
"env": [
"GEMINI_CONFIG_DIR"
]
},
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands/gsd",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands/gsd",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
]
},
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "gemini",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"BeforeAgent",
"AfterAgent",
"BeforeModel"
]
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "graphify",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Knowledge graph",
"description": "Build, query, and inspect the project knowledge graph in `.planning/graphs/`; exposes graphify CLI subcommands (build, query, status, diff) and the /gsd-graphify skill.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "hermes",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Hermes Agent",
"description": "Hermes Agent (NousResearch) — skills nest under skills/gsd/ category bucket; nested skill layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"HERMES_HOME"
]
},
"localConfigDir": ".hermes",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -48,6 +49,41 @@
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"extensionEvents": "hermes",
"hostBehaviors": {
"skillFrontmatterVersion": true,
"skillsManifestPrefix": "skills/gsd/",
"trackCategoryDescription": true,
"writeCategoryDescription": true,
"reportSkillsCount": true,
"legacyCommandsGsdCleanup": true,
"brandingRewrites": {
"CLAUDE.md": "HERMES.md",
"Claude Code": "Hermes Agent",
".claude/": ".hermes/"
},
"reapplyCommand": "gsd-update --reapply (mention the skill name)",
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true
},
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-programmatic",
"dispatch": {
"namedDispatch": false,
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "intel",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Codebase intelligence",
"description": "Code-intelligence store for codebase querying, diff, snapshot, and API-surface extraction; exposes `gsd-tools intel` subcommands (query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface) and backs `/gsd-map-codebase` and `gsd-intel-updater`.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "kilo",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Kilo Code",
"description": "Kilo Code — XDG-based config dir; global skills at ~/.kilo/skills (separate from XDG config); flat command/ + skills artifact layout; no lifecycle hook registration; tier-2 support.",
"tier": "core",
@@ -24,6 +24,7 @@
"env": []
}
},
"localConfigDir": ".kilo",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -65,11 +66,43 @@
},
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "kilo",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": false,
"permissionWriter": "kilo",
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": -1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": false
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"attributionConfigResolver": "kilo",
"flatCommandDir": "command",
"combinedFamilyInstall": true,
"frontmatterDialect": "kilo",
"nativePlugin": {
"dir": "plugins",
"file": "gsd-core.js",
"source": ".kilo/plugins/gsd-core.js"
},
"skipUpdateBannerCommand": true,
"skipSharedHooksInstall": true
}
}
}

View File

@@ -1,9 +1,9 @@
{
"id": "kimi",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Kimi CLI",
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.",
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; native config.toml [[hooks]] bus at ~/.kimi/config.toml; background dispatch; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
@@ -22,6 +22,7 @@
],
"probeExists": "skills"
},
"localConfigDir": ".kimi-code",
"configFormat": "none",
"artifactLayout": {
"global": [
@@ -45,12 +46,43 @@
"local": []
},
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"hooksSurface": "kimi-hooks-toml",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": true
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python"
},
"hostBehaviors": {
"reapplyCommand": "/skill:gsd-update --reapply",
"localInstallDeferred": true,
"verificationStyle": "kimi",
"agentManifestStyle": "kimi-nested",
"doneBannerStyle": "kimi-agent-file",
"skipSharedHooksInstall": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "mempalace",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "MemPalace memory",
"description": "Cross-session, cross-project memory: deliberate recall before discuss/plan and verbatim capture + temporal-KG sync at phase boundaries, via the MemPalace MCP server and CLI.",
"tier": "full",
@@ -37,7 +37,7 @@
"replace"
],
"default": "augment",
"description": "How MemPalace relates to GSD native memory. Only 'augment' (additive) is implemented today; 'kg_backend' and 'replace' are forward-declared (routing seam not yet built) and currently behave as 'augment'."
"description": "How MemPalace relates to GSD native memory during recall/capture. 'augment' (default): the palace is an additive layer alongside .planning/graphs/ — native memory stays authoritative. 'kg_backend': knowledge-graph queries resolve against the palace's temporal KG as the primary source, with .planning/graphs/ as fallback. 'replace': recall resolves through the palace as the source of truth, native memory as fallback. Every mode stays onError:skip and default-resilient — an unreachable palace degrades to native memory, and GSD keeps writing .planning/graphs/ so no memory is lost. Cross-mode migration of existing .planning/graphs/ into the palace is a separate, not-yet-implemented concern."
},
"mempalace.wing": {
"type": "string",

View File

@@ -13,8 +13,8 @@ For each confirmed bug/issue resolved in this wave:
1. **Resolve the wing** (`mempalace.wing`, else `project_code`, else project dir) and target `room: problems`.
2. **Dedupe first.** Call `mempalace_check_duplicate` (interactive) before filing so re-runs don't create duplicate drawers.
3. **File the drawer verbatim.** Store the problem statement and its fix as a drawer in `room: problems` — interactive: `mempalace_add_drawer`; headless: `mempalace mine` / `mempalace hook run`. Include provenance (`source_file`, phase id).
3. **File the drawer verbatim.** Store the problem statement and its fix as a drawer in `room: problems` — interactive: `mempalace_add_drawer`; headless: stage the artifact under the `problems/` folder and run `mempalace mine` (no `--room` flag — see [CLI reference](https://mempalaceofficial.com/reference/cli.html); room assignment is via `detect_room()` folder-path match per the [mining guide](https://mempalaceofficial.com/guide/mining.html); use the same staging pattern documented in `gsd-mempalace-capture` Step 3). Include provenance (`source_file`, phase id).
4. **Mirror the KG fact** when `mempalace.mirror_kg` is on: add `(<bug>, fixed_by, <fix>)` with `valid_from` = the phase date via `mempalace_kg_add`.
5. **Mode awareness.** Only `augment` is currently wired: the fact is an *additive* mirror alongside `.planning/graphs/` (never a replacement). `kg_backend`/`replace` are forward-declared and behave as `augment` today.
5. **Mode awareness** (`mempalace.memory_mode`). Under `augment` the fact is an *additive* mirror alongside `.planning/graphs/`. Under `kg_backend`/`replace` the palace is the *authoritative* store for the fact; GSD still writes `.planning/graphs/` through its normal graphify, so an unreachable palace never loses it.
Captures are idempotent: deterministic drawer IDs + `check_duplicate` mean re-running the wave re-files the same content without duplication. On any error, skip and let the wave complete normally.

View File

@@ -16,7 +16,7 @@ Otherwise — before gathering new context, surface what you already know. This
3. **Targeted recall.** Search the palace for prior work on this phase's topic:
- Interactive → `mempalace_search(query=<phase topic>, wing=<wing>)` and, when `mempalace.mirror_kg` is on, `mempalace_kg_query` / `mempalace_kg_timeline` for decision facts and their validity windows.
- Headless → `mempalace search "<phase topic>" --wing <wing>`.
4. **Mode awareness.** Only `augment` is currently wired: always treat the palace as an *additional* recall layer on top of GSD's native memory — never skip `.planning/graphs/` or STATE. `kg_backend`/`replace` are forward-declared and behave as `augment` today.
4. **Mode awareness** (`mempalace.memory_mode`). `augment` (default): the palace is an *additional* layer — never skip `.planning/graphs/` or STATE. `kg_backend`: query the palace's temporal KG as the *primary* graph source, with `.planning/graphs/` as fallback. `replace`: resolve recall *through the palace as the source of truth*, native memory as fallback. In every mode an unreachable palace falls back to native memory — recall never blocks.
5. **Surface, don't dump.** Fold the top relevant drawers, decisions, patterns, and *surprises* into the discussion as prior context — cite drawer/fact provenance. Do not paste raw search output.
If any MemPalace call errors or times out, skip the rest of recall and proceed with discussion as normal.

View File

@@ -1,7 +1,7 @@
{
"id": "nyquist",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Nyquist validation",
"description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "opencode",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",
@@ -19,6 +19,7 @@
"XDG_CONFIG_HOME"
]
},
"localConfigDir": ".opencode",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -60,11 +61,46 @@
},
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "opencode",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": "opencode",
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"attributionConfigResolver": "opencode",
"flatCommandDir": "command",
"combinedFamilyInstall": true,
"frontmatterDialect": "opencode",
"nativePlugin": {
"dir": "plugins",
"file": "gsd-core.js",
"source": ".opencode/plugins/gsd-core.js"
},
"skipHomePrefixSubstitution": true,
"skipSettingsUi": true,
"skipUpdateBannerCommand": true,
"skipCodexSkillsManifest": true,
"reviewerCli": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "pattern-mapper",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Pattern mapping",
"description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.",
"tier": "full",

View File

@@ -0,0 +1,60 @@
{
"id": "pi",
"role": "runtime",
"version": "1.7.0",
"title": "pi",
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtime": {
"configHome": {
"kind": "dot-home-nested",
"name": "agent",
"parent": ".pi",
"env": []
},
"localConfigDir": ".pi",
"configFormat": "none",
"artifactLayout": {
"global": [],
"local": []
},
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "pi",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-programmatic",
"dispatch": {
"namedDispatch": false,
"nested": false,
"maxDepth": 0,
"background": false,
"backgroundDispatch": false,
"subagentToolkit": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "session-log-append",
"transport": "native-extension",
"runtime": "bun"
},
"hostBehaviors": {
"nativePlugin": {
"dir": "extensions",
"file": "gsd.cjs",
"source": "pi/gsd.cjs"
},
"pluginOnlyInstall": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "profile-pipeline",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Developer profiling pipeline",
"description": "Developer behavioral profiling from Claude Code session history; scans session JSONL files, extracts and samples user messages, and generates profile artifacts (USER-PROFILE.md, dev-preferences.md, CLAUDE.md sections). Exposes eight `gsd-tools` commands: scan-sessions, extract-messages, profile-sample (pipeline phase) and write-profile, profile-questionnaire, generate-dev-preferences, generate-claude-profile, generate-claude-md (output phase). Backs the /gsd-profile-user skill and gsd-user-profiler agent.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "qwen",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Qwen Code",
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"QWEN_CONFIG_DIR"
]
},
"localConfigDir": ".qwen",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
@@ -27,6 +28,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
],
"local": [
@@ -37,6 +46,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
]
},
@@ -51,7 +68,38 @@
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact"
]
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"skillPriorityFrontmatter": true,
"brandingRewrites": {
"CLAUDE.md": "QWEN.md",
"Claude Code": "Qwen Code",
".claude/": ".qwen/"
},
"legacyCommandsGsdCleanup": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true,
"reviewerCli": true
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "research",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Phase research",
"description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.",
"tier": "standard",

View File

@@ -1,7 +1,7 @@
{
"id": "schema-gate",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Schema push detection gate",
"description": "Detects ORM schema-relevant files in the phase scope during planning and injects a mandatory [BLOCKING] schema push task into the plan. Prevents false-positive verification where build/types pass because TypeScript types come from config, not the live database.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "security",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Security enforcement",
"description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "tdd",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "Test-driven development",
"description": "Injects TDD heuristics into the planner and enforces RED/GREEN gate compliance on type:tdd plans after execution. Owns workflow.tdd_mode; the --tdd CLI flag is the ephemeral override.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "trae",
"role": "runtime",
"version": "1.6.1",
"version": "1.7.0",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",
@@ -17,6 +17,7 @@
"TRAE_CONFIG_DIR"
]
},
"localConfigDir": ".trae",
"configFormat": "none",
"artifactLayout": {
"global": [
@@ -27,6 +28,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
@@ -37,6 +46,14 @@
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},
@@ -47,6 +64,27 @@
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": []
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented"
},
"modelMode": "passive",
"hookBus": "engine",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"hostBehaviors": {
"skipSharedHooksInstall": true,
"soloStageMetadata": "workflow"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"id": "ui",
"role": "feature",
"version": "1.6.1",
"version": "1.7.0",
"title": "UI design contracts",
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
"tier": "full",

Some files were not shown because too many files have changed in this diff Show More