@opengsd/gsd-core 1.7.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 +45 -1
- package/README.md +2 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +78 -4
- package/agents/gsd-debugger.md +87 -29
- package/agents/gsd-executor.md +49 -9
- 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 +44 -59
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +1338 -135
- package/commands/gsd/ai-integration-phase.md +1 -1
- package/commands/gsd/mempalace-capture.md +9 -5
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +5 -3
- package/commands/gsd/plan-review-convergence.md +7 -2
- package/gsd-core/bin/gsd-tools.cjs +2690 -2472
- package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
- package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
- package/gsd-core/bin/lib/api-coverage.cjs +360 -53
- package/gsd-core/bin/lib/audit.cjs +8 -8
- package/gsd-core/bin/lib/broken-windows.cjs +716 -0
- package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
- 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 +1450 -160
- 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/capability-writer.cjs +6 -1
- package/gsd-core/bin/lib/check-command-router.cjs +140 -27
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +209 -31
- package/gsd-core/bin/lib/claude-orchestration.cjs +203 -25
- package/gsd-core/bin/lib/command-aliases.cjs +14 -0
- package/gsd-core/bin/lib/commands.cjs +326 -21
- package/gsd-core/bin/lib/config-loader.cjs +214 -30
- package/gsd-core/bin/lib/config.cjs +158 -22
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/decisions.cjs +32 -8
- package/gsd-core/bin/lib/docs.cjs +6 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/gap-checker.cjs +17 -2
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +155 -66
- package/gsd-core/bin/lib/install-engine.cjs +299 -23
- package/gsd-core/bin/lib/install-profiles.cjs +239 -1
- package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
- package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +44 -5
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +107 -0
- package/gsd-core/bin/lib/milestone.cjs +248 -14
- package/gsd-core/bin/lib/model-catalog.cjs +69 -4
- package/gsd-core/bin/lib/model-resolver.cjs +189 -7
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
- 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 +304 -9
- package/gsd-core/bin/lib/phase.cjs +258 -17
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +70 -2
- 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 +61 -10
- package/gsd-core/bin/lib/roadmap.cjs +23 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +38 -5
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +23 -9
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +156 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +70 -5
- package/gsd-core/bin/lib/state-document.cjs +171 -24
- package/gsd-core/bin/lib/state-transition.cjs +50 -11
- package/gsd-core/bin/lib/state.cjs +206 -32
- package/gsd-core/bin/lib/surface.cjs +51 -9
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +428 -11
- 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 +44 -8
- package/gsd-core/bin/lib/verification.cjs +163 -31
- package/gsd-core/bin/lib/verify.cjs +348 -42
- 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 +4 -15
- 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/api-coverage.md +37 -7
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/common-bug-patterns.md +13 -0
- package/gsd-core/references/context-budget.md +40 -0
- package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
- package/gsd-core/references/debugger-fix-acceptance.md +157 -0
- package/gsd-core/references/debugger-philosophy.md +1 -0
- package/gsd-core/references/debugger-prevention.md +98 -0
- package/gsd-core/references/debugger-rca-branching.md +98 -0
- package/gsd-core/references/debugger-repro-hardening.md +130 -0
- package/gsd-core/references/debugger-sbfl.md +110 -0
- package/gsd-core/references/debugger-semantic-recall.md +81 -0
- package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
- package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
- package/gsd-core/references/execute-phase-response-language.md +7 -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/planner-antipatterns.md +6 -0
- package/gsd-core/references/planner-mvp-mode.md +12 -13
- package/gsd-core/references/planner-preconditions.md +156 -0
- package/gsd-core/references/planner-reversibility.md +132 -0
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -19
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/skeleton-template.md +1 -1
- package/gsd-core/references/thinking-models-planning.md +3 -1
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- package/gsd-core/templates/DEBUG.md +5 -3
- 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/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +3 -1
- package/gsd-core/workflows/add-todo.md +32 -1
- package/gsd-core/workflows/ai-integration-phase.md +8 -6
- package/gsd-core/workflows/audit-fix.md +6 -2
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +7 -1
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +93 -24
- package/gsd-core/workflows/complete-milestone.md +3 -0
- package/gsd-core/workflows/debug.md +35 -7
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discovery-phase.md +7 -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/templates/context.md +16 -2
- package/gsd-core/workflows/discuss-phase-assumptions.md +18 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +7 -1
- package/gsd-core/workflows/docs-update.md +9 -0
- package/gsd-core/workflows/eval-review.md +4 -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/steps/post-merge-gate.md +4 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
- package/gsd-core/workflows/execute-phase.md +110 -149
- package/gsd-core/workflows/execute-plan.md +20 -8
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/graduation.md +3 -0
- package/gsd-core/workflows/health.md +7 -1
- package/gsd-core/workflows/help/modes/full.md +9 -5
- package/gsd-core/workflows/import.md +11 -2
- package/gsd-core/workflows/inbox.md +7 -0
- package/gsd-core/workflows/ingest-docs.md +19 -10
- package/gsd-core/workflows/manager.md +3 -1
- package/gsd-core/workflows/map-codebase.md +17 -10
- package/gsd-core/workflows/mvp-phase.md +3 -0
- package/gsd-core/workflows/new-milestone.md +79 -23
- package/gsd-core/workflows/new-project.md +28 -19
- package/gsd-core/workflows/new-workspace.md +3 -1
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/onboard.md +3 -0
- package/gsd-core/workflows/plan-phase.md +56 -51
- package/gsd-core/workflows/plan-review-convergence.md +61 -12
- package/gsd-core/workflows/plant-seed.md +3 -0
- package/gsd-core/workflows/profile-user.md +7 -1
- package/gsd-core/workflows/progress.md +31 -3
- package/gsd-core/workflows/quick.md +33 -10
- package/gsd-core/workflows/remove-workspace.md +3 -0
- package/gsd-core/workflows/review.md +172 -585
- package/gsd-core/workflows/scan.md +10 -2
- package/gsd-core/workflows/secure-phase.md +13 -2
- package/gsd-core/workflows/settings-integrations.md +3 -0
- package/gsd-core/workflows/settings.md +3 -0
- package/gsd-core/workflows/ship.md +88 -11
- package/gsd-core/workflows/sketch.md +3 -0
- package/gsd-core/workflows/smart-entry.md +4 -1
- package/gsd-core/workflows/spike.md +7 -1
- package/gsd-core/workflows/ui-phase.md +11 -2
- package/gsd-core/workflows/ui-review.md +11 -1
- package/gsd-core/workflows/undo.md +7 -0
- package/gsd-core/workflows/update.md +106 -5
- package/gsd-core/workflows/validate-phase.md +13 -2
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +15 -4
- package/hooks/dist/gsd-context-monitor.js +27 -9
- 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 +97 -9
- 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-context-monitor.js +27 -9
- 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 +97 -9
- 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 +10 -8
- package/pi/gsd.cjs +34 -3
- package/scripts/changeset/lint.cjs +1 -0
- package/scripts/changeset/parse.cjs +26 -0
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +244 -0
- package/scripts/ci-rebase-check.cjs +48 -4
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +528 -0
- 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/gen-test-timings.cjs +201 -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-portable-timeout.cjs +140 -0
- package/scripts/lint-resolution-provenance.cjs +9 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -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/release-tarball-smoke.cjs +18 -11
- package/scripts/run-tests.cjs +420 -58
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +9 -5
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +5 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +7 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.stripFencedCode = stripFencedCode;
|
|
15
|
+
exports.stripInlineCode = stripInlineCode;
|
|
16
|
+
exports.scanInlineCodeSpans = scanInlineCodeSpans;
|
|
15
17
|
exports.extractFencedBlock = extractFencedBlock;
|
|
16
18
|
exports.tokenizeHeadings = tokenizeHeadings;
|
|
17
19
|
exports.collectSections = collectSections;
|
|
@@ -84,6 +86,111 @@ function stripFencedCode(content) {
|
|
|
84
86
|
}
|
|
85
87
|
return { text: kept.join('\n'), unterminatedFence: openFence !== null };
|
|
86
88
|
}
|
|
89
|
+
// ─── stripInlineCode ──────────────────────────────────────────────────────────
|
|
90
|
+
/**
|
|
91
|
+
* Remove CommonMark inline code spans (§6.1) from prose, line by line.
|
|
92
|
+
*
|
|
93
|
+
* A span opens with a run of N backticks and closes at the next run of EXACTLY
|
|
94
|
+
* N backticks on the same line (a longer or shorter run is span content, per
|
|
95
|
+
* CommonMark). The whole span — delimiters and content — is replaced by a
|
|
96
|
+
* single space so the surrounding words do not join. A run with no matching
|
|
97
|
+
* closer is literal text and is kept. Spans never cross line boundaries here:
|
|
98
|
+
* multi-line code in planning prose is fenced-block territory
|
|
99
|
+
* (`stripFencedCode`).
|
|
100
|
+
*
|
|
101
|
+
* Companion to `stripFencedCode` for term-matching callers (#2365): strip
|
|
102
|
+
* fenced blocks first, then inline spans, so a trigger term inside backticks
|
|
103
|
+
* is code, not prose evidence.
|
|
104
|
+
*/
|
|
105
|
+
function stripInlineCode(content) {
|
|
106
|
+
if (typeof content !== 'string' || content.length === 0)
|
|
107
|
+
return '';
|
|
108
|
+
return content.split('\n').map(stripInlineCodeLine).join('\n');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Locate every inline code span in `content`, per line (offsets are into the
|
|
112
|
+
* full string; spans never cross a `\n`). Callers that need the span CONTENT
|
|
113
|
+
* (e.g. api-coverage's dependency-evidence scan, #2365) use this; callers that
|
|
114
|
+
* just want spans gone use `stripInlineCode`.
|
|
115
|
+
*/
|
|
116
|
+
function scanInlineCodeSpans(content) {
|
|
117
|
+
if (typeof content !== 'string' || content.length === 0)
|
|
118
|
+
return [];
|
|
119
|
+
const out = [];
|
|
120
|
+
let lineStart = 0;
|
|
121
|
+
for (const line of content.split('\n')) {
|
|
122
|
+
for (const s of scanSpansInLine(line)) {
|
|
123
|
+
out.push({ start: lineStart + s.start, end: lineStart + s.end, content: s.content });
|
|
124
|
+
}
|
|
125
|
+
lineStart += line.length + 1;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function scanSpansInLine(line) {
|
|
130
|
+
const spans = [];
|
|
131
|
+
if (line.indexOf('`') === -1)
|
|
132
|
+
return spans;
|
|
133
|
+
// Collect the maximal backtick RUNS once, then match openers to closers using
|
|
134
|
+
// a per-length forward cursor. A naive "search the rest of the line for the
|
|
135
|
+
// closer" loop is O(n²) on a line of many unmatched increasing-length runs
|
|
136
|
+
// (#2365 review 9); precomputing runs makes the whole scan linear while
|
|
137
|
+
// preserving CommonMark semantics (closer = next run of EXACTLY the same len).
|
|
138
|
+
const runs = [];
|
|
139
|
+
for (let i = 0; i < line.length;) {
|
|
140
|
+
if (line[i] === '`') {
|
|
141
|
+
let n = 1;
|
|
142
|
+
while (i + n < line.length && line[i + n] === '`')
|
|
143
|
+
n++;
|
|
144
|
+
runs.push([i, n]);
|
|
145
|
+
i += n;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
i++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const runsByLen = new Map();
|
|
152
|
+
for (let k = 0; k < runs.length; k++) {
|
|
153
|
+
const len = runs[k][1];
|
|
154
|
+
const arr = runsByLen.get(len);
|
|
155
|
+
if (arr)
|
|
156
|
+
arr.push(k);
|
|
157
|
+
else
|
|
158
|
+
runsByLen.set(len, [k]);
|
|
159
|
+
}
|
|
160
|
+
const cursorByLen = new Map();
|
|
161
|
+
let k = 0;
|
|
162
|
+
while (k < runs.length) {
|
|
163
|
+
const [openPos, n] = runs[k];
|
|
164
|
+
const candidates = runsByLen.get(n); // n came from this map, always present
|
|
165
|
+
let ci = cursorByLen.get(n) ?? 0;
|
|
166
|
+
while (ci < candidates.length && candidates[ci] <= k)
|
|
167
|
+
ci++;
|
|
168
|
+
if (ci < candidates.length) {
|
|
169
|
+
const closeK = candidates[ci];
|
|
170
|
+
const closePos = runs[closeK][0];
|
|
171
|
+
spans.push({ start: openPos, end: closePos + n, content: line.slice(openPos + n, closePos) });
|
|
172
|
+
cursorByLen.set(n, ci + 1);
|
|
173
|
+
k = closeK + 1; // resume after the closer — runs inside the span are code
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
cursorByLen.set(n, ci);
|
|
177
|
+
k++; // unmatched run → literal text, next run is a fresh opener
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return spans;
|
|
181
|
+
}
|
|
182
|
+
function stripInlineCodeLine(line) {
|
|
183
|
+
const spans = scanSpansInLine(line);
|
|
184
|
+
if (spans.length === 0)
|
|
185
|
+
return line;
|
|
186
|
+
let out = '';
|
|
187
|
+
let prev = 0;
|
|
188
|
+
for (const s of spans) {
|
|
189
|
+
out += line.slice(prev, s.start) + ' ';
|
|
190
|
+
prev = s.end;
|
|
191
|
+
}
|
|
192
|
+
return out + line.slice(prev);
|
|
193
|
+
}
|
|
87
194
|
/**
|
|
88
195
|
* Shared low-level fence-scanning engine. Walks `lines` and returns every
|
|
89
196
|
* fenced block found, applying the EXACT SAME CommonMark delimiter rules as
|
|
@@ -38,6 +38,14 @@ const { extractOneLinerFromBody } = coreUtilsMod;
|
|
|
38
38
|
const { planningPaths } = planningWorkspace;
|
|
39
39
|
const { extractFrontmatter } = frontmatterMod;
|
|
40
40
|
const { writeStateMd } = stateMod;
|
|
41
|
+
// #2288 security: a milestone version label becomes a filesystem directory
|
|
42
|
+
// component (`milestones/<label>-phases/`) into which phase directories are
|
|
43
|
+
// MOVED. Any label used as a path segment must be a safe version token —
|
|
44
|
+
// letters/digits/'.'/'-'/'_', leading alphanumeric, no path separators and no
|
|
45
|
+
// `..` (the leading-alphanumeric anchor rejects a bare `..`). This gates both
|
|
46
|
+
// the caller-supplied `--archive-version` override and the STATE.md-derived
|
|
47
|
+
// live-read value, so a crafted value cannot escape `.planning/milestones/`.
|
|
48
|
+
const ARCHIVE_VERSION_LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
41
49
|
/**
|
|
42
50
|
* Scope an `updateTableCell` call to the `## Traceability` (or
|
|
43
51
|
* `## Traceability Status`) heading's own section — up to the next H1/H2
|
|
@@ -229,10 +237,179 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
229
237
|
write_set_complete: (0, write_set_cjs_1.writeSetComplete)(writeSet),
|
|
230
238
|
}, raw, `${updated.length}/${reqIds.length} requirements marked complete`);
|
|
231
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* #2388: a requirement ID shared by more than one plan in a phase must not be
|
|
242
|
+
* handed to `cmdRequirementsMarkComplete` until every plan declaring it has
|
|
243
|
+
* finished (i.e. has a `*-SUMMARY.md`) — otherwise the ID reads `Complete` in
|
|
244
|
+
* REQUIREMENTS.md ~20 minutes before its sibling plans even run, and long
|
|
245
|
+
* before `verify_phase_goal` has a chance to catch a gap.
|
|
246
|
+
*
|
|
247
|
+
* Pure read-only gate: scans sibling `*-PLAN.md` files in the SAME phase
|
|
248
|
+
* directory as `planPath` (excluding `planPath` itself) and, for each
|
|
249
|
+
* candidate ID, blocks it only when a sibling plan ALSO declares that ID in
|
|
250
|
+
* its own `requirements:` frontmatter AND that sibling has no matching
|
|
251
|
+
* `*-SUMMARY.md` yet. An ID no sibling declares is never blocked — a
|
|
252
|
+
* single-plan (non-shared) ID is always `ready`, preserving immediate
|
|
253
|
+
* marking with no added latency (acceptance criterion 4). Does not read or
|
|
254
|
+
* write REQUIREMENTS.md itself; callers pass the `ready` subset on to
|
|
255
|
+
* `cmdRequirementsMarkComplete` (whose own flip semantics are untouched).
|
|
256
|
+
*/
|
|
257
|
+
function cmdRequirementsReadyIds(cwd, args, raw) {
|
|
258
|
+
const planPathArg = args[0];
|
|
259
|
+
if (!planPathArg) {
|
|
260
|
+
error('plan path required. Usage: requirements ready-ids <plan-path> REQ-01,REQ-02');
|
|
261
|
+
}
|
|
262
|
+
const reqIds = args
|
|
263
|
+
.slice(1)
|
|
264
|
+
.join(' ')
|
|
265
|
+
.replace(/[\[\]]/g, '')
|
|
266
|
+
.split(/[,\s]+/)
|
|
267
|
+
.map((r) => r.trim())
|
|
268
|
+
.filter(Boolean);
|
|
269
|
+
if (reqIds.length === 0) {
|
|
270
|
+
output({ ready: [], blocked: [], total: 0 }, raw, 'no requirement IDs provided');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const planAbsPath = node_path_1.default.resolve(cwd, planPathArg);
|
|
274
|
+
const phaseDir = node_path_1.default.dirname(planAbsPath);
|
|
275
|
+
const currentBasename = node_path_1.default.basename(planAbsPath);
|
|
276
|
+
let siblingPlanFiles = [];
|
|
277
|
+
try {
|
|
278
|
+
siblingPlanFiles = node_fs_1.default
|
|
279
|
+
.readdirSync(phaseDir)
|
|
280
|
+
.filter((f) => f.endsWith('-PLAN.md') && f !== currentBasename);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
siblingPlanFiles = [];
|
|
284
|
+
}
|
|
285
|
+
const parseFrontmatterReqIds = (content, sourcePath) => {
|
|
286
|
+
const fm = extractFrontmatter(content, sourcePath);
|
|
287
|
+
const fmReq = fm.requirements;
|
|
288
|
+
if (Array.isArray(fmReq))
|
|
289
|
+
return fmReq.map((r) => String(r).trim()).filter(Boolean);
|
|
290
|
+
if (typeof fmReq === 'string') {
|
|
291
|
+
return fmReq
|
|
292
|
+
.replace(/[\[\]]/g, '')
|
|
293
|
+
.split(/[,\s]+/)
|
|
294
|
+
.map((r) => r.trim())
|
|
295
|
+
.filter(Boolean);
|
|
296
|
+
}
|
|
297
|
+
return [];
|
|
298
|
+
};
|
|
299
|
+
const ready = [];
|
|
300
|
+
const blocked = [];
|
|
301
|
+
for (const reqId of reqIds) {
|
|
302
|
+
let blockedBySibling = false;
|
|
303
|
+
for (const siblingFile of siblingPlanFiles) {
|
|
304
|
+
const siblingPath = node_path_1.default.join(phaseDir, siblingFile);
|
|
305
|
+
let siblingContent;
|
|
306
|
+
try {
|
|
307
|
+
siblingContent = node_fs_1.default.readFileSync(siblingPath, 'utf-8');
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const siblingReqIds = parseFrontmatterReqIds(siblingContent, siblingPath);
|
|
313
|
+
const siblingDeclaresId = siblingReqIds.some((id) => id.toLowerCase() === reqId.toLowerCase());
|
|
314
|
+
if (!siblingDeclaresId)
|
|
315
|
+
continue;
|
|
316
|
+
// Sibling declares the SAME ID — it must have finished (produced a
|
|
317
|
+
// SUMMARY) before this ID is ready to mark Complete.
|
|
318
|
+
const siblingSummaryPath = siblingPath.replace(/-PLAN\.md$/, '-SUMMARY.md');
|
|
319
|
+
if (!node_fs_1.default.existsSync(siblingSummaryPath)) {
|
|
320
|
+
blockedBySibling = true;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (blockedBySibling)
|
|
325
|
+
blocked.push(reqId);
|
|
326
|
+
else
|
|
327
|
+
ready.push(reqId);
|
|
328
|
+
}
|
|
329
|
+
output({ ready, blocked, total: reqIds.length }, raw, `${ready.length}/${reqIds.length} requirement(s) ready to mark complete`);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* #2388: revert this phase's own requirement IDs out of `Complete` when
|
|
333
|
+
* `verify_phase_goal` returns `gaps_found` — a gap verdict must not leave a
|
|
334
|
+
* premature `Complete` (from a shared ID's first-declaring plan, or from any
|
|
335
|
+
* other early write) sitting in REQUIREMENTS.md indefinitely.
|
|
336
|
+
*
|
|
337
|
+
* Mirrors `cmdRequirementsMarkComplete`'s two write surfaces in reverse:
|
|
338
|
+
* checkbox `[x]` -> `[ ]`, and traceability Status `Complete` -> `Gaps
|
|
339
|
+
* Found`. Phase-scoping is the CALLER's responsibility — this function only
|
|
340
|
+
* ever touches the exact IDs it is given, so a caller passing just this
|
|
341
|
+
* phase's own `phase_req_ids` never touches another phase's `Complete` row.
|
|
342
|
+
* Never call this on the pass path; it is `gaps_found`-only.
|
|
343
|
+
*/
|
|
344
|
+
function cmdRequirementsRevertPhase(cwd, reqIdsRaw, raw) {
|
|
345
|
+
const reqIds = (reqIdsRaw || [])
|
|
346
|
+
.join(' ')
|
|
347
|
+
.replace(/[\[\]]/g, '')
|
|
348
|
+
.split(/[,\s]+/)
|
|
349
|
+
.map((r) => r.trim())
|
|
350
|
+
.filter(Boolean);
|
|
351
|
+
if (reqIds.length === 0) {
|
|
352
|
+
output({ reverted: [], unchanged: [], total: 0 }, raw, 'no requirement IDs provided');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const reqPath = planningPaths(cwd).requirements;
|
|
356
|
+
if (!node_fs_1.default.existsSync(reqPath)) {
|
|
357
|
+
output({ reverted: [], unchanged: reqIds, total: reqIds.length, reason: 'REQUIREMENTS.md not found' }, raw, 'no requirements file');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
let reqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
|
|
361
|
+
const reverted = [];
|
|
362
|
+
const unchanged = [];
|
|
363
|
+
for (const reqId of reqIds) {
|
|
364
|
+
const reqEscaped = escapeRegex(reqId);
|
|
365
|
+
let idReverted = false;
|
|
366
|
+
// Surface 1 — checkbox: - [x] **REQ-ID** -> - [ ] **REQ-ID**
|
|
367
|
+
const checkboxPattern = new RegExp(`(-\\s*\\[)x(\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi');
|
|
368
|
+
const afterCheckbox = reqContent.replace(checkboxPattern, '$1 $2');
|
|
369
|
+
if (afterCheckbox !== reqContent) {
|
|
370
|
+
reqContent = afterCheckbox;
|
|
371
|
+
idReverted = true;
|
|
372
|
+
}
|
|
373
|
+
// Surface 2 — traceability row: Status Complete -> Gaps Found. Only
|
|
374
|
+
// flips a row currently reading Complete (mirrors mark-complete's own
|
|
375
|
+
// "only flip Pending -> Complete" gate, in reverse).
|
|
376
|
+
const rowMatch = (row) => (Object.values(row)[0] ?? '').trim().toLowerCase() === reqId.toLowerCase();
|
|
377
|
+
let tableHit = false;
|
|
378
|
+
const tableUpdate = updateTraceabilityCell(reqContent, rowMatch, 'Status', (current) => {
|
|
379
|
+
if (/^complete$/i.test(current.trim())) {
|
|
380
|
+
tableHit = true;
|
|
381
|
+
return ' Gaps Found ';
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
});
|
|
385
|
+
if (tableUpdate.ok) {
|
|
386
|
+
reqContent = tableUpdate.value;
|
|
387
|
+
if (tableHit)
|
|
388
|
+
idReverted = true;
|
|
389
|
+
}
|
|
390
|
+
if (idReverted)
|
|
391
|
+
reverted.push(reqId);
|
|
392
|
+
else
|
|
393
|
+
unchanged.push(reqId);
|
|
394
|
+
}
|
|
395
|
+
if (reverted.length > 0) {
|
|
396
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(reqPath, reqContent);
|
|
397
|
+
}
|
|
398
|
+
output({ reverted, unchanged, total: reqIds.length }, raw, `${reverted.length}/${reqIds.length} requirement(s) reverted from Complete`);
|
|
399
|
+
}
|
|
232
400
|
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
233
401
|
if (!version) {
|
|
234
402
|
error('version required for milestone complete (e.g., v1.0)');
|
|
235
403
|
}
|
|
404
|
+
// #2288 security: `version` is a CLI positional that is interpolated into
|
|
405
|
+
// multiple filesystem sinks below — `path.join(archiveDir, `${version}-ROADMAP.md`)`,
|
|
406
|
+
// `${version}-REQUIREMENTS.md`, `${version}-MILESTONE-AUDIT.md`, and the
|
|
407
|
+
// `${version}-phases` archive directory that phase dirs are MOVED into. Reject
|
|
408
|
+
// path separators / `..` here (same guard as `--archive-version`) so a crafted
|
|
409
|
+
// version cannot write or relocate content outside `.planning/milestones/`.
|
|
410
|
+
if (!ARCHIVE_VERSION_LABEL_RE.test(version)) {
|
|
411
|
+
error(`milestone complete: version "${version}" is invalid — a milestone version label may contain only letters, digits, '.', '-' and '_', and must not contain path separators or "..".`);
|
|
412
|
+
}
|
|
236
413
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
237
414
|
const reqPath = planningPaths(cwd).requirements;
|
|
238
415
|
const statePath = planningPaths(cwd).state;
|
|
@@ -354,7 +531,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
354
531
|
for (const s of summaries) {
|
|
355
532
|
try {
|
|
356
533
|
const content = node_fs_1.default.readFileSync(node_path_1.default.join(phasesDir, dir, s), 'utf-8');
|
|
357
|
-
const fm = extractFrontmatter(content);
|
|
534
|
+
const fm = extractFrontmatter(content, node_path_1.default.join(phasesDir, dir, s));
|
|
358
535
|
const rawOneLiner = fm['one-liner'];
|
|
359
536
|
const oneLiner = (typeof rawOneLiner === 'string' ? rawOneLiner : '') || extractOneLinerFromBody(content);
|
|
360
537
|
if (oneLiner) {
|
|
@@ -491,7 +668,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
491
668
|
kind: 'milestoneComplete',
|
|
492
669
|
version,
|
|
493
670
|
nextMilestoneCommand: (0, runtime_slash_cjs_1.formatGsdSlash)('new-milestone', (0, runtime_slash_cjs_1.resolveRuntime)(cwd)),
|
|
494
|
-
}, { clock: clock_cjs_1.realClock, progressProvider: () => null });
|
|
671
|
+
}, { clock: clock_cjs_1.realClock, progressProvider: () => null, sourcePath: statePath });
|
|
495
672
|
writeStateMd(statePath, result.content, cwd);
|
|
496
673
|
}
|
|
497
674
|
// Archive phase directories if requested
|
|
@@ -552,6 +729,32 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
552
729
|
// --force bypasses the uncommitted-changes guard. Only use when the caller
|
|
553
730
|
// has already archived or explicitly accepts loss of uncommitted work. (#1447)
|
|
554
731
|
const force = Array.isArray(args) && args.includes('--force');
|
|
732
|
+
// #2288: explicit outgoing-version override for the archive destination.
|
|
733
|
+
// new-milestone.md runs `state.milestone-switch` BEFORE `phases.clear --confirm`,
|
|
734
|
+
// so a live read of STATE.md would already report the NEW milestone version by
|
|
735
|
+
// the time we get here. Callers that know the outgoing version pass it explicitly;
|
|
736
|
+
// absent an override, archivePhaseDirectories falls back to the live read.
|
|
737
|
+
const avIndex = Array.isArray(args) ? args.indexOf('--archive-version') : -1;
|
|
738
|
+
let archiveVersionOverride = null;
|
|
739
|
+
if (avIndex !== -1) {
|
|
740
|
+
const rawArchiveVersion = args[avIndex + 1];
|
|
741
|
+
// Missing / flag-shaped value: fail loud instead of silently dropping the
|
|
742
|
+
// override. A truncated invocation (e.g. a broken template substitution
|
|
743
|
+
// leaving `--archive-version` with no value) must NOT fall through to the
|
|
744
|
+
// live read — that silently re-files the archive under the new milestone,
|
|
745
|
+
// the exact #2288 bug this flag exists to prevent.
|
|
746
|
+
if (typeof rawArchiveVersion !== 'string' || rawArchiveVersion.startsWith('--') || rawArchiveVersion.trim() === '') {
|
|
747
|
+
error('--archive-version requires a value (a milestone version token, e.g. v1.0)');
|
|
748
|
+
}
|
|
749
|
+
const trimmed = rawArchiveVersion.trim();
|
|
750
|
+
// #2288 security: reject path separators / `..` so a crafted value cannot
|
|
751
|
+
// relocate phase history outside `.planning/milestones/` (phase dirs are
|
|
752
|
+
// MOVED into the archive dir — a traversal is data loss, not just an odd name).
|
|
753
|
+
if (!ARCHIVE_VERSION_LABEL_RE.test(trimmed)) {
|
|
754
|
+
error(`--archive-version "${trimmed}" is invalid — a milestone version label may contain only letters, digits, '.', '-' and '_', and must not contain path separators or "..".`);
|
|
755
|
+
}
|
|
756
|
+
archiveVersionOverride = trimmed;
|
|
757
|
+
}
|
|
555
758
|
let cleared = 0;
|
|
556
759
|
if (node_fs_1.default.existsSync(phasesDir)) {
|
|
557
760
|
const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
@@ -598,7 +801,8 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
598
801
|
}
|
|
599
802
|
try {
|
|
600
803
|
// #1871: archive phase directories instead of destroying them (shared helper).
|
|
601
|
-
|
|
804
|
+
// #2288: thread the explicit --archive-version override (if any) through.
|
|
805
|
+
cleared = archivePhaseDirectories(cwd, phasesDir, dirs, archiveVersionOverride).archived;
|
|
602
806
|
}
|
|
603
807
|
catch (e) {
|
|
604
808
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -609,18 +813,46 @@ function cmdPhasesClear(cwd, raw, args) {
|
|
|
609
813
|
}
|
|
610
814
|
/**
|
|
611
815
|
* #1871: move each non-999 phase directory under `phasesDir` into
|
|
612
|
-
* `milestones/<version>-phases/` (collision-safe
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
816
|
+
* `milestones/<version>-phases/` (collision-safe). Shared by `phases clear`
|
|
817
|
+
* (archive-then-remove) and the internal milestone.complete phase archival so
|
|
818
|
+
* phase history survives a milestone switch instead of being hard-deleted.
|
|
819
|
+
*
|
|
820
|
+
* Archive-version precedence (#2288): an explicit `archiveVersionOverride` wins
|
|
821
|
+
* first, then a live `getMilestoneInfo(cwd)` read (which itself defaults to a
|
|
822
|
+
* version like `v1.0` when ROADMAP/STATE is absent), and only a dated fallback
|
|
823
|
+
* label if no safe version label is resolvable at all. The override is validated
|
|
824
|
+
* by the caller (`cmdPhasesClear`); the live-read value is re-validated here
|
|
825
|
+
* (defense in depth) because `getMilestoneInfo` derives it from STATE.md's
|
|
826
|
+
* unvalidated `milestone:` field. The override exists because `new-milestone.md`
|
|
827
|
+
* runs `state.milestone-switch` BEFORE `phases.clear --confirm` — by the time
|
|
828
|
+
* this runs, a live read of STATE.md would already report the NEW milestone
|
|
829
|
+
* version, so phase history from the OLD milestone would be misfiled under the
|
|
830
|
+
* new version's archive directory. Callers that know the outgoing version must
|
|
831
|
+
* pass it explicitly.
|
|
616
832
|
*/
|
|
617
|
-
function archivePhaseDirectories(cwd, phasesDir, dirs) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
833
|
+
function archivePhaseDirectories(cwd, phasesDir, dirs, archiveVersionOverride = null) {
|
|
834
|
+
// Self-protecting (#2288 security defense in depth): the sole current caller
|
|
835
|
+
// (`cmdPhasesClear`) already validates the override, but re-test it here so a
|
|
836
|
+
// future caller cannot reopen the path-traversal sink at line ~742. An override
|
|
837
|
+
// that fails the safe-label check is discarded (falls through to the live read
|
|
838
|
+
// / dated label) rather than reaching `path.join` unvalidated.
|
|
839
|
+
const safeOverride = archiveVersionOverride && ARCHIVE_VERSION_LABEL_RE.test(archiveVersionOverride.trim())
|
|
840
|
+
? archiveVersionOverride.trim()
|
|
841
|
+
: null;
|
|
842
|
+
let archiveVersion = safeOverride;
|
|
843
|
+
if (!archiveVersion) {
|
|
844
|
+
try {
|
|
845
|
+
const liveVersion = getMilestoneInfo(cwd).version ?? null;
|
|
846
|
+
// Defense in depth (#2288 security): getMilestoneInfo reads STATE.md's
|
|
847
|
+
// `milestone:` field, which is unvalidated file content. Only accept it
|
|
848
|
+
// as a path component if it is a safe version label; a crafted value
|
|
849
|
+
// (path separators / `..`) falls through to the dated label below rather
|
|
850
|
+
// than escaping `.planning/milestones/`.
|
|
851
|
+
archiveVersion = liveVersion && ARCHIVE_VERSION_LABEL_RE.test(liveVersion) ? liveVersion : null;
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
/* ROADMAP/STATE unreadable — fall back to a dated label */
|
|
855
|
+
}
|
|
624
856
|
}
|
|
625
857
|
if (!archiveVersion) {
|
|
626
858
|
archiveVersion = `archived-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 8)}`;
|
|
@@ -643,6 +875,8 @@ function archivePhaseDirectories(cwd, phasesDir, dirs) {
|
|
|
643
875
|
}
|
|
644
876
|
module.exports = {
|
|
645
877
|
cmdRequirementsMarkComplete,
|
|
878
|
+
cmdRequirementsReadyIds,
|
|
879
|
+
cmdRequirementsRevertPhase,
|
|
646
880
|
cmdMilestoneComplete,
|
|
647
881
|
cmdPhasesClear,
|
|
648
882
|
};
|
|
@@ -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.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;
|
|
@@ -23,11 +24,19 @@ const _require = require;
|
|
|
23
24
|
// works in every layout:
|
|
24
25
|
//
|
|
25
26
|
// 1. Co-located install path — gsd-core/bin/shared/model-catalog.json
|
|
26
|
-
// 2.
|
|
27
|
-
//
|
|
27
|
+
// 2. GSD_MODEL_CATALOG env override
|
|
28
|
+
//
|
|
29
|
+
// A third candidate — `sdk/shared/model-catalog.json`, three levels up — used to
|
|
30
|
+
// sit between them. It was the legacy source-repo path kept as a fallback by the
|
|
31
|
+
// #3288 fix, whose contract was "check the co-located path FIRST, before the
|
|
32
|
+
// legacy source-repo path". ADR-0174 then retired the `@opengsd/gsd-sdk` package
|
|
33
|
+
// boundary and deleted the `sdk/` tree outright, so that candidate can no longer
|
|
34
|
+
// resolve in any layout: in a source repo there is no `sdk/`, and in an install
|
|
35
|
+
// layout it points at `~/.claude/sdk/shared/`, which the installer never writes
|
|
36
|
+
// (the original #3288 bug). It is removed rather than left as dead weight that
|
|
37
|
+
// implies a package boundary this repo no longer has.
|
|
28
38
|
const _catalogCandidates = [
|
|
29
39
|
node_path_1.default.resolve(__dirname, '..', 'shared', 'model-catalog.json'),
|
|
30
|
-
node_path_1.default.resolve(__dirname, '..', '..', '..', 'sdk', 'shared', 'model-catalog.json'),
|
|
31
40
|
...(process.env['GSD_MODEL_CATALOG'] ? [node_path_1.default.resolve(process.env['GSD_MODEL_CATALOG'])] : []),
|
|
32
41
|
];
|
|
33
42
|
let catalog = null;
|
|
@@ -54,6 +63,13 @@ exports.catalog = _catalog;
|
|
|
54
63
|
exports.VALID_PROFILES = [..._catalog.profiles];
|
|
55
64
|
exports.VALID_PHASE_TYPES = new Set(_catalog.phaseTypes);
|
|
56
65
|
exports.VALID_AGENT_TIERS = new Set(Object.keys(_catalog.adaptiveTierMap));
|
|
66
|
+
// Catalog-derived so this can never drift from the resolver's tier gate:
|
|
67
|
+
// Object.values(adaptiveTierMap) === ['opus', 'sonnet', 'haiku'] today, plus 'inherit'.
|
|
68
|
+
exports.VALID_TIERS = new Set([...Object.values(_catalog.adaptiveTierMap), 'inherit']);
|
|
69
|
+
// Same catalog-derived tier values as VALID_TIERS but WITHOUT 'inherit' — used
|
|
70
|
+
// by config-loader's runtime-override validation (model_profile_overrides /
|
|
71
|
+
// model_policy.runtime_tiers), which does not accept 'inherit' as a tier.
|
|
72
|
+
exports.ADAPTIVE_TIER_VALUES = new Set(Object.values(_catalog.adaptiveTierMap));
|
|
57
73
|
exports.MODEL_PROFILES = Object.fromEntries(Object.entries(_catalog.agents).map(([agent, meta]) => [agent, {
|
|
58
74
|
quality: meta.golden,
|
|
59
75
|
balanced: meta.balanced,
|
|
@@ -136,6 +152,55 @@ exports.EFFORT_RENDERING = {
|
|
|
136
152
|
},
|
|
137
153
|
},
|
|
138
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
|
+
}
|
|
139
204
|
/**
|
|
140
205
|
* Render a universal effort string for a specific runtime.
|
|
141
206
|
*/
|