Commit Graph

74 Commits

Author SHA1 Message Date
Tom Boucher
fb2d122d7f feat(#3841): assert gsd-tools identity on every state-mutating verb (#3848)
* feat(#3841): assert gsd-tools identity before any state-mutating verb

only this package publishes. The path-based branches — a project-local install,
a runtime config directory — had no such guarantee; they trusted their
configured location. This closes them.

Mechanism: once resolution finishes, and before any verb runs, the preamble
probes the tool it picked with `runtime-identity --raw` and matches the answer
with a shell `case` pattern ANCHORED to the start of the compact payload
(`{"packageName":"@opengsd/gsd-core"`). An unanchored substring match accepts
the decoy `{"packageName":"get-shit-done-cc","note":"@opengsd/gsd-core"}`, which
any colliding package could publish. The outcome is exported as the two-valued
`GSD_IDENTITY_STATUS` (`ok`/`unverified`), so the gate is asserted on a VALUE
rather than on warning prose. Rollout is warn-then-fail per the #3146 ruling:
`unverified` prints one line naming BOTH causes and continues, because
`no_identity_verb` cannot tell a foreign package from an `@opengsd/gsd-core`
older than the verb, and at rollout the old-version case is the common one.

The blocker was byte budget, not design. The preamble is inlined into 112
shipped files and several sat within single-digit bytes of frozen ceilings
(`gsd-verifier.md` 16 bytes, `gsd-executor.md` 33, `execute-phase.md` 234); a
first attempt broke five of them. What made room was collapsing the resolver's
twenty near-identical `elif [ -f … ]` arms into one candidate-list helper
(`_gsd_at`), which buys far more than the assertion costs. The preamble is now
2,624 bytes against 4,500 — a net 1,876 bytes SMALLER per inlined file, so every
capped file moved away from its ceiling rather than toward it. No cap raised, no
size-budget exception added, no override token emitted.

Resolution order, every runtime-home probe, the `unset -f gsd_run` re-source
fix, the fail-closed `exit 1`, and the `CLAUDE_ENV_FILE` persistence are all
preserved byte-for-byte in substring terms; the snippet still begins with
`_GSD_SHIM_NAME=` and still ends with `fi`, which the parity extractors anchor
on. `gsd-core/references/gsd-run-resolver.md` is re-synced byte-equal.

Also fixes two stale claims found in passing: CONTEXT.md and FEATURES.md both
described an `[ -x ]` guard as the load-bearing re-source defense. That guard
was tried and REMOVED in #3831 — it rejected the bare function name, fell
through every branch, and hit `exit 1`, which kills a sourced caller's shell.
`unset -f gsd_run` is the actual mechanism.

Refs #3841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#3841): pair the anchor's brace by requiring a closed identity payload

The matrix went red on `tests/new-project-mvp-prompt.test.cjs` — "new-project.md
has unbalanced braces: net depth 2" — plus a knock-on report from its parent
`bug #1516` describe, which is the same failure counted once at the child and
once at the block.

Root cause: that guard (:182-189, mirroring #3784 bd53925f) walks characters and
increments on `{`, decrements on `}`, with no awareness of shell quoting. It
scans `new-project.md` PLUS every `new-project/steps/*.md`, and both
`new-project.md` and `steps/auto-mode-config.md` carry one inlined preamble copy
— hence net 2 from a snippet that was off by exactly one. The unpaired brace was
the `{` inside the single-quoted `case` pattern of the identity anchor, which is
correct shell and invisible to a text scanner.

Fix in the snippet, not the guard. The pattern now anchors at BOTH ends:
`'{"packageName":"@opengsd/gsd-core"'*'}'`. That balances 51/51 with a brace that
does real work rather than a cosmetic pair — a truncated payload whose prefix
matches now fails too, where before it verified. Safe for any future additive
field: a JSON object's own closing brace is always the last character, whatever
type the last value has, which is pinned by two negative-space tests (a nested
object and an array-valued last key must both still verify). Cost: +3 bytes,
against the 1,873 the resolver fold already gave back.

The alternative considered and rejected was dropping the literal `{` for a `?`
glob. It balances too, but weakens the anchor from "must be an opening brace" to
"must be any one character", and the anchor is the entire point.

Two guards added so this cannot recur silently:
- runtime-launcher-parity (F0) pins brace balance at the SNIPPET, so the next
  edit to that pattern fails on the file it broke instead of surfacing three
  files downstream in a test whose name mentions neither the launcher nor this
  issue. It also asserts depth never goes negative, since a `}` preceding its
  `{` nets to zero while being unbalanced at every prefix.
- runtime-identity gains behavioral truncated-payload and trailing-garbage
  fixtures, so the added `}` is proven load-bearing rather than merely present.

Verified: snippet 51/51 braces; new-project combined net depth 0; the seven
other preamble-bearing files with nonzero depth are unchanged from merged next
(their own prose, not the preamble, and not in any guard's scan set); all 112
inlined copies and the resolver reference re-synced byte-equal; sync:launcher
idempotent on the second run.

Refs #3841

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#3841): backfill changeset PR number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 01:05:53 -04:00
Tom Boucher
63abcface9 feat(#3146): resolve gsd_run so workflows cannot reach a foreign gsd-tools (#3831)
* feat(#3146): resolve gsd_run so workflows cannot reach a foreign gsd-tools

The predecessor package get-shit-done-cc publishes a colliding gsd-tools bin whose phases.clear DELETES where this package's ARCHIVES, and both print success-shaped output against a gitignored .planning/ -- which is how #3129 cost a user 43 phase directories with no error and nothing recoverable from git.

The launcher's PATH branch now resolves gsd_run, published only by this package and self-locating via its own symlink chain to the sibling shim, instead of the colliding gsd-tools. A foreign handler becomes unreachable from PATH, and when no gsd_run is reachable the resolver fails closed rather than falling back -- that fallback was the vulnerability. This is smaller than the branch it replaces, which matters: the preamble is inlined into 113 shipped files and agents/gsd-verifier.md sits 2 bytes under a red-line size cap.

unset -f gsd_run leads the preamble so a re-source is idempotent. Without it, command -v finds the shell function, returns a bare name, and the resolver falls through to an exit 1 that kills a sourced caller's shell.

Adds gsd-tools runtime-identity, a manual diagnostic reporting this runtime's package coordinates over the baked package-identity (#498) and readHostVersion, with a strict total classifier: only a JSON object with an exact packageName verifies, since JSON.parse admits 0/"str"/[]/null/true.

An inlined identity assertion was built and reviewed first, then withdrawn -- it breaks five frozen size ceilings and no assertion fits in 2 bytes.

Closes #3146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#3146): stop sync:launcher relocating a deliberate preamble placement

Pre-existing defect, surfaced by this PR because sync is a no-op unless the snippet content actually changes. transformFile inserts the preamble into the first block that CALLS gsd_run, but gsd-core/workflows/explore.md deliberately places it in a bootstrap-only block that DEFINES gsd_run without calling it -- its own comment explains why: declining the research offer must not leave Step 5's commit call unbootstrapped. Stripping empties that block of calls, so the preamble migrated forward and broke the define-before-use invariant tests/explore-command.test.cjs pins.

Reproduced on a pristine origin/next checkout with the base snippet and base file, so this was not introduced here. The insertion target now honours a block that already carried the preamble, falling back to the first calling block for files that have none yet. Adds a behavioral regression test over a two-block fixture.

Also updates three runtime-launcher-parity tests that pinned the removed PATH fallback to gsd-tools. Their intent is preserved -- the PATH stub is renamed gsd_run so it is reachable by the new resolver, and the RUNTIME_DIR-wins test still asserts the stub is never invoked. Fixture shebangs move to an absolute /bin/sh, because the fixture PATH is deliberately restricted and #!/usr/bin/env sh could not resolve.

Refs #3146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#3146): backfill changeset PR number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(#3146): document the FEATURES.md section-numbering practice

The monotonically increasing section number in docs/FEATURES.md is the most frequent merge-conflict source in this repo, and it has TWO conflict cells, not one: the ### N. heading and the hand-maintained table of contents. Two PRs adding differently numbered features still collide on the TOC, so renumbering alone does not make a branch safe. This branch alone was renumbered 165 -> 166 -> 167 -> 168 across successive rebases.

Adds a CONTRIBUTING section stating the practice: allocate the number last, never pre-emptively renumber, take max+1 after a rebase and update the TOC in the same commit, and never renumber someone else's section. Fork contributors are told explicitly they may leave the number to a maintainer at merge rather than chasing the counter. Agents are told to lease the allocation and to include the file in their published touched set.

Records the durable fix as planned rather than pretending it exists: FEATURES.md should be generated from per-feature fragments the way CHANGELOG.md is generated from .changeset/, and the way tests/emitted-drift-acks/ works (#2914).

Also renumbers this branch's own section to 168, leaving 167 to the PR already in flight.

Refs #3146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 20:57:16 -04:00
Tom Boucher
8df5cb36c2 enhance(#2951): refuse [VERIFIED] for a compatibility claim resting on absent metadata (#3718)
* test(#2951): pin the absent-evidence provenance contract (failing first)

17 tests / 22 anchors on the deployed agent text. Measured against the parent
commit: 20 anchors fail, 2 pass. The two that pass are the sibling-integrity
guards on the package-name and in-repo-value rules -- green before and after is
their intended signature.

Refs #2951

* enhance(#2951): refuse [VERIFIED] for a compatibility claim resting on absent metadata

A claim of the form "X does not support Y" drawn from MISSING metadata -- no
python_requires, no engines field, no per-version classifier, no changelog entry,
no matching support-matrix row -- no longer earns [VERIFIED] however
authoritative the source consulted. An absence is silence about every value, so
the same evidence would "prove" both the version being ruled out and the version
being standardized on. The only route from an absence to [VERIFIED] is a positive
falsification attempt with its failing output pasted; everything short of that is
[ASSUMED], which the file already routes to "needs user confirmation before
becoming a locked decision".

Third member of the family beside the package-name and in-repo-value provenance
rules, mirroring PR #2768's shape. A present declared constraint and an
affirmatively documented incompatibility are untouched.

Closes #2951

* fix(#2951): close the allow-list ambiguity and the mutation gap review found

Findings from the isolated adversarial pass and the two-axis review, all fixed:

MAJOR (x2, one root cause) -- the absence clause and the present-constraint
carve-out gave opposite verdicts on the same evidence for the commonest real
case: a classifier list enumerating :: 3.9 through :: 3.13 with no :: 3.14. A
researcher could read the enumerated list as a "declared" positive constraint
and re-earn [VERIFIED], which is also the evasion vector. The rule now states
the decision procedure -- does the declaration bound EVERY value or only the
ones it names -- and closes the positive-reframing restatement explicitly. New
contract test pins all four clauses.

MAJOR -- 'licenses a positive falsification attempt as the route to [VERIFIED]'
asserted two independent substrings and never that the route lands on
[VERIFIED]. A mutant swapping the tag for [CITED] or [ASSUMED] inverted the
rule and survived all 17 tests. Now pinned as one joined sentence.

MINOR -- the attributable-failure test regex-matched illustrative examples
("a missing certificate, a wrong host"), so a copy-edit would break it for no
reason; relaxed to the substantive clause. The no-paraphrase guard counted only
the heading, missing the drift mode in its own name; it now also pins the core
proposition to one occurrence, and the test name matches what it checks. An
off-by-one in the new allow-list regex bound (141 actual vs 140) is fixed.

MINOR -- docs/AGENTS.md listed four of the five governed absence forms while
the agent prose, docs/COMMANDS.md and the changeset listed five; three copies
disagreeing on list membership is the drift this repo treats as a defect.

SCOPE -- removed docs/how-to/verify-a-dependency-compatibility-claim.md and its
docs/README.md index line. Both reviewers flagged them as a seventh and eighth
surface beyond the six the requester capped, and CONTRIBUTING's "Agent or skill
change" row requires only docs/AGENTS.md. The actionable four-case guidance is
retained in docs/COMMANDS.md, which is inside the approved scope.

Ack byte figures corrected for the final size: 44250 -> 46602 (+2352), 2550
bytes headroom under the LARGE cap of 49152.

Refs #2951

* docs(#2951): restore the how-to the phase gate requires

Reverses the removal in 6404b43d3. Both /code-review axes had flagged
docs/how-to/verify-a-dependency-compatibility-claim.md and its docs/README.md
index line as a seventh and eighth surface beyond the six the requester capped,
and CONTRIBUTING.md's required-docs row for an "Agent or skill change" names
only docs/AGENTS.md, so they were dropped.

gsd-phase-gate.cjs then denied gh pr create: it refuses when the recorded
enablement sequence has more than one step and the how-to quadrant is empty.
The sequence here is genuinely four steps -- run plan-phase, read the [ASSUMED]
claim, probe or cite or accept it unlocked, then answer discuss-phase's
checkpoint -- and the last step lands on a different capability's surface, so a
reference table cannot carry it. Compressing the sequence to one step to unlock
howToSkipReason would be gaming the gate, which is the same Goodhart failure
this whole change exists to close.

A machine-enforced repo gate outranks two reviewers' scope preference and my own
reading, so the page is restored and the PR body discloses the two extra
surfaces instead of hiding them. Reverting is a one-file change if a maintainer
prefers the tighter scope.

Refs #2951

* chore(#2951): backfill the changeset PR number

pr: 0 -> 3718 now that the real PR exists. The placeholder fails
scripts/changeset/lint.cjs with fail_invalid_fragment, which also blocks
lint-docs-required from consuming the fragment.

Refs #2951

---------

Co-authored-by: sim <sim@local>
2026-08-20 14:35:14 -04:00
Tom Boucher
1591454357 feat(#3409): reject shell guards that cannot observe their own failure arm (#3558)
* test(#3409): failing-first regression tests for unreachable shell guard arms

Drives the three live defects fail-first, executing the shipped workflow
snippets rather than a re-typed copy:

- G1/G2 plan-phase.md Walking Skeleton gate reads `--pick summaries_total`,
  a field that does not exist, so PRIOR_SUMMARIES is always "" and the gate
  has never fired (#3365). G2 is the load-bearing negative-space case: it
  rejects a fix that treats "no answer" as "zero" and fires unconditionally.
- G3 plan-phase.md PHASE_REQ_IDS resolves "" instead of the TBD sentinel on
  a phase with zero requirements.
- G4 complete-milestone.md's bare `cat <glob>` blocks on stdin under a
  nullglob left set by an earlier block (measured hang).

Skipped on Windows for G4 only: the FIFO-blocked-stdin mechanism is POSIX
only, and a weakened assertion there would pass vacuously.

Refs #3409

* fix(#3409): make nine shell guards observe their own failure arm

`--pick` coerces a missing field to empty string and exits 0, so the
`|| echo <default>` fallback after it fires only on a verb typo, never on
the field absence it was written for. Nine sites relied on that arm.

- plan-phase.md walking-skeleton gate: `--pick summaries_total` names a
  field that does not exist under any flag combination, so the gate has
  never fired on any project (#3365). Repointed at the existing single
  owner, `phases.list --type summaries --pick count`, which returns a real
  integer in every case including a project with no `.planning` directory.
  No new counter is added: a second one would duplicate the ownership
  ADR-3180 Decision 1 forbids. The gate now fires only on a literal "0",
  so an unanswerable query fails safe instead of entering skeleton mode.
- plan-phase.md phase_req_ids: now falls back to the documented TBD.
- The remaining seven convert to an explicit empty test.
- complete-milestone.md read all phase summaries through a bare
  `cat <glob>`; under a nullglob left set by an earlier block that is zero
  operands, so cat blocks on stdin. Guarded with the array shape the
  #3300 fix already established in review.md.

Refs #3409

* fix(#3409): guard eleven more globs that defeat their own fallback arm

The nullglob audit this issue asks for turned up the same class in files
#3300 never touched.

- Eight bare `cat <glob>` reads (transition, complete-milestone, planner x4,
  verifier, phase-researcher). With nullglob set that is zero operands, so
  cat reads stdin and blocks; measured rc=137 at 3s.
- Three `ls <glob> || echo "<message>"` sites (session-report,
  review-backlog and its generated skill). nullglob makes ls succeed
  listing the cwd, so the message never prints and the user gets a
  directory listing instead.

Guarded with `[ -e "${_ARR[0]}" ]` rather than `[ ${#_ARR[@]} -gt 0 ]`.
The count form is correct only when nullglob is set, and six of these
seven files never set it: without it the array holds the unmatched literal
pattern, so the count is 1 and the guard passes wrongly. `-e` is correct
in both worlds. review.md keeps its count guards — that block sets
nullglob two lines above them.

skills/gsd-review-backlog regenerated from commands/, never hand-edited.

Refs #3409

* feat(#3409): add the unreachable-shell-guard drift lint

A sibling of lint-planning-prompt-drift.cjs, consuming the shared
scripts/lib/drift-scan.cjs rather than copying it, wired into lint:ci.

Both detectors are one shape — a fallback arm defeated by a legitimate
success-on-empty:

- Detector A: `--pick` and `|| echo` on one line. `--pick` is the
  discriminator because "missing field renders empty at exit 0" is a
  documented CLI contract, not a heuristic. A rule keyed on gsd_run
  matched 111 lines, ~132 of them legitimate, and was rejected.
- Detector B: `cat <glob>` in command position, and `ls <glob>` whose
  exit code feeds a real fallback or an if/while head. Informational
  `ls <glob>` whose stdout is consumed (97 sites) and `|| true` failure
  suppression (~15) are not guards and never fire.

Shrink-only ratchet keyed on (file, trimmed text) with a per-pair count,
POSIX-normalized unconditionally so Windows CI cannot report everything
fresh and stale at once. Ships with a ZERO-entry baseline: every site it
can find is fixed. Exemption is the per-line `# gsd-scan-ignore: #NNN`
marker whose reason must name an issue or URL; a malformed reason reports
a distinct error rather than silently exempting. No file allowlists.

ADR-3409 records the invariant, the measurements behind both detectors,
and why the upstream `--pick` contract fix belongs to #3473.

Refs #3409

* fix(#3409): resolve review findings — typed surface, sanitized reports, tighter marker

Standards axis (blocker): the guard's tests asserted on human-readable
stdout/stderr and on free-form baseline-load prose, which CONTRIBUTING
prohibits by name. Added the typed surface it prescribes instead of
weakening the tests: a frozen REASON enum, a --json report mode,
structured loadBaseline errors, and a test locking Object.keys(REASON)
so a new reason stays three coordinated changes.

Security axis: sanitizeForReport covered every violation field but not
the baseline-load error path, which embeds raw JSON.stringify output --
that escapes nothing above 0x1f, so bidi and C1 controls reached CI logs
unfiltered. Routed through the sanitizer at the output seam.

Security axis: the scan-ignore marker accepted `#0` and a bare
`http://`. Tightened to a positive issue number and a URL with a host.
This diverges deliberately from the sibling in
tests/commit-files-pathspec.test.cjs, whose looser form was copied
verbatim; the header now records the divergence.

Security axis: G4 built its FIFO with `mktemp -u`, reserving a name
without creating it. Now created inside a `mktemp -d` directory.

Spec axis: ADR-3409 claimed a ninth site landed after the issue was
filed. git blame disproves it -- all nine predate it; the issue's hand
count missed one. Corrected. The design and test matrix still specified
B9 as a FLAG after implementation reversed it to PASS; both now record
the reversal and why.

Refs #3409

* docs(#3409): add the how-to for resolving unreachable-guard findings

Reference and Explanation are carried by ADR-3409; this is the
task-oriented quadrant CI cannot check for.

The page exists mainly for one thing the lint structurally cannot catch:
both `[ -e "${_ARR[0]}" ]` and `[ ${#_ARR[@]} -gt 0 ]` remove the glob
from the command and therefore both pass, but the count form is correct
only when nullglob is set — and nullglob is usually set in a different
block of the same file. A reference table cannot carry that; a how-to can.

Also documents the reason codes, so a reader can tell "nothing to report"
from "could not look".

No tutorial: this is a gate inside an existing CI loop, not a new entry
point a newcomer starts from.

Refs #3409

* fix(#3409): bring the touched prompt files back under their size gates

The remote run was red on 14 tests, all size/attribution, none of them
the regression suite.

- agents/gsd-planner.md was 194 chars over a 49152 cap enforced by four
  separate tests, each of which says the remedy is extraction, not a bump.
  It had 41 chars of headroom before this branch. Its `## Checkpoint
  Types` section was an unlinked, condensed duplicate of
  references/checkpoints.md, which already carries all three types and
  their XML shapes; the section now points there and keeps the three
  names and percentages inline. Net -969, margin 1010.
- gsd-core/workflows/execute-phase.md sat 2 chars under a comfortable
  margin assertion. Dropped the AUTO_MODE default: the `|| echo "false"`
  it replaced was unreachable, so the value was already sometimes empty
  on next, and its only consumer compares against `true`. Net -16.
  Left plan-phase.md's AUTO_CHAIN default alone -- that file names an
  explicit `false` branch, so empty would match neither branch.
- Acknowledged the seven prompt files that genuinely grew, one specific
  reason each. Five of those paths were already claimed by spent
  fragments identical to next, which blocks a second source naming the
  same path; removed just the colliding key from each, deleting the two
  that this emptied.

Refs #3409

* test(#3409): extract the whole PHASE_REQ_IDS block, not just its first line

G3 failed on the remote runner with '' !== 'TBD'. The test was wrong, not
the workflow.

The shipped contract is now two consecutive lines -- the capture and the
`${PHASE_REQ_IDS:-TBD}` default -- but the helper's `^PREFIX=.*$` regex
returns only the first match, so the test executed half the contract and
correctly observed the empty string. Renamed to extractAssignmentBlockFor
and taught it to consume the contiguous run of lines sharing the prefix.

The assertion is untouched: TBD is the right expectation, and weakening
it to accept the empty string would have reinstated exactly the class
this suite exists to catch -- a check that cannot observe the thing it
is checking.

extractFencedBashAfterAnchor is unaffected: it is fence-delimited rather
than line-anchored, so G1/G2/G4 still capture their full blocks.

Refs #3409

* chore(#3409): drop a spent ack fragment that collided on complete-milestone.md

#3458 landed on next while this branch was in flight and its fragment
claims complete-milestone.md, which this branch also grows. Two ack
sources may never name the same path.

Its entry is spent: the +9163 it explains is already absorbed at base, so
it can no longer clear anything, and the checker's own guidance for spent
entries is to delete them. Removing the key emptied the fragment, so the
file goes too -- an empty one signals nothing.

Refs #3409

* chore(#3409): backfill changeset pr number 3558

* test(#3409): hoist a regex subject out of exec() to clear the injection scan

CI's prompt-injection scan flagged `MARKER_RE.exec('# gsd-scan-ignore: ...')`.
The pattern `exec[[:space:]]*\(["']` is receiver-blind on purpose, so it
catches `require('child_process').exec('...')` -- and the scanner's own
header records that RegExp.prototype.exec is collateral, to be handled by
its allowlist.

Allowlisting the file would blind it to the real exec vector permanently,
so the subject is hoisted into a const instead: same assertion, scanner
left at full strength, no security surface widened.

Refs #3409

---------

Co-authored-by: sim <sim@local>
2026-08-15 21:09:05 -04:00
Rezolv
2dbee3ebdd enhance(#2229): add three-way claim disposition (admit/refute/abstain) to /gsd-explore research pass (#2543)
Closes #2229.

Each claim surfaced by /gsd-explore's research pass is dispositioned admit, refute, or abstain, with abstentions routed to a visible ledger instead of being smoothed into confident prose. Refute and abstain are separated by whether the disagreeing source is authoritative for that claim; a strong prior is never authoritative alone.

Two guards ride with it: conflict-abstention, and a tier floor that presents a would-be admit as an abstain when the researcher's resolved tier is the budget tier or cannot be determined.

To make that floor enforceable, resolve-model now emits the effective tier (--pick tier). It was already computed above the resolve_model_ids omit gate but was unreachable from a workflow, which left the floor inert on every non-Claude install - the model id is blank under omit and runtime-substituted where a tier map exists, and the profile defaults to balanced. The tier signal mirrors every resolution step that can change which tier runs, including the model_policy preset, and reports unknown rather than guessing. Output is additive; model, profile and effort are unchanged.

Two residuals are disclosed in the workflow rather than papered over: a raw-model-id model_overrides pin reports unknown and is floored (fails closed), and a model_profile_overrides entry repointing a tier at another tier's model can under-report (fails open, and predates this change).

Admin merge used only to satisfy the missing secondary reviewer on a single-maintainer PR. No CI failure and no conflict were bypassed: 38 checks green, remote runner 32255/32255 on both Node lanes.
2026-08-09 23:05:41 -04:00
Tom Boucher
b36e3b7e1f fix(#2751): normalize bare gsd-tools command-position calls to gsd_run in shipped source (#2851)
* test(#2751): regression guard — no command-position bare gsd-tools calls

Agents/workflows instructed bare `gsd-tools <verb>` invocations that fail with
'command not found' on a shim-only install (#725 fixed only the Codex conversion
pipeline; the Claude-facing source shipped them verbatim). Adds a source-text
guard (allow-test-rule: source-text-is-the-product) scanning agents/*.md +
gsd-core/workflows/*.md for the operative shape `gsd-tools <verb> <arg>`,
excluding command -v probes / resolver definitions, with a documented
PROSE_ALLOWLIST for descriptive mentions that name the command without
instructing literal invocation. A stale-allowlist check ensures entries stay
real. RED first; source fix lands next commit.

* fix(#2751): normalize bare gsd-tools calls to gsd_run in Claude-facing source

The 12 command-position bare `gsd-tools <verb>` instructions across agents/ and
gsd-core/workflows/ failed with 'command not found' on a shim-only install (no
gsd-tools binary on PATH). #725 fixed this only for the Codex install-conversion
pipeline; the Claude-facing SOURCE shipped the bare calls verbatim, and new ones
kept accumulating (new-project.md:114 landed 12 days AFTER #725 closed).

Rewrite each operative site to the portable `gsd_run` resolver that the same
files already define (3-18x each) — a pure command-position token swap
preserving all arguments, flags, --files, and surrounding prose. Every runtime
now benefits from one source change instead of each needing its own converter.

Touched sites (12): gsd-intel-updater (validate/snapshot/extract-exports),
gsd-code-fixer (query commit), gsd-planner (learnings.query), gsd-project-
researcher (websearch/research-plan/classify-confidence), gsd-phase-researcher
(websearch/research-plan/classify-confidence), new-project (project-instruction-
file / commit --files), new-milestone (commit --files). Preserves command -v
gsd-tools probes, resolver-snippet definitions, and the 4 descriptive prose
mentions that NAME the command without instructing invocation. RED @ 1bb12ba1.

* chore(#2751): allow-test-rule issue ref + changeset fragment

Add the (#2751) tracking ref to the source-text-is-the-product annotation per
ADR-456, and the .changeset Fixed fragment (pr:0, backfilled post-PR).

* fix(#2751): convert remaining command-position bare gsd-tools calls (verify-summary, windows, worktree, smart-entry, quick-tasks-append)

Isolated adversarial review (Step 4) found the first pass missed genuine
command-position bare calls because the regression test's hand-maintained
6-verb list silently false-passed verify-summary (the 'verify' branch matched
the prefix then died on the hyphen) and omitted windows/worktree/smart-entry/
quick-tasks-append entirely. Convert these 8 additional operative sites across
new-project.md, new-milestone.md, ship.md, execute-phase.md, progress.md,
smart-entry.md, quick.md.

* chore(#2751): backfill changeset PR number (2851)

* fix(#2751): normalize allowlist paths to forward slashes — Windows path-separator false-flag

The PROSE_ALLOWLIST is keyed by file:line using forward-slash paths, but
path.relative() returns backslash separators on Windows, so the allowlist
lookup failed and the 6 descriptive mentions were flagged as offenders on
the windows-latest CI lane. Normalize rel to forward slashes before the
lookup so the allowlist matches identically on every OS.

---------

Co-authored-by: Test <test@example.com>
2026-07-30 11:18:07 -04:00
Rezolv
6932fb16d7 enhance(#1699): require read-and-cite provenance for in-repo discrete values (#2768)
* test(#1699): failing-first contract tests for in-repo value provenance

Nine assertions on the deployed gsd-phase-researcher contract: the discrete-value taxonomy, the same-session Read requirement, path-AND-line-range citation, grep-alone exclusion, the verbatim quote and paraphrase ban, the quote-is-the-checkable-artifact guard, [ASSUMED] routing for unquoted skeleton values, a no-regression guard on the pre-existing package name provenance rule, and a single-definition-site guard.

Eight of the nine fail against the unmodified agent on origin/next; the ninth is the no-regression invariant and passes in both states, which is the intended enhancement shape.

Added to tests/research-agent-profiles.test.cjs rather than a new file: that file already carries the allow-test-rule exemption <runtime-contract-is-the-product> research agent .md content is the governed surface, so no new allowlist entry and no change to the per-module test-file count.

* enhance(#1699): require read-and-cite provenance for in-repo discrete values

The claim-provenance system governed external facts (npm registry, official docs, Context7, package-name provenance). For an in-repo discrete value -- an enum, schema or type union, error code, status constant, or filesystem path -- [VERIFIED] could be earned from training memory or a bare codebase grep, which proves a string occurs, not that the definition was read.

A drifted value passes into RESEARCH.md, is lifted by the planner into PLAN.md's <interfaces> context block, and is trusted by the executor, where it fails at parse()/typecheck as a mid-execution deviation -- the most expensive place to discover it.

The rule lands beside its structural sibling, the package name provenance rule, since both say existence is not verification. The verbatim quote is named as the load-bearing artifact: a citation with no quote does not earn the tag, however precise the line range looks. That keeps the rule falsifiable against the file rather than a self-report, which is the Goodhart guard.

Scope note: the quote goes in RESEARCH.md beside the claim, NOT in an <interfaces> block. <interfaces> appears zero times in this agent on next -- it is planner-side, defined at gsd-core/references/planner-interface-context.md:15 as a PLAN.md structure. The issue text and triage both said <interfaces>; instructing the researcher to populate a block it does not emit would be undefined.

Defined once at the definition site; the source-hierarchy recap is deliberately untouched, since restating it is the paraphrase-drift mode META.RULE.brief-no-paraphrase names.

* test(#1699): regenerate agent size baseline and golden parity fixtures

Regenerated via npm run size:baseline and npm run gen:golden, never hand-edited. agent-size-baseline gsd-phase-researcher.md 40866 to 42020 (LARGE tier, cap 49152, 7132 bytes headroom remaining, per ADR-1610's per-file baseline guard). 18 of 19 golden fixtures updated; pi.json is unchanged because the pi runtime ships zero agents.

* chore(#1699): add changeset for the in-repo value citation rule

User-facing behavior change in gsd-phase-researcher, so a Changed fragment is required. pr: 2768.

* test(#1699): acknowledge the gsd-phase-researcher growth for the emitted-drift gate

CI test (ubuntu-latest, 22) failed on emitted-attribution.test.cjs: gsd-phase-researcher.md grew 1154 bytes (40866 -> 42020) without an acknowledgment. The differential emitted-attribution gate landed on next in 9138271b (#2723) and requires intentional growth to be named AND explained in tests/emitted-drift-ack.json per ADR-2719 section 3.

This creates the file; it did not previously exist on next. The reason records what grew and why the prose was added inline rather than relocated into an eagerly @-imported reference, which ADR-1610 Decision 4 names as gaming the size proxy.

Verified locally against a freshly fetched origin/next: emitted-attribution 35/35, and the differential subtest runs rather than skips (skipped 0).

---------

Co-authored-by: CI Rebase Check <ci@gsd-redux>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-07-28 18:31:26 -04:00
Tom Boucher
68a5258d45 fix(#2017): grant mcp__plugin_context7_context7__* for plugin-marketplace context7 (8 agents) (#2029)
* fix(#2017: grant mcp__plugin_context7_context7__* for plugin-marketplace context7

The 8 context7-using agents granted only mcp__context7__* (standalone server
form). Claude Code's plugin-marketplace context7 install names tools
mcp__plugin_context7_context7__*, so the grant never matched and every
researcher/planner/executor silently lost doc lookup (fell back to WebSearch).

- 8 agents: add mcp__plugin_context7_context7__* alongside mcp__context7__*.
- scripts/research-profiles.cjs: update the researcher profile tools to match.
- tests/context7-plugin-grant-parity.test.cjs: regression guard — no agent
  grants the standalone form without the plugin form.

Closes #2017

* docs(#2017): backfill changeset pr 2029
2026-07-05 17:15:48 -04:00
Tom Boucher
a62079b2da fix(#1865): runtime launcher honors CLAUDE_CONFIG_DIR (#2024)
* fix(#1865): runtime launcher honors CLAUDE_CONFIG_DIR

The gsd_run preamble resolved the Claude global install only at
$HOME/.claude/gsd-core/bin/, but the installer honors CLAUDE_CONFIG_DIR —
so a global install redirected via CLAUDE_CONFIG_DIR was invisible to every
gsd_run call (every command failed with 'gsd-tools.cjs not found').

The Claude resolver arm now uses ${CLAUDE_CONFIG_DIR:-$HOME/.claude},
matching the installer + the other runtimes' ${VAR:-default} pattern.
Default $HOME/.claude behavior is unchanged.

- _runtime-launcher.snippet.sh: Claude arm honors CLAUDE_CONFIG_DIR.
- sync-runtime-launcher.cjs re-run: 95 workflows/agents re-synced.
- review.md / discuss-phase.md: trimmed to stay under their byte budgets.
- runtime-launcher-parity.test.cjs: (A) substring updated for the new form
  + explicit #1865 assertion that the snippet honors CLAUDE_CONFIG_DIR.
- goldens + size baselines recaptured.

Closes #1865

* docs(#1865): backfill changeset pr 2024
2026-07-05 14:20:52 -04:00
Tom Boucher
3c13903dcd feat(#1866): agent-side self-load of configured agent_skills
Each of the 22 consumer agents now self-loads its configured agent_skills
in its mandatory init step, so .planning/config.json agent_skills.<type>
reaches the agent on every runtime — including Cursor and /gsd-autonomous,
where Skill()-delegated workflow bash init did not reliably execute.

- gsd-core/references/agent-skills-bootstrap.md: shared contract
  (query + Read + dedup guard that skips when <agent_skills> is already
  in the prompt, so Claude's orchestrator-side injection never doubles)
- 22 agents/gsd-*.md: one self-load line naming the agent's own type
- gsd-core/workflows/autonomous.md: note that delegated agents self-load
- tests/agent-skills-bootstrap.test.cjs: regression + parity (CONSUMER_AGENTS
  bijection + fast-check property) — Generative-Fix-Divergence guard
- docs: ADR-1866, CONFIGURATION dual-injection How It Works, INVENTORY
  row, Changed changeset

Closes #1866
2026-07-01 20:09:01 -04:00
Alex V.
a63684c222 enhance(#1577): WebFetch/WebSearch injection isolation + opt-in blocking (#1585)
* fix(#1577): isolate WebFetch/WebSearch ingress + opt-in injection blocking

Split A of #1573 (security-critical). Scans WebFetch/WebSearch output (the
largest untrusted channel) in gsd-read-injection-scanner; shared
untrusted-input-boundary reference @-included by the 8 ingest agents
(randomized per-wrap delimiters, in-prompt self-scan guard, task-anchoring);
opt-in security.injection_blocking (default advisory — non-breaking).

arXiv: 2506.05739 (PPA), 2507.15219 (PromptArmor), 2504.20472 (Referencing), 2503.00061 (defense-in-depth).

* fix(#1577): address review — honest blocking docs, config key, ADR, property test, revert localized

- A1: rewrote the opt-in-blocking doc + Security changeset honestly — the PostToolUse hook is a
  circuit-breaker (halts the agent's next step), NOT a redactor; it does not scrub content already
  in the transcript. The prompt-level data/instruction boundary is the primary control.
- A2: registered security.injection_blocking in the config schema + defaults manifests (default
  false) + an e2e config-roundtrip test; the dotted setter writes the nested shape the hook reads.
- A3: reverted the 4 hand-edited localized security-model.md (canonical EN only, per convention).
- A5: ADR-1577 (untrusted-input boundary + opt-in blocking; redaction-vs-circuit-breaker rationale).
- A6: property test — scanner never crashes / only emits valid JSON on unicode/large/malformed input.
- Also: inventory (untrusted-input-boundary.md) + agent-size baseline (8 ingest agents) +
  drift-guard matcher update (Read -> Read|WebFetch|WebSearch). A7 (content<20 early-exit) left as
  the noted pre-existing follow-up.

* fix(#1577): allowlist untrusted-input-boundary.md in injection-scan CI gate

The new reference quotes injection phrases ('ignore previous instructions',
'you are now…') as examples agents must NOT comply with, tripping the repo's
own prompt-injection-scan.sh diff gate (the standalone 'security' CI job, red
on HEAD). Allowlist it alongside the other security docs (security-model.md,
TEST-EXAMPLES.md) that legitimately demonstrate injection patterns. The JS
scanner test doesn't scan references/, so only the shell gate needed it.

Verified: scan --diff origin/next -> 0 findings; scanner JS test 15/15.

* fix(#1577): cover AC #2's gsd-ui-researcher + gsd-assumptions-analyzer

trek-e Major 1: the @-included set dropped two AC #2 agents. Restore them so
no named web-ingress agent is uncovered, keeping the two justified additions
(gsd-ai-researcher, gsd-domain-researcher). Final set = AC's 8 + 2 = 10.
 - gsd-ui-researcher carries the full WebSearch/WebFetch + MCP-fetch toolset.
 - gsd-assumptions-analyzer reads 5-15 codebase source files (external/source-
   document ingress per the boundary), though it has no web tools.
INGEST_AGENTS in the isolation test now asserts all 10; size baselines
regenerated (+60 bytes each, both well under the DEFAULT cap); changeset
reworded 8 -> 10.

Verified: untrusted-input-isolation 14/14; agent-size-budget 39/39.

* docs(#1577): document security.injection_blocking + boundary seam

trek-e Major 2 + Minor:
 - docs/CONFIGURATION.md: add the top-level security.injection_blocking key to
   the Full Schema and a Security Settings subsection, distinguishing it from
   the workflow.security_* namespace; honest circuit-breaker-not-redactor
   framing matching ADR-1577 / security-model.
 - CONTEXT.md: add the 'Untrusted-input boundary' seam glossary entry.

Verified: lint:docs ok; config-field-docs + contributor-standards green.

* test(#1577): make read-injection property test git-text, not binary

trek-e nit (and more): the file embedded a raw U+FFFF AND a raw NUL byte as
degenerate-edge inputs. The NUL is what actually made git classify it binary
(git binary = NUL in first 8K). Replace both with text-safe escapes that keep
the identical runtime values: '\\x00' and String.fromCodePoint(0xFFFF). File
now diffs/blames line-by-line.

Verified: property test 2/2; no NUL/raw-noncharacter bytes remain.

* docs(#1577): align untrusted boundary docs

Name all 10 ingress agents in INVENTORY/security-model and allowlist the intentional read-injection property corpus for the prompt-injection scanner.

* docs(#1577): align ADR ingest agent count

Update ADR-1577 from 8 to 10 ingest agents so it matches the actual boundary include set and the rest of the docs.

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-24 17:07:23 -04:00
Tom Boucher
b108f101b0 fix(#1284): grant mcp__perplexity__* to researcher agents + dispatch-table parity guard (#1288)
Adds mcp__perplexity__* to both researcher profiles (generated source-of-truth) and regenerates the agents; adds a generative dispatch-table↔tools parity guard so future provider drift fails CI. Regenerates the agent-size baseline for the +20-byte frontmatter growth.

Fixes #1284

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:20:08 -04:00
Tom Boucher
cf68841220 enh(#1243): consume Claude plugin-provided skills in agent_skills (epic #1258 Phase B) (#1261)
* feat(#1243): consume Claude plugin-provided skills via native Skill-tool directive + grant Skill to agent_skills-consumer agents

- Relax global skill name validation to accept namespaced form `^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)*$`
- Namespaced names (containing colon) on claude runtime emit a Skill-tool load directive instead of a @-include line
- Namespaced names on non-claude runtimes are skipped with a warning
- Bare unresolved names retain existing warn-and-skip behavior (no promotion to directive)
- Grant `Skill` tool to all 22 agent_skills consumer agents; 5 generated agents updated via research-profiles.cjs + regen, 17 hand-authored agents edited directly
- Add 16 TDD tests in describe('bug #1243') covering happy/mixed/precedence/negative/cross-runtime/regression/grant cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#1243): document plugin-provided skills in agent_skills

Update the Agent Skills Injection reference in CONFIGURATION.md with
the three entry forms (project-relative, global:<name>,
global:<plugin>:<skill>), the Claude-only runtime behaviour of the
namespaced form and the warn-skip on other runtimes, the plugin
pre-install prerequisite, and the consumer-agent Skill tool grant.

Add docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md with a
step-by-step guide for installing the plugin, locating the namespaced
skill name, wiring it into agent_skills, and verifying injection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#1243): align agent_skills docs with emitted block format + mixed-block regression test (code-review)

- Replace two-section mixed-block example (bogus "Load these plugin-provided skills using the Skill tool:" header) with the actual single-section inline format in CONFIGURATION.md and docs/how-to/attach-a-plugin-skill-to-a-gsd-agent.md
- Fix quoted warning text in how-to doc to exactly match the emitted string: [agent-skills] WARNING: Plugin-namespaced skill "global:<name>" requires a Skill-tool-capable runtime (claude) — skipping on runtime "<runtime>"
- Replace phantom agent slugs (gsd-checker, gsd-researcher, gsd-advisor, gsd-synthesizer) in CONFIGURATION.md Supported Agent Types with real agents/gsd-*.md examples (gsd-plan-checker, gsd-phase-researcher, gsd-code-reviewer, gsd-ui-auditor, gsd-research-synthesizer)
- Add byte-identical mixed-block regression test: one path-resolvable global skill + one plugin-namespaced skill on claude runtime → asserts r.ir.block === single-section interleaved block, no secondary header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(#1243): regenerate agent-size baseline for the Skill-tool grant

The 22 agent_skills-consumer agents each grew +7 bytes from adding `Skill`
to their tools list; refresh the committed per-agent size baseline (#1074 guard).

* chore(#1243): add Added changeset fragment

* fix(#1243): traceable allow-test-rule ref + separator-agnostic byte-identical tests (CI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:49:12 -04:00
Tom Boucher
7c07fce70f fix(#381): make gsd_run launcher reachable in fresh-shell-per-block runtimes (#1084)
* fix(#381): make gsd_run launcher reachable in fresh-shell-per-block runtimes

On runtimes that execute each fenced bash block in a separate shell process
(e.g. Claude Code — documented behavior: each Bash command is a separate
process; inline shell functions and exported vars do not persist between
calls), the once-per-file gsd_run() function was undefined in every block
after the preamble block, and the call was swallowed by
`2>/dev/null || echo "{}"` into silent empty state.

Fix (budget-neutral session-level resolution):
- Ship gsd-core/bin/gsd_run, a POSIX sh wrapper that symlink-resolves its own
  location and execs the co-located gsd-tools.cjs. Exposed on PATH via the npm
  `bin` field (global installs) and shipped to local installs via the recursive
  gsd-core/ copy.
- The per-file launcher preamble now appends `export PATH='<bindir>':"$PATH"`
  to the file named by $CLAUDE_ENV_FILE (Claude Code's documented
  env-persistence mechanism) so later fresh-shell blocks resolve gsd_run from
  PATH. Guarded as a strict no-op when CLAUDE_ENV_FILE is unset; the inline
  gsd_run() definition remains the fallback for all other runtimes. The
  single-quoted dir neutralizes shell metacharacters at source time.
- Propagated via scripts/sync-runtime-launcher.cjs to all launcher-using files.
- XL workflow byte budget 93000 -> 93200 (the ~130B clause pushes plan-phase.md
  to 93135; legitimate content growth, ratchet-up per #717).

Regression tests (I)/(J) in runtime-launcher-parity.test.cjs cover wrapper
delegation and end-to-end PATH persistence (sourcing the env file with a
space-bearing install path).

Known limitation: an install path containing a literal single-quote yields a
malformed env-file line and falls back to the status quo (no regression);
rare on sanitized home directories.

Closes #381

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(#381): add changeset for gsd_run fresh-shell reachability fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#381): scope test (J) bare-PATH execution to POSIX (Windows Git Bash exec bit)

Windows Git Bash (msys2) does not honor Node's chmod exec bit for
PATH-executing extension-less scripts, so the bare `gsd_run` command lookup
failed there even though the env-file PATH persistence was correct. The
env-file content assertions (the fix's actual cross-platform logic) still run
on every platform; only the final source-and-execute sub-step is gated to
non-win32. Global installs on Windows are covered by npm's generated bin shim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:42:52 -04:00
Tom Boucher
1fab2e10ba fix(#1041): route all source agents through the canonical multi-runtime gsd-tools resolver (#1045)
* fix(#1041): route all source agents through the canonical multi-runtime gsd-tools resolver

Source agents/*.md (gsd-planner, gsd-executor, gsd-verifier, gsd-plan-checker,
gsd-intel-updater, gsd-debugger, …) called bare "gsd-tools …" in shell blocks.
On a shim-only install — where gsd-tools.cjs exists under the runtime home but
gsd-tools is NOT on PATH — those calls fail with "command not found" and the
agent silently skips init/state/validate/commit ceremony, deferring to the
orchestrator or bypassing GSD bookkeeping entirely.

were never migrated, so it persisted on Claude Code and every other runtime that
consumes the source agents directly. Only gsd-phase-researcher.md carried a
resolver — and a stale, claude-only truncated one.

Fix (all runtimes):
- Inject the canonical multi-runtime gsd_run preamble (byte-equal to
  _runtime-launcher.snippet.sh — claude/codex/cursor/gemini/copilot/windsurf/
  augment/trae/qwen/cline/opencode/kilo/hermes/antigravity homes) at the top of
  the first gsd_run block of all 12 gsd-tools-calling agents, and rewrite every
  command-position bare gsd-tools to gsd_run.
- Upgrade gsd-phase-researcher.md's stale resolver to the canonical one.
- Extend scripts/sync-runtime-launcher.cjs to maintain agents/ in parity (the
  sync caught and corrected a mis-placed preamble during development).
- Extend the bare-gsd-tools (#2851) and launcher-parity (#373) regression guards
  to agents/ so no runtime can silently regress.

Closes #1041

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1041): backfill changeset PR number to 1045

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 11:42:59 -04:00
Tom Boucher
11afca2968 feat(#656): Research module — content-addressed cache + provider seam + registry-API legitimacy (#664)
* feat(#656): add Research Store module (content-addressed cache, TTL staleness)

Content-addressed research cache behind a clock seam: researchKey (sha256, deterministic), putResearch/getResearch ({hit,stale}, never throws), ttlForSource (curated HIGH 30d / MED 7d / web LOW 1d), two-tier resolveStorePath (curated -> ~/.gsd/research-cache, web/synthesis -> project .planning/research/.cache). 28 behavioral + property tests; boundary coverage at ttl-1/ttl/ttl+1.

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#656): add Research Provider module (waterfall + confidence + plan)

Single source of truth for the Balanced provider waterfall (docs Context7->Ref->Jina, web Exa+Tavily, fallback Perplexity/Brave, Firecrawl scrape-only). classifyConfidence stamps HIGH|MEDIUM|LOW by provider (never throws). providerAvailability maps config flags to usable providers. planResearch checks the Research Store (injected seam) and returns cache-hits + a per-question fetch plan, falling through the waterfall to the always-available websearch terminal. 22 behavioral + property tests.

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#656): add Package Legitimacy module (registry-API verdicts, slopcheck optional)

Replaces the pip-install-or-degrade slopcheck prose gate with code: classifyPackage (pure, never throws) computes OK|SUS|SLOP from tunable thresholds (minAgeDays 30, minWeeklyDownloads 1000, requireRepo). checkPackages queries injectable npm/PyPI/crates registry adapters (real https with 5s timeout, degraded-not-thrown on failure); slopcheck is one optional adapter that can only escalate severity, never degrade to [ASSUMED]. 34 behavioral + property tests; boundary coverage on age and downloads (limit-1/limit/limit+1).

Known follow-up: real npm adapter must add api.npmjs.org last-week downloads fetch (currently null -> unknown-downloads). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#656): detect Tavily/Ref/Perplexity/Jina provider keys; complete npm downloads adapter

config: add tavily_search/ref_search/perplexity/jina availability flags (env var or ~/.gsd/<x>_api_key), mirroring brave_search/exa_search/firecrawl, so the Research Provider waterfall can gate them. package-legitimacy: real npm adapter now fetches api.npmjs.org last-week downloads (bounded, degraded-not-thrown) so weeklyDownloads is populated. +12 config tests; 34 legitimacy tests unchanged.

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#656): expose Research seam via gsd-tools query (research-plan, research-store, package-legitimacy)

Routes the L2-hybrid surface so agents reach it as CLI: 'query research-store get/put' (cache, HOME-sandboxable), 'query research-plan --input' (cache-hits + fetch plan from planResearch), 'query package-legitimacy check --ecosystem' (async registry verdicts). Commands skip .planning root resolution and appear in top-level usage. 5 behavioral runGsdTools tests; command-contract unchanged (335).

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(#656): document Research module (CONTEXT predicates, ADR-0656, architecture, changeset)

Adds GSD-RESEARCH.* + DEFECT.RESEARCH-PROVIDER-PROSE-DRIFT predicates to CONTEXT.md, ADR-0656 recording the L2-hybrid seam decision, a docs/ARCHITECTURE.md Research Module subsection, and an Added changeset fragment (pr:0, backfill on PR). Notes the #657 deferrals (agent collapse + install.js MCP mapping).

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#656): sync inventory for research modules

Regenerate INVENTORY-MANIFEST.json and bump docs/INVENTORY.md CLI Modules count 82->85 with rows for research-store/research-provider/package-legitimacy (DEFECT.INVENTORY-DRIFT).

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#656): eslint-ignore generated research .cjs artifacts (ADR-457)

research-store/research-provider/package-legitimacy .cjs are tsc-generated from src/*.cts, so they belong in the ESLint ignore block (lint the .cts source, not the emitted .cjs). Fixes tests/551-eslint-bin-lib-coverage.

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#656): backfill changeset pr number to #664

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#656): satisfy eslint lint-tests gate

Fix 20 eslint errors in the new research files: use helpers.cleanup() instead of raw fs.rmSync() in tests (local/no-raw-rmsync-in-tests, Windows-EBUSY retry budget); drop redundant '| string' union members and unnecessary type assertions; deterministic object normalization in researchKey (no-base-to-string). Logic unchanged; 6180 tests still green.

Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): harden package legitimacy per review (W1/W2/I3/I4)

W1: httpsGet now reads statusCode; npm/PyPI/crates map 404 -> exists:false -> SLOP (registry-existence is the #1 slopsquatting defense; previously only npm caught it). Transport made injectable (_setHttpGet) for hermetic 404 tests. W2: suspicious-postinstall is now terminal SLOP independent of the optional slopcheck adapter, and the regex drops the bare https?:// arm (over-fired on esbuild/sharp/node-gyp) for shell-exec/download-exec signatures only. I3: checkPackages now threads version to registry.lookup and adapters verify that specific version exists. I4: moreServerVerdict -> moreSevereVerdict. +11 regression tests (all RED-first); 45 total green.

Addresses review by @davesienkowski on #664. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): research-store tier coherence + freshness + version TTL (W4/I1/I2/I4)

I1: tier now derives from source (curated -> user ~/.gsd, else -> project .planning), not kind, so put-tier and get-tier can't diverge; kind is a key component only. W4: getResearch searches both tiers and returns the freshest (non-stale preferred), never letting a stale curated entry shadow a fresh web one; blank version caps TTL at 1 day (no 30d on version-blind keys). I2: atomic platformWriteSync instead of raw fs.writeFileSync on the shared global path. I4: dropped the dead ttlForSource arm. CLI get now searches both tiers. +5 RED-first regression tests; 38 green.

Addresses review by @davesienkowski on #664. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): expose classifyConfidence as a CLI route, killing dead code (W3)

Adds 'gsd-tools query classify-confidence --provider X [--verified]' so research agents get the confidence tier FROM CODE (provider waterfall + verification lever) instead of asserting it in prose. classifyConfidence previously had no runtime caller. HIGH means 'trusted provider'; --verified raises web results to MEDIUM (verification semantics documented in ADR-0656). +4 behavioral tests.

Addresses review by @davesienkowski on #664 (W3). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): close Codex adversarial-review findings (path-traversal, version-age, malformed-cache)

HIGH: research key must be 64-hex sha256 (isValidResearchKey) + resolved-path containment check in put/get + CLI validation -> blocks '../../x' arbitrary-file-write. HIGH: package legitimacy now derives publishedAt from the REQUESTED version (npm time[version], PyPI releases[version] upload_time, crates versions[].created_at) so a new malicious version of an old package can't inherit old age and evade 'too-new'. MEDIUM: getResearch validates entry shape (finite fetched_at + positive ttl + required fields) -> malformed cache entry is a miss, not fresh-forever. +regression tests (RED-first); 111 green.

Codex adversarial review (required pre-PR gate). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): close code-review correctness findings

(1) package-legitimacy CLI now rejects unknown --flags instead of silently consuming the following package as a flag value; only --ecosystem takes a value. (2) crates recent_downloads (90-day) normalized to a weekly figure before the minWeeklyDownloads threshold (was ~13x too lenient). (3) research-plan --input validates parsed JSON is an object with an Array questions before destructuring -> clean usage error instead of an uncaught TypeError on null/bad input. (4) research-store put rejects a flag value that is itself a --flag (no more storing '--source' as content). (5) planResearch skips questions whose text is not a non-empty string instead of emitting question:undefined. +13 RED-first regression tests; 143 green.

Code-review gate. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(#657): extract researcher documentation_lookup to shared @-reference

6 researcher agents carried a near-duplicate <documentation_lookup> block; consolidate into gsd-core/references/research-documentation-lookup.md (@-included). Unifies the ctx7 CLI fallback to the safer 'command -v ctx7' guard (drops silent 'npx --yes ctx7@latest' execution in 5 agents). Behavior-preserving dedup; inventory 63->64 references. Phase A of the agent collapse.

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(#657): extract researcher philosophy + verification-protocol to shared @-references

philosophy and the pitfalls+pre-submission-checklist common-core were near-duplicated in project/phase researchers; consolidate into gsd-core/references/research-{philosophy,verification-protocol}.md (@-included). phase-researcher keeps its 3 extra checklist items inline. Pre-submission domains checklist made agent-agnostic so project-researcher doesn't lose features/architecture coverage. Write-contract intentionally left inline (bug-214 tests assert it verbatim). Inventory 64->66 refs. Behavior-preserving. Phase A.

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#657): wire gsd-phase-researcher to the Research seam (Phase B / S1)

The phase researcher now CALLS the code seam instead of carrying inline mechanics: provider waterfall -> 'gsd-tools query research-plan' (+ research-store put to cache digests); confidence-tier prose -> 'gsd-tools query classify-confidence'; slopcheck pip-install protocol -> 'gsd-tools query package-legitimacy check'. This makes the Research module a real runtime consumer (validates the seam end-to-end, addresses reviewer S1) and removes the duplicated waterfall/confidence/slopcheck prose. RESEARCH.md output contract, commit step, structured returns, and Phase-A @-includes unchanged. package-legitimacy-gate.test.cjs rewritten prose-grep -> behavioral (asserts the seam invocation).

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#657): wire gsd-project-researcher to the seam + add tavily/ref/jina MCP tools (Phase C.1)

project-researcher now calls gsd-tools query research-plan / classify-confidence (+ research-store put) instead of the inline provider waterfall + confidence-tier prose (mirrors the phase-researcher rewire; no package-legitimacy — phase-only). Output contract (STACK/FEATURES/ARCHITECTURE/PITFALLS/SUMMARY.md + sections, no-commit, structured returns, Phase-A @-includes) unchanged. Adds mcp__tavily/ref/jina__* to the project/phase/ui researcher tools frontmatter (Balanced provider set) so install.js MCP mapping (C.2) has a consumer.

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(#657): cover tavily/ref/jina MCP install handling + frontmatter parity guard (Phase C.2)

Investigation: exa/firecrawl have no explicit per-runtime tool-mapping — every mcp__<server>__* except context7 rides the generic passthrough (Copilot lowercases; OpenCode/Cursor/Windsurf/Augment keep as-is; Gemini auto-discovers). tavily/ref/jina are handled identically, no install path broken. Added 12 copilot-install passthrough tests + a mcp-tool-inheritance parity guard (tavily co-declared with exa, jina with firecrawl, ref present across the 3 web researchers) so the MCP set can't drift. No io.github registry ids invented (none sourceable in-repo); documented as a follow-up. 488 tests green.

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(#657): profiles as source of truth for researcher agents + drift-guard (Phase C.3)

scripts/research-profiles.cjs declares each of the 7 researcher agents' identity + contract (name, description, color, tools, required @-includes, required gsd-tools seam calls, output-contract markers). scripts/gen-research-agents.cjs --check validates every committed agent against its profile; --write regenerates ONLY the frontmatter from profiles (body untouched) and is a verified no-op against the current agents (zero diff = fidelity). tests/research-agent-profiles.test.cjs is the DEFECT.GENERATIVE-FIX drift guard. Design note: profiles govern the generatable/contract surface rather than destructively regenerating the disparate operational prose bodies (those were deduped via @-includes in Phase A). scripts/ is not inventoried (no inventory change).

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#657): complete agent provider-dispatch + parity guard; align legitimacy field; validate profiles

Adversarial-review findings: (HIGH) the seam-wired agents' Step-C dispatch only mapped 6 providers, so a planResearch result of jina/ref/perplexity/brave (reachable via the waterfall fallbacks) had no handling -> agent stall; completed both agents' dispatch to all 9 PROVIDER_WATERFALL ids + a catch-all, and added a parity test asserting agent dispatch stays in sync with research-provider PROVIDER_WATERFALL (DEFECT.GENERATIVE-FIX). (MEDIUM) phase-researcher package-legitimacy JSON example used 'package' but the module returns 'name' -> aligned. (LOW) gen-research-agents checkAgent now returns a clear failure for a malformed profile instead of throwing. +parity/validation tests (RED-first).

Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(#656): make classifyConfidence verification-evidence-driven (W3)

Confidence conflated provider authority with claim verification — context7/ref
stamped HIGH purely by provider identity, and the only verification lever was a
self-set --verified flag. Split into two axes: provider authority (static) +
verification evidence (code-computed). HIGH now requires ground-truth
corroboration (legitimacyVerdict OK), independent of provider; authority alone
caps at MEDIUM; SLOP caps at LOW; the self-reported --verified is demoted to a
MEDIUM-only web lever. HIGH = corroborated-against-authoritative-source, not a
correctness guarantee. Adds --legitimacy-verdict to the classify-confidence CLI;
updates CONTEXT.md predicate + ADR-0656 (tier set unchanged, ADR-consistent).

Addresses davesienkowski's W3 review on #664.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#656): bind classify-confidence verdict to code, closing CLI self-grading

Adversarial review found the new --legitimacy-verdict flag was caller-supplied,
so an agent could self-assert OK->HIGH without any real legitimacy check —
reintroducing the exact self-grading hole W3 closes. Remove the free flag; the
CLI now computes the verdict via checkPackages only when --package/--ecosystem
is given (code-computed, not agent-asserted). Update the stale CLI test
(context7 alone -> MEDIUM) and extend the property test to vary legitimacyVerdict.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:58:48 -04:00
Tom Boucher
0e259a589c fix(#705): route hardcoded $HOME gsd-tools invocations in agents/commands through gsd_run (#707)
* fix(#705): route hardcoded $HOME gsd-tools invocations in agents/commands through gsd_run

The hardcoded `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" <cmd>` form
(fixed for workflows in #621/#637) survived in agent/command surfaces and
misresolves on global/shim-only installs. Route every agent-executed
invocation through the resolved `gsd_run` launcher in gsd-phase-researcher,
gsd-planner (load_graph_context extracted to a shared reference to stay under
the planner size budget), import, and graphify. Add a regression guard over
agents/ + commands/ + gsd-core/references/ bash blocks. User-facing display
messages and docs are intentionally left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#705): use repo changeset fragment format (type: Fixed, pr: 707)

The hand-written fragment used the standard changesets package format
(package: bump) which lacks the type:/pr: frontmatter the repo's
docs-required lint consumes (fail_malformed_fragment / missing_type).
Regenerated via scripts/changeset/new.cjs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:26:29 -04:00
Tom Boucher
463cffd894 chore(#604): rename get-shit-done/ runtime directory to gsd-core/ (#615)
* chore(#604): rename get-shit-done/ runtime directory to gsd-core/

Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the
on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary
(`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers
are unaffected.

Mechanical (bulk, ~90% of the diff):
- `git mv get-shit-done gsd-core`
- Swept path/identifier references across the repo via
  `perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead
  preserves the five legitimate slug variants that are NOT the directory:
  get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names).
- Build/manifest wiring: package.json (bin, files, coverage globs),
  tsconfig.build.json (outDir), ~86 .gitignore build-output entries,
  stryker.config.mjs, scan-ignore files, install.js path strings.
- Frozen (not rewritten): CHANGELOG.md history; translated docs
  (README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/).

New logic (review here):
- src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper
  ADR-0008 installer migration. On upgrade it walks the legacy
  `~/.claude/get-shit-done/` tree, classifies each file via the prior install
  manifest, and emits remove-managed / backup-and-remove for managed files
  while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked
  root and symlinked entries; bounds-checks every path under configDir). The
  framework rolls back on install failure. Emptied dirs may remain (framework
  has no recursive dir-removal primitive) — documented.
- scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare
  `get-shit-done` directory token (split token to avoid self-match; case-
  insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists
  CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines).
  Wired into the lint-tests CI job.
- Restored scripts/lint-package-identity-drift.cjs detection regexes (the
  mechanical sweep had wrongly rewritten the old-name patterns it exists to
  detect) and marked them as intentional legacy references.
- TDD tests for the migration and the guard; do.md slash-command guard regex
  tightened so a `/gsd-core/bin` path segment is not mistaken for a command;
  changeset + docs/installer-migrations.md row added.

Breaking: the installed runtime path moves `~/.claude/get-shit-done/` ->
`~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed
files (preserving user files) on upgrade. Users with custom hooks/configs
hardcoding the old path must update them.

Closes #604

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unsweep pending changesets + allowlist injection-example docs

CI fixes for the rename PR:
- Do not sweep pending .changeset/*.md (ephemeral release-note fragments,
  like CHANGELOG); reverted those body edits so 5 pre-existing malformed
  fragments (missing type/pr) no longer enter the PR diff and trip docs-lint.
  Allowlisted .changeset/ in the legacy-name guard accordingly.
- Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in
  prompt-injection-scan.sh: they contain intentional injection examples /
  security-model prose; the path-reference rewrites are kept.

CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR;
none in the new migration/guard) and are out of scope for the rename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): resolve CodeQL alerts surfaced on this PR

The rename diff touched files carrying pre-existing CodeQL findings; per the
no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving
them off. All behavior-preserving:

- scripts/ci-test-scope.cjs: build the config-path match from string
  .includes() instead of a RegExp over an arg-derived value (js/regex-injection).
- src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName
  so the table-cell escape is complete (js/incomplete-sanitization).
- tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment
  strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization).
- tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace,
  keep the meaningful POSIX-class conversion (js/identity-replacement).

Verified: build:lib green; the touched test files + ci-test-scope + profile-output
suites pass; lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization)

The prior commit's fixes for two alerts were ineffective:
- ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file`
  reaching static regex `.test(file)` calls (not the config rule). Removed ALL
  regex over file/t — startsWith/includes/=== string checks + an isWindowsHint
  helper — so there is no regex sink for the tainted value.
- js/incomplete-multi-character-sanitization (3 test files): a single
  `.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint
  loop (replace until stable) plus a final bare-opener strip.

Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass;
lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL

CodeQL flags the regex PATTERNS syntactically (regex-injection on the
--files arg split; incomplete-multi-character-sanitization on the <!--...-->
replace), so loop fixes do not satisfy it. Made these paths regex-free:
- ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/).
- 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)).
Behavior preserved; ci-test-scope + the 3 suites pass; guard clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unblock security base64 scan on the large rename diff

The security job hit its 10m timeout: base64-scan.sh choked on the binary
test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/
non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings),
and the ~800-file rename diff is slow to scan regardless.

- scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they
  can't carry base64-obfuscated *text* and feeding NUL bytes through the
  per-line scanner is pathologically slow. collect_files already filtered
  binary *extensions*; this catches binary *content* in text extensions.
- .github/workflows/security-scan.yml: raise the security job timeout 10m->30m
  to accommodate very large diffs (the scan itself is unchanged).

Verified locally: scan skips the fixture, 0 "ignored null byte" warnings,
0 findings, exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): sweep get-shit-done refs introduced by merging next

The branch was updated with next (#614/#384/#618 etc.), which reference the
get-shit-done/ dir (still named that on next). Swept the stale references in
the merged files to gsd-core so the rename stays consistent and lint:legacy-name
passes:
- commands/gsd/discuss-phase.md (runtime-launcher shim paths)
- src/core.cts (getAgentsDir layout comments)
- tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib)

Verified: guard 0 violations; build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant

The #614 runtime-launcher shim added to discuss-phase.md references
`${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y
refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it
mis-read the directory path as a dangling `/gsd-core` command ref (same class as
the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path
segments are not treated as slash-command references.

Verified locally on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22 image) full suite: 0 failures
- bug-3683 + bug-2954 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI)

CI intermittently failed state.test's gsd-tools subprocess with
"findProjectRoot is not a function" (flip-flopping across legs; not reproducible
on mac full suite, gsd-test linux full suite, test:unit, or state.test x8).
findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs);
binding it via destructure at module-load can be undefined under a load-ordering
edge. Resolve it lazily at call time via a small wrapper so the lookup happens
after core.cjs is fully initialized.

Verified green on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22) full suite: 0 failures
- state.test.cjs: 106/106; gsd-tools loads cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): allowlist verification-patterns.md placeholder examples in secret scan

The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling
it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var
examples (illustrative Stripe test-key / database-URL / API-key placeholders) —
not real credentials. Added it to .secretscanignore with the strict annotation,
mirroring the existing gsd-core/workflows/plan-phase.md exception.

Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next
exits 0 with 0 findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:35:29 -04:00
Tom Boucher
82fb847754 fix(#214): make gsd-phase-researcher survive OpenCode write-tool truncation (#598)
* chore: wire docs/agents config into AGENTS.md Agent skills section

Add the `## Agent skills` discovery block pointing the engineering
skills at the existing docs/agents/{issue-tracker,triage-labels,domain}.md
files (issue tracker, triage label mapping, single-context domain docs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#214): make gsd-phase-researcher survive OpenCode write-tool truncation

OpenCode caps model output at OUTPUT_TOKEN_MAX=32000 and the thinking
budget shares that pool (upstream opencode#18108). A single oversized
`write` tool call for RESEARCH.md is truncated mid-payload, yielding
`JSON Parse error: Expected '}'`, which OpenCode misclassifies and then
doom-loops retrying identically. Short content writes fine; long
content fails 100% (reproducible, OpenCode 1.15.10).

Add a Step 6 write contract to agents/gsd-phase-researcher.md: keep the
single-Write default (no behavior change for Claude Code and other
runtimes that don't truncate), but on a truncation/invalid-tool failure
build the file incrementally via a sentinel-based Write -> Read -> Edit
sequence so no single tool-call payload is large enough to truncate;
never silently fall back to returning content (which truncates
identically). This is the upstream-recommended mitigation (write in
smaller chunks; use edit for follow-on writes).

Locked with a prompt-contract regression test mirroring the bug-222
write-contract pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#214): add changeset for OpenCode write-truncation fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:42:08 -04:00
Tom Boucher
692343f8cc fix(#581): add Edit to six writer agents' tools so Edit-only discipline is enforceable (#582)
* fix(#581): add Edit to six writer agents' tools so Edit-only discipline is enforceable

Six writer agents (gsd-eval-planner, gsd-ai-researcher, gsd-domain-researcher,
gsd-phase-researcher, gsd-ui-researcher, gsd-debug-session-manager) shipped with
Write but no Edit in their tools: frontmatter. Their spawn prompts instruct
surgical in-place section edits on existing/shared files (notably the AI-SPEC.md
trio writing disjoint sections of the same file), but with no Edit tool they
fall back to whole-file Write — silently clobbering sibling sections
(last-writer-wins) while still reporting success.

Same bug class as #571, fixed for gsd-doc-writer in #575. This adds Edit
alongside the existing Write for all six (Edit placed adjacent to Write, mirroring
the gsd-doc-writer fix). Write is retained; no prompt-body changes; no other agents
touched.

Adds a regression test (tests/agent-frontmatter.test.cjs) asserting each of the
six section-writer agents carries both Write and Edit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#581): add changeset fragment for writer-agent Edit fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#581): regenerate changeset via npm run changeset

Replace hand-authored fragment with one generated by the official
scripts/changeset/new.cjs script (correct <adjective>-<noun>-<noun>
filename convention).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:25:12 -04:00
Colin Johnson
7a3822fce1 fix: replace removed gsd-sdk prompt references (#355)
* fix: replace removed gsd-sdk prompt references

* chore: add changeset
2026-05-26 18:55:40 -04:00
Tom Boucher
18ffaa86cc fix(3605): scrub retired slash commands from agents/*.md
Six surviving references to /gsd-research-phase (deleted in #3042) and
/gsd-insert-phase (consolidated into /gsd:phase insert in v1.40.0)
remained in five agent contracts because every prior scrub pass (#3029,
#3044, #3131) limited its SEARCH_DIRS to workflows/, references/,
templates/, contexts/, commands/, and hooks/ — agents/ was outside scope.
agents/gsd-executor.md:195 is user-facing: the executor surfaces it
during a package-install failure recovery checkpoint, so a real user
hits "Unknown command" while trying to recover from a stalled phase.

Replacements:
- /gsd-research-phase -> /gsd:plan-phase --research-phase <N>
  (agents/gsd-executor.md:195, agents/gsd-phase-researcher.md:17,
   agents/gsd-planner.md:186, agents/gsd-planner.md:991,
   agents/gsd-research-synthesizer.md:115)
- /gsd-insert-phase -> /gsd:phase insert
  (agents/gsd-roadmapper.md:205)

Adds tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs as
the regression guard. It scans agents/*.md for any retired command name
(/gsd-research-phase, /gsd-insert-phase, /gsd-add-phase,
/gsd-remove-phase, /gsd-analyze-dependencies) with proper word-boundary
matching so a future consolidation that misses agents/ fails CI.

The guard mirrors tests/bug-2950-stale-command-refs.test.cjs which
covers the same anti-pattern for workflows/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:54:03 -04:00
Tom Boucher
a60e05c714 fix(claude): restore namespaced /gsd:<command> references (#3452)
* fix(claude): restore namespaced /gsd:<command> references

* test(claude): align slash-command expectations to /gsd: form

* test(claude): align generated command references to /gsd:

* test(claude): finish /gsd: namespace expectation updates
2026-05-12 21:53:24 -04:00
Tom Boucher
b37c487325 feat(security): package legitimacy gate against slopsquatting (#3215)
* feat(security): package legitimacy gate against slopsquatting (#2827)

GSD's research → plan → execute pipeline had no install-time legitimacy
gate: a hallucinated package name that passes `npm view` could flow all
the way to `gsd-executor` running `npm install <malicious-pkg>` with no
human checkpoint. This PR closes that gap.

Changes:
- gsd-phase-researcher: runs slopcheck on every recommended package;
  emits `## Package Legitimacy Audit` table; strips [SLOP] packages;
  ecosystem-specific verification (pip/npm/cargo); WebSearch-sourced
  packages tagged [ASSUMED]; ctx7 fallback uses `command -v` guard
  instead of `npx --yes`
- gsd-planner: injects `checkpoint:human-verify` before [ASSUMED]/[SUS]
  installs; adds T-{phase}-SC STRIDE row to <threat_model> template;
  ctx7 fallback also uses `command -v` guard
- gsd-executor: RULE 3 excludes package installs from auto-fix; failed
  installs surface as checkpoints, never silent substitutions
- tests/package-legitimacy-gate.test.cjs: 24 structural assertions
  covering the full gate (node:test + node:assert, no raw .includes())
- docs: USER-GUIDE, COMMANDS, ARCHITECTURE updated with gate description
- .changeset: Security fragment for v1.51 release notes

Closes #2827

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: expand Package Legitimacy Gate documentation

Add full user-facing depth to the gate docs across USER-GUIDE,
COMMANDS, and ARCHITECTURE:

- USER-GUIDE: rewrite gate section with concrete RESEARCH.md/PLAN.md
  examples, slopcheck verdict table, [ASSUMED] WebSearch tagging
  explanation, slopcheck-unavailable troubleshooting, and graceful
  degradation behavior
- COMMANDS.md: expand /gsd-plan-phase gate note with verdict bullets;
  add install-failure checkpoint behavior to /gsd-execute-phase
- ARCHITECTURE.md: expand gate section with threat model rationale,
  layer table, claim provenance integration, ecosystem coverage, and
  graceful degradation semantics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): harden package legitimacy checkpoint semantics

* fix(planner): satisfy size gates and tighten package gate wording

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 09:08:06 -04:00
Tom Boucher
54e6da3126 fix(#2767): pass paths via --files to gsd-sdk query commit + lint guard (#2781)
* fix(#2767): pass paths via --files to gsd-sdk query commit + lint guard

Workflows, agents, commands, and references passed file paths positionally
to `gsd-sdk query commit`, which silently appended them to the commit
subject and triggered the `.planning/` wholesale-stage fallback in
sdk/src/query/commit.ts:136. Regression of #733/#798.

Inserted `--files` before the path list at every site (81 invocations
across 50 files). Added tests/bug-2767-gsd-sdk-commit-files-flag.test.cjs
as a permanent lint that scans every shipped .md file and asserts each
`gsd-sdk query commit[-to-subrepo]` invocation either uses `--files` or
carries no path arguments.

Closes #2767

* test(#2767): replace source-grep with behavioral SDK test

The original test walked every shipped .md file and regex-tokenized
`gsd-sdk query commit` invocations to assert `--files` was present.
CONTRIBUTING.md prohibits this source-grep pattern.

Rewrite as behavioral SDK tests against `sdk/dist/cli.js` over a real
tmp git project (createTempGitProject helper). Cover both the
well-formed (`--files <paths>`) form — clean subject, exactly-staged
files, .planning/ left untouched — and the buggy positional form,
asserting the documented misbehavior (paths leak into subject + the
`.planning/` wholesale-stage fallback at commit.ts:136). Also asserts
`commit-to-subrepo` rejects when `--files` is omitted (commit.ts:258).

The doc-lint is retained as a supplementary defense-in-depth guard
since agent-prompt markdown invocations cannot be exercised end-to-end
— but it is no longer the primary contract.

* docs(#2767): correct contradictory --files guidance in zh-CN/en docs + fix test docstring
2026-04-27 12:31:43 -04:00
Tom Boucher
6b7b5c15a5 fix(#2559): remove stale year injection from research agent web search instructions (#2591)
The gsd-phase-researcher and gsd-project-researcher agents instructed
WebSearch queries to always include 'current year' (e.g., 2024). As
time passes, a hardcoded year biases search results toward stale
dated content — users saw 2024-tagged queries producing stale blog
references in 2026.

Remove the year-injection guidance. Instead, rely on checking
publication dates on the returned sources. Query templates and
success criteria updated accordingly.

Closes #2559
2026-04-22 12:04:13 -04:00
Tom Boucher
e208e9757c refactor(agents): consolidate emphasis-marker density in top 4 agents (#2368) (#2412) 2026-04-18 12:10:22 -04:00
Tom Boucher
c5e77c8809 feat(agents): enforce size budget + extract duplicated boilerplate (#2361) (#2362)
Adds tiered agent-size-budget test to prevent unbounded growth in agent
definitions, which are loaded verbatim into context on every subagent
dispatch. Extracts two duplicated blocks (mandatory-initial-read,
project-skills-discovery) to shared references under
get-shit-done/references/ and migrates the 5 top agents (planner,
executor, debugger, verifier, phase-researcher) to @file includes.

Also fixes two broken relative @planner-source-audit.md references in
gsd-planner.md that silently disabled the planner's source audit
discipline.

Closes #2361

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:47:08 -04:00
Rezolv
d3a79917fa feat: Phase 2 caller migration — gsd-sdk query in workflows, agents, commands (#2179)
* feat: Phase 2 caller migration — gsd-sdk query in workflows (#2122)

Cherry-picked orchestration rewrites from feat/sdk-foundation (#2008, 4018fee) onto current main, resolving conflicts to keep upstream worktree guards and post-merge test gate. SDK stub registry omitted (out of Phase 2 scope per #2122).

Refs: #2122 #2008
Made-with: Cursor

* docs: add gsd-sdk query migration blurb

Made-with: Cursor

* docs(workflows): extend Phase 2 gsd-sdk query caller migration

- Swap node gsd-tools.cjs for gsd-sdk query in review, plan-phase, execute-plan,
  ship, extract_learnings, ai-integration-phase, eval-review, next, thread
- Document graphify CJS-only in gsd-planner; dual-path in CLI-TOOLS and ARCHITECTURE
- Update tests: workstreams gsd-sdk path, thread frontmatter.get, workspace init.*,
  CRLF-safe autonomous frontmatter parse
- CHANGELOG: Phase 2 caller migration scope

Made-with: Cursor

* docs(phase2): USER-GUIDE + remaining gsd-sdk query call sites

- USER-GUIDE: dual-path CLI section; state validate/sync use full CJS path
- Commands: debug (config-get+tdd), quick (security note), intel Task prompt
- Agent: gsd-debug-session-manager resolve-model via jq
- Workflows: milestone-summary, forensics, next, complete-milestone/verify-work
  (audit-open CJS notes), discuss-phase, progress, verify-phase, add/insert/remove
  phase, transition, manager, quick workflow; remove-phase commit without --files
- Test: quick-session-management accepts frontmatter.get
- CHANGELOG: Phase 2 follow-up bullet

Made-with: Cursor

* docs(phase2): align gsd-sdk query examples in commands and agents

- init.* query names; frontmatter.get uses positional field name
- state.* handlers use positional args; commit uses positional paths
- CJS-only notes for from-gsd2 and graphify; learnings.query wording
- CHANGELOG: Phase 2 orchestration doc pass

Made-with: Cursor

* docs(phase2): normalize gsd-sdk query commit to positional file paths

- Strip --files from commit examples in workflows, references, commands
- Keep commit-to-subrepo ... --files (separate handler)
- git-planning-commit.md: document positional args
- Tests: new-project commit line, state.record-session, gates CRLF, roadmap.analyze
- CHANGELOG [Unreleased]

Made-with: Cursor

* feat(sdk): gsd-sdk query parity with gsd-tools and PR 2179 registry fixes

- Route query via longest-prefix match and dotted single-token expansion; fall back
  to runGsdToolsQuery (same argv as node gsd-tools.cjs) for full CLI coverage.
- Parse gsd-sdk query permissively so gsd-tools flags (--json, --verify, etc.) are
  not rejected by strict parseArgs.
- resolveGsdToolsPath: honor GSD_TOOLS_PATH; prefer bundled get-shit-done copy
  over project .claude installs; export runGsdToolsQuery from the SDK.
- Fix gsd-tools audit-open (core.output; pass object for --json JSON).
- Register summary-extract as alias of summary.extract; fix audit-fix workflow to
  call audit-uat instead of invalid init.audit-uat (PR review).

Updates QUERY-HANDLERS.md and CHANGELOG [Unreleased].

Made-with: Cursor

* fix(sdk): Phase 2 scope — Trek-e review (#2179, #2122)

- Remove gsd-sdk query passthrough to gsd-tools.cjs; drop GSD_TOOLS_PATH
- Consolidate argv routing in resolveQueryArgv(); update USAGE and QUERY-HANDLERS
- Surface @file: read failures in GSDTools.parseOutput
- execute-plan: defer Task Commit Protocol to gsd-executor
- stale-colon-refs: skip .planning/ and root CLAUDE.md (gitignored overlays)
- CHANGELOG [Unreleased]: maintainer review and routing notes

Made-with: Cursor
2026-04-15 22:46:31 -04:00
pingchesu
c11ec05554 feat: /gsd-graphify integration — knowledge graph for planning agents (#2164)
* feat(01-01): create graphify.cjs library module with config gate, subprocess helper, presence detection, and version check

- isGraphifyEnabled() gates on config.graphify.enabled in .planning/config.json
- disabledResponse() returns structured disabled message with enable instructions
- execGraphify() wraps spawnSync with PYTHONUNBUFFERED=1, 30s timeout, ENOENT/SIGTERM handling
- checkGraphifyInstalled() detects missing binary via --help probe
- checkGraphifyVersion() uses python3 importlib.metadata, validates >=0.4.0,<1.0 range

* feat(01-01): register graphify.enabled in VALID_CONFIG_KEYS

- Added graphify.enabled after intel.enabled in config.cjs VALID_CONFIG_KEYS Set
- Enables gsd-tools config-set graphify.enabled true without key rejection

* test(01-02): add comprehensive unit tests for graphify.cjs module

- 23 tests covering all 5 exported functions across 5 describe blocks
- Config gate tests: enabled/disabled/missing/malformed scenarios (TEST-03, FOUND-01)
- Subprocess tests: success, ENOENT, timeout, env vars, timeout override (FOUND-04)
- Presence tests: --help detection, install instructions (FOUND-02, TEST-04)
- Version tests: compatible/incompatible/unparseable/missing (FOUND-03, TEST-04)
- Fix graphify.cjs to use childProcess.spawnSync (not destructured) for testability

* feat(02-01): add graphifyQuery, graphifyStatus, graphifyDiff to graphify.cjs

- safeReadJson wraps JSON.parse in try/catch, returns null on failure
- buildAdjacencyMap creates bidirectional adjacency map from graph nodes/edges
- seedAndExpand matches on label+description (case-insensitive), BFS-expands up to maxHops
- applyBudget uses chars/4 token estimation, drops AMBIGUOUS then INFERRED edges
- graphifyQuery gates on config, reads graph.json, supports --budget option
- graphifyStatus returns exists/last_build/counts/staleness or no-graph message
- graphifyDiff compares current graph.json against .last-build-snapshot.json

* feat(02-01): add case 'graphify' routing block to gsd-tools.cjs

- Routes query/status/diff/build subcommands to graphify.cjs handlers
- Query supports --budget flag via args.indexOf parsing
- Build returns Phase 3 placeholder error message
- Unknown subcommand lists all 4 available options

* feat(02-01): create commands/gsd/graphify.md command definition

- YAML frontmatter with name, description, argument-hint, allowed-tools
- Config gate reads .planning/config.json directly (not gsd-tools config get-value)
- Inline CLI calls for query/status/diff subcommands
- Agent spawn placeholder for build subcommand
- Anti-read warning and anti-patterns section

* test(02-02): add Phase 2 test scaffolding with fixture helpers and describe blocks

- Import 7 Phase 2 exports (graphifyQuery, graphifyStatus, graphifyDiff, safeReadJson, buildAdjacencyMap, seedAndExpand, applyBudget)
- Add writeGraphJson and writeSnapshotJson fixture helpers
- Add SAMPLE_GRAPH constant with 5 nodes, 5 edges across all confidence tiers
- Scaffold 7 new describe blocks for Phase 2 functions

* test(02-02): add comprehensive unit tests for all Phase 2 graphify.cjs functions

- safeReadJson: valid JSON, malformed JSON, missing file (3 tests)
- buildAdjacencyMap: bidirectional entries, orphan nodes, edge objects (3 tests)
- seedAndExpand: label match, description match, BFS depth, empty results, maxHops (5 tests)
- applyBudget: no budget passthrough, AMBIGUOUS drop, INFERRED drop, trimmed footer (4 tests)
- graphifyQuery: disabled gate, no graph, valid query, confidence tiers, budget, counts (6 tests)
- graphifyStatus: disabled gate, no graph, counts with graph, hyperedge count (4 tests)
- graphifyDiff: disabled gate, no baseline, no graph, added/removed, changed (5 tests)
- Requirements: TEST-01, QUERY-01..03, STAT-01..02, DIFF-01..02
- Full suite: 53 graphify tests pass, 3666 total tests pass (0 regressions)

* feat(03-01): add graphifyBuild() pre-flight, writeSnapshot(), and build_timeout config key

- Add graphifyBuild(cwd) returning spawn_agent JSON with graphs_dir, timeout, version
- Add writeSnapshot(cwd) reading graph.json and writing atomic .last-build-snapshot.json
- Register graphify.build_timeout in VALID_CONFIG_KEYS
- Import atomicWriteFileSync from core.cjs for crash-safe snapshot writes

* feat(03-01): wire build routing in gsd-tools and flesh out builder agent prompt

- Replace Phase 3 placeholder with graphifyBuild() and writeSnapshot() dispatch
- Route 'graphify build snapshot' to writeSnapshot(), 'graphify build' to graphifyBuild()
- Expand Step 3 builder agent prompt with 5-step workflow: invoke, validate, copy, snapshot, summary
- Include error handling guidance: non-zero exit preserves prior .planning/graphs/

* test(03-02): add graphifyBuild test suite with 6 tests

- Disabled config returns disabled response
- Missing CLI returns error with install instructions
- Successful pre-flight returns spawn_agent action with correct shape
- Creates .planning/graphs/ directory if missing
- Reads graphify.build_timeout from config (custom 600s)
- Version warning included when outside tested range

* test(03-02): add writeSnapshot test suite with 6 tests

- Writes snapshot from existing graph.json with correct structure
- Returns error when graph.json does not exist
- Returns error when graph.json is invalid JSON
- Handles empty nodes and edges arrays
- Handles missing nodes/edges keys gracefully
- Overwrites existing snapshot on incremental rebuild

* feat(04-01): add load_graph_context step to gsd-planner agent

- Detects .planning/graphs/graph.json via ls check
- Checks graph staleness via graphify status CLI call
- Queries phase-relevant context with single --budget 2000 query
- Silent no-op when graph.json absent (AGENT-01)

* feat(04-01): add Step 1.3 Load Graph Context to gsd-phase-researcher agent

- Detects .planning/graphs/graph.json via ls check
- Checks graph staleness via graphify status CLI call
- Queries 2-3 capability keywords with --budget 1500 each
- Silent no-op when graph.json absent (AGENT-02)

* test(04-01): add AGENT-03 graceful degradation tests

- 3 AGENT-03 tests: absent-graph query, status, multi-term handling
- 2 D-12 integration tests: known-graph query and status structure
- All 5 tests pass with existing helpers and imports
2026-04-12 18:17:18 -04:00
Tibsfox
67f5c6fd1d docs(agents): standardize required_reading patterns across agent specs (#2176)
Closes #2168

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:56:19 -04:00
Bhaskoro Muthohar
5a802e4fd2 feat: add flow diagram directive to phase researcher agent (#2139) (#2147)
Architecture diagrams generated by gsd-phase-researcher now enforce
data-flow style (conceptual components with arrows) instead of
file-listing style. The directive is language-agnostic and applies
to all project types.

Changes:
- agents/gsd-phase-researcher.md: add System Architecture Diagram
  subsection in Architecture Patterns output template
- get-shit-done/templates/research.md: add matching directive in
  both architecture_patterns template sections
- tests/phase-researcher-flow-diagram.test.cjs: 8 tests validating
  directive presence, content, and ordering in agent and template

Closes #2139
2026-04-12 15:56:20 -04:00
Tom Boucher
72e789432e feat(agents): add Architectural Responsibility Mapping to phase-researcher pipeline (#1988) (#2103)
Before framework-specific research, phase-researcher now maps each
capability to its architectural tier owner (browser, frontend server,
API, database, CDN). The planner sanity-checks task assignments against
this map, and plan-checker enforces tier compliance as Dimension 7c.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 09:16:11 -04:00
Tom Boucher
f8cf54bd01 fix(agents): add Context7 CLI fallback for MCP tools broken by tools: restriction (#2074)
Closes #1885

The upstream bug anthropics/claude-code#13898 causes Claude Code to strip all
inherited MCP tools from agents that declare a `tools:` frontmatter restriction,
making `mcp__context7__*` declarations in agent frontmatter completely inert.

Implements Fix 2 from issue #1885 (trek-e's chosen approach): replace the
`<mcp_tool_usage>` block in gsd-executor and gsd-planner with a
`<documentation_lookup>` block that checks for MCP availability first, then
falls back to the Context7 CLI via Bash (`npx --yes ctx7@latest`). Adds the
same `<documentation_lookup>` block to the six researcher agents that declare
MCP tools but lacked any fallback instruction.

Agents fixed (8 total):
- gsd-executor (had <mcp_tool_usage>, now <documentation_lookup> with CLI fallback)
- gsd-planner (had <mcp_tool_usage>, now compact <documentation_lookup>; stays under 45K limit)
- gsd-phase-researcher (new <documentation_lookup> block)
- gsd-project-researcher (new <documentation_lookup> block)
- gsd-ui-researcher (new <documentation_lookup> block)
- gsd-advisor-researcher (new <documentation_lookup> block)
- gsd-ai-researcher (new <documentation_lookup> block)
- gsd-domain-researcher (new <documentation_lookup> block)

When the upstream Claude Code bug is fixed, the MCP path in step 1 of the block
will become active automatically — no agent changes needed.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:29:37 -04:00
Tom Boucher
aa87993362 feat(agents): add thinking model guidance reference files (#1722) (#1820)
Combines implementation by @davesienkowski (inline @-reference wiring at
decision-point steps, named reasoning models with anti-patterns, sequencing
rules, Gap Closure Mode) and @Tibsfox (test suite covering file existence,
section structure, and agent wiring).

- 5 reference files in get-shit-done/references/ — each with named reasoning
  models, Counters annotations, Conflict Resolution sequencing, and When NOT
  to Think guidance
- Inline @-reference wiring placed inside the specific step/section blocks
  where thinking decisions occur (not at top-of-agent)
- Planning cluster includes Gap Closure Mode root-cause check section
- Test suite: 63 tests covering file existence, named models, Conflict
  Resolution sections, Gap Closure Mode, and inline wiring placement

Closes #1722

Co-authored-by: Tibsfox <tibsfox@users.noreply.github.com>
Co-authored-by: Rezolv <davesienkowski@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 17:01:25 -04:00
Quang Do
d4767ac2e0 fix: replace /gsd: slash command format with /gsd- skill format in all user-facing content (#1579)
* fix: replace /gsd: command format with /gsd- skill format in all suggestions

All next-step suggestions shown to users were still using the old colon
format (/gsd:xxx) which cannot be copy-pasted as skills. Migrated all
occurrences across agents/, commands/, get-shit-done/, docs/, README files,
bin/install.js (hardcoded defaults for claude runtime), and
get-shit-done/bin/lib/*.cjs (generate-claude-md templates and error messages).
Updated tests to assert new hyphen format instead of old colon format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: migrate remaining /gsd: format to /gsd- in hooks, workflows, and sdk

Addresses remaining user-facing occurrences missed in the initial migration:

- hooks/: fix 4 user-facing messages (pause-work, update, fast, quick)
  and 2 comments in gsd-workflow-guard.js
- get-shit-done/workflows/: fix 21 Skill() literal calls that Claude
  executes directly (installer does not transform workflow content)
- sdk/prompt-sanitizer.ts: update regex to strip /gsd- format in addition
  to legacy /gsd: format; update JSDoc comment
- tests/: update autonomous-ui-steps, prompt-sanitizer to assert new format

Note: commands/gsd/*.md frontmatter (name: gsd:xxx) intentionally unchanged
— installer derives skillName from directory path, not the name field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(plan-phase): preserve --chain flag in auto-advance sync and handle ui-phase gate in chain mode

Bug 1: step 15 sync-flag check only guarded against --auto, causing
_auto_chain_active to be cleared when plan-phase is invoked without
--auto in ARGUMENTS even though a --chain pipeline was active. Added
--chain to the guard condition, matching discuss-phase behaviour.

Bug 2: UI Design Contract gate (step 5.6) always exited the workflow
when UI-SPEC was missing, breaking the discuss --chain pipeline
silently. When _auto_chain_active is true, the gate now auto-invokes
gsd-ui-phase --auto via Skill() and continues to step 6 without
prompting. Manual invocations retain the existing AskUserQuestion flow.

* fix: remove <sub>/clear</sub> pattern and duplicate old-format command in discuss-phase.md

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 07:24:31 -04:00
Tom Boucher
d2f537c3b2 Merge pull request #1437 from Tibsfox/fix/researcher-assumption-tagging-1431
feat(researcher): add claim provenance tagging and assumptions log
2026-04-01 17:12:21 -04:00
Tibsfox
e24add196c feat(researcher): add claim provenance tagging and assumptions log
Research agents must now tag every factual claim with its source:
[VERIFIED], [CITED], or [ASSUMED]. An Assumptions Log section in
RESEARCH.md collects all [ASSUMED] claims so downstream agents and
users can identify decisions that need confirmation before execution.

Prevents unvalidated assumptions (e.g. "audit logs should be permanent")
from propagating unchallenged through research → planning → execution.

Closes #1431

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:42:53 -07:00
Bantuson
2154e6bb07 feat: add security-first enforcement layer with threat-model-anchored verification
Adds /gsd:secure-phase command and gsd-security-auditor agent as a
threat-model-anchored security gate parallel to Nyquist validation.

New files:
- agents/gsd-security-auditor.md — verifies PLAN.md threat mitigations
  exist in implemented code; SECURED/OPEN_THREATS/ESCALATE returns
- commands/gsd/secure-phase.md — retroactive command, mirrors validate-phase
- get-shit-done/workflows/secure-phase.md — enforcing gate: threats_open > 0
  blocks phase advancement; accepted risks log prevents resurface
- get-shit-done/templates/SECURITY.md — per-phase threat register artifact

Modified:
- config.json — security_enforcement (absent=enabled), security_asvs_level,
  security_block_on parallel to nyquist_validation pattern
- VALIDATION.md — Threat Ref + Secure Behavior columns in verification map
- gsd-planner.md — <threat_model> block in PLAN.md format + quality gate
- gsd-executor.md — Rule 2 threat model reference + ## Threat Flags scan
- gsd-phase-researcher.md — ## Security Domain mandatory research section
- plan-phase.md — step 5.55 Security Threat Model Gate
- execute-phase.md — security gate announcement in aggregate step
- verify-work.md — /gsd:secure-phase surfaced in completion routing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 11:18:30 +02:00
Tom Boucher
e98b41aa15 feat: add data-flow tracing, environment audit, and behavioral spot-checks
Verification checked structure but not whether data actually flows
end-to-end or whether external dependencies are available. Adds:

- Step 4b (Data-Flow Trace): Level 4 verification traces upstream from
  wired artifacts to verify data sources produce real data, catching
  hollow components that render empty/hardcoded values
- Step 7b (Behavioral Spot-Checks): lightweight smoke tests that verify
  runnable code produces expected output, not just that it exists
- Step 2.6 (Environment Audit): researcher probes target machine for
  external tools/services/runtimes before planning, so plans include
  fallback strategies for missing dependencies

Closes #1245

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 22:26:22 -04:00
Tom Boucher
d032322bcb feat: add CLAUDE.md compliance as plan-checker Dimension 10
Add CLAUDE.md enforcement across the three core agents:
- gsd-plan-checker: new Dimension 10 verifies plans respect project
  conventions, forbidden patterns, and required tools from CLAUDE.md
- gsd-phase-researcher: outputs Project Constraints section from
  CLAUDE.md so planner can verify compliance
- gsd-executor: treats CLAUDE.md directives as hard constraints,
  with precedence over plan instructions

Includes 4 regression tests validating the new dimension and
enforcement directives across all three agents.

Closes #1260

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 15:41:28 -04:00
benzntech
b621d556f0 feat: add Exa and Firecrawl MCP support for research agents
Integrate Exa (semantic search) and Firecrawl (deep web scraping) as
MCP-based research tools, following the existing Brave Search pattern.

- Add tool declarations to all 3 researcher agents
- Add tool strategy sections with usage guidance and priority
- Add config detection for FIRECRAWL_API_KEY and EXA_API_KEY env vars
- Add firecrawl/exa_search config keys to core defaults and init output
- Update source priority hierarchy across all researchers
2026-03-20 14:53:46 +05:30
Eli Herman
3e61c7da94 feat(researcher): add Runtime State Inventory for rename/refactor phases
Adds a Runtime State Inventory step that fires when a phase involves
renaming, rebranding, refactoring, or migrating strings across a codebase.

The core problem: grep audits find files. They do NOT find runtime state —
ChromaDB collection names, Mem0 user_ids, n8n workflows in SQLite, Windows
Task Scheduler descriptions, pm2 process names, SOPS key names, pip
egg-info directories, etc. These survive a complete file-level rename and
will break the system silently after the code is "done".

Three additions:
1. Pre-Submission Checklist — adds a reminder item so the checklist gate
   catches skipped inventories before RESEARCH.md is committed
2. output_format — adds Runtime State Inventory table to the RESEARCH.md
   template so the section appears in every rename/refactor phase's output
3. execution_flow Step 2.5 — structured investigation protocol with the
   five categories (stored data, live service config, OS-registered state,
   secrets/env vars, build artifacts), explicit examples for each, and the
   canonical question that frames the whole exercise
2026-03-18 14:49:27 -05:00
Tom Boucher
540913c09b fix(researcher): verify package versions against registry before recommending (#1077)
The researcher agent now verifies each recommended package version
using 'npm view [package] version' before writing the Standard Stack
section. This prevents recommending stale versions from training data.

Addresses patch 5 from #899
2026-03-16 08:53:55 -06:00
TÂCHES
1d3d1f3f5e fix: strip skills: from agent frontmatter for Gemini compatibility (#1045)
* fix: remove dangling skills: from agent frontmatter and strip in Gemini converter (closes #1023, closes #953, closes #930)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: invert skills frontmatter test to assert absence (fixes CI)

The PR deliberately removed skills: from agent frontmatter (breaks
Gemini CLI), but the test still asserted its presence. Inverted the
assertion to ensure skills: stays removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 21:25:02 -06:00
Lex Christopherson
517ee0dc8f fix: resolve @file: protocol in all INIT consumers for Windows compatibility (#841)
When gsd-tools init output exceeds 50KB, core.cjs writes to a temp file
and outputs @file:<path>. No workflow handled this prefix, causing agents
to hallucinate /tmp paths that fail on Windows (C:\tmp doesn't exist).

Add @file: resolution line after every INIT=$(node ...) call across all
32 workflow, agent, and reference files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:19:12 -06:00
Fana
ef032bc8f1 feat: harden Nyquist defaults, add retroactive validation, compress prompts (#855)
* fix: change nyquist_validation default to true and harden absent-key skip conditions

new-project.md never wrote the key, so agents reading config directly
treated absent as falsy. Changed all agent skip conditions from "is false"
to "explicitly set to false; absent = enabled". Default changed from false
to true in core.cjs, config.cjs, and templates/config.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enforce VALIDATION.md creation with verification gate and Check 8e

Step 5.5 was narrative markdown that Claude skipped under context pressure.
Now MANDATORY with Write tool requirement and file-existence verification.
Step 7.5 gates planner spawn on VALIDATION.md presence. Check 8e blocks
Dimension 8 if file missing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add W008/W009 health checks and addNyquistKey repair for Nyquist drift detection

W008 warns when workflow.nyquist_validation key is absent from config.json
(agents may skip validation). W009 warns when RESEARCH.md has Validation
Architecture section but no VALIDATION.md file exists. addNyquistKey repair
adds the missing key with default true value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add /gsd:validate-phase command and gsd-nyquist-auditor agent

Retroactively applies Nyquist validation to already-executed phases.
Works mid-milestone and post-milestone. Detects existing test coverage,
maps gaps to phase requirements, writes missing tests, debugs failing
ones, and produces {phase}-VALIDATION.md from existing artifacts.

Handles three states: VALIDATION.md exists (audit + update), no
VALIDATION.md (reconstruct from PLAN.md + SUMMARY.md), phase not yet
executed (exit cleanly with guidance).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: audit-milestone reports Nyquist compliance gaps across phases

Adds Nyquist coverage table to audit-milestone output when
workflow.nyquist_validation is true. Identifies phases missing
VALIDATION.md or with nyquist_compliant: false/partial.
Routes to /gsd:validate-phase for resolution. Updates USER-GUIDE
with retroactive validation documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: compress Nyquist prompts to match GSD meta-prompt density conventions

Auditor agent: deleted philosophy section (35 lines), compressed execution
flow 60%, removed redundant constraints. Workflow: cut purpose bloat,
collapsed state narrative, compressed auditor spawn template. Command:
removed redundant process section. Plan-phase Steps 5.5/7.5: replaced
hedging language with directives. Audit-milestone Step 5.5: collapsed
sub-steps into inline instructions. Net: -376 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:19:44 -06:00
Tibsfox
cbe372a434 feat(agents): add skills frontmatter and hooks examples to all agents
Add skills: field to all 11 agent frontmatter files with forward-compatible
GSD workflow skill references (silently ignored until skill files are created).

Add commented hooks: examples to 9 file-writing agents showing PostToolUse
hook syntax for project-specific linting/formatting. Read-only agents
(plan-checker, integration-checker) skip hooks as they cannot modify files.

Per Claude Code docs: subagents don't inherit skills or hooks from the
parent conversation — they must be explicitly listed in frontmatter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:01:54 -06:00
Tibsfox
1a1acd5283 fix(agents): extend anti-heredoc instruction to all file-writing agents
Add 'never use heredoc' instruction to 6 agents that were missing it:
gsd-codebase-mapper, gsd-debugger, gsd-phase-researcher,
gsd-project-researcher, gsd-research-synthesizer, gsd-roadmapper.

All 9 file-writing agents now consistently prevent settings.local.json
corruption from heredoc permission entries (GSD #526).

Read-only agents (plan-checker, integration-checker) excluded as they
cannot write files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:01:54 -06:00
Lex Christopherson
69b28eeca4 fix: use $HOME instead of ~ for gsd-tools.cjs paths to prevent subagent MODULE_NOT_FOUND (#786)
Claude Code subagents sometimes rewrite ~/. paths to relative paths,
causing MODULE_NOT_FOUND when CWD is the project directory. $HOME is a
shell variable resolved at runtime, immune to model path rewriting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:11:19 -06:00