Merge remote-tracking branch 'origin/next' into pr-1487-merge

# Conflicts:
#	CONTEXT.md
#	src/runtime-artifact-layout.cts
#	src/surface.cts
This commit is contained in:
Joe Slitzker
2026-06-22 20:08:44 -05:00
251 changed files with 11784 additions and 1083 deletions

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 1452
---
**`workflow.context_guard_mode` config key** — proactive context-exhaustion guard for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using the degradation signals defined in `context-budget.md`. Values: `warn` (default — emit warning and recommend `/gsd:pause-work` when POOR tier detected), `auto` (automatically invoke `/gsd:pause-work` before next wave), `off` (disable). Set via `gsd config-set workflow.context_guard_mode auto` for fully autonomous checkpoint behaviour. (#1452)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1532
---
**Core-path file locks now verify the holder process is alive before stealing a stale lock (#1532)** — the STATE.md write lock (`acquireStateLock`) and the `.planning/` workspace lock (`withPlanningLock`) previously stole locks on a bare `mtime` timer with no liveness check, so a live-but-slow holder (e.g. a deep `.planning/` scan on slow NFS) could have its lock stolen mid-write, corrupting STATE.md or losing an update. Both locks now gate stealing on `process.kill(pid,0)` liveness with a deadman ceiling above the wait budget (pid-reuse backstop), `withPlanningLock` no longer force-steals a live holder on timeout (and can no longer leak an uncaught `EEXIST`), `writeStateMd` computes its disk scan inside the lock, and `acquireStateLock` no longer leaks a file descriptor or strands an empty lock on a recoverable write error. The steal itself is now race-safe: a lock is never stolen while its body is still being written (the create→pid-write window), and stealing uses an atomic rename with an identity re-confirm so two waiters can no longer both reclaim the same lock and end up holding it concurrently. The uncontended path is unchanged.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1537
---
**Non-Claude runtime installs now resolve their own runtime and never attempt Claude-only worktree isolation** — on any non-Claude install (Cursor, Gemini, Qwen, etc.) a runtime-neutral `.planning/config.json` previously resolved `runtime=claude` and enabled git worktree isolation, which only Claude Code's `isolation="worktree"` can honor — risking main-checkout edits while the workflow believed agents were isolated. Every non-Claude install now resolves its own runtime identity, defaults `workflow.use_worktrees` to `false`, fails closed if worktrees are forced on, and runs plan/execute inline in the manager/autonomous flows since only Codex can background-nest the pipeline's subagents. (#1521)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1456
---
Phase-aware commands now resolve project-code-prefixed ROADMAP headings such as MANIFOLD-117, while the roadmapper is instructed to keep project_code out of phase headings.

View File

@@ -0,0 +1,7 @@
---
type: Changed
pr: 1438
---
**Thread `isGlobal` install scope through the descriptor-driven `convertedAgentsKind` / `stageAgentsForRuntimeWithConverter` plumbing** — a prerequisite for the ADR-1235 agent-conversion cutover. No runtime declares a converted `agents` kind yet; the `capability.json` wiring is deferred to a follow-up that first ships the ADR-1235 §0 byte-for-byte parity harness (so the `/gsd:surface` / `--materialize` consumer can mirror the legacy agent pipeline before the kind goes live). The legacy `bin/install.js` agent loop remains authoritative, so installed agent output is unchanged. (#1173)
<!-- docs-exempt: internal install-path plumbing only (ADR-1235 cutover prerequisite); no runtime declares the converted agents kind, the legacy loop remains authoritative, and installed agent output is unchanged — no user-facing command/config/behavior surface. -->

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1536
---
adr-parser now classifies 9 previously-dropped punctuated ADR headers (Trade-offs, Non-Goals, Won't Do, Follow-up, How We'll Know, etc.) into their intended buckets instead of leaving them unmapped.

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 1421
---
**`/gsd-review` now asks external reviewers to verify plan claims against the source** — the reviewer prompt requires opening the referenced files, citing `file:line` evidence + mechanism, and tracing asserted behavior, with a graceful-degradation clause for reviewers that have no file access. This turns every capable agentic reviewer into a real second source instead of a plan-text paraphraser. (#1318)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1534
---
Add prototype-pollution guard to the workstream/root config merge (_deepMergeConfig) so a config.json with a __proto__/constructor/prototype key can no longer spoof unset config flags.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1418
---
**All GSD agents load on Gemini again** — the Claude `Skill`/`SlashCommand` tools were converted to an invalid `skill` tool that Gemini rejects, aborting the load of 22 of 34 agents. They are now excluded from the Gemini and Gemini-backed Antigravity agent `tools:` frontmatter, the same way `AskUserQuestion` already is. (#1394)

View File

@@ -0,0 +1,6 @@
---
type: Fixed
pr: 667
---
**`/gsd:pr-branch` now handles sub-repos defined in config** — when `planning.sub_repos` is set, the command scans each sub-repo for uncommitted changes and offers to create a branch, commit, push, and open a companion PR per sub-repo. Previously, sub-repos were silently ignored because all git commands ran against the shell's current directory instead of the intended repo path. All sub-repo git operations now use `git -C <repo>` so no shell-state assumptions are made.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1519
---
**Codex installs no longer run with unsafe Claude-style worktree isolation** — a Codex install with a runtime-neutral `.planning/config.json` was resolving its runtime as Claude and enabling git worktree isolation, which Codex's `spawn_agent` cannot honor; the Codex fail-closed guard was also silently dead because runtime/worktree config was read JSON-quoted and broke shell equality checks. Codex-emitted workflows now resolve `runtime=codex`, default `workflow.use_worktrees` to `false`, and fail closed when worktrees are forced on. (#1515)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1504
---
`verify codebase-drift` now reads `workflow.drift_action` and `workflow.drift_threshold` from the correct nested config shape — previously both keys silently no-oped because `loadConfig()` returns a flattened object and `config?.workflow` was always `undefined`.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 722
---
**`/gsd-capture --list-seeds` audits parked seeds** — a new read-only listing of `.planning/seeds/` showing each seed's ID, status, scope, and trigger, with an optional status filter (e.g. `--list-seeds dormant`). Backed by the `gsd-tools list-seeds` command. Previously seeds could only be created or auto-surfaced at `/gsd-new-milestone`, with no way to browse them on demand (#441).

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 1518
---
**verify-phase test-tier prohibition fail-first can now prove the RED is caused by the violation's _content_** — the `node-test` machine-proof (#1279) confirmed a known-bad subject drives the negative test RED, but could not tell a genuine content-violation from a deceptive test that reds merely because `GSD_PROHIB_SUBJECT` is set. An optional fifth flat scalar `check_clean_fixture` (→ `CheckDescriptor.cleanFixture`) threads a KNOWN-CLEAN control subject through `projectProhibitions` + `descriptorFromProjection`; when present the prover also runs the check against it and requires GREEN, so fail-first is proven only when the check is RED on the violation **and** GREEN on the clean subject (content-dependent). It is opt-in and additive: absent a clean fixture the prover behaves exactly as it did post-#1314 (no control, documented residual), preserving the zero-authoring compose path; the lint-rule kind needs no analog. (#1346)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1574
---
**OpenCode and other AGENTS-native runtimes now get a root `AGENTS.md` from `/gsd:new-project`** — the workflow hardcoded a codex-only branch that sent every other runtime to `.claude/CLAUDE.md`, a location OpenCode never loads. A shared `getProjectInstructionFile(runtime)` policy (claude→`.claude/CLAUDE.md`, codex/opencode/kilo/kimi→`AGENTS.md`, copilot→`.github/copilot-instructions.md`, antigravity/gemini→`GEMINI.md`) is now the single source of truth consumed by both the new-project workflow and the generate-claude-md path, with a parity test guarding drift.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1539
---
`roadmap upgrade` now rejects an unsupported or malformed `--convention` value (including the `--convention=` form) instead of silently running the milestone-prefixed migration, and no longer hard-exits inside the command-routing hub.

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 1597
---
**Plugin installs now expose GSD skills** — when GSD is installed as a Claude Code plugin (`claude plugin install`), its skills are available via `gsd-core:<skill>` the native way. Previously, plugin-only installs lacked the skill surface because `bin/install.js` never ran; agents that preload `global:gsd-core:<skill>` (PR #1261) now resolve against plugin-provided skills. (#1596)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1543
---
A failed `roadmap upgrade --apply` now actually rolls back .planning/ even when it is gitignored (commit_docs:false), instead of reporting a successful rollback while leaving the workspace half-migrated. Rollback is surgical and no longer runs a whole-repo git reset --hard.

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1409
---
**Codex runtime no longer crashes on startup** — every `gsd-tools` command previously aborted with `Cannot find module '../../../package.json'` on Codex, whose runtime root has no `package.json`, because a module in the loader chain did a top-level require of it. The version emitted into Hermes skill frontmatter is now sourced lazily from the installed `gsd-core/VERSION` (validated semver), so `gsd-tools` loads on every runtime and never emits `version: undefined`. (#1383)

View File

@@ -0,0 +1,5 @@
---
type: Added
pr: 1448
---
Added a validated `gsd-tools worktree record-agent` writer verb that appends a per-agent entry to the wave cleanup manifest, validating every field at write time with the same rules the `cleanup-wave` reader enforces (write-strict `--agent-id`) and failing loudly with a recovery hint instead of silently appending an under-populated entry. The execute-phase orchestrator now records each spawned worktree through this verb. (#1448)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1410
---
**`query agent-skills` no longer returns empty output on Windows** — the plain (non-`--json`) path wrote the `<agent_skills>` block then immediately called `process.exit(0)`, which truncated the async stdout buffer on Windows pipes/files so every `${AGENT_SKILLS_*}` workflow capture expanded empty and configured per-agent skills were silently dropped. It now flushes synchronously via the same `writeAllSync` helper the `--json` path uses. (#1400)

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1552
---
roadmap analyze no longer reports phantom missing_phase_details for milestone-prefixed (M-NN) phase IDs

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 1541
---
Atomic file writes now retry a transient rename lock on Windows (a reader holding the target open) instead of falling back to a non-atomic write that could let a concurrent reader observe a truncated STATE.md/ROADMAP.md.

View File

@@ -1,7 +1,7 @@
{
"name": "gsd-core",
"displayName": "GSD Core",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
"author": {
"name": "open-gsd",
@@ -19,5 +19,6 @@
"gsd"
],
"commands": "./commands/gsd/",
"skills": "./skills/",
"hooks": "./hooks/hooks.json"
}

View File

@@ -101,7 +101,7 @@ Cross-seam principle (ADR-1411, epic #1411): context resolution — config loadi
Diagnostic-output convention for the Resolution Provenance principle (ADR-1411 P3, #1416). Config-interpreting read verbs expose `Resolution<T> { value, configured, reason, warnings }` (`src/resolution.cts`); agent-skills is the first adopter, where `value = { block, skills_count }` and `source`/`degraded` remain config-provenance extras outside the envelope. Other read verbs expose at least `warnings[]` (e.g. capability-state `{ runtimeConfigDir, capabilities, warnings? }`) without `configured`/`reason`, which are meaningful only for config-interpreting verbs. Mutation verbs expose `warnings[]` (advisory) PLUS `errors[]` (operation-not-applied), e.g. capability-writer `{ capabilities, warnings, errors }`. The shared seam across all shapes is `warnings: string[]`; a single generic `Resolution<T>` across read+write verbs was rejected by the deletion test (`configured`/`reason` are meaningless for capability verbs; `errors[]` cannot fold into `warnings[]`) — ADR-1411 P3 amendment. Recurrence prevention is delivered by P4's CI guard (a configured input resolving empty must carry a `reason`), not by a shared envelope. A CI guard (`scripts/lint-resolution-provenance.cjs`, wired into `lint:ci`) enforces that every registered config-interpreting read verb keeps a `configured_empty`/`not_configured` contract test; the registry in that script is the registration point for future verbs (ADR-1411 P4 / #1417).
### Worktree Safety Policy Module
CJS Module owning worktree lifecycle safety policy for the GSD orchestration layer. Interface: `resolveWorktreeContext(cwd, deps) → WorktreeContext` (linked-worktree root mapping), `parseWorktreePorcelain(output) → WorktreeEntry[]` (porcelain parser, skips detached HEAD), `planWorktreePrune(repoRoot, opts, deps) → PrunePlan` (metadata-prune plan, never destructive by default), `executeWorktreePrunePlan(plan, deps) → PruneResult` (executes prune; degrades gracefully on git timeout), `listLinkedWorktreePaths(repoRoot, deps) → LinkedPathsResult`, `inspectWorktreeHealth(repoRoot, opts, deps) → HealthResult` (orphan + stale detection), `snapshotWorktreeInventory(repoRoot, opts, deps) → InventoryResult`, `planWorktreeWaveCleanup(repoRoot, manifest) → CleanupPlan` (manifest-scoped, fail-closed), `executeWorktreeWaveCleanupPlan(plan, deps) → CleanupResult`. Source of truth: `gsd-core/bin/lib/worktree-safety.cjs`. Timeout path: all git subprocess calls are bounded; callers receive `ok:false, reason:'git_timed_out'` rather than a thrown exception. Test anchor: `tests/worktree-safety.test.cjs`. The `core.cjs` re-export spine was retired in epic #1267: this module absorbed the two thin compositional wrappers that squatted in Core — `resolveWorktreeRoot(cwd, deps)` (a projection over `resolveWorktreeContext`) and `pruneOrphanedWorktrees(...)` (sequences `planWorktreePrune` + `executeWorktreePrunePlan` with a timeout warning) — so callers reach this single worktree-lifecycle seam directly. `gitWorktreeInfoInternal` did NOT move here — worktree-info detection belongs to the Git Query Module.
CJS Module owning worktree lifecycle safety policy for the GSD orchestration layer. Interface: `resolveWorktreeContext(cwd, deps) → WorktreeContext` (linked-worktree root mapping), `parseWorktreePorcelain(output) → WorktreeEntry[]` (porcelain parser, skips detached HEAD), `planWorktreePrune(repoRoot, opts, deps) → PrunePlan` (metadata-prune plan, never destructive by default), `executeWorktreePrunePlan(plan, deps) → PruneResult` (executes prune; degrades gracefully on git timeout), `listLinkedWorktreePaths(repoRoot, deps) → LinkedPathsResult`, `inspectWorktreeHealth(repoRoot, opts, deps) → HealthResult` (orphan + stale detection), `snapshotWorktreeInventory(repoRoot, opts, deps) → InventoryResult`, `planWorktreeWaveCleanup(repoRoot, manifest) → CleanupPlan` (manifest-scoped, fail-closed), `executeWorktreeWaveCleanupPlan(plan, deps) → CleanupResult`, `planWorktreeRecordAgent(manifestRaw, fields) → RecordAgentPlan` (write-strict per-agent manifest append; validates each field at write time via the same `normalizeCleanupManifestEntry` rules the reader enforces; fail-closed on a missing/garbled field or a duplicate `(worktree_path, branch)` the reader would dedup away), `cmdWorktreeRecordAgent(cwd, args, deps) → RecordAgentCmdResult` (thin deps-injectable IO wrapper for the `worktree record-agent` verb). Source of truth: `gsd-core/bin/lib/worktree-safety.cjs`. Timeout path: all git subprocess calls are bounded; callers receive `ok:false, reason:'git_timed_out'` rather than a thrown exception. Test anchor: `tests/worktree-safety.test.cjs`. The `core.cjs` re-export spine was retired in epic #1267: this module absorbed the two thin compositional wrappers that squatted in Core — `resolveWorktreeRoot(cwd, deps)` (a projection over `resolveWorktreeContext`) and `pruneOrphanedWorktrees(...)` (sequences `planWorktreePrune` + `executeWorktreePrunePlan` with a timeout warning) — so callers reach this single worktree-lifecycle seam directly. `gitWorktreeInfoInternal` did NOT move here — worktree-info detection belongs to the Git Query Module.
### Worktree Lifecycle Module
Workflow contract seam covering agent worktree lifecycle orchestration rules. The `worktree_branch_check` block lives in one canonical fragment (`gsd-core/references/worktree-branch-check.md`) that `execute-phase.md`, `quick.md`, `diagnose-issues.md`, and `execute-plan.md` embed at dispatch. Key invariants: `worktree_branch_check` is **verify-only and fail-closed** — the orchestrator owns worktree lifecycle and base recovery, so the sub-agent holds no state-correction primitives; HEAD attachment verified via `git symbolic-ref`; positive allow-list `^worktree-agent-*` enforced; `git update-ref` on protected refs is prohibited; on base mismatch the sub-agent halts with `exit 42` and surfaces to the orchestrator (#48); the orchestrator runs a cwd-drift guard at `execute_waves` entry that resolves the worktree root and refuses drift into an agent worktree (#48); cleanup is manifest-scoped (`WAVE_WORKTREE_MANIFEST`) not global-discovery-based; worktree spawning is sequential (one `run_in_background` at a time to avoid `config.lock` contention). Test anchor: `tests/worktree.test.cjs`.
@@ -152,10 +152,13 @@ Module owning install detection for `/gsd:update`. `resolveUpdateContext({ home,
Module owning which skills and agents are written to runtime config directories at install time (Phase 1) and at runtime via cluster-level toggles (Phase 2). Phase 1: `gsd-core/bin/lib/install-profiles.cjs` defines named profiles (`core`, `standard`, `full`), computes transitive closure over `requires:` frontmatter, stages skills/agents to runtime config dirs, and persists the chosen profile in a `.gsd-profile` marker. Profile resolution precedence: explicit `--profile=` flag > `.gsd-profile` marker > `full`. `--minimal`/`--core-only` are back-compat aliases for `--profile=core`. Phase 2: `gsd-core/bin/lib/surface.cjs` implements the `/gsd:surface` slash command for cluster-level enable/disable without reinstall; cluster definitions live in `gsd-core/bin/lib/clusters.cjs`; per-runtime state persists in `<runtimeConfigDir>/.gsd-surface.json` independent from the `.gsd-profile` marker. See ADR-0011.
### Runtime Artifact Layout Module
Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Owns the per-runtime `nested` skill-bundle decision (#69): a `skillsKind` flag in `src/runtime-artifact-layout.cts` drives whether a runtime receives the nested router layout (6 `gsd-ns-*` routers + concrete skills under `<router>/skills/<name>/`) or the flat `skills/gsd-<stem>/` layout; the evidence/doc-link matrix is recorded in a comment above `resolveRuntimeArtifactLayout`. Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. The shared accessor `getInstallExports` (exported from `runtime-artifact-layout.cjs`) is the single-source seam through which `surface.cjs` reaches `computePathPrefix` and `applyRuntimeContentRewritesInPlace`; the resolved `scope` (`'local'`|`'global'`) is now carried on the `Layout` object returned by `resolveRuntimeArtifactLayout` so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). The `.gsd-source` marker (#1477) is a two-party provisioning contract that lets source resolution succeed on the Claude global skills layout, which ships `gsd-core/{bin,contexts,references,templates,workflows}` but no `commands/gsd` source tree for `findInstallSourceRoot` to walk up to: the writer is `bin/install.js`, which writes `<configDir>/.gsd-source` (content: the absolute path to its own `commands/gsd`, terminated by a newline) when `runtime === 'claude' && isGlobal`, guarded by `fs.existsSync` so a half-published package never writes a dangling marker; the reader is `findInstallSourceRoot(configDir)`, which prefers the marker over its walk-up but falls through to the walk-up if the marker is absent, dangling, or empty/whitespace-only. The marker's package root must also hold `bin/install.js` — `getInstallExports(configDir)` derives the installer-exports path from the resolved source root (`<packageRoot>/commands/gsd` → `<packageRoot>/bin/install.js`), and caches per `configDir` so a no-arg warm-up call cannot poison later marker-aware resolution. See ADR-3660.
Module owning the per-runtime mapping from artifact kind to filesystem placement. ADR-3660 defines the typed `kinds` per runtime (`commands`, `agents`, `skills`) with destination subpath, prefix, and stage adapter (with per-runtime converters in `bin/install.js`: `convertClaudeCommandToClaudeSkill`, `…CodexSkill`, `…CopilotSkill`, `…AntigravitySkill`). Owns the per-runtime `nested` skill-bundle decision (#69): a `skillsKind` flag in `src/runtime-artifact-layout.cts` drives whether a runtime receives the nested router layout (6 `gsd-ns-*` routers + concrete skills under `<router>/skills/<name>/`) or the flat `skills/gsd-<stem>/` layout; the evidence/doc-link matrix is recorded in a comment above `resolveRuntimeArtifactLayout`. Phase 1 applies this seam to the Runtime Surface Module (`surface.cjs:applySurface`); as of #813, `applySurface` applies the same per-runtime skill-body path rewrites as `installRuntimeArtifacts` for `skills` kinds — re-surfacing no longer overwrites installed SKILL.md bodies with converter-default `~/.claude` paths. Per ADR-1508 / #1511 the former `getInstallExports`/`loadInstallExports` relay (a `GSD_TEST_MODE`-guarded `require('bin/install.js')` by which `surface.cjs` reached `computePathPrefix`/`applyRuntimeContentRewritesInPlace`) was DELETED from this module; content rewriting now lives in the Runtime Artifact Conversion Module and `surface.cjs:applySurface` calls its `rewriteStagedSkillBodies` directly. The resolved `scope` is still carried on the `Layout` object so `applySurface` derives the same `pathPrefix` (global `$HOME` form vs. absolute) as a fresh install. Phase 2 is planned to migrate install/uninstall in `bin/install.js` so all lifecycle sites iterate one shared layout table instead of re-encoding runtime layout logic. This design is intended to remove the #3659 class of omissions. Migrations remain under the Installer Migration Module (ADR-0008). The `.gsd-source` marker (#1477) is a two-party provisioning contract that lets source resolution succeed on the Claude global skills layout, which ships `gsd-core/{bin,contexts,references,templates,workflows}` but no `commands/gsd` source tree for `findInstallSourceRoot` to walk up to: the writer is `bin/install.js`, which writes `<configDir>/.gsd-source` (content: the absolute path to its own `commands/gsd`, terminated by a newline) when `runtime === 'claude' && isGlobal`, guarded by `fs.existsSync` so a half-published package never writes a dangling marker; the reader is `findInstallSourceRoot(configDir)`, which prefers the marker over its walk-up but falls through to the walk-up if the marker is absent, dangling, or empty/whitespace-only. See ADR-3660.
### Runtime Artifact Conversion Module
Sibling Module to Runtime Artifact Layout Module. Owns projection from canonical Claude-authored command/agent/skill markdown into runtime-specific artifact bodies, including converter selection, frontmatter/body normalization, runtime path rewrites, and staged artifact generation. Runtime Artifact Layout remains responsible for filesystem placement (`kind`, destination subpath, prefix, nesting); Runtime Artifact Conversion owns the content Implementation behind that placement seam so install, uninstall/surface parity, and future plugin/package projections stop reaching back through `bin/install.js` for converter functions or `GSD_TEST_MODE`-guarded installer exports. Chosen direction: sibling Module, not an expanded Layout Module, to preserve ADR-3660's narrow placement responsibility while deepening artifact content locality. First slice: relocate only the layout-reached conversion family (`convertClaudeCommandTo*Skill`, converted command-file emitters, `buildKimiAgentArtifacts`) plus the minimal helper closure they need; do not leave helper dependencies in `bin/install.js` because that would preserve the same shallow seam under a new filename. Installer integration decision: `bin/install.js` imports the conversion Module at top level and re-exports the moved names for compatibility; the conversion Module must not import `bin/install.js` or Runtime Artifact Layout, so the dependency direction becomes installer/layout Adapters -> conversion Module, never conversion -> installer. First-slice Interface decision: export the existing compatibility names only; do not introduce a grouped `convertRuntimeArtifact` Interface until after relocation proves byte-for-byte behavior.
Sibling Module to Runtime Artifact Layout Module. Owns projection from canonical Claude-authored command/agent/skill markdown into runtime-specific artifact bodies, including converter selection, frontmatter/body normalization, runtime path rewrites, and staged artifact generation. Runtime Artifact Layout remains responsible for filesystem placement (`kind`, destination subpath, prefix, nesting); Runtime Artifact Conversion owns the content Implementation behind that placement seam so install, uninstall/surface parity, and future plugin/package projections stop reaching back through `bin/install.js` for converter functions or `GSD_TEST_MODE`-guarded installer exports. Chosen direction: sibling Module, not an expanded Layout Module, to preserve ADR-3660's narrow placement responsibility while deepening artifact content locality. First slice: relocate only the layout-reached conversion family (`convertClaudeCommandTo*Skill`, converted command-file emitters, `buildKimiAgentArtifacts`) plus the minimal helper closure they need; do not leave helper dependencies in `bin/install.js` because that would preserve the same shallow seam under a new filename. Installer integration decision: `bin/install.js` imports the conversion Module at top level and re-exports the moved names for compatibility; the conversion Module must not import `bin/install.js` or Runtime Artifact Layout, so the dependency direction becomes installer/layout Adapters -> conversion Module, never conversion -> installer. First-slice Interface decision: export the existing compatibility names only; do not introduce a grouped `convertRuntimeArtifact` Interface until after relocation proves byte-for-byte behavior. SHIPPED (ADR-1508): the converter family relocated in #1510 Phase 1 (`getDirName`→runtime-name-policy, `processAttribution` here); #1511 Phase 2 moved the content-rewrite engine here in full — `_applyRuntimeRewrites` (per-runtime switch, injected attribution), the staged-content walkers `applyRuntimeContentRewritesInPlace`/`applyRuntimeContentRewritesForCommandsInPlace`, `computePathPrefix` (private; `_computePathPrefix` for tests), and the deep public seam `rewriteStagedSkillBodies`/`rewriteStagedCommandBodies({runtime,configDir,scope,homedir?,platform?,resolveAttribution?})`. `bin/install.js` binds these back (single owner, exports preserved); `getCommitAttribution` stays in `bin/install.js` (impure install-time config I/O) and is injected. The `getInstallExports` relay in Runtime Artifact Layout Module was deleted; the dependency direction installer/layout → conversion (never upward) is now enforced. Exception: opencode and kilo path-prefix rewriting is a deliberate `bin/install.js`-owned pre-conversion step (`applyOpencodeFamilyPathPrefix`) per #784, not a violation of the single-owner rule. Source: `gsd-core/bin/lib/runtime-artifact-conversion.cjs` (generated from `src/runtime-artifact-conversion.cts`). Also exports `resolveVersionFrom(libDir)` — a lazy, defensive GSD-version resolver (installed-tree `gsd-core/VERSION` first, then the source/npm `package.json` three dirs up, both validated against the repo's shared semver-prefix shape, degrading to `''` on failure) that replaced a module-load-time `require('../../../package.json')` which crashed on runtimes whose root carries no `package.json` (e.g. Codex) (#1383).
### Runtime Artifact Install Plan Module
Module owning install-time staging and content-rewrite selection for a pre-resolved Runtime Artifact Layout. Interface: `createRuntimeArtifactInstallPlan({ layout, resolvedProfile, homedir?, platform?, resolveAttribution?, deps? }) -> { ok:true, plan:{ items, cleanupDirs } } | { ok:false, kind:'stage_failed'|'rewrite_failed', message, cleanupDirs, failedKind? }`. It iterates `layout.kinds` in order, calls each kind's `stage(resolvedProfile)`, delegates `commands` to Runtime Artifact Conversion `rewriteStagedCommandBodies`, delegates `skills` and `kimi-agents` to `rewriteStagedSkillBodies`, leaves non-rewritten kinds unchanged, and projects copy items as `{ kind, sourceDir, destDir }`. It deliberately does not prune, copy, run legacy migrations, print output, or execute cleanup; those remain Installer Module adapter responsibilities until later slices wire the plan into `bin/install.js`. Source: `gsd-core/bin/lib/runtime-artifact-install-plan.cjs` (generated from `src/runtime-artifact-install-plan.cts`). See Runtime Artifact Layout Module and Runtime Artifact Conversion Module.
### Command Roster Module
Tiny read-only helper Module owning discovery of canonical `commands/gsd/*.md` command stems for artifact conversion and runtime projection. It is a sibling dependency of Runtime Artifact Conversion Module, not part of conversion itself: conversion consumes a roster to safely rewrite `gsd:` / `/gsd-` references, while roster discovery owns filesystem/catalog knowledge. First slice: extract existing `readGsdCommandNames` behavior behind this Module instead of moving it into Runtime Artifact Conversion Module or keeping it as installer-owned state.
@@ -424,7 +427,7 @@ A legal deferred state of an Execute step (`external_job_waiting`): the executor
`WORKTREE.SEAM.current=Worktree Safety Policy Module`
`WORKTREE.SEAM.files=[gsd-core/bin/lib/worktree-safety.cjs]`
`WORKTREE.SEAM.interface=[resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune, executeWorktreePrunePlan]`
`WORKTREE.SEAM.interface=[resolveWorktreeContext, parseWorktreePorcelain, planWorktreePrune, executeWorktreePrunePlan, planWorktreeRecordAgent, cmdWorktreeRecordAgent]`
`WORKTREE.SEAM.default-prune-policy=metadata_prune_only (non-destructive)`
`WORKTREE.SEAM.decision-1=retain non-destructive default; destructive path only as explicit future opt-in scaffold`

View File

@@ -834,7 +834,7 @@ Defensive normalization at trust boundaries must validate both the value's type
- **CommonJS** (`.cjs`) — the project uses `require()`, not ESM `import`
- **No external dependencies in core** — `gsd-tools.cjs` and all lib files use only Node.js built-ins
- **Conventional commits** — `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `ci:`
- **Conventional commits** — `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `ci:`. The full grammar is `<type>(<scope>): <subject>` (enforced by `hooks/gsd-validate-commit.sh`; subject ≤72 chars, lowercase, imperative mood, no trailing period). When the work resolves a tracked issue, put the issue number in the scope: `fix(#1520): randomize mktemp temp paths on BSD/macOS`. The same convention applies to PR titles — release notes are grouped by the title's type prefix (`feat` → Feature, `fix` → Fix, everything else → Enhancement).
## File Structure

View File

@@ -226,6 +226,10 @@ current milestone number and a two-digit phase index within that milestone
active milestone context (default: `1` for new projects). This ensures downstream tools that
parse `### Phase N-NN:` headers for milestone-scoped workflows receive correctly prefixed IDs.
`project_code` is only a phase-directory prefix. Never include `project_code` in ROADMAP phase
checklist entries or detail headers. For example, even when `project_code: "PROJ"` is configured,
write `Phase 7` for `sequential` and `Phase 1-07` for `milestone-prefixed`, not `Phase PROJ-7`.
## Granularity Calibration
Read granularity from config.json. Granularity controls compression tolerance.
@@ -328,6 +332,7 @@ After roadmap creation, REQUIREMENTS.md gets updated with phase mappings:
### 1. Summary Checklist (under `## Phases`)
Use the form matching `phase_id_convention` from config.
Do not include `project_code` in checklist phase IDs.
**Sequential (default — when absent or `"sequential"`):**
@@ -348,6 +353,7 @@ Use the form matching `phase_id_convention` from config.
### 2. Detail Sections (under `## Phase Details`)
Use the header form matching `phase_id_convention` from config.
Do not include `project_code` in detail header phase IDs.
**Sequential (default):**

View File

@@ -33,6 +33,11 @@ const {
getGlobalConfigDir,
getGlobalSkillsBase,
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
// getDirName (runtime -> local config dir name) is relocated out of this
// installer to the runtime-name-policy leaf (ADR-1508 / #1510 Phase 1) so the
// conversion module's rewrite engine can consume it without importing
// bin/install.js. Re-exported below for back-compat consumers/tests.
const { getDirName } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const {
applyWorktreeBaseRef,
readBaseRefFromSettings,
@@ -358,6 +363,10 @@ const {
const {
resolveRuntimeArtifactLayout,
} = require(path.join(_gsdLibDir, 'runtime-artifact-layout.cjs'));
const {
createRuntimeArtifactInstallPlan,
createRuntimeArtifactUninstallPlan,
} = require(path.join(_gsdLibDir, 'runtime-artifact-install-plan.cjs'));
const {
planLegacyCleanup,
applyLegacyCleanup,
@@ -461,25 +470,8 @@ Then re-run: npx ${pkg.name}@latest
}
}
// Helper to get directory name for a runtime (used for local/project installs)
function getDirName(runtime) {
if (runtime === 'copilot') return '.github';
if (runtime === 'opencode') return '.opencode';
if (runtime === 'gemini') return '.gemini';
if (runtime === 'kilo') return '.kilo';
if (runtime === 'codex') return '.codex';
if (runtime === 'antigravity') return '.agents';
if (runtime === 'cursor') return '.cursor';
if (runtime === 'windsurf') return '.devin';
if (runtime === 'augment') return '.augment';
if (runtime === 'trae') return '.trae';
if (runtime === 'qwen') return '.qwen';
if (runtime === 'hermes') return '.hermes';
if (runtime === 'kimi') return '.kimi-code';
if (runtime === 'codebuddy') return '.codebuddy';
if (runtime === 'cline') return '.cline';
return '.claude';
}
// getDirName (runtime -> local config dir name) now lives in
// runtime-name-policy.cjs (ADR-1508 / #1510 Phase 1); imported + re-exported.
/**
* Get the config directory path relative to home directory for a runtime
@@ -605,30 +597,12 @@ if (hasHelp) {
process.exit(0);
}
/**
* Compute the path prefix used for `@file` references in installed command/skill
* markdown. For global installs into a runtime config dir under $HOME, we
* normally substitute the home prefix with `$HOME` so paths expand correctly
* inside double-quoted shell commands. OpenCode is exempt on every platform:
* its `@file` include syntax does NOT shell-expand `$HOME`, so a literal
* `@$HOME/...` is treated as a path relative to the config command/ dir, which
* resolves to `command/$HOME/...` (file not found). For OpenCode we always emit
* the absolute resolved path. (#2376 Windows, #2831 macOS/Linux.)
*
* @param {object} args
* @param {boolean} args.isGlobal - Global runtime install vs local project
* @param {boolean} args.isOpencode - Whether the runtime is OpenCode
* @param {boolean} args.isWindowsHost - process.platform === 'win32'
* @param {string} args.resolvedTarget - Absolute target dir, forward-slashed
* @param {string} args.homeDir - User home dir, forward-slashed
* @returns {string} pathPrefix ending with '/'
*/
function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost, resolvedTarget, homeDir }) {
if (isGlobal && resolvedTarget.startsWith(homeDir) && !isOpencode) {
return '$HOME' + resolvedTarget.slice(homeDir.length) + '/';
}
return `${resolvedTarget}/`;
}
// computePathPrefix: implementation moved to runtimeArtifactConversion._computePathPrefix
// (ADR-1508 / #1511 Phase 2 — single owner). The const binding above (~line 638)
// re-exports it here for call sites and module.exports.
// Original doc: Compute the path prefix used for `@file` references in installed
// command/skill markdown. For global installs under $HOME uses $HOME/... form;
// OpenCode always uses the absolute path (#2376 Windows, #2831 macOS/Linux).
// normalizeNodePath, resolveNodeRunner, resolveBashRunner, referencesHook are
// now owned by the runtime-hooks-surface module. Import them here so
@@ -643,6 +617,19 @@ const referencesHook = hooksSurface.referencesHook;
// applySettingsJsonHooks: mutates settings.hooks.* in place with all GSD-managed
// hook registrations for settings.json-surface runtimes (ADR-857 phase 5f-1b).
const applySettingsJsonHooks = hooksSurface.applySettingsJsonHooks;
// processAttribution: pure Co-Authored-By content transform, relocated to the
// conversion module (ADR-1508 / #1510 Phase 1). Bound here so install.js
// callers continue to work and there is a single implementation. (All call
// sites are below this line, so the const binding has no TDZ hazard.)
const processAttribution = runtimeArtifactConversion.processAttribution;
// computePathPrefix / applyRuntimeContentRewritesInPlace / applyRuntimeContentRewritesForCommandsInPlace:
// Single implementations now live in runtimeArtifactConversion (ADR-1508 / #1511 Phase 2).
// Re-bound here so install.js call sites and exports continue to work unchanged.
// Local bodies replaced by breadcrumb comments at their original locations.
// All call sites are below this line → no TDZ hazard.
const computePathPrefix = runtimeArtifactConversion._computePathPrefix;
const applyRuntimeContentRewritesInPlace = runtimeArtifactConversion.applyRuntimeContentRewritesInPlace;
const applyRuntimeContentRewritesForCommandsInPlace = runtimeArtifactConversion.applyRuntimeContentRewritesForCommandsInPlace;
function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
return hooksSurface.rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts);
@@ -1417,24 +1404,9 @@ function getCommitAttribution(runtime) {
return result;
}
/**
* Process Co-Authored-By lines based on attribution setting
* @param {string} content - File content to process
* @param {null|undefined|string} attribution - null=remove, undefined=keep, string=replace
* @returns {string} Processed content
*/
function processAttribution(content, attribution) {
if (attribution === null) {
// Remove Co-Authored-By lines and the preceding blank line
return content.replace(/(\r?\n){2}Co-Authored-By:.*$/gim, '');
}
if (attribution === undefined) {
return content;
}
// Replace with custom attribution (escape $ to prevent backreference injection)
const safeAttribution = attribution.replace(/\$/g, '$$$$');
return content.replace(/Co-Authored-By:.*$/gim, `Co-Authored-By: ${safeAttribution}`);
}
// processAttribution (pure Co-Authored-By content transform) relocated to
// runtime-artifact-conversion.cjs (ADR-1508 / #1510 Phase 1); bound above.
// getCommitAttribution stays here — it is impure install-time config I/O.
/**
* Convert Claude Code frontmatter to opencode format
@@ -1545,11 +1517,17 @@ function convertGeminiToolName(claudeTool) {
// Task/Agent: exclude — agents are auto-registered as callable tools.
// AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
// emitting it causes frontmatter validation errors (#3362).
// Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
// the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
// that fails frontmatter validation (tools.N: Invalid tool name) and aborts
// the entire agent load (#1394).
if (
claudeTool === 'Task' ||
claudeTool === 'Agent' ||
claudeTool === 'AskUserQuestion' ||
claudeTool === 'ask_user'
claudeTool === 'ask_user' ||
claudeTool === 'Skill' ||
claudeTool === 'SlashCommand'
) {
return null;
}
@@ -6689,24 +6667,10 @@ function migrateLegacyDevPreferencesToSkill(targetDir, saved, runtime, scope = '
* @param {string} pathPrefix e.g. "~/.codex/" — trailing-slash string
* @param {boolean} [isGlobal=false] true when the install is a global (home-dir) install
*/
function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal = false) {
if (!fs.existsSync(stagedDir)) return;
// Walk all SKILL.md files under stagedDir
const walkAndRewrite = (dir) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkAndRewrite(fullPath);
} else if (entry.name.endsWith('.md')) {
let content = fs.readFileSync(fullPath, 'utf8');
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal);
fs.writeFileSync(fullPath, content);
}
}
};
walkAndRewrite(stagedDir);
}
// applyRuntimeContentRewritesInPlace: walk loop is now owned by
// runtimeArtifactConversion.applyRuntimeContentRewritesInPlace (ADR-1508 / #1511 Phase 2).
// The const binding above (~line 629) delegates here. Call sites in installRuntimeArtifacts
// pass attribution as the 5th arg (getCommitAttribution(runtime)) per the new contract.
/**
* Apply per-runtime content rewrites to flat .md files in a staged commands dir.
@@ -6724,30 +6688,10 @@ function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGl
* @param {boolean} [isGlobal=false] true when the install is a global (home-dir) install
* @returns {string} path to a temp dir with rewritten files (caller is responsible for cleanup)
*/
function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal = false) {
if (!fs.existsSync(stagedDir)) return stagedDir;
// Always copy to a temp dir — stageSkillsForProfile() returns the original source
// dir on full/default profile (skills === '*'), so writing in-place would corrupt the
// package source. A temp copy is unconditional to keep the code simple and safe.
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-cmd-rewrites-'));
try {
for (const entry of fs.readdirSync(stagedDir, { withFileTypes: true })) {
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
let content = fs.readFileSync(path.join(stagedDir, entry.name), 'utf8');
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal);
// For augment commands, apply the markdown conversion so tool references
// and skill paths use Augment equivalents.
if (runtime === 'augment') {
content = convertClaudeToAugmentMarkdown(content);
}
fs.writeFileSync(path.join(tempDir, entry.name), content);
}
} catch (err) {
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { /* best-effort */ }
throw err;
}
return tempDir;
}
// applyRuntimeContentRewritesForCommandsInPlace: copy+rewrite loop is now owned by
// runtimeArtifactConversion.applyRuntimeContentRewritesForCommandsInPlace (ADR-1508 / #1511 Phase 2).
// The const binding above (~line 630) delegates here. Call sites in installRuntimeArtifacts
// pass attribution as the 5th arg (getCommitAttribution(runtime)) per the new contract.
/**
* Apply the per-runtime rewrite table to a single content string.
@@ -6759,196 +6703,12 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
* @param {boolean} [isGlobal=false] true when the install is a global (home-dir) install
* @returns {string}
*/
function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false) {
const dirName = getDirName(runtime);
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
switch (runtime) {
case 'codex':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.codex\//g, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'cline':
// Slash forms: both the original ~/.claude/ (safety net) and the stage-time
// converted ~/.cline/ (from convertClaudeToCliineMarkdown) → pathPrefix
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.cline\//g, pathPrefix);
content = content.replace(/\$HOME\/\.cline\//g, pathPrefix);
// Bare forms (no trailing slash)
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/~\/\.cline\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.cline\b/g, normalizedPathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'cursor':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
// Bare forms (no trailing slash) — use (?![\w-]) instead of \b so that
// .claude-plugin / .claudeignore are NOT corrupted (the \b word-boundary
// fires between 'e' and '-', which rewrites .claude-plugin → .cursor-plugin).
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
content = content.replace(/~\/\.cursor\//g, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'windsurf': {
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
// Bare forms (no trailing slash) — use (?![\w-]) instead of \b so that
// .claude-plugin / .claudeignore are NOT corrupted (the \b word-boundary
// fires between 'e' and '-', which rewrites .claude-plugin → .devin-plugin).
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/~\/\.codeium\/windsurf\//g, pathPrefix);
// Stage-1 converter rewrites .claude/skills/ → .devin/skills/ (workspace-relative
// form). For global installs the real path is pathPrefix + skills/, so fix that up
// here using the real isGlobal flag (threaded from installRuntimeArtifacts scope,
// not derived from pathPrefix substring which misclassifies custom config dirs).
// For local installs, the relative .devin/ form is correct — leave it. (#1085)
if (isGlobal) {
content = content.replace(/\.devin\/skills\//g, `${pathPrefix}skills/`);
content = content.replace(/\.\/\.devin\//g, pathPrefix);
content = content.replace(/~\/\.devin(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.devin(?![\w-])/g, normalizedPathPrefix);
}
content = processAttribution(content, getCommitAttribution(runtime));
break;
}
case 'augment':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
content = content.replace(/~\/\.augment\//g, pathPrefix);
content = content.replace(/\$HOME\/\.augment\//g, pathPrefix);
content = content.replace(/~\/\.augment(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.augment(?![\w-])/g, normalizedPathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'trae':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
content = content.replace(/~\/\.trae\//g, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'codebuddy':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
// The codebuddy converter rewrites `.claude/` → `.codebuddy/` at stage
// time, so `$HOME/.claude/...` arrives here as `$HOME/.codebuddy/...`.
// Normalize BOTH the `~/` and `$HOME/` forms (slash + bare) to the install
// target so `--config-dir`/local installs don't leak the default home.
content = content.replace(/~\/\.codebuddy\//g, pathPrefix);
content = content.replace(/\$HOME\/\.codebuddy\//g, pathPrefix);
content = content.replace(/~\/\.codebuddy\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.codebuddy\b/g, normalizedPathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'copilot':
// Copilot converter handles path rewrites; only attribution here
content = processAttribution(content, getCommitAttribution('copilot'));
break;
case 'antigravity':
// Antigravity converter handles path rewrites; only attribution here
content = processAttribution(content, getCommitAttribution('antigravity'));
break;
case 'claude':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'qwen':
// Branding rewrites run before path rewrites to avoid consuming
// patterns that the path step would also match.
content = content.replace(/CLAUDE\.md/g, 'QWEN.md');
content = content.replace(/\bClaude Code\b/g, 'Qwen Code');
// Base path rewrites (use ~/ and $HOME/ slash forms first — most specific)
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/~\/\.qwen\//g, pathPrefix);
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/~\/\.qwen(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.qwen(?![\w-])/g, normalizedPathPrefix);
// Bare relative .claude/ → .qwen/ (residual refs not matched above)
content = content.replace(/\.claude\//g, '.qwen/');
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/\.\/\.qwen\//g, `./${dirName}/`);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'hermes':
// Branding rewrites run before path rewrites (same rationale as qwen)
content = content.replace(/CLAUDE\.md/g, 'HERMES.md');
content = content.replace(/\bClaude Code\b/g, 'Hermes Agent');
// Base path rewrites
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/~\/\.hermes\//g, pathPrefix);
content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/~\/\.hermes(?![\w-])/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.hermes(?![\w-])/g, normalizedPathPrefix);
// Bare relative .claude/ → .hermes/ (residual refs)
content = content.replace(/\.claude\//g, '.hermes/');
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
content = processAttribution(content, getCommitAttribution(runtime));
break;
case 'kimi':
content = content.replace(/~\/\.claude\//g, pathPrefix);
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
content = processAttribution(content, getCommitAttribution(runtime));
break;
default:
// Unknown runtime — no rewrites.
// OpenCode/Kilo are intentionally absent: their skills are written by
// installOpencodeFamilySkills, which applies pathPrefix BEFORE the
// command→skill conversion (mirroring copyFlattenedCommands) rather than
// rewriting already-converted SKILL.md bodies. See #784.
break;
}
return content;
}
// _applyRuntimeRewrites: single implementation lives in runtimeArtifactConversion
// (ADR-1508 / #1511 Phase 2). Bound here so install.js call sites and exports are
// reference-identical to the conversion module (consistent with the walkers above).
// All call sites are below this line → no TDZ hazard.
const _applyRuntimeRewrites = runtimeArtifactConversion._applyRuntimeRewrites;
const _stampNonClaudeRuntimeDefaults = runtimeArtifactConversion._stampNonClaudeRuntimeDefaults;
/**
* Copy a staged directory's contents into destDir.
@@ -7258,36 +7018,25 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
_runLegacyInstallMigrations(runtime, configDir, scope);
const layout = resolveRuntimeArtifactLayout(runtime, configDir, scope);
// Compute pathPrefix once for the rewrite step (same derivation as the
// top-level install() function).
const _resolvedTarget = path.resolve(configDir).replace(/\\/g, '/');
const _homeDir = os.homedir().replace(/\\/g, '/');
const pathPrefix = computePathPrefix({
isGlobal: scope === 'global',
isOpencode: runtime === 'opencode',
isWindowsHost: process.platform === 'win32',
resolvedTarget: _resolvedTarget,
homeDir: _homeDir,
const planResult = createRuntimeArtifactInstallPlan({
layout,
resolvedProfile,
homedir: () => os.homedir(),
platform: process.platform,
resolveAttribution: getCommitAttribution,
});
for (const kind of layout.kinds) {
const staged = kind.stage(resolvedProfile);
// stagedForCopy: the directory to copy from (may differ from staged if rewrites
// produce a temp copy — see applyRuntimeContentRewritesForCommandsInPlace).
let stagedForCopy = staged;
const isGlobal = scope === 'global';
if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
applyRuntimeContentRewritesInPlace(staged, runtime, pathPrefix, isGlobal);
} else if (kind.kind === 'commands') {
// Returns a temp dir with rewritten content so source files are never mutated.
stagedForCopy = applyRuntimeContentRewritesForCommandsInPlace(staged, runtime, pathPrefix, isGlobal);
const cleanupDirs = planResult.ok ? planResult.plan.cleanupDirs : planResult.cleanupDirs;
try {
if (!planResult.ok) {
throw new Error(planResult.message);
}
// applyRuntimeContentRewritesForCommandsInPlace() returns a fresh mkdtemp dir under
// os.tmpdir() (gsd-cmd-rewrites-*); remove it once copied so it does not accumulate (#856).
const tempToClean = stagedForCopy !== staged ? stagedForCopy : null;
try {
const dest = path.join(layout.configDir, kind.destSubpath);
const kindsByName = new Map(layout.kinds.map((kind) => [kind.kind, kind]));
for (const item of planResult.plan.items) {
const kind = kindsByName.get(item.kind);
if (!kind) throw new Error(`Install plan returned unknown artifact kind: ${item.kind}`);
const dest = item.destDir;
fs.mkdirSync(dest, { recursive: true });
if (kind.kind === 'skills' && fs.existsSync(dest)) {
// Pre-prune: snapshot user-owned content before _removeGsdEntries wipes it,
@@ -7314,7 +7063,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
}
_removeGsdEntries(dest, kind);
_copyStaged(stagedForCopy, dest, kind);
_copyStaged(item.sourceDir, dest, kind);
// Restore user-owned dirs after the prune+copy
for (const [dirName, snap] of toPreserve) {
@@ -7324,13 +7073,13 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile) {
// For non-skills kinds (commands, agents): no user content to preserve;
// just prune stale gsd-* entries and copy new ones.
_removeGsdEntries(dest, kind);
_copyStaged(stagedForCopy, dest, kind);
}
} finally {
if (tempToClean) {
try { fs.rmSync(tempToClean, { recursive: true, force: true }); } catch { /* best-effort */ }
_copyStaged(item.sourceDir, dest, kind);
}
}
} finally {
for (const dir of cleanupDirs) {
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort */ }
}
}
// Hermes: after the install loop has written all gsd-<stem>/ dirs to
@@ -7447,9 +7196,14 @@ function uninstallRuntimeArtifacts(runtime, configDir, scope) {
const savedLegacyArtifacts = _runLegacyUninstallCleanup(runtime, configDir, scope);
const layout = resolveRuntimeArtifactLayout(runtime, configDir, scope);
for (const kind of layout.kinds) {
const dest = path.join(layout.configDir, kind.destSubpath);
_removeGsdEntries(dest, kind);
const plan = createRuntimeArtifactUninstallPlan(layout);
const kindsByName = new Map(layout.kinds.map((kind) => [kind.kind, kind]));
for (const item of plan.items) {
const kind = kindsByName.get(item.kind);
if (!kind) {
throw new Error(`Runtime artifact uninstall plan referenced unknown kind: ${item.kind}`);
}
_removeGsdEntries(item.destDir, kind);
}
// Hermes: after removing gsd-* skill dirs from skills/gsd/, also remove
@@ -7540,6 +7294,15 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
}
content = processAttribution(content, getCommitAttribution(runtime));
// #1521: stamp the workflow runtime-resolution block so every non-Claude
// install resolves its own runtime identity and defaults use_worktrees=false.
// copyWithPathReplacement is the emit path for gsd-core/workflows/*.md;
// _applyRuntimeRewrites is NOT invoked here, so this is what makes the fix
// live in real installs (it is a no-op for files without those lines).
if (runtime !== 'claude') {
content = _stampNonClaudeRuntimeDefaults(content, runtime);
}
// #3683 — normalize /gsd:<cmd> → /gsd-<cmd> in any body passing through
// copyWithPathReplacement for runtimes that register commands under the
// hyphen form; normalizeAgentBodyForRuntime self-gates on
@@ -10044,7 +9807,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
const stem = entry.name.slice(0, -3);
let content = fs.readFileSync(path.join(gsdSrc, entry.name), 'utf8');
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal);
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, getCommitAttribution(runtime));
content = normalizeAgentBodyForRuntime(content, runtime, cmdNames);
fs.writeFileSync(path.join(commandsDir, `gsd-${stem}.md`), content);
cmdCount++;
@@ -12315,7 +12078,10 @@ module.exports = {
// #1191 — exported so tests exercise the REAL readSettings, not a replica
readSettings,
stripJsonComments,
...runtimeArtifactConversion,
// Compatibility relays retained after auditing the former broad
// runtimeArtifactConversion spread (#1559).
processAttribution,
applyRuntimeContentRewritesForCommandsInPlace,
};
// Main logic — only run when not loaded as a module for testing

View File

@@ -1,7 +1,7 @@
{
"id": "ai-integration",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "antigravity",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Antigravity",
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; nested skill layout; tier-1 support.",
"tier": "core",

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "augment",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "claude",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Claude Code",
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
{
"id": "cline",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "code-review",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Code review",
"description": "Source-file code review and review-fix workflow support for completed execution work.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "codebuddy",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
{
"id": "codex",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "copilot",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "cursor",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Cursor",
"description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
{
"id": "drift",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Drift detection gates",
"description": "Post-execution drift detection gates that run after each wave completes. Provides two gates 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).",
"tier": "full",

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "gemini",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Gemini CLI",
"description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.",
"tier": "core",

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "hermes",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "kilo",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

@@ -1,7 +1,7 @@
{
"id": "kimi",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Kimi CLI",
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
{
"id": "mempalace",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "opencode",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "qwen",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Qwen Code",
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"id": "trae",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
{
"id": "ui",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "UI design contracts",
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
"tier": "full",

View File

@@ -1,7 +1,7 @@
{
"id": "windsurf",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Windsurf",
"description": "Windsurf (Codeium) — nested under ~/.codeium/windsurf; skills-only artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",

View File

@@ -1,7 +1,7 @@
---
name: gsd:capture
description: Capture ideas, tasks, notes, and seeds to their destination
argument-hint: "[--note | --backlog | --seed | --list] [text]"
argument-hint: "[--note | --backlog | --seed | --list | --list-seeds] [text]"
allowed-tools:
- Read
- Write
@@ -21,6 +21,7 @@ Mode routing:
- **--backlog**: Add an idea to the backlog parking lot (999.x numbering) → add-backlog workflow
- **--seed**: Capture a forward-looking idea with trigger conditions → plant-seed workflow
- **--list**: List pending todos and select one to work on → check-todos workflow
- **--list-seeds**: List/audit captured seeds (optional status filter) → list-seeds workflow
</objective>
<routing>
@@ -32,6 +33,7 @@ Mode routing:
| --backlog | ROADMAP.md backlog section (999.x) | add-backlog |
| --seed | .planning/seeds/SEED-NNN-slug.md | plant-seed |
| --list | Interactive todo browser + action router | check-todos |
| --list-seeds | Read-only seed list/audit (optional status filter) | list-seeds |
</routing>
@@ -41,6 +43,7 @@ Mode routing:
@~/.claude/gsd-core/workflows/add-backlog.md
@~/.claude/gsd-core/workflows/plant-seed.md
@~/.claude/gsd-core/workflows/check-todos.md
@~/.claude/gsd-core/workflows/list-seeds.md
@~/.claude/gsd-core/references/ui-brand.md
</execution_context>
@@ -51,6 +54,7 @@ Parse the first token of $ARGUMENTS:
- If it is `--note`: strip the flag, pass remainder to note workflow
- If it is `--backlog`: strip the flag, pass remainder to add-backlog workflow
- If it is `--seed`: strip the flag, pass remainder to plant-seed workflow
- If it is `--list-seeds`: strip the flag, pass remainder (optional status filter) to list-seeds workflow
- If it is `--list`: pass remainder (optional area filter) to check-todos workflow
- Otherwise: pass all of $ARGUMENTS to add-todo workflow
</context>

View File

@@ -477,6 +477,9 @@ node gsd-tools.cjs current-timestamp [full|date|filename]
# Count and list pending todos
node gsd-tools.cjs list-todos [area]
# List captured seeds (optionally filter by status: dormant|active|triggered)
node gsd-tools.cjs list-seeds [status]
# Check file/directory existence
node gsd-tools.cjs verify-path-exists <path>
@@ -546,6 +549,20 @@ node gsd-tools.cjs worktree set-baseref
**`worktree set-baseref`** applies a no-clobber write of `worktree.baseRef:"head"` to `.claude/settings.local.json`. If the file already contains an explicit `baseRef` value other than `"head"`, the existing value is preserved and `skipped:"explicit-other"` is returned. Malformed JSON causes an error rather than a silent overwrite. Both fresh installs and upgrades of GSD Core run this automatically when `workflow.use_worktrees` is enabled (the default); the command is also available for manual use — for example, to apply the setting when worktrees were toggled on after installation, or to re-apply it after a settings change.
### Wave-manifest recording
The execute-phase orchestrator records each spawned executor's worktree identity into a wave cleanup manifest so the matching `cleanup-wave` reader can later merge and remove exactly those worktrees.
```bash
# Append a validated per-agent entry to the wave cleanup manifest.
# Returns JSON: { ok, reason, entry, manifest_path } (exit 0), or
# { ok:false, reason, hint } with a non-zero exit on a rejected entry.
node gsd-tools.cjs worktree record-agent \
--manifest <path> --agent-id <id> --path <worktree> --branch <branch> --base <sha>
```
**`worktree record-agent`** appends one `{agent_id, worktree_path, branch, expected_base}` entry to an already-initialized manifest, validating every field **at write time using the same rules the `cleanup-wave` reader enforces** — `--branch` must match the disposable `^worktree-agent-[A-Za-z0-9._/-]+$` namespace, and `--path`/`--branch`/`--base` must be non-empty. `--agent-id` is required (write-strict), even though the reader treats it as optional. A missing or garbled field — or a duplicate `(worktree_path, branch)` the reader would dedup away — fails loudly with a recovery hint and a non-zero exit **without** writing, instead of appending an under-populated or silently-dropped entry. Whitespace-only `--path`/`--base` are rejected (values are trimmed). The on-disk manifest shape is unchanged (the reader re-derives `allowed_bases`); the orchestrator still initializes the empty `{orchestrator_root, worktrees: []}` shell inline before any agent is recorded.
---
## Graphify

View File

@@ -1370,6 +1370,8 @@ Execute a trivial task inline — no subagents, no planning overhead. For typo f
Cross-AI peer review of phase plans from external AI CLIs.
Reviewers are prompted to verify the plan's claims against the actual repository source — opening the referenced files and citing `file:line` evidence with the mechanism — rather than reviewing the plan text in isolation. A reviewer that has no file access flags what it cannot verify instead of asserting it, and `file:line`-grounded findings are weighted more heavily during consensus synthesis.
| Argument | Required | Description |
|----------|----------|-------------|
| `--phase N` | **Yes** | Phase number to review |
@@ -1485,10 +1487,11 @@ Capture ideas, tasks, notes, and seeds to their appropriate destination. Default
| `--backlog <description>` | Add to the backlog parking lot using 999.x numbering |
| `--seed [idea summary]` | Capture a forward-looking idea with trigger conditions |
| `--list` | List pending todos and select one to work on |
| `--list-seeds [status]` | List/audit captured seeds, optionally filtered by status (read-only) |
| `--global` | Use global scope (for note operations) |
**Backlog:** 999.x numbering keeps items outside the active phase sequence; phase directories are created immediately so `/gsd-discuss-phase` and `/gsd-plan-phase` work on them.
**Seeds:** Preserve full WHY, WHEN to surface, and breadcrumbs — consumed by `/gsd-new-milestone`.
**Seeds:** Preserve full WHY, WHEN to surface, and breadcrumbs — consumed by `/gsd-new-milestone`. Audit parked seeds anytime with `--list-seeds` (optionally `--list-seeds dormant`).
**Produces:** `.planning/todos/` (default), note files (--note), ROADMAP.md backlog section (--backlog), `.planning/seeds/SEED-NNN-slug.md` (--seed)
@@ -1500,6 +1503,8 @@ Capture ideas, tasks, notes, and seeds to their appropriate destination. Default
/gsd-capture --backlog "GraphQL API layer" # Add to backlog
/gsd-capture --seed "Add real-time collaboration when WebSocket infra is in place"
/gsd-capture --list # Browse and act on todos
/gsd-capture --list-seeds # Audit all captured seeds
/gsd-capture --list-seeds dormant # Filter seeds by status
```
---

View File

@@ -248,7 +248,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
| `workflow.max_discuss_passes` | number | `3` | Maximum number of question rounds in discuss-phase before the workflow stops asking. Useful in headless/auto mode to prevent infinite discussion loops. |
| `workflow.skip_discuss` | boolean | `false` | When `true`, `/gsd-autonomous` bypasses the discuss-phase entirely, writing minimal CONTEXT.md from the ROADMAP phase goal. Useful for projects where developer preferences are fully captured in PROJECT.md/REQUIREMENTS.md. Added in v1.28 |
| `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 |
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. |
| `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. **Non-Claude note:** git worktree isolation uses Claude Code's `isolation="worktree"` agent primitive, which no other runtime honors. On any non-Claude install (Codex, Cursor, Gemini, Qwen, etc.) a runtime-neutral `.planning/config.json` resolves the runtime to that install's own id and defaults this key to `false`; forcing `use_worktrees: true` on a non-Claude install fails closed before any executor dispatch (#1515, #1521). |
| `workflow.worktree_skip_hooks` | boolean | `false` | When `true`, executor agents in worktree mode pass `--no-verify` (skipping pre-commit hooks) and post-wave hook validation runs against the merged result instead. Opt-in escape hatch for projects whose hooks cannot run in agent worktrees. Default `false` runs hooks on every commit (#2924). |
| `workflow.code_review` | boolean | `true` | Enable `/gsd-code-review` and `/gsd-code-review --fix` commands. When `false`, the commands exit with a configuration gate message. Added in v1.34 |
| `workflow.code_review_depth` | string | `standard` | Default review depth for `/gsd-code-review`: `quick` (pattern-matching only), `standard` (per-file analysis), or `deep` (cross-file with import graphs). Can be overridden per-run with `--depth=`. Added in v1.34 |
@@ -262,6 +262,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin
| `workflow.tdd_mode` | boolean | `false` | Enable TDD pipeline as a first-class execution mode. When `true`, the planner aggressively applies `type: tdd` to eligible tasks (business logic, APIs, validations, algorithms) and the executor enforces RED/GREEN/REFACTOR gate sequence. An end-of-phase collaborative review checkpoint verifies gate compliance. Added in v1.36 |
| `workflow.mvp_mode` | boolean | `false` | Persist the MVP-mode flag in config so every phase defaults to MVP framing without requiring `--mvp` on the CLI. Resolved via the precedence chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → this config value → `false`. When `true`, the planner, executor, verifier, and discovery surfaces treat the phase as an MVP vertical slice (UI → API → DB) of one user-visible capability instead of a horizontal layer. |
| `workflow.human_verify_mode` | string | `'end-of-phase'` | Controls human verification checkpoints. `'end-of-phase'` (default since #3309) suppresses `checkpoint:human-verify` tasks and embeds checks into `<verify><human-check>` blocks for end-of-phase review. `'mid-flight'` restores blocking checkpoint tasks. `checkpoint:decision` and `checkpoint:human-action` are unaffected. See [Checkpoints Reference](../gsd-core/references/checkpoints.md#checkpoint_types). |
| `workflow.context_guard_mode` | string | `'warn'` | Context exhaustion guard for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using the degradation signals defined in `context-budget.md`. `'warn'` (default) emits a warning and recommends `/gsd:pause-work` when POOR tier (70%+) is detected. `'auto'` automatically invokes `/gsd:pause-work` before the next wave. `'off'` disables the guard. Set via: `gsd config-set workflow.context_guard_mode auto`. Added in #1452. |
| `workflow.cross_ai_execution` | boolean | `false` | Delegate phase execution to an external AI CLI instead of spawning local executor agents. Useful for leveraging a different model's strengths for specific phases. Added in v1.36 |
| `workflow.cross_ai_command` | string | (none) | Shell command template for cross-AI execution. Receives the phase prompt via stdin. Must produce SUMMARY.md-compatible output. Required when `cross_ai_execution` is `true`. Added in v1.36 |
| `workflow.cross_ai_timeout` | number | `300` | Timeout in seconds for cross-AI execution commands. Prevents runaway external processes. Added in v1.36 |

View File

@@ -1230,9 +1230,9 @@ When verification returns `human_needed`, items are persisted as a trackable HUM
### 43. Backlog Parking Lot
**Commands:** `/gsd-capture --backlog <description>`, `/gsd-review-backlog`, `/gsd-capture --seed <idea>`
**Commands:** `/gsd-capture --backlog <description>`, `/gsd-review-backlog`, `/gsd-capture --seed <idea>`, `/gsd-capture --list-seeds [status]`
**Purpose:** Capture ideas that aren't ready for active planning. Backlog items use 999.x numbering to stay outside the active phase sequence. Seeds are forward-looking ideas with trigger conditions that surface automatically at the right milestone.
**Purpose:** Capture ideas that aren't ready for active planning. Backlog items use 999.x numbering to stay outside the active phase sequence. Seeds are forward-looking ideas with trigger conditions that surface automatically at the right milestone. `--list-seeds` provides a read-only audit of all parked seeds (with optional status filter) without waiting for the next milestone.
**Requirements:**
- REQ-BACKLOG-01: Backlog items MUST use 999.x numbering to stay outside active phase sequence
@@ -1241,6 +1241,7 @@ When verification returns `human_needed`, items are persisted as a trackable HUM
- REQ-BACKLOG-04: Promoted items MUST be renumbered into the active milestone sequence
- REQ-SEED-01: Seeds MUST capture the full WHY and WHEN to surface conditions
- REQ-SEED-02: `/gsd-new-milestone` MUST scan seeds and present matches
- REQ-SEED-03: `/gsd-capture --list-seeds` MUST list seeds with status, scope, and trigger for audit, with optional status filtering
**Produces:**
| Artifact | Description |

View File

@@ -147,6 +147,7 @@
"ingest-docs.md",
"insert-phase.md",
"list-phase-assumptions.md",
"list-seeds.md",
"list-workspaces.md",
"manager.md",
"map-codebase.md",
@@ -214,6 +215,7 @@
"edge-probe.md",
"execute-mvp-tdd.md",
"execute-phase-between-wave-reset.md",
"execute-phase-context-guard.md",
"execute-phase-wave-guard.md",
"executor-examples.md",
"gate-prompts.md",
@@ -367,6 +369,7 @@
"roadmap-upgrade.cjs",
"roadmap.cjs",
"runtime-artifact-conversion.cjs",
"runtime-artifact-install-plan.cjs",
"runtime-artifact-layout.cjs",
"runtime-config-adapter-registry.cjs",
"runtime-homes.cjs",

View File

@@ -215,6 +215,7 @@ Full roster at `gsd-core/workflows/*.md`. Workflows are thin orchestrators that
| `ingest-docs.md` | Scan a repo for mixed planning docs; classify, synthesize, and bootstrap or merge into `.planning/` with a conflicts report. | `/gsd-ingest-docs` |
| `insert-phase.md` | Insert a decimal phase for urgent work discovered mid-milestone. | `/gsd-phase --insert` |
| `list-phase-assumptions.md` | Surface Claude's assumptions about a phase before planning. | `/gsd-discuss-phase --assumptions` |
| `list-seeds.md` | List and audit captured seeds (read-only), with optional status filter. | `/gsd-capture --list-seeds` |
| `list-workspaces.md` | List all GSD workspaces found in `~/gsd-workspaces/` with their status. | `/gsd-workspace --list` |
| `manager.md` | Interactive milestone command center — dashboard, inline discuss, background plan/execute. | `/gsd-manager` |
| `map-codebase.md` | Orchestrate parallel codebase mapper agents to produce `.planning/codebase/` docs. | `/gsd-map-codebase` |
@@ -301,6 +302,7 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum
|-----------|------|
| `agent-contracts.md` | Formal interface between orchestrators and agents. |
| `context-budget.md` | Context window budget allocation rules. |
| `execute-phase-context-guard.md` | Context exhaustion guard step for `execute-phase` wave loop — `workflow.context_guard_mode` dispatch table (warn/auto/off) and POOR-tier pause-work trigger (#1452). |
| `continuation-format.md` | Session continuation/resume format. |
| `domain-probes.md` | Domain-specific probing questions for discuss-phase. |
| `edge-probe.md` | Spec-phase edge-completeness probe — 8-category edge taxonomy, shape classification, and the `requirements → checks → verifier` resolution model (Step 5.5). |
@@ -475,6 +477,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `roadmap-upgrade.cjs` | Migration tool for converting legacy `Phase N` entries to milestone-prefixed `Phase M-NN` convention; `computeMigrationPlan` + `applyMigration` with dry-run default and atomic rollback |
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
| `runtime-artifact-conversion.cjs` | Runtime artifact conversion module — projects Claude-authored commands, agents, and skills into runtime-specific artifact bodies while preserving installer compatibility exports |
| `runtime-artifact-install-plan.cjs` | Runtime artifact install plan module — stages pre-resolved layout kinds, applies runtime body rewrites, and returns copy-plan items plus cleanup obligations |
| `runtime-artifact-layout.cjs` | Runtime artifact layout module — resolves the artifact directory shapes (commands, agents, skills) for each supported runtime; single source of truth for per-runtime artifact placement (#3663) |
| `runtime-config-adapter-registry.cjs` | Explicit runtime config adapter registry — resolves per-runtime config-mutation install intent (install surface, shared-settings gate, finish-phase permission writer); see ADR-58. |
| `runtime-hooks-surface.cjs` | Runtime hooks surface module — standalone hook-surface writer functions extracted from bin/install.js (ADR-857 phase 5f-1); owns Cline/Cursor/Copilot/Codex hook artifact generation and reconciliation. |

View File

@@ -334,6 +334,15 @@ Seeds are forward-looking ideas with trigger conditions. Unlike backlog items, s
`/gsd-new-milestone` scans all seeds and presents matches. **Storage:** `.planning/seeds/SEED-NNN-slug.md`
Once you've parked a few, audit them on demand instead of waiting for the next milestone to surface them:
```bash
/gsd-capture --list-seeds # Review every parked seed
/gsd-capture --list-seeds dormant # Narrow to one status
```
This is read-only — it renders an audit table (ID, status, scope, trigger, title) and a per-status summary, and never modifies a seed. Filter by `dormant`, `active`, or `triggered` when you only want to see seeds in one state.
### Persistent Context Threads
Threads are lightweight cross-session knowledge stores for work that spans multiple sessions but doesn't belong to any specific phase.

View File

@@ -1,6 +1,6 @@
# ADR-1016: Runtime Capability Descriptor
- **Status:** Proposed
- **Status:** Accepted
- **Date:** 2026-06-10
- **Issue:** [#1016](https://github.com/open-gsd/gsd-core/issues/1016)
- **Epic:** [#857](https://github.com/open-gsd/gsd-core/issues/857) (Capability system) — rollout phase 5

View File

@@ -1,6 +1,6 @@
# ADR-1235: Migrate agent conversion to the descriptor-driven install path
- **Status:** Proposed
- **Status:** Accepted
- **Date:** 2026-06-14
- **Issue:** #1235
- **Builds on:** [ADR-3660](3660-runtime-artifact-layout-module.md) (runtime artifact layout), [ADR-457](457-generated-cjs-single-source.md) (the `src/*.cts` build-at-publish tree the converters live in), [ADR-1016](1016-runtime-capability-descriptor.md) (runtime capability descriptor)

View File

@@ -0,0 +1,79 @@
# Runtime Artifact Conversion Module owns per-runtime content rewriting
- **Status:** Accepted
- **Date:** 2026-06-20
- **Issue:** #1508
- **Epic:** #1507
- **Implementation:** Phase 1 (helper relocation, no behavior change) → Phase 2 (engine move + relay deletion)
The **Runtime Surface Module** (`src/surface.cts` → `surface.cjs`) re-materializes a resolved skill surface to disk via `applySurface`. For `skills` kinds it must rewrite staged `SKILL.md` bodies so their `@`-ref paths point at the install target (`pathPrefix`) instead of the converter's default `~/.claude` paths (#813). To do that it reaches **up** into the 12,289-line hand-authored `bin/install.js` via `getInstallExports()` (`src/runtime-artifact-layout.cts:53-69`) — a lazy `require('../../../bin/install.js')` guarded by a save/set/restore of `GSD_TEST_MODE` — to borrow `computePathPrefix` and `applyRuntimeContentRewritesInPlace`.
This is the **last upward dependency from the `.cts` source tree into the hand-authored installer**. It forces an env-var dance at a test seam, and it leaks: `applySurface` (and `bin/install.js`'s own three call sites) each re-derive the same five path-prefix inputs (`scope→isGlobal`, `runtime==='opencode'`, `process.platform`, normalized `resolvedTarget`, normalized `homeDir`) before calling `computePathPrefix`. The prefix-derivation knowledge is duplicated across `surface.cts` and `install.js`.
`CONTEXT.md` already names the **Runtime Artifact Conversion Module** (`src/runtime-artifact-conversion.cts`) as the `[Planned]` sibling of the Layout Module — placement vs. content. ADR-3660 *§Initial Scope* deferred exactly this consolidation: *"A future ADR may consolidate them into a Skill Conversion Module if a second consumer emerges."* `surface.cts` is that second consumer. This is that future ADR.
## Decision
- Promote the `[Planned]` **Runtime Artifact Conversion Module** (`src/runtime-artifact-conversion.cts`) to the single owner of per-runtime **content rewriting**: the per-runtime converters (already relocated as ADR-3660's "first slice", #1099), **plus** the rewrite engine `_applyRuntimeRewrites`, the staged-content walkers, path-prefix derivation, and commit attribution. The **Runtime Artifact Layout Module** keeps owning **placement** only. **Exception:** opencode and kilo path-prefix rewriting remains a deliberate `bin/install.js`-owned pre-conversion step (see `applyOpencodeFamilyPathPrefix`); this is intentional per #784 and is not a violation of the single-owner rule.
- **Public seam** — two deep calls; the caller passes only what it has, the module derives the rest:
- `rewriteStagedSkillBodies(stagedDir, { runtime, configDir, scope }, env?)` — in-place walk (skills / kimi-agents).
- `rewriteStagedCommandBodies(stagedDir, { runtime, configDir, scope }, env?) → tempDir` — copy-to-temp (commands).
- The module internally derives `isGlobal`/`isOpencode`/`isWindowsHost`/`resolvedTarget`/`homeDir` and the path prefix. `env = { homedir = os.homedir, platform = process.platform } = {}` is an injected test seam (the clock-seam analog, `RULESET.TESTS.clock-seam`).
- `computePathPrefix` becomes **private** to the module, exported as `_computePathPrefix` for direct unit + `fast-check` property tests (`RULESET.TESTS.property-based-testing`). The hand-reimplemented copy in `tests/path-replacement.test.cjs` is deleted so the **real** function is what's tested (it is effectively untested today).
- **Dependency direction:** `bin/install.js` and `runtime-artifact-layout.cts` import the conversion module; the conversion module imports **nothing upward** (not `install.js`, not `layout`) — only deeper leaves.
- `getDirName(runtime)` relocates to `src/runtime-name-policy.cts` (a clean `fs`/`path`-only leaf), so the conversion module can consume it **without** dragging in `capability-registry.cjs` (which `runtime-homes.cjs` requires). `processAttribution` / `getCommitAttribution` move **into** the conversion module (attribution is content transformation).
- The duplicate `convertClaudeToAugmentMarkdown` (verified **byte-identical** in `install.js:2584` and `conversion.cts:976`) collapses to the conversion-module copy; `install.js`'s local copy is deleted (it already re-exports `...runtimeArtifactConversion`).
- `getInstallExports` / `loadInstallExports` / the `InstallExports` interface **and the `GSD_TEST_MODE` require of `bin/install.js`** are deleted from `runtime-artifact-layout.cts`. `surface.cts` (the sole consumer) calls the conversion module's deep functions directly — removing the last upward `.cts → install.js` dependency.
## Initial Scope
### Phase 1 — helper relocation (no behavior change)
1. Move `getDirName` → `runtime-name-policy.cts`; re-point its 13 `install.js` call sites.
2. Move `processAttribution` + `getCommitAttribution` → `conversion.cts`; re-point their 21 `install.js` call sites.
3. Delete `install.js`'s local `convertClaudeToAugmentMarkdown` (copies confirmed byte-identical); rely on the conversion-module copy via the existing `...runtimeArtifactConversion` export spread. Add a **characterization test** snapshotting current augment skills-rewrite output as insurance — it should pass unchanged.
4. No public-interface change; `install.js` and `surface.cts` behavior unchanged.
### Phase 2 — engine move + deepen + delete relay
1. Move `_applyRuntimeRewrites`, `applyRuntimeContentRewritesInPlace`, `applyRuntimeContentRewritesForCommandsInPlace`, and `computePathPrefix` into `conversion.cts`.
2. Expose `rewriteStagedSkillBodies` / `rewriteStagedCommandBodies`; privatize `computePathPrefix` (`_computePathPrefix` for tests).
3. `surface.cts:applySurface` and `install.js`'s three internal sites (`7261`/`7276`, `9475`) call the deep functions; delete the per-site prefix derivation.
4. Delete `getInstallExports` / `loadInstallExports` / `InstallExports` + the `GSD_TEST_MODE` `bin/install.js` require from `runtime-artifact-layout.cts`.
5. Tests: `fast-check` property test for the rewrite engine (`$HOME`-collapse invariant; path-rewrite idempotency), direct `_computePathPrefix` unit tests, delete the `path-replacement.test.cjs` reimplementation, and a `DEFECT.GENERATIVE-FIX` parity guard ensuring no second converter copy reappears.
### These phases should NOT
- Bundle ADR-3660 **Phase 2** (install/uninstall `layout.kinds` loop collapse, ~250 lines, separate issue #3664).
- Relocate `getConfigDirFromHome` or other general install helpers the rewrite engine does not need.
## Migration Inventory
### New files
- `docs/adr/1508-runtime-artifact-conversion-module.md` (this ADR) + README index row.
- `CONTEXT.md` glossary: flip **Runtime Artifact Conversion Module** `[Planned]` → shipped, and update the Runtime Artifact Layout Module entry (the `getInstallExports` seam sentence is removed). *(lands with Phase 2)*
### Phase 1 modified
- `src/runtime-name-policy.cts` — `+getDirName`.
- `src/runtime-artifact-conversion.cts` — `+processAttribution`, `+getCommitAttribution`.
- `bin/install.js` — re-point 13 (`getDirName`) + 21 (attribution) call sites; delete local `convertClaudeToAugmentMarkdown`.
- tests — augment characterization test.
### Phase 2 modified
- `src/runtime-artifact-conversion.cts` — `+_applyRuntimeRewrites`, `+`both walkers, `+computePathPrefix` (private) + deep seam.
- `src/surface.cts` — deep-call cutover; drop the `getInstallExports` import + prefix math.
- `src/runtime-artifact-layout.cts` — delete `getInstallExports`/`loadInstallExports`/`InstallExports` + the `install.js` require.
- `bin/install.js` — three sites call the deep functions; import them back from the conversion module.
- tests — engine property test, `_computePathPrefix` unit tests, delete `path-replacement.test.cjs` reimplementation, parity guard.
## Consequences
- **+** `surface.cts` and `install.js` stop re-deriving the path prefix — one owner, leak dissolved at both sites.
- **+** The `.cts` source tree no longer reaches into hand-authored `bin/install.js`; `runtime-artifact-layout.cts` no longer requires `install.js` or toggles `GSD_TEST_MODE`.
- **+** `computePathPrefix` gains real unit + property coverage it lacks today.
- **−** `bin/install.js` stays hand-authored JS; it now imports the rewrite engine back from the generated `conversion.cjs` — the same pattern it already uses for `hooksSurface` and `...runtimeArtifactConversion`. Only the moved functions become TypeScript; `install.js` itself is not converted.
- **−** Two-phase sequence; CONTEXT.md glossary, ADR README index, and `lint:ci` (ADR-HEADER) updates required at merge.
## Relationship to other ADRs and issues
- **ADR-3660 (Runtime Artifact Layout Module):** resolves its *§Initial Scope* deferral ("A future ADR may consolidate them … if a second consumer emerges"). Layout owns placement; this module owns content. Independent of ADR-3660 **Phase 2** (#3664).
- **ADR-457 (generated-CJS single source):** the moved engine is authored in `src/*.cts` and consumed as generated `bin/lib/*.cjs`, consistent with the single-source rule.
- **ADR-1235 (descriptor-driven agent conversion):** complementary — both narrow `bin/install.js`'s ownership of conversion concerns.
- **Epic #1507** tracks the phases. **Distinct from epic #1258** (cross-runtime skill mapping + plugin skill provision/consumption): #1258 Phase A documents the converter *transform-contract catalog*; this ADR decides *module ownership + dependency direction + engine relocation*. Continues **#1099** (closed first slice that created the module) and is a sibling of **#1173** (agent-converter wiring).

View File

@@ -0,0 +1,97 @@
# Skill mapping & converter methodology across runtimes
- **Status:** Accepted
- **Date:** 2026-06-22
- **Issue:** [#1593](https://github.com/open-gsd/gsd-core/issues/1593)
- **Epic:** [#1258](https://github.com/open-gsd/gsd-core/issues/1258) (Phase A — *"do first"*)
- **Extends:** [ADR-3660](3660-runtime-artifact-layout-module.md) (layout), [ADR-1016](1016-runtime-capability-descriptor.md) (enum — accepted here)
- **Sibling:** [ADR-1508](1508-runtime-artifact-conversion-module.md) (module ownership), [ADR-766](766-claude-code-plugin-manifest-module.md) (Claude plugin manifest)
## Context
GSD installs skills into 16 host CLIs (claude, codex, gemini, opencode, kilo, cursor, copilot, antigravity, windsurf, augment, trae, qwen, hermes, codebuddy, cline, kimi). The methodology governing *how* a source command file in `commands/gsd/*.md` becomes an installed skill on each runtime is real, load-bearing, and documented in fragments across three sources that disagree on what they own:
1. **[ADR-3660](3660-runtime-artifact-layout-module.md)** (Accepted) — owns the *structural* layout: the `{ kind, destSubpath, prefix, nesting, recursive, converter }` `ArtifactKindDescriptor` shape, per-runtime dest path, the `gsd-` prefix, flat-vs-nested under `gsd-ns-*` routers, and the `stage` closure contract binding each layout to its converter. ADR-3660 says where artifacts go; it does not describe what the converters *do*.
2. **[ADR-1016](1016-runtime-capability-descriptor.md)** (header Status: Proposed — **corrected to Accepted by this ADR**, see Decision 2) — owns the closed `ConverterName` enum and declares `artifactLayout` as descriptor data. Vocabulary only: it closes the set of named converters; it does not describe each converter's transform contract.
3. **`src/runtime-artifact-conversion.cts`** (~2,600 lines, the converter functions) — the actual per-runtime transform semantics: frontmatter filtering, tool-name rewrites, path rewrites, namespacing, description truncation, SKILL.md-vs-flat body format. **No ADR.** A future maintainer (human or agent) has no single place that says "this converter rewrites X, drops Y, truncates at Z."
A just-merged sibling — **[ADR-1508](1508-runtime-artifact-conversion-module.md)** (PR #1509, 2026-06-21) — owns *module ownership + dependency direction* for the conversion engine. Its body explicitly defers the methodology to this ADR: *"Distinct from epic #1258: #1258 Phase A documents the converter transform-contract catalog; this ADR decides module ownership + dependency direction."* The module now has a home; the *methodology it implements* did not.
Two concrete failures fall out of this documentation gap (surfaced while triaging #1243):
1. **Consumption:** `agent_skills`'s `global:` resolver hand-resolved a file path and could not reach plugin-provided skills. The resolution (PR #1261, Claude consume side) had to reverse-engineer the converter + layout + the platform's native skill-resolution mechanism separately because no ADR described how they relate.
2. **Provision:** GSD ships as a first-party plugin/extension on multiple platforms (`.claude-plugin/plugin.json` per ADR-766, `gemini-extension.json` per #775), but those manifests do not provide GSD's skills the platform-native way — the Claude manifest declares `commands` + `hooks`, no `skills`. No ADR states the provision methodology each platform demands.
## Decision
### 1. This ADR is the single authoritative description of the per-runtime skill mapping and converter transform contracts
It codifies — in one place — what ADR-3660 (layout), ADR-1016 (vocabulary), and `runtime-artifact-conversion.cts` (semantics) each carry a third of. The companion reference page, [`docs/reference/skill-mapping-matrix.md`](../reference/skill-mapping-matrix.md), holds the maintainable per-runtime table; this ADR holds the *decisions* behind it. **References, does not duplicate, ADR-3660** (the layout owner) — extends it with the converter + mapping methodology.
### 2. ADR-1016's `ConverterName` enum is Accepted (header correction)
ADR-1016's header says `Proposed`, but its `ConverterName` closed enum is **already code-enforced**: `gsd-core/bin/lib/capability-validator.cjs` rejects unknown converter names (*"is not a known ConverterName"*), and the enum is locked by a fail-first regression test at `tests/capability-registry.test.cjs:3956` (ADR-857 phase 5e). The decision is realized; the record is stale. This ADR accepts the enum and the ADR-1016 header is corrected `Proposed` → `Accepted` as a metadata correction (no behavior change).
The closed enum `VALID_CONVERTER_NAMES` (`capability-validator.cjs:651-678`) holds **24 names** in two blocks:
- **15 commands/skills converters** — the block ADR-1016's *"15 named first-party functions covering the 16 runtimes"* refers to. Of these, 13 are skill converters and 2 are command converters (`convertClaudeCommandToCodebuddyCommand`, `convertClaudeCommandToCursorCommand`). Three runtimes share `convertClaudeCommandToClaudeSkill` (claude, qwen, hermes), so the 15 skill-bearing runtimes (all except commands-only Gemini) resolve to 13 distinct skill converters.
- **9 agent converters** (`convertClaudeAgentTo{Copilot,Antigravity,Cursor,Windsurf,Augment,Trae,Codebuddy,Cline,Codex}Agent`) — added by #1173 for the descriptor-driven agent-conversion wiring (ADR-1235). These are not yet declared by any runtime's `agents` kind descriptor (the `convertedAgentsKind` builder exists but the declarations are deferred to a #1173 follow-up; the legacy `bin/install.js` agent loop remains authoritative).
### 3. The converter transform-contract categories
Every skill converter in `runtime-artifact-conversion.cts` composes some subset of eight transform categories. This is the catalog ADR-1508 deferred:
| # | Category | What it does | Representative functions |
|---|----------|--------------|--------------------------|
| 1 | **Frontmatter extraction & reconstruction** | Extract `(name, description, allowed-tools, argument-hint, agent, context, effort)` from the source command frontmatter; reconstruct in the runtime's skill frontmatter shape. | `extractFrontmatterAndBody`, `skillFrontmatterName`, every `convertClaudeCommandTo*Skill` |
| 2 | **Description truncation** | Runtimes with description-length limits truncate to the cap (e.g. Codex: 180 chars → `metadata.short-description`). | `convertClaudeCommandToCodexSkill` (`toSingleLine` + 177-char slice) |
| 3 | **Tool-name rewrites** | Map Claude tool names to runtime equivalents. | `convertToolName`, `convertKimiToolName`, `convertCopilotToolName`, `convertGeminiToolName`; inline: `AskUserQuestion`→`question`, `SlashCommand`→`skill` (opencode) |
| 4 | **Path rewrites** | `~/.claude` → the runtime's config path; `computePathPrefix` derives the install-target prefix; `transformContentToHyphen` normalizes `/gsd:<cmd>` → `gsd-<cmd>`. | `computePathPrefix`, `applyOpencodeFamilyPathPrefix`, `convertClaudeToOpencodeFrontmatter` |
| 5 | **Slash-command → skill-mention conversion** | For runtimes that surface skills (not slash commands), rewrite `/gsd:<cmd>` invocations into skill-tool mentions. | `convertSlashCommandsTo{Cursor,Windsurf,Augment,Trae,Codebuddy}SkillMentions` |
| 6 | **Runtime-specific branding / fields** | Emit runtime-required frontmatter the source does not carry. | Hermes: `version:`; Qwen: numeric `priority:` (`QWEN_SKILL_PRIORITY`); Codex: `metadata.short-description`; Kimi: name normalization |
| 7 | **Agent-reference neutralization** | For non-Claude runtimes, replace "Claude" → "the agent" and `CLAUDE.md` → the runtime's instruction file. | `neutralizeAgentReferences` |
| 8 | **Body format (SKILL.md-vs-flat)** | Governed by the layout `nesting` flag + the `stage` closure: nested runtimes ship `<router>/skills/<name>/SKILL.md`; flat runtimes ship `<prefix><stem>/SKILL.md` at one level. | `stageSkillsForRuntimeAsSkills` (in `install-profiles.cts`), `buildNamespaceBundleMap` |
A converter's contract is the fixed subset of these eight categories it applies, in order. **Transform order is load-bearing for byte-parity** (cf. ADR-1235 §0): stale-cleanup → path-prefix rewrite → `processAttribution` → runtime converter/branding → body normalization → filename rename. A converter that silently inherits another's ordering breaks byte-for-byte parity without a test signal.
### 4. The per-runtime skill mapping
The full 16-runtime matrix — dest path, prefix, nesting, loader recursion, converter, and per-runtime notes — lives in the companion reference page: [`docs/reference/skill-mapping-matrix.md`](../reference/skill-mapping-matrix.md). The authoritative source for any cell is the runtime's `capabilities/<runtime>/capability.json` `artifactLayout` descriptor (resolved by `resolveRuntimeArtifactLayout` in `runtime-artifact-layout.cts`); the reference page is the human-readable projection, kept in sync going forward.
Three structural facts the matrix encodes:
- **All 15 skill-bearing runtimes use `prefix: "gsd-"`.** (Gemini is commands-only — no skills kind.)
- **Six runtimes nest** under `gsd-ns-*` routers (cline, qwen, hermes, augment, trae, antigravity) because their skill loaders scan one level deep; the rest stay flat because their loaders recurse (cursor, opencode, kilo) or because nesting was reverted (claude — Skill-tool errors on unknown names, #924).
- **Three runtimes share `convertClaudeCommandToClaudeSkill`** (claude, qwen, hermes); the other 12 skill-bearing runtimes each have a dedicated converter.
### 5. Plugin / external-skill provision + consumption methodology
GSD's first-party plugin/extension on every supported platform should both **provide** its own skills and **consume** external/plugin-provided skills through each platform's *documented, native* mechanism — **never** by reaching into an undocumented or ephemeral cache.
**Provision** — ship GSD's skills the platform-native way:
- **Claude Code:** the `.claude-plugin/plugin.json` manifest should declare a `skills` field / `skills/` dir (today it declares only `commands` + `hooks`, per ADR-766). This is Phase B-provide / Phase D.
- **Other platforms:** assessed per-platform in Phase C; where a platform has no documented skill-provision model, record N/A with rationale.
**Consumption** — resolve plugin/external skills through the platform's native skill-resolution mechanism:
- **Claude Code:** the sub-agent `skills:` frontmatter preload (full content injected) and the runtime `Skill` tool (loads a namespaced skill by name). PR #1261 (Phase B consume side, merged 2026-06-15) is the reference implementation: `agent_skills` accepts the namespaced form `global:<plugin>:<skill>` and emits a by-name Skill-tool directive — no cache path is ever read.
- **Other platforms:** assessed per-platform in Phase C.
**Rejected:** reading another plugin's ephemeral cache (e.g. Claude Code's `${CLAUDE_PLUGIN_ROOT}` / `~/.claude/plugins/cache`, which *"changes when the plugin updates"*), or copying skill files to undocumented locations. These are workarounds, not fixes — the platform's native mechanism is the contract.
## Consequences
- **+** One authoritative description of the per-runtime skill mapping + converter transform contracts. A future maintainer or agent reads this ADR + the reference matrix instead of reverse-engineering three sources.
- **+** Unblocks Phases B-provide, C (C1–C6), and D of epic #1258 — each per-platform implementation cites this ADR as its methodology contract.
- **+** ADR-1016's header reflects reality (Accepted, not Proposed) — the ADR README index is corrected.
- **+** Closes the documentation leak adjacent to ADR-1508: the module has a home (ADR-1508), the methodology it implements has a record (this ADR).
- **−** The reference matrix must stay in sync with the capability descriptors. The descriptors (`capabilities/<runtime>/capability.json` `artifactLayout`) remain the source of truth; the reference page is a projection. A future runtime addition must update both the descriptor and the matrix row (the descriptor's `TypeError` on unknown runtime is the structural guard; the matrix drift is a documentation gap, not a runtime failure).
- **−** The eight transform-contract categories are descriptive, not type-enforced. A converter that grows a ninth category does not trip a gate — the closed `ConverterName` enum (ADR-1016) gates the *set* of converters, not the *shape* of each converter's transform.
## Relationship to other ADRs and issues
- **[ADR-3660](3660-runtime-artifact-layout-module.md)** (layout owner, Accepted) — extended, not duplicated. This ADR documents the converter contracts that ADR-3660's `stage` closure binds but does not describe.
- **[ADR-1016](1016-runtime-capability-descriptor.md)** (enum owner, header corrected to Accepted here) — the closed `ConverterName` enum is the type-enforcement substrate; this ADR documents what each named converter *does*.
- **[ADR-1508](1508-runtime-artifact-conversion-module.md)** (module owner, Accepted) — sibling. ADR-1508 decides *module ownership + dependency direction*; this ADR decides *methodology + transform contracts*. ADR-1508's Phase 1–2 implementation (epic #1507, relocating helpers inside `runtime-artifact-conversion.cts`) touches the same file this ADR documents — they are sequenced, not conflicting.
- **[ADR-766](766-claude-code-plugin-manifest-module.md)** (Claude plugin manifest, Accepted) — referenced for the provision methodology (the `skills` manifest field Phase B-provide / Phase D adds).
- **[ADR-1235](1235-descriptor-driven-agent-conversion-migration.md)** (descriptor-driven agent conversion) — complementary; its byte-parity transform-ordering rule is cited in Decision 3.
- **Epic [#1258](https://github.com/open-gsd/gsd-core/issues/1258)** — this is Phase A. Phase B-consume (PR #1261, merged) is the reference implementation canonized in Decision 5. Phases B-provide, C (C1–C6), D are tracked as separate issues per the epic's governance.

View File

@@ -114,9 +114,19 @@ This addendum ratifies three contract points:
Net effect on D4: the *guarantee* ("a `test`-tier prohibition is never a silent pass") was preserved at every step — fail-closed-now (#644), genuine-execution (#1259), and now **machine-proven fail-first (#1279)**. A `test`-tier prohibition reaches `green`/`passed` ONLY when the wired check both genuinely, non-vacuously passes AND is independently proven to fail on a violation; every miss/fail/un-provable hard-gates. The decision also lives in `src/prohibition-enforcement.cts` comments, `gsd-core/references/prohibition-probe.md`, `gsd-core/workflows/verify-phase.md`, and the #1279 changeset.
**Review corrections (#1314 maintainer review) — two soundness items:**
- **node-test fixture-existence guard (was fail-OPEN) — FIXED.** The node-test prover originally guarded only `if (!fixture)`. A missing/typo'd/stale `violationFixture` path made `GSD_PROHIB_SUBJECT` point at a non-existent file; an honest negative test then threw ENOENT *inside its callback* — a failing test named distinctly from the file — which `isNonVacuousNodeTestRed` accepted as proof, **forging a green from a setup crash** (asymmetric with the lint-rule path, which fail-CLOSES on `< 1` file result). Fixed by requiring `fs.existsSync(path.resolve(cwd, fixture))` before spawning (symmetric fail-closed; resolved against the producer's `cwd` to match the child's resolution). **Documented residual (#1346):** existence is necessary but not sufficient — a deceptive test that reds merely *because* `GSD_PROHIB_SUBJECT` is set (not because the subject's CONTENT violates) is still accepted; proving causation generically for an arbitrary author-supplied test is not possible, so it is recorded as a constraint, not implied-solved.
- **node-test fixture-existence guard (was fail-OPEN) — FIXED.** The node-test prover originally guarded only `if (!fixture)`. A missing/typo'd/stale `violationFixture` path made `GSD_PROHIB_SUBJECT` point at a non-existent file; an honest negative test then threw ENOENT *inside its callback* — a failing test named distinctly from the file — which `isNonVacuousNodeTestRed` accepted as proof, **forging a green from a setup crash** (asymmetric with the lint-rule path, which fail-CLOSES on `< 1` file result). Fixed by requiring `fs.existsSync(path.resolve(cwd, fixture))` before spawning (symmetric fail-closed; resolved against the producer's `cwd` to match the child's resolution). **Residual (#1346) — now MITIGATED by an optional control; see the 2026-06-21 addendum below:** existence is necessary but not sufficient — a deceptive test that reds merely *because* `GSD_PROHIB_SUBJECT` is set (not because the subject's CONTENT violates) was still accepted; a generic always-on proof is impossible, so #1346 adds an **opt-in clean-subject control** that proves content-dependence when the author supplies one (and the residual remains, documented, only for checks with no control fixture).
- **`violationFixture` projection source (#1278 ↔ #1279 now COMPOSE) — DELIVERED.** Initially `descriptorFromProjection` reconstructed only `{ kind, target, rule? }` and the projection carried no fixture, so a prohibition wired purely through the deterministic path always hard-gated. This PR threads a **fourth flat scalar `check_violation_fixture`** through `projectProhibitions` + `descriptorFromProjection` (rides both kinds; mirrors `CheckDescriptor.violationFixture`). A prohibition authored with all four scalars now **machine-proves fail-first and greens end-to-end through the projection alone** (zero hand-authoring) — the round-trip is pinned by a fast-check property + CHK-03(D) + an end-to-end COMPOSE capstone. Fail-closed is preserved: a descriptor with no `check_violation_fixture` (or a blank one) projects absent and hard-gates. The remaining work under #1346 is now just the node-test causation residual above.
## Addendum (2026-06-21, #1346) — node-test causation control: prove the RED is CONTENT-caused
The #1314 review left one tracked residual (above): the node-test prover confirms the violation fixture exists and that the negative test goes a non-vacuous RED, but could not prove the RED was caused by the subject's **content** rather than by `GSD_PROHIB_SUBJECT` merely being *set*. A deceptive content-independent test (`assert.ok(!process.env.GSD_PROHIB_SUBJECT)`) was still accepted. A general always-on proof is impossible for an arbitrary author-supplied test, so #1346 closes the gap with an **opt-in control** rather than a forced one.
This addendum ratifies one contract point:
- **(d) `CheckDescriptor.cleanFixture?` / `check_clean_fixture` — the causation control (the 5th flat scalar).** An OPTIONAL author-supplied path to a KNOWN-CLEAN control subject. When present, the node-test prover runs the SAME negative test a second time with `GSD_PROHIB_SUBJECT=<cleanFixture>` and requires it to stay a **non-vacuous GREEN**. Fail-first is then proven ONLY when the check is **RED on the violation AND GREEN on the clean subject** — i.e. the red is content-dependent. A deceptive test that reds whenever the env var is set reds on the clean subject too → the control fails → not proven (fail-closed). The scalar rides both kinds through `projectProhibitions` + `descriptorFromProjection` exactly as `check_violation_fixture` does (round-trip pinned by the fast-check property + an end-to-end COMPOSE capstone exercising both the honest and deceptive subjects).
**Why opt-in, not required:** making the control mandatory would regress the #1314 zero-authoring compose path — every existing node-test prohibition (which carries no clean fixture) would suddenly hard-gate. So **absent `cleanFixture` → no control runs and behavior is byte-identical to post-#1314**; the residual remains a documented permanent constraint *only* for checks whose author did not supply a clean control. An author opts into the stronger machine guarantee by supplying one. The lint-rule kind needs no analog: its "subject" *is* the linted file (no `GSD_PROHIB_SUBJECT` indirection), so the "reds because the env var is set" gap does not exist there. Net effect on D4 is unchanged — every miss/fail/un-provable still hard-gates; this only *tightens* what counts as proven. The mechanism lives in `src/prohibition-enforcement.cts` (`defaultProveFailFirst` node-test branch + the `runNodeTestWithSubject` helper) and `src/probe-core.cts` (`projectProhibitions`), compiled by `build:lib`.
## Addendum (2026-06-15): optional `check` descriptor on the prohibition item — D3 shape extension (#1278)
This ratifies the **deterministic SOURCE** for the test-tier `CheckDescriptor` that #1259 (PR #1273) left caller/verifier-supplied. #1259 shipped the PRODUCER (`check prohibition-enforcement`) that *runs* a wired check given a `{kind, target, rule?}` descriptor, but the descriptor itself was invented by the verify-phase LLM each run (the "locate" half). #1278 makes that locate half **deterministic**: an optional `check` descriptor is authored at spec-phase on the resolved `test`-tier prohibition, projected by `projectProhibitions`, and read back by verify-phase — so a wired, passing test closes the gap with **zero manual authoring**. This extends the **Decision 3 prohibition-item shape** (it adds optional keys to that item), so it is ratified here rather than rewriting D3 in place.

View File

@@ -68,3 +68,13 @@ To keep the Seam honest about where the plugin contract ends:
- Installer Module (`bin/install.js`) — owns the `settings.json` always-on hook wiring this Module mirrors for the plugin path.
- `CONTEXT.md` § Glossary — Domain modules and seams (where this Module is registered).
- Claude Code plugin contract: <https://code.claude.com/docs/en/plugins-reference>.
## Amendment 2026-06-22 — Skills surface projection (#1596)
The original mapping table projected commands + hooks but omitted skills. Phase B-provide of epic #1258 adds the skills surface:
| gsd-core surface / source | Claude Code plugin field | Rule / invariant |
|---|---|---|
| Skill surface (`commands/gsd/*.md` → build-converted) | `skills: "./skills/"` | A `skills/` dir of build-generated `gsd-<stem>/SKILL.md` files, produced by `scripts/gen-plugin-skills.cjs` running `convertClaudeCommandToClaudeSkill` (the same converter the file-copy installer uses). Generated at build time (`npm run build`) and committed (consistent with ADR-457's generated-committed-output pattern). This closes the gap where plugin-only installs lacked the skill surface because `bin/install.js` never ran. Methodology defined by ADR-1593 §5. |
The `skills/` dir is **generated, not hand-authored** — `scripts/gen-plugin-skills.cjs --check` verifies staleness. The conformance test (`tests/issue-766-plugin-manifest.test.cjs` Section H) asserts the manifest field, dir presence, frontmatter validity, and count parity with `commands/gsd/*.md` (`DEFECT.GENERATIVE-FIX`).

View File

@@ -48,7 +48,7 @@ See **[CONTRIBUTING.md — "Proposing an ADR or PRD"](../../CONTRIBUTING.md#prop
| [15-autonomous-cross-ai-convergence.md](15-autonomous-cross-ai-convergence.md) | Cross-AI plan convergence via existing orchestration commands | Proposed |
| [22-plan-drift-guard.md](22-plan-drift-guard.md) | Plan-vs-codebase drift guard: defaults and symbol-resolver seam | Proposed |
| [3524-cjs-sdk-hard-seam.md](3524-cjs-sdk-hard-seam.md) | CJS↔SDK hard seam — single canonical owner per responsibility (#3524) | Superseded by ADR-0174 |
| [3660-runtime-artifact-layout-module.md](3660-runtime-artifact-layout-module.md) | Runtime Artifact Layout Module owns per-runtime artifact placement | Proposed |
| [3660-runtime-artifact-layout-module.md](3660-runtime-artifact-layout-module.md) | Runtime Artifact Layout Module owns per-runtime artifact placement | Accepted |
| [0174-retire-gsd-sdk-package-boundary.md](0174-retire-gsd-sdk-package-boundary.md) | Retire @opengsd/gsd-sdk package boundary — single-runtime collapse | Accepted |
| [452-eslint-lint-harness.md](452-eslint-lint-harness.md) | Adopt standard ESLint flat-config lint harness; retire homegrown regex scanners | Accepted |
| [456-test-rigor-architecture.md](456-test-rigor-architecture.md) | Test-rigor architecture — deterministic scheduling, antagonistic tier, typed-surface mandate, delete-bad-tests policy | Accepted |
@@ -56,9 +56,11 @@ See **[CONTRIBUTING.md — "Proposing an ADR or PRD"](../../CONTRIBUTING.md#prop
| [660-release-from-next-head.md](660-release-from-next-head.md) | Release from the head of next; immutable release tags; @next dist-tag as the RC surface | Proposed |
| [58-runtime-install-policy-module.md](58-runtime-install-policy-module.md) | Runtime Install Policy Module owns the typed install-plan projection | Accepted |
| [766-claude-code-plugin-manifest-module.md](766-claude-code-plugin-manifest-module.md) | Claude Code Plugin Manifest Module owns the projection of gsd-core surfaces onto the Claude Code plugin contract | Accepted |
| [1016-runtime-capability-descriptor.md](1016-runtime-capability-descriptor.md) | Runtime Capability Descriptor | Proposed |
| [1016-runtime-capability-descriptor.md](1016-runtime-capability-descriptor.md) | Runtime Capability Descriptor | Accepted |
| [1235-descriptor-driven-agent-conversion-migration.md](1235-descriptor-driven-agent-conversion-migration.md) | Migrate agent conversion to the descriptor-driven install path (parity + per-runtime cutover) | Proposed |
| [1411-resolution-provenance.md](1411-resolution-provenance.md) | Resolution must report provenance, not fall open silently | Accepted |
| [1508-runtime-artifact-conversion-module.md](1508-runtime-artifact-conversion-module.md) | Runtime Artifact Conversion Module owns per-runtime content rewriting | Accepted |
| [1593-skill-mapping-converter-methodology.md](1593-skill-mapping-converter-methodology.md) | Skill mapping & converter methodology across runtimes | Accepted |
## Seam map

View File

@@ -0,0 +1,82 @@
# Per-runtime skill mapping matrix
> **Reference** page. The authoritative source for every cell is the runtime's `capabilities/<runtime>/capability.json` `artifactLayout` descriptor, resolved by `resolveRuntimeArtifactLayout` in `gsd-core/src/runtime-artifact-layout.cts`. This page is the human-readable projection; when they disagree, the descriptor wins.
>
> **Decision record:** [ADR-1593 — Skill mapping & converter methodology across runtimes](../adr/1593-skill-mapping-converter-methodology.md). See also [ADR-3660](../adr/3660-runtime-artifact-layout-module.md) (layout owner) and [ADR-1016](../adr/1016-runtime-capability-descriptor.md) (converter enum).
## How to read this matrix
GSD ships skills (and commands/agents) as Markdown files under `commands/gsd/*.md`. Each runtime installs them via a per-runtime **layout** (where they go) and a per-runtime **converter** (how their content is rewritten). The layout is a typed `ArtifactKindDescriptor`:
```
{ kind, destSubpath, prefix, nesting, recursive, converter }
```
- **dest** — the destination subpath under the runtime's config dir (e.g. `skills`, `skills/gsd`).
- **prefix** — the filename/dir prefix (`gsd-` for every skill-bearing runtime).
- **nesting** — `flat` (skills at one level) or `nested` (concrete skills nested under `gsd-ns-*` router dirs).
- **loader** — whether the runtime's skill loader recurses (`recursive: true` → nesting saves nothing, so the layout stays flat).
- **converter** — the `ConverterName` (closed enum, ADR-1016) that rewrites the source command into the runtime's skill format. `null` means raw-copy (no conversion).
For the transform each converter applies, see [ADR-1593 §3 — converter transform-contract categories](../adr/1593-skill-mapping-converter-methodology.md#3-the-converter-transform-contract-categories).
## The 16-runtime matrix
| Runtime | Skill dest (global) | Prefix | Nesting | Loader | Converter | Notes |
|---------|---------------------|--------|---------|--------|-----------|-------|
| **claude** | `skills/` | `gsd-` | flat | one-level (reverted from nested, #924) | `convertClaudeCommandToClaudeSkill` | Local scope ships commands+agents only (no `skills` kind). Plugin manifest (ADR-766) ships skills via build-generated `skills/` dir (Phase B-provide, PR #1597, merged). |
| **codex** | `skills/` | `gsd-` | flat | unconfirmed → conservative | `convertClaudeCommandToCodexSkill` | TOML config (`configFormat: toml`). Description truncated to 180 chars (`metadata.short-description`). `sandboxTier: codex-agent-sandbox`. |
| **gemini** | — *(no skills kind)* | — | — | — | — | Commands-only (TOML `.toml` in `commands/gsd`). No skill surface; extension model has no `skills` field (C1: N/A). |
| **opencode** | `skills/` | `gsd-` | flat | recursive (`**` glob) | `convertClaudeCommandToOpencodeSkill` | XDG config home. Shares the opencode-family converter entry point (`convertClaudeCommandToOpencodeFamilySkill`). Also ships `command` (singular) commands. |
| **kilo** | `skills/` | `gsd-` | flat | recursive (`**` glob) | `convertClaudeCommandToKiloSkill` | OpenCode fork; same `**` glob loader. `permissionWriter: kilo`. Also ships `command` commands. |
| **cursor** | `skills/` | `gsd-` | flat | recursive | `convertClaudeCommandToCursorSkill` | Also ships flat `commands/` via `convertClaudeCommandToCursorCommand`. `configFormat: none`. |
| **copilot** | `skills/` | `gsd-` | flat | unconfirmed → conservative | `convertClaudeCommandToCopilotSkill` | Markdown config. Scope-aware converter (global-home vs workspace-relative). |
| **antigravity** | `skills/` | `gsd-` | nested | non-recursive (one-level) | `convertClaudeCommandToAntigravitySkill` | `dot-home-nested` config home. Scope-aware converter. Nesting confirmed: *"will not recursive scan"*. |
| **windsurf** | `skills/` | `gsd-` | flat | unconfirmed → conservative | `convertClaudeCommandToWindsurfSkill` | `configFormat: none`. `installSurface: profile-marker-only`. |
| **augment** | `skills/` | `gsd-` | nested | non-recursive (single-level) | `convertClaudeCommandToAugmentSkill` | Also ships flat `commands/`. Settings-json config. |
| **trae** | `skills/` | `gsd-` | nested | non-recursive (flat; nesting errors) | `convertClaudeCommandToTraeSkill` | `configFormat: none`. Trae IDE (trae.ai), not trae-agent. |
| **qwen** | `skills/` | `gsd-` | nested | non-recursive (flat readdir) | `convertClaudeCommandToClaudeSkill` | **Shares Claude's converter.** Emits numeric `priority:` (`QWEN_SKILL_PRIORITY`) for `/skills` ordering. Settings-json config. |
| **hermes** | `skills/gsd/` | `gsd-` | nested | non-recursive (single-level probe) | `convertClaudeCommandToClaudeSkill` | **Shares Claude's converter.** `destSubpath: skills/gsd` (category dir). Emits required `version:` field. `prefix: gsd-` restored by #947. |
| **codebuddy** | `skills/` | `gsd-` | flat | unconfirmed → conservative | `convertClaudeCommandToCodebuddySkill` | Also ships flat `commands/` via `convertClaudeCommandToCodebuddyCommand`. `dot-home` config. |
| **cline** | `skills/` | `gsd-` | nested | non-recursive (flat `fs.readdir`) | `convertClaudeCommandToClineSkill` | **Global-only** — `local: []` (no local skill install). Targets `~/.cline/skills/<name>/SKILL.md` (Cline ≥ v3.48.0). `markdown-dir` config. |
| **kimi** | `skills/` | `gsd-` | flat | (false) | `convertClaudeCommandToKimiSkill` | Also ships a special `kimi-agents` kind (`buildKimiAgentArtifacts`). Name normalization (`normalizeKimiSkillName`). `generic-agents-root` config. |
### Structural facts
- **All 15 skill-bearing runtimes use `prefix: "gsd-"`.** Gemini is the only runtime with no skills kind (commands-only TOML).
- **Six runtimes nest** (cline, qwen, hermes, augment, trae, antigravity) because their skill loaders scan one level deep — nesting drops nested concrete skills out of the eager top-level listing while keeping them readable by file path (the namespace-router contract, #69).
- **Eight runtimes stay flat**: three because their loaders recurse (cursor, opencode, kilo — nesting saves nothing), one because nesting was reverted (claude — the Skill tool errors on unknown names rather than re-routing, #924), and four conservatively where the loader depth is unconfirmed (codex, copilot, windsurf, codebuddy).
- **Three runtimes share `convertClaudeCommandToClaudeSkill`** (claude, qwen, hermes). The converter branches on the `runtime` arg for per-runtime branding (Hermes `version:`, Qwen `priority:`).
## Nesting/loader verification (June 2026)
The nesting flag is set per the verified loader behavior of each runtime. Sources:
| Behavior | Runtimes | Evidence |
|----------|----------|----------|
| **NEST** (non-recursive / one-level scan) | cline, qwen, hermes, augment, trae, antigravity | cline `skills.ts` flat `fs.readdir`; Qwen `skill-load.ts` flat readdir; hermes single-level subdir probe; augment flat single-level; trae flat (nesting errors, Trae-AI/TRAE#2253); antigravity *"will not recursive scan"* |
| **FLAT** (recursive loader → nesting gives no saving) | cursor, opencode, kilo | cursor walks skills root recursively; opencode `skill/index.ts` glob `skills/**/SKILL.md`; kilo (opencode fork, same `**` glob) |
| **FLAT** (reverted from nested) | claude | anthropics/claude-code#28266 — one-level scan, but Skill-tool errors on unknown names rather than re-routing via the router (#924) |
| **FLAT** (unconfirmed → conservative) | codex, copilot, windsurf, codebuddy | Loader depth not independently verified; kept flat to avoid mis-nesting |
## Plugin / external-skill provision + consumption
Per [ADR-1593 §5](../adr/1593-skill-mapping-converter-methodology.md#5-plugin--external-skill-provision--consumption-methodology), each platform's first-party packaging should provide and consume skills through the platform's *documented, native* mechanism.
| Runtime | Provision model | Consumption model | Outcome |
|---------|-----------------|-------------------|---------|
| **claude** | `.claude-plugin/plugin.json` `"skills": "./skills/"` — build-generated dir (PR #1597, merged) | Sub-agent `skills:` preload + runtime `Skill` tool (PR #1261, merged) | **Implemented (Phase B)** |
| **gemini** | **N/A** — `gemini-extension.json` supports `mcpServers` + `contextFileName` only; no `skills` field. Gemini CLI SDK lists skills as a future extension primitive (*"currently not implemented"*). | **N/A** — same rationale. | **C1: N/A** |
| **codex** | **N/A** — no plugin/extension manifest model. Uses `AGENTS.md` + TOML via file-copy install. | **N/A** — same rationale. | **C2: N/A** |
| **opencode / kilo** | **N/A** — no first-party plugin manifest. Recursive `skills/**/SKILL.md` glob loader scans the local config dir that `bin/install.js` writes to. | **N/A** — same rationale. | **C3: N/A** |
| **cursor, copilot, windsurf, codebuddy** | **N/A** — IDE-based tools with no plugin skill-provision model. File-copy install only. | **N/A** — same rationale. | **C4: N/A** |
| **cline, qwen, hermes, augment, trae, antigravity** | **N/A** — CLI tools with no plugin marketplace model. File-copy install only. | **N/A** — same rationale. | **C5: N/A** |
| **kimi** | **N/A** — special `kimi-agents` kind but no plugin/extension manifest. File-copy install only. | **N/A** — same rationale. | **C6: N/A** |
**Phase D (first-party packaging parity):** Complete. Claude Code's `.claude-plugin/plugin.json` is the only first-party manifest with a `skills` field (Phase B-provide, PR #1597). Gemini's `gemini-extension.json` is context-only (no skills field — C1 N/A). No other first-party packaging exists.
> **Rejected for all platforms:** reading another plugin's ephemeral/undocumented cache (e.g. Claude Code's `${CLAUDE_PLUGIN_ROOT}` / `~/.claude/plugins/cache`). The platform's native mechanism is the contract; cache-reading is a workaround, not a fix.
## Keeping this page in sync
The `capabilities/<runtime>/capability.json` `artifactLayout` descriptors are the source of truth. When a runtime's layout changes, update the descriptor first; this page is the projection. Adding a new runtime requires: (1) a new `capabilities/<runtime>/capability.json` with an `artifactLayout`, (2) a new converter in the closed `ConverterName` enum (ADR-1016), and (3) a new row in this matrix.

View File

@@ -112,6 +112,7 @@ export default tseslint.config(
'gsd-core/bin/lib/planning-workspace.cjs',
'gsd-core/bin/lib/command-roster.cjs',
'gsd-core/bin/lib/runtime-artifact-conversion.cjs',
'gsd-core/bin/lib/runtime-artifact-install-plan.cjs',
'gsd-core/bin/lib/runtime-artifact-layout.cjs',
'gsd-core/bin/lib/runtime-config-adapter-registry.cjs',
'gsd-core/bin/lib/runtime-hooks-surface.cjs',

View File

@@ -1,6 +1,6 @@
{
"name": "gsd-core",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
"contextFileName": "GEMINI.md"
}

View File

@@ -25,6 +25,7 @@
* generate-slug <text> Convert text to URL-safe slug
* current-timestamp [format] Get timestamp (full|date|filename)
* list-todos [area] Count and enumerate pending todos
* list-seeds [status] List captured seeds (optional status filter)
* verify-path-exists <path> Check file/directory existence
* config-ensure-section Initialize .planning/config.json
* history-digest Aggregate all SUMMARY.md data
@@ -631,13 +632,13 @@ async function main() {
// discovery; previously it was a partial subset that didn't include
// phase / roadmap / milestone / progress / etc.
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
'Global flags:\n' +
' --raw Emit raw output without post-processing\n' +
@@ -688,6 +689,10 @@ async function main() {
'worktree', 'prompt-budget',
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
'user-story', // pure string validation — no .planning/ access needed
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
// .planning/ access needed, and resolving project root would break workflow
// invocations that run before .planning/ exists (new-project Step 1).
'project-instruction-file',
]);
if (!SKIP_ROOT_RESOLUTION.has(command)) {
cwd = findProjectRoot(cwd);
@@ -959,6 +964,13 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
break;
}
case 'pr-subrepo': {
const message = args[1];
const { repo, branch } = parseNamedArgs(args, ['repo', 'branch']);
commands.cmdPrSubrepo(cwd, repo, branch, message, raw);
break;
}
case 'verify-summary': {
const summaryPath = args[1];
const countIndex = args.indexOf('--check-count');
@@ -1095,11 +1107,39 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
break;
}
case 'project-instruction-file': {
// #1529: pure runtime→filename projection. Backs the
// `gsd_run query project-instruction-file --runtime <r>` call in
// new-project.md so the bash workflow and profile-output.cjs share one
// source of truth (getProjectInstructionFile in runtime-name-policy.cjs).
// No SDK bridge — pure local lookup, runs before .planning/ exists.
const { getProjectInstructionFile } = require('./lib/runtime-name-policy.cjs');
// Parse --runtime <value> (space or = form); default to empty so the
// safe AGENTS.md cross-agent default applies.
const pifArgs = args.slice(1);
let pifRuntime = '';
for (let i = 0; i < pifArgs.length; i++) {
const a = pifArgs[i];
if (a === '--runtime' && pifArgs[i + 1] !== undefined) { pifRuntime = pifArgs[++i]; continue; }
if (a.startsWith('--runtime=')) { pifRuntime = a.slice('--runtime='.length); continue; }
// First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
}
const filename = getProjectInstructionFile(pifRuntime);
process.stdout.write(filename + '\n');
break;
}
case 'list-todos': {
commands.cmdListTodos(cwd, args[1], raw);
break;
}
case 'list-seeds': {
commands.cmdListSeeds(cwd, args[1], raw);
break;
}
case 'verify-path-exists': {
commands.cmdVerifyPathExists(cwd, args[1], raw);
break;
@@ -2128,6 +2168,8 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
const worktreeSafety = require('./lib/worktree-safety.cjs');
if (subcommand === 'cleanup-wave') {
worktreeSafety.cmdWorktreeCleanupWave(cwd, args.slice(2));
} else if (subcommand === 'record-agent') {
worktreeSafety.cmdWorktreeRecordAgent(cwd, args.slice(2));
} else if (subcommand === 'reap-orphans') {
worktreeSafety.cmdWorktreeReapOrphans(cwd);
} else if (subcommand === 'base-check') {
@@ -2135,7 +2177,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
} else if (subcommand === 'set-baseref') {
require('./lib/worktree-base-ref.cjs').cmdWorktreeSetBaseRef(cwd, args.slice(2));
} else {
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
}
break;
}

View File

@@ -10,7 +10,7 @@ const capabilities = {
"ai-integration": {
"id": "ai-integration",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -63,7 +63,7 @@ const capabilities = {
"antigravity": {
"id": "antigravity",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Antigravity",
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; nested skill layout; tier-1 support.",
"tier": "core",
@@ -123,7 +123,7 @@ const capabilities = {
"audit": {
"id": "audit",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -160,7 +160,7 @@ const capabilities = {
"augment": {
"id": "augment",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -229,7 +229,7 @@ const capabilities = {
"claude": {
"id": "claude",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Claude Code",
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
"tier": "core",
@@ -295,7 +295,7 @@ const capabilities = {
"cline": {
"id": "cline",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -338,7 +338,7 @@ const capabilities = {
"code-review": {
"id": "code-review",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Code review",
"description": "Source-file code review and review-fix workflow support for completed execution work.",
"tier": "full",
@@ -399,7 +399,7 @@ const capabilities = {
"codebuddy": {
"id": "codebuddy",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -468,7 +468,7 @@ const capabilities = {
"codex": {
"id": "codex",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -521,7 +521,7 @@ const capabilities = {
"copilot": {
"id": "copilot",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -574,7 +574,7 @@ const capabilities = {
"cursor": {
"id": "cursor",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Cursor",
"description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",
@@ -643,7 +643,7 @@ const capabilities = {
"drift": {
"id": "drift",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Drift detection gates",
"description": "Post-execution drift detection gates that run after each wave completes. Provides two gates 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).",
"tier": "full",
@@ -707,7 +707,7 @@ const capabilities = {
"gap-analysis": {
"id": "gap-analysis",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -748,7 +748,7 @@ const capabilities = {
"gemini": {
"id": "gemini",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Gemini CLI",
"description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.",
"tier": "core",
@@ -805,7 +805,7 @@ const capabilities = {
"graphify": {
"id": "graphify",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -846,7 +846,7 @@ const capabilities = {
"hermes": {
"id": "hermes",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -899,7 +899,7 @@ const capabilities = {
"intel": {
"id": "intel",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -951,7 +951,7 @@ const capabilities = {
"kilo": {
"id": "kilo",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -1026,7 +1026,7 @@ const capabilities = {
"kimi": {
"id": "kimi",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Kimi CLI",
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",
@@ -1082,7 +1082,7 @@ const capabilities = {
"mempalace": {
"id": "mempalace",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -1256,7 +1256,7 @@ const capabilities = {
"nyquist": {
"id": "nyquist",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Nyquist validation",
"description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.",
"tier": "full",
@@ -1306,7 +1306,7 @@ const capabilities = {
"opencode": {
"id": "opencode",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",
@@ -1376,7 +1376,7 @@ const capabilities = {
"pattern-mapper": {
"id": "pattern-mapper",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Pattern mapping",
"description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.",
"tier": "full",
@@ -1430,7 +1430,7 @@ const capabilities = {
"profile-pipeline": {
"id": "profile-pipeline",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -1507,7 +1507,7 @@ const capabilities = {
"qwen": {
"id": "qwen",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Qwen Code",
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -1564,7 +1564,7 @@ const capabilities = {
"research": {
"id": "research",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Phase research",
"description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.",
"tier": "standard",
@@ -1616,7 +1616,7 @@ const capabilities = {
"schema-gate": {
"id": "schema-gate",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -1662,7 +1662,7 @@ const capabilities = {
"security": {
"id": "security",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Security enforcement",
"description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.",
"tier": "full",
@@ -1761,7 +1761,7 @@ const capabilities = {
"tdd": {
"id": "tdd",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -1814,7 +1814,7 @@ const capabilities = {
"trae": {
"id": "trae",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",
@@ -1866,7 +1866,7 @@ const capabilities = {
"ui": {
"id": "ui",
"role": "feature",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "UI design contracts",
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
"tier": "full",
@@ -1961,7 +1961,7 @@ const capabilities = {
"windsurf": {
"id": "windsurf",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Windsurf",
"description": "Windsurf (Codeium) — nested under ~/.codeium/windsurf; skills-only artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",
@@ -2721,7 +2721,7 @@ const runtimes = {
"antigravity": {
"id": "antigravity",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Antigravity",
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; nested skill layout; tier-1 support.",
"tier": "core",
@@ -2781,7 +2781,7 @@ const runtimes = {
"augment": {
"id": "augment",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -2850,7 +2850,7 @@ const runtimes = {
"claude": {
"id": "claude",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Claude Code",
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
"tier": "core",
@@ -2916,7 +2916,7 @@ const runtimes = {
"cline": {
"id": "cline",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -2959,7 +2959,7 @@ const runtimes = {
"codebuddy": {
"id": "codebuddy",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "CodeBuddy",
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -3028,7 +3028,7 @@ const runtimes = {
"codex": {
"id": "codex",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -3081,7 +3081,7 @@ const runtimes = {
"copilot": {
"id": "copilot",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -3134,7 +3134,7 @@ const runtimes = {
"cursor": {
"id": "cursor",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Cursor",
"description": "Cursor IDE — skills + converted commands artifact layout; hooks.json surface; Claude hook event dialect; recursive skill loader (flat nesting); tier-2 support.",
"tier": "core",
@@ -3203,7 +3203,7 @@ const runtimes = {
"gemini": {
"id": "gemini",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Gemini CLI",
"description": "Google Gemini CLI — commands-only artifact layout (TOML); Gemini hook event dialect; settings-json hook surface; tier-2 support.",
"tier": "core",
@@ -3260,7 +3260,7 @@ const runtimes = {
"hermes": {
"id": "hermes",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -3313,7 +3313,7 @@ const runtimes = {
"kilo": {
"id": "kilo",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"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",
@@ -3388,7 +3388,7 @@ const runtimes = {
"kimi": {
"id": "kimi",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Kimi CLI",
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",
@@ -3444,7 +3444,7 @@ const runtimes = {
"opencode": {
"id": "opencode",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "OpenCode",
"description": "OpenCode — XDG-based config dir; flat command/ + skills artifact layout; settings-json config format; no lifecycle hook registration; tier-2 support.",
"tier": "core",
@@ -3514,7 +3514,7 @@ const runtimes = {
"qwen": {
"id": "qwen",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Qwen Code",
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
"tier": "core",
@@ -3571,7 +3571,7 @@ const runtimes = {
"trae": {
"id": "trae",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Trae IDE",
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
"tier": "core",
@@ -3623,7 +3623,7 @@ const runtimes = {
"windsurf": {
"id": "windsurf",
"role": "runtime",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"title": "Windsurf",
"description": "Windsurf (Codeium) — nested under ~/.codeium/windsurf; skills-only artifact layout; no hook surface; no hook events; tier-2 support.",
"tier": "core",

View File

@@ -0,0 +1,77 @@
'use strict';
/**
* Runtime Artifact Install Plan Module.
*
* Turns a pre-resolved runtime artifact layout into staged copy inputs. The
* installer adapter still owns pruning, copying, migrations, output, and final
* cleanup execution.
*/
// In .cts (CommonJS output) files, `require` is available as a global.
const _require = require;
const path = _require('node:path');
function errorMessage(err) {
if (err instanceof Error)
return err.message;
return String(err);
}
function addCleanupDir(cleanupDirs, stagedDir, rewrittenDir) {
const sourceDir = rewrittenDir ?? stagedDir;
if (sourceDir !== stagedDir)
cleanupDirs.push(sourceDir);
return sourceDir;
}
function createRuntimeArtifactInstallPlan(args) {
const { layout, resolvedProfile, homedir, platform, resolveAttribution, deps = {}, } = args;
const conversionExports = _require('./runtime-artifact-conversion.cjs');
const rewriteStagedSkillBodies = deps.rewriteStagedSkillBodies ?? conversionExports.rewriteStagedSkillBodies;
const rewriteStagedCommandBodies = deps.rewriteStagedCommandBodies ?? conversionExports.rewriteStagedCommandBodies;
const cleanupDirs = [];
const items = [];
const scope = layout.scope ?? 'global';
const rewriteOpts = {
runtime: layout.runtime,
configDir: layout.configDir,
scope,
homedir,
platform,
resolveAttribution,
};
for (const kind of layout.kinds) {
let stagedDir;
try {
stagedDir = kind.stage(resolvedProfile);
}
catch (err) {
return { ok: false, kind: 'stage_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
}
let sourceDir = stagedDir;
try {
if (kind.kind === 'commands') {
const rewrittenDir = rewriteStagedCommandBodies(stagedDir, rewriteOpts);
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
}
else if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
const rewrittenDir = rewriteStagedSkillBodies(stagedDir, rewriteOpts);
sourceDir = addCleanupDir(cleanupDirs, stagedDir, rewrittenDir);
}
}
catch (err) {
return { ok: false, kind: 'rewrite_failed', message: errorMessage(err), cleanupDirs, failedKind: kind.kind };
}
items.push({
kind: kind.kind,
sourceDir,
destDir: path.join(layout.configDir, kind.destSubpath),
});
}
return { ok: true, plan: { items, cleanupDirs } };
}
function createRuntimeArtifactUninstallPlan(layout) {
return {
items: layout.kinds.map((kind) => ({
kind: kind.kind,
destDir: path.join(layout.configDir, kind.destSubpath),
})),
};
}
module.exports = { createRuntimeArtifactInstallPlan, createRuntimeArtifactUninstallPlan };

View File

@@ -53,7 +53,8 @@
"post_planning_gaps": true,
"security_enforcement": true,
"security_asvs_level": 1,
"security_block_on": "high"
"security_block_on": "high",
"context_guard_mode": "warn"
},
"planning": {
"commit_docs": true,

View File

@@ -56,6 +56,7 @@
"workflow.test_command",
"workflow.build_command",
"workflow.mvp_mode",
"workflow.context_guard_mode",
"executor.stall_detect_interval_minutes",
"executor.stall_threshold_minutes",
"workflow.inline_plan_threshold",

View File

@@ -29,14 +29,14 @@ Every workflow that spawns agents or reads significant content must follow these
## Context Degradation Tiers
Monitor context usage and adjust behavior accordingly:
Monitor context usage and adjust behavior accordingly. The `workflow.context_guard_mode` config key (values: `auto`, `warn`, `off`; default `warn`) controls how `execute-phase.md` responds when the guard fires at a wave boundary.
| Tier | Usage | Behavior |
|------|-------|----------|
| PEAK | 0-30% | Full operations. Read bodies, spawn multiple agents, inline results. |
| GOOD | 30-50% | Normal operations. Prefer frontmatter reads, delegate aggressively. |
| DEGRADING | 50-70% | Economize. Frontmatter-only reads, minimal inlining, warn user about budget. |
| POOR | 70%+ | Emergency mode. Checkpoint progress immediately. No new reads unless critical. |
| Tier | Usage | Behavior | Trigger Action (execute-phase) |
|------|-------|----------|-------------------------------|
| PEAK | 0-30% | Full operations. Read bodies, spawn multiple agents, inline results. | None |
| GOOD | 30-50% | Normal operations. Prefer frontmatter reads, delegate aggressively. | None |
| DEGRADING | 50-70% | Economize. Frontmatter-only reads, minimal inlining, warn user about budget. | Emit warning, continue |
| POOR | 70%+ | Emergency mode. Checkpoint progress immediately. No new reads unless critical. | `warn`: emit warning + recommend `/gsd:pause-work`. `auto`: invoke pause-work before next wave. `off`: proceed anyway. |
## Context Degradation Warning Signs

View File

@@ -0,0 +1,16 @@
0. **Context exhaustion guard — `context_guard` (BEFORE spawning, #1452):**
Before spawning any agents for this wave, self-assess context pressure using the
degradation signals in `references/context-budget.md`. Signs of POOR tier (70%+):
increasing vagueness, skipped steps, silent partial completion.
Read `workflow.context_guard_mode` from `.planning/config.json` (default `warn`).
| Tier | `warn` (default) | `auto` | `off` |
|------|-----------------|--------|-------|
| PEAK / GOOD | No output | No output | No output |
| DEGRADING (50-70%) | Emit: "⚠ Context pressure DEGRADING — switching to frontmatter-only reads for remaining waves." Continue. | Same as warn | Skip |
| POOR (70%+) | Emit: "🛑 Context pressure POOR — risk of context exhaustion. Run `/gsd:pause-work` to checkpoint before this wave, then resume in a fresh session." Continue (user decides). | Invoke `/gsd:pause-work` immediately and halt. Do NOT spawn wave agents. | Skip |
The guard is heuristic — no programmatic context-percentage API exists. Use your
assessment of degradation signals, not a fixed token count.

View File

@@ -267,6 +267,7 @@ Set via `workflow.*` namespace in config.json (e.g., `"workflow": { "research":
| `workflow.test_command` | string\|null | `null` | Any shell command | Regression/test gate command run by verify-phase, execute-phase, audit-fix, and post-merge-gate. Unset → GSD auto-detects (Makefile / package.json / Cargo.toml / go.mod / pyproject.toml). |
| `workflow.build_command` | string\|null | `null` | Any shell command | Build gate command run by the post-merge gate. Unset → build step auto-detected/skipped. |
| `workflow.mvp_mode` | boolean | `false` | `true`, `false` | Persist the MVP-mode flag in config so every phase defaults to MVP framing without requiring `--mvp` on the CLI. Resolved via the chain: `--mvp` CLI flag → ROADMAP.md `**Mode:** mvp` field → this config value → `false`. When `true`, the planner, executor, verifier, and discovery surfaces (progress, stats, graphify) all treat the phase as an MVP vertical slice (UI → API → DB) of one user-visible capability. |
| `workflow.context_guard_mode` | string | `"warn"` | `"auto"`, `"warn"`, `"off"` | Context exhaustion guard mode for `execute-phase`. Before each wave, the orchestrator self-assesses context pressure using degradation signals from `context-budget.md`. `"warn"` (default): emit a warning and recommend `/gsd:pause-work` when POOR tier is detected. `"auto"`: automatically invoke `/gsd:pause-work` before the next wave when POOR tier is detected. `"off"`: disable the guard. The guard is heuristic — no programmatic context-% API exists. |
| `workflow.plan_chunked` | boolean | `false` | `true`, `false` | Enable chunked planning mode. When `true`, the plan-phase orchestrator splits the single long-lived planner Task into a short outline Task followed by N short per-plan Tasks (~3–5 min each). Each plan is committed individually for crash resilience. Particularly useful on Windows where long-lived Tasks may hang on stdio. Also activated by the `--chunked` flag. |
| `workflow.code_review_command` | string\|null | `null` | Any shell command | External code-review command integrated into `/gsd:ship`. The diff is piped to the command via stdin; the command must output JSON with a `verdict` field (`"APPROVED"` or `"REVISE"`). Non-zero exit or `"REVISE"` verdict blocks the ship workflow. When unset, the built-in review flow runs. Example: `my-review-tool --review`. |
| `workflow.inline_plan_threshold` | number | `2` | `0`–`10` | Plans with ≤N tasks execute inline instead of spawning a subagent |

View File

@@ -157,7 +157,7 @@ A `resolved`/`test`-tier prohibition MAY carry an **optional `check` descriptor*
the wired mechanical check, so verify-phase locates it deterministically instead of inventing
`{kind, target, rule}` each run. The descriptor is captured at spec-phase (soft / optional —
the author wires it when the negative test or lint rule already exists) and is represented as
**four flat scalar keys** on the `must_haves.prohibitions` item — never a nested `check: {}`
**five flat scalar keys** on the `must_haves.prohibitions` item — never a nested `check: {}`
object:
- `check_kind` — `node-test` | `lint-rule` (which producer mechanism runs the check).
@@ -165,16 +165,19 @@ object:
- `check_rule` — the `ruleId` to filter on, **lint-rule only** (absent for `node-test`).
- `check_violation_fixture` — path to a KNOWN-BAD subject the #1279 prover runs the check against to
machine-prove fail-first (rides BOTH kinds; for `node-test` it is injected via `GSD_PROHIB_SUBJECT`).
- `check_clean_fixture` — **optional** path to a KNOWN-CLEAN control subject (#1346). When present the
node-test prover also runs the check against it and requires GREEN, proving the violation's RED is
caused by the subject's *content* (not merely by `GSD_PROHIB_SUBJECT` being set). Absent → no control.
The flat-scalar shape is load-bearing: the shared `parseMustHavesBlock` is a flat parser and a
nested object would flatten/mangle the round-trip (ADR-550 2026-06-15 addendum; #644 "no parser
rewrite" precedent). `projectProhibitions` emits these keys **only for a well-formed descriptor**
(valid `check_kind` + non-empty `check_target`; `check_rule` only on the lint-rule path;
`check_violation_fixture` only when non-empty), and verify-phase reads them back via
`descriptorFromProjection` into the `CheckDescriptor` handed to `check prohibition-enforcement`. This
closes **both** the locate (#1278) and the machine-proof-fixture (#1346) halves with **zero manual
descriptor authoring**: a prohibition authored with all four scalars greens end-to-end through the
projection alone.
`check_violation_fixture` and `check_clean_fixture` only when non-empty), and verify-phase reads them
back via `descriptorFromProjection` into the `CheckDescriptor` handed to `check prohibition-enforcement`.
This closes the locate (#1278), the machine-proof-fixture (#1279), and the causation-control (#1346)
halves with **zero manual descriptor authoring**: a prohibition authored with the scalars greens
end-to-end through the projection alone.
**Fail-closed + backward-compat.** A partial descriptor (`lint-rule` missing `check_rule`), an
unknown `check_kind`, an **absent** descriptor, OR a descriptor with **no `check_violation_fixture`**
@@ -182,8 +185,11 @@ falls through to the producer's fail-closed paths (`located: false`, or located-
never a silent green. A prohibition with no descriptor parses and disposes byte-identically to today.
`failFirst` is **not** sourced from the descriptor and is **demoted** (machine-proven fail-first
DELIVERED in #1279 — no path greens on attestation alone, FF-08); the `dispositionForProhibition`
policy is unchanged. Residual (tracked **#1346**): the node-test proof confirms the fixture exists and
the check goes RED, but cannot generically prove the red was *caused by* the subject's content.
policy is unchanged. Causation (**#1346**): the node-test proof confirms the fixture exists and the
check goes RED; supplying `check_clean_fixture` adds an opt-in control that *also* requires GREEN on a
known-clean subject, proving the red is content-caused. With no clean fixture the control cannot run,
so that one residual case (a deceptive test reding merely because the env var is set) stays a
documented constraint — an author opts into the stronger proof by wiring a clean control subject.
## Output schema
@@ -191,7 +197,7 @@ The probe emits, per kept prohibition, an item of the form:
```
{ requirement_id, category, status, verification, resolution, reason, statement,
check_kind?, check_target?, check_rule? }
check_kind?, check_target?, check_rule?, check_violation_fixture?, check_clean_fixture? }
```
where `statement` is the must-NOT sentence and `category` is the values/safety/ethics class

View File

@@ -61,7 +61,7 @@ fi
When `--only` is set, also set `FROM_PHASE` to the same value so existing filter logic applies.
When `--interactive` is set, discuss runs inline with questions (not auto-answered). On runtimes where a backgrounded agent can spawn subagents, plan and execute are dispatched as background agents — keeping the main context lean (only discuss conversations accumulate) and enabling overlap. On Claude Code, where a backgrounded agent cannot nest subagents, plan and execute run inline to preserve worktree isolation and independent verification, so they run sequentially and their work accumulates in the main context. Either way, user input is preserved on all design decisions.
When `--interactive` is set, discuss runs inline with questions (not auto-answered). On Codex, where a backgrounded agent can still spawn subagents, plan and execute are dispatched as background agents — keeping the main context lean (only discuss conversations accumulate) and enabling overlap. On every other runtime (Claude Code and all other non-Codex runtimes), backgrounded agents cannot reliably nest subagents, so plan and execute run inline to preserve worktree isolation and independent verification, and phases run sequentially with their work accumulating in the main context. Either way, user input is preserved on all design decisions.
When `PLAN_STRATEGY=converge`, the planning step MUST invoke the plan-review convergence workflow instead of `gsd-plan-phase`. `--cross-ai` is an alias for `--converge`. Forward `CONVERGENCE_ARGS` exactly as parsed so reviewer flags and `--max-cycles N` retain the same meaning as they have on `/gsd:plan-review-convergence`.
@@ -111,7 +111,7 @@ Display startup banner:
If `ONLY_PHASE` is set, display: `Single phase mode: Phase ${ONLY_PHASE}`
Else if `FROM_PHASE` is set, display: `Starting from phase ${FROM_PHASE}`
If `TO_PHASE` is set, display: `Stopping after phase ${TO_PHASE}`
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute in background)`
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute inline — background on Codex only)`
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`
</step>
@@ -357,27 +357,13 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
**3b. Plan**
**If `INTERACTIVE` is set:** Background dispatch is only safe where a backgrounded agent can still spawn subagents. On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so the plan-checker never runs and `workflow.plan_check` silently degrades to a self-check. Resolve the runtime first:
**If `INTERACTIVE` is set:** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background. Resolve the runtime first:
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
```
- **On Claude Code (`RUNTIME` is `claude`):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
- If `PLAN_STRATEGY=converge`:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```
- Otherwise (local planning):
```
Skill(skill="gsd-plan-phase", args="${PHASE_NUM}")
```
- **On other runtimes:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
- **If `RUNTIME` is `codex`:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
- If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
@@ -401,6 +387,20 @@ RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo
Store the agent task_id. After discuss for the next phase completes (or if no next phase), wait for the plan agent to finish before proceeding to execute.
- **Otherwise (Claude Code or any other non-Codex runtime):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
- If `PLAN_STRATEGY=converge`:
```
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
```
- Otherwise (local planning):
```
Skill(skill="gsd-plan-phase", args="${PHASE_NUM}")
```
**If `INTERACTIVE` is NOT set (default):** Run plan inline.
If `PLAN_STRATEGY=converge`, run the convergence loop:
@@ -419,19 +419,13 @@ Verify plan produced output — re-run `init phase-op` and check `has_plans`. If
**3c. Execute**
**If `INTERACTIVE` is set:** Wait for the plan agent to complete (if not already) and verify plans exist. Background dispatch is only safe where a backgrounded agent can still spawn subagents. On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so the per-plan worktree-isolated executors and the verifier never run (`workflow.use_worktrees` and `workflow.verifier` silently degrade). Resolve the runtime first:
**If `INTERACTIVE` is set:** Wait for the plan agent to complete (if not already) and verify plans exist. Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background. Resolve the runtime first:
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
```
- **On Claude Code (`RUNTIME` is `claude`):** Run execute **inline** (do NOT background) so worktree isolation and verification run:
```
Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
```
- **On other runtimes:** Dispatch execute as a background agent:
- **If `RUNTIME` is `codex`:** Dispatch execute as a background agent:
```
Agent(
@@ -443,6 +437,12 @@ Agent(
Store the agent task_id. The workflow can now start discussing the next phase while this phase executes in the background. Before starting post-execution routing for this phase, wait for the execute agent to complete.
- **Otherwise (Claude Code or any other non-Codex runtime):** Run execute **inline** (do NOT background) so worktree isolation and verification run:
```
Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
```
**If `INTERACTIVE` is NOT set (default):** Run execute inline as before.
```
@@ -656,12 +656,12 @@ Check for blockers in the Blockers/Concerns section. If blockers are found, go t
If incomplete phases remain: proceed to next phase, loop back to execute_phase.
**Interactive mode overlap:** When `INTERACTIVE` is set, the iterate step enables pipeline parallelism **on runtimes where a backgrounded agent can spawn subagents** (on Claude Code, plan/execute run inline — see 3b/3c — so there is no overlap and phases run sequentially):
**Interactive mode overlap:** When `INTERACTIVE` is set, the iterate step enables pipeline parallelism **on Codex** (on every other runtime, plan/execute run inline — see 3b/3c — so there is no overlap and phases run sequentially):
1. After discuss completes for Phase N, dispatch plan+execute as background agents
2. Immediately start discuss for Phase N+1 (the next incomplete phase) while Phase N builds
3. Before starting plan for Phase N+1, wait for Phase N's execute agent to complete and handle its post-execution routing (verification, gap closure, etc.)
This means the user is always answering discuss questions (lightweight, interactive) while the heavy work (planning, code generation) runs in the background. The main context only accumulates discuss conversations — plan and execute contexts are isolated in their agents. (On Claude Code, plan and execute run inline, so they run sequentially and their work accumulates in the main context.)
This means the user is always answering discuss questions (lightweight, interactive) while the heavy work (planning, code generation) runs in the background. The main context only accumulates discuss conversations — plan and execute contexts are isolated in their agents. (On Claude Code and all other non-Codex runtimes, plan and execute run inline, so they run sequentially and their work accumulates in the main context.)
If all phases complete, proceed to lifecycle step.
@@ -873,9 +873,9 @@ When any phase operation fails or a blocker is detected, present 3 options via A
- [ ] `--to N` handle_blocker resume message preserves --to flag
- [ ] `--to N` skips lifecycle when not all milestone phases complete
- [ ] `--interactive` runs discuss inline via gsd-discuss-phase (asks questions, waits for user)
- [ ] `--interactive` dispatches plan and execute as background agents on runtimes that support nested background dispatch; runs them inline on Claude Code
- [ ] `--interactive` enables pipeline parallelism (discuss Phase N+1 while Phase N builds) on runtimes with background dispatch; phases run sequentially on Claude Code
- [ ] `--interactive` main context only accumulates discuss conversations on runtimes with background dispatch (on Claude Code, inline plan/execute also accumulate)
- [ ] `--interactive` dispatches plan and execute as background agents on Codex (the only runtime where a backgrounded agent can nest subagents); runs them inline on all other runtimes
- [ ] `--interactive` enables pipeline parallelism (discuss Phase N+1 while Phase N builds) on Codex; phases run sequentially on all other runtimes
- [ ] `--interactive` main context only accumulates discuss conversations on Codex (on all other runtimes, inline plan/execute also accumulate)
- [ ] `--interactive` waits for background agents before post-execution routing
- [ ] `--interactive` compatible with `--only`, `--from`, and `--to` flags
- [ ] `--converge` routes planning through `gsd-plan-review-convergence`

View File

@@ -59,7 +59,12 @@ gaps = [
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
exit 1
fi
```
**Report diagnosis plan to user:**

View File

@@ -91,13 +91,13 @@ Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelizat
Read runtime/worktree config and fail closed before any executor dispatch:
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
EXECUTOR_STALL_INTERVAL_MINUTES=$(gsd_run query config-get executor.stall_detect_interval_minutes 2>/dev/null || echo "5")
EXECUTOR_STALL_THRESHOLD_MINUTES=$(gsd_run query config-get executor.stall_threshold_minutes 2>/dev/null || echo "10")
if [ "$RUNTIME" = "codex" ] && [ "$USE_WORKTREES" != "false" ]; then
echo "FATAL: Codex execute-phase worktree isolation is unsupported. Set workflow.use_worktrees=false or use a runtime with Agent isolation=\"worktree\" support." >&2
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
exit 1
fi
# Sweep orphaned locked worktrees from prior crashed sessions before spawning executors (#3707).
@@ -113,7 +113,7 @@ if [ "$RUNTIME" = "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
fi
fi
```
Codex maps subagents to `spawn_agent`, which has no direct Codex mapping for Claude Code's `isolation="worktree"` parameter. Failing closed prevents main-checkout edits while the workflow believes agents are isolated.
`isolation="worktree"` is a Claude-Code-specific agent primitive; no other runtime can honor it (Codex maps subagents to `spawn_agent`, others prohibit or omit worktree binding). Failing closed prevents main-checkout edits while the workflow believes agents are isolated.
If the project uses git submodules, worktree isolation is unsafe **only when a plan touches a submodule path** — the executor commit protocol cannot correctly handle submodule commits inside isolated worktrees. The previous behavior unconditionally disabled worktree isolation whenever `.gitmodules` existed, which penalised every plan in a submodule project even when the plan was nowhere near a submodule. Compute submodule paths once and intersect them per-plan with the plan's declared `files_modified` frontmatter.
@@ -493,6 +493,8 @@ increases monotonically across waves. `{status}` is `complete` (success),
@~/.claude/gsd-core/references/execute-phase-wave-guard.md
@~/.claude/gsd-core/references/execute-phase-context-guard.md
1. **Intra-wave files_modified overlap check (BEFORE spawning):**
Before spawning any agents for this wave, inspect the `files_modified` list of all plans
@@ -685,7 +687,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
)
```
After each `Agent()` returns, parse executor-returned worktree metadata (`<worktree_metadata>`) before harness metadata, then atomically append `{agent_id, worktree_path, branch, expected_base}` to `WAVE_WORKTREE_MANIFEST`. Missing: stop and ask for recovery instead of scanning worktrees.
After each `Agent()` returns, parse executor-returned worktree metadata (`<worktree_metadata>`) before harness metadata, then record the `{agent_id, worktree_path, branch, expected_base}` entry with `gsd_run query worktree.record-agent --manifest "$WAVE_WORKTREE_MANIFEST" --agent-id … --path … --branch … --base …`. The verb validates every field at write time using the same rules the `cleanup-wave` reader enforces (write-strict `--agent-id`), failing loudly with a non-zero exit and recovery hint rather than appending an under-populated entry the reader would later drop silently. On a non-zero exit or any missing field: stop and ask for recovery instead of scanning worktrees.
> **Worktree recovery policy (#48 + #1292):** See `execute-phase/steps/worktree-recovery-policy.md` — FAIL-CLOSED rule for base/HEAD-namespace mismatches AND isolated-run fail-safe recovery.

View File

@@ -394,6 +394,16 @@ List pending todos and select one to work on.
Usage: `/gsd:capture --list`
Usage: `/gsd:capture --list api`
**`/gsd:capture --list-seeds [status]`**
List and audit captured seeds (read-only).
- Lists all seeds with ID, status, scope, trigger, and title
- Optional status filter (e.g., `/gsd:capture --list-seeds dormant`)
- Does not modify any seed — enrich with `/gsd:capture --seed --enrich SEED-NNN`
Usage: `/gsd:capture --list-seeds`
Usage: `/gsd:capture --list-seeds dormant`
### User Acceptance Testing
**`/gsd:verify-work [phase]`**

View File

@@ -0,0 +1,63 @@
<purpose>
List captured seeds for browsing and audit, with an optional status filter. Read-only — never mutates seeds.
</purpose>
<required_reading>
Read all files referenced by the invoking prompt's execution_context before starting.
</required_reading>
<process>
<step name="load_seeds">
Load seed context. An optional status filter (e.g. `dormant`, `active`, `triggered`) may follow `--list-seeds`.
```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
SEEDS=$(gsd_run list-seeds "$STATUS_FILTER")
if [[ "$SEEDS" == @file:* ]]; then SEEDS=$(cat "${SEEDS#@file:}"); fi
```
Replace `$STATUS_FILTER` with the filter token from `$ARGUMENTS` if one was given, otherwise omit it.
Extract from the JSON: `count`, `seeds[]` (each has `seed_id`, `status`, `scope`, `trigger_when`, `planted`, `title`), and `summary` (a `{ status: count }` map).
</step>
<step name="empty_case">
If `count` is 0:
```
No seeds found.
Plant one with /gsd:capture --seed "<forward-looking idea>".
```
(If a status filter was given and nothing matched, say so: `No seeds with status "<filter>".`) Exit.
</step>
<step name="render_table">
Render the seeds as a table, sorted by `seed_id` (already sorted by the tool). Truncate `trigger_when` and `title` to keep the table readable.
```
Seeds
─────────────────────────────────────────────────────────────────────
ID Status Scope Trigger Title
SEED-001 dormant large when websockets land Real-time collaboration
SEED-006 triggered medium MILE-04 planning Remove legacy auth crates
─────────────────────────────────────────────────────────────────────
<count> seeds (<summary rendered as "N status" pairs, e.g. "1 dormant, 1 triggered">)
```
Then offer next actions as plain text (no mutation here):
```
- /gsd:capture --seed --enrich <ID> enrich a seed with trigger, why, and scope
- /gsd:capture --list-seeds <status> filter by status
```
</step>
</process>
<success_criteria>
- [ ] Seeds listed with ID, status, scope, trigger, and title
- [ ] Status filter applied when provided
- [ ] Empty / no-match case handled with guidance
- [ ] Summary line shows total and per-status counts
- [ ] No seed files were modified (read-only)
</success_criteria>

View File

@@ -1,6 +1,6 @@
<purpose>
Interactive command center for managing a milestone from a single terminal. Shows a dashboard of all phases with visual status, dispatches discuss inline and plan/execute as background agents, and loops back to the dashboard after each action. Enables parallel phase work from one terminal.
Interactive command center for managing a milestone from a single terminal. Shows a dashboard of all phases with visual status, dispatches discuss inline and runs plan/execute inline (backgrounded only on Codex), and loops back to the dashboard after each action. Enables parallel phase work from one terminal.
</purpose>
@@ -45,7 +45,7 @@ Display startup banner:
{milestone_version} — {milestone_name}
{phase_count} phases · {completed_count} complete
✓ Discuss → inline ◆ Plan/Execute → background
✓ Discuss → inline ◆ Plan/Execute → inline (background on Codex)
Dashboard auto-refreshes when background work is active.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
@@ -221,8 +221,8 @@ Go to exit step.
When the user selects a compound option, behavior depends on the runtime — the Plan Phase N / Execute Phase N handlers below resolve it via `gsd_run query config-get runtime`:
- **On Claude Code:** a backgrounded agent cannot nest the pipeline's subagents, so run the chosen plan/execute step(s) **inline** via their handlers below (in order), then run the inline discuss. There is no overlap.
- **On other runtimes:** **Spawn all background agents first** (plan/execute) — dispatch them in parallel using the Plan Phase N / Execute Phase N handlers below — then run the inline discuss; the background agents continue while you discuss.
- **On Codex:** **Spawn all background agents first** (plan/execute) — dispatch them in parallel using the Plan Phase N / Execute Phase N handlers below — then run the inline discuss; the background agents continue while you discuss.
- **Otherwise (Claude Code or any other non-Codex runtime):** a backgrounded agent cannot reliably nest the pipeline's subagents, so run the chosen plan/execute step(s) **inline** via their handlers below (in order), then run the inline discuss. There is no overlap.
Inline discuss:
@@ -244,27 +244,13 @@ After discuss completes, loop back to dashboard step.
### Plan Phase N
Planning runs autonomously. **First resolve the runtime.** On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so it cannot spawn the plan-checker the pipeline relies on — backgrounding it there silently turns `workflow.plan_check` into a self-check. So run plan **inline** on Claude Code, and **background** it only on runtimes where a backgrounded agent can still nest subagents.
Planning runs autonomously. **First resolve the runtime.** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background.
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
```
**If `RUNTIME` is `claude` (Claude Code):** Run plan inline so the plan-checker and quality gates actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
```
Skill(skill="gsd-plan-phase", args="{N} --auto {manager_flags.plan}")
```
Display while it runs:
```
◆ Planning Phase {N}: {phase_name}... (runs inline so the plan-checker runs — the dashboard resumes when it returns, ~1–5 min; expected, not a freeze)
```
Then loop back to dashboard step.
**If `RUNTIME` is not `claude` (e.g. Codex):** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
**If `RUNTIME` is `codex`:** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
```
Agent(
@@ -286,7 +272,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
)
```
> **ORCHESTRATOR RULE — NON-CLAUDE RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any planning work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume planning-related work when the subagent result is available.
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any planning work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume planning-related work when the subagent result is available.
Display:
@@ -296,29 +282,29 @@ Display:
Loop back to dashboard step.
### Execute Phase N
Execution runs autonomously. **First resolve the runtime.** On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so it cannot spawn the per-plan worktree-isolated executors or the verifier — backgrounding it there silently disables `workflow.use_worktrees` isolation and `workflow.verifier`. So run execute **inline** on Claude Code, and **background** it only on runtimes where a backgrounded agent can still nest subagents.
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
```
**If `RUNTIME` is `claude` (Claude Code):** Run execute inline so worktree isolation and the verifier actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
**Otherwise (Claude Code or any other non-Codex runtime):** Run plan inline so the plan-checker and quality gates actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
```
Skill(skill="gsd-execute-phase", args="{N} {manager_flags.execute}")
Skill(skill="gsd-plan-phase", args="{N} --auto {manager_flags.plan}")
```
Display while it runs:
```
◆ Executing Phase {N}: {phase_name}... (runs inline so worktree isolation and verification run — the dashboard resumes when it returns; expected, not a freeze)
◆ Planning Phase {N}: {phase_name}... (runs inline so the plan-checker runs — the dashboard resumes when it returns, ~1–5 min; expected, not a freeze)
```
Then loop back to dashboard step.
**If `RUNTIME` is not `claude` (e.g. Codex):** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
### Execute Phase N
Execution runs autonomously. **First resolve the runtime.** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background.
```bash
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
```
**If `RUNTIME` is `codex`:** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
```
Agent(
@@ -340,7 +326,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
)
```
> **ORCHESTRATOR RULE — NON-CLAUDE RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any execution work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume execution-related work when the subagent result is available.
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any execution work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume execution-related work when the subagent result is available.
Display:
@@ -350,6 +336,20 @@ Display:
Loop back to dashboard step.
**Otherwise (Claude Code or any other non-Codex runtime):** Run execute inline so worktree isolation and the verifier actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
```
Skill(skill="gsd-execute-phase", args="{N} {manager_flags.execute}")
```
Display while it runs:
```
◆ Executing Phase {N}: {phase_name}... (runs inline so worktree isolation and verification run — the dashboard resumes when it returns; expected, not a freeze)
```
Then loop back to dashboard step.
</step>
<step name="background_completion">
@@ -422,8 +422,8 @@ Display final status with progress bar:
- [ ] Dependency resolution: blocked phases show which deps are missing
- [ ] Recommendations prioritize: execute > plan > discuss
- [ ] Discuss phases run inline via Skill() — interactive questions work
- [ ] Plan phases spawn background Task agents — return to dashboard immediately
- [ ] Execute phases spawn background Task agents — return to dashboard immediately
- [ ] Plan phases run inline (or as background Task agents on Codex) — dashboard resumes when complete
- [ ] Execute phases run inline (or as background Task agents on Codex) — dashboard resumes when complete
- [ ] Dashboard refreshes pick up changes from background agents via disk state
- [ ] Background agent completion triggers notification and dashboard refresh
- [ ] Background agent errors present retry/skip options

View File

@@ -109,9 +109,9 @@ elif [ -n "$OPENCODE_CONFIG_DIR" ] || [ -n "$OPENCODE_CONFIG" ]; then RUNTIME="o
else RUNTIME="claude"; fi
```
Set the instruction file variable:
Set the instruction file variable via the shared runtime-name policy adapter (`gsd-tools query project-instruction-file`, backed by `getProjectInstructionFile` in `runtime-name-policy.cjs` — the single source of truth shared with `profile-output.cjs`):
```bash
if [ "$RUNTIME" = "codex" ]; then INSTRUCTION_FILE="AGENTS.md"; else INSTRUCTION_FILE=".claude/CLAUDE.md"; fi
INSTRUCTION_FILE=$(gsd_run query project-instruction-file --runtime "$RUNTIME")
```
All subsequent references to the project instruction file use `$INSTRUCTION_FILE`.
@@ -1533,7 +1533,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
- `.planning/REQUIREMENTS.md`
- `.planning/ROADMAP.md`
- `.planning/STATE.md`
- `$INSTRUCTION_FILE` (`AGENTS.md` for Codex, `.claude/CLAUDE.md` for all other runtimes)
- `$INSTRUCTION_FILE` (runtime-derived via the shared `getProjectInstructionFile` policy: `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude)
</output>
@@ -1555,7 +1555,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
- [ ] ROADMAP.md created with phases, requirement mappings, success criteria
- [ ] STATE.md initialized
- [ ] REQUIREMENTS.md traceability updated
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (AGENTS.md for Codex, `.claude/CLAUDE.md` otherwise; an existing hand-crafted file without GSD markers is left untouched unless `--force`)
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (runtime-derived via the shared `getProjectInstructionFile` policy — `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude; an existing hand-crafted file without GSD markers is left untouched unless `--force`)
- [ ] User knows next step is `/gsd:discuss-phase 1`
**Atomic commits:** Each phase commits its artifacts immediately. If context is lost, artifacts persist.

View File

@@ -43,6 +43,162 @@ Commits: {AHEAD} ahead
```
</step>
<step name="handle_sub_repos">
Read the sub-repo list from config using the canonical key path — `planning.sub_repos`.
A non-zero exit code means the key is absent; treat that as "no sub-repos configured".
```bash
SUB_REPOS_JSON=$(gsd_run query config-get planning.sub_repos 2>/dev/null)
if [ $? -ne 0 ] || [ -z "$SUB_REPOS_JSON" ] || [ "$SUB_REPOS_JSON" = "null" ] || [ "$SUB_REPOS_JSON" = "[]" ]; then
: # Not configured or empty — skip to analyze_commits
fi
```
Scan each sub-repo for uncommitted changes using node (always available — avoids undeclared
jq dependency). Write dirty repo names to a temp file so the list survives across
subsequent command executions:
```bash
ROOT=$(git rev-parse --show-toplevel)
DIRTY_FILE=$(mktemp)
node -e "
const repos = JSON.parse(process.argv[1]);
const { execFileSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const root = process.argv[2];
// realpath parity with the pr-subrepo seam's validatePath: resolve $ROOT through
// symlinks once so the containment check below compares real paths, not text.
let realRoot;
try { realRoot = fs.realpathSync(root); } catch (_) { realRoot = path.resolve(root); }
const out = [];
for (const r of repos) {
// Reject before any git invocation: this scan runs on raw config values,
// ahead of the pr-subrepo seam's own validatePath guard. A traversal,
// embedded-newline, or symlink entry here would run git outside the
// workspace, or inject a spurious record into the dirty-file output.
if (typeof r !== 'string' || !/^[A-Za-z0-9._\/-]+$/.test(r)) continue;
// realpathSync follows symlinks — path.resolve only normalizes '..' textually,
// so an in-tree symlink pointing outside root would otherwise smuggle git out.
let resolved;
try { resolved = fs.realpathSync(path.resolve(realRoot, r)); } catch (_) { continue; }
if (resolved !== realRoot && !resolved.startsWith(realRoot + path.sep)) continue;
try {
const res = execFileSync('git', ['-C', resolved, 'status', '--porcelain'],
{ encoding: 'utf8', timeout: 10_000 });
// Exclude untracked-only repos: seam filters ?? lines, so detection must match.
const tracked = res.split('\n').filter(l => l.length > 0 && !l.startsWith('??'));
if (tracked.length > 0) out.push(r);
} catch (_) {}
}
fs.writeFileSync(process.argv[3], out.join('\n'));
" "$SUB_REPOS_JSON" "$ROOT" "$DIRTY_FILE"
DIRTY_REPOS=$(cat "$DIRTY_FILE")
```
If `$DIRTY_REPOS` is empty, remove the temp file and continue to `analyze_commits`.
Display dirty repos and prompt the user:
```
Sub-repos with uncommitted changes:
backend
frontend
How should sub-repo changes be handled?
1. all — branch, commit (explicit files only), push -u, open companion PR per repo
2. select — choose which sub-repos to process
3. skip — ignore sub-repos, continue with root repo only
```
If the user chooses **skip**, remove the temp file and continue to `analyze_commits`.
For each selected sub-repo `$REPO_REL`, delegate all git work to the `pr-subrepo` query
seam — it stages explicit changed files (never `git add -A`), creates the branch,
commits, and pushes with `--set-upstream`. Branch names include the repo slug to avoid
colliding with the root `PR_BRANCH` that `create_pr_branch` creates later:
```bash
# Replace path separators to make the name safe as a branch component
REPO_SAFE="${REPO_REL//\//-}"
SUB_BRANCH="${CURRENT_BRANCH}-${REPO_SAFE}-pr"
COMMIT_MSG="fix(${REPO_REL}): sync uncommitted changes for PR"
RESULT=$(gsd_run query pr-subrepo "$COMMIT_MSG" \
--repo "$REPO_REL" \
--branch "$SUB_BRANCH")
SUBREPO_EXIT=$?
```
If the seam exited non-zero (stage/commit/push failure), report its error and move on to
the next selected sub-repo. **Do not run the companion-PR step below for this repo** —
the seam's stderr already explains the failure, and the "branch pushed" path would
otherwise contradict it:
```bash
if [ "$SUBREPO_EXIT" -ne 0 ]; then
echo "pr-subrepo failed for $REPO_REL — see error above; skipping companion PR." >&2
fi
```
Only when `$SUBREPO_EXIT` is `0`, parse the structured result with node and open the
companion PR. If `remote_slug` is null (non-GitHub remote), skip `gh pr create` and show
the push URL instead:
```bash
REMOTE_SLUG=$(node -e "
try { console.log(JSON.parse(process.argv[1]).remote_slug || ''); } catch(_) {}
" "$RESULT")
if [ -n "$REMOTE_SLUG" ]; then
# Defense-in-depth: $REPO_REL was already validated by the dirty-scan filter and
# the pr-subrepo seam's validatePath, but these are separate, independent git -C
# invocations on the same value. Resolve it through symlinks with the SAME realpath
# containment the seam uses (path.resolve alone would not catch a symlink escape),
# and run git against the validated absolute path rather than re-concatenating.
SUB_REPO_DIR=$(node -e "
const fs = require('fs'), path = require('path');
try {
const realRoot = fs.realpathSync(process.argv[1]);
const resolved = fs.realpathSync(path.resolve(realRoot, process.argv[2]));
if (resolved !== realRoot && !resolved.startsWith(realRoot + path.sep)) process.exit(1);
process.stdout.write(resolved);
} catch (_) { process.exit(1); }
" "$ROOT" "$REPO_REL" 2>/dev/null)
if [ -z "$SUB_REPO_DIR" ]; then
echo "Refusing unsafe sub-repo path: $REPO_REL" >&2
SUB_TARGET="$TARGET"
else
# Resolve base branch: use $TARGET if it exists in sub-repo, else fall back to
# the sub-repo's own default branch
if git -C "$SUB_REPO_DIR" ls-remote --exit-code --heads origin "$TARGET" \
> /dev/null 2>&1; then
SUB_TARGET="$TARGET"
else
SUB_TARGET=$(git -C "$SUB_REPO_DIR" remote show origin 2>/dev/null \
| awk '/HEAD branch/ {print $NF}')
SUB_TARGET="${SUB_TARGET:-main}"
fi
fi
gh pr create \
--repo "$REMOTE_SLUG" \
--base "$SUB_TARGET" \
--head "$SUB_BRANCH" \
--title "$COMMIT_MSG" \
--body "Companion PR for root repo branch \`$CURRENT_BRANCH\`."
else
echo "No GitHub remote detected for $REPO_REL — branch pushed, open PR manually."
fi
```
After processing all selected sub-repos, remove the temp file and continue to
`analyze_commits` for the root repo.
</step>
<step name="analyze_commits">
Classify commits:

View File

@@ -137,7 +137,12 @@ AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
```bash
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
exit 1
fi
```
If `USE_WORKTREES` is not `"false"`, run a startup orphan sweep before spawning any executors. This reaps locked worktrees whose lock-owner process is dead, whose branch is merged into the default branch, and whose lock file mtime is older than 5 minutes. Running it at startup prevents accumulation of orphaned worktrees from prior sessions that exited without cleanup (#3707).

View File

@@ -157,6 +157,14 @@ Provide structured feedback on plan quality, completeness, and risks.
## Review Instructions
**Verify against source — do not review the plan text in isolation.** You are running inside the project's git working tree (the current directory). The plans reference real files, migrations, routes, and tests that exist in this repo now.
1. Open the referenced files and check each claim against the actual code.
2. For every strength or concern, cite concrete `path/to/file:line` evidence plus the mechanism.
3. When a plan asserts a mechanism works (a guard, a query filter, a test that exercises a path), trace whether it actually does what is claimed — do not take the plan's word for it.
4. If you cannot read the repo (no file access), say so and downgrade that finding to an open question rather than asserting it.
Findings citing `file:line` evidence are weighted far more heavily than impressionistic ones; a review that only restates the plan's own claims has low value.
Analyze each plan and provide:
1. **Summary** — One-paragraph assessment
@@ -273,7 +281,7 @@ fi
**CodeRabbit:**
Note: CodeRabbit reviews the current git diff/working tree — it does not accept a prompt or model flag. It may take up to 5 minutes. Use `timeout: 360000` on the Bash tool call.
Note: CodeRabbit reviews the current git diff/working tree — it does not accept a prompt or model flag. It may take up to 5 minutes. Use `timeout: 360000` on the Bash tool call. The source-grounding requirement in the build_prompt Review Instructions applies only to the prompt-fed reviewers above; CodeRabbit is a diff-only reviewer and never receives it. Treat its output as a diff observation, not a grounded plan-level verdict.
```bash
coderabbit review --prompt-only 2>/dev/null > /tmp/gsd-review-coderabbit-{phase}.md
@@ -714,7 +722,7 @@ trimmed_reviewers: # only present if at least one reviewer was trimmed
## Consensus Summary
{synthesize common concerns across all reviewers}
{synthesize common concerns across all reviewers. CodeRabbit is a diff-only reviewer (it never received the source-grounding prompt), so do not weight its verdict as a grounded plan review — fold in its diff findings, but base plan-level consensus on the prompt-fed reviewers.}
### Agreed Strengths
{strengths mentioned by 2+ reviewers}

View File

@@ -365,10 +365,15 @@ For each Requirement gathered so far, run the two-stage recall→precision pass:
- `check_target` — the negative-test file path (for `node-test`), or the path to lint
(for `lint-rule`).
- `check_rule` — the eslint rule id (e.g. `local/no-source-grep`); `lint-rule` only.
- `check_violation_fixture` (#1346) — path to a KNOWN-BAD subject the wired check is run
- `check_violation_fixture` (#1279) — path to a KNOWN-BAD subject the wired check is run
against to **machine-prove fail-first**; rides BOTH kinds. Capture it to let the item green
end-to-end with zero hand-authoring at verify time; for `node-test` the negative test should
read its subject from the `GSD_PROHIB_SUBJECT` env var so the prover can inject this fixture.
- `check_clean_fixture` (#1346) — **optional** path to a KNOWN-CLEAN control subject. When
captured, the `node-test` prover also runs the check against it and requires GREEN — proving
the violation's RED is caused by the subject's *content*, not by `GSD_PROHIB_SUBJECT` merely
being set. Capture it for a stronger guarantee; omit it and the check still proves fail-first
on the violation alone (the content-causation residual stays documented for that case).
This is a **SOFT capture (CHK-04): a `test`-tier prohibition WITHOUT a descriptor is still
allowed** — if the author cannot yet name the wired check, leave the descriptor empty and
proceed. It is NOT a hard authoring block; the item simply stays fail-closed/flagged
@@ -395,7 +400,7 @@ For each Requirement gathered so far, run the two-stage recall→precision pass:
written (test or judgment tier); otherwise leave `unresolved`. **`--auto` NEVER auto-dismisses
a prohibition** — a wrong dismissal is the exact silent failure this probe eliminates (PROB-06,
the load-bearing safety property). On a `test`-tier auto-resolution, capture the `check_kind` /
`check_target` / `check_rule` / `check_violation_fixture` descriptor **only when a wired check is unambiguous**; otherwise
`check_target` / `check_rule` / `check_violation_fixture` / `check_clean_fixture` descriptor **only when a wired check is unambiguous**; otherwise
leave it empty — `--auto` NEVER fabricates a check path or fixture (a wrong locate is re-validated and
fails closed at the producer, but a fabricated path is still noise to avoid). Log:
`[auto] prohibitions: R resolved, U unresolved`.
@@ -408,7 +413,7 @@ Populate the `## Prohibitions` section of SPEC.md from the resolved prohibitions
`resolved`/`test` row is a checkable negative acceptance criterion; `resolved`/`judgment`
rows route to judgment review; `⚠ UNRESOLVED` rows are flagged as assumptions). A
`resolved`/`test` row ALSO carries its captured `check_kind` / `check_target` / `check_rule` /
`check_violation_fixture` descriptor when present (so the projection feeds `verify-phase`'s deterministic locate + machine-proof, #1278 + #1346);
`check_violation_fixture` / `check_clean_fixture` descriptor when present (so the projection feeds `verify-phase`'s deterministic locate + machine-proof + causation control, #1278 + #1279 + #1346);
a `test` row with no captured descriptor is still valid — it stays fail-closed/flagged
downstream rather than blocking authoring.

View File

@@ -76,11 +76,11 @@ Aggregate all must_haves across plans for phase-level verification.
gsd_run check prohibition-enforcement <request.json>
```
where `<request.json>` carries `{ prohibition, check, mode }` — `check` being the wired mechanical-check descriptor `{ kind: 'node-test' | 'lint-rule', target, rule?, violationFixture, failFirst? }`, with `kind`/`target`/`rule`/`violationFixture` now sourced from the projected `check_*` scalars (not author/verifier invention — #1278 + #1346). For `node-test`, `target` (from `check_target`) is the negative-test file path; for `lint-rule`, `target` is the PATH to lint and `rule` (from `check_rule`) is the eslint rule id (e.g. `local/no-source-grep`) — both required (a lint-rule without `rule` is not a valid wired check). `violationFixture` (from `check_violation_fixture`) is the path to a KNOWN-BAD subject the producer runs the check against to **machine-prove fail-first** (for `node-test`, injected via the `GSD_PROHIB_SUBJECT` env convention — #1279); `failFirst` is a DEMOTED, non-authoritative hint kept only for backward route-JSON shape (no path greens on it alone — FF-08). The producer LOCATES the wired check from the projection, **machine-proves it is fail-first** by running it against the violation and confirming it goes RED, RUNS it for a genuine non-vacuous pass, builds `enforcementEvidence`, and emits the `dispositionForProhibition()` verdict (#1259 + #1278 + #1279, ADR-550 D5d). Fail-first is **machine-proven, not caller-attested** — absent a provable violation the producer fails closed, never falling back to attestation. Route the result by its typed fields:
where `<request.json>` carries `{ prohibition, check, mode }` — `check` being the wired mechanical-check descriptor `{ kind: 'node-test' | 'lint-rule', target, rule?, violationFixture, cleanFixture?, failFirst? }`, with `kind`/`target`/`rule`/`violationFixture`/`cleanFixture` now sourced from the projected `check_*` scalars (not author/verifier invention — #1278 + #1279 + #1346). For `node-test`, `target` (from `check_target`) is the negative-test file path; for `lint-rule`, `target` is the PATH to lint and `rule` (from `check_rule`) is the eslint rule id (e.g. `local/no-source-grep`) — both required (a lint-rule without `rule` is not a valid wired check). `violationFixture` (from `check_violation_fixture`) is the path to a KNOWN-BAD subject the producer runs the check against to **machine-prove fail-first** (for `node-test`, injected via the `GSD_PROHIB_SUBJECT` env convention — #1279); the optional `cleanFixture` (from `check_clean_fixture`) is a KNOWN-CLEAN control subject the `node-test` prover ALSO requires to stay GREEN, proving the RED is content-caused (#1346); `failFirst` is a DEMOTED, non-authoritative hint kept only for backward route-JSON shape (no path greens on it alone — FF-08). The producer LOCATES the wired check from the projection, **machine-proves it is fail-first** by running it against the violation and confirming it goes RED, RUNS it for a genuine non-vacuous pass, builds `enforcementEvidence`, and emits the `dispositionForProhibition()` verdict (#1259 + #1278 + #1279, ADR-550 D5d). Fail-first is **machine-proven, not caller-attested** — absent a provable violation the producer fails closed, never falling back to attestation. Route the result by its typed fields:
- **`status: 'green'`, `flagged: false`** (a genuinely-passing wired negative test / lint rule, `located: true`, non-empty `evidence`) → the item is satisfiable → it can reach **passed**.
- **missing, non-attested, or genuinely-non-passing check** (`located: false` OR `status: 'unverified'`, `flagged: true`) → **hard-gate**: disposes flagged-unverified, NEVER green, routing to `gaps_found` in BOTH interactive and autonomous modes (a failing mechanical check blocks even AFK; ADR-550 D4 / D3). The deterministic fail-closed default backing every miss/fail is `dispositionForProhibition()` in probe-core (`status: 'unverified'`, `flagged: true` on empty `enforcementEvidence`).
> **Descriptor source — deterministic locate + machine-proof compose (#1278 + #1346, DELIVERED).** The `check` descriptor's `{ kind, target, rule, violationFixture }` is now sourced **deterministically from the projected `check_kind` / `check_target` / `check_rule` / `check_violation_fixture` scalars** on the `must_haves.prohibitions` item (authored at `/gsd:spec-phase`, projected by `projectProhibitions`, read back via the `descriptorFromProjection` adapter). So both halves close with **zero manual descriptor authoring** — the verifier neither invents the locate (#1278) nor hand-supplies the violation fixture (#1346): a prohibition authored with all four scalars machine-proves fail-first and greens end-to-end through the projection alone (removing the spoofable invent-at-verify-time surface; ADR-857 §147 exogenous grading). **Fail-closed is preserved:** an item with NO projected descriptor, a PARTIAL one (e.g. a `lint-rule` missing `check_rule`), OR a descriptor with **no `check_violation_fixture`** makes `descriptorFromProjection` return `null` / an under-specified or fixture-less descriptor, which falls through to the producer's fail-closed paths (`located: false`, or located-but-unprovable) → flagged-unverified, NEVER green, in BOTH modes. `failFirst` is demoted and greens nothing on its own (#1279, FF-08). Residual (tracked **#1346**): the node-test proof confirms the fixture exists and the check goes RED, but cannot generically prove the red was *caused by* the subject's content vs the env merely being set.
> **Descriptor source — deterministic locate + machine-proof compose (#1278 + #1346, DELIVERED).** The `check` descriptor's `{ kind, target, rule, violationFixture }` is now sourced **deterministically from the projected `check_kind` / `check_target` / `check_rule` / `check_violation_fixture` scalars** on the `must_haves.prohibitions` item (authored at `/gsd:spec-phase`, projected by `projectProhibitions`, read back via the `descriptorFromProjection` adapter). So both halves close with **zero manual descriptor authoring** — the verifier neither invents the locate (#1278) nor hand-supplies the violation fixture (#1346): a prohibition authored with all four scalars machine-proves fail-first and greens end-to-end through the projection alone (removing the spoofable invent-at-verify-time surface; ADR-857 §147 exogenous grading). **Fail-closed is preserved:** an item with NO projected descriptor, a PARTIAL one (e.g. a `lint-rule` missing `check_rule`), OR a descriptor with **no `check_violation_fixture`** makes `descriptorFromProjection` return `null` / an under-specified or fixture-less descriptor, which falls through to the producer's fail-closed paths (`located: false`, or located-but-unprovable) → flagged-unverified, NEVER green, in BOTH modes. `failFirst` is demoted and greens nothing on its own (#1279, FF-08). Causation (**#1346**): supplying `check_clean_fixture` adds an opt-in control — the `node-test` prover also requires GREEN on a known-clean subject, proving the RED is content-caused; with no clean fixture that one residual case (a deceptive test reding merely because the env var is set) stays a documented constraint, an author opting into the stronger proof by wiring a clean control.
**Option B: Use Success Criteria from ROADMAP.md**

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@opengsd/gsd-core",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@opengsd/gsd-core",
"version": "1.5.1-dev.0",
"version": "1.6.0-rc.2",
"license": "MIT",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.84",

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