@opengsd/gsd-core 1.7.0-rc.6 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/bin/install.js +5 -4
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +57 -57
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/init.cjs +27 -13
- package/gsd-core/bin/lib/install-engine.cjs +3 -2
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +7 -6
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +2 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +25 -17
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +1 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +61 -25
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +2 -2
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/checkpoints.md +12 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/hooks/dist/gsd-statusline.js +164 -14
- package/hooks/gsd-statusline.js +164 -14
- package/package.json +4 -2
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -45
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -3,60 +3,29 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Standalone golden-fixture generator for tests/golden-install-parity.
|
|
5
5
|
*
|
|
6
|
-
* This is a BUILD-TIME generation script — NOT a test run. It
|
|
7
|
-
* buildParityManifest
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* This is a BUILD-TIME generation script — NOT a test run. It imports the
|
|
7
|
+
* canonical buildParityManifest builder from tests/helpers/install-shared.cjs
|
|
8
|
+
* (issue #2266 — single source of truth shared with
|
|
9
|
+
* tests/golden-install-parity.test.cjs) and captures the zcode fixture so the
|
|
10
|
+
* parity test (which the gsd-test gate runs) has a committed artifact to
|
|
11
|
+
* compare against. The authoritative test gate remains `gsd-test run`, never
|
|
12
|
+
* a local `node --test`.
|
|
11
13
|
*
|
|
12
14
|
* Usage: node scripts/gen-golden-install-parity-zcode.cjs
|
|
13
15
|
*/
|
|
14
16
|
const fs = require('node:fs');
|
|
15
17
|
const path = require('node:path');
|
|
16
|
-
const
|
|
18
|
+
const { execFileSync } = require('node:child_process');
|
|
17
19
|
|
|
18
20
|
const ROOT = path.resolve(__dirname, '..');
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
// buildParityManifest (and its exclusion constants) is the canonical single
|
|
22
|
+
// source of truth in tests/helpers/install-shared.cjs (issue #2266) — the
|
|
23
|
+
// generator no longer keeps its own inline copy, which had drifted from the
|
|
24
|
+
// test harness's copy (missing the realpath/`<HOME>` normalization) and
|
|
25
|
+
// mis-generated the claude-local fixture (#2100).
|
|
26
|
+
const { runMinimalInstall, RUNTIME_META, buildParityManifest, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
|
|
21
27
|
const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'golden-install-parity');
|
|
22
28
|
|
|
23
|
-
const VOLATILE_FILES = new Set([
|
|
24
|
-
'gsd-file-manifest.json',
|
|
25
|
-
'gsd-install-state.json',
|
|
26
|
-
'.gsd-source',
|
|
27
|
-
'gsd-core/CHANGELOG.md',
|
|
28
|
-
]);
|
|
29
|
-
// Must match tests/golden-install-parity.test.cjs exactly — settings.local.json
|
|
30
|
-
// (Claude LOCAL hook surface, #338/#2086) embeds the same platform-varying
|
|
31
|
-
// node-runner command and is excluded there; omitting it here mis-generated the
|
|
32
|
-
// claude-local fixture (#2100).
|
|
33
|
-
const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hooks.json']);
|
|
34
|
-
// Kimi's native config.toml (#2095) — see tests/golden-install-parity.test.cjs'
|
|
35
|
-
// HOOK_CONFIG_RELATIVE_PATHS comment for why this is an exact relative-path
|
|
36
|
-
// exclusion rather than a HOOK_CONFIG_FILES basename entry (a basename entry
|
|
37
|
-
// would also blind Codex's stable, platform-independent config.toml fixture).
|
|
38
|
-
const HOOK_CONFIG_RELATIVE_PATHS = new Set(['.kimi/config.toml']);
|
|
39
|
-
const EXCLUDED_PREFIXES = ['gsd-core/bin/lib/'];
|
|
40
|
-
|
|
41
|
-
function buildParityManifest(configDir, root) {
|
|
42
|
-
const allFiles = walk(configDir);
|
|
43
|
-
const unsorted = {};
|
|
44
|
-
for (const full of allFiles) {
|
|
45
|
-
const rel = path.relative(configDir, full).split(path.sep).join('/');
|
|
46
|
-
if (VOLATILE_FILES.has(rel)) continue;
|
|
47
|
-
if (HOOK_CONFIG_FILES.has(path.basename(rel))) continue;
|
|
48
|
-
if (HOOK_CONFIG_RELATIVE_PATHS.has(rel)) continue;
|
|
49
|
-
if (EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue;
|
|
50
|
-
const content = fs.readFileSync(full);
|
|
51
|
-
const normalized = content.toString('utf8').split(root).join('<HOME>').split(PKG_VERSION).join('<VERSION>');
|
|
52
|
-
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
|
|
53
|
-
unsorted[rel] = hash;
|
|
54
|
-
}
|
|
55
|
-
const sorted = {};
|
|
56
|
-
for (const key of Object.keys(unsorted).sort()) sorted[key] = unsorted[key];
|
|
57
|
-
return sorted;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
29
|
function cleanup(root) {
|
|
61
30
|
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
62
31
|
}
|
|
@@ -69,6 +38,13 @@ function cleanup(root) {
|
|
|
69
38
|
// With no args, regenerates ALL runtimes. With args, only the named runtimes.
|
|
70
39
|
const targets = process.argv.slice(2).length > 0 ? process.argv.slice(2) : Object.keys(RUNTIME_META);
|
|
71
40
|
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
|
|
41
|
+
|
|
42
|
+
// Build hooks/dist before capturing so fixtures are complete even in a clean
|
|
43
|
+
// checkout (hooks/dist is gitignored + built; DEFECT.HOOKS-DIST-SCOPED-CI). The
|
|
44
|
+
// test harness's before() hook does the same; without it a fresh checkout omits
|
|
45
|
+
// every hooks/* path and this generator silently writes short fixtures.
|
|
46
|
+
execFileSync(process.execPath, [BUILD_SCRIPT], { stdio: 'pipe' });
|
|
47
|
+
|
|
72
48
|
for (const runtime of targets) {
|
|
73
49
|
if (!Object.prototype.hasOwnProperty.call(RUNTIME_META, runtime)) {
|
|
74
50
|
process.stderr.write(`[gen] unknown runtime '${runtime}' (not in RUNTIME_META) — skipping\n`);
|
|
@@ -85,3 +61,17 @@ for (const runtime of targets) {
|
|
|
85
61
|
fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8');
|
|
86
62
|
process.stdout.write(`[gen] ${runtime}: wrote ${Object.keys(actual).length} file hashes -> ${fixturePath}\n`);
|
|
87
63
|
}
|
|
64
|
+
|
|
65
|
+
// Also regenerate the claude LOCAL legacy-layout fixture (claude-local.json).
|
|
66
|
+
// This layout is distinct from the global install (commands/gsd-*.md +
|
|
67
|
+
// agents/gsd-*.md) and has its own parity assertion in the test harness.
|
|
68
|
+
const { configDir: localConfigDir, root: localRoot } = runMinimalInstall({ runtime: 'claude', scope: 'local' });
|
|
69
|
+
let localActual;
|
|
70
|
+
try {
|
|
71
|
+
localActual = buildParityManifest(localConfigDir, localRoot);
|
|
72
|
+
} finally {
|
|
73
|
+
cleanup(localRoot);
|
|
74
|
+
}
|
|
75
|
+
const localFixturePath = path.join(FIXTURE_DIR, 'claude-local.json');
|
|
76
|
+
fs.writeFileSync(localFixturePath, JSON.stringify(localActual, null, 2) + '\n', 'utf8');
|
|
77
|
+
process.stdout.write(`[gen] claude-local: wrote ${Object.keys(localActual).length} file hashes -> ${localFixturePath}\n`);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Standalone golden-fixture generator for tests/golden-install-tree (#2267 Phase 2).
|
|
5
|
+
*
|
|
6
|
+
* This is a BUILD-TIME generation script — NOT a test run. It imports the
|
|
7
|
+
* canonical buildInstallTree builder from tests/helpers/install-shared.cjs,
|
|
8
|
+
* which in turn reuses buildParityManifest's exact exclusion set (issue
|
|
9
|
+
* #2266) so the file-set fixtures here and the golden-install-parity content
|
|
10
|
+
* fixtures never diverge on which files they cover. The authoritative test
|
|
11
|
+
* gate remains `gsd-test run`, never a local `node --test`.
|
|
12
|
+
*
|
|
13
|
+
* Usage: node scripts/gen-install-tree-fixtures.cjs [runtime ...]
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { execFileSync } = require('node:child_process');
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
20
|
+
// buildInstallTree (and the exclusion constants it reuses from
|
|
21
|
+
// buildParityManifest) is the canonical single source of truth in
|
|
22
|
+
// tests/helpers/install-shared.cjs (issue #2266/#2267) — this generator does
|
|
23
|
+
// not keep its own inline copy of the walk/exclusion logic.
|
|
24
|
+
const { runMinimalInstall, RUNTIME_META, buildInstallTree, BUILD_SCRIPT } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
|
|
25
|
+
const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'install-tree');
|
|
26
|
+
|
|
27
|
+
function cleanup(root) {
|
|
28
|
+
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Regenerate the fixture for every runtime in RUNTIME_META. Needed when a
|
|
32
|
+
// SHARED gsd-core payload file (e.g. model-catalog.json, capability-registry)
|
|
33
|
+
// changes content — its path appears in every runtime's manifest, so all
|
|
34
|
+
// fixtures must be recaptured together. Usage:
|
|
35
|
+
// node scripts/gen-install-tree-fixtures.cjs [runtime ...]
|
|
36
|
+
// With no args, regenerates ALL runtimes. With args, only the named runtimes.
|
|
37
|
+
const targets = process.argv.slice(2).length > 0 ? process.argv.slice(2) : Object.keys(RUNTIME_META);
|
|
38
|
+
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
|
|
39
|
+
|
|
40
|
+
// Build hooks/dist before capturing so fixtures are complete even in a clean
|
|
41
|
+
// checkout (hooks/dist is gitignored + built; DEFECT.HOOKS-DIST-SCOPED-CI). The
|
|
42
|
+
// test harness's before() hook does the same; without it a fresh checkout omits
|
|
43
|
+
// every hooks/* path and this generator silently writes short fixtures.
|
|
44
|
+
execFileSync(process.execPath, [BUILD_SCRIPT], { stdio: 'pipe' });
|
|
45
|
+
|
|
46
|
+
for (const runtime of targets) {
|
|
47
|
+
if (!Object.prototype.hasOwnProperty.call(RUNTIME_META, runtime)) {
|
|
48
|
+
process.stderr.write(`[gen] unknown runtime '${runtime}' (not in RUNTIME_META) — skipping\n`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' });
|
|
52
|
+
let actual;
|
|
53
|
+
try {
|
|
54
|
+
actual = buildInstallTree(configDir, root);
|
|
55
|
+
} finally {
|
|
56
|
+
cleanup(root);
|
|
57
|
+
}
|
|
58
|
+
const fixturePath = path.join(FIXTURE_DIR, `${runtime}.json`);
|
|
59
|
+
fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8');
|
|
60
|
+
process.stdout.write(`[gen] ${runtime}: wrote ${actual.length} paths -> ${fixturePath}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Also regenerate the claude LOCAL legacy-layout fixture (claude-local.json).
|
|
64
|
+
// This layout is distinct from the global install (commands/gsd-*.md +
|
|
65
|
+
// agents/gsd-*.md) and has its own snapshot assertion in the test harness.
|
|
66
|
+
const { configDir: localConfigDir, root: localRoot } = runMinimalInstall({ runtime: 'claude', scope: 'local' });
|
|
67
|
+
let localActual;
|
|
68
|
+
try {
|
|
69
|
+
localActual = buildInstallTree(localConfigDir, localRoot);
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup(localRoot);
|
|
72
|
+
}
|
|
73
|
+
const localFixturePath = path.join(FIXTURE_DIR, 'claude-local.json');
|
|
74
|
+
fs.writeFileSync(localFixturePath, JSON.stringify(localActual, null, 2) + '\n', 'utf8');
|
|
75
|
+
process.stdout.write(`[gen] claude-local: wrote ${localActual.length} paths -> ${localFixturePath}\n`);
|
|
@@ -99,7 +99,6 @@
|
|
|
99
99
|
"tests/mcp-tool-inheritance.test.cjs :: source-text-is-the-product",
|
|
100
100
|
"tests/milestone-summary.test.cjs :: source-text-is-the-product",
|
|
101
101
|
"tests/milestone.test.cjs :: source-text-is-the-product",
|
|
102
|
-
"tests/milestone.test.cjs :: structural-regression-guard",
|
|
103
102
|
"tests/model-catalog-runtime-defaults.test.cjs :: source-text-is-the-product",
|
|
104
103
|
"tests/next-safety-gates.test.cjs :: source-text-is-the-product",
|
|
105
104
|
"tests/next-up-clear-order.test.cjs :: source-text-is-the-product",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Table-schema drift lint (ADR-2143 §3 Generative-Fix-Divergence guard, §7
|
|
6
|
+
* "prohibition with teeth", epic #2143).
|
|
7
|
+
*
|
|
8
|
+
* `TABLE_SCHEMAS` (`src/markdown-table.cts`, compiled to
|
|
9
|
+
* `gsd-core/bin/lib/markdown-table.cjs`) is the single-source registry naming
|
|
10
|
+
* every canonical GSD pipe-table's column-header shape. Each registered
|
|
11
|
+
* variant's exact `| col | col |` header string MUST appear verbatim in the
|
|
12
|
+
* ONE template/workflow file that emits that table — otherwise the writer and
|
|
13
|
+
* the registry have silently drifted apart, the exact bug class this ADR
|
|
14
|
+
* closes (#2137 — reader hard-codes a shape the writer doesn't emit; #2133 —
|
|
15
|
+
* the `quick.md` writer and its shell guard disagree on column count; #2119 —
|
|
16
|
+
* dual SECURITY.md writers with conflicting shapes).
|
|
17
|
+
*
|
|
18
|
+
* Modeled on scripts/lint-phase-id-drift.cjs / scripts/lint-package-identity-drift.cjs:
|
|
19
|
+
* a standalone node script (not a node:test), wired into `lint:ci`, exit 0 clean /
|
|
20
|
+
* exit 1 + message on drift.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
|
|
26
|
+
// Schema id -> the ONE canonical template/workflow file that must emit every
|
|
27
|
+
// variant's header verbatim (ADR-2143 §3).
|
|
28
|
+
const SCHEMA_SOURCE_FILES = {
|
|
29
|
+
RoadmapProgress: path.join('gsd-core', 'templates', 'roadmap.md'),
|
|
30
|
+
RequirementsTraceability: path.join('gsd-core', 'templates', 'requirements.md'),
|
|
31
|
+
QuickTasks: path.join('gsd-core', 'workflows', 'quick.md'),
|
|
32
|
+
Security: path.join('gsd-core', 'templates', 'SECURITY.md'),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Build the exact `| a | b | c |` header line for one schema variant. */
|
|
36
|
+
function buildHeader(variant) {
|
|
37
|
+
return `| ${variant.columns.join(' | ')} |`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Normalize whitespace around pipes so template formatting quirks (extra
|
|
41
|
+
* spaces added by an editor, etc.) don't produce a false drift report. */
|
|
42
|
+
function normalize(line) {
|
|
43
|
+
return line.replace(/[ \t]*\|[ \t]*/g, '|').trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pure: given `schemas` (a `TABLE_SCHEMAS`-shaped object) and a
|
|
48
|
+
* `readFile(relPath) -> string|null` accessor, find every variant whose exact
|
|
49
|
+
* header does not appear verbatim (pipe-whitespace normalized) in its
|
|
50
|
+
* schema's registered source file. Returns
|
|
51
|
+
* `[{ schemaId, label, header, file, reason }]`; empty when clean.
|
|
52
|
+
*/
|
|
53
|
+
function findTableSchemaDrift(schemas, readFile, sourceFiles = SCHEMA_SOURCE_FILES) {
|
|
54
|
+
const violations = [];
|
|
55
|
+
for (const [schemaId, variants] of Object.entries(schemas)) {
|
|
56
|
+
const relPath = sourceFiles[schemaId];
|
|
57
|
+
if (!relPath) {
|
|
58
|
+
violations.push({
|
|
59
|
+
schemaId,
|
|
60
|
+
label: null,
|
|
61
|
+
header: null,
|
|
62
|
+
file: null,
|
|
63
|
+
reason: 'no canonical source file registered for this schema id in SCHEMA_SOURCE_FILES',
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const content = readFile(relPath);
|
|
69
|
+
if (content == null) {
|
|
70
|
+
for (const variant of variants) {
|
|
71
|
+
violations.push({
|
|
72
|
+
schemaId,
|
|
73
|
+
label: variant.label,
|
|
74
|
+
header: buildHeader(variant),
|
|
75
|
+
file: relPath,
|
|
76
|
+
reason: 'source file not found or unreadable',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const normalizedLines = content.split(/\r?\n/).map(normalize);
|
|
83
|
+
for (const variant of variants) {
|
|
84
|
+
const expected = normalize(buildHeader(variant));
|
|
85
|
+
if (!normalizedLines.includes(expected)) {
|
|
86
|
+
violations.push({
|
|
87
|
+
schemaId,
|
|
88
|
+
label: variant.label,
|
|
89
|
+
header: buildHeader(variant),
|
|
90
|
+
file: relPath,
|
|
91
|
+
reason: 'header not found verbatim in source file',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return violations;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Load the built seam and scan the real repo tree. Returns the same shape as
|
|
101
|
+
* `findTableSchemaDrift`. If the seam hasn't been built yet (`npm run
|
|
102
|
+
* build:lib`), reports a single actionable violation rather than throwing.
|
|
103
|
+
*/
|
|
104
|
+
function scanRepo(root) {
|
|
105
|
+
const seamPath = path.join(root, 'gsd-core', 'bin', 'lib', 'markdown-table.cjs');
|
|
106
|
+
let seam;
|
|
107
|
+
try {
|
|
108
|
+
seam = require(seamPath);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return [{
|
|
111
|
+
schemaId: null,
|
|
112
|
+
label: null,
|
|
113
|
+
header: null,
|
|
114
|
+
file: null,
|
|
115
|
+
reason: `cannot load the markdown-table seam at ${path.relative(root, seamPath)} — run 'npm run build:lib' first (${e.message})`,
|
|
116
|
+
}];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const readFile = (relPath) => {
|
|
120
|
+
try {
|
|
121
|
+
return fs.readFileSync(path.join(root, relPath), 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return findTableSchemaDrift(seam.TABLE_SCHEMAS, readFile);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function main() {
|
|
131
|
+
const root = path.join(__dirname, '..');
|
|
132
|
+
const violations = scanRepo(root);
|
|
133
|
+
if (violations.length === 0) {
|
|
134
|
+
process.stdout.write(
|
|
135
|
+
'ok table-schema-drift: every TABLE_SCHEMAS variant header appears verbatim in its canonical template/workflow\n',
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
process.stderr.write(
|
|
140
|
+
'table-schema-drift: TABLE_SCHEMAS variant(s) whose header is absent from their canonical source file (ADR-2143 §3).\n',
|
|
141
|
+
);
|
|
142
|
+
process.stderr.write(
|
|
143
|
+
'Either update the template/workflow to emit the registered header verbatim, or update\n' +
|
|
144
|
+
'TABLE_SCHEMAS in src/markdown-table.cts to match — the two must never drift.\n',
|
|
145
|
+
);
|
|
146
|
+
for (const v of violations) {
|
|
147
|
+
const id = v.label ? `${v.schemaId}.${v.label}` : (v.schemaId ?? '?');
|
|
148
|
+
const loc = v.file ? ` (${v.file})` : '';
|
|
149
|
+
const expected = v.header ? ` — expected ${JSON.stringify(v.header)}` : '';
|
|
150
|
+
process.stderr.write(` ${id}${loc}: ${v.reason}${expected}\n`);
|
|
151
|
+
}
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (require.main === module) main();
|
|
156
|
+
|
|
157
|
+
module.exports = { findTableSchemaDrift, scanRepo, buildHeader, normalize, SCHEMA_SOURCE_FILES };
|
|
@@ -55,7 +55,33 @@ This step is `onError: skip` at `discuss:post` / `plan:post` / `verify:post` --
|
|
|
55
55
|
On any error or timeout, stop and let the phase continue -- capture is best-effort.
|
|
56
56
|
|
|
57
57
|
1. **Dedup first.** Interactive: `mempalace_check_duplicate` on the artifact's deterministic drawer id. Headless: rely on `mempalace mine`'s content-hash idempotency.
|
|
58
|
-
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless:
|
|
58
|
+
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless: see below.
|
|
59
|
+
|
|
60
|
+
**`mempalace mine` has no `--room` flag** — only `search` accepts `--room` ([CLI reference: https://mempalaceofficial.com/reference/cli.html](https://mempalaceofficial.com/reference/cli.html)). Room assignment is driven by `detect_room()` matching folder-path segments against the `rooms:` list in `mempalace.yaml` ([mining guide: https://mempalaceofficial.com/guide/mining.html](https://mempalaceofficial.com/guide/mining.html) — "Rooms are auto-detected from your folder structure"; [config reference: https://mempalaceofficial.com/guide/configuration.html](https://mempalaceofficial.com/guide/configuration.html)). Stage the artifact under a room-named folder so `detect_room()` assigns it correctly:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
STAGE=".planning/.mempalace-stage"
|
|
64
|
+
# One-time: declare the GSD room taxonomy so detect_room() recognizes these folders
|
|
65
|
+
mkdir -p "$STAGE"
|
|
66
|
+
[ -f "$STAGE/mempalace.yaml" ] || cat > "$STAGE/mempalace.yaml" <<'YAML'
|
|
67
|
+
rooms:
|
|
68
|
+
- decisions
|
|
69
|
+
- planning
|
|
70
|
+
- milestones
|
|
71
|
+
- problems
|
|
72
|
+
- general
|
|
73
|
+
YAML
|
|
74
|
+
# Suppress MemPalace cache artifacts written into the scanned tree
|
|
75
|
+
[ -f "$STAGE/.gitignore" ] || echo "mempalace_embedder.json" > "$STAGE/.gitignore"
|
|
76
|
+
# Stage under <room>/<phase-id>/<basename> — stable path so mine's content-hash
|
|
77
|
+
# idempotency (file_already_mined keys on absolute source_file + mtime) deduplicates
|
|
78
|
+
# instead of creating duplicate drawers on re-runs.
|
|
79
|
+
ROOM_DIR="$STAGE/<room>/<phase-id>"
|
|
80
|
+
mkdir -p "$ROOM_DIR"
|
|
81
|
+
cp "<artifact-path>" "$ROOM_DIR/<basename>"
|
|
82
|
+
# Mine with --wing only — no --room flag; detect_room() assigns from the folder path
|
|
83
|
+
mempalace mine "$STAGE" --wing <wing>
|
|
84
|
+
```
|
|
59
85
|
3. **Mirror KG facts** when `config.mempalace.mirror_kg` is true: extract decision/delivery facts and `mempalace_kg_add` them with `valid_from` = the phase date (e.g. `(<project>, decided, <decision>)` from CONTEXT; `(<phase>, delivered, <capability>)` from SUMMARY). Under `augment` these are an *additive* mirror of GSD's native `.planning/graphs/`. Under `kg_backend`/`replace` the palace KG is the *authoritative* fact store — GSD still produces `.planning/graphs/` through its normal graphify, so an unreachable palace never loses a fact.
|
|
60
86
|
4. Re-running a phase MUST NOT create duplicate drawers (deterministic ids + `check_duplicate`).
|
|
61
87
|
|
|
@@ -37,9 +37,9 @@ Parse the first token of $ARGUMENTS:
|
|
|
37
37
|
## list / status
|
|
38
38
|
|
|
39
39
|
Load the capability registry and call `listSurface(runtimeConfigDir, manifest, CLUSTERS, registry)` from
|
|
40
|
-
|
|
40
|
+
the engine module at `${runtimeConfigDir}/gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
|
41
41
|
```js
|
|
42
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
42
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
43
43
|
```
|
|
44
44
|
Display:
|
|
45
45
|
|
|
@@ -71,7 +71,7 @@ Install profile: standard (from .gsd-profile)
|
|
|
71
71
|
3. `writeSurface(runtimeConfigDir, surfaceState)`.
|
|
72
72
|
4. Resolve and re-apply:
|
|
73
73
|
```js
|
|
74
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
74
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
75
75
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
76
76
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
77
77
|
```
|
|
@@ -89,7 +89,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
89
89
|
3. Add cluster to `surfaceState.disabledClusters` (deduplicate).
|
|
90
90
|
4. `writeSurface` → resolve layout → `applySurface`:
|
|
91
91
|
```js
|
|
92
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
92
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
93
93
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
94
94
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
95
95
|
```
|
|
@@ -103,7 +103,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
103
103
|
2. Remove cluster from `surfaceState.disabledClusters`.
|
|
104
104
|
3. `writeSurface` → resolve layout → `applySurface`:
|
|
105
105
|
```js
|
|
106
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
106
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
107
107
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
108
108
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
109
109
|
```
|
|
@@ -151,7 +151,7 @@ All paths can be overridden by reading the `CLAUDE_CONFIG_DIR` env var if set.
|
|
|
151
151
|
|
|
152
152
|
- Unknown cluster name → list valid cluster names, exit without writing.
|
|
153
153
|
- Unknown profile name → list known profiles (`core`, `standard`, `full`), exit.
|
|
154
|
-
- Missing `surface.cjs` → prompt: "Run `npm i -g gsd-core` to reinstall GSD."
|
|
154
|
+
- Missing `surface.cjs` → prompt: "Run `npm i -g @opengsd/gsd-core` to reinstall GSD."
|
|
155
155
|
|
|
156
156
|
<execution_context>
|
|
157
157
|
Surface state file: `~/.claude/.gsd-surface.json`
|
package/vscode/package.json
CHANGED