Files
msd-core/gsd-core/bin/lib/capability-registry.cjs
0xdhx 88b5775dc8 enhance(#4223): default-off interaction capture for gsd-ui-auditor via the chrome-devtools CLI (#4477)
* enhance(#4223): default-off interaction capture for gsd-ui-auditor via the chrome-devtools CLI

gsd-ui-auditor is chartered to audit interaction and handed a capture
driver with no interaction verb: `npx playwright screenshot` cannot
click, fill, hover, press or snapshot, so a hover state, an open menu,
a focus ring or a form's validation state never appears in its
evidence and every Experience Design finding degrades to code reading.

Implements the shape approved at triage, not a new capability:

- capabilities/ui/capability.json declares `workflow.ui_interaction_capture`
  (boolean, default false) on the capability that already owns the
  auditor (ADR-894 one-owner invariant); capability-registry.cjs regenerated.
- gsd-core/workflows/ui-review.md reads the key through gsd_run and hands
  it to the auditor as `interaction_capture:` in the spawn <config> block —
  the auditor carries no gsd_run resolver, so the key travels by value.
- agents/gsd-ui-auditor.md gains an anchored interaction-capture section
  AFTER the static block. With the key on and a Chrome binary resolved it
  starts the `chrome-devtools` CLI (chrome-devtools-mcp, floor ^1.8.0) on
  an --isolated profile, opens the dev URL the static block reached,
  takes the a11y snapshot for element uids, captures the baseline and a
  Tab focus-ring state, drives the UI-SPEC's interactive components, saves
  console output, and stops the daemon unconditionally. Key off, no dev
  server, or no Chrome: one status line, and the Playwright-only static
  path runs exactly as before — the static fence is untouched.

Needs only Bash: no MCP server, no tools: change. Chromium-only by
nature; Firefox/WebKit stay on Playwright. `wait_for` is MCP-only, so
readiness is polled through evaluate_script.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* test(#4223): bind the interaction-capture shape and containment

- manifest, generated registry, config schema and config-set/loadConfig
  all know workflow.ui_interaction_capture as a default-off boolean, and
  hand-written non-booleans fall to the slice default
- the orchestrator reads the key and hands it down; the auditor never
  grows a gsd_run dependency
- the static fence stays Playwright-only and the interaction fence
  chrome-devtools-only, so key-off is today's path
- the interaction fence runs under bash with a stub driver on PATH: key
  off / absent / no dev server / no Chrome invoke nothing; the happy path
  starts first and stops last on the [selected] pageId with the documented
  flags; a failed capture is removed and not counted; new_page and start
  failures still honour the stop-only-if-started rule; CHROME_BIN and
  CHROME_DEVTOOLS_MCP_VERSION overrides flow through
- docs/CONFIGURATION.md row shape; registered in the docs-guard lane

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* docs(#4223): document workflow.ui_interaction_capture and its how-to

- docs/CONFIGURATION.md: one row in the workflow.* table, default-off
- docs/AGENTS.md: the gsd-ui-auditor entry names the key and what the
  interaction-capture section adds, skips and never claims
- docs/how-to/enable-ui-interaction-capture.md: turn it on, read the
  `**Interaction captures:**` outcomes, what it does not do, turn it off
- docs/README.md: index the how-to beside live-DOM verification

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* chore(#4223): add changeset

Added-type fragment; pr: carries the issue number until the PR exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* enhance(#4223): use the /gsd:ui-review namespace form in the auditor's prose

Claude-facing source (agents/, workflows/) uses the /gsd:<cmd> namespace;
the hyphen form is retired there and the slash-command-namespace guard
rejects it. docs/ keep the hyphen form by convention.

Emitted-Drift-Ack-Growth: gsd-ui-auditor.md — #4223: the anchored default-off interaction-capture section (prose + one bash fence) appended after the static Playwright block inside <screenshot_approach>, plus one `**Interaction captures:**` line in each of the two report templates, one completion-checklist line and one Step-3 sentence. The static fence is byte-identical to next; nothing was removed or reordered.
Emitted-Drift-Ack-Growth: ui-review.md — #4223: a two-line config-get read + true/false normalisation in step 0 and one `interaction_capture:` line in the spawn <config> block with a three-line note on why the value travels by prompt. No step, gate, or dispatch shape changed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* enhance(#4223): per-run daemon session, bounded navigation, and step failures that count

Three findings from the pre-file adversarial review of the interaction
fence, folded in:

- `--sessionId <epoch>-<pid>` on every driver call. `start` restarts
  whatever daemon shares its session and --isolated isolates only the
  browser profile, so two concurrent audits — or an audit beside the
  operator's own CLI daemon — would otherwise stop each other. The CLI
  accepts hex and dashes only; the id is validated by the test stub.
- `new_page --timeout 30000`: the one verb that takes a bound, placed
  before every verb that does not, so a hung page is caught first.
- a failed take_snapshot or press_key now increments the failure count
  and is named on stdout; two clean screenshots can no longer read as
  `0 failed` after the step that gives the interactions their uids failed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* enhance(#4223): subshell-unique session id, CRLF-safe page-id parse, stale-snapshot removal

Second review round, both reviewers:

- session id is `<epoch>-<BASHPID>-<RANDOM>`: `$$` is inherited by a
  subshell, so two audits forked from one parent in the same second
  shared an id and could stop each other's daemon (driven by the reviewer)
- `tr -d '\r'` before the `[selected]` parse so a CRLF-emitting driver
  under Git Bash still matches the `$` anchor, and `|| true` on the
  assignment so a failed new_page cannot abort the block under
  `set -e -o pipefail` before the unconditional stop
- a failed take_snapshot removes any snapshot.txt it left or inherited
  from a reused directory, so stale uids never drive the interactions
- `<config>` placeholder is `{interaction_capture}`, lowercase like its
  `{phase_dir}` / `{padded_phase}` siblings — the block is a prompt
  template, not a bash heredoc
- how-to: the `not captured` row no longer claims the daemon started

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* enhance(#4223): check new_page's exit status before parsing its output; regression cases for the edges

Third review round:

- a new_page that prints a page line and then exits non-zero is a failed
  navigation, not a page id: the exit status is checked in an `if` before
  the output is parsed (driven by the reviewer against the previous
  `|| true`, which masked exactly that)
- regression cases for what the last two rounds added: CRLF driver
  output, a stale snapshot removed on failure, partial-output new_page
  failure, and the whole fence under `set -e -o pipefail` (both the
  failed-navigation path and the happy path)
- the harness whitelist gains `date`; the session-id assertion now
  requires all three parts, so a silently empty epoch cannot hide again

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* enhance(#4223): keep gsd-ui-auditor under the DEFAULT-tier size cap; changeset pr placeholder

- the three review folds pushed agents/gsd-ui-auditor.md to 25179 bytes,
  over the 24576-byte hard cap tests/agent-size-budget.test.cjs enforces;
  the interaction section's comments are tightened to the same content
  in fewer bytes (23559 now). No bash changed — the fence's own tests and
  the real-browser run are unchanged.
- .changeset/vivid-yaks-fly.md carries the policy placeholder `pr: 0`, which
  the post-create backfill rewrites to the PR's own number.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* chore(#4223): set changeset fragment pr to 4477

* test(#4223): compare the fence's status path with the separator the fence uses

On the windows-latest lane the happy-path case failed on `\interaction` vs
`/interaction` alone: the fence joins "$SCREENSHOT_DIR/interaction" with a
literal slash, and the assertion built its expectation with path.join. Every
other case in the file passed on that lane, including the CRLF and
errexit/pipefail ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PanfY8KaLb4RVVubcUoGP6

* test(#4223): drop the inert file-header allow-test-rule marker

Review round 1 on #4477: the `source-text-is-the-product` marker sat at
line 2, outside no-source-grep's 8-line lookahead of every readFileSync
site (the first is ~60 lines down), so it suppressed nothing. It was also
unnecessary: every read in this file is a .md/.json path, which the rule
does not trigger on. Deleted rather than relocated — there is no site to
relocate it to. Negative control: `eslint` on the file is clean without it.

* chore(#4223): regenerate the platform-conformance-tier lists for the new test

Review round 3 on #4477. `next` gained chore(#4591)'s platform-conformance-tier
gate after this branch opened; its two committed lists must name every file
under tests/, and this PR's tests/ui-interaction-capture.test.cjs had never
been in them. Once the branch was updated against next the lists were stale
and three jobs went red on head 575667dd: lint-tests (gen-platform-conformance-tier
--check), conformance test (macos-latest) at 546 !== 547, and shard 1/3's
fragment-single-edit-propagation, which sees the same staleness as regen:derived
touching files beyond the fragment edit under test.

Regenerated with the repo's own generators, no hand-editing. The general tier
goes 546 -> 547 and the macOS tier 196 -> 197, each by exactly this one entry;
both --check arms are clean. Verified the red is this PR's own file and not
base drift: at upstream/next both generators report "list matches" (546 / 196),
and our committed copies were byte-identical to next's before this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CBQTeGX1JYHF5DRWp4wvZ

* fix(#4223): bound, confine and trap the chrome-devtools driver fence

Round 4 — three findings in one fence, interleaved on the same lines, so one
commit:

- Every driver call is time-bounded. `cdt <ceiling> <verb>` runs the client as a
  background job in its own process group (`set -m`) under a watchdog that kills
  the whole group at the ceiling — TERM, then KILL two seconds later. One pid is
  not enough: npm forwards SIGTERM only to its direct child, so killing `npx`
  alone leaves the client holding the fence's stdout and a `$(cdt … new_page)`
  capture blocked past the ceiling (driven against a real npx tree by the
  round's adversarial review; the pid-only first cut of this commit had exactly
  that hole). The watchdog is an exec'd bash (`"$BASH" -c`), never a `( … )`
  subshell: a subshell inherits bash's saved copies of the caller's stdio (the
  fds ≥10 a function-level `>/dev/null` redirect leaves behind) and holds them
  open, so a runner waiting for EOF waited out the whole 60 s ceiling whenever a
  watchdog outlived its kill — measured as the intermittent 30 s test run the
  review flagged; 0/60 after. It polls the job's process GROUP (`kill -0 --
  -pgid`, every 0.1 s) and stands down by itself once the group is empty;
  nothing ever signals it. The group, not the leader pid: a child can outlive
  the leader while holding the `$(cdt … new_page)` pipe, and a leader-pid poll
  stood down at once and left the substitution open-ended (driven by the
  round's adversarial review at 6× the ceiling; a pgid cannot be reused while
  any member lives, which a bare pid can). The daemon `start` launches is
  spawned detached (its own session) and never in that group. Two platforms
  forced the never-signalled shape. Under bash 3.2.57 the earlier `trap … TERM;
  sleep & wait $!` form ignored its TERM in 3 of 300 fast calls and slept out
  the whole ceiling — CI's macos job hanging 30 s right after `start`. On Git
  Bash a signal to a watchdog still starting up hung the fence's `wait` for it:
  18 of 20 fence tests at the harness's 30 s cap in 3 of 3 full-file runs,
  while a fence slowed by xtrace, or three tests run alone, never hit it (a
  startup race; the mechanism is not pinned further). Polling: 0/300 slow calls
  and 0 orphaned sleeps under 3.2.57 and 5.2, the fence suite 20/20 in 3 of 3
  full-file runs on Git Bash 5.2.37 (fractional `sleep 0.1`: driven on GNU,
  msys and busybox sleep; BSD sleep documents it). A clock that cannot launch
  (`sleep … || exit 0`) stands the watchdog down rather than firing at once
  and killing a healthy call — by design that leaves a hung call unbounded,
  the pre-round-4 behaviour, instead of failing a healthy one. A hung call
  returns once its group is gone: at the ceiling, plus up to the 2 s
  TERM-to-KILL grace. The KILL after the grace is sent only to a group that
  is still alive: a pgid freed during the grace can be reused, and an
  unconditional KILL could hit an unrelated group (the round's review).
  `start` (npx fetch + Chrome launch) gets CHROME_DEVTOOLS_START_TIMEOUT
  (180 s), every verb CHROME_DEVTOOLS_STEP_TIMEOUT (60 s). timeout(1) is absent
  on macOS and this agent carries no gsd-tools resolver, hence a bash watchdog
  rather than either.
- --allowUnrestrictedPaths -> --workspace "$INTERACTION_DIR": the driver may
  write under the run's interaction/ directory and nowhere else. Relative, like
  every --filePath (unchanged from rounds 1-3): the daemon resolves both against
  one cwd (chrome-devtools-mcp 1.9.0 spawns it with cwd: process.cwd() and
  path.resolve()s both), and a relative path needs no dialect translation — an
  absolute `pwd -P` path is an msys path on Git Bash, which a Windows-native
  daemon cannot resolve (CI's windows conformance shard caught the first cut).
  --workspace is a 1.9.0 flag (absent from 1.8.0's `start --help`, verified),
  so the documented floor moves from ^1.8.0 to ^1.9.0, where
  --allowUnrestrictedPaths is deprecated.
- `stop` is owed by an EXIT trap after a successful `start`, not by position
  (it replaces any earlier EXIT trap — none exists in this file); the explicit
  call keeps it in order, a flag makes the trap a no-op afterwards, and only the
  shell that installed the trap may act: a subshell copy of the fence state
  carries CDT_STARTED=1 and, under a timing race CI's ubuntu job hit (reproduced
  locally at 3/40 under load: the second `stop` came from a subshell pid, never
  main), issued a second `stop`. The identity is `$(exec /bin/sh -c 'echo
  "$PPID"')`, not $BASHPID — macOS ships bash 3.2, where BASHPID does not exist
  and CI's macos conformance job showed the guard comparing empty to empty. The
  fence was driven under bash 3.2.57 for the injected-subshell, errexit
  failed-new_page, errexit failed-resize, hung-start, hung-new_page and happy
  paths. A failed resize_page is a counted failed step now, not the one bare
  command an errexit runner could abort on.

Prose in the section is tightened to pay for the mechanism: 23559 -> 24517
bytes against the 24576 DEFAULT-tier cap.

Tests: the stub driver hangs as a real child tree (sh waiting on a child that
holds stdout — never an exec), so a pid-only kill fails the new
aHungNewPageWhoseChildHoldsStdoutIsStillCutOffAtTheCeiling test (negative-
controlled: it blocks for the harness's whole cap on the old wrapper). A hung
start and a hung capture are cut off within ceiling + grace + slack and still
reach stop; an injected bare failure under errexit reaches stop through the
trap, exactly once; an injected subshell call of cdt_stop issues nothing; the
happy path issues exactly one stop; every driver call site names a ceiling and
the only bare $CDT is the wrapper's own spawn; the start line carries
--workspace with the capture directory, every --filePath lies under it, and no
code line carries --allowUnrestrictedPaths. A driver whose leader exits at
once while a child keeps holding the capture pipe is still cut off at the
ceiling (negative-controlled: a leader-pid poll blocks for the harness's whole
cap). A watchdog whose clock cannot launch leaves a 300 ms driver call alone
(negative-controlled: the trap form kills `start` in under 20 ms). The harness
EXPORTS its stub-only PATH — unexported, the exec'd
watchdog fell through to bash's compiled-in default PATH and never saw the stub
dir — and ships `sleep` there as an exec-wrapper script (portable to Git Bash,
pid-preserving).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNMb2G67rAJfFQRHEBTAj

* fix(#4223): gitignore gate covers the capture directory, and upgrades an existing file

Round 4 Blocker. The gate enumerated image extensions, so snapshot.txt (the
accessibility tree, with entered form values) and console.txt (which can carry
tokens) were committable by `git add .`. The gate now ignores `interaction/` as
a directory — the next artifact type is covered by construction — and it appends
whatever an existing .gitignore lacks instead of writing once. The write-once
form was the same defect one step later: every project that had already run an
audit would never have received the new pattern at all.

Tests run the gate fence under bash: a fresh file carries every pattern; an
image-only file from an earlier audit gains interaction/ and keeps its own
header without duplicating present lines; a second run appends nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNMb2G67rAJfFQRHEBTAj

* test(#4223): declare the interaction-capture anchor as a comment marker

The #4324 colon-token gate (slash-command-namespace) landed on next after this
branch was opened and reads `<!-- gsd:ui-interaction-capture -->` as an
unconvertible /gsd: command token. It is a section anchor of the same family as
gsd:live-dom-families and gsd:write-continue, so it is declared in
COMMENT_MARKER_TOKENS rather than renamed. Found by running the base-added
gates against the merged tree; CI at ca8d2508 predates the gate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgNMb2G67rAJfFQRHEBTAj

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
Co-authored-by: CI Rebase Check <ci@gsd-redux>
2026-09-20 05:45:15 -04:00

8105 lines
313 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* capability-registry.cjs — generated by scripts/gen-capability-registry.cjs
* DO NOT EDIT BY HAND. Run: node scripts/gen-capability-registry.cjs --write
* ADR-894 §5 — role-partitioned Capability Registry.
*/
const capabilities = {
"ai-integration": {
"id": "ai-integration",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"ai-integration-phase"
],
"agents": [
"gsd-framework-selector",
"gsd-ai-researcher",
"gsd-domain-researcher",
"gsd-eval-planner"
],
"hooks": [],
"config": {
"workflow.ai_integration_phase": {
"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": [
{
"point": "plan:pre",
"ref": {
"skill": "ai-integration-phase"
},
"produces": [
"AI-SPEC.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.ai_integration_phase",
"onError": "skip"
}
],
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/api-coverage-plan-pre.md",
"inline": "# API Coverage Decision Checkpoint\n\n> Full API Coverage by Default — Opt Out, Never Opt In. Fires when a phase\n> integrates an external API / SDK / service. Most non-API phases will not fire\n> it — that is the point.\n\n## Why this exists\n\n\"We integrated the API\" too often silently means \"we integrated whatever the\nfirst use case exercised.\" Every un-built capability is then an invisible hole,\ndiscovered later by a user who reasonably expected it to work. The phase sealed\ngreen because its tasks completed; nobody decided the gaps were acceptable,\nbecause nobody enumerated them. This checkpoint makes the surface **visible and\ndecided** before the phase can seal.\n\n## Detect whether this phase integrates an external API\n\nThe detector is a deterministic scan over the phase scope. It strips fenced\ncode blocks first, so a trigger term inside a code snippet does not fire. It\nreturns a typed result: `{ detected, signals[], terms }`. Run it on the phase\nscope (the concatenation of this phase's ROADMAP section + the PLAN body):\n\n```bash\nSCOPE=\"$(cat \"${PHASE_DIR}\"/*-PLAN.md 2>/dev/null) $(gsd_run query roadmap.get-phase \"${PHASE}\" 2>/dev/null || true)\"\nAPI_COVERAGE_JSON=$(printf '%s' \"$SCOPE\" | node gsd-core/bin/lib/api-coverage.cjs --json 2>/dev/null) || true\n[ -n \"$API_COVERAGE_JSON\" ] || API_COVERAGE_JSON='{\"skipped\":true,\"reason\":\"probe_unavailable\"}'\n```\n\nThe `|| true` neutralizes the assignment's status without discarding the\ndetector's own payload: the detector exits **1** for a real \"no integration\"\nverdict, so treating any non-zero exit as failure would throw away a correct\nanswer. Emptiness — not exit status — is what proves the probe never ran, and\nthe second line is the only place the fragment manufactures a payload of its\nown — one that records the *absence* of a verdict rather than asserting one.\n\nThe detector's exit code and `--json` payload now distinguish a real negative\nfrom an unexamined input (ADR-3889 Phase 3, #3907): empty/whitespace-only\n`$SCOPE` or a stdin read failure emit `{\"skipped\":true,\"reason\":\"no_input\"|\n\"stdin_error\"}` — no `detected` key at all. **Check for `skipped` before\nreading `detected`**: a `skipped` payload is not a confirmed \"no API\nintegration\" verdict, it means the detector never examined real input. Do not\ntreat it as `detected:false`. Read `API_COVERAGE_JSON.detected` only when\n`skipped` is absent — act on it only, do **not** pattern-match the prose\nyourself.\n\n**If `skipped` is `true`:** the detector could not establish a scope (empty\n`$SCOPE`) or failed to run (stdin read error). Skip the checkpoint for this\nrun rather than asserting a verdict about input that was never examined; do\nnot raise it with the user.\n\n**If `detected` is `false`:** this phase does not integrate an external API. Skip\nthe checkpoint entirely and continue planning. Do not raise it with the user.\n\n**If `detected` is `true`:** an external-API integration is in scope. You MUST\nproduce a **coverage matrix** before the plan is finalized.\n\n**If `detected` is `true` but the phase genuinely integrates no external API**\n(the detector is deterministic, not infallible — confirm by re-reading the phase\nscope, not by preference): do NOT fabricate a matrix row for a capability that\ndoes not exist. Write a reasoned declaration to `${PHASE_DIR}/COVERAGE.md`\ninstead:\n\n```markdown\nNo external API integration: <one-line reason — what the phase touches instead>.\n```\n\nThe reason is required, exactly like an `OPT-OUT` reason. The seal-time gate\naccepts this declaration in place of a matrix.\n\n## Produce the coverage matrix\n\nEnumerate the external API's full **capability surface** — the verb/endpoint/method\nlist (e.g. for a music service: `search`, `play`, `pause`, `skip`, `set_volume`,\n`get_playlist`, `create_playlist`, `add_to_playlist`, …). For each capability\nrecord a decision, starting from **full coverage** as the default:\n\n| capability | decision | reason |\n|---|---|---|\n| `<capability-id>` | `INTEGRATE` \\| `OPT-OUT` | `<one-line reason if OPT-OUT>` |\n\nRules:\n\n- **`INTEGRATE` is the default.** Every capability starts as INTEGRATE; the\n matrix is the *subtraction record*.\n- **Every `OPT-OUT` MUST carry a one-line reason** (`not needed`, `not needed\n yet`, `explicitly out of scope`, …). An opt-out without a reason is an\n un-decided hole — the exact failure mode this gate exists to close.\n- **A second integration against the same need** (e.g. a second platform for the\n same capability) starts from the **same full-coverage baseline** as the first.\n Do not carry over the first integration's opt-outs silently — re-decide each\n capability for the new surface, so a first-class/fallback asymmetry cannot\n accumulate.\n\nWrite the matrix to `${PHASE_DIR}/COVERAGE.md` (canonical markdown-table form):\n\n```markdown\n# API Coverage — <service>\n\n> Full coverage by default. Opt-outs are explicit, reasoned decisions.\n\n| capability | decision | reason |\n|---|---|---|\n| search | INTEGRATE | |\n| playlists | INTEGRATE | |\n| skip | OPT-OUT | not needed yet — tracked for follow-up phase |\n```\n\nA fenced ` ```coverage ` JSON block is also accepted for machine-generated\nmatrices; the markdown table is preferred (human-editable, diff-friendly).\n\n## The seal-time gate\n\nThis checkpoint is enforced. At `verify:pre` the `api-coverage.verify-pre` gate\nruns `check api-coverage.verify-pre <phase-dir>`:\n\n- If `COVERAGE.md` exists, it is validated — every row needs a valid decision and\n every `OPT-OUT` a reason. A malformed/partial matrix **blocks the seal**. A\n reasoned `No external API integration: …` declaration (and no rows) passes.\n- If `COVERAGE.md` is absent, the detector runs again over the phase scope. If a\n strong external-API-integration signal is found, the seal is **blocked** until a\n matrix is produced. If no signal is found, the phase is treated as a non-API\n phase and the seal proceeds.\n\nSo: an API-integrating phase cannot seal without a decided matrix. Produce it at\nplan time; do not leave it for seal time.\n\n## Tuning the vocabulary (optional)\n\nThe trigger vocabulary is a curated, additive-only set in\n`gsd-core/bin/lib/api-coverage.cjs` (`DEFAULT_API_COVERAGE_TERMS`). To widen it\nfor a project, override at the call site:\n\n```bash\nprintf '%s' \"$SCOPE\" | node gsd-core/bin/lib/api-coverage.cjs --json \\\n --verbs integrate,wrap,connect,embed --nouns api,sdk,rest,grpc,webhook,plugin\n```\n\nThe whole checkpoint is toggleable via `workflow.api_coverage_gate` in\n`.planning/config.json`.\n"
},
"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"
}
]
},
"antigravity": {
"id": "antigravity",
"role": "runtime",
"version": "1.14.0",
"title": "Antigravity",
"description": "Google Antigravity IDE — config/settings home nested under ~/.gemini/antigravity (probed across 1.x and 2.x layouts); global skills/agents install under ~/.gemini/config, the dir AGY scans for global discovery (#3738); Gemini hook event dialect; flat skill layout; tier-1 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home-nested",
"name": "antigravity",
"parent": ".gemini",
"env": [
"ANTIGRAVITY_CONFIG_DIR"
],
"probe": [
"antigravity",
"antigravity-ide",
"antigravity-cli"
],
"probeExists": "gsd-core/VERSION"
},
"localConfigDir": ".agents",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill",
"home": ".gemini/config"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent",
"home": ".gemini/config"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "gemini",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": "antigravity",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": "undocumented",
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "go",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"projectInstructionFile": "GEMINI.md",
"noPathRewrite": true,
"hookPathStyle": "raw",
"globalDirResolver": "antigravity"
}
},
"reviewer": {
"slug": "antigravity",
"flags": [
"--antigravity",
"--agy"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "agy"
},
"invoke": {
"binary": "agy",
"args": [
"--print-timeout",
"{{nativeTimeout}}",
"{{model}}",
"-p",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "none"
},
"timeoutFloorMs": 600000,
"timeoutConfigKey": "review.timeouts.antigravity",
"emptyOutput": "handler-owned",
"reviewsSection": "Antigravity",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.antigravity",
"modelConfigKey": "review.models.agy",
"effortConfigKey": null,
"defaultEffort": null,
"handler": "antigravity"
},
"config": {
"review.models.agy": {
"type": "string",
"default": "",
"description": "Model passed to the Antigravity reviewer lane. The key suffix is the lane binary/flag alias `agy`, not the slug `antigravity` — preserved verbatim so existing .planning/config.json files keep working."
},
"review.max_prompt_tokens_per_reviewer.antigravity": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\". Keyed on the reviewer slug `antigravity`, not the `agy` binary alias used by review.models.agy."
},
"review.timeouts.antigravity": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"assumption-delta": {
"id": "assumption-delta",
"role": "feature",
"version": "1.14.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",
"inline": "# Assumption-Delta Architecture Checkpoint\n\n> 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.\n\n## Why this exists\n\nMost 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.\n\n## Run the detector\n\nThe 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`):\n\n```bash\nASSUMPTION_DELTA_JSON=$(gsd_run query assumption-delta scan \"${PHASE}\" --json 2>/dev/null) || true\n[ -n \"$ASSUMPTION_DELTA_JSON\" ] || ASSUMPTION_DELTA_JSON='{\"skipped\":true,\"reason\":\"probe_unavailable\"}'\n```\n\n> If the phase section cannot be resolved (no `ROADMAP.md` / unknown phase, or a section with no body), the query emits `{ \"skipped\": true, \"reason\": \"phase_unresolved\" }` — **not** `detected:false`. A probe that never had input does not get to assert that this phase changes no core assumption. The checkpoint does not fire either way; the difference is that a skip is now distinguishable from a real negative. Do not block on it.\n>\n> 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`.\n\n## Decision branch\n\nRead `ASSUMPTION_DELTA_JSON`. Act on `detected` only — do **not** pattern-match the human prose.\n\n**If `skipped` is `true`:** the detector never examined a phase section — it could not resolve one (`phase_unresolved`) or could not run at all (`probe_unavailable`). Skip the checkpoint for this run rather than asserting a verdict about input that was never examined; do not raise it with the user. **Check for `skipped` before reading `detected`** — a skipped payload carries no `detected` key, and treating its absence as `false` re-creates the fabrication this branch exists to prevent.\n\n**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.\n\n**If `detected` is `true`:** a core assumption may have lost its monopoly. The `signals[]` array tells you which family fired:\n\n| `kind` | What changed | The question to answer |\n|---|---|---|\n| `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? |\n| `optional` | A required / `only` field became optional | Is the field still the right anchor, or has the anchor moved? |\n| `chosen` | A derived value became chosen, or a constant became a parameter | Has a configuration decision become a modeling decision? |\n\nBefore finalizing the plan, answer this for the user and record the decision explicitly:\n\n> **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).\n\nRecord the outcome in the PLAN.md front matter / a `<assumption_delta_decision>` block:\n\n- The **noun** that is now primary (the generalized identity).\n- The **decision**: `promote` | `add-alongside` | `no-change`, with a one-line rationale.\n- If `add-alongside`: call it out as accepted debt and note what would force a later promote.\n\n## Optional companion: an invariant test\n\nWhen `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.\n\n## Tuning the vocabulary (optional)\n\nThe 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`.\n\nThis checkpoint is advisory: it informs and records; it never blocks the phase.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.assumption_delta",
"onError": "skip"
}
],
"gates": []
},
"audit": {
"id": "audit",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"config": {},
"commands": [
{
"family": "audit-uat",
"module": "audit-command-router.cjs",
"router": "routeAuditUat"
},
{
"family": "audit-open",
"module": "audit-command-router.cjs",
"router": "routeAuditOpen"
}
],
"hooks": [],
"steps": [],
"contributions": [],
"gates": []
},
"augment": {
"id": "augment",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".augment",
"env": [
"AUGMENT_CONFIG_DIR"
]
},
"localConfigDir": ".augment",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"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",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
}
}
},
"broken-windows": {
"id": "broken-windows",
"role": "feature",
"version": "1.14.0",
"title": "Broken-windows ledger",
"description": "Cross-phase defect register accumulating stubs, TODOs, skipped tests, unrun verifies, and unmet truths into .planning/WINDOWS.md. When enforcement is enabled, it blocks /gsd-ship while any window is open unless explicitly waived with a recorded reason. Operationalizes GSD's no-defer discipline as a tracked artifact (issue #1950).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.windows_enforce": {
"type": "boolean",
"default": false,
"description": "Enable the blocking ship:pre gate for the broken-windows ledger. When true (opt-in), /gsd-ship blocks while .planning/WINDOWS.md has any open entry. When false (default), windows are still tracked (the executor and verifier still populate WINDOWS.md via gsd-tools windows append) but ship does not block — teams can adopt tracking before enforcement. Issue #1950."
}
},
"steps": [],
"contributions": [],
"gates": [
{
"point": "ship:pre",
"check": {
"predicate": {
"kind": "artifact-frontmatter-equals",
"artifact": "WINDOWS.md",
"field": "open_count",
"equals": 0
}
},
"when": "workflow.windows_enforce",
"blocking": true,
"onError": "halt"
}
]
},
"claude": {
"id": "claude",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".claude",
"env": [
"CLAUDE_CONFIG_DIR"
]
},
"localConfigDir": ".claude",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"FileChanged"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "harness-worktree",
"maxConcurrency": 20
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "argv"
},
"harnessIsolationFlag": "isolation=\"worktree\"",
"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
}
},
"reviewer": {
"slug": "claude",
"flags": [
"--claude"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "claude"
},
"invoke": {
"binary": "claude",
"args": [
"{{model}}",
"{{effort}}",
"-p",
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "argv",
"env": {
"CLAUDE_CODE_DISABLE_CLAUDE_MDS": "1",
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1"
}
},
"timeoutFloorMs": 1200000,
"timeoutConfigKey": "review.timeouts.claude",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Claude",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.claude",
"modelConfigKey": "review.models.claude",
"effortConfigKey": "review.effort.claude",
"defaultEffort": "high",
"handler": null
},
"config": {
"review.models.claude": {
"type": "string",
"default": "",
"description": "Model passed to the Claude reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.claude": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Claude reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.claude": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Claude reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.claude": {
"type": "string",
"default": "",
"description": "Reasoning effort for the Claude reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"claude-orchestration": {
"id": "claude-orchestration",
"role": "feature",
"version": "1.14.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",
"resolve-wave-dispatch"
]
}
],
"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": "plan:post",
"into": "planner",
"fragment": {
"path": "fragments/plan-post.md",
"inline": "# Claude orchestration — ultraplan plan-offload ownership (BETA)\n\n> Injected at `plan:post` `into: planner` only when\n> `claude_orchestration.enabled` is true. Default-off; `onError: skip`.\n\n## Ownership declaration\n\nThe `gsd-ultraplan-phase` plan-offload surface (offloading GSD's plan phase to\nClaude Code's ultraplan cloud) is **owned by this capability**, not by a\nstandalone BETA skill. Both surfaces share one runtime gate\n(`claude_orchestration.enabled`), one BETA boundary, and one Claude-Code-only\ndetection seam.\n\n## When the planner should consider ultraplan offload\n\nWhen this contribution is active (capability enabled, Claude Code runtime), the\nplanner MAY offer the `/gsd-ultraplan-phase` path as an alternative to local\n`/gsd-plan-phase` for phases where cloud-assisted planning adds value. This is\nadvisory, not mandatory — the stable local planner remains the default.\n\n## Fallback contract\n\nIf the capability is disabled, or the runtime is not Claude Code, ultraplan\noffload is **not surfaced** and the planner proceeds with the standard local\n`/gsd-plan-phase`. The `gsd-ultraplan-phase` command itself remains installed\n(its own runtime gate already no-ops on non-Claude runtimes); this contribution\nonly governs whether the capability manifest advertises it as part of the\norchestration surface.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "claude_orchestration.enabled",
"onError": "skip"
}
],
"gates": []
},
"cline": {
"id": "cline",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".cline",
"env": [
"CLINE_CONFIG_DIR"
]
},
"localConfigDir": ".cline",
"configFormat": "markdown-dir",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClineSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
],
"local": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "cline-rules",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "cline-rules",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"frontmatterDialect": "cline",
"skipSharedHooksInstall": true,
"localTargetIsProjectRoot": true,
"clineRulesSurface": true,
"localCommandsViaRules": true
}
}
},
"code-review": {
"id": "code-review",
"role": "feature",
"version": "1.14.0",
"title": "Code review",
"description": "Source-file code review and review-fix workflow support for completed execution work.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"code-review"
],
"agents": [
"gsd-code-reviewer",
"gsd-code-fixer"
],
"hooks": [],
"config": {
"workflow.code_review": {
"type": "boolean",
"default": true,
"description": "Enable code-review participation in post-execution review flows."
},
"workflow.code_review_depth": {
"type": "enum",
"values": [
"quick",
"standard",
"deep"
],
"default": "standard",
"description": "Default depth for code review when no --depth override is supplied."
},
"workflow.code_review_point": {
"type": "enum",
"values": [
"execute:post",
"execute:wave:post"
],
"default": "execute:post",
"description": "Loop point at which the code-review step registers — execute:post reviews once per phase (default); execute:wave:post reviews once per completed wave, scoped to that wave's diff."
}
},
"steps": [
{
"point": "execute:post",
"ref": {
"skill": "code-review"
},
"produces": [
"REVIEW.md"
],
"consumes": [
"SUMMARY.md"
],
"supportsReviewerLanes": true,
"when": "workflow.code_review",
"pointFrom": "workflow.code_review_point",
"onError": "skip"
},
{
"point": "execute:wave:post",
"ref": {
"skill": "code-review"
},
"produces": [
"REVIEW.md"
],
"consumes": [],
"supportsReviewerLanes": true,
"when": "workflow.code_review",
"pointFrom": "workflow.code_review_point",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"codebuddy": {
"id": "codebuddy",
"role": "runtime",
"version": "1.14.0",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".codebuddy",
"env": [
"CODEBUDDY_CONFIG_DIR"
]
},
"localConfigDir": ".codebuddy",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddyCommand"
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddyCommand"
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reportCommandsDir": true
}
}
},
"coderabbit": {
"id": "coderabbit",
"role": "reviewer",
"version": "1.14.0",
"title": "CodeRabbit",
"description": "CodeRabbit CLI — cross-AI /gsd:review reviewer lane only; not a GSD install target (no runtime body, no artifacts). Reviews the working-tree diff (`coderabbit review --prompt-only`), not the source tree, and accepts neither a prompt nor a model flag; findings are down-weighted in consensus (evidenceClass: diff-only).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.8.0"
},
"reviewer": {
"slug": "coderabbit",
"flags": [
"--coderabbit"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "coderabbit"
},
"invoke": {
"binary": "coderabbit",
"args": [
"review",
"--prompt-only"
],
"promptChannel": "none",
"outputChannel": "stdout",
"modelArg": null,
"effortChannel": "none"
},
"timeoutFloorMs": 360000,
"timeoutConfigKey": null,
"emptyOutput": "stub-with-stderr",
"reviewsSection": "CodeRabbit",
"evidenceClass": "diff-only",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.coderabbit",
"modelConfigKey": null,
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.max_prompt_tokens_per_reviewer.coderabbit": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the CodeRabbit reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
}
}
},
"codex": {
"id": "codex",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".codex",
"env": [
"CODEX_HOME"
]
},
"localConfigDir": ".codex",
"configFormat": "toml",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill",
"home": ".agents"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodexAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodexAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "shell-var",
"hooksSurface": "codex-hooks-json",
"hookEvents": "claude",
"sandboxTier": "codex-agent-sandbox",
"supportTier": 1,
"installSurface": "codex-toml",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "argv"
},
"orchestratorExec": {
"command": "codex",
"args": [
"exec"
],
"cwdFlag": "--cd",
"promptFlag": null,
"modelFlag": "--model"
},
"hostBehaviors": {
"reapplyCommand": "$gsd-update --reapply",
"tomlConfigInstall": true,
"cleanupSkillSidecars": true,
"agentTomlFiles": true,
"frontmatterDialect": "codex",
"unsupportedFeatures": [
"context-warnings",
"phase-lifecycle-display"
]
}
},
"reviewer": {
"slug": "codex",
"flags": [
"--codex"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "codex"
},
"invoke": {
"binary": "codex",
"args": [
"exec",
"--ephemeral",
"{{model}}",
"{{effort}}",
"--skip-git-repo-check",
"{{output}}",
"-"
],
"promptChannel": "stdin",
"outputChannel": "file-arg",
"outputArg": "-o",
"modelArg": "--model",
"effortChannel": "argv"
},
"timeoutFloorMs": 1200000,
"timeoutConfigKey": "review.timeouts.codex",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Codex",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.codex",
"modelConfigKey": "review.models.codex",
"effortConfigKey": "review.effort.codex",
"defaultEffort": "high",
"handler": null
},
"config": {
"review.models.codex": {
"type": "string",
"default": "",
"description": "Model passed to the Codex reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.codex": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Codex reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.codex": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Codex reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.codex": {
"type": "string",
"default": "",
"description": "Reasoning effort for the Codex reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"copilot": {
"id": "copilot",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".copilot",
"env": [
"COPILOT_CONFIG_DIR",
"COPILOT_HOME"
]
},
"localConfigDir": ".github",
"configFormat": "markdown",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "copilot-inline",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "copilot-instructions",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "undocumented",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"agentFileExtension": ".agent.md",
"skipSharedHooksInstall": true,
"noPathRewrite": true
}
}
},
"cursor": {
"id": "cursor",
"role": "runtime",
"version": "1.14.0",
"title": "Cursor",
"description": "Cursor IDE — skills-only workflow surface; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".cursor",
"env": [
"CURSOR_CONFIG_DIR"
]
},
"localConfigDir": ".cursor",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToCursorSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToCursorSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "cursor-hooks-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "cursor-hooks-json",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 2,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true,
"isolation": "harness-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"harnessIsolationFlag": "--worktree",
"hostBehaviors": {
"reapplyCommand": "gsd-update --reapply (mention the skill name)",
"frontmatterDialect": "cursor",
"hooksJsonSurface": true,
"skipSharedHooksInstall": true,
"retiredArtifacts": [
{
"destSubpath": "commands",
"prefix": "gsd-",
"suffix": ".md"
}
],
"skipUpdateBannerCommand": true,
"skipSettingsUi": true,
"managedHookEvents": [
"sessionStart",
"postToolUse",
"preToolUse",
"stop",
"subagentStart",
"subagentStop"
]
}
},
"reviewer": {
"slug": "cursor",
"flags": [
"--cursor"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "cursor-agent"
},
"invoke": {
"binary": "cursor-agent",
"args": [
"-p",
"{{model}}",
"--mode",
"ask",
"--trust",
"--output-format",
"text",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": null,
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Cursor",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.cursor",
"modelConfigKey": "review.models.cursor",
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.models.cursor": {
"type": "string",
"default": "",
"description": "Model passed to the Cursor reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.cursor": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Cursor reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
}
}
},
"drift": {
"id": "drift",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.drift_threshold": {
"type": "number",
"default": 3,
"description": "Minimum number of new structural elements (directories, barrel exports, migrations, routes) before the codebase drift gate triggers a warn or auto-remap action."
},
"workflow.drift_action": {
"type": "enum",
"values": [
"warn",
"auto-remap"
],
"default": "warn",
"description": "Action taken by the codebase drift gate when the threshold is exceeded: warn (advisory message) or auto-remap (spawn gsd-codebase-mapper agent to refresh STRUCTURE.md)."
},
"workflow.schema_drift_gate": {
"type": "boolean",
"default": true,
"description": "Enable the drift gates at execute:wave:post. When enabled, the schema drift gate blocks verification if schema-relevant files changed during execution but no database push command was executed; the codebase drift gate (non-blocking) warns when structural additions exceed the drift_threshold."
},
"workflow.plan_drift_precheck": {
"type": "boolean",
"default": true,
"description": "Enable the non-blocking codebase drift pre-check at plan:pre, before /gsd:plan-phase spawns the planner. When enabled, a stale STRUCTURE.md (structural additions exceeding drift_threshold) is surfaced up front as a warn-only advisory pointing to /gsd:map-codebase; it never blocks planning and never spawns the mapper agent. Separate from schema_drift_gate so autonomous/CI runs can silence the plan-time advisory while keeping the execute:wave:post gates enabled."
},
"workflow.context_drift_precheck": {
"type": "boolean",
"default": true,
"description": "Enable the non-blocking context-drift pre-check at plan:pre, before /gsd:plan-phase reuses an existing RESEARCH.md/PATTERNS.md/VALIDATION.md/SPEC.md. Compares each artifact's effective last-changed time (git commit time, falling back to mtime for uncommitted edits) against CONTEXT.md's own — an artifact that predates CONTEXT.md's newest decision was derived from a premise that has since changed. Warn-only by default (see workflow.context_drift_action); never blocks planning on its own."
},
"workflow.context_drift_action": {
"type": "enum",
"values": [
"warn",
"block"
],
"default": "warn",
"description": "Action taken by the context-drift gate when a stale upstream artifact is found: warn (advisory message naming the stale artifacts and how to regenerate them) or block (halt plan-phase until the artifacts are regenerated or the check is disabled)."
}
},
"steps": [],
"contributions": [],
"gates": [
{
"point": "execute:wave:post",
"check": {
"query": "verify.schema-drift"
},
"when": "workflow.schema_drift_gate",
"blocking": true,
"onError": "skip"
},
{
"point": "execute:wave:post",
"check": {
"query": "verify.codebase-drift"
},
"when": "workflow.schema_drift_gate",
"blocking": false,
"onError": "skip"
},
{
"point": "plan:pre",
"check": {
"query": "verify.codebase-drift"
},
"when": "workflow.plan_drift_precheck",
"blocking": false,
"onError": "skip"
},
{
"point": "plan:pre",
"check": {
"query": "verify.context-drift"
},
"when": "workflow.context_drift_precheck",
"blocking": false,
"onError": "skip"
}
]
},
"external-job": {
"id": "external-job",
"role": "feature",
"version": "1.14.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 classification at execute:wave:pre and recording at execute:wave:post. This capability still contributes executor guidance at wave:post; execute-phase now renders wave:pre entries and dispatches generic step hooks there independently. Moving external-job classification to wave:pre is a separate capability change, not part of #4148. 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",
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n #1164 specifies classification at wave:pre and recording at wave:post. This\n capability still contributes executor guidance at wave:post; execute-phase\n now renders wave:pre entries and dispatches generic step hooks there. Moving\n external-job classification is a separate capability change, not part of\n #4148. Until then, this guidance cannot classify the wave that already ran. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
},
"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",
"inline": "<!-- external-job capability — plan:post fragment, injected into the planner (#1164). -->\n\n## Tag runtime budgets on long tasks\n\nFor every `<task>` likely to exceed ~2 minutes of real compute, emit a\n`<runtime_budget>` child element so execute can classify it:\n\n- `<runtime_budget>quick</runtime_budget>` — under ~2 min; runs normally.\n- `<runtime_budget>medium</runtime_budget>` — ~2–30 min; foreground, but with\n progress expectations.\n- `<runtime_budget>unknown</runtime_budget>` — runtime not yet characterized;\n execute must run a first-health check and set a soft-review deadline before\n trusting the child timeout. Define a progress signal and an abort condition.\n- `<runtime_budget>long_compute</runtime_budget>` — legitimately over ~30–60 min\n (HPC solver, model training, large simulations). Execute must **externalize**\n this as an async external job (see the execute:wave:post fragment) rather than\n blocking the agent turn.\n\nFor any `long_compute` task, also declare the async contract the executor will\nneed: the `submit_command`, the `expected_artifacts` the job must produce, and\nthe `verification_command` that proves the output before the plan can close.\nDo not hardcode a cluster account, partition, or project path — the planner\nnever knows the scheduler layout; it declares the contract, the executor's\nadapter fills the backend specifics.\n"
},
"produces": [],
"consumes": [],
"when": "external_job.enabled",
"onError": "skip"
}
],
"gates": []
},
"gap-analysis": {
"id": "gap-analysis",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.post_planning_gaps": {
"type": "boolean",
"default": true,
"description": "Run the post-planning gap analysis report after plans are generated."
}
},
"steps": [],
"contributions": [],
"gates": [
{
"point": "plan:post",
"check": {
"query": "gap-analysis.plan-post"
},
"when": "workflow.post_planning_gaps",
"blocking": false,
"onError": "skip"
}
]
},
"graphify": {
"id": "graphify",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"graphify"
],
"agents": [],
"activationKey": "graphify.enabled",
"config": {
"graphify.enabled": {
"type": "boolean",
"default": false,
"description": "Enable the graphify knowledge-graph command + skill."
}
},
"commands": [
{
"family": "graphify",
"module": "graphify-command-router.cjs",
"router": "routeGraphifyCommand"
}
],
"hooks": [],
"steps": [],
"contributions": [],
"gates": []
},
"hermes": {
"id": "hermes",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".hermes",
"env": [
"HERMES_HOME"
]
},
"localConfigDir": ".hermes",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills/gsd",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToHermesAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills/gsd",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToHermesAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"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,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python",
"effortSurface": "undocumented"
}
}
},
"intel": {
"id": "intel",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"activationKey": "intel.enabled",
"config": {
"intel.enabled": {
"type": "boolean",
"default": false,
"description": "Enable the intel code-intelligence command."
}
},
"commands": [
{
"family": "intel",
"module": "intel-command-router.cjs",
"router": "routeIntelCommand"
}
],
"hooks": [],
"steps": [
{
"point": "plan:pre",
"ref": {
"command": "intel api-surface"
},
"produces": [
".planning/intel/API-SURFACE.md"
],
"consumes": [],
"when": "intel.enabled",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"kilo": {
"id": "kilo",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "xdg",
"name": "kilo",
"env": [
"KILO_CONFIG_DIR",
"KILO_CONFIG",
"XDG_CONFIG_HOME"
],
"skillsHome": {
"kind": "dot-home",
"name": ".kilo",
"env": []
}
},
"localConfigDir": ".kilo",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "command",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToKiloSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToKiloFrontmatter"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "command",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToKiloSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToKiloFrontmatter"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "kilo",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": false,
"permissionWriter": "kilo",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": -1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun",
"effortSurface": "undocumented"
},
"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
}
}
},
"kimi": {
"id": "kimi",
"role": "runtime",
"version": "1.14.0",
"title": "Kimi CLI",
"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": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "generic-agents-root",
"name": "agents",
"env": [
"KIMI_CONFIG_DIR"
],
"probe": [
"~/.config/agents",
"~/.agents"
],
"probeExists": "skills"
},
"localConfigDir": ".kimi-code",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToKimiSkill"
},
{
"kind": "kimi-agents",
"destSubpath": "agents",
"prefix": "gsd",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "kimi-hooks-toml",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": true,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python",
"effortSurface": "undocumented"
},
"orchestratorExec": {
"command": "kimi",
"args": [
"--print"
],
"cwdFlag": "--work-dir",
"promptFlag": "--prompt"
},
"hostBehaviors": {
"reapplyCommand": "/skill:gsd-update --reapply",
"localInstallDeferred": true,
"verificationStyle": "kimi",
"agentManifestStyle": "kimi-nested",
"doneBannerStyle": "kimi-agent-file",
"skipSharedHooksInstall": true,
"noPathRewrite": true
}
}
},
"kimi-code": {
"id": "kimi-code",
"role": "runtime",
"version": "1.14.0",
"title": "Kimi Code CLI",
"description": "Kimi Code CLI (Moonshot AI, Node) — Agent Skills auto-discovered at ~/.kimi-code/skills; global AGENTS.md at ~/.kimi-code/AGENTS.md; native config.toml + [[hooks]] bus; three built-in subagents (coder/explore/plan), NO custom named subagents; background dispatch; tier-2 support. Distinct from Python kimi-cli (the 'kimi' capability) per ADR-1239 EoS — Kimi Code cannot dispatch named subagents so the kimi-agents YAML layout does NOT apply; persona injection rides the existing ${AGENT_SKILLS_*} workflow fallback. Install-layout, agent-install-check, and install-time decision (kimi vs kimi-code) land in follow-up PRs; this descriptor is the EoS foundation.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".kimi-code",
"env": [
"KIMI_CODE_HOME"
],
"probe": [
"~/.kimi-code"
],
"probeExists": "config.toml"
},
"localConfigDir": ".kimi-code",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToKimiCodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "kimi-hooks-toml",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": false,
"nested": true,
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "built-in-only",
"backgroundDispatch": true,
"builtInSubagents": [
"coder",
"explore",
"plan"
],
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"orchestratorExec": {
"command": "kimi",
"args": [],
"cwdFlag": null,
"promptFlag": "--prompt"
},
"hostBehaviors": {
"reapplyCommand": "/skill:gsd-update --reapply",
"localInstallDeferred": true,
"verificationStyle": "kimi-code",
"agentManifestStyle": "none",
"doneBannerStyle": "kimi-code",
"skipSharedHooksInstall": true,
"namedSubagentsSupported": false
}
},
"reviewer": {
"slug": "kimi-code",
"flags": [
"--kimi-code"
],
"transport": "spawn",
"probe": {
"kind": "command-capability",
"binary": "kimi",
"needle": "--output-format",
"timeoutMs": 5000
},
"invoke": {
"binary": "kimi",
"args": [
"{{model}}",
"-p",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "-m",
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": "review.timeouts.kimi-code",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Kimi Code",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.kimi-code",
"modelConfigKey": "review.models.kimi-code",
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.models.kimi-code": {
"type": "string",
"default": "",
"description": "Model passed to the Kimi Code reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.kimi-code": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.kimi-code": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"live-dom-uat": {
"id": "live-dom-uat",
"role": "feature",
"version": "1.14.0",
"title": "Live-DOM UAT",
"description": "Default-off live-DOM verification (#2856). Confines browser MCP reach to one purpose-built agent (gsd-dom-verifier) that carries the browser globs in its own tools: line, registered as an additive step hook at execute:wave:post. agents/gsd-executor.md is deliberately NOT widened: for a first-party agent the static tool list is the only control that exists, no capability can grant tools to one (ADR-1244 D2), no hook kind grants tool permissions (ADR-857 D4), and there is no per-dispatch tool override. Gated by activationKey workflow.live_dom_uat (default false), so with the key off the capability resolves inactive and the hook does not render at all. NOTE on the browser profile lock: chrome-devtools-mcp holds an exclusive lock on $HOME/.cache/chrome-devtools-mcp/chrome-profile, and --isolated is a flag on the user's own MCP-server registration that GSD cannot pass. Concurrent execution waves sharing one profile will therefore collide; the step tolerates and reports that (onError: skip, never blocking) rather than pretending to coordinate a resource it does not own.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.11.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [
"gsd-dom-verifier"
],
"activationKey": "workflow.live_dom_uat",
"config": {
"workflow.live_dom_uat": {
"type": "boolean",
"default": false,
"description": "Enable live-DOM verification. Default-off: browser MCP reach is opt-in per project. When on, the orchestrator's automated UI verification may additionally use mcp__chrome-devtools__* / mcp__claude-in-chrome__* when present, and a gsd-dom-verifier step runs after each execution wave. When off, neither surface reaches a browser and the pre-existing mcp__playwright__* path is unchanged."
}
},
"hooks": [],
"steps": [
{
"point": "execute:wave:post",
"ref": {
"agent": "gsd-dom-verifier"
},
"fragment": {
"path": "fragments/execute-wave-post.md",
"inline": "<objective>\nVerify the live-DOM acceptance criteria for the execution wave that just completed.\nAnswer: \"of this wave's stated UI acceptance criteria, which can I observe in a live DOM\nright now, and which could I not look at?\"\n\nThis step is ADDITIVE. It never halts the wave, never fails the phase, and never rewrites\nSUMMARY.md. If you cannot look, say so and finish.\n</objective>\n\n<required_reading>\n- {phase_dir}/{phase_num}-PLAN.md (the wave's tasks and their acceptance criteria)\n- {phase_dir}/{phase_num}-UI-SPEC.md if it exists (the design contract, when the phase has one)\n</required_reading>\n\n<browser_surface>\nYou carry exactly two browser MCP families: `mcp__chrome-devtools__*` and\n`mcp__claude-in-chrome__*`. Use whichever responds. Do not assume they expose the same\ntool names — probe, then use what is there. Do not paper over differences between them.\n\nYou do NOT carry the Playwright MCP family. That path belongs to the orchestrator's\nown verification step and is not yours.\n</browser_surface>\n\n<profile_lock>\n`chrome-devtools-mcp` holds an exclusive lock on its browser profile\n(`$HOME/.cache/chrome-devtools-mcp/chrome-profile`). A second concurrent instance fails with:\n\n```\nThe browser is already running for <dir>. Use --isolated to run multiple browser instances.\n```\n\nIf you see that, or any equivalent lock error:\n\n1. Record `outcome: could_not_look` and `reason: profile_locked`.\n2. Name `--isolated` in the notes, so the operator knows the remedy is a flag on THEIR MCP\n server registration.\n3. **Stop.** Do not retry, do not loop, do not wait for the lock. GSD cannot pass\n `--isolated` — it is not GSD's flag — and a retry loop here just holds up the wave.\n\nParallel execution waves sharing one profile WILL hit this. It is an expected condition,\nnot a defect, and it is not a reason to fail anything.\n</profile_lock>\n\n<method>\nFor each UI acceptance criterion you can identify in the wave's plan:\n\n1. Resolve its target URL. If no dev server or target is reachable, that criterion is\n `could_not_look` / `target_unreachable` — not a failure.\n2. Open it with the browser family that responded.\n3. Observe the DOM for the specific, stated condition. Assert on structure and content —\n an element's presence, its text, its attributes, its computed state.\n4. Record `passed` when the stated condition is observably true, `needs_review` when it is\n ambiguous or requires human judgement (subjective aesthetics, content accuracy).\n\nScope limit for this version: DOM observation against stated criteria only. No screenshot\ndiffing, no accessibility audit, no performance tracing. If a criterion needs one of those,\nmark it `needs_review` and say which.\n\nNever invent a criterion. If the plan states no UI acceptance criteria, that is\n`outcome: nothing_to_report` / `reason: no_criteria`, and it is a perfectly good result.\n</method>\n\n<output>\nWrite to: {phase_dir}/{phase_num}-DOM-VERIFY.md\n\nFrontmatter carries scalars only, so a reader can get the verdict without parsing prose:\n\n```\n---\nschema_version: 1\nwave: {wave_number}\noutcome: verified | nothing_to_report | could_not_look\nreason: ok | no_criteria | no_browser_mcp | profile_locked | target_unreachable\nchecked: <integer>\npassed: <integer>\nneeds_review: <integer>\n---\n```\n\nThen a short body: one line per criterion with its verdict, and — when `outcome` is\n`could_not_look` — exactly what stopped you and what the operator would change.\n\n**`nothing_to_report` and `could_not_look` are different outcomes and must never be\nconflated.** \"There were no UI criteria in this wave\" and \"there were criteria but I had no\nbrowser\" look identical in a summary that collapses them, and that ambiguity is the reported\nproblem this capability exists to remove.\n</output>\n"
},
"produces": [
"DOM-VERIFY.md"
],
"consumes": [
"PLAN.md"
],
"when": "workflow.live_dom_uat",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"llama-cpp": {
"id": "llama-cpp",
"role": "reviewer",
"version": "1.14.0",
"title": "llama.cpp",
"description": "llama.cpp server — cross-AI /gsd:review reviewer lane only; not a GSD install target (no runtime body, no artifacts). OpenAI-compatible HTTP transport against a user-configured `review.llama_cpp_host` (POST /v1/chat/completions); model discovered via GET /v1/models piped through jq. Capability id/folder are kebab (`llama-cpp`, required by KEBAB_RE); `reviewer.slug` stays snake (`llama_cpp`) to match the shipped roster and the `review.llama_cpp_host` config key (ADR-2782's three-namespace trap).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.8.0"
},
"reviewer": {
"slug": "llama_cpp",
"flags": [
"--llama-cpp"
],
"transport": "openai-http",
"probe": {
"kind": "http-reachable",
"hostConfigKey": "review.llama_cpp_host",
"path": "/v1/models",
"timeoutMs": 2000
},
"invoke": {
"hostConfigKey": "review.llama_cpp_host",
"defaultHost": "http://localhost:8080",
"path": "/v1/chat/completions",
"modelDiscovery": "first-from-models-endpoint",
"fallbackModel": "local-model",
"effortChannel": "none"
},
"timeoutFloorMs": 120000,
"timeoutConfigKey": "review.timeouts.llama_cpp",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "llama.cpp",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.llama_cpp",
"modelConfigKey": "review.models.llama_cpp",
"effortConfigKey": null,
"defaultEffort": null,
"handler": "openai-compatible"
},
"config": {
"review.models.llama_cpp": {
"type": "string",
"default": "",
"description": "Model requested from the llama.cpp reviewer lane; empty discovers the first model from /v1/models."
},
"review.llama_cpp_host": {
"type": "string",
"default": "",
"description": "Base URL of the llama.cpp OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.llama_cpp": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the llama.cpp reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.llama_cpp": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the llama.cpp reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"lm-studio": {
"id": "lm-studio",
"role": "reviewer",
"version": "1.14.0",
"title": "LM Studio",
"description": "LM Studio local model server — cross-AI /gsd:review reviewer lane only; not a GSD install target (no runtime body, no artifacts). OpenAI-compatible HTTP transport against a user-configured `review.lm_studio_host` (POST /v1/chat/completions); model discovered via GET /v1/models piped through jq. Capability id/folder are kebab (`lm-studio`, required by KEBAB_RE); `reviewer.slug` stays snake (`lm_studio`) to match the shipped roster and the `review.lm_studio_host` config key (ADR-2782's three-namespace trap).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.8.0"
},
"reviewer": {
"slug": "lm_studio",
"flags": [
"--lm-studio"
],
"transport": "openai-http",
"probe": {
"kind": "http-reachable",
"hostConfigKey": "review.lm_studio_host",
"path": "/v1/models",
"timeoutMs": 2000
},
"invoke": {
"hostConfigKey": "review.lm_studio_host",
"defaultHost": "http://localhost:1234",
"path": "/v1/chat/completions",
"modelDiscovery": "first-from-models-endpoint",
"fallbackModel": "local-model",
"effortChannel": "none"
},
"timeoutFloorMs": 120000,
"timeoutConfigKey": "review.timeouts.lm_studio",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "LM Studio",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.lm_studio",
"modelConfigKey": "review.models.lm_studio",
"effortConfigKey": null,
"defaultEffort": null,
"handler": "openai-compatible"
},
"config": {
"review.models.lm_studio": {
"type": "string",
"default": "",
"description": "Model requested from the LM Studio reviewer lane; empty discovers the first model from /v1/models."
},
"review.lm_studio_host": {
"type": "string",
"default": "",
"description": "Base URL of the LM Studio OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.lm_studio": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the LM Studio reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.lm_studio": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the LM Studio reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"mempalace": {
"id": "mempalace",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"mempalace-recall",
"mempalace-capture"
],
"agents": [
"gsd-mempalace-curator"
],
"hooks": [],
"config": {
"mempalace.enabled": {
"type": "boolean",
"default": false,
"description": "Master toggle for the MemPalace memory capability."
},
"mempalace.memory_mode": {
"type": "enum",
"values": [
"augment",
"kg_backend",
"replace"
],
"default": "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",
"default": "",
"description": "Palace wing name; empty derives from project_code / project dir."
},
"mempalace.recall_on_discuss": {
"type": "boolean",
"default": true,
"description": "Inject wake-up + search recall at discuss:pre."
},
"mempalace.recall_on_plan": {
"type": "boolean",
"default": true,
"description": "Produce MEMORY-RECALL.md at plan:pre."
},
"mempalace.capture_artifacts": {
"type": "boolean",
"default": true,
"description": "File CONTEXT/PLAN/SUMMARY and learnings into the palace at phase boundaries."
},
"mempalace.mirror_kg": {
"type": "boolean",
"default": true,
"description": "Mirror decisions/learnings into MemPalace's temporal knowledge graph."
},
"mempalace.cross_project_tunnels": {
"type": "boolean",
"default": false,
"description": "Propose/create cross-wing tunnels at ship:post."
},
"mempalace.diary_journal": {
"type": "boolean",
"default": true,
"description": "Write a per-agent diary entry at ship:post."
},
"mempalace.auto_capture_hooks": {
"type": "boolean",
"default": false,
"description": "Reserved / not yet implemented: will install MemPalace's native stop/precompact Claude Code hooks for passive mid-session capture (the capability's hooks array is currently empty)."
}
},
"steps": [
{
"point": "discuss:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"point": "plan:pre",
"ref": {
"skill": "mempalace-recall"
},
"produces": [
"MEMORY-RECALL.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"point": "plan:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"PLAN.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"point": "verify:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"SUMMARY.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"point": "ship:post",
"ref": {
"agent": "gsd-mempalace-curator"
},
"produces": [],
"consumes": [
"UAT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"contributions": [
{
"point": "discuss:pre",
"into": "orchestrator",
"fragment": {
"path": "fragments/recall-discuss.md",
"inline": "<!--\n MemPalace capability — contribution fragment.\n Rendered into the discuss:pre orchestrator prompt when `mempalace.recall_on_discuss` is true.\n Contributes DATA (recall instructions), not control flow. onError: skip — never blocks discussion.\n-->\n### Memory recall (MemPalace)\n\n**Gate first.** Read `.planning/config.json`. If `mempalace.enabled` is not `true`, or `mempalace.recall_on_discuss` is `false`, **skip this entire section** and continue the discussion unchanged. (This contribution is only injected when the capability is enabled; the `recall_on_discuss` check lets you turn discuss-time recall off without disabling the rest of the capability.)\n\nOtherwise — before gathering new context, surface what you already know. This is read-only and side-effect-free; if MemPalace is unreachable, note \"memory unavailable\" and continue — recall never blocks discussion.\n\n1. **Resolve the wing.** Use `mempalace.wing` if set; otherwise derive it from `project_code` (fall back to the project directory name).\n2. **Wake up (cheap, ~600–900 tokens).**\n - Interactive run → call `mempalace_search` after a wake-up read of the wing.\n - Headless/cron run (no MCP server) → run `mempalace wake-up --wing <wing>` via the CLI.\n3. **Targeted recall.** Search the palace for prior work on this phase's topic:\n - 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.\n - Headless → `mempalace search \"<phase topic>\" --wing <wing>`.\n4. **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.\n5. **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.\n\nIf any MemPalace call errors or times out, skip the rest of recall and proceed with discussion as normal.\n"
},
"produces": [],
"consumes": [],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"point": "execute:wave:post",
"into": "verifier",
"fragment": {
"path": "fragments/capture-problems.md",
"inline": "<!--\n MemPalace capability — contribution fragment.\n Rendered into the execute:wave:post verifier prompt when `mempalace.capture_artifacts` is true.\n Contributes DATA (capture instructions), not control flow. onError: skip — never fails a wave.\n-->\n### Capture problems → fixes (MemPalace)\n\n**Gate first.** Read `.planning/config.json`. If `mempalace.enabled` is not `true`, or `mempalace.capture_artifacts` is `false`, **skip this entire section** and let the wave complete unchanged. (This contribution is only injected when the capability is enabled; the `capture_artifacts` check lets you turn capture off without disabling the rest of the capability.)\n\nOtherwise — after verifying this wave, persist any *confirmed* problem→fix pairs into the palace so they are recalled in future phases. This is best-effort; if MemPalace is unreachable, skip silently — capture never fails a wave.\n\nFor each confirmed bug/issue resolved in this wave:\n\n1. **Resolve the wing** (`mempalace.wing`, else `project_code`, else project dir) and target `room: problems`.\n2. **Dedupe first.** Call `mempalace_check_duplicate` (interactive) before filing so re-runs don't create duplicate drawers.\n3. **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 --daemon --background` (no `--room` flag; the background queue defers the write when another writer holds the palace lock, #4700 — 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).\n4. **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`.\n5. **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.\n\nCaptures 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.\n"
},
"produces": [],
"consumes": [],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"gates": []
},
"nyquist": {
"id": "nyquist",
"role": "feature",
"version": "1.14.0",
"title": "Nyquist validation",
"description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"validate-phase"
],
"agents": [
"gsd-nyquist-auditor"
],
"hooks": [],
"config": {
"workflow.nyquist_validation": {
"type": "boolean",
"default": true,
"description": "Enable Nyquist validation coverage auditing."
}
},
"steps": [
{
"point": "verify:post",
"ref": {
"skill": "validate-phase"
},
"produces": [
"VALIDATION.md"
],
"consumes": [
"SUMMARY.md"
],
"when": "workflow.nyquist_validation",
"onError": "halt"
}
],
"contributions": [],
"gates": []
},
"ollama": {
"id": "ollama",
"role": "reviewer",
"version": "1.14.0",
"title": "Ollama",
"description": "Ollama local model server — cross-AI /gsd:review reviewer lane only; not a GSD install target (no runtime body, no artifacts). OpenAI-compatible HTTP transport against a user-configured `review.ollama_host` (POST /v1/chat/completions); model discovered via GET /v1/models piped through jq.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.8.0"
},
"reviewer": {
"slug": "ollama",
"flags": [
"--ollama"
],
"transport": "openai-http",
"probe": {
"kind": "http-reachable",
"hostConfigKey": "review.ollama_host",
"path": "/v1/models",
"timeoutMs": 2000
},
"invoke": {
"hostConfigKey": "review.ollama_host",
"defaultHost": "http://localhost:11434",
"path": "/v1/chat/completions",
"modelDiscovery": "first-from-models-endpoint",
"fallbackModel": "llama3",
"effortChannel": "none"
},
"timeoutFloorMs": 120000,
"timeoutConfigKey": "review.timeouts.ollama",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Ollama",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.ollama",
"modelConfigKey": "review.models.ollama",
"effortConfigKey": null,
"defaultEffort": null,
"handler": "openai-compatible"
},
"config": {
"review.models.ollama": {
"type": "string",
"default": "",
"description": "Model requested from the Ollama reviewer lane; empty discovers the first model from /v1/models."
},
"review.ollama_host": {
"type": "string",
"default": "",
"description": "Base URL of the Ollama OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.ollama": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Ollama reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.ollama": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Ollama reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"opencode": {
"id": "opencode",
"role": "runtime",
"version": "1.14.0",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat commands/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "xdg",
"name": "opencode",
"env": [
"OPENCODE_CONFIG_DIR",
"OPENCODE_CONFIG",
"XDG_CONFIG_HOME"
]
},
"localConfigDir": ".opencode",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToOpencodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToOpencodeFrontmatter"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToOpencodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToOpencodeFrontmatter"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "opencode",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": "opencode",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun",
"effortSurface": "argv"
},
"orchestratorExec": {
"command": "opencode",
"args": [
"run"
],
"cwdFlag": "--dir",
"promptFlag": null
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"attributionConfigResolver": "opencode",
"flatCommandDir": "commands",
"combinedFamilyInstall": true,
"frontmatterDialect": "opencode",
"nativePlugin": {
"dir": "plugins",
"file": "gsd-core.js",
"source": ".opencode/plugins/gsd-core.js"
},
"skipHomePrefixSubstitution": true,
"skipSettingsUi": true,
"skipUpdateBannerCommand": true
}
},
"reviewer": {
"slug": "opencode",
"flags": [
"--opencode"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "opencode"
},
"invoke": {
"binary": "opencode",
"args": [
"run",
"{{model}}",
"{{effort}}",
"--format",
"json",
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "argv"
},
"timeoutFloorMs": 660000,
"timeoutConfigKey": "review.timeouts.opencode",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "OpenCode",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.opencode",
"modelConfigKey": "review.models.opencode",
"effortConfigKey": "review.effort.opencode",
"defaultEffort": "high",
"handler": "opencode"
},
"config": {
"review.models.opencode": {
"type": "string",
"default": "",
"description": "Model passed to the OpenCode reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.opencode": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.opencode": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.opencode": {
"type": "string",
"default": "",
"description": "Reasoning effort for the OpenCode reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"pattern-mapper": {
"id": "pattern-mapper",
"role": "feature",
"version": "1.14.0",
"title": "Pattern mapping",
"description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.",
"tier": "full",
"requires": [
"research"
],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [
"gsd-pattern-mapper"
],
"hooks": [],
"config": {
"workflow.pattern_mapper": {
"type": "boolean",
"default": true,
"description": "Run the pattern mapper before planning when context or research is available."
}
},
"steps": [
{
"point": "plan:pre",
"ref": {
"agent": "gsd-pattern-mapper"
},
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "<pattern_mapping_context>\n**Phase:** {phase_number} - {phase_name}\n**Phase directory:** {phase_dir}\n**Padded phase:** {padded_phase}\n\n<required_reading>\n- {context_path} (USER DECISIONS from /gsd:discuss-phase)\n- {research_path} (Technical Research)\n</required_reading>\n\n**Output file:** {phase_dir}/{padded_phase}-PATTERNS.md\n\nExtract the list of files to be created/modified from CONTEXT.md and RESEARCH.md. For each file, classify by role and data flow, find the closest existing analog in the codebase, extract concrete code excerpts, and produce PATTERNS.md.\n</pattern_mapping_context>\n"
},
"produces": [
"PATTERNS.md"
],
"consumes": [
"RESEARCH.md"
],
"when": "workflow.pattern_mapper",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"pi": {
"id": "pi",
"role": "runtime",
"version": "1.14.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.js (.js, not .cjs — pi's extension auto-discovery accepts only .ts/.js, #2470); 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": [
"PI_CODING_AGENT_DIR"
]
},
"localConfigDir": ".pi",
"configFormat": "none",
"artifactLayout": {
"global": [],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"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",
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "session-log-append",
"transport": "native-extension",
"runtime": "bun",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"nativePlugin": {
"dir": "extensions",
"file": "gsd.js",
"source": "pi/gsd.cjs"
},
"pluginOnlyInstall": true,
"sharedHooksDirName": "gsd-hooks"
}
}
},
"profile-pipeline": {
"id": "profile-pipeline",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"profile-user"
],
"agents": [
"gsd-user-profiler"
],
"config": {
"profile-pipeline.enabled": {
"type": "boolean",
"default": false,
"description": "Enable the developer profiling pipeline commands (scan-sessions, extract-messages, profile-sample, write-profile, etc.)."
}
},
"commands": [
{
"family": "scan-sessions",
"module": "profile-pipeline-command-router.cjs",
"router": "routeScanSessions"
},
{
"family": "extract-messages",
"module": "profile-pipeline-command-router.cjs",
"router": "routeExtractMessages"
},
{
"family": "profile-sample",
"module": "profile-pipeline-command-router.cjs",
"router": "routeProfileSample"
},
{
"family": "write-profile",
"module": "profile-pipeline-command-router.cjs",
"router": "routeWriteProfile"
},
{
"family": "profile-questionnaire",
"module": "profile-pipeline-command-router.cjs",
"router": "routeProfileQuestionnaire"
},
{
"family": "generate-dev-preferences",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateDevPreferences"
},
{
"family": "generate-claude-profile",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateClaudeProfile"
},
{
"family": "generate-claude-md",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateClaudeMd"
}
],
"hooks": [],
"steps": [],
"contributions": [],
"gates": []
},
"qwen": {
"id": "qwen",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".qwen",
"env": [
"QWEN_CONFIG_DIR"
]
},
"localConfigDir": ".qwen",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skillPriorityFrontmatter": true,
"brandingRewrites": {
"CLAUDE.md": "QWEN.md",
"Claude Code": "Qwen Code",
".claude/": ".qwen/"
},
"legacyCommandsGsdCleanup": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true
}
},
"reviewer": {
"slug": "qwen",
"flags": [
"--qwen"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "qwen"
},
"invoke": {
"binary": "qwen",
"args": [
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": null,
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": null,
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Qwen",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.qwen",
"modelConfigKey": null,
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.max_prompt_tokens_per_reviewer.qwen": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Qwen Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
}
}
},
"refactor-trigger": {
"id": "refactor-trigger",
"role": "feature",
"version": "1.14.0",
"title": "Complexity-triggered refactor",
"description": "Measures the complexity of the code a phase touched and, when a function crosses a configured threshold or jumps past its recorded anchor, surfaces a scoped refactor proposal at .planning/phases/<N>/<NN>-REFACTOR.md. Advisory by default — it never edits code and never blocks. Opt-in strict mode blocks /gsd-ship while a proposal is untriaged; a declined proposal is recorded in the broken-windows ledger when that capability is present. Operationalizes 'refactor early, refactor often' as continuous pressure instead of a thing you have to remember (issue #1953).",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.10.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"activationKey": "refactor.trigger_enabled",
"config": {
"refactor.trigger_enabled": {
"type": "boolean",
"default": false,
"description": "Enable the complexity-triggered refactor hook. When true, an execute:post step evaluates the complexity of the files the phase touched and writes a scoped refactor proposal if a function crosses refactor.complexity_threshold or jumps past refactor.complexity_jump_delta. Opt-in; when false the hook never runs. Issue #1953."
},
"refactor.complexity_threshold": {
"type": "number",
"default": 15,
"description": "Absolute per-function complexity above which a refactor proposal is surfaced. Semantics match ESLint's `complexity: {max: N}` — the trigger is STRICTLY GREATER, so a score of exactly N does not trigger. Default 15 follows SonarSource's default; ESLint's own default is 20 and radon's rank C begins at 11. Raise it if proposals feel like noise."
},
"refactor.complexity_jump_delta": {
"type": "number",
"default": 5,
"description": "Complexity growth above which a refactor proposal is surfaced even when the absolute threshold is not reached. Measured against the function's anchor — the score recorded the last time the function was consciously dispositioned — so it accumulates across phases and catches slow creep the absolute threshold would miss. Strictly greater, as with the threshold."
},
"refactor.trigger_strict": {
"type": "boolean",
"default": false,
"description": "Record an untriaged refactor proposal as an open `deviation` entry in the broken-windows ledger, so it becomes a tracked task that must be resolved before ship. Off by default and deliberately so: a blocking complexity number is a metric an executor can satisfy by splitting one coherent function into two incoherent ones, so the entry clears on the proposal being DISPOSITIONED (gsd-tools refactor accept|decline), never on the score improving. Ship blocking is the broken-windows capability's existing ship:pre gate — enable it with workflow.windows_enforce. With broken-windows absent, strict mode still records the proposal locally and says so; it cannot block on its own. Advisory mode (the default) surfaces the same proposal and tracks nothing."
}
},
"commands": [
{
"family": "refactor",
"module": "refactor-trigger-command-router.cjs",
"router": "routeRefactorTriggerCommand"
}
],
"hooks": [],
"steps": [
{
"point": "execute:post",
"ref": {
"command": "refactor evaluate"
},
"produces": [
"REFACTOR.md"
],
"consumes": [],
"when": "refactor.trigger_enabled",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"research": {
"id": "research",
"role": "feature",
"version": "1.14.0",
"title": "Phase research",
"description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.",
"tier": "standard",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [
"gsd-phase-researcher"
],
"hooks": [],
"config": {
"workflow.research": {
"type": "boolean",
"default": true,
"description": "Run phase research before planning when research artifacts are missing or explicitly refreshed."
}
},
"steps": [
{
"point": "plan:pre",
"ref": {
"agent": "gsd-phase-researcher"
},
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "<objective>\nResearch how to implement Phase {phase_number}: {phase_name}\nAnswer: \"What do I need to know to PLAN this phase well?\"\n</objective>\n\n<required_reading>\n- {context_path} (USER DECISIONS from /gsd:discuss-phase)\n- {requirements_path} (Project requirements)\n- {state_path} (Project decisions and history)\n</required_reading>\n\n${AGENT_SKILLS_RESEARCHER}\n\n<additional_context>\n**Phase description:** {phase_description}\n**Phase requirement IDs (MUST address):** {phase_req_ids}\n\n**Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists; follow project-specific guidelines.\n**Project skills:** Check .claude/skills/ or .agents/skills/ directory if either exists. Read SKILL.md files and account for project skill patterns.\n</additional_context>\n\n<output>\nWrite to: {phase_dir}/{phase_num}-RESEARCH.md\n</output>\n"
},
"produces": [
"RESEARCH.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.research",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"schema-gate": {
"id": "schema-gate",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.schema_push_detection": {
"type": "boolean",
"default": true,
"description": "Enable ORM schema push detection during planning. When schema-relevant files are detected in the phase scope, a [BLOCKING] push task is injected into the plan."
}
},
"steps": [],
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "# Schema Push Detection Gate\n\n> Detects schema-relevant files in the phase scope 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.\n\nCheck if any files in the phase scope match schema patterns:\n\n```bash\nPHASE_SECTION=$(gsd_run query roadmap.get-phase \"${PHASE}\" --pick section 2>/dev/null)\n```\n\nScan `PHASE_SECTION`, `CONTEXT.md` (if loaded), and `RESEARCH.md` (if exists) for file paths matching these ORM patterns:\n\n| ORM | File Patterns |\n|-----|--------------|\n| Payload CMS | `src/collections/**/*.ts`, `src/globals/**/*.ts` |\n| Prisma | `prisma/schema.prisma`, `prisma/schema/*.prisma` |\n| Drizzle | `drizzle/schema.ts`, `src/db/schema.ts`, `drizzle/*.ts` |\n| Supabase | `supabase/migrations/*.sql` |\n| TypeORM | `src/entities/**/*.ts`, `src/migrations/**/*.ts` |\n\nAlso check if any existing PLAN.md files for this phase already reference these file patterns in `files_modified`.\n\n**If schema-relevant files detected:**\n\nSet `SCHEMA_PUSH_REQUIRED=true` and `SCHEMA_ORM={detected_orm}`.\n\nDetermine the push command for the detected ORM:\n\n| ORM | Push Command | Non-TTY Workaround |\n|-----|-------------|-------------------|\n| Payload CMS | `npx payload migrate` | `CI=true PAYLOAD_MIGRATING=true npx payload migrate` |\n| Prisma | `npx prisma db push` | `npx prisma db push --accept-data-loss` (if destructive) |\n| Drizzle | `npx drizzle-kit push` | `npx drizzle-kit push` |\n| Supabase | `supabase db push` | Set `SUPABASE_ACCESS_TOKEN` env var |\n| TypeORM | `npx typeorm migration:run` | `npx typeorm migration:run -d src/data-source.ts` |\n\nInject the following into the planner prompt (step 8) as an additional constraint:\n\n```markdown\n<schema_push_requirement>\n**[BLOCKING] Schema Push Required**\n\nThis phase modifies schema-relevant files ({detected_files}). The planner MUST include\na `[BLOCKING]` task that runs the database schema push command AFTER all schema file\nmodifications are complete but BEFORE verification.\n\n- ORM detected: {SCHEMA_ORM}\n- Push command: {push_command}\n- Non-TTY workaround: {env_hint}\n- If push requires interactive prompts that cannot be suppressed, flag the task for\n manual intervention with `autonomous: false`\n\nThis task is mandatory — the phase CANNOT pass verification without it. Build and\ntype checks will pass without the push (types come from config, not the live database),\ncreating a false-positive verification state.\n</schema_push_requirement>\n```\n\nDisplay: `Schema files detected ({SCHEMA_ORM}) — [BLOCKING] push task will be injected into plans`\n\n**If no schema-relevant files detected:** Skip silently.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.schema_push_detection",
"onError": "skip"
}
],
"gates": []
},
"security": {
"id": "security",
"role": "feature",
"version": "1.14.0",
"title": "Security enforcement",
"description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"secure-phase"
],
"agents": [
"gsd-security-auditor"
],
"hooks": [],
"config": {
"workflow.security_enforcement": {
"type": "boolean",
"default": true,
"description": "Enable security threat-mitigation verification before phase advancement."
},
"workflow.security_asvs_level": {
"type": "number",
"default": 1,
"description": "OWASP ASVS level used by security review guidance."
},
"workflow.security_block_on": {
"type": "enum",
"values": [
"critical",
"high",
"medium",
"low",
"none"
],
"default": "high",
"description": "Minimum open threat severity that blocks advancement."
}
},
"steps": [
{
"point": "verify:post",
"ref": {
"skill": "secure-phase"
},
"produces": [
"SECURITY.md"
],
"consumes": [
"SUMMARY.md"
],
"when": "workflow.security_enforcement",
"onError": "halt"
}
],
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"inline": "Each PLAN.md must include a <threat_model> block when security enforcement is active. Use the configured ASVS level and blocking threshold from workflow.security_asvs_level and workflow.security_block_on."
},
"configValues": {
"security_asvs_level": "workflow.security_asvs_level",
"security_block_on": "workflow.security_block_on"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.security_enforcement"
}
],
"gates": [
{
"point": "ship:pre",
"check": {
"predicate": {
"kind": "artifact-frontmatter-equals",
"artifact": "SECURITY.md",
"field": "threats_open",
"equals": 0
}
},
"when": "workflow.security_enforcement",
"blocking": true,
"onError": "halt"
}
]
},
"tdd": {
"id": "tdd",
"role": "feature",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [],
"agents": [],
"hooks": [],
"config": {
"workflow.tdd_mode": {
"type": "boolean",
"default": false,
"description": "Enable TDD mode: planner annotates eligible tasks type:tdd and executor enforces RED/GREEN/REFACTOR gate sequence."
}
},
"steps": [],
"contributions": [
{
"point": "plan:pre",
"into": "planner",
"fragment": {
"inline": "<tdd_mode_active>\n**TDD Mode is ENABLED.** Apply TDD heuristics to all eligible tasks:\n- Business logic with defined I/O → type: tdd\n- API endpoints with request/response contracts → type: tdd\n- Data transformations, validation, algorithms → type: tdd\n- UI, config, glue code, CRUD → standard plan (type: execute)\nEach TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.\n</tdd_mode_active>"
},
"produces": [],
"consumes": [],
"when": "workflow.tdd_mode",
"onError": "skip"
}
],
"gates": [
{
"point": "execute:post",
"check": {
"query": "tdd.review-checkpoint"
},
"when": "workflow.tdd_mode",
"blocking": false,
"onError": "skip"
}
]
},
"trae": {
"id": "trae",
"role": "runtime",
"version": "1.14.0",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".trae",
"env": [
"TRAE_CONFIG_DIR"
]
},
"localConfigDir": ".trae",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "engine",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true,
"soloStageMetadata": "workflow",
"projectInstructionFile": ".trae/rules/rules.md"
}
}
},
"ui": {
"id": "ui",
"role": "feature",
"version": "1.14.0",
"title": "UI design contracts",
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
"tier": "full",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtimeCompat": {
"supported": [
"*"
],
"unsupported": []
},
"skills": [
"ui-phase",
"ui-review"
],
"agents": [
"gsd-ui-checker",
"gsd-ui-auditor"
],
"hooks": [],
"config": {
"workflow.ui_phase": {
"type": "boolean",
"default": true,
"description": "Enable the UI design-contract gate during planning."
},
"workflow.ui_review": {
"type": "boolean",
"default": true,
"description": "Enable the retrospective UI audit."
},
"workflow.ui_safety_gate": {
"type": "boolean",
"default": true,
"description": "Block execution on unmet UI-SPEC contracts."
},
"workflow.ui_interaction_capture": {
"type": "boolean",
"default": false,
"description": "Default-off. Let gsd-ui-auditor add post-interaction captures (hover, focus, open menus, filled forms) through the chrome-devtools CLI, driven from Bash — no MCP server and no tools: change (#4223). Requires an installed Chrome; when off, or when none resolves, the auditor's Playwright-only static capture is unchanged."
}
},
"steps": [
{
"point": "plan:pre",
"ref": {
"skill": "ui-phase"
},
"produces": [
"UI-SPEC.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.ui_phase",
"onError": "skip"
},
{
"point": "verify:post",
"ref": {
"skill": "ui-review"
},
"produces": [
"UI-REVIEW.md"
],
"consumes": [
"UI-SPEC.md"
],
"when": "workflow.ui_review",
"onError": "skip"
}
],
"contributions": [],
"gates": [
{
"point": "plan:pre",
"check": {
"query": "ui.plan-gate"
},
"when": "workflow.ui_safety_gate",
"blocking": true,
"onError": "halt"
},
{
"point": "execute:wave:post",
"check": {
"query": "ui.safety-gate"
},
"when": "workflow.ui_safety_gate",
"blocking": true,
"onError": "halt"
}
]
},
"vscode": {
"id": "vscode",
"role": "runtime",
"version": "1.14.0",
"title": "VS Code",
"description": "VS Code — Marketplace/VSIX extension; no file-projected config directory; IDE-profile reference host (active vscode.lm model, engine-owned hook bus, sandboxed globalState/workspaceState stateIO).",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtime": {
"configHome": {
"kind": "none",
"name": "vscode",
"env": []
},
"localConfigDir": null,
"configFormat": "none",
"artifactLayout": {
"global": [],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "none",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "none",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "palette",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "engine",
"stateIO": "sandboxed-storage",
"transport": "mcp",
"runtime": "sandboxed-web",
"effortSurface": "undocumented"
}
}
},
"windsurf": {
"id": "windsurf",
"role": "runtime",
"version": "1.14.0",
"title": "Windsurf",
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; Cascade native hooks.json blocking hook bus (pre_write_code, pre_run_command); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home-nested",
"name": "windsurf",
"parent": ".codeium",
"env": [
"WINDSURF_CONFIG_DIR"
]
},
"localConfigDir": ".windsurf",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "workflows",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfWorkflow"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "windsurf-hooks-json",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": "undocumented",
"nested": "undocumented",
"maxDepth": "undocumented",
"background": "undocumented",
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "undocumented",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true,
"legacyDevinSkillsCleanup": true,
"installsCommandBodiesForWorkflowDelegation": true,
"verificationStyle": "windsurf-workflows"
}
}
},
"zcode": {
"id": "zcode",
"role": "runtime",
"version": "1.14.0",
"title": "ZCode",
"description": "ZCode (Z.ai) — desktop Agentic Development Environment for GLM-5.2; Claude-shaped nested skills at ~/.zcode/skills/<name>/SKILL.md, slash commands, named subagents, native MCP; declarative plugin surface; profile-marker install; tier-2 community support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".zcode",
"env": [
"ZCODE_CONFIG_DIR"
]
},
"localConfigDir": ".zcode",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToZcodeAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToZcodeAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "electron",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true
}
}
}
};
const bySkill = {
"ai-integration-phase": "ai-integration",
"code-review": "code-review",
"graphify": "graphify",
"mempalace-recall": "mempalace",
"mempalace-capture": "mempalace",
"validate-phase": "nyquist",
"profile-user": "profile-pipeline",
"secure-phase": "security",
"ui-phase": "ui",
"ui-review": "ui"
};
const byAgent = {
"gsd-framework-selector": "ai-integration",
"gsd-ai-researcher": "ai-integration",
"gsd-domain-researcher": "ai-integration",
"gsd-eval-planner": "ai-integration",
"gsd-code-reviewer": "code-review",
"gsd-code-fixer": "code-review",
"gsd-dom-verifier": "live-dom-uat",
"gsd-mempalace-curator": "mempalace",
"gsd-nyquist-auditor": "nyquist",
"gsd-pattern-mapper": "pattern-mapper",
"gsd-user-profiler": "profile-pipeline",
"gsd-phase-researcher": "research",
"gsd-security-auditor": "security",
"gsd-ui-checker": "ui",
"gsd-ui-auditor": "ui"
};
const byLoopPoint = {
"discuss:pre": {
"steps": [],
"contributions": [
{
"capId": "mempalace",
"point": "discuss:pre",
"into": "orchestrator",
"fragment": {
"path": "fragments/recall-discuss.md",
"inline": "<!--\n MemPalace capability — contribution fragment.\n Rendered into the discuss:pre orchestrator prompt when `mempalace.recall_on_discuss` is true.\n Contributes DATA (recall instructions), not control flow. onError: skip — never blocks discussion.\n-->\n### Memory recall (MemPalace)\n\n**Gate first.** Read `.planning/config.json`. If `mempalace.enabled` is not `true`, or `mempalace.recall_on_discuss` is `false`, **skip this entire section** and continue the discussion unchanged. (This contribution is only injected when the capability is enabled; the `recall_on_discuss` check lets you turn discuss-time recall off without disabling the rest of the capability.)\n\nOtherwise — before gathering new context, surface what you already know. This is read-only and side-effect-free; if MemPalace is unreachable, note \"memory unavailable\" and continue — recall never blocks discussion.\n\n1. **Resolve the wing.** Use `mempalace.wing` if set; otherwise derive it from `project_code` (fall back to the project directory name).\n2. **Wake up (cheap, ~600–900 tokens).**\n - Interactive run → call `mempalace_search` after a wake-up read of the wing.\n - Headless/cron run (no MCP server) → run `mempalace wake-up --wing <wing>` via the CLI.\n3. **Targeted recall.** Search the palace for prior work on this phase's topic:\n - 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.\n - Headless → `mempalace search \"<phase topic>\" --wing <wing>`.\n4. **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.\n5. **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.\n\nIf any MemPalace call errors or times out, skip the rest of recall and proceed with discussion as normal.\n"
},
"produces": [],
"consumes": [],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"gates": []
},
"discuss:post": {
"steps": [
{
"capId": "mempalace",
"point": "discuss:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"plan:pre": {
"steps": [
{
"capId": "ai-integration",
"point": "plan:pre",
"ref": {
"skill": "ai-integration-phase"
},
"produces": [
"AI-SPEC.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.ai_integration_phase",
"onError": "skip"
},
{
"capId": "intel",
"point": "plan:pre",
"ref": {
"command": "intel api-surface"
},
"produces": [
".planning/intel/API-SURFACE.md"
],
"consumes": [],
"when": "intel.enabled",
"onError": "skip"
},
{
"capId": "mempalace",
"point": "plan:pre",
"ref": {
"skill": "mempalace-recall"
},
"produces": [
"MEMORY-RECALL.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"capId": "research",
"point": "plan:pre",
"ref": {
"agent": "gsd-phase-researcher"
},
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "<objective>\nResearch how to implement Phase {phase_number}: {phase_name}\nAnswer: \"What do I need to know to PLAN this phase well?\"\n</objective>\n\n<required_reading>\n- {context_path} (USER DECISIONS from /gsd:discuss-phase)\n- {requirements_path} (Project requirements)\n- {state_path} (Project decisions and history)\n</required_reading>\n\n${AGENT_SKILLS_RESEARCHER}\n\n<additional_context>\n**Phase description:** {phase_description}\n**Phase requirement IDs (MUST address):** {phase_req_ids}\n\n**Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists; follow project-specific guidelines.\n**Project skills:** Check .claude/skills/ or .agents/skills/ directory if either exists. Read SKILL.md files and account for project skill patterns.\n</additional_context>\n\n<output>\nWrite to: {phase_dir}/{phase_num}-RESEARCH.md\n</output>\n"
},
"produces": [
"RESEARCH.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.research",
"onError": "skip"
},
{
"capId": "ui",
"point": "plan:pre",
"ref": {
"skill": "ui-phase"
},
"produces": [
"UI-SPEC.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.ui_phase",
"onError": "skip"
},
{
"capId": "pattern-mapper",
"point": "plan:pre",
"ref": {
"agent": "gsd-pattern-mapper"
},
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "<pattern_mapping_context>\n**Phase:** {phase_number} - {phase_name}\n**Phase directory:** {phase_dir}\n**Padded phase:** {padded_phase}\n\n<required_reading>\n- {context_path} (USER DECISIONS from /gsd:discuss-phase)\n- {research_path} (Technical Research)\n</required_reading>\n\n**Output file:** {phase_dir}/{padded_phase}-PATTERNS.md\n\nExtract the list of files to be created/modified from CONTEXT.md and RESEARCH.md. For each file, classify by role and data flow, find the closest existing analog in the codebase, extract concrete code excerpts, and produce PATTERNS.md.\n</pattern_mapping_context>\n"
},
"produces": [
"PATTERNS.md"
],
"consumes": [
"RESEARCH.md"
],
"when": "workflow.pattern_mapper",
"onError": "skip"
}
],
"contributions": [
{
"capId": "ai-integration",
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/api-coverage-plan-pre.md",
"inline": "# API Coverage Decision Checkpoint\n\n> Full API Coverage by Default — Opt Out, Never Opt In. Fires when a phase\n> integrates an external API / SDK / service. Most non-API phases will not fire\n> it — that is the point.\n\n## Why this exists\n\n\"We integrated the API\" too often silently means \"we integrated whatever the\nfirst use case exercised.\" Every un-built capability is then an invisible hole,\ndiscovered later by a user who reasonably expected it to work. The phase sealed\ngreen because its tasks completed; nobody decided the gaps were acceptable,\nbecause nobody enumerated them. This checkpoint makes the surface **visible and\ndecided** before the phase can seal.\n\n## Detect whether this phase integrates an external API\n\nThe detector is a deterministic scan over the phase scope. It strips fenced\ncode blocks first, so a trigger term inside a code snippet does not fire. It\nreturns a typed result: `{ detected, signals[], terms }`. Run it on the phase\nscope (the concatenation of this phase's ROADMAP section + the PLAN body):\n\n```bash\nSCOPE=\"$(cat \"${PHASE_DIR}\"/*-PLAN.md 2>/dev/null) $(gsd_run query roadmap.get-phase \"${PHASE}\" 2>/dev/null || true)\"\nAPI_COVERAGE_JSON=$(printf '%s' \"$SCOPE\" | node gsd-core/bin/lib/api-coverage.cjs --json 2>/dev/null) || true\n[ -n \"$API_COVERAGE_JSON\" ] || API_COVERAGE_JSON='{\"skipped\":true,\"reason\":\"probe_unavailable\"}'\n```\n\nThe `|| true` neutralizes the assignment's status without discarding the\ndetector's own payload: the detector exits **1** for a real \"no integration\"\nverdict, so treating any non-zero exit as failure would throw away a correct\nanswer. Emptiness — not exit status — is what proves the probe never ran, and\nthe second line is the only place the fragment manufactures a payload of its\nown — one that records the *absence* of a verdict rather than asserting one.\n\nThe detector's exit code and `--json` payload now distinguish a real negative\nfrom an unexamined input (ADR-3889 Phase 3, #3907): empty/whitespace-only\n`$SCOPE` or a stdin read failure emit `{\"skipped\":true,\"reason\":\"no_input\"|\n\"stdin_error\"}` — no `detected` key at all. **Check for `skipped` before\nreading `detected`**: a `skipped` payload is not a confirmed \"no API\nintegration\" verdict, it means the detector never examined real input. Do not\ntreat it as `detected:false`. Read `API_COVERAGE_JSON.detected` only when\n`skipped` is absent — act on it only, do **not** pattern-match the prose\nyourself.\n\n**If `skipped` is `true`:** the detector could not establish a scope (empty\n`$SCOPE`) or failed to run (stdin read error). Skip the checkpoint for this\nrun rather than asserting a verdict about input that was never examined; do\nnot raise it with the user.\n\n**If `detected` is `false`:** this phase does not integrate an external API. Skip\nthe checkpoint entirely and continue planning. Do not raise it with the user.\n\n**If `detected` is `true`:** an external-API integration is in scope. You MUST\nproduce a **coverage matrix** before the plan is finalized.\n\n**If `detected` is `true` but the phase genuinely integrates no external API**\n(the detector is deterministic, not infallible — confirm by re-reading the phase\nscope, not by preference): do NOT fabricate a matrix row for a capability that\ndoes not exist. Write a reasoned declaration to `${PHASE_DIR}/COVERAGE.md`\ninstead:\n\n```markdown\nNo external API integration: <one-line reason — what the phase touches instead>.\n```\n\nThe reason is required, exactly like an `OPT-OUT` reason. The seal-time gate\naccepts this declaration in place of a matrix.\n\n## Produce the coverage matrix\n\nEnumerate the external API's full **capability surface** — the verb/endpoint/method\nlist (e.g. for a music service: `search`, `play`, `pause`, `skip`, `set_volume`,\n`get_playlist`, `create_playlist`, `add_to_playlist`, …). For each capability\nrecord a decision, starting from **full coverage** as the default:\n\n| capability | decision | reason |\n|---|---|---|\n| `<capability-id>` | `INTEGRATE` \\| `OPT-OUT` | `<one-line reason if OPT-OUT>` |\n\nRules:\n\n- **`INTEGRATE` is the default.** Every capability starts as INTEGRATE; the\n matrix is the *subtraction record*.\n- **Every `OPT-OUT` MUST carry a one-line reason** (`not needed`, `not needed\n yet`, `explicitly out of scope`, …). An opt-out without a reason is an\n un-decided hole — the exact failure mode this gate exists to close.\n- **A second integration against the same need** (e.g. a second platform for the\n same capability) starts from the **same full-coverage baseline** as the first.\n Do not carry over the first integration's opt-outs silently — re-decide each\n capability for the new surface, so a first-class/fallback asymmetry cannot\n accumulate.\n\nWrite the matrix to `${PHASE_DIR}/COVERAGE.md` (canonical markdown-table form):\n\n```markdown\n# API Coverage — <service>\n\n> Full coverage by default. Opt-outs are explicit, reasoned decisions.\n\n| capability | decision | reason |\n|---|---|---|\n| search | INTEGRATE | |\n| playlists | INTEGRATE | |\n| skip | OPT-OUT | not needed yet — tracked for follow-up phase |\n```\n\nA fenced ` ```coverage ` JSON block is also accepted for machine-generated\nmatrices; the markdown table is preferred (human-editable, diff-friendly).\n\n## The seal-time gate\n\nThis checkpoint is enforced. At `verify:pre` the `api-coverage.verify-pre` gate\nruns `check api-coverage.verify-pre <phase-dir>`:\n\n- If `COVERAGE.md` exists, it is validated — every row needs a valid decision and\n every `OPT-OUT` a reason. A malformed/partial matrix **blocks the seal**. A\n reasoned `No external API integration: …` declaration (and no rows) passes.\n- If `COVERAGE.md` is absent, the detector runs again over the phase scope. If a\n strong external-API-integration signal is found, the seal is **blocked** until a\n matrix is produced. If no signal is found, the phase is treated as a non-API\n phase and the seal proceeds.\n\nSo: an API-integrating phase cannot seal without a decided matrix. Produce it at\nplan time; do not leave it for seal time.\n\n## Tuning the vocabulary (optional)\n\nThe trigger vocabulary is a curated, additive-only set in\n`gsd-core/bin/lib/api-coverage.cjs` (`DEFAULT_API_COVERAGE_TERMS`). To widen it\nfor a project, override at the call site:\n\n```bash\nprintf '%s' \"$SCOPE\" | node gsd-core/bin/lib/api-coverage.cjs --json \\\n --verbs integrate,wrap,connect,embed --nouns api,sdk,rest,grpc,webhook,plugin\n```\n\nThe whole checkpoint is toggleable via `workflow.api_coverage_gate` in\n`.planning/config.json`.\n"
},
"produces": [
"COVERAGE.md"
],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.api_coverage_gate",
"onError": "skip"
},
{
"capId": "assumption-delta",
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "# Assumption-Delta Architecture Checkpoint\n\n> 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.\n\n## Why this exists\n\nMost 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.\n\n## Run the detector\n\nThe 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`):\n\n```bash\nASSUMPTION_DELTA_JSON=$(gsd_run query assumption-delta scan \"${PHASE}\" --json 2>/dev/null) || true\n[ -n \"$ASSUMPTION_DELTA_JSON\" ] || ASSUMPTION_DELTA_JSON='{\"skipped\":true,\"reason\":\"probe_unavailable\"}'\n```\n\n> If the phase section cannot be resolved (no `ROADMAP.md` / unknown phase, or a section with no body), the query emits `{ \"skipped\": true, \"reason\": \"phase_unresolved\" }` — **not** `detected:false`. A probe that never had input does not get to assert that this phase changes no core assumption. The checkpoint does not fire either way; the difference is that a skip is now distinguishable from a real negative. Do not block on it.\n>\n> 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`.\n\n## Decision branch\n\nRead `ASSUMPTION_DELTA_JSON`. Act on `detected` only — do **not** pattern-match the human prose.\n\n**If `skipped` is `true`:** the detector never examined a phase section — it could not resolve one (`phase_unresolved`) or could not run at all (`probe_unavailable`). Skip the checkpoint for this run rather than asserting a verdict about input that was never examined; do not raise it with the user. **Check for `skipped` before reading `detected`** — a skipped payload carries no `detected` key, and treating its absence as `false` re-creates the fabrication this branch exists to prevent.\n\n**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.\n\n**If `detected` is `true`:** a core assumption may have lost its monopoly. The `signals[]` array tells you which family fired:\n\n| `kind` | What changed | The question to answer |\n|---|---|---|\n| `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? |\n| `optional` | A required / `only` field became optional | Is the field still the right anchor, or has the anchor moved? |\n| `chosen` | A derived value became chosen, or a constant became a parameter | Has a configuration decision become a modeling decision? |\n\nBefore finalizing the plan, answer this for the user and record the decision explicitly:\n\n> **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).\n\nRecord the outcome in the PLAN.md front matter / a `<assumption_delta_decision>` block:\n\n- The **noun** that is now primary (the generalized identity).\n- The **decision**: `promote` | `add-alongside` | `no-change`, with a one-line rationale.\n- If `add-alongside`: call it out as accepted debt and note what would force a later promote.\n\n## Optional companion: an invariant test\n\nWhen `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.\n\n## Tuning the vocabulary (optional)\n\nThe 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`.\n\nThis checkpoint is advisory: it informs and records; it never blocks the phase.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.assumption_delta",
"onError": "skip"
},
{
"capId": "schema-gate",
"point": "plan:pre",
"into": "planner",
"fragment": {
"path": "fragments/plan-pre.md",
"inline": "# Schema Push Detection Gate\n\n> Detects schema-relevant files in the phase scope 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.\n\nCheck if any files in the phase scope match schema patterns:\n\n```bash\nPHASE_SECTION=$(gsd_run query roadmap.get-phase \"${PHASE}\" --pick section 2>/dev/null)\n```\n\nScan `PHASE_SECTION`, `CONTEXT.md` (if loaded), and `RESEARCH.md` (if exists) for file paths matching these ORM patterns:\n\n| ORM | File Patterns |\n|-----|--------------|\n| Payload CMS | `src/collections/**/*.ts`, `src/globals/**/*.ts` |\n| Prisma | `prisma/schema.prisma`, `prisma/schema/*.prisma` |\n| Drizzle | `drizzle/schema.ts`, `src/db/schema.ts`, `drizzle/*.ts` |\n| Supabase | `supabase/migrations/*.sql` |\n| TypeORM | `src/entities/**/*.ts`, `src/migrations/**/*.ts` |\n\nAlso check if any existing PLAN.md files for this phase already reference these file patterns in `files_modified`.\n\n**If schema-relevant files detected:**\n\nSet `SCHEMA_PUSH_REQUIRED=true` and `SCHEMA_ORM={detected_orm}`.\n\nDetermine the push command for the detected ORM:\n\n| ORM | Push Command | Non-TTY Workaround |\n|-----|-------------|-------------------|\n| Payload CMS | `npx payload migrate` | `CI=true PAYLOAD_MIGRATING=true npx payload migrate` |\n| Prisma | `npx prisma db push` | `npx prisma db push --accept-data-loss` (if destructive) |\n| Drizzle | `npx drizzle-kit push` | `npx drizzle-kit push` |\n| Supabase | `supabase db push` | Set `SUPABASE_ACCESS_TOKEN` env var |\n| TypeORM | `npx typeorm migration:run` | `npx typeorm migration:run -d src/data-source.ts` |\n\nInject the following into the planner prompt (step 8) as an additional constraint:\n\n```markdown\n<schema_push_requirement>\n**[BLOCKING] Schema Push Required**\n\nThis phase modifies schema-relevant files ({detected_files}). The planner MUST include\na `[BLOCKING]` task that runs the database schema push command AFTER all schema file\nmodifications are complete but BEFORE verification.\n\n- ORM detected: {SCHEMA_ORM}\n- Push command: {push_command}\n- Non-TTY workaround: {env_hint}\n- If push requires interactive prompts that cannot be suppressed, flag the task for\n manual intervention with `autonomous: false`\n\nThis task is mandatory — the phase CANNOT pass verification without it. Build and\ntype checks will pass without the push (types come from config, not the live database),\ncreating a false-positive verification state.\n</schema_push_requirement>\n```\n\nDisplay: `Schema files detected ({SCHEMA_ORM}) — [BLOCKING] push task will be injected into plans`\n\n**If no schema-relevant files detected:** Skip silently.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.schema_push_detection",
"onError": "skip"
},
{
"capId": "security",
"point": "plan:pre",
"into": "planner",
"fragment": {
"inline": "Each PLAN.md must include a <threat_model> block when security enforcement is active. Use the configured ASVS level and blocking threshold from workflow.security_asvs_level and workflow.security_block_on."
},
"configValues": {
"security_asvs_level": "workflow.security_asvs_level",
"security_block_on": "workflow.security_block_on"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "workflow.security_enforcement"
},
{
"capId": "tdd",
"point": "plan:pre",
"into": "planner",
"fragment": {
"inline": "<tdd_mode_active>\n**TDD Mode is ENABLED.** Apply TDD heuristics to all eligible tasks:\n- Business logic with defined I/O → type: tdd\n- API endpoints with request/response contracts → type: tdd\n- Data transformations, validation, algorithms → type: tdd\n- UI, config, glue code, CRUD → standard plan (type: execute)\nEach TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.\n</tdd_mode_active>"
},
"produces": [],
"consumes": [],
"when": "workflow.tdd_mode",
"onError": "skip"
}
],
"gates": [
{
"capId": "drift",
"point": "plan:pre",
"check": {
"query": "verify.codebase-drift"
},
"when": "workflow.plan_drift_precheck",
"blocking": false,
"onError": "skip"
},
{
"capId": "drift",
"point": "plan:pre",
"check": {
"query": "verify.context-drift"
},
"when": "workflow.context_drift_precheck",
"blocking": false,
"onError": "skip"
},
{
"capId": "ui",
"point": "plan:pre",
"check": {
"query": "ui.plan-gate"
},
"when": "workflow.ui_safety_gate",
"blocking": true,
"onError": "halt"
}
]
},
"plan:post": {
"steps": [
{
"capId": "mempalace",
"point": "plan:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"PLAN.md"
],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"contributions": [
{
"capId": "claude-orchestration",
"point": "plan:post",
"into": "planner",
"fragment": {
"path": "fragments/plan-post.md",
"inline": "# Claude orchestration — ultraplan plan-offload ownership (BETA)\n\n> Injected at `plan:post` `into: planner` only when\n> `claude_orchestration.enabled` is true. Default-off; `onError: skip`.\n\n## Ownership declaration\n\nThe `gsd-ultraplan-phase` plan-offload surface (offloading GSD's plan phase to\nClaude Code's ultraplan cloud) is **owned by this capability**, not by a\nstandalone BETA skill. Both surfaces share one runtime gate\n(`claude_orchestration.enabled`), one BETA boundary, and one Claude-Code-only\ndetection seam.\n\n## When the planner should consider ultraplan offload\n\nWhen this contribution is active (capability enabled, Claude Code runtime), the\nplanner MAY offer the `/gsd-ultraplan-phase` path as an alternative to local\n`/gsd-plan-phase` for phases where cloud-assisted planning adds value. This is\nadvisory, not mandatory — the stable local planner remains the default.\n\n## Fallback contract\n\nIf the capability is disabled, or the runtime is not Claude Code, ultraplan\noffload is **not surfaced** and the planner proceeds with the standard local\n`/gsd-plan-phase`. The `gsd-ultraplan-phase` command itself remains installed\n(its own runtime gate already no-ops on non-Claude runtimes); this contribution\nonly governs whether the capability manifest advertises it as part of the\norchestration surface.\n"
},
"produces": [],
"consumes": [
"CONTEXT.md"
],
"when": "claude_orchestration.enabled",
"onError": "skip"
},
{
"capId": "external-job",
"point": "plan:post",
"into": "planner",
"fragment": {
"path": "fragments/plan-post.md",
"inline": "<!-- external-job capability — plan:post fragment, injected into the planner (#1164). -->\n\n## Tag runtime budgets on long tasks\n\nFor every `<task>` likely to exceed ~2 minutes of real compute, emit a\n`<runtime_budget>` child element so execute can classify it:\n\n- `<runtime_budget>quick</runtime_budget>` — under ~2 min; runs normally.\n- `<runtime_budget>medium</runtime_budget>` — ~2–30 min; foreground, but with\n progress expectations.\n- `<runtime_budget>unknown</runtime_budget>` — runtime not yet characterized;\n execute must run a first-health check and set a soft-review deadline before\n trusting the child timeout. Define a progress signal and an abort condition.\n- `<runtime_budget>long_compute</runtime_budget>` — legitimately over ~30–60 min\n (HPC solver, model training, large simulations). Execute must **externalize**\n this as an async external job (see the execute:wave:post fragment) rather than\n blocking the agent turn.\n\nFor any `long_compute` task, also declare the async contract the executor will\nneed: the `submit_command`, the `expected_artifacts` the job must produce, and\nthe `verification_command` that proves the output before the plan can close.\nDo not hardcode a cluster account, partition, or project path — the planner\nnever knows the scheduler layout; it declares the contract, the executor's\nadapter fills the backend specifics.\n"
},
"produces": [],
"consumes": [],
"when": "external_job.enabled",
"onError": "skip"
}
],
"gates": [
{
"capId": "gap-analysis",
"point": "plan:post",
"check": {
"query": "gap-analysis.plan-post"
},
"when": "workflow.post_planning_gaps",
"blocking": false,
"onError": "skip"
}
]
},
"execute:pre": {
"steps": [],
"contributions": [],
"gates": []
},
"execute:wave:pre": {
"steps": [],
"contributions": [],
"gates": []
},
"execute:wave:post": {
"steps": [
{
"capId": "code-review",
"point": "execute:wave:post",
"ref": {
"skill": "code-review"
},
"produces": [
"REVIEW.md"
],
"consumes": [],
"supportsReviewerLanes": true,
"when": "workflow.code_review",
"pointFrom": "workflow.code_review_point",
"onError": "skip"
},
{
"capId": "live-dom-uat",
"point": "execute:wave:post",
"ref": {
"agent": "gsd-dom-verifier"
},
"fragment": {
"path": "fragments/execute-wave-post.md",
"inline": "<objective>\nVerify the live-DOM acceptance criteria for the execution wave that just completed.\nAnswer: \"of this wave's stated UI acceptance criteria, which can I observe in a live DOM\nright now, and which could I not look at?\"\n\nThis step is ADDITIVE. It never halts the wave, never fails the phase, and never rewrites\nSUMMARY.md. If you cannot look, say so and finish.\n</objective>\n\n<required_reading>\n- {phase_dir}/{phase_num}-PLAN.md (the wave's tasks and their acceptance criteria)\n- {phase_dir}/{phase_num}-UI-SPEC.md if it exists (the design contract, when the phase has one)\n</required_reading>\n\n<browser_surface>\nYou carry exactly two browser MCP families: `mcp__chrome-devtools__*` and\n`mcp__claude-in-chrome__*`. Use whichever responds. Do not assume they expose the same\ntool names — probe, then use what is there. Do not paper over differences between them.\n\nYou do NOT carry the Playwright MCP family. That path belongs to the orchestrator's\nown verification step and is not yours.\n</browser_surface>\n\n<profile_lock>\n`chrome-devtools-mcp` holds an exclusive lock on its browser profile\n(`$HOME/.cache/chrome-devtools-mcp/chrome-profile`). A second concurrent instance fails with:\n\n```\nThe browser is already running for <dir>. Use --isolated to run multiple browser instances.\n```\n\nIf you see that, or any equivalent lock error:\n\n1. Record `outcome: could_not_look` and `reason: profile_locked`.\n2. Name `--isolated` in the notes, so the operator knows the remedy is a flag on THEIR MCP\n server registration.\n3. **Stop.** Do not retry, do not loop, do not wait for the lock. GSD cannot pass\n `--isolated` — it is not GSD's flag — and a retry loop here just holds up the wave.\n\nParallel execution waves sharing one profile WILL hit this. It is an expected condition,\nnot a defect, and it is not a reason to fail anything.\n</profile_lock>\n\n<method>\nFor each UI acceptance criterion you can identify in the wave's plan:\n\n1. Resolve its target URL. If no dev server or target is reachable, that criterion is\n `could_not_look` / `target_unreachable` — not a failure.\n2. Open it with the browser family that responded.\n3. Observe the DOM for the specific, stated condition. Assert on structure and content —\n an element's presence, its text, its attributes, its computed state.\n4. Record `passed` when the stated condition is observably true, `needs_review` when it is\n ambiguous or requires human judgement (subjective aesthetics, content accuracy).\n\nScope limit for this version: DOM observation against stated criteria only. No screenshot\ndiffing, no accessibility audit, no performance tracing. If a criterion needs one of those,\nmark it `needs_review` and say which.\n\nNever invent a criterion. If the plan states no UI acceptance criteria, that is\n`outcome: nothing_to_report` / `reason: no_criteria`, and it is a perfectly good result.\n</method>\n\n<output>\nWrite to: {phase_dir}/{phase_num}-DOM-VERIFY.md\n\nFrontmatter carries scalars only, so a reader can get the verdict without parsing prose:\n\n```\n---\nschema_version: 1\nwave: {wave_number}\noutcome: verified | nothing_to_report | could_not_look\nreason: ok | no_criteria | no_browser_mcp | profile_locked | target_unreachable\nchecked: <integer>\npassed: <integer>\nneeds_review: <integer>\n---\n```\n\nThen a short body: one line per criterion with its verdict, and — when `outcome` is\n`could_not_look` — exactly what stopped you and what the operator would change.\n\n**`nothing_to_report` and `could_not_look` are different outcomes and must never be\nconflated.** \"There were no UI criteria in this wave\" and \"there were criteria but I had no\nbrowser\" look identical in a summary that collapses them, and that ambiguity is the reported\nproblem this capability exists to remove.\n</output>\n"
},
"produces": [
"DOM-VERIFY.md"
],
"consumes": [
"PLAN.md"
],
"when": "workflow.live_dom_uat",
"onError": "skip"
}
],
"contributions": [
{
"capId": "external-job",
"point": "execute:wave:post",
"into": "executor",
"fragment": {
"path": "fragments/execute-wave-post.md",
"inline": "<!-- external-job capability — execute:wave:post fragment, injected into the executor (#1164).\n\n #1164 specifies classification at wave:pre and recording at wave:post. This\n capability still contributes executor guidance at wave:post; execute-phase\n now renders wave:pre entries and dispatches generic step hooks there. Moving\n external-job classification is a separate capability change, not part of\n #4148. Until then, this guidance cannot classify the wave that already ran. -->\n\n## Externalize long-running compute (async external job)\n\nIf the current plan's task is tagged `<runtime_budget>long_compute</runtime_budget>`\n(see the plan-phase fragment), do **not** run it in the foreground — it would\nblock the agent turn for hours. Instead externalize it and record a durable\nhalf-state:\n\n1. **Classify the runtime.** `quick` (<2 min) and `medium` (<~30 min) run\n normally. `unknown` requires a first-health check and a soft-review deadline\n before consuming the child timeout. `long_compute` (>30–60 min) is\n externalized.\n2. **Submit via the scheduler adapter** (default `external_job.backend: slurm`):\n ```bash\n node scripts/slurm-adapter.cjs submit \\\n --plan <plan_id> --phase <phase> -- sbatch --parsable \\\n --output=Artifacts/jobs/%j/out.log ./run.sh\n ```\n The helper writes `.planning/async-jobs/<job>.json` (the versioned stability\n contract — `docs/reference/planning-artifacts.md`) and refuses to create a\n second non-terminal manifest for a `plan_id` that already has one\n (duplicate-execution guard).\n3. **Commit the manifest + a handoff**, then return **`external_job_waiting`**\n and stop. Do **not** write `SUMMARY.md` — SUMMARY is deferred until the job\n reaches a terminal state and its `expected_artifacts` are verified.\n4. **Resume path.** `execute-phase` safe-resume, `resume-project`, and\n `pause-work` reconcile against the manifest and never re-dispatch the plan.\n When the job is `completed-unverified`, run `verification_command` (surface\n it; it is untrusted — confirm before executing), then write `SUMMARY.md` and\n close the plan.\n\nManifest commands cross a trust seam: a Capability (or anything that can write\n`.planning/`) produces them; the core loop consumes them. Never auto-run\n`submit_command` / `verification_command` / `resume_command` — surface the exact\ncommand and require explicit confirmation first.\n"
},
"produces": [
".planning/async-jobs/<job>.json"
],
"consumes": [
"PLAN.md"
],
"when": "external_job.enabled",
"onError": "skip"
},
{
"capId": "mempalace",
"point": "execute:wave:post",
"into": "verifier",
"fragment": {
"path": "fragments/capture-problems.md",
"inline": "<!--\n MemPalace capability — contribution fragment.\n Rendered into the execute:wave:post verifier prompt when `mempalace.capture_artifacts` is true.\n Contributes DATA (capture instructions), not control flow. onError: skip — never fails a wave.\n-->\n### Capture problems → fixes (MemPalace)\n\n**Gate first.** Read `.planning/config.json`. If `mempalace.enabled` is not `true`, or `mempalace.capture_artifacts` is `false`, **skip this entire section** and let the wave complete unchanged. (This contribution is only injected when the capability is enabled; the `capture_artifacts` check lets you turn capture off without disabling the rest of the capability.)\n\nOtherwise — after verifying this wave, persist any *confirmed* problem→fix pairs into the palace so they are recalled in future phases. This is best-effort; if MemPalace is unreachable, skip silently — capture never fails a wave.\n\nFor each confirmed bug/issue resolved in this wave:\n\n1. **Resolve the wing** (`mempalace.wing`, else `project_code`, else project dir) and target `room: problems`.\n2. **Dedupe first.** Call `mempalace_check_duplicate` (interactive) before filing so re-runs don't create duplicate drawers.\n3. **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 --daemon --background` (no `--room` flag; the background queue defers the write when another writer holds the palace lock, #4700 — 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).\n4. **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`.\n5. **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.\n\nCaptures 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.\n"
},
"produces": [],
"consumes": [],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"gates": [
{
"capId": "drift",
"point": "execute:wave:post",
"check": {
"query": "verify.schema-drift"
},
"when": "workflow.schema_drift_gate",
"blocking": true,
"onError": "skip"
},
{
"capId": "drift",
"point": "execute:wave:post",
"check": {
"query": "verify.codebase-drift"
},
"when": "workflow.schema_drift_gate",
"blocking": false,
"onError": "skip"
},
{
"capId": "ui",
"point": "execute:wave:post",
"check": {
"query": "ui.safety-gate"
},
"when": "workflow.ui_safety_gate",
"blocking": true,
"onError": "halt"
}
]
},
"execute:post": {
"steps": [
{
"capId": "code-review",
"point": "execute:post",
"ref": {
"skill": "code-review"
},
"produces": [
"REVIEW.md"
],
"consumes": [
"SUMMARY.md"
],
"supportsReviewerLanes": true,
"when": "workflow.code_review",
"pointFrom": "workflow.code_review_point",
"onError": "skip"
},
{
"capId": "refactor-trigger",
"point": "execute:post",
"ref": {
"command": "refactor evaluate"
},
"produces": [
"REFACTOR.md"
],
"consumes": [],
"when": "refactor.trigger_enabled",
"onError": "skip"
}
],
"contributions": [],
"gates": [
{
"capId": "tdd",
"point": "execute:post",
"check": {
"query": "tdd.review-checkpoint"
},
"when": "workflow.tdd_mode",
"blocking": false,
"onError": "skip"
}
]
},
"verify:pre": {
"steps": [],
"contributions": [],
"gates": [
{
"capId": "ai-integration",
"point": "verify:pre",
"check": {
"query": "api-coverage.verify-pre"
},
"when": "workflow.api_coverage_gate",
"blocking": true,
"onError": "halt"
}
]
},
"verify:post": {
"steps": [
{
"capId": "mempalace",
"point": "verify:post",
"ref": {
"skill": "mempalace-capture"
},
"produces": [],
"consumes": [
"SUMMARY.md"
],
"when": "mempalace.enabled",
"onError": "skip"
},
{
"capId": "nyquist",
"point": "verify:post",
"ref": {
"skill": "validate-phase"
},
"produces": [
"VALIDATION.md"
],
"consumes": [
"SUMMARY.md"
],
"when": "workflow.nyquist_validation",
"onError": "halt"
},
{
"capId": "security",
"point": "verify:post",
"ref": {
"skill": "secure-phase"
},
"produces": [
"SECURITY.md"
],
"consumes": [
"SUMMARY.md"
],
"when": "workflow.security_enforcement",
"onError": "halt"
},
{
"capId": "ui",
"point": "verify:post",
"ref": {
"skill": "ui-review"
},
"produces": [
"UI-REVIEW.md"
],
"consumes": [
"UI-SPEC.md"
],
"when": "workflow.ui_review",
"onError": "skip"
}
],
"contributions": [],
"gates": []
},
"ship:pre": {
"steps": [],
"contributions": [],
"gates": [
{
"capId": "broken-windows",
"point": "ship:pre",
"check": {
"predicate": {
"kind": "artifact-frontmatter-equals",
"artifact": "WINDOWS.md",
"field": "open_count",
"equals": 0
}
},
"when": "workflow.windows_enforce",
"blocking": true,
"onError": "halt"
},
{
"capId": "security",
"point": "ship:pre",
"check": {
"predicate": {
"kind": "artifact-frontmatter-equals",
"artifact": "SECURITY.md",
"field": "threats_open",
"equals": 0
}
},
"when": "workflow.security_enforcement",
"blocking": true,
"onError": "halt"
}
]
},
"ship:post": {
"steps": [
{
"capId": "mempalace",
"point": "ship:post",
"ref": {
"agent": "gsd-mempalace-curator"
},
"produces": [],
"consumes": [
"UAT.md"
],
"when": "mempalace.enabled",
"onError": "skip"
}
],
"contributions": [],
"gates": []
}
};
const configKeys = {
"workflow.ai_integration_phase": "ai-integration",
"workflow.api_coverage_gate": "ai-integration",
"review.models.agy": "antigravity",
"review.max_prompt_tokens_per_reviewer.antigravity": "antigravity",
"review.timeouts.antigravity": "antigravity",
"workflow.assumption_delta": "assumption-delta",
"workflow.windows_enforce": "broken-windows",
"review.models.claude": "claude",
"review.max_prompt_tokens_per_reviewer.claude": "claude",
"review.timeouts.claude": "claude",
"review.effort.claude": "claude",
"claude_orchestration.enabled": "claude-orchestration",
"claude_orchestration.execution_backend": "claude-orchestration",
"claude_orchestration.min_agent_sdk_version": "claude-orchestration",
"workflow.code_review": "code-review",
"workflow.code_review_depth": "code-review",
"workflow.code_review_point": "code-review",
"review.max_prompt_tokens_per_reviewer.coderabbit": "coderabbit",
"review.models.codex": "codex",
"review.max_prompt_tokens_per_reviewer.codex": "codex",
"review.timeouts.codex": "codex",
"review.effort.codex": "codex",
"review.models.cursor": "cursor",
"review.max_prompt_tokens_per_reviewer.cursor": "cursor",
"workflow.drift_threshold": "drift",
"workflow.drift_action": "drift",
"workflow.schema_drift_gate": "drift",
"workflow.plan_drift_precheck": "drift",
"workflow.context_drift_precheck": "drift",
"workflow.context_drift_action": "drift",
"external_job.enabled": "external-job",
"external_job.backend": "external-job",
"external_job.artifact_dir": "external-job",
"external_job.submit_timeout_ms": "external-job",
"external_job.poll_timeout_ms": "external-job",
"workflow.post_planning_gaps": "gap-analysis",
"graphify.enabled": "graphify",
"intel.enabled": "intel",
"review.models.kimi-code": "kimi-code",
"review.max_prompt_tokens_per_reviewer.kimi-code": "kimi-code",
"review.timeouts.kimi-code": "kimi-code",
"workflow.live_dom_uat": "live-dom-uat",
"review.models.llama_cpp": "llama-cpp",
"review.llama_cpp_host": "llama-cpp",
"review.max_prompt_tokens_per_reviewer.llama_cpp": "llama-cpp",
"review.timeouts.llama_cpp": "llama-cpp",
"review.models.lm_studio": "lm-studio",
"review.lm_studio_host": "lm-studio",
"review.max_prompt_tokens_per_reviewer.lm_studio": "lm-studio",
"review.timeouts.lm_studio": "lm-studio",
"mempalace.enabled": "mempalace",
"mempalace.memory_mode": "mempalace",
"mempalace.wing": "mempalace",
"mempalace.recall_on_discuss": "mempalace",
"mempalace.recall_on_plan": "mempalace",
"mempalace.capture_artifacts": "mempalace",
"mempalace.mirror_kg": "mempalace",
"mempalace.cross_project_tunnels": "mempalace",
"mempalace.diary_journal": "mempalace",
"mempalace.auto_capture_hooks": "mempalace",
"workflow.nyquist_validation": "nyquist",
"review.models.ollama": "ollama",
"review.ollama_host": "ollama",
"review.max_prompt_tokens_per_reviewer.ollama": "ollama",
"review.timeouts.ollama": "ollama",
"review.models.opencode": "opencode",
"review.max_prompt_tokens_per_reviewer.opencode": "opencode",
"review.timeouts.opencode": "opencode",
"review.effort.opencode": "opencode",
"workflow.pattern_mapper": "pattern-mapper",
"profile-pipeline.enabled": "profile-pipeline",
"review.max_prompt_tokens_per_reviewer.qwen": "qwen",
"refactor.trigger_enabled": "refactor-trigger",
"refactor.complexity_threshold": "refactor-trigger",
"refactor.complexity_jump_delta": "refactor-trigger",
"refactor.trigger_strict": "refactor-trigger",
"workflow.research": "research",
"workflow.schema_push_detection": "schema-gate",
"workflow.security_enforcement": "security",
"workflow.security_asvs_level": "security",
"workflow.security_block_on": "security",
"workflow.tdd_mode": "tdd",
"workflow.ui_phase": "ui",
"workflow.ui_review": "ui",
"workflow.ui_safety_gate": "ui",
"workflow.ui_interaction_capture": "ui"
};
const configSchema = {
"workflow.ai_integration_phase": {
"owner": "ai-integration",
"type": "boolean",
"default": true,
"description": "Prompt for an AI-SPEC design contract before planning phases that involve AI systems."
},
"workflow.api_coverage_gate": {
"owner": "ai-integration",
"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)."
},
"review.models.agy": {
"owner": "antigravity",
"type": "string",
"default": "",
"description": "Model passed to the Antigravity reviewer lane. The key suffix is the lane binary/flag alias `agy`, not the slug `antigravity` — preserved verbatim so existing .planning/config.json files keep working."
},
"review.max_prompt_tokens_per_reviewer.antigravity": {
"owner": "antigravity",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\". Keyed on the reviewer slug `antigravity`, not the `agy` binary alias used by review.models.agy."
},
"review.timeouts.antigravity": {
"owner": "antigravity",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"workflow.assumption_delta": {
"owner": "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)."
},
"workflow.windows_enforce": {
"owner": "broken-windows",
"type": "boolean",
"default": false,
"description": "Enable the blocking ship:pre gate for the broken-windows ledger. When true (opt-in), /gsd-ship blocks while .planning/WINDOWS.md has any open entry. When false (default), windows are still tracked (the executor and verifier still populate WINDOWS.md via gsd-tools windows append) but ship does not block — teams can adopt tracking before enforcement. Issue #1950."
},
"review.models.claude": {
"owner": "claude",
"type": "string",
"default": "",
"description": "Model passed to the Claude reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.claude": {
"owner": "claude",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Claude reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.claude": {
"owner": "claude",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Claude reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.claude": {
"owner": "claude",
"type": "string",
"default": "",
"description": "Reasoning effort for the Claude reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
},
"claude_orchestration.enabled": {
"owner": "claude-orchestration",
"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": {
"owner": "claude-orchestration",
"type": "enum",
"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.",
"values": [
"auto",
"workflow",
"inline"
]
},
"claude_orchestration.min_agent_sdk_version": {
"owner": "claude-orchestration",
"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."
},
"workflow.code_review": {
"owner": "code-review",
"type": "boolean",
"default": true,
"description": "Enable code-review participation in post-execution review flows."
},
"workflow.code_review_depth": {
"owner": "code-review",
"type": "enum",
"default": "standard",
"description": "Default depth for code review when no --depth override is supplied.",
"values": [
"quick",
"standard",
"deep"
]
},
"workflow.code_review_point": {
"owner": "code-review",
"type": "enum",
"default": "execute:post",
"description": "Loop point at which the code-review step registers — execute:post reviews once per phase (default); execute:wave:post reviews once per completed wave, scoped to that wave's diff.",
"values": [
"execute:post",
"execute:wave:post"
]
},
"review.max_prompt_tokens_per_reviewer.coderabbit": {
"owner": "coderabbit",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the CodeRabbit reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.models.codex": {
"owner": "codex",
"type": "string",
"default": "",
"description": "Model passed to the Codex reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.codex": {
"owner": "codex",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Codex reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.codex": {
"owner": "codex",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Codex reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.codex": {
"owner": "codex",
"type": "string",
"default": "",
"description": "Reasoning effort for the Codex reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
},
"review.models.cursor": {
"owner": "cursor",
"type": "string",
"default": "",
"description": "Model passed to the Cursor reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.cursor": {
"owner": "cursor",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Cursor reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"workflow.drift_threshold": {
"owner": "drift",
"type": "number",
"default": 3,
"description": "Minimum number of new structural elements (directories, barrel exports, migrations, routes) before the codebase drift gate triggers a warn or auto-remap action."
},
"workflow.drift_action": {
"owner": "drift",
"type": "enum",
"default": "warn",
"description": "Action taken by the codebase drift gate when the threshold is exceeded: warn (advisory message) or auto-remap (spawn gsd-codebase-mapper agent to refresh STRUCTURE.md).",
"values": [
"warn",
"auto-remap"
]
},
"workflow.schema_drift_gate": {
"owner": "drift",
"type": "boolean",
"default": true,
"description": "Enable the drift gates at execute:wave:post. When enabled, the schema drift gate blocks verification if schema-relevant files changed during execution but no database push command was executed; the codebase drift gate (non-blocking) warns when structural additions exceed the drift_threshold."
},
"workflow.plan_drift_precheck": {
"owner": "drift",
"type": "boolean",
"default": true,
"description": "Enable the non-blocking codebase drift pre-check at plan:pre, before /gsd:plan-phase spawns the planner. When enabled, a stale STRUCTURE.md (structural additions exceeding drift_threshold) is surfaced up front as a warn-only advisory pointing to /gsd:map-codebase; it never blocks planning and never spawns the mapper agent. Separate from schema_drift_gate so autonomous/CI runs can silence the plan-time advisory while keeping the execute:wave:post gates enabled."
},
"workflow.context_drift_precheck": {
"owner": "drift",
"type": "boolean",
"default": true,
"description": "Enable the non-blocking context-drift pre-check at plan:pre, before /gsd:plan-phase reuses an existing RESEARCH.md/PATTERNS.md/VALIDATION.md/SPEC.md. Compares each artifact's effective last-changed time (git commit time, falling back to mtime for uncommitted edits) against CONTEXT.md's own — an artifact that predates CONTEXT.md's newest decision was derived from a premise that has since changed. Warn-only by default (see workflow.context_drift_action); never blocks planning on its own."
},
"workflow.context_drift_action": {
"owner": "drift",
"type": "enum",
"default": "warn",
"description": "Action taken by the context-drift gate when a stale upstream artifact is found: warn (advisory message naming the stale artifacts and how to regenerate them) or block (halt plan-phase until the artifacts are regenerated or the check is disabled).",
"values": [
"warn",
"block"
]
},
"external_job.enabled": {
"owner": "external-job",
"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": {
"owner": "external-job",
"type": "enum",
"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.",
"values": [
"slurm"
]
},
"external_job.artifact_dir": {
"owner": "external-job",
"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": {
"owner": "external-job",
"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": {
"owner": "external-job",
"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)."
},
"workflow.post_planning_gaps": {
"owner": "gap-analysis",
"type": "boolean",
"default": true,
"description": "Run the post-planning gap analysis report after plans are generated."
},
"graphify.enabled": {
"owner": "graphify",
"type": "boolean",
"default": false,
"description": "Enable the graphify knowledge-graph command + skill."
},
"intel.enabled": {
"owner": "intel",
"type": "boolean",
"default": false,
"description": "Enable the intel code-intelligence command."
},
"review.models.kimi-code": {
"owner": "kimi-code",
"type": "string",
"default": "",
"description": "Model passed to the Kimi Code reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.kimi-code": {
"owner": "kimi-code",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.kimi-code": {
"owner": "kimi-code",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"workflow.live_dom_uat": {
"owner": "live-dom-uat",
"type": "boolean",
"default": false,
"description": "Enable live-DOM verification. Default-off: browser MCP reach is opt-in per project. When on, the orchestrator's automated UI verification may additionally use mcp__chrome-devtools__* / mcp__claude-in-chrome__* when present, and a gsd-dom-verifier step runs after each execution wave. When off, neither surface reaches a browser and the pre-existing mcp__playwright__* path is unchanged."
},
"review.models.llama_cpp": {
"owner": "llama-cpp",
"type": "string",
"default": "",
"description": "Model requested from the llama.cpp reviewer lane; empty discovers the first model from /v1/models."
},
"review.llama_cpp_host": {
"owner": "llama-cpp",
"type": "string",
"default": "",
"description": "Base URL of the llama.cpp OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.llama_cpp": {
"owner": "llama-cpp",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the llama.cpp reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.llama_cpp": {
"owner": "llama-cpp",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the llama.cpp reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.models.lm_studio": {
"owner": "lm-studio",
"type": "string",
"default": "",
"description": "Model requested from the LM Studio reviewer lane; empty discovers the first model from /v1/models."
},
"review.lm_studio_host": {
"owner": "lm-studio",
"type": "string",
"default": "",
"description": "Base URL of the LM Studio OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.lm_studio": {
"owner": "lm-studio",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the LM Studio reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.lm_studio": {
"owner": "lm-studio",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the LM Studio reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"mempalace.enabled": {
"owner": "mempalace",
"type": "boolean",
"default": false,
"description": "Master toggle for the MemPalace memory capability."
},
"mempalace.memory_mode": {
"owner": "mempalace",
"type": "enum",
"default": "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.",
"values": [
"augment",
"kg_backend",
"replace"
]
},
"mempalace.wing": {
"owner": "mempalace",
"type": "string",
"default": "",
"description": "Palace wing name; empty derives from project_code / project dir."
},
"mempalace.recall_on_discuss": {
"owner": "mempalace",
"type": "boolean",
"default": true,
"description": "Inject wake-up + search recall at discuss:pre."
},
"mempalace.recall_on_plan": {
"owner": "mempalace",
"type": "boolean",
"default": true,
"description": "Produce MEMORY-RECALL.md at plan:pre."
},
"mempalace.capture_artifacts": {
"owner": "mempalace",
"type": "boolean",
"default": true,
"description": "File CONTEXT/PLAN/SUMMARY and learnings into the palace at phase boundaries."
},
"mempalace.mirror_kg": {
"owner": "mempalace",
"type": "boolean",
"default": true,
"description": "Mirror decisions/learnings into MemPalace's temporal knowledge graph."
},
"mempalace.cross_project_tunnels": {
"owner": "mempalace",
"type": "boolean",
"default": false,
"description": "Propose/create cross-wing tunnels at ship:post."
},
"mempalace.diary_journal": {
"owner": "mempalace",
"type": "boolean",
"default": true,
"description": "Write a per-agent diary entry at ship:post."
},
"mempalace.auto_capture_hooks": {
"owner": "mempalace",
"type": "boolean",
"default": false,
"description": "Reserved / not yet implemented: will install MemPalace's native stop/precompact Claude Code hooks for passive mid-session capture (the capability's hooks array is currently empty)."
},
"workflow.nyquist_validation": {
"owner": "nyquist",
"type": "boolean",
"default": true,
"description": "Enable Nyquist validation coverage auditing."
},
"review.models.ollama": {
"owner": "ollama",
"type": "string",
"default": "",
"description": "Model requested from the Ollama reviewer lane; empty discovers the first model from /v1/models."
},
"review.ollama_host": {
"owner": "ollama",
"type": "string",
"default": "",
"description": "Base URL of the Ollama OpenAI-compatible server."
},
"review.max_prompt_tokens_per_reviewer.ollama": {
"owner": "ollama",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Ollama reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.ollama": {
"owner": "ollama",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Ollama reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.models.opencode": {
"owner": "opencode",
"type": "string",
"default": "",
"description": "Model passed to the OpenCode reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.opencode": {
"owner": "opencode",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.opencode": {
"owner": "opencode",
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.opencode": {
"owner": "opencode",
"type": "string",
"default": "",
"description": "Reasoning effort for the OpenCode reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
},
"workflow.pattern_mapper": {
"owner": "pattern-mapper",
"type": "boolean",
"default": true,
"description": "Run the pattern mapper before planning when context or research is available."
},
"profile-pipeline.enabled": {
"owner": "profile-pipeline",
"type": "boolean",
"default": false,
"description": "Enable the developer profiling pipeline commands (scan-sessions, extract-messages, profile-sample, write-profile, etc.)."
},
"review.max_prompt_tokens_per_reviewer.qwen": {
"owner": "qwen",
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Qwen Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"refactor.trigger_enabled": {
"owner": "refactor-trigger",
"type": "boolean",
"default": false,
"description": "Enable the complexity-triggered refactor hook. When true, an execute:post step evaluates the complexity of the files the phase touched and writes a scoped refactor proposal if a function crosses refactor.complexity_threshold or jumps past refactor.complexity_jump_delta. Opt-in; when false the hook never runs. Issue #1953."
},
"refactor.complexity_threshold": {
"owner": "refactor-trigger",
"type": "number",
"default": 15,
"description": "Absolute per-function complexity above which a refactor proposal is surfaced. Semantics match ESLint's `complexity: {max: N}` — the trigger is STRICTLY GREATER, so a score of exactly N does not trigger. Default 15 follows SonarSource's default; ESLint's own default is 20 and radon's rank C begins at 11. Raise it if proposals feel like noise."
},
"refactor.complexity_jump_delta": {
"owner": "refactor-trigger",
"type": "number",
"default": 5,
"description": "Complexity growth above which a refactor proposal is surfaced even when the absolute threshold is not reached. Measured against the function's anchor — the score recorded the last time the function was consciously dispositioned — so it accumulates across phases and catches slow creep the absolute threshold would miss. Strictly greater, as with the threshold."
},
"refactor.trigger_strict": {
"owner": "refactor-trigger",
"type": "boolean",
"default": false,
"description": "Record an untriaged refactor proposal as an open `deviation` entry in the broken-windows ledger, so it becomes a tracked task that must be resolved before ship. Off by default and deliberately so: a blocking complexity number is a metric an executor can satisfy by splitting one coherent function into two incoherent ones, so the entry clears on the proposal being DISPOSITIONED (gsd-tools refactor accept|decline), never on the score improving. Ship blocking is the broken-windows capability's existing ship:pre gate — enable it with workflow.windows_enforce. With broken-windows absent, strict mode still records the proposal locally and says so; it cannot block on its own. Advisory mode (the default) surfaces the same proposal and tracks nothing."
},
"workflow.research": {
"owner": "research",
"type": "boolean",
"default": true,
"description": "Run phase research before planning when research artifacts are missing or explicitly refreshed."
},
"workflow.schema_push_detection": {
"owner": "schema-gate",
"type": "boolean",
"default": true,
"description": "Enable ORM schema push detection during planning. When schema-relevant files are detected in the phase scope, a [BLOCKING] push task is injected into the plan."
},
"workflow.security_enforcement": {
"owner": "security",
"type": "boolean",
"default": true,
"description": "Enable security threat-mitigation verification before phase advancement."
},
"workflow.security_asvs_level": {
"owner": "security",
"type": "number",
"default": 1,
"description": "OWASP ASVS level used by security review guidance."
},
"workflow.security_block_on": {
"owner": "security",
"type": "enum",
"default": "high",
"description": "Minimum open threat severity that blocks advancement.",
"values": [
"critical",
"high",
"medium",
"low",
"none"
]
},
"workflow.tdd_mode": {
"owner": "tdd",
"type": "boolean",
"default": false,
"description": "Enable TDD mode: planner annotates eligible tasks type:tdd and executor enforces RED/GREEN/REFACTOR gate sequence."
},
"workflow.ui_phase": {
"owner": "ui",
"type": "boolean",
"default": true,
"description": "Enable the UI design-contract gate during planning."
},
"workflow.ui_review": {
"owner": "ui",
"type": "boolean",
"default": true,
"description": "Enable the retrospective UI audit."
},
"workflow.ui_safety_gate": {
"owner": "ui",
"type": "boolean",
"default": true,
"description": "Block execution on unmet UI-SPEC contracts."
},
"workflow.ui_interaction_capture": {
"owner": "ui",
"type": "boolean",
"default": false,
"description": "Default-off. Let gsd-ui-auditor add post-interaction captures (hover, focus, open menus, filled forms) through the chrome-devtools CLI, driven from Bash — no MCP server and no tools: change (#4223). Requires an installed Chrome; when off, or when none resolves, the auditor's Playwright-only static capture is unchanged."
}
};
const runtimes = {
"antigravity": {
"id": "antigravity",
"role": "runtime",
"version": "1.14.0",
"title": "Antigravity",
"description": "Google Antigravity IDE — config/settings home nested under ~/.gemini/antigravity (probed across 1.x and 2.x layouts); global skills/agents install under ~/.gemini/config, the dir AGY scans for global discovery (#3738); Gemini hook event dialect; flat skill layout; tier-1 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home-nested",
"name": "antigravity",
"parent": ".gemini",
"env": [
"ANTIGRAVITY_CONFIG_DIR"
],
"probe": [
"antigravity",
"antigravity-ide",
"antigravity-cli"
],
"probeExists": "gsd-core/VERSION"
},
"localConfigDir": ".agents",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill",
"home": ".gemini/config"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent",
"home": ".gemini/config"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToAntigravitySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAntigravityAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "gemini",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": "antigravity",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": "undocumented",
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "go",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"projectInstructionFile": "GEMINI.md",
"noPathRewrite": true,
"hookPathStyle": "raw",
"globalDirResolver": "antigravity"
}
},
"reviewer": {
"slug": "antigravity",
"flags": [
"--antigravity",
"--agy"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "agy"
},
"invoke": {
"binary": "agy",
"args": [
"--print-timeout",
"{{nativeTimeout}}",
"{{model}}",
"-p",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "none"
},
"timeoutFloorMs": 600000,
"timeoutConfigKey": "review.timeouts.antigravity",
"emptyOutput": "handler-owned",
"reviewsSection": "Antigravity",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.antigravity",
"modelConfigKey": "review.models.agy",
"effortConfigKey": null,
"defaultEffort": null,
"handler": "antigravity"
},
"config": {
"review.models.agy": {
"type": "string",
"default": "",
"description": "Model passed to the Antigravity reviewer lane. The key suffix is the lane binary/flag alias `agy`, not the slug `antigravity` — preserved verbatim so existing .planning/config.json files keep working."
},
"review.max_prompt_tokens_per_reviewer.antigravity": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\". Keyed on the reviewer slug `antigravity`, not the `agy` binary alias used by review.models.agy."
},
"review.timeouts.antigravity": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Antigravity reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"augment": {
"id": "augment",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".augment",
"env": [
"AUGMENT_CONFIG_DIR"
]
},
"localConfigDir": ".augment",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToAugmentSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToAugmentAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"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",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
}
}
},
"claude": {
"id": "claude",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".claude",
"env": [
"CLAUDE_CONFIG_DIR"
]
},
"localConfigDir": ".claude",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"FileChanged"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "harness-worktree",
"maxConcurrency": 20
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "argv"
},
"harnessIsolationFlag": "isolation=\"worktree\"",
"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
}
},
"reviewer": {
"slug": "claude",
"flags": [
"--claude"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "claude"
},
"invoke": {
"binary": "claude",
"args": [
"{{model}}",
"{{effort}}",
"-p",
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "argv",
"env": {
"CLAUDE_CODE_DISABLE_CLAUDE_MDS": "1",
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1"
}
},
"timeoutFloorMs": 1200000,
"timeoutConfigKey": "review.timeouts.claude",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Claude",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.claude",
"modelConfigKey": "review.models.claude",
"effortConfigKey": "review.effort.claude",
"defaultEffort": "high",
"handler": null
},
"config": {
"review.models.claude": {
"type": "string",
"default": "",
"description": "Model passed to the Claude reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.claude": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Claude reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.claude": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Claude reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.claude": {
"type": "string",
"default": "",
"description": "Reasoning effort for the Claude reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"cline": {
"id": "cline",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".cline",
"env": [
"CLINE_CONFIG_DIR"
]
},
"localConfigDir": ".cline",
"configFormat": "markdown-dir",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClineSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
],
"local": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToClineAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "cline-rules",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "cline-rules",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "read-only",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"frontmatterDialect": "cline",
"skipSharedHooksInstall": true,
"localTargetIsProjectRoot": true,
"clineRulesSurface": true,
"localCommandsViaRules": true
}
}
},
"codebuddy": {
"id": "codebuddy",
"role": "runtime",
"version": "1.14.0",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".codebuddy",
"env": [
"CODEBUDDY_CONFIG_DIR"
]
},
"localConfigDir": ".codebuddy",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddyCommand"
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddyCommand"
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodebuddySkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodebuddyAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reportCommandsDir": true
}
}
},
"codex": {
"id": "codex",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".codex",
"env": [
"CODEX_HOME"
]
},
"localConfigDir": ".codex",
"configFormat": "toml",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill",
"home": ".agents"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodexAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCodexSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCodexAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "shell-var",
"hooksSurface": "codex-hooks-json",
"hookEvents": "claude",
"sandboxTier": "codex-agent-sandbox",
"supportTier": 1,
"installSurface": "codex-toml",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "argv"
},
"orchestratorExec": {
"command": "codex",
"args": [
"exec"
],
"cwdFlag": "--cd",
"promptFlag": null,
"modelFlag": "--model"
},
"hostBehaviors": {
"reapplyCommand": "$gsd-update --reapply",
"tomlConfigInstall": true,
"cleanupSkillSidecars": true,
"agentTomlFiles": true,
"frontmatterDialect": "codex",
"unsupportedFeatures": [
"context-warnings",
"phase-lifecycle-display"
]
}
},
"reviewer": {
"slug": "codex",
"flags": [
"--codex"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "codex"
},
"invoke": {
"binary": "codex",
"args": [
"exec",
"--ephemeral",
"{{model}}",
"{{effort}}",
"--skip-git-repo-check",
"{{output}}",
"-"
],
"promptChannel": "stdin",
"outputChannel": "file-arg",
"outputArg": "-o",
"modelArg": "--model",
"effortChannel": "argv"
},
"timeoutFloorMs": 1200000,
"timeoutConfigKey": "review.timeouts.codex",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Codex",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.codex",
"modelConfigKey": "review.models.codex",
"effortConfigKey": "review.effort.codex",
"defaultEffort": "high",
"handler": null
},
"config": {
"review.models.codex": {
"type": "string",
"default": "",
"description": "Model passed to the Codex reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.codex": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Codex reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.codex": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Codex reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.codex": {
"type": "string",
"default": "",
"description": "Reasoning effort for the Codex reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"copilot": {
"id": "copilot",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".copilot",
"env": [
"COPILOT_CONFIG_DIR",
"COPILOT_HOME"
]
},
"localConfigDir": ".github",
"configFormat": "markdown",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToCopilotSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCopilotAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "copilot-inline",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "copilot-instructions",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "undocumented",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"agentFileExtension": ".agent.md",
"skipSharedHooksInstall": true,
"noPathRewrite": true
}
}
},
"cursor": {
"id": "cursor",
"role": "runtime",
"version": "1.14.0",
"title": "Cursor",
"description": "Cursor IDE — skills-only workflow surface; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".cursor",
"env": [
"CURSOR_CONFIG_DIR"
]
},
"localConfigDir": ".cursor",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToCursorSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToCursorSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToCursorAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "cursor-hooks-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "cursor-hooks-json",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 2,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": true,
"isolation": "harness-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"harnessIsolationFlag": "--worktree",
"hostBehaviors": {
"reapplyCommand": "gsd-update --reapply (mention the skill name)",
"frontmatterDialect": "cursor",
"hooksJsonSurface": true,
"skipSharedHooksInstall": true,
"retiredArtifacts": [
{
"destSubpath": "commands",
"prefix": "gsd-",
"suffix": ".md"
}
],
"skipUpdateBannerCommand": true,
"skipSettingsUi": true,
"managedHookEvents": [
"sessionStart",
"postToolUse",
"preToolUse",
"stop",
"subagentStart",
"subagentStop"
]
}
},
"reviewer": {
"slug": "cursor",
"flags": [
"--cursor"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "cursor-agent"
},
"invoke": {
"binary": "cursor-agent",
"args": [
"-p",
"{{model}}",
"--mode",
"ask",
"--trust",
"--output-format",
"text",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": null,
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Cursor",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.cursor",
"modelConfigKey": "review.models.cursor",
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.models.cursor": {
"type": "string",
"default": "",
"description": "Model passed to the Cursor reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.cursor": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Cursor reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
}
}
},
"hermes": {
"id": "hermes",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".hermes",
"env": [
"HERMES_HOME"
]
},
"localConfigDir": ".hermes",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills/gsd",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToHermesAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills/gsd",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToHermesAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"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,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python",
"effortSurface": "undocumented"
}
}
},
"kilo": {
"id": "kilo",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "xdg",
"name": "kilo",
"env": [
"KILO_CONFIG_DIR",
"KILO_CONFIG",
"XDG_CONFIG_HOME"
],
"skillsHome": {
"kind": "dot-home",
"name": ".kilo",
"env": []
}
},
"localConfigDir": ".kilo",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "command",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToKiloSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToKiloFrontmatter"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "command",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToKiloSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToKiloFrontmatter"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "kilo",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": false,
"permissionWriter": "kilo",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": -1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun",
"effortSurface": "undocumented"
},
"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
}
}
},
"kimi": {
"id": "kimi",
"role": "runtime",
"version": "1.14.0",
"title": "Kimi CLI",
"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": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "generic-agents-root",
"name": "agents",
"env": [
"KIMI_CONFIG_DIR"
],
"probe": [
"~/.config/agents",
"~/.agents"
],
"probeExists": "skills"
},
"localConfigDir": ".kimi-code",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToKimiSkill"
},
{
"kind": "kimi-agents",
"destSubpath": "agents",
"prefix": "gsd",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "kimi-hooks-toml",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": true,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "python",
"effortSurface": "undocumented"
},
"orchestratorExec": {
"command": "kimi",
"args": [
"--print"
],
"cwdFlag": "--work-dir",
"promptFlag": "--prompt"
},
"hostBehaviors": {
"reapplyCommand": "/skill:gsd-update --reapply",
"localInstallDeferred": true,
"verificationStyle": "kimi",
"agentManifestStyle": "kimi-nested",
"doneBannerStyle": "kimi-agent-file",
"skipSharedHooksInstall": true,
"noPathRewrite": true
}
}
},
"kimi-code": {
"id": "kimi-code",
"role": "runtime",
"version": "1.14.0",
"title": "Kimi Code CLI",
"description": "Kimi Code CLI (Moonshot AI, Node) — Agent Skills auto-discovered at ~/.kimi-code/skills; global AGENTS.md at ~/.kimi-code/AGENTS.md; native config.toml + [[hooks]] bus; three built-in subagents (coder/explore/plan), NO custom named subagents; background dispatch; tier-2 support. Distinct from Python kimi-cli (the 'kimi' capability) per ADR-1239 EoS — Kimi Code cannot dispatch named subagents so the kimi-agents YAML layout does NOT apply; persona injection rides the existing ${AGENT_SKILLS_*} workflow fallback. Install-layout, agent-install-check, and install-time decision (kimi vs kimi-code) land in follow-up PRs; this descriptor is the EoS foundation.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".kimi-code",
"env": [
"KIMI_CODE_HOME"
],
"probe": [
"~/.kimi-code"
],
"probeExists": "config.toml"
},
"localConfigDir": ".kimi-code",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToKimiCodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
],
"local": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "kimi-hooks-toml",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": false,
"nested": true,
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "built-in-only",
"backgroundDispatch": true,
"builtInSubagents": [
"coder",
"explore",
"plan"
],
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node"
},
"orchestratorExec": {
"command": "kimi",
"args": [],
"cwdFlag": null,
"promptFlag": "--prompt"
},
"hostBehaviors": {
"reapplyCommand": "/skill:gsd-update --reapply",
"localInstallDeferred": true,
"verificationStyle": "kimi-code",
"agentManifestStyle": "none",
"doneBannerStyle": "kimi-code",
"skipSharedHooksInstall": true,
"namedSubagentsSupported": false
}
},
"reviewer": {
"slug": "kimi-code",
"flags": [
"--kimi-code"
],
"transport": "spawn",
"probe": {
"kind": "command-capability",
"binary": "kimi",
"needle": "--output-format",
"timeoutMs": 5000
},
"invoke": {
"binary": "kimi",
"args": [
"{{model}}",
"-p",
"{{prompt}}"
],
"promptChannel": "argv-file-ref",
"outputChannel": "stdout",
"modelArg": "-m",
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": "review.timeouts.kimi-code",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Kimi Code",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.kimi-code",
"modelConfigKey": "review.models.kimi-code",
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.models.kimi-code": {
"type": "string",
"default": "",
"description": "Model passed to the Kimi Code reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.kimi-code": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.kimi-code": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the Kimi Code reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
}
}
},
"opencode": {
"id": "opencode",
"role": "runtime",
"version": "1.14.0",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat commands/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "xdg",
"name": "opencode",
"env": [
"OPENCODE_CONFIG_DIR",
"OPENCODE_CONFIG",
"XDG_CONFIG_HOME"
]
},
"localConfigDir": ".opencode",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToOpencodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToOpencodeFrontmatter"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "flat",
"recursive": true,
"converter": "convertClaudeCommandToOpencodeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeToOpencodeFrontmatter"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "opencode",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": "opencode",
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "orchestrator-worktree",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "bun",
"effortSurface": "argv"
},
"orchestratorExec": {
"command": "opencode",
"args": [
"run"
],
"cwdFlag": "--dir",
"promptFlag": null
},
"hostBehaviors": {
"reapplyCommand": "/gsd-update --reapply",
"attributionConfigResolver": "opencode",
"flatCommandDir": "commands",
"combinedFamilyInstall": true,
"frontmatterDialect": "opencode",
"nativePlugin": {
"dir": "plugins",
"file": "gsd-core.js",
"source": ".opencode/plugins/gsd-core.js"
},
"skipHomePrefixSubstitution": true,
"skipSettingsUi": true,
"skipUpdateBannerCommand": true
}
},
"reviewer": {
"slug": "opencode",
"flags": [
"--opencode"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "opencode"
},
"invoke": {
"binary": "opencode",
"args": [
"run",
"{{model}}",
"{{effort}}",
"--format",
"json",
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": "--model",
"effortChannel": "argv"
},
"timeoutFloorMs": 660000,
"timeoutConfigKey": "review.timeouts.opencode",
"emptyOutput": "stub-with-stderr",
"reviewsSection": "OpenCode",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.opencode",
"modelConfigKey": "review.models.opencode",
"effortConfigKey": "review.effort.opencode",
"defaultEffort": "high",
"handler": "opencode"
},
"config": {
"review.models.opencode": {
"type": "string",
"default": "",
"description": "Model passed to the OpenCode reviewer lane."
},
"review.max_prompt_tokens_per_reviewer.opencode": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
},
"review.timeouts.opencode": {
"type": "number",
"default": -1,
"description": "Outer wall-clock timeout override (seconds) for the OpenCode reviewer lane. Unset is -1, a sentinel: 0 or a negative number is also treated as unset (a timeout has no legitimate zero/negative value), so no second sentinel is needed. Falls back to the lane's built-in timeoutFloorMs when unset."
},
"review.effort.opencode": {
"type": "string",
"default": "",
"description": "Reasoning effort for the OpenCode reviewer lane: minimal, low, medium, high, xhigh, max, or inherit. Unset falls back to the lane's declared review default (high); inherit emits no effort argument so the CLI's own configuration decides. An unrecognized value falls back to the lane default rather than being forwarded."
}
}
},
"pi": {
"id": "pi",
"role": "runtime",
"version": "1.14.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.js (.js, not .cjs — pi's extension auto-discovery accepts only .ts/.js, #2470); 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": [
"PI_CODING_AGENT_DIR"
]
},
"localConfigDir": ".pi",
"configFormat": "none",
"artifactLayout": {
"global": [],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"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",
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "host",
"stateIO": "session-log-append",
"transport": "native-extension",
"runtime": "bun",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"nativePlugin": {
"dir": "extensions",
"file": "gsd.js",
"source": "pi/gsd.cjs"
},
"pluginOnlyInstall": true,
"sharedHooksDirName": "gsd-hooks"
}
}
},
"qwen": {
"id": "qwen",
"role": "runtime",
"version": "1.14.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",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".qwen",
"env": [
"QWEN_CONFIG_DIR"
]
},
"localConfigDir": ".qwen",
"configFormat": "settings-json",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToQwenAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "settings-json",
"hookEvents": "claude",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "settings-json",
"writesSharedSettings": true,
"permissionWriter": null,
"extendedHookEvents": [
"SubagentStop",
"Stop",
"PreCompact",
"SubagentStart"
],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": false,
"maxDepth": 1,
"background": true,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skillPriorityFrontmatter": true,
"brandingRewrites": {
"CLAUDE.md": "QWEN.md",
"Claude Code": "Qwen Code",
".claude/": ".qwen/"
},
"legacyCommandsGsdCleanup": true,
"legacyCommandsGsdInstallMigration": true,
"legacyCommandsGsdUninstall": true,
"hyphenNameAgentBody": true
}
},
"reviewer": {
"slug": "qwen",
"flags": [
"--qwen"
],
"transport": "spawn",
"probe": {
"kind": "command-exists",
"binary": "qwen"
},
"invoke": {
"binary": "qwen",
"args": [
"-"
],
"promptChannel": "stdin",
"outputChannel": "stdout",
"modelArg": null,
"effortChannel": "none"
},
"timeoutFloorMs": 900000,
"timeoutConfigKey": null,
"emptyOutput": "stub-with-stderr",
"reviewsSection": "Qwen",
"evidenceClass": "source-grounded",
"requiresBinaries": [],
"promptBudgetKey": "review.max_prompt_tokens_per_reviewer.qwen",
"modelConfigKey": null,
"effortConfigKey": null,
"defaultEffort": null,
"handler": null
},
"config": {
"review.max_prompt_tokens_per_reviewer.qwen": {
"type": "number",
"default": -1,
"description": "Prompt-token budget for the Qwen Code reviewer lane. Unset is -1, a sentinel: 0 is a legitimate value meaning \"do not trim this lane\", so it cannot double as \"not configured\"."
}
}
},
"trae": {
"id": "trae",
"role": "runtime",
"version": "1.14.0",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".trae",
"env": [
"TRAE_CONFIG_DIR"
]
},
"localConfigDir": ".trae",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToTraeSkill"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToTraeAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "engine",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "node",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true,
"soloStageMetadata": "workflow",
"projectInstructionFile": ".trae/rules/rules.md"
}
}
},
"vscode": {
"id": "vscode",
"role": "runtime",
"version": "1.14.0",
"title": "VS Code",
"description": "VS Code — Marketplace/VSIX extension; no file-projected config directory; IDE-profile reference host (active vscode.lm model, engine-owned hook bus, sandboxed globalState/workspaceState stateIO).",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.7.0"
},
"runtime": {
"configHome": {
"kind": "none",
"name": "vscode",
"env": []
},
"localConfigDir": null,
"configFormat": "none",
"artifactLayout": {
"global": [],
"local": []
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"extensionEvents": "none",
"sandboxTier": "none",
"supportTier": 1,
"installSurface": "none",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "imperative",
"commandSurface": "palette",
"dispatch": {
"namedDispatch": true,
"nested": true,
"maxDepth": 5,
"background": true,
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "undocumented",
"maxConcurrency": "undocumented"
},
"modelMode": "active",
"hookBus": "engine",
"stateIO": "sandboxed-storage",
"transport": "mcp",
"runtime": "sandboxed-web",
"effortSurface": "undocumented"
}
}
},
"windsurf": {
"id": "windsurf",
"role": "runtime",
"version": "1.14.0",
"title": "Windsurf",
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; Cascade native hooks.json blocking hook bus (pre_write_code, pre_run_command); tier-2 support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home-nested",
"name": "windsurf",
"parent": ".codeium",
"env": [
"WINDSURF_CONFIG_DIR"
]
},
"localConfigDir": ".windsurf",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
],
"local": [
{
"kind": "commands",
"destSubpath": "workflows",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeCommandToWindsurfWorkflow"
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToWindsurfAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "windsurf-hooks-json",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": "undocumented",
"nested": "undocumented",
"maxDepth": "undocumented",
"background": "undocumented",
"subagentToolkit": "undocumented",
"backgroundDispatch": "undocumented",
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "undocumented",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true,
"legacyDevinSkillsCleanup": true,
"installsCommandBodiesForWorkflowDelegation": true,
"verificationStyle": "windsurf-workflows"
}
}
},
"zcode": {
"id": "zcode",
"role": "runtime",
"version": "1.14.0",
"title": "ZCode",
"description": "ZCode (Z.ai) — desktop Agentic Development Environment for GLM-5.2; Claude-shaped nested skills at ~/.zcode/skills/<name>/SKILL.md, slash commands, named subagents, native MCP; declarative plugin surface; profile-marker install; tier-2 community support.",
"tier": "core",
"requires": [],
"engines": {
"gsd": ">=1.6.0"
},
"runtime": {
"configHome": {
"kind": "dot-home",
"name": ".zcode",
"env": [
"ZCODE_CONFIG_DIR"
]
},
"localConfigDir": ".zcode",
"configFormat": "none",
"artifactLayout": {
"global": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToZcodeAgent"
}
],
"local": [
{
"kind": "skills",
"destSubpath": "skills",
"prefix": "gsd-",
"nesting": "nested",
"recursive": false,
"converter": "convertClaudeCommandToClaudeSkill"
},
{
"kind": "commands",
"destSubpath": "commands",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": null
},
{
"kind": "agents",
"destSubpath": "agents",
"prefix": "gsd-",
"nesting": "flat",
"recursive": false,
"converter": "convertClaudeAgentToZcodeAgent"
}
]
},
"triggerPrecedence": [
"skills",
"commands"
],
"commandStyle": "slash-hyphen",
"hooksSurface": "none",
"sandboxTier": "none",
"supportTier": 2,
"installSurface": "profile-marker-only",
"writesSharedSettings": false,
"permissionWriter": null,
"extendedHookEvents": [],
"hostIntegration": {
"embeddingMode": "declarative",
"commandSurface": "slash-file",
"dispatch": {
"namedDispatch": true,
"nested": "undocumented",
"maxDepth": "undocumented",
"background": false,
"subagentToolkit": "full",
"backgroundDispatch": false,
"isolation": "none",
"maxConcurrency": "undocumented"
},
"modelMode": "passive",
"hookBus": "host",
"stateIO": "filesystem",
"transport": "mcp",
"runtime": "electron",
"effortSurface": "undocumented"
},
"hostBehaviors": {
"skipSharedHooksInstall": true
}
}
}
};
const commandFamilies = {
"audit-open": {
"capId": "audit",
"module": "audit-command-router.cjs",
"router": "routeAuditOpen"
},
"audit-uat": {
"capId": "audit",
"module": "audit-command-router.cjs",
"router": "routeAuditUat"
},
"claude-orchestration": {
"capId": "claude-orchestration",
"module": "claude-orchestration-command-router.cjs",
"router": "routeClaudeOrchestrationCommand"
},
"extract-messages": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeExtractMessages"
},
"generate-claude-md": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateClaudeMd"
},
"generate-claude-profile": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateClaudeProfile"
},
"generate-dev-preferences": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeGenerateDevPreferences"
},
"graphify": {
"capId": "graphify",
"module": "graphify-command-router.cjs",
"router": "routeGraphifyCommand"
},
"intel": {
"capId": "intel",
"module": "intel-command-router.cjs",
"router": "routeIntelCommand"
},
"profile-questionnaire": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeProfileQuestionnaire"
},
"profile-sample": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeProfileSample"
},
"refactor": {
"capId": "refactor-trigger",
"module": "refactor-trigger-command-router.cjs",
"router": "routeRefactorTriggerCommand"
},
"scan-sessions": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeScanSessions"
},
"write-profile": {
"capId": "profile-pipeline",
"module": "profile-pipeline-command-router.cjs",
"router": "routeWriteProfile"
}
};
const capabilityClusters = {
"ai-integration": [
"ai-integration-phase"
],
"code-review": [
"code-review"
],
"graphify": [
"graphify"
],
"mempalace": [
"mempalace-capture",
"mempalace-recall"
],
"nyquist": [
"validate-phase"
],
"profile-pipeline": [
"profile-user"
],
"security": [
"secure-phase"
],
"ui": [
"ui-phase",
"ui-review"
]
};
const profileMembership = {
"ai-integration": {
"tier": "full",
"profiles": [
"full"
]
},
"code-review": {
"tier": "full",
"profiles": [
"full"
]
},
"graphify": {
"tier": "full",
"profiles": [
"full"
]
},
"mempalace": {
"tier": "full",
"profiles": [
"full"
]
},
"nyquist": {
"tier": "full",
"profiles": [
"full"
]
},
"profile-pipeline": {
"tier": "full",
"profiles": [
"full"
]
},
"security": {
"tier": "full",
"profiles": [
"full"
]
},
"ui": {
"tier": "full",
"profiles": [
"full"
]
}
};
const _requiresGraph = {
"ai-integration": [],
"antigravity": [],
"assumption-delta": [],
"audit": [],
"augment": [],
"broken-windows": [],
"claude": [],
"claude-orchestration": [],
"cline": [],
"code-review": [],
"codebuddy": [],
"coderabbit": [],
"codex": [],
"copilot": [],
"cursor": [],
"drift": [],
"external-job": [],
"gap-analysis": [],
"graphify": [],
"hermes": [],
"intel": [],
"kilo": [],
"kimi": [],
"kimi-code": [],
"live-dom-uat": [],
"llama-cpp": [],
"lm-studio": [],
"mempalace": [],
"nyquist": [],
"ollama": [],
"opencode": [],
"pattern-mapper": [
"research"
],
"pi": [],
"profile-pipeline": [],
"qwen": [],
"refactor-trigger": [],
"research": [],
"schema-gate": [],
"security": [],
"tdd": [],
"trae": [],
"ui": [],
"vscode": [],
"windsurf": [],
"zcode": []
};
function requiresClosure(id) {
const visited = new Set();
const queue = [id];
while (queue.length > 0) {
const current = queue.shift();
const reqs = _requiresGraph[current] || [];
for (const req of reqs) {
if (!visited.has(req)) {
visited.add(req);
queue.push(req);
}
}
}
return visited;
}
module.exports = {
version: '1',
capabilities,
bySkill,
byAgent,
byLoopPoint,
configKeys,
configSchema,
runtimes,
commandFamilies,
capabilityClusters,
profileMembership,
requiresClosure,
};