Commit Graph

4284 Commits

Author SHA1 Message Date
Tom Boucher
dd21bb7451 fix(#2046): config-set <key> null unsets (removes) the key instead of writing "null"
`gsd-tools config-set <key> null` — the "Clear" action documented in
settings-integrations.md / settings-advanced.md — previously fell through the
value parser as the literal STRING "null" and persisted it. Consequences:
"cleared" keys stayed set (config-get returned truthy "null"), and for secret
keys (brave_search/firecrawl/exa_search) a masked success line hid a truthy
4-char value on disk that integrations could pass along as a real credential.
There was also no unset/delete verb at all.

Fix: parse a bare `null` to JS null and short-circuit to a real UNSET that
DELETES the key from config.json — the semantic the docs already describe
("Remove the stored key" / "remove the key by setting it to null"). Deleting
(not persisting JSON null) is the correct clear: a persisted null is still a
present value consumers must special-case.

- src/config.cts:
  - parse block: `else if (val === 'null') parsedValue = null;`
  - cmdConfigSet: when parsedValue === null, short-circuit BEFORE the typed
    per-key validator gauntlet (so clearing an enum/boolean/number key removes
    it rather than being rejected) and before the project_code special-case;
    mask the previous value for secret keys in the output.
  - new `unsetConfigValue()` + `_unsetNestedValue()` mirroring setConfigValue/
    _setNestedValue: same prototype-pollution guard, but never creates missing
    intermediates and never prunes empty parents; returns { previousValue,
    existed }. Unsetting a never-set key is an idempotent no-op success.
- tests/config.test.cjs: new suite covering non-secret routing key, secret key,
  typed-enum-key bypass (context), idempotent unset, literal-"null"-on-disk
  guard, the unset-path prototype-pollution guard (alert #26 parity), and a
  4-segment deep-nested unset.
- tests/review-model-config.test.cjs: update the stale round-trip test that
  codified the bug (asserted config-set null → config-get returns "null") to the
  fixed contract — the model key is removed; the review workflow's
  `[ -n "$VAR" ] && [ "$VAR" != "null" ]` guard handles the empty read as
  "no override → reviewer default", same as the old "null" sentinel.

The 4 documented "Clear" flows (settings-integrations.md, settings-advanced.md)
were verified — their prose already describes removal, so the fix makes them
accurate rather than aspirational; no doc wording change required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 09:41:14 -04:00
Tom Boucher
a3b9cbaeb8 Merge pull request #2054 from open-gsd/fix/2045-third-party-skills-surface
fix(#2045): third-party capability skills surface correctly
2026-07-07 08:38:23 -04:00
Tom Boucher
847de596b8 fix: third-party capability skills surface correctly (#2045)
A skills-only role:feature third-party capability installed 'active' but its skills never reached the runtime surface, capability enable/set rejected it as 'unknown capability', and capability list disagreed with capability state. Three defects, fix shape 1b (teach resolveSurface, no on-disk linking):

D1 (materialization): resolveSurface built the surfaced skills Set only from the on-disk manifest; third-party cap skills live at ~/.gsd/capabilities/<id>/skills/ and never entered the Set -> surfaced:false. Fix: union registry.capabilityClusters values into the Set in the full-profile branch (idempotent for first-party, additive for third-party; prototype-pollution guarded).

D2 (enable/set unknown): setCapabilityState validated the capId against the static first-party registry instead of the composed overlay-aware registry. Fix: validate against loadRegistry({includeInstalled}) like capability-state.cts does.

D3 (list vs state): capability list derived status purely from ledger existence, never surface composition. Fix: add a 'surfaced' field to list rows sourced from the same resolver capability state uses, so list and state agree.

Regression coverage: tests/issue-2045-third-party-skills-surface.test.cjs asserts all five acceptance criteria (resolveSurface union, state surfaced:true, enable/set not-unknown, list/state agreement, first-party + unknown-id regression). gsd-test: 23916/23916 green (linux-node22 + linux-node24).
2026-07-07 08:25:33 -04:00
Tom Boucher
2372a221eb Merge pull request #1835 from open-gsd/feat/1820-specless-predicate-rail
feat(#1820): spec-optional predicate rail — author probe predicates into must_haves when SPEC omits them
2026-07-07 08:05:30 -04:00
Tom Boucher
ea4378063f Merge branch 'next' into feat/1820-specless-predicate-rail 2026-07-07 07:49:07 -04:00
Tom Boucher
d2ae7fe391 Merge pull request #2053 from open-gsd/chore/sync-next-version-1.7.0-rc.4
chore: sync next package version to 1.7.0-rc.4
2026-07-07 02:08:34 -04:00
github-actions[bot]
a6263bba8a chore: sync next package version to 1.7.0-rc.4 2026-07-07 06:08:27 +00:00
Tom Boucher
ed2afd6204 Merge pull request #2051 from open-gsd/fix/2003-capability-state-runtime-flag
fix(#2003): add --runtime override to capability state + loop render-hooks
2026-07-07 01:40:36 -04:00
Tom Boucher
0d7c15badc Merge branch 'next' into fix/2003-capability-state-runtime-flag 2026-07-07 00:24:27 -04:00
Tom Boucher
29c3ed102c Merge pull request #1994 from open-gsd/codex/gsd-onboard
feat(#1990): add brownfield onboarding workflow
2026-07-07 00:23:55 -04:00
Tom Boucher
8a935a08a3 Merge branch 'next' into fix/2003-capability-state-runtime-flag 2026-07-07 00:19:08 -04:00
Tom Boucher
bc751a64ec docs(#1990): point adr index at renamed file 2026-07-07 00:01:57 -04:00
Tom Boucher
1171499f38 docs(#1990): remove pre-rename ADR filename 2026-07-07 00:01:56 -04:00
Tom Boucher
d0b8eacd3c docs(#1990): rename ADR to Existing Code Onboarding 2026-07-07 00:01:55 -04:00
Tom Boucher
e8fb05e965 docs(#1990): index ADR-1990 in adr README 2026-07-06 23:58:50 -04:00
Tom Boucher
3c7d722ed9 docs(#1990): add ADR-1990 onboard projection module 2026-07-06 23:58:27 -04:00
Tom Boucher
d7129222c0 Merge branch 'next' into codex/gsd-onboard 2026-07-06 23:49:49 -04:00
Tom Boucher
cc0372007a Merge pull request #1991 from jslitzkerttcu/fix/1941-quick-worktree-stale-base
fix(#1941): degrade /gsd-quick worktree dispatch when fork base is stale
2026-07-06 23:49:25 -04:00
Tom Boucher
579ad30eae docs(#2003): backfill changeset pr number to 2051 2026-07-06 23:44:25 -04:00
Tom Boucher
9b34fd5c08 Merge branch 'next' into fix/1941-quick-worktree-stale-base 2026-07-06 23:37:23 -04:00
Tom Boucher
327b6409e8 fix(#2003): address code+security review findings
- warn (don't silently ignore) when --runtime is an unknown runtime that
  canonicalizeRuntimeName rejects; the warning surfaces via warnings[] so a
  typo like --runtime cluade or a runtime known to runtime-homes but not the
  alias manifest (e.g. grok) no longer silently resolves to the persisted
  runtime's config dir on this diagnostic command [M-1]
- add end-to-end CLI test for loop render-hooks --runtime (the exact command
  the bug report calls out as silently no-op'ing) [L-2]
- add closed-vocabulary rejection test: crafted --runtime values
  (../../etc/passwd, __proto__, --config-dir, garbage) are rejected, warn,
  and fall through to the persisted runtime — pins the security-load-bearing
  contract [NIT-01]
- add boundary tests: --config-dir wins over --runtime (precedence); missing
  --runtime value errors with USAGE [N-1]

Both orthogonal reviews returned APPROVE with no Critical/High findings.
Security review confirmed --runtime cannot coerce getGlobalConfigDir into an
arbitrary path (closed-vocabulary Map lookup + registry hash-key gate) and
does not expand the trust surface beyond the existing operator-controlled
--config-dir flag.
2026-07-06 23:32:06 -04:00
Tom Boucher
def745fa6b test(#2003): regenerate golden-install-parity fixtures for gsd-tools.cjs change
The --runtime parsing + help-text edit to gsd-core/bin/gsd-tools.cjs changes
the installed file's content (gsd-tools.cjs is installed and compared by the
golden snapshot, unlike gsd-core/bin/lib/ which is excluded). Regenerated via
UPDATE_GOLDEN=1; every runtime's manifest updates exactly one line (the
gsd-tools.cjs hash).
2026-07-06 23:09:54 -04:00
Tom Boucher
49552b3485 docs(#2003): add changeset fragment for --runtime override 2026-07-06 22:57:39 -04:00
Tom Boucher
ab82e73af3 fix(#2003): add --runtime override to capability state + loop render-hooks
resolveCapabilityRuntimeState derived the config dir from resolveRuntime(cwd)
(GSD_RUNTIME -> config.runtime -> 'claude') when no --config-dir was passed,
so a repo with persisted runtime:'codex' resolved the config dir to ~/.codex
where the Claude skill isn't installed -> surfaced:false / hooks silently
no-op when the operator drove from Claude Code. capability state and loop
render-hooks parsed only --config-dir, never --runtime, so there was no way
to assert the actually-active runtime.

Add a runtimeOverride param to resolveCapabilityRuntimeState (canonicalized
via runtime-name-policy so aliases like codex-app work); when present it
short-circuits the persisted-runtime fallback and resolves getGlobalConfigDir
for the explicit runtime. Thread --runtime through cmdCapabilityState and
cmdLoopRenderHooks, and parse it in gsd-tools.cjs for both commands (dual
--runtime X / --runtime=X form, mirroring --config-dir and the existing
capability-set --runtime precedent). Help text updated.

Without the override, behavior is byte-identical to today (regression-guarded).
2026-07-06 22:57:05 -04:00
Tom Boucher
6a15ab9345 test(#2003): add regression tests for --runtime override on capability state/loop render-hooks
Mirrors the #1160 installed-layout block for the runtime auto-detection gap.
Covers: runtimeOverride='claude' bypasses persisted config.runtime:'codex';
no override still honours persisted runtime (regression guard); alias
canonicalization (codex-app -> codex); and an end-to-end CLI test proving
'capability state --runtime claude' resolves the Claude config dir despite a
persisted runtime:'codex'.

Expected RED against unfixed resolveCapabilityRuntimeState (no runtimeOverride
param) and unfixed gsd-tools.cjs (no --runtime parsing for capability state /
loop render-hooks).
2026-07-06 22:57:05 -04:00
Codesmith
192764f0c3 chore(#1990): recapture zcode golden install fixture with onboard artifacts
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
2026-07-07 02:55:18 +00:00
Tom Boucher
080bacdb4b Merge branch 'next' into codex/gsd-onboard 2026-07-06 22:43:32 -04:00
Tom Boucher
46d18c0800 Merge pull request #2049 from open-gsd/fix/1858-flat-layout-skill-manifest
fix(#1858): detect flat commands/gsd-*.md layout in _resolveManifest
2026-07-06 22:42:29 -04:00
Tom Boucher
12b4c625ad Merge branch 'next' into fix/1858-flat-layout-skill-manifest 2026-07-06 22:33:07 -04:00
Tom Boucher
4424a366d7 docs(#1858): backfill changeset pr number to 2049 2026-07-06 22:18:40 -04:00
Tom Boucher
2c853822a1 fix(#1858): address code+security review findings
- restructure _loadFlatCommandsGsdManifest try/catch to wrap read+parse+set
  together (mirrors loadSkillsManifest exactly), so a thrown parser degrades
  both keys to [] — closes the latent catch-scope parity drift [Nit-1]
- add boundary test: gsd-.md (empty stem) skipped, gsd-x.md single-char stem
  kept (slice(4,-3) boundary) [Low-1]
- add unreadable-file test (POSIX-gated): both keys degrade to [] [Low-2]
- strengthen parity test to also compare requires + _calls_agents_ VALUES,
  not just the stem set [Nit-2]

Both orthogonal reviews returned APPROVE with no Critical/High findings.
Security review confirmed no new trust-boundary crossing, prototype-pollution
immune (Map/Set throughout), and symlink/path-traversal surface identical to
the pre-existing nested loader (not a regression).
2026-07-06 22:06:30 -04:00
Tom Boucher
d99e1f9c82 Merge pull request #2048 from open-gsd/fix/2041-model-overrides-claude-alias
fix(#2041): map model_overrides Claude IDs to Agent-tool aliases
2026-07-06 21:36:12 -04:00
Tom Boucher
e3d053619c docs(#1858): add changeset fragment for flat-layout manifest fix 2026-07-06 21:14:44 -04:00
Tom Boucher
9dcd370332 fix(#1858): detect flat commands/gsd-<stem>.md layout in _resolveManifest
_resolveManifest only recognized the nested source layout (commands/gsd/*.md)
and the installed-runtime skills layout (skills/gsd-<stem>/SKILL.md). A flat
source install (Claude local project shape: commands/gsd-<stem>.md, no
commands/gsd/ subdir) matched neither branch, so the manifest came back empty
and resolveSurface materialized the full profile to an empty Set — silently
reporting every skill-bearing capability as surfaced:false / enabled:false /
active:false. The nyquist/code-review/security/ui verify:post and execute:post
hooks never fired even with their workflow.* toggles on.

Add a third branch: when commandsGsdDir is absent, scan dirname(commandsGsdDir)
for gsd-<stem>.md files, strip the gsd- prefix, and build the same Map shape
the nested loader produces (requires via shared parseRequires, companion
_calls_agents_<stem> via shared parseCallsAgents). Falls through to the
installed-skills branch when the flat dir has no gsd-*.md files (precedence:
nested > flat-source > installed).

Also export parseCallsAgents from install-profiles so capability-state reuses
the SAME parser the nested loader uses (no drift; mirrors the existing
parseRequires export+reuse pattern).
2026-07-06 21:13:42 -04:00
Tom Boucher
4b1825e5f8 test(#1858): add regression tests for flat commands/gsd-*.md layout
Mirrors the #1160 installed-layout tests for the flat source layout
(<repo>/commands/gsd-<stem>.md, no commands/gsd/ subdir). Covers stem
extraction (strip gsd- prefix), requires parsing via shared parseRequires,
companion _calls_agents_ key parity, _resolveManifest flat-branch detection,
precedence (flat-empty falls through to installed), and a generative-parity
assertion that the flat loader and nested loader produce identical stem sets
for the real command tree.

Expected RED against unfixed capability-state.cts (_resolveManifest has no
flat branch; _loadFlatCommandsGsdManifest not exported).
2026-07-06 21:10:27 -04:00
Tom Boucher
6136aa20de docs(#2041): backfill changeset pr number to 2048 2026-07-06 20:59:38 -04:00
Tom Boucher
e95af39a8c fix(#2041): address code+security review findings
- add typeof guard so a non-string override passes through verbatim instead of
  crashing on .startsWith (preserves pre-fix no-crash behaviour) [LOW-1]
- use Object.hasOwn() for the alias lookup so __proto__/constructor cannot
  return a truthy non-string from the plain object literal [LOW-D3]
- cap the unmappable-override stderr warning at 64 chars so an oversized or
  secret-shaped value cannot leak in full to stderr/logs [LOW-D4]
- remove the unused mapClaudeOverrideForRuntime export (helpers are covered
  behaviourally via resolveModelInternal/resolveModelForTier) [NIT]
- add resolveModelForTier unmappable-override fall-through test (closes the
  mutation-score gap) [MEDIUM-1]
- add case-sensitivity contract test (Claude-Sonnet-5 passes through verbatim) [LOW-2]

Both orthogonal reviews returned APPROVE with no Critical/High findings.
2026-07-06 20:45:40 -04:00
Tom Boucher
13e40fcbf7 docs(#2041): add changeset fragment for model_overrides alias fix 2026-07-06 20:45:40 -04:00
Tom Boucher
f214f1320d fix(#2041): map model_overrides full claude IDs to agent-tool aliases
model_overrides values that are full Claude model IDs (claude-sonnet-5,
claude-opus-4-8, claude-haiku-4-5, claude-fable-5) were returned verbatim on
the claude runtime and handed to the Claude Agent tool, whose typed model
parameter documents only tier aliases (opus/sonnet/haiku/fable). The
model_policy path already mapped full IDs -> aliases via
CLAUDE_POLICY_ID_TO_ALIAS (#1144); model_overrides skipped that mapping, so
the two resolver paths produced different shapes for the same underlying
Claude model. The fix mirrors #1144 on the override path via a shared
mapClaudeOverrideForRuntime helper used by both resolveModelInternal and
resolveModelForTier. Bare aliases pass through verbatim; non-Claude runtimes
and non-Claude custom/vendor values keep full IDs verbatim (parity). An
unmappable Claude ID (e.g. claude-opus-4-5) warns once to stderr and falls
through to tier resolution, exactly as the model_policy path already does.
Alias mapping is also the documented best practice (prevents staleness when
new model versions ship).
2026-07-06 20:06:30 -04:00
Tom Boucher
9ea5519bc0 test(#2041): add regression test for model_overrides claude alias mapping
Mirrors the #1133 model_policy alias-mapping tests for the model_overrides
path. Covers AC1-AC6: mappable Claude full IDs (claude-sonnet-5/opus-4-8/
haiku-4-5/fable-5) resolve to aliases on runtime:claude; bare aliases pass
through; non-claude runtimes keep full IDs verbatim; unmappable Claude IDs
warn-once + fall through; resolveModelForTier escalation path also maps;
non-Claude custom/vendor values pass through verbatim (regression guards).

Expected RED against unfixed model-resolver.cts (override short-circuit at
lines 162-167 / 288-290 returns override verbatim with no alias mapping).
2026-07-06 19:33:43 -04:00
Tom Boucher
2aa013aecc Merge pull request #2044 from open-gsd/feat/1143-claude-orchestration-capability
feat(#1143): add claude-orchestration capability (Workflow backend)
2026-07-06 16:57:32 -04:00
Tom Boucher
3f7c9aa828 docs(#1143): add how-to for enabling and using the Claude orchestration backend 2026-07-06 16:33:24 -04:00
Tom Boucher
256d2aa95c fix(#1143): backfill changeset pr number (2044) 2026-07-06 16:03:26 -04:00
Tom Boucher
f433db8b88 fix(#1143): address adversarial review — full semver precedence, docs/reality alignment
- compareSemver: implement full SemVer 2.0.0 §11 pre-release identifier
  comparison (two pre-releases of the same triple now order correctly; was 0).
- capability description + fragment: scope the plan-checker/verifier claim
  (this capability delivers the parallel-execution backend; those gates remain
  inline until separately wired). Correct the 'each wave is one barrier' prose
  (a wave splits into multiple sequential parallel() barriers on files_modified
  overlap). Frame detect-backend CLI as a simulation harness; the pure function
  with the live host descriptor is the real detection seam.
- partitionStages docstring: 'near-minimal via greedy first-fit' (not 'fewest');
  document empty-files_modified behavior.
2026-07-06 15:41:19 -04:00
Tom Boucher
e3262d94d3 feat(capabilities): add claude-orchestration capability (Workflow backend) (#1143)
Default-off, BETA, claude-only capability adopting Claude Code's Workflow tool
(/effort ultracode, Agent SDK >= v0.3.149) as an optional parallel-execution
backend for the GSD loop. Restores the wave parallelism + plan-checker + verifier
that #853 forces inline on Claude Code, and folds gsd-ultraplan-phase under one
runtime gate.

- Pure fail-closed core (src/claude-orchestration.cts): detectWorkflowBackend
  (gate ladder: enabled -> Claude -> backend != inline -> nested+background host
  -> valid Agent SDK -> SDK >= floor; every miss degrades to inline) and
  emitWorkflowScript (waves -> parallel() barriers, plans -> gsd-executor +
  worktree, files_modified overlap -> separate stages, resumeFromRunId, budget).
  All interpolated identifiers validated script-safe; briefs JSON-quoted.
- claude-orchestration command family (gsd-tools claude-orchestration
  detect-backend|emit-workflow) for orchestrator invocation.
- Two gated loop contributions at wired points (execute:wave:post, plan:post);
  federated config keys (enabled/execution_backend/min_agent_sdk_version).
- ADR-1143 implementation amendment; CONTEXT.md glossary entry; explanation doc.

On any runtime lacking the Workflow tool, behaviour is byte-identical to today.

closes #1143
2026-07-06 15:18:23 -04:00
Dave
91998dcbca chore(#1820): regen goldens + workflow size baseline after rebase onto next 2026-07-06 14:41:00 -04:00
Dave
0a7d41c3f0 docs(#1820): add ADR-1820 for the spec-section module seam, fallback toggle, and SPEC↔probe precedence contract
Documents the new architectural surface #1820 introduces, per the
contributor-standards ADR requirement (a new Module seam that other code
will depend on):

- The spec-section detection Module seam (src/spec-section.cts) and its
  locked exported surface, supply rule, suffix-tolerant header invariant,
  and ownership boundary (detection only).
- The workflow.specless_probe_fallback toggle as a policy decision
  (default-on, disableable cost-gate over the fallback INVOCATION path, not
  the verifier<->predicate contract) — records the maintainer 857:66 ruling
  rather than amending it.
- The SPEC-supplied <-> probe-derived precedence & authoring contract:
  section-level precedence (a SPEC-supplied section is never re-run), one
  projectProhibitions serializer (no second producer), descriptor-less
  fallback predicates flag/abstain (never green, never auto-dismissed),
  no-silent-drop equality.

Does not restate ADR-857/550/1606; cross-references them. Resolves the
sole remaining review blocker on #1835.

Refs #1820

Claude-Session: https://claude.ai/code/session_017vYn26e3nkDNxcpty1ciPJ
2026-07-06 14:40:38 -04:00
Dave
5708c4a43b docs(#1820): add CONTEXT.md Spec-Section Helper Module entry (R1 review)
Addresses trek-e's R1 blocker: src/spec-section.cts is a new named module
but had no ## Domain terms entry in CONTEXT.md (INVENTORY.md had one).
Adds the ### Spec-Section Helper Module paragraph covering the owned
surface (SpecSectionKey, SECTION_HEADERS, SectionStatus, countSectionDataRows,
specSectionStatus), the suffix-tolerant header matching invariant, the
supplied = present AND dataRows > 0 rule, and the source-of-truth note
(gitignored per ADR-457). Maintainer owns final wording.
2026-07-06 14:40:38 -04:00
Dave
751e24a1ce chore(#1820): add changeset fragment for #1835 2026-07-06 14:40:38 -04:00
Dave
7ef834cabc feat(#1820): spec-optional predicate rail — author probe predicates into must_haves when SPEC omits them 2026-07-06 14:40:38 -04:00