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.
This commit is contained in:
Jakub Zych
2026-10-06 01:47:40 +02:00
parent fe069b2a56
commit a9a7a328e6
2763 changed files with 78465 additions and 78434 deletions

View File

@@ -142,7 +142,7 @@ function isMilestoneShippedInRoadmap(content: string, version: string): boolean
return false;
}
// #2761 B1: matches a bracket MILESTONE heading's intro (`[GSD.02]`) at the
// #2761 B1: matches a bracket MILESTONE heading's intro (`[MSD.02]`) at the
// START of a heading's text, capturing the bracket id in group 1. Shared by
// isBracketMilestoneBoundary below — the ONE recognizer for "is this heading
// bracket-shaped", so computeSectionEnd and the preambleCutoff scan cannot
@@ -152,13 +152,13 @@ const BRACKET_HEADING_INTRO_RE = new RegExp(`^\\[(${BRACKET_ID_SRC})\\]`, 'i');
// #2761 B2 (round-2 fix, Blocker 2): ADR-612 Decision 1's own discriminator
// (docs/adr/612-bracket-phase-id-convention.md:56) — "a phase heading is a
// bracket followed by a digit-then-colon (`[GSD.02] 05:`); a milestone
// bracket followed by a digit-then-colon (`[MSD.02] 05:`); a milestone
// heading is a bracket followed by a name" — is CONTENT, not heading level.
// The prior `h.level <= 2` level cap broke on a level-3 bracket milestone
// heading (`### [GSD.02] Foundation`): its own level-3 phase children
// (`#### [GSD.02] 01: One`) never reached this check at all (excluded
// heading (`### [MSD.02] Foundation`): its own level-3 phase children
// (`#### [MSD.02] 01: One`) never reached this check at all (excluded
// upstream by the `h.level > level` sibling-depth filter), but a level-3
// SIBLING milestone heading (`### [GSD.03] Later`) was ALSO excluded by the
// SIBLING milestone heading (`### [MSD.03] Later`) was ALSO excluded by the
// level cap, so the section ran to EOF instead of stopping there — trek-e's
// original #612 defect, reopened on any milestone heading below level 2.
//
@@ -168,7 +168,7 @@ const BRACKET_HEADING_INTRO_RE = new RegExp(`^\\[(${BRACKET_ID_SRC})\\]`, 'i');
// digit + optional-tag + colon tail every phase-heading counter in this file
// already spells (mirrors the `([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:`
// shape at :nnn below) — not a re-typed grammar. Covers the dotted sub-phase
// heading form too (`[GSD.02] 05.03:`) via the same `[\w][\w.-]*` token,
// heading form too (`[MSD.02] 05.03:`) via the same `[\w][\w.-]*` token,
// which admits an embedded `.`.
const BRACKET_PHASE_TAIL_RE = new RegExp(
`^${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, 'bracket', false)}[\\w][\\w.-]*(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`,
@@ -183,7 +183,7 @@ const BRACKET_PHASE_TAIL_RE = new RegExp(
* dotted sub-phase form), which must never terminate a milestone's own
* section, or (b) a heading that names the SAME milestone already selected,
* which is a CONTINUATION of the current milestone's own section (a
* version-less split like `## [GSD.02] Foundation (Phase Details)`), not the
* version-less split like `## [MSD.02] Foundation (Phase Details)`), not the
* boundary to a DIFFERENT one?
*
* `level` is the CANDIDATE heading's own depth (`h.level`), capped at 3 — a
@@ -204,8 +204,8 @@ function isBracketMilestoneBoundary(headingText: string, level: number, selected
if (level > 3) return false;
const introMatch = BRACKET_HEADING_INTRO_RE.exec(headingText);
if (!introMatch) return false;
// #2761 B2: a bracket PHASE heading (`[GSD.02] 05:`, or the dotted
// sub-phase form `[GSD.02] 05.03:`) is never a milestone boundary,
// #2761 B2: a bracket PHASE heading (`[MSD.02] 05:`, or the dotted
// sub-phase form `[MSD.02] 05.03:`) is never a milestone boundary,
// regardless of level.
if (BRACKET_PHASE_TAIL_RE.test(headingText)) return false;
// #2761 B1: fold-before-identity — this branch's own convention
@@ -224,7 +224,7 @@ function isBracketMilestoneBoundary(headingText: string, level: number, selected
*
* Used ONLY at the preambleCutoff scan below, to distinguish a genuine prior
* or later sibling milestone — whose subtree contains a real phase heading
* carrying its bracket id (`## [GSD.01] Setup` / `### [GSD.01] 01: …`)
* carrying its bracket id (`## [MSD.01] Setup` / `### [MSD.01] 01: …`)
* — from an unrelated bracket-shaped PROSE heading sitting above the current
* milestone's own content. Same-id-ness alone is insufficient: round-4 F1's
* `[ADR.612] Heading convention` owns `[ADR.612] Examples`, but that child is
@@ -450,7 +450,7 @@ const MILESTONE_HEADING_SIGNAL_PATTERN = /v\d+\.\d+|✅|📋|🚧|\bMilestone\b/
* / `## Phase 3:`), so "strictly deeper" never matches for either sibling
* and the predicate answers false, letting the whole-document count
* conflate them exactly as #1761 did. (b) #3204 reintroduced — the
* bundled greenfield template itself (`gsd-core/templates/roadmap.md:149-171`:
* bundled greenfield template itself (`msd-core/templates/roadmap.md:149-171`:
* `## Phases` -> `### 🚧 v1.1 — …` -> `#### Phase 5: …`) nests a Phase
* heading arbitrarily deep under EVERY ancestor in the chain, so a
* generic wrapper heading ("Phases") with no milestone meaning of its own
@@ -601,13 +601,13 @@ function sliceMilestoneWindow(content: string, version: string): string | null {
*/
// #3641: the bracket-convention phase-ENTRY heading shape — ADR-612 Decision
// 1's own discriminator: a phase heading is a bracket followed by a
// DIGIT-then-colon (`### [GSD.04] 01: Name`); a bracket followed by a NAME
// DIGIT-then-colon (`### [MSD.04] 01: Name`); a bracket followed by a NAME
// is a milestone heading and must never count. Every fragment interpolates a
// single-owner export from phase-id.cts — the heading intro
// (PHASE_HEADING_PREFIX_SRC: a `[...]` bracket optionally followed by a
// `Phase ` label, or a bare `Phase ` label), the digit-bearing token
// (PHASE_NUMBER_TOKEN_SOURCE, which also covers the dotted sub-phase form
// `[GSD.02] 05.03:`), and the optional pre-colon tag
// `[MSD.02] 05.03:`), and the optional pre-colon tag
// (OPTIONAL_PHASE_TAG_SOURCE) — never a re-typed grammar. Tested IN
// ADDITION to the legacy pattern below, so bracket mode is a strict
// superset: mid-migration legacy-labeled headings (`Phase AUTH-101:`-style
@@ -615,7 +615,7 @@ function sliceMilestoneWindow(content: string, version: string): string | null {
// earlier single-alternative form with a `[\\w]` token admitted
// `[bracket] Word:` shapes — a colon-bearing MILESTONE heading inside the
// window read as an entry (defeating V005 outright for that spelling) and a
// decoy `### [GSD.04] Notes:` outside the window manufactured a false V005
// decoy `### [MSD.04] Notes:` outside the window manufactured a false V005
// while suppressing the correct V004. The digit anchor forecloses both.
const BRACKET_PHASE_ENTRY_HEADING_RE = new RegExp(
`^${PHASE_HEADING_PREFIX_SRC}${PHASE_NUMBER_TOKEN_SOURCE}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`,
@@ -797,7 +797,7 @@ function scanMilestonePhaseIds(
* defined as a MIRROR of the parser's terminator vocabulary, and on this
* branch that vocabulary is convention-SELECTED: `computeBracketSectionEnd`
* adds `isBracketMilestoneBoundary` as a terminator arm, so the ADR-canonical
* `## [GSD.09] Hidden` — no version token, no status emoji, not the word
* `## [MSD.09] Hidden` — no version token, no status emoji, not the word
* "Milestone" — terminates the window on an opted-in bracket repo while
* matching NONE of the signals above. Left unmirrored, the guard accepts
* exactly the description that narrows the window on the one convention this
@@ -826,7 +826,7 @@ function findMilestoneScopeHeadingLines(text: string, convention: string | null
//
// `selectedBracketId` is deliberately `null`, so the same-milestone
// CONTINUATION exemption never fires and a value naming the ACTIVE
// milestone (`## [GSD.02] Foundation (Phase Details)`) is flagged even
// milestone (`## [MSD.02] Foundation (Phase Details)`) is flagged even
// though the real parser would treat it as a continuation. That is the
// third instance of this function's stated conservatism and rests on the
// same argument as the other two: WHICH milestone is active is a property
@@ -877,7 +877,7 @@ function classifyMilestoneWindow(input: {
* @param ws - #2562: workstream name, so the companion STATE.md is read from
* `.planning/workstreams/<ws>/` instead of the project root. Omitted (the
* default) preserves the prior `planningDir(cwd)` resolution exactly,
* including its `GSD_WORKSTREAM` env fallback.
* including its `MSD_WORKSTREAM` env fallback.
*
* #3184: `extractCurrentMilestone`'s CRITICAL blast radius (200+ affected
* symbols, 20 direct callers) means its signature and return type do not
@@ -887,7 +887,7 @@ function classifyMilestoneWindow(input: {
* @param phaseIdConvention - #3641: the RESOLVED `phase_id_convention`
* config value, threaded to `hasPhaseEntries` so the scope axis's row-8
* comparison recognizes bracket-convention phase entries
* (`### [GSD.04] 01: Name`). Optional: absent, or any value other than
* (`### [MSD.04] 01: Name`). Optional: absent, or any value other than
* `'bracket'`, compiles the legacy entry grammar byte-identically — the
* widening engages only for a project that resolved the convention
* explicitly. `extractCurrentMilestone`'s wrapper deliberately does NOT
@@ -1170,9 +1170,9 @@ function extractCurrentMilestoneScoped(content: string, cwd?: string, ws?: strin
// SUBTREE in the preamble, not merely its own inert heading text.
// That subtree can still contain a DIFFERENT-id bracket PHASE
// heading, which DOES form a qualified key and CAN admit a foreign
// directory (F7: `## [GSD.01] Setup` / `### [GSD.07] 01: Foreign` —
// no same-id child, so `[GSD.01] Setup` is not a boundary, and
// `GSD.07-01-foreign`'s directory is admitted into the CURRENT
// directory (F7: `## [MSD.01] Setup` / `### [MSD.07] 01: Foreign` —
// no same-id child, so `[MSD.01] Setup` is not a boundary, and
// `MSD.07-01-foreign`'s directory is admitted into the CURRENT
// milestone's filter, reading 3/2/67%). This is NOT a regression —
// base, round-1 and HEAD all read 3/2/67% on F7 (base via its own
// pass-all degrade) — and it remains the declared OVER-inclusive,
@@ -1455,7 +1455,7 @@ function getRoadmapPhaseInternal(cwd: string, phaseNum: unknown): RoadmapPhaseRe
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
if (isSentinelPhaseId(normalizedPhase)) return null;
// Resolved INSIDE the try for the same reason as getMilestoneInfo below: planningDir
// throws a plain Error for an invalid GSD_WORKSTREAM/GSD_PROJECT segment, and resolving
// throws a plain Error for an invalid MSD_WORKSTREAM/MSD_PROJECT segment, and resolving
// it outside let that escape uncaught, crashing every caller for a malformed workstream
// name. ADR-227 is explicit that throwing breaks pipeline continuity, and this read path
// has no reason to be the exception -- it already degrades to null for every other
@@ -1628,7 +1628,7 @@ function hasNameableContent(s: string): boolean {
* keeps the generic re-derivation. Anchoring on the known literal is what
* makes a hostile STATE `milestone:` value (regex metacharacters, single-
* segment `vN`, a literal `$&`/`$1`) resolve correctly: the generic pattern
* only recognizes the real GSD version grammar and stops early on anything
* only recognizes the real MSD version grammar and stops early on anything
* outside it, leaving hostile characters in the extracted "name".
*/
function extractMilestoneHeadingName(
@@ -1706,7 +1706,7 @@ function scoped(value: MilestoneInfo | null, scope: Scope): { value: MilestoneIn
function getMilestoneInfo(cwd?: string): { value: MilestoneInfo | null; scope: Scope } {
// Declared here but RESOLVED INSIDE the try, so the catch can name the file without
// moving planningDir() out of the protected region. planningDir throws a plain Error
// for an invalid GSD_WORKSTREAM/GSD_PROJECT segment, and hoisting the call let that
// for an invalid MSD_WORKSTREAM/MSD_PROJECT segment, and hoisting the call let that
// escape uncaught — breaking the invariant #2245 relies on, that this function never
// throws. When the path never resolved there is nothing to name, so the diagnostic is
// skipped and the default is returned exactly as before.
@@ -1890,9 +1890,9 @@ type MilestonePhaseFilter = ((dirName: string) => boolean) & {
* never see spurious warnings.
* @param ws - #2562: workstream name, so the ROADMAP/STATE pair is read from
* `.planning/workstreams/<ws>/` instead of the project root. Required by any
* caller that iterates workstreams (it cannot set `GSD_WORKSTREAM` per
* caller that iterates workstreams (it cannot set `MSD_WORKSTREAM` per
* iteration). Omitted (the default) preserves the prior `planningDir(cwd)`
* resolution exactly, including its `GSD_WORKSTREAM` env fallback — every
* resolution exactly, including its `MSD_WORKSTREAM` env fallback — every
* pre-#2562 call site is unaffected.
*/
function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, phaseIdConvention?: string | null, ws?: string | null): MilestonePhaseFilter {
@@ -1906,7 +1906,7 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p
//
// Stated exactly, because the narrower claim is the true one: this keeps
// QUALIFIED IDS out of that flag's input, not hyphens in general. A heading
// whose TOKEN carries its own hyphen (`### [GSD.02] Phase 02-01:`) still flips
// whose TOKEN carries its own hyphen (`### [MSD.02] Phase 02-01:`) still flips
// it through milestonePhaseNums — as it also does at base, which matches that
// spelling through the un-widened intro. See the token-hyphen guard below.
const milestoneQualifiedIds = new Set<string>();
@@ -1937,9 +1937,9 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p
const hasPhaseHeadings = /#{2,4}\s*(?:\[[^\]]{1,200}\]\s*)?Phase\s+[\w]/i.test(roadmapContent);
if (!hasVersionedMilestonesGlobal && hasPhaseHeadings && phaseIdConvention === 'milestone-prefixed') {
console.warn(
'[gsd] Deprecated: free-form ROADMAP.md detected (no versioned milestone headings). ' +
'[msd] Deprecated: free-form ROADMAP.md detected (no versioned milestone headings). ' +
'The project has phase_id_convention set to "milestone-prefixed" in config.json but the ' +
'ROADMAP does not use versioned milestone headings. Run `gsd-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default).'
'ROADMAP does not use versioned milestone headings. Run `msd-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default).'
);
}
@@ -2063,15 +2063,15 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p
// heading side selected. Without it the heading scan's new bracket reach was
// half a fix: `milestonePhaseNums` became non-empty, so the pass-all degrade
// stopped firing, but no bracket directory could satisfy the three legacy
// checks below (numericRe fails on `GSD.02-05-five`, the custom-id match
// captures the project code `GSD`, and stripProjectCodePrefix does not strip
// checks below (numericRe fails on `MSD.02-05-five`, the custom-id match
// captures the project code `MSD`, and stripProjectCodePrefix does not strip
// a dotted prefix) — so EVERY bracket directory was rejected and
// completed_phases / total_plans / completed_plans / percent collapsed to 0
// while `state sync` went on writing a percent off the unfiltered disk.
//
// Matching is delegated to phaseTokenMatches against the milestone-QUALIFIED
// id, not the bare token: READING-B puts the milestone in the bracket, so
// `GSD.01-01-old-one` and `GSD.02-01-one` share the token `01` and only the
// `MSD.01-01-old-one` and `MSD.02-01-one` share the token `01` and only the
// qualified key separates them. That is the scoping this filter exists to do.
// ADDITIVE: on a miss we fall through to the three legacy checks, so a
// bracket repo carrying legacy-shaped directories reads exactly as before.
@@ -2087,7 +2087,7 @@ function getMilestonePhaseFilter(cwd: string, versionOverride?: string | null, p
// `^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*)` swallowed the WHOLE hyphenated
// directory name (A-tool-output-contract was captured as
// "A-tool-output-contract", not "A"), so every letter-named phase directory
// (Phase A:..Phase L: — GSD's own convention, ADR-612 first-class non-numeric
// (Phase A:..Phase L: — MSD's own convention, ADR-612 first-class non-numeric
// IDs) fell out of the milestone and counts were silently fabricated over
// whatever numeric directory survived. A letter-leading directory belongs if
// its lowercased name EQUALS a declared phase ID, or BEGINS with that ID