3 Commits

Author SHA1 Message Date
Jakub Zych
a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00
Tom Boucher
385ed619f1 fix(#4487): stamp broken-windows ledger entries with the resolved milestone (#4583)
* enhance(#4487): stamp windows-ledger entries with the resolved milestone

Broken-windows ledger entries (`.planning/WINDOWS.md`) carry `phase` as
a bare number. Phase numbers are unique only within one active phases/
directory -- `milestone complete` archives phases and frees their
numbers for reuse, so two milestones routinely produce entries sharing
the same phase value with nothing distinguishing them. Since
`/gsd-ship` blocks while any entry is open, an already-archived
milestone's open entries could silently block shipping the CURRENT
milestone, with no supported way to attribute which entry belonged to
which milestone short of manually cross-referencing MILESTONES.md
timestamps against decision IDs that happened to appear in description
prose.

Added an optional `milestone: string | null` field to WindowEntry,
stamped by `windows append` (cmdWindowsAppend, which already does file
I/O) from the workstream's resolved milestone version. Reused the
existing `readCurrentMilestoneVersion` (workstream-inventory.cts --
STATE.md `milestone:` frontmatter first, ROADMAP.md in-progress marker
as fallback) rather than writing a parallel implementation: exported it
via that module's existing `export = {...}` CJS-interop convention
(matching the `import ... = require(...)` pattern already used in
workstream.cts/init.cts). appendWindow itself stays pure -- it accepts
milestone as an optional input field and passes it through; only the
CLI-facing cmdWindowsAppend resolves it from disk.

Backward compatible by construction: validateEntryShape does NOT add
`milestone` to its required fields, so an existing ledger entry with no
milestone key at all parses without error and reads back as null --
exactly "recorded before this change," no migration needed. The
rendered markdown table is deliberately left unchanged (the issue's own
words: "the JSON is the source of truth"); adding a table column would
be a separate, larger change than adding an optional JSON field.

Two smaller gaps the issue itself flags as separable ("happy to split
them out") are explicitly NOT addressed here: no verb to amend an
entry's description, and the table/JSON drift-repair advice that can
destroy table-only edits on a parse failure.

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

* fix(#4487): preserve absent-vs-null milestone through parse/render roundtrip

validateEntryShape stamped an explicit `milestone: null` onto every
entry lacking the key, so a pre-#4487 ledger entry gained permanent
JSON churn ("milestone": null) the first time ANY entry in the ledger
was touched -- breaking the pure parse/render roundtrip-identity
property test (render(parse(render(ledger))) must equal render(ledger))
and, in real usage, contaminating unrelated entries' diffs on every
append/waive/fixed of an old ledger.

Fixed by distinguishing "key genuinely absent" (undefined -- JSON.
stringify drops it, matching pre-#4487 behavior exactly) from "recorded
but unresolvable" (explicit null, the real signal appendWindow stamps
on brand-new entries). WindowEntry.milestone is now optional
(`milestone?: string | null`) so returning undefined type-checks.

Updated tests/broken-windows.test.cjs's roundtrip property generator to
exercise all three states (absent/null/string) -- its prior silence on
this field is exactly what let the regression through. Also corrected
the earlier backward-compatibility test's assertion: a pre-#4487 entry
reads as milestone: undefined, not null, and re-rendering it must not
introduce a milestone key at all.

Also ran npm run regen:derived: docs/features/broken-windows-ledger.md
(edited in an earlier commit) had never been propagated to its
generated docs/FEATURES.md projection, which is what was independently
failing tests/features-index-gate.test.cjs and, as a side effect of
staleness, tripping tests/fragment-single-edit-propagation.install.
test.cjs's second-source-surface check.

Manually verified via the compiled lib (500 fast-check iterations plus
direct legacy/new-entry roundtrip checks) before wiring the test file,
since this repo blocks local node --test.

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

* fix(#4487): materialize milestone via conditional spread, not undefined assignment

An object literal property set to `milestone: undefined` is still an
OWN property -- `'milestone' in entry` reads true regardless of the
assigned value, only JSON.stringify treats undefined specially. My
prior commit's own new backward-compat test asserted `'milestone' in
entry === false` for a pre-#4487 entry and failed on exactly this.
Switched to conditionally spreading the key in only when the source
object actually had it, so a genuinely absent milestone is not
materialized at all -- matching both the `in` check and JSON
serialization. Re-verified via the compiled lib (500 fast-check
roundtrip iterations, plus the specific in/undefined/JSON assertions
the failing test makes) before re-running gsd-test.

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

* docs(#4487): backfill changeset pr number to 4583

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

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 15:37:50 -04:00
Tom Boucher
36375513b9 feat(#3840): generate docs/FEATURES.md from per-feature fragments (#3845)
* feat(#3840): generate docs/FEATURES.md from per-feature fragments

docs/FEATURES.md was hand-maintained, and every feature PR wrote into two
shared mutable cells: the '### N.' heading whose integer was hand-allocated at
authoring time, and the hand-maintained table of contents. Concurrent PRs all
picked the same next integer, and two PRs adding differently numbered features
still collided on the TOC. #3831 was renumbered 165 -> 166 -> 167 -> 168 across
successive rebases, each collision also costing a full matrix verification run
because the sha-keyed pass marker dies with the rebase.

Mechanism: one fragment per feature at docs/features/<slug>.md carrying
id/title/group (and an optional order) in frontmatter, consolidated by
scripts/gen-features.cjs --write|--check into a marker-delimited region of
docs/FEATURES.md that holds BOTH the TOC and every section body. Group headings
and their order are derived too - a group sorts by its lowest-ordered member -
so there is no shared registry to edit either; optional per-group prose lives in
docs/features/_groups/<slug>.md. A contributor adds exactly one new file.
Wired into regen:derived and lint:generated-sync alongside the eight existing
generators, matching gen-adr-index.cjs's CLI shape and typed-REASON reporting.

Migration froze all 168 existing numbers verbatim: identical section set,
identical order, identical bodies. Two defects found in the tree are fixed
inline rather than carried forward - the '## Related' block had been spliced
into the middle of the document, orphaning §142's Reference line, and four
inbound anchors were already broken on next (FEATURES.md#runtime-identity in
two files, and #143-spec-phase-edge-completeness-probe off by one). Since the
repo has no link checker, --check now validates every inbound
FEATURES.md#anchor by resolved target, so that class cannot ship silently
again; locale FEATURES.md files resolve elsewhere and stay out of scope.

Refs #3840

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

* fix(#3840): carry upstream §69 delta into its fragment and harden the generator

Review found section 69 missing '[--strict]' and REQ-STATE-05/06 versus
origin/next. Root cause was a stale base, not extraction loss: those lines
landed in 394bf384b (#3844) AFTER this branch forked at 63abcface, and
'git diff 63abcface origin/next -- docs/FEATURES.md' is exactly that hunk.
Merging origin/next auto-applied the hunk into the GENERATED region, which
--check immediately reported as stale; the delta is now carried in
docs/features/statemd-consistency-gates.md and regenerated from there.

--write is now fail-closed. It previously rendered the region even with
violations outstanding, warning only on stderr and exiting 0, so a
'--write && git commit' chain could commit a FEATURES.md carrying two
colliding sections. It now refuses and exits 1; --force is the explicit
override and says so in the report. The test that pinned the old behavior now
pins the refusal, plus the --force override and its scoping.

Marker forgery is rejected at two layers. A fragment body containing
'<!-- FEATURES:START' or '<!-- FEATURES:END' is a typed
body_forges_region_marker violation (fragments and group notes alike), and
spliceIntoFeatures anchors the end boundary with lastIndexOf instead of
indexOf, so a marker that reaches the document by any other route can only
make the generated region grow, never shrink. Matching is on marker PREFIXES,
so a decorated variant comment cannot slip past.

Symlinked corpus entries are refused with a typed dirent_not_regular_file
rather than read. A fork PR could otherwise commit docs/features/evil.md as a
symlink to any readable path and have the generator inline those bytes into
the committed docs/FEATURES.md on the next regen.

Equivalence re-verified with a method that cannot cancel out. The first
check extracted both operands with the same body-normalising helper, so
anything that helper dropped was dropped on both sides. The replacement runs
two independent passes: a global content-line multiset diff with no
per-section logic at all (0 gained, 19 lost, all 19 the stale hand-written
mini-TOC links this change deliberately deletes), and a per-section
byte-exact body diff carrying a coverage assertion that fails loudly per file
when the extractor accounts for fewer lines than the file contains. That
assertion caught two blind spots in the checker itself. 168/168 sections
present, order identical, one intended body difference (§142 regains the
Reference line orphaned by the misplaced '## Related' block).

Refs #3840

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

* chore(#3840): backfill changeset PR number

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

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:49:00 -04:00