@mjasnikovs/pi-task 0.38.2 → 0.38.4
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 +7 -0
- package/dist/config/config.js +10 -4
- package/dist/config/register.d.ts +37 -0
- package/dist/config/register.js +89 -114
- package/dist/remote/events.js +0 -3
- package/dist/remote/register.js +12 -3
- package/dist/task/auto-orchestrator.js +119 -94
- package/dist/task/command-run.d.ts +104 -0
- package/dist/task/command-run.js +138 -0
- package/dist/task/coverage-loop.d.ts +45 -0
- package/dist/task/critique-probes.d.ts +82 -0
- package/dist/task/critique-probes.js +156 -0
- package/dist/task/deep-render-check.d.ts +30 -0
- package/dist/task/deep-render-check.js +19 -11
- package/dist/task/enforce-guidelines.d.ts +14 -17
- package/dist/task/enforce-guidelines.js +44 -31
- package/dist/task/final-gate.d.ts +8 -10
- package/dist/task/final-gate.js +36 -74
- package/dist/task/gate-child.d.ts +104 -0
- package/dist/task/gate-child.js +177 -0
- package/dist/task/gate-deps.d.ts +13 -0
- package/dist/task/gate-deps.js +72 -208
- package/dist/task/orchestrator.js +13 -22
- package/dist/task/phases.js +109 -182
- package/dist/task/plan-session.d.ts +4 -22
- package/dist/task/plan-session.js +4 -33
- package/dist/task/question-dialog.d.ts +71 -0
- package/dist/task/question-dialog.js +89 -0
- package/dist/task/terminal-outcome.d.ts +67 -0
- package/dist/task/terminal-outcome.js +76 -0
- package/dist/task/type-only-answer.js +2 -3
- package/dist/workers/abstention.d.ts +71 -0
- package/dist/workers/abstention.js +108 -0
- package/dist/workers/docs-chunk.d.ts +74 -0
- package/dist/workers/docs-chunk.js +143 -0
- package/dist/workers/docs-core.d.ts +10 -1
- package/dist/workers/docs-core.js +22 -19
- package/dist/workers/docs-index.js +2 -69
- package/dist/workers/docs-project.d.ts +15 -1
- package/dist/workers/docs-project.js +27 -66
- package/dist/workers/fetch-core.d.ts +1 -1
- package/dist/workers/fetch-core.js +2 -1
- package/dist/workers/pi-worker-core.js +157 -86
- package/dist/workers/pi-worker-docs.js +5 -10
- package/dist/workers/pi-worker-fetch.js +8 -1
- package/dist/workers/typeonly-log.js +2 -10
- package/dist/workers/worker-failure.d.ts +91 -0
- package/dist/workers/worker-failure.js +82 -0
- package/package.json +1 -1
package/dist/task/gate-deps.js
CHANGED
|
@@ -18,7 +18,7 @@ import * as fsp from 'node:fs/promises';
|
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { tasksDir, readTaskFile, appendGateRecord } from './task-io.js';
|
|
20
20
|
import { gitCommitAll, gitDropLastCommit, git } from './auto-commit.js';
|
|
21
|
-
import { runGuidelineEnforcement
|
|
21
|
+
import { runGuidelineEnforcement } from './enforce-guidelines.js';
|
|
22
22
|
import { runWorkVerification, extractSpecForVerification } from './verify-work.js';
|
|
23
23
|
import { readEnvNotes, appendEnvNotes } from './env-notes.js';
|
|
24
24
|
import { readContracts } from './contracts.js';
|
|
@@ -41,11 +41,11 @@ import { findScriptEscapesInManifest, scriptEscapeVerifyFindings } from './scrip
|
|
|
41
41
|
import { assessRunnerGlobs, runnerGlobVerifyFindings } from './runner-globs.js';
|
|
42
42
|
import { captureGitState, reconcileGitState } from './git-state-guard.js';
|
|
43
43
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
44
|
-
import { formatLoopHint } from './child-runner.js';
|
|
45
44
|
import { getConfig } from '../config/config.js';
|
|
46
45
|
import { makeDebugAppender } from './debug-log.js';
|
|
47
46
|
import { startAutoLoader } from './widget.js';
|
|
48
47
|
import { resolveContextUsage } from './context-usage.js';
|
|
48
|
+
import { makeGateChild } from './gate-child.js';
|
|
49
49
|
/** Max chars of a tool result kept in the gate debug log (mx5 run 10 item 6). */
|
|
50
50
|
const TOOL_RESULT_LOG_LIMIT = 300;
|
|
51
51
|
/**
|
|
@@ -61,6 +61,18 @@ export function truncateToolResult(text, limit = TOOL_RESULT_LOG_LIMIT) {
|
|
|
61
61
|
}
|
|
62
62
|
/** Keep the gate machinery's own artifacts out of every git pathspec below. */
|
|
63
63
|
const EXCLUDE_TASKS_DIR = ':(exclude).pi-tasks';
|
|
64
|
+
/**
|
|
65
|
+
* Pin the diff header prefixes on any command whose output we PARSE for paths.
|
|
66
|
+
*
|
|
67
|
+
* `parseAddedLines` reads the file out of the `+++ b/…` header, but the prefix is
|
|
68
|
+
* user-configurable: `diff.mnemonicPrefix=true` emits `i/` and `w/` instead of
|
|
69
|
+
* `a/` and `b/`, and `diff.noprefix=true` emits none. Both are common developer
|
|
70
|
+
* settings, and under either one every added line was attributed to a path like
|
|
71
|
+
* `w/src/app.ts`, which exists nowhere — so the probe-gaming and sandbox-path
|
|
72
|
+
* probes silently read a diff of files that are not in the repo. Passing the
|
|
73
|
+
* prefixes explicitly makes the output independent of the host's git config.
|
|
74
|
+
*/
|
|
75
|
+
const DIFF_PREFIX_ARGS = ['--src-prefix=a/', '--dst-prefix=b/'];
|
|
64
76
|
const splitLines = (s) => s
|
|
65
77
|
.split('\n')
|
|
66
78
|
.map(l => l.trim())
|
|
@@ -110,7 +122,7 @@ export async function collectChangedFiles(cwd, signal) {
|
|
|
110
122
|
* never a blocker. The `.pi-tasks/` bookkeeping is excluded from every git command.
|
|
111
123
|
*/
|
|
112
124
|
export async function collectAddedLines(cwd, signal) {
|
|
113
|
-
const tracked = await git(cwd, ['diff', 'HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
125
|
+
const tracked = await git(cwd, ['diff', ...DIFF_PREFIX_ARGS, 'HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
114
126
|
const lines = tracked.exitCode === 0 ? parseAddedLines(tracked.stdout) : [];
|
|
115
127
|
const untracked = await git(cwd, ['ls-files', '--others', '--exclude-standard', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
116
128
|
for (const name of splitLines(untracked.exitCode === 0 ? untracked.stdout : '')) {
|
|
@@ -124,7 +136,7 @@ export async function collectAddedLines(cwd, signal) {
|
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
if (lines.length === 0) {
|
|
127
|
-
const last = await git(cwd, ['diff', 'HEAD~1..HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
139
|
+
const last = await git(cwd, ['diff', ...DIFF_PREFIX_ARGS, 'HEAD~1..HEAD', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
128
140
|
return last.exitCode === 0 ? parseAddedLines(last.stdout) : [];
|
|
129
141
|
}
|
|
130
142
|
return lines;
|
|
@@ -141,7 +153,7 @@ export async function collectAddedLines(cwd, signal) {
|
|
|
141
153
|
* cannot repair still reaches the child under rule 4e. Failures degrade to no
|
|
142
154
|
* findings — a sharpener, never a blocker.
|
|
143
155
|
*/
|
|
144
|
-
async function collectForeignPathFindings(cwd, signal, logDebug) {
|
|
156
|
+
export async function collectForeignPathFindings(cwd, signal, logDebug) {
|
|
145
157
|
const lines = await collectAddedLines(cwd, signal);
|
|
146
158
|
if (lines.length === 0)
|
|
147
159
|
return [];
|
|
@@ -474,136 +486,51 @@ export function buildGateDeps(params) {
|
|
|
474
486
|
await git(cwd2, ['checkout', '--', '.', EXCLUDE_TASKS_DIR], signal);
|
|
475
487
|
await git(cwd2, ['clean', '-fd', '-e', '.pi-tasks'], signal);
|
|
476
488
|
};
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
log(`=== ${kind} start: ${taskTitle} ===`);
|
|
498
|
-
// GIT-STATE GUARD: these children are read-only BY CONTRACT, but the
|
|
499
|
-
// contract is prompt-level and the live model breaks it (mx5 run 6: the
|
|
500
|
-
// verify child `git stash`ed the task's uncommitted work and never popped
|
|
501
|
-
// — the impl was destroyed and the orphan stash detonated 2 days later).
|
|
502
|
-
// Snapshot before, deterministically restore after; lint-fix is excluded
|
|
503
|
-
// because editing is its job (it carries its own revert guard).
|
|
504
|
-
const guardSnapshot = kind === 'verify' || kind === 'recommend' ? await captureGitState(cwd2, sig) : null;
|
|
505
|
-
const stopLoader = opts.loader === false ?
|
|
506
|
-
() => { }
|
|
507
|
-
: startAutoLoader(gateCtx, () => ({
|
|
508
|
-
title: taskTitle,
|
|
509
|
-
kind,
|
|
510
|
-
step: kind,
|
|
511
|
-
stepNum: 1,
|
|
512
|
-
stepTotal: 1,
|
|
513
|
-
startedAt,
|
|
514
|
-
lastLine,
|
|
515
|
-
contextUsage
|
|
516
|
-
}));
|
|
517
|
-
try {
|
|
518
|
-
let r;
|
|
519
|
-
try {
|
|
520
|
-
r = await runWorker({
|
|
521
|
-
prompt,
|
|
522
|
-
cwd: cwd2,
|
|
523
|
-
signal: sig,
|
|
524
|
-
tools,
|
|
525
|
-
timeoutMs: 0,
|
|
526
|
-
// The gate child runs to completion (timeoutMs 0), but a
|
|
527
|
-
// single command inside it must still be bounded: pi's bash
|
|
528
|
-
// tool has no default timeout, so a `bun run dev` / hung
|
|
529
|
-
// check the model forgot to bound wedges the gate forever.
|
|
530
|
-
// The stall guard cannot see it — a reachable model endpoint
|
|
531
|
-
// reads as proof of life while the command blocks. Same
|
|
532
|
-
// ceiling the main session uses, so one /task-config knob
|
|
533
|
-
// covers implementation and gates alike.
|
|
534
|
-
commandTimeoutMs: getConfig().requestTimeoutMs,
|
|
535
|
-
// Same reasoning one level up: a gate child with no
|
|
536
|
-
// wall-clock cap also needs the HUNG-STREAM bound, which
|
|
537
|
-
// the probe-based stall guard structurally cannot supply
|
|
538
|
-
// (a healthy endpoint reads as proof of life).
|
|
539
|
-
streamInactivityMs: getConfig().streamInactivityMs,
|
|
540
|
-
loop: { pathThreshold: Number.POSITIVE_INFINITY },
|
|
541
|
-
// A discarded attempt is otherwise invisible here too: the
|
|
542
|
-
// returned exitCode/text describe the FINAL attempt, so a
|
|
543
|
-
// gate child that burned two attempts and its wall clock
|
|
544
|
-
// reads exactly like one that ran clean.
|
|
545
|
-
onRestart: rs => log(`=== ${kind} RESTART (attempt ${rs.attempt} discarded)`
|
|
546
|
-
+ ` reason=${rs.reason} wall=${rs.wallMs}ms`
|
|
547
|
-
+ (rs.detail ? ` — ${rs.detail}` : '')
|
|
548
|
-
+ ' ==='),
|
|
549
|
-
onLine: line => {
|
|
550
|
-
// `lastLine` feeds the LIVE status widget and is not
|
|
551
|
-
// logging — it stays outside the gate, or a quiet
|
|
552
|
-
// trail would also blank the progress display.
|
|
553
|
-
lastLine = line;
|
|
554
|
-
log(line, 'stream');
|
|
555
|
-
},
|
|
556
|
-
// Log tool OUTPUTS, not just the command (mx5 run 10 item 6):
|
|
557
|
-
// without the result "verify claimed curl PASS on a server that
|
|
558
|
-
// cannot serve" is undecidable from the log. Truncated, tail-kept
|
|
559
|
-
// (a bind failure / status usually lands at the end), error-flagged.
|
|
560
|
-
onToolResult: ({ name, isError, text }) => log(`↳ ${name} [${isError ? 'ERR' : 'ok'}]: ${truncateToolResult(text)}`, 'stream'),
|
|
561
|
-
onContextUsage: snapshot => {
|
|
562
|
-
contextUsage = resolveContextUsage(snapshot, contextUsage, parentContextWindow);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
finally {
|
|
567
|
-
// Restore whatever the child moved BEFORE any verdict/failure is
|
|
568
|
-
// acted on — a crashed child must not skip the restore either.
|
|
569
|
-
if (guardSnapshot) {
|
|
570
|
-
const rec = await reconcileGitState(cwd2, guardSnapshot, sig);
|
|
571
|
-
lastGuardReconcile = rec;
|
|
572
|
-
if (rec.mutated) {
|
|
573
|
-
// Distinguish the two outcomes in the trail: a tainting
|
|
574
|
-
// mutation (graded work altered → verdict will be
|
|
575
|
-
// discarded) vs benign cleanup (test-runner output the
|
|
576
|
-
// child left behind → verdict stands).
|
|
577
|
-
const label = rec.verdictTainted ?
|
|
578
|
-
'child mutated graded state (verdict discarded)'
|
|
579
|
-
: 'cleaned child test-runner artifacts (verdict kept)';
|
|
580
|
-
log(`=== ${kind} GIT-STATE GUARD — ${label}; restored: ${rec.actions.join('; ')} ===`);
|
|
581
|
-
if (rec.verdictTainted) {
|
|
582
|
-
gateCtx.ui.notify(`${taskTitle}: ${kind} child mutated repo state — restored (${rec.actions.join('; ').slice(0, 140)}).`, 'warning');
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (r.loopHit) {
|
|
588
|
-
log(`=== ${kind} LOOP WARNING — ${formatLoopHint(r.loopHit)} ===`);
|
|
589
|
-
gateCtx.ui.notify(`${taskTitle}: ${kind} worker looped past the nudges — continuing (not blocked).`, 'warning');
|
|
489
|
+
// Adapter onto the shared gate-child runner (gate-child.ts). What survives
|
|
490
|
+
// here is WIRING — which context, which log file, which config knobs, and
|
|
491
|
+
// where the live-widget state lives; the ritual and the per-kind policy are
|
|
492
|
+
// the table's. The enforce child below goes through the same call.
|
|
493
|
+
const gateChild = (gateCtx, cwd2, taskTitle, kind, logFile, opts = {}) => {
|
|
494
|
+
// An accessor box, not a copy: the runner writes these fields and the
|
|
495
|
+
// loader snapshot reads them on every tick, so both must see the same
|
|
496
|
+
// closure state the rest of buildGateDeps already shares.
|
|
497
|
+
const widget = {
|
|
498
|
+
get lastLine() {
|
|
499
|
+
return lastLine;
|
|
500
|
+
},
|
|
501
|
+
set lastLine(v) {
|
|
502
|
+
lastLine = v;
|
|
503
|
+
},
|
|
504
|
+
get contextUsage() {
|
|
505
|
+
return contextUsage;
|
|
506
|
+
},
|
|
507
|
+
set contextUsage(v) {
|
|
508
|
+
contextUsage = v;
|
|
590
509
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
510
|
+
};
|
|
511
|
+
return makeGateChild({
|
|
512
|
+
ctx: gateCtx,
|
|
513
|
+
cwd: cwd2,
|
|
514
|
+
taskTitle,
|
|
515
|
+
kind,
|
|
516
|
+
logPath: path.join(tasksDir(cwd2), logFile),
|
|
517
|
+
...(opts.loader === undefined ? {} : { loader: opts.loader }),
|
|
518
|
+
commandTimeoutMs: getConfig().requestTimeoutMs,
|
|
519
|
+
streamInactivityMs: getConfig().streamInactivityMs,
|
|
520
|
+
parentContextWindow,
|
|
521
|
+
runWorker,
|
|
522
|
+
makeDebugAppender,
|
|
523
|
+
startAutoLoader,
|
|
524
|
+
captureGitState,
|
|
525
|
+
reconcileGitState,
|
|
526
|
+
describeTreeChanges: async (c, sig) => formatTreeChanges(await collectTreeChanges(c, sig)),
|
|
527
|
+
resolveContextUsage,
|
|
528
|
+
truncateToolResult,
|
|
529
|
+
widget,
|
|
530
|
+
onReconcile: rec => {
|
|
531
|
+
lastGuardReconcile = rec;
|
|
601
532
|
}
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
finally {
|
|
605
|
-
stopLoader();
|
|
606
|
-
}
|
|
533
|
+
});
|
|
607
534
|
};
|
|
608
535
|
return {
|
|
609
536
|
runTask,
|
|
@@ -710,77 +637,14 @@ export function buildGateDeps(params) {
|
|
|
710
637
|
// research-worker guards mislabel that as a runaway and kill good work
|
|
711
638
|
// (proven on mx5 TASK_0002). classifyEnforceChildFailure still blocks
|
|
712
639
|
// on a real failure (non-zero exit, leaked tool call) or a user cancel.
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
title: taskTitle,
|
|
722
|
-
kind: 'enforce',
|
|
723
|
-
step: 'guidelines',
|
|
724
|
-
stepNum: 1,
|
|
725
|
-
stepTotal: 1,
|
|
726
|
-
startedAt,
|
|
727
|
-
lastLine,
|
|
728
|
-
contextUsage
|
|
729
|
-
}));
|
|
730
|
-
try {
|
|
731
|
-
const r = await runWorker({
|
|
732
|
-
prompt,
|
|
733
|
-
cwd: cwd2,
|
|
734
|
-
signal: sig,
|
|
735
|
-
tools,
|
|
736
|
-
timeoutMs: 0, // no wall-clock timeout — run to completion
|
|
737
|
-
// …but still bound any SINGLE command (see makeGateChild).
|
|
738
|
-
// enforce is read,edit today, so nothing here can hang on
|
|
739
|
-
// bash — wired anyway so a future tool grant can't quietly
|
|
740
|
-
// re-open the hole.
|
|
741
|
-
commandTimeoutMs: getConfig().requestTimeoutMs,
|
|
742
|
-
// Unbounded wall clock here too — the hung-stream
|
|
743
|
-
// bound is the only thing that ends a dead stream.
|
|
744
|
-
streamInactivityMs: getConfig().streamInactivityMs,
|
|
745
|
-
// Exact-match loop guard only: pathThreshold Infinity
|
|
746
|
-
// disables the path-revisit heuristic, so revisiting one
|
|
747
|
-
// file (which IS this pass's job) never trips — only a
|
|
748
|
-
// literally-identical call repeated past threshold does.
|
|
749
|
-
loop: { pathThreshold: Number.POSITIVE_INFINITY },
|
|
750
|
-
// Same reasoning as the gate child: without this a
|
|
751
|
-
// discarded attempt leaves no trace anywhere.
|
|
752
|
-
onRestart: rs => logEnforce(`=== enforce RESTART (attempt ${rs.attempt} discarded)`
|
|
753
|
-
+ ` reason=${rs.reason} wall=${rs.wallMs}ms`
|
|
754
|
-
+ (rs.detail ? ` — ${rs.detail}` : '')
|
|
755
|
-
+ ' ==='),
|
|
756
|
-
onLine: line => {
|
|
757
|
-
// `lastLine` drives the live widget, not the trail.
|
|
758
|
-
lastLine = line;
|
|
759
|
-
logEnforce(line, 'stream');
|
|
760
|
-
},
|
|
761
|
-
onContextUsage: snapshot => {
|
|
762
|
-
contextUsage = resolveContextUsage(snapshot, contextUsage, parentContextWindow);
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
// A loop that survived the restart-with-hint nudges is a
|
|
766
|
-
// warning, not a failure: log it and tell the user, but let the
|
|
767
|
-
// verdict gate be the only thing that can block.
|
|
768
|
-
if (r.loopHit) {
|
|
769
|
-
logEnforce(`=== enforce LOOP WARNING — ${formatLoopHint(r.loopHit)} ===`);
|
|
770
|
-
enforceCtx.ui.notify(`${taskTitle}: enforce worker looped past the nudges — continuing (not blocked).`, 'warning');
|
|
771
|
-
}
|
|
772
|
-
const failure = classifyEnforceChildFailure(r);
|
|
773
|
-
logEnforce(failure ?
|
|
774
|
-
`=== enforce end: FAIL — ${failure} ===`
|
|
775
|
-
: '=== enforce end: verdict captured ===');
|
|
776
|
-
if (failure)
|
|
777
|
-
throw new Error(failure);
|
|
778
|
-
return r.text;
|
|
779
|
-
}
|
|
780
|
-
finally {
|
|
781
|
-
stopLoader();
|
|
782
|
-
}
|
|
783
|
-
}
|
|
640
|
+
//
|
|
641
|
+
// This used to be an inline ~85-line copy of the gate-child ritual,
|
|
642
|
+
// differing only in the four things GATE_CHILD_KINDS now carries as
|
|
643
|
+
// row data: no git-state guard (editing is this pass's job), no
|
|
644
|
+
// tool-result logging, no tree-change capture, and its own end
|
|
645
|
+
// marker. Its own debug log stays — the enforce child is otherwise
|
|
646
|
+
// unobservable.
|
|
647
|
+
runChild: gateChild(enforceCtx, cwd2, taskTitle, 'enforce', 'enforce-debug.log')
|
|
784
648
|
});
|
|
785
649
|
},
|
|
786
650
|
verify: async (verifyCtx, cwd2, taskTitle, taskId) => {
|
|
@@ -836,7 +700,7 @@ export function buildGateDeps(params) {
|
|
|
836
700
|
// The child renders no loader of its own: the gate-wide one above is
|
|
837
701
|
// already live and reads the same `lastLine`/`contextUsage` the child
|
|
838
702
|
// feeds, so a second widget on the same key would only fight it.
|
|
839
|
-
runChild:
|
|
703
|
+
runChild: gateChild(verifyCtx, cwd2, taskTitle, 'verify', 'verify-debug.log', {
|
|
840
704
|
loader: deadAirBaseline
|
|
841
705
|
}),
|
|
842
706
|
// Names the deterministic step in the live status line.
|
|
@@ -956,7 +820,7 @@ export function buildGateDeps(params) {
|
|
|
956
820
|
cwd: cwd2,
|
|
957
821
|
signal,
|
|
958
822
|
failReason,
|
|
959
|
-
runChild:
|
|
823
|
+
runChild: gateChild(fixCtx, cwd2, taskTitle, 'lint-fix', 'verify-debug.log'),
|
|
960
824
|
repoHealth: () => runRepoHealthCheckAsync(cwd2, { signal }),
|
|
961
825
|
git: async (args) => {
|
|
962
826
|
const r = await git(cwd2, args, signal);
|
|
@@ -1005,7 +869,7 @@ export function buildGateDeps(params) {
|
|
|
1005
869
|
cwd: cwd2,
|
|
1006
870
|
signal,
|
|
1007
871
|
failReason,
|
|
1008
|
-
runChild:
|
|
872
|
+
runChild: gateChild(fixCtx, cwd2, 'final integration gate', 'final-fix', 'final-gate-debug.log'),
|
|
1009
873
|
// The gate re-run is the only arbiter of convergence, and the
|
|
1010
874
|
// shrink guard's discovery is the gate's own (see final-gate.ts).
|
|
1011
875
|
gate: c => runFinalIntegrationGate(c),
|
|
@@ -1055,7 +919,7 @@ export function buildGateDeps(params) {
|
|
|
1055
919
|
signal,
|
|
1056
920
|
spec,
|
|
1057
921
|
failReason,
|
|
1058
|
-
runChild:
|
|
922
|
+
runChild: gateChild(recCtx, cwd2, taskTitle, 'recommend', 'verify-debug.log')
|
|
1059
923
|
});
|
|
1060
924
|
}
|
|
1061
925
|
};
|
|
@@ -42,6 +42,7 @@ import { beginRun, endRun, takeHeldInput } from './mid-run-input.js';
|
|
|
42
42
|
import { reportDroppedInput } from './dropped-input.js';
|
|
43
43
|
import { formatTimings } from './timings.js';
|
|
44
44
|
import { getParentContextWindow, resolveContextUsage } from './context-usage.js';
|
|
45
|
+
import { TERMINAL_OUTCOMES, formatAt, formatWhy } from './terminal-outcome.js';
|
|
45
46
|
// ─── Module-level state ──────────────────────────────────────────────────────
|
|
46
47
|
let activeTask = null;
|
|
47
48
|
/** Set the module-level active task (avoids `this` aliasing in TaskRunner.run). */
|
|
@@ -792,28 +793,18 @@ async function runGatedTaskInner(ctx, cwd, raw, opts = {}) {
|
|
|
792
793
|
// No sibling plan → no scope fence; no parent list → no check-off.
|
|
793
794
|
});
|
|
794
795
|
active = gate.ctx;
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
case 'failed': {
|
|
808
|
-
await markResumable(cwd, res.taskId);
|
|
809
|
-
const why = gate.reason ? ` — ${gate.reason.slice(0, 160)}` : '';
|
|
810
|
-
announce(`${tag} stopped${why} — fix and run /task-resume.`, 'error');
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
case 'done':
|
|
814
|
-
announce(`${tag} complete — verified.`, 'info');
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
796
|
+
// What each outcome means for persistence and for the user is stated once, in
|
|
797
|
+
// TERMINAL_OUTCOMES, and shared with /task-auto's loop. `failParent` is
|
|
798
|
+
// ignored here: /task runs one task and has no parent run file to fail.
|
|
799
|
+
const outcome = TERMINAL_OUTCOMES[gate.kind];
|
|
800
|
+
if (outcome.markResumable)
|
|
801
|
+
await markResumable(cwd, res.taskId);
|
|
802
|
+
announce(outcome.message({
|
|
803
|
+
tag,
|
|
804
|
+
at: formatAt(),
|
|
805
|
+
why: formatWhy(gate.kind === 'failed' ? gate.reason : undefined),
|
|
806
|
+
resumeCmd: '/task-resume'
|
|
807
|
+
}), outcome.level);
|
|
817
808
|
}
|
|
818
809
|
// ─── Command handlers ────────────────────────────────────────────────────────
|
|
819
810
|
async function handleTask(args, ctx) {
|