@mjasnikovs/pi-task 0.38.15 → 0.38.16
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/shared/child-process.js +9 -16
- package/dist/task/accept-debt.d.ts +7 -5
- package/dist/task/accept-debt.js +16 -13
- package/dist/task/auto-orchestrator.js +38 -36
- package/dist/task/autofix-ledger.d.ts +113 -0
- package/dist/task/autofix-ledger.js +152 -0
- package/dist/task/boot-probe.d.ts +63 -1
- package/dist/task/boot-probe.js +98 -2
- package/dist/task/child-runner.d.ts +50 -6
- package/dist/task/child-runner.js +48 -69
- package/dist/task/command-run.d.ts +49 -6
- package/dist/task/command-run.js +154 -18
- package/dist/task/external-context.d.ts +9 -12
- package/dist/task/external-context.js +5 -5
- package/dist/task/failure-classifier.d.ts +9 -1
- package/dist/task/failure-classifier.js +9 -0
- package/dist/task/final-gate-fix.d.ts +22 -26
- package/dist/task/final-gate-fix.js +2 -7
- package/dist/task/final-gate.d.ts +10 -2
- package/dist/task/final-gate.js +49 -88
- package/dist/task/gate-deps.js +20 -13
- package/dist/task/orchestrator.d.ts +33 -24
- package/dist/task/orchestrator.js +66 -44
- package/dist/task/phases.d.ts +58 -34
- package/dist/task/phases.js +140 -113
- package/dist/task/plan-orchestrator.js +2 -2
- package/dist/task/repo-health-check.d.ts +21 -21
- package/dist/task/repo-health-check.js +43 -112
- package/dist/task/run-end.d.ts +77 -0
- package/dist/task/run-end.js +37 -0
- package/dist/task/run-final-gate.js +71 -79
- package/dist/task/task-gates.d.ts +8 -0
- package/dist/task/task-gates.js +23 -4
- package/dist/task/terminal-outcome.d.ts +1 -1
- package/dist/task/terminal-outcome.js +12 -0
- package/dist/workers/brave-search.d.ts +7 -0
- package/dist/workers/brave-search.js +36 -55
- package/dist/workers/ddg-search.d.ts +1 -1
- package/dist/workers/ddg-search.js +27 -47
- package/dist/workers/exa-search.d.ts +2 -2
- package/dist/workers/exa-search.js +53 -68
- package/dist/workers/html-clean.js +67 -88
- package/dist/workers/http-request.d.ts +74 -0
- package/dist/workers/http-request.js +103 -0
- package/dist/workers/npm-version.js +37 -42
- package/dist/workers/pi-worker-core.d.ts +13 -2
- package/dist/workers/pi-worker-core.js +12 -17
- package/dist/workers/pi-worker-docs.d.ts +1 -1
- package/dist/workers/pi-worker-docs.js +49 -68
- package/dist/workers/pi-worker-fetch.d.ts +1 -1
- package/dist/workers/pi-worker-fetch.js +20 -21
- package/dist/workers/pi-worker-search.js +6 -4
- package/dist/workers/pi-worker.js +5 -4
- package/dist/workers/search-core.d.ts +1 -1
- package/dist/workers/search-core.js +36 -42
- package/dist/workers/search-types.d.ts +13 -0
- package/dist/workers/search-types.js +27 -0
- package/dist/workers/shared.d.ts +51 -11
- package/dist/workers/shared.js +0 -0
- package/dist/workers/worker-channels.d.ts +60 -0
- package/dist/workers/worker-channels.js +98 -0
- package/package.json +1 -1
package/dist/task/phases.d.ts
CHANGED
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
* critique) plus the config table that drives the orchestrator loop.
|
|
4
4
|
*/
|
|
5
5
|
import type { ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
6
|
-
import {
|
|
7
|
-
import { fetchFocused } from '../workers/fetch-core.js';
|
|
8
|
-
import { type RunWorkerInput, type RunWorkerResult } from '../workers/pi-worker-core.js';
|
|
9
|
-
import type { SearchCoreInput, SearchCoreResult } from '../workers/search-core.js';
|
|
10
|
-
import type { SearchProvider } from '../workers/search-types.js';
|
|
11
|
-
import { type ExternalContextDeps } from './external-context.js';
|
|
6
|
+
import { type SearchProvider } from '../workers/search-types.js';
|
|
12
7
|
import { MAX_GRILL_QUESTIONS } from './prompts.js';
|
|
13
8
|
import { type PhaseName } from './task-types.js';
|
|
14
9
|
import { type WidgetState } from './widget.js';
|
|
@@ -101,29 +96,9 @@ export declare function resolveOwnedFreezeForThisTask(deps: PhaseDeps, spec: str
|
|
|
101
96
|
export declare function claimOwnedFreezeForThisTask(deps: PhaseDeps, refined: string): Promise<void>;
|
|
102
97
|
export declare const phaseRefine: (deps: PhaseDeps, raw: string, planContext?: string) => Promise<string>;
|
|
103
98
|
export declare function phaseVerifyTooling(deps: PhaseDeps, research: string): Promise<string>;
|
|
104
|
-
export interface PhaseResearchDeps extends ExternalContextDeps {
|
|
105
|
-
getFileInventory?: (cwd: string, signal?: AbortSignal) => Promise<string>;
|
|
106
|
-
/**
|
|
107
|
-
* Run ONE research worker. Absent (production) → the real `runWorker`.
|
|
108
|
-
*
|
|
109
|
-
* The seam exists for the same reason `getFileInventory` does: every decision
|
|
110
|
-
* `runSpec` makes — the three Research retry gates, the fatal/runaway/empty
|
|
111
|
-
* classification, the marker choice, `postProcess` — is a pure function of the
|
|
112
|
-
* returned `RunWorkerResult`, but reaching any of them used to require driving
|
|
113
|
-
* a fake process that emits JSON events. Substituting the result lets a gate be
|
|
114
|
-
* tested by the fields it actually reads.
|
|
115
|
-
*
|
|
116
|
-
* `label` is the worker's name — the same one `recordWorker` trails. It is
|
|
117
|
-
* passed because a substitute must answer differently per worker, and the only
|
|
118
|
-
* alternative is matching a marker sentence inside the prompt, which makes
|
|
119
|
-
* prompt copy load-bearing test infrastructure. Same reason
|
|
120
|
-
* `PhaseDeps.runChild` takes a name.
|
|
121
|
-
*/
|
|
122
|
-
runWorker?: (label: string, input: RunWorkerInput) => Promise<RunWorkerResult>;
|
|
123
|
-
}
|
|
124
99
|
/**
|
|
125
100
|
* Is live web search configured for this process? The keyless providers (exa,
|
|
126
|
-
* ddg) always are; only brave needs its API key
|
|
101
|
+
* ddg) always are; only brave needs its API key.
|
|
127
102
|
*/
|
|
128
103
|
export declare function searchConfigured(getEnv?: (k: string) => string | undefined, provider?: SearchProvider): boolean;
|
|
129
104
|
/** Extra prompt block for the APIS worker when search is available — trigger-framed
|
|
@@ -177,13 +152,8 @@ export declare function degradedSectionBody(name: string, reason: string, partia
|
|
|
177
152
|
*/
|
|
178
153
|
export declare function emptySectionBody(name: string): string;
|
|
179
154
|
export declare function isBareNoneAnswer(text: string): boolean;
|
|
180
|
-
export declare function phaseResearch(deps: PhaseDeps, refined: string
|
|
181
|
-
export
|
|
182
|
-
docsFocused?: typeof docsFocused;
|
|
183
|
-
fetchFocused?: typeof fetchFocused;
|
|
184
|
-
searchFn?: (input: SearchCoreInput) => Promise<SearchCoreResult>;
|
|
185
|
-
}
|
|
186
|
-
export declare function phaseAutoAnswer(deps: PhaseDeps, refined: string, research: string, question: string, autoDeps?: PhaseAutoAnswerDeps): Promise<AutoAnswer>;
|
|
155
|
+
export declare function phaseResearch(deps: PhaseDeps, refined: string): Promise<string>;
|
|
156
|
+
export declare function phaseAutoAnswer(deps: PhaseDeps, refined: string, research: string, question: string): Promise<AutoAnswer>;
|
|
187
157
|
export declare function phaseGrill(deps: PhaseDeps, ctx: ExtensionCommandContext, widgetState: WidgetState, refined: string, research: string): Promise<string>;
|
|
188
158
|
/**
|
|
189
159
|
* A refutation is a DELETION. Where the run's own research explicitly says a
|
|
@@ -220,5 +190,59 @@ export declare function phaseCritique(deps: PhaseDeps, spec: string, refined: st
|
|
|
220
190
|
*/
|
|
221
191
|
extraDefects?: string | null): Promise<string>;
|
|
222
192
|
export declare function critiqueWithFallback(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
193
|
+
/**
|
|
194
|
+
* REFINE — restate the raw prompt as a bounded 4-section spec, then subtractively
|
|
195
|
+
* strike any phantom runtime specifier (`bun:sql`) it carried up verbatim from the
|
|
196
|
+
* spec doc, BEFORE it flows to research/grill/compose. An appended correction alone
|
|
197
|
+
* loses: the affirmative survives into the composed GOAL and on to the implementer
|
|
198
|
+
* (proven: compose re-leaks it 4/4). Rewriting the source so compose has nothing to
|
|
199
|
+
* contradict is the fix. Silent + no-op when nothing is wrong or the runtime's types
|
|
200
|
+
* aren't installed.
|
|
201
|
+
*/
|
|
202
|
+
export declare function refinePhase(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
203
|
+
/**
|
|
204
|
+
* RESEARCH — the four workers, then the TOOLING verification pass, then a
|
|
205
|
+
* deterministic check of every runtime builtin specifier the refined task names
|
|
206
|
+
* (`bun:sql`, `node:…`) against the installed types. A doc can confidently name a
|
|
207
|
+
* module that does not exist; left unchecked it rides through every phase and the
|
|
208
|
+
* implementer fabricates a `declare module` shim to compile it. The corrections are
|
|
209
|
+
* APPENDED so compose folds them into CONSTRAINTS. No LLM cost, silent when clean.
|
|
210
|
+
*/
|
|
211
|
+
export declare function researchPhase(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
212
|
+
/** GRILL — the adaptive question loop, and the only phase that talks to the user. */
|
|
213
|
+
export declare function grillPhase(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
214
|
+
/**
|
|
215
|
+
* COMPOSE — drop constraints research REFUTED before composing, then compose.
|
|
216
|
+
*
|
|
217
|
+
* The drop mutates `p.refined` in place on purpose: the refuted constraint must be
|
|
218
|
+
* gone from every later reader of the refined spec, not just from this call's
|
|
219
|
+
* argument. Critique re-reads it.
|
|
220
|
+
*/
|
|
221
|
+
export declare function composePhase(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
222
|
+
/**
|
|
223
|
+
* CRITIQUE — the last spec-producing step, and the two host-side corrections that
|
|
224
|
+
* must run after it in THIS ORDER.
|
|
225
|
+
*
|
|
226
|
+
* BRACES (mx5 run 16): append any owned design obligation the spec still omits as a
|
|
227
|
+
* CONSTRAINTS bullet. The belt block upstream is obeyed ~25% (measured); a host-side
|
|
228
|
+
* append is obeyed by construction. Idempotent — quotes the spec already carries
|
|
229
|
+
* (belt-obeying reps) are skipped.
|
|
230
|
+
*
|
|
231
|
+
* Then DETACH: an owned obligation whose only file this spec also FREEZES is
|
|
232
|
+
* unsatisfiable here, so it moves to the pending task that writes that file rather
|
|
233
|
+
* than shipping a requirement no one can meet. It MUST run after the append, because
|
|
234
|
+
* the append is what writes the stamp the detach reads — a critique-time probe
|
|
235
|
+
* measured 0/40 because the stamp did not exist yet.
|
|
236
|
+
*/
|
|
237
|
+
export declare function critiquePhase(d: PhaseDeps, p: PhaseContext): Promise<string>;
|
|
238
|
+
/**
|
|
239
|
+
* The pipeline, as a table with no bodies.
|
|
240
|
+
*
|
|
241
|
+
* Every row's `run` is a named exported function, so the COMPOSITION inside a
|
|
242
|
+
* step — which is where this codebase's recorded phase defects have lived, not in
|
|
243
|
+
* the parts — is drivable directly instead of only through a whole TaskRunner run.
|
|
244
|
+
* The parts stay exported and separately covered; what changed is that the ORDER
|
|
245
|
+
* they run in is now asserted by driving the row rather than retyped in a test.
|
|
246
|
+
*/
|
|
223
247
|
export declare const PHASES: PhaseConfig[];
|
|
224
248
|
export declare function postCommitPhase(phase: PhaseConfig, deps: PhaseDeps, pc: PhaseContext, out: string): Promise<void>;
|
package/dist/task/phases.js
CHANGED
|
@@ -8,6 +8,8 @@ import { fetchFocused } from '../workers/fetch-core.js';
|
|
|
8
8
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
9
9
|
import { classifyWorkerFailure } from '../workers/worker-failure.js';
|
|
10
10
|
import { findPhantomImports, formatApiCorrections, rewritePhantomSpecifiers } from '../workers/phantom-imports.js';
|
|
11
|
+
import { searchProviderKey } from '../workers/search-types.js';
|
|
12
|
+
import { channelSet } from '../workers/worker-channels.js';
|
|
11
13
|
import { fanoutTimeoutPolicy, workerCarryForward, workerProgressCeilingMs, projectDocsBudget, projectDocsBudgetNotice } from './research-fanout-budget.js';
|
|
12
14
|
import { isIntegrationUnknown } from './unknown-routing.js';
|
|
13
15
|
import { extractUserDirectives, preserveDirectivesBlock, enforceDirectives } from './user-directives.js';
|
|
@@ -36,7 +38,7 @@ import { readContracts, buildContractsBlock, buildContractsVerifyBlock } from '.
|
|
|
36
38
|
import { readRequirements, buildRequirementsBlock, buildOwnedRequirementsBlock, readOwnedRequirements, writeOwnedRequirements, ownedForTitle, appendOwnedConstraints } from './requirements.js';
|
|
37
39
|
import { detachUnsatisfiableRequirements, claimPendingRequirements, unclaimedPendingRequirements, formatReassignActions } from './owned-freeze-reassign.js';
|
|
38
40
|
import { trackedSourceOracle } from './owned-freeze-conflict.js';
|
|
39
|
-
import { runPhaseChild,
|
|
41
|
+
import { runPhaseChild, runWithEmphasisRetry, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
40
42
|
import { SessionUI } from '../remote/bridge.js';
|
|
41
43
|
import { isYoloMode, yoloPickAutoAnswer, YOLO_STAMP } from './yolo.js';
|
|
42
44
|
// ─── Re-export constants from their home modules ────────────────────────────
|
|
@@ -262,7 +264,7 @@ export const phaseRefine = async (deps, raw, planContext) => {
|
|
|
262
264
|
// re-check the output below (lever). Empty on an ordinary prompt → refine unchanged.
|
|
263
265
|
const directives = extractUserDirectives(raw);
|
|
264
266
|
const directivesBlock = preserveDirectivesBlock(directives);
|
|
265
|
-
const refined = await
|
|
267
|
+
const refined = await runPhaseChild(deps, 'refine', 'read', appendNoThink(REFINE_PROMPT(raw, planContext, existingFiles, contracts, directivesBlock)),
|
|
266
268
|
// refine's deliverable is a 4-section text rewrite that never strictly
|
|
267
269
|
// needs a successful read — on a test-writing task against a large
|
|
268
270
|
// existing codebase the model over-explores (re-reads source hunting for
|
|
@@ -270,7 +272,7 @@ export const phaseRefine = async (deps, raw, planContext) => {
|
|
|
270
272
|
// attempt instead of hard-failing the whole run. See TASK_0016 (mx5):
|
|
271
273
|
// refine looped 3×/resume forever; the deliverable was always producible
|
|
272
274
|
// from the title + design doc alone.
|
|
273
|
-
{ degradeOnExhaustion: true });
|
|
275
|
+
{ degradeOnExhaustion: true, verb: 'restart' });
|
|
274
276
|
// Deterministic backstop: if the refined spec still dropped a directive, append
|
|
275
277
|
// it verbatim rather than trusting the paraphrase. No model in this path.
|
|
276
278
|
const { text, appended } = enforceDirectives(refined, directives);
|
|
@@ -300,21 +302,34 @@ export async function phaseVerifyTooling(deps, research) {
|
|
|
300
302
|
await setTaskSection(deps.cwd, deps.taskId, 'verified tooling', verifiedSection);
|
|
301
303
|
return replaceToolingWithVerified(research, parsed.verified);
|
|
302
304
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
|
|
305
|
+
/**
|
|
306
|
+
* The worker channels the APIS research worker is given.
|
|
307
|
+
*
|
|
308
|
+
* `pi-worker-search` + `pi-worker-fetch` ride along only when the configured
|
|
309
|
+
* engine is usable (a keyless engine always is; brave needs its key). A tool
|
|
310
|
+
* without a key just errors, and a weak model burns calls on it — while search
|
|
311
|
+
* being ABSENT was structural in the other direction: three consecutive audited
|
|
312
|
+
* runs made 0 search calls because the child literally did not have the tool.
|
|
313
|
+
*
|
|
314
|
+
* Both halves of "given a channel" — the tools string and the `-e` path — come
|
|
315
|
+
* from the same rows, so they cannot disagree.
|
|
316
|
+
*/
|
|
317
|
+
function apisWorkerChannels() {
|
|
318
|
+
return channelSet([
|
|
319
|
+
'pi-worker-docs',
|
|
320
|
+
...(searchConfigured() ? ['pi-worker-search', 'pi-worker-fetch'] : [])
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
310
323
|
/**
|
|
311
324
|
* Is live web search configured for this process? The keyless providers (exa,
|
|
312
|
-
* ddg) always are; only brave needs its API key
|
|
325
|
+
* ddg) always are; only brave needs its API key.
|
|
313
326
|
*/
|
|
314
327
|
export function searchConfigured(getEnv = k => process.env[k], provider = getConfig().searchProvider) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
328
|
+
// Asks the SAME row `search()` asks. This used to re-state brave's env pair
|
|
329
|
+
// under a comment saying it "mirrors search-core's lookup" — two statements of
|
|
330
|
+
// one fact, and the one that decides whether the APIS worker is even handed the
|
|
331
|
+
// search tool.
|
|
332
|
+
return searchProviderKey(provider, getEnv) !== null;
|
|
318
333
|
}
|
|
319
334
|
/** Extra prompt block for the APIS worker when search is available — trigger-framed
|
|
320
335
|
* (the validated shape for getting a local model to actually reach for search). */
|
|
@@ -604,10 +619,10 @@ const CONTEXT_SILENT_RETRY_PREAMBLE = 'STOP. Your previous attempt at this task
|
|
|
604
619
|
+ 'nothing else. Keep the same rules as before: state an external library/API behaviour as fact '
|
|
605
620
|
+ 'ONLY when quoting an EXTERNAL CONTEXT block; otherwise write it as an "unverified:" open '
|
|
606
621
|
+ 'question. One claim per bullet. Better to emit three sharp sourced bullets than to say nothing.';
|
|
607
|
-
export async function phaseResearch(deps, refined
|
|
608
|
-
const fileInventoryFn =
|
|
609
|
-
const runWorkerFn =
|
|
610
|
-
const externalContext = await gatherExternalContext(refined, deps
|
|
622
|
+
export async function phaseResearch(deps, refined) {
|
|
623
|
+
const fileInventoryFn = deps.getFileInventory ?? getFileInventory;
|
|
624
|
+
const runWorkerFn = deps.runWorker ?? ((_label, input) => runWorker(input));
|
|
625
|
+
const externalContext = await gatherExternalContext(refined, deps);
|
|
611
626
|
// Pre-compute the project file inventory once and hand it to every worker.
|
|
612
627
|
// Workers can then jump straight to targeted read/grep on known paths
|
|
613
628
|
// instead of each spawning its own discovery loop (find/ls). A '' result
|
|
@@ -721,6 +736,9 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
721
736
|
// The worker still calls as many tools as it wants; it just stops narrating
|
|
722
737
|
// between them. See appendNoThink. Result order (files, apis, context,
|
|
723
738
|
// tooling) is preserved for assembly.
|
|
739
|
+
// Resolved once: `searchConfigured()` reads the environment, and the tools
|
|
740
|
+
// string and the `-e` paths must be derived from the SAME answer.
|
|
741
|
+
const apisChannels = apisWorkerChannels();
|
|
724
742
|
const workerSpecs = [
|
|
725
743
|
{
|
|
726
744
|
section: 'FILES',
|
|
@@ -746,13 +764,12 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
746
764
|
// budget enforced without being announced would just read
|
|
747
765
|
// to the worker as a broken tool.
|
|
748
766
|
+ (fanoutBudget === null ? '' : projectDocsBudgetNotice(fanoutBudget))),
|
|
749
|
-
tools
|
|
750
|
-
|
|
767
|
+
// The tools string and the `-e` paths are ONE fact — which worker
|
|
768
|
+
// channels this research worker is given — and used to be two literals
|
|
769
|
+
// kept in step by eye. `channelSet` derives both from the same rows.
|
|
770
|
+
tools: `read,grep,find,ls,${apisChannels.tools}`,
|
|
751
771
|
fanoutBounded: true,
|
|
752
|
-
extensions:
|
|
753
|
-
DOCS_EXTENSION_PATH,
|
|
754
|
-
...(searchConfigured() ? [SEARCH_EXTENSION_PATH] : [])
|
|
755
|
-
],
|
|
772
|
+
extensions: apisChannels.extensions,
|
|
756
773
|
// ZERO-RETRIEVAL GATE (mx5 run-15 F-1, distinct from the STAGE 1-3 stopping-point
|
|
757
774
|
// thread). In a MINORITY of reps worker:apis emits a complete, plausible APIS section
|
|
758
775
|
// having made ZERO retrieval tool calls — the whole thing recalled from memory.
|
|
@@ -1069,9 +1086,9 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
1069
1086
|
}
|
|
1070
1087
|
return sections.map(({ name, text }) => `${name}\n${text}`).join('\n\n');
|
|
1071
1088
|
}
|
|
1072
|
-
export async function phaseAutoAnswer(deps, refined, research, question
|
|
1073
|
-
const docsFocusedFn =
|
|
1074
|
-
const fetchFocusedFn =
|
|
1089
|
+
export async function phaseAutoAnswer(deps, refined, research, question) {
|
|
1090
|
+
const docsFocusedFn = deps.docsFocused ?? docsFocused;
|
|
1091
|
+
const fetchFocusedFn = deps.fetchFocused ?? fetchFocused;
|
|
1075
1092
|
try {
|
|
1076
1093
|
// Same assembly as the research phase (see external-context.ts); what
|
|
1077
1094
|
// differs is POLICY and the worker variant, and both are arguments now.
|
|
@@ -1109,7 +1126,7 @@ export async function phaseAutoAnswer(deps, refined, research, question, autoDep
|
|
|
1109
1126
|
});
|
|
1110
1127
|
return { body: countBody(r.answer || undefined) };
|
|
1111
1128
|
},
|
|
1112
|
-
search:
|
|
1129
|
+
search: deps.searchFn
|
|
1113
1130
|
}, { targetCap: 2, serviceCap: 2 });
|
|
1114
1131
|
const basePrompt = externalContext + GRILL_AUTO_ANSWER_PROMPT(refined, research, question);
|
|
1115
1132
|
let text = await runPhaseChild(deps, 'grill-auto', 'read', basePrompt);
|
|
@@ -1209,7 +1226,7 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
|
|
|
1209
1226
|
for (let n = 0; n < MAX_GRILL_QUESTIONS; n++) {
|
|
1210
1227
|
const tGenStart = Date.now();
|
|
1211
1228
|
const genHint = dupHint;
|
|
1212
|
-
const raw = await
|
|
1229
|
+
const raw = await runPhaseChild(deps, 'grill-gen', 'read', prependHint(genHint, GRILL_GEN_PROMPT(refined, research, qa.join('\n'))), { verb: 'restart' });
|
|
1213
1230
|
deps.recordSubStep?.('gen', Date.now() - tGenStart);
|
|
1214
1231
|
const questions = parseGrillQuestions(raw);
|
|
1215
1232
|
if (questions.length === 0)
|
|
@@ -1497,91 +1514,101 @@ export async function critiqueWithFallback(d, p) {
|
|
|
1497
1514
|
}
|
|
1498
1515
|
}
|
|
1499
1516
|
// ─── Phase config table ──────────────────────────────────────────────────────
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
// shim to compile it. Append the corrections so compose folds them into
|
|
1537
|
-
// CONSTRAINTS. No LLM cost and silent when nothing is wrong.
|
|
1538
|
-
const corrections = formatApiCorrections(await findPhantomImports(p.refined, d.cwd));
|
|
1539
|
-
if (corrections) {
|
|
1540
|
-
d.logDebug?.(`phantom imports flagged:\n${corrections}`);
|
|
1541
|
-
return `${out}\n\n${corrections}`;
|
|
1542
|
-
}
|
|
1543
|
-
return out;
|
|
1544
|
-
}
|
|
1545
|
-
},
|
|
1546
|
-
{
|
|
1547
|
-
name: 'grill',
|
|
1548
|
-
section: 'grill Q&A',
|
|
1549
|
-
field: 'qa',
|
|
1550
|
-
run: (d, p) => phaseGrill(d, p.ctx, p.widgetState, p.refined, p.research)
|
|
1551
|
-
},
|
|
1552
|
-
{
|
|
1553
|
-
name: 'compose',
|
|
1554
|
-
section: 'spec',
|
|
1555
|
-
field: 'spec',
|
|
1556
|
-
run: async (d, p) => {
|
|
1557
|
-
p.refined = await dropRefutedConstraints(d, p.refined, p.research);
|
|
1558
|
-
return await phaseCompose(d, p.refined, p.research, p.qa);
|
|
1559
|
-
}
|
|
1560
|
-
},
|
|
1561
|
-
{
|
|
1562
|
-
name: 'critique',
|
|
1563
|
-
section: 'spec',
|
|
1564
|
-
field: 'spec',
|
|
1565
|
-
run: async (d, p) => {
|
|
1566
|
-
const spec = await critiqueWithFallback(d, p);
|
|
1567
|
-
// BRACES (mx5 run 16): after the LAST spec-producing step, append any
|
|
1568
|
-
// owned design obligation the spec still omits as a CONSTRAINTS
|
|
1569
|
-
// bullet. The belt block upstream is obeyed ~25% (measured); a
|
|
1570
|
-
// host-side append is obeyed by construction. Idempotent: quotes the
|
|
1571
|
-
// spec already carries (belt-obeying reps) are skipped.
|
|
1572
|
-
const owned = await ownedForThisTask(d);
|
|
1573
|
-
if (owned.length === 0)
|
|
1574
|
-
return spec;
|
|
1575
|
-
const out = appendOwnedConstraints(spec, owned);
|
|
1576
|
-
if (out !== spec) {
|
|
1577
|
-
d.logDebug?.('owned-requirements braces: appended omitted design obligation(s) to CONSTRAINTS');
|
|
1578
|
-
}
|
|
1579
|
-
// An owned obligation whose only file this spec also FREEZES is
|
|
1580
|
-
// unsatisfiable here; move it to the pending task that writes that
|
|
1581
|
-
// file rather than shipping a requirement no one can meet.
|
|
1582
|
-
return await resolveOwnedFreezeForThisTask(d, out);
|
|
1583
|
-
}
|
|
1517
|
+
/**
|
|
1518
|
+
* REFINE — restate the raw prompt as a bounded 4-section spec, then subtractively
|
|
1519
|
+
* strike any phantom runtime specifier (`bun:sql`) it carried up verbatim from the
|
|
1520
|
+
* spec doc, BEFORE it flows to research/grill/compose. An appended correction alone
|
|
1521
|
+
* loses: the affirmative survives into the composed GOAL and on to the implementer
|
|
1522
|
+
* (proven: compose re-leaks it 4/4). Rewriting the source so compose has nothing to
|
|
1523
|
+
* contradict is the fix. Silent + no-op when nothing is wrong or the runtime's types
|
|
1524
|
+
* aren't installed.
|
|
1525
|
+
*/
|
|
1526
|
+
export async function refinePhase(d, p) {
|
|
1527
|
+
const refined = await phaseRefine(d, p.rawPrompt, p.planContext);
|
|
1528
|
+
const phantoms = await findPhantomImports(refined, d.cwd);
|
|
1529
|
+
if (phantoms.length === 0)
|
|
1530
|
+
return refined;
|
|
1531
|
+
d.logDebug?.(`phantom specifiers rewritten in refined: ${phantoms.map(x => x.spec).join(', ')}`);
|
|
1532
|
+
return rewritePhantomSpecifiers(refined, phantoms);
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* RESEARCH — the four workers, then the TOOLING verification pass, then a
|
|
1536
|
+
* deterministic check of every runtime builtin specifier the refined task names
|
|
1537
|
+
* (`bun:sql`, `node:…`) against the installed types. A doc can confidently name a
|
|
1538
|
+
* module that does not exist; left unchecked it rides through every phase and the
|
|
1539
|
+
* implementer fabricates a `declare module` shim to compile it. The corrections are
|
|
1540
|
+
* APPENDED so compose folds them into CONSTRAINTS. No LLM cost, silent when clean.
|
|
1541
|
+
*/
|
|
1542
|
+
export async function researchPhase(d, p) {
|
|
1543
|
+
const tResearch = Date.now();
|
|
1544
|
+
const rawResearch = await phaseResearch(d, p.refined);
|
|
1545
|
+
d.recordSubStep?.('workers', Date.now() - tResearch);
|
|
1546
|
+
const tVerify = Date.now();
|
|
1547
|
+
const out = await phaseVerifyTooling(d, rawResearch);
|
|
1548
|
+
d.recordSubStep?.('verify-tooling', Date.now() - tVerify);
|
|
1549
|
+
const corrections = formatApiCorrections(await findPhantomImports(p.refined, d.cwd));
|
|
1550
|
+
if (corrections) {
|
|
1551
|
+
d.logDebug?.(`phantom imports flagged:\n${corrections}`);
|
|
1552
|
+
return `${out}\n\n${corrections}`;
|
|
1584
1553
|
}
|
|
1554
|
+
return out;
|
|
1555
|
+
}
|
|
1556
|
+
/** GRILL — the adaptive question loop, and the only phase that talks to the user. */
|
|
1557
|
+
export function grillPhase(d, p) {
|
|
1558
|
+
return phaseGrill(d, p.ctx, p.widgetState, p.refined, p.research);
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* COMPOSE — drop constraints research REFUTED before composing, then compose.
|
|
1562
|
+
*
|
|
1563
|
+
* The drop mutates `p.refined` in place on purpose: the refuted constraint must be
|
|
1564
|
+
* gone from every later reader of the refined spec, not just from this call's
|
|
1565
|
+
* argument. Critique re-reads it.
|
|
1566
|
+
*/
|
|
1567
|
+
export async function composePhase(d, p) {
|
|
1568
|
+
p.refined = await dropRefutedConstraints(d, p.refined, p.research);
|
|
1569
|
+
return await phaseCompose(d, p.refined, p.research, p.qa);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* CRITIQUE — the last spec-producing step, and the two host-side corrections that
|
|
1573
|
+
* must run after it in THIS ORDER.
|
|
1574
|
+
*
|
|
1575
|
+
* BRACES (mx5 run 16): append any owned design obligation the spec still omits as a
|
|
1576
|
+
* CONSTRAINTS bullet. The belt block upstream is obeyed ~25% (measured); a host-side
|
|
1577
|
+
* append is obeyed by construction. Idempotent — quotes the spec already carries
|
|
1578
|
+
* (belt-obeying reps) are skipped.
|
|
1579
|
+
*
|
|
1580
|
+
* Then DETACH: an owned obligation whose only file this spec also FREEZES is
|
|
1581
|
+
* unsatisfiable here, so it moves to the pending task that writes that file rather
|
|
1582
|
+
* than shipping a requirement no one can meet. It MUST run after the append, because
|
|
1583
|
+
* the append is what writes the stamp the detach reads — a critique-time probe
|
|
1584
|
+
* measured 0/40 because the stamp did not exist yet.
|
|
1585
|
+
*/
|
|
1586
|
+
export async function critiquePhase(d, p) {
|
|
1587
|
+
const spec = await critiqueWithFallback(d, p);
|
|
1588
|
+
const owned = await ownedForThisTask(d);
|
|
1589
|
+
if (owned.length === 0)
|
|
1590
|
+
return spec;
|
|
1591
|
+
const out = appendOwnedConstraints(spec, owned);
|
|
1592
|
+
if (out !== spec) {
|
|
1593
|
+
d.logDebug?.('owned-requirements braces: appended omitted design obligation(s) to CONSTRAINTS');
|
|
1594
|
+
}
|
|
1595
|
+
return await resolveOwnedFreezeForThisTask(d, out);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* The pipeline, as a table with no bodies.
|
|
1599
|
+
*
|
|
1600
|
+
* Every row's `run` is a named exported function, so the COMPOSITION inside a
|
|
1601
|
+
* step — which is where this codebase's recorded phase defects have lived, not in
|
|
1602
|
+
* the parts — is drivable directly instead of only through a whole TaskRunner run.
|
|
1603
|
+
* The parts stay exported and separately covered; what changed is that the ORDER
|
|
1604
|
+
* they run in is now asserted by driving the row rather than retyped in a test.
|
|
1605
|
+
*/
|
|
1606
|
+
export const PHASES = [
|
|
1607
|
+
{ name: 'refine', section: 'refined prompt', field: 'refined', run: refinePhase },
|
|
1608
|
+
{ name: 'research', section: 'research', field: 'research', run: researchPhase },
|
|
1609
|
+
{ name: 'grill', section: 'grill Q&A', field: 'qa', run: grillPhase },
|
|
1610
|
+
{ name: 'compose', section: 'spec', field: 'spec', run: composePhase },
|
|
1611
|
+
{ name: 'critique', section: 'spec', field: 'spec', run: critiquePhase }
|
|
1585
1612
|
];
|
|
1586
1613
|
// INTEGRATION-DEPTH APPEND (2026-07-27): the lever proposed for this exact site —
|
|
1587
1614
|
// deterministically append a known-runnable integration command to the VERIFY block
|
|
@@ -187,8 +187,8 @@ async function defaultHandoff(ctx, cwd, prompt) {
|
|
|
187
187
|
await runGatedTask(ctx, cwd, prompt);
|
|
188
188
|
return undefined;
|
|
189
189
|
}
|
|
190
|
-
const { taskId,
|
|
191
|
-
if (
|
|
190
|
+
const { taskId, end } = await runSingleTask(ctx, cwd, prompt, { notifyFinish: true });
|
|
191
|
+
if (end.kind === 'no-session') {
|
|
192
192
|
ctx.ui.notify('Could not start a fresh session for /task-plan.', 'warning');
|
|
193
193
|
return undefined;
|
|
194
194
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CommandRunner } from './command-run.js';
|
|
1
2
|
export interface HealthOutcome {
|
|
2
3
|
/** true → every discovered static check passed, or there was nothing to run.
|
|
3
4
|
* false → a discovered command actually ran and exited non-zero. */
|
|
@@ -29,38 +30,37 @@ export declare function discoverHealthCommands(cwd: string): {
|
|
|
29
30
|
ecosystem: string | null;
|
|
30
31
|
cmds: HealthCommand[];
|
|
31
32
|
};
|
|
33
|
+
/** Progress hook: called with each command's label as it STARTS, so a caller can
|
|
34
|
+
* keep a live status line naming what is currently running. */
|
|
35
|
+
export type HealthProgress = (command: string) => void;
|
|
32
36
|
/**
|
|
33
37
|
* Run the discovered static checks whole-repo and let the real exit codes decide.
|
|
34
|
-
* Deterministic and synchronous under the hood (a wrapper keeps the caller async).
|
|
35
38
|
*
|
|
36
39
|
* - No manifest / no static command → ok (nothing can regress).
|
|
37
|
-
* - A command that CANNOT run (ENOENT / null exit
|
|
40
|
+
* - A command that CANNOT run (ENOENT / null exit / 127 inside the chain) → skipped,
|
|
38
41
|
* treated as an environment gap, not a fault.
|
|
39
42
|
* - A command that ran and exited non-zero → the first such failure is returned.
|
|
40
43
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
44
|
+
* This module owns DISCOVERY and its own output policy; running a command and
|
|
45
|
+
* deciding what its ending MEANS is `command-run.ts`'s. It used to own those too —
|
|
46
|
+
* `HealthRun`, `classifyHealthRun` and `spawnHealthCommand` were a second statement
|
|
47
|
+
* of the gap ladder, with no injectable runner, so every classification case in the
|
|
48
|
+
* suite spawned a real shell. `command-run.ts`'s own header notes that this module
|
|
49
|
+
* "had solved exactly this shape years earlier" and the gate never adopted it; this
|
|
50
|
+
* is the adoption, in the other direction.
|
|
43
51
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* for callers that genuinely have no async seam.
|
|
48
|
-
*/
|
|
49
|
-
export declare function runRepoHealthCheck(cwd: string, timeoutMs?: number): HealthOutcome;
|
|
50
|
-
/** Progress hook: called with each command's label as it STARTS, so a caller can
|
|
51
|
-
* keep a live status line naming what is currently running. */
|
|
52
|
-
export type HealthProgress = (command: string) => void;
|
|
53
|
-
/**
|
|
54
|
-
* Same check, same verdicts, without blocking the event loop.
|
|
52
|
+
* `captureHealthOutput` stays this module's own: 40 lines of a linter's report is a
|
|
53
|
+
* real difference from `outputTail`'s 400 characters, and that is a parameter, not a
|
|
54
|
+
* thing to unify.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* fired during a 69s aiz-client run), so no spinner, clock or queued notify could
|
|
60
|
-
* paint. `onCommand` lets the caller name the running command in a live status line.
|
|
56
|
+
* `onCommand` lets the caller name the running command in a live status line — the
|
|
57
|
+
* gate runs this immediately after the implementation turn ends, when the impl
|
|
58
|
+
* widget has just been cleared.
|
|
61
59
|
*/
|
|
62
|
-
export declare function
|
|
60
|
+
export declare function runRepoHealthCheck(cwd: string, opts?: {
|
|
63
61
|
timeoutMs?: number;
|
|
64
62
|
signal?: AbortSignal;
|
|
65
63
|
onCommand?: HealthProgress;
|
|
64
|
+
/** The spawner. Injected so a verdict is testable without a real shell. */
|
|
65
|
+
run?: CommandRunner;
|
|
66
66
|
}): Promise<HealthOutcome>;
|