@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
|
@@ -55,32 +55,59 @@ function _readRawConfigKey(filePath, dotKey) {
|
|
|
55
55
|
return { found: false, value: undefined };
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the raw value for a dotted config key using the four-level precedence
|
|
60
|
+
* walk. Returns { found, value } with the RAW value (not coerced to boolean),
|
|
61
|
+
* so callers can decide how to interpret the value (boolean gate vs. raw config
|
|
62
|
+
* value for numeric/string settings like security_asvs_level).
|
|
63
|
+
*
|
|
64
|
+
* Precedence (mirrors _resolveActivationValue):
|
|
65
|
+
* 1. loadConfig result (config arg) — guarded nested-lookup.
|
|
66
|
+
* 2. Workstream config.json at planningDir(cwd)/config.json.
|
|
67
|
+
* 3. Root config.json at planningRoot(cwd)/config.json (only if path differs).
|
|
68
|
+
* 4. registry.configSchema[dotKey].default — schema default.
|
|
69
|
+
* 5. Absent → { found: false, value: undefined }.
|
|
70
|
+
*/
|
|
71
|
+
function resolveConfigKey(dotKey, opts) {
|
|
72
|
+
const { config, cwd, registry } = opts;
|
|
73
|
+
// Level 1: loadConfig result
|
|
59
74
|
const fromConfig = _getNestedConfigValue(config, dotKey);
|
|
60
75
|
if (fromConfig.found)
|
|
61
|
-
return
|
|
76
|
+
return { found: true, value: fromConfig.value };
|
|
77
|
+
// Level 2 + 3: raw config.json files (only when cwd is available)
|
|
62
78
|
if (cwd) {
|
|
63
79
|
const wsConfigPath = node_path_1.default.join(planningDir(cwd), 'config.json');
|
|
64
80
|
const rootConfigPath = node_path_1.default.join(planningRoot(cwd), 'config.json');
|
|
65
81
|
const fromWs = _readRawConfigKey(wsConfigPath, dotKey);
|
|
66
82
|
if (fromWs.found)
|
|
67
|
-
return
|
|
83
|
+
return { found: true, value: fromWs.value };
|
|
68
84
|
if (wsConfigPath !== rootConfigPath) {
|
|
69
85
|
const fromRoot = _readRawConfigKey(rootConfigPath, dotKey);
|
|
70
86
|
if (fromRoot.found)
|
|
71
|
-
return
|
|
87
|
+
return { found: true, value: fromRoot.value };
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
// Level 4: registry configSchema default
|
|
91
|
+
const schemaMap = registry['configSchema'];
|
|
92
|
+
if (schemaMap && typeof schemaMap === 'object' && !Array.isArray(schemaMap)
|
|
93
|
+
&& Object.prototype.hasOwnProperty.call(schemaMap, dotKey)) {
|
|
94
|
+
const schemaEntry = schemaMap[dotKey];
|
|
95
|
+
if (schemaEntry && typeof schemaEntry === 'object' && schemaEntry !== null) {
|
|
96
|
+
const def = schemaEntry['default'];
|
|
97
|
+
if (def !== undefined)
|
|
98
|
+
return { found: true, value: def };
|
|
99
|
+
}
|
|
79
100
|
}
|
|
80
|
-
|
|
101
|
+
// Level 5: absent
|
|
102
|
+
return { found: false, value: undefined };
|
|
103
|
+
}
|
|
104
|
+
function _resolveActivationValue(dotKey, config, cwd, registry) {
|
|
105
|
+
const r = resolveConfigKey(dotKey, { config, cwd, registry });
|
|
106
|
+
return r.found ? Boolean(r.value) : false;
|
|
81
107
|
}
|
|
82
108
|
module.exports = {
|
|
83
109
|
_getNestedConfigValue,
|
|
84
110
|
_readRawConfigKey,
|
|
85
111
|
_resolveActivationValue,
|
|
112
|
+
resolveConfigKey,
|
|
86
113
|
};
|
|
@@ -762,6 +762,7 @@ const capabilities = {
|
|
|
762
762
|
"graphify"
|
|
763
763
|
],
|
|
764
764
|
"agents": [],
|
|
765
|
+
"activationKey": "graphify.enabled",
|
|
765
766
|
"config": {
|
|
766
767
|
"graphify.enabled": {
|
|
767
768
|
"type": "boolean",
|
|
@@ -845,6 +846,7 @@ const capabilities = {
|
|
|
845
846
|
},
|
|
846
847
|
"skills": [],
|
|
847
848
|
"agents": [],
|
|
849
|
+
"activationKey": "intel.enabled",
|
|
848
850
|
"config": {
|
|
849
851
|
"intel.enabled": {
|
|
850
852
|
"type": "boolean",
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
* pure, config-only resolution with no filesystem I/O.
|
|
19
19
|
* cmdCapabilityState is the I/O handler.
|
|
20
20
|
*
|
|
21
|
-
* Dependencies (leaf modules only — no
|
|
21
|
+
* Dependencies (leaf modules only — no circular risk):
|
|
22
22
|
* - node:path
|
|
23
|
-
* - ./
|
|
23
|
+
* - ./io.cjs (output, error)
|
|
24
24
|
* - ./capability-activation.cjs (_resolveActivationValue)
|
|
25
25
|
* - ./install-profiles.cjs (readActiveProfile, loadSkillsManifest, resolveProfile)
|
|
26
26
|
* - ./surface.cjs (resolveSurface)
|
|
27
27
|
* - ./config-loader.cjs (loadConfig)
|
|
28
28
|
* - ./runtime-homes.cjs (getGlobalConfigDir — for runtimeConfigDir auto-detection)
|
|
29
|
+
* - ./runtime-slash.cjs (resolveRuntime — GSD_RUNTIME > config.runtime > 'claude' precedence)
|
|
29
30
|
* - capability-registry.cjs (loaded at call time)
|
|
30
31
|
*/
|
|
31
32
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
@@ -34,8 +35,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
35
|
const node_path_1 = __importDefault(require("node:path"));
|
|
35
36
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
36
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
-
const
|
|
38
|
-
const { output: coreOutput, error: coreError } =
|
|
38
|
+
const ioMod = require("./io.cjs");
|
|
39
|
+
const { output: coreOutput, error: coreError } = ioMod;
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
41
|
const activationMod = require("./capability-activation.cjs");
|
|
41
42
|
const { _resolveActivationValue } = activationMod;
|
|
@@ -135,6 +136,19 @@ function resolveCapabilityState(input) {
|
|
|
135
136
|
surfaced = skills.every((s) => surfacedSkills.has(s));
|
|
136
137
|
}
|
|
137
138
|
const enabled = installed && surfaced;
|
|
139
|
+
// ── per-capability config activation ──────────────────────────────────────
|
|
140
|
+
// Resolve the capability's own activationKey (if present). This is the
|
|
141
|
+
// config-level toggle that gates the whole capability — separate from the
|
|
142
|
+
// per-hook `when` keys that gate individual hooks. When activationKey is
|
|
143
|
+
// absent, configActivation defaults to true (no config gate on the cap).
|
|
144
|
+
// active = enabled && configActivation (enabled unchanged: installed && surfaced)
|
|
145
|
+
const activationKey = typeof capObj['activationKey'] === 'string' && capObj['activationKey'].length > 0
|
|
146
|
+
? capObj['activationKey']
|
|
147
|
+
: undefined;
|
|
148
|
+
const configActivation = activationKey !== undefined
|
|
149
|
+
? _resolveActivationValue(activationKey, config, cwd, registry)
|
|
150
|
+
: true;
|
|
151
|
+
const active = enabled && configActivation;
|
|
138
152
|
// ── hooks ──────────────────────────────────────────────────────────────────
|
|
139
153
|
// Collect from steps, gates, contributions. Each may have a `when` key.
|
|
140
154
|
// Activation semantics (mirrors loop-resolver.isActive exactly):
|
|
@@ -165,7 +179,12 @@ function resolveCapabilityState(input) {
|
|
|
165
179
|
// (mirrors loop-resolver.isActive: `typeof when !== 'string' || when.length === 0` → false)
|
|
166
180
|
configured = false;
|
|
167
181
|
}
|
|
168
|
-
|
|
182
|
+
// Hook active = capability-level active AND hook's own config gate.
|
|
183
|
+
// The capability's `active` constant (= enabled && configActivation) is
|
|
184
|
+
// used here so that a config-disabled capability (active=false) cannot
|
|
185
|
+
// produce active hooks even when the hook's own `when` is unconditional
|
|
186
|
+
// (configured=true). The capability gate cascades to all its hooks.
|
|
187
|
+
hooks.push({ point, kind, when: whenRaw, configured, active: active && configured });
|
|
169
188
|
}
|
|
170
189
|
}
|
|
171
190
|
const stepsRaw = capObj['steps'];
|
|
@@ -174,7 +193,7 @@ function resolveCapabilityState(input) {
|
|
|
174
193
|
processHooks(Array.isArray(stepsRaw) ? stepsRaw : [], 'step');
|
|
175
194
|
processHooks(Array.isArray(gatesRaw) ? gatesRaw : [], 'gate');
|
|
176
195
|
processHooks(Array.isArray(contributionsRaw) ? contributionsRaw : [], 'contribution');
|
|
177
|
-
results.push({ id: capId, tier, skills, installed, surfaced, enabled, hooks });
|
|
196
|
+
results.push({ id: capId, tier, skills, installed, surfaced, enabled, active, hooks });
|
|
178
197
|
}
|
|
179
198
|
// Deterministic sort by id for stable output across calls
|
|
180
199
|
results.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
@@ -283,16 +302,20 @@ function _resolveManifest(commandsGsdDir, configDir) {
|
|
|
283
302
|
}
|
|
284
303
|
/**
|
|
285
304
|
* Command entry point: resolve install profile, surface, and config; compute
|
|
286
|
-
* capability state; emit the envelope via
|
|
305
|
+
* capability state; emit the envelope via io.output.
|
|
287
306
|
*
|
|
288
307
|
* Envelope: { runtimeConfigDir, warnings?: string[], capabilities: CapabilityStateEntry[] }
|
|
289
308
|
*
|
|
290
309
|
* runtimeConfigDir resolution (when not provided or empty):
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
310
|
+
* Detects the active runtime via the canonical precedence:
|
|
311
|
+
* process.env.GSD_RUNTIME → config.runtime → 'claude'
|
|
312
|
+
* (using resolveRuntime() from runtime-slash.cjs, the same precedence used
|
|
313
|
+
* by profile-output.cjs and the rest of the runtime resolution chain).
|
|
314
|
+
* Then calls getGlobalConfigDir(detectedRuntime) from runtime-homes.cjs —
|
|
315
|
+
* the same resolver used by install.js. This correctly handles all supported
|
|
316
|
+
* runtimes (claude, codex, cursor, gemini, opencode, grok, etc.) and their
|
|
317
|
+
* env-var overrides (CLAUDE_CONFIG_DIR, CODEX_HOME, CURSOR_CONFIG_DIR, …).
|
|
318
|
+
* Defaults to ~/.claude if either resolver throws.
|
|
296
319
|
*
|
|
297
320
|
* Failure surfacing: genuine resolution failures (manifest/profile/surface
|
|
298
321
|
* errors) are reported in the `warnings` array in the envelope. The output
|
|
@@ -308,15 +331,18 @@ function _resolveManifest(commandsGsdDir, configDir) {
|
|
|
308
331
|
* Providing a value without a next token (e.g. the flag
|
|
309
332
|
* is last in argv with no following value) should be
|
|
310
333
|
* caught by the caller before invoking this function.
|
|
311
|
-
* @param raw Whether to emit raw JSON (
|
|
334
|
+
* @param raw Whether to emit raw JSON (io.output raw mode)
|
|
312
335
|
* @param _options Reserved for future use
|
|
313
336
|
*/
|
|
314
|
-
function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
337
|
+
function resolveCapabilityRuntimeState(cwd, runtimeConfigDir, configOverride) {
|
|
315
338
|
const warnings = [];
|
|
316
339
|
// Resolve runtimeConfigDir using the canonical runtime-homes resolver.
|
|
317
|
-
// When not provided,
|
|
318
|
-
//
|
|
319
|
-
//
|
|
340
|
+
// When not provided, the active runtime is detected via the canonical
|
|
341
|
+
// precedence: process.env.GSD_RUNTIME → config.runtime → 'claude'
|
|
342
|
+
// (mirrors resolveRuntime() from runtime-slash.cjs and the precedence used
|
|
343
|
+
// by profile-output.cjs and the rest of the runtime resolution chain).
|
|
344
|
+
// getGlobalConfigDir(detectedRuntime) is then called, which honours the
|
|
345
|
+
// runtime-specific env-var override (CLAUDE_CONFIG_DIR, CODEX_HOME,
|
|
320
346
|
// CURSOR_CONFIG_DIR, GROK_AGENTS_HOME, etc.) correctly and without
|
|
321
347
|
// fabricating env vars that don't exist upstream.
|
|
322
348
|
let resolvedConfigDir = runtimeConfigDir || '';
|
|
@@ -324,13 +350,13 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
|
324
350
|
try {
|
|
325
351
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
326
352
|
const runtimeHomes = require('./runtime-homes.cjs');
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
resolvedConfigDir = runtimeHomes.getGlobalConfigDir(
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
354
|
+
const runtimeSlash = require('./runtime-slash.cjs');
|
|
355
|
+
// Detect the active runtime via GSD_RUNTIME → config.runtime → 'claude'.
|
|
356
|
+
// resolveRuntime reads config.json directly (no side effects) and returns
|
|
357
|
+
// a lowercased canonical runtime name.
|
|
358
|
+
const detectedRuntime = runtimeSlash.resolveRuntime(cwd);
|
|
359
|
+
resolvedConfigDir = runtimeHomes.getGlobalConfigDir(detectedRuntime);
|
|
334
360
|
}
|
|
335
361
|
catch {
|
|
336
362
|
// Defensive fallback: use ~/.claude if the canonical resolver throws.
|
|
@@ -392,12 +418,20 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
|
392
418
|
surfacedSkills = new Set();
|
|
393
419
|
}
|
|
394
420
|
// ── Load config ───────────────────────────────────────────────────────────────
|
|
421
|
+
// When the caller already holds a loadConfig snapshot (e.g. cmdLoopRenderHooks),
|
|
422
|
+
// accept it via configOverride so capability `active` and hook resolution
|
|
423
|
+
// share the SAME config object — single snapshot, no TOCTOU window.
|
|
395
424
|
let config;
|
|
396
|
-
|
|
397
|
-
config =
|
|
425
|
+
if (configOverride !== undefined) {
|
|
426
|
+
config = configOverride;
|
|
398
427
|
}
|
|
399
|
-
|
|
400
|
-
|
|
428
|
+
else {
|
|
429
|
+
try {
|
|
430
|
+
config = loadConfig(cwd);
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
config = {};
|
|
434
|
+
}
|
|
401
435
|
}
|
|
402
436
|
// ── Resolve state ────────────────────────────────────────────────────────────
|
|
403
437
|
const result = resolveCapabilityState({
|
|
@@ -410,8 +444,6 @@ function resolveCapabilityRuntimeState(cwd, runtimeConfigDir) {
|
|
|
410
444
|
return {
|
|
411
445
|
runtimeConfigDir: resolvedConfigDir,
|
|
412
446
|
warnings,
|
|
413
|
-
registry,
|
|
414
|
-
config,
|
|
415
447
|
capabilities: result.capabilities,
|
|
416
448
|
};
|
|
417
449
|
}
|
|
@@ -431,9 +463,27 @@ function cmdCapabilityState(cwd, runtimeConfigDir, raw, _options = {}) {
|
|
|
431
463
|
}
|
|
432
464
|
coreOutput(envelope, raw);
|
|
433
465
|
}
|
|
466
|
+
/**
|
|
467
|
+
* Convenience predicate: returns true if the capability identified by `capId`
|
|
468
|
+
* is active (installed && surfaced && config-enabled) in the current runtime
|
|
469
|
+
* environment at `cwd`.
|
|
470
|
+
*
|
|
471
|
+
* Internally calls `resolveCapabilityRuntimeState(cwd, undefined)` and returns
|
|
472
|
+
* the `active` field of the matching CapabilityStateEntry.
|
|
473
|
+
* Returns `false` when the capability is not found in the registry.
|
|
474
|
+
*
|
|
475
|
+
* @param capId Capability identifier (e.g. 'graphify', 'intel')
|
|
476
|
+
* @param cwd Project root directory for config resolution
|
|
477
|
+
*/
|
|
478
|
+
function isCapabilityActive(capId, cwd) {
|
|
479
|
+
const result = resolveCapabilityRuntimeState(cwd, undefined);
|
|
480
|
+
const entry = result.capabilities.find((c) => c.id === capId);
|
|
481
|
+
return entry !== undefined ? entry.active : false;
|
|
482
|
+
}
|
|
434
483
|
module.exports = {
|
|
435
484
|
resolveCapabilityState,
|
|
436
485
|
resolveCapabilityRuntimeState,
|
|
486
|
+
isCapabilityActive,
|
|
437
487
|
cmdCapabilityState,
|
|
438
488
|
// Exported for tests
|
|
439
489
|
_resolveCommandsGsdDir,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - re-resolve: always calls resolveCapabilityRuntimeState for the return value
|
|
15
15
|
*
|
|
16
16
|
* Dependencies (leaf modules only — no circular risk):
|
|
17
|
-
* - ./
|
|
17
|
+
* - ./io.cjs (output, error)
|
|
18
18
|
* - ./capability-state.cjs (resolveCapabilityRuntimeState, _resolveManifest, _resolveCommandsGsdDir)
|
|
19
19
|
* - ./surface.cjs (readSurface, writeSurface, applySurface)
|
|
20
20
|
* - ./install-profiles.cjs (readActiveProfile)
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* - capability-registry.cjs (loaded at call time)
|
|
24
24
|
*/
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
|
-
const
|
|
27
|
-
const { output: coreOutput, error: coreError } =
|
|
26
|
+
const ioMod = require("./io.cjs");
|
|
27
|
+
const { output: coreOutput, error: coreError } = ioMod;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const capabilityStateMod = require("./capability-state.cjs");
|
|
30
30
|
const { resolveCapabilityRuntimeState, _resolveManifest, _resolveCommandsGsdDir } = capabilityStateMod;
|
|
@@ -71,7 +71,8 @@ function setCapabilityState(cwd, runtimeConfigDir, desired, opts) {
|
|
|
71
71
|
const before = resolveCapabilityRuntimeState(cwd, runtimeConfigDir);
|
|
72
72
|
const resolvedConfigDir = before.runtimeConfigDir;
|
|
73
73
|
// ── Load registry ─────────────────────────────────────────────────────────
|
|
74
|
-
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
75
|
+
const registry = require('./capability-registry.cjs');
|
|
75
76
|
const capabilitiesMap = (registry['capabilities'] && typeof registry['capabilities'] === 'object' && !Array.isArray(registry['capabilities'])
|
|
76
77
|
? registry['capabilities']
|
|
77
78
|
: {});
|
|
@@ -13,8 +13,14 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_child_process_1 = require("node:child_process");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
-
const
|
|
17
|
-
const { output, error, ERROR_REASON } =
|
|
16
|
+
const io = require("./io.cjs");
|
|
17
|
+
const { output, error, ERROR_REASON } = io;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
20
|
+
const { planningDir } = planningWorkspaceMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
23
|
+
const { findPhaseInternal } = phaseLocatorMod;
|
|
18
24
|
const decisions_cjs_1 = require("./decisions.cjs");
|
|
19
25
|
const ui_safety_gate_cjs_1 = require("./ui-safety-gate.cjs");
|
|
20
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -26,6 +32,7 @@ const { getRoadmapPhaseWithFallback } = roadmapModule;
|
|
|
26
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
33
|
const gapCheckerModule = require("./gap-checker.cjs");
|
|
28
34
|
const { runGapAnalysis } = gapCheckerModule;
|
|
35
|
+
const prohibition_enforcement_cjs_1 = require("./prohibition-enforcement.cjs");
|
|
29
36
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
30
37
|
function normalizePhrase(text) {
|
|
31
38
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
@@ -341,7 +348,7 @@ function cmdDecisionCoverageVerify(projectDir, args, raw) {
|
|
|
341
348
|
* Invocable as: gsd_run check ui-plan-gate <phase>
|
|
342
349
|
*
|
|
343
350
|
* Uses checkUiPresence from ui-safety-gate.cjs — does NOT reimplement frontend detection.
|
|
344
|
-
* Uses
|
|
351
|
+
* Uses getRoadmapPhaseWithFallback + findPhaseInternal from leaf modules for phase data.
|
|
345
352
|
*/
|
|
346
353
|
function findUiSpecInDir(phaseDir) {
|
|
347
354
|
if (!phaseDir || !node_fs_1.default.existsSync(phaseDir))
|
|
@@ -368,7 +375,7 @@ function findUiSpecInDir(phaseDir) {
|
|
|
368
375
|
* surface the miss — we do NOT silently degrade to frontend:false if the roadmap
|
|
369
376
|
* exists but the phase header is absent.
|
|
370
377
|
* (b) Runs checkUiPresence (frontend detection) — no reimplementation.
|
|
371
|
-
* (c) Resolves the phase directory via
|
|
378
|
+
* (c) Resolves the phase directory via findPhaseInternal (phase-locator.cjs); checks for *-UI-SPEC.md.
|
|
372
379
|
*
|
|
373
380
|
* Returns: { frontend, hasUiSpec, block, uiSpecPath, phaseLookupFailed }
|
|
374
381
|
* block = frontend && !hasUiSpec
|
|
@@ -385,10 +392,8 @@ function computeUiPlanGate(projectDir, phase) {
|
|
|
385
392
|
const section = getRoadmapPhaseWithFallback(projectDir, phase);
|
|
386
393
|
if (section === null) {
|
|
387
394
|
// Distinguish: ROADMAP.md missing (no-roadmap project) vs phase not found in ROADMAP.
|
|
388
|
-
//
|
|
389
|
-
const planDir =
|
|
390
|
-
? core['planningDir'](projectDir)
|
|
391
|
-
: node_path_1.default.join(projectDir, '.planning');
|
|
395
|
+
// planningDir(cwd) resolves the .planning/ root for workstream-aware paths.
|
|
396
|
+
const planDir = planningDir(projectDir);
|
|
392
397
|
const roadmapPath = node_path_1.default.join(planDir, 'ROADMAP.md');
|
|
393
398
|
if (node_fs_1.default.existsSync(roadmapPath)) {
|
|
394
399
|
// ROADMAP.md exists but phase was not found → surface the miss
|
|
@@ -405,24 +410,20 @@ function computeUiPlanGate(projectDir, phase) {
|
|
|
405
410
|
const presenceResult = (0, ui_safety_gate_cjs_1.checkUiPresence)(phaseSection);
|
|
406
411
|
const frontend = presenceResult.hasUI;
|
|
407
412
|
// (c) Resolve phase directory via findPhaseInternal and check for *-UI-SPEC.md
|
|
408
|
-
const coreModule = core;
|
|
409
413
|
let phaseDir = '';
|
|
410
414
|
try {
|
|
411
|
-
const
|
|
412
|
-
if (typeof
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (relDir) {
|
|
419
|
-
phaseDir = node_path_1.default.resolve(projectDir, relDir);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
else if (typeof result === 'string') {
|
|
423
|
-
phaseDir = result;
|
|
415
|
+
const result = findPhaseInternal(projectDir, phase);
|
|
416
|
+
if (result && typeof result === 'object') {
|
|
417
|
+
// findPhaseInternal returns { directory: '<relative-posix-path>', ... }
|
|
418
|
+
// directory is relative to cwd — resolve it to absolute.
|
|
419
|
+
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
|
420
|
+
if (relDir) {
|
|
421
|
+
phaseDir = node_path_1.default.resolve(projectDir, relDir);
|
|
424
422
|
}
|
|
425
423
|
}
|
|
424
|
+
else if (typeof result === 'string') {
|
|
425
|
+
phaseDir = result;
|
|
426
|
+
}
|
|
426
427
|
}
|
|
427
428
|
catch { /* phase dir lookup failure → hasUiSpec=false */ }
|
|
428
429
|
const uiSpecPath = findUiSpecInDir(phaseDir);
|
|
@@ -475,7 +476,7 @@ const UI_PATH_PATTERNS_RE = /\/(components|pages|views|screens|layouts|ui|fronte
|
|
|
475
476
|
* same lookup as computeUiPlanGate — to determine if this is a frontend phase.
|
|
476
477
|
* (b) Runs checkUiPresence (frontend detection) — no reimplementation.
|
|
477
478
|
* (c) Checks git diff HEAD~1..HEAD for UI file changes in the current worktree.
|
|
478
|
-
* (d) Resolves the phase directory via
|
|
479
|
+
* (d) Resolves the phase directory via findPhaseInternal (phase-locator.cjs); checks for *-UI-SPEC.md.
|
|
479
480
|
*
|
|
480
481
|
* Returns: { frontend, hasUiFiles, hasUiSpec, block, message?, phaseLookupFailed? }
|
|
481
482
|
* block = frontend && hasUiFiles && !hasUiSpec
|
|
@@ -488,9 +489,7 @@ function computeUiSafetyGate(projectDir, phase) {
|
|
|
488
489
|
try {
|
|
489
490
|
const section = getRoadmapPhaseWithFallback(projectDir, phase);
|
|
490
491
|
if (section === null) {
|
|
491
|
-
const planDir =
|
|
492
|
-
? core['planningDir'](projectDir)
|
|
493
|
-
: node_path_1.default.join(projectDir, '.planning');
|
|
492
|
+
const planDir = planningDir(projectDir);
|
|
494
493
|
const roadmapPath = node_path_1.default.join(planDir, 'ROADMAP.md');
|
|
495
494
|
if (node_fs_1.default.existsSync(roadmapPath)) {
|
|
496
495
|
phaseLookupFailed = true;
|
|
@@ -519,22 +518,18 @@ function computeUiSafetyGate(projectDir, phase) {
|
|
|
519
518
|
}
|
|
520
519
|
catch { /* git unavailable or no prior commit — treat as no UI files changed */ }
|
|
521
520
|
// (d) Resolve phase directory and check for *-UI-SPEC.md (same as computeUiPlanGate)
|
|
522
|
-
const coreModule = core;
|
|
523
521
|
let phaseDir = '';
|
|
524
522
|
try {
|
|
525
|
-
const
|
|
526
|
-
if (typeof
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
|
|
530
|
-
if (relDir) {
|
|
531
|
-
phaseDir = node_path_1.default.resolve(projectDir, relDir);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else if (typeof result === 'string') {
|
|
535
|
-
phaseDir = result;
|
|
523
|
+
const result = findPhaseInternal(projectDir, phase);
|
|
524
|
+
if (result && typeof result === 'object') {
|
|
525
|
+
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
|
526
|
+
if (relDir) {
|
|
527
|
+
phaseDir = node_path_1.default.resolve(projectDir, relDir);
|
|
536
528
|
}
|
|
537
529
|
}
|
|
530
|
+
else if (typeof result === 'string') {
|
|
531
|
+
phaseDir = result;
|
|
532
|
+
}
|
|
538
533
|
}
|
|
539
534
|
catch { /* phase dir lookup failure → hasUiSpec=false */ }
|
|
540
535
|
const uiSpecPath = findUiSpecInDir(phaseDir);
|
|
@@ -567,20 +562,16 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
|
|
|
567
562
|
return;
|
|
568
563
|
}
|
|
569
564
|
// Resolve phase directory
|
|
570
|
-
const coreModule = core;
|
|
571
565
|
let phaseDir = '';
|
|
572
566
|
try {
|
|
573
|
-
const
|
|
574
|
-
if (typeof
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
else if (typeof result === 'string') {
|
|
582
|
-
phaseDir = result;
|
|
583
|
-
}
|
|
567
|
+
const result = findPhaseInternal(projectDir, phase);
|
|
568
|
+
if (result && typeof result === 'object') {
|
|
569
|
+
const relDir = typeof result['directory'] === 'string' ? result['directory'] : '';
|
|
570
|
+
if (relDir)
|
|
571
|
+
phaseDir = node_path_1.default.resolve(projectDir, relDir);
|
|
572
|
+
}
|
|
573
|
+
else if (typeof result === 'string') {
|
|
574
|
+
phaseDir = result;
|
|
584
575
|
}
|
|
585
576
|
}
|
|
586
577
|
catch { /* phase dir lookup failure */ }
|
|
@@ -788,7 +779,15 @@ function routeCheckCommand({ args, cwd, raw }) {
|
|
|
788
779
|
cmdVerifyCodebaseDrift(cwd, raw);
|
|
789
780
|
return;
|
|
790
781
|
}
|
|
791
|
-
|
|
782
|
+
if (subcommand === 'prohibition-enforcement') {
|
|
783
|
+
// The deterministic test-tier prohibition PRODUCER/gate (#1259, ADR-550 D5d). Locates the
|
|
784
|
+
// wired mechanical check (node-test or lint-rule), confirms fail-first, runs it, builds
|
|
785
|
+
// enforcementEvidence, and emits the dispositionForProhibition verdict. Invocable as
|
|
786
|
+
// `gsd_run check prohibition-enforcement <request.json>`.
|
|
787
|
+
(0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
792
791
|
}
|
|
793
792
|
module.exports = {
|
|
794
793
|
routeCheckCommand,
|
|
@@ -13,8 +13,26 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
16
|
+
const ioMod = require("./io.cjs");
|
|
17
|
+
const { output, error } = ioMod;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const configLoaderMod = require("./config-loader.cjs");
|
|
20
|
+
const { loadConfig, isGitIgnored } = configLoaderMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
23
|
+
const { toPosixPath, generateSlugInternal, extractOneLinerFromBody } = coreUtilsMod;
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
26
|
+
const { normalizePhaseName, comparePhaseNum, extractPhaseToken } = phaseIdMod;
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
|
+
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
29
|
+
const { getArchivedPhaseDirs, findPhaseInternal } = phaseLocatorMod;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
|
+
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
32
|
+
const { extractCurrentMilestone, stripShippedMilestones: _stripShippedMilestones, getMilestoneInfo, getMilestonePhaseFilter, getRoadmapPhaseInternal } = roadmapParserMod;
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const modelResolverMod = require("./model-resolver.cjs");
|
|
35
|
+
const { resolveModelInternal, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
|
|
18
36
|
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
19
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
38
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
* normalizes legacy keys, applies the active-workstream overlay, validates
|
|
9
9
|
* against the config schema, and warns on unknown keys/profile overrides.
|
|
10
10
|
* Behaviour is preserved byte-for-behaviour from the prior location; only
|
|
11
|
-
* the module boundary moved. core.cjs re-
|
|
11
|
+
* the module boundary moved. The core.cjs re-export spine was retired in
|
|
12
|
+
* epic #1267; callers import loadConfig from config-loader.cjs directly.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Dependencies (leaf modules only — no core.cjs):
|
|
14
|
+
* Dependencies (leaf modules only):
|
|
16
15
|
* - node:fs / node:os / node:path (stdlib)
|
|
17
16
|
* - ./configuration.cjs (normalizeLegacyKeys, CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
|
|
18
17
|
* - ./config-schema.cjs (VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS)
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Imported by:
|
|
8
8
|
* - config.cjs (isValidConfigKey validator)
|
|
9
|
-
* - core.cjs
|
|
10
9
|
* - many tests (config-schema.property.test.cjs, bug-*, feat-*, etc.)
|
|
10
|
+
* (core.cjs re-export spine retired in epic #1267)
|
|
11
11
|
*
|
|
12
12
|
* See Phase 2 Cycle 5 (#3536) — schema manifest migration.
|
|
13
13
|
*
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* TypeScript type definitions for GSD project config — model_policy block.
|
|
4
4
|
*
|
|
5
5
|
* These types reflect the model_policy config shape consumed by
|
|
6
|
-
* resolveModelPolicy in
|
|
6
|
+
* resolveModelPolicy in model-resolver.cjs and validated by config-schema.cjs.
|
|
7
|
+
* (core.cjs re-export spine retired in epic #1267)
|
|
7
8
|
*
|
|
8
9
|
* See feat #49 (model_policy presets) and config-schema.manifest.json.
|
|
9
10
|
* Added under ADR-457: TS sources in src/ compile to CJS artifacts in
|
|
@@ -13,8 +13,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
-
const
|
|
17
|
-
const { output, error, ERROR_REASON
|
|
16
|
+
const io = require("./io.cjs");
|
|
17
|
+
const { output, error, ERROR_REASON } = io;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const configLoader = require("./config-loader.cjs");
|
|
20
|
+
const { CONFIG_DEFAULTS } = configLoader;
|
|
18
21
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
19
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
23
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -61,7 +61,13 @@ function parseDecisions(content) {
|
|
|
61
61
|
// in addition to numeric-only IDs (D-42). The first character after `D-` must
|
|
62
62
|
// be alphanumeric, so malformed shapes like `D--foo` or `D-_bar` are rejected.
|
|
63
63
|
// CJS callers consume {id, text} and ignore the optional extras.
|
|
64
|
-
|
|
64
|
+
// #1343: `[^:*]*` replaces the old `\s*` before `:**` so that a freeform run
|
|
65
|
+
// such as `(parenthetical)`, an em-dash, or other prose between the optional
|
|
66
|
+
// bracket-tag group and the closing `:**` is tolerated rather than silently
|
|
67
|
+
// dropping the whole decision. `[^:*]*` subsumes plain whitespace and stops
|
|
68
|
+
// correctly at `:**`. Capture groups 1 (id), 2 (bracket tags), 3 (text) are
|
|
69
|
+
// unchanged.
|
|
70
|
+
const bulletRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:\*\*\s*(.*)$/;
|
|
65
71
|
let current = null;
|
|
66
72
|
const flush = () => {
|
|
67
73
|
if (current) {
|
|
@@ -102,6 +108,18 @@ function parseDecisions(content) {
|
|
|
102
108
|
current = { id, text: bulletMatch[3], category, tags, trackable };
|
|
103
109
|
continue;
|
|
104
110
|
}
|
|
111
|
+
// Parse-miss guard (#1343): a line that looks like a `D-NN` decision bullet
|
|
112
|
+
// but failed `bulletRe` (e.g. a `:` or `*` inside the pre-colon run) must NOT
|
|
113
|
+
// be silently dropped — a narrowed trackable set lets a blocking coverage gate
|
|
114
|
+
// report a false pass. Surface it loudly instead.
|
|
115
|
+
if (/^\s*-\s+\*\*D-/.test(line)) {
|
|
116
|
+
// A malformed D-bullet still starts a (failed) new decision, so it ends the
|
|
117
|
+
// previous one — flush before warning so a following continuation line cannot
|
|
118
|
+
// be mis-appended to the prior valid decision.
|
|
119
|
+
flush();
|
|
120
|
+
console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
105
123
|
// Continuation line for current decision (indented with space OR tab,
|
|
106
124
|
// non-bullet, non-empty) — tab indentation must work too (review F12).
|
|
107
125
|
if (current && trimmed !== '' && !trimmed.startsWith('-') && /^[ \t]/.test(line)) {
|