Fold 51 issue-named CLI black-box + scripts-tooling regression files into their
canonical module suites (runtime-launcher-parity, worktree-safety, install-*, managed-hooks,
read-guard, capability-registry, etc.), plus a NEW slash-command-namespace.test.cjs grouping
the 4 slash/colon-namespace-leak invariant suites that had no canonical owner. Verbatim
block-scoped describe wrappers; 427 subtests conserved 1:1.
Host-env pre-check (per B2): no CLI-receiving host sets a redirecting GSD_WORKSTREAM/GSD_PROJECT
value. One folded suite (bug-3668 runtime resolver) creates an extension-less PATH gsd-tools
stub + bash -c; co-locating it with the host's chmodSync tripped local/no-unguarded-nonportable-exec,
so it's now Windows-guarded (skip on win32) matching the host suite's own bash -c guard.
Regenerates regression-name allowlist (222->182), ratchets file-count allowlist (graphify 7->6,
docs entry removed), makes 26 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456;
prunes stale ids). Repoints 13 tests/ references across CONTEXT.md, COMMANDS.md/FEATURES.md
(EN + ja/ko/pt/zh) and ADR-0002. lint:ci green.
Part of epic #1969. Closes#1975.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final epic-#1969 batch. Fold 22 issue-named files: the 4 genuine repo-wide invariant
scans (551-eslint-bin-lib-coverage, bug-3054 stale /gsd-next, bug-3810 no-gsd-sdk-runtime-refs,
feat-3593 cli-negative-universal) into a NEW shared repo-invariants.test.cjs; the other 18 as
singletons into their nearest module suite (model-resolver, codex-config, runtime-converters,
security, state-transition, worktree-safety, roadmap-parser, etc.). Verbatim block-scoped
describe wrappers; 334 subtests conserved 1:1.
Host-env pre-check (B2+B6): the 6 CLI folds into GSD_TEST_MODE-setting hosts (model-resolver/
codex-config/runtime-converters) are benign — each origin independently sets GSD_TEST_MODE=1
itself (idempotent), unlike the B6 real-install case.
Regenerates regression-name allowlist (222->213), ratchets file-count allowlist (state 17->16),
makes 7 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes stale ids).
Repoints 2 tests/ refs in docs/TESTING-SUITES.md. lint:ci green.
Part of epic #1969. Closes#1977.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold 27 issue-named installer/hooks/statusline/migration/reapply regression files
into their canonical module suites (installer-migrations, installer-migration-report,
gsd-statusline, reapply-verify-hunks, install-*, gsd-check-update-worker-platform-gate,
etc.). Verbatim block-scoped describe wrappers; 276 subtests conserved 1:1. No new files.
The one subdir origin (tests/installer-migrations/001-legacy-orphan-files) moved up one
level into installer-migrations.test.cjs; its single ../../ module require corrected to
../ so it resolves from tests/ root (verified). Host-env pre-check: no CLI-receiving host
sets a redirecting GSD_WORKSTREAM/GSD_PROJECT value.
Regenerates regression-name allowlist (222->205), ratchets file-count allowlist (verify
11->8, validate entry removed), makes 16 relocated allow-test-rule exemptions issue-ref-
compliant (ADR-456; prunes stale ids). Repoints 15 tests/ references across state-md.md
(EN + ja/ko/pt/zh). lint:ci green.
Part of epic #1969. Closes#1974.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold 48 issue-named workflow-markdown regression files into the canonical test
that owns each workflow aspect (execute-phase-*, plan-phase-*, quick-*, discuss-*,
worktree-cleanup, secure-phase, verify, update, settings, etc.), across 32 existing
suites. Verbatim block-scoped describe wrappers; 281 subtests conserved 1:1. No new
test files. Host-env pre-check: only gsd-settings-advanced spawns CLI and it sets no
GSD_WORKSTREAM/GSD_PROJECT value — no leak risk.
Regenerates regression-name allowlist (222->181), ratchets file-count allowlist
(verify 11->10), makes 30 relocated allow-test-rule exemptions issue-ref-compliant
(ADR-456; prunes 30 stale ids). lint:ci green.
Part of epic #1969. Closes#1973.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold 94 issue-named command/module regression files into the canonical test file
that owns each subject-under-test, across 52 existing suites (state, config, frontmatter,
roadmap-parser, capability-registry, shell-command-projection-dispatch, plan-phase-drift-guard,
health-validation, runtime-converters, commands, etc.). Verbatim block-scoped describe
wrappers; 881 subtests conserved 1:1. No new test files.
Host-env pre-check (per B2): the only GSD_WORKSTREAM/GSD_PROJECT-touching destinations
(intel, planning-workspace) clear those vars hermetically, so folded CLI tests are safe.
Regenerates regression-name allowlist (222->162), ratchets file-count allowlist across
8 buckets (validate entry removed after dropping <=2), makes 34 relocated allow-test-rule
exemptions issue-ref-compliant (ADR-456; prunes 34 stale ids). Repoints CONTEXT.md +
ADR-0002/443/1235/3524 test-file references. lint:ci green.
Part of epic #1969. Closes#1972.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold 48 issue-named gsd-tools CLI regression files into the canonical test file
that owns each subcommand subject (state, roadmap, phase, milestone, audit, config,
router/dispatch, stats, verify, health, etc.), preserving every assertion and its
origin issue number as provenance (block-scoped describe wrappers, 299 subtests
conserved 1:1). No monolithic gsd-tools.test.cjs created — routes into 18 existing
per-subject suites.
Removes 48 tests/ files. Regenerates regression-name allowlist (271->231), ratchets
the file-count allowlist across 6 buckets (audit/milestone/phase/roadmap/state/verify),
and makes 10 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes
10 stale ids). Repoints one CONTEXT.md symptom ref and ADR-3524's parity-test ref.
lint:ci green.
Part of epic #1969. Closes#1971.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate cmdStateSync, cmdStatePrune, cmdStateUpdate (state.cts) onto the
STATE.md Transition Module substrate and close the maintenance bug pair
#1760/#1761 (ADR-1769, epic #1769). Completes the substrate: all 10
lifecycle/maintenance transitions now route through transitionCore.
- Add {kind: 'sync'|'prune'|'update'} to StateTransitionIntent with syncCore,
pruneCore, updateCore in src/state-transition.cts.
- updateCore: body-only single-field update (strip/reassemble), mirroring the
pre-migration cmdStateUpdate contract.
- pruneCore: pure content→content section pruning (Decisions / Recently
Completed / resolved Blockers / Performance Metrics rows at or below cutoff),
byte-identical tokenizeHeadings splicing. Adapter owns currentPhase, dry-run,
and STATE-ARCHIVE.md.
- syncCore: body writes (Total Plans in Phase, Progress bar, Last Activity)
given disk-derived numbers. Adapter owns the disk scan + roadmap scope.
- #1760: cmdStatePrune now derives currentPhase from 'Current Phase' OR 'Phase'
(the canonical template emits 'Phase: X of Y'), so prune engages on
template-conformant STATE.md instead of bailing 'Only 0 phases'.
- #1761: cmdStateSync skips the Progress write (percent=null) when a milestone
version is set in frontmatter but the ROADMAP has no versioned heading for it
(milestone cannot be bounded). Projects without a milestone version are
unaffected. Leaves progress untouched rather than silently writing fallback-
derived wrong values.
- Regression: bug-1760 (prune engages on template field) + bug-1761 (sync
leaves progress untouched when unbounded). ADR-1769 marked Accepted.
All 82 transition + 515 regression tests pass.
Closes#1793
Migrate cmdStatePatch (state.cts) onto the STATE.md Transition Module substrate
and close the curated-field clobber bug class #1743/#1695 (ADR-1769, epic #1769).
- Add {kind: 'patch'} to StateTransitionIntent, with patchCore in
src/state-transition.cts. Applies each caller-supplied {field:value} pair via
stateReplaceField over the full content (body + frontmatter), tracking
updated vs. failed. data.updated/data.failed mirror the CLI output shape.
- Collapse cmdStatePatch to a transitionCore dispatch. Field-name validation
(security) and the resync-progress decision stay in the adapter.
- #1695/#1743 fix: extend the #1230 delta heuristic in readModifyWriteStateMd to
the curated current_phase_name, table-driven via
getFieldClassification('current_phase_name').preservation === 'preserve-always'.
When a write does NOT change the body Phase: source line, the curated
frontmatter value wins over syncStateFrontmatter's body re-derivation (which
harvests a wrong parenthetical aside — #1695). begin/planned/complete-phase
rewrite their body Phase line, so the delta does not fire for them and
current_phase_name still advances.
- Regression: bug-1695-state-patch-clobbers-phase-name.test.cjs — unrelated
patch preserves curated current_phase_name; patching the Phase source advances.
All 70 transition + 493 regression tests pass (state/phase/milestone + #905/#397/#3242 lineages).
Closes#1791
* fix(#983): rewrite bare .claude paths in Trae/Windsurf converters (Codex/Cline parity)
Both convertClaudeToWindsurfMarkdown and convertClaudeToTraeMarkdown only
handled trailing-slash .claude/ forms; bare ~/.claude and $HOME/.claude
references (e.g. configDir = ~/.claude, RUNTIME_CONFIG_DIR=".../$HOME/.claude")
survived conversion and pointed users at the wrong config dir.
Fix: add bare-form replacements using negative lookahead (?![\w-]) to protect
.claude-plugin and .claudeignore, mirroring Cline (#782) and Codex (#570) precedent.
Also adds CLAUDE_CONFIG_DIR -> WINDSURF_CONFIG_DIR / TRAE_CONFIG_DIR rewrite.
_applyRuntimeRewrites windsurf case gets matching \b-anchored bare-form lines,
mirroring the existing trae case.
Closes#983
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(#983): backfill changeset pr number (995)
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#974): error on graphify --budget with missing/non-numeric value instead of silent NaN no-op
When `--budget` was the last arg or followed by a non-numeric token,
parseInt(undefined/NaN-string, 10) produced NaN. NaN is falsy so both
the router check and applyBudget gate silently skipped budget trimming.
The query ran unbounded with no warning.
Fix: guard in graphify-command-router.cts — if args[budgetIdx+1] is
absent or parses to NaN, emit ERROR_REASON.USAGE and return early.
Defensive fix in graphify.cts: tighten `if (!budgetTokens)` →
`if (budgetTokens == null)` and `if (options.budget)` →
`if (options.budget != null)` so a real 0/NaN caller is handled
predictably by both independent guards.
Regression tests: 16 cases (unit/mock, subprocess, property-based)
covering boundary inputs: missing value, non-numeric, valid integers,
and fast-check properties over the budget parse contract.
Closes#974
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(#974): backfill changeset pr number (986)
* fix(#974): convert static property (b) to real fc.assert property with constrained generator + deterministic seed
The test named "property: --budget as last arg always produces usage error"
was a static test with no fc.assert — it only checked a single hardcoded
term ("someterm") and could never flake or produce a fast-check path. This
is a generator/property bug (case b): the test was mislabeled as a property
test but lacked parameterization.
Root-cause of CI path "46:0:0" / seed 42 failure: a naive parameterized
version without the !startsWith('--') filter could feed term='--budget',
causing args.indexOf('--budget') to hit index 2 (the term slot) rather than
index 3 (the flag slot), placing the router in a different code path. The
property still holds — NaN detection fires on rawBudget='--budget' — but
the assertion text referenced the wrong invariant, making the failure appear
spurious. Fix: constrain the generator to non-flag terms (filter out
strings starting with '--') and pass explicit { seed: 42, numRuns: 200 } to
fc.assert so the test is fully deterministic in CI regardless of GSD_FC_SEED.
No change to src/graphify-command-router.cts (router is correct).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(#974): constrain non-numeric budget property generator to genuinely-NaN values and pin fast-check seeds (CI determinism)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(#974): use a strict valid-term generator and pin seeds so budget property tests are deterministic
Replace fc.string({minLength:1}).filter(!startsWith('--')) term generators in
properties (b) and (d) with a shared validTerm = fc.stringMatching(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,29}$/)
that is alphanumeric-leading and contains no whitespace, flags, or sign-numerics.
This eliminates the class of CI failures where the old generator produced out-of-
contract inputs (" ", "+5", empty) that the router legitimately rejects for reasons
outside the budget-parse contract under test. Pin distinct seeds (1001–1004) on
every fc.assert for CI determinism. Stress-tested at numRuns=100000 per property
and across 10 seeds (1,2,7,13,42,43,44,99,12345,999999) at numRuns=2000 — all pass.
No src change: gsd-core/bin/lib/graphify-command-router.cjs is correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(#974): replace flaky term-fuzzing properties with deterministic examples; keep value-fuzzing properties
The validTerm regex /^[A-Za-z0-9][A-Za-z0-9_.-]{0,29}$/ admitted single-digit
strings like "0" which are falsy; the router's `if (!term)` guard fires before
the budget-missing-value path, producing a spurious errFn call. Properties (b)
and (d), which test the BUDGET contract (not term handling), are replaced with
deterministic example loops over fixed valid terms. Properties (a) and (c),
which fuzz the BUDGET VALUE with a fixed term, are kept unchanged (seed+numRuns
pinned). The validTerm generator is fully removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(#978): parse --force in milestone complete dispatcher so the guard's documented override works
The dispatcher built `{ name, archivePhases }` but never parsed `--force`, so
`options.force` was always `undefined` and the guard inside `cmdMilestoneComplete`
(which tells users to "Re-run with --force to override") could never be bypassed.
Add `const force = args.includes('--force')` and pass it into the options object.
The guard already honors `options.force` — no changes to milestone.cts needed.
Closes#978
* chore(#978): backfill changeset pr number (982)
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(#851): correct Codex adapter for generic multi_agent_v1 schema
The Codex skill adapter header in getCodexSkillAdapterHeader() documented
typed spawn_agent(agent_type=...) as a direct, unconditional mapping for all
Task()/Agent() calls. In sessions exposing only the generic multi_agent_v1
schema (message/items/fork_context — no agent_type field), this mapping is
silently invalid: the orchestrator cannot natively dispatch typed gsd-planner/
gsd-executor agents and may fall back to inline execution or produce errors.
Fix: Section C now requires schema detection before spawning. It documents the
typed mapping as conditional on the agent_type-capable schema (e.g. multi_agent_v2)
and introduces an explicitly-labeled generic-agent workaround for multi_agent_v1
sessions — read the agent TOML, inject its instructions as a role-preamble, and
call spawn_agent(message=...) — clearly marking the result as NOT equivalent to
typed gsd-planner/gsd-executor execution.
Regression test: tests/bug-851-codex-quick-adapter-agent-type-fallback.test.cjs
asserts schema-awareness language, the multi_agent_v1 fallback, the workaround
label, and backward compat with the existing bug-279 typed-spawn contract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: add changeset for PR #958
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#851): keep Codex adapter block consistent with materialized skill surface
The `~/.codex/agents/<agent-name>.toml` literal introduced in the #851 prose
was being rewritten to the real install path by `_applyRuntimeRewrites` (the
`~/.codex/` → pathPrefix substitution) before the SKILL.md was written to
disk. `getCodexSkillAdapterHeader()` still returned `~/.codex/agents/...` so
the test assertion (exact match between builder output and materialized file)
always failed.
Fix: replace the `~/.codex/agents/` literal with the runtime-neutral form
`agents/<agent-name>.toml` plus a parenthetical naming `$CODEX_HOME/` — which
is not matched by any rewrite pattern and survives the path-substitution step
unchanged. The #851 schema-detection + generic-subagent-fallback intent is
fully preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(#851): resolve active Codex config root in fallback; strengthen tests (adversarial review)
- Rewrites the generic-agent workaround step 1 to explicitly describe
active config root resolution (priority: $CODEX_HOME → --config-dir →
--local .codex → default global dir) without the literal ~/.codex/
substring that _applyRuntimeRewrites replaces, preventing bug-3582
divergence.
- Replaces OR/loose-includes test assertions in bug-851 with AND-logic
checks covering all four required elements: (a) schema-detection step,
(b) active-config-root resolution for the TOML path including all three
override mechanisms, (c) NOT-equivalent-to-typed-gsd-planner/gsd-executor
label, and (d) fail-closed rule when typed dispatch is mandatory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(#851): register bug-851/947/948/950 in lint-regression-test-names allowlist
The ratchet (622e4be) bans NEW top-level bug-NNNN test files; the four
sibling PRs (#851, #947, #948, #950) landed AFTER the baseline was cut,
so their test files were not yet grandfathered. Add all four to the
identity allowlist so lint-regression-test-names passes.
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>
13 bug-* files landed upstream between the audit baseline and this branch's
rebase; they predate the ratchet policy, so they are grandfathered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
244 one-off bug-* files (~38% of the suite) are grandfathered in
lint-regression-test-names.allowlist.json; new ones fail lint with
fold-into-module guidance, and deletions force allowlist pruning so the
baseline only shrinks. Wired into npm run lint:ci (new single entry point
for every CI lint). Policy documented in docs/TESTING-SUITES.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>