@mjasnikovs/pi-task 0.38.29 → 0.38.31
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/index.js +2 -0
- 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 +85 -16
- package/dist/shared/command-watchdog.js +115 -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 +192 -165
- 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 +124 -55
- package/dist/task/child-runner.js +298 -90
- 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 +30 -8
- 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-guards.d.ts +26 -0
- package/dist/task/implementation-guards.js +177 -0
- package/dist/task/implementation-thinking.d.ts +33 -31
- package/dist/task/implementation-thinking.js +5 -6
- package/dist/task/implementation-turn.d.ts +39 -31
- package/dist/task/implementation-turn.js +41 -28
- 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 +27 -8
- package/dist/task/loop-detector.js +38 -14
- 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 +89 -66
- 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 +196 -252
- 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 +13 -12
- package/dist/workers/focused-extractor.js +27 -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 +65 -96
- package/dist/workers/pi-worker-core.js +93 -181
- 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 +27 -30
- package/dist/workers/single-read-guard.js +36 -36
- 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 +54 -56
- package/dist/workers/worker-profiles.js +63 -39
- 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';
|
|
@@ -20,7 +22,7 @@ import { drainRepairQueue, mergeRepairCandidates, planHasRepairFor, parseRepairT
|
|
|
20
22
|
import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasksDir } from './task-io.js';
|
|
21
23
|
import { readTextFile } from '../shared/fs-text.js';
|
|
22
24
|
import { findPhantomImports, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
23
|
-
import { prependHint, USER_CANCELLED } from './child-runner.js';
|
|
25
|
+
import { isFatalChildCause, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
24
26
|
import { requestCancel, resetCancel, isCancelRequested, cancelCheckpoint } from './cancel-points.js';
|
|
25
27
|
import { withRun, announceTerminal } from './run-bracket.js';
|
|
26
28
|
import { refineExistingFilesBlock, SINGLE_READ_EXTENSION_PATH } from './phases.js';
|
|
@@ -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
|
-
* vite.config.ts.
|
|
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.
|
|
302
307
|
*
|
|
303
|
-
* Precedence is the crux: a clarification is a CORRECTION to a
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
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.
|
|
313
|
+
*
|
|
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,30 @@ 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
|
-
catch {
|
|
484
|
-
//
|
|
496
|
+
catch (e) {
|
|
497
|
+
// Best-effort covers a child that answered badly. It must NOT cover a user
|
|
498
|
+
// ESC or a dead backend: planning would continue on an EMPTY ledger, moving
|
|
499
|
+
// the granularity floor and shipping a degraded plan instead of a cancel or
|
|
500
|
+
// a failure. Same rule as verify-resolution.ts.
|
|
501
|
+
if (isFatalChildCause(e))
|
|
502
|
+
throw e;
|
|
503
|
+
// Best-effort, but not silent: nothing else records a guard kill here.
|
|
504
|
+
logPlanDebug(cwd, `requirement extraction: skipped — ${e.message}`);
|
|
485
505
|
}
|
|
486
|
-
// Granularity floor
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
506
|
+
// Granularity floor: without it the plan's task COUNT is set by an
|
|
507
|
+
// auto-resolved clarify line the user never sees, so the same spec and the same
|
|
508
|
+
// code can plan coarse one run and fine the next. Derive the floor from the
|
|
509
|
+
// requirements a task can own instead, so an unreviewable "one task per
|
|
490
510
|
// milestone" decision cannot collapse the plan; it also gates whether the
|
|
491
|
-
// plan-shape fork below is the host's to answer at all.
|
|
511
|
+
// plan-shape fork below is the host's to answer at all. Measured:
|
|
512
|
+
// granularityFloor is 0 for three or fewer ownable requirements — no channel —
|
|
513
|
+
// and roughly half the count above that.
|
|
492
514
|
const ownableRequirements = reqEntries.filter(e => !isCrossCuttingRequirement(e.quote)).length;
|
|
493
515
|
const coarseFloor = granularityFloor(ownableRequirements);
|
|
494
516
|
if (coarseFloor > 0) {
|
|
@@ -509,8 +531,9 @@ export async function orientFeature(cwd, feature, deps) {
|
|
|
509
531
|
* settles is auto-resolved and never shown — only genuine open forks reach the user.
|
|
510
532
|
* The model emits NONE when nothing remains.
|
|
511
533
|
*
|
|
512
|
-
* The ONLY stage that
|
|
513
|
-
*
|
|
534
|
+
* The ONLY stage that ASKS the user anything, and so the only one that can be
|
|
535
|
+
* dismissed — the others only notify, which cannot be. `null` means the user
|
|
536
|
+
* cancelled and the cancellation has already been announced.
|
|
514
537
|
* Every other outcome is a transcript, possibly empty.
|
|
515
538
|
*/
|
|
516
539
|
export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
@@ -527,17 +550,18 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
527
550
|
// strikes out (can't produce anything novel) we stop instead of barraging the
|
|
528
551
|
// user with the same decision worded N ways. Also caps the absolute count.
|
|
529
552
|
// 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.
|
|
553
|
+
// task/question-source.ts, shared with the plan session. A second copy here
|
|
554
|
+
// would drift from the sibling, and the shared source buys two things: it
|
|
555
|
+
// PICKS a question rather than taking the first parsed line, so an analysis
|
|
556
|
+
// note is never shown as the question and its SUGGESTED is not lost; and an
|
|
557
|
+
// unparseable reply costs one format re-prompt instead of ending clarify
|
|
558
|
+
// outright and decomposing the feature with ZERO clarifications.
|
|
537
559
|
//
|
|
538
|
-
// Of plan's three quality rules only the DEFERRAL guard crosses.
|
|
539
|
-
//
|
|
540
|
-
//
|
|
560
|
+
// Of plan's three quality rules only the DEFERRAL guard crosses. Checked:
|
|
561
|
+
// PLAN_QUALITY_RULES holds three ('no SUGGESTED', 'SUGGESTED deferred the
|
|
562
|
+
// decision', 'fork-shaped question with no ALT') and CLARIFY_QUALITY_RULES
|
|
563
|
+
// holds exactly the middle one. The other two cost an extra child call every
|
|
564
|
+
// time they fire, so moving them is its own change to make and measure.
|
|
541
565
|
const source = makeQuestionSource({
|
|
542
566
|
generate: hint => deps.runChild('auto-clarify', 'read', prependHint(hint, AUTO_CLARIFY_PROMPT(featureForModel, transcript.forGenerator()))),
|
|
543
567
|
formatHint: PLAN_FORMAT_HINT,
|
|
@@ -554,9 +578,10 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
554
578
|
// for the editable default and the persisted file.
|
|
555
579
|
const shownQ = renderInlineMarkdown(question, theme);
|
|
556
580
|
const plainQ = drawn.plain;
|
|
557
|
-
// PLAN SHAPE is the host's call, not the triage's
|
|
558
|
-
// same
|
|
559
|
-
//
|
|
581
|
+
// PLAN SHAPE is the host's call, not the triage's — the same spec off the
|
|
582
|
+
// same base commit can plan far coarser or finer depending on this one
|
|
583
|
+
// answer (see decompose-granularity.ts).
|
|
584
|
+
// The triage answers this fork for itself every time and stamps it
|
|
560
585
|
// "already settled by the spec" while the spec settles no such thing, so the
|
|
561
586
|
// single most load-bearing decision in a run was an invisible coin flip.
|
|
562
587
|
// Answer it deterministically instead: same channel, same transcript, but a
|
|
@@ -610,9 +635,9 @@ export async function elicitClarifications(ctx, cwd, deps, oriented) {
|
|
|
610
635
|
*/
|
|
611
636
|
export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
612
637
|
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
|
-
//
|
|
638
|
+
// Tests-in-the-same-change cadence: when the
|
|
639
|
+
// decisions mandate it, a whole-project batch test task contradicts them, and
|
|
640
|
+
// one still ships because
|
|
616
641
|
// decompose mirrors the spec's milestone shape. The decisions channel
|
|
617
642
|
// OVERRIDES the spec doc, so this resolves toward the decision without asking.
|
|
618
643
|
const noBatchTests = mandatesTestsInSameChange(clarifications, featureForModel);
|
|
@@ -622,7 +647,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
622
647
|
}
|
|
623
648
|
// decompose
|
|
624
649
|
const decomposePrompt = AUTO_DECOMPOSE_PROMPT(featureForModel, clarifications, buildRequirementsLedger(reqEntries), noBatchTests);
|
|
625
|
-
// Parse + FIDELITY RECONCILIATION
|
|
650
|
+
// Parse + FIDELITY RECONCILIATION: ground each title's
|
|
626
651
|
// [source: "…"] citation against the doc, strip the clause, and re-attach any
|
|
627
652
|
// `+`-joined constraint fragment the paraphrased title dropped (the silently
|
|
628
653
|
// stripped "+ tests" class). Applied to EVERY decompose output — initial,
|
|
@@ -639,7 +664,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
639
664
|
// Batch-test ban (item 6): drop or scope a whole-project "write all the
|
|
640
665
|
// tests" task. Identity unless the cadence decision is present, and the
|
|
641
666
|
// sweep replacement re-grounds every requirement the drop would cost — so
|
|
642
|
-
// planned coverage cannot fall
|
|
667
|
+
// planned coverage cannot fall.
|
|
643
668
|
const debatched = rewriteBatchTestPlan(plan.titles, clarifications, featureForModel, reqEntries.map(e => e.quote), isCrossCuttingRequirement);
|
|
644
669
|
for (const a of debatched.actions) {
|
|
645
670
|
logPlanDebug(cwd, `batch test task ${a.kind} (tests-in-same-change decision): "${a.title}"`
|
|
@@ -654,7 +679,7 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
654
679
|
logPlanDebug(cwd, `decompose produced ${planTitles.length} title(s)`);
|
|
655
680
|
// BRACES for the floor: the prompt clause alone is a preference the model can
|
|
656
681
|
// 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
|
|
682
|
+
// regenerated — a fresh roll can drop a covered area). Longer plan
|
|
658
683
|
// wins; a still-coarse plan falls through to the coverage judge as before, so
|
|
659
684
|
// this can never block planning.
|
|
660
685
|
if (isTooCoarse(planTitles.length, coarseFloor)) {
|
|
@@ -668,8 +693,8 @@ export async function decomposePlan(cwd, deps, oriented, clarifications) {
|
|
|
668
693
|
}
|
|
669
694
|
// Distrust floor (see isSuspectPlan): a ≤2-title plan for a multi-KB spec is
|
|
670
695
|
// regenerated once BEFORE the judge runs — the judge cannot be trusted to
|
|
671
|
-
// catch it
|
|
672
|
-
//
|
|
696
|
+
// catch it, and a hinted retry heals it reliably.
|
|
697
|
+
// Longer list wins; a still-suspect plan falls through to the
|
|
673
698
|
// judge loop as before, so this never blocks planning.
|
|
674
699
|
// An EMPTY plan gets extra attempts (see EMPTY_PLAN_RETRIES): falling through
|
|
675
700
|
// with zero titles aborts the whole run, so one roll of the dice is not enough.
|
|
@@ -717,17 +742,16 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
717
742
|
const { featureForModel, reqEntries } = oriented;
|
|
718
743
|
const { decomposePrompt, parsePlan } = decomposed;
|
|
719
744
|
let planTitles = decomposed.planTitles;
|
|
720
|
-
// Coverage gate: a
|
|
721
|
-
//
|
|
745
|
+
// Coverage gate: a degenerate completion — ONE task and a natural EOS for a
|
|
746
|
+
// whole design document — is nonempty, so the length guard below
|
|
722
747
|
// never fires and the whole run "completes" after one task. Judge the list
|
|
723
748
|
// against the feature with a no-tools child; on INCOMPLETE, re-run decompose
|
|
724
749
|
// with the missing areas as a hint. Best-effort so a triage fault never blocks
|
|
725
750
|
// planning (mirrors triageClarifyQuestion).
|
|
726
751
|
//
|
|
727
|
-
// Two
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
// requirements no task could own that kept the verdict INCOMPLETE forever):
|
|
752
|
+
// Two invariants. Without them a complete full-stack plan is overwritten by a
|
|
753
|
+
// narrower regeneration, driven by a handful of NEGATIVE requirements that no
|
|
754
|
+
// task can own and that therefore keep the verdict INCOMPLETE forever:
|
|
731
755
|
// • MONOTONIC replacement (coverage-loop.ts): a retry that DROPS a requirement
|
|
732
756
|
// the current plan already owns is REJECTED, never adopted. Coverage can
|
|
733
757
|
// only hold or grow across rounds — a worse regeneration can no longer
|
|
@@ -759,8 +783,8 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
759
783
|
// numbers. Live (Qwen3.6-27B) the model over-credits ownership, mapping a
|
|
760
784
|
// "--json output" requirement to a generic "scaffold + argument parser"
|
|
761
785
|
// task, so a plan with no --json task still "owned" it and the drop guard
|
|
762
|
-
// went blind
|
|
763
|
-
//
|
|
786
|
+
// went blind. Grounding the drop-signal in the titles the model cannot
|
|
787
|
+
// fake restores it. The model map still drives Fix A's
|
|
764
788
|
// cross-cutting/unmapped accounting below (that only affects reprompt
|
|
765
789
|
// aggressiveness, which the monotonic guard now backstops).
|
|
766
790
|
const covered = groundedCoverage(reqEntries.map(e => e.quote), titles, isCrossCuttingRequirement);
|
|
@@ -796,9 +820,9 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
796
820
|
// `best` is both the plan the next round reprompts FROM and the plan that
|
|
797
821
|
// ships — kept identical because adoption is monotone (see coverage-loop.ts).
|
|
798
822
|
//
|
|
799
|
-
// It is also the ONLY handle on the accounting.
|
|
800
|
-
//
|
|
801
|
-
//
|
|
823
|
+
// It is also the ONLY handle on the accounting. A second one carried
|
|
824
|
+
// alongside is how a requirement gets attached to the wrong task — see the
|
|
825
|
+
// ScoredPlan doc comment.
|
|
802
826
|
// The record, and the decisions it makes: task/plan-rounds.ts. This was five
|
|
803
827
|
// locals threaded by closure through a ~90-line loop, plus a
|
|
804
828
|
// snapshot-before-overwrite pair that existed only because the bonus-round
|
|
@@ -859,8 +883,8 @@ export async function coverPlan(ctx, cwd, deps, oriented, clarifications, decomp
|
|
|
859
883
|
const round = rounds.round();
|
|
860
884
|
planTitles = best.plan.titles;
|
|
861
885
|
// Exhausted still INCOMPLETE: the best plan ships (the gate is best-effort), but
|
|
862
|
-
// silently shipping a KNOWN-gapped plan is how
|
|
863
|
-
//
|
|
886
|
+
// silently shipping a KNOWN-gapped plan is how a run loses a whole area of
|
|
887
|
+
// work — tell the user what is still uncovered.
|
|
864
888
|
const unresolvedMissing = rounds.unresolved();
|
|
865
889
|
if (unresolvedMissing !== null) {
|
|
866
890
|
logPlanDebug(cwd, `decompose-coverage exhausted ${round} round(s) still INCOMPLETE — missing: `
|
|
@@ -883,14 +907,14 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
883
907
|
const clarifications = await elicitClarifications(ctx, cwd, deps, oriented);
|
|
884
908
|
if (clarifications === null)
|
|
885
909
|
return null; // dismissed; already announced
|
|
886
|
-
// Artifact-production closure, plan side
|
|
910
|
+
// Artifact-production closure, plan side: runtime
|
|
887
911
|
// files the spec REFERENCES (server snippets, prose "serve the built
|
|
888
912
|
// index.html") that neither its file tree, its parsed build outputs, nor the
|
|
889
913
|
// existing scaffold produce. Sentence-grounded coverage credited the SERVING
|
|
890
914
|
// side and reported "0 unowned" while nothing ever CREATED the file — so
|
|
891
915
|
// these ride the coverage loop's `missing` list as unowned areas until some
|
|
892
916
|
// task title claims the artifact (grounded in titles, which the coverage-map
|
|
893
|
-
// model cannot fake — the
|
|
917
|
+
// model cannot fake — the lesson). Deterministic and best-effort.
|
|
894
918
|
let specDangling = [];
|
|
895
919
|
try {
|
|
896
920
|
specDangling = findSpecDanglingArtifacts(featureForModel, rel => existsSync(path.join(cwd, rel)));
|
|
@@ -910,16 +934,16 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
910
934
|
const planTitles = covered.planTitles;
|
|
911
935
|
// Carry what no single task owns (goal A(b)/(c)): cross-cutting requirements
|
|
912
936
|
// become `.pi-tasks/requirements.md`, injected VERBATIM into every task's
|
|
913
|
-
// refine/compose
|
|
914
|
-
// is authoritative" pointer
|
|
915
|
-
// pointers
|
|
937
|
+
// refine/compose. A rule stated only in the spec body has no carrier, and a
|
|
938
|
+
// "spec is authoritative" pointer recovers it in a minority of tasks: content
|
|
939
|
+
// travels, pointers do not. Requirements still unmapped after the rounds are carried
|
|
916
940
|
// too — marked — and recorded user-visibly in the plan file, never dropped.
|
|
917
941
|
//
|
|
918
942
|
// #1: the holistic-judge missing areas are carried as a THIRD channel. They are
|
|
919
943
|
// areas requirement-extraction never captured as a tracked entry (so the
|
|
920
944
|
// grounded accounting is structurally blind to them), seen only by the judge —
|
|
921
|
-
// exactly the class that, having no carrier,
|
|
922
|
-
//
|
|
945
|
+
// exactly the class that, having no carrier, is warned about and then
|
|
946
|
+
// dropped. Carried independent of `accounting` so a mapping
|
|
923
947
|
// fault (accounting === null) can't strand them either.
|
|
924
948
|
const carriedCrossCutting = best.accounting?.crossCutting ?? [];
|
|
925
949
|
const carriedUnmapped = best.accounting?.unmapped ?? [];
|
|
@@ -959,7 +983,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
959
983
|
// empty-plan path the plan is discarded one line later, so running them first
|
|
960
984
|
// burned two model calls and left contracts.md / launch-contract.md carrying
|
|
961
985
|
// facts for a run that never produced a task.
|
|
962
|
-
// Cross-slice contract registry
|
|
986
|
+
// Cross-slice contract registry: now that the plan is settled,
|
|
963
987
|
// extract the interface facts MORE THAN ONE slice must agree on — endpoint paths,
|
|
964
988
|
// exported signatures, file layouts, env var names the DESIGN pins — into a
|
|
965
989
|
// run-level artifact each downstream refine/compose/verify reads. The extraction
|
|
@@ -979,12 +1003,12 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
979
1003
|
ground: emitted => keepGroundedContracts(emitted, featureForModel),
|
|
980
1004
|
append: appendContracts
|
|
981
1005
|
});
|
|
982
|
-
// Launch contract
|
|
1006
|
+
// Launch contract: extract the package/build SCRIPTS the design
|
|
983
1007
|
// declares the project must expose (`migrate`/`seed` fell through decompose and
|
|
984
1008
|
// shipped missing, unchecked). Each emitted name is re-grounded against the design
|
|
985
1009
|
// (keepGroundedScripts — kept only if the design backticks it), so the final gate's
|
|
986
1010
|
// manifest diff can never false-flag a hallucinated script. Recall is mechanical
|
|
987
|
-
|
|
1011
|
+
//: enumerateScriptCandidates hands the child every backticked
|
|
988
1012
|
// script-shaped token near the word "script" as a checklist, so a script declared
|
|
989
1013
|
// far from the design's summary list (`test:ct` in §2 vs §9's five) can't be
|
|
990
1014
|
// missed by a weak model's recall — the child classifies, it no longer recalls.
|
|
@@ -1001,10 +1025,10 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
1001
1025
|
append: appendDeclaredScripts
|
|
1002
1026
|
});
|
|
1003
1027
|
// Persist the TASK-MAPPED requirements keyed by the (spec-ref-attached) title
|
|
1004
|
-
// each task will carry
|
|
1005
|
-
//
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1028
|
+
// each task will carry. With only cross-cutting entries travelling, the
|
|
1029
|
+
// mapped ones shape the title list and then vanish, and a task can narrow a
|
|
1030
|
+
// requirement out of its own spec with nothing to stop it.
|
|
1031
|
+
// Inert until the owned-requirements injection is wired into the phase
|
|
1008
1032
|
// prompts; recorded regardless so the plan's mapping is auditable per run.
|
|
1009
1033
|
if (best.accounting && best.accounting.mapped.length > 0) {
|
|
1010
1034
|
await writeOwnedRequirements(cwd, best.accounting.mapped
|
|
@@ -1042,7 +1066,7 @@ export async function planAuto(ctx, cwd, feature, deps) {
|
|
|
1042
1066
|
*
|
|
1043
1067
|
* The grounding step is the reason this shape exists rather than a plain child
|
|
1044
1068
|
* call. A child asked for interface facts will paraphrase and occasionally invent
|
|
1045
|
-
* them
|
|
1069
|
+
* them, and an invented fact in a run-level registry is read as
|
|
1046
1070
|
* authoritative by every downstream refine/compose/verify. So nothing the child
|
|
1047
1071
|
* says is trusted: `ground` re-checks each emitted line against the design text
|
|
1048
1072
|
* host-side, and only substrings survive.
|
|
@@ -1084,11 +1108,15 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1084
1108
|
const status = new ChildStatus({ parentContextWindow });
|
|
1085
1109
|
const phaseDeps = {
|
|
1086
1110
|
cwd,
|
|
1111
|
+
// No task file, so appendLoopEvent swallows its ENOENT. Its docblock
|
|
1112
|
+
// allows that because "the kill is already reported through the debug
|
|
1113
|
+
// log" — which is why logDebug below is not optional here.
|
|
1087
1114
|
taskId: '',
|
|
1088
1115
|
signal,
|
|
1089
|
-
|
|
1090
|
-
//
|
|
1091
|
-
//
|
|
1116
|
+
logDebug: msg => logPlanDebug(cwd, msg),
|
|
1117
|
+
// IN-RUN thrash guard for the planning children: without it a decompose
|
|
1118
|
+
// child can re-read its design document until it fills the whole context
|
|
1119
|
+
// window, and never return. Every planning child
|
|
1092
1120
|
// gets its source doc INLINED in its prompt, so a second read of a file
|
|
1093
1121
|
// it has already opened can only be thrash — which makes the read-once
|
|
1094
1122
|
// block safe here in a way it is not for a phase that must explore.
|
|
@@ -1128,14 +1156,14 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
1128
1156
|
runFinalIntegrationGate(cwd2, { planText, signal })
|
|
1129
1157
|
: Promise.resolve({ ok: true, reason: 'disabled' }),
|
|
1130
1158
|
// Uncommitted paths, for the stranded-sub-fix handling around the final-gate
|
|
1131
|
-
// picker
|
|
1159
|
+
// picker. Every task is committed by the time
|
|
1132
1160
|
// the gate runs, so whatever is dirty here belongs to the fix pass.
|
|
1133
1161
|
pendingChanges: async (cwd2) => {
|
|
1134
1162
|
const changes = await collectTreeChanges(cwd2, signal);
|
|
1135
1163
|
return [...changes.modified, ...changes.added, ...changes.deleted].sort();
|
|
1136
1164
|
},
|
|
1137
1165
|
// Re-derive the debt ledger against the FINAL tree after a converged
|
|
1138
|
-
// autofix
|
|
1166
|
+
// autofix. Only ever reached from inside the gate's own
|
|
1139
1167
|
// resolution loop, so it needs no `verify work` switch of its own.
|
|
1140
1168
|
// Same section, same cancel: this re-runs every ACCEPT-debt VERIFY command
|
|
1141
1169
|
// against the final tree, each under its own 300s cap.
|
|
@@ -1150,11 +1178,11 @@ export function requestAutoCancel() {
|
|
|
1150
1178
|
/**
|
|
1151
1179
|
* Report a stash pushed during one task and left behind.
|
|
1152
1180
|
*
|
|
1153
|
-
* An orphan stash later pops as an unresolvable conflict
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1181
|
+
* An orphan stash later pops as an unresolvable conflict, so the capture before
|
|
1182
|
+
* the task and this check after it are ONE fact — which is why the call sits in a
|
|
1183
|
+
* `finally` rather than on the fall-through, where it would only run when the task
|
|
1184
|
+
* succeeded. Best-effort: never throws, so it cannot mask the outcome of the
|
|
1185
|
+
* attempt it closes.
|
|
1158
1186
|
*/
|
|
1159
1187
|
async function reportStashDrift(active, deps, cwd, id, title, before) {
|
|
1160
1188
|
if (!deps.stashRef || before === undefined)
|
|
@@ -1198,8 +1226,8 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1198
1226
|
const next = entries.find(e => !e.done);
|
|
1199
1227
|
if (!next) {
|
|
1200
1228
|
// FINAL INTEGRATION GATE: every task passed its own per-slice gates,
|
|
1201
|
-
// but per-slice green
|
|
1202
|
-
//
|
|
1229
|
+
// but per-slice green can still ship a dead app — every slice's own
|
|
1230
|
+
// checks clean while the assembled whole does not serve. The run-level stage
|
|
1203
1231
|
// runs the project's OWN whole-repo commands once, unaided, before the
|
|
1204
1232
|
// run is declared complete, and resolves a FAIL with the user. It
|
|
1205
1233
|
// touches none of this loop's per-task state — see run-final-gate.ts —
|
|
@@ -1210,7 +1238,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1210
1238
|
runId: id,
|
|
1211
1239
|
// The parent plan (the task list) is what lets the gate tell a
|
|
1212
1240
|
// served app from a CLI — the boot check requires a listener only
|
|
1213
|
-
// for the former
|
|
1241
|
+
// for the former.
|
|
1214
1242
|
planText: body,
|
|
1215
1243
|
taskCount: entries.length
|
|
1216
1244
|
});
|
|
@@ -1228,8 +1256,7 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1228
1256
|
return;
|
|
1229
1257
|
}
|
|
1230
1258
|
// 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.
|
|
1259
|
+
// commit ahead and a `git add -A` would silently mis-resolve it.
|
|
1233
1260
|
const unmerged = deps.unmergedPaths ? await deps.unmergedPaths(cwd) : [];
|
|
1234
1261
|
if (unmerged.length > 0) {
|
|
1235
1262
|
await updateTaskFrontMatter(cwd, id, { state: 'failed' });
|
|
@@ -1280,8 +1307,8 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1280
1307
|
try {
|
|
1281
1308
|
// SAFE CHECKPOINT (pre-task): the tree is committed and no inner task
|
|
1282
1309
|
// is stamped yet, so stopping here just leaves this entry unchecked —
|
|
1283
|
-
// a resume restarts it from scratch.
|
|
1284
|
-
// last one before
|
|
1310
|
+
// a resume restarts it from scratch. The cheapest possible stop, and
|
|
1311
|
+
// the last one before a whole task is under way.
|
|
1285
1312
|
if (cancelCheckpoint('pre-task')) {
|
|
1286
1313
|
announceDone(active, `${id} cancelled before "${next.title}" — resume with /task-auto-resume.`, 'warning');
|
|
1287
1314
|
return;
|
|
@@ -1297,18 +1324,18 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1297
1324
|
onStart: resumeId ? undefined : (innerId => stampTaskInProgress(cwd, id, next.index, innerId, next.title))
|
|
1298
1325
|
});
|
|
1299
1326
|
active = res.ctx ?? active;
|
|
1300
|
-
// One dispatch over the named ending. The
|
|
1301
|
-
//
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1327
|
+
// One dispatch over the named ending. The runner NAMES how the run
|
|
1328
|
+
// ended, so nothing here has to infer a user stop from a fault by
|
|
1329
|
+
// consulting a module global — the way that inference went wrong was
|
|
1330
|
+
// announcing a cancel in red as "stopped … fix and resume" and
|
|
1331
|
+
// overwriting the inner file's `cancelled` with `failed`.
|
|
1332
|
+
// Resumability is RUN_END_POLICY's call (shared with /task's loop);
|
|
1333
|
+
// only the wording is this command's.
|
|
1307
1334
|
if (!runSucceeded(res.end)) {
|
|
1308
1335
|
const policy = RUN_END_POLICY[res.end.kind];
|
|
1309
1336
|
// Demote the INNER task file: it reads `completed` from
|
|
1310
1337
|
// spec-handoff, and leaving it that way is how a failed run's task
|
|
1311
|
-
// file
|
|
1338
|
+
// file claims success after the run failed.
|
|
1312
1339
|
if (policy.resumable)
|
|
1313
1340
|
await markResumable(cwd, res.taskId);
|
|
1314
1341
|
// The PLAN fails only on a fault. A declined-steer interrupt leaves
|
|
@@ -1373,20 +1400,20 @@ export async function runAutoLoop(ctx, cwd, id, deps) {
|
|
|
1373
1400
|
}), outcome.level);
|
|
1374
1401
|
return;
|
|
1375
1402
|
}
|
|
1376
|
-
// ROOT-CAUSE REPAIR
|
|
1403
|
+
// ROOT-CAUSE REPAIR: the gate may have attributed a
|
|
1377
1404
|
// FAIL to a pre-existing defect in a file some OTHER task created. It can
|
|
1378
1405
|
// only QUEUE that finding — mutating the plan is this loop's job. Drain
|
|
1379
1406
|
// the queue and splice a scoped repair step in right after the step that
|
|
1380
1407
|
// just finished, so the defect is fixed BEFORE the next dependent task
|
|
1381
|
-
// trips over it too
|
|
1382
|
-
//
|
|
1408
|
+
// trips over it too. Recording the same cause twice and scheduling
|
|
1409
|
+
// nothing lets one defect outlive most of a run.
|
|
1383
1410
|
await schedulePendingRepairs(cwd, id, next.index, active, deps);
|
|
1384
1411
|
}
|
|
1385
1412
|
finally {
|
|
1386
1413
|
// EVERY exit from this attempt passes here — the two mid-attempt
|
|
1387
|
-
// returns and a throw included.
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1414
|
+
// returns and a throw included. Sitting at the end of the
|
|
1415
|
+
// fall-through instead, below the capture and three returns, it
|
|
1416
|
+
// would run only when the task SUCCEEDED and the gate said
|
|
1390
1417
|
// `done`. On a failed or interrupted task the user is told to run
|
|
1391
1418
|
// /task-auto-resume, straight onto the landmine the guard exists to
|
|
1392
1419
|
// name. The pairing is structural now, not positional.
|
|
@@ -1428,7 +1455,7 @@ async function handleTaskAuto(args, ctx) {
|
|
|
1428
1455
|
// and the ordinary command path cannot reach us.
|
|
1429
1456
|
try {
|
|
1430
1457
|
await withRun(ctx, { onCancel: terminalCancel }, async () => {
|
|
1431
|
-
// Stamp a fresh per-run research-cache id
|
|
1458
|
+
// Stamp a fresh per-run research-cache id BEFORE planning so enrichment and
|
|
1432
1459
|
// every task's research phase share one run's cache; disabled ⇒ clears any token a
|
|
1433
1460
|
// prior run left, so nothing is cached.
|
|
1434
1461
|
configureResearchRun(getConfig().researchCache);
|
|
@@ -1493,10 +1520,10 @@ async function handleTaskAutoResume(args, ctx) {
|
|
|
1493
1520
|
try {
|
|
1494
1521
|
await withRun(ctx, { onCancel: terminalCancel }, async () => {
|
|
1495
1522
|
// Reuse the interrupted run's research-cache id, dropping only the entries whose
|
|
1496
|
-
// own package moved version
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1523
|
+
// own package moved version. A fresh id per resume discards the whole
|
|
1524
|
+
// working cache, and a whole-file freshness gate can never hold on a
|
|
1525
|
+
// greenfield run that installs packages as it goes — so invalidation is
|
|
1526
|
+
// per entry. See resumeResearchRun.
|
|
1500
1527
|
const research = await resumeResearchRun(cwd, getConfig().researchCache);
|
|
1501
1528
|
if (research.reused) {
|
|
1502
1529
|
logPlanDebug(cwd, `research cache: resume reused ${research.entries} entr(ies), `
|