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

@@ -331,7 +331,7 @@ function shouldResyncStateProgress(fields: Iterable<string>): boolean {
// Cache disk scan results from buildStateFrontmatter per cwd per process (#1967).
// Avoids re-reading N+1 directories on every state write when the phase structure
// hasn't changed within the same gsd-tools invocation.
// hasn't changed within the same msd-tools invocation.
const _diskScanCache = new Map<string, {
// #3354: null is the milestoned-but-unbounded WITHHOLD sentinel — the scan
// refused to substitute the on-disk dir count for a rejected whole-document
@@ -921,7 +921,7 @@ function stateReplaceFieldWithFallback(content: string, primary: string, fallbac
// Neither pattern matched — field may have been reformatted or removed.
// Log diagnostic so template drift is detected early rather than silently swallowed.
process.stderr.write(
`[gsd-tools] WARNING: STATE.md field "${primary}"${fallback ? ` (fallback: "${fallback}")` : ''} not found — update skipped. ` +
`[msd-tools] WARNING: STATE.md field "${primary}"${fallback ? ` (fallback: "${fallback}")` : ''} not found — update skipped. ` +
`This may indicate STATE.md was externally modified or uses an unexpected format.\n`
);
return content;
@@ -1260,7 +1260,7 @@ function cmdStateRecordMetric(cwd: string, options: StateRecordMetricOptions, ra
// Locate THIS command's OWN metrics table by its HEADER shape, using the
// exact same splitTableRow/isDelimiterRow header/delimiter-shape checks
// `parseMarkdownTable` uses. A live "## Performance Metrics" section
// (gsd-core/templates/state.md:39-56) also carries the "By Phase"
// (msd-core/templates/state.md:39-56) also carries the "By Phase"
// velocity table (`| Phase | Plans | Total | Avg/Plan |`) — the prior
// "first table in the section" targeting spliced every per-plan row into
// THAT table instead, polluting it on EVERY plan completion
@@ -1342,7 +1342,7 @@ function cmdStateRecordMetric(cwd: string, options: StateRecordMetricOptions, ra
if (metricsSection) {
// Section EXISTS but carries no metrics table of its own — e.g. a live
// STATE.md whose "## Performance Metrics" section holds only the
// By-Phase velocity table (gsd-core/templates/state.md:48). Self-heal
// By-Phase velocity table (msd-core/templates/state.md:48). Self-heal
// by appending a fresh Per-Plan Metrics table to the END of the
// section body — every existing byte (By-Phase table, Recent Trend,
// footer) is preserved verbatim, and no second "## Performance
@@ -1506,7 +1506,7 @@ function cmdStateUpdateProgress(cwd: string, raw: boolean): void {
// disclosure): the JSON `reason` field alone is easy for a caller to
// never read, and STATE.md's Progress field goes stale with no
// user-visible signal beyond it. Mirrors the established
// `[gsd-tools] WARNING:` stderr convention this file already uses
// `[msd-tools] WARNING:` stderr convention this file already uses
// (stateReplaceFieldWithFallback above) for a comparable silent no-op —
// now routed through the shared `declineNoOp` helper (#3957) so the
// pairing is structural rather than hand-written per arm.
@@ -2277,12 +2277,12 @@ function matchSessionSection(body: string): string | null {
/**
* Match the "Current Position" section body from a STATE.md body. #2956: this
* is the Phase analogue of matchSessionSection. `Phase` canonically lives under
* `## Current Position` (gsd-core/templates/state.md), so — like Stopped At /
* `## Current Position` (msd-core/templates/state.md), so — like Stopped At /
* Paused At under `## Session` — it must be extracted from THAT section, not
* from the first `Phase:` / `**Phase:**` line anywhere in the body. Without the
* scope, a historical `Phase:` line in an archive section silently overwrites
* `current_phase` on every write, and because `current_phase` is routing input
* for gsd-progress / --next the rewind routes work to the wrong phase.
* for msd-progress / --next the rewind routes work to the wrong phase.
*
* Level-flexible: the canonical template uses an h2 `## Current Position`, the
* bootstrap template an h3 `### Current Position` (templates/state.md). Both
@@ -2585,7 +2585,7 @@ function cmdStateSnapshot(cwd: string, raw: boolean): void {
* #612: is the asserted milestone bounded to a heading in this ROADMAP?
*
* The legacy rule matches STATE's milestone STRING (`v2.0`) inside a heading.
* The ADR-canonical bracket milestone heading is `## [GSD.02] Foundation` — a
* The ADR-canonical bracket milestone heading is `## [MSD.02] Foundation` — a
* name, no version — so that rule finds nothing, the milestone reads as
* unbounded, and total_phases falls back to the on-disk directory count. Under
* the bracket convention the milestone integer in the bracket is matched against
@@ -2611,7 +2611,7 @@ function isMilestoneBounded(roadmapRaw: string, milestone: string, convention?:
// was a convention two files had to keep agreeing on by hand — and the drift
// guard could not see either copy.
// #612 round-4 (Major 1, F12): fence-aware via tokenizeHeadings, not a raw
// `.test(roadmapRaw)` — a FENCED `[GSD.02]` example heading (the ONLY one
// `.test(roadmapRaw)` — a FENCED `[MSD.02]` example heading (the ONLY one
// in the document, with no real section for the asserted milestone at
// all) previously bounded a milestone that isn't actually in the roadmap,
// un-suppressing a percent computed off the wrong (prior-milestone-plus-
@@ -2698,7 +2698,7 @@ function extractRetiredPhaseNumbers(scope: string, convention?: string | null):
* text against the phase-heading-intro grammar directly. Fence-aware by
* construction — `tokenizeHeadings` never produces a token for a fenced
* line — closing round-4's Major 1: a fenced EXAMPLE phase heading in the
* preamble (`` ### [GSD.02] 05: Example phase `` inside a
* preamble (`` ### [MSD.02] 05: Example phase `` inside a
* ` ```markdown ` block) previously inflated this count via the raw regex
* below, which ran over the whole scope STRING with no fence awareness at
* all (F9, F10 — `total_phases` read 3 where the milestone has 2 real
@@ -2740,7 +2740,7 @@ function countRoadmapPhaseHeadings(
// numeric phases (01, 05.1) and project-code IDs (PROJ-42).
if (!/\d/.test(token)) continue;
// #612 READING-B: a bracket heading carries its sentinel in the
// bracket, so `### [GSD.999] 01:` is an icebox item even though its
// bracket, so `### [MSD.999] 01:` is an icebox item even though its
// token is `01`.
if (bracketId && isSentinelPhaseId(`${bracketId}-${token}`, 'bracket')) continue;
// #612: under bracket the token rule composes with the bracket-id
@@ -2961,7 +2961,7 @@ function buildStateFrontmatter(
const survivor = dir < incumbent ? dir : incumbent;
seenPhaseNums.set(key, survivor);
process.stderr.write(
`gsd: warning — phase directories '${incumbent}' and '${dir}' both normalize to phase key '${key}' (duplicate phase number in .planning/phases/); keeping '${survivor}' by deterministic lexicographic order. (#3355)\n`
`msd: warning — phase directories '${incumbent}' and '${dir}' both normalize to phase key '${key}' (duplicate phase number in .planning/phases/); keeping '${survivor}' by deterministic lexicographic order. (#3355)\n`
);
}
}
@@ -3066,7 +3066,7 @@ function buildStateFrontmatter(
const milestonedButUnbounded = !milestoneBounded && roadmapHasAnyMilestoneSection;
if (milestonedButUnbounded) {
process.stderr.write(
`gsd: warning — milestone '${String(assertedMilestoneVersion ?? '').trim()}' is asserted in STATE.md but matches no ROADMAP heading, and the ROADMAP carries milestone section(s) — one (#3642) or several (#3354) — none matching it; the whole-document count would attribute a foreign section's phases to this milestone and the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3354/#3642/#4094)\n`
`msd: warning — milestone '${String(assertedMilestoneVersion ?? '').trim()}' is asserted in STATE.md but matches no ROADMAP heading, and the ROADMAP carries milestone section(s) — one (#3642) or several (#3354) — none matching it; the whole-document count would attribute a foreign section's phases to this milestone and the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3354/#3642/#4094)\n`
);
}
// #3573: the roadmap-absent sibling of the #3354 shape. With ROADMAP.md
@@ -3086,7 +3086,7 @@ function buildStateFrontmatter(
storedMilestone.trim() !== '';
if (roadmapAbsentWithAssertedMilestone) {
process.stderr.write(
`gsd: warning — milestone '${storedMilestone.trim()}' is asserted in STATE.md but ROADMAP.md is absent or unreadable, so the phase-heading total cannot be derived; the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3573) (#4094)\n`
`msd: warning — milestone '${storedMilestone.trim()}' is asserted in STATE.md but ROADMAP.md is absent or unreadable, so the phase-heading total cannot be derived; the on-disk phase-directory count would understate the declared total, so the progress counters (total_phases, completed_phases, total_plans, completed_plans) are left at their stored values. (#3573) (#4094)\n`
);
}
// #4094: the withhold condition covers ALL FOUR progress counters,
@@ -3258,7 +3258,7 @@ function buildStateFrontmatter(
normalizedStatus = 'executing';
}
const fm: Record<string, unknown> = { gsd_state_version: '1.0' };
const fm: Record<string, unknown> = { msd_state_version: '1.0' };
if (milestone) fm['milestone'] = milestone;
if (milestoneName) fm['milestone_name'] = milestoneName;
@@ -3342,7 +3342,7 @@ function projectOwnsItsRepo(projectRoot: string): boolean {
function readGitHeadSha(cwd: string | undefined): string | null {
if (!cwd) return null;
// #2573 degrade path D5. `git rev-parse HEAD` walks UP from cwd to the nearest
// enclosing `.git`, and nothing pins that repo to the project. A GSD project
// enclosing `.git`, and nothing pins that repo to the project. A MSD project
// living inside an unrelated checkout — a dotfiles/notes repo, or the outer
// workspace of a `planning.sub_repos` layout where all code commits land in
// the sub-repos — would otherwise measure freshness against a repo it has no
@@ -3606,7 +3606,7 @@ function syncStateFrontmatter(
// place (re-derivation, not deletion — the document never lost its frontmatter FENCE).
//
// `sanctionedPermanentEmptyFallback` is threaded ONLY from `writeStateMd`, itself consumed
// ONLY by `cmdStateSync` (#905) and `/gsd-health --repair`'s `REGENERATE_STATE` — ADR-3408
// ONLY by `cmdStateSync` (#905) and `/msd-health --repair`'s `REGENERATE_STATE` — ADR-3408
// §8.3's CLOSED list of commands whose documented contract is "body wins, re-derive
// unconditionally" (a factory reset / explicit resync). Those two are untouched here: this
// guard fires only on the OTHER call path (`syncAndPreserveStateMd`, i.e. every
@@ -3680,7 +3680,7 @@ function syncStateFrontmatter(
// `divergedFields` stayed empty even though a curated value had just won
// over a genuine derived-empty.
//
// `writeStateMd`'s two callers — `cmdStateSync` and `/gsd-health --repair`'s
// `writeStateMd`'s two callers — `cmdStateSync` and `/msd-health --repair`'s
// `REGENERATE_STATE` — are §8.3's closed, sanctioned-permanent exception
// list: NEITHER ever runs `applyStatePreservation` afterward, because their
// whole contract is "re-derive frontmatter FROM the body, body wins" (the
@@ -3749,7 +3749,7 @@ function syncStateFrontmatter(
// `derive`, but they are body/disk-sourced and MUST still carry forward when
// the writer omits them this pass — dropping `last_activity` here is silent
// frontmatter data loss and would defeat #2570's staleness fix downstream.
// `last_updated` and `gsd_state_version` are the only other `free` rows and are
// `last_updated` and `msd_state_version` are the only other `free` rows and are
// both produced unconditionally by buildStateFrontmatter, so this loop never
// reaches them; `state_head` is the sole field the skip governs. Consult the
// table rather than naming fields, so the policy stays single-sourced.
@@ -4189,7 +4189,7 @@ function applyPostSyncPreservation(
// FRESH frontmatter block from the result, destroying the raw block a second time even
// though `syncStateFrontmatter` just finished protecting it. `applyPostSyncPreservation` is
// reached ONLY via the non-sanctioned path (`syncAndPreserveStateMd`; `writeStateMd`'s two
// ADR-3408 §8.3 closed-list callers — `cmdStateSync` #905 and `/gsd-health --repair`'s
// ADR-3408 §8.3 closed-list callers — `cmdStateSync` #905 and `/msd-health --repair`'s
// `REGENERATE_STATE` — never call it at all), so this guard needs no extra parameter to stay
// scoped off that list. Confirmed by execution: `state begin-phase` on a conflict-marked
// STATE.md reached exactly this second clobber even after the `syncStateFrontmatter` fix.
@@ -5231,14 +5231,14 @@ function cmdStateBeginPhase(cwd: string, phaseNumber: string | number | null | u
}
/**
* Write a WAITING.json signal file when GSD hits a decision point.
* Write a WAITING.json signal file when MSD hits a decision point.
* External watchers (fswatch, polling, orchestrators) can detect this.
* File is written to .planning/WAITING.json (or .gsd/WAITING.json if .gsd exists).
* File is written to .planning/WAITING.json (or .msd/WAITING.json if .msd exists).
* Fixes #1034.
*/
function cmdSignalWaiting(cwd: string, type: string | undefined, question: string | undefined, options: string | undefined, phase: string | undefined, raw: boolean): void {
const gsdDir = fs.existsSync(path.join(cwd, '.gsd')) ? path.join(cwd, '.gsd') : planningDir(cwd);
const waitingPath = path.join(gsdDir, 'WAITING.json');
const msdDir = fs.existsSync(path.join(cwd, '.msd')) ? path.join(cwd, '.msd') : planningDir(cwd);
const waitingPath = path.join(msdDir, 'WAITING.json');
const signal = {
status: 'waiting',
@@ -5250,7 +5250,7 @@ function cmdSignalWaiting(cwd: string, type: string | undefined, question: strin
};
try {
platformEnsureDir(gsdDir);
platformEnsureDir(msdDir);
platformWriteSync(waitingPath, JSON.stringify(signal, null, 2));
output({ signaled: true, path: waitingPath }, raw, 'true');
} catch (e) {
@@ -5263,7 +5263,7 @@ function cmdSignalWaiting(cwd: string, type: string | undefined, question: strin
*/
function cmdSignalResume(cwd: string, raw: boolean): void {
const paths = [
path.join(cwd, '.gsd', 'WAITING.json'),
path.join(cwd, '.msd', 'WAITING.json'),
path.join(planningDir(cwd), 'WAITING.json'),
];
@@ -5551,7 +5551,7 @@ function cmdStatePlannedPhase(cwd: string, phaseNumber: string | number | null |
// over `existingFm`), so an unrecognized-format STATE.md — zero fields the
// transition could actually apply, `updated: []`, the "transition was a
// no-op" warning above — can still make the raw returned string differ
// from the input (frontmatter gets synthesized: `gsd_state_version`,
// from the input (frontmatter gets synthesized: `msd_state_version`,
// `last_updated`, a zeroed `progress` block, `current_phase_name`), so the
// RMW guard does NOT fire and a real write happens. That write is not a
// meaningful state transition by this command's OWN reporting contract
@@ -5607,7 +5607,7 @@ function cmdStateMilestoneSwitch(cwd: string, version: string | undefined, name:
// #3227: publish AFTER releaseStateLock — publishStateContract derives `next`
// from classifyProject, which shells out to git (bounded, but up to 3 x 10s).
// Holding the STATE.md lock across that would turn a millisecond hold into a
// git-bound one for every concurrent GSD process.
// git-bound one for every concurrent MSD process.
if (switched) publishStateContract(cwd);
}
@@ -5802,7 +5802,7 @@ function cmdStateValidate(cwd: string, raw: boolean, opts: { strict?: boolean }
// directory without an explicit signal (a bracket dir is string-
// indistinguishable from the legacy letter-prefixed-decimal family, ADR-2121),
// so un-threaded it returns the WHOLE dir name as the key —
// `GSD.02-05-delta` -> `GSD.02-5-DELTA` — while `selectedPhaseKey` is the bare
// `MSD.02-05-delta` -> `MSD.02-5-DELTA` — while `selectedPhaseKey` is the bare
// `05` that `parsePhaseFromProse` yields. The two sides of one comparison were
// derived under different conventions, which is #2562's defect class and the
// thing this file's other three `phaseKeyFromDir` call sites already thread
@@ -6226,7 +6226,7 @@ function cmdStateSync(cwd: string, options: StateSyncOptions | undefined, raw: b
const syncWillWrite = changes.length > 0 || modified !== content;
if (syncWillWrite && syncFrontmatterWasUnparseable) {
const unparseableWarning =
`gsd: warning — STATE.md's existing frontmatter could not be parsed (malformed YAML, or ` +
`msd: warning — STATE.md's existing frontmatter could not be parsed (malformed YAML, or ` +
`unresolved content such as git merge-conflict markers) and was regenerated from the body; ` +
`any content in the old frontmatter block — including merge-conflict markers — has been ` +
`replaced. (#3881)`;
@@ -6353,7 +6353,7 @@ function cmdStatePrune(cwd: string, options: StatePruneOptions, raw: boolean): v
/**
* Rebuild STATE.md body structure from canonical sources (ADR-1817).
*
* Implements the `gsd state rebuild` subcommand (issue #1817 Phase 2, #1826).
* Implements the `msd state rebuild` subcommand (issue #1817 Phase 2, #1826).
* Wires the pure `rebuildCore` transition (Phase 1, #1827) to the CLI:
* - Locks via `readModifyWriteStateMd` (real path) or reads-only (dry-run).
* - Wires `phaseInventoryProvider` to a real `.planning/phases/` disk scan.
@@ -6518,7 +6518,7 @@ function cmdStateRebuild(cwd: string, options: StateRebuildOptions, raw: boolean
* Mark the current phase as COMPLETE in STATE.md.
* Updates Status, Last Activity, and the Current Position section to reflect
* that the phase execution is finished and the project is ready for the next phase.
* Implements the `gsd state complete-phase` subcommand (issue #2735).
* Implements the `msd state complete-phase` subcommand (issue #2735).
*/
function resolvePhaseIdForCompletePhase(fm: Record<string, unknown>, body: string, overridePhase: string | undefined): string | null {
// #3187: route through the single #1760 fallback-chain owner (fm scalar