Commit Graph

762 Commits

Author SHA1 Message Date
Tom Boucher
fa1ffb4824 fix(#1437): add phase.list-plans to gsd-tools (#1485)
* fix(#1437): add phase.list-plans to gsd-tools

Register phase.list-plans in PHASE_COMMAND_ALIASES, implement
cmdPhaseListPlans in src/phase.cts (uses findPhaseInternal + scanPhasePlans
to return plan_count/has_plans/plans/phase_dir), and wire the handler in
phase-command-router. Previously every call produced "Unknown phase
subcommand".

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

* fix(#1437): register new test file in lint-test-file-count allowlist

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

* fix(#1437): rename test to fix-NNN convention; update file-count allowlist

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:37:22 -04:00
Tom Boucher
ba3181204b fix(#1422,#1447): fix sub_repos/.git precedence; guard uncommitted data in new-milestone (#1484)
* fix(#1422): sub_repos config takes precedence over .git in findProjectRoot

When heuristic-3 (.git + parent .planning/) fires, do a lookahead walk
over ancestors above the matching parent to check if any further ancestor
has a sub_repos entry that explicitly claims the starting directory. If
found, return that ancestor instead — explicit config wins over the
implicit .git signal.

Regression tests updated: the heuristic-3 precedence test now asserts the
correct new behavior (sub_repos wins), and two additional coverage cases
added (startDir directly in sub_repo child, and startDir nested 2+ levels
inside the child).

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

* fix(#1447): guard against uncommitted changes before deleting phase dirs in new-milestone

cmdPhasesClear now runs `git status --porcelain <phasesDir>` before
executing any rmSync. If uncommitted changes are detected it calls
error() and aborts, preventing silent data loss when new-milestone's
§6 "phases.clear --confirm" fires before the operator has archived
or committed outgoing phase work.

A new --force flag is added to bypass the guard for callers that have
already verified archival is done (or explicitly accept the loss).
When git is unavailable or the directory is not inside a git repo the
guard silently skips, preserving the existing behaviour for non-git
projects.

Five new regression tests cover: untracked files abort, staged-but-
uncommitted abort, --force bypasses, committed files pass, non-git
project passes without guard.

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

* chore: add changeset for #1422 and #1447

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

* fix: correct changeset format

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:37:16 -04:00
Tom Boucher
bb88a78faa fix(#1472,#1454): workstream-aware health paths; exclude active worktree from W017 (#1483)
* fix(#1472,#1454): validate health workstream-aware paths; exclude active worktree from W017

#1472: cmdValidateHealth now uses planningRoot(cwd) for shared-root files
(PROJECT.md, config.json, MILESTONES.md) and planningDir(cwd) for
workstream-scoped files (ROADMAP.md, STATE.md, phases/). Previously a
single planningDir() call was used for all paths, causing false
E002/E003/E004/W003 when GSD_WORKSTREAM is set.

#1454: W017 no longer fires for a stale worktree whose path equals or is
an ancestor of process.cwd(), preventing advice to remove the active
session's own worktree.

Regression tests added for both bugs; all 40 existing health tests pass.

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

* fix: correct changeset format

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:37:09 -04:00
Tom Boucher
fe0f9f904d fix(#1478,#1479,#1480): prohibit ungrounded baselines, error-suppressing fallbacks, and stale-artifact authority in planner verify blocks (#1482)
* fix(#1478,#1479,#1480): prohibit ungrounded baselines, error-suppressing fallbacks, and stale-artifact authority in verify blocks

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

* chore: add changeset for #1478/#1479/#1480 planner verify gate fix

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

* fix: correct changeset format

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

* fix(#1478,#1479,#1480): fix test contract violations from new planner dimensions

gsd-planner.md exceeded both the planner-decomposition 48K char limit and
the reachability-check 50K char limit after the new HARD RULE blocks were
added inline. The full rule details already exist in planner-antipatterns.md
(added in the same PR); replace the verbose inline blocks with a single
@-reference pointer to the antipatterns file, reducing the file from 50981
to 49130 chars (under both limits).

Also regenerate tests/agent-size-baseline.json to reflect the new sizes of
gsd-planner.md and gsd-plan-checker.md.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 13:37:03 -04:00
Tom Boucher
7c93d9e222 feat(#1463): add capability outdated (per-source update check); drop phantom slash-command docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 10:50:12 -04:00
Tom Boucher
08d1c57d6e fix(#1460): verify-or-reject capability --integrity per source; confine hook commands to the bundle
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 09:10:04 -04:00
Tom Boucher
f7c6ce7a1f fix(#1461): loader never crashes on a malformed overlay (skip-with-warning); bound the source fetch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 03:24:47 -04:00
Tom Boucher
e7855bc217 fix(#1459): user-owned consent store gates third-party capability activation; env/cwd in disclosure; loader validator parity (#1473) 2026-06-20 01:59:03 -04:00
Tom Boucher
c866ac1b24 fix(#1462): fail closed without data loss on a corrupt capability ledger; atomic ledger write (#1469) 2026-06-19 19:55:10 -04:00
Tom Boucher
0d56f544d2 feat(#1435): capability matrix (generated + drift-guarded) + trust-model doc consolidation (#1458)
* feat(#1435): capability matrix (generated + drift-guarded) + trust-model doc consolidation

ADR-1244 Phase 6. Adds the capability matrix reference, generated FROM the committed registry so it
can never drift from the actual capability set:
- scripts/gen-capability-matrix.cjs (--write / --check); --check is a CI drift guard.
- tests/capability-matrix-sync.test.cjs (4 tests): drift guard, buildMatrix==committed, every cap
  present, no placeholders.
- docs/reference/capability-matrix.md regenerated from the registry (release-stable: shows engines.gsd,
  omits the lockstep per-cap version that would churn the file every release).
- Consolidated the duplicate trust-model doc: deleted docs/explanation/the-capability-trust-model.md,
  merged its content into capability-trust-model.md, redirected ~10 references; no stale links remain.
- Diataxis verification (now that gsd capability is a real command): corrected the matrix's third-party
  section — the matrix is the first-party catalogue; the overlay-aware view of installed third-party
  capabilities is 'gsd capability list', not this generated file.

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

* docs(#1435): Added changeset for the capability matrix reference

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

* fix(#1435): address code-review — non-vacuous matrix test + generator polish

- capability-matrix-sync.test.cjs: assert the 'security registers a ship:pre gate' precondition
  unconditionally so the extension-point check can never degrade to a vacuous pass on registry drift.
- gen-capability-matrix.cjs: warn (stderr) on an unknown loop point at generation time; rename
  enginesOf -> fmtEngines for consistency with the other fmt* helpers (output unchanged).
- capability-trust-model.md: point the two how-to links at the real files
  (import-a-capability-from-a-url.md, version-a-capability.md) instead of the bare directory.

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

* chore(#1435): backfill changeset PR number → #1458

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:45:26 -04:00
Tom Boucher
34bc096ec2 feat(#1451): wire gsd capability install/update/remove/list/disable/enable management CLI (#1457)
* feat(#1451): wire gsd capability install/update/remove/list/disable/enable CLI

ADR-1244 D5/D6: the management command was built as a library (capability-lifecycle.cjs
install/upgrade/remove + capability-ledger.cjs) across Phases 3-5 but never wired to a
user-facing command — gsd-tools.cjs 'capability' only handled state/set. This adds the
six subcommands, dispatching to the existing lifecycle/ledger:

- install <spec> [--integrity] [--scope global|project] [--yes] [--shared-file <rel>]…
- update [<id>|--all] [--scope] [--yes] [--shared-file]  (re-resolves recorded source)
- remove <id> [--purge-data] [--scope]  (first-party rejected)
- list [--json]  (first-party + overlay, both scopes, JSON array)
- disable|enable <id>  (activation-state alias of capability set --off/--on)

Scope→runtimeDir mapping matches capability-loader exactly (global=$GSD_HOME||home,
project=project root; caps at <root>/.gsd/capabilities/<id>, ledger at <root>/.gsd-capabilities.json).
Consent is non-interactive: --yes grants; without it an executable install aborts after
printing the disclosure and writes nothing. Best-effort reconcile before each mutation.

Tests: tests/capability-cli.test.cjs (20 behavioral, real resolver via local specs,
GSD_HOME-sandboxed) — install consent/block/usage matrix, list, update round-trip,
remove round-trip + first-party guard, disable/enable, unknown subcommand.
Docs: docs/reference/gsd-capability-command.md reconciled to the real surface
(ledger paths, --shared-file, consent model, disable mechanism, outdated marked planned);
docs/COMMANDS.md gains the gsd capability entry.

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

* fix(#1451): resolve adversarial-review findings + root-cause the --raw silent-output bug

Adversarial-review (Codex) fixes:
- capReadStrict passes a malformed strict_known_registries value THROUGH so the trust gate
  fail-closes on it (was silently downgrading to permissive)
- installCapability/upgradeCapability gain an expectedId guard + first-party-id rejection
  (capability-lifecycle.cts): an overlay can't shadow a first-party id, and 'update <id>' can't
  act on a different id if the recorded source was retargeted
- capability update: prints the consent disclosure, exits non-zero on --all partial failure,
  no longer masks the resolved id
- capability remove: ledger-first ordering so an overlay is removable even if it shadows a
  first-party name; first-party guard only fires for ids not in the ledger
- gsd-capability-command.md: disable/enable doc corrected (registry-known ids; overlay toggle
  not yet wired through this path)

Silent-output bug (root cause, not waved off as pre-existing):
- captureStdoutSyncWrites buffered fd-1 output and DISCARDED it on the throw path — any --raw
  command that emitted a result/error envelope then threw (to set a non-zero exit) lost ALL of
  stdout. Now it flushes the captured buffer before re-throwing (exit code preserved).
- cmdCapabilitySet threw via process.exit() (bypassing the capture wrapper entirely); now throws
  ExitError so the wrapper flushes — matches the repo's no-process-exit architecture.
- Regression test: capability disable <unknown> --raw must emit the JSON error envelope on stdout.

Verified: capability suite 165/165, @file/json-errors/phase 183/183, lint clean.

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

* fix(#1451): address adversarial-review R2 — shared-file confinement, MCP no-clobber, config fail-closed

- confinedSharedFile(): realpath-confine every shared-config write/strip to the scope root (mirrors
  safeRmUnder), so a --shared-file whose parent is a symlink escaping the scope can't write outside it.
- mcpServers shared edits: never overwrite an UNOWNED entry — a name collision with the user's (or
  another capability's) server is skipped, so install/remove can't silently clobber user MCP config
  (hooks already append; the map-keyed mcpServers path was the gap).
- capReadStrict: a PRESENT-but-unparseable .planning/config.json now fails CLOSED (lockdown) instead
  of silently downgrading the strict_known_registries policy to permissive.
- Tests: symlink-escape shared-file writes nothing outside scope; colliding user mcpServers entry
  preserved; unparseable config blocks an external install. capability suite 83/83, lint clean.

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

* fix(#1451): address code-review — aborted-status robustness + coverage + project-scoped strict doc

- install/update: handle an 'aborted' result independently of the requiresConsent flag so it can never
  fall through to the generic 'blocked: unknown reason' arm (aborted always means consent-needed per
  the lifecycle contract; latent today, hardened for future status additions).
- Clarify capResolveScope comment (project scope === already-resolved cwd) and document that
  strict_known_registries is a PROJECT-scoped policy (read regardless of --scope; no machine-wide
  allowlist) in gsd-capability-command.md.
- Tests: update --all over an empty ledger returns an empty result set (exit 0); a flag value that
  looks like another flag (--integrity --scope) is rejected, not swallowed. CLI suite 33/33, lint clean.

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

* docs(#1451): FEATURES.md entry #147 + Added/Fixed changesets for the capability CLI

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

* chore(#1451): backfill changeset PR number → #1457

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:02:36 -04:00
Tom Boucher
1abebbf4fd feat(#1434): registry-driven dispatch for third-party capabilities (ADR-1244 Phase 5) (#1450)
ADR-1244 Phase 5 (D7). dispatchOverlayCapabilityCommand in gsd-tools.cjs dispatches an installed third-party capability command family via loadRegistry({includeInstalled}), gated on a committed ledger entry (consent) and confined to the capability's install root (defaultRequireFromInstallRoot: bare-.cjs basename + realpath containment, rejects ../ traversal + symlink escape); same own-property/function/sync/ExitError guards as the first-party path. capability-loader records _overlay.commandRoots only for accepted overlay caps with a committed, structurally-valid ledger entry (fail closed). First-party graphify/intel/audit unchanged (already on the registry seam). 3 Codex rounds converged + /security-review (no HIGH) + /code-review (Approve); gsd-test green both platforms; CI green.

Closes #1434.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:37:43 -04:00
Behruz Nassre Esfahani
c26947808a feat(#1269): expand same-prefix numeric ID ranges in --phase-req-ids (#1419)
* feat(#1269): expand same-prefix numeric ID ranges in --phase-req-ids

normalizePhaseReqIds treated a range token like "SEL-01..SEL-03" as a single
literal ID, so gap-analysis reported the range string as a missing requirement
even when SEL-01/02/03 existed individually.

Add a per-token range expander (run AFTER the existing split, preserving the
string[] | null | undefined contract): a token matching <PREFIX>-<NN>..<PREFIX>-<MM>
with identical prefixes, ascending bounds, and EQUAL digit width expands to the
individual IDs preserving that width; anything ambiguous stays literal
(fail-closed). Differing-width bounds stay literal so the expander never invents
a zero-padding the author didn't type, and ranges beyond MAX_PHASE_REQ_RANGE
(1000) stay literal as a DoS guard.

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

* chore(#1269): add changeset for --phase-req-ids range expansion

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

* chore(#1269): mark changeset docs-exempt (internal flag, no user docs surface)

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

* test(#1269): isolate the DoS-cap branch with a same-width range; doc nits

Review fixes: the AC4 DoS test used REQ-1..REQ-100000, whose differing digit
widths trip the width guard before the cap is reached. Use a same-width
REQ-0001..REQ-1001 (span 1001 > 1000) so the test actually exercises the cap.
Clarify the PHASE_REQ_RANGE_RE capture-group JSDoc and the property-test width
assertion comment.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-18 21:59:10 -04:00
Rezolv
dcceb1a004 fix(#1441): antigravity resolver prefers GSD-owned dir over first-existing (#1442)
* fix(#1441): antigravity resolver prefers GSD-owned dir over first-existing

resolveConfigHomeFromDescriptor's dot-home-nested probe returned the first
bare-existing candidate, so a CLI user (~/.gemini/antigravity-cli) who also
had the IDE's ~/.gemini/antigravity dir was silently shadowed to the legacy
dir (probed first). Regression from #217 — pre-#217 returned
~/.gemini/antigravity unconditionally.

Add an optional probeMarker (gsd-core/VERSION) to the dot-home-nested
descriptor: a two-pass probe prefers the candidate GSD installed into, then
bare existence, then probe[0]. Behavior is byte-identical when probeMarker is
absent (windsurf etc. unaffected). Adds detectAntigravityDirAmbiguity() for
installer/operator guidance on already-misinstalled users (auto-relocation
ruled out per ADR-0008's single-configDir migration bound).

Regression tests fail before / pass after: coexistence + marker-priority
cases, end-to-end through the registry descriptor.

Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB

* chore(#1441): add changeset for antigravity resolver fix

Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB
2026-06-18 21:47:18 -04:00
Tom Boucher
9219af3360 feat(#1433): capability trust gate + upgrade/compat (ADR-1244 Phase 4) (#1449)
ADR-1244 Phase 4 (D5 trust + D6 upgrade/compat). capability-trust.cjs (disclosure/consent, strict_known_registries, engines+compatVersions, reserved namespace) + capability-lifecycle.cjs (install/upgrade/remove/reconcile; ledger-as-commit-point _pending intent; atomic stage-then-swap; surgical marker-isolated shared-edit strip; owner-token lock) + capability-source promote/skipEnginesGate seams + loader pending-skip + config keys. No sandbox re-derived (consent+integrity+reversibility). 6 Codex adversarial rounds + /security-review (no HIGH) + /code-review; gsd-test green both platforms; CI green. Phase 5 (#1434) wires the CLI dispatch.

Closes #1433.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:40:37 -04:00
Tom Boucher
1abb0d3427 feat(#1432): capability source resolver + ledger (ADR-1244 Phase 3) (#1443)
* feat(#1432): capability source resolver + ledger (ADR-1244 Phase 3)

ADR-1244 D3 + D4 — additive, testable-in-isolation modules (the /gsd:capability
install command + consent gate are Phase 4). NEW modules only; not yet wired into
bin/install.js.

- src/capability-source.cts → resolveCapabilitySource(spec, opts): one seam, one
  adapter per source kind — local (fs copy), git (execGit clone+checkout, https/ssh/
  git transports only), npm (execNpm pack --ignore-scripts + tar, NEVER npm install),
  tarball (https download + sha512 integrity-before-extraction + tar), registry (stub).
  SECURITY: install never executes capability code (copy/extract only, --ignore-scripts);
  integrity verified before staging; symlink members rejected at interior, source-root,
  AND tar-member (verbose-listing) layers; tar-slip member paths rejected pre-extraction;
  npm specs with shell metacharacters (incl %) rejected (execNpm uses a Windows shell);
  git ext::/file:// transports + leading-dash/metachar refs rejected; atomic staging
  (.staging→rename, restore-on-failure); full Phase 1/2 validator suite + engines.gsd
  pre-check on the fetched manifest. Test seam _setCapabilitySourceHttpGet.
- src/capability-ledger.cts → per-runtime .gsd-capabilities.json install manifest:
  readLedger/writeLedger (atomic via platformWriteSync)/recordInstall (idempotent,
  prototype-pollution-guarded)/removeEntry/reconcile (orphan report, hardened against
  hostile/non-string/'..' files[] members — never throws, never oracles outside runtimeDir).
- 48 new tests (34 source incl. the full security matrix, 14 ledger). Two Codex
  adversarial rounds; all 12 findings fixed + regression-tested.
- .gitignore + eslint.config.mjs: built capability-source/ledger.cjs git+eslint-ignored
  (ADR-457 #551 migration coverage); CONTEXT glossary + INVENTORY rows + manifest regen.

Closes #1432

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

* docs(#1432): add changeset for capability source resolver + ledger

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:23:20 -04:00
Tom Boucher
353f63d170 feat(#1431): runtime capability registry overlay (ADR-1244 Phase 2) (#1440)
* feat(#1431): runtime capability registry overlay (ADR-1244 Phase 2)

Promote the registry from a frozen data file to loadRegistry({includeInstalled}),
composing the first-party registry with a validated installed overlay (ADR-1244 D2):

- Extract the conformance validator to a shared runtime-callable module
  (gsd-core/bin/lib/capability-validator.cjs); the generator re-exports it
  verbatim, guarded by a generative-parity test (no build-time/runtime drift).
- capability-loader.cts: loadRegistry({includeInstalled}) composes first-party
  ∪ validated overlay from $GSD_HOME/.gsd/capabilities (global) and
  <root>/.gsd/capabilities (project) via the canonical buildRegistry. First-party
  always wins (id/skill/agent/config/command-family + reserved gsd-/anthropic-
  prefixes); full merged-set cross-capability validation; engines.gsd load-time
  re-gate (skip-with-warning); gate-kind capabilities FAIL CLOSED; fragment-path
  escapes rejected.
- semverSatisfies (hand-written, no dep) for the engines.gsd gate, fail-closed.
- Wire surface/state + loop to the overlay; loop injects a blocking gate for each
  skipped gate-kind overlay (fail-closed).
- cwd-aware overlay config-key federation: config-loader _federatedConfigSchema(cwd)
  + config-schema isValidConfigKey(key, cwd) compose the overlay per loadConfig/
  config-set call (never eager at module load, never wrong-cwd); first-party path
  unchanged with no cwd.
- run-tests.cjs sandboxes GSD_HOME (idempotent — nested spawns reuse it) for test
  hermeticity; capability-loader.cjs git+eslint-ignored (tsc artifact);
  capability-validator.cjs stays linted (#551 migration coverage).

Closes #1431

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

* docs(#1431): add changeset for runtime capability registry overlay

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

* test(#1431): kill config-schema cwd-aware federation mutants (Stryker ≥52)

The cwd-aware overlay config-key federation added to config-schema.cts
(_capabilityConfigSchema(cwd) + isCapabilityConfigKey/isValidConfigKey cwd
threading) introduced mutable surface uncovered by config-schema's mutation
test set, dropping its score to 39.58% (below the 52 break threshold). Add a
real-overlay-fixture describe block exercising every branch (cwd guard, overlay
loadRegistry, found-branch, first-party fallback, cwd threading); local Stryker
score 39.58% -> 77.08%.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:41:20 -04:00
Tom Boucher
2421cf1b4a feat(#1430): versioned capability manifest + native stamping (ADR-1244 Phase 1) (#1436)
* feat(#1430): versioned capability manifest + native stamping (ADR-1244 Phase 1)

Make the capability manifest versioned — the data substrate the Capability
Ecosystem (ADR-1244) keys off:

- capability.json gains a REQUIRED semver `version` plus the optional
  ecosystem envelope (`engines.gsd`, `compatVersions`, `integrity`,
  `provenance`); the build-time conformance validator enforces them via a new
  `validateVersionEnvelope()` (exported for the Phase 2 runtime overlay).
- All 32 native capabilities stamped with `version` (= package version,
  lockstep) + `engines.gsd`; `sync-manifest-versions.cjs` gains a glob sweep
  that keeps them in sync, and the issue-844 regression guard is extended.
- Strict SemVer 2.0.0 grammar blocks metacharacter/space/unicode smuggling in
  version strings; range/integrity fields are shape-validated (satisfaction
  and the load-time gate are deferred to Phase 2/4).
- Capability rel-paths emitted forward-slash for cross-platform git correctness.

Closes #1430

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

* docs(#1430): add changeset for versioned capability manifest

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:45:53 -04:00
Tom Boucher
b0c774c2e3 feat(#1416): formalize Resolution convention + agent-skills value envelope (Resolution Provenance P3) (#1425)
Narrows P3 of ADR-1411 (Resolution Provenance, epic #1411) based on an
adversarial fit-analysis that showed a single Resolution<T> envelope adopted
by agent-skills, capability-state, and capability-writer fails the deletion
test: configured/reason are meaningless for capability verbs, and
capability-writer's errors[] (operation-not-applied) cannot fold into
warnings[]. The only genuinely shared seam is warnings: string[].

Changes:

- src/resolution.cts: new pure types+builder leaf — exports Resolution<T>
  {value, configured, reason, warnings}, makeResolution<T>() builder, and
  AgentSkillsValue {block, skills_count}. No other src/ imports.

- src/init.cts: cmdAgentSkills --json IR gains additive value:{block,
  skills_count} field (built via makeResolution). All existing flat fields
  (agent_type, block, skills_count, warnings, configured, reason, source,
  degraded) are retained unchanged for back-compat.

- src/capability-state.cts: doc comment on ResolveCapabilityRuntimeStateResult
  naming it the canonical read-verb envelope. No JSON change.

- src/capability-writer.cts: doc comment on SetCapabilityStateResult naming it
  the canonical mutation-verb result (warnings=advisory, errors=operation-
  not-applied). No JSON change.

- CONTEXT.md: new ### Resolution Convention glossary entry after
  ### Resolution Provenance.

- docs/adr/1411-resolution-provenance.md: P3 narrowing amendment appended.

- tests/resolution.test.cjs: 9 unit tests for makeResolution (new).
- tests/agent-skills.test.cjs: 2 P3 tests for value.block/value.skills_count
  and back-compat of all flat fields.

All 277 tests pass (5 suites). npm run lint clean. All lint checks pass.

Part of #1411

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 07:44:52 -04:00
Tom Boucher
484a5b7b86 fix(#1415): loadConfig provenance + agent-skills diagnostic (Resolution Provenance P2) (#1424)
Implements ADR-1411 P2 / #1415, closing #1366.

Part 1 — config-loader.cts:
- Adds `loadConfigResolved(cwd, options) → ConfigResolution { config, source, degraded }`
  with six tagged branch return paths:
  A1 ws+wsconfig → source:'workstream', degraded:false
  A2 no-ws+config → source:'root', degraded:false
  B  ws requested, wsconfig absent → source:'root', degraded:true (intercepts recursive call)
  C  .planning/ exists, no config → source:'builtin-defaults', degraded:false
  D  no .planning/, global defaults readable → source:'global-defaults', degraded:false
  E  no .planning/, no global → source:'builtin-defaults', degraded:false
- `loadConfigResolved` calls `findProjectRoot` at entry to anchor resolution to the
  nearest .planning/ ancestor (cwd-drift fix, heuristic 4 from P1)
- `loadConfig` becomes a one-line delegation: `return loadConfigResolved(cwd, options).config`
- Exports `loadConfigResolved` in the `export =` block

Part 2 — init.cts cmdAgentSkills:
- Imports `findProjectRoot` from `./project-root.cjs`
- Anchors to project root before loading config (fixes #1366 cwd-drift)
- Uses `loadConfigResolved` for provenance; passes projectRoot to buildAgentSkillsBlock
- Computes `configured` + `reason` (AgentSkillsReason enum): 'resolved' |
  'not_configured' | 'configured_empty' | 'configured_unresolved'
- configured_empty and configured_unresolved emit stderr WARNING; not_configured is silent
- --json IR gains: configured, reason, source, degraded (in addition to existing
  agent_type, block, skills_count, warnings)

Tests (TDD):
- tests/config-loader.test.cjs: 8 new provenance tests (RED before impl, GREEN after)
- tests/agent-skills.test.cjs: 7 new diagnostic tests (RED before impl, GREEN after)
- All 306 tests across 4 suites pass (config-loader:33, agent-skills:72, init:105, workstream:96)

Docs:
- CONTEXT.md: Config Loader Module entry updated with loadConfigResolved interface;
  Resolution Provenance entry notes P2 is now implemented
- docs/CLI-TOOLS.md: --json field reference table added for agent-skills

Closes #1366
Part of #1411

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 07:05:15 -04:00
Tom Boucher
d220ba6fd6 fix(#1414): resolve project root from descendant via nearest ancestor .planning/ (Resolution Provenance P1) (#1423)
Add heuristic (4) to findProjectRoot: after heuristics (1)-(3) (sub_repos,
multiRepo, .git+parent-.planning/) are exhausted without a match, perform a
second bounded walk-up within FIND_PROJECT_ROOT_MAX_DEPTH to locate the
nearest ancestor directory containing a .planning/ subdirectory. Returns that
ancestor as the project root, so loadConfig finds the correct config instead
of falling through to defaults when gsd-tools is invoked from a plain
descendant subdirectory of a single-repo project (#1366 cwd-drift gap).

Ordering is load-bearing: the new walk runs AFTER the existing loop so
sub_repos workspaces (where a child sub-repo may have its own .planning/)
still resolve correctly to the parent workspace. The existing own-.planning/
guard (heuristic 0, #1362) and the depth bound (FIND_PROJECT_ROOT_MAX_DEPTH=10)
are both preserved unchanged.

Part of #1411

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 00:50:30 -04:00
Tom Boucher
cfaa3908cd Merge remote-tracking branch 'origin/next' into chore/backmerge-main-to-next-350fba48
# Conflicts:
#	.changeset/924-claude-flat-skill-layout.md
#	.changeset/happy-finches-travel.md
2026-06-17 13:22:28 -04:00
Tom Boucher
e58b5e1721 fix(#1364): decisions adopt markdown-sectionizer seam + fail-loud coverage gate (epic #1372 T1) (#1386)
* test(#1364,#1365): add decisions regression tests (fail-first proof)

Adds tests/decisions.test.cjs with:
- #1364 recall tests: parseDecisions from markdown-header + em-dash bullets
  (these FAIL on pre-T1 code, proving the bug is present before the fix)
- #1365 fail-loud tests: check.decision-coverage-plan must return passed:false
  for decision-shaped but 0-extracted content (FAIL pre-T1, gate silently passed)
- extractDecisions outcome enum tests (could-not-parse/none-present/parsed)
- Parser QA matrix: CRLF, unicode headings, fenced-code suppression, both bullet forms
- Boundary/threshold tests at limit-1 (0), limit (1)

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

* fix(#1364,#1365): adopt markdown-sectionizer seam in decisions.cts; add fail-loud gate

#1364 — Recall: decisions.cts now uses the seam's extractTaggedBlocks and
collectSection for the markdown-header fallback path. Em-dash bullet form
(- **D-NN — title** body) is now recognised alongside the existing colon form.

#1365 — Fail-loud: adds extractDecisions() returning a typed DecisionExtraction
{ decisions, outcome } where outcome is 'parsed' | 'none-present' | 'could-not-parse'.
The blocking gate (cmdDecisionCoveragePlan) now treats could-not-parse as
passed:false with a format-mismatch reason instead of the prior silent passed:true/skip.
gap-checker runGapAnalysis surfaces 'extracted 0 of N — possible format mismatch'
for could-not-parse instead of 'No requirements or decisions to check'.

parseDecisions remains a thin delegate over extractDecisions, so all existing
callers are unaffected.

Seam adoption: stripFencedCode (seam), extractTaggedBlocks(content,'decisions') (seam),
collectSection(content, /decisions?/i, {levelBounded,stripFences}) (seam).

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

* fix(#1364,#1365): tighten could-not-parse, parse-miss fail-loud, curly-quote discretion, gap-checker FIX D

FIX A: empty <decisions> scaffolds and all-prose sections no longer return
could-not-parse; outcome is none-present unless the block/section contains
a \bD- token or a parse-miss, preventing false blocks on legitimate phases.

FIX B: parseDecisionLines now tracks parse-misses (D-NN-shaped bullets that
fail both regexes); extractDecisions returns could-not-parse when parseMisses>0
even if some decisions parsed — silent drops no longer mask format errors.

FIX C: curly-quote normalization regex now includes actual U+2018/U+2019
characters so '### Claude's Discretion' (curly apostrophe) correctly yields
trackable:false (regression vs pre-T1 behavior).

FIX D: gap-checker runGapAnalysis surfaces the decision could-not-parse
format-mismatch signal independently of whether requirements items exist —
previously masked inside `if (items.length === 0)`.

Adds 14 behavioral regression tests (fail-first verified manually before fixes).

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

* fix(#1365): fail-loud gate on parse-miss regardless of covered decisions

Change the `could-not-parse` guard in `cmdDecisionCoveragePlan` and
`cmdDecisionCoverageVerify` from `decisions.length === 0 && outcome ===
'could-not-parse'` to fire on `outcome === 'could-not-parse'` alone.

Previously a CONTEXT.md with a valid D-01 (covered by the plan) plus a
malformed D-02 (parse-miss) would skip the guard (length === 1), proceed
to coverage, find D-01 covered, and silently return passed:true — hiding
the D-02 parse-miss entirely.

Adds a gate-level fail-first test that places D-01 into a ## Must Haves
section (DESIGNATED_HEADINGS_RE match) so coverage of D-01 would pass on
its own, proving the only path to passed:false is the parse-miss fix.
Also adds the matching verify-side advisory assertion.

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

* chore(#1364,#1365): add Fixed changeset (pr:0 placeholder)

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

* chore(#1364): backfill changeset PR number (1386)

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 12:35:38 -04:00
Tom Boucher
afd95a15a9 fix(#1384): scan live changeset fragments in the #1777 purity gate (#1385)
The product-name-purity gate scanned CHANGELOG.md only, never the
.changeset/*.md fragments that render into it. An impure fragment passed
PR review, sat dormant, and re-introduced a forbidden parenthetical
product description at the next release — even after CHANGELOG.md had been
hand-fixed. This is the recurrence vector behind the 1.5.0 back-merge (#1379)
failure.

- Purify the two live fragments to the already-accepted forms:
  - happy-finches-travel.md: "Claude Code (background dispatch …)"
    -> "Claude Code; background dispatch …"
  - 924-claude-flat-skill-layout.md: "Claude (`~/.claude/…`)"
    -> "Claude at `~/.claude/…`"
- Extend the #1777 gate to also scan live .changeset/*.md fragments,
  reusing one shared detection helper. Archived fragments never re-render
  and are intentionally out of scope.

Test-only + changeset-prose change; no production behavior change.

Closes #1384

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:14:15 -04:00
github-actions[bot]
6a2b6729bc chore: back-merge main into next (350fba48) 2026-06-17 14:28:45 +00:00
Tom Boucher
66085d0080 fix(#1374): surface diagnostic when configured agent skills all fail to resolve (#1376)
* fix(#1374): surface diagnostic when configured agent skills all fail to resolve

buildAgentSkillsBlock returned '' (only ad-hoc per-path stderr warnings) when an agent configured via agent_skills had paths that all failed to resolve — missing SKILL.md, unsafe path, invalid global name, OR a malformed (non-string/non-array) value. query agent-skills --json reported skills_count>0 with an empty block and no machine-readable signal, so a fully-dropped configuration was indistinguishable from a resolved one.

Thread an optional diagnostics collector through buildAgentSkillsBlock: route every skip warning through a warn() helper (stderr + collector), flag truthy-but-malformed config values, emit an aggregate warning when configured paths resolve to zero skills, and surface the collected reasons in a new warnings[] field on the query agent-skills --json IR. Empty arrays and falsy values stay silent (skills_count is honestly 0). skills_count semantics unchanged. Docs updated for the new IR field.

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

* chore(#1374): backfill changeset PR number (#1376)

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:16:51 -04:00
Tom Boucher
120f85164b feat(#1355): detect-and-warn guard for claude-code agent-teams (#1371)
* feat(#1355): detect-and-warn guard for claude-code agent-teams

GSD's multi-agent orchestration can stall under claude-code's experimental
agent-teams (a subagent's completion fails to route to the orchestrator). Per
the maintainer decision, the accepted scope is a read-only detector + one
non-fatal warning — NOT the declined run_in_background/TaskOutput conversion.

- New Teams Status Module (src/teams-status.cts → gsd-core/bin/lib/teams-status.cjs):
  pure resolveTeamsStatus({runtime, env}) + thin CLI cmdTeamsStatus reusing
  resolveRuntime. active = strictly-truthy env flag AND runtime === 'claude'.
- Wire `gsd-tools query teams-status [--active]` (read-only; no capability
  registration needed — conformance gates govern features, not query commands).
- One non-fatal warning in plan-phase.md before the first Agent spawn, gated on
  `query teams-status --active`; zero behavior change on non-claude/teams-off.
- Hermeticity: clear CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS in run-tests.cjs +
  SESSION_ENV_KEYS. Docs reference + CONTEXT.md glossary. Built lib gitignored.

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

* chore(#1355): add changeset for teams-detect guard

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

* test(#1355): bump plan-phase.md workflow size baseline (+407B for teams warning)

The non-fatal agent-teams warning block added to plan-phase.md grew it
92759 → 93166 bytes, past its committed per-file baseline ratchet. The growth
is small, deliberate, and still well under the workflow tier hard cap. Regenerate
the baseline via `npm run size:baseline` (only plan-phase.md changed).

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

* chore(#1355): register teams-status.cjs in the inventory manifest

The new teams-status CLI module is a tracked surface; regenerate
docs/INVENTORY-MANIFEST.json (cli_modules family) via
gen-inventory-manifest.cjs --write so the inventory-manifest-sync gate passes.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:52:23 -04:00
Tom Boucher
08e42b0ef1 fix(#1356): rewrite bare ~/.claude paths in the Cursor install branch (#1368)
* fix(#1356): rewrite bare ~/.claude paths in the Cursor install branch

The Cursor branch of _applyRuntimeRewrites only rewrote the trailing-slash
.claude forms, so bare ~/.claude / $HOME/.claude references survived into
installed Cursor artifacts (skills/gsd-surface, skills/gsd-graphify,
workflows/plan-phase, workflows/autonomous), tripping the post-install
"unreplaced .claude path reference(s)" audit. Same regression class as
#983/#2418/#2545 — every other affected branch was patched; cursor was missed.

- Add the three bare-form rewrites (~/.claude, $HOME/.claude, ./.claude) to the
  cursor branch, mirroring cline/trae/augment/codebuddy. They run after the
  slash forms (no double-replace) and use (?![\w-]) so .claude-plugin /
  .claudeignore are not corrupted.
- SKILL.md content also passes through this stage, so no stage-1 converter
  change is needed. Verified: 40 bare refs in the real leaking files → 0.

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

* chore(#1356): add changeset for Cursor bare-path rewrite fix

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:51:48 -04:00
Tom Boucher
c03f3cc6af fix(#1348): canonicalize Codex hooks.json writes to the nested { hooks } shape (#1363)
* fix(#1348): canonicalize Codex hooks.json writes to the nested { hooks } shape

reconcileCodexHooksJsonEvent preserved whatever shape it read, so on an empty,
absent, or legacy top-level hooks.json it wrote top-level event keys
(`{ "SessionStart": [...] }`) that current Codex (deny_unknown_fields) rejects,
instead of the canonical `{ "hooks": { "SessionStart": [...] } }`.

- Lift any top-level event arrays (legacy, empty, or mixed nested+top-level)
  into the nested `hooks` table, merging same-named events so no user/legacy
  entry is dropped and no stray top-level event key survives. Mirrors
  reconcileCursorHooksJson.
- Collapse an empty hook table back to `{}` so removal on an absent file does
  not write a spurious `{ "hooks": {} }`.
- Read path still tolerates both shapes; dedup/removal unchanged.

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

* chore(#1348): add changeset for Codex hooks.json canonicalization

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:51:11 -04:00
Tom Boucher
07adeb50a0 fix(#1342): scope worktree-path-guard to GSD executor runs; fail open for no-repo targets (#1361)
* fix(#1342): scope worktree-path-guard to GSD executor runs; fail open for no-repo targets

The PreToolUse worktree-path-guard fired for any Write/Edit in any linked git
worktree, with no check for active GSD work — so Claude Code plan-mode writing
~/.claude/plans/<slug>.md from a manually-created worktree was hard-blocked.

- Gate enforcement on the GSD isolated-executor branch namespace
  (^worktree-agent-[A-Za-z0-9._/-]+$, per worktree-branch-check.md #2924); the
  guard is a no-op in non-GSD linked worktrees.
- Fail open when a target resolves to no git repository (e.g. ~/.claude/plans/)
  instead of blocking — that is not the #260 main-repo vector. A target inside
  a .git directory still blocks (git rev-parse --is-inside-git-dir).
- The #260 different-git-root hard block (escape to the main repo) is preserved.

Detached-HEAD executors no-op the gate; this is accepted because they are
fail-closed by worktree-branch-check.md (exit 42) before committing.

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

* chore(#1342): add changeset for worktree-path-guard scoping fix

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

* test(#1342): build dot-dot traversal path portably (Windows drive-letter fix)

The traversal test built its file_path by stripping a leading slash from an
absolute externalDir and path.join-ing it after a `..` chain. On Windows the
drive letter (C:\) is not a leading slash, so it survived and path.resolve
produced an invalid doubled-drive path (C:\C:\Users\...), which resolves to no
git repo — the hook failed open (exit 0) and the test expected a block (exit 2).

Use path.relative(worktreeDir, externalTarget) + string concat so the file_path
carries literal `..` segments that resolve to externalTarget on both posix and
win32 (no drive doubling). Verified with path.win32/path.posix.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 08:50:28 -04:00
Tom Boucher
c53fd1f654 fix(#1324): resolve glued phase tokens (#1353) 2026-06-16 21:55:58 -04:00
Tom Boucher
c4161735dc fix(#1325): scope update backup detection (#1354) 2026-06-16 21:55:55 -04:00
Tom Boucher
aab26c7bf4 fix(#1343): parse decision bullets with text before the colon (#1358)
* fix(#1343): parse decision bullets with text before the colon

parseDecisions() silently dropped any `- **D-NN ...:**` decision bullet
whose header had freeform text (a parenthetical, em-dash, or prose) before
the `:**`, so the blocking check.decision-coverage-plan gate computed
coverage over a narrowed set and reported a false pass.

- Broaden bulletRe to tolerate a freeform run before the colon while
  preserving the optional [bracket] tag capture (drives `trackable`).
- Add a parse-miss guard: a line that looks like a D-NN bullet but still
  fails the regex flushes the current decision and warns instead of
  vanishing — the gate-integrity floor.

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

* chore(#1343): add changeset for decision-coverage false-pass fix

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

* test(#1343): relocate decision-parser regression into owning module test file

CI's lint-regression-test-names bans new bug-NNNN-*.test.cjs files. Move the
9 regression cases from tests/bug-1343-parsedecisions-drop.test.cjs into the
owning parser test file tests/post-planning-gaps-2493.test.cjs (which already
exercises parseDecisions) and delete the banned file.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 21:55:51 -04:00
Tom Boucher
f3c06f59df fix(#1326): stop emitting Codex agents/openai.yaml sidecars; clean up stale ones (#1360)
* fix(#1326): stop emitting Codex agents/openai.yaml sidecars; clean up stale ones

Codex installs wrote an agents/openai.yaml sidecar under every managed gsd-*
skill dir. Recent Codex builds index both SKILL.md and the sidecar, so each
GSD skill appeared twice in autocomplete (canonical gsd-* name + humanized
display_name).

- Replace writeCodexSkillMetadataFiles / generateCodexSkillMetadataYaml with
  cleanupCodexSkillMetadataSidecars: Codex-only (if isCodex), removes stale
  managed gsd-*/agents/openai.yaml and prunes the now-empty agents/ dir.
- Preserve user-owned dirs (gsd-dev-preferences), non-empty agents/ dirs, and
  non-gsd dirs; lstat-guard against symlinked agents/ so a delete can never
  escape the skills tree; fail-open per directory.
- Codex relies on SKILL.md alone for /skills discovery.
- Update USER-GUIDE/FEATURES docs and rewrite the #774 emission tests into
  cleanup tests.

Scope: the sidecar duplicate only. The separate multi-root (~/.agents/skills
shared-skills) duplicate facet is a distinct concern, not addressed here.

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

* chore(#1326): add changeset for Codex sidecar cleanup

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 21:55:49 -04:00
Tom Boucher
1a678eb0e9 fix(#1359): migrate workflows off deprecated TaskOutput to Read(outputFile) (#1362)
* fix(#1359): migrate workflows off deprecated TaskOutput to Read(outputFile)

The map-codebase and docs-update workflows collected background sub-agent
results with the deprecated Claude Code `TaskOutput` tool using `block: true`,
which has a confirmed main-session hang after the agent completes
(anthropics/claude-code#20236).

Migrate the collection steps to the upstream-recommended pattern: keep
`run_in_background=true` on the Agent spawn, then `Read` each agent's
`outputFile` (from the `async_launched` result) once it reports completion.
Completion-marker contracts and on-disk verification are unchanged, and the
non-Claude runtime fallbacks (sequential_mapping / sequential_generation) are
preserved byte-for-byte. docs-update's timeout note no longer references the
unwired `workflow.subagent_timeout` key (it kept a literal before).

Regression coverage folded into tests/subagent-timeout.test.cjs (the owning
module for background-subagent collection). Workflow size baseline regenerated
for the justified prose growth.

Refs #1355 (same latent hang surface).

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

* chore(#1359): add changeset for TaskOutput migration

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 21:55:45 -04:00
Rezolv
00c05eb717 Merge branch 'next' into feat/1279-fail-first-prover 2026-06-16 17:35:55 -04:00
Tom Boucher
a0dbf8bbdf fix(#1319): use portable Claude skill effort (#1352) 2026-06-16 15:30:17 -04:00
Tom Boucher
c20d741dc9 fix(#1316): preserve prose STATE phase names (#1351) 2026-06-16 15:11:23 -04:00
Tom Boucher
284dc7bc44 fix: resume UAT checkpoint from paused placeholder (#1350) 2026-06-16 14:47:17 -04:00
Tom Boucher
9540fe43b9 fix: have executor self-report worktree metadata (#1349) 2026-06-16 14:19:08 -04:00
Dave
56d4a1bf39 enhance(#1279): project check_violation_fixture scalar — #1278 locate + #1279 proof compose end-to-end (#1346)
Delivers option (a) from the #1314 maintainer review: thread a fourth flat
scalar check_violation_fixture through the projection so a prohibition authored
at spec-phase machine-proves fail-first and greens through the deterministic
path alone — zero hand-authoring at verify time.

- src/probe-core.cts: Prohibition gains check_violation_fixture?; projectProhibitions
  emits it (both kinds) ONLY for a well-formed descriptor and ONLY when non-empty
  (blank/absent -> projects absent -> producer hard-gates, never a partial green).
- src/prohibition-enforcement.cts: descriptorFromProjection reads it back into
  violationFixture via the same numeric-coercion-safe scalar() normalizer.
- Tests (RED-first, proven non-vacuous by reverting both src edits): CHK-02(#1346)
  projection emit, CHK-08(#1346) read-back, CHK-03(D) example round-trip, the
  fast-check round-trip property extended to the 4th scalar (the contract trek-e
  blocked #1301 on), and a real-subprocess COMPOSE capstone greening end-to-end
  through project -> descriptorFromProjection -> default prover+runner.
- Docs flipped from 'hard-gates until #1346' to 'composes end-to-end': verify-phase.md,
  prohibition-probe.md, spec-phase.md authoring, ADR-550 addendum, changeset.
  #1346 now tracks only the node-test causation residual.

190 affected-suite tests green; eslint + tsc clean; size baseline regenerated.
2026-06-16 14:00:49 -04:00
Tom Boucher
6e242bd76a fix: allow quick worktree parent plan base (#1347) 2026-06-16 14:00:06 -04:00
Dave
3cbbcdab42 Merge branch 'next' into feat/1279-fail-first-prover 2026-06-16 13:06:37 -04:00
Dave
33b6ee6f0c fix(#1279): node-test prover fail-closes on missing violationFixture + honest projection docs
Addresses the #1314 maintainer review (trek-e):

- Major 1 (fail-OPEN): defaultProveFailFirst's node-test branch only guarded
  `if (!fixture)`. A missing/typo'd/stale violationFixture made GSD_PROHIB_SUBJECT
  point at a missing file; an honest negative test threw ENOENT *inside its
  callback* (a failing test named distinctly from the file), which
  isNonVacuousNodeTestRed accepted as proof -> a green forged from a setup crash.
  Now requires fs.existsSync(path.resolve(cwd, fixture)) before spawning, symmetric
  with the lint-rule path's file-result guard. Regression test pins it (RED without
  the guard); a second test pins cwd-relative fixture resolution.

- Major 2 (misleading prose): the #1278 projection carries no violationFixture, so
  the deterministic-locate path always hard-gates (fail-closed) until a
  check_violation_fixture scalar is threaded through. verify-phase.md and
  prohibition-probe.md no longer read as if the projected path produces greens;
  the ADR-550 addendum records both items. Tracked as follow-up #1346.

- Documented residual: existence is necessary but not sufficient (a red caused by
  the env being set vs the subject's content); recorded as a constraint, in #1346.

- Nit: stale 'NOT attested fail-first' comment -> 'NOT machine-proven fail-first'.

64 tests pass; eslint + tsc clean; changeset valid.
2026-06-16 13:05:20 -04:00
Tom Boucher
80014109c1 fix: preserve state patch progress counters (#1345) 2026-06-16 11:52:09 -04:00
Tom Boucher
ee9ef7a8e4 Merge branch 'next' into feat/1279-fail-first-prover 2026-06-16 11:32:57 -04:00
Tom Boucher
704d7bc2a6 fix(#1263): restore init phase requirements from flat Phase Details (#1344)
* fix: resolve init phase details fallback

* chore: add changeset for phase details fallback
2026-06-16 11:03:47 -04:00
Tom Boucher
ddeb90bba2 chore(#1336): back-merge main into next — reconcile CHANGELOG + prune released fragments
auto-backmerge.yml has failed since v1.4.0, freezing next's CHANGELOG.md at 1.3.1 while main reached 1.4.5. This one-time reconciliation merges main into next with -s ours (next already contains every code fix on main — hotfix fixes #866/#921,#922/#934 are present under their original commits a7b288b9d/b866b9529/74a121bb4), overlays main's CHANGELOG.md, and prunes the 52 already-released .changeset fragments main consumed at release.

next's 1.5.0-rc.5 version is preserved; net diff vs next is CHANGELOG + fragment deletions only (verified: exclude-diff empty = zero code changes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 09:05:03 -04:00
Tom Boucher
2d264da661 enhance(#968): region-scoped negative-grep idiom + cross-task conflict warning (#1320)
Adds a region/function-scoped negative-grep idiom to the gsd-planner verification guidance plus a warn-only `validate_plan` check (`scanFileWideNegativeGateConflict`) that flags when a task's file-wide negative grep bans a construct a sibling task legitimately requires elsewhere in the same file. ReDoS-safe (linear, no RegExp on author patterns); region-scoped gates are exempt. Warn-only — never errors, never flips `valid`.

Closes #968

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 02:20:34 -04:00
Dave
49bef1927e Merge remote-tracking branch 'origin/next' into feat/1279-fail-first-prover
# Conflicts:
#	docs/adr/550-spec-phase-probe-contract.md
#	gsd-core/workflows/verify-phase.md
#	tests/workflow-size-baseline.json
2026-06-16 00:32:22 -04:00