Files
msd-core/docs
Tom Boucher 481ac7c71b fix(#2946): run milestone complete unstarted-phase guard independent of STATE (#3081)
* test(#2946): milestone complete unstarted-phase guard fails open on STATE desync

Row 1 of the test matrix: the regression test that fails first. Adds seven
cases to tests/milestone.test.cjs covering the desync, absent, no-file,
--force-override, mismatch-WARNING, fresh-project-noop, and sentinel-skip
behaviors. RED on next: the guard's entire scan is nested inside
`if (stateVersion && stateVersion === version)`, so any STATE.md milestone:
value that does not exactly equal the version argument skips the scan with
no warning — functionally an implicit --force on a one-way-door operation.

* fix(#2946): run milestone complete unstarted-phase guard independent of STATE

The entire ROADMAP phase-directory scan was nested inside
`if (stateVersion && stateVersion === version)`, so any STATE.md milestone:
value that did not exactly string-equal the version argument — a desynced
value, or no milestone: field at all — skipped the scan with no warning,
functionally an implicit --force. The operation the guard fronts is a one-way
door: ROADMAP.md and REQUIREMENTS.md are archived and phase directories are
MOVED into .planning/milestones/<version>-phases/.

The scan was already driven by the version argument through
getMilestonePhaseFilter / extractCurrentMilestone; the STATE match was a
redundant second gate that shadowed and broke it. Decouple: the scan now runs
whenever --force is absent, and a present-but-mismatched STATE milestone:
field emits a WARNING naming both values so the suspicious condition is
visible rather than silent. A fresh project with no Phase headings in the
scoped slice still yields an empty scan (no false positives) — the intent the
STATE-match short-circuit was reaching for, now achieved by the scan itself.

* docs(#2946): document milestone complete --force, --dry-run, and the unstarted-phase guard

The CLI-TOOLS reference signature omitted --force and --dry-run entirely,
and neither the unstarted-phase guard nor its override was documented
anywhere user-facing. Add a flags table and a factual guard description
to the Reference page (CLI-TOOLS.md), and a practical guard note to the
/gsd-complete-milestone How-to (COMMANDS.md) covering what to do when
the guard fires and the new STATE-mismatch WARNING (#2946).

American English per CONTRIBUTING.md language policy.

* fix(#2946): emit STATE-mismatch WARNING as JSON in --json-errors mode

Follow-up to the guard decoupling: a structured caller using --json-errors
parses stderr line-by-line as JSON, so the plain-text WARNING would break
such a parser. Honor getJsonErrorMode() and emit a structured JSON object
({ ok, level, message }) in that mode, plain text otherwise — mirroring
io.cts error()'s JSON shape. Addresses the isolated-review observation
(~45% but credible, since --json-errors is a documented CLI flag).

* fix(#2946): address review — drop JSON-mode WARNING scope creep, tighten test assertions

Standards + spec review findings (code-review two-axis + isolated adversarial):

1. The --json-errors JSON WARNING branch (commit dee719404) was scope creep
   the issue never asked for, AND emitted ok:true for a suspicious-condition
   warning (a category error — a stderr JSON parser keying on ok would treat
   the suspicious state as success), AND its comment falsely claimed to mirror
   io.cts error()'s {ok:false,reason,message} shape. Dropped: the WARNING is
   now plain-text stderr only, matching the existing [gsd-tools] WARNING
   convention (state.cts). The issue asked for 'at minimum warn', not a
   structured JSON surface.

2. The WARNING test asserted on /WARNING/ regex (raw-text matching on stderr
   prose). Tightened to assert on the stable operator-facing tokens — the
   WARNING: marker and both version literals the operator must see — not the
   surrounding formatter prose. Added a paired negative test confirming no
   WARNING is emitted for an absent milestone: field (a missing declaration
   is a normal fresh-project state, not suspicious drift).

* fix(#2946): sanitize STATE milestone value before stderr WARNING interpolation

Security review (minor): stateVersion is read from a user-controlled file
(STATE.md) and is not validated like the CLI version arg. Sanitize before
interpolating into the WARNING — strip ANSI/control chars
(/[\x00-\x1f\x7f]/g -> '?') and truncate to 80 chars — so a corrupted or
hostile STATE.md cannot echo terminal escapes or secret-looking strings
verbatim into a CI log or terminal aggregator (CONTRIBUTING.md security:
secret-looking values in stderr). version is already constrained to
[A-Za-z0-9._-] by ARCHIVE_VERSION_LABEL_RE upstream, so it needs no
sanitization.

* test(#2946): correct stale fixtures that relied on the guard being silently disabled

Four pre-existing tests broke under the #2946 fix because their fixtures
only passed thanks to the bug — the unstarted-phase guard was skipping on
STATE mismatch, so fixtures with missing or non-matching phase directories
slipped through. The tests exercise version-forwarding / version-scoping,
not the guard, so give them legitimate directories:

- milestone.test.cjs #3043: dirs were '103.old'/'104.old'/'108.new' (dot),
  which phaseTokenMatches rejects — renamed to hyphen form. The v3.6 stats
  scoping still yields 1 phase (getMilestonePhaseFilter scopes correctly);
  the guard now sees all three phases as having directories.
- milestone-archive.test.cjs 'returns version in response data': ROADMAP
  listed Phase 1 but no directory was created. Added 01-foundation so the
  scan is satisfied.

Per CONTRIBUTING.md, test-fixture corrections land as their own test:
commit, not bundled into fix: (release hotfix cherry-pick routes by prefix).

* chore(#2946): backfill changeset PR number 3081

---------

Co-authored-by: sim <sim@local>
2026-08-05 10:42:42 -04:00
..

GSD Core documentation

Documentation is organised into four quadrants: tutorials help you learn by doing, how-to guides solve specific tasks, reference states authoritative facts, and explanation explores concepts and design decisions.

Language versions: English · Português (pt-BR) · 日本語 · 简体中文


Tutorials


How-to guides


Reference

  • Commands — every command with flags and examples
  • Configuration — full config schema, model profiles, git branching strategies
  • CLI tools — gsd-tools.cjs programmatic API for workflows and agents
  • Features — complete feature index
  • Inventory — installed skills and surface map
  • STATE.md schema — field-by-field reference for .planning/STATE.md
  • CONTEXT.md schema — field-by-field reference for .planning/phases/<N>/CONTEXT.md
  • PLAN.md schema — field-by-field reference for .planning/phases/<N>/PLAN.md
  • Planning artifacts — all .planning/ files and their roles
  • Review and verification capabilities — code review, security, and Nyquist capability ownership and hook contracts
  • Gate predicates — canonical specification of the phase-gate predicate vocabulary
  • Capability matrix — generated catalogue of every capability's role, tier, extension points, hook kinds, and engines.gsd
  • Capability manifest — the full capability.json schema and validation rules
  • gsd capability command — install / update / remove / list reference for third-party capabilities
  • Workflow fragments — in-file <!-- gsd:section --> marker grammar for fragmentizing workflow markdown at emission time
  • Reviewer Lane Registry — generated catalogue of third-party reviewer lanes, with their flags, transport, and install commands

Explanation