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:
@@ -3,21 +3,21 @@
|
||||
/**
|
||||
* loop-walk.cjs — a small stateful harness around `tests/helpers.cjs` for the
|
||||
* loop QA walk. This module owns NONE of the subprocess mechanics itself
|
||||
* (spawn, retry-on-kill, quote-splitting) — those live in `runGsdTools` /
|
||||
* (spawn, retry-on-kill, quote-splitting) — those live in `runMsdTools` /
|
||||
* `createTempGitProject` and are reused verbatim. What this module adds is
|
||||
* the walk-specific concerns: turning a raw helper result into the typed
|
||||
* `RunResult` from `./result.cjs`, guaranteeing the child never inherits an
|
||||
* ambient `GSD_*` variable that would silently redirect the engine, pinning
|
||||
* ambient `MSD_*` variable that would silently redirect the engine, pinning
|
||||
* the clock so output is reproducible run-to-run, and giving callers a
|
||||
* content-free way to observe what the SUT wrote to disk.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { runGsdTools, cleanup } = require('../helpers.cjs');
|
||||
const { runMsdTools, cleanup } = require('../helpers.cjs');
|
||||
const { classify } = require('./result.cjs');
|
||||
const { createFixture } = require('../fixtures/index.cjs');
|
||||
const { LOOP_HOST_CONTRACT } = require('../../gsd-core/bin/lib/loop-host-contract.cjs');
|
||||
const { LOOP_HOST_CONTRACT } = require('../../msd-core/bin/lib/loop-host-contract.cjs');
|
||||
const { resolveWithin } = require('./paths.cjs');
|
||||
|
||||
/**
|
||||
@@ -48,19 +48,19 @@ const DEFAULT_NOW_MS = 1767225600000;
|
||||
* only passes `git: true` silently also gets `.planning/PROJECT.md` seeded.
|
||||
* A "greenfield" walk built that way would report `project_exists: true`
|
||||
* from its very first step, which makes the greenfield trajectory
|
||||
* meaningless: it can never exercise the create path (`/gsd-new-project`)
|
||||
* meaningless: it can never exercise the create path (`/msd-new-project`)
|
||||
* because a project already exists before the walk begins. Every entry
|
||||
* below therefore states `projectDoc` explicitly rather than relying on
|
||||
* that default, and `greenfield` is the harness default precisely because
|
||||
* an empty git repo with no `.planning/` at all is what a real user's
|
||||
* working tree looks like before running `/gsd-new-project` for the first
|
||||
* working tree looks like before running `/msd-new-project` for the first
|
||||
* time.
|
||||
*
|
||||
* @type {Record<string, (prefix: string) => string>}
|
||||
*/
|
||||
const FIXTURE_BUILDERS = {
|
||||
// A git repo with NO `.planning/` at all — what a user has before
|
||||
// `/gsd-new-project`.
|
||||
// `/msd-new-project`.
|
||||
greenfield: (prefix) => createFixture({ prefix, git: true, planning: false, projectDoc: false }),
|
||||
// `.planning/phases/` exists, but no PROJECT.md yet.
|
||||
planning: (prefix) => createFixture({ prefix, git: true, planning: true, projectDoc: false }),
|
||||
@@ -117,7 +117,7 @@ function collectStatSnapshot(root) {
|
||||
class LoopWalk {
|
||||
/**
|
||||
* @param {string} dir - absolute project root (an already-created temp git project).
|
||||
* @param {number} nowMs - pinned epoch ms passed to every `run()` as `GSD_NOW_MS`.
|
||||
* @param {number} nowMs - pinned epoch ms passed to every `run()` as `MSD_NOW_MS`.
|
||||
*/
|
||||
constructor(dir, nowMs) {
|
||||
this.dir = dir;
|
||||
@@ -150,7 +150,7 @@ class LoopWalk {
|
||||
* @returns {LoopWalk}
|
||||
*/
|
||||
static create(opts = {}) {
|
||||
const { prefix = 'gsd-loop-walk-', nowMs = DEFAULT_NOW_MS, fixture = 'greenfield' } = opts;
|
||||
const { prefix = 'msd-loop-walk-', nowMs = DEFAULT_NOW_MS, fixture = 'greenfield' } = opts;
|
||||
const build = FIXTURE_BUILDERS[fixture];
|
||||
if (!build) {
|
||||
throw new Error(
|
||||
@@ -162,7 +162,7 @@ class LoopWalk {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a `gsd-tools` invocation inside this walk's project and return the
|
||||
* Run a `msd-tools` invocation inside this walk's project and return the
|
||||
* typed `RunResult` from `classify()`.
|
||||
*
|
||||
* SIGNATURE: `run(...argvTokens)` where the LAST argument, if it is a
|
||||
@@ -176,7 +176,7 @@ class LoopWalk {
|
||||
*
|
||||
* WHY `jsonErrors` defaults to `true`: `--json-errors` (`docs/json-errors.md`)
|
||||
* is a real CLI flag, but it is the TOOLING/TEST surface — a human or a real
|
||||
* workflow invokes `gsd_run <cmd>` directly, WITHOUT it. Defaulting to
|
||||
* workflow invokes `msd_run <cmd>` directly, WITHOUT it. Defaulting to
|
||||
* `true` keeps every pre-existing call site's behavior byte-for-byte
|
||||
* unchanged (they all exercised `--json-errors` before this option
|
||||
* existed), while `{ jsonErrors: false }` lets a scenario step opt into
|
||||
@@ -184,16 +184,16 @@ class LoopWalk {
|
||||
* prove the tooling surface works and could never catch a regression a
|
||||
* real user would actually hit.
|
||||
*
|
||||
* WHY the env is built the way it is (ambient `GSD_*` sanitization):
|
||||
* `runGsdTools` composes the child env as
|
||||
* `{ ...process.env, ...TEST_ENV_BASE, ...env }` — so whatever `GSD_*`
|
||||
* WHY the env is built the way it is (ambient `MSD_*` sanitization):
|
||||
* `runMsdTools` composes the child env as
|
||||
* `{ ...process.env, ...TEST_ENV_BASE, ...env }` — so whatever `MSD_*`
|
||||
* variables happen to be set in the *parent* shell (e.g. a developer or CI
|
||||
* runner with `GSD_WORKSTREAM` / `GSD_PROJECT` exported for an unrelated
|
||||
* runner with `MSD_WORKSTREAM` / `MSD_PROJECT` exported for an unrelated
|
||||
* reason) flow straight through into the child and can silently redirect
|
||||
* the engine at a different workstream or project root than the one this
|
||||
* walk created — an invisible, non-deterministic test-pollution vector.
|
||||
* `runGsdTools`'s own merge order means the last object spread wins, so
|
||||
* this method builds an `env` override that sets EVERY ambient `GSD_*` key
|
||||
* `runMsdTools`'s own merge order means the last object spread wins, so
|
||||
* this method builds an `env` override that sets EVERY ambient `MSD_*` key
|
||||
* (scanned live from `process.env`, not a hardcoded list — a new leaking
|
||||
* var needs no code change here to be caught) to `undefined`, then layers
|
||||
* the two intentionally-pinned vars on top.
|
||||
@@ -203,29 +203,29 @@ class LoopWalk {
|
||||
* exercised here via `execFileSync`) iterates `Object.keys(env)` and
|
||||
* OMITS any key whose value is `undefined` from the actual `KEY=VALUE`
|
||||
* pairs handed to the OS — it does not stringify it to the literal text
|
||||
* `"undefined"`. That means `{ GSD_WORKSTREAM: undefined }` in the `env`
|
||||
* option makes the child process behave exactly as if `GSD_WORKSTREAM`
|
||||
* were never exported at all, even though `process.env.GSD_WORKSTREAM` is
|
||||
* `"undefined"`. That means `{ MSD_WORKSTREAM: undefined }` in the `env`
|
||||
* option makes the child process behave exactly as if `MSD_WORKSTREAM`
|
||||
* were never exported at all, even though `process.env.MSD_WORKSTREAM` is
|
||||
* still set and non-empty in the parent. This was verified empirically
|
||||
* (not assumed) — see the module verification transcript — because
|
||||
* `delete`-based approaches were not available here (the merge is inside
|
||||
* `runGsdTools`, not under this method's control) and a stringified
|
||||
* `runMsdTools`, not under this method's control) and a stringified
|
||||
* `"undefined"` would have been a silent correctness bug indistinguishable
|
||||
* from a passing run until an actual leak test caught it.
|
||||
*
|
||||
* ⚠️ KNOWN LIMIT — SUCCESS-PATH STDERR IS NOT OBSERVABLE THROUGH THIS
|
||||
* SUBSTRATE, SO `result.warnings` IS ERROR-PATH-ONLY TODAY: this method
|
||||
* builds `raw.stderr` as `result.success ? '' : (result.error ?? '')`
|
||||
* (below), and `runGsdTools` (`tests/helpers.cjs`) invokes the child via
|
||||
* (below), and `runMsdTools` (`tests/helpers.cjs`) invokes the child via
|
||||
* `execFileSync`, which discards the child's stderr stream entirely on a
|
||||
* clean (non-throwing) exit — Node never captures it, so there is no text
|
||||
* to forward even if this method wanted to. The practical effect: for any
|
||||
* exit-0 invocation, `classify()` always receives `stderr: ''`, so
|
||||
* `result.warnings` can never be non-empty on the success path, no matter
|
||||
* what the real `gsd-tools` process actually wrote to stderr. `warnings`
|
||||
* what the real `msd-tools` process actually wrote to stderr. `warnings`
|
||||
* only ever populates on the exit-1 (error) path, where `result.error`
|
||||
* (helpers.cjs's captured stderr-on-failure text) is threaded through.
|
||||
* Capturing success-path stderr would require changing `runGsdTools` /
|
||||
* Capturing success-path stderr would require changing `runMsdTools` /
|
||||
* `tests/helpers.cjs` (e.g. to `spawnSync`) — out of scope here because
|
||||
* that helper is shared by ~131 test files. DO NOT build an oracle that
|
||||
* assumes `.warnings` reflects success-path stderr; it structurally cannot
|
||||
@@ -246,17 +246,17 @@ class LoopWalk {
|
||||
/** @type {Record<string, string|undefined>} */
|
||||
const sanitize = {};
|
||||
for (const key of Object.keys(process.env)) {
|
||||
if (key.startsWith('GSD_')) sanitize[key] = undefined;
|
||||
if (key.startsWith('MSD_')) sanitize[key] = undefined;
|
||||
}
|
||||
const env = {
|
||||
...sanitize,
|
||||
GSD_TEST_MODE: '1',
|
||||
GSD_NOW_MS: String(this.nowMs),
|
||||
MSD_TEST_MODE: '1',
|
||||
MSD_NOW_MS: String(this.nowMs),
|
||||
};
|
||||
|
||||
let raw;
|
||||
try {
|
||||
const result = runGsdTools(argv, this.dir, env);
|
||||
const result = runMsdTools(argv, this.dir, env);
|
||||
raw = {
|
||||
exitCode: result.exitCode,
|
||||
stdout: result.output,
|
||||
@@ -265,7 +265,7 @@ class LoopWalk {
|
||||
argv,
|
||||
};
|
||||
} catch {
|
||||
// `runGsdTools` throws only after a retried, persistent subprocess
|
||||
// `runMsdTools` throws only after a retried, persistent subprocess
|
||||
// kill (host OOM / scheduler contention — see helpers.cjs
|
||||
// `throwResourceStarvation`). That is a statement about the HOST, not
|
||||
// the engine under test, so a walk must degrade to a TIMEOUT result
|
||||
@@ -308,7 +308,7 @@ class LoopWalk {
|
||||
* Remove this walk's temp project. Safe to call multiple times.
|
||||
*
|
||||
* `opts.keep` (default `false`) skips the removal entirely — the caller
|
||||
* (a QA-report run, typically via `--keep` / `GSD_QA_KEEP=1`) wants the
|
||||
* (a QA-report run, typically via `--keep` / `MSD_QA_KEEP=1`) wants the
|
||||
* failing/inspected tree left on disk for a human to `cd` into. When kept,
|
||||
* this returns `this.dir` so the caller can record it (e.g. as
|
||||
* `preservedDir` on a scenario report); when actually cleaned up, it
|
||||
|
||||
Reference in New Issue
Block a user