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:
@@ -4,7 +4,7 @@
|
||||
* Shared helper for the canonical shipped-agent roster.
|
||||
*
|
||||
* Several tests derive "the set of agents we ship" from the source `agents/`
|
||||
* directory via `fs.readdirSync(...).filter(/^gsd-.*\.md$/)`. This consolidates
|
||||
* directory via `fs.readdirSync(...).filter(/^msd-.*\.md$/)`. This consolidates
|
||||
* that hand-duplicated logic into one canonical SOURCE-roster derivation.
|
||||
*
|
||||
* NOTE: This returns the SOURCE roster (basenames without `.md`, sorted). Sites
|
||||
@@ -16,7 +16,7 @@
|
||||
* existing roster entry, served by the `#2454` fallback in `cmdAgentSkills`
|
||||
* (`src/init.cts`) when `workflow.compact_content` is on. Every roster-identity
|
||||
* check this helper backs (frontmatter/tools invariants, "every shipped agent"
|
||||
* install assertions, etc.) is about the SET of distinct agents GSD ships, which
|
||||
* install assertions, etc.) is about the SET of distinct agents MSD ships, which
|
||||
* a variant does not add to.
|
||||
*/
|
||||
|
||||
@@ -32,12 +32,12 @@ const AGENTS_DIR = path.join(__dirname, '..', '..', 'agents');
|
||||
*
|
||||
* @param {string} [agentsDir] Override for the source agents directory.
|
||||
* Defaults to the canonical `<repo-root>/agents`.
|
||||
* @returns {string[]} Sorted `gsd-*` basenames with `.md` stripped.
|
||||
* @returns {string[]} Sorted `msd-*` basenames with `.md` stripped.
|
||||
*/
|
||||
function listAgentFiles(agentsDir = AGENTS_DIR) {
|
||||
return fs
|
||||
.readdirSync(agentsDir)
|
||||
.filter((f) => /^gsd-.*\.md$/.test(f) && !f.endsWith('.compact.md'))
|
||||
.filter((f) => /^msd-.*\.md$/.test(f) && !f.endsWith('.compact.md'))
|
||||
.map((f) => f.replace(/\.md$/, ''))
|
||||
.sort();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CLI negative-matrix harness (#3593).
|
||||
*
|
||||
* Wraps spawnSync of gsd-core/bin/gsd-tools.cjs so test files can
|
||||
* Wraps spawnSync of msd-core/bin/msd-tools.cjs so test files can
|
||||
* assert on structured outputs (exit code, typed reason, stack-trace
|
||||
* absence) without each test re-implementing the JSON-errors parsing
|
||||
* dance. Hostile values are passed as argv elements — never composed
|
||||
@@ -29,12 +29,12 @@
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
|
||||
const TOOLS_PATH = path.resolve(__dirname, '..', '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
|
||||
const TOOLS_PATH = path.resolve(__dirname, '..', '..', 'msd-core', 'bin', 'msd-tools.cjs');
|
||||
|
||||
/**
|
||||
* Run gsd-tools with the given argv against a project directory.
|
||||
* Run msd-tools with the given argv against a project directory.
|
||||
*
|
||||
* @param {string[]} argv - argument vector passed to gsd-tools.cjs. Each
|
||||
* @param {string[]} argv - argument vector passed to msd-tools.cjs. Each
|
||||
* element reaches the child as a single argv element regardless of
|
||||
* shell metacharacters in its value.
|
||||
* @param {object} options
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
|
||||
/**
|
||||
* Build a hermetic "cold tree" install fixture for #3582 — a copy of hooks/
|
||||
* plus gsd-core/bin/ensure-runtime-build.cjs with the compiled
|
||||
* gsd-core/bin/lib/*.cjs directory and tsconfig.build.json deliberately
|
||||
* plus msd-core/bin/ensure-runtime-build.cjs with the compiled
|
||||
* msd-core/bin/lib/*.cjs directory and tsconfig.build.json deliberately
|
||||
* ABSENT, mirroring a raw plugin-marketplace / git-clone install that never
|
||||
* ran `npm run build:lib`.
|
||||
*
|
||||
* Deliberately NOT `tests/helpers/copy-script-fixture.cjs`'s
|
||||
* `copyScriptWithDeps`: that helper walks the require graph and copies every
|
||||
* dependency it finds — including gsd-core/bin/lib/*.cjs, which exist in
|
||||
* dependency it finds — including msd-core/bin/lib/*.cjs, which exist in
|
||||
* THIS repo's already-built tree — so it would faithfully reproduce a WARM
|
||||
* tree, the opposite of what a cold-tree test needs. This helper copies only
|
||||
* hooks/ and the seam module itself, and never touches gsd-core/bin/lib/ or
|
||||
* hooks/ and the seam module itself, and never touches msd-core/bin/lib/ or
|
||||
* tsconfig.build.json — so `ensureRuntimeBuild()` inside the fixture
|
||||
* deterministically throws `RuntimeBuildError` ("tsconfig.build.json not
|
||||
* found") the first time any fixture hook reaches it, without ever deleting
|
||||
* or touching the real repo's gsd-core/bin/lib/.
|
||||
* or touching the real repo's msd-core/bin/lib/.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
@@ -39,7 +39,7 @@ function shouldCopyHookEntry(name) {
|
||||
/**
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.repoRoot] TEST-ONLY: source tree root to copy hooks/
|
||||
* and gsd-core/bin/ensure-runtime-build.cjs from, in place of the real
|
||||
* and msd-core/bin/ensure-runtime-build.cjs from, in place of the real
|
||||
* REPO_ROOT. Lets a test point this fixture at a hermetic fake tree instead
|
||||
* of mutating the live repo's hooks/ directory (which other test files may
|
||||
* be concurrently reading). Defaults to REPO_ROOT.
|
||||
@@ -47,7 +47,7 @@ function shouldCopyHookEntry(name) {
|
||||
*/
|
||||
function buildColdInstallTree(opts = {}) {
|
||||
const repoRoot = opts.repoRoot || REPO_ROOT;
|
||||
const dir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'gsd-cold-tree-'));
|
||||
const dir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'msd-cold-tree-'));
|
||||
|
||||
// hooks/ — entire directory (top-level hook scripts + hooks/lib/*.js +
|
||||
// managed-hooks-registry.cjs + hooks.json). hooks/dist/ (gitignored,
|
||||
@@ -86,14 +86,14 @@ function buildColdInstallTree(opts = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
// gsd-core/bin/ensure-runtime-build.cjs — the seam itself. Deliberately
|
||||
// NOT gsd-core/bin/lib/ (absent — isBuilt() reads false) and NOT
|
||||
// msd-core/bin/ensure-runtime-build.cjs — the seam itself. Deliberately
|
||||
// NOT msd-core/bin/lib/ (absent — isBuilt() reads false) and NOT
|
||||
// tsconfig.build.json at the fixture root (absent — ensureRuntimeBuild's
|
||||
// "cannot auto-build" branch fires deterministically).
|
||||
fs.mkdirSync(path.join(dir, 'gsd-core', 'bin'), { recursive: true });
|
||||
fs.mkdirSync(path.join(dir, 'msd-core', 'bin'), { recursive: true });
|
||||
fs.copyFileSync(
|
||||
path.join(repoRoot, 'gsd-core', 'bin', 'ensure-runtime-build.cjs'),
|
||||
path.join(dir, 'gsd-core', 'bin', 'ensure-runtime-build.cjs'),
|
||||
path.join(repoRoot, 'msd-core', 'bin', 'ensure-runtime-build.cjs'),
|
||||
path.join(dir, 'msd-core', 'bin', 'ensure-runtime-build.cjs'),
|
||||
);
|
||||
|
||||
function cleanup() {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* spec this module implements — it is the source of truth for behavior; this
|
||||
* file is the mechanics.
|
||||
*
|
||||
* A "registered split" is any `gsd-core/workflows/<name>/detail/*.md` path
|
||||
* paired with the spine at `gsd-core/workflows/<name>.md`. There is no
|
||||
* A "registered split" is any `msd-core/workflows/<name>/detail/*.md` path
|
||||
* paired with the spine at `msd-core/workflows/<name>.md`. There is no
|
||||
* separate registry — a pair is registered by existing on disk
|
||||
* (`discoverRegisteredSplits`). Everything else here is one of the five
|
||||
* checks PARTITION-RULES.md describes, or a primitive those checks share:
|
||||
@@ -38,8 +38,8 @@ const {
|
||||
const { NEW_FILE_CAP, ACK_TRAILER_DELIM, normalizeAckReason } = require('./emitted-diff.cjs');
|
||||
const { runGit, OUTCOME } = require('./process-seam.cjs');
|
||||
|
||||
/** Default scan root: `gsd-core/workflows/` at repo root. */
|
||||
const DEFAULT_WORKFLOWS_DIR = path.join(__dirname, '..', '..', 'gsd-core', 'workflows');
|
||||
/** Default scan root: `msd-core/workflows/` at repo root. */
|
||||
const DEFAULT_WORKFLOWS_DIR = path.join(__dirname, '..', '..', 'msd-core', 'workflows');
|
||||
|
||||
/**
|
||||
* Discover every registered spine/detail split under `workflowsDir`.
|
||||
@@ -173,13 +173,13 @@ const KNOWN_BOILERPLATE_PREFIXES = [
|
||||
|
||||
/**
|
||||
* Is `line` one of the corpus's known sanctioned cross-file duplicates: the
|
||||
* canonical `gsd_run` launcher bootstrap preamble (see
|
||||
* `gsd-core/workflows/_runtime-launcher.snippet.sh`,
|
||||
* canonical `msd_run` launcher bootstrap preamble (see
|
||||
* `msd-core/workflows/_runtime-launcher.snippet.sh`,
|
||||
* `tests/runtime-launcher-parity.test.cjs`), or one of the `KNOWN_BOILERPLATE_PREFIXES`
|
||||
* above?
|
||||
*
|
||||
* The launcher preamble's own guard mandates exactly one inlined copy in every
|
||||
* workflow/detail file that calls `gsd_run` — spine and detail both call it,
|
||||
* workflow/detail file that calls `msd_run` — spine and detail both call it,
|
||||
* so both legitimately carry their own copy. That is sanctioned
|
||||
* cross-file duplication, not something the disjointness check should ever
|
||||
* flag, so it is filtered out wherever this module compares lines across a
|
||||
@@ -189,17 +189,17 @@ const KNOWN_BOILERPLATE_PREFIXES = [
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isKnownSanctionedBoilerplate(line) {
|
||||
if (line.startsWith('_GSD_SHIM_NAME="gsd-tools.cjs";')) return true;
|
||||
if (line.startsWith('_MSD_SHIM_NAME="msd-tools.cjs";')) return true;
|
||||
return KNOWN_BOILERPLATE_PREFIXES.some((prefix) => line.startsWith(prefix));
|
||||
}
|
||||
|
||||
const PROTECTED_START = '<!-- gsd:protected:start -->';
|
||||
const PROTECTED_END = '<!-- gsd:protected:end -->';
|
||||
const PROTECTED_SINGLE = '<!-- gsd:protected -->';
|
||||
const PROTECTED_START = '<!-- msd:protected:start -->';
|
||||
const PROTECTED_END = '<!-- msd:protected:end -->';
|
||||
const PROTECTED_SINGLE = '<!-- msd:protected -->';
|
||||
|
||||
/**
|
||||
* Extract every `<!-- gsd:protected -->` (single-line) and
|
||||
* `<!-- gsd:protected:start -->`/`<!-- gsd:protected:end -->` (paired)
|
||||
* Extract every `<!-- msd:protected -->` (single-line) and
|
||||
* `<!-- msd:protected:start -->`/`<!-- msd:protected:end -->` (paired)
|
||||
* sentinel-wrapped block from `content`.
|
||||
*
|
||||
* Never throws on a malformed sentinel (unclosed start, orphan end, a single
|
||||
@@ -207,7 +207,7 @@ const PROTECTED_SINGLE = '<!-- gsd:protected -->';
|
||||
* those are reported best-effort in `blocks` (covering whatever content is
|
||||
* actually present, or an empty span) AND named explicitly in `malformed`,
|
||||
* mirroring the well-formedness rules `tests/plan-phase-compact-split.test.cjs`'s
|
||||
* `'every gsd:protected sentinel...'` test already enforced for the one
|
||||
* `'every msd:protected sentinel...'` test already enforced for the one
|
||||
* existing split. The check-4 caller decides what to do with a malformed
|
||||
* sentinel; this function's job is only to describe the file accurately.
|
||||
*
|
||||
@@ -236,12 +236,12 @@ function extractProtectedBlocks(content) {
|
||||
// A new start while one is already open: the earlier open span is
|
||||
// abandoned (best-effort — it never gets a `blocks` entry) and this
|
||||
// one becomes the tracked open start.
|
||||
malformed.push({ line: i + 1, message: `nested/unclosed gsd:protected:start at line ${i + 1}` });
|
||||
malformed.push({ line: i + 1, message: `nested/unclosed msd:protected:start at line ${i + 1}` });
|
||||
}
|
||||
openStart = i;
|
||||
} else if (trimmed === PROTECTED_END) {
|
||||
if (openStart === -1) {
|
||||
malformed.push({ line: i + 1, message: `gsd:protected:end with no matching start at line ${i + 1}` });
|
||||
malformed.push({ line: i + 1, message: `msd:protected:end with no matching start at line ${i + 1}` });
|
||||
continue;
|
||||
}
|
||||
const covered = lines.slice(openStart + 1, i).map((l) => l.trim()).filter((l) => l.length > 0);
|
||||
@@ -259,7 +259,7 @@ function extractProtectedBlocks(content) {
|
||||
}
|
||||
|
||||
if (openStart !== -1) {
|
||||
malformed.push({ line: openStart + 1, message: 'a gsd:protected:start sentinel was never closed' });
|
||||
malformed.push({ line: openStart + 1, message: 'a msd:protected:start sentinel was never closed' });
|
||||
const covered = lines.slice(openStart + 1).map((l) => l.trim()).filter((l) => l.length > 0);
|
||||
blocks.push({ kind: 'paired', lines: covered, firstLine: covered[0] || '' });
|
||||
}
|
||||
@@ -446,7 +446,7 @@ function readBoundaryMoveTrailers({ baseRef, headRef = 'HEAD', cwd = REPO_ROOT,
|
||||
* what a given PR touched — this is what keeps duplication from creeping
|
||||
* back in after a split is made.
|
||||
*
|
||||
* The canonical `gsd_run` launcher preamble is excluded from both sides
|
||||
* The canonical `msd_run` launcher preamble is excluded from both sides
|
||||
* before comparing (`isKnownSanctionedBoilerplate`) — it is sanctioned
|
||||
* cross-file duplication under a different guard's contract, not a
|
||||
* violation of this one.
|
||||
@@ -526,12 +526,12 @@ function checkRegistration(splits, workflowsDir) {
|
||||
}
|
||||
}
|
||||
|
||||
// The prose spells these out in full — e.g. `gsd-core/workflows/plan-phase/
|
||||
// The prose spells these out in full — e.g. `msd-core/workflows/plan-phase/
|
||||
// detail/elaboration.md` — i.e. relative to the REPO ROOT (`workflowsDir`'s
|
||||
// grandparent: `workflowsDir` = `<root>/gsd-core/workflows`), not relative
|
||||
// grandparent: `workflowsDir` = `<root>/msd-core/workflows`), not relative
|
||||
// to `workflowsDir` itself (verified against plan-phase.md's real
|
||||
// references; resolving against `workflowsDir` directly double-prefixes
|
||||
// `gsd-core/workflows/` and false-positives every real reference).
|
||||
// `msd-core/workflows/` and false-positives every real reference).
|
||||
const repoRoot = path.join(workflowsDir, '..', '..');
|
||||
const referenced = new Set(spineContent.match(DETAIL_REFERENCE_PATTERN) || []);
|
||||
for (const referencedPath of referenced) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* Shared library for the compact-content VARIANT guard (ADR-4139, epic #4139,
|
||||
* Phase 6 #4406). See `gsd-core/references/compact-content-gate.md` §"Streams
|
||||
* Phase 6 #4406). See `msd-core/references/compact-content-gate.md` §"Streams
|
||||
* 1b and 4 — variant resolution" for the operational rule this module checks;
|
||||
* this file is the mechanics, not the source of truth for behavior.
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
* compact path is never named by any spine's "Read ... variant
|
||||
* resolution" call site is unwired dead weight).
|
||||
* 3. Protected content preserved — `checkProtectedContentPreserved` (a
|
||||
* `<!-- gsd:protected -->` block's lines must appear verbatim in BOTH
|
||||
* `<!-- msd:protected -->` block's lines must appear verbatim in BOTH
|
||||
* files, since nothing is "moved" in a variant pair — it is duplicated).
|
||||
* 4. Size smaller — `checkSizeSmaller`.
|
||||
* 5. Template consumer parity — NOT implemented here; it needs a real
|
||||
@@ -50,13 +50,13 @@ const { extractProtectedBlocks, normalizeNonTrivialLines } = require('./compact-
|
||||
|
||||
/** Default scan roots: everywhere a `.compact.md` sibling can legally live. */
|
||||
const DEFAULT_VARIANT_ROOTS = [
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'workflows'),
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'templates'),
|
||||
path.join(__dirname, '..', '..', 'msd-core', 'workflows'),
|
||||
path.join(__dirname, '..', '..', 'msd-core', 'templates'),
|
||||
];
|
||||
|
||||
/** Every markdown-source root a spine/fragment might name a variant path from. */
|
||||
const DEFAULT_SEARCH_ROOTS = [
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'workflows'),
|
||||
path.join(__dirname, '..', '..', 'msd-core', 'workflows'),
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -158,16 +158,16 @@ function checkRegistration(pairs) {
|
||||
* at least one markdown file under `searchRoots` (a spine's "Read ... variant
|
||||
* resolution" call site). Three needle forms, matched differently, because
|
||||
* this corpus has two live conventions for naming these paths (found by
|
||||
* walking up from the compact file itself to its nearest `gsd-core` ancestor,
|
||||
* walking up from the compact file itself to its nearest `msd-core` ancestor,
|
||||
* so this works the same way against the real repo and against a fixture
|
||||
* that builds its own `<tmp>/gsd-core/...` tree):
|
||||
* that builds its own `<tmp>/msd-core/...` tree):
|
||||
*
|
||||
* - The `gsd-core/<rest>` form (e.g. `gsd-core/workflows/autonomous/steps/
|
||||
* - The `msd-core/<rest>` form (e.g. `msd-core/workflows/autonomous/steps/
|
||||
* converge-fail-fast.md`'s own convention) is unambiguous on its own — a
|
||||
* different, longer path coincidentally ending in this exact multi-segment
|
||||
* suffix is not a realistic false positive, so a plain substring match is
|
||||
* sufficient without the "unprefixed" guard below.
|
||||
* - The `<rest>` form without the leading `gsd-core/` (e.g. `workflows/help/
|
||||
* - The `<rest>` form without the leading `msd-core/` (e.g. `workflows/help/
|
||||
* modes/full.compact.md`, `help.md`'s own dispatch-table convention) is
|
||||
* equally unambiguous for the same reason.
|
||||
* - The bare `<stem>.compact.md` form has no such guarantee — a same-named
|
||||
@@ -192,11 +192,11 @@ function checkReachability(pairs, searchRoots = DEFAULT_SEARCH_ROOTS) {
|
||||
}
|
||||
for (const pair of pairs) {
|
||||
if (!pair.canonicalExists) continue; // already reported by checkRegistration
|
||||
const gsdCoreRelative = relativeToNearestGsdCore(pair.compactPath);
|
||||
const msdCoreRelative = relativeToNearestMsdCore(pair.compactPath);
|
||||
const stem = path.basename(pair.compactPath, COMPACT_SUFFIX);
|
||||
const bareNeedle = `${stem}${COMPACT_SUFFIX}`;
|
||||
const reached = haystacks.some((text) => {
|
||||
if (gsdCoreRelative && (text.includes(`gsd-core/${gsdCoreRelative}`) || text.includes(gsdCoreRelative))) {
|
||||
if (msdCoreRelative && (text.includes(`msd-core/${msdCoreRelative}`) || text.includes(msdCoreRelative))) {
|
||||
return true;
|
||||
}
|
||||
return isUnprefixedMatch(text, bareNeedle);
|
||||
@@ -210,17 +210,17 @@ function checkReachability(pairs, searchRoots = DEFAULT_SEARCH_ROOTS) {
|
||||
|
||||
/**
|
||||
* Walk up from `filePath` to the nearest ancestor directory literally named
|
||||
* `gsd-core`, and return the path from there to `filePath` (POSIX-separated).
|
||||
* `msd-core`, and return the path from there to `filePath` (POSIX-separated).
|
||||
* Returns `null` if no such ancestor exists. Anchoring on the literal
|
||||
* `gsd-core` segment — rather than a hardcoded repo-root constant — is what
|
||||
* `msd-core` segment — rather than a hardcoded repo-root constant — is what
|
||||
* lets this match both the real repo and a fixture built under its own
|
||||
* `<tmp>/gsd-core/...` tree the same way.
|
||||
* `<tmp>/msd-core/...` tree the same way.
|
||||
* @param {string} filePath
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function relativeToNearestGsdCore(filePath) {
|
||||
function relativeToNearestMsdCore(filePath) {
|
||||
const segments = filePath.split(path.sep);
|
||||
const idx = segments.lastIndexOf('gsd-core');
|
||||
const idx = segments.lastIndexOf('msd-core');
|
||||
if (idx === -1) return null;
|
||||
return segments.slice(idx + 1).join('/');
|
||||
}
|
||||
@@ -244,7 +244,7 @@ function findMarkdownFiles(dir) {
|
||||
* lines in the canonical file must also appear (verbatim, after the same
|
||||
* normalization the partition guard uses) somewhere in the compact sibling.
|
||||
* Unlike the partition guard, this is NOT a sentinel-presence check on the
|
||||
* compact file itself — the compact file need not carry `<!-- gsd:protected -->`
|
||||
* compact file itself — the compact file need not carry `<!-- msd:protected -->`
|
||||
* markers of its own, since it is not itself audited for content it might
|
||||
* shed later; it only must not have DROPPED the protected wording.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* Preload fixture for #3808 — pin the SPAWNED context-monitor hook's
|
||||
* `Date.now()` to `GSD_TEST_NOW_MS`, so any wall-clock boundary can be driven
|
||||
* `Date.now()` to `MSD_TEST_NOW_MS`, so any wall-clock boundary can be driven
|
||||
* at exactly limit-1 / limit / limit+1. Added for STALE_SECONDS (review round 3,
|
||||
* Major 2); now also drives AC2, AC3, the COMPACT_GRACE_SECONDS trio and the
|
||||
* WATERMARK_SKEW_SECONDS trio, which is what lets those rows use the real
|
||||
@@ -17,7 +17,7 @@
|
||||
* render a date string inside the breadcrumb text, never for comparisons.
|
||||
*/
|
||||
|
||||
const fixed = Number(process.env.GSD_TEST_NOW_MS || '');
|
||||
const fixed = Number(process.env.MSD_TEST_NOW_MS || '');
|
||||
|
||||
if (Number.isFinite(fixed) && fixed > 0) {
|
||||
Date.now = () => fixed;
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
/**
|
||||
* Preload fixture for #3709 review Blocker 2 — force `fs.unlinkSync` to throw
|
||||
* EPERM inside the SPAWNED gsd-context-monitor.js hook, so the PreCompact
|
||||
* EPERM inside the SPAWNED msd-context-monitor.js hook, so the PreCompact
|
||||
* unlink-failure fallback branch is actually executed.
|
||||
*
|
||||
* `GSD_TEST_UNLINK_EPERM_MATCH` selects WHICH unlink fails: any path containing
|
||||
* `MSD_TEST_UNLINK_EPERM_MATCH` selects WHICH unlink fails: any path containing
|
||||
* the substring throws EPERM; every other path unlinks for real. That lets one
|
||||
* row fail only the warn sentinel (`-warned.json`) and another only the metrics
|
||||
* bridge, so each half of the fallback is pinned separately.
|
||||
*
|
||||
* Loaded via `node --require <this file> hooks/gsd-context-monitor.js` — the
|
||||
* Loaded via `node --require <this file> hooks/msd-context-monitor.js` — the
|
||||
* same seam as `shadow-report-throws-preload.cjs`, and for the same reasons:
|
||||
* this is NOT a chmod/mode-bit trick (CONTRIBUTING.md: those no-op under root,
|
||||
* so Docker/CI would pass the test with zero coverage), and NOT the in-process
|
||||
@@ -21,7 +21,7 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const realUnlinkSync = fs.unlinkSync;
|
||||
const match = process.env.GSD_TEST_UNLINK_EPERM_MATCH || '';
|
||||
const match = process.env.MSD_TEST_UNLINK_EPERM_MATCH || '';
|
||||
|
||||
if (match) {
|
||||
fs.unlinkSync = function unlinkSyncWithInjectedEperm(p) {
|
||||
@@ -34,12 +34,12 @@ if (match) {
|
||||
};
|
||||
}
|
||||
|
||||
// `GSD_TEST_LSTAT_CLAIMS_FILE_MATCH`: make lstat CLAIM a regular file for
|
||||
// `MSD_TEST_LSTAT_CLAIMS_FILE_MATCH`: make lstat CLAIM a regular file for
|
||||
// matching paths — the lstat→open substitution-race shape (a symlink swapped in
|
||||
// after the lstat), so the O_NOFOLLOW backstop is the guard actually under test
|
||||
// (review of #3808, round 3, Minor 3). The real stat object is returned with
|
||||
// only isFile overridden; everything else stays truthful.
|
||||
const lstatMatch = process.env.GSD_TEST_LSTAT_CLAIMS_FILE_MATCH || '';
|
||||
const lstatMatch = process.env.MSD_TEST_LSTAT_CLAIMS_FILE_MATCH || '';
|
||||
|
||||
if (lstatMatch) {
|
||||
const realLstatSync = fs.lstatSync;
|
||||
@@ -53,14 +53,14 @@ if (lstatMatch) {
|
||||
// being the guard under test (Codex review of #3808, round 3). The row
|
||||
// asserts this file exists.
|
||||
try {
|
||||
fs.writeFileSync(`${p}.gsd-test-lstat-claimed`, '1');
|
||||
fs.writeFileSync(`${p}.msd-test-lstat-claimed`, '1');
|
||||
} catch { /* marker is best-effort; the row will fail loudly without it */ }
|
||||
}
|
||||
return st;
|
||||
};
|
||||
}
|
||||
|
||||
// `GSD_TEST_SHRINK_AFTER_LSTAT_MATCH`: TRUNCATE a matching file immediately after
|
||||
// `MSD_TEST_SHRINK_AFTER_LSTAT_MATCH`: TRUNCATE a matching file immediately after
|
||||
// `lstatSync` has measured it, so the subsequent `readSync` returns FEWER bytes than
|
||||
// the size the guard allocated for. That is the shape round 11's Minor names — a
|
||||
// concurrent legitimate writer truncating mid-write, independent of the planted-object
|
||||
@@ -69,7 +69,7 @@ if (lstatMatch) {
|
||||
//
|
||||
// Layered over the lstat wrapper above rather than folded into it: the two injections
|
||||
// are selected by different env vars and a row may want either alone.
|
||||
const shrinkMatch = process.env.GSD_TEST_SHRINK_AFTER_LSTAT_MATCH || '';
|
||||
const shrinkMatch = process.env.MSD_TEST_SHRINK_AFTER_LSTAT_MATCH || '';
|
||||
|
||||
if (shrinkMatch) {
|
||||
const beforeShrink = fs.lstatSync;
|
||||
@@ -85,19 +85,19 @@ if (shrinkMatch) {
|
||||
// landed or not. The marker is the only durable evidence that this injection fired.
|
||||
try {
|
||||
fs.truncateSync(p, 1);
|
||||
fs.writeFileSync(`${p}.gsd-test-shrunk`, '1');
|
||||
fs.writeFileSync(`${p}.msd-test-shrunk`, '1');
|
||||
} catch { /* best effort — the row asserts the marker and fails loudly without it */ }
|
||||
}
|
||||
return st;
|
||||
};
|
||||
}
|
||||
|
||||
// `GSD_TEST_SHORT_WRITE_MATCH`: make `fs.writeSync` write only the FIRST BYTE for a
|
||||
// `MSD_TEST_SHORT_WRITE_MATCH`: make `fs.writeSync` write only the FIRST BYTE for a
|
||||
// matching fd's payload, once. A short write is permitted by the syscall and was
|
||||
// previously discarded, so a truncated sentinel reached disk and every later read
|
||||
// rejected it — the debounce accounting and the compaction watermark silently lost.
|
||||
// The injection fires once so the retry loop under test can complete normally.
|
||||
const shortWriteMatch = process.env.GSD_TEST_SHORT_WRITE_MATCH || '';
|
||||
const shortWriteMatch = process.env.MSD_TEST_SHORT_WRITE_MATCH || '';
|
||||
|
||||
if (shortWriteMatch) {
|
||||
const realWriteSync = fs.writeSync;
|
||||
@@ -105,7 +105,7 @@ if (shortWriteMatch) {
|
||||
fs.writeSync = function writeSyncShort(fd, buf, off, len) {
|
||||
if (!fired && Buffer.isBuffer(buf) && len > 1) {
|
||||
fired = true;
|
||||
try { fs.writeFileSync(`${shortWriteMatch}.gsd-test-short-write`, '1'); } catch { /* marker best effort */ }
|
||||
try { fs.writeFileSync(`${shortWriteMatch}.msd-test-short-write`, '1'); } catch { /* marker best effort */ }
|
||||
return realWriteSync.call(fs, fd, buf, off, 1);
|
||||
}
|
||||
return realWriteSync.apply(fs, arguments);
|
||||
|
||||
@@ -39,7 +39,7 @@ const RELATIVE_SPEC_RE = /^\.{1,2}[\\/]/;
|
||||
* script relies on exactly this (`scripts/check-coverage-gate.cjs` has a
|
||||
* top-level `return`), so dropping the flag silently breaks the #2858
|
||||
* packaging guard that scans it. Do not remove this without re-verifying
|
||||
* every shipped `scripts/**`, `bin/**`, and `gsd-core/bin/**` .cjs/.js file
|
||||
* every shipped `scripts/**`, `bin/**`, and `msd-core/bin/**` .cjs/.js file
|
||||
* still parses.
|
||||
*/
|
||||
const PARSE_ATTEMPTS = [
|
||||
@@ -160,7 +160,7 @@ function escapesContainment(rel) {
|
||||
*
|
||||
* Throws when a relative require does not resolve on disk, or when the entry
|
||||
* or a dependency resolves outside the repo. Unresolved-require failures are
|
||||
* nearly always an unbuilt artifact (`gsd-core/bin/lib/*.cjs` requires
|
||||
* nearly always an unbuilt artifact (`msd-core/bin/lib/*.cjs` requires
|
||||
* `npm run build:lib`), and failing here names the cause instead of letting
|
||||
* the spawned subprocess die with a bare MODULE_NOT_FOUND.
|
||||
*
|
||||
@@ -255,7 +255,7 @@ function copyScriptWithDeps(repoRoot, fixtureRoot, scriptRel) {
|
||||
`copyScriptWithDeps(${entry}) could not resolve ${unresolved.length} ` +
|
||||
`relative require(s); the fixture would fail with MODULE_NOT_FOUND:\n` +
|
||||
unresolved.map((u) => ` - ${u}`).join('\n') +
|
||||
`\nIf these are compiled artifacts under gsd-core/bin/lib/, run \`npm run build:lib\`.`,
|
||||
`\nIf these are compiled artifacts under msd-core/bin/lib/, run \`npm run build:lib\`.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
*
|
||||
* Keep it that way: anything that sets this variable is asserting "use THIS, or stop".
|
||||
*/
|
||||
const BASELINE_ENV = 'GSD_EMITTED_BASELINE';
|
||||
const BASELINE_ENV = 'MSD_EMITTED_BASELINE';
|
||||
|
||||
/** Default on-disk cache location, relative to the repo root. */
|
||||
const DEFAULT_CACHE_PATH = '.gsd-cache/emitted-baseline.json';
|
||||
const DEFAULT_CACHE_PATH = '.msd-cache/emitted-baseline.json';
|
||||
|
||||
/** Baseline artifact schema version — pinned so a format change fails loudly.
|
||||
* v2 (#3547): the harness now measures every global family in the REAL
|
||||
@@ -115,7 +115,7 @@ function validateBaseline(doc, expectedSha, source) {
|
||||
* Resolve the baseline through the documented precedence, reporting WHICH step supplied
|
||||
* it so a failure message can say where the answer came from.
|
||||
*
|
||||
* 1. `GSD_EMITTED_BASELINE` — an operator pin; a mismatch here is a HARD STOP
|
||||
* 1. `MSD_EMITTED_BASELINE` — an operator pin; a mismatch here is a HARD STOP
|
||||
* 2. the on-disk cache, validated against the expected sha — a mismatch RECOVERS
|
||||
* 3. an in-job build at `origin/next` (slow fallback)
|
||||
* 4. none → explicit failure (NEVER a silent pass)
|
||||
|
||||
@@ -150,11 +150,11 @@ const REMEDIATION = Object.freeze({
|
||||
addTrailerGrowth:
|
||||
'Add a trailer to a commit in this PR (never a new file). Use the '
|
||||
+ `\`${ACK_TRAILER_GROWTH}\` trailer for growth (key = the bare filename as it `
|
||||
+ 'appears under gsd-core/workflows/ or agents/).',
|
||||
+ 'appears under msd-core/workflows/ or agents/).',
|
||||
doNotRegenerate:
|
||||
'Do NOT regenerate anything to silence this — there is nothing left to regenerate.',
|
||||
/** The size ratchet keys on `entry.name` from readdirSync (emitted-runtime.cjs `currentSizes`). */
|
||||
growthKeyRule: 'Key on the BARE FILENAME as it appears under gsd-core/workflows/ or agents/',
|
||||
growthKeyRule: 'Key on the BARE FILENAME as it appears under msd-core/workflows/ or agents/',
|
||||
/** The hash pass keys on the emitted manifest path, which always carries a `/`. */
|
||||
rippleKeyRule: 'Key on the EMITTED PATH exactly as printed above',
|
||||
rippleReason: '<why this ripple is deliberate>',
|
||||
@@ -171,7 +171,7 @@ const REMEDIATION = Object.freeze({
|
||||
* negative-space note.
|
||||
*/
|
||||
ackTrailerExample: renderAckTrailer(
|
||||
ACK_TRAILER_HASH, 'skills/gsd-add-tests/SKILL.md', 'converter rewrite, ADR-2719',
|
||||
ACK_TRAILER_HASH, 'skills/msd-add-tests/SKILL.md', 'converter rewrite, ADR-2719',
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
* stays hard-coded, so a dest change still fails loud.
|
||||
*
|
||||
* ── The trap this table exists to avoid ─────────────────────────────────────
|
||||
* The repo contains `skills/gsd-<stem>/SKILL.md` (71 dirs) that LOOK like the source
|
||||
* The repo contains `skills/msd-<stem>/SKILL.md` (71 dirs) that LOOK like the source
|
||||
* of the emitted `skills/` family. They are not: scripts/gen-plugin-skills.cjs
|
||||
* GENERATES them from commands/gsd/*.md, and the installer stages from commands/gsd/
|
||||
* GENERATES them from commands/msd/*.md, and the installer stages from commands/msd/
|
||||
* directly (src/install-profiles.cts:637-708). Attributing emitted skills to repo
|
||||
* skills/ would be false attribution that still passes totality — the exact residual
|
||||
* risk ADR-2719 records. The spot-check tests pin this pair.
|
||||
@@ -59,14 +59,14 @@ const { MANIFEST_FAMILIES, runMinimalInstall, buildParityManifest } = require('.
|
||||
const EXPECTED_MANIFEST_COUNT = MANIFEST_FAMILIES.length;
|
||||
|
||||
// ─── Emitted roots ────────────────────────────────────────────────────────────
|
||||
// Longest-first: `skills/gsd` (hermes category dir) must win over `skills` for
|
||||
// `skills/gsd/...`, and `.agents/skills` must win over `.agents`.
|
||||
// Longest-first: `skills/msd` (hermes category dir) must win over `skills` for
|
||||
// `skills/msd/...`, and `.agents/skills` must win over `.agents`.
|
||||
|
||||
const SKILLS_ROOTS = ['skills/gsd', '.agents/skills', 'skills'];
|
||||
const SKILLS_ROOTS = ['skills/msd', '.agents/skills', 'skills'];
|
||||
const HOOKS_ROOTS = ['.kimi-code/hooks', '.kimi/hooks', 'hooks'];
|
||||
|
||||
/** Source-of-truth command dir every skill/command surface converts from. */
|
||||
const COMMANDS_SRC = 'commands/gsd';
|
||||
const COMMANDS_SRC = 'commands/msd';
|
||||
|
||||
/** Installer source file that emits the Cline/AGENTS.md instruction bodies as
|
||||
* code literals (buildClineRulesBody / buildClineAgentsMdBody /
|
||||
@@ -168,7 +168,7 @@ const AGENT_TRANSFORM_SRCS = [
|
||||
// #3738: antigravity's global skills pass through the antigravity converter
|
||||
// (src/runtime-artifact-conversion.cts, mirrored hand-authored in bin/install.js
|
||||
// per ADR-1508), whose rewrites — e.g. ~/.claude/skills/ → ~/.gemini/config/skills/
|
||||
// — can move emitted bytes with NO commands/gsd source changing. Declaring the
|
||||
// — can move emitted bytes with NO commands/msd source changing. Declaring the
|
||||
// transform here attributes that ripple class permanently, the same way
|
||||
// AGENT_TRANSFORM_SRCS does for agents; scoped to runtime 'antigravity' so a
|
||||
// converter change never blankets the other skills runtimes' attribution.
|
||||
@@ -179,7 +179,7 @@ const ANTIGRAVITY_SKILL_TRANSFORM_SRCS = [
|
||||
|
||||
// #4002: zcode's command AND skill bodies flow through `_applyRuntimeRewrites`
|
||||
// (converter: null — the rewrite pass is their only path-rewriting step), so a
|
||||
// converter change moves emitted bytes with no commands/gsd source changing.
|
||||
// converter change moves emitted bytes with no commands/msd source changing.
|
||||
// Same permanent-attribution shape as ANTIGRAVITY_SKILL_TRANSFORM_SRCS (#3738),
|
||||
// scoped to runtime 'zcode' for the same reason.
|
||||
const ZCODE_BODY_TRANSFORM_SRCS = [
|
||||
@@ -214,7 +214,7 @@ const SOURCE_PREFIX_SUFFIX = '/';
|
||||
* stem (src/install-profiles.cts:696 joins `stem`, not `prefix + stem`).
|
||||
*/
|
||||
function stripSkillPrefix(dirName) {
|
||||
return dirName.startsWith('gsd-') ? dirName.slice(4) : dirName;
|
||||
return dirName.startsWith('msd-') ? dirName.slice(4) : dirName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,10 +227,10 @@ function nativePluginDescriptor(runtime) {
|
||||
// rather than at module load for callers that never touch this family.
|
||||
let registry;
|
||||
try {
|
||||
registry = require('../../gsd-core/bin/lib/capability-registry.cjs');
|
||||
registry = require('../../msd-core/bin/lib/capability-registry.cjs');
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
'emitted-provenance: cannot load gsd-core/bin/lib/capability-registry.cjs ' +
|
||||
'emitted-provenance: cannot load msd-core/bin/lib/capability-registry.cjs ' +
|
||||
`(run \`npm run build\` first): ${err.message}`,
|
||||
);
|
||||
}
|
||||
@@ -282,18 +282,18 @@ function nativePluginDescriptor(runtime) {
|
||||
const PROVENANCE_RULES = [
|
||||
// ── Verbatim engine payload ────────────────────────────────────────────────
|
||||
{
|
||||
id: 'gsd-core-verbatim',
|
||||
id: 'msd-core-verbatim',
|
||||
// Markdown payloads pass through copyWithPathReplacement's audience
|
||||
// filter for non-Copilot runtimes. Non-Markdown payloads remain byte-for-
|
||||
// byte copies, but one rule has one kind; the match-specific transform
|
||||
// list below keeps the causal attribution precise.
|
||||
kind: 'derived',
|
||||
roots: ['gsd-core'],
|
||||
// Enumerated subdirs, NOT `.+`: a new gsd-core/<subdir> must fail totality
|
||||
roots: ['msd-core'],
|
||||
// Enumerated subdirs, NOT `.+`: a new msd-core/<subdir> must fail totality
|
||||
// loudly rather than being absorbed silently. Also keeps this mutually
|
||||
// exclusive with the two synthesized gsd-core top-level files below.
|
||||
// exclusive with the two synthesized msd-core top-level files below.
|
||||
pattern: /^(workflows|references|templates|contexts|bin)\/.+$/,
|
||||
sources: (m) => [`gsd-core/${m[0]}`],
|
||||
sources: (m) => [`msd-core/${m[0]}`],
|
||||
transforms: (m, ctx) => (
|
||||
m[0].endsWith('.md') && RUNTIME_NOTE_FILTERED_FAMILIES.has(ctx.runtime)
|
||||
? RUNTIME_NOTE_FILTER_TRANSFORM_SRCS
|
||||
@@ -301,17 +301,17 @@ const PROVENANCE_RULES = [
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'gsd-core-commands-corpus',
|
||||
id: 'msd-core-commands-corpus',
|
||||
kind: 'rewrite',
|
||||
roots: ['gsd-core'],
|
||||
pattern: /^commands\/gsd\/(.+)$/,
|
||||
sources: (m) => [`commands/gsd/${m[1]}`],
|
||||
roots: ['msd-core'],
|
||||
pattern: /^commands\/msd\/(.+)$/,
|
||||
sources: (m) => [`commands/msd/${m[1]}`],
|
||||
transforms: [INSTALL_ENGINE_SRC, INSTALLER_SRC],
|
||||
},
|
||||
{
|
||||
id: 'gsd-core-agents-corpus',
|
||||
id: 'msd-core-agents-corpus',
|
||||
kind: 'rewrite',
|
||||
roots: ['gsd-core'],
|
||||
roots: ['msd-core'],
|
||||
pattern: /^agents\/(.+)$/,
|
||||
sources: (m) => [`agents/${m[1]}`],
|
||||
transforms: [INSTALL_ENGINE_SRC, INSTALLER_SRC],
|
||||
@@ -330,7 +330,7 @@ const PROVENANCE_RULES = [
|
||||
id: 'agents-verbatim',
|
||||
// #2757 (was `identity`): measured against origin/next's own committed fixtures,
|
||||
// the SAME emitted agents/<name>.md hashes DIFFERENTLY per runtime (e.g. codex vs
|
||||
// claude for gsd-nyquist-auditor.md) — impossible for a true verbatim copy.
|
||||
// claude for msd-nyquist-auditor.md) — impossible for a true verbatim copy.
|
||||
// Verified empirically by installing every runtime and diffing the output against
|
||||
// the raw repo source: no runtime reproduces it byte-for-byte. Every one rewrites
|
||||
// frontmatter quoting and the hardcoded `.claude/` self-reference
|
||||
@@ -340,15 +340,15 @@ const PROVENANCE_RULES = [
|
||||
// per-runtime `kind`) and why this wholesale reclassification was chosen instead.
|
||||
kind: 'derived',
|
||||
roots: ['agents'],
|
||||
// Excludes `gsd.md`: that is Kimi's ROOT agent, built from a code literal and
|
||||
// Excludes `msd.md`: that is Kimi's ROOT agent, built from a code literal and
|
||||
// NOT a repo agent file. Without the exclusion it matched here and resolved to
|
||||
// `agents/gsd.md`, which does not exist — a false attribution that still passed
|
||||
// `agents/msd.md`, which does not exist — a false attribution that still passed
|
||||
// totality, i.e. the exact residual ADR-2719 records. Every repo agent is
|
||||
// `gsd-<name>.md`, so excluding the bare `gsd.md` is precise.
|
||||
// `msd-<name>.md`, so excluding the bare `msd.md` is precise.
|
||||
// `.agent.md` is likewise excluded — Copilot emits a RENAMED copy
|
||||
// (`<name>.agent.md`) whose source is `agents/<name>.md`; matching it here
|
||||
// resolved to a file that does not exist. Same false-attribution class.
|
||||
pattern: /^(?!gsd\.md$)(?!.*\.agent\.md$)[^/]+\.md$/,
|
||||
pattern: /^(?!msd\.md$)(?!.*\.agent\.md$)[^/]+\.md$/,
|
||||
sources: (m) => [`agents/${m[0]}`],
|
||||
transforms: AGENT_TRANSFORM_SRCS,
|
||||
},
|
||||
@@ -401,7 +401,7 @@ const PROVENANCE_RULES = [
|
||||
// Attribute to the REPO source a PR actually edits, not the build artifact.
|
||||
// Excludes Copilot's hook-registration JSON (next rule) — that is a code
|
||||
// literal, not a built script, and attributing it here resolved to a
|
||||
// nonexistent `hooks/gsd-session.json`. `package.json` is excluded for the
|
||||
// nonexistent `hooks/msd-session.json`. `package.json` is excluded for the
|
||||
// same reason (the #2544 `commonjs-marker` rule below): there is no
|
||||
// `hooks/package.json` in the repo to attribute to.
|
||||
//
|
||||
@@ -414,11 +414,11 @@ const PROVENANCE_RULES = [
|
||||
// (HOOKS_WINDOWS_SHIM_SRC, src/runtime-hooks-surface.cts). The `.cmd` bytes
|
||||
// are `@ECHO OFF\r\n@SETLOCAL\r\n@<runner> <script> %*\r\n` — built from the
|
||||
// install-time interpreter token and the absolute install path, plus a
|
||||
// hardcoded literal filename (e.g. `gsd-check-update.js`) baked into that
|
||||
// hardcoded literal filename (e.g. `msd-check-update.js`) baked into that
|
||||
// same source file. The wrapped `.js` file's NAME flows into the `.cmd`
|
||||
// bytes; its CONTENT never does — see the `sources` comment below for why
|
||||
// that rules out attributing to `hooks/<name>.js`.
|
||||
pattern: /^(?!gsd-session\.json$|package\.json$).+$/,
|
||||
pattern: /^(?!msd-session\.json$|package\.json$).+$/,
|
||||
// `package.json` (the CommonJS marker) is excluded here and owned by the
|
||||
// dedicated `commonjs-marker` rule below. #2717 attributed it inside THIS
|
||||
// rule, routing it to HOOKS_WINDOWS_SHIM_SRC because at that point
|
||||
@@ -447,11 +447,11 @@ const PROVENANCE_RULES = [
|
||||
{
|
||||
id: 'commonjs-marker',
|
||||
kind: 'code-derived',
|
||||
// Every root GSD stages its own `.js` files into and therefore pins to
|
||||
// Every root MSD stages its own `.js` files into and therefore pins to
|
||||
// CommonJS: the hooks roots, plus the native plugin/extension dirs.
|
||||
roots: [...HOOKS_ROOTS, 'plugins', 'extensions'],
|
||||
// #2544: a `{"type":"commonjs"}` module-type marker, written as a code
|
||||
// literal so Node's ancestor walk resolves GSD's staged `.js` files as
|
||||
// literal so Node's ancestor walk resolves MSD's staged `.js` files as
|
||||
// CommonJS under an ambient `"type": "module"`. Like the Copilot
|
||||
// registration JSON above it is emitted, never built — there is no
|
||||
// `hooks/package.json` or `plugins/package.json` in the repo, so the
|
||||
@@ -497,12 +497,12 @@ const PROVENANCE_RULES = [
|
||||
},
|
||||
{
|
||||
// #3023: pi renames the shared hooks bundle's staged directory from the
|
||||
// default `hooks/` to `gsd-hooks/` (hostBehaviors.sharedHooksDirName,
|
||||
// default `hooks/` to `msd-hooks/` (hostBehaviors.sharedHooksDirName,
|
||||
// bin/install.js's resolveSharedHooksDirName). Same family as `hooks-built`
|
||||
// above (built from hooks/ via scripts/build-hooks.js), staged under a
|
||||
// different root for exactly one runtime — a dedicated, `runtimes`-scoped
|
||||
// rule keeps that pi-only rename from ever being able to shadow another
|
||||
// host's `(rel, runtime)` pair, rather than folding 'gsd-hooks' into the
|
||||
// host's `(rel, runtime)` pair, rather than folding 'msd-hooks' into the
|
||||
// shared HOOKS_ROOTS list `hooks-built`/`commonjs-marker` both key off.
|
||||
// `package.json` (the CommonJS marker) is excluded here and owned by the
|
||||
// dedicated `pi-shared-hooks-commonjs-marker` rule below, mirroring how
|
||||
@@ -510,19 +510,19 @@ const PROVENANCE_RULES = [
|
||||
id: 'pi-shared-hooks-built',
|
||||
kind: 'derived',
|
||||
runtimes: new Set(['pi']),
|
||||
roots: ['gsd-hooks'],
|
||||
roots: ['msd-hooks'],
|
||||
pattern: /^(?!package\.json$).+$/,
|
||||
sources: (m) => [`hooks/${m[0]}`],
|
||||
},
|
||||
{
|
||||
// Companion to `pi-shared-hooks-built`: the CommonJS-mode marker written
|
||||
// into pi's renamed `gsd-hooks/` root by the same installSharedHooksBundle
|
||||
// into pi's renamed `msd-hooks/` root by the same installSharedHooksBundle
|
||||
// call the generic `commonjs-marker` rule attributes for the `hooks/`
|
||||
// family. Kept as its own pi-scoped rule for the same reason as above.
|
||||
id: 'pi-shared-hooks-commonjs-marker',
|
||||
kind: 'code-derived',
|
||||
runtimes: new Set(['pi']),
|
||||
roots: ['gsd-hooks'],
|
||||
roots: ['msd-hooks'],
|
||||
pattern: /^package\.json$/,
|
||||
sources: () => [COMMONJS_MARKER_SRC, INSTALLER_SRC],
|
||||
},
|
||||
@@ -534,11 +534,11 @@ const PROVENANCE_RULES = [
|
||||
// by HOOK_CONFIG_FILES because they embed a platform-varying node-runner
|
||||
// command), this one is platform-stable and stays in the manifest
|
||||
// (src/runtime-hooks-surface.cts:73,89).
|
||||
pattern: /^gsd-session\.json$/,
|
||||
pattern: /^msd-session\.json$/,
|
||||
sources: () => [CLINE_BODY_SRC],
|
||||
},
|
||||
|
||||
// ── Skill / command surfaces — all convert from commands/gsd/*.md ──────────
|
||||
// ── Skill / command surfaces — all convert from commands/msd/*.md ──────────
|
||||
{
|
||||
id: 'skills-from-commands',
|
||||
kind: 'rewrite',
|
||||
@@ -577,7 +577,7 @@ const PROVENANCE_RULES = [
|
||||
id: 'flat-commands-from-commands',
|
||||
kind: 'rewrite',
|
||||
roots: ['commands', 'command'],
|
||||
pattern: /^gsd-([^/]+)\.md$/,
|
||||
pattern: /^msd-([^/]+)\.md$/,
|
||||
sources: (m) => [`${COMMANDS_SRC}/${m[1]}.md`],
|
||||
// #4482: OpenCode command bodies pass through its command converter.
|
||||
// #4002: zcode command bodies pass through _applyRuntimeRewrites with
|
||||
@@ -596,7 +596,7 @@ const PROVENANCE_RULES = [
|
||||
roots: ['plugins', 'extensions'],
|
||||
pattern: /^[^/]+\.(js|cjs|mjs)$/,
|
||||
// Source is per-runtime (opencode -> .opencode/…, kilo -> .kilo/…,
|
||||
// pi -> pi/gsd.cjs), so attribution is a function of (rel, runtime).
|
||||
// pi -> pi/msd.cjs), so attribution is a function of (rel, runtime).
|
||||
sources: (m, ctx) => {
|
||||
const np = nativePluginDescriptor(ctx.runtime);
|
||||
if (!np || !np.source) {
|
||||
@@ -616,15 +616,15 @@ const PROVENANCE_RULES = [
|
||||
id: 'cline-rules-code-derived',
|
||||
kind: 'code-derived',
|
||||
roots: ['.clinerules'],
|
||||
pattern: /^(gsd\.md|hooks\/PreToolUse)$/,
|
||||
pattern: /^(msd\.md|hooks\/PreToolUse)$/,
|
||||
sources: () => [CLINE_BODY_SRC],
|
||||
},
|
||||
// #3547 removed `agents-md-code-derived` (roots: ['.agents'],
|
||||
// ^AGENTS\.md$) and `synthesized-gsd-defaults` (^\.gsd/defaults\.json$):
|
||||
// ^AGENTS\.md$) and `synthesized-msd-defaults` (^\.msd/defaults\.json$):
|
||||
// both paths only appeared in a manifest while the harness collapsed
|
||||
// configDir onto the sandbox HOME, walking HOME-level siblings
|
||||
// (cline's ~/.agents/AGENTS.md, every non-Claude runtime's
|
||||
// ~/.gsd/defaults.json). With the harness installing into each runtime's
|
||||
// ~/.msd/defaults.json). With the harness installing into each runtime's
|
||||
// real global subdirectory those files sit outside the walked configDir,
|
||||
// the rules matched nothing, and the totality guard's dead-rule arm fired
|
||||
// — exactly as designed. The files are still written, still covered by the
|
||||
@@ -633,7 +633,7 @@ const PROVENANCE_RULES = [
|
||||
{
|
||||
id: 'hermes-category-description',
|
||||
kind: 'code-derived',
|
||||
roots: ['skills/gsd'],
|
||||
roots: ['skills/msd'],
|
||||
pattern: /^DESCRIPTION\.md$/,
|
||||
sources: () => [INSTALLER_SRC],
|
||||
},
|
||||
@@ -645,7 +645,7 @@ const PROVENANCE_RULES = [
|
||||
// (src/runtime-artifact-layout.cts:303), and the YAML additionally enumerates
|
||||
// every staged subagent — so adding or removing an agents/*.md legitimately
|
||||
// moves this file too. Both sources are declared.
|
||||
pattern: /^gsd\.(yaml|md)$/,
|
||||
pattern: /^msd\.(yaml|md)$/,
|
||||
sources: () => [KIMI_ROOT_AGENT_SRC, 'agents/'],
|
||||
},
|
||||
|
||||
@@ -658,7 +658,7 @@ const PROVENANCE_RULES = [
|
||||
// `{"type":"commonjs"}` CommonJS-mode marker as the root one, written into
|
||||
// each Kimi product's separate hooks root (installSharedHooksBundle; the
|
||||
// per-runtime root split is #2755).
|
||||
pattern: /^(\.gsd-profile|package\.json|\.kimi(-code)?\/package\.json|gsd-core\/VERSION|gsd-core\/\.gsd-runtime)$/,
|
||||
pattern: /^(\.msd-profile|package\.json|\.kimi(-code)?\/package\.json|msd-core\/VERSION|msd-core\/\.msd-runtime)$/,
|
||||
sources: () => [],
|
||||
},
|
||||
{
|
||||
@@ -675,8 +675,8 @@ const PROVENANCE_RULES = [
|
||||
/**
|
||||
* Reject an emitted key that could escape the repo once turned into a source path.
|
||||
*
|
||||
* Two rules (`gsd-core-verbatim`, `scripts-verbatim`) capture a whole tail with
|
||||
* `.+` rather than `[^/]+`, so a key like `gsd-core/workflows/../../../etc/passwd`
|
||||
* Two rules (`msd-core-verbatim`, `scripts-verbatim`) capture a whole tail with
|
||||
* `.+` rather than `[^/]+`, so a key like `msd-core/workflows/../../../etc/passwd`
|
||||
* would otherwise produce a source path that `path.join(REPO_ROOT, src)` resolves
|
||||
* OUTSIDE the repo. Nothing reachable today exploits it — real manifest keys come
|
||||
* from installer output and the only consumer is an `fs.existsSync` probe — but
|
||||
|
||||
@@ -47,7 +47,7 @@ const {
|
||||
} = require('./install-shared.cjs');
|
||||
const { ACK_TRAILER_HASH, ACK_TRAILER_GROWTH, parseAckTrailers } = require('./emitted-diff.cjs');
|
||||
const { runGit, OUTCOME } = require('./process-seam.cjs');
|
||||
const { escapeRegex } = require('../../gsd-core/bin/lib/pattern.cjs');
|
||||
const { escapeRegex } = require('../../msd-core/bin/lib/pattern.cjs');
|
||||
|
||||
const REPO_ROOT = path.join(__dirname, '..', '..');
|
||||
const FIXTURE_SUBDIR = 'tests/fixtures/golden-install-parity';
|
||||
@@ -141,7 +141,7 @@ function touchesRuntimeRegistry(changedPaths) {
|
||||
* that shrank uniformly, which a same-count self-check passes vacuously.
|
||||
*
|
||||
* Pure and IO-free by construction: the real-tree caller skips wherever no base ref
|
||||
* exists (the gsd-test runner shallow-clones, so `origin/*` is absent), which would make
|
||||
* exists (the msd-test runner shallow-clones, so `origin/*` is absent), which would make
|
||||
* a regression written at that altitude silently skip instead of proving anything.
|
||||
*
|
||||
* @param {object} o
|
||||
@@ -299,19 +299,19 @@ function resolveBaseSha(base = 'origin/next') {
|
||||
* Base-ref candidates, most-specific first.
|
||||
*
|
||||
* The differential needs a ref for `next`, and that ref is NOT universally present:
|
||||
* - the gsd-test runner shallow-clones and merges base+head, so no `origin/*`
|
||||
* - the msd-test runner shallow-clones and merges base+head, so no `origin/*`
|
||||
* remote-tracking refs exist in the container (verified: `git rev-parse
|
||||
* origin/next` fails there, which is what turned this test red on its first run);
|
||||
* - GitHub Actions' checkout does not create remote-tracking branches for OTHER
|
||||
* branches by default, which is exactly why `changeset-required.yml` carries an
|
||||
* explicit `git fetch origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}"` step.
|
||||
*
|
||||
* `GSD_EMITTED_BASE` lets a lane name the ref (or sha) directly. `GITHUB_BASE_REF` is
|
||||
* `MSD_EMITTED_BASE` lets a lane name the ref (or sha) directly. `GITHUB_BASE_REF` is
|
||||
* set by Actions on pull_request events.
|
||||
*/
|
||||
function baseRefCandidates(env = process.env) {
|
||||
const candidates = [];
|
||||
if (env.GSD_EMITTED_BASE) candidates.push(env.GSD_EMITTED_BASE);
|
||||
if (env.MSD_EMITTED_BASE) candidates.push(env.MSD_EMITTED_BASE);
|
||||
if (env.GITHUB_BASE_REF) {
|
||||
candidates.push(`origin/${env.GITHUB_BASE_REF}`, env.GITHUB_BASE_REF);
|
||||
}
|
||||
@@ -325,7 +325,7 @@ function baseRefCandidates(env = process.env) {
|
||||
* Returning null is NOT a pass — the caller turns it into an explicit `t.skip()` with
|
||||
* the full candidate list in the message, so an environment where the gate did not run
|
||||
* says so out loud. A bare `return` there would be a PASS (ADR-2719 §6), and a hard
|
||||
* failure would make the suite permanently red in the gsd-test container, where no
|
||||
* failure would make the suite permanently red in the msd-test container, where no
|
||||
* base ref can exist by construction.
|
||||
*/
|
||||
function resolveBase(env = process.env) {
|
||||
@@ -431,7 +431,7 @@ function baselineManifestsAtRef(base = 'origin/next') {
|
||||
* needs no `npm ci`: `bin/install.js` and the `tests/helpers/*.cjs` real-tree shells are
|
||||
* all Node-builtins-only (CONTRIBUTING.md's "No external dependencies in core"). It DOES
|
||||
* need `npm run build:lib` run there first, though — `tests/helpers/install-shared.cjs`
|
||||
* requires the TSC-COMPILED `gsd-core/bin/lib/runtime-artifact-layout.cjs`, which is
|
||||
* requires the TSC-COMPILED `msd-core/bin/lib/runtime-artifact-layout.cjs`, which is
|
||||
* gitignored, not committed, and therefore absent from a bare worktree checkout, and
|
||||
* `<worktreeDir>/bin/install.js` (spawned BY `cwd`'s generator via `currentManifests`'s
|
||||
* `repoRoot` override) needs its own compiled copy alongside it, not `cwd`'s. `node_modules`
|
||||
@@ -466,12 +466,12 @@ const BUILD_LIB_TIMEOUT_MS = 180_000;
|
||||
// observation and 20% above the censored one, while leaving 240s of chunk
|
||||
// headroom for every other file sharing the chunk.
|
||||
//
|
||||
// The old 300s sat INSIDE that variance band. Under gsd-test this slow path runs
|
||||
// The old 300s sat INSIDE that variance band. Under msd-test this slow path runs
|
||||
// on every verification, because the on-disk baseline cache is restored by
|
||||
// actions/cache keyed on github.event.pull_request.base.sha — a key that exists
|
||||
// only inside GitHub Actions. The real remedy is making that cache reachable from
|
||||
// the remote runner so the in-job build returns to being the rare fallback
|
||||
// ADR-2719 §5 describes; that is a gsd-test-runner change, not one this repo can
|
||||
// ADR-2719 §5 describes; that is a msd-test-runner change, not one this repo can
|
||||
// make.
|
||||
const BUILD_TIMEOUT_MS = 360_000;
|
||||
// Mirrors `scripts/run-tests.cjs:973`'s default. Duplicated deliberately and
|
||||
@@ -481,11 +481,11 @@ const BUILD_TIMEOUT_MS = 360_000;
|
||||
const CHUNK_TIMEOUT_CEILING_MS = 600_000;
|
||||
|
||||
function buildBaselineAtRef(ref, { cwd = REPO_ROOT } = {}) {
|
||||
const worktreeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-emitted-baseline-wt-'));
|
||||
const worktreeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-emitted-baseline-wt-'));
|
||||
// mkdtempSync already created the directory; `git worktree add` requires the
|
||||
// target to not exist (or be empty) — remove it and let git recreate it.
|
||||
fs.rmdirSync(worktreeDir);
|
||||
const outFile = path.join(os.tmpdir(), `gsd-emitted-baseline-out-${crypto.randomBytes(8).toString('hex')}.json`);
|
||||
const outFile = path.join(os.tmpdir(), `msd-emitted-baseline-out-${crypto.randomBytes(8).toString('hex')}.json`);
|
||||
|
||||
// Per-step timings, carried into the thrown error. A bare "spawnSync ETIMEDOUT"
|
||||
// names neither the step nor its elapsed time, which is exactly the information
|
||||
@@ -584,7 +584,7 @@ function baselineSizesAtRef(base = 'origin/next') {
|
||||
/**
|
||||
* The package version of the tree whose emitted output is about to be measured.
|
||||
*
|
||||
* `bin/install.js` bakes `{{GSD_VERSION}}` -> `pkg.version` into every emitted hook,
|
||||
* `bin/install.js` bakes `{{MSD_VERSION}}` -> `pkg.version` into every emitted hook,
|
||||
* and `buildParityManifest`'s `pkgVersion` normalization exists to collapse that stamp
|
||||
* back to '<VERSION>' so a version bump alone never moves a hash. That normalization is
|
||||
* only correct when `pkgVersion` matches the version of the tree that PRODUCED the
|
||||
@@ -697,14 +697,14 @@ function currentManifests({ repoRoot } = {}) {
|
||||
/**
|
||||
* Current on-disk sizes for the workflow + agent families the ratchet covers.
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.repoRoot] - Read `<repoRoot>/gsd-core/workflows` and
|
||||
* @param {string} [opts.repoRoot] - Read `<repoRoot>/msd-core/workflows` and
|
||||
* `<repoRoot>/agents` instead of this checkout's own (#2767) — same rationale as
|
||||
* `currentManifests`'s `repoRoot`.
|
||||
*/
|
||||
function currentSizes({ repoRoot = REPO_ROOT } = {}) {
|
||||
const sizes = {};
|
||||
for (const [dir, filter] of [
|
||||
[path.join(repoRoot, 'gsd-core', 'workflows'), (f) => f.endsWith('.md')],
|
||||
[path.join(repoRoot, 'msd-core', 'workflows'), (f) => f.endsWith('.md')],
|
||||
[path.join(repoRoot, 'agents'), (f) => f.endsWith('.md')],
|
||||
]) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
@@ -747,7 +747,7 @@ function ackTrailerGit(args, { cwd = REPO_ROOT, timeoutMs = GIT_TIMEOUT_MS, inpu
|
||||
// commit unconditionally — silently bury it: git's parser (correctly, by its own
|
||||
// contract) stops at the first non-conforming line scanning backward, so it never even
|
||||
// reaches past `---------` to see the real content underneath, let alone further back
|
||||
// to an earlier bullet. Confirmed directly against a real squash commit (gsd-core
|
||||
// to an earlier bullet. Confirmed directly against a real squash commit (msd-core
|
||||
// 78013b3b74): `%(trailers)` there returns ONLY GitHub's own two `Co-authored-by:`
|
||||
// lines — not even the last original commit's own trailer survives, only GitHub's
|
||||
// appended one.
|
||||
@@ -844,7 +844,7 @@ function extractSquashBuriedTrailers(rawBody, { cwd, timeoutMs }) {
|
||||
|
||||
// Record/field/value separators for the `git log --format` trailer extraction below.
|
||||
// ASCII control characters (RS/US/GS) so they can never collide with real trailer
|
||||
// content, following the precedent at gsd-core/workflows/ship.md:312 (`%x1f`/`%x1e` for
|
||||
// content, following the precedent at msd-core/workflows/ship.md:312 (`%x1f`/`%x1e` for
|
||||
// a different `%(trailers:...)` extraction in this same repo).
|
||||
const ACK_TRAILER_RECORD_SEP = '\x1e'; // between commits
|
||||
const ACK_TRAILER_FIELD_SEP = '\x1f'; // between the hash-space list and growth-space list
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* Any call that supplies `--out` without also supplying matching overrides
|
||||
* for the other four would, under `--write`, clobber the real committed
|
||||
* `scripts/lib/exit-code-registry.cjs`, `hooks/lib/exit-code-registry.js`,
|
||||
* `src/exit-code-registry.d.cts`, and `gsd-core/bin/shared/exit-codes.sh` —
|
||||
* `src/exit-code-registry.d.cts`, and `msd-core/bin/shared/exit-codes.sh` —
|
||||
* dangerous since test files in this repo run in parallel.
|
||||
*
|
||||
* `ensureScriptsOut` derives co-located, per-call-unique secondary/hooks/
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
*
|
||||
* Settings:
|
||||
* numRuns: 200 — enough to catch boundary bugs without slow CI
|
||||
* seed: 42 — deterministic across CI runs; set GSD_FC_SEED=<n> to
|
||||
* seed: 42 — deterministic across CI runs; set MSD_FC_SEED=<n> to
|
||||
* override locally for exploration
|
||||
*/
|
||||
|
||||
const fc = require('fast-check');
|
||||
|
||||
const seed = process.env.GSD_FC_SEED ? Number(process.env.GSD_FC_SEED) : 42;
|
||||
const seed = process.env.MSD_FC_SEED ? Number(process.env.MSD_FC_SEED) : 42;
|
||||
|
||||
fc.configureGlobal({
|
||||
numRuns: 200,
|
||||
|
||||
@@ -50,7 +50,7 @@ function commitEmpty(cwd, message) {
|
||||
// optional .last-build-status.json written as autoUpdateValue.
|
||||
// autoUpdateValue === null means no status file is written.
|
||||
function makeStatusProject(autoUpdateValue) {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3347-status-'));
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-3347-status-'));
|
||||
gitOrThrow(['init', '-q', '-b', 'main'], { cwd: tmpDir });
|
||||
gitOrThrow(['config', 'user.email', 'test@example.com'], { cwd: tmpDir });
|
||||
gitOrThrow(['config', 'user.name', 'Test'], { cwd: tmpDir });
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* exported `HOOKS_TO_COPY` list. This replaces a former `.js`-extension-count
|
||||
* heuristic ("populated" if at least one `.js` file exists), which could not
|
||||
* see a dist missing exactly the file class that caused #3108: build-hooks
|
||||
* also ships `.sh` files (e.g. `gsd-session-state.sh`), so a dist with every
|
||||
* also ships `.sh` files (e.g. `msd-session-state.sh`), so a dist with every
|
||||
* `.js` present and every `.sh` absent read as fully populated and the
|
||||
* rebuild was silently skipped. The set-membership check has no such blind
|
||||
* spot: any expected entry missing, of any extension, is stale. It does NOT
|
||||
@@ -50,7 +50,7 @@ const BUILD_HOOKS_SCRIPT = path.join(REPO_ROOT, 'scripts', 'build-hooks.js');
|
||||
* expects to have copied there (`HOOKS_TO_COPY`, bare top-level filenames,
|
||||
* and `HOOKS_SUBDIRS_TO_COPY`, bare subdirectory names such as `lib`). A dist
|
||||
* with every top-level file present but no `lib/` (e.g. missing
|
||||
* `gsd-graphify-rebuild.sh`) is exactly the same blindness the old
|
||||
* `msd-graphify-rebuild.sh`) is exactly the same blindness the old
|
||||
* `.js`-count heuristic had, one level down — so each subdir entry is first
|
||||
* checked against the same top-level readdir Set, THEN additionally required
|
||||
* to be a readable, non-empty directory (one extra `readdirSync` per subdir
|
||||
@@ -85,7 +85,7 @@ function isHooksDistStale(dir) {
|
||||
// Presence in the top-level Set only proves an entry named `lib`
|
||||
// exists — not that it is a directory, nor that it is populated. A
|
||||
// stray regular file named `lib`, or an empty `lib/` missing e.g.
|
||||
// `gsd-graphify-rebuild.sh`, would otherwise read as fresh: exactly
|
||||
// `msd-graphify-rebuild.sh`, would otherwise read as fresh: exactly
|
||||
// the blind spot the subdir check exists to close. One `readdirSync`
|
||||
// per subdir entry (there is exactly one, `lib`) — no per-expected-file
|
||||
// `existsSync`/`statSync`, keeping the added cost to one syscall total.
|
||||
|
||||
@@ -23,11 +23,11 @@ const { runNode } = require('./process-seam.cjs');
|
||||
|
||||
const {
|
||||
resolveRuntimeArtifactLayout,
|
||||
} = require('../../gsd-core/bin/lib/runtime-artifact-layout.cjs');
|
||||
const { escapeRegex: escapeRegExp } = require('../../gsd-core/bin/lib/pattern.cjs');
|
||||
} = require('../../msd-core/bin/lib/runtime-artifact-layout.cjs');
|
||||
const { escapeRegex: escapeRegExp } = require('../../msd-core/bin/lib/pattern.cjs');
|
||||
|
||||
const INSTALL_SCRIPT = path.join(__dirname, '..', '..', 'bin', 'install.js');
|
||||
const MANIFEST_NAME = 'gsd-file-manifest.json';
|
||||
const MANIFEST_NAME = 'msd-file-manifest.json';
|
||||
|
||||
// #3145: class-norm timeout (bounds the previously-unbounded spawnSync in
|
||||
// runMinimalInstall) — not a per-suite value. See helpers/timeouts.cjs for
|
||||
@@ -39,23 +39,23 @@ const BUILD_SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'build-hooks.js
|
||||
const HOOKS_DIST = path.join(__dirname, '..', '..', 'hooks', 'dist');
|
||||
|
||||
const EXPECTED_SH_HOOKS = [
|
||||
'gsd-session-state.sh',
|
||||
'gsd-validate-commit.sh',
|
||||
'gsd-phase-boundary.sh',
|
||||
'msd-session-state.sh',
|
||||
'msd-validate-commit.sh',
|
||||
'msd-phase-boundary.sh',
|
||||
];
|
||||
|
||||
const EXPECTED_ALL_HOOKS = [
|
||||
'gsd-agent-isolation-guard.js',
|
||||
'gsd-check-update.js',
|
||||
'gsd-config-reload.js',
|
||||
'gsd-context-monitor.js',
|
||||
'msd-agent-isolation-guard.js',
|
||||
'msd-check-update.js',
|
||||
'msd-config-reload.js',
|
||||
'msd-context-monitor.js',
|
||||
// #997: SessionStart canonical-path bootstrap for plugin installs.
|
||||
'gsd-ensure-canonical-path.js',
|
||||
'gsd-prompt-guard.js',
|
||||
'gsd-read-guard.js',
|
||||
'gsd-read-injection-scanner.js',
|
||||
'gsd-statusline.js',
|
||||
'gsd-workflow-guard.js',
|
||||
'msd-ensure-canonical-path.js',
|
||||
'msd-prompt-guard.js',
|
||||
'msd-read-guard.js',
|
||||
'msd-read-injection-scanner.js',
|
||||
'msd-statusline.js',
|
||||
'msd-workflow-guard.js',
|
||||
...EXPECTED_SH_HOOKS,
|
||||
];
|
||||
|
||||
@@ -87,8 +87,8 @@ const RUNTIME_META = {
|
||||
*
|
||||
* NOT simply `Object.keys(RUNTIME_META)`: that has 18 entries while the fixture set has
|
||||
* 19. The extra one is `claude-local` — claude is the reference host and the ONLY
|
||||
* runtime with a distinct LOCAL "legacy flat-commands" layout (`commands/gsd-*.md` +
|
||||
* `agents/gsd-*.md` at project scope), which `golden-install-parity.test.cjs` guards
|
||||
* runtime with a distinct LOCAL "legacy flat-commands" layout (`commands/msd-*.md` +
|
||||
* `agents/msd-*.md` at project scope), which `golden-install-parity.test.cjs` guards
|
||||
* with a hand-coded test outside its RUNTIME_META loop (#2086).
|
||||
*
|
||||
* Enumerating from RUNTIME_META alone dropped that family from BOTH sides of the
|
||||
@@ -136,7 +136,7 @@ const SKILL_RUNTIMES = [
|
||||
|
||||
// This checkout's own package version — the DEFAULT normalized in hash computation so
|
||||
// the golden is stable across version bumps (the rc step runs `npm version X.Y.Z-rc.N`
|
||||
// before tests, which rebakes the version into hook files and gsd-core/VERSION).
|
||||
// before tests, which rebakes the version into hook files and msd-core/VERSION).
|
||||
//
|
||||
// This is only a default. buildParityManifest's `pkgVersion` option exists precisely
|
||||
// because the tree being MEASURED is not always this checkout (#2767's `repoRoot`
|
||||
@@ -147,24 +147,24 @@ const SKILL_RUNTIMES = [
|
||||
const PKG_VERSION = require('../../package.json').version;
|
||||
|
||||
// Volatile metadata files always excluded from the parity manifest.
|
||||
// .gsd-source (#1477, claude-global only) records the install-time absolute path
|
||||
// to the package's commands/gsd source tree, which is the checkout/CI workspace
|
||||
// .msd-source (#1477, claude-global only) records the install-time absolute path
|
||||
// to the package's commands/msd source tree, which is the checkout/CI workspace
|
||||
// path — NOT the temp HOME root, so it is never normalized to '<HOME>' and its
|
||||
// hash varies by environment. Excluded for the same reason as gsd-install-state.json.
|
||||
// gsd-core/CHANGELOG.md is excluded because it contains historical version strings
|
||||
// hash varies by environment. Excluded for the same reason as msd-install-state.json.
|
||||
// msd-core/CHANGELOG.md is excluded because it contains historical version strings
|
||||
// that cause hash drift between local (PKG_VERSION=1.x.x) and CI (PKG_VERSION=1.x.x-rc.N):
|
||||
// the PKG_VERSION normalization below replaces only the *current* version, but
|
||||
// CHANGELOG.md references prior-release versions, so the normalized hash diverges.
|
||||
// gsd-file-manifest.json's exclusion was revisited deliberately for #2872: the
|
||||
// msd-file-manifest.json's exclusion was revisited deliberately for #2872: the
|
||||
// manifest gained `manifestVersion`/`runtime`/`scope` fields, all of which are
|
||||
// deterministic and would not by themselves force an exclusion, but `timestamp`
|
||||
// — the original reason this file is volatile — is unchanged by #2872, so the
|
||||
// exclusion still holds for exactly the same reason it always has.
|
||||
const VOLATILE_FILES = new Set([
|
||||
'gsd-file-manifest.json',
|
||||
'gsd-install-state.json',
|
||||
'.gsd-source',
|
||||
'gsd-core/CHANGELOG.md',
|
||||
'msd-file-manifest.json',
|
||||
'msd-install-state.json',
|
||||
'.msd-source',
|
||||
'msd-core/CHANGELOG.md',
|
||||
]);
|
||||
|
||||
// Hook-registration config files excluded from the parity manifest. These are
|
||||
@@ -199,7 +199,7 @@ const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hook
|
||||
// compares base-vs-current on the same machine, so the platform-varying
|
||||
// node-runner command embedded in the TOML never crosses platforms inside a
|
||||
// gate (that was a golden-content-era hazard, and the goldens are gone).
|
||||
// Kimi CLI's config.toml (KIMI_SHARE_DIR root ~/.kimi) lives OUTSIDE its GSD
|
||||
// Kimi CLI's config.toml (KIMI_SHARE_DIR root ~/.kimi) lives OUTSIDE its MSD
|
||||
// configDir (~/.config/agents) and never enters the walk. The pre-#3547
|
||||
// relative-path exclusions ('.kimi/config.toml', '.kimi-code/config.toml')
|
||||
// existed only for the collapsed shape — where the walked root was the HOME
|
||||
@@ -208,7 +208,7 @@ const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hook
|
||||
// resolves its own root since #2755.
|
||||
const HOOK_CONFIG_RELATIVE_PATHS = new Set();
|
||||
|
||||
// Path prefixes excluded from the parity manifest. `gsd-core/bin/lib/` holds the
|
||||
// Path prefixes excluded from the parity manifest. `msd-core/bin/lib/` holds the
|
||||
// tsc-built runtime artifacts (compiled from src/*.cts) that the install COPIES
|
||||
// verbatim — they are NOT produced by installRuntimeArtifacts (the move's parity
|
||||
// scope), and their exact bytes depend on the BUILD environment (a clean tsc
|
||||
@@ -218,7 +218,7 @@ const HOOK_CONFIG_RELATIVE_PATHS = new Set();
|
||||
// (e.g. milestone.cjs, roadmap.cjs). The .cts sources are type-checked + drift-
|
||||
// guarded + coverage-gated elsewhere; this harness asserts the CONVERTED artifact
|
||||
// output (skills/commands/agents) that the engine actually emits.
|
||||
const EXCLUDED_PREFIXES = ['gsd-core/bin/lib/'];
|
||||
const EXCLUDED_PREFIXES = ['msd-core/bin/lib/'];
|
||||
|
||||
// ─── Helper functions ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -250,22 +250,22 @@ const SEMVER_ISH_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
||||
* (false negative), while an UNCHANGED file that happens to contain a literal equal to
|
||||
* only the CURRENT version collapses on one side only and reports as spurious drift
|
||||
* (false positive). Emitted sources really do carry bare semver literals that are NOT
|
||||
* install-time stamps — e.g. gsd-core/workflows/update.md's `1.4.0`/`1.3.1` examples,
|
||||
* agents/gsd-project-researcher.md's `1.2.3`, gsd-core/workflows/help/modes/full.md's
|
||||
* install-time stamps — e.g. msd-core/workflows/update.md's `1.4.0`/`1.3.1` examples,
|
||||
* agents/msd-project-researcher.md's `1.2.3`, msd-core/workflows/help/modes/full.md's
|
||||
* `1.0.0` — and those must stay VISIBLE to the parity gate if they ever change.
|
||||
*
|
||||
* So instead of replacing the version everywhere, this only normalizes it at the
|
||||
* specific places `bin/install.js` actually stamps `pkg.version` into emitted content
|
||||
* as a version FIELD/marker (not prose):
|
||||
* - `// gsd-hook-version: <ver>` / `# gsd-hook-version: <ver>` — the
|
||||
* `{{GSD_VERSION}}` substitution done for every emitted hook file.
|
||||
* - `// msd-hook-version: <ver>` / `# msd-hook-version: <ver>` — the
|
||||
* `{{MSD_VERSION}}` substitution done for every emitted hook file.
|
||||
* - `"version": "<ver>"` — JSON manifests (plugin/extension/capability-style)
|
||||
* embedding the package version as a string field.
|
||||
* - `version: "<ver>"` / `version: <ver>` — YAML frontmatter version fields (e.g.
|
||||
* skill frontmatter's `yamlQuote(pkg.version)`, Hermes' category
|
||||
* `DESCRIPTION.md`).
|
||||
* - `@opengsd/gsd-core@<ver>` — pinned package-spec references.
|
||||
* - a file whose ENTIRE trimmed content IS the version (`gsd-core/VERSION`).
|
||||
* - `@golem15/msd-core@<ver>` — pinned package-spec references.
|
||||
* - a file whose ENTIRE trimmed content IS the version (`msd-core/VERSION`).
|
||||
* Each pattern only matches when the version in the content EQUALS the supplied
|
||||
* `pkgVersion` — this is deliberately narrow, at the cost of needing a new pattern any
|
||||
* time the installer grows a new stamp site (see the empirical repro-harness check
|
||||
@@ -281,11 +281,11 @@ function normalizeVersionStamps(content, pkgVersion) {
|
||||
// from matching as a PREFIX of a longer version-shaped string it is not equal to
|
||||
// (e.g. pkgVersion '1.9.0' must not match inside '1.9.0-rc.1' or '1.9.0.1').
|
||||
return content
|
||||
.replace(new RegExp(`((?:\\/\\/|#)\\s*gsd-hook-version:\\s*)${v}(?![\\w.+-])`, 'g'), '$1<VERSION>')
|
||||
.replace(new RegExp(`((?:\\/\\/|#)\\s*msd-hook-version:\\s*)${v}(?![\\w.+-])`, 'g'), '$1<VERSION>')
|
||||
.replace(new RegExp(`("version"\\s*:\\s*")${v}(")`, 'g'), '$1<VERSION>$2')
|
||||
.replace(new RegExp(`(version:\\s*")${v}(")`, 'g'), '$1<VERSION>$2')
|
||||
.replace(new RegExp(`(version:\\s*)${v}(?![\\w.+-])`, 'g'), '$1<VERSION>')
|
||||
.replace(new RegExp(`(@opengsd/gsd-core@)${v}(?![\\w.+-])`, 'g'), '$1<VERSION>')
|
||||
.replace(new RegExp(`(@golem15/msd-core@)${v}(?![\\w.+-])`, 'g'), '$1<VERSION>')
|
||||
.replace(new RegExp(`^(\\s*)${v}(\\s*)$`), '$1<VERSION>$2');
|
||||
}
|
||||
|
||||
@@ -582,18 +582,18 @@ function simulateHookCopy(hooksSrc, hooksDest) {
|
||||
}
|
||||
|
||||
/** Build a clean env for spawned installer processes.
|
||||
* Must strip GSD_TEST_MODE so the child runs the real install, not the no-op guard. */
|
||||
* Must strip MSD_TEST_MODE so the child runs the real install, not the no-op guard. */
|
||||
function installerEnv(overrides = {}) {
|
||||
// #3156: delegate to the ONE canonical raw-installer-spawn env rather than
|
||||
// carrying a second shape of it. The installer writes GSD's own user store to
|
||||
// <home>/.gsd/defaults.json through os.homedir() DIRECTLY
|
||||
// (bin/install.js writeNonClaudeDefaults, #2834), which reads no GSD variable,
|
||||
// carrying a second shape of it. The installer writes MSD's own user store to
|
||||
// <home>/.msd/defaults.json through os.homedir() DIRECTLY
|
||||
// (bin/install.js writeNonClaudeDefaults, #2834), which reads no MSD variable,
|
||||
// so no config-location scrub can reach it — only a sandboxed HOME can. Every
|
||||
// caller that already passes an explicit { HOME, USERPROFILE } still wins:
|
||||
// overrides spread last.
|
||||
const { installSpawnEnv } = require('../helpers.cjs');
|
||||
const env = installSpawnEnv(overrides);
|
||||
delete env.GSD_TEST_MODE;
|
||||
delete env.MSD_TEST_MODE;
|
||||
return env;
|
||||
}
|
||||
|
||||
@@ -614,7 +614,7 @@ function installerEnv(overrides = {}) {
|
||||
*/
|
||||
function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INSTALL_SCRIPT, root: providedRoot = null, extraEnv = {} }) {
|
||||
const ownsRoot = providedRoot === null;
|
||||
const root = providedRoot ?? fs.mkdtempSync(path.join(os.tmpdir(), `gsd-${runtime}-${scope}-`));
|
||||
const root = providedRoot ?? fs.mkdtempSync(path.join(os.tmpdir(), `msd-${runtime}-${scope}-`));
|
||||
try {
|
||||
let configDir;
|
||||
let cwd = process.cwd();
|
||||
@@ -625,7 +625,7 @@ function runMinimalInstall({ runtime, scope, extraArgs = [], installScript = INS
|
||||
// (RUNTIME_META.globalSuffix mirrors the registry's getGlobalConfigDir
|
||||
// for every runtime). The previous `--config-dir <root>` collapsed
|
||||
// configDir onto HOME, so computePathPrefix emitted bare `$HOME/`
|
||||
// prefixes and the emitted bytes referenced `$HOME/gsd-core/…` — a path
|
||||
// prefixes and the emitted bytes referenced `$HOME/msd-core/…` — a path
|
||||
// no real install produces — leaving every emitted-artifact gate
|
||||
// (ADR-2719 differential, install-tree fixtures, the 19-family baseline)
|
||||
// blind to drift confined to the real global shape (#3544 evidence: 54
|
||||
@@ -704,21 +704,21 @@ function manifestSkillSet(manifest) {
|
||||
const out = new Set();
|
||||
for (const key of Object.keys(manifest.files)) {
|
||||
if (key.startsWith('skills/')) {
|
||||
const seg = key.split('/')[1].replace(/^gsd-/, '').replace(/\.md$/, '');
|
||||
const seg = key.split('/')[1].replace(/^msd-/, '').replace(/\.md$/, '');
|
||||
out.add(seg);
|
||||
} else if (key.startsWith('command/')) {
|
||||
// OpenCode/Kilo: command/gsd-<cmd>.md
|
||||
// OpenCode/Kilo: command/msd-<cmd>.md
|
||||
const file = key.split('/')[1];
|
||||
out.add(file.replace(/^gsd-/, '').replace(/\.md$/, ''));
|
||||
} else if (key.startsWith('commands/gsd/')) {
|
||||
// Gemini: commands/gsd/<cmd>.toml (nested, colon-namespaced)
|
||||
out.add(file.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
} else if (key.startsWith('commands/msd/')) {
|
||||
// Gemini: commands/msd/<cmd>.toml (nested, colon-namespaced)
|
||||
const file = key.split('/')[2];
|
||||
out.add(file.replace(/\.(md|toml)$/, ''));
|
||||
} else if (key.startsWith('commands/') && key.split('/').length === 2) {
|
||||
// Claude local (#1367 fix): flat commands/gsd-<cmd>.md
|
||||
// Claude local (#1367 fix): flat commands/msd-<cmd>.md
|
||||
const file = key.split('/')[1];
|
||||
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^gsd-/, '').replace(/\.md$/, ''));
|
||||
if (file.startsWith('msd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -731,7 +731,7 @@ function manifestAgentCount(manifest) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect gsd-* skill/command basenames actually present on disk under configDir.
|
||||
* Collect msd-* skill/command basenames actually present on disk under configDir.
|
||||
*
|
||||
* @param {string} configDir
|
||||
* @param {string} [runtime] - when provided, the skills-kind destination is
|
||||
@@ -753,35 +753,35 @@ function collectSkillBasenamesOnDisk(configDir, runtime, scope = 'global') {
|
||||
}
|
||||
if (fs.existsSync(skillsDir)) {
|
||||
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
||||
out.add(entry.name.replace(/^gsd-/, ''));
|
||||
} else if (entry.isFile() && entry.name.startsWith('gsd-') && entry.name.endsWith('.md')) {
|
||||
out.add(entry.name.replace(/^gsd-/, '').replace(/\.md$/, ''));
|
||||
if (entry.isDirectory() && entry.name.startsWith('msd-')) {
|
||||
out.add(entry.name.replace(/^msd-/, ''));
|
||||
} else if (entry.isFile() && entry.name.startsWith('msd-') && entry.name.endsWith('.md')) {
|
||||
out.add(entry.name.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
const commandDir = path.join(configDir, 'command');
|
||||
if (fs.existsSync(commandDir)) {
|
||||
for (const file of fs.readdirSync(commandDir)) {
|
||||
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^gsd-/, '').replace(/\.md$/, ''));
|
||||
if (file.startsWith('msd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
const commandsGsdDir = path.join(configDir, 'commands', 'gsd');
|
||||
if (fs.existsSync(commandsGsdDir)) {
|
||||
for (const file of fs.readdirSync(commandsGsdDir)) {
|
||||
const commandsMsdDir = path.join(configDir, 'commands', 'msd');
|
||||
if (fs.existsSync(commandsMsdDir)) {
|
||||
for (const file of fs.readdirSync(commandsMsdDir)) {
|
||||
if (file.endsWith('.md') || file.endsWith('.toml')) {
|
||||
out.add(file.replace(/\.(md|toml)$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Claude local (#1367 fix): flat gsd-*.md files at commands/ level
|
||||
// Claude local (#1367 fix): flat msd-*.md files at commands/ level
|
||||
const flatCommandsDir = path.join(configDir, 'commands');
|
||||
if (fs.existsSync(flatCommandsDir)) {
|
||||
for (const file of fs.readdirSync(flatCommandsDir)) {
|
||||
if (file.startsWith('gsd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^gsd-/, '').replace(/\.md$/, ''));
|
||||
if (file.startsWith('msd-') && file.endsWith('.md')) {
|
||||
out.add(file.replace(/^msd-/, '').replace(/\.md$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* `tests/inventory-manifest-sync.test.cjs`. This module supplies the second
|
||||
* comparison that test runs: every manifest entry must also have a hand-written row
|
||||
* in `docs/INVENTORY.md`, which calls itself "Authoritative roster of every shipped
|
||||
* GSD surface" (`docs/INVENTORY.md:3`) and promises "a new file without a matching
|
||||
* MSD surface" (`docs/INVENTORY.md:3`) and promises "a new file without a matching
|
||||
* row here will fail CI" (`docs/INVENTORY.md:681`). Until #3762 neither claim was
|
||||
* true: PR #3758 shipped `gsd-core/references/planner-coupling.md` with a manifest
|
||||
* true: PR #3758 shipped `msd-core/references/planner-coupling.md` with a manifest
|
||||
* entry, no roster row, and fully green CI.
|
||||
*
|
||||
* Everything here is PURE — text and the manifest object in, findings out, no
|
||||
@@ -45,10 +45,10 @@
|
||||
* `host-integration-adapters/imperative-hook-bus.cjs` row must not satisfy the
|
||||
* separate top-level `hook-bus.cjs` entry, which is genuinely unrostered.
|
||||
* • Subsections (`###`), column count, column order, CRLF, and trailing HTML
|
||||
* comments (`| … |<!-- gsd-allow-legacy-name -->`) are all irrelevant.
|
||||
* comments (`| … |<!-- msd-allow-legacy-name -->`) are all irrelevant.
|
||||
* • COMMANDS MATCH ON THEIR SOURCE LINK, not their rendered name. Six namespace
|
||||
* routers deliberately render `/gsd-workflow` while sourcing
|
||||
* `commands/gsd/ns-workflow.md`; the display value is not the file identity.
|
||||
* routers deliberately render `/msd-workflow` while sourcing
|
||||
* `commands/msd/ns-workflow.md`; the display value is not the file identity.
|
||||
* `docs/INVENTORY.md:63` documents every command row as carrying "a link to the
|
||||
* source file", so the link is both the documented shape and the only
|
||||
* collision-free key.
|
||||
@@ -70,9 +70,9 @@ const ROSTER_SECTIONS = Object.freeze({
|
||||
hooks: 'Hooks',
|
||||
});
|
||||
|
||||
/** A manifest `commands` entry (`/gsd-ns-workflow`) → its source basename (`ns-workflow.md`). */
|
||||
/** A manifest `commands` entry (`/msd-ns-workflow`) → its source basename (`ns-workflow.md`). */
|
||||
function commandSourceBasename(entry) {
|
||||
return entry.replace(/^\/gsd-/, '') + '.md';
|
||||
return entry.replace(/^\/msd-/, '') + '.md';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +183,7 @@ function tableCells(lines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Basenames of every `commands/gsd/<file>.md` markdown-link target in `lines`,
|
||||
* Basenames of every `commands/msd/<file>.md` markdown-link target in `lines`,
|
||||
* regardless of how many `../` hops the relative path takes or whether it carries a
|
||||
* `#anchor` / `?query`.
|
||||
*/
|
||||
@@ -194,7 +194,7 @@ function commandSourceLinks(lines) {
|
||||
let m;
|
||||
while ((m = linkRe.exec(line)) !== null) {
|
||||
const target = m[1].split('#')[0].split('?')[0];
|
||||
const hit = /(?:^|\/)commands\/gsd\/([^/]+\.md)$/.exec(target);
|
||||
const hit = /(?:^|\/)commands\/msd\/([^/]+\.md)$/.exec(target);
|
||||
if (hit) basenames.add(hit[1]);
|
||||
}
|
||||
}
|
||||
@@ -252,7 +252,7 @@ function formatRosterFailure({ missingSections, missingRows }) {
|
||||
missingRows.map((e) => ' ! ' + e).join('\n') +
|
||||
'\nAdd a row to the matching "## " section of docs/INVENTORY.md: the file name in a cell,\n' +
|
||||
'plus the one-line role that section\'s table asks for. For a command, the row\'s Source\n' +
|
||||
'column must link to ../commands/gsd/<file>.md — that link, not the rendered /gsd-name,\n' +
|
||||
'column must link to ../commands/msd/<file>.md — that link, not the rendered /msd-name,\n' +
|
||||
'is what this gate reads. Regenerating the manifest does NOT satisfy this: the roster row\n' +
|
||||
'is hand-written by design, because a role sentence cannot be generated.'
|
||||
: '',
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
// allow-test-rule: source-text-is-the-product
|
||||
// commands/gsd/*.md files ARE the deployed registry — reading their frontmatter
|
||||
// commands/msd/*.md files ARE the deployed registry — reading their frontmatter
|
||||
// validates the structural contract of the command surface, not application source.
|
||||
'use strict';
|
||||
/**
|
||||
* live-command-registry.cjs
|
||||
*
|
||||
* Derives the canonical set of live slash-command tokens from the source-of-truth
|
||||
* registry: commands/gsd/*.md (one file per registered command).
|
||||
* registry: commands/msd/*.md (one file per registered command).
|
||||
*
|
||||
* Each command file has YAML frontmatter with a `name:` field:
|
||||
* name: gsd:slug (colon-style — most commands)
|
||||
* name: gsd-slug (dash-style — ns-* namespace commands)
|
||||
* name: msd:slug (colon-style — most commands)
|
||||
* name: msd-slug (dash-style — ns-* namespace commands)
|
||||
*
|
||||
* For each slug, three canonical token forms are emitted:
|
||||
* /gsd-slug — Claude / non-Gemini runtimes
|
||||
* /gsd:slug — Gemini runtime
|
||||
* $gsd-slug — Codex runtime
|
||||
* /msd-slug — Claude / non-Gemini runtimes
|
||||
* /msd:slug — Gemini runtime
|
||||
* $msd-slug — Codex runtime
|
||||
*
|
||||
* The result is memoized per process — a single fs walk is amortized across
|
||||
* all test files that import this helper. The cache is intentionally not
|
||||
@@ -31,7 +31,7 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const COMMANDS_DIR = path.join(__dirname, '..', '..', 'commands', 'gsd');
|
||||
const COMMANDS_DIR = path.join(__dirname, '..', '..', 'commands', 'msd');
|
||||
|
||||
// Module-level memoization — set on first call, reused thereafter.
|
||||
let _cache = null;
|
||||
@@ -45,13 +45,13 @@ let _cache = null;
|
||||
* `${prefix}${slug}` for one of these three prefixes — see the `tokens.add`
|
||||
* calls in `getLiveCommandTokens()` below, which is the sole producer.
|
||||
*/
|
||||
const LIVE_COMMAND_TOKEN_PREFIXES = Object.freeze(['/gsd-', '/gsd:', '$gsd-']);
|
||||
const LIVE_COMMAND_TOKEN_PREFIXES = Object.freeze(['/msd-', '/msd:', '$msd-']);
|
||||
|
||||
/**
|
||||
* Extract the first whitespace-delimited "word" of a string, e.g.
|
||||
* `"/gsd-plan-phase 2"` -> `"/gsd-plan-phase"`. Real command-token payloads
|
||||
* `"/msd-plan-phase 2"` -> `"/msd-plan-phase"`. Real command-token payloads
|
||||
* carry trailing arguments (`init`'s `recommended_actions[].command` is
|
||||
* literally `/gsd-plan-phase 2`), so an exact-match test against the WHOLE
|
||||
* literally `/msd-plan-phase 2`), so an exact-match test against the WHOLE
|
||||
* string would reject every argument-carrying token — the token itself is
|
||||
* always the first word.
|
||||
*
|
||||
@@ -75,8 +75,8 @@ function firstToken(value) {
|
||||
* exemption. First it was keyed on the leaf name `command`; then on a bare
|
||||
* `String.startsWith` prefix test with an unconstrained remainder, which
|
||||
* exempted any string merely SHARING A PREFIX with a real token — e.g.
|
||||
* `/gsd-x/../../../Users/someone/.ssh/id_rsa` or `/gsd:/etc/passwd` both
|
||||
* satisfied `startsWith('/gsd-')`/`startsWith('/gsd:')`).
|
||||
* `/msd-x/../../../Users/someone/.ssh/id_rsa` or `/msd:/etc/passwd` both
|
||||
* satisfied `startsWith('/msd-')`/`startsWith('/msd:')`).
|
||||
*
|
||||
* Deliberately NOT a `startsWith` fast path over `LIVE_COMMAND_TOKEN_PREFIXES`
|
||||
* short-circuiting this check: exact membership of the first word is the
|
||||
@@ -102,10 +102,10 @@ function isLiveCommandToken(value, liveTokens) {
|
||||
* introducing a YAML parser dependency would be disproportionate.
|
||||
*
|
||||
* Supported name forms:
|
||||
* name: gsd:slug → slug = "slug"
|
||||
* name: gsd-slug → slug = "slug"
|
||||
* name: "gsd:slug" → slug = "slug" (quoted)
|
||||
* name: "gsd-slug" → slug = "slug" (quoted)
|
||||
* name: msd:slug → slug = "slug"
|
||||
* name: msd-slug → slug = "slug"
|
||||
* name: "msd:slug" → slug = "slug" (quoted)
|
||||
* name: "msd-slug" → slug = "slug" (quoted)
|
||||
*/
|
||||
function parseSlug(content, filePath) {
|
||||
// Frontmatter must start with '---' on the very first line.
|
||||
@@ -126,22 +126,22 @@ function parseSlug(content, filePath) {
|
||||
const frontmatter = content.slice(0, closingIdx);
|
||||
|
||||
// Match `name:` line, allowing optional quotes around the value.
|
||||
// The value must be one of: gsd:<slug> or gsd-<slug>
|
||||
// The value must be one of: msd:<slug> or msd-<slug>
|
||||
// where slug = [a-z0-9][a-z0-9-]*
|
||||
const nameMatch = frontmatter.match(/^name:\s*"?(gsd[:‑-])([a-z0-9][a-z0-9-]*)"?\s*$/m);
|
||||
const nameMatch = frontmatter.match(/^name:\s*"?(msd[:‑-])([a-z0-9][a-z0-9-]*)"?\s*$/m);
|
||||
if (!nameMatch) {
|
||||
throw new Error(
|
||||
`[live-command-registry] ${filePath}: could not extract slug from frontmatter ` +
|
||||
`(expected "name: gsd:<slug>" or "name: gsd-<slug>")`
|
||||
`(expected "name: msd:<slug>" or "name: msd-<slug>")`
|
||||
);
|
||||
}
|
||||
|
||||
return nameMatch[2]; // the slug after "gsd:" or "gsd-"
|
||||
return nameMatch[2]; // the slug after "msd:" or "msd-"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Set<string> of all canonical slash-command tokens derived from
|
||||
* commands/gsd/*.md. Memoized — safe to call repeatedly without extra fs I/O.
|
||||
* commands/msd/*.md. Memoized — safe to call repeatedly without extra fs I/O.
|
||||
*
|
||||
* Throws on the first malformed file (fail-loud per CONTEXT.md k302) so
|
||||
* registry drift is caught immediately rather than silently producing an
|
||||
@@ -176,9 +176,9 @@ function getLiveCommandTokens() {
|
||||
const slug = parseSlug(content, filePath);
|
||||
|
||||
// Emit all three canonical token forms per slug.
|
||||
tokens.add(`/gsd-${slug}`); // Claude / non-Gemini
|
||||
tokens.add(`/gsd:${slug}`); // Gemini
|
||||
tokens.add(`$gsd-${slug}`); // Codex
|
||||
tokens.add(`/msd-${slug}`); // Claude / non-Gemini
|
||||
tokens.add(`/msd:${slug}`); // Gemini
|
||||
tokens.add(`$msd-${slug}`); // Codex
|
||||
}
|
||||
|
||||
_cache = tokens;
|
||||
|
||||
@@ -4,30 +4,30 @@
|
||||
*
|
||||
* Single source of truth for the concrete-skill → namespace-router map and the
|
||||
* nested SKILL.md path layout. The map is DERIVED at require-time by parsing
|
||||
* each commands/gsd/ns-*.md router's `requires:` frontmatter list with the
|
||||
* each commands/msd/ns-*.md router's `requires:` frontmatter list with the
|
||||
* production parser (install-profiles.parseRequires) — never hand-maintained.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { parseRequires } = require('../../gsd-core/bin/lib/install-profiles.cjs');
|
||||
const { parseRequires } = require('../../msd-core/bin/lib/install-profiles.cjs');
|
||||
|
||||
const COMMANDS_GSD = path.join(__dirname, '..', '..', 'commands', 'gsd');
|
||||
const COMMANDS_MSD = path.join(__dirname, '..', '..', 'commands', 'msd');
|
||||
|
||||
// Router stems (ns-*.md basenames), discovered from disk and sorted.
|
||||
const ROUTER_STEMS = fs
|
||||
.readdirSync(COMMANDS_GSD)
|
||||
.readdirSync(COMMANDS_MSD)
|
||||
.filter((f) => f.startsWith('ns-') && f.endsWith('.md'))
|
||||
.map((f) => f.replace(/\.md$/, ''))
|
||||
.sort();
|
||||
|
||||
/**
|
||||
* Read a router's `requires:` child list from commands/gsd/<routerStem>.md
|
||||
* Read a router's `requires:` child list from commands/msd/<routerStem>.md
|
||||
* using the production frontmatter parser.
|
||||
*/
|
||||
function routerChildren(routerStem) {
|
||||
const srcFile = path.join(COMMANDS_GSD, `${routerStem}.md`);
|
||||
const srcFile = path.join(COMMANDS_MSD, `${routerStem}.md`);
|
||||
return parseRequires(fs.readFileSync(srcFile, 'utf8'));
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ for (const routerStem of ROUTER_STEMS) {
|
||||
/**
|
||||
* Nested SKILL.md path for a concrete skill stem:
|
||||
* <skillsRoot>/<prefix><router>/skills/<stem>/SKILL.md
|
||||
* prefix is '' for runtimes that nest under a skills/gsd parent dir (hermes),
|
||||
* or 'gsd-' for flat-prefixed runtimes (claude, cline, qwen, …).
|
||||
* prefix is '' for runtimes that nest under a skills/msd parent dir (hermes),
|
||||
* or 'msd-' for flat-prefixed runtimes (claude, cline, qwen, …).
|
||||
*/
|
||||
function nestedSkillPath(skillsRoot, prefix, stem) {
|
||||
const router = CHILD_ROUTER[stem];
|
||||
@@ -57,7 +57,7 @@ function nestedSkillPath(skillsRoot, prefix, stem) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
COMMANDS_GSD,
|
||||
COMMANDS_MSD,
|
||||
ROUTER_STEMS,
|
||||
CHILD_ROUTER,
|
||||
routerChildren,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* ── The overlay technique ────────────────────────────────────────────────
|
||||
*
|
||||
* A test that needs a spawned `bin/install.js` to read a DIFFERENT
|
||||
* `gsd-core/workflows/execute-phase.md` (or any other repo file) than this
|
||||
* `msd-core/workflows/execute-phase.md` (or any other repo file) than this
|
||||
* checkout's real one, without paying to copy the ~400 MB repository (mostly
|
||||
* node_modules) for every run, calls `buildOverlayRepo` with a map of
|
||||
* POSIX-relative-path -> replacement content. `buildOverlayRepo` mirrors the
|
||||
@@ -216,7 +216,7 @@ function buildOverlayRepo(fileOverrides, opts = {}) {
|
||||
// synthetic root instead of walking the whole repo; every existing caller
|
||||
// uses the default REPO_ROOT and is unaffected.
|
||||
const root = opts.root || REPO_ROOT;
|
||||
const tmpRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2930-overlay-'));
|
||||
const tmpRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-2930-overlay-'));
|
||||
const entries = Object.entries(fileOverrides).map(([relPath, content]) => ({
|
||||
parts: relPath.split('/'),
|
||||
content,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
* An invocation that reaches is a violation UNLESS it sits inside an open
|
||||
* `commit_docs` conditional in the SAME fenced block, carries an exclude
|
||||
* pathspec naming `.planning`, or the line carries a tracked
|
||||
* `# gsd-scan-ignore: #NNN` declaration (shared machinery — see
|
||||
* `# msd-scan-ignore: #NNN` declaration (shared machinery — see
|
||||
* tests/helpers/shipped-command-scan.cjs).
|
||||
*
|
||||
* ## Known limits
|
||||
@@ -37,7 +37,7 @@
|
||||
* `.planning/`-reaching stage by a contributor editing shipped content, not
|
||||
* a determined attempt to defeat it. An isolated security review (#3585)
|
||||
* empirically confirmed the following shapes stage `.planning/` at runtime
|
||||
* while scoring ZERO offenders here, and none of them is a shape GSD
|
||||
* while scoring ZERO offenders here, and none of them is a shape MSD
|
||||
* workflow content actually uses — chasing them means writing a shell
|
||||
* interpreter:
|
||||
* - `eval "git add -A"` — the invocation lives inside a string literal
|
||||
@@ -318,7 +318,7 @@ const scanText = (file, text) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A `# gsd-scan-ignore:` attempt with no tracking reference is a
|
||||
// A `# msd-scan-ignore:` attempt with no tracking reference is a
|
||||
// malformed declaration — reported on its own terms, never silently
|
||||
// folded into "unguarded" (see shipped-command-scan.cjs's declaration
|
||||
// comment for why the diagnosis must be specific).
|
||||
@@ -361,8 +361,8 @@ const scanText = (file, text) => {
|
||||
|
||||
// The repo-wide walk: every `.md` file (recursive) under each scan root.
|
||||
const SCAN_ROOTS = [
|
||||
'gsd-core/workflows',
|
||||
'gsd-core/references',
|
||||
'msd-core/workflows',
|
||||
'msd-core/references',
|
||||
'agents',
|
||||
'commands',
|
||||
'skills',
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
/**
|
||||
* Parsing seam for `tests/pr-branch-planning-filter.test.cjs` against
|
||||
* `/gsd-pr-branch`'s planning-path filter.
|
||||
* `/msd-pr-branch`'s planning-path filter.
|
||||
*
|
||||
* The workflow (`gsd-core/workflows/pr-branch.md`) is the single source of
|
||||
* The workflow (`msd-core/workflows/pr-branch.md`) is the single source of
|
||||
* truth for which `.planning/` subdirectories are "transient" (excluded from
|
||||
* a PR branch in non-strict mode), for the "structural" regex that carves
|
||||
* out an exception even in that non-strict filter, AND for the
|
||||
@@ -34,9 +34,9 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { escapeRegex: escapeRe } = require('../../gsd-core/bin/lib/pattern.cjs');
|
||||
const { escapeRegex: escapeRe } = require('../../msd-core/bin/lib/pattern.cjs');
|
||||
|
||||
const WORKFLOW_PATH = path.join(__dirname, '..', '..', 'gsd-core', 'workflows', 'pr-branch.md');
|
||||
const WORKFLOW_PATH = path.join(__dirname, '..', '..', 'msd-core', 'workflows', 'pr-branch.md');
|
||||
|
||||
const TRANSIENT_DIRS_RE = /^\s*TRANSIENT_DIRS="([^"]*)"/gm;
|
||||
const STRUCTURAL_RE_RE = /^\s*STRUCTURAL_RE="([^"]*)"/gm;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
* Process seam — the single spawnSync-based primitive test helpers use to
|
||||
* run a subprocess and get back a typed, discriminated-union result.
|
||||
*
|
||||
* Design contract: .gsd/phase/test-3055-process-seam-module/40-design.md
|
||||
* Test matrix: .gsd/phase/test-3055-process-seam-module/50-test-matrix.md
|
||||
* Design contract: .msd/phase/test-3055-process-seam-module/40-design.md
|
||||
* Test matrix: .msd/phase/test-3055-process-seam-module/50-test-matrix.md
|
||||
*
|
||||
* Scope (Phase 1, #3055): this module and the `runGsdTools` adapter in
|
||||
* Scope (Phase 1, #3055): this module and the `runMsdTools` adapter in
|
||||
* tests/helpers.cjs. The 23 local wrapper helpers are migrated in a later
|
||||
* wave — they are not touched here.
|
||||
*
|
||||
@@ -33,7 +33,7 @@
|
||||
* `exitCode: null` under an EXITED outcome, an incoherent shape, and
|
||||
* would silently drop the #969 kill-discrimination retry for the exact
|
||||
* case it exists to catch. KILLED is reported as its own outcome so the
|
||||
* `runGsdTools` adapter can retry it exactly like TIMED_OUT.
|
||||
* `runMsdTools` adapter can retry it exactly like TIMED_OUT.
|
||||
* - `status !== null` with a populated `result.error` -> EXITED, evidence
|
||||
* over classification, checked before any error-code branch below. At
|
||||
* the exact timeout boundary, spawnSync can report `error.code ===
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* (`tests/helpers/process-seam.cjs`'s `runNode` forwards extra argv
|
||||
* verbatim, ahead of the target script) so the patch lands in the require
|
||||
* cache BEFORE `bin/install.js`'s own
|
||||
* `require('../gsd-core/bin/lib/install-shadow-report.cjs')` resolves.
|
||||
* `require('../msd-core/bin/lib/install-shadow-report.cjs')` resolves.
|
||||
*
|
||||
* One-shot subprocess: no restoration needed — the process exits right
|
||||
* after the single `install()` call this preload targets. This is NOT a
|
||||
@@ -35,7 +35,7 @@
|
||||
const path = require('node:path');
|
||||
|
||||
const modPath = require.resolve(
|
||||
path.join(__dirname, '..', '..', 'gsd-core', 'bin', 'lib', 'install-shadow-report.cjs'),
|
||||
path.join(__dirname, '..', '..', 'msd-core', 'bin', 'lib', 'install-shadow-report.cjs'),
|
||||
);
|
||||
const shadowReportModule = require(modPath);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { scanFencedBlocks } = require('../../gsd-core/bin/lib/markdown-sectionizer.cjs');
|
||||
const { splitLines } = require('../../gsd-core/bin/lib/text-lines.cjs');
|
||||
const { scanFencedBlocks } = require('../../msd-core/bin/lib/markdown-sectionizer.cjs');
|
||||
const { splitLines } = require('../../msd-core/bin/lib/text-lines.cjs');
|
||||
|
||||
/**
|
||||
* Fold unquoted shell backslash-newline continuations into logical lines.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* commit_docs guard consumes ONE shell tokenizer rather than hand-rolling a
|
||||
* second. Only the primitives BOTH guards need live here — the shell
|
||||
* tokenizer, the bare-command-name extraction, the -c payload search, and
|
||||
* the `gsd-scan-ignore:` declaration machinery.
|
||||
* the `msd-scan-ignore:` declaration machinery.
|
||||
*
|
||||
* Commit-specific logic stays in the test file and is deliberately NOT
|
||||
* moved here: `isCommitInvocation`, `hasScopedFiles`, `SYNOPSIS_TOKEN_RE`,
|
||||
@@ -14,11 +14,11 @@
|
||||
* scan and do not generalize to other guards.
|
||||
*/
|
||||
|
||||
// 443 live substitution sites are `VAR=$(gsd_run …)`. That is the majority
|
||||
// 443 live substitution sites are `VAR=$(msd_run …)`. That is the majority
|
||||
// and it is the wrong generalisation — the remaining six are neither, and
|
||||
// they are live today:
|
||||
// for REVIEW_FLAG in $(gsd_run review-lane flags) (×3)
|
||||
// ${PLAN_PRE_HOOKS_JSON:-$(gsd_run loop render-hooks …)} (×3)
|
||||
// for REVIEW_FLAG in $(msd_run review-lane flags) (×3)
|
||||
// ${PLAN_PRE_HOOKS_JSON:-$(msd_run loop render-hooks …)} (×3)
|
||||
// A parameter-expansion default glues just as hard as an assignment, and so
|
||||
// does ordinary text (`pre$(…)`) and an indexed assignment (`A[0]=$(…)`).
|
||||
// Enumerating prefixes means a new one every time the shell is used
|
||||
@@ -38,13 +38,13 @@
|
||||
// this regex rather than of the grammar and the next edit could lose it.
|
||||
// THE STRIP IS REFUSED ON A TOKEN THAT CARRIED A QUOTE, because quoting is
|
||||
// exactly what decides whether an opener is syntax or data. `printf %s
|
||||
// '$(gsd_run' query commit fixup` runs no gsd command — the opener is
|
||||
// single-quoted literal text — but quote removal leaves the token `$(gsd_run`
|
||||
// '$(msd_run' query commit fixup` runs no msd command — the opener is
|
||||
// single-quoted literal text — but quote removal leaves the token `$(msd_run`
|
||||
// and a strip would then read it as a binary, fabricating an invocation out
|
||||
// of a string. The tokenizer already knows; it just was not saying.
|
||||
//
|
||||
// RESIDUAL, named rather than implied: this closes the strip's exposure, not
|
||||
// the tokenizer's quote-blindness in general. `printf %s 'gsd_run' query
|
||||
// the tokenizer's quote-blindness in general. `printf %s 'msd_run' query
|
||||
// commit fixup` still reads as an invocation, because no markup is stripped
|
||||
// there — the token's value simply IS the binary name. That predates this
|
||||
// rule and is unchanged by it; closing it needs quote provenance carried
|
||||
@@ -54,7 +54,7 @@ const bareCommandName = (t) => {
|
||||
const mask = t.qmask || '0'.repeat(t.value.length);
|
||||
// The LAST opener whose own two characters were unquoted. Per-character,
|
||||
// not per-token: a quote elsewhere in the word protects only itself, and
|
||||
// `echo "pre"$(gsd_run …)` really does run.
|
||||
// `echo "pre"$(msd_run …)` really does run.
|
||||
for (let i = t.value.length - 2; i >= 0; i -= 1) {
|
||||
if (t.value[i] === '$' && t.value[i + 1] === '(' && mask[i] === '0' && mask[i + 1] === '0') {
|
||||
return t.value.slice(i + 2);
|
||||
@@ -69,15 +69,15 @@ const bareCommandName = (t) => {
|
||||
// parity walk counted only `"`, so a single-quoted message was transparent
|
||||
// to it in both directions), backslash escapes, and unquoted control
|
||||
// operators — which become their own tokens, so an operator glued to its
|
||||
// neighbour (`"a"&&gsd_run`) still separates. Quoted operators and quoted
|
||||
// neighbour (`"a"&&msd_run`) still separates. Quoted operators and quoted
|
||||
// whitespace stay literal, which is what keeps a command substitution inside
|
||||
// a message ("$(gsd-tools query phase-list)") from reading as a second
|
||||
// a message ("$(msd-tools query phase-list)") from reading as a second
|
||||
// invocation, with no separator lookahead needed.
|
||||
//
|
||||
// Beyond the operators, the shell consumes THREE more things before argv
|
||||
// exists, and each one previously leaked into the token list as a value —
|
||||
// so `--files >/dev/null 2>&1 || true` (a live tail shape in
|
||||
// gsd-core/references/execute-phase-requirement-revert.md) scored as scoped
|
||||
// msd-core/references/execute-phase-requirement-revert.md) scored as scoped
|
||||
// while the runtime saw files=[] and took the blanket-.planning/ default:
|
||||
// - a single `&` is a control operator like `;` (`&&` is checked first);
|
||||
// - an unquoted `#` at the START of a word begins a comment and ends the
|
||||
@@ -112,7 +112,7 @@ const tokenize = (str) => {
|
||||
// A PER-CHARACTER quote mask, parallel to `cur`: '1' where the character was
|
||||
// quoted or backslash-escaped (i.e. protected, therefore data), '0' where it
|
||||
// was bare syntax. A per-TOKEN boolean is the obvious shape and it is wrong
|
||||
// in the unaffordable direction — `echo "pre"$(gsd_run …)` executes, and a
|
||||
// in the unaffordable direction — `echo "pre"$(msd_run …)` executes, and a
|
||||
// token-wide flag suppresses the strip on it, turning a false positive into
|
||||
// a silent false negative.
|
||||
let curMask = '';
|
||||
@@ -212,7 +212,7 @@ const commentPortion = (line) => {
|
||||
//
|
||||
// IT IS ONLY A MARKER IN COMMENT POSITION. Matching the token anywhere on
|
||||
// the line let a commit MESSAGE carry it — `commit "docs: explain
|
||||
// gsd-scan-ignore: semantics"` — and silently exempt a real offender. A
|
||||
// msd-scan-ignore: semantics"` — and silently exempt a real offender. A
|
||||
// false-negative escape hatch is the one thing this file must not ship, and
|
||||
// an exemption keyed to attacker-controlled-looking text is precisely that.
|
||||
// AND IT MUST CARRY A TRACKING ISSUE. An exemption whose reason is free text
|
||||
@@ -239,10 +239,10 @@ const ISSUE_REF_RE = /#\d+|https?:\/\//;
|
||||
// declaration must not fail as a MYSTERY: left un-exempt and nothing more,
|
||||
// its author is told their commit is unscoped — a true statement about a line
|
||||
// they had already explained, which is the mangle-until-it-shuts-up loop the
|
||||
// marker exists to prevent. `# gsd-scan-ignore:` with an empty reason is the
|
||||
// marker exists to prevent. `# msd-scan-ignore:` with an empty reason is the
|
||||
// likeliest way to get this wrong and so is the case that most needs the
|
||||
// specific diagnosis; a `\S`-requiring pattern silently drops it.
|
||||
const SCAN_IGNORE_REASON_RE = /gsd-scan-ignore:(.*)$/;
|
||||
const SCAN_IGNORE_REASON_RE = /msd-scan-ignore:(.*)$/;
|
||||
const declarationReason = (portion) => {
|
||||
const m = SCAN_IGNORE_REASON_RE.exec(portion);
|
||||
return m === null ? null : m[1];
|
||||
@@ -259,7 +259,7 @@ const declarationReason = (portion) => {
|
||||
// The marker's reason, but only when the marker is in COMMENT position and
|
||||
// survives neither as an argv token. Returns null when there is no attempt.
|
||||
const declaredReasonFor = (line) => {
|
||||
if (tokenize(line).some((t) => /gsd-scan-ignore:/.test(t.value))) return null;
|
||||
if (tokenize(line).some((t) => /msd-scan-ignore:/.test(t.value))) return null;
|
||||
return declarationReason(commentPortion(line));
|
||||
};
|
||||
const isDeclared = (line) => {
|
||||
@@ -276,11 +276,11 @@ const isUntrackedDeclaration = (line) => {
|
||||
|
||||
// A shell invoked with -c runs its next argument AS A COMMAND, so the
|
||||
// invocation lives inside a quoted token and no amount of markup-stripping
|
||||
// reaches it: `bash -c "gsd_run query commit fixup"` scored zero candidates
|
||||
// reaches it: `bash -c "msd_run query commit fixup"` scored zero candidates
|
||||
// while being perfectly executable. The recursion is keyed on the INVOKER,
|
||||
// not on "a quoted token that parses as an invocation" — that wider rule
|
||||
// would flag a commit MESSAGE quoting a command, a false positive against
|
||||
// ordinary documentation. Here the outer command is bash, so a gsd commit
|
||||
// ordinary documentation. Here the outer command is bash, so a msd commit
|
||||
// message can never reach it.
|
||||
// The invoker set, widened past the four spellings the first cut guessed at.
|
||||
// `ash`, `csh`, `tcsh`, `fish` and `yash` all take -c and all run what
|
||||
@@ -293,7 +293,7 @@ const shellDashCPayloads = (str) => {
|
||||
const drain = () => {
|
||||
if (!group.length) { return; }
|
||||
// THE INVOKER MUST BE THE COMMAND, not merely present in the segment.
|
||||
// Searching anywhere made `echo bash -c "gsd_run query commit fixup"` a
|
||||
// Searching anywhere made `echo bash -c "msd_run query commit fixup"` a
|
||||
// candidate — `echo` prints the string, it does not run it — which is a
|
||||
// false positive against prose that merely quotes a command line. Only an
|
||||
// env assignment, a shell keyword, or a list/prompt marker may precede the
|
||||
|
||||
@@ -160,7 +160,7 @@ const FIXTURE_HOOK_TIMEOUT_SECONDS = 5;
|
||||
const STAGED_HOOK_SCRIPT_TIMEOUT_MS = 20000;
|
||||
|
||||
/**
|
||||
* A single `gsd-tools.cjs` CLI subcommand invocation, spawned directly (never
|
||||
* A single `msd-tools.cjs` CLI subcommand invocation, spawned directly (never
|
||||
* via an intermediate shell script), used by a loop/hook-point e2e test. The
|
||||
* command does real in-process work -- capability/config resolution, project
|
||||
* scaffolding, or (for the `check` verbs that inspect git-tracked state) real
|
||||
@@ -170,12 +170,12 @@ const STAGED_HOOK_SCRIPT_TIMEOUT_MS = 20000;
|
||||
* norm's 7 sites: `loop render-hooks <point>`, `check <check-id>`,
|
||||
* `execute <hook-point>`, and `init new-project` (tests/loop-walk.qa.test.cjs's
|
||||
* concurrency test) -- the norm describes the CALL SHAPE (single direct
|
||||
* gsd-tools.cjs spawn, no fan-out), not one specific verb family, so this list
|
||||
* msd-tools.cjs spawn, no fan-out), not one specific verb family, so this list
|
||||
* is illustrative, not exhaustive.
|
||||
*
|
||||
* Distinct from `HOOK_FANOUT_TIMEOUT_MS` (a bash-hosted script that itself
|
||||
* shells out to further subprocesses) despite the coincidentally-matching
|
||||
* value: this norm's sites spawn `gsd-tools.cjs` and nothing beneath it.
|
||||
* value: this norm's sites spawn `msd-tools.cjs` and nothing beneath it.
|
||||
* Distinct from `PROBE_TIMEOUT_MS`: the same underlying `loop render-hooks`
|
||||
* command is ALSO called at `PROBE_TIMEOUT_MS` (15000ms) elsewhere in the
|
||||
* suite against lighter temp-project fixtures -- the two bounds are kept
|
||||
@@ -211,7 +211,7 @@ const LOOP_HOOK_POINT_CLI_TIMEOUT_MS = 60000;
|
||||
const SCAN_USAGE_ERROR_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* A Node hook script (gsd-prompt-guard-hook.js, gsd-read-injection-scanner.js)
|
||||
* A Node hook script (msd-prompt-guard-hook.js, msd-read-injection-scanner.js)
|
||||
* spawned directly and fed malformed JSON on stdin -- expected to fail closed
|
||||
* gracefully, no subprocess fan-out, no real scan work. A different operation
|
||||
* family from `SCAN_USAGE_ERROR_TIMEOUT_MS` (a bash scan script given no
|
||||
@@ -282,7 +282,7 @@ const REAL_REPO_GIT_TIMEOUT_MS = 30000;
|
||||
const PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS = 60000;
|
||||
|
||||
/**
|
||||
* A single `gsd-tools.cjs` CLI subcommand invocation, spawned directly
|
||||
* A single `msd-tools.cjs` CLI subcommand invocation, spawned directly
|
||||
* (execFileSync/spawnSync, or the process seam's runNode wrapping the same
|
||||
* shape -- never via an intermediate shell script), with no confirmed
|
||||
* nested-subprocess fan-out. A distinct, heavier tier than
|
||||
@@ -298,7 +298,7 @@ const PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS = 60000;
|
||||
* migration, epic #4445 -- every site independently arrived at this exact
|
||||
* value -- that is why it lives here rather than as a file-local constant.
|
||||
*/
|
||||
const GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS = 30000;
|
||||
const MSD_TOOLS_CLI_MODERATE_TIMEOUT_MS = 30000;
|
||||
|
||||
/**
|
||||
* NOT a subprocess spawn timeout. Fixture DATA -- the default, plausible-
|
||||
@@ -348,7 +348,7 @@ module.exports = {
|
||||
GENERATOR_SCRIPT_TIMEOUT_MS,
|
||||
REAL_REPO_GIT_TIMEOUT_MS,
|
||||
PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS,
|
||||
GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS,
|
||||
MSD_TOOLS_CLI_MODERATE_TIMEOUT_MS,
|
||||
TASK_RESOLVER_INVOKE_TIMEOUT_MS,
|
||||
HTTP_REACHABLE_PROBE_TIMEOUT_FIXTURE_MS,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user