Merge branch 'next' into fix/2137-derive-progress-header-driven
This commit is contained in:
6
.changeset/2099-eos-copilot.md
Normal file
6
.changeset/2099-eos-copilot.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2172
|
||||
---
|
||||
|
||||
**GitHub Copilot now wires GSD's full lifecycle hook bus and is driven by its capability descriptor** — installing GSD into Copilot registers `preToolUse`, `postToolUse`, `userPromptSubmitted`, and `sessionEnd` handlers in its `hooks/gsd-session.json` (beyond today's `sessionStart`-only advisory), and Copilot's residual hardcoded runtime branches are folded onto descriptor-driven `hostBehaviors`. (#2099)
|
||||
5
.changeset/2100-eos-windsurf.md
Normal file
5
.changeset/2100-eos-windsurf.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2190
|
||||
---
|
||||
**Windsurf now enforces GSD's write/command safety guards through Cascade's native hook bus** — installing GSD into Windsurf registers blocking `pre_write_code`/`pre_run_command` hooks in `.windsurf/hooks.json` (exit-code-2 blocking) and drives Windsurf's install from its capability descriptor instead of hardcoded runtime branches. (#2100)
|
||||
5
.changeset/2101-eos-zcode.md
Normal file
5
.changeset/2101-eos-zcode.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2195
|
||||
---
|
||||
**ZCode's install is now driven and regression-tested through its capability descriptor** — ZCode joins the dogfooded declarative-adapter reference hosts with a byte-identical install, and its shared-hooks exclusion is folded onto `hostBehaviors` instead of a hardcoded runtime branch. (Hook-automation and MCP upgrades remain blocked on ZCode publishing its on-disk config formats.) (#2101)
|
||||
5
.changeset/2102-eos-pi.md
Normal file
5
.changeset/2102-eos-pi.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2205
|
||||
---
|
||||
**GSD is now installable on pi** — `npx @opengsd/gsd-core --pi` installs the GSD extension to `~/.pi/agent/extensions/gsd.cjs`, and `/gsd <family> <subcommand>` now dispatches real commands through the embedded engine (the reference binding previously could only run `query help`). Drives pi through the negotiated imperative Host-Integration adapter, with active-model steering and the full pi lifecycle-event surface. (#2102)
|
||||
5
.changeset/2116-surface-bare-require.md
Normal file
5
.changeset/2116-surface-bare-require.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2213
|
||||
---
|
||||
**Fixed unresolvable bare `require('gsd-core/...')` in `gsd-surface` command doc** — the four `require()` examples now derive the engine path from `runtimeConfigDir` (resolvable at runtime), and the reinstall hint corrects `npm i -g gsd-core` to `npm i -g @opengsd/gsd-core`. (#2116)
|
||||
5
.changeset/2198-security-dead-scan-exports.md
Normal file
5
.changeset/2198-security-dead-scan-exports.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2211
|
||||
---
|
||||
**Dead security scan exports removed; injection-scan docs corrected to match reality** — `scanEntropyAnomalies` and `shannonEntropy` were dead code with zero production callers (live hooks inline their own patterns for independence). REQ-SCAN-INJ-02/-03 now accurately describe what runs live (injection patterns, invisible Unicode) vs CI-only (base64-decode, codebase scan). (#2198)
|
||||
5
.changeset/agile-rams-climb.md
Normal file
5
.changeset/agile-rams-climb.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2222
|
||||
---
|
||||
**Non-frontend phases with `UI hint: no` are no longer blocked by the UI-SPEC gate** — the UI safety gate's token list included the bare token `UI`, which matched GSD's own `**UI hint**: no` metadata line and false-detected a UI, blocking backend/infra phases at /gsd-plan-phase. An explicit `UI hint: yes|no` is now authoritative and the hint line is no longer token-sniffed. (#2150)
|
||||
5
.changeset/bold-seals-chatter.md
Normal file
5
.changeset/bold-seals-chatter.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2223
|
||||
---
|
||||
**`stale-bake-guard` hermeticity fix (test-isolation)** — the readGsdEffectiveModelOverrides subtest no longer reads the developer's real `~/.gsd/defaults.json`; the resolver now accepts a homedir seam so the test sandboxes HOME. (#2152)
|
||||
5
.changeset/daring-cats-snooze.md
Normal file
5
.changeset/daring-cats-snooze.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2216
|
||||
---
|
||||
**`last_activity` now shows your local calendar day** — the clock seam derived the date by slicing a UTC instant, so in negative-UTC-offset zones during UTC's early evening the date-only `last_activity` field jumped a day ahead of the operator's actual date (and of `last_updated`'s local date). Operator-facing date fields now use a host-local calendar day while internal/cosmetic stamps stay UTC. (#2136)
|
||||
5
.changeset/eager-bears-wander.md
Normal file
5
.changeset/eager-bears-wander.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2215
|
||||
---
|
||||
**`milestone_name` is no longer clobbered with a delimiter-led fragment** — getMilestoneInfo's `##` heading regex was unanchored, so it matched a heading quoted inside backticks in the Milestones bullet and wrote garbage like `— Active Milestone` over the curated milestone name on every phase transition. Now consults the 🚧 marker first, anchors the regex to line start, strips the leading delimiter, and widens the preserve guard so a bad derive keeps the existing name. (#2135)
|
||||
5
.changeset/gallant-herons-rest.md
Normal file
5
.changeset/gallant-herons-rest.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2219
|
||||
---
|
||||
**`requirements mark-complete` no longer reports silent success when the traceability row is missing** — it OR-ed its checkbox and table-row writes into one flag, so a checkbox-only reconcile returned a payload byte-identical to a full reconcile while the traceability row stayed Pending (and re-run masked it as already-complete). It now surfaces `table_unmatched` for IDs whose checkbox reconciled but whose table row is absent, and treats a checked box with no table row as partial rather than done. (#2140)
|
||||
5
.changeset/gallant-rams-rally.md
Normal file
5
.changeset/gallant-rams-rally.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2188
|
||||
---
|
||||
**Discover third-party GSD Capabilities in a new Community Capability Registry.** — A non-endorsing discoverability catalog where authors register a Capability via a documentation PR; each entry carries a live latest-release badge and a per-entry GitHub Discussion for community ranking and comments. (#2188)
|
||||
5
.changeset/happy-seals-roam.md
Normal file
5
.changeset/happy-seals-roam.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2214
|
||||
---
|
||||
**`/gsd-fast` now appends Quick Task rows to STATE.md again** — the log_to_state column-count guard used an off-by-one awk formula (`NF-1`) that was always one too high, so the schema gate rejected the very table quick.md creates and silently skipped the STATE.md update. Also now supports the 6-column validate-mode table. (#2133)
|
||||
5
.changeset/jolly-jays-hop.md
Normal file
5
.changeset/jolly-jays-hop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2226
|
||||
---
|
||||
**Cross-AI review no longer silently drops the Codex/Claude/Gemini lanes on large plan sets** — the prompt-fed reviewer blocks in review.md invoked each CLI with no explicit timeout, so a slow source-grounded review was killed at the host default (~2 min) and the lane was silently lost. The workflow now directs a high Bash timeout and frames an empty output as a timeout (not the crash it was misdiagnosed as). (#2194)
|
||||
5
.changeset/merry-tigers-parade.md
Normal file
5
.changeset/merry-tigers-parade.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2193
|
||||
---
|
||||
**Discover third-party GSD Embeddable Orchestration System (EoS) integrations in a new EoS Registry.** — A non-endorsing discoverability catalog where host-integration authors register via a documentation PR; each entry declares its Host-Integration interface points, negotiated axes, and protocol version, with a live release badge and a per-entry GitHub Discussion for ranking and comments. (#2193)
|
||||
5
.changeset/plucky-sloths-forage.md
Normal file
5
.changeset/plucky-sloths-forage.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2224
|
||||
---
|
||||
**`state update-progress` no longer mangles the frontmatter and discards the progress suffix** — its Progress: regex matched the raw STATE.md including frontmatter, so the YAML `progress:` key was hit first (corrupting the frontmatter) while the body line stayed stale and was silently reverted on the next write, and any descriptive suffix after the progress bar was destroyed. It now targets the body line only and preserves the suffix. (#2177)
|
||||
5
.changeset/plucky-wasps-sprint.md
Normal file
5
.changeset/plucky-wasps-sprint.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2210
|
||||
---
|
||||
**GSD now drives VS Code through the Embeddable Orchestration System** — the VS Code extension is rewired through the negotiated imperative Host-Integration adapter (active `vscode.lm` model, engine hook bus, sandboxed storage), gains native Language Model Tools (GSD skills as `#gsd-*` tools) and `#runSubagent` dispatch, and runs as a Web Extension (no Node APIs). (#2103)
|
||||
5
.changeset/plucky-zebras-jump.md
Normal file
5
.changeset/plucky-zebras-jump.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2217
|
||||
---
|
||||
**`/gsd-ship` no longer silently drops the ship-status note from STATE on merge** — the track_shipping step committed the STATE ship-note after creating the PR but never pushed it, so on a fast merge the note stayed local-only and never reached the default branch. The ship-note is now pushed onto the PR branch with a `[ci skip]` trailer so it lands on merge without a redundant pipeline. (#2138)
|
||||
5
.changeset/tidy-badgers-caper.md
Normal file
5
.changeset/tidy-badgers-caper.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2225
|
||||
---
|
||||
**Linuxbrew users no longer lose all GSD-managed hooks after `brew upgrade node`** — normalizeNodePath only recognized macOS Homebrew Cellar paths, so on Linux the version-pinned node path stayed baked into hook commands and 404'd after a node bump (and reinstall couldn't repair it). It now rewrites any Homebrew Cellar path — Intel, Apple Silicon, Linuxbrew, custom HOMEBREW_PREFIX — to the stable `<prefix>/bin/node` symlink. (#2185)
|
||||
5
.changeset/witty-dogs-hop.md
Normal file
5
.changeset/witty-dogs-hop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2209
|
||||
---
|
||||
**Milestone audit no longer flags a not-yet-validated phase as a Nyquist failure** — a phase that was planned but never run through `validate-phase` now reports as NOT-VALIDATED (a "run validate-phase" TODO) instead of collapsing into PARTIAL alongside phases whose validation genuinely failed. (#2117)
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "gsd-core",
|
||||
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"source": "./",
|
||||
"author": {
|
||||
"name": "open-gsd",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gsd-core",
|
||||
"displayName": "GSD Core",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
||||
"author": {
|
||||
"name": "open-gsd",
|
||||
|
||||
105
.github/PULL_REQUEST_TEMPLATE/registry-entry.md
vendored
Normal file
105
.github/PULL_REQUEST_TEMPLATE/registry-entry.md
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
## Registry Entry PR
|
||||
|
||||
> **Using the wrong template?**
|
||||
> — Bug fix: use [fix.md](?template=fix.md)
|
||||
> — New feature (not a registry listing): use [feature.md](?template=feature.md)
|
||||
> — Enhancement to existing behavior: use [enhancement.md](?template=enhancement.md)
|
||||
|
||||
Full schema and process: [docs/registries/README.md](../../docs/registries/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Registry type
|
||||
|
||||
<!-- Check exactly one. -->
|
||||
|
||||
- [ ] Capability Registry entry — adds/updates one object in `docs/registries/capabilities.json`
|
||||
- [ ] EoS Registry entry — adds/updates one object in `docs/registries/eos.json`
|
||||
|
||||
## The entry
|
||||
|
||||
<!-- Paste the exact JSON object you added, unmodified. -->
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"type": "",
|
||||
"repo": "",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"enginesGsd": "",
|
||||
"install": "",
|
||||
"uninstall": "",
|
||||
"interactions": {},
|
||||
"discussion": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Required-field checklist
|
||||
|
||||
- [ ] `id`, `name`, `type`, `repo`, `description`, `author`, `license`, `enginesGsd`, `install`, `uninstall`, `interactions`, `discussion` are all present and non-empty
|
||||
- [ ] **(Capability entries only)** `interactions.loopExtensionPoints` is a non-empty subset of the 12 Loop Extension Points, `interactions.hookKinds` ⊆ `{step, contribution, gate}`, and `interactions.configKeys` / `requires` / `runtimeCompat` / `produces` / `consumes` are present (empty arrays are fine where nothing applies)
|
||||
- [ ] **(EoS entries only)** `protocolVersion` is an integer ≥ 1, `interactions.interfacePoints` is a non-empty subset of the six interface points, `interactions.profile` is one of `programmatic-cli` / `declarative-cli` / `ide`, and `interactions.axes` has exactly the eight required axis keys
|
||||
|
||||
## Ownership & non-endorsement
|
||||
|
||||
- [ ] `repo` links to a repository **I own or am the primary maintainer of** — not a fork, mirror, or someone else's project
|
||||
- [ ] I understand that inclusion in this registry means only that a maintainer merged this PR — it is **not** an endorsement, and GSD has not reviewed, tested, audited, or verified my solution or its claimed GSD interactions
|
||||
- [ ] I understand this entry is removed only for illegal content, malware, spam, or a dead/non-functional link — never for quality — and a maintainer may remove it on that narrow basis without further notice
|
||||
|
||||
## One entry, one PR
|
||||
|
||||
- [ ] This PR adds or updates exactly **one** entry, in exactly one of `capabilities.json` / `eos.json`
|
||||
- [ ] I have not bundled any other registry entry, code change, or unrelated docs change into this PR
|
||||
|
||||
## Generated file in sync
|
||||
|
||||
- [ ] I ran `npm run gen:registry` after editing the JSON source, and this PR includes the regenerated `docs/registries/capability-registry.md` or `docs/registries/eos-registry.md`
|
||||
- [ ] I did **not** hand-edit the generated `.md` file directly — all edits were made to the JSON source
|
||||
|
||||
## Documentation
|
||||
|
||||
> CI enforces `lint:docs` for any changeset fragment typed `Added` / `Changed` / `Deprecated` / `Removed` — it must also touch a file under `docs/`. The JSON source and its regenerated markdown, both under `docs/registries/`, satisfy this.
|
||||
|
||||
- [ ] This PR includes both the JSON source file and the regenerated markdown file under `docs/registries/`
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `npm run validate:registry` passes locally against my entry
|
||||
- [ ] `discussion` links to a GitHub Discussion in the `Registry` category (or notes that one will be created on merge, per [docs/registries/README.md](../../docs/registries/README.md))
|
||||
- [ ] `.changeset/` fragment added with an `Added` type describing the new listing
|
||||
|
||||
---
|
||||
|
||||
## Example filled entry
|
||||
|
||||
<!-- Reference only — delete this section before submitting your PR. -->
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "linear-issue-sync",
|
||||
"name": "Linear Issue Sync",
|
||||
"type": "capability",
|
||||
"repo": "some-org/gsd-cap-linear-sync",
|
||||
"description": "Mirrors ROADMAP.md items to Linear issues as a ship:post contribution.",
|
||||
"author": "Some Org <hello@some-org.example>",
|
||||
"license": "MIT",
|
||||
"enginesGsd": ">=1.6.0",
|
||||
"install": "gsd capability install https://github.com/some-org/gsd-cap-linear-sync.git#v1.0.0",
|
||||
"uninstall": "gsd capability remove linear-issue-sync",
|
||||
"interactions": {
|
||||
"loopExtensionPoints": ["ship:post"],
|
||||
"hookKinds": ["contribution"],
|
||||
"configKeys": ["linear-issue-sync.enabled"],
|
||||
"requires": [],
|
||||
"runtimeCompat": ["all"],
|
||||
"produces": ["linear-issue-links"],
|
||||
"consumes": ["ROADMAP.md"]
|
||||
},
|
||||
"discussion": "https://github.com/open-gsd/gsd-core/discussions/1234"
|
||||
}
|
||||
```
|
||||
63
.out-of-scope/plan-md-human-rendering.md
Normal file
63
.out-of-scope/plan-md-human-rendering.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Human-Readable Rendering of PLAN.md
|
||||
|
||||
GSD does not change PLAN.md's structural tag convention (`<task>`, `<action>`,
|
||||
`<tasks>`, `<files>`, `<verify>`, etc.) to improve how a PLAN.md renders when a
|
||||
human opens the raw file in a markdown viewer on GitHub/GitLab.
|
||||
|
||||
## Why this is out of scope
|
||||
|
||||
PLAN.md is a **machine artifact**, not a human-facing document. The docs are
|
||||
explicit:
|
||||
|
||||
- `docs/reference/plan-md.md` — a PLAN.md is *"an executable unit of work — a
|
||||
structured document that tells an executor agent exactly what to build and how
|
||||
to verify it was built correctly."*
|
||||
- `agents/gsd-planner.md` — *"Produce PLAN.md files that Claude executors can
|
||||
implement without interpretation. Plans are prompts, not documents that become
|
||||
prompts."*
|
||||
|
||||
PLAN.md is produced by `gsd-planner` and consumed by `gsd-executor`,
|
||||
`gsd-plan-checker`, `gsd-verifier`, and cross-AI review agents. There is no
|
||||
documented step in which a human opens, reads, reviews, or signs off on a
|
||||
PLAN.md — unlike `SUMMARY.md` / `VERIFICATION.md`, which are produced for human
|
||||
validation.
|
||||
|
||||
The reported symptom — alphabet-only tags like `<task>` / `<action>` tripping
|
||||
CommonMark's HTML-block rule so inner markdown renders as cramped run-on text —
|
||||
only manifests when a human views the raw file in a markdown renderer. It does
|
||||
**not** affect either machine consumer:
|
||||
|
||||
- Tag location/extraction is regex-based (`extractTaggedBlocks` in
|
||||
`src/markdown-sectionizer.cts`), operating on raw text, not rendered HTML.
|
||||
- Agents read the raw file content, not a rendered view.
|
||||
|
||||
```js
|
||||
// The extraction contract is a raw-text regex, indifferent to CommonMark
|
||||
// HTML-block folding:
|
||||
new RegExp(`<${escapedTag}>([\\s\\S]*?)</${escapedTag}>`, 'g')
|
||||
```
|
||||
|
||||
The proposed fixes (HTML-comment markers `<!-- task -->`, or underscored tag
|
||||
names `<task_node>`) would change a load-bearing machine convention that is
|
||||
duplicated across ~6 surfaces — the extractor regex, the `execute-plan` grep
|
||||
counter, `verify.cjs`, `decisions.cjs`, and the planner/executor schema docs. A
|
||||
drift between those surfaces silently breaks plan extraction (the executor finds
|
||||
zero tasks), which is a far worse failure than cosmetic rendering. The
|
||||
underscore option additionally increases token consumption on every plan read
|
||||
(longer tag names, repeated across every PLAN.md, read in full by the executor)
|
||||
and leaves the marker names visible as literal noise in any rendered view.
|
||||
Incurring that cost and risk to improve a rendering path that is not a
|
||||
documented use of PLAN.md does not align with the project's model of PLAN.md as
|
||||
an agent instruction set.
|
||||
|
||||
The same reasoning covers the report's secondary point (unquoted `|` in PLAN.md
|
||||
frontmatter breaking rendered markdown tables): that too is a human-render
|
||||
concern for a machine artifact.
|
||||
|
||||
**Revisit if** GSD ever introduces a human-review gate for PLAN.md — a step
|
||||
where a person reads and approves the plan before execution. At that point
|
||||
PLAN.md gains a documented human audience and its rendering becomes in-scope.
|
||||
|
||||
## Prior requests
|
||||
|
||||
- #2158 — "PLAN.md XML task tags trigger CommonMark HTML-block rule — task content renders as cramped run-on text"
|
||||
38
.out-of-scope/statusline-account-usage.md
Normal file
38
.out-of-scope/statusline-account-usage.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Statusline Account / Usage Segment (credential-reading, external API)
|
||||
|
||||
GSD's statusline does not read credentials or call external network APIs to
|
||||
display account-level resource state (5-hour / 7-day rate-limit utilization,
|
||||
usage windows, plan quotas).
|
||||
|
||||
## Why this is out of scope
|
||||
|
||||
The statusline draws its data boundary at **local, read-only** sources — see
|
||||
[`docs/adr/2164-statusline-scope-boundary.md`](../docs/adr/2164-statusline-scope-boundary.md).
|
||||
It refines the stdin payload Claude Code already sends (model, context meter,
|
||||
GSD-state) and may add a new *local* source (e.g. `git`), but it does not:
|
||||
|
||||
- read Claude Code's OAuth credentials (`.credentials.json`, or the macOS login
|
||||
Keychain via `security`), or
|
||||
- make authenticated network calls (e.g. `https://api.anthropic.com/api/oauth/usage`)
|
||||
to fetch data.
|
||||
|
||||
Reasons:
|
||||
|
||||
- **Trust surface.** A planning-workflow hook reading an OAuth token is a
|
||||
materially larger trust surface than any rendering concern — even read-only,
|
||||
never-logged, and opt-in. Credential custody belongs to the platform, not to
|
||||
a markdown planning tool.
|
||||
- **Unstable dependency.** The usage endpoint is undocumented; it can change or
|
||||
disappear and silently rot the feature.
|
||||
- **Scope.** Surfacing account/rate-limit state is a platform (Claude Code)
|
||||
concern. This matches the prior in
|
||||
[`temporal-context.md`](./temporal-context.md): *"Statusline / TUI re-entry is
|
||||
platform-level, not GSD-level."*
|
||||
|
||||
**Revisit if** a documented, first-party usage API — or a platform-provided
|
||||
value delivered to the hook without GSD reading credentials — becomes
|
||||
available. That would move usage display out of the excluded tier.
|
||||
|
||||
## Prior requests
|
||||
|
||||
- #2164 — "enhancement(statusline): opt-in 5-hour/7-day account usage segment"
|
||||
71
.pr-body-2100.md
Normal file
71
.pr-body-2100.md
Normal file
@@ -0,0 +1,71 @@
|
||||
## Linked Issue
|
||||
|
||||
Closes #2100
|
||||
|
||||
The linked issue carries the `approved-feature` label.
|
||||
|
||||
---
|
||||
|
||||
## Feature summary
|
||||
|
||||
Migrates **Windsurf** onto the ADR-1239 Embeddable Orchestration System — the largest of the EoS migrations. Folds all 10 residual `isWindsurf` branches onto the capability descriptor **and** wires GSD's write/command safety guards into Windsurf/Cascade's native blocking hook bus.
|
||||
|
||||
## What changed (highlights)
|
||||
|
||||
| File | What changed |
|
||||
|------|-------------|
|
||||
| `bin/install.js` | Folded 10 `isWindsurf` sites onto `hostBehaviors` (skipSharedHooksInstall, legacyDevinSkillsCleanup, installsCommandBodiesForWorkflowDelegation [#1629], verificationStyle); dropped 2 dead destructures + the dead `else if (isWindsurf)` agent arm; wired the Cascade hook-bridge install/uninstall; corrected stale comments |
|
||||
| `capabilities/windsurf/capability.json` | `hostBehaviors` block; `hooksSurface: "none"` → `"windsurf-hooks-json"` |
|
||||
| `src/runtime-hooks-surface.cts` | `writeWindsurfHooksJson`/`reconcileWindsurfHooksJson`/`removeWindsurfHooksJson` (Cursor-templated, Cascade's flat `{hooks:{<event>:[{command}]}}` shape) + event/script constants |
|
||||
| `hooks/gsd-windsurf-pre-write.js`, `gsd-windsurf-pre-command.js` | **New** Cascade-native blocking guard scripts (stdin JSON, exit-code-2 blocking) |
|
||||
| `gsd-core/bin/lib/capability-validator.cjs`, `src/runtime-config-adapter-registry.cts` | `windsurf-hooks-json` added to `VALID_HOOKS_SURFACES`, GATE A's `profile-marker-only` allowlist, and the `HooksSurface` union |
|
||||
| managed-hooks-registry / build-hooks / INVENTORY | registered the 2 new guard scripts |
|
||||
| tests / docs / changeset | `declarative-reference-windsurf` + `windsurf-hooks-bridge` (live blocking); matrix hookBus delta; changeset (`Changed`) |
|
||||
|
||||
## Implementation notes
|
||||
|
||||
- **Byte-parity concretely verified** (via the review): a real windsurf install rebuilt through the golden-parity harness → 327 files, 0 drift; only `windsurf.json` gains the 2 new script hashes. cursor/trae re-verified 0 drift. The load-bearing #1629 command-body copy (`.windsurf/gsd-core/commands/gsd/*.md` for local installs) is intact.
|
||||
- **The hook-bridge is faithful, not padding.** Cascade's `hooks.json` genuinely supports blocking via exit code 2 (confirmed against docs.windsurf.com / docs.devin.ai). Only **2 of GSD's 6 guards** faithfully map — the worktree-path guard (→ `pre_write_code`) and a destructive-command guard (→ `pre_run_command`). The 4 advisory guards + `pre_mcp_tool_use` + the 5 `post_*` logging events are **deliberately not wired**: Cascade's hook bus has no context-injection channel to carry GSD's advisory reminders faithfully, and GSD has no MCP-tool policy — porting them would be non-functional padding. This is the same faithful-subset pattern used for codebuddy #2098 / copilot #2099, and it satisfies AC4's testable requirement ("a real blocking hook rejecting a disallowed write/command").
|
||||
- **Security (reviewed, clean).** The guard scripts parse untrusted stdin and spawn `git rev-parse` — command injection via `file_path` was **refuted** (argv array, no `shell:true`, PATH-resolved git). No traversal / prototype-pollution (frozen 2-event set, fixed script names). The pre-command guard was **hardened post-review**: a tokenize-based classifier (no catastrophic-backtracking regex — a 200k-char pathological input now completes in ~32ms via a 4096-char cap), catching prefixed `rm -rf` forms (`sudo`/`env`/`/bin/rm`) and refspec force-pushes (`HEAD:main`, `+main`), and a fail-closed false-positive fixed (a `feature/main-fix` branch or a trailing-`# ...main` comment no longer wrongly blocks a legit force-push). Guards fail-open (never wedge Cascade) by design.
|
||||
- **Golden mechanics.** `.windsurf/hooks.json` is golden-excluded by basename (like settings.json); the 2 guard scripts under `hooks/` are windsurf-specific → only windsurf.json regenerates, additively.
|
||||
|
||||
## Spec compliance (acceptance criteria)
|
||||
|
||||
- [x] Golden parity: byte-identical for the folds across all 16 runtimes (windsurf.json regen is the additive hook-script delta only)
|
||||
- [x] Driven through the descriptor — zero live `runtime==='windsurf'`/`isWindsurf` branches (AC2 guard over 4 files)
|
||||
- [x] Every axis populated + `capability-validator`-clean (`runtime`/dispatch stay `undocumented` per the cited search trail)
|
||||
- [x] UPGRADE implemented AND exercised by a test driving a real blocking hook (exit-2 on a disallowed write/command)
|
||||
- [x] `negotiateHostCapabilities` fail-closes for windsurf (test)
|
||||
- [x] `gsd-test` green (linux node22/24); no other-runtime regression (cursor.json byte-identical)
|
||||
- [x] Docs (matrix hookBus delta) + changeset (`Changed`)
|
||||
|
||||
## Testing
|
||||
|
||||
- [x] macOS (real install byte-parity harness + live guard-script exit-2 probing + ReDoS timing)
|
||||
- [x] Windows (backslash; Windows destructive-command forms handled) — GitHub CI
|
||||
- [x] Linux (`gsd-test`)
|
||||
- [x] Runtimes: Windsurf (primary) + all 16 golden fixtures (only windsurf's 2 new scripts)
|
||||
|
||||
---
|
||||
|
||||
## Scope confirmation
|
||||
|
||||
- [x] Windsurf only; other runtimes byte-identical. The hook-bridge's faithful 2-guard scope (vs. the AC's fuller event list) is disclosed above — the unbridged events have no faithful GSD logic / Cascade channel.
|
||||
- [x] Cascade envelope/schema is best-effort per the official docs (guards fail-open if the live schema differs, never breaking Cascade); flagged for a live-Cascade schema confirmation follow-up.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [x] matrix (## windsurf hookBus/hooksSurface delta + the not-ported-guards rationale); English
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] `Closes #2100`; issue has `approved-feature`
|
||||
- [x] Acceptance criteria met (faithful hook-bridge scope disclosed)
|
||||
- [x] `gsd-test` green
|
||||
- [x] New tests cover the folds (AC2 guard) + the blocking hook bus (live exit-2) + fail-closed negotiation
|
||||
- [x] `.changeset/` fragment (`Changed`)
|
||||
- [x] No new dependencies
|
||||
|
||||
## Breaking changes
|
||||
|
||||
None at landing. New Windsurf install output is additive: 2 guard scripts + a `.windsurf/hooks.json` registering blocking pre-hooks. No skill, agent, workflow, or path is removed or altered; the guards fail-open.
|
||||
@@ -124,6 +124,9 @@ Module owning runtime identity normalization at runtime-selection seams. Canonic
|
||||
### Host-Integration Interface
|
||||
Pure, additive, no-I/O Module owning the versioned, negotiated contract over the six host-integration interface points (command, dispatch, model, hooks, state, artifact) — ADR-1239 Phase A. Extends the ADR-1016 runtime descriptor with eight closed-vocabulary axes carried under `capability.json` `runtime.hostIntegration`: `embeddingMode` (`imperative|declarative`), `commandSurface` (`slash-file|slash-programmatic|slash-toml|palette|prose-only`), `dispatch` (`{namedDispatch,nested,maxDepth,background,backgroundDispatch,subagentToolkit}`), `modelMode` (`active|passive`), `hookBus` (`host|engine|none`), `stateIO` (`filesystem|sandboxed-storage|session-log-append`), `transport` (`mcp|native-extension`), `runtime` (`node|bun|sandboxed-web|python|go|rust|electron|other`). Interface: `negotiateHostCapabilities(host, engine?) → { protocolVersion, effective, points, warnings }` enforcing the trust-boundary invariant `effective ⊆ host-declared ∩ engine-known` (never augment with an undeclared or unknown/future-`protocolVersion` value — fail-closed via the most-restrictive-known `SAFE_DEFAULTS`); `degradationFor(point, axes) → { level, fallback }` (a pure Full/Degraded/Absent ladder table, never throws); `profileOf(axes) → 'programmatic-cli'|'declarative-cli'|'ide'|null`; plus `PROTOCOL_VERSION` (integer, starts at 1 — distinct from the package `version`/`engines.gsd` semver), `HOST_INTEGRATION_AXES` (the frozen closed vocabulary, single source of truth), `PROFILE_BASELINES`, and `shouldFlattenDispatch(dispatch) → boolean` (ADR-1239 Phase B / #1708 — graduates the #853 rule: returns `true` = run the orchestrator inline UNLESS the host is documented to background a nesting-capable orchestrator (`background === true && backgroundDispatch === true`); fail-closed to inline; exposed to the plan/execute workflows via the `gsd_run query dispatch-should-flatten --raw` CLI, which replaced the former scattered `RUNTIME === 'codex'` prose check). The runtime-descriptor validator (`gsd-core/bin/lib/capability-validator.cjs` `validateRuntimeBody`) mirrors the closed vocabulary inline (exported as `_HOST_INTEGRATION_VOCAB`) and is kept in lock-step by the parity guard `tests/host-integration-validator-parity.test.cjs`. Orthogonal axes (resolved explicitly per ADR-1239 Phase A): `commandStyle` (GSD emission style, retained) vs `commandSurface` (host surface type); `hookEvents` dialect vs `hookBus` ownership (a host with `hooksSurface:none` may still be `hookBus:host` — e.g. opencode); `runtimeCompat` (feature→host) vs these negotiated runtime→engine axes. Phase A defined the interface; Phase B (#1679) wires it incrementally — `destSubpath` write-confinement (#1704) and the typed documentation-sourced #853 dispatch-flatten (#1708, the first consumer of a negotiated `dispatch` axis); adapters/MCP/host-bindings remain Phases C–E. Source of truth: `gsd-core/bin/lib/host-integration.cjs` (generated from `src/host-integration.cts`). See ADR-1239 and ADR-1016.
|
||||
|
||||
### Statusline
|
||||
Host-integration hook (`hooks/gsd-statusline.js`) that renders the session status line: model name, context-window meter, workspace directory, and the GSD-state segment (`formatGsdState()` projecting `.planning/` STATE.md). Opt-in segments are gated by `.planning/config.json` keys (`statusline.show_last_command`, `statusline.context_position`, plus the approved `statusline.show_context_tokens` and `statusline.state_format`), each registered across `gsd-core/bin/shared/config-schema.manifest.json` + `src/config.cts` + the `loadConfig` whitelist + `docs/CONFIGURATION.md`. The compact GSD-state format consumes the canonical status vocabulary from `normalizeStateStatus()` (STATE.md Document Module) rather than a parallel keyword list. **Data-source boundary (ADR-2164):** the statusline sources only local, read-only data — it refines the stdin payload Claude Code already sends and may add a new *local* source (e.g. `git`), but does not read credentials or call external/network APIs for data; account/usage/platform-level state is out of scope.
|
||||
|
||||
### Install Engine Module
|
||||
Module owning the layout-driven runtime-artifact install pipeline — `installRuntimeArtifacts`, `uninstallRuntimeArtifacts`, `installOpencodeFamilySkills`, and their cluster helpers (`_copyStaged`, `_snapshotDir`/`_restoreDir`, legacy-migration + GSD-entry pruning, user-artifact preserve/restore). Extracted from the 12k-line `bin/install.js` (ADR-1239 Phase B, #1679) so adapters import the engine instead of reaching into the installer. Commit-attribution resolution stays in `bin/install.js` and is injected via a `resolveAttribution` parameter (the engine takes no config I/O). Source: `src/install-engine.cts` -> `gsd-core/bin/lib/install-engine.cjs`.
|
||||
|
||||
@@ -196,6 +199,12 @@ ADR-857 phase 3b seam that merges capability-declared config slices into the `lo
|
||||
### Capability Registry Overlay
|
||||
Runtime seam (`gsd-core/bin/lib/capability-loader.cjs`, ADR-1244 D2) that composes the frozen first-party Capability Registry (`capability-registry.cjs`) with a validated installed overlay of third-party capability manifests discovered at load time. Install roots are global (`$GSD_HOME/.gsd/capabilities/<id>/capability.json`, where `GSD_HOME` defaults to `~`) and project (`<projectRoot>/.gsd/capabilities/<id>/capability.json`). Primary interface: `loadRegistry({ includeInstalled }) → registry` — when `includeInstalled` is true the overlay is merged via the canonical `buildRegistry` so all derived views (bySkill, byAgent, byLoopPoint, configKeys) cover first-party and overlay entries identically. First-party always wins: any overlay entry whose id, owned skill/agent stem, or federated config key collides with first-party, or whose id uses a reserved `gsd-`/`gsd-core-`/`anthropic-` prefix, is rejected at load time. Load-time re-gate: an overlay failing schema validation or whose `engines.gsd` semver range does not satisfy the running GSD version is skipped with a warning and never crashes the load loop. Per-hook-kind policy: a skipped capability that declared a `gate`-kind hook fails CLOSED (the loop resolver injects a blocking gate); skipped `step` or `contribution` capabilities skip open. A capability dir whose co-located ledger entry carries an in-flight `_pending` intent (a crashed/uncommitted install or upgrade, ADR-1244 Phase 4) is skipped OPEN (never activated until reconciliation commits or rolls it back). #1459 user-owned consent gate: a PROJECT-scope overlay is activated (declarative surfaces AND command dispatch) ONLY when the user-owned Capability Consent Store holds a record for `(realpath(projectRoot), id)` whose stored `contentHash` equals the bundle content hash the loader RECOMPUTES at load (`bundleContentHash(capDir)` over the whole on-disk bundle) — NOT the repo-plantable ledger integrity nor the executable-only disclosure signature — otherwise the cap is DISCOVERED-BUT-INACTIVE (a warning carrying `kind:'unconsented'`, no surfaces, empty commandRoots), so a forged/cloned in-repo project ledger or any post-consent tamper no longer activates anything; GLOBAL scope (under the user's own home) is trusted without a record, and the global-vs-project root dedup/escalation is realpath-keyed so a symlinked `GSD_HOME` aliasing the project root cannot bypass the gate (finding 1). The consent lookup is wrapped to fail CLOSED (inactive); both the per-scope ledger AND the `capability.json` manifest are read via the shared bounded `readSmallRegularFile` (a repo-planted FIFO/oversized ledger or manifest can no longer hang or OOM the loader — finding 2). The loader reuses the ledger's shared `isValidLedgerEntry` for committed-entry parity. Consumers wired to the overlay-aware registry: `config-loader.cjs`, `config-schema.cjs`, `capability-state.cjs`, `loop-resolver.cjs`.
|
||||
|
||||
### Community Capability Registry
|
||||
Human-facing discoverability catalog (`docs/registries/capability-registry.md`, generated from `docs/registries/capabilities.json`; issue #2182) listing third-party Feature Capabilities registered by a docs PR so a solo developer can find one before installing it. Distinct from **Capability Registry** (the generated runtime manifest compiled from first-party `capability.json` declarations, ADR-894) and **Capability Registry Overlay** (the runtime seam that merges an installed third-party manifest into that generated registry at load time, ADR-1244 D2): this registry is a static document rendered by `scripts/gen-registry.cjs`, not a runtime data structure or loader. Each entry enumerates the capability's Loop Extension Points and hook kinds so a reader can judge blast radius before running `gsd capability install`, and declares its `engines.gsd` range. Inclusion is an explicit non-endorsement — a maintainer merged a link, nothing more — per `docs/registries/README.md`.
|
||||
|
||||
### EoS Registry
|
||||
Human-facing discoverability catalog (`docs/registries/eos-registry.md`, generated from `docs/registries/eos.json`; issue #2182) listing third-party Embeddable Orchestration System (EoS) host integrations — projects that embed GSD as an orchestration engine behind the ADR-1239 six-interface-point Host-Integration Interface. Entries are registered by the same docs-PR process, schema conventions, and non-endorsement stance as the **Community Capability Registry**, but enumerate the six interface points, the eight negotiated axes, and `protocolVersion` in place of Loop Extension Points and hook kinds. It has no generated-manifest or Capability Registry Overlay counterpart: an ADR-1239 host integration runs inside the third-party host, not inside GSD's own capability loader, so there is nothing for a runtime registry to merge. See `docs/registries/README.md` for the full entry schema.
|
||||
|
||||
### Capability Validator
|
||||
Shared conformance validator (`gsd-core/bin/lib/capability-validator.cjs`, ADR-1244 D2) extracted from `scripts/gen-capability-registry.cjs` so the build-time generator and the runtime overlay loader share one validator implementation. Exports the same `validateCapability(manifest)` surface consumed by both the generator (build-time) and `capability-loader.cjs` (runtime). Generative-parity is CI-guarded: a drift between the generator's validation logic and the extracted module is a hard failure. Callers that previously inlined validation against the generator's internal helpers are migrated to import this module directly. Source of truth: `gsd-core/bin/lib/capability-validator.cjs`.
|
||||
|
||||
|
||||
318
bin/install.js
318
bin/install.js
@@ -278,6 +278,28 @@ const GSD_CURSOR_HOOK_SCRIPTS = [
|
||||
// Marker comment embedded in managed hook entries so GSD can find+remove them.
|
||||
const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
||||
|
||||
// #2100 Stage 2 — Windsurf/Cascade lifecycle hook constants.
|
||||
// Windsurf/Cascade reads hook configs from <project-root>/.windsurf/hooks.json
|
||||
// (local) or ~/.codeium/windsurf/hooks.json (global) with the shape
|
||||
// { hooks: { <event>: [ { command, ... } ] } } — note: no top-level `version`
|
||||
// field, and each entry carries a bare `command` shell string (no `type`
|
||||
// field), unlike Cursor's hooks.json. GSD registers two managed BLOCKING
|
||||
// hooks (exit code 2 to block, vs. Cursor's stdout-JSON form):
|
||||
// pre_write_code → gsd-windsurf-pre-write.js (write-path guard)
|
||||
// pre_run_command → gsd-windsurf-pre-command.js (destructive-command guard)
|
||||
// Cascade has no context-injection channel, so the 4 advisory hooks GSD
|
||||
// registers on Cursor (sessionStart, postToolUse, stop, subagentStart/Stop)
|
||||
// have no Windsurf counterpart and are deliberately NOT ported.
|
||||
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
||||
// https://docs.devin.ai/desktop/cascade/hooks
|
||||
const GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT = 'gsd-windsurf-pre-write.js';
|
||||
const GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT = 'gsd-windsurf-pre-command.js';
|
||||
// All GSD-managed Windsurf hook scripts (used by uninstall cleanup).
|
||||
const GSD_WINDSURF_HOOK_SCRIPTS = [
|
||||
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
||||
];
|
||||
|
||||
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.sh hooks).
|
||||
// git-cmd.js does not start with "gsd-" (shared classifier for #3129), gsd-graphify-rebuild.sh does.
|
||||
const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh'];
|
||||
@@ -485,6 +507,7 @@ const {
|
||||
installRuntimeArtifacts,
|
||||
uninstallRuntimeArtifacts,
|
||||
installOpencodeFamilySkills,
|
||||
_installNativePluginIfDeclared,
|
||||
_copyStaged,
|
||||
hasExistingSymlinkBetween,
|
||||
preserveUserArtifacts,
|
||||
@@ -534,7 +557,7 @@ if (hasMinimal && _profileArgRaw) {
|
||||
|
||||
function selectRuntimesFromArgs(runtimeArgs) {
|
||||
if (runtimeArgs.includes('--all')) {
|
||||
return ['claude', 'kimi', 'kilo', 'opencode', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
||||
return ['claude', 'kimi', 'kilo', 'opencode', 'pi', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
||||
}
|
||||
if (runtimeArgs.includes('--both')) {
|
||||
return ['claude', 'opencode'];
|
||||
@@ -543,6 +566,7 @@ function selectRuntimesFromArgs(runtimeArgs) {
|
||||
const selected = [];
|
||||
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
||||
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
||||
if (runtimeArgs.includes('--pi')) selected.push('pi');
|
||||
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
||||
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
||||
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
||||
@@ -688,7 +712,7 @@ const banner = '\n' +
|
||||
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
||||
' Git. Ship. Done.\n' +
|
||||
' A meta-prompting, context engineering and spec-driven\n' +
|
||||
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy and ZCode.\n';
|
||||
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy, ZCode and pi.\n';
|
||||
|
||||
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
||||
// Returns the path string, '' for an empty equals-form value, or null when the
|
||||
@@ -1115,9 +1139,10 @@ function writeSettings(settingsPath, settings) {
|
||||
* Used by Codex TOML and OpenCode agent file generators to embed per-agent
|
||||
* model assignments so that model_overrides is respected on non-Claude runtimes (#2256).
|
||||
*/
|
||||
function readGsdGlobalModelOverrides() {
|
||||
function readGsdGlobalModelOverrides(options = {}) {
|
||||
try {
|
||||
const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
|
||||
const home = options.homedir ? options.homedir() : os.homedir();
|
||||
const defaultsPath = path.join(home, '.gsd', 'defaults.json');
|
||||
if (!fs.existsSync(defaultsPath)) return null;
|
||||
const raw = fs.readFileSync(defaultsPath, 'utf-8');
|
||||
const parsed = JSON.parse(raw);
|
||||
@@ -1154,8 +1179,8 @@ function readGsdGlobalModelOverrides() {
|
||||
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
||||
* source defines `model_overrides`.
|
||||
*/
|
||||
function readGsdEffectiveModelOverrides(targetDir = null) {
|
||||
const global = readGsdGlobalModelOverrides();
|
||||
function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
||||
const global = readGsdGlobalModelOverrides(options);
|
||||
|
||||
let projectOverrides = null;
|
||||
if (targetDir) {
|
||||
@@ -5819,6 +5844,37 @@ function removeCursorHooksJson(targetDir) {
|
||||
return hooksSurface.removeCursorHooksJson(targetDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* #2100 Stage 2 — Write GSD-managed Windsurf/Cascade lifecycle hooks into
|
||||
* <targetDir>/hooks.json. Both managed hook scripts
|
||||
* (gsd-windsurf-pre-write.js, gsd-windsurf-pre-command.js) are copied from
|
||||
* the GSD hooks/ source to <targetDir>/hooks/ first, so the hooks.json
|
||||
* entries never reference a script that wasn't installed. Mirrors
|
||||
* writeCursorHooksJson's structure; Cascade's blocking protocol (exit code 2)
|
||||
* and entry shape (bare `command` string, no `type` field) are distinct from
|
||||
* Cursor's.
|
||||
*
|
||||
* @param {string} targetDir - The Windsurf config dir (global: ~/.codeium/windsurf; local: .windsurf)
|
||||
* @param {string} src - The GSD install source root (for copying hook scripts)
|
||||
* @param {{ platform?: string }} opts
|
||||
* @returns {{ hooksJsonPath: string, changed: boolean }}
|
||||
*/
|
||||
function writeWindsurfHooksJson(targetDir, src, opts) {
|
||||
return hooksSurface.writeWindsurfHooksJson(targetDir, src, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all GSD-managed Windsurf/Cascade lifecycle hook entries from
|
||||
* hooks.json. User-owned entries are preserved. If the file becomes empty,
|
||||
* it is removed.
|
||||
*
|
||||
* @param {string} targetDir - The Windsurf config dir
|
||||
* @returns {{ changed: boolean }}
|
||||
*/
|
||||
function removeWindsurfHooksJson(targetDir) {
|
||||
return hooksSurface.removeWindsurfHooksJson(targetDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* #786 — Build the GSD-managed GitHub Copilot lifecycle hook config object.
|
||||
*
|
||||
@@ -6851,7 +6907,10 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
||||
// consumer, so its former `&& !isKimi` uninstall guards were removed.
|
||||
// #2096: isAntigravity dropped — unused in this function.
|
||||
// #2098: isCodebuddy dropped — unused in this function.
|
||||
const { isOpencode, isCodex, isCopilot, isCursor, isWindsurf, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
// #2099: isCopilot dropped — both Copilot side-effect branches below are now
|
||||
// gated on resolveInstallPlan(runtime).installSurface === 'copilot-instructions'.
|
||||
// #2100: isWindsurf dropped — unused in this function.
|
||||
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
const dirName = getDirName(runtime);
|
||||
|
||||
// Get the target directory based on runtime and install type. Cline local
|
||||
@@ -6880,7 +6939,13 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
||||
// #786: AGENTS.md lives at the repo root (outside targetDir) for local Copilot
|
||||
// installs, so its cleanup must run even when .github (targetDir) was already
|
||||
// removed — i.e. BEFORE the "target directory missing" early-return below.
|
||||
if (isCopilot && !isGlobal) {
|
||||
// #2099: descriptor-driven via resolveInstallPlan(runtime).installSurface ===
|
||||
// 'copilot-instructions' (was hardcoded `isCopilot`). Mirrors the install-time
|
||||
// gate at the 'copilot-instructions' branch below (~line 10471 equivalent),
|
||||
// which writes this same repo-root AGENTS.md only for local ('!isGlobal')
|
||||
// installs — 'copilot-instructions' is unique to copilot's descriptor, so
|
||||
// this is byte-parity.
|
||||
if (resolveInstallPlan(runtime).installSurface === 'copilot-instructions' && !isGlobal) {
|
||||
const agentsMdPath = path.join(process.cwd(), 'AGENTS.md');
|
||||
if (fs.existsSync(agentsMdPath)) {
|
||||
const content = fs.readFileSync(agentsMdPath, 'utf8');
|
||||
@@ -7064,7 +7129,10 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
||||
}
|
||||
|
||||
// 1b. Non-layout Copilot side-effect: copilot-instructions.md cleanup
|
||||
if (isCopilot) {
|
||||
// #2099: descriptor-driven via resolveInstallPlan(runtime).installSurface ===
|
||||
// 'copilot-instructions' (was hardcoded `isCopilot`), mirroring the same
|
||||
// gate used at the install-time 'copilot-instructions' branch.
|
||||
if (resolveInstallPlan(runtime).installSurface === 'copilot-instructions') {
|
||||
const instructionsPath = path.join(targetDir, 'copilot-instructions.md');
|
||||
if (fs.existsSync(instructionsPath)) {
|
||||
const content = fs.readFileSync(instructionsPath, 'utf8');
|
||||
@@ -7179,6 +7247,38 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
// 1b-windsurf. Descriptor-driven hook-bus cleanup (ADR-1239 / #2100 Stage 2):
|
||||
// remove GSD-managed Cascade hook entries from hooks.json and clean up the
|
||||
// managed hook scripts. Gated on resolveInstallPlan(runtime).hooksSurface
|
||||
// === 'windsurf-hooks-json' (mirrors the kimi-hooks-toml gate above) —
|
||||
// NOT the shared hostBehaviors.hooksJsonSurface flag the Cursor block above
|
||||
// uses, since that flag drives Cursor's own remove function + script list
|
||||
// and is not (and must not be) set for Windsurf.
|
||||
if (resolveInstallPlan(runtime).hooksSurface === 'windsurf-hooks-json') {
|
||||
const windsurfHooksJsonCleanup = removeWindsurfHooksJson(targetDir);
|
||||
if (windsurfHooksJsonCleanup.changed) {
|
||||
removedCount++;
|
||||
console.log(` ${green}✓${reset} Removed GSD-managed Windsurf hooks from hooks.json`);
|
||||
}
|
||||
// Remove all GSD-managed hook scripts (pre_write_code, pre_run_command).
|
||||
const windsurfHooksDir = path.join(targetDir, 'hooks');
|
||||
for (const script of GSD_WINDSURF_HOOK_SCRIPTS) {
|
||||
const p = path.join(windsurfHooksDir, script);
|
||||
try {
|
||||
if (fs.existsSync(p)) {
|
||||
fs.unlinkSync(p);
|
||||
removedCount++;
|
||||
}
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
// Prune hooks/ if empty.
|
||||
try {
|
||||
if (fs.existsSync(windsurfHooksDir) && fs.readdirSync(windsurfHooksDir).length === 0) {
|
||||
fs.rmdirSync(windsurfHooksDir);
|
||||
}
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
// 1c. Claude local: remove flat gsd-*.md commands from commands/ (current layout,
|
||||
// #1367 fix). Also remove legacy commands/gsd/ subdirectory from prior installs.
|
||||
if (!isGlobal && _hostBehaviors(runtime).localInstallStyle === 'legacy-flat') {
|
||||
@@ -8223,7 +8323,11 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// settings-json-adjacent runtime, so the `&& !isKimi` term below was removed.
|
||||
// #2096: isAntigravity dropped — unused in this function.
|
||||
// #2098: isCodebuddy dropped — unused in this function.
|
||||
const { isOpencode, isCodex, isCopilot, isCursor, isWindsurf, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
// #2099: isCopilot dropped — was only used in the hooks-tracking conditional
|
||||
// above, now covered by hostBehaviors.skipSharedHooksInstall.
|
||||
// #2100: isWindsurf dropped — was only used in the hooks-tracking conditional
|
||||
// above, now covered by hostBehaviors.skipSharedHooksInstall.
|
||||
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
const gsdDir = path.join(configDir, 'gsd-core');
|
||||
// #1367: Claude local now writes flat gsd-*.md files at commands/ (not commands/gsd/).
|
||||
// Claude local uses flatCommandsDir instead for manifest recording.
|
||||
@@ -8335,7 +8439,11 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isTrae` was removed.
|
||||
// #2095: kimi is now a hooks/ consumer (native config.toml [[hooks]] bus) —
|
||||
// the redundant `&& !isKimi` was removed so its hook files are tracked too.
|
||||
if (!isCodex && !isCopilot && _hostBehaviors(runtime).skipSharedHooksInstall !== true && !isWindsurf) {
|
||||
// #2099: Copilot's exclusion is likewise descriptor-driven (copilot declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
||||
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
||||
if (!isCodex && _hostBehaviors(runtime).skipSharedHooksInstall !== true) {
|
||||
const hooksDir = path.join(configDir, 'hooks');
|
||||
if (fs.existsSync(hooksDir)) {
|
||||
// Drive from INSTALLED_HOOK_FILES (the canonical HOOKS_TO_COPY set from
|
||||
@@ -8738,7 +8846,24 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// _DESCRIPTOR_AGENTS_RUNTIMES below, so its legacy converter-dispatch branch
|
||||
// (the `isCodebuddy` arm calling convertClaudeAgentToCodebuddyAgent) was
|
||||
// unreachable dead code and was removed rather than re-gated.
|
||||
const { isOpencode, isZcode, isCodex, isCopilot, isCursor, isWindsurf, isAugment, isTrae, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
// #2099: isCopilot dropped — copilot is also in _DESCRIPTOR_AGENTS_RUNTIMES
|
||||
// below, so its three legacy-agent-loop branches (the path-rewrite skip,
|
||||
// the converter dispatch, and the .agent.md destName ternary) were
|
||||
// unreachable dead code and were removed rather than re-gated; the
|
||||
// .agent.md suffix now lives on hostBehaviors.agentFileExtension in
|
||||
// src/install-engine.cts, and the skipSharedHooksInstall check above no
|
||||
// longer needs `&& !isCopilot`.
|
||||
// #2100: isWindsurf dropped — its four former isWindsurf-gated branches
|
||||
// (legacy .devin/skills/gsd-* cleanup, the #1629 command-bodies copy, the
|
||||
// workflow-verification report, and the shared-hooks-install exclusion) are
|
||||
// now descriptor-driven via hostBehaviors.legacyDevinSkillsCleanup,
|
||||
// hostBehaviors.installsCommandBodiesForWorkflowDelegation,
|
||||
// hostBehaviors.verificationStyle === 'windsurf-workflows', and
|
||||
// hostBehaviors.skipSharedHooksInstall respectively; its legacy-agent-loop
|
||||
// converter arm was likewise unreachable dead code (windsurf is in
|
||||
// _DESCRIPTOR_AGENTS_RUNTIMES) and was removed above.
|
||||
// #2101: isZcode dropped — folded onto hostBehaviors.skipSharedHooksInstall.
|
||||
const { isOpencode, isCodex, isCursor, isAugment, isTrae, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
const plan = resolveInstallPlan(runtime);
|
||||
const dirName = getDirName(runtime);
|
||||
const src = path.join(__dirname, '..');
|
||||
@@ -9248,7 +9373,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// dirs from pre-#1615 installs. #1615 moved Windsurf to .windsurf/workflows/
|
||||
// but never cleaned up the old .devin/skills/ layout (#1085). User-owned
|
||||
// content is preserved (non-gsd- dirs, gsd-dev-preferences, symlinks).
|
||||
if (isWindsurf && !isGlobal) {
|
||||
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
||||
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
||||
// declares it, so this is byte-parity).
|
||||
if (_hostBehaviors(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
||||
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
||||
if (removedCount > 0) {
|
||||
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
||||
@@ -9295,7 +9423,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
} else {
|
||||
failures.push('agents/gsd.yaml');
|
||||
}
|
||||
} else if (isWindsurf) {
|
||||
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
||||
// hostBehaviors.verificationStyle === 'windsurf-workflows' (extends the
|
||||
// same mechanism the 'kimi' verificationStyle branch above uses; windsurf
|
||||
// is the only runtime that declares this value, so this is byte-parity).
|
||||
} else if (_hostBehaviors(runtime).verificationStyle === 'windsurf-workflows') {
|
||||
if (isGlobal) {
|
||||
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
||||
} else {
|
||||
@@ -9365,6 +9497,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
||||
// hostBehaviors.localCommandsViaRules.
|
||||
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
||||
} else if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
||||
// pi (ADR-1239 / #2102 Stage 1): plugin-only install — pi's /gsd command is
|
||||
// registered programmatically by the native extension (pi/gsd.cjs →
|
||||
// extensions/gsd.cjs, staged separately below) and dispatches in-process
|
||||
// through the embedded gsd-core command-routing hub. pi has no host-read
|
||||
// markdown surface (unlike Claude/OpenCode/etc., which scan commands/ or
|
||||
// command/ directories), so writing flat gsd-<cmd>.md files here would be
|
||||
// dead weight the extension never reads. Skip the flat-commands fallback
|
||||
// entirely for pluginOnlyInstall runtimes.
|
||||
console.log(` ${green}✓${reset} pi: /gsd registered via native extension (no declarative command files)`);
|
||||
} else {
|
||||
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
||||
// commands from .claude/commands/ using the filename stem as the command
|
||||
@@ -9435,6 +9577,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Native-extension/plugin staging for runtimes OUTSIDE the layout-driven
|
||||
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1: pi). OpenCode/Kilo
|
||||
// already get their nativePlugin file from installOpencodeFamilyArtifacts
|
||||
// (called inside the _isSkillsRuntime branch, since both declare a non-empty
|
||||
// artifactLayout) — guard on `!_isSkillsRuntime` so this standalone call never
|
||||
// double-stages their plugin file. A runtime like pi, whose artifactLayout is
|
||||
// intentionally empty for both scopes (`_isSkillsRuntime` is false), still
|
||||
// needs its declared hostBehaviors.nativePlugin file copied into targetDir.
|
||||
if (!_isSkillsRuntime && _hostBehaviors(runtime).nativePlugin) {
|
||||
_installNativePluginIfDeclared(runtime, targetDir, _hostBehaviors(runtime), src);
|
||||
}
|
||||
|
||||
// Copy gsd-core skill with path replacement
|
||||
// Preserve user-generated files before the wipe-and-copy so they survive re-install
|
||||
const skillSrc = path.join(src, 'gsd-core');
|
||||
@@ -9486,7 +9640,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// this copy, every /gsd-* workflow in Cascade references a missing file and the LLM
|
||||
// cannot execute the command body. Surfaced by the #1629 regression test after the
|
||||
// original adversarial review of #1622 missed it.
|
||||
if (isWindsurf && !isGlobal) {
|
||||
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
||||
// hostBehaviors.installsCommandBodiesForWorkflowDelegation (windsurf is the
|
||||
// only runtime that declares it, so this is byte-parity — the #1629 fix
|
||||
// itself is unchanged).
|
||||
if (_hostBehaviors(runtime).installsCommandBodiesForWorkflowDelegation && !isGlobal) {
|
||||
const commandsSrc = path.join(src, 'commands', 'gsd');
|
||||
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
||||
if (fs.existsSync(commandsSrc)) {
|
||||
@@ -9556,8 +9714,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// `--minimal` actually shrinks a previously-full install.
|
||||
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
||||
// sources so a full → minimal switch doesn't leave stale registrations.
|
||||
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes).
|
||||
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && fs.existsSync(agentsDest)) {
|
||||
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes) and
|
||||
// for pluginOnlyInstall runtimes (pi, ADR-1239 / #2102 Stage 1 — no agents/
|
||||
// dir is ever written for them, see the leading branch below).
|
||||
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && !_hostBehaviors(runtime).pluginOnlyInstall && fs.existsSync(agentsDest)) {
|
||||
for (const file of fs.readdirSync(agentsDest)) {
|
||||
if (
|
||||
file.startsWith('gsd-') &&
|
||||
@@ -9568,7 +9728,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
||||
if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
||||
// pi (ADR-1239 / #2102 Stage 1): programmatic dispatch has no named-dispatch
|
||||
// subagent toolkit (dispatch.subagentToolkit: "undocumented", no Agent-tool
|
||||
// equivalent) and no host-read markdown surface — skip writing agents/ entirely.
|
||||
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
||||
} else if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
||||
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
||||
// via its own prune pass. No further action needed.
|
||||
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
||||
@@ -9608,12 +9773,14 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// _DESCRIPTOR_AGENTS_RUNTIMES above, so this whole branch is already
|
||||
// unreachable for it; the path-rewrite skip for antigravity now lives
|
||||
// in the descriptor-driven `applyAgentPathRewrites` (hostBehaviors.noPathRewrite).
|
||||
if (!isCopilot) {
|
||||
content = content.replace(dirRegex, pathPrefix);
|
||||
content = content.replace(homeDirRegex, pathPrefix);
|
||||
content = content.replace(bareDirRegex, normalizedPathPrefix);
|
||||
content = content.replace(bareHomeDirRegex, normalizedPathPrefix);
|
||||
}
|
||||
// #2099: `if (!isCopilot)` guard dropped — copilot is ALSO in
|
||||
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9564 above), so this whole
|
||||
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
||||
// isCopilot was therefore always false here, making the guard a no-op.
|
||||
content = content.replace(dirRegex, pathPrefix);
|
||||
content = content.replace(homeDirRegex, pathPrefix);
|
||||
content = content.replace(bareDirRegex, normalizedPathPrefix);
|
||||
content = content.replace(bareHomeDirRegex, normalizedPathPrefix);
|
||||
content = processAttribution(content, getCommitAttribution(runtime));
|
||||
// Convert frontmatter for runtime compatibility (agents need different handling)
|
||||
if (_hostBehaviors(runtime).frontmatterDialect === 'opencode') {
|
||||
@@ -9657,10 +9824,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
content = convertClaudeToKiloFrontmatter(content, { isAgent: true, modelOverride: _kiloModelOverride });
|
||||
} else if (_hostBehaviors(runtime).frontmatterDialect === 'codex') {
|
||||
content = convertClaudeAgentToCodexAgent(content);
|
||||
} else if (isCopilot) {
|
||||
content = convertClaudeAgentToCopilotAgent(content, isGlobal);
|
||||
} else if (isWindsurf) {
|
||||
content = convertClaudeAgentToWindsurfAgent(content);
|
||||
// #2099: `else if (isCopilot)` arm dropped — copilot is unreachable
|
||||
// here (see the isCopilot-guard-drop comment above); its content
|
||||
// conversion is applied pre-staging via the descriptor's
|
||||
// artifactLayout.converter (runtime-artifact-layout.cts), independent
|
||||
// of this legacy loop.
|
||||
// #2100: `else if (isWindsurf)` arm dropped — windsurf is ALSO in
|
||||
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9575 above), so this whole
|
||||
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
||||
// isWindsurf was therefore always false here, making the arm dead.
|
||||
// Its content conversion is applied pre-staging via the descriptor's
|
||||
// artifactLayout.converter (convertClaudeAgentToWindsurfAgent),
|
||||
// independent of this legacy loop.
|
||||
} else if (_hostBehaviors(runtime).frontmatterDialect === 'cline') {
|
||||
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
||||
// hostBehaviors.frontmatterDialect === 'cline'.
|
||||
@@ -9698,7 +9873,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// shouldNormalizeHyphenNamespaceInAgentBody above. Mirrors the
|
||||
// SKILL.md-body fix shipped via #3629.
|
||||
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
||||
const destName = isCopilot ? entry.name.replace('.md', '.agent.md') : entry.name;
|
||||
// #2099: `isCopilot ? ... : entry.name` ternary dropped — copilot is
|
||||
// unreachable here (see the isCopilot-guard-drop comment above), so
|
||||
// the ternary always evaluated to entry.name in practice; its
|
||||
// .agent.md suffix is applied by the descriptor-driven fold in
|
||||
// src/install-engine.cts (hostBehaviors.agentFileExtension).
|
||||
const destName = entry.name;
|
||||
fs.writeFileSync(path.join(agentsDest, destName), content);
|
||||
}
|
||||
}
|
||||
@@ -9879,14 +10059,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isKilo` was removed.
|
||||
// #2094: Trae's exclusion is likewise descriptor-driven (trae declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isTrae` was removed.
|
||||
// ZCode still has an empty hostBehaviors, so `&& !isZcode` stays.
|
||||
// #2101: ZCode's exclusion is likewise descriptor-driven (zcode declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isZcode` was removed.
|
||||
// #2095: Kimi's exclusion is likewise descriptor-driven (kimi declares
|
||||
// skipSharedHooksInstall:true) — kimi's shared hooks/ + package.json marker
|
||||
// are instead installed into its OWN native hook root (~/.kimi, resolved by
|
||||
// resolveKimiHooksTomlDir) via installSharedHooksBundle, at the
|
||||
// kimi-hooks-toml branch further below — never under the generic
|
||||
// Agent-Skills configDir GSD installs skills/agents into for kimi.
|
||||
if (!isCodex && !isCopilot && _hostBehaviors(runtime).skipSharedHooksInstall !== true && !isWindsurf && !isZcode) {
|
||||
// #2099: Copilot's exclusion is likewise descriptor-driven (copilot declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isCopilot` was removed.
|
||||
// #2100: Windsurf's exclusion is likewise descriptor-driven (windsurf declares
|
||||
// skipSharedHooksInstall:true) — the redundant `&& !isWindsurf` was removed.
|
||||
if (!isCodex && _hostBehaviors(runtime).skipSharedHooksInstall !== true) {
|
||||
if (!installSharedHooksBundle(targetDir)) {
|
||||
failures.push('hooks');
|
||||
}
|
||||
@@ -10511,7 +10696,11 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
} else {
|
||||
console.log(` ${green}✓${reset} Cursor lifecycle hooks already up to date`);
|
||||
}
|
||||
// Re-run the manifest pass so the hook scripts + hooks.json are hash-tracked.
|
||||
// Re-run the manifest pass to capture any files the hooks-json write path
|
||||
// produced. NOTE: hooks.json and the gsd-cursor-*.js scripts are NOT
|
||||
// manifest-tracked (verified) — uninstall removes them explicitly via
|
||||
// removeCursorHooksJson + its script list, and reconcile is idempotent.
|
||||
// The re-run is retained for parity with the settings.json install path.
|
||||
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: isGlobal ? 'global' : 'local' });
|
||||
persistActiveProfileMarker();
|
||||
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
||||
@@ -10559,6 +10748,34 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
||||
console.log(` ${green}✓${reset} Configured ${kimiHooksResult.entryCount} GSD hook(s) in ${kimiHooksTomlPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ADR-1239 / #2100 Stage 2: Windsurf's own independent hooksSurface —
|
||||
// Cascade's native hooks.json blocking hook bus (pre_write_code,
|
||||
// pre_run_command), wired via runtime-hooks-surface.cts exactly like
|
||||
// Cursor's writeCursorHooksJson but with Cascade's exit-code-2 blocking
|
||||
// protocol instead of Cursor's stdout-JSON form. Unlike kimi's branch
|
||||
// above, this is NOT gated to `isGlobal` — Windsurf has no
|
||||
// hostBehaviors.localInstallDeferred early-return, so both local
|
||||
// (.windsurf/hooks.json) and global (~/.codeium/windsurf/hooks.json)
|
||||
// installs reach this branch and must get the hook bus wired.
|
||||
if (plan.hooksSurface === 'windsurf-hooks-json') {
|
||||
const windsurfHookResult = writeWindsurfHooksJson(targetDir, src, {
|
||||
platform: process.platform,
|
||||
});
|
||||
if (windsurfHookResult.changed) {
|
||||
console.log(` ${green}✓${reset} Configured Windsurf lifecycle hooks (pre_write_code, pre_run_command)`);
|
||||
} else {
|
||||
console.log(` ${green}✓${reset} Windsurf lifecycle hooks already up to date`);
|
||||
}
|
||||
// Re-run the manifest pass, mirroring the cursor writer's pattern above
|
||||
// for parity. This does NOT hash-track hooks.json or the
|
||||
// gsd-windsurf-*.js scripts (same as cursor): uninstall removes them
|
||||
// explicitly via removeWindsurfHooksJson, and reconcileWindsurfHooksJson
|
||||
// is idempotent on repeated installs, so manifest tracking isn't needed
|
||||
// for correctness here.
|
||||
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: isGlobal ? 'global' : 'local' });
|
||||
}
|
||||
|
||||
persistActiveProfileMarker();
|
||||
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
||||
}
|
||||
@@ -10869,7 +11086,9 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
||||
// _hostBehaviors(runtime).doneBannerStyle === 'kimi-agent-file' (descriptor-driven), not this flag.
|
||||
// #2096: isAntigravity dropped — unused in this function.
|
||||
// #2098: isCodebuddy dropped — unused in this function.
|
||||
const { isOpencode, isCodex, isCopilot, isCursor, isWindsurf, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
// #2099: isCopilot dropped — unused in this function.
|
||||
// #2100: isWindsurf dropped — unused in this function.
|
||||
const { isOpencode, isCodex, isCursor, isAugment, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
||||
const plan = resolveInstallPlan(runtime);
|
||||
|
||||
if (shouldInstallStatusline && plan.writesSharedSettings && !_hostBehaviors(runtime).skipSettingsUi) {
|
||||
@@ -11114,13 +11333,14 @@ const runtimeMap = {
|
||||
'10': 'kimi',
|
||||
'11': 'kilo',
|
||||
'12': 'opencode',
|
||||
'13': 'qwen',
|
||||
'14': 'trae',
|
||||
'15': 'windsurf',
|
||||
'16': 'zcode'
|
||||
'13': 'pi',
|
||||
'14': 'qwen',
|
||||
'15': 'trae',
|
||||
'16': 'windsurf',
|
||||
'17': 'zcode'
|
||||
};
|
||||
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf', 'zcode'];
|
||||
const ALL_RUNTIMES_OPTION = '17';
|
||||
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi', 'qwen', 'trae', 'windsurf', 'zcode'];
|
||||
const ALL_RUNTIMES_OPTION = '18';
|
||||
|
||||
/**
|
||||
* Build the runtime-selection prompt text shown by the interactive installer.
|
||||
@@ -11140,11 +11360,12 @@ function buildRuntimePromptText() {
|
||||
${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
||||
${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
||||
${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
||||
${cyan}13${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
||||
${cyan}14${reset}) Trae ${dim}(~/.trae)${reset}
|
||||
${cyan}15${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
||||
${cyan}16${reset}) ZCode ${dim}(~/.zcode)${reset}
|
||||
${cyan}17${reset}) All
|
||||
${cyan}13${reset}) pi ${dim}(~/.pi/agent)${reset}
|
||||
${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
||||
${cyan}15${reset}) Trae ${dim}(~/.trae)${reset}
|
||||
${cyan}16${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
||||
${cyan}17${reset}) ZCode ${dim}(~/.zcode)${reset}
|
||||
${cyan}18${reset}) All
|
||||
|
||||
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
||||
`;
|
||||
@@ -11691,8 +11912,8 @@ const _LEGACY_SCAN_SUBDIR_NAMES = [
|
||||
'.agents', // antigravity local form (canonical, #791)
|
||||
'.agent', // antigravity local form (legacy, backward-compat)
|
||||
'.cursor',
|
||||
'.devin', // windsurf local form (canonical, #1085; Devin Desktop preferred dir)
|
||||
'.windsurf', // windsurf local form (legacy, backward-compat with pre-#1085 installs)
|
||||
'.devin', // windsurf local form (legacy, pre-#1615; Devin Desktop preferred dir, #1085)
|
||||
'.windsurf', // windsurf local form (canonical since #1615; capability.json localConfigDir)
|
||||
'.codeium/windsurf',
|
||||
'.augment',
|
||||
'.trae',
|
||||
@@ -12003,6 +12224,11 @@ module.exports = {
|
||||
reconcileCursorHooksJson,
|
||||
writeCursorHooksJson,
|
||||
removeCursorHooksJson,
|
||||
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_HOOK_SCRIPTS,
|
||||
writeWindsurfHooksJson,
|
||||
removeWindsurfHooksJson,
|
||||
stripGsdFromAgentsMd,
|
||||
GSD_AGENTS_MD_MARKER,
|
||||
GSD_AGENTS_MD_CLOSE_MARKER,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "ai-integration",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "antigravity",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Antigravity",
|
||||
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.",
|
||||
"tier": "core",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "assumption-delta",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Assumption-delta architecture checkpoint",
|
||||
"description": "Rarely-firing advisory checkpoint that triggers when a phase makes something plural, optional, or chosen that used to be singular, required, or derived. Surfaces one identity-model question (promote the new general representation to primary, or add it alongside?) so a silent primary-key drift does not accumulate into a later user-facing bug. Non-blocking; fires only on a detected signal.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "audit",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "augment",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "claude-orchestration",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Claude orchestration (Workflow backend)",
|
||||
"description": "Default-off, BETA, claude-only capability that adopts Claude Code's Workflow tool (the engine behind /effort ultracode) as an optional parallel-execution backend for the GSD loop. When the runtime exposes the Workflow tool and claude_orchestration.execution_backend resolves to 'workflow', execute-phase emits a generated Workflow script (waves -> parallel() barriers, plans -> agent({ agentType: 'gsd-executor', isolation: 'worktree' }), files_modified overlap -> separate sequential stages, resumeFromRunId wired to the phase run id, shared token budget) that composes the SAME gsd-executor agent and worktree isolation the inline path uses, restoring the wave parallelism the #853 backgrounded-agent nesting limitation forces inline on Claude Code. (The plan-checker and verifier remain inline until separately wired — this capability delivers the parallel-execution backend, not those gates.) Also folds the ultraplan plan-offload under one runtime gate (plan:* surface). On any runtime lacking the Workflow tool, or when the capability is disabled, behaviour is byte-identical to today (inline/manual dispatch). Detection + emission live in gsd-core/bin/lib/claude-orchestration.cjs (pure, fail-closed). Mirrors the existing gsd-ultraplan-phase BETA-isolation posture.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "claude",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Claude Code",
|
||||
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
|
||||
"tier": "core",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cline",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "code-review",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Code review",
|
||||
"description": "Source-file code review and review-fix workflow support for completed execution work.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "codebuddy",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "CodeBuddy",
|
||||
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "codex",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "copilot",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -84,7 +84,10 @@
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"reapplyCommand": "/gsd-update --reapply"
|
||||
"reapplyCommand": "/gsd-update --reapply",
|
||||
"agentFileExtension": ".agent.md",
|
||||
"skipSharedHooksInstall": true,
|
||||
"noPathRewrite": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "cursor",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "drift",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Drift detection gates",
|
||||
"description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "external-job",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Async external-job scheduler adapter",
|
||||
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies execute:wave:pre, but execute-phase.md only dispatches execute:wave:post today (wave:pre is declared in the loop host contract but not rendered); wiring wave:pre dispatch is a core-loop change #1164 explicitly puts out of scope, so this capability registers at wave:post and the executor honors the runtime_budget classification guidance before running any tagged task. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "gap-analysis",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "graphify",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "hermes",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "intel",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "kilo",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "kimi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Kimi CLI",
|
||||
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; native config.toml [[hooks]] bus at ~/.kimi/config.toml; background dispatch; tier-2 support.",
|
||||
"tier": "core",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "mempalace",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "nyquist",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Nyquist validation",
|
||||
"description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "opencode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "pattern-mapper",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Pattern mapping",
|
||||
"description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.",
|
||||
"tier": "full",
|
||||
|
||||
60
capabilities/pi/capability.json
Normal file
60
capabilities/pi/capability.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"id": "pi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "pi",
|
||||
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "dot-home-nested",
|
||||
"name": "agent",
|
||||
"parent": ".pi",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": ".pi",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "pi",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "slash-programmatic",
|
||||
"dispatch": {
|
||||
"namedDispatch": false,
|
||||
"nested": false,
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
"stateIO": "session-log-append",
|
||||
"transport": "native-extension",
|
||||
"runtime": "bun"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"nativePlugin": {
|
||||
"dir": "extensions",
|
||||
"file": "gsd.cjs",
|
||||
"source": "pi/gsd.cjs"
|
||||
},
|
||||
"pluginOnlyInstall": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "profile-pipeline",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "qwen",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Qwen Code",
|
||||
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -90,7 +90,11 @@
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skillPriorityFrontmatter": true,
|
||||
"brandingRewrites": { "CLAUDE.md": "QWEN.md", "Claude Code": "Qwen Code", ".claude/": ".qwen/" },
|
||||
"brandingRewrites": {
|
||||
"CLAUDE.md": "QWEN.md",
|
||||
"Claude Code": "Qwen Code",
|
||||
".claude/": ".qwen/"
|
||||
},
|
||||
"legacyCommandsGsdCleanup": true,
|
||||
"legacyCommandsGsdInstallMigration": true,
|
||||
"legacyCommandsGsdUninstall": true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "research",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Phase research",
|
||||
"description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.",
|
||||
"tier": "standard",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "schema-gate",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "security",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Security enforcement",
|
||||
"description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.",
|
||||
"tier": "full",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "tdd",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "trae",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Trae IDE",
|
||||
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
|
||||
"tier": "core",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "ui",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "UI design contracts",
|
||||
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
|
||||
"tier": "full",
|
||||
|
||||
51
capabilities/vscode/capability.json
Normal file
51
capabilities/vscode/capability.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"id": "vscode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "VS Code",
|
||||
"description": "VS Code — Marketplace/VSIX extension; no file-projected config directory; IDE-profile reference host (active vscode.lm model, engine-owned hook bus, sandboxed globalState/workspaceState stateIO).",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "none",
|
||||
"name": "vscode",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": null,
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "none",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 1,
|
||||
"installSurface": "none",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "palette",
|
||||
"dispatch": {
|
||||
"namedDispatch": true,
|
||||
"nested": true,
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
"stateIO": "sandboxed-storage",
|
||||
"transport": "mcp",
|
||||
"runtime": "sandboxed-web"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"id": "windsurf",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Windsurf",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; Cascade native hooks.json blocking hook bus (pre_write_code, pre_run_command); tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
@@ -51,7 +51,7 @@
|
||||
]
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"hooksSurface": "windsurf-hooks-json",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
@@ -74,6 +74,12 @@
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true,
|
||||
"legacyDevinSkillsCleanup": true,
|
||||
"installsCommandBodiesForWorkflowDelegation": true,
|
||||
"verificationStyle": "windsurf-workflows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "zcode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "ZCode",
|
||||
"description": "ZCode (Z.ai) — desktop Agentic Development Environment for GLM-5.2; Claude-shaped nested skills at ~/.zcode/skills/<name>/SKILL.md, slash commands, named subagents, native MCP; declarative plugin surface; profile-marker install; tier-2 community support.",
|
||||
"tier": "core",
|
||||
@@ -97,6 +97,9 @@
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "electron"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ Parse the first token of $ARGUMENTS:
|
||||
## list / status
|
||||
|
||||
Load the capability registry and call `listSurface(runtimeConfigDir, manifest, CLUSTERS, registry)` from
|
||||
`gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
||||
the engine module at `${runtimeConfigDir}/gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
||||
```js
|
||||
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
||||
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
||||
```
|
||||
Display:
|
||||
|
||||
@@ -71,7 +71,7 @@ Install profile: standard (from .gsd-profile)
|
||||
3. `writeSurface(runtimeConfigDir, surfaceState)`.
|
||||
4. Resolve and re-apply:
|
||||
```js
|
||||
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
||||
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
||||
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
||||
```
|
||||
@@ -89,7 +89,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
||||
3. Add cluster to `surfaceState.disabledClusters` (deduplicate).
|
||||
4. `writeSurface` → resolve layout → `applySurface`:
|
||||
```js
|
||||
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
||||
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
||||
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
||||
```
|
||||
@@ -103,7 +103,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
||||
2. Remove cluster from `surfaceState.disabledClusters`.
|
||||
3. `writeSurface` → resolve layout → `applySurface`:
|
||||
```js
|
||||
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
||||
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
||||
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
||||
```
|
||||
@@ -151,7 +151,7 @@ All paths can be overridden by reading the `CLAUDE_CONFIG_DIR` env var if set.
|
||||
|
||||
- Unknown cluster name → list valid cluster names, exit without writing.
|
||||
- Unknown profile name → list known profiles (`core`, `standard`, `full`), exit.
|
||||
- Missing `surface.cjs` → prompt: "Run `npm i -g gsd-core` to reinstall GSD."
|
||||
- Missing `surface.cjs` → prompt: "Run `npm i -g @opengsd/gsd-core` to reinstall GSD."
|
||||
|
||||
<execution_context>
|
||||
Surface state file: `~/.claude/.gsd-surface.json`
|
||||
|
||||
@@ -1425,6 +1425,7 @@ When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtim
|
||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) |
|
||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
||||
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
||||
| `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) |
|
||||
| Group B (`cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | |
|
||||
|
||||
> **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity.
|
||||
|
||||
@@ -987,6 +987,8 @@ continues. Drift detection cannot fail verification.
|
||||
|
||||
## Infrastructure Features
|
||||
|
||||
> **Looking for a third-party add-on instead?** See the [GSD Community Capability Registry & EoS Registry](registries/README.md) — non-endorsing discoverability catalogs for community-contributed Capabilities and EoS host integrations.
|
||||
|
||||
### 34. Git Integration
|
||||
|
||||
**Purpose:** Atomic commits, branching strategies, and clean history management.
|
||||
@@ -2265,15 +2267,15 @@ Test suite that scans all agent, workflow, and command files for embedded inject
|
||||
|
||||
### 99. Improved Prompt Injection Scanner
|
||||
|
||||
**Hook:** `gsd-prompt-guard.js`
|
||||
**Script:** `scripts/prompt-injection-scan.sh`
|
||||
**Hook:** `gsd-prompt-guard.js`, `gsd-read-injection-scanner.js`
|
||||
**Script:** `scripts/prompt-injection-scan.sh`, `scripts/base64-scan.sh`
|
||||
|
||||
**Purpose:** Enhanced detection of prompt injection attempts in planning artifacts, adding invisible Unicode character detection, encoding obfuscation patterns, and entropy-based analysis.
|
||||
**Purpose:** Defense-in-depth detection of prompt injection attempts in planning artifacts and ingested content. Live hooks inline their own pattern subsets for hook independence (they do not import from `security.cts`). The CI scanner (`scanForInjection` in `security.cts`) provides a centralized engine for codebase-wide scanning in tests.
|
||||
|
||||
**Requirements:**
|
||||
- REQ-SCAN-INJ-01: Scanner MUST detect invisible Unicode characters (zero-width spaces, soft hyphens, etc.)
|
||||
- REQ-SCAN-INJ-02: Scanner MUST detect encoding obfuscation patterns (base64-encoded instructions, homoglyphs)
|
||||
- REQ-SCAN-INJ-03: Scanner MUST apply entropy analysis to flag high-entropy strings in unexpected positions
|
||||
- REQ-SCAN-INJ-01: Live hooks MUST detect invisible Unicode characters (zero-width spaces, soft hyphens, Unicode tag block U+E0000–E007F)
|
||||
- REQ-SCAN-INJ-02: Live hooks MUST detect known injection patterns (instruction override, role manipulation, system-prompt extraction, fake message boundaries). Base64-decode scanning is a CI-time control (`scripts/base64-scan.sh`), not a live hook — live hooks match a base64-exfiltration phrase regex only, they do not decode.
|
||||
- REQ-SCAN-INJ-03: ~~Scanner MUST apply entropy analysis~~ — Entropy analysis (`scanEntropyAnomalies`) was removed in #2198 as dead code (zero production callers; live hooks do not perform entropy analysis). This requirement is deferred pending a maintainable live implementation.
|
||||
- REQ-SCAN-INJ-04: Scanner MUST remain advisory-only — detection is logged, not blocking
|
||||
|
||||
---
|
||||
|
||||
@@ -469,6 +469,8 @@
|
||||
"gsd-statusline.js",
|
||||
"gsd-update-banner.js",
|
||||
"gsd-validate-commit.sh",
|
||||
"gsd-windsurf-pre-command.js",
|
||||
"gsd-windsurf-pre-write.js",
|
||||
"gsd-workflow-guard.js",
|
||||
"gsd-worktree-path-guard.js"
|
||||
]
|
||||
|
||||
@@ -554,6 +554,8 @@ Full listing: `hooks/`.
|
||||
| `gsd-cursor-stop.js` | Cursor `stop` | Cursor-native verify-work reminder on agent stop (ADR-1239 / #2089) |
|
||||
| `gsd-cursor-subagent-start.js` | Cursor `subagentStart` | Cursor-native subagent context injection (ADR-1239 / #2089) |
|
||||
| `gsd-cursor-subagent-stop.js` | Cursor `subagentStop` | Cursor-native subagent completion reminder (ADR-1239 / #2089) |
|
||||
| `gsd-windsurf-pre-write.js` | Windsurf/Cascade `pre_write_code` | Blocking (exit-code-2) write-path guard — blocks a write resolving to a different git root than cwd, or inside `.git/` internals (ADR-1239 / #2100) |
|
||||
| `gsd-windsurf-pre-command.js` | Windsurf/Cascade `pre_run_command` | Blocking (exit-code-2) destructive-command guard — conservative deny-list (`rm -rf` root/home wipes, force-push to a protected branch) (ADR-1239 / #2100) |
|
||||
| `gsd-prompt-guard.js` | `PreToolUse` | Scans `.planning/` writes for prompt-injection patterns (advisory) |
|
||||
| `gsd-workflow-guard.js` | `PreToolUse` | Detects file edits outside GSD workflow context (advisory, opt-in) |
|
||||
| `gsd-read-guard.js` | `PreToolUse` | Advisory guard preventing Edit/Write on unread files |
|
||||
|
||||
@@ -1010,3 +1010,4 @@ To disable parallel execution entirely: `/gsd-settings` → set `parallelization
|
||||
- [Commands](COMMANDS.md)
|
||||
- [Configuration](CONFIGURATION.md)
|
||||
- [The phase loop](explanation/the-phase-loop.md)
|
||||
- [Community Capability Registry & EoS Registry](registries/README.md) — discover third-party Capabilities and EoS host integrations
|
||||
|
||||
44
docs/adr/2164-statusline-scope-boundary.md
Normal file
44
docs/adr/2164-statusline-scope-boundary.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Statusline draws its data boundary at local, read-only sources
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-11
|
||||
- **Issue:** #2164
|
||||
- **Implementation:** Policy ADR — no code change. Governs triage of statusline enhancement/feature requests.
|
||||
|
||||
## Decision
|
||||
|
||||
The GSD statusline (`hooks/gsd-statusline.js`) may source data from three tiers, and is bounded to the first two:
|
||||
|
||||
| Tier | Data source | In scope? |
|
||||
|------|-------------|-----------|
|
||||
| 0 — Refine existing | The stdin payload Claude Code already sends (model name, context-window usage, GSD-state read from `.planning/`) | Yes |
|
||||
| 1 — New local source | Read-only local reads / bounded subprocesses scoped to the workspace (e.g. `git status`) | Yes, if opt-in and bounded |
|
||||
| 2 — External / credentialed | Reading credentials (OAuth tokens, keychains) or calling external/network APIs for data | No |
|
||||
|
||||
The statusline refines what it is already handed and may add a new **local, read-only** source, but it does not read credentials or make authenticated/network calls to fetch data. Surfacing account-level or platform-level resource state (usage limits, rate-limit windows) from a GSD hook is a platform concern, not GSD's.
|
||||
|
||||
## Rationale
|
||||
|
||||
- The statusline is a planning-workflow surface, not a platform dashboard. Its existing segments (model, context meter, directory, GSD-state) are all local, read-only projections of data GSD is already given.
|
||||
- Reading credentials from a planning hook is a materially larger trust surface than any rendering concern; even read-only and opt-in, it is not something a planning tool should own.
|
||||
- External/undocumented endpoints (e.g. an OAuth usage API) are unstable dependencies that rot silently when they change.
|
||||
- Consistent with the existing prior in `.out-of-scope/temporal-context.md`: *"Statusline / TUI re-entry is platform-level, not GSD-level."*
|
||||
|
||||
## Consequences
|
||||
|
||||
- New statusline requests are triaged against the tier table. The **data-source** axis (this ADR) is orthogonal to the **enhancement-vs-feature** axis (CONTRIBUTING.md): refining an existing segment is an enhancement; adding a new segment or data source is a feature (a new concept/integration), regardless of tier.
|
||||
- Applied at decision time:
|
||||
- **#2160 / #2161 / #2162** — refine existing model / context-meter / GSD-state rendering. Tier 0; approved as enhancements.
|
||||
- **#2163** — git segment. Tier 1 (new local source): in scope, but routed to the feature track (`approved-feature` + complete spec) because it adds a new segment.
|
||||
- **#2164** — 5h/7d account-usage segment. Tier 2 (reads OAuth creds + calls `api.anthropic.com/api/oauth/usage`): out of scope; closed `wontfix`, recorded in `.out-of-scope/statusline-account-usage.md`.
|
||||
|
||||
## Revisit if
|
||||
|
||||
A documented, first-party usage API — or a platform-provided value delivered to the hook without GSD reading credentials — becomes available. That would move usage display out of Tier 2.
|
||||
|
||||
## References
|
||||
|
||||
- `.out-of-scope/statusline-account-usage.md` — the #2164 rejection record.
|
||||
- `.out-of-scope/temporal-context.md` — prior "statusline is platform-level" note.
|
||||
- `CONTRIBUTING.md` — enhancement vs feature gates.
|
||||
- Issues: #2160, #2161, #2162 (approved enhancements), #2163 (feature-track), #2164 (this ADR's trigger).
|
||||
33
docs/adr/2207-status-field-lifecycle-ownership.md
Normal file
33
docs/adr/2207-status-field-lifecycle-ownership.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# ADR-2207: STATE.md `Status` lifecycle — phase-completion writes an intermediate state; milestone-close owns termination
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-12
|
||||
- **Issue:** [#2207](https://github.com/open-gsd/gsd-core/issues/2207)
|
||||
- **Implements:** [#2204](https://github.com/open-gsd/gsd-core/issues/2204) (Bug 7b, split from the #2191 batch)
|
||||
|
||||
## Context
|
||||
|
||||
STATE.md's `Status` field is written by two transitions with an **overloaded** value:
|
||||
|
||||
- `completePhaseCore` (phase-completion) writes a bare `Status: Milestone complete` on the last phase, keyed on `isLastPhase`.
|
||||
- `milestoneCompleteCore` (milestone-close) writes the terminal `Status: <version> milestone complete` and resets `## Current Position` to `Awaiting next milestone`.
|
||||
|
||||
"Milestone complete" therefore spans **two distinct states** — an intermediate "all phases done, awaiting formal close" and the terminal archived state — and a **phase-level verb owns a milestone-level field**. Because `isLastPhase` is derived from the ROADMAP parse, a mis-parse (the bullet-form / membership bugs, #2199 / #2200) can flip the milestone status on the wrong phase.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Phase-completion writes an intermediate state, not the terminal one.** `completePhaseCore` writes the **existing** `All phases complete` value (already used in `gsd2-import.cts`) on the last phase — not `Milestone complete`.
|
||||
2. **Milestone termination is owned solely by the milestone-close verb.** Only `milestoneCompleteCore` writes `<version> milestone complete` / `Awaiting next milestone`.
|
||||
3. **The coupling is retained, not removed.** "Is this the last phase" stays on the phase-completion path; its correctness is carried by the existing `#2028` checkbox guard, the parse fixes (#2199 / #2200), and the `verify.cts` ship gate that already errors when STATE claims milestone-complete while phases are unstarted.
|
||||
|
||||
**Rejected alternative — decouple** (phase verbs never write milestone `Status`): rejected because `#2028` shows the last-phase signal is deliberately wanted on the phase-completion path; removing it would regress that.
|
||||
|
||||
## The `Status` lifecycle (ubiquitous language)
|
||||
|
||||
`Ready to plan` → `All phases complete` (all phases done, milestone awaiting formal close) → `<version> milestone complete` → `Awaiting next milestone` (terminal / archived).
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:** the overload is removed; the intermediate and terminal "complete" states are distinct; a phase-level verb no longer writes the terminal milestone state; the wrong-phase flip becomes a parse-correctness concern already owned upstream.
|
||||
|
||||
**Cost / follow-through (implemented in #2204):** consumers that key on the `Milestone complete` string must recognize `All phases complete` — `workflows/progress.md` (Route D), `verify.cts`, and `workstream-inventory-builder.cts`. `normalizeStateStatus` already maps any status containing "complete" → `completed`, so it needs no change. A `CONTEXT.md` glossary entry enumerating the `Status` lifecycle lands with the #2204 implementation.
|
||||
@@ -67,6 +67,7 @@ See **[CONTRIBUTING.md — "Proposing an ADR or PRD"](../../CONTRIBUTING.md#prop
|
||||
| [1990-existing-code-onboarding.md](1990-existing-code-onboarding.md) | Existing Code Onboarding Module owns deterministic repo-state detection and onboarding route selection | Proposed |
|
||||
| [2121-phase-identifier-parsing-consolidation.md](2121-phase-identifier-parsing-consolidation.md) | Phase-identifier parsing consolidation — single canonical owner (phase-id.cts) + anti-divergence guard | Accepted |
|
||||
| [2143-markdown-table-and-mutation-consolidation.md](2143-markdown-table-and-mutation-consolidation.md) | Markdown table model, bounded mutation, and fail-loud consolidation (#1372 part 2) | Accepted |
|
||||
| [2164-statusline-scope-boundary.md](2164-statusline-scope-boundary.md) | Statusline draws its data boundary at local, read-only sources (no external/credentialed data) | Accepted |
|
||||
|
||||
## Seam map
|
||||
|
||||
|
||||
@@ -147,6 +147,19 @@ yields the same truth value**, so behavior is unchanged and only the brittle cou
|
||||
the host correctly, negotiation fails closed on a corrupted descriptor, and — with a source-grep behind
|
||||
an `// allow-test-rule:` exemption — that **no `runtime === '<id>'` branch remains** in `bin/install.js`.
|
||||
|
||||
**Another completed worked example: `copilot` (#2099).** Copilot was already installing through the
|
||||
declarative artifactLayout (not the direct `installRuntimeArtifacts` calls step 4 describes), so its
|
||||
migration folded the *residual* hardcoded branches rather than the whole install path: the `.agent.md`
|
||||
destination-suffix rename in `src/install-engine.cts` (→ `hostBehaviors.agentFileExtension`), two
|
||||
uninstall side-effect branches in `bin/install.js` (→
|
||||
`resolveInstallPlan(runtime).installSurface === 'copilot-instructions'`, already a live descriptor field
|
||||
elsewhere in the same file), and two `skipSharedHooksInstall` gates (→
|
||||
`hostBehaviors.skipSharedHooksInstall: true`). A dead legacy agent-converter dispatch arm — unreachable
|
||||
because copilot is a member of `_DESCRIPTOR_AGENTS_RUNTIMES` — was deleted outright rather than re-gated,
|
||||
mirroring step 6's guard: `tests/declarative-reference-copilot.test.cjs` source-greps both files for the
|
||||
retired `isCopilot` reads. See the `copilot` section of the reference matrix for the full EoS migration
|
||||
note, including the two upgrades (multi-event hook bus; negotiated `dispatch.background`) this PR adds.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
@@ -457,6 +457,22 @@ npx @opengsd/gsd-core@latest --zcode --global
|
||||
|
||||
ZCode's skill format is identical to Claude Code's, so no runtime-specific converter is required — GSD lands as a pure declarative descriptor with no hardcoded installer branches. ZCode also natively imports skills and MCP config from `~/.claude`; if you install GSD for **both** Claude and ZCode, you may see duplicate GSD skills inside ZCode, which is expected. To connect ZCode's MCP servers to GSD's companion server, see [how to connect the GSD MCP server](connect-gsd-mcp-server.md).
|
||||
|
||||
GSD's hook-automation and native-MCP-registration integrations are not yet wired for ZCode — both are blocked on ZCode not yet publishing the on-disk config format for its plugin `Hook` component or the settings filename/schema for its MCP store. See the [`## zcode`](host-integration-capability-matrix.md#zcode) section of the host-integration capability matrix for the cited source URLs.
|
||||
|
||||
---
|
||||
|
||||
### pi
|
||||
|
||||
```bash
|
||||
npx @opengsd/gsd-core@latest --pi --global
|
||||
```
|
||||
|
||||
[pi](https://pi.dev) is a bun-runtime programmatic CLI whose extensions implement pi's own `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) rather than a settings-file or slash-markdown surface. GSD ships a single native-extension file:
|
||||
|
||||
- **Extension** → `~/.pi/agent/extensions/gsd.cjs` (global) or `.pi/extensions/gsd.cjs` (local)
|
||||
|
||||
The extension registers a `/gsd` command and a `gsd_invoke` tool that dispatch GSD commands via a bounded subprocess call to `gsd-core/bin/gsd-tools.cjs` (no fully-populated in-process command-routing hub exists — see the matrix's Stage 2 note). This is a **plugin-only install**: pi has no shared-settings hook surface (`hooksSurface: none`) and, unlike Claude/OpenCode/Kilo, no host-read markdown surface at all — pi's `/gsd` command is registered programmatically by the extension, not discovered from files, so GSD installs the extension plus its universal `gsd-core/` engine payload and the shared `hooks/`/`hooks/lib/` bundle (spawned by the extension itself, not by any config-file hook bus), and does **not** write any `commands/`, `agents/`, or `skills/` directory for pi. The extension bridges GSD's `session_start`/`before_agent_start`/`session_before_compact`/`tool_call` lifecycle events to those staged `hooks/` scripts as bounded, fail-open subprocesses, and steers pi's active model (`modelMode: active`) to a tier-resolved bare anthropic id via `pi.on('before_provider_request', ...)`. See the [`## pi`](host-integration-capability-matrix.md#pi) section of the host-integration capability matrix for the negotiated axes and citations.
|
||||
|
||||
---
|
||||
|
||||
## Local vs global install
|
||||
|
||||
@@ -2195,15 +2195,15 @@ Claude が GSD ワークフローコンテキスト外でファイル編集を
|
||||
|
||||
### 99. 改善されたプロンプトインジェクションスキャナー
|
||||
|
||||
**フック:** `gsd-prompt-guard.js`
|
||||
**スクリプト:** `scripts/prompt-injection-scan.sh`
|
||||
**フック:** `gsd-prompt-guard.js`、`gsd-read-injection-scanner.js`
|
||||
**スクリプト:** `scripts/prompt-injection-scan.sh`、`scripts/base64-scan.sh`
|
||||
|
||||
**目的:** プランニングアーティファクト内のプロンプトインジェクション試みの検出を強化し、不可視 Unicode 文字検出、エンコードの難読化パターン、エントロピーベースの分析を追加します。
|
||||
**目的:** プランニングアーティファクトおよび取り込んだコンテンツ内のプロンプトインジェクション試行の多層防御検出。ライブフックはフック独立性のために独自のパターンサブセットをインライン化します(`security.cts` からインポートしません)。CIスキャナー(`security.cts` の `scanForInjection`)は、テストでのコードベース全体スキャン用の集中エンジンを提供します。
|
||||
|
||||
**要件:**
|
||||
- REQ-SCAN-INJ-01: スキャナーは不可視 Unicode 文字(ゼロ幅スペース、ソフトハイフンなど)を検出しなければならない
|
||||
- REQ-SCAN-INJ-02: スキャナーはエンコードの難読化パターン(base64 エンコードされた命令、ホモグリフ)を検出しなければならない
|
||||
- REQ-SCAN-INJ-03: スキャナーは予期しない位置の高エントロピー文字列にフラグを立てるためにエントロピー分析を適用しなければならない
|
||||
- REQ-SCAN-INJ-01: ライブフックは不可視 Unicode 文字(ゼロ幅スペース、ソフトハイフン、Unicode タグブロック U+E0000–E007F)を検出しなければならない
|
||||
- REQ-SCAN-INJ-02: ライブフックは既知のインジェクションパターン(命令オーバーライド、ロール操作、システムプロンプト抽出、偽のメッセージ境界)を検出しなければならない。Base64 デコードスキャンは CI 時制御(`scripts/base64-scan.sh`)であり、ライブフックではない — ライブフックは base64 持ち出しフレーズ正規表現のみを一致させ、デコードはしない。
|
||||
- REQ-SCAN-INJ-03: ~~スキャナーはエントロピー分析を適用しなければならない~~ — エントロピー分析(`scanEntropyAnomalies`)は #2198 でデッドコードとして削除された(本番呼び出し元ゼロ;ライブフックはエントロピー分析を実行しない)。この要件は保守可能なライブ実装まで保留。
|
||||
- REQ-SCAN-INJ-04: スキャナーは勧告的のみでなければならない — 検出はログに記録されるが、ブロッキングではない
|
||||
|
||||
---
|
||||
|
||||
@@ -72,7 +72,7 @@ points.
|
||||
| `tdd` | feature | full | `>=1.6.0` | `plan:pre`, `execute:post` | contribution, gate | first-party |
|
||||
| `ui` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post`, `verify:post` | step, gate | first-party |
|
||||
|
||||
### Runtime capabilities (role: runtime) — 16
|
||||
### Runtime capabilities (role: runtime) — 18
|
||||
|
||||
Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting
|
||||
skills, agents, hooks configuration, and surface files for that host. They
|
||||
@@ -93,8 +93,10 @@ emission), so their extension-point and hook-kind cells are `—`.
|
||||
| `kilo` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `kimi` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `opencode` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `pi` | runtime | core | `>=1.7.0` | — | — | first-party |
|
||||
| `qwen` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `trae` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `vscode` | runtime | core | `>=1.7.0` | — | — | first-party |
|
||||
| `windsurf` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
| `zcode` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||
|
||||
|
||||
@@ -461,6 +461,8 @@ Documentation gaps:
|
||||
- runtime — docs describe the CLI binary and the SDK (Node.js/Go/Python/Rust) but do not state what runtime the CLI host itself or its plugin/extension loader executes in.
|
||||
- dispatch.nested exact authoritative source is awesome-copilot.github.com (community docs) not docs.github.com.
|
||||
|
||||
**EoS migration status (#2099):** Migrated onto the declarative adapter (dogfooded in `tests/declarative-reference-copilot.test.cjs`). The residual `isCopilot` branches were folded onto descriptor-driven `runtime.hostBehaviors`: the `.agent.md` destination-suffix rename in `src/install-engine.cts` now reads `hostBehaviors.agentFileExtension`; `bin/install.js`'s two uninstall side-effect branches (repo-root `AGENTS.md` cleanup, `copilot-instructions.md`/hook cleanup) now gate on `resolveInstallPlan(runtime).installSurface === 'copilot-instructions'` (unique to copilot, so byte-identical); and the two `skipSharedHooksInstall` checks now read `hostBehaviors.skipSharedHooksInstall:true` (copilot's golden has only `hooks/gsd-session.json`, no shared `gsd-*.js` scripts). A dead legacy agent-converter dispatch arm in the inline agent-copy loop — unreachable since copilot is a member of `_DESCRIPTOR_AGENTS_RUNTIMES` — was removed outright; `isCopilot` no longer appears as a live read anywhere in `bin/install.js` or `src/install-engine.cts`. Two upgrades land: (1) **multi-event hook bus** — `buildCopilotHookConfig()` previously emitted only `sessionStart`; this PR wires four additional events — `preToolUse`/`postToolUse`/`userPromptSubmitted`/`sessionEnd` — each a static, deterministic advisory command (no node-runner invocation), so an install's `hooks/gsd-session.json` now registers all five events. (2) **`dispatch.background`** — the descriptor already declared `true`, exceeding the `declarative-cli` profile baseline of `false`; the negotiation contract (`negotiateHostCapabilities`) surfaces that value with no downgrade warning, documenting the legitimate deviation. Note: Copilot's `.agent.md` frontmatter has no background-dispatch field (fields are `description`/`infer`/`mcp-servers`/`model`/`name`/`tools`) — background dispatch remains a negotiated-contract-only axis, not a field GSD's agent artifacts emit. MCP companion tooling is out of scope for this migration (AC4 names only the two upgrades above).
|
||||
|
||||
---
|
||||
|
||||
## kilo
|
||||
@@ -506,7 +508,7 @@ Documentation gaps:
|
||||
| embeddingMode | declarative | https://docs.devin.ai/desktop/cascade/cascade | "Cascade operates through configuration files rather than code plugins: .codeiumignore for file filtering, Memories and Rules for customizing" |
|
||||
| commandSurface | slash-file | https://docs.devin.ai/desktop/cascade/workflows | "Workflows are authored as markdown files (.md extension) … triggered through slash commands using the format /[workflow-name]." |
|
||||
| modelMode | passive | https://docs.devin.ai/desktop/models.md | "Models are selectable via configuration/UI only (SWE-1.5, SWE-1.6, Adaptive, Arena tiers, Claude, GPT)." |
|
||||
| hookBus | host | https://docs.devin.ai/desktop/cascade/hooks.md | "Cascade supports twelve hook events covering critical workflow points … Pre-hooks (can block actions): pre_read_code, pre_write_code, pre_ru" |
|
||||
| hookBus | host | https://docs.devin.ai/desktop/cascade/hooks.md | "Cascade supports twelve hook events covering critical workflow points … Pre-hooks (can block actions): pre_read_code, pre_write_code, pre_run_command, …" (quote elided beyond the pre-hook enumeration — see #2100 CASCADE FACTS reference) |
|
||||
| stateIO | filesystem | https://docs.devin.ai/desktop/cascade/cascade | "Cascade can create and modify codebases directly … File access can be restricted through .codeiumignore files" |
|
||||
| transport | mcp | https://docs.devin.ai/desktop/cascade/mcp | "Cascade now natively integrates with MCP, allowing you to bring your own selection of MCP servers for Cascade to use." |
|
||||
| runtime | undocumented | no authoritative doc — searched: https://docs.devin.ai/windsurf/plugins/getting-started.md, /llmstxt/windsurf_llms-full_txt (Context7) | — |
|
||||
@@ -535,6 +537,8 @@ Documentation gaps:
|
||||
- dispatch.subagentToolkit — no documentation for toolkit restrictions on Cascade sub-agents.
|
||||
- runtime — Windsurf IDE is Electron-based but no programmatic plugin runtime is documented to developers.
|
||||
|
||||
**EoS migration status (#2100 Stage 2 — HOOK-BRIDGE):** `hooksSurface` moved from `"none"` to `"windsurf-hooks-json"`. GSD now wires two of Cascade's documented pre-hooks with BLOCKING semantics via `.windsurf/hooks.json` (local) / `~/.codeium/windsurf/hooks.json` (global): `pre_write_code` (write-path guard — blocks a write resolving to a different git root than cwd, or into a `.git/` internals directory) and `pre_run_command` (a conservative destructive-command deny-list — whole-disk/home `rm -rf`, force-push to a protected branch). Cascade blocks via **exit code 2** (+ a stderr reason string) — a materially different protocol from Cursor's stdout-JSON `{block, reason}` hooks.json form, even though the surrounding install/reconcile infra (`writeWindsurfHooksJson`/`removeWindsurfHooksJson` in `src/runtime-hooks-surface.cts`) mirrors `writeCursorHooksJson`/`removeCursorHooksJson`'s shape. Cascade has **no context-injection channel** (no `additional_context`-style advisory response channel), so the 4 advisory hook events GSD registers on Cursor (`sessionStart`, `postToolUse`, `stop`, `subagentStart`/`subagentStop`) have no Windsurf/Cascade counterpart and are deliberately **not ported** — only the 2 events with a genuine blocking analog are wired. `installSurface` stays `profile-marker-only` (unchanged); the hook bus is wired from inside that branch, gated on `hooksSurface === 'windsurf-hooks-json'` rather than a hardcoded runtime check.
|
||||
|
||||
---
|
||||
|
||||
## trae
|
||||
@@ -645,3 +649,122 @@ Documentation gaps:
|
||||
- configHome — skills/commands/agents homes are documented (`~/.zcode/skills`, `~/.zcode/commands`, `~/.zcode/agents`); the exact settings filename under `~/.zcode` (where MCP server config is stored) is not fully documented at time of writing.
|
||||
- Maintenance note — ZCode is a young, fast-moving app (observed at v3.2.x); these axes may need revision as its on-disk config layout stabilizes. Because ZCode also natively imports skills/MCP from `~/.claude`, installing GSD to BOTH `claude` and `zcode` can surface duplicated skills inside ZCode; this overlap is expected and documented.
|
||||
|
||||
EoS migration status (#2101, ADR-1239): ZCode's install is fully dogfooded through the declarative adapter — its shared-hooks exclusion (previously a hardcoded `!isZcode` branch in `bin/install.js`) is now folded onto `hostBehaviors.skipSharedHooksInstall`, byte-parity with the prior install (ZCode's golden install tree has zero hook files). The two capability upgrades anticipated for ZCode both remain **blocked** on undocumented on-disk formats — `hookBus` and `transport` above stay documented-but-unimplemented pending ZCode publishing those formats, and implementing a guessed format risks a false-green descriptor, so neither upgrade is wired:
|
||||
- **Hook automation** (the plugin `Hook` component, `hookBus: host` above) — https://zcode.z.ai/en/docs/plugin documents the capability only at a high level ("Automation hooks triggered on specific events"; components are "detected from directory layout, shown as badges"). No config file format, on-disk location, event-name vocabulary, or payload schema is published, so GSD cannot faithfully wire hook events into a plugin bundle. BLOCKED (undocumented on-disk hook-config format).
|
||||
- **MCP registration** (`transport: mcp` above) — https://zcode.z.ai/en/docs/mcp-services confirms servers are "stored in the .zcode configuration file of the chosen scope" and accepts both a bare `{"server-name":{...}}` map and an `{"mcpServers":{...}}` wrapper shape, but does not document the exact settings filename/path or full schema (the docs describe the UI flow, not the on-disk contract) — this is the same gap already noted under `configHome` above. BLOCKED (undocumented settings-filename/schema gap).
|
||||
|
||||
---
|
||||
|
||||
## pi
|
||||
|
||||
> pi (pi.dev) is a bun-runtime Programmatic-CLI: it exposes an in-process TypeScript `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) rather than a settings-file or slash-markdown surface. GSD ships a single native-extension file (`pi/gsd.cjs`) installed to `~/.pi/agent/extensions/gsd.cjs` (global) or `.pi/extensions/gsd.cjs` (local) — the programmatic-CLI peer of the OpenCode/Kilo native-plugin binding. **Sourcing note:** the citations below are the pi.dev documentation pages named in ADR-1239 Stage 1 (#2102) as the source for each axis; this environment did not have live doc-fetch access at authoring time, so the Evidence column below is a paraphrase of pi's documented extension model rather than a verbatim excerpt — a maintainer with Context7/web access should verify the exact wording before treating this section as fully cited (flagged in the #2102 PR).
|
||||
|
||||
| Axis | Value | Source | Evidence |
|
||||
|---|---|---|---|
|
||||
| embeddingMode | imperative | https://pi.dev/docs/latest/extensions | pi extensions are loaded in-process (via jiti) and call an `ExtensionAPI` object directly (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) — an in-process programmatic API, not a config-file-only integration. |
|
||||
| commandSurface | slash-programmatic | https://pi.dev/docs/latest/extensions | Commands are registered by calling `registerCommand(name, definition)` from extension code, not by dropping a markdown/TOML file — the command surface is code, not a file format. |
|
||||
| modelMode | active | https://pi.dev/docs/latest/extensions | The `ExtensionAPI` exposes `registerProvider`, letting an extension supply/select model providers programmatically rather than only reading a static config value. |
|
||||
| hookBus | host | https://pi.dev/docs/latest/extensions | `pi.on(event, handler)` subscribes an extension to host-fired lifecycle events (e.g. `tool_call`) — the pi host owns and fires the event bus; extensions only subscribe. |
|
||||
| stateIO | session-log-append | https://pi.dev/docs/latest/session-format | pi persists conversation/tool-call state as an append-only session log/transcript format rather than exposing unrestricted local filesystem access to extensions. |
|
||||
| transport | native-extension | https://pi.dev/docs/latest/extensions | Integration is a single loaded extension file (`~/.pi/agent/extensions/<file>.cjs`), not an MCP server process — the peer mechanism to OpenCode's native `plugins/*.js` adapter. |
|
||||
| runtime | bun | https://pi.dev | pi is distributed and executed as a bun-runtime CLI (its extensions are loaded via jiti under bun, not Node.js or Python). |
|
||||
| dispatch.namedDispatch | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | The `ExtensionAPI` documents `registerCommand`/`registerTool`/`registerProvider`/`pi.on`; it does not document a named-subagent-invocation primitive. |
|
||||
| dispatch.nested | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No documented subagent-of-subagent nesting capability. |
|
||||
| dispatch.maxDepth | 0 | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No named-dispatch primitive is documented at all (see `dispatch.namedDispatch`), so there is no nesting depth to bound; `0` records "no dispatch levels beyond the root extension," not a measured limit. |
|
||||
| dispatch.background | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No documented background/async subagent-execution primitive. |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | pi has no named-dispatch primitive (see `dispatch.namedDispatch`), so there is no subagent tool-surface to classify as `full`/`read-only`. |
|
||||
| dispatch.backgroundDispatch | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | Same gap as `dispatch.background` — no background-dispatch primitive is documented, so a background-dispatched agent spawning further named sub-agents is not possible. |
|
||||
|
||||
Sources consulted:
|
||||
- https://pi.dev
|
||||
- https://pi.dev/docs/latest/extensions
|
||||
- https://pi.dev/docs/latest/session-format
|
||||
|
||||
Documentation gaps:
|
||||
- dispatch.namedDispatch / dispatch.nested / dispatch.subagentToolkit — pi's `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) does not document a named-subagent-dispatch primitive at all, unlike Claude Code/Codex/OpenCode-style "Agent tool" surfaces; all three axes stay `undocumented` and negotiation fails closed (no named dispatch, dispatch flattened).
|
||||
- dispatch.maxDepth / dispatch.background / dispatch.backgroundDispatch — recorded as `0`/`false`/`false` (not `undocumented`) because the absence of any dispatch primitive is itself the documented ceiling, matching `shouldFlattenDispatch`'s fail-closed default.
|
||||
- This section's Evidence-column wording was authored without live Context7/web-fetch access (see the sourcing note above the table) — verify against the cited pi.dev pages before relying on it for a future capability upgrade.
|
||||
|
||||
EoS migration status (#2102 Stage 1, ADR-1239): pi lands as a NET-NEW installable runtime — pure additive descriptor + installer wiring, no prior `runtime === 'pi'` branches existed to fold. `artifactLayout` is declared empty (`global: []`, `local: []`) — pi has no skills/commands/agents layout, and installs as **PLUGIN-ONLY**: `hostBehaviors.pluginOnlyInstall: true` explicitly skips `bin/install.js`'s generic flat-commands-and-agents fallback (the legacy path Claude Code's LOCAL layout also uses), which would otherwise write inert `commands/gsd-<cmd>.md` + `agents/gsd-<name>.md` reference files no part of pi ever reads. pi's `/gsd` command and `gsd_invoke` tool are registered **programmatically** by the native extension (`pi/gsd.cjs` → `extensions/gsd.cjs`, mirroring OpenCode/Kilo's `nativePlugin` shape) — pi has no host-read markdown surface at all (unlike Claude/OpenCode/Kilo, which scan a `commands/`/`command/` directory), so a declarative artifact surface would be dead weight, not merely unused. `dispatch.subagentToolkit: "undocumented"` and `dispatch.backgroundDispatch: false` are both required by the capability validator's dispatch schema and reflect that pi has no documented named-dispatch primitive at all. (Stage 1 originally also set `hostBehaviors.skipSharedHooksInstall:true`, reasoning the staged `hooks/*.js` bundle would be dead weight for pi the way it genuinely is for Kilo/ZCode — **corrected in Stage 2 below**: pi's native extension DOES spawn them, so they are live, not dead, and the flag was removed.)
|
||||
|
||||
EoS migration status (#2102 Stage 2, ADR-1239): Stage 1's "in-process `gsd-core` command-routing hub" framing was aspirational and is corrected here — no fully-populated hub factory exists anywhere in gsd-core (every `createHub()` caller in the tree builds a single-family hub for its own narrow purpose), so `/gsd` and `gsd_invoke` instead dispatch via **SUBPROCESS REUSE**: `dispatchGsdCommand` (`src/shell-command-projection.cts`) spawns `gsd-core/bin/gsd-tools.cjs <family> [subcommand] ... --cwd <dir> --raw --json-errors` bounded and non-throwing, mirroring the precedent already established for the OpenCode/Kilo hook bridge (`.opencode/plugins/gsd-core.js`'s "Architecture: SUBPROCESS REUSE" header). The companion MCP server's `gsd_invoke_command` tool dispatches through the SAME shared helper (it had the identical `createHub()`-with-no-args bug). `/gsd`'s command handler is `handler(args, ctx)` (pi's real ExtensionAPI shape — a raw args string, not `execute(ctx)`); `gsd_invoke`'s tool handler is the real 5-arg `execute(toolCallId, params, signal, onUpdate, ctx)`. The event surface (`EXTENSION_EVENT_SURFACES.pi`, `src/host-integration.cts`) now declares the full ~30-event pi ExtensionAPI vocabulary (was a placeholder `['tool_call']`), and `pi/gsd.cjs` binds `session_start` (→ `gsd-ensure-canonical-path.js`), `before_agent_start` (→ `gsd-workflow-guard.js`, a forward-compatible no-op today since that hook's triggers are tool-scoped), `session_before_compact` (→ `gsd-context-monitor.js`), and `tool_call`, each as a bounded fail-open `spawnSync` subprocess (mirroring `.opencode/plugins/gsd-core.js`'s `runHook`). `modelMode: active` is realized via `pi.on('before_provider_request', ...)`, which resolves a tier through the model-catalog's now-populated `runtimeTierDefaults.pi` entries (bare anthropic ids — `claude-opus-4-8`/`claude-sonnet-5`/`claude-haiku-4-5`, matching the `claude` runtime's own ids since pi talks the anthropic API) and returns a modified payload, or `undefined` (fail-open, pi's model left untouched) when resolution comes back null — **not** `registerProvider`, which would register a new model provider rather than steering pi's existing built-in anthropic models.
|
||||
|
||||
**Adversarial-review correction (#2102 Stage 2, post-review):** the event bridges above and the `/gsd` tokenizer's `hooks/lib/git-cmd.js` require were DEAD in a real install — Stage 1's `hostBehaviors.skipSharedHooksInstall:true` meant pi shipped NO `hooks/` directory at all, so `runHook('gsd-ensure-canonical-path.js', ...)` etc. always hit the "hook file absent → silent no-op" branch, and the tokenizer always fell back to plain whitespace-splitting. The tests masked this because they run against the dev tree, where `hooks/` genuinely exists. **Fix:** `capabilities/pi/capability.json` no longer sets `skipSharedHooksInstall` — pi is architecturally identical to OpenCode here (`hooksSurface: "none"` + a native extension that spawns the staged hooks), not to Kilo/ZCode (`hooksSurface: "none"` with NO plugin surface, where the same hooks genuinely are dead weight). pi now installs `hooks/` + `hooks/lib/` (27 entries: the same `INSTALLED_HOOK_FILES` set OpenCode gets) alongside `extensions/gsd.cjs`, verified end-to-end via a real `node bin/install.js --pi --global`/`--local` — `resolveEngineRoot`'s walk-up from the installed extension's own directory finds `ENGINE_ROOT/hooks/{gsd-ensure-canonical-path.js,gsd-workflow-guard.js,gsd-context-monitor.js,lib/git-cmd.js}`, and each bridge/`runHook` call exits 0 against the real installed files. `hooksSurface: "none"` + `configFormat: "none"` + `writesSharedSettings: false` are unaffected — no settings/hooks.json/config.toml is written for pi; the extension spawns hooks by absolute path, not via a config-file hook bus. `tests/fixtures/golden-install-parity/pi.json` grew from 292 → 320 entries (the 28 new `hooks/`/`hooks/lib/` files); `commands/`, `agents/`, `skills/` remain absent (`pluginOnlyInstall` is untouched — it only gates the declarative-markdown surfaces, not hooks). `tests/install-minimal-hooks.test.cjs`'s #1821 suite moved pi from the Kilo/ZCode (no-hooks) group into the OpenCode (ships-hooks) group accordingly.
|
||||
|
||||
## vscode
|
||||
|
||||
> VS Code is the IDE-profile reference host: a Marketplace/VSIX-distributed extension, NOT
|
||||
> file-projected onto a config directory — it has no `runtime.localConfigDir` in the usual sense
|
||||
> (`configHome.kind: "none"`, `localConfigDir: null`) and no CLI install surface at all
|
||||
> (`installSurface: "none"`; it is never installed by `bin/install.js` — no `--vscode` flag, no
|
||||
> `allRuntimes` membership; see `capabilities/vscode/capability.json`). The extension IS the host.
|
||||
> **Sourcing note:** the citations below are the VS Code extension API documentation pages named
|
||||
> in ADR-1239 (#2103) as the source for each axis; this environment did not have live Context7/
|
||||
> web-fetch access at authoring time, so the Evidence column is a paraphrase of VS Code's
|
||||
> documented extension model rather than a verbatim excerpt — a maintainer with Context7/web
|
||||
> access should verify the exact wording before treating this section as fully cited (same caveat
|
||||
> already flagged for the pi section above).
|
||||
|
||||
| Axis | Value | Source | Evidence |
|
||||
|---|---|---|---|
|
||||
| embeddingMode | imperative | https://code.visualstudio.com/api/references/vscode-api | The extension is loaded in-process by the extension host and calls the `vscode` namespace API directly (`vscode.commands.registerCommand`, `vscode.chat.createChatParticipant`, `vscode.lm.registerTool`) — an in-process programmatic API, not a config-file-only integration. |
|
||||
| commandSurface | palette | https://code.visualstudio.com/api/extension-guides/command | Commands are contributed via `contributes.commands` in package.json and registered with `vscode.commands.registerCommand`, surfaced through the Command Palette (and the Chat view via the chat participant) — not a markdown/TOML slash-command file format. |
|
||||
| modelMode | active | https://code.visualstudio.com/api/extension-guides/ai/language-model | The `vscode.lm` namespace lets an extension actively select a model (`vscode.lm.selectChatModels`) and send requests to it programmatically, rather than only reading a static config value. |
|
||||
| hookBus | engine | https://code.visualstudio.com/api/references/activation-events | VS Code has no cross-extension lifecycle-hook bus that GSD subscribes to; the extension host (the "engine" here, per this axis's own `host`/`engine`/`none` vocabulary) owns activation events, and GSD's own hook lifecycle runs fully in-process/engine-owned inside the extension. |
|
||||
| stateIO | sandboxed-storage | https://code.visualstudio.com/api/references/vscode-api#Memento | `context.globalState`/`context.workspaceState` (both `Memento`) are the extension's persistent storage surface — sandboxed key/value storage scoped to the extension, not unrestricted local filesystem access. |
|
||||
| transport | mcp | https://code.visualstudio.com/api/extension-guides/ai/mcp | VS Code 1.99 added native MCP client support; on the Web (webworker) entry, full GSD command dispatch is available through VS Code's native MCP client connecting to the GSD companion MCP server (`gsd-mcp-server`), not an in-process Node dispatch (which the web entry cannot run at all). |
|
||||
| runtime | sandboxed-web | https://code.visualstudio.com/api/extension-guides/web-extensions | The `browser` entry point (`vscode/browser.js`) runs in a webworker context with no Node core modules — the Web Extension execution model VS Code documents for extensions that must run in vscode.dev/github.dev. |
|
||||
| dispatch.namedDispatch | true | https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_agent-mode-tools | Registered `languageModelTools` (and the chat participant) are addressable by name — the primary agent references a tool/participant by its declared name/`toolReferenceName`, not only positionally. |
|
||||
| dispatch.nested | true | https://code.visualstudio.com/docs/copilot/copilot-chat-agents (subagents) | VS Code's chat subagent model (`#runSubagent`) explicitly supports a subagent invoking further subagents, gated by `chat.subagents.allowInvocationsFromSubagents`. |
|
||||
| dispatch.maxDepth | 5 | https://code.visualstudio.com/docs/copilot/copilot-chat-agents (subagents) | Documented as VS Code's maximum nesting depth for `#runSubagent` chains — also matches this repo's existing `PROFILE_BASELINES.ide.dispatch.maxDepth` baseline. |
|
||||
| dispatch.background | true | https://code.visualstudio.com/api/extension-guides/ai/tools | Language Model Tools can be invoked as part of an asynchronous agent turn (the primary agent does not block synchronously on a single extension call). |
|
||||
| dispatch.subagentToolkit | undocumented | no authoritative doc found at authoring time | VS Code's subagent documentation does not state whether a subagent's tool surface is restricted to read-only tools or the full set an extension registers; recorded `undocumented` (fails closed to `read-only` in negotiation) rather than guessed. |
|
||||
| dispatch.backgroundDispatch | undocumented | no authoritative doc found at authoring time | Whether a background-dispatched subagent can itself spawn further NAMED subagents (the #853 discriminator) is not stated in the sources reviewed; recorded `undocumented` (fails closed to `false`) rather than guessed. |
|
||||
|
||||
Sources consulted:
|
||||
- https://code.visualstudio.com/api/references/vscode-api
|
||||
- https://code.visualstudio.com/api/extension-guides/command
|
||||
- https://code.visualstudio.com/api/extension-guides/ai/language-model
|
||||
- https://code.visualstudio.com/api/extension-guides/ai/tools
|
||||
- https://code.visualstudio.com/api/extension-guides/ai/mcp
|
||||
- https://code.visualstudio.com/api/extension-guides/web-extensions
|
||||
- https://code.visualstudio.com/api/references/activation-events
|
||||
- https://code.visualstudio.com/docs/copilot/copilot-chat-agents
|
||||
|
||||
Documentation gaps:
|
||||
- dispatch.subagentToolkit / dispatch.backgroundDispatch — the reviewed sources document that
|
||||
`#runSubagent` exists (v1.105+, `chat.subagents.allowInvocationsFromSubagents`, max nesting
|
||||
depth 5) but do not state the subagent tool-restriction model or whether a background-dispatched
|
||||
subagent can itself spawn further named subagents; both stay `undocumented` and negotiation
|
||||
fails closed.
|
||||
- This section's Evidence-column wording was authored without live Context7/web-fetch access (see
|
||||
the sourcing note above the table) — verify against the cited pages before relying on it for a
|
||||
future capability upgrade, same caveat as the pi section above.
|
||||
|
||||
EoS migration status (#2103): vscode lands as a registry runtime (role:runtime) for
|
||||
validator/host-integration coverage ONLY — it is deliberately NOT a CLI-installable runtime
|
||||
(`installSurface: "none"`, never in `bin/install.js`'s `allRuntimes`; see the
|
||||
`NON_INSTALLABLE_RUNTIMES` carve-out in `tests/runtime-flags.test.cjs`). The extension surface
|
||||
(`vscode/extension.js`, `vscode/browser.js`, `vscode/host-binding.js`, `vscode/package.json`) is
|
||||
distributed via the Marketplace/VSIX, not `npx --vscode` — there is no `docs/how-to/install-on-
|
||||
your-runtime.md` entry for it. Dispatch is SUBPROCESS REUSE on desktop (the same shared
|
||||
`dispatchGsdCommand` in `gsd-core/bin/lib/shell-command-projection.cjs` the pi extension and the
|
||||
companion MCP server use) via `vscode/extension.js`'s `main` entry (Node). The `browser` entry
|
||||
(`vscode/browser.js`) is a SEPARATE, independently zero-Node-API file: it does NOT require
|
||||
`host-binding.js` because that module's engine-lib dependencies (`state-io.cjs`,
|
||||
`adapter-imperative.cjs` → `install-engine.cjs`/`capability-loader.cjs`,
|
||||
`model-adapter.cjs` → `model-resolver.cjs` → `config-loader.cjs`/`configuration.cjs`) all pull in
|
||||
Node's `fs`/`os`/`path` at module-load time — requiring any of them from a webworker context would
|
||||
throw immediately. `browser.js` instead composes its own minimal surface directly against
|
||||
`vscode.lm`, and its command/tool/chat handlers surface an honest "full dispatch is unavailable on
|
||||
web; configure the GSD MCP server" message rather than a silent failure. The chat participant
|
||||
(`@gsd`) and Language Model Tools (a representative 3-tool set — `gsd_progress`, `gsd_workstreams`,
|
||||
`gsd_plan_phase` — matching real shipped skills that map onto a single, safe, read-only
|
||||
`gsd-tools.cjs` command) are registered on BOTH entries identically; only the dispatch behavior
|
||||
differs. `#runSubagent` wiring (`registerSubagentDispatch`/`dispatchAsSubagent`, gated on
|
||||
`chat.subagents.allowInvocationsFromSubagents` availability, fail-soft on older/Insiders-gated
|
||||
hosts) adds a belt-and-suspenders `maxDepth: 5` ceiling independent of whatever VS Code's own chat
|
||||
engine enforces natively — there is no separate extension-side "subagent contribution"
|
||||
registration API beyond the chat participant + Language Model Tools already registered; VS Code's
|
||||
chat engine surfaces them to `#runSubagent` on its own.
|
||||
|
||||
|
||||
210
docs/registries/README.md
Normal file
210
docs/registries/README.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# GSD Registries: Community Capability Registry & EoS Registry
|
||||
|
||||
Specification, entry schema, and submission process for GSD's two third-party discoverability catalogs — the **GSD Community Capability Registry** and the **GSD EoS Registry**.
|
||||
|
||||
---
|
||||
|
||||
## Non-endorsement stance
|
||||
|
||||
> Inclusion in this registry means only that a maintainer merged a PR that linked to the author's repository. It is not an endorsement. GSD has not reviewed, tested, audited, or verified the correctness, quality, safety, or security of any listed solution, nor its claimed GSD interactions. Use at your own risk; evaluate the linked source yourself. Entries are removed only for illegal content, malware, spam, or a link that is dead/completely non-functional — never curated for quality.
|
||||
|
||||
This stance applies identically to every entry in both registries. It is reproduced verbatim at the top of each generated catalog (`capability-registry.md`, `eos-registry.md`).
|
||||
|
||||
## Narrow removal policy
|
||||
|
||||
A merged entry is removed **only** for one of these reasons:
|
||||
|
||||
- The linked content is illegal.
|
||||
- The linked repository distributes malware.
|
||||
- The entry is spam (not a genuine, working solution).
|
||||
- The linked repository or its default branch is dead or completely non-functional (404, archived-and-empty, permanently inaccessible).
|
||||
|
||||
A registry entry is **never** removed for quality, staleness of a working project, disagreement with its design, or because a maintainer would have built it differently. The registry is a directory, not a curated marketplace — see [Non-endorsement stance](#non-endorsement-stance) above.
|
||||
|
||||
---
|
||||
|
||||
## What gets listed
|
||||
|
||||
Two independent catalogs, sharing one schema shape, one non-endorsement stance, and one submission process:
|
||||
|
||||
- **Community Capability Registry** (`docs/registries/capability-registry.md`, generated from `docs/registries/capabilities.json`) — third-party **Feature Capabilities**: plug-ins that attach at GSD's Loop Extension Points (ADR-857, ADR-894, ADR-1244) and are installed with `gsd capability install <spec>`.
|
||||
- **EoS Registry** (`docs/registries/eos-registry.md`, generated from `docs/registries/eos.json`) — third-party **Embeddable Orchestration System (EoS)** host integrations: projects that embed GSD as an orchestration engine inside a host through the ADR-1239 Host-Integration Interface.
|
||||
|
||||
Both registries are non-endorsing discoverability catalogs (issue #2182). Neither is the runtime **Capability Registry** (the generated manifest consumed at load time, ADR-894 §5) or the **Capability Registry Overlay** (the runtime loader that merges an installed third-party manifest into that generated registry, ADR-1244 D2) — see `CONTEXT.md` → "Community Capability Registry" and "EoS Registry" for the full disambiguation.
|
||||
|
||||
---
|
||||
|
||||
## Entry schema
|
||||
|
||||
Every entry is one JSON object in `docs/registries/capabilities.json` or `docs/registries/eos.json`, validated by `scripts/registry-schema.cjs`. Field names below are exact and case-sensitive; unknown top-level keys are rejected.
|
||||
|
||||
### Capability entries (`capabilities.json`, `type: "capability"`)
|
||||
|
||||
| Field | Required | Meaning |
|
||||
|---|---|---|
|
||||
| `id` | yes | Unique slug across the registry (`^[a-z0-9]+(-[a-z0-9]+)*$`). |
|
||||
| `name` | yes | Human-readable name. |
|
||||
| `type` | yes | Must equal `"capability"`. |
|
||||
| `repo` | yes | `owner/repo` on github.com — the author's own repository. |
|
||||
| `description` | yes | One-paragraph plain-language description of the solution and the problem it solves. |
|
||||
| `author` | yes | Author name (and, optionally, contact). |
|
||||
| `license` | yes | SPDX identifier (or `UNLICENSED` / `Proprietary`). |
|
||||
| `enginesGsd` | yes | Declared `engines.gsd` semver range (ADR-1244 D1), e.g. `>=1.6.0`. |
|
||||
| `install` | yes | Exact, copy-pasteable install command — the ADR-1244 URL-import flow, e.g. `gsd capability install https://github.com/OWNER/REPO.git#v1.0.0`. |
|
||||
| `uninstall` | yes | Exact, copy-pasteable removal command, e.g. `gsd capability remove <id>`. |
|
||||
| `interactions` | yes | Object — see below. |
|
||||
| `discussion` | yes | URL of this entry's GitHub Discussion (`https://github.com/<owner>/<repo>/discussions/<n>`). |
|
||||
|
||||
`interactions` (Capability):
|
||||
|
||||
| Field | Required | Meaning |
|
||||
|---|---|---|
|
||||
| `loopExtensionPoints` | yes, non-empty | Subset of the 12 Loop Extension Points the capability registers on: `discuss:pre`, `discuss:post`, `plan:pre`, `plan:post`, `execute:pre`, `execute:wave:pre`, `execute:wave:post`, `execute:post`, `verify:pre`, `verify:post`, `ship:pre`, `ship:post`. |
|
||||
| `hookKinds` | yes | Subset of `step`, `contribution`, `gate` — the hook kind registered at each point above. |
|
||||
| `configKeys` | yes | Array of federated config keys the capability owns (may be empty). |
|
||||
| `requires` | yes | Array of other Capability ids this capability depends on (may be empty). |
|
||||
| `runtimeCompat` | yes | Array of compatible runtimes; `["all"]` is allowed. |
|
||||
| `produces` | yes | Array describing artifacts/data the capability produces (may be empty). |
|
||||
| `consumes` | yes | Array describing artifacts/data the capability consumes (may be empty). |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "linear-issue-sync",
|
||||
"name": "Linear Issue Sync",
|
||||
"type": "capability",
|
||||
"repo": "some-org/gsd-cap-linear-sync",
|
||||
"description": "Mirrors ROADMAP.md items to Linear issues as a ship:post contribution.",
|
||||
"author": "Some Org <hello@some-org.example>",
|
||||
"license": "MIT",
|
||||
"enginesGsd": ">=1.6.0",
|
||||
"install": "gsd capability install https://github.com/some-org/gsd-cap-linear-sync.git#v1.0.0",
|
||||
"uninstall": "gsd capability remove linear-issue-sync",
|
||||
"interactions": {
|
||||
"loopExtensionPoints": ["ship:post"],
|
||||
"hookKinds": ["contribution"],
|
||||
"configKeys": ["linear-issue-sync.enabled"],
|
||||
"requires": [],
|
||||
"runtimeCompat": ["all"],
|
||||
"produces": ["linear-issue-links"],
|
||||
"consumes": ["ROADMAP.md"]
|
||||
},
|
||||
"discussion": "https://github.com/open-gsd/gsd-core/discussions/1234"
|
||||
}
|
||||
```
|
||||
|
||||
### EoS entries (`eos.json`, `type: "eos"`)
|
||||
|
||||
| Field | Required | Meaning |
|
||||
|---|---|---|
|
||||
| `id` | yes | Unique slug across the registry. |
|
||||
| `name` | yes | Human-readable name. |
|
||||
| `type` | yes | Must equal `"eos"`. |
|
||||
| `repo` | yes | `owner/repo` on github.com — the author's own repository. |
|
||||
| `description` | yes | One-paragraph plain-language description of the host integration. |
|
||||
| `author` | yes | Author name (and, optionally, contact). |
|
||||
| `license` | yes | SPDX identifier (or `UNLICENSED` / `Proprietary`). |
|
||||
| `enginesGsd` | yes | Declared `engines.gsd` semver range this integration targets. |
|
||||
| `protocolVersion` | yes | Integer ≥ 1 — the ADR-1239 `PROTOCOL_VERSION` this integration implements. |
|
||||
| `install` | yes | The host-plugin's own install steps (free string). |
|
||||
| `uninstall` | yes | The host-plugin's own teardown steps (free string). |
|
||||
| `interactions` | yes | Object — see below. |
|
||||
| `discussion` | yes | URL of this entry's GitHub Discussion. |
|
||||
|
||||
`interactions` (EoS):
|
||||
|
||||
| Field | Required | Meaning |
|
||||
|---|---|---|
|
||||
| `interfacePoints` | yes, non-empty | Subset of the six ADR-1239 interface points it binds: `command`, `dispatch`, `model`, `hooks`, `state`, `artifact`. |
|
||||
| `profile` | yes | One of the three host-capability profiles: `programmatic-cli`, `declarative-cli`, `ide`. |
|
||||
| `axes` | yes | Object with **exactly** the eight ADR-1239 negotiated axes keys: `embeddingMode`, `commandSurface`, `dispatch`, `modelMode`, `hookBus`, `stateIO`, `transport`, `runtime`. |
|
||||
|
||||
`axes` value vocabulary:
|
||||
|
||||
| Axis | Allowed values |
|
||||
|---|---|
|
||||
| `embeddingMode` | `imperative`, `declarative` |
|
||||
| `commandSurface` | `slash-file`, `slash-programmatic`, `slash-toml`, `palette`, `prose-only` |
|
||||
| `dispatch` | Free descriptive string (ADR-1239 `dispatch` is a structured object; the registry accepts a human summary). |
|
||||
| `modelMode` | `active`, `passive` |
|
||||
| `hookBus` | `host`, `engine`, `none` |
|
||||
| `stateIO` | `filesystem`, `sandboxed-storage`, `session-log-append` |
|
||||
| `transport` | `mcp`, `native-extension` |
|
||||
| `runtime` | `node`, `bun`, `sandboxed-web`, `python`, `go`, `rust`, `electron`, `other` |
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "acme-editor-embed",
|
||||
"name": "Acme Editor GSD Embed",
|
||||
"type": "eos",
|
||||
"repo": "some-org/acme-gsd-embed",
|
||||
"description": "Embeds GSD as an orchestration engine inside the Acme editor's command palette.",
|
||||
"author": "Some Org <hello@some-org.example>",
|
||||
"license": "Apache-2.0",
|
||||
"enginesGsd": ">=1.6.0",
|
||||
"protocolVersion": 1,
|
||||
"install": "Install the Acme GSD Embed extension from the Acme marketplace — see https://github.com/some-org/acme-gsd-embed#install",
|
||||
"uninstall": "Remove the extension from Acme's extension manager.",
|
||||
"interactions": {
|
||||
"interfacePoints": ["command", "dispatch", "model", "hooks", "state", "artifact"],
|
||||
"profile": "ide",
|
||||
"axes": {
|
||||
"embeddingMode": "declarative",
|
||||
"commandSurface": "palette",
|
||||
"dispatch": "Routes palette invocations through Acme's own task-runner to gsd_run",
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
"stateIO": "filesystem",
|
||||
"transport": "native-extension",
|
||||
"runtime": "electron"
|
||||
}
|
||||
},
|
||||
"discussion": "https://github.com/open-gsd/gsd-core/discussions/1235"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Submission process
|
||||
|
||||
Registration is a **documentation PR**, per [CONTRIBUTING.md → Documentation Updates](../../CONTRIBUTING.md#documentation-updates--update-the-relevant-docs):
|
||||
|
||||
1. **Fork** the repository.
|
||||
2. **Edit** `docs/registries/capabilities.json` (Capability Registry) or `docs/registries/eos.json` (EoS Registry) and append exactly one entry matching the [schema](#entry-schema) above.
|
||||
3. **Run `npm run gen:registry`** to regenerate the corresponding `docs/registries/capability-registry.md` or `docs/registries/eos-registry.md`. Commit both the JSON source and the regenerated markdown.
|
||||
4. **Open a PR** from a `docs/<issue#>-<slug>` branch (see CONTRIBUTING.md branch-naming conventions) using the [registry-entry PR template](../../.github/PULL_REQUEST_TEMPLATE/registry-entry.md).
|
||||
5. A maintainer reviews and merges. The only gate is whether the entry is a real, linkable solution with all required fields present — not a quality judgment (see [Non-endorsement stance](#non-endorsement-stance)).
|
||||
|
||||
**One entry = one PR.** Do not bundle multiple registry additions, updates, or removals into a single PR.
|
||||
|
||||
**The generated `.md` files are GENERATED — never hand-edit them.** `docs/registries/capability-registry.md` and `docs/registries/eos-registry.md` are produced by `scripts/gen-registry.cjs` from `capabilities.json` / `eos.json`. A PR that edits the generated markdown without a matching JSON source change will fail the `gen:registry --check` drift gate. Always edit the JSON and regenerate.
|
||||
|
||||
---
|
||||
|
||||
## Latest-release tracking
|
||||
|
||||
Each entry embeds a live [shields.io](https://shields.io) badge and a permalink to the linked repository's latest GitHub Release:
|
||||
|
||||
```
|
||||

|
||||
```
|
||||
|
||||
```
|
||||
https://github.com/OWNER/REPO/releases/latest
|
||||
```
|
||||
|
||||
There is no re-registration on new releases: register once, and your GitHub Releases are the update channel forever. The badge and permalink are rendered live by GitHub's markdown viewer directly from the linked repository — the registry itself never needs a follow-up PR when you cut a new version.
|
||||
|
||||
---
|
||||
|
||||
## Ranking + comments
|
||||
|
||||
Ranking and community feedback live in **GitHub Discussions**, not in the registry markdown. Each merged entry gets exactly one Discussion in a dedicated `Registry` Discussions category:
|
||||
|
||||
- **Upvotes** on the Discussion post and on individual comments, with GitHub's built-in **Top** sort surfacing the most-upvoted community feedback first.
|
||||
- **Threaded comments** for experience reports, questions, and follow-up from other users.
|
||||
|
||||
**Operational setup (one-time, per repo):** a repo admin creates the `Registry` category under this repository's Discussions settings. From then on, every merged entry gets its own Discussion thread created in that category, and the thread's URL is recorded in the entry's `discussion` field (see [Entry schema](#entry-schema) above) so the generated catalog links directly to it.
|
||||
1
docs/registries/capabilities.json
Normal file
1
docs/registries/capabilities.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
9
docs/registries/capability-registry.md
Normal file
9
docs/registries/capability-registry.md
Normal file
@@ -0,0 +1,9 @@
|
||||
<!-- GENERATED by scripts/gen-registry.cjs from docs/registries/capabilities.json — do not edit by hand; run `npm run gen:registry` -->
|
||||
|
||||
# GSD Community Capability Registry
|
||||
|
||||
> **Not an endorsement.** Inclusion means only that a maintainer merged a PR linking the author's repository — GSD has not reviewed, tested, or verified any listing. See the [registry README](./README.md).
|
||||
|
||||
_To add your capability, see the [registry README](./README.md)._
|
||||
|
||||
_No entries yet — be the first: see [README](./README.md)._
|
||||
9
docs/registries/eos-registry.md
Normal file
9
docs/registries/eos-registry.md
Normal file
@@ -0,0 +1,9 @@
|
||||
<!-- GENERATED by scripts/gen-registry.cjs from docs/registries/eos.json — do not edit by hand; run `npm run gen:registry` -->
|
||||
|
||||
# GSD EoS Registry
|
||||
|
||||
> **Not an endorsement.** Inclusion means only that a maintainer merged a PR linking the author's repository — GSD has not reviewed, tested, or verified any listing. See the [registry README](./README.md).
|
||||
|
||||
_To add your integration, see the [registry README](./README.md)._
|
||||
|
||||
_No entries yet — be the first: see [README](./README.md)._
|
||||
1
docs/registries/eos.json
Normal file
1
docs/registries/eos.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -133,11 +133,16 @@ file before the job passes.
|
||||
|
||||
### 2.4 Locale-safe text scanning
|
||||
|
||||
**Control:** Text output and user-facing strings are scanned for locale-unsafe
|
||||
constructs (non-ASCII homoglyphs, bidirectional override characters, invisible
|
||||
Unicode) that could be used to obscure malicious content in diffs or logs.
|
||||
**Control:** Text output and user-facing strings are scanned for invisible
|
||||
Unicode and bidirectional override characters that could be used to obscure
|
||||
malicious content in diffs or logs. The live hooks
|
||||
(`gsd-prompt-guard.js`, `gsd-read-injection-scanner.js`) inline their own
|
||||
Unicode-detection patterns for hook independence — they do not call
|
||||
`scanForInjection` from `security.cts`. The centralized `scanForInjection`
|
||||
function serves as the CI codebase-scanner engine
|
||||
(`tests/prompt-injection-scan.security.test.cjs`).
|
||||
|
||||
**Why it matters:** Unicode homoglyph and BiDi attacks are documented
|
||||
**Why it matters:** Unicode invisible-character and BiDi attacks are documented
|
||||
supply-chain vectors (CVE-2021-42574 — "Trojan Source"). Detecting them at scan
|
||||
time prevents invisible payload injection in source and output files.
|
||||
|
||||
|
||||
@@ -2211,15 +2211,15 @@ PreToolUse 钩子,检测 Claude 在 GSD 工作流上下文之外尝试文件
|
||||
|
||||
### 99. 改进的提示注入扫描器
|
||||
|
||||
**钩子:** `gsd-prompt-guard.js`
|
||||
**脚本:** `scripts/prompt-injection-scan.sh`
|
||||
**钩子:** `gsd-prompt-guard.js`、`gsd-read-injection-scanner.js`
|
||||
**脚本:** `scripts/prompt-injection-scan.sh`、`scripts/base64-scan.sh`
|
||||
|
||||
**目的:** 增强对规划构件中提示注入尝试的检测,添加不可见 Unicode 字符检测、编码混淆模式和基于熵的分析。
|
||||
**目的:** 对规划构件和摄入内容中提示注入尝试的深度防御检测。实时钩子为保持独立性内联了自己的模式子集(不导入 `security.cts`)。CI 扫描器(`security.cts` 中的 `scanForInjection`)为测试中的全代码库扫描提供集中引擎。
|
||||
|
||||
**需求:**
|
||||
- REQ-SCAN-INJ-01:扫描器必须检测不可见 Unicode 字符(零宽空格、软连字符等)
|
||||
- REQ-SCAN-INJ-02:扫描器必须检测编码混淆模式(base64 编码的指令、同形字)
|
||||
- REQ-SCAN-INJ-03:扫描器必须应用熵分析以标记意外位置的高熵字符串
|
||||
- REQ-SCAN-INJ-01:实时钩子必须检测不可见 Unicode 字符(零宽空格、软连字符、Unicode 标签块 U+E0000–E007F)
|
||||
- REQ-SCAN-INJ-02:实时钩子必须检测已知注入模式(指令覆盖、角色操纵、系统提示提取、伪造消息边界)。Base64 解码扫描是 CI 时控制(`scripts/base64-scan.sh`),不是实时钩子 — 实时钩子仅匹配 base64 外泄短语正则,不解码。
|
||||
- REQ-SCAN-INJ-03:~~扫描器必须应用熵分析~~ — 熵分析(`scanEntropyAnomalies`)在 #2198 中作为死代码被移除(零生产调用者;实时钩子不执行熵分析)。此需求推迟到有可维护的实时实现时。
|
||||
- REQ-SCAN-INJ-04:扫描器必须保持仅建议性 — 检测会被记录,而不会阻止
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@ const capabilities = {
|
||||
"ai-integration": {
|
||||
"id": "ai-integration",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -95,7 +95,7 @@ const capabilities = {
|
||||
"antigravity": {
|
||||
"id": "antigravity",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Antigravity",
|
||||
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.",
|
||||
"tier": "core",
|
||||
@@ -196,7 +196,7 @@ const capabilities = {
|
||||
"assumption-delta": {
|
||||
"id": "assumption-delta",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Assumption-delta architecture checkpoint",
|
||||
"description": "Rarely-firing advisory checkpoint that triggers when a phase makes something plural, optional, or chosen that used to be singular, required, or derived. Surfaces one identity-model question (promote the new general representation to primary, or add it alongside?) so a silent primary-key drift does not accumulate into a later user-facing bug. Non-blocking; fires only on a detected signal.",
|
||||
"tier": "full",
|
||||
@@ -242,7 +242,7 @@ const capabilities = {
|
||||
"audit": {
|
||||
"id": "audit",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -279,7 +279,7 @@ const capabilities = {
|
||||
"augment": {
|
||||
"id": "augment",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -386,7 +386,7 @@ const capabilities = {
|
||||
"claude": {
|
||||
"id": "claude",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Claude Code",
|
||||
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
|
||||
"tier": "core",
|
||||
@@ -491,7 +491,7 @@ const capabilities = {
|
||||
"claude-orchestration": {
|
||||
"id": "claude-orchestration",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Claude orchestration (Workflow backend)",
|
||||
"description": "Default-off, BETA, claude-only capability that adopts Claude Code's Workflow tool (the engine behind /effort ultracode) as an optional parallel-execution backend for the GSD loop. When the runtime exposes the Workflow tool and claude_orchestration.execution_backend resolves to 'workflow', execute-phase emits a generated Workflow script (waves -> parallel() barriers, plans -> agent({ agentType: 'gsd-executor', isolation: 'worktree' }), files_modified overlap -> separate sequential stages, resumeFromRunId wired to the phase run id, shared token budget) that composes the SAME gsd-executor agent and worktree isolation the inline path uses, restoring the wave parallelism the #853 backgrounded-agent nesting limitation forces inline on Claude Code. (The plan-checker and verifier remain inline until separately wired — this capability delivers the parallel-execution backend, not those gates.) Also folds the ultraplan plan-offload under one runtime gate (plan:* surface). On any runtime lacking the Workflow tool, or when the capability is disabled, behaviour is byte-identical to today (inline/manual dispatch). Detection + emission live in gsd-core/bin/lib/claude-orchestration.cjs (pure, fail-closed). Mirrors the existing gsd-ultraplan-phase BETA-isolation posture.",
|
||||
"tier": "full",
|
||||
@@ -578,7 +578,7 @@ const capabilities = {
|
||||
"cline": {
|
||||
"id": "cline",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -647,7 +647,7 @@ const capabilities = {
|
||||
"code-review": {
|
||||
"id": "code-review",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Code review",
|
||||
"description": "Source-file code review and review-fix workflow support for completed execution work.",
|
||||
"tier": "full",
|
||||
@@ -708,7 +708,7 @@ const capabilities = {
|
||||
"codebuddy": {
|
||||
"id": "codebuddy",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "CodeBuddy",
|
||||
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -819,7 +819,7 @@ const capabilities = {
|
||||
"codex": {
|
||||
"id": "codex",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -904,7 +904,7 @@ const capabilities = {
|
||||
"copilot": {
|
||||
"id": "copilot",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -987,14 +987,17 @@ const capabilities = {
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"reapplyCommand": "/gsd-update --reapply"
|
||||
"reapplyCommand": "/gsd-update --reapply",
|
||||
"agentFileExtension": ".agent.md",
|
||||
"skipSharedHooksInstall": true,
|
||||
"noPathRewrite": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cursor": {
|
||||
"id": "cursor",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1115,7 +1118,7 @@ const capabilities = {
|
||||
"drift": {
|
||||
"id": "drift",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Drift detection gates",
|
||||
"description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.",
|
||||
"tier": "full",
|
||||
@@ -1193,7 +1196,7 @@ const capabilities = {
|
||||
"external-job": {
|
||||
"id": "external-job",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Async external-job scheduler adapter",
|
||||
"description": "Default-off producer of the async external-job manifest (#1164). At execute:wave:post an executor can externalize long-running compute (SLURM first, scheduler-pluggable), commit a .planning/async-jobs/<job>.json manifest, defer SUMMARY.md, and return external_job_waiting. The core loop (#1165) consumes the manifest; this capability is the only thing that writes it. NOTE on contribution point: #1164 specifies execute:wave:pre, but execute-phase.md only dispatches execute:wave:post today (wave:pre is declared in the loop host contract but not rendered); wiring wave:pre dispatch is a core-loop change #1164 explicitly puts out of scope, so this capability registers at wave:post and the executor honors the runtime_budget classification guidance before running any tagged task. The adapter (scripts/slurm-adapter.cjs) reads external_job.submit_timeout_ms / poll_timeout_ms / artifact_dir through the canonical capability-config seam (env override > config > registry default).",
|
||||
"tier": "full",
|
||||
@@ -1276,7 +1279,7 @@ const capabilities = {
|
||||
"gap-analysis": {
|
||||
"id": "gap-analysis",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1317,7 +1320,7 @@ const capabilities = {
|
||||
"graphify": {
|
||||
"id": "graphify",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1358,7 +1361,7 @@ const capabilities = {
|
||||
"hermes": {
|
||||
"id": "hermes",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1447,7 +1450,7 @@ const capabilities = {
|
||||
"intel": {
|
||||
"id": "intel",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1499,7 +1502,7 @@ const capabilities = {
|
||||
"kilo": {
|
||||
"id": "kilo",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1607,7 +1610,7 @@ const capabilities = {
|
||||
"kimi": {
|
||||
"id": "kimi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Kimi CLI",
|
||||
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; native config.toml [[hooks]] bus at ~/.kimi/config.toml; background dispatch; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -1695,7 +1698,7 @@ const capabilities = {
|
||||
"mempalace": {
|
||||
"id": "mempalace",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -1869,7 +1872,7 @@ const capabilities = {
|
||||
"nyquist": {
|
||||
"id": "nyquist",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Nyquist validation",
|
||||
"description": "Validation coverage audit that maps executed work back to tests and manual-only evidence.",
|
||||
"tier": "full",
|
||||
@@ -1919,7 +1922,7 @@ const capabilities = {
|
||||
"opencode": {
|
||||
"id": "opencode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -2025,7 +2028,7 @@ const capabilities = {
|
||||
"pattern-mapper": {
|
||||
"id": "pattern-mapper",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Pattern mapping",
|
||||
"description": "Optional codebase-pattern mapping before planning; owns the pattern mapper agent and workflow.pattern_mapper activation key.",
|
||||
"tier": "full",
|
||||
@@ -2076,10 +2079,70 @@ const capabilities = {
|
||||
"contributions": [],
|
||||
"gates": []
|
||||
},
|
||||
"pi": {
|
||||
"id": "pi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "pi",
|
||||
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "dot-home-nested",
|
||||
"name": "agent",
|
||||
"parent": ".pi",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": ".pi",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "pi",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "slash-programmatic",
|
||||
"dispatch": {
|
||||
"namedDispatch": false,
|
||||
"nested": false,
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
"stateIO": "session-log-append",
|
||||
"transport": "native-extension",
|
||||
"runtime": "bun"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"nativePlugin": {
|
||||
"dir": "extensions",
|
||||
"file": "gsd.cjs",
|
||||
"source": "pi/gsd.cjs"
|
||||
},
|
||||
"pluginOnlyInstall": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile-pipeline": {
|
||||
"id": "profile-pipeline",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -2156,7 +2219,7 @@ const capabilities = {
|
||||
"qwen": {
|
||||
"id": "qwen",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Qwen Code",
|
||||
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -2261,7 +2324,7 @@ const capabilities = {
|
||||
"research": {
|
||||
"id": "research",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Phase research",
|
||||
"description": "Optional phase research before planning; owns the phase researcher agent and workflow.research activation key.",
|
||||
"tier": "standard",
|
||||
@@ -2313,7 +2376,7 @@ const capabilities = {
|
||||
"schema-gate": {
|
||||
"id": "schema-gate",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -2359,7 +2422,7 @@ const capabilities = {
|
||||
"security": {
|
||||
"id": "security",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Security enforcement",
|
||||
"description": "Threat mitigation verification and ship-time security blocking for phases with security enforcement enabled.",
|
||||
"tier": "full",
|
||||
@@ -2458,7 +2521,7 @@ const capabilities = {
|
||||
"tdd": {
|
||||
"id": "tdd",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -2511,7 +2574,7 @@ const capabilities = {
|
||||
"trae": {
|
||||
"id": "trae",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Trae IDE",
|
||||
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -2601,7 +2664,7 @@ const capabilities = {
|
||||
"ui": {
|
||||
"id": "ui",
|
||||
"role": "feature",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "UI design contracts",
|
||||
"description": "UI-SPEC design contract + retrospective UI audit for frontend phases.",
|
||||
"tier": "full",
|
||||
@@ -2693,12 +2756,63 @@ const capabilities = {
|
||||
}
|
||||
]
|
||||
},
|
||||
"vscode": {
|
||||
"id": "vscode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "VS Code",
|
||||
"description": "VS Code — Marketplace/VSIX extension; no file-projected config directory; IDE-profile reference host (active vscode.lm model, engine-owned hook bus, sandboxed globalState/workspaceState stateIO).",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "none",
|
||||
"name": "vscode",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": null,
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "none",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 1,
|
||||
"installSurface": "none",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "palette",
|
||||
"dispatch": {
|
||||
"namedDispatch": true,
|
||||
"nested": true,
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
"stateIO": "sandboxed-storage",
|
||||
"transport": "mcp",
|
||||
"runtime": "sandboxed-web"
|
||||
}
|
||||
}
|
||||
},
|
||||
"windsurf": {
|
||||
"id": "windsurf",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Windsurf",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; Cascade native hooks.json blocking hook bus (pre_write_code, pre_run_command); tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
@@ -2746,7 +2860,7 @@ const capabilities = {
|
||||
]
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"hooksSurface": "windsurf-hooks-json",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
@@ -2769,13 +2883,19 @@ const capabilities = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true,
|
||||
"legacyDevinSkillsCleanup": true,
|
||||
"installsCommandBodiesForWorkflowDelegation": true,
|
||||
"verificationStyle": "windsurf-workflows"
|
||||
}
|
||||
}
|
||||
},
|
||||
"zcode": {
|
||||
"id": "zcode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "ZCode",
|
||||
"description": "ZCode (Z.ai) — desktop Agentic Development Environment for GLM-5.2; Claude-shaped nested skills at ~/.zcode/skills/<name>/SKILL.md, slash commands, named subagents, native MCP; declarative plugin surface; profile-marker install; tier-2 community support.",
|
||||
"tier": "core",
|
||||
@@ -2871,6 +2991,9 @@ const capabilities = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "electron"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3783,7 +3906,7 @@ const runtimes = {
|
||||
"antigravity": {
|
||||
"id": "antigravity",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Antigravity",
|
||||
"description": "Google Antigravity IDE — nested under ~/.gemini/antigravity; probed across 1.x and 2.x layouts; Gemini hook event dialect; flat skill layout; tier-1 support.",
|
||||
"tier": "core",
|
||||
@@ -3884,7 +4007,7 @@ const runtimes = {
|
||||
"augment": {
|
||||
"id": "augment",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -3991,7 +4114,7 @@ const runtimes = {
|
||||
"claude": {
|
||||
"id": "claude",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Claude Code",
|
||||
"description": "Anthropic Claude Code — primary development runtime; tier-1 support with full hook surface and skills-based global install.",
|
||||
"tier": "core",
|
||||
@@ -4096,7 +4219,7 @@ const runtimes = {
|
||||
"cline": {
|
||||
"id": "cline",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4165,7 +4288,7 @@ const runtimes = {
|
||||
"codebuddy": {
|
||||
"id": "codebuddy",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "CodeBuddy",
|
||||
"description": "CodeBuddy (Tencent) — converted commands + skills artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -4276,7 +4399,7 @@ const runtimes = {
|
||||
"codex": {
|
||||
"id": "codex",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4361,7 +4484,7 @@ const runtimes = {
|
||||
"copilot": {
|
||||
"id": "copilot",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4444,14 +4567,17 @@ const runtimes = {
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"reapplyCommand": "/gsd-update --reapply"
|
||||
"reapplyCommand": "/gsd-update --reapply",
|
||||
"agentFileExtension": ".agent.md",
|
||||
"skipSharedHooksInstall": true,
|
||||
"noPathRewrite": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cursor": {
|
||||
"id": "cursor",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4572,7 +4698,7 @@ const runtimes = {
|
||||
"hermes": {
|
||||
"id": "hermes",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4661,7 +4787,7 @@ const runtimes = {
|
||||
"kilo": {
|
||||
"id": "kilo",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4769,7 +4895,7 @@ const runtimes = {
|
||||
"kimi": {
|
||||
"id": "kimi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Kimi CLI",
|
||||
"description": "Kimi CLI (Moonshot AI) — generic agents root at ~/.config/agents; skills + kimi-agents artifact layout; native config.toml [[hooks]] bus at ~/.kimi/config.toml; background dispatch; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -4857,7 +4983,7 @@ const runtimes = {
|
||||
"opencode": {
|
||||
"id": "opencode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"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",
|
||||
@@ -4960,10 +5086,70 @@ const runtimes = {
|
||||
}
|
||||
}
|
||||
},
|
||||
"pi": {
|
||||
"id": "pi",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "pi",
|
||||
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "dot-home-nested",
|
||||
"name": "agent",
|
||||
"parent": ".pi",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": ".pi",
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "pi",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "slash-programmatic",
|
||||
"dispatch": {
|
||||
"namedDispatch": false,
|
||||
"nested": false,
|
||||
"maxDepth": 0,
|
||||
"background": false,
|
||||
"backgroundDispatch": false,
|
||||
"subagentToolkit": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "host",
|
||||
"stateIO": "session-log-append",
|
||||
"transport": "native-extension",
|
||||
"runtime": "bun"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"nativePlugin": {
|
||||
"dir": "extensions",
|
||||
"file": "gsd.cjs",
|
||||
"source": "pi/gsd.cjs"
|
||||
},
|
||||
"pluginOnlyInstall": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"qwen": {
|
||||
"id": "qwen",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Qwen Code",
|
||||
"description": "Qwen Code (Alibaba) — nested-skill artifact layout; settings-json hook surface; Claude hook event dialect; tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -5068,7 +5254,7 @@ const runtimes = {
|
||||
"trae": {
|
||||
"id": "trae",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Trae IDE",
|
||||
"description": "Trae IDE — nested-skill artifact layout; no hook surface (profile-marker-only config); tier-2 support.",
|
||||
"tier": "core",
|
||||
@@ -5155,12 +5341,63 @@ const runtimes = {
|
||||
}
|
||||
}
|
||||
},
|
||||
"vscode": {
|
||||
"id": "vscode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "VS Code",
|
||||
"description": "VS Code — Marketplace/VSIX extension; no file-projected config directory; IDE-profile reference host (active vscode.lm model, engine-owned hook bus, sandboxed globalState/workspaceState stateIO).",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
"gsd": ">=1.7.0"
|
||||
},
|
||||
"runtime": {
|
||||
"configHome": {
|
||||
"kind": "none",
|
||||
"name": "vscode",
|
||||
"env": []
|
||||
},
|
||||
"localConfigDir": null,
|
||||
"configFormat": "none",
|
||||
"artifactLayout": {
|
||||
"global": [],
|
||||
"local": []
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "none",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 1,
|
||||
"installSurface": "none",
|
||||
"writesSharedSettings": false,
|
||||
"permissionWriter": null,
|
||||
"extendedHookEvents": [],
|
||||
"hostIntegration": {
|
||||
"embeddingMode": "imperative",
|
||||
"commandSurface": "palette",
|
||||
"dispatch": {
|
||||
"namedDispatch": true,
|
||||
"nested": true,
|
||||
"maxDepth": 5,
|
||||
"background": true,
|
||||
"subagentToolkit": "undocumented",
|
||||
"backgroundDispatch": "undocumented"
|
||||
},
|
||||
"modelMode": "active",
|
||||
"hookBus": "engine",
|
||||
"stateIO": "sandboxed-storage",
|
||||
"transport": "mcp",
|
||||
"runtime": "sandboxed-web"
|
||||
}
|
||||
}
|
||||
},
|
||||
"windsurf": {
|
||||
"id": "windsurf",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "Windsurf",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; no hook surface; no hook events; tier-2 support.",
|
||||
"description": "Windsurf (Codeium) — workspace workflow artifact layout for slash commands; Cascade native hooks.json blocking hook bus (pre_write_code, pre_run_command); tier-2 support.",
|
||||
"tier": "core",
|
||||
"requires": [],
|
||||
"engines": {
|
||||
@@ -5208,7 +5445,7 @@ const runtimes = {
|
||||
]
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"hooksSurface": "windsurf-hooks-json",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "profile-marker-only",
|
||||
@@ -5231,13 +5468,19 @@ const runtimes = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "undocumented"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true,
|
||||
"legacyDevinSkillsCleanup": true,
|
||||
"installsCommandBodiesForWorkflowDelegation": true,
|
||||
"verificationStyle": "windsurf-workflows"
|
||||
}
|
||||
}
|
||||
},
|
||||
"zcode": {
|
||||
"id": "zcode",
|
||||
"role": "runtime",
|
||||
"version": "1.7.0-rc.5",
|
||||
"version": "1.7.0-rc.6",
|
||||
"title": "ZCode",
|
||||
"description": "ZCode (Z.ai) — desktop Agentic Development Environment for GLM-5.2; Claude-shaped nested skills at ~/.zcode/skills/<name>/SKILL.md, slash commands, named subagents, native MCP; declarative plugin surface; profile-marker install; tier-2 community support.",
|
||||
"tier": "core",
|
||||
@@ -5333,6 +5576,9 @@ const runtimes = {
|
||||
"stateIO": "filesystem",
|
||||
"transport": "mcp",
|
||||
"runtime": "electron"
|
||||
},
|
||||
"hostBehaviors": {
|
||||
"skipSharedHooksInstall": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5514,6 +5760,7 @@ const _requiresGraph = {
|
||||
"pattern-mapper": [
|
||||
"research"
|
||||
],
|
||||
"pi": [],
|
||||
"profile-pipeline": [],
|
||||
"qwen": [],
|
||||
"research": [],
|
||||
@@ -5522,6 +5769,7 @@ const _requiresGraph = {
|
||||
"tdd": [],
|
||||
"trae": [],
|
||||
"ui": [],
|
||||
"vscode": [],
|
||||
"windsurf": [],
|
||||
"zcode": []
|
||||
};
|
||||
|
||||
@@ -703,9 +703,11 @@ const VALID_CONVERTER_NAMES = new Set([
|
||||
|
||||
// C3: Validate role:runtime body
|
||||
const VALID_CONFIG_FORMATS = new Set(['settings-json', 'toml', 'markdown', 'markdown-dir', 'none']);
|
||||
const VALID_CONFIG_HOME_KINDS = new Set(['dot-home', 'dot-home-nested', 'xdg', 'generic-agents-root']);
|
||||
// 'none' added #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) with
|
||||
// no file-projected config directory at all.
|
||||
const VALID_CONFIG_HOME_KINDS = new Set(['dot-home', 'dot-home-nested', 'xdg', 'generic-agents-root', 'none']);
|
||||
const VALID_COMMAND_STYLES = new Set(['slash-hyphen', 'shell-var']);
|
||||
const VALID_HOOKS_SURFACES = new Set(['settings-json', 'codex-hooks-json', 'cursor-hooks-json', 'copilot-inline', 'cline-rules', 'kimi-hooks-toml', 'none']);
|
||||
const VALID_HOOKS_SURFACES = new Set(['settings-json', 'codex-hooks-json', 'cursor-hooks-json', 'copilot-inline', 'cline-rules', 'kimi-hooks-toml', 'windsurf-hooks-json', 'none']);
|
||||
const VALID_HOOK_EVENTS = new Set(['claude', 'gemini']);
|
||||
// extensionEvents — the plugin/extension-system event dialect (ADR-1239 amendment / #1943).
|
||||
// DISTINCT from hookEvents (managed-hook dialect): extensionEvents describes the
|
||||
@@ -716,7 +718,9 @@ const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
||||
const VALID_ARTIFACT_KIND_NAMES = new Set(['commands', 'agents', 'skills', 'kimi-agents']);
|
||||
const VALID_ARTIFACT_NESTINGS = new Set(['flat', 'nested']);
|
||||
const FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME = ['skills', 'agents', 'steps', 'contributions', 'gates', 'hooks', 'activationKey'];
|
||||
const VALID_INSTALL_SURFACES = new Set(['settings-json', 'codex-toml', 'copilot-instructions', 'cline-rules', 'cursor-hooks-json', 'profile-marker-only']);
|
||||
// 'none' added #2103 — Marketplace/VSIX-distributed hosts (e.g. VS Code) that
|
||||
// are never CLI-installed (no allRuntimes membership, no install flag).
|
||||
const VALID_INSTALL_SURFACES = new Set(['settings-json', 'codex-toml', 'copilot-instructions', 'cline-rules', 'cursor-hooks-json', 'profile-marker-only', 'none']);
|
||||
// 'antigravity' added #2096 Phase B Upgrade 1 — settings.json permissions.allow writer.
|
||||
const VALID_PERMISSION_WRITERS = new Set(['opencode', 'kilo', 'antigravity']);
|
||||
// SubagentStart added #2092 Phase B Upgrade 2 (qwen-only today — see
|
||||
@@ -741,7 +745,10 @@ const INSTALL_SURFACE_TO_ALLOWED_HOOKS_SURFACES = new Map([
|
||||
['copilot-instructions', new Set(['copilot-inline'])],
|
||||
['cline-rules', new Set(['cline-rules'])],
|
||||
['cursor-hooks-json', new Set(['cursor-hooks-json'])],
|
||||
['profile-marker-only', new Set(['none', 'kimi-hooks-toml'])],
|
||||
['profile-marker-only', new Set(['none', 'kimi-hooks-toml', 'windsurf-hooks-json'])],
|
||||
// 'none' added #2103 — VS Code has no CLI install surface at all; its only
|
||||
// valid hooksSurface pairing is the other 'none' (engine owns the hook bus).
|
||||
['none', new Set(['none'])],
|
||||
]);
|
||||
|
||||
// GATE B: extended hook event families → required hookEvents value
|
||||
@@ -778,9 +785,18 @@ function validateConfigHome(capId, ch) {
|
||||
);
|
||||
}
|
||||
|
||||
// name — required string
|
||||
if (typeof ch.name !== 'string' || ch.name.length === 0) {
|
||||
errors.push(ctx + '.name must be a non-empty string');
|
||||
// name — required string, except when kind === 'none': the runtime has no
|
||||
// file-projected config directory at all, so a descriptive name is
|
||||
// optional (a carve-out mirroring the dot-home-nested⇒parent conditional
|
||||
// below, not a new validation mechanism). If present it must still be a
|
||||
// non-empty string (e.g. vscode's configHome.name stays a descriptive
|
||||
// "vscode" string even though it is never used to build a path).
|
||||
if (ch.kind !== 'none') {
|
||||
if (typeof ch.name !== 'string' || ch.name.length === 0) {
|
||||
errors.push(ctx + '.name must be a non-empty string');
|
||||
}
|
||||
} else if (ch.name !== undefined && (typeof ch.name !== 'string' || ch.name.length === 0)) {
|
||||
errors.push(ctx + '.name must be a non-empty string if present when kind is "none"');
|
||||
}
|
||||
|
||||
// parent — required when kind == dot-home-nested
|
||||
@@ -1061,15 +1077,27 @@ function validateRuntimeBody(cap) {
|
||||
// localConfigDir — REQUIRED non-empty dot-dir string (ADR-1239 Phase B #1679)
|
||||
// Must start with '.' (e.g. ".claude", ".cursor"). Validated here so the registry
|
||||
// generator catches any descriptor missing the field before regenerating.
|
||||
if (typeof r.localConfigDir !== 'string' || r.localConfigDir.length === 0) {
|
||||
//
|
||||
// #2103: conditional on configHome.kind !== 'none' — a Marketplace/VSIX
|
||||
// host with no file-projected config directory (e.g. VS Code) has no
|
||||
// local dir to name; localConfigDir may be null/absent for such runtimes.
|
||||
const configHomeKind = (r.configHome && typeof r.configHome === 'object') ? r.configHome.kind : undefined;
|
||||
if (configHomeKind !== 'none') {
|
||||
if (typeof r.localConfigDir !== 'string' || r.localConfigDir.length === 0) {
|
||||
errors.push(
|
||||
'runtime.localConfigDir is required and must be a non-empty string (e.g. ".claude"); ' +
|
||||
'got: ' + JSON.stringify(r.localConfigDir),
|
||||
);
|
||||
} else if (!r.localConfigDir.startsWith('.')) {
|
||||
errors.push(
|
||||
'runtime.localConfigDir must start with "." (a dot-dir); got: ' + JSON.stringify(r.localConfigDir),
|
||||
);
|
||||
}
|
||||
} else if (r.localConfigDir !== null && r.localConfigDir !== undefined) {
|
||||
errors.push(
|
||||
'runtime.localConfigDir is required and must be a non-empty string (e.g. ".claude"); ' +
|
||||
'runtime.localConfigDir must be null or absent when configHome.kind is "none"; ' +
|
||||
'got: ' + JSON.stringify(r.localConfigDir),
|
||||
);
|
||||
} else if (!r.localConfigDir.startsWith('.')) {
|
||||
errors.push(
|
||||
'runtime.localConfigDir must start with "." (a dot-dir); got: ' + JSON.stringify(r.localConfigDir),
|
||||
);
|
||||
}
|
||||
|
||||
// extendedHookEvents — required array; every element must be in closed enum
|
||||
@@ -2175,6 +2203,9 @@ const INSTALL_SURFACE_TO_CONFIG_FORMAT = new Map([
|
||||
['cline-rules', 'markdown-dir'],
|
||||
['cursor-hooks-json', 'none'],
|
||||
['profile-marker-only', 'none'],
|
||||
// 'none' added #2103 — a runtime with NO CLI install surface at all (e.g.
|
||||
// VS Code) has no config-file format to write either.
|
||||
['none', 'none'],
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,7 +226,7 @@ function beginPhaseCore(content, intent, deps) {
|
||||
const reassemble = (b) => hasFrontmatter
|
||||
? `---\n${reconstructFrontmatter(existingFm)}\n---\n\n${b}`
|
||||
: b;
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
// Consult the field-classification table for the frontmatter keys this
|
||||
// transition touches (codex Phase 1 review: "table not consulted by
|
||||
// transitionCore"). The table tracks FRONTMATTER keys (lowercase: `status`,
|
||||
@@ -502,7 +502,7 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc
|
||||
* adapter to construct CLI output.
|
||||
*/
|
||||
function advancePlanCore(content, deps) {
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
// #1255: body-field replacements operate on body only (frontmatter stripped),
|
||||
// not on the full content. The YAML `status:` key matches `^Status:\s*`
|
||||
// before the body field if full content is passed (codex Phase 2 review:
|
||||
@@ -608,7 +608,7 @@ function advancePlanCore(content, deps) {
|
||||
*/
|
||||
function completePhaseCore(content, intent, deps) {
|
||||
const updated = [];
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
// Consult the field-classification table for the frontmatter keys this
|
||||
// transition touches (same guard beginPhaseCore applies). A missing row is a
|
||||
// substrate defect — fail loudly rather than silently re-encoding policy.
|
||||
@@ -762,7 +762,7 @@ function completePhaseCore(content, intent, deps) {
|
||||
*/
|
||||
function plannedPhaseCore(content, intent, deps) {
|
||||
const updated = [];
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
for (const fmKey of ['status', 'last_activity', 'last_activity_desc']) {
|
||||
const cls = getFieldClassification(fmKey);
|
||||
if (cls === null) {
|
||||
@@ -840,7 +840,7 @@ function plannedPhaseCore(content, intent, deps) {
|
||||
* directly and must not run the steady-state `syncStateFrontmatter` post-sync.
|
||||
*/
|
||||
function milestoneSwitchCore(content, intent, deps) {
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
const updated = [
|
||||
'milestone',
|
||||
'milestone_name',
|
||||
@@ -927,7 +927,7 @@ function milestoneSwitchCore(content, intent, deps) {
|
||||
*/
|
||||
function milestoneCompleteCore(content, intent, deps) {
|
||||
const updated = [];
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
const version = intent.version;
|
||||
for (const fmKey of ['status', 'last_activity', 'last_activity_desc']) {
|
||||
const cls = getFieldClassification(fmKey);
|
||||
@@ -1175,7 +1175,7 @@ function pruneCore(content, intent) {
|
||||
* (rather than silently writing fallback-derived wrong values).
|
||||
*/
|
||||
function syncCore(content, intent, deps) {
|
||||
const today = deps.clock.today();
|
||||
const today = deps.clock.localToday();
|
||||
const changes = [];
|
||||
let modified = content;
|
||||
const updated = [];
|
||||
|
||||
@@ -86,6 +86,11 @@
|
||||
"opus": null,
|
||||
"sonnet": null,
|
||||
"haiku": null
|
||||
},
|
||||
"pi": {
|
||||
"opus": { "model": "claude-opus-4-8" },
|
||||
"sonnet": { "model": "claude-sonnet-5" },
|
||||
"haiku": { "model": "claude-haiku-4-5" }
|
||||
}
|
||||
},
|
||||
"providerPresets": {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
phase: {N}
|
||||
slug: {phase-slug}
|
||||
# status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6)
|
||||
# audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117)
|
||||
status: draft
|
||||
nyquist_compliant: false
|
||||
wave_0_complete: false
|
||||
|
||||
@@ -153,17 +153,20 @@ Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` a
|
||||
|
||||
If no active validate-phase step hook exists: skip entirely.
|
||||
|
||||
For each phase directory, check `*-VALIDATION.md`. If exists, parse frontmatter (`nyquist_compliant`, `wave_0_complete`).
|
||||
For each phase directory, check `*-VALIDATION.md`. If exists, parse frontmatter (`status`, `nyquist_compliant`, `wave_0_complete`).
|
||||
|
||||
Classify per phase:
|
||||
|
||||
| Status | Condition |
|
||||
|--------|-----------|
|
||||
| COMPLIANT | `nyquist_compliant: true` and all tasks green |
|
||||
| PARTIAL | VALIDATION.md exists, `nyquist_compliant: false` or red/pending |
|
||||
| COMPLIANT | `status: validated` and `nyquist_compliant: true` and all tasks green |
|
||||
| PARTIAL | `status: validated` and (`nyquist_compliant: false` or red/pending) |
|
||||
| NOT-VALIDATED | `status: draft` (or absent) — validate-phase has not yet reconciled this file (#2117) |
|
||||
| MISSING | No VALIDATION.md |
|
||||
|
||||
Add to audit YAML: `nyquist: { compliant_phases, partial_phases, missing_phases, overall }`
|
||||
> **NOT-VALIDATED vs PARTIAL (#2117):** A phase reads `status: draft` when it was seeded by plan-phase but never reconciled by validate-phase, OR when its `VALIDATION.md` predates the `status` field (files written before #2117 stay `draft` whether or not validation ran). In both cases `nyquist_compliant` is not authoritative, so this is a coverage TODO ("run validate-phase") — not a compliance failure. Re-running validate-phase promotes the file to `status: validated` and yields the real COMPLIANT/PARTIAL verdict. Only `status: validated` + `nyquist_compliant: false` is a genuine PARTIAL.
|
||||
|
||||
Add to audit YAML: `nyquist: { compliant_phases, partial_phases, not_validated_phases, missing_phases, overall }`
|
||||
|
||||
Discovery only — never auto-calls `/gsd:validate-phase`.
|
||||
|
||||
|
||||
@@ -72,22 +72,30 @@ malformed row.
|
||||
# Detect whether STATE.md has a Quick Tasks Completed table
|
||||
if grep -q "Quick Tasks Completed" .planning/STATE.md 2>/dev/null; then
|
||||
# Read the table header line to determine the column schema.
|
||||
# quick.md Step 7 creates a 5-column table:
|
||||
# | # | Description | Date | Commit | Directory |
|
||||
# Count pipe characters in the header to determine column count.
|
||||
# quick.md Step 7b writes two shapes:
|
||||
# 5-column (non-validate): | # | Description | Date | Commit | Directory |
|
||||
# 6-column (validate): | # | Description | Date | Commit | Status | Directory |
|
||||
HEADER_LINE=$(grep -A2 "Quick Tasks Completed" .planning/STATE.md 2>/dev/null | grep "^|" | head -1)
|
||||
# Count columns: number of | separators minus 1 gives column count
|
||||
COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-1}')
|
||||
# Count REAL columns: a markdown header has a leading and a trailing pipe, so
|
||||
# awk's NF counts (real columns + 2). NF-2 yields the real column count.
|
||||
# (NF-1 was the off-by-one root cause of #2133: it returned the pipe count,
|
||||
# making the `-eq 5` test unsatisfiable for the very header quick.md writes.)
|
||||
COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-2}')
|
||||
|
||||
# Next row number + latest commit hash are schema-independent.
|
||||
NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1")
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—")
|
||||
|
||||
# Select the appended row's template by the detected column count so its cell
|
||||
# count always matches the header (prevents the malformed-row symptom of #27).
|
||||
if [ "$COL_COUNT" -eq 5 ] && echo "$HEADER_LINE" | grep -qi "Description" && echo "$HEADER_LINE" | grep -qi "Commit" && echo "$HEADER_LINE" | grep -qi "Directory"; then
|
||||
# 5-column schema from quick.md Step 7: | # | Description | Date | Commit | Directory |
|
||||
# Determine the next row number by counting existing data rows (non-separator, non-header).
|
||||
NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1")
|
||||
# Get the latest commit hash (short)
|
||||
COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—")
|
||||
# 5-column schema from quick.md Step 7b (non-validate).
|
||||
echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — |" >> .planning/STATE.md
|
||||
elif [ "$COL_COUNT" -eq 6 ] && echo "$HEADER_LINE" | grep -qi "Status" && echo "$HEADER_LINE" | grep -qi "Directory"; then
|
||||
# 6-column schema from quick.md Step 7b (validate, with Status).
|
||||
echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — | — |" >> .planning/STATE.md
|
||||
else
|
||||
# Unrecognized table schema — skip to avoid appending a malformed row.
|
||||
# Unrecognized table schema — skip to avoid appending a malformed row (#27).
|
||||
echo "⚠ fast.md log_to_state: Quick Tasks Completed table has unrecognized schema (${COL_COUNT} columns); skipping STATE.md update."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -255,6 +255,8 @@ shell-interpolated). Exact invocation in `gsd-core/references/reviewer-instances
|
||||
|
||||
For each selected CLI, invoke in sequence (not parallel — avoid rate limits):
|
||||
|
||||
**Timeout guidance (#2194):** prompt-fed source-grounded reviews are slow — measured ~570s for Codex at `xhigh` effort and ~525s for headless Claude on a large plan set. Each of the Gemini / Claude / Codex blocks below MUST be invoked with a high Bash `timeout:` — at least `900000` (15 min), and `1200000` (20 min) for Codex `xhigh` or headless Claude — so a lane is not killed mid-review. On Claude Code, raise the host cap via `BASH_MAX_TIMEOUT_MS` if a review can exceed it. A silent empty output after a long run is a **timeout kill, not a crash** — the Codex `0xc0000142` misdiagnosis persisted because the empty-output branches below cannot distinguish the two; treat an empty result on a slow lane as a dropped lane and re-run with more time rather than diagnosing a CLI/sandbox failure. A cross-AI review that silently drops a lane is blind in one eye.
|
||||
|
||||
**Gemini:**
|
||||
```bash
|
||||
if [ -n "$GEMINI_MODEL" ] && [ "$GEMINI_MODEL" != "null" ]; then
|
||||
|
||||
@@ -361,6 +361,7 @@ Built-in tier defaults by runtime:
|
||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||
| `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
||||
| Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
||||
|
||||
Display a table to the user showing the effective configuration:
|
||||
|
||||
@@ -394,9 +394,15 @@ gsd_run query state.update "Last Activity" "$(date +%Y-%m-%d)"
|
||||
gsd_run query state.update "Status" "Phase ${PHASE_NUMBER} shipped — PR #${PR_NUMBER}"
|
||||
```
|
||||
|
||||
If `commit_docs` is true:
|
||||
If `commit_docs` is true, commit the ship-note AND push it onto the PR branch so
|
||||
it reaches the default branch when the PR merges. Without this push the ship-note
|
||||
commit stays local-only and is silently discarded when the branch is deleted on
|
||||
merge (#2138). The `[ci skip]` trailer suppresses the redundant pipeline the push
|
||||
would otherwise trigger (GitHub honors `[ci skip]` / `[skip ci]`):
|
||||
|
||||
```bash
|
||||
gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER}" --files .planning/STATE.md
|
||||
gsd_run query commit "docs(${padded_phase}): ship phase ${PHASE_NUMBER} — PR #${PR_NUMBER} [ci skip]" --files .planning/STATE.md
|
||||
git push origin ${CURRENT_BRANCH} 2>&1 || echo "⚠ track_shipping: ship-note push failed — it is local-only; rerun: git push origin ${CURRENT_BRANCH}"
|
||||
```
|
||||
</step>
|
||||
|
||||
|
||||
@@ -122,11 +122,11 @@ Handle return:
|
||||
|
||||
**State B (create):**
|
||||
1. Read template from `~/.claude/gsd-core/templates/VALIDATION.md`
|
||||
2. Fill: frontmatter, Test Infrastructure, Per-Task Map, Manual-Only, Sign-Off
|
||||
2. Fill: frontmatter (**set `status: validated`**), Test Infrastructure, Per-Task Map, Manual-Only, Sign-Off
|
||||
3. Write to `${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md`
|
||||
|
||||
**State A (update):**
|
||||
1. Update Per-Task Map statuses, add escalated to Manual-Only, update frontmatter
|
||||
1. Update Per-Task Map statuses, add escalated to Manual-Only, update frontmatter (**set `status: validated`**)
|
||||
2. Append audit trail:
|
||||
|
||||
```markdown
|
||||
|
||||
275
hooks/gsd-windsurf-pre-command.js
Normal file
275
hooks/gsd-windsurf-pre-command.js
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env node
|
||||
// gsd-hook-version: {{GSD_VERSION}}
|
||||
// gsd-windsurf-pre-command.js — Windsurf/Cascade pre_run_command hook (ADR-1239 / #2100)
|
||||
//
|
||||
// Cascade (Windsurf's agent) invokes this script before each shell-command
|
||||
// tool call executes, via the workspace/global hooks.json hook bus.
|
||||
//
|
||||
// Input schema (Cascade pre_run_command envelope, JSON on stdin):
|
||||
// { agent_action_name: 'pre_run_command', trajectory_id, execution_id,
|
||||
// timestamp, model_name,
|
||||
// tool_info: { command_line } }
|
||||
//
|
||||
// Decision protocol — DISTINCT from Cursor's stdout-JSON form:
|
||||
// - exit 0 -> allow the command to run (no stdout contract)
|
||||
// - exit 2 -> BLOCK the command; the printed stderr text is the reason
|
||||
// shown to the agent/user
|
||||
//
|
||||
// Behaviour: blocks a small, CONSERVATIVE, well-scoped, BEST-EFFORT deny-list
|
||||
// of obviously destructive commands. This is intentionally not exhaustive —
|
||||
// a broad deny-list would false-positive on legitimate agent/tooling work,
|
||||
// and Cascade honors exit 2 unconditionally, so a false positive blocks the
|
||||
// user's real work. When in doubt, this script allows:
|
||||
// - a fork-bomb pattern
|
||||
// - `rm -rf` (or equivalent combined/long flags), including through common
|
||||
// prefixed forms (`sudo rm -rf /`, `/bin/rm -rf /`, `env FOO=1 rm -rf /`),
|
||||
// targeting the filesystem root, the user's home directory, or a Windows
|
||||
// drive root/profile root
|
||||
// - `git push` with a force flag (`-f`/`--force`/`--force-with-lease`) or a
|
||||
// `+`-prefixed refspec, explicitly targeting a protected branch
|
||||
// (main / master / next) as the push destination — not merely mentioning
|
||||
// that name elsewhere in a longer branch name or a trailing comment
|
||||
// Everything else — including force-pushes to feature branches and `rm -rf`
|
||||
// against ordinary project subdirectories — is intentionally left alone.
|
||||
// Fails OPEN on any error, timeout, or unrecognized shape — a hook bug must
|
||||
// never wedge Cascade.
|
||||
//
|
||||
// Classification is TOKENIZE-based (split into shell segments, then
|
||||
// whitespace-split tokens), not a single mega-regex over the raw string —
|
||||
// this keeps every check linear in input length. `command_line` longer than
|
||||
// MAX_COMMAND_LENGTH is allowed outright before any pattern matching runs:
|
||||
// no realistic destructive command is anywhere near that long, so the cap
|
||||
// both fails open on pathological input and bounds the worst-case cost of
|
||||
// every classifier below (defense-in-depth against regex-based DoS).
|
||||
//
|
||||
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
||||
// https://docs.devin.ai/desktop/cascade/hooks
|
||||
|
||||
'use strict';
|
||||
|
||||
// No realistic destructive command comes anywhere close to this length.
|
||||
const MAX_COMMAND_LENGTH = 4096;
|
||||
|
||||
// Classic bash fork bomb: `:(){ :|:& };:`
|
||||
const FORK_BOMB_RE = /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/;
|
||||
|
||||
// Command-prefix wrappers to look through when locating the "real" command at
|
||||
// the head of a segment: `sudo rm -rf /`, `/bin/rm -rf /` (basename strip),
|
||||
// `env FOO=1 rm -rf /` (env's leading VAR=val args are skipped too).
|
||||
const CMD_PREFIXES = new Set(['sudo', 'env', 'command', 'nice', 'nohup', 'time', 'doas']);
|
||||
|
||||
// Bare filesystem-root-class tokens for `rm`'s target. Ordinary paths like
|
||||
// `/tmp/foo` or `/home/user/project` never match this set.
|
||||
const ROOT_SENTINELS = new Set(['/', '/*', '~', '~/', '$HOME', '${HOME}']);
|
||||
|
||||
const PROTECTED_BRANCHES = new Set(['main', 'master', 'next']);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tokenizing helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Split a command line into shell segments on `;`, `&&`, `||`, `|`, newline —
|
||||
// each segment is classified independently.
|
||||
function splitSegments(cmd) {
|
||||
return cmd.split(/\|\||&&|[;\n|]/);
|
||||
}
|
||||
|
||||
// A `#` starts a bash comment when it's the first character of a "word"
|
||||
// (preceded by whitespace, or at the very start of the segment). Strip it
|
||||
// before classifying, so a comment mentioning a protected branch name never
|
||||
// counts as a real command argument.
|
||||
function stripBashComment(segment) {
|
||||
const m = segment.match(/(^|\s)#/);
|
||||
if (!m) return segment;
|
||||
const idx = m.index + m[1].length;
|
||||
return segment.slice(0, idx).replace(/\s+$/, '');
|
||||
}
|
||||
|
||||
function tokenize(segment) {
|
||||
return segment.split(/\s+/).filter(Boolean);
|
||||
}
|
||||
|
||||
// Strip any directory path from a token: `/bin/rm` -> `rm`.
|
||||
function basename(tok) {
|
||||
const parts = tok.split(/[\\/]/);
|
||||
return parts[parts.length - 1] || tok;
|
||||
}
|
||||
|
||||
// Find the index of the "real" command token in a token list, skipping past
|
||||
// known command-prefix wrappers (and, for `env`, its leading VAR=val args).
|
||||
function indexOfCommandAfterPrefixes(tokens) {
|
||||
let i = 0;
|
||||
while (i < tokens.length) {
|
||||
const base = basename(tokens[i]).toLowerCase();
|
||||
if (!CMD_PREFIXES.has(base)) return i;
|
||||
const wasEnv = base === 'env';
|
||||
i++;
|
||||
if (wasEnv) {
|
||||
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) i++;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
// True if `tokens` contains a flag matching either the exact long form, or a
|
||||
// combined/short `-xyz` cluster containing `shortChar` (e.g. `-rf`, `-fr`,
|
||||
// `-r`). A single `[a-zA-Z]+` quantifier with no nested ambiguity — linear,
|
||||
// no catastrophic backtracking regardless of token length.
|
||||
function hasFlag(tokens, shortChar, longFlag) {
|
||||
return tokens.some((t) => {
|
||||
if (t === longFlag) return true;
|
||||
if (t.length > 1 && t[0] === '-' && t[1] !== '-' && /^[a-zA-Z]+$/.test(t.slice(1))) {
|
||||
return t.slice(1).toLowerCase().includes(shortChar);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function isRootSentinel(tok) {
|
||||
if (ROOT_SENTINELS.has(tok)) return true;
|
||||
// Bare Windows drive root: `C:\` or `C:/`.
|
||||
if (/^[A-Za-z]:[\\/]$/.test(tok)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Classifiers (each operates on one already comment-stripped segment)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// `rm` (any flag order/spelling, optionally through `sudo`/`env FOO=1`/an
|
||||
// absolute path/etc.) with BOTH a recursive flag and a force flag, targeting
|
||||
// a bare filesystem-root-class token.
|
||||
function isDestructiveRmRf(segment) {
|
||||
const tokens = tokenize(segment);
|
||||
const cmdIdx = indexOfCommandAfterPrefixes(tokens);
|
||||
if (cmdIdx >= tokens.length) return null;
|
||||
if (basename(tokens[cmdIdx]) !== 'rm') return null;
|
||||
const args = tokens.slice(cmdIdx + 1);
|
||||
const hasRecursive = hasFlag(args, 'r', '--recursive');
|
||||
const hasForce = hasFlag(args, 'f', '--force');
|
||||
if (!hasRecursive || !hasForce) return null;
|
||||
const rootTok = args.find(isRootSentinel);
|
||||
if (rootTok) return `rm -rf targeting the filesystem root or home directory ('${rootTok}')`;
|
||||
return null;
|
||||
}
|
||||
|
||||
function isWindowsRootSentinel(tok) {
|
||||
if (/^[A-Za-z]:\\?$/.test(tok)) return true;
|
||||
if (/^\$env:userprofile\\?$/i.test(tok)) return true;
|
||||
if (/^~\\?$/.test(tok)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function isWindowsDriveRoot(tok) {
|
||||
return /^[A-Za-z]:\\?$/.test(tok);
|
||||
}
|
||||
|
||||
// Windows equivalents: `Remove-Item -Recurse -Force <drive-root|profile-root>`
|
||||
// and `rd /s /q <drive-root>` / `rmdir /s /q <drive-root>`.
|
||||
function isDestructiveWindowsRmRf(segment) {
|
||||
const tokens = tokenize(segment);
|
||||
if (tokens.length === 0) return null;
|
||||
const first = basename(tokens[0]).toLowerCase();
|
||||
const rest = tokens.slice(1);
|
||||
if (first === 'remove-item') {
|
||||
const hasRecurse = rest.some((t) => t.toLowerCase() === '-recurse');
|
||||
const hasForce = rest.some((t) => t.toLowerCase() === '-force');
|
||||
if (hasRecurse && hasForce && rest.some(isWindowsRootSentinel)) {
|
||||
return 'Remove-Item -Recurse -Force targeting a drive root or user-profile root';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (first === 'rd' || first === 'rmdir') {
|
||||
const hasS = rest.some((t) => t.toLowerCase() === '/s');
|
||||
const hasQ = rest.some((t) => t.toLowerCase() === '/q');
|
||||
if (hasS && hasQ) {
|
||||
const rootTok = rest.find(isWindowsDriveRoot);
|
||||
if (rootTok) return `rd /s /q targeting drive root '${rootTok}'`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isForceToken(tok) {
|
||||
if (tok === '--force' || tok === '-f') return true;
|
||||
if (/^--force-with-lease(=.*)?$/i.test(tok)) return true;
|
||||
if (tok.startsWith('+')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve the branch a push-argument token targets, honoring `+<dst>` and
|
||||
// `<src>:<dst>` refspec forms and an optional `refs/heads/` prefix. Returns
|
||||
// the lower-cased protected branch name, or null. Whole-token comparison
|
||||
// only — `feature/main-fix` never matches `main`.
|
||||
function protectedTargetFromToken(tok) {
|
||||
let t = tok;
|
||||
if (t.startsWith('+')) t = t.slice(1);
|
||||
const colonIdx = t.lastIndexOf(':');
|
||||
const candidate = colonIdx !== -1 ? t.slice(colonIdx + 1) : t;
|
||||
const stripped = candidate.replace(/^refs\/heads\//i, '');
|
||||
const lower = stripped.toLowerCase();
|
||||
return PROTECTED_BRANCHES.has(lower) ? lower : null;
|
||||
}
|
||||
|
||||
// `git push` with a force flag/refspec AND an explicit protected-branch push
|
||||
// target (main / master / next — see scripts/setup-branch-protection.sh).
|
||||
function isProtectedBranchForcePush(segment) {
|
||||
const tokens = tokenize(segment);
|
||||
for (let i = 0; i < tokens.length - 1; i++) {
|
||||
if (tokens[i].toLowerCase() === 'git' && tokens[i + 1].toLowerCase() === 'push') {
|
||||
const rest = tokens.slice(i + 2);
|
||||
if (!rest.some(isForceToken)) return null;
|
||||
for (const tok of rest) {
|
||||
const target = protectedTargetFromToken(tok);
|
||||
if (target) return `git push --force targeting protected branch '${target}'`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function destructiveReason(cmd) {
|
||||
if (FORK_BOMB_RE.test(cmd)) return 'fork-bomb pattern';
|
||||
for (const rawSegment of splitSegments(cmd)) {
|
||||
const segment = stripBashComment(rawSegment).trim();
|
||||
if (!segment) continue;
|
||||
const reason = isDestructiveRmRf(segment)
|
||||
|| isDestructiveWindowsRmRf(segment)
|
||||
|| isProtectedBranchForcePush(segment);
|
||||
if (reason) return reason;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function block(reason) {
|
||||
process.stderr.write(`GSD windsurf pre_run_command guard: ${reason}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function allow() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let input = '';
|
||||
const stdinTimeout = setTimeout(() => process.exit(0), 10000);
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', (chunk) => { input += chunk; });
|
||||
process.stdin.on('end', () => {
|
||||
clearTimeout(stdinTimeout);
|
||||
try {
|
||||
const data = JSON.parse(input || '{}');
|
||||
const toolInfo = (data && typeof data.tool_info === 'object' && data.tool_info) || {};
|
||||
const commandLine = typeof toolInfo.command_line === 'string' ? toolInfo.command_line : '';
|
||||
if (!commandLine) { allow(); return; }
|
||||
if (commandLine.length > MAX_COMMAND_LENGTH) { allow(); return; }
|
||||
|
||||
const reason = destructiveReason(commandLine);
|
||||
if (reason) { block(reason); return; }
|
||||
allow();
|
||||
} catch {
|
||||
// Silent fail-open — never block a valid tool call due to a hook bug.
|
||||
allow();
|
||||
}
|
||||
});
|
||||
132
hooks/gsd-windsurf-pre-write.js
Normal file
132
hooks/gsd-windsurf-pre-write.js
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env node
|
||||
// gsd-hook-version: {{GSD_VERSION}}
|
||||
// gsd-windsurf-pre-write.js — Windsurf/Cascade pre_write_code hook (ADR-1239 / #2100)
|
||||
//
|
||||
// Cascade (Windsurf's agent) invokes this script before each file-write tool
|
||||
// call executes, via the workspace/global hooks.json hook bus.
|
||||
//
|
||||
// Input schema (Cascade pre_write_code envelope, JSON on stdin):
|
||||
// { agent_action_name: 'pre_write_code', trajectory_id, execution_id,
|
||||
// timestamp, model_name,
|
||||
// tool_info: { file_path, edits: [{ old_string, new_string }] } }
|
||||
//
|
||||
// Decision protocol — DISTINCT from Cursor's stdout-JSON form:
|
||||
// - exit 0 -> allow the write to proceed (no stdout contract)
|
||||
// - exit 2 -> BLOCK the write; the printed stderr text is the reason shown
|
||||
// to the agent/user
|
||||
//
|
||||
// Behaviour: reimplements the core containment check from
|
||||
// hooks/gsd-worktree-path-guard.js — block a write whose file_path resolves
|
||||
// (via `git rev-parse --show-toplevel`) to a DIFFERENT git root than the
|
||||
// current working directory, or lands inside a `.git/` internals directory.
|
||||
// Fails OPEN on any error, timeout, non-git cwd, or missing git binary — a
|
||||
// hook bug must never wedge Cascade.
|
||||
//
|
||||
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
||||
// https://docs.devin.ai/desktop/cascade/hooks
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const SPAWNOPT = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000, windowsHide: true };
|
||||
|
||||
function git(args, cwd) {
|
||||
return spawnSync('git', args, { ...SPAWNOPT, cwd });
|
||||
}
|
||||
|
||||
// Walk up from `start` to find the nearest existing DIRECTORY (not merely an
|
||||
// existing filesystem entry) — a linked git worktree's `.git` is a plain FILE
|
||||
// (a `gitdir:` pointer), not a directory, so a plain existence check would
|
||||
// hand spawnSync an invalid `cwd` and silently fail the git calls below.
|
||||
// Returns null if we reach the filesystem root without finding one.
|
||||
function nearestExistingDir(start) {
|
||||
let dir = start;
|
||||
let prev;
|
||||
do {
|
||||
prev = dir;
|
||||
try { if (fs.statSync(dir).isDirectory()) return dir; } catch { /* keep walking */ }
|
||||
dir = path.dirname(dir);
|
||||
} while (dir !== prev);
|
||||
return null;
|
||||
}
|
||||
|
||||
function block(reason) {
|
||||
process.stderr.write(`GSD windsurf pre_write_code guard: ${reason}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function allow() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let input = '';
|
||||
const stdinTimeout = setTimeout(() => process.exit(0), 10000);
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', (chunk) => { input += chunk; });
|
||||
process.stdin.on('end', () => {
|
||||
clearTimeout(stdinTimeout);
|
||||
try {
|
||||
const data = JSON.parse(input || '{}');
|
||||
const toolInfo = (data && typeof data.tool_info === 'object' && data.tool_info) || {};
|
||||
const rawFilePath = typeof toolInfo.file_path === 'string' ? toolInfo.file_path : '';
|
||||
if (!rawFilePath) { allow(); return; }
|
||||
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Determine the active project's git root. No git root at all -> nothing
|
||||
// to enforce a boundary against -> fail open.
|
||||
const cwdTopResult = git(['rev-parse', '--show-toplevel'], cwd);
|
||||
if (cwdTopResult.status !== 0 || !cwdTopResult.stdout) { allow(); return; }
|
||||
const cwdTopRaw = cwdTopResult.stdout.trim();
|
||||
|
||||
const filePath = path.isAbsolute(rawFilePath) ? path.resolve(rawFilePath) : path.resolve(cwd, rawFilePath);
|
||||
|
||||
// Find the nearest existing ancestor of filePath so we can ask git for its
|
||||
// toplevel. The file itself may not exist yet (a write can create it).
|
||||
const checkDir = nearestExistingDir(
|
||||
(() => {
|
||||
try {
|
||||
return fs.statSync(filePath).isDirectory() ? filePath : path.dirname(filePath);
|
||||
} catch {
|
||||
return path.dirname(filePath);
|
||||
}
|
||||
})(),
|
||||
);
|
||||
if (!checkDir) { allow(); return; } // synthetic path with no existing ancestor — fail open
|
||||
|
||||
const fileTopResult = git(['rev-parse', '--show-toplevel'], checkDir);
|
||||
if (fileTopResult.status !== 0 || !fileTopResult.stdout) {
|
||||
// Not inside any git worktree. Distinguish "inside a .git/ internals
|
||||
// directory" (dangerous — BLOCK) from "outside all git repos entirely"
|
||||
// (not the escape vector this guard targets — fail open).
|
||||
const insideGitDir = git(['rev-parse', '--is-inside-git-dir'], checkDir);
|
||||
if (insideGitDir.status === 0 && insideGitDir.stdout && insideGitDir.stdout.trim() === 'true') {
|
||||
block(
|
||||
`'${filePath}' is inside a git internal (.git) directory, not the active project at ` +
|
||||
`'${cwdTopRaw}'. Writing to repository internals via an absolute path is not permitted. ` +
|
||||
`Use a relative path. (cwd: '${cwd}')`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
allow();
|
||||
return;
|
||||
}
|
||||
|
||||
const fileTopRaw = fileTopResult.stdout.trim();
|
||||
if (fileTopRaw === cwdTopRaw) { allow(); return; }
|
||||
|
||||
// BLOCK: file resolves to a different git root than the active project.
|
||||
block(
|
||||
`'${filePath}' resolves to git root '${fileTopRaw}' which differs from the active project root ` +
|
||||
`'${cwdTopRaw}'. This likely means an absolute path was derived from a different repository. ` +
|
||||
`Use a relative path within the active project, or re-derive the base directory with ` +
|
||||
`\`git rev-parse --show-toplevel\` from the active project. (cwd: '${cwd}')`,
|
||||
);
|
||||
} catch {
|
||||
// Silent fail-open — never block a valid tool call due to a hook bug.
|
||||
allow();
|
||||
}
|
||||
});
|
||||
@@ -36,6 +36,8 @@ const MANAGED_HOOKS = [
|
||||
'gsd-statusline.js',
|
||||
'gsd-update-banner.js',
|
||||
'gsd-validate-commit.sh',
|
||||
'gsd-windsurf-pre-command.js',
|
||||
'gsd-windsurf-pre-write.js',
|
||||
'gsd-workflow-guard.js',
|
||||
'gsd-worktree-path-guard.js',
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user