@mjasnikovs/pi-task 0.38.29 → 0.38.30
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/dist/config/config.d.ts +70 -70
- package/dist/config/config.js +26 -35
- package/dist/config/extension-list.d.ts +6 -5
- package/dist/config/extension-list.js +3 -2
- package/dist/config/reasoning-args.d.ts +9 -7
- package/dist/config/reasoning-args.js +12 -10
- package/dist/config/reasoning.d.ts +44 -105
- package/dist/config/reasoning.js +27 -704
- package/dist/config/register.d.ts +34 -48
- package/dist/config/register.js +41 -51
- package/dist/config/tool-list.d.ts +16 -16
- package/dist/config/tool-list.js +1 -1
- package/dist/remote/bridge.d.ts +19 -10
- package/dist/remote/bridge.js +3 -2
- package/dist/remote/broadcast.js +3 -1
- package/dist/remote/events.js +12 -11
- package/dist/remote/history.d.ts +1 -1
- package/dist/remote/protocol.d.ts +6 -3
- package/dist/remote/protocol.js +2 -1
- package/dist/remote/push.d.ts +16 -16
- package/dist/remote/push.js +27 -27
- package/dist/remote/register.d.ts +3 -3
- package/dist/remote/register.js +17 -19
- package/dist/remote/server.d.ts +9 -8
- package/dist/remote/server.js +15 -14
- package/dist/remote/session-state.d.ts +5 -4
- package/dist/remote/session-state.js +8 -5
- package/dist/remote/sw.d.ts +7 -6
- package/dist/remote/sw.js +7 -6
- package/dist/remote/tailscale.d.ts +4 -2
- package/dist/remote/tailscale.js +4 -2
- package/dist/remote/ui-highlight.js +6 -5
- package/dist/remote/ui-render.js +4 -4
- package/dist/remote/ui-script.js +24 -24
- package/dist/remote/ui-styles.d.ts +1 -1
- package/dist/remote/ui-styles.js +10 -13
- package/dist/remote/ui-tools.js +9 -6
- package/dist/shared/child-extensions.d.ts +29 -17
- package/dist/shared/child-extensions.js +29 -17
- package/dist/shared/child-output.d.ts +30 -24
- package/dist/shared/child-output.js +25 -17
- package/dist/shared/child-process.d.ts +47 -40
- package/dist/shared/child-process.js +50 -59
- package/dist/shared/command-watchdog.d.ts +22 -16
- package/dist/shared/command-watchdog.js +28 -21
- package/dist/shared/fs-text.d.ts +16 -10
- package/dist/shared/fs-text.js +16 -10
- package/dist/shared/git-runner.d.ts +25 -25
- package/dist/shared/git-runner.js +25 -25
- package/dist/shared/leaked-tool-call.d.ts +17 -11
- package/dist/shared/leaked-tool-call.js +23 -15
- package/dist/shared/model-endpoint.d.ts +29 -16
- package/dist/shared/model-endpoint.js +33 -21
- package/dist/shared/pi-invocation.d.ts +7 -4
- package/dist/shared/pi-invocation.js +12 -7
- package/dist/shared/pkg-version.d.ts +13 -5
- package/dist/shared/pkg-version.js +13 -5
- package/dist/shared/reasoning-capability.d.ts +35 -24
- package/dist/shared/reasoning-capability.js +35 -24
- package/dist/shared/stream-watchdog.d.ts +60 -44
- package/dist/shared/stream-watchdog.js +62 -45
- package/dist/task/accept-debt.d.ts +41 -43
- package/dist/task/accept-debt.js +73 -65
- package/dist/task/api-synthesis.d.ts +24 -21
- package/dist/task/api-synthesis.js +32 -26
- package/dist/task/apis-contract.d.ts +32 -64
- package/dist/task/apis-contract.js +32 -64
- package/dist/task/artifact-closure.d.ts +27 -13
- package/dist/task/artifact-closure.js +95 -67
- package/dist/task/auto-commit.d.ts +46 -35
- package/dist/task/auto-commit.js +51 -38
- package/dist/task/auto-io.d.ts +45 -25
- package/dist/task/auto-io.js +57 -29
- package/dist/task/auto-orchestrator.d.ts +26 -24
- package/dist/task/auto-orchestrator.js +178 -162
- package/dist/task/auto-prompts.d.ts +36 -24
- package/dist/task/auto-prompts.js +40 -26
- package/dist/task/autofix-ledger.d.ts +27 -25
- package/dist/task/autofix-ledger.js +29 -26
- package/dist/task/batch-test-task.d.ts +20 -12
- package/dist/task/batch-test-task.js +67 -60
- package/dist/task/boot-probe.d.ts +60 -44
- package/dist/task/boot-probe.js +91 -72
- package/dist/task/cancel-input.d.ts +30 -16
- package/dist/task/cancel-input.js +20 -11
- package/dist/task/cancel-points.d.ts +27 -20
- package/dist/task/cancel-points.js +30 -22
- package/dist/task/child-runner.d.ts +46 -51
- package/dist/task/child-runner.js +48 -49
- package/dist/task/child-status.d.ts +23 -16
- package/dist/task/child-status.js +23 -16
- package/dist/task/clamp-output.js +12 -5
- package/dist/task/command-run.d.ts +31 -28
- package/dist/task/command-run.js +44 -35
- package/dist/task/command-shrink.d.ts +25 -18
- package/dist/task/command-shrink.js +37 -31
- package/dist/task/command-watchdog.d.ts +9 -6
- package/dist/task/command-watchdog.js +21 -15
- package/dist/task/context-attribution.d.ts +34 -26
- package/dist/task/context-attribution.js +34 -26
- package/dist/task/context-silence.d.ts +39 -29
- package/dist/task/context-silence.js +35 -25
- package/dist/task/context-usage.d.ts +16 -9
- package/dist/task/context-usage.js +16 -9
- package/dist/task/contracts.d.ts +8 -4
- package/dist/task/contracts.js +25 -17
- package/dist/task/coverage-loop.d.ts +22 -18
- package/dist/task/coverage-loop.js +35 -30
- package/dist/task/critique-probes.d.ts +13 -14
- package/dist/task/critique-probes.js +50 -39
- package/dist/task/debug-log.d.ts +13 -5
- package/dist/task/debug-log.js +32 -20
- package/dist/task/decompose-fidelity.d.ts +11 -9
- package/dist/task/decompose-fidelity.js +38 -33
- package/dist/task/decompose-granularity.d.ts +41 -38
- package/dist/task/decompose-granularity.js +41 -38
- package/dist/task/deep-render-check.d.ts +22 -14
- package/dist/task/deep-render-check.js +40 -31
- package/dist/task/dropped-input.d.ts +12 -7
- package/dist/task/dropped-input.js +5 -2
- package/dist/task/enforce-attribution.d.ts +38 -47
- package/dist/task/enforce-attribution.js +46 -52
- package/dist/task/enforce-guidelines.d.ts +31 -20
- package/dist/task/enforce-guidelines.js +32 -21
- package/dist/task/enrichment.d.ts +7 -2
- package/dist/task/enrichment.js +26 -14
- package/dist/task/env-notes.d.ts +16 -7
- package/dist/task/env-notes.js +48 -31
- package/dist/task/env-template-closure.d.ts +4 -4
- package/dist/task/env-template-closure.js +42 -34
- package/dist/task/external-context.d.ts +28 -21
- package/dist/task/external-context.js +17 -12
- package/dist/task/failure-classifier.d.ts +4 -5
- package/dist/task/failure-classifier.js +6 -7
- package/dist/task/file-inventory.d.ts +15 -11
- package/dist/task/file-inventory.js +25 -22
- package/dist/task/final-gate-fix.d.ts +74 -86
- package/dist/task/final-gate-fix.js +97 -116
- package/dist/task/final-gate-progress.d.ts +29 -46
- package/dist/task/final-gate-progress.js +40 -51
- package/dist/task/final-gate.d.ts +64 -97
- package/dist/task/final-gate.js +192 -199
- package/dist/task/fix-child.d.ts +21 -27
- package/dist/task/fix-child.js +21 -27
- package/dist/task/foreign-path.d.ts +6 -5
- package/dist/task/foreign-path.js +0 -0
- package/dist/task/frozen-conflict.d.ts +9 -10
- package/dist/task/frozen-conflict.js +61 -64
- package/dist/task/frozen-path-guard.d.ts +35 -14
- package/dist/task/frozen-path-guard.js +56 -39
- package/dist/task/gate-child.d.ts +27 -28
- package/dist/task/gate-child.js +36 -35
- package/dist/task/gate-deps.d.ts +34 -27
- package/dist/task/gate-deps.js +169 -159
- package/dist/task/gate-tally.d.ts +77 -80
- package/dist/task/gate-tally.js +65 -68
- package/dist/task/git-state-guard.d.ts +15 -11
- package/dist/task/git-state-guard.js +76 -66
- package/dist/task/impl-widget.d.ts +25 -16
- package/dist/task/impl-widget.js +27 -17
- package/dist/task/implementation-thinking.d.ts +33 -31
- package/dist/task/implementation-thinking.js +5 -6
- package/dist/task/implementation-turn.d.ts +34 -31
- package/dist/task/implementation-turn.js +29 -27
- package/dist/task/inline-markdown.d.ts +20 -7
- package/dist/task/inline-markdown.js +15 -6
- package/dist/task/launch-config-gap.js +25 -39
- package/dist/task/launch-contract.d.ts +18 -21
- package/dist/task/launch-contract.js +28 -30
- package/dist/task/launch-manifest.d.ts +6 -2
- package/dist/task/launch-manifest.js +35 -34
- package/dist/task/ledger.js +16 -14
- package/dist/task/lint-fix.d.ts +6 -8
- package/dist/task/lint-fix.js +67 -69
- package/dist/task/loop-detector.d.ts +9 -8
- package/dist/task/loop-detector.js +16 -12
- package/dist/task/mid-run-input.d.ts +17 -15
- package/dist/task/mid-run-input.js +17 -15
- package/dist/task/orchestrator.d.ts +24 -28
- package/dist/task/orchestrator.js +62 -64
- package/dist/task/orientation.d.ts +18 -23
- package/dist/task/orientation.js +24 -31
- package/dist/task/owned-freeze-conflict.d.ts +21 -20
- package/dist/task/owned-freeze-conflict.js +52 -85
- package/dist/task/owned-freeze-reassign.d.ts +40 -60
- package/dist/task/owned-freeze-reassign.js +41 -61
- package/dist/task/parsers.d.ts +4 -2
- package/dist/task/parsers.js +4 -4
- package/dist/task/phases.d.ts +41 -48
- package/dist/task/phases.js +179 -248
- package/dist/task/plan-io.d.ts +6 -7
- package/dist/task/plan-io.js +6 -7
- package/dist/task/plan-orchestrator.d.ts +10 -8
- package/dist/task/plan-orchestrator.js +14 -10
- package/dist/task/plan-prompts.d.ts +6 -5
- package/dist/task/plan-prompts.js +6 -5
- package/dist/task/plan-readonly.d.ts +4 -5
- package/dist/task/plan-readonly.js +4 -5
- package/dist/task/plan-rounds.d.ts +17 -29
- package/dist/task/plan-rounds.js +21 -34
- package/dist/task/plan-session.d.ts +58 -72
- package/dist/task/plan-session.js +61 -83
- package/dist/task/probe-gaming.d.ts +28 -27
- package/dist/task/probe-gaming.js +0 -0
- package/dist/task/prohibition-probe.d.ts +14 -16
- package/dist/task/prompts.d.ts +3 -4
- package/dist/task/prompts.js +17 -26
- package/dist/task/qa-transcript.d.ts +15 -22
- package/dist/task/qa-transcript.js +15 -21
- package/dist/task/question-box.d.ts +17 -13
- package/dist/task/question-box.js +19 -15
- package/dist/task/question-dedup.d.ts +6 -7
- package/dist/task/question-dedup.js +13 -14
- package/dist/task/question-dialog.d.ts +22 -32
- package/dist/task/question-dialog.js +22 -32
- package/dist/task/question-source.d.ts +18 -44
- package/dist/task/question-source.js +22 -51
- package/dist/task/refuted-constraint.d.ts +11 -31
- package/dist/task/refuted-constraint.js +27 -51
- package/dist/task/regenerable-artifacts.d.ts +12 -31
- package/dist/task/regenerable-artifacts.js +12 -31
- package/dist/task/render-check.d.ts +11 -22
- package/dist/task/render-check.js +33 -46
- package/dist/task/repo-health-check.d.ts +10 -14
- package/dist/task/repo-health-check.js +17 -23
- package/dist/task/requirements.d.ts +38 -71
- package/dist/task/requirements.js +78 -126
- package/dist/task/research-fanout-budget.d.ts +51 -88
- package/dist/task/research-fanout-budget.js +51 -88
- package/dist/task/research-worker.d.ts +29 -39
- package/dist/task/research-worker.js +37 -61
- package/dist/task/resume-gap.d.ts +14 -15
- package/dist/task/root-cause-repair.d.ts +9 -9
- package/dist/task/root-cause-repair.js +28 -40
- package/dist/task/run-bracket.d.ts +10 -13
- package/dist/task/run-end.d.ts +12 -22
- package/dist/task/run-end.js +8 -16
- package/dist/task/run-final-gate.d.ts +19 -21
- package/dist/task/run-final-gate.js +62 -80
- package/dist/task/runner-globs.d.ts +12 -13
- package/dist/task/runner-globs.js +12 -13
- package/dist/task/runner-resolve.d.ts +9 -9
- package/dist/task/runner-resolve.js +22 -23
- package/dist/task/script-escape.d.ts +10 -12
- package/dist/task/script-escape.js +13 -14
- package/dist/task/serve-entry.d.ts +1 -1
- package/dist/task/serve-entry.js +22 -25
- package/dist/task/service-blocks.js +4 -2
- package/dist/task/shipped-source.d.ts +11 -29
- package/dist/task/shipped-source.js +11 -29
- package/dist/task/skip-escape.js +10 -14
- package/dist/task/spec-urls.d.ts +26 -65
- package/dist/task/spec-urls.js +26 -65
- package/dist/task/spec-validation.d.ts +17 -20
- package/dist/task/spec-validation.js +17 -20
- package/dist/task/stall-detector.d.ts +23 -30
- package/dist/task/stall-detector.js +23 -30
- package/dist/task/stream-watchdog.d.ts +14 -12
- package/dist/task/stream-watchdog.js +14 -12
- package/dist/task/substitution-probe.d.ts +17 -20
- package/dist/task/substitution-probe.js +17 -20
- package/dist/task/task-gates.d.ts +36 -41
- package/dist/task/task-gates.js +95 -106
- package/dist/task/task-io.d.ts +4 -4
- package/dist/task/task-io.js +4 -4
- package/dist/task/task-parsers.js +4 -3
- package/dist/task/task-provenance.d.ts +2 -2
- package/dist/task/task-provenance.js +11 -13
- package/dist/task/task-types.d.ts +4 -3
- package/dist/task/terminal-outcome.d.ts +14 -16
- package/dist/task/terminal-outcome.js +12 -14
- package/dist/task/test-assembly.d.ts +13 -20
- package/dist/task/test-assembly.js +13 -20
- package/dist/task/timings.d.ts +5 -3
- package/dist/task/timings.js +5 -3
- package/dist/task/title-label.d.ts +9 -4
- package/dist/task/title-label.js +9 -4
- package/dist/task/type-only-answer.d.ts +44 -52
- package/dist/task/type-only-answer.js +44 -52
- package/dist/task/unfailable-command.d.ts +18 -24
- package/dist/task/unfailable-command.js +21 -27
- package/dist/task/unknown-routing.d.ts +10 -4
- package/dist/task/unknown-routing.js +10 -4
- package/dist/task/user-directives.d.ts +5 -8
- package/dist/task/user-directives.js +5 -8
- package/dist/task/verify-quality.d.ts +18 -22
- package/dist/task/verify-quality.js +45 -46
- package/dist/task/verify-reconcile.d.ts +15 -10
- package/dist/task/verify-reconcile.js +45 -43
- package/dist/task/verify-resolution.d.ts +24 -20
- package/dist/task/verify-resolution.js +51 -50
- package/dist/task/verify-work.d.ts +59 -66
- package/dist/task/verify-work.js +101 -138
- package/dist/task/widget.d.ts +15 -14
- package/dist/task/widget.js +22 -17
- package/dist/task/wiring-claims.d.ts +25 -32
- package/dist/task/wiring-claims.js +30 -35
- package/dist/task/write-guard.d.ts +39 -39
- package/dist/task/write-guard.js +48 -51
- package/dist/task/yolo.d.ts +34 -30
- package/dist/task/yolo.js +42 -37
- package/dist/workers/abstention.d.ts +21 -41
- package/dist/workers/abstention.js +27 -48
- package/dist/workers/brave-search.d.ts +4 -3
- package/dist/workers/brave-search.js +5 -2
- package/dist/workers/brave-warning.d.ts +7 -4
- package/dist/workers/brave-warning.js +19 -7
- package/dist/workers/ddg-search.d.ts +6 -6
- package/dist/workers/ddg-search.js +18 -12
- package/dist/workers/docs-cache.js +5 -2
- package/dist/workers/docs-chunk.d.ts +30 -37
- package/dist/workers/docs-chunk.js +37 -41
- package/dist/workers/docs-core.d.ts +28 -44
- package/dist/workers/docs-core.js +25 -44
- package/dist/workers/docs-index.js +4 -3
- package/dist/workers/docs-lookup.d.ts +15 -22
- package/dist/workers/docs-lookup.js +12 -21
- package/dist/workers/docs-project.d.ts +15 -9
- package/dist/workers/docs-project.js +17 -10
- package/dist/workers/docs-resolve.d.ts +19 -20
- package/dist/workers/docs-resolve.js +35 -32
- package/dist/workers/docs-retrieve.d.ts +5 -6
- package/dist/workers/docs-retrieve.js +18 -15
- package/dist/workers/exa-search.d.ts +9 -6
- package/dist/workers/exa-search.js +23 -12
- package/dist/workers/fetch-core.d.ts +13 -16
- package/dist/workers/fetch-core.js +23 -23
- package/dist/workers/focused-extractor.d.ts +12 -12
- package/dist/workers/focused-extractor.js +16 -19
- package/dist/workers/html-clean.js +24 -14
- package/dist/workers/http-request.d.ts +28 -20
- package/dist/workers/http-request.js +22 -17
- package/dist/workers/npm-version.d.ts +28 -11
- package/dist/workers/npm-version.js +24 -15
- package/dist/workers/phantom-imports.d.ts +15 -12
- package/dist/workers/phantom-imports.js +30 -24
- package/dist/workers/pi-worker-core.d.ts +69 -71
- package/dist/workers/pi-worker-core.js +100 -109
- package/dist/workers/pi-worker-docs.d.ts +24 -19
- package/dist/workers/pi-worker-docs.js +67 -76
- package/dist/workers/pi-worker-fetch.d.ts +7 -3
- package/dist/workers/pi-worker-fetch.js +27 -19
- package/dist/workers/pi-worker-search.js +12 -8
- package/dist/workers/pi-worker.d.ts +9 -4
- package/dist/workers/pi-worker.js +21 -14
- package/dist/workers/reasoning-warning.d.ts +18 -17
- package/dist/workers/reasoning-warning.js +22 -20
- package/dist/workers/research-cache.js +50 -78
- package/dist/workers/search-core.js +7 -5
- package/dist/workers/search-types.d.ts +10 -9
- package/dist/workers/search-types.js +9 -8
- package/dist/workers/session-hint.d.ts +13 -14
- package/dist/workers/session-hint.js +8 -9
- package/dist/workers/shared.d.ts +21 -25
- package/dist/workers/shared.js +0 -0
- package/dist/workers/single-read-extension.d.ts +14 -7
- package/dist/workers/single-read-extension.js +14 -7
- package/dist/workers/single-read-guard.d.ts +25 -28
- package/dist/workers/single-read-guard.js +32 -32
- package/dist/workers/typeonly-log.d.ts +12 -9
- package/dist/workers/typeonly-log.js +29 -33
- package/dist/workers/worker-channels.d.ts +15 -23
- package/dist/workers/worker-channels.js +15 -23
- package/dist/workers/worker-failure.d.ts +38 -46
- package/dist/workers/worker-failure.js +31 -39
- package/dist/workers/worker-kill.d.ts +25 -26
- package/dist/workers/worker-kill.js +16 -19
- package/dist/workers/worker-profiles.d.ts +43 -53
- package/dist/workers/worker-profiles.js +30 -38
- package/package.json +10 -8
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* /task-auto — plans a feature into a resumable list of task titles, then runs
|
|
3
3
|
* each title through the existing single-task pipeline one at a time.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The whole command lives here: the planning half (orient → elicit → decompose →
|
|
6
|
+
* cover → planAuto), the run loop (runAutoLoop), the production dependency table
|
|
7
|
+
* (defaultDeps), and the three command handlers registerTaskAuto wires up —
|
|
8
|
+
* /task-auto, /task-auto-resume and /task-auto-cancel.
|
|
7
9
|
*/
|
|
8
10
|
import { existsSync } from 'node:fs';
|
|
9
11
|
import * as fsp from 'node:fs/promises';
|
|
@@ -53,7 +55,7 @@ import { findSpecDanglingArtifacts, titlesCoverArtifact, danglingMissingText, da
|
|
|
53
55
|
import { LAUNCH_EXTRACT_PROMPT, enumerateScriptCandidates, parseScriptLines, keepGroundedScripts, appendDeclaredScripts } from './launch-contract.js';
|
|
54
56
|
// Hard ceiling on clarify questions per feature. The loop is open-ended (it stops
|
|
55
57
|
// when the model emits NONE), but a model that never says NONE would otherwise
|
|
56
|
-
// barrage the user
|
|
58
|
+
// barrage the user with redundant questions.
|
|
57
59
|
const MAX_CLARIFY_QUESTIONS = 8;
|
|
58
60
|
// Bounded coverage-triage rounds after decompose: judge → reprompt-with-missing
|
|
59
61
|
// → judge again, at most. Two rounds so one flaky retry doesn't end the gate,
|
|
@@ -80,30 +82,32 @@ function coverageRepromptHint(missing) {
|
|
|
80
82
|
+ 'the areas above. Output every task, one "- [ ] " line each, nothing else.]');
|
|
81
83
|
}
|
|
82
84
|
// Deterministic distrust floor for the coverage gate. The gate's judge is the
|
|
83
|
-
// same stochastic model as the decompose call it guards, and
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
85
|
+
// same stochastic model as the decompose call it guards, and it will sometimes
|
|
86
|
+
// rubber-stamp a one-task plan for a whole design document — as the bare
|
|
87
|
+
// "COVERAGE: COMPLETE" line, byte-identical to a legitimate verdict, so the
|
|
88
|
+
// rubber-stamp is NOT detectable from the judge's output. The
|
|
87
89
|
// distrust signal must come from the input: a plan this small for a spec this
|
|
88
|
-
// large is near-certainly
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
90
|
+
// large is near-certainly a degenerate generation rather than a real plan. The
|
|
91
|
+
// floor only ever forces a REGENERATION — it never rejects a plan on count alone,
|
|
92
|
+
// so a model that insists twice still ships its small plan, with a warning. Nine
|
|
93
|
+
// tests pin the behaviour, including that a still-suspect plan ships and that a
|
|
94
|
+
// shorter retry keeps the original list.
|
|
92
95
|
const SUSPECT_PLAN_MAX_TITLES = 2;
|
|
93
96
|
const SUSPECT_PLAN_MIN_SPEC_CHARS = 4000;
|
|
94
97
|
/**
|
|
95
98
|
* Extra retries granted when the plan is EMPTY rather than merely small. One
|
|
96
|
-
* hinted retry heals a small-but-nonempty plan
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
+
* hinted retry heals a small-but-nonempty plan; an empty generation is a harder
|
|
100
|
+
* fault that can recur back-to-back, and falling through with zero titles aborts
|
|
101
|
+
* the whole run rather than merely shipping a thin plan — so it is worth more
|
|
102
|
+
* than one roll of the dice.
|
|
99
103
|
*/
|
|
100
104
|
const EMPTY_PLAN_RETRIES = 2;
|
|
101
105
|
/**
|
|
102
106
|
* An empty list is NEVER a valid decomposition of any feature request, at any spec
|
|
103
|
-
* size.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
+
* size. A predicate opening with `titles.length > 0` lets zero titles escape
|
|
108
|
+
* entirely: not "suspect", so the suspect-retry never fires, the coverage loop
|
|
109
|
+
* breaks immediately on `titles.length === 0`, and the run aborts with "no tasks
|
|
110
|
+
* produced from the feature". A single degenerate
|
|
107
111
|
* generation killed the whole run with no retry, which is the opposite of how the
|
|
108
112
|
* same fault is treated one title higher.
|
|
109
113
|
*/
|
|
@@ -129,41 +133,43 @@ const MENTION_RE = /(?:^|\s)@([^\s]+)/g;
|
|
|
129
133
|
// [^\s]+ above would otherwise swallow into the path. Left unstripped, the
|
|
130
134
|
// resulting "design.md," resolves to no file, expansion is silently skipped, and
|
|
131
135
|
// the planner reasons over a one-line "Implement @design.md" with NO spec inline
|
|
132
|
-
// → it fabricates generic questions
|
|
133
|
-
//
|
|
134
|
-
//
|
|
136
|
+
// → it fabricates generic questions and tasks the spec never called for.
|
|
137
|
+
//
|
|
138
|
+
// Measured against a real file: the greedy token from "Implement @design.md,
|
|
139
|
+
// reuse the parser" is `design.md,`, which does not exist; stripped, `design.md`
|
|
140
|
+
// does. None of these chars are legitimate trailing characters of a doc path.
|
|
135
141
|
const MENTION_TRAILING_PUNCT = /[.,;:!?)\]}>"']+$/;
|
|
136
142
|
/** The cleaned path token of an @-mention: greedy match minus trailing prose punctuation. */
|
|
137
143
|
function mentionPath(token) {
|
|
138
144
|
return token.replace(MENTION_TRAILING_PUNCT, '');
|
|
139
145
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Fire-and-forget debug line for the PLAN phase (clarify/decompose), which runs
|
|
142
|
-
* before any task file — hence any per-task `TASK_XXXX-debug.log` — exists. Writes
|
|
143
|
-
* to `.pi-tasks/plan-debug.log`; the `*-debug.log` suffix keeps it grep-compatible
|
|
144
|
-
* with the per-task logs. Never throws (mkdir + append are best-effort).
|
|
145
|
-
*
|
|
146
|
-
* Every call site here records a plan DECISION (how many titles a round produced,
|
|
147
|
-
* whether a retry was adopted, which clarify answer was auto-resolved), so all of
|
|
148
|
-
* them are `'event'` — this file carries no model chatter and survives at the
|
|
149
|
-
* default level. It is also the only channel the plan phase has: it runs before
|
|
150
|
-
* any task file, hence any `TASK_NNNN-debug.log`, exists.
|
|
151
|
-
*/
|
|
152
146
|
/**
|
|
153
147
|
* Every plan-debug write not yet on disk, chained.
|
|
154
148
|
*
|
|
155
149
|
* Fire-and-forget is right for production — a plan must never wait on its own
|
|
156
|
-
* trail — but it leaves nothing to synchronise on,
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* appends, which is what keeps a line whole.
|
|
150
|
+
* trail — but it leaves nothing to synchronise on, so a test that reads
|
|
151
|
+
* `plan-debug.log` back races the append that writes it and fails on ENOENT.
|
|
152
|
+
* Chaining gives {@link flushPlanDebug} something to await, and it also
|
|
153
|
+
* serialises concurrent appends, which is what keeps a line whole.
|
|
161
154
|
*/
|
|
162
155
|
let planDebugChain = Promise.resolve();
|
|
163
156
|
/** Wait for every plan-debug line written so far to reach disk. Tests only. */
|
|
164
157
|
export function flushPlanDebug() {
|
|
165
158
|
return planDebugChain;
|
|
166
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Fire-and-forget debug line for the PLAN phase (clarify/decompose). It is the
|
|
162
|
+
* only trail that phase has: planning runs before any task file exists, so there
|
|
163
|
+
* is no per-task `TASK_NNNN-debug.log` to write into yet. This goes to
|
|
164
|
+
* `.pi-tasks/plan-debug.log`, whose `*-debug.log` suffix matches the pattern
|
|
165
|
+
* debug-log.ts documents, so one grep still finds every log. Never throws — the
|
|
166
|
+
* mkdir and the append are both best-effort.
|
|
167
|
+
*
|
|
168
|
+
* Every call site records a plan DECISION (how many titles a round produced,
|
|
169
|
+
* whether a retry was adopted, which clarify answer was auto-resolved), so all of
|
|
170
|
+
* them are `'event'` and survive at the default level. No model chatter reaches
|
|
171
|
+
* this file.
|
|
172
|
+
*/
|
|
167
173
|
function logPlanDebug(cwd, msg) {
|
|
168
174
|
if (!shouldLogDebug('event', debugLogLevel()))
|
|
169
175
|
return;
|
|
@@ -205,8 +211,8 @@ async function triageClarifyQuestion(deps, cwd, featureForModel, existingFilesBl
|
|
|
205
211
|
// manifest/config content) so a "scaffold/create/from scratch" question is
|
|
206
212
|
// auto-resolved as an in-place UPDATE that PRESERVES what is on disk —
|
|
207
213
|
// instead of "greenfield, from scratch", which the spec-only triage emitted
|
|
208
|
-
//
|
|
209
|
-
// outrank refine's preserve directive
|
|
214
|
+
// most of the time, minting a destructive decompose decision that can
|
|
215
|
+
// outrank refine's preserve directive.
|
|
210
216
|
// Empty (greenfield repo / orientation off) → byte-identical to before.
|
|
211
217
|
const source = existingFilesBlock.length > 0 ?
|
|
212
218
|
`${existingFilesBlock}\n\n${featureForModel}`
|
|
@@ -293,19 +299,20 @@ export async function readableMentions(cwd, feature) {
|
|
|
293
299
|
const DECISIONS_RE = /\s*\[decisions:\s*(.+?)\]\s*$/i;
|
|
294
300
|
/**
|
|
295
301
|
* Thread the feature's spec references AND any per-task decisions into every
|
|
296
|
-
* decomposed task title. A title is ALL a per-task pipeline ever sees, so both
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
+
* decomposed task title. A title is ALL a per-task pipeline ever sees, so both the
|
|
303
|
+
* design doc the feature pointed at and the user's clarification choices have to
|
|
304
|
+
* ride along or they are invisible downstream — a task that cannot see the doc
|
|
305
|
+
* invents its own schema, and one that cannot see "do not use vite" is overridden
|
|
306
|
+
* by whatever the doc's own config says.
|
|
307
|
+
*
|
|
308
|
+
* Precedence is the crux: a clarification is a CORRECTION to a possibly stale spec
|
|
309
|
+
* doc, so the decisions clause is marked as overriding the doc while the doc stays
|
|
310
|
+
* authoritative for everything the decisions do not touch. The emitted order puts
|
|
311
|
+
* decisions first, then the spec ref. Decompose scopes each decision to the tasks
|
|
312
|
+
* it governs, so most titles carry none.
|
|
302
313
|
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* stays authoritative for everything the decisions don't touch. Decompose scopes
|
|
306
|
-
* each decision to the task(s) it governs, so most titles carry none. No readable
|
|
307
|
-
* refs and no decisions → title unchanged, so a doc-less /task-auto behaves
|
|
308
|
-
* exactly as before.
|
|
314
|
+
* Run: with no refs and no decisions the title comes back unchanged, and
|
|
315
|
+
* re-threading an already-threaded list is a no-op.
|
|
309
316
|
*/
|
|
310
317
|
export function attachSpecRefs(titles, refs) {
|
|
311
318
|
const list = refs.map(r => '@' + r).join(' ');
|
|
@@ -329,15 +336,15 @@ export function attachSpecRefs(titles, refs) {
|
|
|
329
336
|
* Build the refine scope fence for step `currentIndex` of an N-step /task-auto
|
|
330
337
|
* plan. Every per-step pipeline only ever sees its own title, so without this the
|
|
331
338
|
* refine phase — told "the task title is only a pointer into that spec; follow the
|
|
332
|
-
* spec" — re-expands the whole referenced design into
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* slice. Validated on the local model: with the fence, refine's CONSTRAINTS gained
|
|
336
|
-
* an explicit per-step deferral list and tool calls dropped 27→11.
|
|
339
|
+
* spec" — re-expands the whole referenced design into a single task, implementing
|
|
340
|
+
* the entire plan under step one. The fence lists the sibling steps by number and
|
|
341
|
+
* forbids touching anything they own, so refine bounds this step's slice.
|
|
337
342
|
*
|
|
338
343
|
* The plan listing strips the threaded "| decisions … | spec …" tail from each
|
|
339
|
-
* title
|
|
340
|
-
*
|
|
344
|
+
* title and keeps the human-readable head, so the model reads clean step names,
|
|
345
|
+
* and it marks the current one "(THIS STEP)" — both confirmed by building a fence
|
|
346
|
+
* over threaded titles. The authoritative spec ref still rides on THIS step's own
|
|
347
|
+
* title via attachSpecRefs.
|
|
341
348
|
*/
|
|
342
349
|
export function buildScopeFence(titles, currentIndex) {
|
|
343
350
|
const n = titles.length;
|
|
@@ -364,8 +371,11 @@ export function buildScopeFence(titles, currentIndex) {
|
|
|
364
371
|
* a queued root-cause repair. A repair title ("repair test/teardown.ts: …") reads
|
|
365
372
|
* to refine like any other feature step, and refine's job is to expand a title into
|
|
366
373
|
* a full spec — which is exactly how "repair the teardown" becomes "overhaul the
|
|
367
|
-
* test infrastructure"
|
|
368
|
-
*
|
|
374
|
+
* test infrastructure". The extra fence pins the one editable file, and pins VERIFY
|
|
375
|
+
* to the failing command WHEN the title carries one: `extractFailingCommand` reads
|
|
376
|
+
* a backticked runner command out of the defect text, so a defect quoting
|
|
377
|
+
* `bun run test` yields it while a plain-prose defect yields undefined and only the
|
|
378
|
+
* file pin applies.
|
|
369
379
|
*/
|
|
370
380
|
function buildStepFence(titles, currentIndex) {
|
|
371
381
|
const base = buildScopeFence(titles, currentIndex);
|
|
@@ -379,15 +389,17 @@ function buildStepFence(titles, currentIndex) {
|
|
|
379
389
|
* repair step per accused FILE, spliced in directly after the step that just
|
|
380
390
|
* finished.
|
|
381
391
|
*
|
|
382
|
-
* Three bounds, all mandatory
|
|
383
|
-
* - DEDUP by file —
|
|
392
|
+
* Three bounds, all mandatory:
|
|
393
|
+
* - DEDUP by file — two debts naming the same file must yield ONE repair
|
|
384
394
|
* step, not two. mergeRepairCandidates collapses the drained queue, and
|
|
385
395
|
* planHasRepairFor rejects a file the plan already carries a repair for.
|
|
386
396
|
* - CAP 1 per file per RUN — planHasRepairFor counts CHECKED-OFF entries too, so
|
|
387
397
|
* a repair step that itself failed is never re-spawned; it lands in the
|
|
388
398
|
* accept-debt ledger like any other task. That is what stops a repair loop.
|
|
399
|
+
* Both bounds run as described: three candidates over two files merge to two,
|
|
400
|
+
* and a plan carrying an already-`[x]` repair for a file still answers true.
|
|
389
401
|
* - MONOTONIC — insertTaskAfter only splices; no existing entry is rewritten,
|
|
390
|
-
* reordered or dropped
|
|
402
|
+
* reordered or dropped.
|
|
391
403
|
*
|
|
392
404
|
* Best-effort throughout: a fault here must never fail the run that produced the
|
|
393
405
|
* finding — the debt is already durably recorded either way.
|
|
@@ -431,7 +443,7 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
431
443
|
// clarify/decompose ever see it. Layer A only rewrites the per-task `refined`
|
|
432
444
|
// text — which is DOWNSTREAM of here: clarify is the first phase and runs on
|
|
433
445
|
// this raw inline, so the doc's affirmative `bun:sql` is parroted straight into
|
|
434
|
-
// the very first clarifying question
|
|
446
|
+
// the very first clarifying question. Apply the
|
|
435
447
|
// same deterministic, no-LLM strike at the single point that feeds both planning
|
|
436
448
|
// children. Silent + no-op when nothing is flagged or the runtime's types aren't
|
|
437
449
|
// installed.
|
|
@@ -451,7 +463,7 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
451
463
|
taskId: '',
|
|
452
464
|
signal: new AbortController().signal
|
|
453
465
|
}).catch(() => '');
|
|
454
|
-
// Requirement extraction
|
|
466
|
+
// Requirement extraction: grounded requirement units,
|
|
455
467
|
// extracted from whatever structure the spec has, BEFORE decompose — they ride
|
|
456
468
|
// into the decompose prompt as a ledger (structure-mirroring can't discharge
|
|
457
469
|
// them) and drive the per-requirement coverage accounting below.
|
|
@@ -462,8 +474,8 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
462
474
|
try {
|
|
463
475
|
// Recall floor: the obligation-marked passages ride into the prompt as a
|
|
464
476
|
// checklist, and a marked passage that produced NO quote is hard evidence
|
|
465
|
-
// for one forced re-extraction
|
|
466
|
-
// entire marked
|
|
477
|
+
// for one forced re-extraction: without it an extraction can miss an
|
|
478
|
+
// entire marked section.
|
|
467
479
|
const passages = enumerateObligationPassages(featureForModel);
|
|
468
480
|
const extractOnce = async (hint) => keepGroundedRequirements(parseRequirementLines(await deps.runChild('requirement-extract', '', prependHint(hint, REQUIREMENT_EXTRACT_PROMPT(featureForModel, passages)))), featureForModel);
|
|
469
481
|
reqEntries = await extractOnce(null);
|
|
@@ -475,20 +487,23 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
475
487
|
// Union of both grounded passes (keepGrounded dedupes).
|
|
476
488
|
reqEntries = keepGroundedRequirements([...reqEntries, ...retry], featureForModel);
|
|
477
489
|
}
|
|
478
|
-
// Bound with marked-passage priority
|
|
479
|
-
// doc's tail sections
|
|
490
|
+
// Bound with marked-passage priority. A plain first-N cap truncates the
|
|
491
|
+
// doc's tail sections, because an eager extraction fills the budget from the
|
|
492
|
+
// top down and never reaches them.
|
|
480
493
|
reqEntries = capRequirements(reqEntries, passages, featureForModel);
|
|
481
494
|
logPlanDebug(cwd, `requirement extraction: ${reqEntries.length} grounded requirement(s) kept`);
|
|
482
495
|
}
|
|
483
496
|
catch {
|
|
484
497
|
// best-effort channel
|
|
485
498
|
}
|
|
486
|
-
// Granularity floor
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
499
|
+
// Granularity floor: without it the plan's task COUNT is set by an
|
|
500
|
+
// auto-resolved clarify line the user never sees, so the same spec and the same
|
|
501
|
+
// code can plan coarse one run and fine the next. Derive the floor from the
|
|
502
|
+
// requirements a task can own instead, so an unreviewable "one task per
|
|
490
503
|
// milestone" decision cannot collapse the plan; it also gates whether the
|
|
491
|
-
// plan-shape fork below is the host's to answer at all.
|
|
504
|
+
// plan-shape fork below is the host's to answer at all. Measured:
|
|
505
|
+
// granularityFloor is 0 for three or fewer ownable requirements — no channel —
|
|
506
|
+
// and roughly half the count above that.
|
|
492
507
|
const ownableRequirements = reqEntries.filter(e => !isCrossCuttingRequirement(e.quote)).length;
|
|
493
508
|
const coarseFloor = granularityFloor(ownableRequirements);
|
|
494
509
|
if (coarseFloor > 0) {
|
|
@@ -509,8 +524,9 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
509
524
|
* settles is auto-resolved and never shown — only genuine open forks reach the user.
|
|
510
525
|
* The model emits NONE when nothing remains.
|
|
511
526
|
*
|
|
512
|
-
* The ONLY stage that
|
|
513
|
-
*
|
|
527
|
+
* The ONLY stage that ASKS the user anything, and so the only one that can be
|
|
528
|
+
* dismissed — the others only notify, which cannot be. `null` means the user
|
|
529
|
+
* cancelled and the cancellation has already been announced.
|
|
514
530
|
* Every other outcome is a transcript, possibly empty.
|
|
515
531
|
*/
|
|
516
532
|
export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
@@ -527,17 +543,18 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
527
543
|
// strikes out (can't produce anything novel) we stop instead of barraging the
|
|
528
544
|
// user with the same decision worded N ways. Also caps the absolute count.
|
|
529
545
|
// The generate → parse → pick → dedupe → re-prompt state machine is
|
|
530
|
-
// task/question-source.ts, shared with the plan session.
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
// the feature with ZERO clarifications — on a formatting slip.
|
|
546
|
+
// task/question-source.ts, shared with the plan session. A second copy here
|
|
547
|
+
// would drift from the sibling, and the shared source buys two things: it
|
|
548
|
+
// PICKS a question rather than taking the first parsed line, so an analysis
|
|
549
|
+
// note is never shown as the question and its SUGGESTED is not lost; and an
|
|
550
|
+
// unparseable reply costs one format re-prompt instead of ending clarify
|
|
551
|
+
// outright and decomposing the feature with ZERO clarifications.
|
|
537
552
|
//
|
|
538
|
-
// Of plan's three quality rules only the DEFERRAL guard crosses.
|
|
539
|
-
//
|
|
540
|
-
//
|
|
553
|
+
// Of plan's three quality rules only the DEFERRAL guard crosses. Checked:
|
|
554
|
+
// PLAN_QUALITY_RULES holds three ('no SUGGESTED', 'SUGGESTED deferred the
|
|
555
|
+
// decision', 'fork-shaped question with no ALT') and CLARIFY_QUALITY_RULES
|
|
556
|
+
// holds exactly the middle one. The other two cost an extra child call every
|
|
557
|
+
// time they fire, so moving them is its own change to make and measure.
|
|
541
558
|
const source = makeQuestionSource({
|
|
542
559
|
generate: hint => deps.runChild('auto-clarify', 'read', prependHint(hint, AUTO_CLARIFY_PROMPT(featureForModel, transcript.forGenerator()))),
|
|
543
560
|
formatHint: PLAN_FORMAT_HINT,
|
|
@@ -554,9 +571,10 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
554
571
|
// for the editable default and the persisted file.
|
|
555
572
|
const shownQ = renderInlineMarkdown(question, theme);
|
|
556
573
|
const plainQ = drawn.plain;
|
|
557
|
-
// PLAN SHAPE is the host's call, not the triage's
|
|
558
|
-
// same
|
|
559
|
-
//
|
|
574
|
+
// PLAN SHAPE is the host's call, not the triage's — the same spec off the
|
|
575
|
+
// same base commit can plan far coarser or finer depending on this one
|
|
576
|
+
// answer (see decompose-granularity.ts).
|
|
577
|
+
// The triage answers this fork for itself every time and stamps it
|
|
560
578
|
// "already settled by the spec" while the spec settles no such thing, so the
|
|
561
579
|
// single most load-bearing decision in a run was an invisible coin flip.
|
|
562
580
|
// Answer it deterministically instead: same channel, same transcript, but a
|
|
@@ -610,9 +628,9 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
610
628
|
*/
|
|
611
629
|
export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
612
630
|
const { featureForModel, reqEntries, ownableRequirements, coarseFloor } = oriented;
|
|
613
|
-
// Tests-in-the-same-change cadence
|
|
614
|
-
// decisions mandate it, a whole-project batch test task contradicts them
|
|
615
|
-
//
|
|
631
|
+
// Tests-in-the-same-change cadence: when the
|
|
632
|
+
// decisions mandate it, a whole-project batch test task contradicts them, and
|
|
633
|
+
// one still ships because
|
|
616
634
|
// decompose mirrors the spec's milestone shape. The decisions channel
|
|
617
635
|
// OVERRIDES the spec doc, so this resolves toward the decision without asking.
|
|
618
636
|
const noBatchTests = mandatesTestsInSameChange(clarifications, featureForModel);
|
|
@@ -622,7 +640,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
622
640
|
}
|
|
623
641
|
// decompose
|
|
624
642
|
const decomposePrompt = AUTO_DECOMPOSE_PROMPT(featureForModel, clarifications, buildRequirementsLedger(reqEntries), noBatchTests);
|
|
625
|
-
// Parse + FIDELITY RECONCILIATION
|
|
643
|
+
// Parse + FIDELITY RECONCILIATION: ground each title's
|
|
626
644
|
// [source: "…"] citation against the doc, strip the clause, and re-attach any
|
|
627
645
|
// `+`-joined constraint fragment the paraphrased title dropped (the silently
|
|
628
646
|
// stripped "+ tests" class). Applied to EVERY decompose output — initial,
|
|
@@ -639,7 +657,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
639
657
|
// Batch-test ban (item 6): drop or scope a whole-project "write all the
|
|
640
658
|
// tests" task. Identity unless the cadence decision is present, and the
|
|
641
659
|
// sweep replacement re-grounds every requirement the drop would cost — so
|
|
642
|
-
// planned coverage cannot fall
|
|
660
|
+
// planned coverage cannot fall.
|
|
643
661
|
const debatched = rewriteBatchTestPlan(plan.titles, clarifications, featureForModel, reqEntries.map(e => e.quote), isCrossCuttingRequirement);
|
|
644
662
|
for (const a of debatched.actions) {
|
|
645
663
|
logPlanDebug(cwd, `batch test task ${a.kind} (tests-in-same-change decision): "${a.title}"`
|
|
@@ -654,7 +672,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
654
672
|
logPlanDebug(cwd, `decompose produced ${planTitles.length} title(s)`);
|
|
655
673
|
// BRACES for the floor: the prompt clause alone is a preference the model can
|
|
656
674
|
// ignore, so a plan under the floor is sent back ONCE to be split (never
|
|
657
|
-
// regenerated — a fresh roll can drop a covered area
|
|
675
|
+
// regenerated — a fresh roll can drop a covered area). Longer plan
|
|
658
676
|
// wins; a still-coarse plan falls through to the coverage judge as before, so
|
|
659
677
|
// this can never block planning.
|
|
660
678
|
if (isTooCoarse(planTitles.length, coarseFloor)) {
|
|
@@ -668,8 +686,8 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
668
686
|
}
|
|
669
687
|
// Distrust floor (see isSuspectPlan): a ≤2-title plan for a multi-KB spec is
|
|
670
688
|
// regenerated once BEFORE the judge runs — the judge cannot be trusted to
|
|
671
|
-
// catch it
|
|
672
|
-
//
|
|
689
|
+
// catch it, and a hinted retry heals it reliably.
|
|
690
|
+
// Longer list wins; a still-suspect plan falls through to the
|
|
673
691
|
// judge loop as before, so this never blocks planning.
|
|
674
692
|
// An EMPTY plan gets extra attempts (see EMPTY_PLAN_RETRIES): falling through
|
|
675
693
|
// with zero titles aborts the whole run, so one roll of the dice is not enough.
|
|
@@ -717,17 +735,16 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
717
735
|
const { featureForModel, reqEntries } = oriented;
|
|
718
736
|
const { decomposePrompt, parsePlan } = decomposed;
|
|
719
737
|
let planTitles = decomposed.planTitles;
|
|
720
|
-
// Coverage gate: a
|
|
721
|
-
//
|
|
738
|
+
// Coverage gate: a degenerate completion — ONE task and a natural EOS for a
|
|
739
|
+
// whole design document — is nonempty, so the length guard below
|
|
722
740
|
// never fires and the whole run "completes" after one task. Judge the list
|
|
723
741
|
// against the feature with a no-tools child; on INCOMPLETE, re-run decompose
|
|
724
742
|
// with the missing areas as a hint. Best-effort so a triage fault never blocks
|
|
725
743
|
// planning (mirrors triageClarifyQuestion).
|
|
726
744
|
//
|
|
727
|
-
// Two
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
// requirements no task could own that kept the verdict INCOMPLETE forever):
|
|
745
|
+
// Two invariants. Without them a complete full-stack plan is overwritten by a
|
|
746
|
+
// narrower regeneration, driven by a handful of NEGATIVE requirements that no
|
|
747
|
+
// task can own and that therefore keep the verdict INCOMPLETE forever:
|
|
731
748
|
// • MONOTONIC replacement (coverage-loop.ts): a retry that DROPS a requirement
|
|
732
749
|
// the current plan already owns is REJECTED, never adopted. Coverage can
|
|
733
750
|
// only hold or grow across rounds — a worse regeneration can no longer
|
|
@@ -759,8 +776,8 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
759
776
|
// numbers. Live (Qwen3.6-27B) the model over-credits ownership, mapping a
|
|
760
777
|
// "--json output" requirement to a generic "scaffold + argument parser"
|
|
761
778
|
// task, so a plan with no --json task still "owned" it and the drop guard
|
|
762
|
-
// went blind
|
|
763
|
-
//
|
|
779
|
+
// went blind. Grounding the drop-signal in the titles the model cannot
|
|
780
|
+
// fake restores it. The model map still drives Fix A's
|
|
764
781
|
// cross-cutting/unmapped accounting below (that only affects reprompt
|
|
765
782
|
// aggressiveness, which the monotonic guard now backstops).
|
|
766
783
|
const covered = groundedCoverage(reqEntries.map(e => e.quote), titles, isCrossCuttingRequirement);
|
|
@@ -796,9 +813,9 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
796
813
|
// `best` is both the plan the next round reprompts FROM and the plan that
|
|
797
814
|
// ships — kept identical because adoption is monotone (see coverage-loop.ts).
|
|
798
815
|
//
|
|
799
|
-
// It is also the ONLY handle on the accounting.
|
|
800
|
-
//
|
|
801
|
-
//
|
|
816
|
+
// It is also the ONLY handle on the accounting. A second one carried
|
|
817
|
+
// alongside is how a requirement gets attached to the wrong task — see the
|
|
818
|
+
// ScoredPlan doc comment.
|
|
802
819
|
// The record, and the decisions it makes: task/plan-rounds.ts. This was five
|
|
803
820
|
// locals threaded by closure through a ~90-line loop, plus a
|
|
804
821
|
// snapshot-before-overwrite pair that existed only because the bonus-round
|
|
@@ -859,8 +876,8 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
859
876
|
const round = rounds.round();
|
|
860
877
|
planTitles = best.plan.titles;
|
|
861
878
|
// Exhausted still INCOMPLETE: the best plan ships (the gate is best-effort), but
|
|
862
|
-
// silently shipping a KNOWN-gapped plan is how
|
|
863
|
-
//
|
|
879
|
+
// silently shipping a KNOWN-gapped plan is how a run loses a whole area of
|
|
880
|
+
// work — tell the user what is still uncovered.
|
|
864
881
|
const unresolvedMissing = rounds.unresolved();
|
|
865
882
|
if (unresolvedMissing !== null) {
|
|
866
883
|
logPlanDebug(cwd, `decompose-coverage exhausted ${round} round(s) still INCOMPLETE — missing: `
|
|
@@ -883,14 +900,14 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
883
900
|
const clarifications = await elicitClarifications(ctx, cwd, deps, oriented);
|
|
884
901
|
if (clarifications === null)
|
|
885
902
|
return null; // dismissed; already announced
|
|
886
|
-
// Artifact-production closure, plan side
|
|
903
|
+
// Artifact-production closure, plan side: runtime
|
|
887
904
|
// files the spec REFERENCES (server snippets, prose "serve the built
|
|
888
905
|
// index.html") that neither its file tree, its parsed build outputs, nor the
|
|
889
906
|
// existing scaffold produce. Sentence-grounded coverage credited the SERVING
|
|
890
907
|
// side and reported "0 unowned" while nothing ever CREATED the file — so
|
|
891
908
|
// these ride the coverage loop's `missing` list as unowned areas until some
|
|
892
909
|
// task title claims the artifact (grounded in titles, which the coverage-map
|
|
893
|
-
// model cannot fake — the
|
|
910
|
+
// model cannot fake — the lesson). Deterministic and best-effort.
|
|
894
911
|
let specDangling = [];
|
|
895
912
|
try {
|
|
896
913
|
specDangling = findSpecDanglingArtifacts(featureForModel, rel => existsSync(path.join(cwd, rel)));
|
|
@@ -910,16 +927,16 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
910
927
|
const planTitles = covered.planTitles;
|
|
911
928
|
// Carry what no single task owns (goal A(b)/(c)): cross-cutting requirements
|
|
912
929
|
// become `.pi-tasks/requirements.md`, injected VERBATIM into every task's
|
|
913
|
-
// refine/compose
|
|
914
|
-
// is authoritative" pointer
|
|
915
|
-
// pointers
|
|
930
|
+
// refine/compose. A rule stated only in the spec body has no carrier, and a
|
|
931
|
+
// "spec is authoritative" pointer recovers it in a minority of tasks: content
|
|
932
|
+
// travels, pointers do not. Requirements still unmapped after the rounds are carried
|
|
916
933
|
// too — marked — and recorded user-visibly in the plan file, never dropped.
|
|
917
934
|
//
|
|
918
935
|
// #1: the holistic-judge missing areas are carried as a THIRD channel. They are
|
|
919
936
|
// areas requirement-extraction never captured as a tracked entry (so the
|
|
920
937
|
// grounded accounting is structurally blind to them), seen only by the judge —
|
|
921
|
-
// exactly the class that, having no carrier,
|
|
922
|
-
//
|
|
938
|
+
// exactly the class that, having no carrier, is warned about and then
|
|
939
|
+
// dropped. Carried independent of `accounting` so a mapping
|
|
923
940
|
// fault (accounting === null) can't strand them either.
|
|
924
941
|
const carriedCrossCutting = best.accounting?.crossCutting ?? [];
|
|
925
942
|
const carriedUnmapped = best.accounting?.unmapped ?? [];
|
|
@@ -959,7 +976,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
959
976
|
// empty-plan path the plan is discarded one line later, so running them first
|
|
960
977
|
// burned two model calls and left contracts.md / launch-contract.md carrying
|
|
961
978
|
// facts for a run that never produced a task.
|
|
962
|
-
// Cross-slice contract registry
|
|
979
|
+
// Cross-slice contract registry: now that the plan is settled,
|
|
963
980
|
// extract the interface facts MORE THAN ONE slice must agree on — endpoint paths,
|
|
964
981
|
// exported signatures, file layouts, env var names the DESIGN pins — into a
|
|
965
982
|
// run-level artifact each downstream refine/compose/verify reads. The extraction
|
|
@@ -979,12 +996,12 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
979
996
|
ground: emitted => keepGroundedContracts(emitted, featureForModel),
|
|
980
997
|
append: appendContracts
|
|
981
998
|
});
|
|
982
|
-
// Launch contract
|
|
999
|
+
// Launch contract: extract the package/build SCRIPTS the design
|
|
983
1000
|
// declares the project must expose (`migrate`/`seed` fell through decompose and
|
|
984
1001
|
// shipped missing, unchecked). Each emitted name is re-grounded against the design
|
|
985
1002
|
// (keepGroundedScripts — kept only if the design backticks it), so the final gate's
|
|
986
1003
|
// manifest diff can never false-flag a hallucinated script. Recall is mechanical
|
|
987
|
-
|
|
1004
|
+
//: enumerateScriptCandidates hands the child every backticked
|
|
988
1005
|
// script-shaped token near the word "script" as a checklist, so a script declared
|
|
989
1006
|
// far from the design's summary list (`test:ct` in §2 vs §9's five) can't be
|
|
990
1007
|
// missed by a weak model's recall — the child classifies, it no longer recalls.
|
|
@@ -1001,10 +1018,10 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
1001
1018
|
append: appendDeclaredScripts
|
|
1002
1019
|
});
|
|
1003
1020
|
// Persist the TASK-MAPPED requirements keyed by the (spec-ref-attached) title
|
|
1004
|
-
// each task will carry
|
|
1005
|
-
//
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1021
|
+
// each task will carry. With only cross-cutting entries travelling, the
|
|
1022
|
+
// mapped ones shape the title list and then vanish, and a task can narrow a
|
|
1023
|
+
// requirement out of its own spec with nothing to stop it.
|
|
1024
|
+
// Inert until the owned-requirements injection is wired into the phase
|
|
1008
1025
|
// prompts; recorded regardless so the plan's mapping is auditable per run.
|
|
1009
1026
|
if (best.accounting && best.accounting.mapped.length > 0) {
|
|
1010
1027
|
await writeOwnedRequirements(cwd, best.accounting.mapped
|
|
@@ -1042,7 +1059,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
1042
1059
|
*
|
|
1043
1060
|
* The grounding step is the reason this shape exists rather than a plain child
|
|
1044
1061
|
* call. A child asked for interface facts will paraphrase and occasionally invent
|
|
1045
|
-
* them
|
|
1062
|
+
* them, and an invented fact in a run-level registry is read as
|
|
1046
1063
|
* authoritative by every downstream refine/compose/verify. So nothing the child
|
|
1047
1064
|
* says is trusted: `ground` re-checks each emitted line against the design text
|
|
1048
1065
|
* host-side, and only substrings survive.
|
|
@@ -1086,9 +1103,9 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1086
1103
|
cwd,
|
|
1087
1104
|
taskId: '',
|
|
1088
1105
|
signal,
|
|
1089
|
-
// IN-RUN thrash guard for the planning children
|
|
1090
|
-
//
|
|
1091
|
-
//
|
|
1106
|
+
// IN-RUN thrash guard for the planning children: without it a decompose
|
|
1107
|
+
// child can re-read its design document until it fills the whole context
|
|
1108
|
+
// window, and never return. Every planning child
|
|
1092
1109
|
// gets its source doc INLINED in its prompt, so a second read of a file
|
|
1093
1110
|
// it has already opened can only be thrash — which makes the read-once
|
|
1094
1111
|
// block safe here in a way it is not for a phase that must explore.
|
|
@@ -1128,14 +1145,14 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1128
1145
|
runFinalIntegrationGate(cwd2, { planText, signal })
|
|
1129
1146
|
: Promise.resolve({ ok: true, reason: 'disabled' }),
|
|
1130
1147
|
// Uncommitted paths, for the stranded-sub-fix handling around the final-gate
|
|
1131
|
-
// picker
|
|
1148
|
+
// picker. Every task is committed by the time
|
|
1132
1149
|
// the gate runs, so whatever is dirty here belongs to the fix pass.
|
|
1133
1150
|
pendingChanges: async (cwd2) => {
|
|
1134
1151
|
const changes = await collectTreeChanges(cwd2, signal);
|
|
1135
1152
|
return [...changes.modified, ...changes.added, ...changes.deleted].sort();
|
|
1136
1153
|
},
|
|
1137
1154
|
// Re-derive the debt ledger against the FINAL tree after a converged
|
|
1138
|
-
// autofix
|
|
1155
|
+
// autofix. Only ever reached from inside the gate's own
|
|
1139
1156
|
// resolution loop, so it needs no `verify work` switch of its own.
|
|
1140
1157
|
// Same section, same cancel: this re-runs every ACCEPT-debt VERIFY command
|
|
1141
1158
|
// against the final tree, each under its own 300s cap.
|
|
@@ -1150,11 +1167,11 @@ export function requestAutoCancel() {
|
|
|
1150
1167
|
/**
|
|
1151
1168
|
* Report a stash pushed during one task and left behind.
|
|
1152
1169
|
*
|
|
1153
|
-
* An orphan stash later pops as an unresolvable conflict
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1170
|
+
* An orphan stash later pops as an unresolvable conflict, so the capture before
|
|
1171
|
+
* the task and this check after it are ONE fact — which is why the call sits in a
|
|
1172
|
+
* `finally` rather than on the fall-through, where it would only run when the task
|
|
1173
|
+
* succeeded. Best-effort: never throws, so it cannot mask the outcome of the
|
|
1174
|
+
* attempt it closes.
|
|
1158
1175
|
*/
|
|
1159
1176
|
async function reportStashDrift(active, deps, cwd, id, title, before) {
|
|
1160
1177
|
if (!deps.stashRef || before === undefined)
|
|
@@ -1198,8 +1215,8 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1198
1215
|
const next = entries.find(e => !e.done);
|
|
1199
1216
|
if (!next) {
|
|
1200
1217
|
// FINAL INTEGRATION GATE: every task passed its own per-slice gates,
|
|
1201
|
-
// but per-slice green
|
|
1202
|
-
//
|
|
1218
|
+
// but per-slice green can still ship a dead app — every slice's own
|
|
1219
|
+
// checks clean while the assembled whole does not serve. The run-level stage
|
|
1203
1220
|
// runs the project's OWN whole-repo commands once, unaided, before the
|
|
1204
1221
|
// run is declared complete, and resolves a FAIL with the user. It
|
|
1205
1222
|
// touches none of this loop's per-task state — see run-final-gate.ts —
|
|
@@ -1210,7 +1227,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1210
1227
|
runId: id,
|
|
1211
1228
|
// The parent plan (the task list) is what lets the gate tell a
|
|
1212
1229
|
// served app from a CLI — the boot check requires a listener only
|
|
1213
|
-
// for the former
|
|
1230
|
+
// for the former.
|
|
1214
1231
|
planText: body,
|
|
1215
1232
|
taskCount: entries.length
|
|
1216
1233
|
});
|
|
@@ -1228,8 +1245,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1228
1245
|
return;
|
|
1229
1246
|
}
|
|
1230
1247
|
// REFUSE to start on a conflicted tree: an unmerged index dooms every
|
|
1231
|
-
// commit ahead and a `git add -A` would silently mis-resolve it.
|
|
1232
|
-
// run 6 burned a full impl turn + three verify passes exactly here.
|
|
1248
|
+
// commit ahead and a `git add -A` would silently mis-resolve it.
|
|
1233
1249
|
const unmerged = deps.unmergedPaths ? await deps.unmergedPaths(cwd) : [];
|
|
1234
1250
|
if (unmerged.length > 0) {
|
|
1235
1251
|
await updateTaskFrontMatter(cwd, id, { state: 'failed' });
|
|
@@ -1280,8 +1296,8 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1280
1296
|
try {
|
|
1281
1297
|
// SAFE CHECKPOINT (pre-task): the tree is committed and no inner task
|
|
1282
1298
|
// is stamped yet, so stopping here just leaves this entry unchecked —
|
|
1283
|
-
// a resume restarts it from scratch.
|
|
1284
|
-
// last one before
|
|
1299
|
+
// a resume restarts it from scratch. The cheapest possible stop, and
|
|
1300
|
+
// the last one before a whole task is under way.
|
|
1285
1301
|
if (cancelCheckpoint('pre-task')) {
|
|
1286
1302
|
announceDone(active, `${id} cancelled before "${next.title}" — resume with /task-auto-resume.`, 'warning');
|
|
1287
1303
|
return;
|
|
@@ -1297,18 +1313,18 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1297
1313
|
onStart: resumeId ? undefined : (innerId => stampTaskInProgress(cwd, id, next.index, innerId, next.title))
|
|
1298
1314
|
});
|
|
1299
1315
|
active = res.ctx ?? active;
|
|
1300
|
-
// One dispatch over the named ending. The
|
|
1301
|
-
//
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1316
|
+
// One dispatch over the named ending. The runner NAMES how the run
|
|
1317
|
+
// ended, so nothing here has to infer a user stop from a fault by
|
|
1318
|
+
// consulting a module global — the way that inference went wrong was
|
|
1319
|
+
// announcing a cancel in red as "stopped … fix and resume" and
|
|
1320
|
+
// overwriting the inner file's `cancelled` with `failed`.
|
|
1321
|
+
// Resumability is RUN_END_POLICY's call (shared with /task's loop);
|
|
1322
|
+
// only the wording is this command's.
|
|
1307
1323
|
if (!runSucceeded(res.end)) {
|
|
1308
1324
|
const policy = RUN_END_POLICY[res.end.kind];
|
|
1309
1325
|
// Demote the INNER task file: it reads `completed` from
|
|
1310
1326
|
// spec-handoff, and leaving it that way is how a failed run's task
|
|
1311
|
-
// file
|
|
1327
|
+
// file claims success after the run failed.
|
|
1312
1328
|
if (policy.resumable)
|
|
1313
1329
|
await markResumable(cwd, res.taskId);
|
|
1314
1330
|
// The PLAN fails only on a fault. A declined-steer interrupt leaves
|
|
@@ -1373,20 +1389,20 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1373
1389
|
}), outcome.level);
|
|
1374
1390
|
return;
|
|
1375
1391
|
}
|
|
1376
|
-
// ROOT-CAUSE REPAIR
|
|
1392
|
+
// ROOT-CAUSE REPAIR: the gate may have attributed a
|
|
1377
1393
|
// FAIL to a pre-existing defect in a file some OTHER task created. It can
|
|
1378
1394
|
// only QUEUE that finding — mutating the plan is this loop's job. Drain
|
|
1379
1395
|
// the queue and splice a scoped repair step in right after the step that
|
|
1380
1396
|
// just finished, so the defect is fixed BEFORE the next dependent task
|
|
1381
|
-
// trips over it too
|
|
1382
|
-
//
|
|
1397
|
+
// trips over it too. Recording the same cause twice and scheduling
|
|
1398
|
+
// nothing lets one defect outlive most of a run.
|
|
1383
1399
|
await schedulePendingRepairs(cwd, id, next.index, active, deps);
|
|
1384
1400
|
}
|
|
1385
1401
|
finally {
|
|
1386
1402
|
// EVERY exit from this attempt passes here — the two mid-attempt
|
|
1387
|
-
// returns and a throw included.
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1403
|
+
// returns and a throw included. Sitting at the end of the
|
|
1404
|
+
// fall-through instead, below the capture and three returns, it
|
|
1405
|
+
// would run only when the task SUCCEEDED and the gate said
|
|
1390
1406
|
// `done`. On a failed or interrupted task the user is told to run
|
|
1391
1407
|
// /task-auto-resume, straight onto the landmine the guard exists to
|
|
1392
1408
|
// name. The pairing is structural now, not positional.
|
|
@@ -1428,7 +1444,7 @@ async function handleTaskAuto(args, ctx) {
|
|
|
1428
1444
|
// and the ordinary command path cannot reach us.
|
|
1429
1445
|
try {
|
|
1430
1446
|
await withRun(ctx, { onCancel: terminalCancel }, async () => {
|
|
1431
|
-
// Stamp a fresh per-run research-cache id
|
|
1447
|
+
// Stamp a fresh per-run research-cache id BEFORE planning so enrichment and
|
|
1432
1448
|
// every task's research phase share one run's cache; disabled ⇒ clears any token a
|
|
1433
1449
|
// prior run left, so nothing is cached.
|
|
1434
1450
|
configureResearchRun(getConfig().researchCache);
|
|
@@ -1493,10 +1509,10 @@ async function handleTaskAutoResume(args, ctx) {
|
|
|
1493
1509
|
try {
|
|
1494
1510
|
await withRun(ctx, { onCancel: terminalCancel }, async () => {
|
|
1495
1511
|
// Reuse the interrupted run's research-cache id, dropping only the entries whose
|
|
1496
|
-
// own package moved version
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1512
|
+
// own package moved version. A fresh id per resume discards the whole
|
|
1513
|
+
// working cache, and a whole-file freshness gate can never hold on a
|
|
1514
|
+
// greenfield run that installs packages as it goes — so invalidation is
|
|
1515
|
+
// per entry. See resumeResearchRun.
|
|
1500
1516
|
const research = await resumeResearchRun(cwd, getConfig().researchCache);
|
|
1501
1517
|
if (research.reused) {
|
|
1502
1518
|
logPlanDebug(cwd, `research cache: resume reused ${research.entries} entr(ies), `
|