@opengsd/gsd-core 1.9.0 → 1.10.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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +131 -34
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-executor.md +7 -5
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-plan-checker.md +9 -0
- package/agents/gsd-planner.md +5 -8
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-verifier.md +14 -70
- package/bin/install.js +503 -341
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +607 -63
- package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
- package/gsd-core/bin/lib/api-coverage.cjs +120 -0
- package/gsd-core/bin/lib/audit.cjs +89 -1
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-registry.cjs +96 -110
- package/gsd-core/bin/lib/capability-validator.cjs +12 -2
- package/gsd-core/bin/lib/check-command-router.cjs +43 -1
- package/gsd-core/bin/lib/command-aliases.cjs +72 -0
- package/gsd-core/bin/lib/commands.cjs +26 -25
- package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +12 -1
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +91 -12
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/frontmatter.cjs +84 -12
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1021 -57
- package/gsd-core/bin/lib/install-engine.cjs +64 -10
- package/gsd-core/bin/lib/install-profiles.cjs +27 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
- package/gsd-core/bin/lib/io.cjs +28 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone.cjs +106 -51
- package/gsd-core/bin/lib/phase-id.cjs +63 -0
- package/gsd-core/bin/lib/phase-locator.cjs +138 -45
- package/gsd-core/bin/lib/phase.cjs +260 -25
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
- package/gsd-core/bin/lib/project-root.cjs +48 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
- package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
- package/gsd-core/bin/lib/roadmap.cjs +37 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
- package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
- package/gsd-core/bin/lib/smart-entry.cjs +12 -0
- package/gsd-core/bin/lib/state-transition.cjs +73 -8
- package/gsd-core/bin/lib/state.cjs +151 -62
- package/gsd-core/bin/lib/surface.cjs +12 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
- package/gsd-core/bin/lib/uat.cjs +320 -21
- package/gsd-core/bin/lib/unusable-input.cjs +9 -0
- package/gsd-core/bin/lib/verification.cjs +29 -12
- package/gsd-core/bin/lib/verify.cjs +29 -5
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
- package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
- package/gsd-core/bin/lib/workstream.cjs +6 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/specless-probe-fallback.md +7 -6
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +32 -69
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
- package/gsd-core/workflows/code-review.md +42 -145
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +23 -81
- package/gsd-core/workflows/debug.md +9 -12
- package/gsd-core/workflows/diagnose-issues.md +22 -0
- package/gsd-core/workflows/discovery-phase.md +4 -4
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +65 -137
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/ingest-docs.md +2 -1
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +21 -38
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +13 -226
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +49 -193
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +11 -153
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +20 -390
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +15 -8
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +17 -14
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +49 -11
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +8 -21
- package/gsd-core/workflows/ui-phase.md +8 -7
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +18 -7
- package/gsd-core/workflows/verify-phase.md +4 -7
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +8 -58
- package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
- package/hooks/dist/gsd-check-update-worker.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
- package/hooks/dist/gsd-read-injection-scanner.js +7 -0
- package/hooks/dist/gsd-statusline.js +72 -6
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/isolation-sentinel.js +268 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +428 -0
- package/hooks/gsd-check-update-worker.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +532 -26
- package/hooks/gsd-read-injection-scanner.js +7 -0
- package/hooks/gsd-statusline.js +72 -6
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/isolation-sentinel.js +268 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +14 -5
- package/pi/gsd.cjs +57 -12
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +9 -2
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-context-index.cjs +448 -0
- package/scripts/gen-inventory-manifest.cjs +101 -1
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +39 -15
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-test-file-count.allowlist.json +27 -1
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/prompt-injection-scan.sh +27 -6
- package/scripts/registry-schema.cjs +323 -94
- package/scripts/run-tests.cjs +3 -2
- package/scripts/validate-registry.cjs +10 -6
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +2 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-emitted-baseline.cjs +0 -145
|
@@ -16,6 +16,15 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
17
|
const capabilityStateMod = require("./capability-state.cjs");
|
|
18
18
|
const { isCapabilityActive } = capabilityStateMod;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
20
|
+
const ioMod = require("./io.cjs");
|
|
21
|
+
const { serializeForOutput } = ioMod;
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- prompt-budget.cjs is an export= CommonJS module
|
|
23
|
+
const promptBudget = require("./prompt-budget.cjs");
|
|
24
|
+
// The repo's single token scale (phase-estimation.cts documents the rule: a
|
|
25
|
+
// ratio between two measurement methods measures the methods, not the miss).
|
|
26
|
+
// This module previously carried a private copy of the same chars/4 formula.
|
|
27
|
+
const { estimateTokens } = promptBudget;
|
|
19
28
|
/**
|
|
20
29
|
* Return the standard disabled response object.
|
|
21
30
|
*/
|
|
@@ -52,8 +61,12 @@ function execGraphify(cwd, args, options = {}) {
|
|
|
52
61
|
reason: GRAPHIFY_REASON.ENOENT,
|
|
53
62
|
};
|
|
54
63
|
}
|
|
55
|
-
// Timeout —
|
|
56
|
-
|
|
64
|
+
// Timeout — result.timedOut is derived by the shared isSpawnTimeout predicate
|
|
65
|
+
// (shell-command-projection.cts), keyed on error.code === 'ETIMEDOUT' rather
|
|
66
|
+
// than signal === 'SIGTERM': Windows does not reliably report SIGTERM on a
|
|
67
|
+
// timeout kill, and an externally-delivered SIGTERM (error is null) is not
|
|
68
|
+
// a timeout at all.
|
|
69
|
+
if (result.timedOut) {
|
|
57
70
|
return {
|
|
58
71
|
exitCode: 124,
|
|
59
72
|
stdout: result.stdout,
|
|
@@ -104,15 +117,28 @@ function checkGraphifyVersion() {
|
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
119
|
// Strategy 2: fall back to python3 importlib.metadata
|
|
120
|
+
let pyPackageConfirmed = false;
|
|
107
121
|
if (!versionStr) {
|
|
108
122
|
const pyResult = (0, shell_command_projection_cjs_1.execTool)('python3', [
|
|
109
123
|
'-c',
|
|
110
124
|
'from importlib.metadata import version; print(version("graphifyy"))',
|
|
111
125
|
], { timeout: 5000 });
|
|
112
126
|
if (!pyResult.error && pyResult.exitCode === 0 && pyResult.stdout) {
|
|
113
|
-
versionStr = pyResult.stdout;
|
|
127
|
+
versionStr = pyResult.stdout.trim();
|
|
128
|
+
pyPackageConfirmed = true; // importlib.metadata confirmed the package
|
|
114
129
|
}
|
|
115
130
|
}
|
|
131
|
+
else {
|
|
132
|
+
// #3020: verify the `graphify` binary on PATH is actually the graphifyy
|
|
133
|
+
// package — a foreign binary that happens to print a version-like string
|
|
134
|
+
// must not silently report compatible. If importlib.metadata cannot confirm
|
|
135
|
+
// the package, emit an identity warning even if the version looks right.
|
|
136
|
+
const pyVerify = (0, shell_command_projection_cjs_1.execTool)('python3', [
|
|
137
|
+
'-c',
|
|
138
|
+
'from importlib.metadata import version; print(version("graphifyy"))',
|
|
139
|
+
], { timeout: 5000 });
|
|
140
|
+
pyPackageConfirmed = !pyVerify.error && pyVerify.exitCode === 0 && !!pyVerify.stdout;
|
|
141
|
+
}
|
|
116
142
|
if (!versionStr) {
|
|
117
143
|
return { version: null, compatible: null, warning: 'Could not determine graphify version' };
|
|
118
144
|
}
|
|
@@ -120,9 +146,19 @@ function checkGraphifyVersion() {
|
|
|
120
146
|
if (parts.length < 2 || parts.some(isNaN)) {
|
|
121
147
|
return { version: versionStr, compatible: null, warning: 'Could not parse version: ' + versionStr };
|
|
122
148
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
const versionInRange = parts[0] === 0 && parts[1] >= 4;
|
|
150
|
+
// #3020: if the `graphify` binary answered --version but the Python package
|
|
151
|
+
// graphifyy could not be confirmed, the tool identity is unverified — emit
|
|
152
|
+
// a warning naming the mismatch regardless of version-range compatibility.
|
|
153
|
+
if (!pyPackageConfirmed) {
|
|
154
|
+
return {
|
|
155
|
+
version: versionStr,
|
|
156
|
+
compatible: false,
|
|
157
|
+
warning: 'graphify version ' + versionStr + ' detected but the graphifyy Python package could not be confirmed — the `graphify` binary on PATH may be a different tool. Verify with: pip show graphifyy',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const warning = versionInRange ? null : 'graphify version ' + versionStr + ' is outside tested range >=0.4.0,<1.0';
|
|
161
|
+
return { version: versionStr, compatible: versionInRange, warning };
|
|
126
162
|
}
|
|
127
163
|
/**
|
|
128
164
|
* Safely read and parse a JSON file. Returns null on missing file or parse error.
|
|
@@ -195,41 +231,118 @@ function seedAndExpand(graph, term, maxHops = 2) {
|
|
|
195
231
|
const resultNodes = [...visitedNodes].map(id => nodeMap[id]).filter((n) => Boolean(n));
|
|
196
232
|
return { nodes: resultNodes, edges: collectedEdges, seeds: new Set(seeds.map(n => n.id)) };
|
|
197
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* The single definition of `graphifyQuery`'s wire shape.
|
|
236
|
+
*
|
|
237
|
+
* Both the emitter (`graphifyQuery`'s return) and the budget estimator go
|
|
238
|
+
* through here, so `budget_estimate` measures the object the caller actually
|
|
239
|
+
* receives rather than a private approximation of it (#2738).
|
|
240
|
+
*/
|
|
241
|
+
function buildQueryResponse(term, core) {
|
|
242
|
+
return {
|
|
243
|
+
term,
|
|
244
|
+
nodes: core.nodes,
|
|
245
|
+
edges: core.edges,
|
|
246
|
+
total_nodes: core.nodes.length,
|
|
247
|
+
total_edges: core.edges.length,
|
|
248
|
+
trimmed: core.trimmed,
|
|
249
|
+
// Budget outcome (#2738) — only present when a budget was requested
|
|
250
|
+
...(core.budget ? { budget_met: core.budget.met, budget_estimate: core.budget.estimate } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
198
253
|
/**
|
|
199
254
|
* Apply token budget by dropping edges by confidence tier (D-04, D-05, D-06).
|
|
200
|
-
* Token estimation: Math.ceil(JSON.stringify(obj).length / 4).
|
|
201
255
|
* Drop order: AMBIGUOUS -> INFERRED -> EXTRACTED.
|
|
256
|
+
*
|
|
257
|
+
* The estimate measures the response **as emitted** — `serializeForOutput()`
|
|
258
|
+
* over the same object `graphifyQuery` returns, pretty-printed and including
|
|
259
|
+
* the wrapper keys. Measuring a compact `{nodes, edges}` instead understates
|
|
260
|
+
* the payload the caller receives, which makes `budget_met` a confident claim
|
|
261
|
+
* about a payload nobody is handed (#2738).
|
|
262
|
+
*
|
|
263
|
+
* `term` participates in the emitted bytes, so it is threaded through; the
|
|
264
|
+
* default keeps direct unit calls on the same wire shape, minus those bytes.
|
|
202
265
|
*/
|
|
203
|
-
function applyBudget(result, budgetTokens) {
|
|
204
|
-
|
|
266
|
+
function applyBudget(result, budgetTokens, term = '') {
|
|
267
|
+
// == null (not truthiness): --budget 0 is a valid parsed budget the router
|
|
268
|
+
// forwards, and treating it as "no budget" silently returns the unbounded
|
|
269
|
+
// result — the same silent-non-application defect class as #974/#2738.
|
|
270
|
+
//
|
|
271
|
+
// Number.isFinite additionally keeps NaN out of the comparisons below, where
|
|
272
|
+
// every `estimate <= NaN` is false: the loop would strip all three tiers and
|
|
273
|
+
// return a seeds-only payload indistinguishable from a legitimate aggressive
|
|
274
|
+
// trim. The CLI cannot reach that state — graphify-command-router rejects a
|
|
275
|
+
// non-numeric --budget before this is called — but graphifyQuery and
|
|
276
|
+
// applyBudget are module-level entry points a future caller could reach
|
|
277
|
+
// without that validation. Infinity routes here too, and deliberately: an
|
|
278
|
+
// unbounded budget is not a budget.
|
|
279
|
+
if (budgetTokens == null || !Number.isFinite(budgetTokens))
|
|
205
280
|
return result;
|
|
206
281
|
const CONFIDENCE_ORDER = ['AMBIGUOUS', 'INFERRED', 'EXTRACTED'];
|
|
207
282
|
let edges = [...result.edges];
|
|
208
283
|
let omitted = 0;
|
|
209
|
-
|
|
284
|
+
// Nodes that survive a given edge set: edge-reachable, plus seeds (always kept)
|
|
285
|
+
const survivingNodes = (edgeSet) => {
|
|
286
|
+
const reachableNodes = new Set();
|
|
287
|
+
for (const edge of edgeSet) {
|
|
288
|
+
reachableNodes.add(edge.source);
|
|
289
|
+
reachableNodes.add(edge.target);
|
|
290
|
+
}
|
|
291
|
+
return result.nodes.filter(n => reachableNodes.has(n.id) || (result.seeds && result.seeds.has(n.id)));
|
|
292
|
+
};
|
|
293
|
+
const trimmedLabel = (dropped, unreachable) => dropped > 0 ? `[${dropped} edges omitted, ${unreachable} nodes unreachable]` : null;
|
|
294
|
+
/**
|
|
295
|
+
* Tokens of the response as `output()` will emit it.
|
|
296
|
+
*
|
|
297
|
+
* Self-referential by construction: `budget_estimate` is itself one of the
|
|
298
|
+
* emitted fields, so its own digit width counts toward the total. Resolved by
|
|
299
|
+
* iterating to a fixed point — the sequence is non-decreasing (a wider number,
|
|
300
|
+
* and `false` over `true`, can only add characters), so it settles in a couple
|
|
301
|
+
* of passes. The cap is a guard rather than an expectation, and it exits on the
|
|
302
|
+
* larger value: over-reporting is the safe direction for a budget signal;
|
|
303
|
+
* under-reporting is the defect this fixes.
|
|
304
|
+
*/
|
|
305
|
+
const wireEstimate = (candidateNodes, candidateEdges, trimmed) => {
|
|
306
|
+
let est = 0;
|
|
307
|
+
for (let i = 0; i < 8; i++) {
|
|
308
|
+
const next = estimateTokens(serializeForOutput(buildQueryResponse(term, {
|
|
309
|
+
nodes: candidateNodes,
|
|
310
|
+
edges: candidateEdges,
|
|
311
|
+
trimmed,
|
|
312
|
+
budget: { met: est <= budgetTokens, estimate: est },
|
|
313
|
+
})));
|
|
314
|
+
if (next === est)
|
|
315
|
+
break;
|
|
316
|
+
est = next;
|
|
317
|
+
}
|
|
318
|
+
return est;
|
|
319
|
+
};
|
|
320
|
+
// Estimate against the post-pruning node set after each tier removal, so a
|
|
321
|
+
// removal that already fits (once orphaned nodes are excluded) stops the loop
|
|
322
|
+
// instead of dropping the next, higher-confidence tier too (#2738).
|
|
323
|
+
let nodes = survivingNodes(edges);
|
|
324
|
+
let estimate = wireEstimate(nodes, edges, trimmedLabel(omitted, result.nodes.length - nodes.length));
|
|
210
325
|
for (const tier of CONFIDENCE_ORDER) {
|
|
211
|
-
if (
|
|
326
|
+
if (estimate <= budgetTokens)
|
|
212
327
|
break;
|
|
213
328
|
const before = edges.length;
|
|
214
329
|
// Check both confidence and confidence_score field names (Open Question 1)
|
|
215
330
|
edges = edges.filter(e => (e.confidence || e.confidence_score) !== tier);
|
|
216
331
|
omitted += before - edges.length;
|
|
332
|
+
nodes = survivingNodes(edges);
|
|
333
|
+
estimate = wireEstimate(nodes, edges, trimmedLabel(omitted, result.nodes.length - nodes.length));
|
|
217
334
|
}
|
|
218
|
-
// Find unreachable nodes after edge removal
|
|
219
|
-
const reachableNodes = new Set();
|
|
220
|
-
for (const edge of edges) {
|
|
221
|
-
reachableNodes.add(edge.source);
|
|
222
|
-
reachableNodes.add(edge.target);
|
|
223
|
-
}
|
|
224
|
-
// Always keep seed nodes
|
|
225
|
-
const nodes = result.nodes.filter(n => reachableNodes.has(n.id) || (result.seeds && result.seeds.has(n.id)));
|
|
226
335
|
const unreachable = result.nodes.length - nodes.length;
|
|
227
336
|
return {
|
|
228
337
|
nodes,
|
|
229
338
|
edges,
|
|
230
|
-
trimmed: omitted
|
|
339
|
+
trimmed: trimmedLabel(omitted, unreachable),
|
|
231
340
|
total_nodes: nodes.length,
|
|
232
341
|
total_edges: edges.length,
|
|
342
|
+
// Seeds are retained unconditionally, so the seed set is a floor the
|
|
343
|
+
// reduction cannot go below — report the outcome instead of hiding a miss (#2738)
|
|
344
|
+
budget_met: estimate <= budgetTokens,
|
|
345
|
+
budget_estimate: estimate,
|
|
233
346
|
};
|
|
234
347
|
}
|
|
235
348
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
@@ -303,17 +416,19 @@ function graphifyQuery(cwd, term, options = {}) {
|
|
|
303
416
|
return { error: 'Failed to parse graph.json' };
|
|
304
417
|
}
|
|
305
418
|
let result = seedAndExpand(graph, term);
|
|
306
|
-
if (options.budget) {
|
|
307
|
-
result = applyBudget(result, options.budget);
|
|
419
|
+
if (options.budget != null) {
|
|
420
|
+
result = applyBudget(result, options.budget, term);
|
|
308
421
|
}
|
|
309
|
-
|
|
310
|
-
|
|
422
|
+
// Same builder the estimator measured, so budget_estimate describes exactly
|
|
423
|
+
// these bytes (#2738).
|
|
424
|
+
return buildQueryResponse(term, {
|
|
311
425
|
nodes: result.nodes,
|
|
312
426
|
edges: result.edges,
|
|
313
|
-
total_nodes: result.nodes.length,
|
|
314
|
-
total_edges: result.edges.length,
|
|
315
427
|
trimmed: 'trimmed' in result ? (result.trimmed || null) : null,
|
|
316
|
-
|
|
428
|
+
budget: 'budget_met' in result
|
|
429
|
+
? { met: result.budget_met, estimate: result.budget_estimate }
|
|
430
|
+
: undefined,
|
|
431
|
+
});
|
|
317
432
|
}
|
|
318
433
|
/**
|
|
319
434
|
* Return status information about the knowledge graph (STAT-01, STAT-02).
|
|
@@ -27,9 +27,15 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
27
27
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
28
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
29
29
|
const clock_cjs_1 = require("./clock.cjs");
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
31
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
30
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
33
|
const ioMod = require("./io.cjs");
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
35
|
+
const frontmatterMod = require("./frontmatter.cjs");
|
|
32
36
|
const { output } = ioMod;
|
|
37
|
+
const { transliterateForSlug } = coreUtilsMod;
|
|
38
|
+
const { stripFrontmatter } = frontmatterMod;
|
|
33
39
|
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
34
40
|
function readOptional(filePath) {
|
|
35
41
|
try {
|
|
@@ -43,7 +49,11 @@ function zeroPad(n, width = 2) {
|
|
|
43
49
|
return String(n).padStart(width, '0');
|
|
44
50
|
}
|
|
45
51
|
function slugify(title) {
|
|
46
|
-
|
|
52
|
+
// #2848: transliterate Cyrillic to ASCII before the filter so a non-Latin
|
|
53
|
+
// title does not collapse to an empty slug. The shared primitive keeps this
|
|
54
|
+
// in sync with generateSlugInternal. slugify's DISTINCT contract is preserved:
|
|
55
|
+
// single leading/trailing hyphen strip (/^-|-$/), and NO 60-char truncation.
|
|
56
|
+
return transliterateForSlug(title).replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
47
57
|
}
|
|
48
58
|
// ─── GSD-2 Parser ───────────────────────────────────────────────────────────
|
|
49
59
|
/**
|
|
@@ -218,9 +228,22 @@ function buildPlanMd(task, phasePrefix, planPrefix, phaseSlug, milestoneTitle) {
|
|
|
218
228
|
*/
|
|
219
229
|
function buildSummaryMd(task, phasePrefix, planPrefix) {
|
|
220
230
|
const raw = task.summary || '';
|
|
221
|
-
// Strip GSD-2 frontmatter block
|
|
222
|
-
|
|
223
|
-
|
|
231
|
+
// Strip the GSD-2 frontmatter block via the canonical primitive (#2703). The
|
|
232
|
+
// previous local regex required a bare `\n` after the closing `---`, so a
|
|
233
|
+
// CRLF-authored summary never matched, fell through to the untouched-raw
|
|
234
|
+
// branch, and had its frontmatter emitted a second time inside the body of
|
|
235
|
+
// the document this function then wrapped in a fresh v1 block.
|
|
236
|
+
//
|
|
237
|
+
// `extractFrontmatter` — which the issue names — returns only the parsed
|
|
238
|
+
// object and never the body, so it cannot serve this call site;
|
|
239
|
+
// `stripFrontmatter` is the same module's canonical body primitive.
|
|
240
|
+
//
|
|
241
|
+
// `once` is load-bearing. A GSD-2 summary is an arbitrary user-authored
|
|
242
|
+
// document, not a GSD artefact with a known frontmatter-doubling failure
|
|
243
|
+
// mode, so a body opening with a thematic-break-delimited section
|
|
244
|
+
// (`---` / heading / `---`) is far likelier than a corrupt second header —
|
|
245
|
+
// and the default greedy loop would delete it without a trace.
|
|
246
|
+
const body = stripFrontmatter(raw, { once: true }).trim();
|
|
224
247
|
return [
|
|
225
248
|
'---',
|
|
226
249
|
`phase: "${phasePrefix}"`,
|
|
@@ -464,8 +464,20 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
|
|
|
464
464
|
function shouldFlattenDispatch(dispatch) {
|
|
465
465
|
if (!dispatch || typeof dispatch !== 'object')
|
|
466
466
|
return true;
|
|
467
|
+
// Can background at all: both background flags must be explicitly true.
|
|
467
468
|
const canBackground = dispatch.background === true && dispatch.backgroundDispatch === true;
|
|
468
|
-
|
|
469
|
+
if (!canBackground)
|
|
470
|
+
return true;
|
|
471
|
+
// #2939: can background a NESTING orchestrator with room to delegate. A depth budget of 1
|
|
472
|
+
// is consumed by the backgrounded orchestrator itself; it needs > 1 (or unbounded, maxDepth < 0)
|
|
473
|
+
// to host a delegated leaf at depth 2. Non-finite/missing maxDepth fails closed (no budget →
|
|
474
|
+
// flatten), mirroring degradationFor's treatment of non-finite depth as 0.
|
|
475
|
+
const canNest = dispatch.nested === true && dispatch.subagentToolkit === 'full';
|
|
476
|
+
if (!canNest)
|
|
477
|
+
return true;
|
|
478
|
+
const depth = typeof dispatch.maxDepth === 'number' && Number.isFinite(dispatch.maxDepth) ? dispatch.maxDepth : 0;
|
|
479
|
+
const depthSufficient = depth < 0 || depth > 1;
|
|
480
|
+
return !depthSufficient;
|
|
469
481
|
}
|
|
470
482
|
// ---------------------------------------------------------------------------
|
|
471
483
|
// resolveDispatchType — ADR-1239 / epic #2505 Phase 4 (Option A)
|
|
@@ -29,32 +29,107 @@ function routeInitCommand({ init, args, cwd, raw, error }) {
|
|
|
29
29
|
error,
|
|
30
30
|
unknownMessage: (_subcommand, available) => `Unknown init workflow: ${_subcommand}\nAvailable: ${available.join(', ')}`,
|
|
31
31
|
handlers: {
|
|
32
|
+
// #2932/#2992: `parseNamedArgs` never yields `undefined` for an absent
|
|
33
|
+
// flag (value-flags default to `null`, booleanFlags default to `false`);
|
|
34
|
+
// `buildSectionManifestField`'s flags-Set builder (src/init.cts) is the
|
|
35
|
+
// single source of truth for flag ABSENCE and gates on value truthiness,
|
|
36
|
+
// so `namedArgs` is passed through here uncoerced.
|
|
32
37
|
'execute-phase': () => {
|
|
33
|
-
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['validate', 'tdd']);
|
|
34
|
-
init.cmdInitExecutePhase(cwd, args[2], raw, {
|
|
38
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['validate', 'tdd', 'wave']);
|
|
39
|
+
init.cmdInitExecutePhase(cwd, args[2], raw, {
|
|
40
|
+
validate: namedArgs['validate'],
|
|
41
|
+
tdd: namedArgs['tdd'],
|
|
42
|
+
wave: namedArgs['wave'],
|
|
43
|
+
});
|
|
35
44
|
},
|
|
36
45
|
'plan-phase': () => {
|
|
37
|
-
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity'], ['validate', 'tdd']);
|
|
38
|
-
init.cmdInitPlanPhase(cwd, args[2], raw, {
|
|
46
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity', 'prd', 'ingest', 'research-phase'], ['validate', 'tdd', 'reviews', 'chunked']);
|
|
47
|
+
init.cmdInitPlanPhase(cwd, args[2], raw, {
|
|
48
|
+
validate: namedArgs['validate'],
|
|
49
|
+
tdd: namedArgs['tdd'],
|
|
50
|
+
granularity: namedArgs['granularity'],
|
|
51
|
+
prd: namedArgs['prd'],
|
|
52
|
+
ingest: namedArgs['ingest'],
|
|
53
|
+
'research-phase': namedArgs['research-phase'],
|
|
54
|
+
reviews: namedArgs['reviews'],
|
|
55
|
+
chunked: namedArgs['chunked'],
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
'new-project': () => {
|
|
59
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['auto']);
|
|
60
|
+
init.cmdInitNewProject(cwd, raw, { auto: namedArgs['auto'] });
|
|
61
|
+
},
|
|
62
|
+
'new-milestone': () => {
|
|
63
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['reset-phase-numbers']);
|
|
64
|
+
init.cmdInitNewMilestone(cwd, raw, {
|
|
65
|
+
'reset-phase-numbers': namedArgs['reset-phase-numbers'],
|
|
66
|
+
});
|
|
39
67
|
},
|
|
40
|
-
'new-project': () => init.cmdInitNewProject(cwd, raw),
|
|
41
|
-
'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
|
|
42
68
|
onboard: () => {
|
|
43
69
|
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['fast', 'text']);
|
|
44
70
|
init.cmdInitOnboard(cwd, raw, { fast: namedArgs['fast'], text: namedArgs['text'] });
|
|
45
71
|
},
|
|
46
|
-
quick: () =>
|
|
72
|
+
quick: () => {
|
|
73
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['discuss', 'research', 'validate', 'full']);
|
|
74
|
+
// #2994: `args.slice(2)` is the free-text description, but section-manifest
|
|
75
|
+
// gating (buildSectionManifestField, src/init.cts) now requires forwarding
|
|
76
|
+
// --discuss/--research/--validate/--full alongside it — a plain `.join(' ')`
|
|
77
|
+
// would otherwise fold those recognized flag tokens straight into the
|
|
78
|
+
// description text. Strip them before joining so the description stays
|
|
79
|
+
// exactly what it was before this workflow started forwarding flags.
|
|
80
|
+
const quickFlagTokens = new Set(['--discuss', '--research', '--validate', '--full']);
|
|
81
|
+
const description = args
|
|
82
|
+
.slice(2)
|
|
83
|
+
.filter((token) => !quickFlagTokens.has(token))
|
|
84
|
+
.join(' ');
|
|
85
|
+
init.cmdInitQuick(cwd, description, raw, {
|
|
86
|
+
discuss: namedArgs['discuss'],
|
|
87
|
+
research: namedArgs['research'],
|
|
88
|
+
validate: namedArgs['validate'],
|
|
89
|
+
full: namedArgs['full'],
|
|
90
|
+
});
|
|
91
|
+
},
|
|
47
92
|
'ingest-docs': () => init.cmdInitIngestDocs(cwd, raw),
|
|
48
93
|
resume: () => init.cmdInitResume(cwd, raw),
|
|
49
94
|
'verify-work': () => init.cmdInitVerifyWork(cwd, args[2], raw),
|
|
50
95
|
'phase-op': () => init.cmdInitPhaseOp(cwd, args[2], raw),
|
|
96
|
+
'code-review': () => {
|
|
97
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['fix']);
|
|
98
|
+
init.cmdInitCodeReview(cwd, args[2], raw, { fix: namedArgs['fix'] });
|
|
99
|
+
},
|
|
100
|
+
review: () => init.cmdInitReview(cwd, args[2], raw, {}),
|
|
101
|
+
'discuss-phase-assumptions': () => {
|
|
102
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['auto']);
|
|
103
|
+
init.cmdInitDiscussPhaseAssumptions(cwd, args[2], raw, { auto: namedArgs['auto'] });
|
|
104
|
+
},
|
|
51
105
|
todos: () => init.cmdInitTodos(cwd, args[2], raw),
|
|
52
106
|
'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),
|
|
53
107
|
'map-codebase': () => init.cmdInitMapCodebase(cwd, raw),
|
|
54
|
-
progress: () =>
|
|
108
|
+
progress: () => {
|
|
109
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['forensic']);
|
|
110
|
+
init.cmdInitProgress(cwd, raw, { forensic: namedArgs['forensic'] });
|
|
111
|
+
},
|
|
55
112
|
// Keep manager on CJS for now so runtime-specific command rendering
|
|
56
113
|
// (e.g. $gsd-* for codex) stays consistent with runtime-slash helpers.
|
|
57
114
|
manager: () => init.cmdInitManager(cwd, raw),
|
|
115
|
+
'complete-milestone': () => init.cmdInitCompleteMilestone(cwd, raw),
|
|
116
|
+
autonomous: () => {
|
|
117
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['converge', 'cross-ai']);
|
|
118
|
+
init.cmdInitAutonomous(cwd, raw, {
|
|
119
|
+
converge: namedArgs['converge'],
|
|
120
|
+
'cross-ai': namedArgs['cross-ai'],
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
'docs-update': () => init.cmdInitDocsUpdate(cwd, raw, {}),
|
|
124
|
+
update: () => {
|
|
125
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['next', 'rc']);
|
|
126
|
+
init.cmdInitUpdate(cwd, raw, { next: namedArgs['next'], rc: namedArgs['rc'] });
|
|
127
|
+
},
|
|
128
|
+
transition: () => init.cmdInitTransition(cwd, raw, {}),
|
|
129
|
+
debug: () => {
|
|
130
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['diagnose']);
|
|
131
|
+
init.cmdInitDebug(cwd, raw, { diagnose: namedArgs['diagnose'] });
|
|
132
|
+
},
|
|
58
133
|
'new-workspace': () => init.cmdInitNewWorkspace(cwd, raw),
|
|
59
134
|
'list-workspaces': () => init.cmdInitListWorkspaces(cwd, raw),
|
|
60
135
|
'remove-workspace': () => init.cmdInitRemoveWorkspace(cwd, args[2], raw),
|