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

@@ -42,7 +42,7 @@ import scanPhasePlans = require('./plan-scan.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-document.cjs is an export= CommonJS module
import planDocument = require('./plan-document.cjs');
import { stateExtractField } from './state-document.cjs';
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
import { formatMsdSlash, resolveRuntime } from './runtime-slash.cjs';
import { resolveReportedRuntime } from './host-runtime-detection.cjs';
// eslint-disable-next-line @typescript-eslint/no-require-imports -- commands.cjs is an export= CommonJS module
import commandsMod = require('./commands.cjs');
@@ -364,9 +364,9 @@ function withProjectRoot(cwd: string, result: Record<string, unknown>): Record<s
}
// #4455 follow-up (self-discovered): PROJECT.md is shared across a
// project's own workstreams (never cloned per workstream) but DOES
// respect the separate GSD_PROJECT multi-project namespace (#3749) — see
// respect the separate MSD_PROJECT multi-project namespace (#3749) — see
// cmdInitCompleteMilestone's projectPath comment for the full evidence.
// `ws` explicitly nulled, `project` left to default from GSD_PROJECT.
// `ws` explicitly nulled, `project` left to default from MSD_PROJECT.
// Reading via the workstream-aware planningDir(cwd) meant every init.*
// call's project_title silently vanished whenever a workstream was
// active, since no PROJECT.md ever exists at the workstream path.
@@ -455,16 +455,16 @@ function getInitGitState(cwd: string): GitState {
}
// #2932 (Phase 5, ADR-1671): shipped, generated artifact — see
// scripts/gen-section-manifest.cjs and gsd-core/workflows/section-manifest.json.
// scripts/gen-section-manifest.cjs and msd-core/workflows/section-manifest.json.
// Resolved the same way model-catalog.cts resolves model-catalog.json: relative
// to the compiled module's own directory (gsd-core/bin/lib -> gsd-core/workflows),
// with a GSD_SECTION_MANIFEST env override so tests can point at a temp fixture
// to the compiled module's own directory (msd-core/bin/lib -> msd-core/workflows),
// with a MSD_SECTION_MANIFEST env override so tests can point at a temp fixture
// (missing/malformed-JSON degraded-path coverage) without mutating the shipped
// artifact — the shipped file is a shared, concurrently-read resource across
// parallel test runs and must never be moved/corrupted in place.
const _sectionManifestCandidatePath = (): string =>
process.env['GSD_SECTION_MANIFEST']
? path.resolve(process.env['GSD_SECTION_MANIFEST'])
process.env['MSD_SECTION_MANIFEST']
? path.resolve(process.env['MSD_SECTION_MANIFEST'])
: path.resolve(__dirname, '..', '..', 'workflows', 'section-manifest.json');
/** A manifest entry as shipped on disk: {@link sectionManifest.SelectableSection} plus the `read` step-file path. */
@@ -635,7 +635,7 @@ function readConfigJsonValue(cwd: string, keyPath: readonly string[]): unknown {
/**
* `state:fallow-enabled` ground truth (#2994): resolves `code-review.md`'s
* `structural_pre_pass` fallow config gate — previously re-derived INSIDE the
* gated section body itself (`gsd_run query config-get code_quality.fallow.*`),
* gated section body itself (`msd_run query config-get code_quality.fallow.*`),
* which is circular/self-disabling the moment a section is gated on a fact
* its own body computes (the same hazard `state:chunked-mode` /
* `state:ui-phase-active` document for a compound condition). Fail-closed
@@ -665,7 +665,7 @@ function detectFallowConfig(cwd: string): {
/**
* `state:git-create-tag` ground truth (#2994): resolves `complete-milestone.md`'s
* `git_tag` step config gate — previously re-derived INSIDE a `<config-check>`
* sub-tag at the top of the step itself (`gsd-tools.cjs query config-get
* sub-tag at the top of the step itself (`msd-tools.cjs query config-get
* git.create_tag 2>/dev/null || echo "true"`), gating the step's OWN inclusion
* on a fact only that same step computed. Fail-OPEN default `true` (an unset
* or missing `git.create_tag` key means "create the tag"), matching the
@@ -737,7 +737,7 @@ function detectUiPhaseActive(cwd: string, phaseInfo: Record<string, unknown> | n
const state = resolveCapabilityRuntimeState(cwd, undefined, config) as {
capabilities: Array<{ id: string; enabled?: boolean; active: boolean }>;
};
const registry = loadRegistry({ includeInstalled: true, cwd, gsdHome: process.env['GSD_HOME'] });
const registry = loadRegistry({ includeInstalled: true, cwd, msdHome: process.env['MSD_HOME'] });
const capabilityStatesById = new Map<string, { enabled?: boolean; active: boolean }>();
for (const cap of state.capabilities || []) {
capabilityStatesById.set(cap.id, cap);
@@ -1007,8 +1007,8 @@ function cmdInitExecutePhase(
const threatIdDuplicates = findDuplicateThreatIds(cwd, phaseInfo?.['directory'] as string | undefined);
const result: Record<string, unknown> = {
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
executor_model: resolveModelInternal(cwd, 'msd-executor'),
verifier_model: resolveModelInternal(cwd, 'msd-verifier'),
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
commit_docs: config.commit_docs,
@@ -1102,8 +1102,8 @@ function cmdInitExecutePhase(
// planningDir(cwd, null) on the assumption that config.json is shared
// like PROJECT.md (workstream-flag.md's directory diagram marks it
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and gsd-test
// handlers honor MSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and msd-test
// caught the regression immediately. The diagram is stale for
// config.json specifically (same class of staleness already found for
// `milestones/` during the #4455 follow-up) — reverted to the
@@ -1111,7 +1111,7 @@ function cmdInitExecutePhase(
// ADR-governed, tested contract.
config_path: toPosixPath(path.join(planningDir(cwd), 'config.json')),
// #2376: execute-phase.md's verify_phase_goal step reads this instead of
// hardcoding '.planning/REQUIREMENTS.md' into the gsd-verifier spawn prompt.
// hardcoding '.planning/REQUIREMENTS.md' into the msd-verifier spawn prompt.
requirements_path: fs.existsSync(requirementsPath) ? toPosixPath(requirementsPath) : null,
};
@@ -1217,9 +1217,9 @@ function cmdInitPlanPhase(
const requirementsPath = path.join(planningDir(cwd), 'REQUIREMENTS.md');
const result: Record<string, unknown> = {
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
researcher_model: resolveModelInternal(cwd, 'msd-phase-researcher'),
planner_model: resolveModelInternal(cwd, 'msd-planner'),
checker_model: resolveModelInternal(cwd, 'msd-plan-checker'),
tdd_mode: options['tdd'] || Boolean(config.tdd_mode) || false,
granularity,
@@ -1469,13 +1469,13 @@ function cmdInitNewProject(cwd: string, raw: boolean, options: Record<string, un
const config = loadConfig(cwd);
const homedir = os.homedir();
const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
const braveKeyFile = path.join(homedir, '.msd', 'brave_api_key');
const hasBraveSearch = !!(process.env['BRAVE_API_KEY'] || fs.existsSync(braveKeyFile));
const firecrawlKeyFile = path.join(homedir, '.gsd', 'firecrawl_api_key');
const firecrawlKeyFile = path.join(homedir, '.msd', 'firecrawl_api_key');
const hasFirecrawl = !!(process.env['FIRECRAWL_API_KEY'] || fs.existsSync(firecrawlKeyFile));
const exaKeyFile = path.join(homedir, '.gsd', 'exa_api_key');
const exaKeyFile = path.join(homedir, '.msd', 'exa_api_key');
const hasExaSearch = !!(process.env['EXA_API_KEY'] || fs.existsSync(exaKeyFile));
const hasCode = hasCodeFilesInternal(cwd);
@@ -1485,9 +1485,9 @@ function cmdInitNewProject(cwd: string, raw: boolean, options: Record<string, un
const hasCodebaseMap = codebaseMapFiles.length === REQUIRED_CODEBASE_MAP_FILES.length;
const result: Record<string, unknown> = {
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
researcher_model: resolveModelInternal(cwd, 'msd-project-researcher'),
synthesizer_model: resolveModelInternal(cwd, 'msd-research-synthesizer'),
roadmapper_model: resolveModelInternal(cwd, 'msd-roadmapper'),
commit_docs: config.commit_docs,
@@ -1534,8 +1534,8 @@ function cmdInitNewProject(cwd: string, raw: boolean, options: Record<string, un
// planningDir(cwd, null) on the assumption that config.json is shared
// like PROJECT.md (workstream-flag.md's directory diagram marks it
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and gsd-test
// handlers honor MSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and msd-test
// caught the regression immediately. The diagram is stale for
// config.json specifically (same class of staleness already found for
// `milestones/` during the #4455 follow-up) — reverted to the
@@ -1568,9 +1568,9 @@ function cmdInitNewMilestone(cwd: string, raw: boolean, options: Record<string,
const phaseDirCount = listMilestonePhaseDirs(phasesDir, { cwd }).value.length;
const result: Record<string, unknown> = {
researcher_model: resolveModelInternal(cwd, 'gsd-project-researcher'),
synthesizer_model: resolveModelInternal(cwd, 'gsd-research-synthesizer'),
roadmapper_model: resolveModelInternal(cwd, 'gsd-roadmapper'),
researcher_model: resolveModelInternal(cwd, 'msd-project-researcher'),
synthesizer_model: resolveModelInternal(cwd, 'msd-research-synthesizer'),
roadmapper_model: resolveModelInternal(cwd, 'msd-roadmapper'),
commit_docs: config.commit_docs,
research_enabled: config.research,
@@ -1609,8 +1609,8 @@ function cmdInitNewMilestone(cwd: string, raw: boolean, options: Record<string,
// planningDir(cwd, null) on the assumption that config.json is shared
// like PROJECT.md (workstream-flag.md's directory diagram marks it
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and gsd-test
// handlers honor MSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and msd-test
// caught the regression immediately. The diagram is stale for
// config.json specifically (same class of staleness already found for
// `milestones/` during the #4455 follow-up) — reverted to the
@@ -1641,7 +1641,7 @@ function cmdInitNewMilestone(cwd: string, raw: boolean, options: Record<string,
// SEPARATE, positively-phrased atom whose fact is the inverse is the
// sanctioned resolution (same discipline as `state:chunked-mode` folding
// an OR — never an operator in the grammar itself). Same authoritative
// source as `cmdInitTransition`: `GSD_WORKSTREAM` env, falling back to the
// source as `cmdInitTransition`: `MSD_WORKSTREAM` env, falling back to the
// stored active-workstream pointer (mirrors `cmdInitProgress`'s own
// resolution above).
//
@@ -1691,17 +1691,17 @@ function cmdInitQuick(
: null;
const result: Record<string, unknown> = {
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
// #3936: Step 4.75 dispatches gsd-phase-researcher; resolve its own tier
planner_model: resolveModelInternal(cwd, 'msd-planner'),
executor_model: resolveModelInternal(cwd, 'msd-executor'),
checker_model: resolveModelInternal(cwd, 'msd-plan-checker'),
verifier_model: resolveModelInternal(cwd, 'msd-verifier'),
// #3936: Step 4.75 dispatches msd-phase-researcher; resolve its own tier
// (parity with cmdInitPlanPhase) so the research spawn stops pinning
// planner_model.
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
// #2072: the quick review step spawns gsd-code-reviewer; resolve its own model
researcher_model: resolveModelInternal(cwd, 'msd-phase-researcher'),
// #2072: the quick review step spawns msd-code-reviewer; resolve its own model
// so model_overrides / models.verification apply (was reusing executor_model).
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
reviewer_model: resolveModelInternal(cwd, 'msd-code-reviewer'),
commit_docs: config.commit_docs,
branch_name: quickBranchName,
@@ -1731,7 +1731,7 @@ function cmdInitQuick(
// builds its `InvocationFacts.flags` Set, so `discussion-phase`/`research-phase`/
// `plan-checker-loop`/`quick-verification` (all gated on their own single
// `flag:--discuss`/`flag:--research`/`flag:--validate` atom) include correctly
// for a bare `/gsd:quick --full` invocation that never passed the individual
// for a bare `/msd:quick --full` invocation that never passed the individual
// tokens — the grammar still sees exactly one atom per marker, no OR.
const sectionManifestOptions: Record<string, unknown> = options['full']
? { ...options, discuss: true, research: true, validate: true }
@@ -1768,12 +1768,12 @@ function cmdInitQuickBatch(
const config = loadConfig(cwd);
const result: Record<string, unknown> = {
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
verifier_model: resolveModelInternal(cwd, 'gsd-verifier'),
researcher_model: resolveModelInternal(cwd, 'gsd-phase-researcher'),
reviewer_model: resolveModelInternal(cwd, 'gsd-code-reviewer'),
planner_model: resolveModelInternal(cwd, 'msd-planner'),
executor_model: resolveModelInternal(cwd, 'msd-executor'),
checker_model: resolveModelInternal(cwd, 'msd-plan-checker'),
verifier_model: resolveModelInternal(cwd, 'msd-verifier'),
researcher_model: resolveModelInternal(cwd, 'msd-phase-researcher'),
reviewer_model: resolveModelInternal(cwd, 'msd-code-reviewer'),
commit_docs: config.commit_docs,
@@ -1804,7 +1804,7 @@ function cmdInitIngestDocs(cwd: string, raw: boolean): void {
...getInitGitState(cwd),
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase. The
// classify_parallel/synthesize/route_new_mode spawns in ingest-docs.md
// (gsd-doc-classifier, gsd-doc-synthesizer, gsd-roadmapper) previously
// (msd-doc-classifier, msd-doc-synthesizer, msd-roadmapper) previously
// hardcoded bare '.planning/intel/...', '.planning/PROJECT.md', etc.
// literals into their Agent(prompt=...) blocks; those now interpolate
// these fields instead.
@@ -1922,8 +1922,8 @@ function cmdInitVerifyWork(cwd: string, phase: string, raw: boolean): void {
const uiPhaseActive = detectUiPhaseActive(cwd, phaseInfo);
const result: Record<string, unknown> = {
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'),
planner_model: resolveModelInternal(cwd, 'msd-planner'),
checker_model: resolveModelInternal(cwd, 'msd-plan-checker'),
commit_docs: config.commit_docs,
@@ -1949,7 +1949,7 @@ function cmdInitVerifyWork(cwd: string, phase: string, raw: boolean): void {
// #2994 (resolver-hoist-guard G5): hoisted `state:ui-phase-active` ground
// truth (previously re-derived inline inside the automated_ui_verification
// step body via its own `gsd_run loop render-hooks plan:pre --raw` call —
// step body via its own `msd_run loop render-hooks plan:pre --raw` call —
// a circular, self-disabling resolver, since the section is only read
// when this same fact is already true). Resolved once here, exposed so
// the step body can consume it directly instead of recomputing it.
@@ -2140,7 +2140,7 @@ function cmdInitReview(
* carried: `state:auto-advance-active` — the workflow's own `auto_advance`
* step resolves `--auto` OR a consolidated `check auto-mode --pick active`
* fact (itself `workflow._auto_chain_active` OR `workflow.auto_advance`) via
* a runtime `gsd_run` call; that identical disjunction is folded into ONE
* a runtime `msd_run` call; that identical disjunction is folded into ONE
* boolean FACT here (same discipline as `state:chunked-mode` /
* `state:plan-strategy-converge`), exposed as `auto_advance_active`.
*/
@@ -2427,7 +2427,7 @@ function cmdInitPhaseOp(cwd: string, phase: string, raw: boolean): void {
// 240 matches the bound already vetted by maintainer review on the prior
// attempt at this issue (PR #2662) — re-deriving a different number would be
// pure bikeshedding, not a correctness improvement. See
// .gsd/phase/feat-2618-compact-todo-pointers/40-design.md.
// .msd/phase/feat-2618-compact-todo-pointers/40-design.md.
const PENDING_TODO_BULLET_MAX_CHARS = 240;
const PENDING_TODO_TITLE_FLOOR = 15;
const PENDING_TODO_AREA_FLOOR = 3;
@@ -2448,7 +2448,7 @@ function truncatePendingTodoText(value: string, maxLen: number): string {
/**
* #2618: pure renderer for STATE.md's "### Pending Todos" section BODY (not
* the heading). One bullet per todo, each capped at
* PENDING_TODO_BULLET_MAX_CHARS. `gsd-core/workflows/add-todo.md` and
* PENDING_TODO_BULLET_MAX_CHARS. `msd-core/workflows/add-todo.md` and
* `check-todos.md` splice this string in verbatim instead of free-hand
* editing STATE.md — see the design doc for why this is real, unit-tested
* code rather than a prose algorithm (DEFECT.GENERATIVE-FIX: a prose
@@ -2802,7 +2802,7 @@ function cmdInitMapCodebase(cwd: string, raw: boolean): void {
// below (and verify.cts's codebase drift check) — has_maps/existing_maps
// reading the flat root made the same payload claim a scoped codebase dir
// exists while reporting zero maps, so map-codebase's Refresh/Skip gate
// always forced a re-map under GSD_PROJECT.
// always forced a re-map under MSD_PROJECT.
const codebaseDir = path.join(planningDir(cwd), 'codebase');
let existingMaps: string[] = [];
try {
@@ -2812,7 +2812,7 @@ function cmdInitMapCodebase(cwd: string, raw: boolean): void {
}
const result: Record<string, unknown> = {
mapper_model: resolveModelInternal(cwd, 'gsd-codebase-mapper'),
mapper_model: resolveModelInternal(cwd, 'msd-codebase-mapper'),
commit_docs: config.commit_docs,
search_gitignored: config.search_gitignored,
@@ -2825,7 +2825,7 @@ function cmdInitMapCodebase(cwd: string, raw: boolean): void {
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
// #3964: scoped like verify.cts's codebase drift check (planningDir, not
// the flat planningRoot) so the two surfaces cannot disagree under
// GSD_PROJECT.
// MSD_PROJECT.
codebase_dir: toPosixPath(path.join(planningDir(cwd), 'codebase')),
existing_maps: existingMaps,
@@ -2857,10 +2857,10 @@ function cmdInitManager(cwd: string, raw: boolean): void {
const paths = planningPaths(cwd);
if (!fs.existsSync(paths.roadmap)) {
error(`No ROADMAP.md found. Run ${formatGsdSlash('new-milestone', _slashRuntime) as string} first.`);
error(`No ROADMAP.md found. Run ${formatMsdSlash('new-milestone', _slashRuntime) as string} first.`);
}
if (!fs.existsSync(paths.state)) {
error(`No STATE.md found. Run ${formatGsdSlash('new-milestone', _slashRuntime) as string} first.`);
error(`No STATE.md found. Run ${formatMsdSlash('new-milestone', _slashRuntime) as string} first.`);
}
const rawContent = fs.readFileSync(paths.roadmap, 'utf-8');
const content = extractCurrentMilestone(rawContent, cwd);
@@ -3171,12 +3171,12 @@ function cmdInitManager(cwd: string, raw: boolean): void {
let waitingSignal: unknown = null;
try {
// #3964: mirror cmdSignalWaiting's write locations exactly — `.gsd/`
// #3964: mirror cmdSignalWaiting's write locations exactly — `.msd/`
// first when it exists, else the project-aware planning dir — so the
// signal is read from the project (and location) it is written to.
const gsdWaiting = path.join(cwd, '.gsd', 'WAITING.json');
const waitingPath = fs.existsSync(path.join(cwd, '.gsd'))
? gsdWaiting
const msdWaiting = path.join(cwd, '.msd', 'WAITING.json');
const waitingPath = fs.existsSync(path.join(cwd, '.msd'))
? msdWaiting
: path.join(planningDir(cwd), 'WAITING.json');
const waitingRaw = platformReadSync(waitingPath);
if (waitingRaw !== null) {
@@ -3206,7 +3206,7 @@ function cmdInitManager(cwd: string, raw: boolean): void {
phase_name: phase['name'],
action: 'execute',
reason: `${phase['plan_count'] as number} plans ready, dependencies met`,
command: `${formatGsdSlash('execute-phase', _slashRuntime) as string} ${phase['number'] as string}`,
command: `${formatMsdSlash('execute-phase', _slashRuntime) as string} ${phase['number'] as string}`,
});
} else if (
phase['disk_status'] === 'discussed' ||
@@ -3217,7 +3217,7 @@ function cmdInitManager(cwd: string, raw: boolean): void {
phase_name: phase['name'],
action: 'plan',
reason: 'Context gathered, ready for planning',
command: `${formatGsdSlash('plan-phase', _slashRuntime) as string} ${phase['number'] as string}`,
command: `${formatMsdSlash('plan-phase', _slashRuntime) as string} ${phase['number'] as string}`,
});
} else if (
(phase['disk_status'] === 'empty' || phase['disk_status'] === 'no_directory') &&
@@ -3228,7 +3228,7 @@ function cmdInitManager(cwd: string, raw: boolean): void {
phase_name: phase['name'],
action: 'discuss',
reason: 'Unblocked, ready to gather context',
command: `${formatGsdSlash('discuss-phase', _slashRuntime) as string} ${phase['number'] as string}`,
command: `${formatMsdSlash('discuss-phase', _slashRuntime) as string} ${phase['number'] as string}`,
});
}
}
@@ -3273,7 +3273,7 @@ function cmdInitManager(cwd: string, raw: boolean): void {
);
if (!safe) {
process.stderr.write(
`gsd-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`,
`msd-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`,
);
return '';
}
@@ -3364,7 +3364,7 @@ function cmdInitCompleteMilestone(
const milestonesPath = path.join(planningBase, 'MILESTONES.md');
// #4455 follow-up round 2 (self-discovered regression): PROJECT.md, unlike
// MILESTONES.md, is genuinely SHARED across a project's own workstreams —
// never cloned per workstream. gsd-core/references/workstream-flag.md's
// never cloned per workstream. msd-core/references/workstream-flag.md's
// directory diagram marks it `# Shared`; new-milestone.md states it
// outright ("PROJECT.md is shared across workstreams") and explicitly
// SKIPS writing its `## Current Milestone` heading under an active
@@ -3374,13 +3374,13 @@ function cmdInitCompleteMilestone(
// wrongly generalized from planningPaths()'s structural shape (which
// composes `project` under the workstream base) without checking an
// actual PROJECT.md write path — resolved against planningRoot(cwd)
// (round 2), but that ALSO ignores the separate GSD_PROJECT dimension
// (round 2), but that ALSO ignores the separate MSD_PROJECT dimension
// (multi-project namespacing, #3749: PROJECT.md legitimately lives at
// `.planning/<project>/PROJECT.md` when GSD_PROJECT is set — a real,
// `.planning/<project>/PROJECT.md` when MSD_PROJECT is set — a real,
// tested, pre-existing feature planningRoot's blanket root-only read
// broke), caught by gsd-test on this fix's own first push. `planningDir`
// with `ws` explicitly nulled (never read from GSD_WORKSTREAM) but
// `project` left to default from GSD_PROJECT is the correct middle
// broke), caught by msd-test on this fix's own first push. `planningDir`
// with `ws` explicitly nulled (never read from MSD_WORKSTREAM) but
// `project` left to default from MSD_PROJECT is the correct middle
// ground: respects project-namespacing, ignores workstream-namespacing.
const projectPath = path.join(planningDir(cwd, null), 'PROJECT.md');
// REQUIREMENTS.md is workstream-scoped the same way (planningPaths(cwd).requirements,
@@ -3446,7 +3446,7 @@ function cmdInitAutonomous(
/**
* `docs-update.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3 — final slice). `docs-update.md` previously carried NO `gsd_run query
* 6.3 — final slice). `docs-update.md` previously carried NO `msd_run query
* init.*` call at all: its own `docs-init` command (`cmdDocsInit`,
* src/docs.cts) is a SEPARATE, pre-existing entry point outside this
* `init.*` family and is left untouched here. This function's only job is
@@ -3473,17 +3473,17 @@ function cmdInitDocsUpdate(cwd: string, raw: boolean, options: Record<string, un
/**
* `update.md`'s dedicated init entry point (#2994, epic #1671 Phase 6.3 —
* final slice). `update.md` previously carried NO `gsd_run query init.*`
* call at all: it resolves `gsd-tools.cjs` itself (its own bespoke
* `PREFERRED_CONFIG_DIR`/`PREFERRED_RUNTIME`-aware `$GSD_TOOLS` cascade,
* final slice). `update.md` previously carried NO `msd_run query init.*`
* call at all: it resolves `msd-tools.cjs` itself (its own bespoke
* `PREFERRED_CONFIG_DIR`/`PREFERRED_RUNTIME`-aware `$MSD_TOOLS` cascade,
* `update.md` ~lines 13-45) because the update workflow must run BEFORE any
* install can be assumed resolvable — the canonical launcher preamble's
* fixed candidate list is not a substitute for that cascade, and both
* resolutions assign the identical `$GSD_TOOLS` shell variable, so copying
* resolutions assign the identical `$MSD_TOOLS` shell variable, so copying
* the canonical preamble in ADDITION to the existing cascade would silently
* clobber the value `backup_custom_files`/`restore_custom_files` (later
* steps) still depend on. This function is invoked via that ALREADY
* resolved `$GSD_TOOLS`, not a redundant `gsd_run()` shell function.
* resolved `$MSD_TOOLS`, not a redundant `msd_run()` shell function.
*
* `state:next-channel` ground truth: `--next` OR its documented alias
* `--rc` (same disjunction-to-one-boolean discipline as
@@ -3492,7 +3492,7 @@ function cmdInitDocsUpdate(cwd: string, raw: boolean, options: Record<string, un
* (`TAG="next"`/`TAG="latest"`) — issue #815's regression test
* (`tests/update-workflow.test.cjs`) asserts that literal
* case-statement text stays in `update.md` verbatim (the npm dist-tag
* selection has to run in the workflow's own shell before any `gsd_run`
* selection has to run in the workflow's own shell before any `msd_run`
* round-trip), so `next_channel` exists purely to gate the `channel-banner`
* section's admission — a parallel, consistent-but-not-replacing
* resolution of the same flags.
@@ -3514,22 +3514,22 @@ function cmdInitUpdate(cwd: string, raw: boolean, options: Record<string, unknow
/**
* `transition.md`'s dedicated init entry point (#2994, epic #1671 Phase
* 6.3 — final slice). `transition.md` is an internal workflow (no
* user-facing `/gsd-transition` command) that previously carried NO
* `gsd_run query init.*` call at all; it already establishes `gsd_run()`
* user-facing `/msd-transition` command) that previously carried NO
* `msd_run query init.*` call at all; it already establishes `msd_run()`
* via the canonical launcher preamble in its `update_roadmap_and_state`
* step (before this call's insertion point in `offer_next_phase`), so no
* second preamble copy is needed in the host file.
*
* `state:workstream-active` ground truth: a workstream is active — resolved
* via `GSD_WORKSTREAM` env, falling back to the stored active-workstream
* via `MSD_WORKSTREAM` env, falling back to the stored active-workstream
* pointer (mirrors `cmdInitProgress`'s own `_resolvedWorkstream` resolution
* above, the established authoritative source for "is a workstream active"
* in this file).
*
* `other_active_workstreams` hoists the resolver-in-body hazard out of
* `transition.md`'s `workstream-collision-check` step: that step's body
* previously re-derived this via an inline `gsd_run query workstream.list
* --raw` call gated on the identical `if [ -n "$GSD_WORKSTREAM" ]`
* previously re-derived this via an inline `msd_run query workstream.list
* --raw` call gated on the identical `if [ -n "$MSD_WORKSTREAM" ]`
* condition that now backs this section's OWN admission — resolving it here
* instead reuses `getOtherActiveWorkstreamInventories` (src/workstream-
* inventory.cts), the SAME primitive `workstream.list` itself calls
@@ -3537,7 +3537,7 @@ function cmdInitUpdate(cwd: string, raw: boolean, options: Record<string, unknow
* step's own prose described (excludes the current workstream and any
* workstream whose status contains "milestone complete" or "archived",
* case-insensitively — `isCompletedInventory`), so the step body becomes a
* pure JSON consumer with no `gsd_run` call of its own.
* pure JSON consumer with no `msd_run` call of its own.
*/
function cmdInitTransition(cwd: string, raw: boolean, options: Record<string, unknown> = {}): void {
// #3579 root-cause fix: read-only informational field — peek, don't
@@ -3563,9 +3563,9 @@ function cmdInitTransition(cwd: string, raw: boolean, options: Record<string, un
/**
* `debug.md`'s dedicated init entry point (#3149; prerequisite for #3128).
* `debug.md` previously carried NO `gsd_run query init.*` call at all — it made
* `debug.md` previously carried NO `msd_run query init.*` call at all — it made
* THREE separate round-trips instead: `state.load` (for `commit_docs`,
* `config.response_language` and `debug_dir`), `resolve-model gsd-debugger
* `config.response_language` and `debug_dir`), `resolve-model msd-debugger
* --pick model`, and `config-get workflow.tdd_mode --raw`. Because no
* debug-scoped fact was computed at any entry point, a `when=` atom naming one
* would have evaluated FALSE forever — ADR-1671's admission gate (2) and the
@@ -3584,14 +3584,14 @@ function cmdInitTransition(cwd: string, raw: boolean, options: Record<string, un
* - `debugger_model` — `resolveModelInternal`, which IS what `query
* resolve-model --pick model` returns (`cmdResolveModel`, src/commands.cts).
* - `tdd_mode` — the `Boolean(config.tdd_mode)` idiom `cmdInitExecutePhase` and
* `cmdInitPlanPhase` already use. `/gsd:debug` has no `--tdd` flag, so the
* `cmdInitPlanPhase` already use. `/msd:debug` has no `--tdd` flag, so the
* sibling handlers' `options['tdd'] ||` disjunct is deliberately omitted
* rather than carried as a phantom.
*
* `state.load` is deliberately NOT narrowed — see the note beside its own
* `debug_dir` field. This handler is purely additive alongside it.
*
* `diagnose` is the one flag `/gsd:debug` already documents. Exposing it as a
* `diagnose` is the one flag `/msd:debug` already documents. Exposing it as a
* top-level fact follows `cmdInitUpdate`'s `next_channel` and
* `cmdInitAutonomous`'s `plan_strategy_converge` precedent, and is what makes
* the router's flag forwarding observable. No `when=` atom consumes it yet:
@@ -3605,16 +3605,16 @@ function cmdInitDebug(cwd: string, raw: boolean, options: Record<string, unknown
const result: Record<string, unknown> = {
commit_docs: config.commit_docs,
// #2376: absolute — debug.md builds `debug_file_path` as
// `{debug_dir}/{slug}.md` for its gsd-debug-session-manager spawns, whose
// `{debug_dir}/{slug}.md` for its msd-debug-session-manager spawns, whose
// own cwd may differ from the orchestrator's.
debug_dir: toPosixPath(planningPaths(cwd).debug),
debugger_model: resolveModelInternal(cwd, 'gsd-debugger'),
debugger_model: resolveModelInternal(cwd, 'msd-debugger'),
tdd_mode: Boolean(config.tdd_mode),
diagnose: options['diagnose'] === true,
};
// Additive, optional field — degrades to null while `debug` has no key in
// `gsd-core/workflows/section-manifest.json` (it has no `gsd:section` markers
// `msd-core/workflows/section-manifest.json` (it has no `msd:section` markers
// until #3128). null means "read everything", which is NOT the same as a
// computed empty selection.
result['section_manifest'] = buildSectionManifestField(cwd, null, options, 'debug', {});
@@ -3635,8 +3635,8 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
// #1912: fail safe in workstream mode with no active workstream. With no active
// workstream and no --ws, planningDir(cwd) resolves to root .planning — silently
// reporting a stale root milestone. Require an explicit workstream instead.
// Mirror planningDir's resolution (GSD_WORKSTREAM env > stored active pointer) so
// an explicit --ws (which sets GSD_WORKSTREAM) satisfies the check.
// Mirror planningDir's resolution (MSD_WORKSTREAM env > stored active pointer) so
// an explicit --ws (which sets MSD_WORKSTREAM) satisfies the check.
const _availableWorkstreams = listAvailableWorkstreams(cwd);
// #3579 root-cause fix: this is a check, not a consuming read — use the
// non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
@@ -3654,7 +3654,7 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
if (_diagnosis.present) {
error(
`init.progress requires a workstream in workstream mode — the active-workstream marker names '${_diagnosis.value}', but it did not resolve: ${describeUnresolvedWorkstreamReason(_diagnosis.reason)}. Root STATE.md (likely stale) would be reported otherwise. ` +
`Pass --ws <name> or run ${formatGsdSlash('workstream set', _slashRuntime) as string} to point it at an existing workstream. ` +
`Pass --ws <name> or run ${formatMsdSlash('workstream set', _slashRuntime) as string} to point it at an existing workstream. ` +
`Available workstreams: ${_availableWorkstreams.join(', ')}`,
ERROR_REASON.WORKSTREAM_MODE_MARKER_UNRESOLVED,
{ marker_value: _diagnosis.value, marker_reason: _diagnosis.reason },
@@ -3662,7 +3662,7 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
}
error(
`init.progress requires a workstream in workstream mode — no active workstream is set, so root STATE.md (likely stale) would be reported. ` +
`Pass --ws <name> or run ${formatGsdSlash('workstream set', _slashRuntime) as string} first. ` +
`Pass --ws <name> or run ${formatMsdSlash('workstream set', _slashRuntime) as string} first. ` +
`Available workstreams: ${_availableWorkstreams.join(', ')}`,
ERROR_REASON.WORKSTREAM_MODE_NONE_ACTIVE,
);
@@ -3844,7 +3844,7 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
// #2994: the CURRENT phase's number, used both to expose `phase_mvp_mode`
// at the top level (so the `mvp-display` step body can consume an
// already-resolved fact instead of re-invoking `gsd_run query
// already-resolved fact instead of re-invoking `msd_run query
// phase.mvp-mode` itself — that inline resolver would otherwise gate a
// section on a fact the section's own body recomputes, which is circular
// and self-disabling) and to thread a real `phase_number` into
@@ -3857,8 +3857,8 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
const phaseMvpMode = detectPhaseMvpMode(cwd, currentPhaseNumber);
const result: Record<string, unknown> = {
executor_model: resolveModelInternal(cwd, 'gsd-executor'),
planner_model: resolveModelInternal(cwd, 'gsd-planner'),
executor_model: resolveModelInternal(cwd, 'msd-executor'),
planner_model: resolveModelInternal(cwd, 'msd-planner'),
commit_docs: config.commit_docs,
@@ -3901,8 +3901,8 @@ function cmdInitProgress(cwd: string, raw: boolean, options: Record<string, unkn
// planningDir(cwd, null) on the assumption that config.json is shared
// like PROJECT.md (workstream-flag.md's directory diagram marks it
// `# Shared`) — but ADR-0006's own tests (tests/init.test.cjs, "init
// handlers honor GSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and gsd-test
// handlers honor MSD_WORKSTREAM") assert config_path IS workstream-scoped
// for execute-phase/new-project/new-milestone/progress, and msd-test
// caught the regression immediately. The diagram is stale for
// config.json specifically (same class of staleness already found for
// `milestones/` during the #4455 follow-up) — reverted to the
@@ -3951,7 +3951,7 @@ function detectChildRepos(dir: string): { name: string; path: string; has_uncomm
function cmdInitNewWorkspace(cwd: string, raw: boolean): void {
const homedir = process.env['HOME'] || os.homedir();
const defaultBase = path.join(homedir, 'gsd-workspaces');
const defaultBase = path.join(homedir, 'msd-workspaces');
const childRepos = detectChildRepos(cwd);
@@ -3972,7 +3972,7 @@ function cmdInitNewWorkspace(cwd: string, raw: boolean): void {
function cmdInitListWorkspaces(cwd: string, raw: boolean): void {
const homedir = process.env['HOME'] || os.homedir();
const defaultBase = path.join(homedir, 'gsd-workspaces');
const defaultBase = path.join(homedir, 'msd-workspaces');
const workspaces: Record<string, unknown>[] = [];
if (fs.existsSync(defaultBase)) {
@@ -4026,7 +4026,7 @@ function cmdInitListWorkspaces(cwd: string, raw: boolean): void {
function cmdInitRemoveWorkspace(cwd: string, name: string | undefined, raw: boolean): void {
const homedir = process.env['HOME'] || os.homedir();
const defaultBase = path.join(homedir, 'gsd-workspaces');
const defaultBase = path.join(homedir, 'msd-workspaces');
if (!name) {
error('workspace name required for init remove-workspace');
@@ -4302,7 +4302,7 @@ function cmdAgentSkills(
// documented "unconfigured → empty block" contract that agent-skills tests
// pin.
//
// #4407 (ADR-4139 stream 2): this is the one place GSD's own agent-persona
// #4407 (ADR-4139 stream 2): this is the one place MSD's own agent-persona
// content is served through a real code seam rather than an eagerly
// @-included file, so the compact/canonical choice is made here in code
// (a real exit code) instead of a prose config-get gate. Compact is tried
@@ -4310,7 +4310,7 @@ function cmdAgentSkills(
// with the fallback disclosed in the payload itself, never a silent switch.
let agentPayloadVariant: 'compact' | 'canonical' | null = null;
if (!block) {
const runtime = (config && (config['runtime'] as string)) || process.env['GSD_RUNTIME'] || 'claude';
const runtime = (config && (config['runtime'] as string)) || process.env['MSD_RUNTIME'] || 'claude';
if (runtime !== 'claude') {
const agentCheck = checkAgentsInstalled(runtime, projectRoot) as unknown as { agents_dir?: string } | null;
const agentsDir = agentCheck?.agents_dir;
@@ -4326,7 +4326,7 @@ function cmdAgentSkills(
const canonicalContent = readNonEmptyFileOrNull(path.join(agentsDir, `${agentType}.md`));
if (canonicalContent !== null) {
block = compactRequested
? `<!-- gsd: no compact payload registered for ${agentType}; serving canonical -->\n\n${canonicalContent}`
? `<!-- msd: no compact payload registered for ${agentType}; serving canonical -->\n\n${canonicalContent}`
: canonicalContent;
agentPayloadVariant = 'canonical';
}
@@ -4434,7 +4434,7 @@ interface SkillManifest {
skills: SkillEntry[];
roots: RootSummary[];
installation: {
gsd_skills_installed: boolean;
msd_skills_installed: boolean;
legacy_claude_commands_installed: boolean;
};
counts: {
@@ -4512,7 +4512,7 @@ function buildSkillManifest(cwd: string, skillsDir: string | null = null): Skill
{
// Codex's deprecated fallback skill root ($CODEX_HOME/skills). Kept as a
// discovery-only legacy root so pre-move installs remain inventoried;
// GSD no longer installs here (#2088).
// MSD no longer installs here (#2088).
root: '~/.codex/skills',
path: path.join(getGlobalConfigDir('codex'), 'skills'),
scope: 'global',
@@ -4520,15 +4520,15 @@ function buildSkillManifest(cwd: string, skillsDir: string | null = null): Skill
deprecated: true,
},
{
root: '.claude/gsd-core/skills',
path: path.join(os.homedir(), '.claude', 'gsd-core', 'skills'),
root: '.claude/msd-core/skills',
path: path.join(os.homedir(), '.claude', 'msd-core', 'skills'),
scope: 'import-only',
kind: 'skills',
deprecated: true,
},
{
root: '.claude/commands/gsd',
path: path.join(os.homedir(), '.claude', 'commands', 'gsd'),
root: '.claude/commands/msd',
path: path.join(os.homedir(), '.claude', 'commands', 'msd'),
scope: 'legacy-commands',
kind: 'commands',
deprecated: true,
@@ -4580,7 +4580,7 @@ function buildSkillManifest(cwd: string, skillsDir: string | null = null): Skill
}
// Track skill names seen within this root to deduplicate dual-routed concretes
// (e.g. spec-phase nested under both gsd-ns-workflow and gsd-ns-manage).
// (e.g. spec-phase nested under both msd-ns-workflow and msd-ns-manage).
const seenNamesInRoot = new Set<string>();
function pushSkillEntry(
@@ -4640,9 +4640,9 @@ function buildSkillManifest(cwd: string, skillsDir: string | null = null): Skill
// Nested layout: <entry>/skills/<stem>/SKILL.md
// Used by cline, qwen, hermes, augment, trae, antigravity (#69 nested=true).
// Descend exactly one level into <entry>/skills/ — no deeper recursion.
// Scope to gsd-ns-* routers only: never vacuum up an unrelated user skill
// Scope to msd-ns-* routers only: never vacuum up an unrelated user skill
// that happens to have its own `skills/` subdirectory.
if (!entry.name.startsWith('gsd-ns-')) continue;
if (!entry.name.startsWith('msd-ns-')) continue;
const nestedSkillsDir = path.join(rootPath, entry.name, 'skills');
let nestedEntries: fs.Dirent[] = [];
try {
@@ -4672,13 +4672,13 @@ function buildSkillManifest(cwd: string, skillsDir: string | null = null): Skill
return rootCmp !== 0 ? rootCmp : a.name.localeCompare(b.name);
});
const gsdSkillsInstalled = skills.some((skill) => skill.name.startsWith('gsd-'));
const msdSkillsInstalled = skills.some((skill) => skill.name.startsWith('msd-'));
return {
skills,
roots,
installation: {
gsd_skills_installed: gsdSkillsInstalled,
msd_skills_installed: msdSkillsInstalled,
legacy_claude_commands_installed: legacyClaudeCommandsInstalled,
},
counts: {