@opengsd/gsd-core 1.8.0 → 1.9.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/.opencode/plugins/gsd-core.js +31 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +36 -0
- package/agents/gsd-executor.md +20 -7
- package/agents/gsd-intel-updater.md +3 -3
- package/agents/gsd-phase-researcher.md +4 -2
- package/agents/gsd-plan-checker.md +20 -0
- package/agents/gsd-planner.md +15 -23
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/bin/install.js +186 -55
- package/commands/gsd/plan-review-convergence.md +5 -1
- package/gsd-core/bin/gsd-tools.cjs +849 -2
- package/gsd-core/bin/lib/api-coverage.cjs +22 -8
- package/gsd-core/bin/lib/audit.cjs +8 -8
- package/gsd-core/bin/lib/capability-consent.cjs +40 -1
- package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
- package/gsd-core/bin/lib/capability-loader.cjs +23 -1
- package/gsd-core/bin/lib/capability-registry.cjs +1353 -132
- package/gsd-core/bin/lib/capability-trust.cjs +468 -33
- package/gsd-core/bin/lib/capability-validator.cjs +882 -6
- package/gsd-core/bin/lib/check-command-router.cjs +12 -2
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +102 -12
- package/gsd-core/bin/lib/claude-orchestration.cjs +125 -22
- package/gsd-core/bin/lib/commands.cjs +246 -18
- package/gsd-core/bin/lib/config-loader.cjs +200 -28
- package/gsd-core/bin/lib/config.cjs +90 -5
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +44 -19
- package/gsd-core/bin/lib/install-engine.cjs +1 -0
- package/gsd-core/bin/lib/milestone.cjs +5 -5
- package/gsd-core/bin/lib/model-catalog.cjs +51 -1
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
- package/gsd-core/bin/lib/phase-id.cjs +278 -5
- package/gsd-core/bin/lib/phase.cjs +57 -5
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +1 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
- package/gsd-core/bin/lib/profile-output.cjs +34 -8
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
- package/gsd-core/bin/lib/roadmap-parser.cjs +54 -6
- package/gsd-core/bin/lib/roadmap.cjs +10 -4
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +31 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +140 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +1 -1
- package/gsd-core/bin/lib/state-document.cjs +164 -20
- package/gsd-core/bin/lib/state-transition.cjs +28 -10
- package/gsd-core/bin/lib/state.cjs +141 -21
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +9 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
- package/gsd-core/bin/lib/unusable-input.cjs +216 -0
- package/gsd-core/bin/lib/validate.cjs +32 -0
- package/gsd-core/bin/lib/verification.cjs +51 -14
- package/gsd-core/bin/lib/verify.cjs +128 -20
- package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -13
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
- package/gsd-core/references/context-budget.md +40 -0
- package/gsd-core/references/gate-prompts.md +6 -3
- package/gsd-core/references/model-profile-resolution.md +64 -13
- package/gsd-core/references/offer-next.md +88 -0
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -21
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- package/gsd-core/templates/summary-minimal.md +4 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +7 -0
- package/gsd-core/workflows/ai-integration-phase.md +4 -4
- package/gsd-core/workflows/audit-fix.md +4 -0
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +76 -19
- package/gsd-core/workflows/debug.md +10 -2
- package/gsd-core/workflows/diagnose-issues.md +4 -0
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
- package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
- package/gsd-core/workflows/discuss-phase-assumptions.md +15 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/docs-update.md +8 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
- package/gsd-core/workflows/execute-phase.md +85 -115
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/help/modes/full.md +3 -3
- package/gsd-core/workflows/import.md +4 -1
- package/gsd-core/workflows/ingest-docs.md +4 -0
- package/gsd-core/workflows/map-codebase.md +13 -6
- package/gsd-core/workflows/new-milestone.md +10 -2
- package/gsd-core/workflows/new-project.md +11 -4
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/plan-phase.md +42 -46
- package/gsd-core/workflows/plan-review-convergence.md +18 -14
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +14 -3
- package/gsd-core/workflows/review.md +146 -575
- package/gsd-core/workflows/scan.md +9 -1
- package/gsd-core/workflows/secure-phase.md +10 -2
- package/gsd-core/workflows/ship.md +41 -11
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/ui-phase.md +8 -1
- package/gsd-core/workflows/ui-review.md +8 -1
- package/gsd-core/workflows/update.md +104 -5
- package/gsd-core/workflows/validate-phase.md +10 -2
- package/gsd-core/workflows/verify-work.md +8 -1
- package/hooks/dist/gsd-cursor-session-start.js +6 -2
- package/hooks/dist/gsd-cursor-stop.js +6 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +9 -0
- package/hooks/dist/gsd-phase-boundary.sh +14 -2
- package/hooks/dist/gsd-prompt-guard.js +101 -2
- package/hooks/dist/gsd-read-guard.js +100 -2
- package/hooks/dist/gsd-read-injection-scanner.js +109 -2
- package/hooks/dist/gsd-statusline.js +9 -6
- package/hooks/dist/gsd-workflow-guard.js +110 -6
- package/hooks/dist/gsd-worktree-path-guard.js +132 -8
- package/hooks/dist/lib/cursor-workspace.js +74 -0
- package/hooks/gsd-cursor-session-start.js +6 -2
- package/hooks/gsd-cursor-stop.js +6 -2
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +9 -0
- package/hooks/gsd-phase-boundary.sh +14 -2
- package/hooks/gsd-prompt-guard.js +101 -2
- package/hooks/gsd-read-guard.js +100 -2
- package/hooks/gsd-read-injection-scanner.js +109 -2
- package/hooks/gsd-statusline.js +9 -6
- package/hooks/gsd-workflow-guard.js +110 -6
- package/hooks/gsd-worktree-path-guard.js +132 -8
- package/hooks/lib/cursor-workspace.js +74 -0
- package/package.json +7 -7
- package/pi/gsd.cjs +26 -1
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +24 -0
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +6 -4
- package/scripts/gen-capability-matrix.cjs +26 -2
- package/scripts/gen-capability-registry.cjs +132 -34
- package/scripts/gen-emitted-baseline.cjs +145 -0
- package/scripts/lint-compiled-artifact-sync.cjs +146 -0
- package/scripts/lint-emitted-drift-ack.cjs +149 -0
- package/scripts/lint-fix-has-regression-test.cjs +131 -0
- package/scripts/lint-resolution-provenance.cjs +9 -0
- package/scripts/mutation-matrix.cjs +4 -0
- package/scripts/prompt-injection-scan.sh +6 -0
- package/scripts/registry-schema.cjs +57 -8
- package/scripts/release-notes/conventional-title.cjs +19 -1
- package/scripts/release-notes/format-github-release-notes.cjs +7 -3
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
package/pi/gsd.cjs
CHANGED
|
@@ -197,10 +197,35 @@ function buildBeforeProviderRequestHandler({ tier = 'sonnet' } = {}) {
|
|
|
197
197
|
return async function onBeforeProviderRequest(event, ctx) {
|
|
198
198
|
try {
|
|
199
199
|
const effectiveCwd = (ctx && ctx.cwd) || process.cwd();
|
|
200
|
-
const { resolveTierEntry } = require(path.join(GSD_CORE, 'bin', 'lib', 'model-resolver.cjs'));
|
|
201
200
|
const { loadConfig } = require(path.join(GSD_CORE, 'bin', 'lib', 'config-loader.cjs'));
|
|
202
201
|
const config = loadConfig(effectiveCwd);
|
|
203
202
|
const overrides = (config && config.model_profile_overrides) || undefined;
|
|
203
|
+
|
|
204
|
+
// #2460: FAIL-OPEN when the user has not explicitly configured
|
|
205
|
+
// model_profile_overrides[runtime][tier]. pi is provider-agnostic
|
|
206
|
+
// (kimi-coding, zai, openrouter, openai-codex, minimax, anthropic, …);
|
|
207
|
+
// the built-in RUNTIME_PROFILE_MAP.pi.sonnet default is `claude-sonnet-5`,
|
|
208
|
+
// an Anthropic-ecosystem assumption that is wrong for every non-Anthropic
|
|
209
|
+
// provider. Returning the built-in default unconditionally rewrote every
|
|
210
|
+
// outgoing request to a model the active provider did not know.
|
|
211
|
+
//
|
|
212
|
+
// resolveTierEntry falls back to the built-in catalog when no override is
|
|
213
|
+
// present, so we cannot call it to detect "did the user opt in?" — we
|
|
214
|
+
// inspect the override map directly. Only when the user has explicitly
|
|
215
|
+
// set model_profile_overrides[runtime][tier] do we steer; otherwise we
|
|
216
|
+
// return undefined and pi's chosen model flows through untouched.
|
|
217
|
+
const userRaw = overrides && overrides.pi ? overrides.pi[tier] : undefined;
|
|
218
|
+
// `''` is treated the same as null/undefined: an explicit empty-string
|
|
219
|
+
// override is the user clearing a previously-set value, NOT opting in to
|
|
220
|
+
// steering. Without this guard, `resolveTierEntry`'s falsy `if (userRaw)`
|
|
221
|
+
// check would silently fall back to the built-in catalog and rewrite
|
|
222
|
+
// payload.model to claude-sonnet-5 — re-introducing the exact bug this
|
|
223
|
+
// handler exists to prevent.
|
|
224
|
+
if (userRaw === undefined || userRaw === null || userRaw === '') {
|
|
225
|
+
return undefined; // fail-open — leave pi's model untouched
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { resolveTierEntry } = require(path.join(GSD_CORE, 'bin', 'lib', 'model-resolver.cjs'));
|
|
204
229
|
const entry = resolveTierEntry({ runtime: 'pi', tier, overrides });
|
|
205
230
|
const modelId = entry && typeof entry.model === 'string' && entry.model.length > 0 ? entry.model : null;
|
|
206
231
|
if (!modelId) return undefined; // fail-open — leave pi's model untouched
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const summaryPath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
|
|
8
|
+
if (!fs.existsSync(summaryPath)) {
|
|
9
|
+
console.error('check-coverage-gate: coverage/coverage-summary.json not found.');
|
|
10
|
+
console.error('Ensure c8 runs with --reporter json-summary before this script.');
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
|
|
16
|
+
const errors = [];
|
|
17
|
+
|
|
18
|
+
const OVERALL_LINES = 70;
|
|
19
|
+
const OVERALL_BRANCHES = 60;
|
|
20
|
+
const PER_FILE_BRANCHES = 70;
|
|
21
|
+
const PER_FILE_FILES = [
|
|
22
|
+
'gsd-core/bin/lib/state.cjs',
|
|
23
|
+
'gsd-core/bin/lib/phase.cjs',
|
|
24
|
+
'gsd-core/bin/lib/verify.cjs',
|
|
25
|
+
'gsd-core/bin/lib/init.cjs',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const overall = summary.total;
|
|
29
|
+
if (overall.lines.pct < OVERALL_LINES) {
|
|
30
|
+
errors.push(`Overall lines ${overall.lines.pct}% < ${OVERALL_LINES}%`);
|
|
31
|
+
}
|
|
32
|
+
if (overall.branches.pct < OVERALL_BRANCHES) {
|
|
33
|
+
errors.push(`Overall branches ${overall.branches.pct}% < ${OVERALL_BRANCHES}%`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const file of PER_FILE_FILES) {
|
|
37
|
+
const key = Object.keys(summary).find((k) => k.endsWith(file));
|
|
38
|
+
if (!key) {
|
|
39
|
+
errors.push(`${file}: not found in coverage summary`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const pct = summary[key].branches.pct;
|
|
43
|
+
if (pct < PER_FILE_BRANCHES) {
|
|
44
|
+
errors.push(`${file}: branches ${pct}% < ${PER_FILE_BRANCHES}%`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (errors.length > 0) {
|
|
49
|
+
for (const e of errors) console.error(`ERROR: ${e}`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
@@ -64,6 +64,29 @@ const TRACKED_PREFIXES = [
|
|
|
64
64
|
/** The only bare (no-prefix-match) tokens this gate checks by exact name. */
|
|
65
65
|
const TRACKED_EXACT = new Set(['bin/install.js', 'package.json']);
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Paths CONTEXT.md documents whose ABSENCE is the healthy steady state (#2778).
|
|
69
|
+
*
|
|
70
|
+
* `TRACKED_PREFIXES` skips claims this gate *cannot* check. This is the narrower
|
|
71
|
+
* third case: a claim it must not check, because "does not exist" is the correct
|
|
72
|
+
* state rather than drift.
|
|
73
|
+
*
|
|
74
|
+
* `tests/emitted-drift-ack.json` is the acknowledgment file from ADR-2719 §3. Its
|
|
75
|
+
* whole design property is that it appears in the changed-files list ONLY when
|
|
76
|
+
* something rippled unexpectedly — "touching it IS the alarm". A permanently
|
|
77
|
+
* committed copy would signal nothing, which is exactly why the ADR rejected
|
|
78
|
+
* shipping an empty stub. So the file is absent on a healthy `next` and present
|
|
79
|
+
* only inside a PR that needs it, and asserting either way is wrong.
|
|
80
|
+
*
|
|
81
|
+
* Until #2778 this passed only by accident: CONTEXT.md's `RULESET.` entries are
|
|
82
|
+
* themselves backtick-wrapped and contain backticks, so the sequential pairing in
|
|
83
|
+
* `extractTrackedRefs` happened to leave this token outside a code span. Any edit
|
|
84
|
+
* that shifted the parity — such as #2778's own — exposed it. A gate that passes
|
|
85
|
+
* by luck is not passing; naming the exemption makes the intent explicit and
|
|
86
|
+
* survives the next edit.
|
|
87
|
+
*/
|
|
88
|
+
const INTENTIONALLY_ABSENT = new Set(['tests/emitted-drift-ack.json']);
|
|
89
|
+
|
|
67
90
|
/**
|
|
68
91
|
* Shape a backticked token must have to even be considered a path candidate:
|
|
69
92
|
* one or more `/`-separated segments of word/dot/dash characters, with an
|
|
@@ -75,6 +98,7 @@ const PATH_TOKEN_RE = /^[\w.-]+(?:\/[\w.-]+)*(?::\d+)?$/;
|
|
|
75
98
|
|
|
76
99
|
/** Whether `token` (line-suffix already stripped) is one this gate checks. */
|
|
77
100
|
function isTracked(token) {
|
|
101
|
+
if (INTENTIONALLY_ABSENT.has(token)) return false;
|
|
78
102
|
return TRACKED_EXACT.has(token) || TRACKED_PREFIXES.some((prefix) => token.startsWith(prefix));
|
|
79
103
|
}
|
|
80
104
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const { execFileSync } = require('child_process');
|
|
5
6
|
const { existsSync, readdirSync, appendFileSync } = require('fs');
|
|
6
7
|
|
|
@@ -105,9 +106,8 @@ const RULES = [
|
|
|
105
106
|
fullMatrix: true,
|
|
106
107
|
tests: [
|
|
107
108
|
'tests/check-env.test.cjs',
|
|
108
|
-
'tests/npm-integrity-gate.test.cjs',
|
|
109
|
+
'tests/npm-integrity-gate.test.cjs', // #2758: absorbs the former tests/bug-3588-npm-audit-clean.test.cjs (folded into it by consolidation epic #1969 B6 #1975; the stale filename here was a silent coverage hole this rule never actually re-selected)
|
|
109
110
|
'tests/package-manifest.test.cjs',
|
|
110
|
-
'tests/bug-3588-npm-audit-clean.test.cjs',
|
|
111
111
|
],
|
|
112
112
|
},
|
|
113
113
|
{
|
|
@@ -116,9 +116,9 @@ const RULES = [
|
|
|
116
116
|
// still trigger the migrated module's tests (otherwise CI silently skips them).
|
|
117
117
|
match: path => path.startsWith('src/') || path === 'tsconfig.build.json',
|
|
118
118
|
tests: [
|
|
119
|
-
'tests/semver-compare.test.cjs',
|
|
120
|
-
'tests/
|
|
121
|
-
'tests/
|
|
119
|
+
'tests/semver-compare.test.cjs', // #2758: absorbs the former tests/bug-10-semver-policy-consolidation.test.cjs (folded into it by consolidation epic #1969 B3 #1972; the stale filename here was a silent coverage hole this rule never actually re-selected)
|
|
120
|
+
'tests/emitted-provenance.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify provenance totality (#2724: golden-install-parity retired, this is the sole gate)
|
|
121
|
+
'tests/emitted-attribution.test.cjs',
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -142,19 +142,23 @@ const RULES = [
|
|
|
142
142
|
// SCOPED_LANE_EXCLUDE guard below, which also drops it when it is itself a
|
|
143
143
|
// changed test file.
|
|
144
144
|
'tests/runtime-artifact-layout.test.cjs',
|
|
145
|
-
'tests/
|
|
145
|
+
'tests/emitted-provenance.test.cjs', // any src/installer change can alter emitted install artifacts → re-verify provenance totality (#2724: golden-install-parity retired, this is the sole gate)
|
|
146
|
+
'tests/emitted-attribution.test.cjs',
|
|
146
147
|
],
|
|
147
148
|
},
|
|
148
149
|
{
|
|
149
|
-
name: 'shipped install content (
|
|
150
|
+
name: 'shipped install content (emitted-attribution drift guard, #2267/#2724)',
|
|
150
151
|
// Every source file the installer EMITS into a runtime layout is captured by
|
|
151
|
-
//
|
|
152
|
-
// changes emitted output MUST re-verify
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
152
|
+
// the emitted-attribution differential + the install-tree snapshot. A source
|
|
153
|
+
// edit here that changes emitted output MUST re-verify (#2266: a
|
|
154
|
+
// hooks/gsd-statusline.js edit changed installed output but no rule selected
|
|
155
|
+
// the drift guard, so a stale emitted state shipped to next undetected).
|
|
156
|
+
// Union semantics: this ADDS the drift guard on top of each path's existing
|
|
157
|
+
// content-specific tests. Targeted lane only (the real-tree test skips win32
|
|
158
|
+
// by design), no fullMatrix.
|
|
159
|
+
// #2724: golden-install-parity.test.cjs is retired (ADR-2719 Phase 4); the
|
|
160
|
+
// emitted differential (ADR-2719 Phase 2/3) is now the sole gate for a PR
|
|
161
|
+
// editing only shipped content, the archetypal emitted-ripple case.
|
|
158
162
|
// NOTE: intentionally NOT a blanket 'gsd-core/' prefix, for two reasons:
|
|
159
163
|
// (1) gsd-core/bin/** is tsc-compiled runtime output — EXCLUDED_PREFIXES-
|
|
160
164
|
// excluded from both manifests, and already covered by the 'installer and
|
|
@@ -172,8 +176,9 @@ const RULES = [
|
|
|
172
176
|
(path.startsWith('gsd-core/bin/shared/') && path.endsWith('.json')) ||
|
|
173
177
|
['scripts/fix-slash-commands.cjs', 'scripts/gen-capability-registry.cjs', 'scripts/gen-loop-host-contract.cjs'].includes(path),
|
|
174
178
|
tests: [
|
|
175
|
-
'tests/golden-install-parity.test.cjs',
|
|
176
179
|
'tests/golden-install-tree.test.cjs',
|
|
180
|
+
'tests/emitted-provenance.test.cjs',
|
|
181
|
+
'tests/emitted-attribution.test.cjs',
|
|
177
182
|
],
|
|
178
183
|
},
|
|
179
184
|
{
|
|
@@ -236,7 +241,13 @@ const RULES = [
|
|
|
236
241
|
'tests/workflow-size-budget.test.cjs',
|
|
237
242
|
'tests/workflow-guard-registration.test.cjs',
|
|
238
243
|
'tests/commands.test.cjs',
|
|
239
|
-
'tests/bug-3683-workflow-colon-namespace-leak.test.cjs',
|
|
244
|
+
// #2758: was 'tests/bug-3683-workflow-colon-namespace-leak.test.cjs', deleted by
|
|
245
|
+
// consolidation epic #1969 (B6 #1975) and folded into slash-command-namespace.test.cjs
|
|
246
|
+
// ("folded:bug-3683-workflow-colon-namespace-leak" describe block). The stale filename
|
|
247
|
+
// here was itself an instance of this issue's defect class — silently dropped by
|
|
248
|
+
// existingTests() below, so gsd-core/workflows/ changes stopped re-running this
|
|
249
|
+
// regression's coverage with nothing signaling it.
|
|
250
|
+
'tests/slash-command-namespace.test.cjs',
|
|
240
251
|
],
|
|
241
252
|
},
|
|
242
253
|
{
|
|
@@ -291,6 +302,41 @@ const RULES = [
|
|
|
291
302
|
},
|
|
292
303
|
];
|
|
293
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Every RULES[].tests entry (deduped, across every rule) that does NOT exist on
|
|
307
|
+
* disk. #2758: a rule naming a test file that no longer exists is not merely
|
|
308
|
+
* inert — existingTests() below silently drops it out of targeted_tests, with
|
|
309
|
+
* nothing in the CI output signaling why. Phase 4 (#2724) deletes
|
|
310
|
+
* tests/golden-install-parity.test.cjs; without this check, any rule still
|
|
311
|
+
* naming it would stop selecting the guard entirely and CI would stay green
|
|
312
|
+
* throughout. Pure and independent of which rule / which file: it catches ANY
|
|
313
|
+
* phantom entry, not only the two names this issue is about.
|
|
314
|
+
* Paths resolve relative to the repo root (this file's parent directory), not
|
|
315
|
+
* the caller's cwd, so the check behaves identically whether invoked as the CLI
|
|
316
|
+
* (`node scripts/ci-test-scope.cjs ...`, cwd == repo root by convention) or
|
|
317
|
+
* required directly by a test.
|
|
318
|
+
*/
|
|
319
|
+
function missingRuleTestFiles(rules) {
|
|
320
|
+
const referenced = new Set();
|
|
321
|
+
for (const rule of rules) {
|
|
322
|
+
for (const f of rule.tests) referenced.add(f);
|
|
323
|
+
}
|
|
324
|
+
return [...referenced].filter(f => !existsSync(path.join(__dirname, '..', f))).sort();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Fail loudly at module load, mirroring the PROTECTED_WORKFLOWS check above —
|
|
328
|
+
// this fires on EVERY invocation of the CLI (including the real `changes` job
|
|
329
|
+
// in .github/workflows/test.yml), not only when a test suite happens to run.
|
|
330
|
+
{
|
|
331
|
+
const missing = missingRuleTestFiles(RULES);
|
|
332
|
+
if (missing.length > 0) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`ci-test-scope: RULES reference test file(s) that do not exist on disk ` +
|
|
335
|
+
`(silent coverage hole — see #2758):\n ${missing.join('\n ')}`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
294
340
|
function usage() {
|
|
295
341
|
return [
|
|
296
342
|
'Usage:',
|
|
@@ -523,4 +569,8 @@ function main() {
|
|
|
523
569
|
}
|
|
524
570
|
}
|
|
525
571
|
|
|
526
|
-
|
|
572
|
+
if (require.main === module) {
|
|
573
|
+
runMain(main);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
module.exports = { RULES, missingRuleTestFiles };
|
|
@@ -347,10 +347,12 @@ function validate({ adrs, byFile, byId, nonConforming }) {
|
|
|
347
347
|
//
|
|
348
348
|
// Only a RATIFIED (Accepted) claimant is owed the back-link. A Proposed ADR's
|
|
349
349
|
// supersession claim is prospective — it has not taken effect, so stamping its
|
|
350
|
-
// target as superseded would assert something untrue
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
350
|
+
// target as superseded would assert something untrue. When such an ADR is
|
|
351
|
+
// ratified to Accepted, this check starts demanding the back-links at exactly
|
|
352
|
+
// the right moment — as ADR-857 shows: it was Proposed when this guard was
|
|
353
|
+
// written, was ratified to Accepted on 2026-07-17 (its claim over ADR-0011 /
|
|
354
|
+
// ADR-58 restated as "Subsumes", since both remain Accepted and live), and
|
|
355
|
+
// both targets now carry the reciprocal "Subsumed by" back-link this demands.
|
|
354
356
|
const OPPOSITE = { out: 'in', in: 'out' };
|
|
355
357
|
for (const a of adrs) {
|
|
356
358
|
for (const kind of Object.keys(RELATION_SPEC)) {
|
|
@@ -114,8 +114,15 @@ function buildMatrix(registry) {
|
|
|
114
114
|
const extByCap = extensionsByCapability(registry);
|
|
115
115
|
const featureTable = renderTable(caps, 'feature', extByCap);
|
|
116
116
|
const runtimeTable = renderTable(caps, 'runtime', extByCap);
|
|
117
|
+
// ADR-2782 D3 added a third role. Rendering only feature+runtime silently
|
|
118
|
+
// DROPPED every role:"reviewer" capability from the catalogue — and because
|
|
119
|
+
// `--check` compares generated output against the committed file, both omitted
|
|
120
|
+
// them identically, so the drift guard reported "up to date" while five shipped
|
|
121
|
+
// capabilities were invisible. A guard blind to a whole role is not guarding.
|
|
122
|
+
const reviewerTable = renderTable(caps, 'reviewer', extByCap);
|
|
117
123
|
const featureCount = caps.filter((c) => c.role === 'feature').length;
|
|
118
124
|
const runtimeCount = caps.filter((c) => c.role === 'runtime').length;
|
|
125
|
+
const reviewerCount = caps.filter((c) => c.role === 'reviewer').length;
|
|
119
126
|
|
|
120
127
|
return `# Capability matrix reference
|
|
121
128
|
|
|
@@ -138,7 +145,7 @@ See also: [ADR-1244](../adr/1244-capability-ecosystem.md) —
|
|
|
138
145
|
| Column | Description |
|
|
139
146
|
|---|---|
|
|
140
147
|
| **id** | Canonical capability identifier; unique across first- and third-party capabilities. Reserved prefixes: \`gsd-\`, \`gsd-core-\`, \`anthropic-\`. |
|
|
141
|
-
| **role** | \`feature\` — extends what the loop does; \`runtime\` — adapts GSD to a specific AI runtime/IDE. |
|
|
148
|
+
| **role** | \`feature\` — extends what the loop does; \`runtime\` — adapts GSD to a specific AI runtime/IDE; \`reviewer\` — declares a cross-AI reviewer lane (ADR-2782). A capability may be both a runtime and a reviewer. |
|
|
142
149
|
| **tier** | \`core\` — always active; \`standard\` — active when the runtime supports it; \`full\` — opt-in or runtime-specific. |
|
|
143
150
|
| **engines.gsd** | Semver RANGE expressing host-version compatibility. A hard gate at install and at load. \`—\` means the capability declares no range. |
|
|
144
151
|
| **extension points** | The loop points this capability registers hooks into (from the registry's \`byLoopPoint\` index). \`—\` means it registers none (typical for runtime capabilities, whose job is surface emission). |
|
|
@@ -180,6 +187,23 @@ emission), so their extension-point and hook-kind cells are \`—\`.
|
|
|
180
187
|
|
|
181
188
|
${runtimeTable}
|
|
182
189
|
|
|
190
|
+
### Reviewer capabilities (role: reviewer) — ${reviewerCount}
|
|
191
|
+
|
|
192
|
+
Reviewer capabilities declare a cross-AI **reviewer lane** — one external CLI or
|
|
193
|
+
model endpoint \`/gsd:review\` hands a plan to (ADR-2782 D3). They are not install
|
|
194
|
+
targets: they emit no skills, agents, hooks or surface files, so their
|
|
195
|
+
extension-point and hook-kind cells are \`—\`. A host that is *also* a reviewer
|
|
196
|
+
(Claude, Codex, Cursor, OpenCode, Qwen, Antigravity) keeps one manifest and
|
|
197
|
+
appears under **runtime** above, carrying its lane alongside its runtime body;
|
|
198
|
+
only lanes that GSD never installs into appear here.
|
|
199
|
+
|
|
200
|
+
Because a lane receives the plan text, requirements, research findings and
|
|
201
|
+
\`CONTEXT.md\` decisions, it is a disclosed executable surface and is consent-gated
|
|
202
|
+
at install like any other — see
|
|
203
|
+
[the trust model](../explanation/capability-trust-model.md).
|
|
204
|
+
|
|
205
|
+
${reviewerTable}
|
|
206
|
+
|
|
183
207
|
---
|
|
184
208
|
|
|
185
209
|
## Third-party capabilities
|
|
@@ -199,7 +223,7 @@ fields described below, with \`source\` = \`third-party\`.
|
|
|
199
223
|
| Column | Value |
|
|
200
224
|
|---|---|
|
|
201
225
|
| **id** | As declared in \`capability.json\`. Must not use reserved prefixes (\`gsd-\`, \`gsd-core-\`, \`anthropic-\`). |
|
|
202
|
-
| **role** | \`feature\` or \`
|
|
226
|
+
| **role** | \`feature\`, \`runtime\`, or \`reviewer\`, as declared. |
|
|
203
227
|
| **tier** | \`core\`, \`standard\`, or \`full\`, as declared. |
|
|
204
228
|
| **engines.gsd** | Range from \`capability.json\`; verified at install and at each load. |
|
|
205
229
|
| **extension points** | The loop points the capability registers into, validated against the known 12 identifiers. |
|
|
@@ -71,6 +71,7 @@ const {
|
|
|
71
71
|
validateArtifactKindEntry,
|
|
72
72
|
validateArtifactLayout,
|
|
73
73
|
validateRuntimeBody,
|
|
74
|
+
collectReviewerWarnings,
|
|
74
75
|
materializeHookFragments,
|
|
75
76
|
validateAgainstContract,
|
|
76
77
|
validateConsumesGlobal,
|
|
@@ -128,6 +129,71 @@ function loadCentralConfigKeys(schemaPath = CONFIG_SCHEMA_PATH) {
|
|
|
128
129
|
return new Set(Array.isArray(manifest.validKeys) ? manifest.validKeys : []);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Loads the central config-schema's DYNAMIC key patterns (#2797).
|
|
134
|
+
*
|
|
135
|
+
* `loadCentralConfigKeys` above reads `validKeys` only, so a federated key
|
|
136
|
+
* claimed by a central *pattern* was invisible to the exclusivity check. That is
|
|
137
|
+
* not a cosmetic gap: `isCentralConfigKey` consults these same patterns, and
|
|
138
|
+
* `mergeFederatedConfig` skips every key for which it returns true — so an
|
|
139
|
+
* overlapping slice is inert while the build stays green.
|
|
140
|
+
*
|
|
141
|
+
* The patterns are read from the SAME manifest the runtime reads and compiled
|
|
142
|
+
* with the same `source`, rather than re-implementing a matcher here, so the two
|
|
143
|
+
* cannot drift.
|
|
144
|
+
*
|
|
145
|
+
* Failure contract MATCHES `loadCentralConfigKeys` above deliberately — the two
|
|
146
|
+
* read the same file and must not disagree about what a broken one means:
|
|
147
|
+
* - ENOENT → empty list. Legitimately absent.
|
|
148
|
+
* - Any other read error, or a JSON parse error → prominent stderr + throw.
|
|
149
|
+
*
|
|
150
|
+
* An earlier revision swallowed the parse error and returned []. That is
|
|
151
|
+
* fail-OPEN on the gate this function exists to feed: with zero patterns,
|
|
152
|
+
* `validateCrossCapability`'s pattern-collision check silently passes and an
|
|
153
|
+
* inert federated slice ships green. It was masked in the one production call
|
|
154
|
+
* site only because `loadCentralConfigKeys` runs first against the same path and
|
|
155
|
+
* throws — a coincidence of ordering, not a guarantee, and this function is
|
|
156
|
+
* exported and called standalone.
|
|
157
|
+
*
|
|
158
|
+
* A single unparseable PATTERN is still skipped rather than fatal: that is a
|
|
159
|
+
* per-entry defect the central schema's own tests own, and skipping one pattern
|
|
160
|
+
* degrades to "checked less" rather than blocking every build.
|
|
161
|
+
*/
|
|
162
|
+
function loadCentralConfigPatterns(schemaPath = CONFIG_SCHEMA_PATH) {
|
|
163
|
+
let raw;
|
|
164
|
+
try {
|
|
165
|
+
raw = fs.readFileSync(schemaPath, 'utf8');
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (err && err.code === 'ENOENT') return [];
|
|
168
|
+
process.stderr.write(
|
|
169
|
+
' ERROR Failed to read config-schema manifest at ' + schemaPath + ': ' + err.message + '\n',
|
|
170
|
+
);
|
|
171
|
+
throw new ExitError(1, 'could not read config-schema manifest');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let manifest;
|
|
175
|
+
try {
|
|
176
|
+
manifest = JSON.parse(raw);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
process.stderr.write(
|
|
179
|
+
' ERROR Config-schema manifest at ' + schemaPath + ' is broken (JSON parse error): ' + err.message + '\n',
|
|
180
|
+
);
|
|
181
|
+
throw new ExitError(1, 'config-schema manifest JSON is malformed');
|
|
182
|
+
}
|
|
183
|
+
const declared = Array.isArray(manifest.dynamicKeyPatterns) ? manifest.dynamicKeyPatterns : [];
|
|
184
|
+
const out = [];
|
|
185
|
+
for (const entry of declared) {
|
|
186
|
+
const src = entry && typeof entry.source === 'string' ? entry.source : null;
|
|
187
|
+
if (!src) continue;
|
|
188
|
+
try {
|
|
189
|
+
out.push(new RegExp(src));
|
|
190
|
+
} catch {
|
|
191
|
+
// Unparseable pattern — the central schema's own tests own that failure.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
131
197
|
// ─── ADR-857 Phase 4a: Derived views ─────────────────────────────────────────
|
|
132
198
|
|
|
133
199
|
// (Config-slice validation, per-capability validators, contract validators,
|
|
@@ -335,14 +401,21 @@ function runConsistencyGate(capabilityClusters, profileMembership, capMap) {
|
|
|
335
401
|
* (used during 3a-impl while migration is in-progress).
|
|
336
402
|
* @param {string} [capabilitiesDir] Override capabilities dir (for testing with fixtures).
|
|
337
403
|
*/
|
|
338
|
-
function loadAndValidate(centralKeys, capabilitiesDir) {
|
|
404
|
+
function loadAndValidate(centralKeys, capabilitiesDir, centralPatterns) {
|
|
339
405
|
const resolvedCentralKeys = centralKeys !== undefined ? centralKeys : loadCentralConfigKeys();
|
|
406
|
+
// #2797: patterns default to the real manifest unless a caller passes its own
|
|
407
|
+
// (tests pass [] to isolate the exact-key path).
|
|
408
|
+
const resolvedCentralPatterns = centralPatterns !== undefined ? centralPatterns : loadCentralConfigPatterns();
|
|
340
409
|
const resolvedCapDir = capabilitiesDir !== undefined ? capabilitiesDir : CAPABILITIES_DIR;
|
|
341
410
|
const errors = [];
|
|
342
411
|
const capMap = new Map();
|
|
412
|
+
// ADR-2782 D4 — non-fatal diagnostics (e.g. an unknown field inside a reviewer
|
|
413
|
+
// body). These NEVER fail the build; they surface on stderr so a forward-built
|
|
414
|
+
// manifest degrades visibly instead of silently.
|
|
415
|
+
const warnings = [];
|
|
343
416
|
|
|
344
417
|
if (!fs.existsSync(resolvedCapDir)) {
|
|
345
|
-
return { capMap, errors };
|
|
418
|
+
return { capMap, errors, warnings };
|
|
346
419
|
}
|
|
347
420
|
|
|
348
421
|
// Compute wired points ONCE before iterating capabilities so the filesystem
|
|
@@ -366,6 +439,10 @@ function loadAndValidate(centralKeys, capabilitiesDir) {
|
|
|
366
439
|
continue;
|
|
367
440
|
}
|
|
368
441
|
|
|
442
|
+
// Collected BEFORE the error short-circuit below so a manifest that is both
|
|
443
|
+
// forward-built and invalid still reports why it looked unfamiliar.
|
|
444
|
+
for (const w of collectReviewerWarnings(cap)) warnings.push(folderId + '/capability.json: ' + w);
|
|
445
|
+
|
|
369
446
|
const capErrors = validateCapability(cap, folderId);
|
|
370
447
|
if (capErrors.length > 0) {
|
|
371
448
|
for (const e of capErrors) errors.push(folderId + '/capability.json: ' + e);
|
|
@@ -397,7 +474,7 @@ function loadAndValidate(centralKeys, capabilitiesDir) {
|
|
|
397
474
|
}
|
|
398
475
|
|
|
399
476
|
// Cross-capability invariants — capMap contains only fully-valid capabilities at this point.
|
|
400
|
-
const crossErrors = validateCrossCapability(capMap, resolvedCentralKeys);
|
|
477
|
+
const crossErrors = validateCrossCapability(capMap, resolvedCentralKeys, resolvedCentralPatterns);
|
|
401
478
|
errors.push(...crossErrors);
|
|
402
479
|
|
|
403
480
|
// C2: Global consumes-satisfiability — runs after capMap is fully built so cross-capability
|
|
@@ -406,7 +483,7 @@ function loadAndValidate(centralKeys, capabilitiesDir) {
|
|
|
406
483
|
const consumesErrors = validateConsumesGlobal(capMap);
|
|
407
484
|
errors.push(...consumesErrors);
|
|
408
485
|
|
|
409
|
-
return { capMap, errors };
|
|
486
|
+
return { capMap, errors, warnings };
|
|
410
487
|
}
|
|
411
488
|
|
|
412
489
|
/**
|
|
@@ -446,6 +523,46 @@ function buildRegistry(capMap) {
|
|
|
446
523
|
if (capId === '__proto__' || capId === 'constructor' || capId === 'prototype') continue;
|
|
447
524
|
capabilities[capId] = cap;
|
|
448
525
|
|
|
526
|
+
// Federated config slice — harvested from ANY role that declares one.
|
|
527
|
+
//
|
|
528
|
+
// ADR-2782 D1/D9: this loop was nested inside the `role === 'feature'` branch,
|
|
529
|
+
// so a `role: "runtime"` capability's `config` was read by nothing and dropped
|
|
530
|
+
// in silence — the actual reason reviewer config keys are stranded in the
|
|
531
|
+
// central schema. (The often-cited reason, that the runtime body forbids
|
|
532
|
+
// feature-only fields, does not apply: `config` is NOT in
|
|
533
|
+
// FEATURE_FIELDS_FORBIDDEN_ON_RUNTIME.) Owning a config slice is a property of
|
|
534
|
+
// DECLARING one, not of being a feature. Verified inert at introduction — no
|
|
535
|
+
// shipped capability declares `config` on a non-feature role — so this changes
|
|
536
|
+
// no existing key; it stops a latent silent drop and unblocks Phase 4 (#2797).
|
|
537
|
+
for (const key of Object.keys(cap.config || {})) {
|
|
538
|
+
// S2b: inline literal guard at each write site (CodeQL barrier)
|
|
539
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
|
|
540
|
+
configKeys[key] = capId;
|
|
541
|
+
|
|
542
|
+
// Build configSchema entry — validate the slice first (throw on violation)
|
|
543
|
+
const slice = (cap.config || {})[key];
|
|
544
|
+
const sliceErrors = validateConfigSliceEntry(capId, key, slice);
|
|
545
|
+
if (sliceErrors.length > 0) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
'configSchema validation failed during registry build:\n' +
|
|
548
|
+
sliceErrors.map((e) => ' ' + e).join('\n'),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
// S2b: inline literal guard for configSchema write site
|
|
552
|
+
if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') {
|
|
553
|
+
configSchema[key] = {
|
|
554
|
+
owner: capId,
|
|
555
|
+
type: slice.type,
|
|
556
|
+
default: slice.default,
|
|
557
|
+
description: slice.description,
|
|
558
|
+
};
|
|
559
|
+
// Preserve values array for enum types if present
|
|
560
|
+
if (slice.type === 'enum' && Array.isArray(slice.values)) {
|
|
561
|
+
configSchema[key].values = slice.values;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
449
566
|
if (cap.role === 'feature') {
|
|
450
567
|
for (const skill of (cap.skills || [])) {
|
|
451
568
|
// S2b: inline literal guard at each write site (CodeQL barrier)
|
|
@@ -457,34 +574,6 @@ function buildRegistry(capMap) {
|
|
|
457
574
|
if (agent === '__proto__' || agent === 'constructor' || agent === 'prototype') continue;
|
|
458
575
|
byAgent[agent] = capId;
|
|
459
576
|
}
|
|
460
|
-
for (const key of Object.keys(cap.config || {})) {
|
|
461
|
-
// S2b: inline literal guard at each write site (CodeQL barrier)
|
|
462
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
|
|
463
|
-
configKeys[key] = capId;
|
|
464
|
-
|
|
465
|
-
// Build configSchema entry — validate the slice first (throw on violation)
|
|
466
|
-
const slice = (cap.config || {})[key];
|
|
467
|
-
const sliceErrors = validateConfigSliceEntry(capId, key, slice);
|
|
468
|
-
if (sliceErrors.length > 0) {
|
|
469
|
-
throw new Error(
|
|
470
|
-
'configSchema validation failed during registry build:\n' +
|
|
471
|
-
sliceErrors.map((e) => ' ' + e).join('\n'),
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
// S2b: inline literal guard for configSchema write site
|
|
475
|
-
if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') {
|
|
476
|
-
configSchema[key] = {
|
|
477
|
-
owner: capId,
|
|
478
|
-
type: slice.type,
|
|
479
|
-
default: slice.default,
|
|
480
|
-
description: slice.description,
|
|
481
|
-
};
|
|
482
|
-
// Preserve values array for enum types if present
|
|
483
|
-
if (slice.type === 'enum' && Array.isArray(slice.values)) {
|
|
484
|
-
configSchema[key].values = slice.values;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
577
|
|
|
489
578
|
for (const step of (cap.steps || [])) {
|
|
490
579
|
if (VALID_LOOP_POINTS.has(step.point)) {
|
|
@@ -741,7 +830,11 @@ function main() {
|
|
|
741
830
|
if (flag === '--check') {
|
|
742
831
|
// Fix #3: read the REAL central config keys so collision detection fires and is visible.
|
|
743
832
|
const centralKeys = loadCentralConfigKeys();
|
|
744
|
-
const { capMap, errors } = loadAndValidate(centralKeys);
|
|
833
|
+
const { capMap, errors, warnings } = loadAndValidate(centralKeys);
|
|
834
|
+
|
|
835
|
+
// ADR-2782 D4 — non-fatal manifest diagnostics. Emitted BEFORE the hard-error
|
|
836
|
+
// exit so a forward-built manifest still explains itself on a failing build.
|
|
837
|
+
for (const w of warnings) process.stderr.write(w + '\n');
|
|
745
838
|
|
|
746
839
|
// Separate pending-migration warnings from hard errors
|
|
747
840
|
const { hardErrors, pendingMigrationWarnings } = classifyCrossErrors(errors);
|
|
@@ -778,7 +871,11 @@ function main() {
|
|
|
778
871
|
} else if (flag === '--write') {
|
|
779
872
|
// Fix #3: read the REAL central config keys so collision detection fires and is visible.
|
|
780
873
|
const centralKeys = loadCentralConfigKeys();
|
|
781
|
-
const { capMap, errors } = loadAndValidate(centralKeys);
|
|
874
|
+
const { capMap, errors, warnings } = loadAndValidate(centralKeys);
|
|
875
|
+
|
|
876
|
+
// ADR-2782 D4 — non-fatal manifest diagnostics. Emitted BEFORE the hard-error
|
|
877
|
+
// exit so a forward-built manifest still explains itself on a failing build.
|
|
878
|
+
for (const w of warnings) process.stderr.write(w + '\n');
|
|
782
879
|
|
|
783
880
|
// Separate pending-migration warnings from hard errors
|
|
784
881
|
const { hardErrors, pendingMigrationWarnings } = classifyCrossErrors(errors);
|
|
@@ -828,6 +925,7 @@ module.exports = {
|
|
|
828
925
|
validateCrossCapability,
|
|
829
926
|
classifyCrossErrors,
|
|
830
927
|
loadCentralConfigKeys,
|
|
928
|
+
loadCentralConfigPatterns,
|
|
831
929
|
loadAndValidate,
|
|
832
930
|
buildRegistry,
|
|
833
931
|
serializeRegistry,
|