Files
msd-core/scripts/gen-golden-install-parity-zcode.cjs
Tom Boucher d49ac81306 chore(#2143): markdown table model + schema registry + fail-loud pilot — Phase 1 (#2248)
* chore(#2143): markdown table model + schema registry + fail-loud pilot — Phase 1

Phase 1 of epic #2143 (ADR-2143): consolidate markdown table parsing onto a
canonical seam and migrate the pilot reader.

- Add src/markdown-table.cts: parseMarkdownTable (GFM tables -> typed
  {columns, rows} addressed by column NAME; ragged rows are typed parse
  errors, not silent), a single-source TABLE_SCHEMAS registry
  (RoadmapProgress / RequirementsTraceability / QuickTasks / Security, with
  variants under one id), matchTableSchema, and findTableBySchema. Result<T>
  is scoped to this seam (distinct from the dispatch Result).
- Migrate deriveProgressFromRoadmap (src/phase-lifecycle.cts) off the
  position-anchored regex to name-based resolution via the seam — fixes #2137
  (the 5-column milestone-grouped Progress table previously returned all-null).
- Add a schema-backed `gsd-tools quick-tasks-append` subcommand and route
  fast.md's log_to_state through it, retiring the inline `awk NF-2` column
  arithmetic — fixes #2133 (addresses #2012, #2119). Cell values are escaped
  (| and newlines) and the STATE.md read-modify-write is atomic under
  readModifyWriteStateMd (lost-update race, cf. #500/#905/#1230).
- Writer/reader/template parity test guards TABLE_SCHEMAS against drift
  (ADR-2143 §3 Generative-Fix-Divergence).

Registration: .gitignore, eslint.config.mjs, docs/INVENTORY.md +
INVENTORY-MANIFEST.json, CONTEXT.md glossary, docs/CLI-TOOLS.md.

Behaviour-preserving for the canonical 4-column Progress table; the named
bugs are driven fail-first. Extend-never-mutate (ADR-2143 §2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#2242): backfill changeset PR number (#2248)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2242): escape backslash before pipe in markdown-table cell escaping

CodeQL js/incomplete-sanitization (high): escapeCell escaped | -> \| but not
the backslash itself. Now escapes \ -> \\ before | -> \|, and splitTableRow
unescapes both \\ -> \ and \| -> | symmetrically so cell values (incl.
literal backslashes) round-trip exactly. Added backslash round-trip tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#2242): read ROADMAP Progress table by column name — supersede #2168 ad-hoc scan

Rebase reconciliation with #2168 (the tactical #2137 fix that marked itself
"pending #2143"). deriveProgressFromRoadmap now resolves the Progress table via
a new seam helper findTableWithColumns (first table whose header is a superset of
Phase/Plans Complete/Status/Completed, any order, extra columns ignored) and reads
cells by NAME — order/injection-invariant per ADR-2143 §3 — instead of the exact
TABLE_SCHEMAS match. This satisfies #2168's column-invariance property test while
staying seam-based and preserving its `## Progress` scoping (#2012/#1445).
Ragged Progress tables now resolve to null (ADR-2143 fail-loud); updated the stale
state.test.cjs assertion that predated the Phase-1 migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 20:36:14 -04:00

103 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* Standalone golden-fixture generator for tests/golden-install-parity.
*
* This is a BUILD-TIME generation script — NOT a test run. It replicates the
* buildParityManifest logic from tests/golden-install-parity.test.cjs and
* captures the zcode fixture so the parity test (which the gsd-test gate runs)
* has a committed artifact to compare against. The authoritative test gate
* remains `gsd-test run`, never a local `node --test`.
*
* Usage: node scripts/gen-golden-install-parity-zcode.cjs
*/
const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
const ROOT = path.resolve(__dirname, '..');
const { walk, runMinimalInstall, RUNTIME_META } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
const PKG_VERSION = require(path.join(ROOT, 'package.json')).version;
const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'golden-install-parity');
const VOLATILE_FILES = new Set([
'gsd-file-manifest.json',
'gsd-install-state.json',
'.gsd-source',
'gsd-core/CHANGELOG.md',
]);
// Must match tests/golden-install-parity.test.cjs exactly — settings.local.json
// (Claude LOCAL hook surface, #338/#2086) embeds the same platform-varying
// node-runner command and is excluded there; omitting it here mis-generated the
// claude-local fixture (#2100).
const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hooks.json']);
// Kimi's native config.toml (#2095) — see tests/golden-install-parity.test.cjs'
// HOOK_CONFIG_RELATIVE_PATHS comment for why this is an exact relative-path
// exclusion rather than a HOOK_CONFIG_FILES basename entry (a basename entry
// would also blind Codex's stable, platform-independent config.toml fixture).
const HOOK_CONFIG_RELATIVE_PATHS = new Set(['.kimi/config.toml']);
const EXCLUDED_PREFIXES = ['gsd-core/bin/lib/'];
function buildParityManifest(configDir, root) {
const allFiles = walk(configDir);
const unsorted = {};
for (const full of allFiles) {
const rel = path.relative(configDir, full).split(path.sep).join('/');
if (VOLATILE_FILES.has(rel)) continue;
if (HOOK_CONFIG_FILES.has(path.basename(rel))) continue;
if (HOOK_CONFIG_RELATIVE_PATHS.has(rel)) continue;
if (EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue;
const content = fs.readFileSync(full);
const normalized = content.toString('utf8').split(root).join('<HOME>').split(PKG_VERSION).join('<VERSION>');
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
unsorted[rel] = hash;
}
const sorted = {};
for (const key of Object.keys(unsorted).sort()) sorted[key] = unsorted[key];
return sorted;
}
function cleanup(root) {
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* best effort */ }
}
// Regenerate the fixture for every runtime in RUNTIME_META. Needed when a
// SHARED gsd-core payload file (e.g. model-catalog.json, capability-registry)
// changes content — its hash appears in every runtime's manifest, so all
// fixtures must be recaptured together. Usage:
// node scripts/gen-golden-install-parity-zcode.cjs [runtime ...]
// With no args, regenerates ALL runtimes. With args, only the named runtimes.
const targets = process.argv.slice(2).length > 0 ? process.argv.slice(2) : Object.keys(RUNTIME_META);
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
for (const runtime of targets) {
if (!Object.prototype.hasOwnProperty.call(RUNTIME_META, runtime)) {
process.stderr.write(`[gen] unknown runtime '${runtime}' (not in RUNTIME_META) — skipping\n`);
continue;
}
const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' });
let actual;
try {
actual = buildParityManifest(configDir, root);
} finally {
cleanup(root);
}
const fixturePath = path.join(FIXTURE_DIR, `${runtime}.json`);
fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8');
process.stdout.write(`[gen] ${runtime}: wrote ${Object.keys(actual).length} file hashes -> ${fixturePath}\n`);
}
// Also regenerate the claude LOCAL legacy-layout fixture (claude-local.json).
// This layout is distinct from the global install (commands/gsd-*.md +
// agents/gsd-*.md) and has its own parity assertion in the test harness.
const { configDir: localConfigDir, root: localRoot } = runMinimalInstall({ runtime: 'claude', scope: 'local' });
let localActual;
try {
localActual = buildParityManifest(localConfigDir, localRoot);
} finally {
cleanup(localRoot);
}
const localFixturePath = path.join(FIXTURE_DIR, 'claude-local.json');
fs.writeFileSync(localFixturePath, JSON.stringify(localActual, null, 2) + '\n', 'utf8');
process.stdout.write(`[gen] claude-local: wrote ${Object.keys(localActual).length} file hashes -> ${localFixturePath}\n`);