@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
|
@@ -37,7 +37,32 @@ const HOST_INTEGRATION_AXES = Object.freeze({
|
|
|
37
37
|
stateIO: Object.freeze(['filesystem', 'sandboxed-storage', 'session-log-append']),
|
|
38
38
|
transport: Object.freeze(['mcp', 'native-extension']),
|
|
39
39
|
runtime: Object.freeze(['node', 'bun', 'sandboxed-web', 'python', 'go', 'rust', 'electron', 'other']),
|
|
40
|
-
subagentToolkit: Object.freeze(['full', 'read-only']),
|
|
40
|
+
subagentToolkit: Object.freeze(['full', 'read-only', 'built-in-only']),
|
|
41
|
+
// ADR-1239 amendment (#2481): how reasoning effort reaches this host.
|
|
42
|
+
// `argv` — deliverable as an argument on the host's own invocation.
|
|
43
|
+
// `none` — the host exposes no reasoning-effort mechanism.
|
|
44
|
+
// `undocumented` is NOT a member here; it is the corpus-wide sentinel above.
|
|
45
|
+
// A config-file-only surface deliberately has NO vocabulary member: the only
|
|
46
|
+
// host that ever had one (Gemini CLI's thinkingConfig) was removed as a sunset
|
|
47
|
+
// runtime in #1928/#1996, and neither its successor Antigravity CLI nor ZCode
|
|
48
|
+
// documents a reasoning setting. Adding a member with no host would be a guess.
|
|
49
|
+
effortSurface: Object.freeze(['argv', 'none']),
|
|
50
|
+
// ADR-1239 Codex-binding amendment (#2584): a `dispatch` sub-field — not a new
|
|
51
|
+
// axis — declaring how a host isolates concurrent same-wave executors.
|
|
52
|
+
// `harness-worktree` — the host's own harness creates + binds a git worktree
|
|
53
|
+
// per executor; GSD passes the host's own isolation flag and calls no git
|
|
54
|
+
// itself (host-driven fan-out).
|
|
55
|
+
// `orchestrator-worktree` — GSD itself process-spawns each executor with an
|
|
56
|
+
// explicit working directory into a worktree GSD created, validated, and
|
|
57
|
+
// merges (GSD-driven fan-out; concurrency is OS-level, not the host's).
|
|
58
|
+
// `none` — no isolation primitive; same-wave plans run inline/sequentially
|
|
59
|
+
// (the #853 flatten rule).
|
|
60
|
+
// `undocumented` is NOT a member here; it is the corpus-wide sentinel above.
|
|
61
|
+
// Mechanism-specific ("worktree"), not abstract — same "name only what a
|
|
62
|
+
// host actually has" rule that kept effortSurface from guessing a
|
|
63
|
+
// config-file member above. A future non-worktree isolation mechanism adds a
|
|
64
|
+
// `*-container` member then, evidence-backed.
|
|
65
|
+
isolation: Object.freeze(['harness-worktree', 'orchestrator-worktree', 'none']),
|
|
41
66
|
});
|
|
42
67
|
const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks', 'state', 'artifact']);
|
|
43
68
|
// ---------------------------------------------------------------------------
|
|
@@ -47,43 +72,47 @@ const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks',
|
|
|
47
72
|
const SAFE_DEFAULTS = {
|
|
48
73
|
embeddingMode: 'declarative',
|
|
49
74
|
commandSurface: 'prose-only',
|
|
50
|
-
dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false },
|
|
75
|
+
dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false, isolation: 'none' },
|
|
51
76
|
modelMode: 'passive',
|
|
52
77
|
hookBus: 'none',
|
|
53
78
|
stateIO: 'session-log-append',
|
|
54
79
|
transport: 'mcp',
|
|
55
80
|
runtime: 'node',
|
|
81
|
+
effortSurface: 'none',
|
|
56
82
|
};
|
|
57
83
|
const PROFILE_BASELINES = Object.freeze({
|
|
58
84
|
'programmatic-cli': Object.freeze({
|
|
59
85
|
embeddingMode: 'imperative',
|
|
60
86
|
commandSurface: 'slash-file',
|
|
61
|
-
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
|
87
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
|
|
62
88
|
modelMode: 'passive',
|
|
63
89
|
hookBus: 'host',
|
|
64
90
|
stateIO: 'filesystem',
|
|
65
91
|
transport: 'mcp',
|
|
66
92
|
runtime: 'node',
|
|
93
|
+
effortSurface: 'none',
|
|
67
94
|
}),
|
|
68
95
|
'declarative-cli': Object.freeze({
|
|
69
96
|
embeddingMode: 'declarative',
|
|
70
97
|
commandSurface: 'slash-file',
|
|
71
|
-
dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false }),
|
|
98
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false, isolation: 'none' }),
|
|
72
99
|
modelMode: 'passive',
|
|
73
100
|
hookBus: 'host',
|
|
74
101
|
stateIO: 'filesystem',
|
|
75
102
|
transport: 'mcp',
|
|
76
103
|
runtime: 'node',
|
|
104
|
+
effortSurface: 'none',
|
|
77
105
|
}),
|
|
78
106
|
'ide': Object.freeze({
|
|
79
107
|
embeddingMode: 'imperative',
|
|
80
108
|
commandSurface: 'palette',
|
|
81
|
-
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
|
|
109
|
+
dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
|
|
82
110
|
modelMode: 'active',
|
|
83
111
|
hookBus: 'engine',
|
|
84
112
|
stateIO: 'sandboxed-storage',
|
|
85
113
|
transport: 'mcp',
|
|
86
114
|
runtime: 'sandboxed-web',
|
|
115
|
+
effortSurface: 'none',
|
|
87
116
|
}),
|
|
88
117
|
});
|
|
89
118
|
// ---------------------------------------------------------------------------
|
|
@@ -136,6 +165,13 @@ function degradationFor(point, axes) {
|
|
|
136
165
|
return { level: 'degraded', fallback: 'flat dispatch — waves run inline' };
|
|
137
166
|
}
|
|
138
167
|
case 'model': {
|
|
168
|
+
// NOTE (#2481): the effortSurface axis is deliberately NOT folded into this
|
|
169
|
+
// level. `modelMode` has graded interface point 3 since Phase A, and every
|
|
170
|
+
// existing consumer reads it as "can GSD drive model selection". Widening it
|
|
171
|
+
// to also mean "…and deliver effort" silently redefines an established
|
|
172
|
+
// contract — an `active` host with no declared effort surface would flip
|
|
173
|
+
// from `full` to `absent`. Effort is negotiated on its own axis and read
|
|
174
|
+
// from `effective.effortSurface` by the consumers that care.
|
|
139
175
|
const mm = axes.modelMode;
|
|
140
176
|
if (mm === 'active')
|
|
141
177
|
return { level: 'full', fallback: '' };
|
|
@@ -199,12 +235,13 @@ const DEFAULT_ENGINE = {
|
|
|
199
235
|
axes: {
|
|
200
236
|
embeddingMode: 'imperative',
|
|
201
237
|
commandSurface: 'slash-file',
|
|
202
|
-
dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true },
|
|
238
|
+
dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' },
|
|
203
239
|
modelMode: 'active',
|
|
204
240
|
hookBus: 'host',
|
|
205
241
|
stateIO: 'filesystem',
|
|
206
242
|
transport: 'mcp',
|
|
207
243
|
runtime: 'node',
|
|
244
|
+
effortSurface: 'argv',
|
|
208
245
|
},
|
|
209
246
|
known: HOST_INTEGRATION_AXES,
|
|
210
247
|
};
|
|
@@ -264,6 +301,15 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
264
301
|
if (hostVal === 'active' && engineVal === 'passive')
|
|
265
302
|
return 'passive';
|
|
266
303
|
}
|
|
304
|
+
// For effortSurface: 'argv' > 'none'. An engine that cannot deliver the
|
|
305
|
+
// host's richer channel caps the result to what it can drive.
|
|
306
|
+
if (axis === 'effortSurface') {
|
|
307
|
+
const RANK = { argv: 1, none: 0 };
|
|
308
|
+
const hr = RANK[hostVal] ?? 0;
|
|
309
|
+
const er = RANK[engineVal] ?? 0;
|
|
310
|
+
if (hr > er)
|
|
311
|
+
return engineVal;
|
|
312
|
+
}
|
|
267
313
|
return hostVal;
|
|
268
314
|
}
|
|
269
315
|
// Negotiate all scalar axes
|
|
@@ -274,6 +320,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
274
320
|
const effectiveStateIO = negotiateScalar('stateIO');
|
|
275
321
|
const effectiveTransport = negotiateScalar('transport');
|
|
276
322
|
const effectiveRuntime = negotiateScalar('runtime');
|
|
323
|
+
const effectiveEffortSurface = negotiateScalar('effortSurface');
|
|
277
324
|
// ---------------------------------------------------------------------------
|
|
278
325
|
// Dispatch struct negotiation
|
|
279
326
|
// ---------------------------------------------------------------------------
|
|
@@ -287,6 +334,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
287
334
|
let effectiveBackgroundDispatch;
|
|
288
335
|
let effectiveSubagentToolkit;
|
|
289
336
|
let effectiveMaxDepth;
|
|
337
|
+
let effectiveIsolation;
|
|
290
338
|
if (hostDispatch === null) {
|
|
291
339
|
// Host didn't declare dispatch at all — fail-closed to most-restrictive values
|
|
292
340
|
warnings.push(`host did not declare 'dispatch'`);
|
|
@@ -296,6 +344,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
296
344
|
effectiveBackgroundDispatch = false;
|
|
297
345
|
effectiveSubagentToolkit = 'read-only';
|
|
298
346
|
effectiveMaxDepth = 0;
|
|
347
|
+
effectiveIsolation = 'none';
|
|
299
348
|
}
|
|
300
349
|
else {
|
|
301
350
|
// N1: observability warnings for 'undocumented' sentinel on dispatch fields
|
|
@@ -314,6 +363,16 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
314
363
|
if (hostDispatch.backgroundDispatch === 'undocumented') {
|
|
315
364
|
warnings.push(`dispatch.backgroundDispatch is undocumented — degraded closed`);
|
|
316
365
|
}
|
|
366
|
+
if (hostDispatch.isolation === 'undocumented') {
|
|
367
|
+
warnings.push(`dispatch.isolation is undocumented — degraded closed (none)`);
|
|
368
|
+
}
|
|
369
|
+
if (hostDispatch.maxDepth === UNDOCUMENTED) {
|
|
370
|
+
// #2603: maxDepth was the one dispatch sub-axis with no sentinel-specific
|
|
371
|
+
// warning, so a descriptor carrying the documented fail-closed sentinel was
|
|
372
|
+
// reported as `missing or not a number` — indistinguishable from a genuinely
|
|
373
|
+
// malformed descriptor. Six shipped runtimes use the sentinel here.
|
|
374
|
+
warnings.push(`dispatch.maxDepth is undocumented — degraded closed (0)`);
|
|
375
|
+
}
|
|
317
376
|
effectiveNamedDispatch = (hostDispatch.namedDispatch === true) && engineDispatch.namedDispatch;
|
|
318
377
|
effectiveNested = (hostDispatch.nested === true) && engineDispatch.nested;
|
|
319
378
|
effectiveBackground = (hostDispatch.background === true) && engineDispatch.background;
|
|
@@ -323,10 +382,22 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
323
382
|
const hostToolkit = hostDispatch.subagentToolkit === 'full' ? 'full' : 'read-only';
|
|
324
383
|
const engineToolkit = engineDispatch.subagentToolkit === 'read-only' ? 'read-only' : 'full';
|
|
325
384
|
effectiveSubagentToolkit = (hostToolkit === 'read-only' || engineToolkit === 'read-only') ? 'read-only' : 'full';
|
|
326
|
-
//
|
|
385
|
+
// isolation: effective = the host's declared value only if it is a known
|
|
386
|
+
// valid vocabulary member; otherwise 'none'. NOT host && engine gated —
|
|
387
|
+
// GSD owns the vocabulary, so "engine-known" == "in the valid set" (this
|
|
388
|
+
// still satisfies effective ⊆ host-declared ∩ engine-known).
|
|
389
|
+
const hostIso = hostDispatch.isolation;
|
|
390
|
+
effectiveIsolation = (typeof hostIso === 'string' && HOST_INTEGRATION_AXES.isolation.includes(hostIso))
|
|
391
|
+
? hostIso
|
|
392
|
+
: 'none';
|
|
393
|
+
// maxDepth: missing/non-number/non-finite → 0 + warning. The documented
|
|
394
|
+
// 'undocumented' sentinel also degrades to 0, but is reported by the
|
|
395
|
+
// sentinel-specific warning above rather than as a malformed value (#2603).
|
|
327
396
|
let hostMaxDepth;
|
|
328
397
|
if (typeof hostDispatch.maxDepth !== 'number' || !Number.isFinite(hostDispatch.maxDepth)) {
|
|
329
|
-
|
|
398
|
+
if (hostDispatch.maxDepth !== UNDOCUMENTED) {
|
|
399
|
+
warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`);
|
|
400
|
+
}
|
|
330
401
|
hostMaxDepth = 0;
|
|
331
402
|
}
|
|
332
403
|
else {
|
|
@@ -352,6 +423,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
352
423
|
background: effectiveBackground,
|
|
353
424
|
subagentToolkit: effectiveSubagentToolkit,
|
|
354
425
|
backgroundDispatch: effectiveBackgroundDispatch,
|
|
426
|
+
isolation: effectiveIsolation,
|
|
355
427
|
};
|
|
356
428
|
// ---------------------------------------------------------------------------
|
|
357
429
|
// Assemble effective axes
|
|
@@ -365,6 +437,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
365
437
|
stateIO: effectiveStateIO,
|
|
366
438
|
transport: effectiveTransport,
|
|
367
439
|
runtime: effectiveRuntime,
|
|
440
|
+
effortSurface: effectiveEffortSurface,
|
|
368
441
|
};
|
|
369
442
|
// ---------------------------------------------------------------------------
|
|
370
443
|
// Compute points (fresh objects — mutation-safe)
|
|
@@ -395,6 +468,63 @@ function shouldFlattenDispatch(dispatch) {
|
|
|
395
468
|
return !canBackground;
|
|
396
469
|
}
|
|
397
470
|
// ---------------------------------------------------------------------------
|
|
471
|
+
// resolveDispatchType — ADR-1239 / epic #2505 Phase 4 (Option A)
|
|
472
|
+
//
|
|
473
|
+
// Maps a requested GSD subagent name (e.g. "gsd-planner") to the type an
|
|
474
|
+
// Agent() call should actually use on the CURRENT runtime. On runtimes whose
|
|
475
|
+
// descriptor declares `hostIntegration.dispatch.namedDispatch: true` (Claude,
|
|
476
|
+
// OpenCode, Cursor, …), the requested name is returned unchanged — those hosts
|
|
477
|
+
// can dispatch GSD's named subagents directly. On runtimes with
|
|
478
|
+
// `namedDispatch: false` (kimi-code — only three built-in subagents
|
|
479
|
+
// `coder`/`explore`/`plan`, per moonshotai.github.io/kimi-code/en/customization/
|
|
480
|
+
// agents), the name is mapped to the closest built-in by role-suffix
|
|
481
|
+
// heuristic. The persona rides the existing `${AGENT_SKILLS_*}` prompt
|
|
482
|
+
// injection (Phase 3 / #2510) regardless of the resolved type, so the
|
|
483
|
+
// dispatcher does not need to know the persona — only the toolkit tier.
|
|
484
|
+
//
|
|
485
|
+
// This is Option A of the Phase 4 design (per-workflow runtime detection via
|
|
486
|
+
// `gsd_run query resolve-dispatch-type`), not Option B (PreToolUse mutation) —
|
|
487
|
+
// Kimi Code's documented hook API supports only allow/deny on PreToolUse, not
|
|
488
|
+
// tool_input rewriting, so a hook-based remap is infeasible (see #2508).
|
|
489
|
+
//
|
|
490
|
+
// Fail-closed: unknown dispatch shape or missing namedDispatch axis ⇒ return
|
|
491
|
+
// the requested name unchanged (named-dispatch is the GSD default; degrading
|
|
492
|
+
// to it on unknown runtimes preserves behavior for every runtime already in
|
|
493
|
+
// the field).
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Role-suffix → built-in mapping. Order matters: the first match wins.
|
|
496
|
+
// `plan`-tier agents plan/design without touching files; `explore`-tier agents
|
|
497
|
+
// are read-only; everything else (executors, writers, fixers, debuggers) maps
|
|
498
|
+
// to `coder` (the general-purpose built-in with the full tool set).
|
|
499
|
+
const DISPATCH_TYPE_SUFFIX_MAP = Object.freeze([
|
|
500
|
+
[/-?(planner|roadmapper|selector|spec)$/i, 'plan'],
|
|
501
|
+
[/-?(researcher|mapper|checker|verifier|auditor|analyzer|synthesizer|profiler|curator|classifier|reviewer)$/i, 'explore'],
|
|
502
|
+
]);
|
|
503
|
+
// Names that are already generic (not gsd-*) and should map to the
|
|
504
|
+
// general-purpose built-in on built-in-only runtimes.
|
|
505
|
+
const GENERIC_NAMES_TO_CODER = Object.freeze(new Set([
|
|
506
|
+
'general-purpose', 'general', 'default', 'sonnet', 'opus', 'haiku',
|
|
507
|
+
]));
|
|
508
|
+
function resolveDispatchType(requested, dispatch) {
|
|
509
|
+
if (typeof requested !== 'string' || requested.length === 0)
|
|
510
|
+
return 'coder';
|
|
511
|
+
// Built-in-only runtime (EXPLICIT namedDispatch: false, e.g. kimi-code):
|
|
512
|
+
// map to coder/explore/plan by suffix heuristic.
|
|
513
|
+
if (dispatch && typeof dispatch === 'object' && dispatch.namedDispatch === false) {
|
|
514
|
+
if (GENERIC_NAMES_TO_CODER.has(requested))
|
|
515
|
+
return 'coder';
|
|
516
|
+
for (const [pattern, builtin] of DISPATCH_TYPE_SUFFIX_MAP) {
|
|
517
|
+
if (pattern.test(requested))
|
|
518
|
+
return builtin;
|
|
519
|
+
}
|
|
520
|
+
return 'coder';
|
|
521
|
+
}
|
|
522
|
+
// Named-dispatch runtime (namedDispatch: true OR unknown/absent): use the
|
|
523
|
+
// requested name unchanged. Absent namedDispatch degrades to named-dispatch
|
|
524
|
+
// (the GSD default) so every runtime already in the field keeps working.
|
|
525
|
+
return requested;
|
|
526
|
+
}
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
398
528
|
// Managed-hook event surface per hookEvents dialect (ADR-1239 / ADR-1016)
|
|
399
529
|
// ---------------------------------------------------------------------------
|
|
400
530
|
// Host-fireable MANAGED-hook events per `hookEvents` dialect. `hookEvents` is the
|
|
@@ -492,6 +622,81 @@ function extensionEventSurfaceFor(extensionEvents) {
|
|
|
492
622
|
return null;
|
|
493
623
|
return EXTENSION_EVENT_SURFACES[extensionEvents] || null;
|
|
494
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Resolve an `orchestratorExec` descriptor + target cwd (+ optional executor
|
|
627
|
+
* prompt) into a concrete argv/cwd shape for a process-spawn primitive.
|
|
628
|
+
*
|
|
629
|
+
* Fail-closed: never throws, always returns a discriminated result. When
|
|
630
|
+
* `cwdFlag` is a non-empty string, `[cwdFlag, cwd]` is appended to `args`
|
|
631
|
+
* exactly once (e.g. codex: `exec --cd <cwd>`); when `cwdFlag` is `null` or
|
|
632
|
+
* absent (e.g. kimi-code, which binds via the spawned process's own cwd —
|
|
633
|
+
* "process-cwd" case), no flag is appended and `cwd` is returned for the
|
|
634
|
+
* caller to bind via the subprocess's own working-directory option.
|
|
635
|
+
*
|
|
636
|
+
* Prompt passing (Phase 3, #2627) is descriptor data for the same reason the
|
|
637
|
+
* cwd flag is: the confirmed `orchestrator-worktree` hosts disagree on the
|
|
638
|
+
* shape. `codex exec "<prompt>"` and `opencode run "<prompt>"` take it
|
|
639
|
+
* positionally; `kimi --print --prompt "<p>"` and Kimi Code's `kimi -p "<p>"`
|
|
640
|
+
* take a flag. Encoding that as `promptFlag` keeps the scheduler free of the
|
|
641
|
+
* per-host branch ADR-1239 exists to remove. Omit `prompt` entirely and the
|
|
642
|
+
* resolution is byte-identical to Phase 2's (the unconsumed-resolver shape).
|
|
643
|
+
*
|
|
644
|
+
* Argv order is base args → cwd flag → prompt, so the prompt stays the final
|
|
645
|
+
* positional token for the hosts that read it that way.
|
|
646
|
+
*/
|
|
647
|
+
function resolveOrchestratorExec(orchestratorExec, cwd, prompt) {
|
|
648
|
+
if (!orchestratorExec || typeof orchestratorExec !== 'object' || Array.isArray(orchestratorExec)) {
|
|
649
|
+
return { ok: false, reason: 'missing_command' };
|
|
650
|
+
}
|
|
651
|
+
const oe = orchestratorExec;
|
|
652
|
+
if (typeof oe.command !== 'string' || oe.command.length === 0) {
|
|
653
|
+
return { ok: false, reason: 'missing_command' };
|
|
654
|
+
}
|
|
655
|
+
if (typeof cwd !== 'string' || cwd.length === 0) {
|
|
656
|
+
return { ok: false, reason: 'invalid_cwd' };
|
|
657
|
+
}
|
|
658
|
+
if (oe.args !== undefined && (!Array.isArray(oe.args) || !oe.args.every((a) => typeof a === 'string'))) {
|
|
659
|
+
return { ok: false, reason: 'invalid_args' };
|
|
660
|
+
}
|
|
661
|
+
if (oe.cwdFlag !== undefined && oe.cwdFlag !== null && typeof oe.cwdFlag !== 'string') {
|
|
662
|
+
return { ok: false, reason: 'invalid_cwd_flag' };
|
|
663
|
+
}
|
|
664
|
+
if (oe.promptFlag !== undefined && oe.promptFlag !== null && typeof oe.promptFlag !== 'string') {
|
|
665
|
+
return { ok: false, reason: 'invalid_prompt_flag' };
|
|
666
|
+
}
|
|
667
|
+
// An executor spawned with no instruction is a hang, not a degraded run —
|
|
668
|
+
// fail closed rather than launching a prompt-less process.
|
|
669
|
+
if (prompt !== undefined && (typeof prompt !== 'string' || prompt.length === 0)) {
|
|
670
|
+
return { ok: false, reason: 'invalid_prompt' };
|
|
671
|
+
}
|
|
672
|
+
// Leading-dash guard, mirroring worktree-safety.cts's `unsafe_leading_dash`
|
|
673
|
+
// check on git arguments. A positional prompt (or a cwd) beginning with '-'
|
|
674
|
+
// is parsed by the spawned CLI as a FLAG, not a value — the same failure the
|
|
675
|
+
// git path already rejects, and for the same reason: `--` end-of-options
|
|
676
|
+
// support is inconsistent across these CLIs, so rejecting outright is the
|
|
677
|
+
// portable fix rather than relying on a separator. Applied to the resolver
|
|
678
|
+
// (not just its current caller) because this is a general descriptor->argv
|
|
679
|
+
// seam: a future caller must not have to rediscover the hazard.
|
|
680
|
+
if (typeof prompt === 'string' && prompt.startsWith('-')) {
|
|
681
|
+
return { ok: false, reason: 'unsafe_leading_dash_prompt' };
|
|
682
|
+
}
|
|
683
|
+
if (cwd.startsWith('-')) {
|
|
684
|
+
return { ok: false, reason: 'unsafe_leading_dash_cwd' };
|
|
685
|
+
}
|
|
686
|
+
const baseArgs = Array.isArray(oe.args) ? [...oe.args] : [];
|
|
687
|
+
const args = typeof oe.cwdFlag === 'string' && oe.cwdFlag.length > 0
|
|
688
|
+
? [...baseArgs, oe.cwdFlag, cwd]
|
|
689
|
+
: baseArgs;
|
|
690
|
+
if (typeof prompt === 'string') {
|
|
691
|
+
if (typeof oe.promptFlag === 'string' && oe.promptFlag.length > 0) {
|
|
692
|
+
args.push(oe.promptFlag, prompt);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
args.push(prompt);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return { ok: true, command: oe.command, args, cwd };
|
|
699
|
+
}
|
|
495
700
|
module.exports = {
|
|
496
701
|
PROTOCOL_VERSION,
|
|
497
702
|
UNDOCUMENTED,
|
|
@@ -505,6 +710,8 @@ module.exports = {
|
|
|
505
710
|
profileOf,
|
|
506
711
|
negotiateHostCapabilities,
|
|
507
712
|
shouldFlattenDispatch,
|
|
713
|
+
resolveDispatchType,
|
|
508
714
|
hookEventSurfaceFor,
|
|
509
715
|
extensionEventSurfaceFor,
|
|
716
|
+
resolveOrchestratorExec,
|
|
510
717
|
};
|
|
@@ -112,18 +112,6 @@ function listPhaseSummaryFiles(phaseDir) {
|
|
|
112
112
|
function listPhasePlanFiles(phaseDir) {
|
|
113
113
|
return scanPhasePlans(phaseDir)['planFiles'];
|
|
114
114
|
}
|
|
115
|
-
function verificationNextCommand(status, phaseNumber, slashRuntime) {
|
|
116
|
-
if (status === 'gaps_found') {
|
|
117
|
-
return `${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', slashRuntime)} ${phaseNumber} --gaps`;
|
|
118
|
-
}
|
|
119
|
-
if (status === 'human_needed' || status === 'stale') {
|
|
120
|
-
return `${(0, runtime_slash_cjs_1.formatGsdSlash)('verify-work', slashRuntime)} ${phaseNumber}`;
|
|
121
|
-
}
|
|
122
|
-
if (status === 'missing' || status === 'unknown') {
|
|
123
|
-
return `${(0, runtime_slash_cjs_1.formatGsdSlash)('execute-phase', slashRuntime)} ${phaseNumber}`;
|
|
124
|
-
}
|
|
125
|
-
return '';
|
|
126
|
-
}
|
|
127
115
|
function projectCompletionStatus(implementationComplete, verificationPassed) {
|
|
128
116
|
if (implementationComplete && verificationPassed)
|
|
129
117
|
return 'complete';
|
|
@@ -134,8 +122,14 @@ function projectCompletionStatus(implementationComplete, verificationPassed) {
|
|
|
134
122
|
function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, summaryCount, slashRuntime) {
|
|
135
123
|
const implementationComplete = planCount > 0 && summaryCount >= planCount;
|
|
136
124
|
const phaseFullDir = phaseDir ? node_path_1.default.join(cwd, phaseDir) : '';
|
|
125
|
+
// #2617: ONE verification-routing seam. init used to re-derive next_command
|
|
126
|
+
// from the status with its own projector, which had drifted from the router's
|
|
127
|
+
// table — it appended the phase number and answered `human_needed`; the table
|
|
128
|
+
// did neither. The router now owns both the content and the runtime
|
|
129
|
+
// projection, and init passes the phase number it already knows (its phaseDir
|
|
130
|
+
// is unresolved in some branches, where the router could not derive one).
|
|
137
131
|
const verificationStatus = implementationComplete
|
|
138
|
-
? readVerificationStatus(phaseFullDir)
|
|
132
|
+
? readVerificationStatus(phaseFullDir, { runtime: slashRuntime, phaseNumber })
|
|
139
133
|
: { status: 'not_required', next_action: '', next_command: '' };
|
|
140
134
|
const projectedVerificationStatus = verificationStatus.status;
|
|
141
135
|
const projectedVerificationAction = verificationStatus.next_action;
|
|
@@ -148,7 +142,7 @@ function buildPhaseCompletionProjection(cwd, phaseNumber, phaseDir, planCount, s
|
|
|
148
142
|
phase_complete: phaseComplete,
|
|
149
143
|
completion_status: projectCompletionStatus(implementationComplete, verificationPassed),
|
|
150
144
|
verification_next_action: projectedVerificationAction,
|
|
151
|
-
verification_next_command:
|
|
145
|
+
verification_next_command: verificationStatus.next_command,
|
|
152
146
|
};
|
|
153
147
|
}
|
|
154
148
|
function getLatestCompletedMilestone(cwd) {
|
|
@@ -1854,7 +1848,38 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
1854
1848
|
const projectRoot = (0, project_root_cjs_1.findProjectRoot)(cwd);
|
|
1855
1849
|
const { config, source, degraded } = loadConfigResolved(projectRoot);
|
|
1856
1850
|
const diagnostics = { warnings: [] };
|
|
1857
|
-
|
|
1851
|
+
let block = buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics);
|
|
1852
|
+
// #2454: Agent prompt fallback for AGENTS-native runtimes where named
|
|
1853
|
+
// subagents are NOT dispatchable (kimi-code, kimi, opencode, kilo, etc.).
|
|
1854
|
+
// On these runtimes, workflows inject ${AGENT_SKILLS_*} into the dispatch
|
|
1855
|
+
// prompt of a built-in subagent (coder/explore/plan). If no
|
|
1856
|
+
// model_profile_overrides or agent_skills config entry exists, the block
|
|
1857
|
+
// is empty — but the agent's prompt CONTENT is installed on disk at the
|
|
1858
|
+
// runtime's agents directory. Read it as a fallback so the persona survives
|
|
1859
|
+
// the dispatch even without explicit config opt-in.
|
|
1860
|
+
//
|
|
1861
|
+
// GATED to non-claude runtimes: Claude Code supports named subagent dispatch
|
|
1862
|
+
// and its ${AGENT_SKILLS_*} contract is a skills-injection path, not a
|
|
1863
|
+
// persona fallback. Triggering the fallback for claude would change the
|
|
1864
|
+
// documented "unconfigured → empty block" contract that agent-skills tests
|
|
1865
|
+
// pin.
|
|
1866
|
+
if (!block) {
|
|
1867
|
+
const runtime = (config && config['runtime']) || process.env['GSD_RUNTIME'] || 'claude';
|
|
1868
|
+
if (runtime !== 'claude') {
|
|
1869
|
+
const agentCheck = checkAgentsInstalled(runtime);
|
|
1870
|
+
const agentsDir = agentCheck?.agents_dir;
|
|
1871
|
+
if (typeof agentsDir === 'string' && agentsDir.length > 0) {
|
|
1872
|
+
const agentFile = node_path_1.default.join(agentsDir, `${agentType}.md`);
|
|
1873
|
+
try {
|
|
1874
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(agentFile);
|
|
1875
|
+
if (content && content.length > 0) {
|
|
1876
|
+
block = content;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
catch { /* agent file not found — fall through to empty block */ }
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1858
1883
|
// Compute configured + reason for diagnostic output.
|
|
1859
1884
|
const agentSkillsMap = (config && config['agent_skills'] && typeof config['agent_skills'] === 'object')
|
|
1860
1885
|
? config['agent_skills']
|
|
@@ -2053,8 +2078,8 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
2053
2078
|
// posix-style for cross-platform stability; flat entries use template
|
|
2054
2079
|
// literals that always produce '/'; nested entries are joined below
|
|
2055
2080
|
// with explicit '/' separators rather than path.join).
|
|
2056
|
-
relPath, content) {
|
|
2057
|
-
const frontmatter = extractFrontmatter(content);
|
|
2081
|
+
relPath, content, sourcePath) {
|
|
2082
|
+
const frontmatter = extractFrontmatter(content, sourcePath);
|
|
2058
2083
|
const dirPart = relPath.replace(/\/SKILL\.md$/, '');
|
|
2059
2084
|
const stem = dirPart.includes('/') ? dirPart.split('/').pop() : dirPart;
|
|
2060
2085
|
const name = frontmatter['name'] || stem;
|
|
@@ -2095,7 +2120,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
2095
2120
|
const skillMdPath = node_path_1.default.join(rootPath, entry.name, 'SKILL.md');
|
|
2096
2121
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(skillMdPath);
|
|
2097
2122
|
if (content !== null) {
|
|
2098
|
-
if (pushSkillEntry(`${entry.name}/SKILL.md`, content))
|
|
2123
|
+
if (pushSkillEntry(`${entry.name}/SKILL.md`, content, skillMdPath))
|
|
2099
2124
|
skillCount++;
|
|
2100
2125
|
}
|
|
2101
2126
|
// Nested layout: <entry>/skills/<stem>/SKILL.md
|
|
@@ -2124,7 +2149,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
2124
2149
|
// Use forward-slash separator explicitly so manifest paths are posix-style
|
|
2125
2150
|
// on all platforms, matching the flat-layout behaviour above.
|
|
2126
2151
|
const relPath = `${entry.name}/skills/${nested.name}/SKILL.md`;
|
|
2127
|
-
if (pushSkillEntry(relPath, nestedContent))
|
|
2152
|
+
if (pushSkillEntry(relPath, nestedContent, nestedSkillMd))
|
|
2128
2153
|
skillCount++;
|
|
2129
2154
|
}
|
|
2130
2155
|
}
|
|
@@ -115,6 +115,7 @@ function convertClaudeCommandToKiloSkill(content, skillName) {
|
|
|
115
115
|
const SKILLS_CONVERTER_REGISTRY = {
|
|
116
116
|
convertClaudeCommandToOpencodeSkill,
|
|
117
117
|
convertClaudeCommandToKiloSkill,
|
|
118
|
+
convertClaudeCommandToKimiCodeSkill: runtimeArtifactConversion.convertClaudeCommandToKimiCodeSkill,
|
|
118
119
|
};
|
|
119
120
|
// ---------------------------------------------------------------------------
|
|
120
121
|
// User-artifact preservation helpers
|
|
@@ -282,8 +282,8 @@ function cmdRequirementsReadyIds(cwd, args, raw) {
|
|
|
282
282
|
catch {
|
|
283
283
|
siblingPlanFiles = [];
|
|
284
284
|
}
|
|
285
|
-
const parseFrontmatterReqIds = (content) => {
|
|
286
|
-
const fm = extractFrontmatter(content);
|
|
285
|
+
const parseFrontmatterReqIds = (content, sourcePath) => {
|
|
286
|
+
const fm = extractFrontmatter(content, sourcePath);
|
|
287
287
|
const fmReq = fm.requirements;
|
|
288
288
|
if (Array.isArray(fmReq))
|
|
289
289
|
return fmReq.map((r) => String(r).trim()).filter(Boolean);
|
|
@@ -309,7 +309,7 @@ function cmdRequirementsReadyIds(cwd, args, raw) {
|
|
|
309
309
|
catch {
|
|
310
310
|
continue;
|
|
311
311
|
}
|
|
312
|
-
const siblingReqIds = parseFrontmatterReqIds(siblingContent);
|
|
312
|
+
const siblingReqIds = parseFrontmatterReqIds(siblingContent, siblingPath);
|
|
313
313
|
const siblingDeclaresId = siblingReqIds.some((id) => id.toLowerCase() === reqId.toLowerCase());
|
|
314
314
|
if (!siblingDeclaresId)
|
|
315
315
|
continue;
|
|
@@ -531,7 +531,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
531
531
|
for (const s of summaries) {
|
|
532
532
|
try {
|
|
533
533
|
const content = node_fs_1.default.readFileSync(node_path_1.default.join(phasesDir, dir, s), 'utf-8');
|
|
534
|
-
const fm = extractFrontmatter(content);
|
|
534
|
+
const fm = extractFrontmatter(content, node_path_1.default.join(phasesDir, dir, s));
|
|
535
535
|
const rawOneLiner = fm['one-liner'];
|
|
536
536
|
const oneLiner = (typeof rawOneLiner === 'string' ? rawOneLiner : '') || extractOneLinerFromBody(content);
|
|
537
537
|
if (oneLiner) {
|
|
@@ -668,7 +668,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
668
668
|
kind: 'milestoneComplete',
|
|
669
669
|
version,
|
|
670
670
|
nextMilestoneCommand: (0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd)),
|
|
671
|
-
}, { clock: clock_cjs_1.realClock, progressProvider: () => null });
|
|
671
|
+
}, { clock: clock_cjs_1.realClock, progressProvider: () => null, sourcePath: statePath });
|
|
672
672
|
writeStateMd(statePath, result.content, cwd);
|
|
673
673
|
}
|
|
674
674
|
// Archive phase directories if requested
|
|
@@ -10,10 +10,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.RUNTIMES_WITH_FAST_MODE = exports.EFFORT_RENDERING = exports.KNOWN_PROVIDERS = exports.PROVIDER_PRESETS = exports.RUNTIMES_WITH_REASONING_EFFORT = exports.KNOWN_RUNTIMES = exports.RUNTIME_PROFILE_MAP = exports.MODEL_ALIAS_MAP = exports.AGENT_DEFAULT_TIERS = exports.AGENT_TO_PHASE_TYPE = exports.MODEL_PROFILES = exports.ADAPTIVE_TIER_VALUES = exports.VALID_TIERS = exports.VALID_AGENT_TIERS = exports.VALID_PHASE_TYPES = exports.VALID_PROFILES = exports.catalog = void 0;
|
|
13
|
+
exports.RUNTIMES_WITH_FAST_MODE = exports.EFFORT_ARGV = exports.EFFORT_RENDERING = exports.KNOWN_PROVIDERS = exports.PROVIDER_PRESETS = exports.RUNTIMES_WITH_REASONING_EFFORT = exports.KNOWN_RUNTIMES = exports.RUNTIME_PROFILE_MAP = exports.MODEL_ALIAS_MAP = exports.AGENT_DEFAULT_TIERS = exports.AGENT_TO_PHASE_TYPE = exports.MODEL_PROFILES = exports.ADAPTIVE_TIER_VALUES = exports.VALID_TIERS = exports.VALID_AGENT_TIERS = exports.VALID_PHASE_TYPES = exports.VALID_PROFILES = exports.catalog = void 0;
|
|
14
14
|
exports.nextTier = nextTier;
|
|
15
15
|
exports.formatAgentToModelMapAsTable = formatAgentToModelMapAsTable;
|
|
16
16
|
exports.getAgentToModelMapForProfile = getAgentToModelMapForProfile;
|
|
17
|
+
exports.renderEffortArgv = renderEffortArgv;
|
|
17
18
|
exports.renderEffortForRuntime = renderEffortForRuntime;
|
|
18
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
20
|
// In .cts (CommonJS output) files, `require` is available as a global;
|
|
@@ -151,6 +152,55 @@ exports.EFFORT_RENDERING = {
|
|
|
151
152
|
},
|
|
152
153
|
},
|
|
153
154
|
};
|
|
155
|
+
exports.EFFORT_ARGV = {
|
|
156
|
+
// Verified against `claude --help`: `--effort <level>`.
|
|
157
|
+
claude: {
|
|
158
|
+
render: (level) => ['--effort', level],
|
|
159
|
+
supported: new Set(['low', 'medium', 'high', 'xhigh', 'max']),
|
|
160
|
+
clamp: (level) => (level === 'minimal' ? 'low' : level),
|
|
161
|
+
},
|
|
162
|
+
// Verified against `opencode run --help`: `--variant` — "model variant
|
|
163
|
+
// (provider-specific reasoning effort, e.g., high, max, minimal)".
|
|
164
|
+
opencode: {
|
|
165
|
+
render: (level) => ['--variant', level],
|
|
166
|
+
supported: new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']),
|
|
167
|
+
clamp: (level) => level,
|
|
168
|
+
},
|
|
169
|
+
// First-party Codex docs: `model_reasoning_effort` is a config-only key with no
|
|
170
|
+
// dedicated flag, so the generic `-c key=value` override is the only argv route.
|
|
171
|
+
codex: {
|
|
172
|
+
render: (level) => ['-c', `model_reasoning_effort=${level}`],
|
|
173
|
+
supported: new Set(['minimal', 'low', 'medium', 'high', 'xhigh']),
|
|
174
|
+
clamp: (level) => (level === 'max' ? 'xhigh' : level),
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Render the invocation-time effort argument for a host.
|
|
179
|
+
*
|
|
180
|
+
* `effortSurface` is the host's negotiated axis value. Only `argv` produces an
|
|
181
|
+
* argument; `none`, `undocumented`, and anything unrecognised produce nothing.
|
|
182
|
+
* Never throws.
|
|
183
|
+
*/
|
|
184
|
+
function renderEffortArgv(host, universalEffort, effortSurface) {
|
|
185
|
+
const empty = { argv: [], value: null, host };
|
|
186
|
+
if (effortSurface !== 'argv')
|
|
187
|
+
return empty;
|
|
188
|
+
// Own-property lookup only. A plain `EFFORT_ARGV[host]` resolves `__proto__`
|
|
189
|
+
// (and `constructor`/`toString`) to inherited members, which are truthy but
|
|
190
|
+
// carry no `clamp`/`render` — a hostile host id would throw instead of
|
|
191
|
+
// degrading. The host id reaches here from a descriptor, i.e. untrusted JSON.
|
|
192
|
+
if (typeof host !== 'string' || !Object.prototype.hasOwnProperty.call(exports.EFFORT_ARGV, host))
|
|
193
|
+
return empty;
|
|
194
|
+
const spec = exports.EFFORT_ARGV[host];
|
|
195
|
+
if (!spec || typeof spec.clamp !== 'function' || typeof spec.render !== 'function')
|
|
196
|
+
return empty;
|
|
197
|
+
if (typeof universalEffort !== 'string' || universalEffort.length === 0)
|
|
198
|
+
return empty;
|
|
199
|
+
const clamped = spec.clamp(universalEffort);
|
|
200
|
+
if (!spec.supported.has(clamped))
|
|
201
|
+
return empty;
|
|
202
|
+
return { argv: spec.render(clamped), value: clamped, host };
|
|
203
|
+
}
|
|
154
204
|
/**
|
|
155
205
|
* Render a universal effort string for a specific runtime.
|
|
156
206
|
*/
|
|
@@ -41,7 +41,12 @@ function _safeStringify(value) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* Determine whether
|
|
44
|
+
* Determine whether observability is opt-in enabled — via the GSD_AUDIT env var
|
|
45
|
+
* or config.audit.enabled. Exported (#2620) so the live dispatch seam can decide
|
|
46
|
+
* whether to inject the reference logger at all: when observability is off we
|
|
47
|
+
* inject nothing (the Hub stays byte-for-byte silent, preserving the default
|
|
48
|
+
* dispatch output contract, incl. --json-errors); when on, the caller injects
|
|
49
|
+
* createDefaultLogger and gets the stderr-on-error line + opt-in file audit.
|
|
45
50
|
*/
|
|
46
51
|
function _isAuditEnabled(config) {
|
|
47
52
|
if (process.env['GSD_AUDIT'] === '1')
|
|
@@ -143,4 +148,4 @@ function createDefaultLogger({ cwd = process.cwd(), config } = {}) {
|
|
|
143
148
|
},
|
|
144
149
|
};
|
|
145
150
|
}
|
|
146
|
-
module.exports = { createDefaultLogger, createNoOpLogger };
|
|
151
|
+
module.exports = { createDefaultLogger, createNoOpLogger, isAuditEnabled: _isAuditEnabled };
|
|
@@ -20,6 +20,12 @@ const command_aliases_cjs_1 = require("./command-aliases.cjs");
|
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
21
|
const commandRoutingHub = require("./command-routing-hub.cjs");
|
|
22
22
|
const { createHub, ERROR_KINDS, makeInvalidArgs } = commandRoutingHub;
|
|
23
|
+
// #2620 (ADR-0174 §6): inject the reference DispatchLogger on the live phase
|
|
24
|
+
// dispatch path, but only when observability is opt-in enabled; otherwise the
|
|
25
|
+
// Hub stays byte-for-byte silent via its no-op fallback.
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const observabilityLogger = require("./observability/logger.cjs");
|
|
28
|
+
const { createDefaultLogger, isAuditEnabled } = observabilityLogger;
|
|
23
29
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
24
30
|
function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
25
31
|
// ── Unsupported subcommands ─────────────────────────────────────────────────
|
|
@@ -229,7 +235,10 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
|
229
235
|
const manifest = { phase: manifestSubcommands };
|
|
230
236
|
// ── Construct hub ──────────────────────────────────────────────────────────
|
|
231
237
|
// #175: Hub is CJS-only — no mode param, no sdkLoader.
|
|
232
|
-
|
|
238
|
+
// #2620: wire the reference logger (ADR-0174 §6) only when observability is
|
|
239
|
+
// opt-in enabled; otherwise leave it unset so the Hub stays byte-for-byte
|
|
240
|
+
// silent via its no-op fallback.
|
|
241
|
+
const hub = createHub({ cjsRegistry, manifest, logger: isAuditEnabled() ? createDefaultLogger({ cwd }) : undefined });
|
|
233
242
|
// ── Dispatch ────────────────────────────────────────────────────────────────
|
|
234
243
|
const result = hub.dispatch({
|
|
235
244
|
family: 'phase',
|