@opengsd/gsd-core 1.5.0 → 1.6.0-rc.2
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/plugin.json +1 -1
- package/agents/gsd-plan-checker.md +34 -0
- package/agents/gsd-planner.md +2 -0
- package/agents/gsd-roadmapper.md +6 -0
- package/bin/install.js +199 -365
- package/commands/gsd/capture.md +5 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +695 -5
- package/gsd-core/bin/lib/adr-parser.cjs +45 -23
- package/gsd-core/bin/lib/audit.cjs +2 -2
- package/gsd-core/bin/lib/capability-consent.cjs +763 -0
- package/gsd-core/bin/lib/capability-ledger.cjs +831 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1551 -0
- package/gsd-core/bin/lib/capability-loader.cjs +764 -0
- package/gsd-core/bin/lib/capability-lock.cjs +553 -0
- package/gsd-core/bin/lib/capability-registry.cjs +198 -4
- package/gsd-core/bin/lib/capability-source.cjs +1242 -0
- package/gsd-core/bin/lib/capability-state.cjs +9 -6
- package/gsd-core/bin/lib/capability-trust.cjs +550 -0
- package/gsd-core/bin/lib/capability-validator.cjs +2066 -0
- package/gsd-core/bin/lib/capability-writer.cjs +14 -5
- package/gsd-core/bin/lib/check-command-router.cjs +69 -18
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/commands.cjs +247 -0
- package/gsd-core/bin/lib/config-loader.cjs +98 -84
- package/gsd-core/bin/lib/config-schema.cjs +26 -7
- package/gsd-core/bin/lib/config.cjs +7 -1
- package/gsd-core/bin/lib/decisions.cjs +149 -60
- package/gsd-core/bin/lib/frontmatter.cjs +7 -3
- package/gsd-core/bin/lib/gap-checker.cjs +126 -11
- package/gsd-core/bin/lib/init.cjs +91 -22
- package/gsd-core/bin/lib/legacy-cleanup.cjs +96 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +26 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +471 -0
- package/gsd-core/bin/lib/milestone.cjs +41 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +5 -0
- package/gsd-core/bin/lib/phase-id.cjs +25 -11
- package/gsd-core/bin/lib/phase-lifecycle.cjs +14 -5
- package/gsd-core/bin/lib/phase.cjs +33 -4
- package/gsd-core/bin/lib/probe-core.cjs +7 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +59 -26
- package/gsd-core/bin/lib/project-root.cjs +89 -2
- package/gsd-core/bin/lib/resolution.cjs +26 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +16 -3
- package/gsd-core/bin/lib/roadmap-parser.cjs +73 -106
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -17
- package/gsd-core/bin/lib/roadmap.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +423 -3
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +77 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -28
- package/gsd-core/bin/lib/runtime-homes.cjs +53 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +44 -0
- package/gsd-core/bin/lib/semver-compare.cjs +127 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +55 -1
- package/gsd-core/bin/lib/state-document.cjs +4 -2
- package/gsd-core/bin/lib/state.cjs +317 -161
- package/gsd-core/bin/lib/surface.cjs +12 -19
- package/gsd-core/bin/lib/uat-predicate.cjs +7 -47
- package/gsd-core/bin/lib/uat.cjs +39 -26
- package/gsd-core/bin/lib/validate.cjs +5 -2
- package/gsd-core/bin/lib/verify.cjs +40 -15
- package/gsd-core/bin/lib/worktree-safety.cjs +202 -0
- package/gsd-core/bin/shared/config-defaults.manifest.json +6 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -1
- package/gsd-core/references/context-budget.md +8 -8
- package/gsd-core/references/execute-phase-between-wave-reset.md +43 -0
- package/gsd-core/references/execute-phase-context-guard.md +16 -0
- package/gsd-core/references/execute-phase-wave-guard.md +33 -0
- package/gsd-core/references/planner-antipatterns.md +48 -0
- package/gsd-core/references/planning-config.md +4 -0
- package/gsd-core/references/prohibition-probe.md +15 -9
- package/gsd-core/references/scout-codebase.md +2 -2
- package/gsd-core/workflows/autonomous.md +33 -33
- package/gsd-core/workflows/diagnose-issues.md +6 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -2
- package/gsd-core/workflows/execute-phase.md +12 -12
- package/gsd-core/workflows/help/modes/full.md +10 -0
- package/gsd-core/workflows/list-seeds.md +63 -0
- package/gsd-core/workflows/manager.md +37 -37
- package/gsd-core/workflows/pr-branch.md +156 -0
- package/gsd-core/workflows/quick.md +6 -1
- package/gsd-core/workflows/review.md +10 -2
- package/gsd-core/workflows/spec-phase.md +8 -3
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/package.json +6 -3
- package/scripts/gen-capability-matrix.cjs +284 -0
- package/scripts/gen-capability-registry.cjs +96 -1853
- package/scripts/lint-regression-test-names.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.allowlist.json +1 -0
- package/scripts/lint-resolution-provenance.cjs +192 -0
- package/scripts/lint-test-file-count.allowlist.json +9 -0
- package/scripts/prompt-injection-scan.sh +1 -0
- package/scripts/run-tests.cjs +14 -0
- package/scripts/sync-manifest-versions.cjs +77 -5
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"bug-10-semver-policy-consolidation.test.cjs",
|
|
3
3
|
"bug-130-finishinstall-opencode-testmode.test.cjs",
|
|
4
4
|
"bug-131-release-tarball-smoke-explicit-home.test.cjs",
|
|
5
|
+
"bug-1367-claude-local-flat-command-layout.test.cjs",
|
|
5
6
|
"bug-14-progress-auto-flag-dropped.test.cjs",
|
|
6
7
|
"bug-167-query-meta-command.test.cjs",
|
|
7
8
|
"bug-17-askuserquestion-option-cap.test.cjs",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-resolution-provenance.cjs — CI guard for Resolution Provenance contracts.
|
|
6
|
+
*
|
|
7
|
+
* ## Purpose (ADR-1411 P4 / #1417)
|
|
8
|
+
*
|
|
9
|
+
* This is a REGRESSION-LOCK and REGISTRATION RATCHET, NOT a universal static
|
|
10
|
+
* detector (which is intractable given false positives from config-reading
|
|
11
|
+
* helpers that don't consume a `reason`).
|
|
12
|
+
*
|
|
13
|
+
* The guard maintains a REGISTRY of config-interpreting read verbs that MUST
|
|
14
|
+
* carry provenance — each entry names the verb, its source file, and its test
|
|
15
|
+
* file. For every registered verb, the guard asserts that its test file
|
|
16
|
+
* contains BOTH a `configured_empty` assertion AND a `not_configured`
|
|
17
|
+
* assertion, proving that the configured-empty-vs-not-configured contract is
|
|
18
|
+
* explicitly tested (not silently open-to-defaults).
|
|
19
|
+
*
|
|
20
|
+
* ## Registration protocol
|
|
21
|
+
*
|
|
22
|
+
* When adding a NEW config-interpreting read verb:
|
|
23
|
+
* 1. Add an entry to REGISTRY below: { verb, sourceFile, testFile }.
|
|
24
|
+
* 2. Add a `configured_empty` test and a `not_configured` test to testFile.
|
|
25
|
+
* 3. If the test coverage cannot land in the same PR, add the verb to
|
|
26
|
+
* scripts/lint-resolution-provenance.allowlist.json to grandfather it —
|
|
27
|
+
* but the allowlist MUST shrink over time (stale entries fail).
|
|
28
|
+
*
|
|
29
|
+
* See docs/adr/1411-resolution-provenance.md and #1417.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { assertWithinAllowlist } = require('./lib/allowlist-ratchet.cjs');
|
|
35
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
36
|
+
|
|
37
|
+
const ROOT = path.join(__dirname, '..');
|
|
38
|
+
const ALLOWLIST_PATH = path.join(__dirname, 'lint-resolution-provenance.allowlist.json');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Registry of config-interpreting read verbs that must carry provenance.
|
|
42
|
+
* Each entry: { verb, sourceFile, testFile }
|
|
43
|
+
*
|
|
44
|
+
* - verb: Short stable name for this verb (used in error messages and the
|
|
45
|
+
* allowlist).
|
|
46
|
+
* - sourceFile: Path (relative to ROOT) to the source implementation.
|
|
47
|
+
* - testFile: Path (relative to ROOT) to the test file that MUST contain
|
|
48
|
+
* both a `configured_empty` assertion and a `not_configured`
|
|
49
|
+
* assertion.
|
|
50
|
+
*
|
|
51
|
+
* Seed: agent-skills (P2/P3 fix, #1415/#1416) is the founding member.
|
|
52
|
+
*/
|
|
53
|
+
const REGISTRY = [
|
|
54
|
+
{
|
|
55
|
+
verb: 'agent-skills',
|
|
56
|
+
sourceFile: 'src/init.cts',
|
|
57
|
+
testFile: 'tests/agent-skills.test.cjs',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Markers that MUST appear in every registered verb's test file.
|
|
62
|
+
const MARKER_CONFIGURED_EMPTY = 'configured_empty';
|
|
63
|
+
const MARKER_NOT_CONFIGURED = 'not_configured';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pure check logic — factored out for unit testing without I/O.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} opts
|
|
69
|
+
* @param {Array<{verb: string, sourceFile: string, testFile: string}>} opts.registry
|
|
70
|
+
* The REGISTRY to check (or an injected subset for tests).
|
|
71
|
+
* @param {string[]} opts.allowlist
|
|
72
|
+
* Array of verb names to grandfather (stale entries fail).
|
|
73
|
+
* @param {function(string): string} opts.readFile
|
|
74
|
+
* Reads a file path and returns its content. Injected for testability;
|
|
75
|
+
* callers pass `(p) => fs.readFileSync(p, 'utf8')`.
|
|
76
|
+
* @param {function(string): void} opts.fail
|
|
77
|
+
* Callback invoked with a descriptive failure message.
|
|
78
|
+
* @returns {{ ok: boolean }}
|
|
79
|
+
*/
|
|
80
|
+
function checkRegistry({ registry, allowlist, readFile, fail }) {
|
|
81
|
+
const allowlistSet = new Set(allowlist);
|
|
82
|
+
const offenders = []; // verbs that ARE failing (for ratchet: stale check)
|
|
83
|
+
let anyFail = false;
|
|
84
|
+
|
|
85
|
+
for (const entry of registry) {
|
|
86
|
+
const { verb, testFile } = entry;
|
|
87
|
+
const resolvedTestFile = path.isAbsolute(testFile) ? testFile : path.join(ROOT, testFile);
|
|
88
|
+
|
|
89
|
+
// Grandfathered? Check markers anyway to detect when it's been fixed.
|
|
90
|
+
let content;
|
|
91
|
+
try {
|
|
92
|
+
content = readFile(resolvedTestFile);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
fail(
|
|
95
|
+
`[resolution-provenance] Cannot read test file for verb "${verb}" (${testFile}): ${err.message}\n` +
|
|
96
|
+
` Register the verb's test file correctly, or remove the registry entry.`
|
|
97
|
+
);
|
|
98
|
+
anyFail = true;
|
|
99
|
+
offenders.push(verb);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const hasConfiguredEmpty = content.includes(MARKER_CONFIGURED_EMPTY);
|
|
104
|
+
const hasNotConfigured = content.includes(MARKER_NOT_CONFIGURED);
|
|
105
|
+
|
|
106
|
+
if (!hasConfiguredEmpty || !hasNotConfigured) {
|
|
107
|
+
offenders.push(verb);
|
|
108
|
+
|
|
109
|
+
if (allowlistSet.has(verb)) {
|
|
110
|
+
// Grandfathered — tolerate but don't report.
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const missing = [];
|
|
115
|
+
if (!hasConfiguredEmpty) missing.push(`\`configured_empty\``);
|
|
116
|
+
if (!hasNotConfigured) missing.push(`\`not_configured\``);
|
|
117
|
+
|
|
118
|
+
fail(
|
|
119
|
+
`[resolution-provenance] verb "${verb}" (${testFile}) is missing contract test marker(s):\n` +
|
|
120
|
+
` Missing: ${missing.join(', ')}\n` +
|
|
121
|
+
` Each registered config-interpreting read verb must have both a\n` +
|
|
122
|
+
` \`configured_empty\` assertion and a \`not_configured\` assertion in its\n` +
|
|
123
|
+
` test file to prove the configured-empty-vs-not-configured contract is\n` +
|
|
124
|
+
` tested (ADR-1411 P4 / #1417).\n` +
|
|
125
|
+
` Add the missing test(s) or grandfather the verb in\n` +
|
|
126
|
+
` scripts/lint-resolution-provenance.allowlist.json.`
|
|
127
|
+
);
|
|
128
|
+
anyFail = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Ratchet: stale allowlist entries (verb is compliant but still grandfathered)
|
|
133
|
+
// must be pruned so the allowlist only ever shrinks.
|
|
134
|
+
const offenderSet = new Set(offenders);
|
|
135
|
+
const staleEntries = [];
|
|
136
|
+
for (const v of allowlistSet) {
|
|
137
|
+
if (!offenderSet.has(v)) {
|
|
138
|
+
staleEntries.push(v);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Also verify stale entries via assertWithinAllowlist for consistent messaging.
|
|
143
|
+
const ratchetFailures = [];
|
|
144
|
+
assertWithinAllowlist({
|
|
145
|
+
label: 'resolution-provenance',
|
|
146
|
+
current: offenders,
|
|
147
|
+
known: allowlist,
|
|
148
|
+
fail: (msg) => ratchetFailures.push(msg),
|
|
149
|
+
pruneHint: 'edit scripts/lint-resolution-provenance.allowlist.json',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Only report stale entries from the ratchet (novel offenders are already
|
|
153
|
+
// reported above with more actionable messages).
|
|
154
|
+
if (staleEntries.length > 0) {
|
|
155
|
+
for (const msg of ratchetFailures) {
|
|
156
|
+
// Only surface the stale-entry message (it contains "stale" or "no longer").
|
|
157
|
+
if (msg.includes('stale') || msg.includes('no longer')) {
|
|
158
|
+
fail(msg);
|
|
159
|
+
anyFail = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { ok: !anyFail };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function main() {
|
|
168
|
+
const allowlist = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
|
|
169
|
+
|
|
170
|
+
const failures = [];
|
|
171
|
+
const { ok } = checkRegistry({
|
|
172
|
+
registry: REGISTRY,
|
|
173
|
+
allowlist,
|
|
174
|
+
readFile: (filePath) => fs.readFileSync(filePath, 'utf8'),
|
|
175
|
+
fail: (msg) => failures.push(msg),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!ok) {
|
|
179
|
+
for (const msg of failures) process.stderr.write(`${msg}\n`);
|
|
180
|
+
throw new ExitError(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(
|
|
184
|
+
`ok lint-resolution-provenance: ${REGISTRY.length} registered verb(s), all carry configured_empty + not_configured contract tests`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = { checkRegistry, REGISTRY };
|
|
189
|
+
|
|
190
|
+
// Only run the CLI check when executed directly, not when imported by tests
|
|
191
|
+
// (keeps the unit tests hermetic — importing checkRegistry must not run main).
|
|
192
|
+
if (require.main === module) runMain(main);
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"phase": {
|
|
62
62
|
"files": [
|
|
63
|
+
"fix-1437-phase-list-plans.test.cjs",
|
|
63
64
|
"bug-214-phase-researcher-write-truncation-contract.test.cjs",
|
|
64
65
|
"phase-dependency-levels.test.cjs",
|
|
65
66
|
"phase.test.cjs"
|
|
@@ -174,6 +175,14 @@
|
|
|
174
175
|
"external-job-waiting.test.cjs"
|
|
175
176
|
],
|
|
176
177
|
"issue": "#1165"
|
|
178
|
+
},
|
|
179
|
+
"docs": {
|
|
180
|
+
"files": [
|
|
181
|
+
"docs-parity-live-registry.test.cjs",
|
|
182
|
+
"docs-update.test.cjs",
|
|
183
|
+
"fix-1464-docs-manifest-validation.test.cjs"
|
|
184
|
+
],
|
|
185
|
+
"issue": "1496"
|
|
177
186
|
}
|
|
178
187
|
}
|
|
179
188
|
}
|
|
@@ -78,6 +78,7 @@ ALLOWLIST=(
|
|
|
78
78
|
'hooks/gsd-read-injection-scanner.js'
|
|
79
79
|
'tests/read-injection-scanner.security.test.cjs'
|
|
80
80
|
'tests/security-prompt-injection.security.test.cjs'
|
|
81
|
+
'tests/list-seeds.test.cjs'
|
|
81
82
|
'tests/fixtures/adversarial/security/'
|
|
82
83
|
'SECURITY.md'
|
|
83
84
|
# These files contain intentional injection examples / security-model prose
|
package/scripts/run-tests.cjs
CHANGED
|
@@ -462,6 +462,20 @@ function main() {
|
|
|
462
462
|
delete process.env.GSD_PROJECT;
|
|
463
463
|
delete process.env.GSD_WORKSTREAM;
|
|
464
464
|
delete process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
465
|
+
// Sandbox the overlay home so the loader's global scan ($GSD_HOME/.gsd/capabilities)
|
|
466
|
+
// cannot read a developer's real installed capabilities during tests (ADR-1244 D2).
|
|
467
|
+
// IDEMPOTENT: a nested run-tests spawn (e.g. tests/run-tests-harness.test.cjs)
|
|
468
|
+
// inherits this sandbox via env — it must REUSE it, never mkdtemp a fresh dir per
|
|
469
|
+
// invocation (that churned ~20+ temp dirs per harness run and amplified Docker load).
|
|
470
|
+
{
|
|
471
|
+
const { mkdtempSync } = require('fs');
|
|
472
|
+
const { join: _join, basename: _basename } = require('path');
|
|
473
|
+
const { tmpdir } = require('os');
|
|
474
|
+
const _gh = process.env.GSD_HOME;
|
|
475
|
+
if (!_gh || !_basename(_gh).startsWith('gsd-test-home-')) {
|
|
476
|
+
process.env.GSD_HOME = mkdtempSync(_join(tmpdir(), 'gsd-test-home-'));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
465
479
|
|
|
466
480
|
// Log selected files to stderr for CI / harness-test visibility.
|
|
467
481
|
// node:test default reporter doesn't echo filenames, so this gives
|
|
@@ -68,6 +68,66 @@ function findDrift(opts) {
|
|
|
68
68
|
return drift;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// ─── ADR-1244 D6: native capability manifests ────────────────────────────────
|
|
72
|
+
//
|
|
73
|
+
// Native capabilities (capabilities/<id>/capability.json) carry a `version`
|
|
74
|
+
// stamped in lockstep with the package version at release. Unlike
|
|
75
|
+
// VERSIONED_MANIFESTS (fixed paths), capabilities are discovered by glob so a
|
|
76
|
+
// new capability is auto-covered without editing this file. The version-sync
|
|
77
|
+
// regression guard (issue #844) treats every swept capability manifest as
|
|
78
|
+
// registered.
|
|
79
|
+
|
|
80
|
+
// Discover capabilities/<id>/capability.json under `root`, sorted for stable
|
|
81
|
+
// staging order. Returns [] when there is no capabilities/ directory.
|
|
82
|
+
function listCapabilityManifests(opts) {
|
|
83
|
+
const root = (opts && opts.root) || ROOT;
|
|
84
|
+
const dir = path.join(root, 'capabilities');
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
88
|
+
} catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return entries
|
|
92
|
+
.filter((e) => e.isDirectory())
|
|
93
|
+
// Forward-slash rel paths (NOT path.join) so they match `git ls-files`
|
|
94
|
+
// output, git pathspecs, and the forward-slash VERSIONED_MANIFESTS on every
|
|
95
|
+
// platform — path.join would emit backslashes on Windows and break the
|
|
96
|
+
// issue-844 regression guard's ALLOWED-set comparison.
|
|
97
|
+
.map((e) => 'capabilities/' + e.name + '/capability.json')
|
|
98
|
+
.filter((rel) => fs.existsSync(path.join(root, rel)))
|
|
99
|
+
.sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Stamp `version` into each native capability manifest. Returns changed rel paths.
|
|
103
|
+
function syncCapabilityVersions(opts) {
|
|
104
|
+
const root = (opts && opts.root) || ROOT;
|
|
105
|
+
const v = (opts && opts.version) != null ? opts.version : getPackageVersion(root);
|
|
106
|
+
const changed = [];
|
|
107
|
+
for (const rel of listCapabilityManifests({ root })) {
|
|
108
|
+
const abs = path.join(root, rel);
|
|
109
|
+
const manifest = readJson(abs);
|
|
110
|
+
if (manifest.version !== v) {
|
|
111
|
+
manifest.version = v;
|
|
112
|
+
fs.writeFileSync(abs, JSON.stringify(manifest, null, 2) + '\n');
|
|
113
|
+
changed.push(rel);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return changed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Native capability manifests whose version != package version.
|
|
120
|
+
function findCapabilityDrift(opts) {
|
|
121
|
+
const root = (opts && opts.root) || ROOT;
|
|
122
|
+
const v = (opts && opts.version) != null ? opts.version : getPackageVersion(root);
|
|
123
|
+
const drift = [];
|
|
124
|
+
for (const rel of listCapabilityManifests({ root })) {
|
|
125
|
+
const found = readJson(path.join(root, rel)).version;
|
|
126
|
+
if (found !== v) drift.push({ manifest: rel, found, expected: v });
|
|
127
|
+
}
|
|
128
|
+
return drift;
|
|
129
|
+
}
|
|
130
|
+
|
|
71
131
|
// Best-effort outside git; fail-closed inside a work tree so a release never
|
|
72
132
|
// ships a stale manifest that the working-tree test already accepted.
|
|
73
133
|
function stageManifests(opts) {
|
|
@@ -83,21 +143,32 @@ function stageManifests(opts) {
|
|
|
83
143
|
console.warn('sync-manifest-versions: not a git work tree; skipping staging.');
|
|
84
144
|
return;
|
|
85
145
|
}
|
|
146
|
+
const toStage = [...VERSIONED_MANIFESTS, ...listCapabilityManifests({ root })];
|
|
86
147
|
try {
|
|
87
|
-
execFileSync('git', ['add', '--', ...
|
|
148
|
+
execFileSync('git', ['add', '--', ...toStage], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] });
|
|
88
149
|
} catch (err) {
|
|
89
150
|
const detail = err && err.stderr ? err.stderr.toString().trim() : (err && err.message) || 'unknown error';
|
|
90
151
|
throw new Error(`sync-manifest-versions: failed to git-add manifests inside a work tree: ${detail}`);
|
|
91
152
|
}
|
|
92
153
|
}
|
|
93
154
|
|
|
94
|
-
module.exports = {
|
|
155
|
+
module.exports = {
|
|
156
|
+
VERSIONED_MANIFESTS,
|
|
157
|
+
syncManifestVersions,
|
|
158
|
+
findDrift,
|
|
159
|
+
getPackageVersion,
|
|
160
|
+
stageManifests,
|
|
161
|
+
// ADR-1244 D6: native capability version sweep
|
|
162
|
+
listCapabilityManifests,
|
|
163
|
+
syncCapabilityVersions,
|
|
164
|
+
findCapabilityDrift,
|
|
165
|
+
};
|
|
95
166
|
|
|
96
167
|
if (require.main === module) {
|
|
97
168
|
const args = process.argv.slice(2);
|
|
98
169
|
const version = getPackageVersion();
|
|
99
170
|
if (args.includes('--check')) {
|
|
100
|
-
const drift = findDrift({ version });
|
|
171
|
+
const drift = [...findDrift({ version }), ...findCapabilityDrift({ version })];
|
|
101
172
|
if (drift.length) {
|
|
102
173
|
for (const d of drift) {
|
|
103
174
|
console.error('Manifest ' + d.manifest + ' version ' + d.found + ' != package.json ' + d.expected);
|
|
@@ -105,10 +176,11 @@ if (require.main === module) {
|
|
|
105
176
|
console.error('Run `node scripts/sync-manifest-versions.cjs` to fix.');
|
|
106
177
|
process.exitCode = 1;
|
|
107
178
|
} else {
|
|
108
|
-
|
|
179
|
+
const total = VERSIONED_MANIFESTS.length + listCapabilityManifests().length;
|
|
180
|
+
console.log('All ' + total + ' versioned manifests in sync at ' + version + '.');
|
|
109
181
|
}
|
|
110
182
|
} else {
|
|
111
|
-
const changed = syncManifestVersions({ version });
|
|
183
|
+
const changed = [...syncManifestVersions({ version }), ...syncCapabilityVersions({ version })];
|
|
112
184
|
if (changed.length) {
|
|
113
185
|
console.log('Stamped ' + version + ' into: ' + changed.join(', '));
|
|
114
186
|
} else {
|