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>
cmdPhasesClear hard-deleted committed phase directories (rmSync) with no
archive, so browsable phase history was silently lost at a milestone switch.
The #1447 dirty-tree guard was a no-op for the common committed case (a clean
tree passes the guard, then rmSync destroyed the dirs, leaving orphaned
uncommitted deletions and no archive).
Archive-then-remove: move each non-999 phase dir to
milestones/<version>-phases/ (version from getMilestoneInfo; timestamp
fallback; collision-safe) using retryRenameSync — mirroring the existing
archivePhases path in cmdMilestoneComplete. The #1447 uncommitted-changes
guard is retained as a secondary backstop.
Tests in tests/new-milestone-clear-phases.test.cjs updated: phase content is
asserted to SURVIVE in milestones/*-phases/ (archived), not be destroyed —
including the committed-dirs case that previously codified the no-op.
Closes#1871
* 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>
The windows-test-parity ratchet greps test source for fs.rmSync-without-
maxRetries (and six other Windows-portability anti-patterns), failing when an
integer offender COUNT exceeds a frozen baseline (rmSync: 95). A count ratchet
is a Goodhart metric: fixing one offender and adding another keeps the count
constant, so a new defect slips through green. Replace it — and every other
count ratchet in the repo — with a layered, masking-proof design.
Behavioral seam test
- tests/helpers-cleanup.test.cjs proves helpers.cleanup() carries the Windows
EBUSY retry budget. cleanup() delegates retries to Node's fs.rmSync via
maxRetries (it owns no loop), so the test asserts the option contract
(recursive/force/maxRetries>0/retryDelay>0) + real-FS removal + the cwd-guard,
rather than a loop that does not exist. The EBUSY risk is now tested ONCE at
the helper, not approximated textually at every call site.
Write-time ESLint rule (AST-accurate, replaces the grep)
- eslint-rules/no-raw-rmsync-in-tests.cjs (error in tests/**/*.test.cjs) bans
raw fs.rmSync, steering to cleanup(). Catches member, computed (fs['rmSync']),
destructured and aliased forms; escape hatch is inline
`// eslint-disable-next-line local/no-raw-rmsync-in-tests -- <reason>` only.
- Migrated 336 raw fs.rmSync teardown calls across ~116 test files to cleanup().
~18 genuinely load-bearing sites (mid-test SUT/fault-injection removals,
error-swallowing or name-colliding local teardown helpers) keep the raw call
with an inline eslint-disable + reason.
Shared anti-ratchet primitive
- scripts/lib/allowlist-ratchet.cjs:
- assertWithinAllowlist: fails on NOVEL ids (new offender introduced) AND on
STALE ids (a known offender was fixed but not pruned) — identity, not count,
and a ratchet DOWN toward zero.
- assertTightCeiling: a size/length budget whose ceiling must stay within a
grace band of the high-water mark, so budgets may only tighten, never creep.
Ratchets converted onto the primitive
- windows-test-parity-guard.test.cjs: rmSync rule deleted (now ESLint-enforced);
the remaining six patterns moved from integer baselines to named-set
allowlists with ratchet-down.
- scripts/lint-test-file-count.{cjs,allowlist.json}: per-module integer counts →
named filename sets (closes the swap-a-file-keep-the-count blind spot); a
module dropping under cap now FAILS to force pruning its allowlist entry.
- enh-2790 skill-count `<= 63` → named skill allowlist (ratchets toward ~58).
Size budgets hardened (tighten-only)
- agent-size / workflow-size / feat-3039 help-tiered: ceilings lowered to the
current high-water mark and an assertTightCeiling anti-creep check added per
tier. Fixed external-contract limits (description ≤100 chars, agent ≤100 KB)
are intentionally left as-is — they are not grandfathered creeping budgets.
No user-facing behavior change (tests + tooling only); no USER_FACING_PREFIXES
touched, so no changeset fragment is required.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
phases clear now checks for phase dirs before deleting. If any exist and
--confirm is absent, the command exits non-zero with a message showing the
count and how to proceed. Empty phases dir (nothing to delete) succeeds
without --confirm unchanged.
Updates new-milestone.md workflow to pass --confirm (intentional programmatic
caller). Updates existing new-milestone-clear-phases tests to match new API.
Closes#1826
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(tests): standardize to node:assert/strict and t.after() per CONTRIBUTING.md
- Replace require('node:assert') with require('node:assert/strict') across
all 73 test files to enforce strict equality (no type coercion)
- Replace try/finally cleanup blocks with t.after() hooks in core.test.cjs
and hooks-opt-in.test.cjs per the test lifecycle standards
- Utility functions in codex-config and security-scan retain try/finally
as that is appropriate for per-function resource guards, not lifecycle hooks
Closes#1674
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf(tests): add --test-concurrency=4 to test runner for parallel file execution
Node.js --test-concurrency controls how many test files run as parallel child
processes. Set to 4 by default, configurable via TEST_CONCURRENCY env var.
Fixes tests at a known level rather than inheriting os.availableParallelism()
which varies across CI environments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): allowlist verify.test.cjs in prompt-injection scanner
tests/verify.test.cjs uses <human>...</human> as GSD phase task-type
XML (meaning "a human should verify this step"), which matches the
scanner's fake-message-boundary pattern for LLM APIs. This is a
false positive — add it to the allowlist alongside the other test files
that legitimately contain injection-adjacent patterns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: clear phases directory when creating new milestone (#1588)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: re-run CI with Windows pointer lifecycle fix in main
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>