@opengsd/gsd-core 1.5.0-rc.4 → 1.5.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/plugin.json +1 -1
- package/LICENSE +1 -1
- package/agents/gsd-executor.md +22 -0
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-planner.md +4 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-verifier.md +45 -13
- package/bin/install.js +56 -100
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +85 -53
- package/gsd-core/bin/lib/agent-command-router.cjs +2 -2
- package/gsd-core/bin/lib/agent-install-check.cjs +143 -0
- package/gsd-core/bin/lib/audit-command-router.cjs +4 -4
- package/gsd-core/bin/lib/capability-activation.cjs +37 -10
- package/gsd-core/bin/lib/capability-registry.cjs +2 -0
- package/gsd-core/bin/lib/capability-state.cjs +80 -30
- package/gsd-core/bin/lib/capability-writer.cjs +5 -4
- package/gsd-core/bin/lib/check-command-router.cjs +50 -51
- package/gsd-core/bin/lib/commands.cjs +20 -2
- package/gsd-core/bin/lib/config-loader.cjs +3 -4
- package/gsd-core/bin/lib/config-schema.cjs +1 -1
- package/gsd-core/bin/lib/config-types.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +5 -2
- package/gsd-core/bin/lib/decisions.cjs +19 -1
- package/gsd-core/bin/lib/docs.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +2 -2
- package/gsd-core/bin/lib/gap-checker.cjs +5 -2
- package/gsd-core/bin/lib/git-base-branch.cjs +27 -1
- package/gsd-core/bin/lib/graphify-command-router.cjs +6 -8
- package/gsd-core/bin/lib/graphify.cjs +7 -31
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +30 -4
- package/gsd-core/bin/lib/intel-command-router.cjs +6 -3
- package/gsd-core/bin/lib/intel.cjs +28 -34
- package/gsd-core/bin/lib/io.cjs +2 -4
- package/gsd-core/bin/lib/learnings.cjs +2 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +45 -167
- package/gsd-core/bin/lib/milestone.cjs +13 -5
- package/gsd-core/bin/lib/model-resolver.cjs +3 -4
- package/gsd-core/bin/lib/phase-id.cjs +3 -5
- package/gsd-core/bin/lib/phase-locator.cjs +3 -6
- package/gsd-core/bin/lib/phase.cjs +59 -13
- package/gsd-core/bin/lib/probe-core.cjs +40 -11
- package/gsd-core/bin/lib/profile-output.cjs +5 -2
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +660 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +28 -25
- package/gsd-core/bin/lib/roadmap.cjs +9 -4
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +4 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +24 -3
- package/gsd-core/bin/lib/state.cjs +75 -17
- package/gsd-core/bin/lib/task-command-router.cjs +2 -2
- package/gsd-core/bin/lib/teams-status.cjs +74 -0
- package/gsd-core/bin/lib/template.cjs +11 -2
- package/gsd-core/bin/lib/uat.cjs +64 -2
- package/gsd-core/bin/lib/verification.cjs +8 -5
- package/gsd-core/bin/lib/verify.cjs +311 -4
- package/gsd-core/bin/lib/workstream-inventory.cjs +2 -2
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +44 -3
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/planner-antipatterns.md +46 -0
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/prohibition-probe.md +80 -2
- package/gsd-core/references/worktree-branch-check.md +11 -5
- package/gsd-core/templates/verification-report.md +16 -3
- package/gsd-core/workflows/docs-update.md +23 -31
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
- package/gsd-core/workflows/execute-phase.md +7 -2
- package/gsd-core/workflows/map-codebase.md +8 -10
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/quick.md +26 -2
- package/gsd-core/workflows/settings-advanced.md +5 -5
- package/gsd-core/workflows/settings-integrations.md +5 -5
- package/gsd-core/workflows/spec-phase.md +30 -2
- package/gsd-core/workflows/verify-phase.md +22 -7
- package/hooks/dist/gsd-worktree-path-guard.js +34 -18
- package/hooks/gsd-worktree-path-guard.js +34 -18
- package/package.json +3 -3
- package/scripts/ci-prepare-test-scope.cjs +56 -14
- package/scripts/diff-touches-shipped-paths.cjs +5 -11
- package/scripts/gen-capability-registry.cjs +27 -1
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/research-profiles.cjs +2 -2
- package/scripts/run-tests.cjs +1 -0
- package/gsd-core/bin/lib/core.cjs +0 -345
|
@@ -20,31 +20,27 @@
|
|
|
20
20
|
* Both pure functions (resolveLoopHooks, renderLoopHooks) take explicit
|
|
21
21
|
* registry/config arguments so they are trivially testable without I/O.
|
|
22
22
|
*
|
|
23
|
-
* Dependencies (leaf modules only — no
|
|
24
|
-
* - node:fs / node:path (raw config.json read for capability-key activation)
|
|
23
|
+
* Dependencies (leaf modules only — no circular risk):
|
|
25
24
|
* - ./config-loader.cjs (loadConfig)
|
|
26
|
-
* - ./
|
|
27
|
-
* - ./
|
|
25
|
+
* - ./io.cjs (output, error)
|
|
26
|
+
* - ./capability-activation.cjs (resolveConfigKey, _resolveActivationValue, _getNestedConfigValue, _readRawConfigKey)
|
|
28
27
|
* - loop-host-contract.cjs (CANONICAL_POINTS via LOOP_HOST_CONTRACT)
|
|
29
28
|
* - capability-registry.cjs (byLoopPoint, consumed at call time)
|
|
29
|
+
* - capability-state.cjs (resolveCapabilityRuntimeState — for capabilities list)
|
|
30
30
|
*/
|
|
31
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
32
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
|
-
};
|
|
34
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
35
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
36
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
-
const
|
|
38
|
-
const { output: coreOutput, error: coreError } =
|
|
32
|
+
const ioMod = require("./io.cjs");
|
|
33
|
+
const { output: coreOutput, error: coreError } = ioMod;
|
|
39
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
35
|
const configLoaderModule = require("./config-loader.cjs");
|
|
41
36
|
const { loadConfig } = configLoaderModule;
|
|
42
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
38
|
const capabilityStateModule = require("./capability-state.cjs");
|
|
44
39
|
const { resolveCapabilityRuntimeState } = capabilityStateModule;
|
|
40
|
+
// ─── Capability-activation engine (single owner for config-key precedence) ────
|
|
45
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
46
|
-
const
|
|
47
|
-
const {
|
|
42
|
+
const capabilityActivationModule = require("./capability-activation.cjs");
|
|
43
|
+
const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, resolveConfigKey } = capabilityActivationModule;
|
|
48
44
|
// ─── Canonical points (derived from LOOP_HOST_CONTRACT — authoritative 12) ───
|
|
49
45
|
// FIX 2: Derive the authoritative canonical set from LOOP_HOST_CONTRACT so it
|
|
50
46
|
// cannot drift from the host contract. CANONICAL_POINTS_FALLBACK is kept as an
|
|
@@ -92,123 +88,12 @@ const CANONICAL_POINTS_FALLBACK = CANONICAL_POINTS;
|
|
|
92
88
|
function _getCanonicalPoints(_registry) {
|
|
93
89
|
return CANONICAL_POINTS;
|
|
94
90
|
}
|
|
95
|
-
// ─── Prototype-pollution guard (inline literal, CodeQL barrier) ───────────────
|
|
96
|
-
/**
|
|
97
|
-
* Traverse a dotted config key through a nested config object.
|
|
98
|
-
* E.g. "workflow.ui_phase" in { workflow: { ui_phase: true } } → { found: true, value: true }
|
|
99
|
-
* Returns { found: false } if any segment is a forbidden key or not an own property.
|
|
100
|
-
*/
|
|
101
|
-
function _getNestedConfigValue(config, dotKey) {
|
|
102
|
-
const segments = dotKey.split('.');
|
|
103
|
-
let current = config;
|
|
104
|
-
for (const seg of segments) {
|
|
105
|
-
// Inline literal prototype-pollution guard (CodeQL barrier)
|
|
106
|
-
if (seg === '__proto__' || seg === 'constructor' || seg === 'prototype') {
|
|
107
|
-
return { found: false, value: undefined };
|
|
108
|
-
}
|
|
109
|
-
if (typeof current !== 'object' || current === null) {
|
|
110
|
-
return { found: false, value: undefined };
|
|
111
|
-
}
|
|
112
|
-
const cur = current;
|
|
113
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
114
|
-
return { found: false, value: undefined };
|
|
115
|
-
}
|
|
116
|
-
current = cur[seg];
|
|
117
|
-
}
|
|
118
|
-
return { found: true, value: current };
|
|
119
|
-
}
|
|
120
|
-
// ─── Single-key activation resolver (FIX 1) ───────────────────────────────────
|
|
121
|
-
/**
|
|
122
|
-
* Warn-once set for raw config.json parse errors.
|
|
123
|
-
* Avoids noisy per-call stderr from a single malformed file.
|
|
124
|
-
*/
|
|
125
|
-
const _warnedRawConfigPaths = new Set();
|
|
126
|
-
/**
|
|
127
|
-
* Read a raw config.json file and perform a guarded nested-lookup of a single
|
|
128
|
-
* dotted key. Returns { found: false } if the file is missing (ENOENT) or if
|
|
129
|
-
* the key is absent/forbidden. On a genuine JSON parse error: warns once to
|
|
130
|
-
* stderr and returns { found: false } — never throws.
|
|
131
|
-
*/
|
|
132
|
-
function _readRawConfigKey(filePath, dotKey) {
|
|
133
|
-
try {
|
|
134
|
-
const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
135
|
-
let parsed;
|
|
136
|
-
try {
|
|
137
|
-
parsed = JSON.parse(raw);
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
if (!_warnedRawConfigPaths.has(filePath)) {
|
|
141
|
-
_warnedRawConfigPaths.add(filePath);
|
|
142
|
-
try {
|
|
143
|
-
process.stderr.write(`gsd-tools: warning: failed to parse ${filePath} as JSON — skipping for activation resolution\n`);
|
|
144
|
-
}
|
|
145
|
-
catch { /* stderr might be closed */ }
|
|
146
|
-
}
|
|
147
|
-
return { found: false, value: undefined };
|
|
148
|
-
}
|
|
149
|
-
return _getNestedConfigValue(parsed, dotKey);
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
// ENOENT (missing file) is expected → skip silently. All other errors → also skip (defensive).
|
|
153
|
-
return { found: false, value: undefined };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* FIX 1: Resolve the effective value for a hook's `when` key using the
|
|
158
|
-
* four-level precedence:
|
|
159
|
-
*
|
|
160
|
-
* 1. loadConfig result (`config` arg) — guarded nested-lookup of the dotted key.
|
|
161
|
-
* This is the post-cutover federated path (covers keys that loadConfig now exposes).
|
|
162
|
-
* 2. Raw workstream `.planning/.../config.json` — guarded single-key lookup.
|
|
163
|
-
* Workstream wins over root (mirrors loadConfig inheritance).
|
|
164
|
-
* 3. Raw root `.planning/config.json` — guarded single-key lookup.
|
|
165
|
-
* 4. `registry.configSchema[when]?.default` — schema default.
|
|
166
|
-
* A `default: true` hook is active out-of-the-box without any config.
|
|
167
|
-
* 5. Absent → inactive (return false).
|
|
168
|
-
*
|
|
169
|
-
* Never constructs a merged object from raw JSON keys — only reads the single
|
|
170
|
-
* leaf value at the guarded dotted path. Prototype-pollution sink is eliminated.
|
|
171
|
-
*/
|
|
172
|
-
function _resolveActivationValue(dotKey, config, cwd, registry) {
|
|
173
|
-
// Level 1: loadConfig result
|
|
174
|
-
const fromConfig = _getNestedConfigValue(config, dotKey);
|
|
175
|
-
if (fromConfig.found)
|
|
176
|
-
return Boolean(fromConfig.value);
|
|
177
|
-
// Level 2 + 3: raw config.json files (only when cwd is available)
|
|
178
|
-
if (cwd) {
|
|
179
|
-
// Level 2: workstream config (planningDir respects GSD_WORKSTREAM env)
|
|
180
|
-
const wsConfigPath = node_path_1.default.join(planningDir(cwd), 'config.json');
|
|
181
|
-
// Level 3: root config (planningRoot = cwd/.planning always)
|
|
182
|
-
const rootConfigPath = node_path_1.default.join(planningRoot(cwd), 'config.json');
|
|
183
|
-
// Workstream wins over root (mirroring loadConfig root→workstream precedence:
|
|
184
|
-
// workstream overlays root, so workstream value takes precedence).
|
|
185
|
-
const fromWs = _readRawConfigKey(wsConfigPath, dotKey);
|
|
186
|
-
if (fromWs.found)
|
|
187
|
-
return Boolean(fromWs.value);
|
|
188
|
-
// Only read root if it differs from the workstream path (avoids double-read
|
|
189
|
-
// when no workstream is active and both paths resolve to the same file).
|
|
190
|
-
if (wsConfigPath !== rootConfigPath) {
|
|
191
|
-
const fromRoot = _readRawConfigKey(rootConfigPath, dotKey);
|
|
192
|
-
if (fromRoot.found)
|
|
193
|
-
return Boolean(fromRoot.value);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
// Level 4: registry configSchema default
|
|
197
|
-
const schemaEntry = registry['configSchema']?.[dotKey];
|
|
198
|
-
if (schemaEntry && typeof schemaEntry === 'object' && schemaEntry !== null) {
|
|
199
|
-
const def = schemaEntry['default'];
|
|
200
|
-
if (def !== undefined)
|
|
201
|
-
return Boolean(def);
|
|
202
|
-
}
|
|
203
|
-
// Level 5: absent → inactive
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
91
|
// ─── Pure resolver ─────────────────────────────────────────────────────────────
|
|
207
92
|
/**
|
|
208
93
|
* Pure resolver: given a point, registry, and config, returns the active hooks.
|
|
209
94
|
*
|
|
210
95
|
* Throws if `point` is not one of the 12 canonical points (caller converts to
|
|
211
|
-
*
|
|
96
|
+
* io.error). Never throws for malformed registry/hook entries — skips and
|
|
212
97
|
* continues.
|
|
213
98
|
*
|
|
214
99
|
* Ordering: steps first, then contributions, then gates. Within each array,
|
|
@@ -248,7 +133,7 @@ function resolveLoopHooks(input) {
|
|
|
248
133
|
return false;
|
|
249
134
|
return _resolveActivationValue(when, config, cwd, registry);
|
|
250
135
|
}
|
|
251
|
-
function
|
|
136
|
+
function isCapabilityActive(capId) {
|
|
252
137
|
if (!capabilityStatesById)
|
|
253
138
|
return true;
|
|
254
139
|
const state = capabilityStatesById instanceof Map
|
|
@@ -256,7 +141,12 @@ function resolveLoopHooks(input) {
|
|
|
256
141
|
: capabilityStatesById[capId];
|
|
257
142
|
if (!state)
|
|
258
143
|
return false;
|
|
259
|
-
|
|
144
|
+
// Fail-closed gate: only render the hook when active is explicitly true.
|
|
145
|
+
// A capability can be installed and surfaced (enabled=true) but config-disabled
|
|
146
|
+
// (active=false); in that case the hook must not render.
|
|
147
|
+
// Phase 4 tri-state alignment: `active` is now required (not optional), so
|
|
148
|
+
// `=== true` is the correct fail-closed check (not `!== false`).
|
|
149
|
+
return state.active === true;
|
|
260
150
|
}
|
|
261
151
|
// Helper: safe string array
|
|
262
152
|
function toStringArray(v) {
|
|
@@ -294,39 +184,9 @@ function resolveLoopHooks(input) {
|
|
|
294
184
|
continue;
|
|
295
185
|
if (typeof dotKey !== 'string')
|
|
296
186
|
continue;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
resolved[alias] = fromConfig.value;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
// Level 2 + 3: raw config.json files
|
|
304
|
-
if (cwd) {
|
|
305
|
-
const wsConfigPath = node_path_1.default.join(planningDir(cwd), 'config.json');
|
|
306
|
-
const rootConfigPath = node_path_1.default.join(planningRoot(cwd), 'config.json');
|
|
307
|
-
const fromWs = _readRawConfigKey(wsConfigPath, dotKey);
|
|
308
|
-
if (fromWs.found) {
|
|
309
|
-
resolved[alias] = fromWs.value;
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (wsConfigPath !== rootConfigPath) {
|
|
313
|
-
const fromRoot = _readRawConfigKey(rootConfigPath, dotKey);
|
|
314
|
-
if (fromRoot.found) {
|
|
315
|
-
resolved[alias] = fromRoot.value;
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// Level 4: registry configSchema default
|
|
321
|
-
const schemaEntry = registry['configSchema']?.[dotKey];
|
|
322
|
-
if (schemaEntry && typeof schemaEntry === 'object' && schemaEntry !== null) {
|
|
323
|
-
const def = schemaEntry['default'];
|
|
324
|
-
if (def !== undefined) {
|
|
325
|
-
resolved[alias] = def;
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// Level 5: absent → undefined (omit from resolved map)
|
|
187
|
+
const r = resolveConfigKey(dotKey, { config, cwd, registry });
|
|
188
|
+
if (r.found)
|
|
189
|
+
resolved[alias] = r.value;
|
|
330
190
|
}
|
|
331
191
|
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
332
192
|
}
|
|
@@ -337,7 +197,7 @@ function resolveLoopHooks(input) {
|
|
|
337
197
|
if (!hook || typeof hook !== 'object')
|
|
338
198
|
continue;
|
|
339
199
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
340
|
-
if (!
|
|
200
|
+
if (!isCapabilityActive(capId))
|
|
341
201
|
continue;
|
|
342
202
|
if (!isActive(hook))
|
|
343
203
|
continue;
|
|
@@ -371,7 +231,7 @@ function resolveLoopHooks(input) {
|
|
|
371
231
|
if (!hook || typeof hook !== 'object')
|
|
372
232
|
continue;
|
|
373
233
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
374
|
-
if (!
|
|
234
|
+
if (!isCapabilityActive(capId))
|
|
375
235
|
continue;
|
|
376
236
|
if (!isActive(hook))
|
|
377
237
|
continue;
|
|
@@ -406,7 +266,7 @@ function resolveLoopHooks(input) {
|
|
|
406
266
|
if (!hook || typeof hook !== 'object')
|
|
407
267
|
continue;
|
|
408
268
|
const capId = typeof hook['capId'] === 'string' ? hook['capId'] : '';
|
|
409
|
-
if (!
|
|
269
|
+
if (!isCapabilityActive(capId))
|
|
410
270
|
continue;
|
|
411
271
|
if (!isActive(hook))
|
|
412
272
|
continue;
|
|
@@ -524,7 +384,7 @@ function renderLoopHooks(resolved) {
|
|
|
524
384
|
* Command entry point: load registry + config, resolve + render, emit envelope.
|
|
525
385
|
*
|
|
526
386
|
* Envelope: { point, activeHooks, rendered }
|
|
527
|
-
* On invalid point, emits
|
|
387
|
+
* On invalid point, emits io.error instead of throwing.
|
|
528
388
|
*
|
|
529
389
|
* Config note: FIX 1 replaced _loadMergedConfig (whole-config deep-merge) with a
|
|
530
390
|
* per-hook single-key activation resolver (_resolveActivationValue). The resolver
|
|
@@ -555,9 +415,24 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
555
415
|
const runtimeConfigDir = typeof options['configDir'] === 'string'
|
|
556
416
|
? options['configDir']
|
|
557
417
|
: undefined;
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
418
|
+
// Load the config snapshot ONCE and share it with both the capability-state
|
|
419
|
+
// resolver (via configOverride) and loop-hook resolution, so federated keys
|
|
420
|
+
// present in loadConfig resolve identically for `active` and for hook when/
|
|
421
|
+
// configValues — eliminating the previous double loadConfig() call. Note: keys
|
|
422
|
+
// absent from loadConfig still fall through to raw .planning/config.json reads
|
|
423
|
+
// (precedence levels 2-3) in each pass; that residual re-read window is
|
|
424
|
+
// pre-existing (unchanged by this consolidation), not introduced here.
|
|
425
|
+
let config;
|
|
426
|
+
try {
|
|
427
|
+
config = loadConfig(cwd);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
config = {};
|
|
431
|
+
}
|
|
432
|
+
const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
|
|
433
|
+
// Registry is the static generated module — same object capability-state uses internally.
|
|
434
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
435
|
+
const registry = require('./capability-registry.cjs');
|
|
561
436
|
const capabilityStatesById = new Map();
|
|
562
437
|
for (const cap of state.capabilities || []) {
|
|
563
438
|
capabilityStatesById.set(cap.id, cap);
|
|
@@ -596,6 +471,9 @@ module.exports = {
|
|
|
596
471
|
_getNestedConfigValue,
|
|
597
472
|
_resolveActivationValue,
|
|
598
473
|
_readRawConfigKey,
|
|
474
|
+
// Re-exported for identity parity guard (FIX 2: resolveConfigValues in this module
|
|
475
|
+
// calls resolveConfigKey; exporting it here makes the single-owner contract testable).
|
|
476
|
+
resolveConfigKey,
|
|
599
477
|
CANONICAL_POINTS_FALLBACK,
|
|
600
478
|
CANONICAL_POINTS,
|
|
601
479
|
};
|
|
@@ -11,8 +11,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core.cjs is an export= CommonJS module
|
|
15
|
-
const core = require("./core.cjs");
|
|
16
14
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
17
15
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
18
16
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
@@ -21,7 +19,18 @@ const frontmatterMod = require("./frontmatter.cjs");
|
|
|
21
19
|
const stateMod = require("./state.cjs");
|
|
22
20
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
23
21
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
24
|
-
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const ioMod = require("./io.cjs");
|
|
24
|
+
const { output, error } = ioMod;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
27
|
+
const { escapeRegex, normalizePhaseName, phaseTokenMatches } = phaseIdMod;
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
30
|
+
const { getMilestonePhaseFilter, extractCurrentMilestone } = roadmapParserMod;
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
33
|
+
const { extractOneLinerFromBody } = coreUtilsMod;
|
|
25
34
|
const { planningPaths } = planningWorkspace;
|
|
26
35
|
const { extractFrontmatter } = frontmatterMod;
|
|
27
36
|
const { writeStateMd, stateReplaceFieldWithFallback } = stateMod;
|
|
@@ -109,7 +118,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
109
118
|
// Ensure archive directory exists
|
|
110
119
|
(0, shell_command_projection_cjs_1.platformEnsureDir)(archiveDir);
|
|
111
120
|
// Scope stats and accomplishments to only the phases belonging to the
|
|
112
|
-
// current milestone's ROADMAP. Uses the shared filter from
|
|
121
|
+
// current milestone's ROADMAP. Uses the shared filter from roadmap-parser.cjs
|
|
113
122
|
// (same logic used by cmdPhasesList and other callers).
|
|
114
123
|
const isDirInMilestone = getMilestonePhaseFilter(cwd, version);
|
|
115
124
|
if (isDirInMilestone.missingExplicitVersion) {
|
|
@@ -138,7 +147,6 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
138
147
|
/* skip */
|
|
139
148
|
}
|
|
140
149
|
if (stateVersion && stateVersion === version) {
|
|
141
|
-
const { extractCurrentMilestone } = core;
|
|
142
150
|
const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
143
151
|
const scopedContent = extractCurrentMilestone(roadmapContent, cwd);
|
|
144
152
|
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
* planning granularity, reasoning effort, and fast-mode for a given agent by
|
|
8
8
|
* reading project config and resolving against the model profiles and catalog.
|
|
9
9
|
* Behaviour is preserved byte-for-behaviour from the prior location; only
|
|
10
|
-
* the module boundary moved. core.cjs re-
|
|
10
|
+
* the module boundary moved. The core.cjs re-export spine was retired in
|
|
11
|
+
* epic #1267; callers import resolvers from model-resolver.cjs directly.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Dependencies (leaf modules only — no core.cjs):
|
|
13
|
+
* Dependencies (leaf modules only):
|
|
15
14
|
* - node:fs / node:path (stdlib, not currently needed — included for future use)
|
|
16
15
|
* - ./config-loader.cjs (loadConfig)
|
|
17
16
|
* - ./configuration.cjs (CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Extracted from core.cts (ADR-857 rollout phase 2a / issue #865).
|
|
7
7
|
* The hand-written bodies are preserved byte-for-behaviour; only the module
|
|
8
|
-
* boundary moved. core.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* New imports should pull phase-id helpers from phase-id.cjs directly.
|
|
8
|
+
* boundary moved. The core.cjs re-export spine was retired in epic #1267;
|
|
9
|
+
* callers import phase-id helpers from phase-id.cjs directly.
|
|
12
10
|
*
|
|
13
11
|
* Dependencies: none (pure string/regex, no Node built-ins required).
|
|
14
12
|
*/
|
|
@@ -175,7 +173,7 @@ function extractPhaseToken(dirName) {
|
|
|
175
173
|
const tokenSegments = [];
|
|
176
174
|
for (let i = 0; i < segments.length; i++) {
|
|
177
175
|
const seg = segments[i];
|
|
178
|
-
if (/^\d/.test(seg)) {
|
|
176
|
+
if (/^\d/.test(seg) || (i === 0 && /^[A-Za-z]{1,3}\d/.test(seg))) {
|
|
179
177
|
tokenSegments.push(seg);
|
|
180
178
|
}
|
|
181
179
|
else {
|
|
@@ -7,13 +7,10 @@
|
|
|
7
7
|
* (`searchPhaseInDir`, `findPhaseInternal`) and archived-phase-dir
|
|
8
8
|
* enumeration (`getArchivedPhaseDirs`), matching phase ids/tokens against
|
|
9
9
|
* the filesystem. Behaviour is preserved byte-for-behaviour from the prior
|
|
10
|
-
* location; only the module boundary moved. core.cjs re-
|
|
11
|
-
*
|
|
10
|
+
* location; only the module boundary moved. The core.cjs re-export spine
|
|
11
|
+
* was retired in epic #1267; callers import phase-locator helpers directly.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* directly.
|
|
15
|
-
*
|
|
16
|
-
* Dependencies (leaf modules only — no core.cjs, no loadConfig):
|
|
13
|
+
* Dependencies (leaf modules only — no loadConfig):
|
|
17
14
|
* - node:fs / node:path (stdlib)
|
|
18
15
|
* - ./phase-id.cjs (normalizePhaseName, phaseTokenMatches, extractPhaseToken)
|
|
19
16
|
* - ./core-utils.cjs (readSubdirectories, getPhaseFileStats, extractCanonicalPlanId, toPosixPath)
|
|
@@ -21,8 +21,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
};
|
|
22
22
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports --
|
|
25
|
-
const
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
25
|
+
const ioMod = require("./io.cjs");
|
|
26
|
+
const { output, error, ERROR_REASON } = ioMod;
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
28
|
+
const configLoaderMod = require("./config-loader.cjs");
|
|
29
|
+
const { loadConfig } = configLoaderMod;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
31
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
32
|
+
const { toPosixPath, generateSlugInternal, readSubdirectories } = coreUtilsMod;
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
34
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
35
|
+
const { escapeRegex, normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, phaseTokenMatches } = phaseIdMod;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
|
|
37
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
38
|
+
const { findPhaseInternal, getArchivedPhaseDirs } = phaseLocatorMod;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
|
|
40
|
+
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
41
|
+
const { stripShippedMilestones, extractCurrentMilestone, getMilestonePhaseFilter } = roadmapParserMod;
|
|
26
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
27
43
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
28
44
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
@@ -36,14 +52,9 @@ const clock_cjs_1 = require("./clock.cjs");
|
|
|
36
52
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
|
|
37
53
|
const uatPredicate = require("./uat-predicate.cjs");
|
|
38
54
|
const { evaluateUatPassed } = uatPredicate;
|
|
39
|
-
const { escapeRegex, loadConfig, normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, toPosixPath, output, error, readSubdirectories, phaseTokenMatches, ERROR_REASON, } = core;
|
|
40
55
|
const { planningDir, withPlanningLock } = planningWorkspace;
|
|
41
56
|
const { extractFrontmatter } = frontmatterMod;
|
|
42
57
|
const { readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, syncStateFrontmatter, withStateLock, updatePerformanceMetricsSection, } = stateMod;
|
|
43
|
-
// Unused import silences TS — keep for structural parity with .cjs (stripShippedMilestones,
|
|
44
|
-
// replaceInCurrentMilestone are exported from core but only used in phase.cjs as-is).
|
|
45
|
-
void stripShippedMilestones;
|
|
46
|
-
void replaceInCurrentMilestone;
|
|
47
58
|
// #2893 — strict canonical filter: `{padded_phase}-{NN}-PLAN.md` or `PLAN.md`.
|
|
48
59
|
const isCanonicalPlanFile = (f) => f.endsWith('-PLAN.md') || f === 'PLAN.md';
|
|
49
60
|
// Any .md file with PLAN anywhere in the basename — diagnostic net
|
|
@@ -1029,6 +1040,22 @@ function writePlanningFileSet(writes) {
|
|
|
1029
1040
|
throw err;
|
|
1030
1041
|
}
|
|
1031
1042
|
}
|
|
1043
|
+
function phaseDisplayNameFromRoadmap(roadmapContent, phaseNum) {
|
|
1044
|
+
if (!roadmapContent || !phaseNum)
|
|
1045
|
+
return null;
|
|
1046
|
+
const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
|
|
1047
|
+
const heading = roadmapContent.match(new RegExp(`^#{2,4}\\s*Phase\\s+${phaseEscaped}\\s*:\\s*([^\\n]+)`, 'im'));
|
|
1048
|
+
if (!heading)
|
|
1049
|
+
return null;
|
|
1050
|
+
const name = heading[1].replace(/\(INSERTED\)/i, '').trim();
|
|
1051
|
+
return name || null;
|
|
1052
|
+
}
|
|
1053
|
+
function phaseDisplayNameFromSlug(slug) {
|
|
1054
|
+
if (!slug)
|
|
1055
|
+
return null;
|
|
1056
|
+
const name = slug.replace(/-/g, ' ').trim();
|
|
1057
|
+
return name || null;
|
|
1058
|
+
}
|
|
1032
1059
|
function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
1033
1060
|
if (!phaseNum) {
|
|
1034
1061
|
error('phase number required for phase complete');
|
|
@@ -1276,6 +1303,8 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
1276
1303
|
const originalStateContent = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
|
|
1277
1304
|
let stateContent = originalStateContent;
|
|
1278
1305
|
const phaseValue = nextPhaseNum || phaseNum;
|
|
1306
|
+
const nextPhaseDisplayName = phaseDisplayNameFromRoadmap(roadmapContent, nextPhaseNum) ??
|
|
1307
|
+
phaseDisplayNameFromSlug(nextPhaseName);
|
|
1279
1308
|
const existingPhaseField = stateExtractField(stateContent, 'Current Phase') ||
|
|
1280
1309
|
stateExtractField(stateContent, 'Phase');
|
|
1281
1310
|
let newPhaseValue = String(phaseValue);
|
|
@@ -1284,22 +1313,39 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
1284
1313
|
const nameMatch = existingPhaseField.match(/\(([^)]+)\)/);
|
|
1285
1314
|
if (totalMatch) {
|
|
1286
1315
|
const total = totalMatch[1];
|
|
1287
|
-
const nameStr =
|
|
1288
|
-
? ` (${
|
|
1316
|
+
const nameStr = nextPhaseDisplayName
|
|
1317
|
+
? ` (${nextPhaseDisplayName})`
|
|
1289
1318
|
: nameMatch
|
|
1290
1319
|
? ` (${nameMatch[1]})`
|
|
1291
1320
|
: '';
|
|
1292
1321
|
newPhaseValue = `${phaseValue} of ${total}${nameStr}`;
|
|
1293
1322
|
}
|
|
1323
|
+
else if (nextPhaseDisplayName) {
|
|
1324
|
+
newPhaseValue = `${phaseValue} — ${nextPhaseDisplayName}`;
|
|
1325
|
+
}
|
|
1294
1326
|
}
|
|
1295
1327
|
stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Phase', 'Phase', newPhaseValue);
|
|
1296
|
-
if (
|
|
1297
|
-
stateContent =
|
|
1328
|
+
if (nextPhaseDisplayName) {
|
|
1329
|
+
stateContent =
|
|
1330
|
+
stateReplaceField(stateContent, 'Current Phase Name', nextPhaseDisplayName) ||
|
|
1331
|
+
stateContent;
|
|
1298
1332
|
}
|
|
1299
1333
|
stateContent = stateReplaceFieldWithFallback(stateContent, 'Status', null, isLastPhase ? 'Milestone complete' : 'Ready to plan');
|
|
1300
1334
|
stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Plan', 'Plan', 'Not started');
|
|
1301
|
-
|
|
1302
|
-
|
|
1335
|
+
const lastActivityDescription = `Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`;
|
|
1336
|
+
if (/^Last activity:/m.test(stateContent)) {
|
|
1337
|
+
stateContent =
|
|
1338
|
+
stateReplaceField(stateContent, 'Last activity', `${today} — ${lastActivityDescription}`) ||
|
|
1339
|
+
stateContent;
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
stateContent =
|
|
1343
|
+
stateReplaceField(stateContent, 'Last Activity', today) ||
|
|
1344
|
+
stateContent;
|
|
1345
|
+
}
|
|
1346
|
+
stateContent =
|
|
1347
|
+
stateReplaceField(stateContent, 'Last Activity Description', lastActivityDescription) ||
|
|
1348
|
+
stateContent;
|
|
1303
1349
|
const completedRaw = stateExtractField(stateContent, 'Completed Phases');
|
|
1304
1350
|
if (completedRaw !== null) {
|
|
1305
1351
|
let newCompleted = parseInt(completedRaw, 10);
|
|
@@ -241,6 +241,15 @@ function validateProhibitionResolution(resolution) {
|
|
|
241
241
|
* and are intentionally NOT projected into the plan block (which is keyed on the must-NOT
|
|
242
242
|
* statement, not the source requirement). A non-array input projects to `[]` (fail-soft on the
|
|
243
243
|
* empty/zero-prohibition case), never a throw.
|
|
244
|
+
*
|
|
245
|
+
* An OPTIONAL wired-check descriptor (#1278) projects as the LOCKED flat scalar keys
|
|
246
|
+
* `check_kind`/`check_target`/`check_rule` (NEVER a nested `check:{}` object; `failFirst` is never
|
|
247
|
+
* projected). These ride the EXISTING continuation-KV path of `parseMustHavesBlock`
|
|
248
|
+
* (src/frontmatter.cts:344) with NO shared-parser rewrite (IMPL-SCOPING §3 Option 1). The keys are
|
|
249
|
+
* emitted ONLY for a well-formed descriptor (valid `check_kind` + non-empty `check_target`; plus
|
|
250
|
+
* `check_rule` only for a lint-rule that carries one); a descriptor-less or under-specified item is
|
|
251
|
+
* byte-identical to today (CHK-07), so an under-specified descriptor projects absent and fails closed
|
|
252
|
+
* at the producer downstream (CHK-06), never as a partial-but-locatable green.
|
|
244
253
|
*/
|
|
245
254
|
function projectProhibitions(items) {
|
|
246
255
|
if (!Array.isArray(items))
|
|
@@ -259,6 +268,27 @@ function projectProhibitions(items) {
|
|
|
259
268
|
entry.verification = String(p.verification);
|
|
260
269
|
if (p.reason != null && String(p.reason).trim())
|
|
261
270
|
entry.reason = String(p.reason);
|
|
271
|
+
// Optional wired-check descriptor (#1278): emit flat scalars ONLY when well-formed. A valid kind
|
|
272
|
+
// plus a non-empty target is the minimum; under that bar nothing is emitted (CHK-07 byte-identity,
|
|
273
|
+
// and the producer fails closed on the absent descriptor — CHK-06).
|
|
274
|
+
const kind = p.check_kind;
|
|
275
|
+
const targetOk = typeof p.check_target === 'string' && p.check_target.trim() !== '';
|
|
276
|
+
if ((kind === 'node-test' || kind === 'lint-rule') && targetOk) {
|
|
277
|
+
entry.check_kind = kind;
|
|
278
|
+
entry.check_target = String(p.check_target);
|
|
279
|
+
// `check_rule` rides only the lint-rule path (node-test never carries one); a lint-rule missing
|
|
280
|
+
// its rule leaves check_rule absent so the producer's fail-closed locate rejects it (CHK-06).
|
|
281
|
+
if (kind === 'lint-rule' && typeof p.check_rule === 'string' && p.check_rule.trim() !== '') {
|
|
282
|
+
entry.check_rule = String(p.check_rule);
|
|
283
|
+
}
|
|
284
|
+
// `check_violation_fixture` (#1346) rides BOTH kinds — it's what the #1279 prover machine-proves
|
|
285
|
+
// fail-first against. Emit ONLY a non-empty fixture (a blank one projects absent so green still
|
|
286
|
+
// hard-gates downstream — never a partial green); meaningless without the descriptor, so it lives
|
|
287
|
+
// inside this well-formed-descriptor branch.
|
|
288
|
+
if (typeof p.check_violation_fixture === 'string' && p.check_violation_fixture.trim() !== '') {
|
|
289
|
+
entry.check_violation_fixture = String(p.check_violation_fixture);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
262
292
|
out.push(entry);
|
|
263
293
|
}
|
|
264
294
|
return out;
|
|
@@ -270,11 +300,10 @@ function projectProhibitions(items) {
|
|
|
270
300
|
* This is the cheap safety guarantee: a well-formed prohibition that reaches verify-phase with NO
|
|
271
301
|
* wired enforcement evidence can NEVER be a silent pass. It is `{ status: 'unverified', flagged:
|
|
272
302
|
* true }` — never `green` — exactly like an unresolved judgment item. The HEAVY half (a real
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* evidence — test- or judgment-tier — disposes as flagged-unverified.
|
|
303
|
+
* negative-test enforcement mechanism that, given evidence, flips a test-tier item to green) was OUT
|
|
304
|
+
* of #644 scope and LANDED in #1259 as the `prohibition-enforcement` producer (it builds the
|
|
305
|
+
* `enforcementEvidence` this helper reads). This helper's policy is unchanged: ANY prohibition
|
|
306
|
+
* without enforcement evidence — test- or judgment-tier — disposes as flagged-unverified.
|
|
278
307
|
*
|
|
279
308
|
* The function is pure: same input always yields the same disposition (no LLM judgment, ADR-550
|
|
280
309
|
* D5). The LLM-judge soft-gate for judgment-tier items is a verify-phase PROSE concern (the
|
|
@@ -287,23 +316,23 @@ function dispositionForProhibition(prohibition, context = {}) {
|
|
|
287
316
|
const evidence = Array.isArray(context.enforcementEvidence) ? context.enforcementEvidence : [];
|
|
288
317
|
const hasEnforcement = evidence.length > 0;
|
|
289
318
|
// FAIL CLOSED: no wired enforcement evidence -> flagged unverified, never green. This holds for
|
|
290
|
-
// every tier
|
|
291
|
-
//
|
|
292
|
-
// be silently skipped.
|
|
319
|
+
// every tier (the producer that builds enforcement evidence for a test-tier item — the
|
|
320
|
+
// `prohibition-enforcement` module — landed in #1259). The guard the safety assertion proves: an
|
|
321
|
+
// unwired item can never be silently skipped.
|
|
293
322
|
if (!hasEnforcement) {
|
|
294
323
|
return {
|
|
295
324
|
status: 'unverified',
|
|
296
325
|
flagged: true,
|
|
297
326
|
tier,
|
|
298
327
|
reason: tier === 'test'
|
|
299
|
-
? 'test-tier prohibition has no wired enforcement
|
|
328
|
+
? 'test-tier prohibition has no passing wired enforcement check — flagged unverified (fail-closed; never a silent pass, ADR-550 D5d)'
|
|
300
329
|
: 'prohibition has no enforcement evidence — flagged unverified (fail-closed; never a silent pass, ADR-550 D5d)',
|
|
301
330
|
};
|
|
302
331
|
}
|
|
303
332
|
// D4 GUARD: a judgment-tier (or unknown-tier) prohibition is NEVER a silent green from this
|
|
304
333
|
// deterministic helper — it always routes to human/LLM judgment review (ADR-550 D4; verify-phase.md).
|
|
305
|
-
// Only a test-tier item with wired enforcement evidence may go green
|
|
306
|
-
//
|
|
334
|
+
// Only a test-tier item with wired enforcement evidence may go green; the producer that supplies
|
|
335
|
+
// that evidence (`prohibition-enforcement`, #1259) runs the wired check and requires a genuine pass.
|
|
307
336
|
if (tier === 'test') {
|
|
308
337
|
return {
|
|
309
338
|
status: 'green',
|
|
@@ -20,8 +20,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
20
20
|
const node_path_1 = __importDefault(require("node:path"));
|
|
21
21
|
const node_os_1 = __importDefault(require("node:os"));
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
-
const
|
|
24
|
-
const { output, error
|
|
23
|
+
const io = require("./io.cjs");
|
|
24
|
+
const { output, error } = io;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
+
const configLoader = require("./config-loader.cjs");
|
|
27
|
+
const { loadConfig } = configLoader;
|
|
25
28
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
26
29
|
const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
|
|
27
30
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|