@mjasnikovs/pi-task 0.40.50 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/config/config.d.ts +61 -18
- package/dist/config/config.js +50 -6
- package/dist/config/register.js +18 -5
- package/dist/shared/content-tokens.d.ts +16 -0
- package/dist/shared/content-tokens.js +137 -0
- package/dist/task/accept-debt.d.ts +15 -1
- package/dist/task/accept-debt.js +4 -0
- package/dist/task/artifact-closure.js +7 -1
- package/dist/task/auto-io.d.ts +59 -7
- package/dist/task/auto-io.js +140 -47
- package/dist/task/auto-orchestrator.d.ts +22 -19
- package/dist/task/auto-orchestrator.js +126 -112
- package/dist/task/child-runner.d.ts +18 -0
- package/dist/task/child-runner.js +7 -8
- package/dist/task/command-run.d.ts +4 -0
- package/dist/task/command-run.js +4 -2
- package/dist/task/constraint-policy.d.ts +52 -0
- package/dist/task/constraint-policy.js +66 -0
- package/dist/task/context-silence.d.ts +5 -4
- package/dist/task/context-silence.js +13 -5
- package/dist/task/coverage-loop.d.ts +7 -4
- package/dist/task/coverage-loop.js +16 -138
- package/dist/task/debug-log.d.ts +25 -2
- package/dist/task/debug-log.js +64 -10
- package/dist/task/decompose-fidelity.d.ts +17 -4
- package/dist/task/decompose-fidelity.js +95 -55
- package/dist/task/decompose-granularity.d.ts +7 -7
- package/dist/task/decompose-granularity.js +8 -8
- package/dist/task/env-notes.d.ts +65 -19
- package/dist/task/env-notes.js +152 -50
- package/dist/task/external-context.js +25 -5
- package/dist/task/fix-context.d.ts +39 -0
- package/dist/task/fix-context.js +58 -0
- package/dist/task/gate-deps.d.ts +49 -0
- package/dist/task/gate-deps.js +199 -38
- package/dist/task/gate-evidence.d.ts +51 -0
- package/dist/task/gate-evidence.js +90 -0
- package/dist/task/gate-resolution.d.ts +119 -0
- package/dist/task/gate-resolution.js +120 -0
- package/dist/task/git-state-guard.js +3 -26
- package/dist/task/handoff.d.ts +35 -0
- package/dist/task/handoff.js +100 -0
- package/dist/task/health-baseline.d.ts +87 -0
- package/dist/task/health-baseline.js +158 -0
- package/dist/task/ledger.d.ts +10 -0
- package/dist/task/ledger.js +22 -11
- package/dist/task/lint-fix.d.ts +11 -0
- package/dist/task/lint-fix.js +14 -8
- package/dist/task/loop-detector.d.ts +52 -3
- package/dist/task/loop-detector.js +81 -5
- package/dist/task/mentions.d.ts +17 -0
- package/dist/task/mentions.js +86 -0
- package/dist/task/orchestrator.d.ts +53 -9
- package/dist/task/orchestrator.js +104 -28
- package/dist/task/orientation.d.ts +135 -29
- package/dist/task/orientation.js +259 -67
- package/dist/task/owned-freeze-reassign.d.ts +7 -3
- package/dist/task/owned-freeze-reassign.js +13 -5
- package/dist/task/parsers.d.ts +6 -1
- package/dist/task/parsers.js +21 -4
- package/dist/task/phases.d.ts +1 -1
- package/dist/task/phases.js +125 -153
- package/dist/task/plan-orchestrator.js +5 -6
- package/dist/task/plan-rounds.d.ts +1 -0
- package/dist/task/plan-rounds.js +8 -1
- package/dist/task/prohibition-probe.d.ts +6 -1
- package/dist/task/prohibition-probe.js +26 -6
- package/dist/task/prompts.js +13 -2
- package/dist/task/qa-transcript.d.ts +17 -0
- package/dist/task/qa-transcript.js +28 -0
- package/dist/task/question-source.d.ts +16 -2
- package/dist/task/question-source.js +21 -8
- package/dist/task/repo-health-check.d.ts +27 -0
- package/dist/task/repo-health-check.js +39 -5
- package/dist/task/requirements.d.ts +67 -13
- package/dist/task/requirements.js +129 -67
- package/dist/task/research-worker.d.ts +43 -2
- package/dist/task/research-worker.js +103 -8
- package/dist/task/run-bracket.d.ts +9 -4
- package/dist/task/run-bracket.js +9 -0
- package/dist/task/run-context.d.ts +152 -0
- package/dist/task/run-context.js +277 -0
- package/dist/task/spec-doc.d.ts +78 -0
- package/dist/task/spec-doc.js +205 -0
- package/dist/task/spec-model.d.ts +70 -0
- package/dist/task/spec-model.js +139 -0
- package/dist/task/stall-detector.d.ts +1 -1
- package/dist/task/stall-detector.js +9 -5
- package/dist/task/state-dir.d.ts +29 -0
- package/dist/task/state-dir.js +80 -0
- package/dist/task/suppression-probe.d.ts +67 -0
- package/dist/task/suppression-probe.js +78 -0
- package/dist/task/task-gates.d.ts +39 -57
- package/dist/task/task-gates.js +115 -195
- package/dist/task/task-io.d.ts +10 -0
- package/dist/task/task-io.js +69 -18
- package/dist/task/task-parsers.js +6 -6
- package/dist/task/task-types.d.ts +7 -0
- package/dist/task/timings.d.ts +11 -0
- package/dist/task/timings.js +21 -0
- package/dist/task/tree-hash.d.ts +8 -0
- package/dist/task/tree-hash.js +55 -0
- package/dist/task/verify-reconcile.d.ts +4 -3
- package/dist/task/verify-reconcile.js +7 -8
- package/dist/task/verify-resolution.d.ts +28 -5
- package/dist/task/verify-resolution.js +70 -13
- package/dist/task/verify-work.d.ts +98 -35
- package/dist/task/verify-work.js +227 -60
- package/dist/task/yolo.d.ts +0 -13
- package/dist/task/yolo.js +9 -21
- package/dist/workers/pi-worker-core.d.ts +20 -0
- package/dist/workers/pi-worker-core.js +16 -11
- package/dist/workers/pi-worker-docs.d.ts +6 -5
- package/dist/workers/pi-worker-docs.js +8 -7
- package/dist/workers/pi-worker-fetch.d.ts +3 -2
- package/dist/workers/pi-worker-fetch.js +5 -4
- package/dist/workers/pi-worker-search.js +5 -4
- package/dist/workers/research-cache.d.ts +23 -1
- package/dist/workers/research-cache.js +63 -3
- package/dist/workers/shared.d.ts +18 -0
- package/dist/workers/shared.js +38 -22
- package/package.json +1 -1
package/dist/task/auto-io.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* `## coverage`. The checkbox list under `## tasks` is the resume cursor, and the
|
|
8
8
|
* loop finds its next step with `entries.find(e => !e.done)` rather than a
|
|
9
9
|
* remembered position.
|
|
10
|
+
*
|
|
11
|
+
* A checkbox line reads `- [ ] P01 TASK_0006 a2 title` — see {@link ENTRY_RE}.
|
|
10
12
|
*/
|
|
11
13
|
import * as fsp from 'node:fs/promises';
|
|
12
14
|
import * as path from 'node:path';
|
|
@@ -14,6 +16,7 @@ import { tasksDir, ensureTasksDir, readTaskFile, setTaskSection } from './task-i
|
|
|
14
16
|
import { extractSection, parseFrontMatter } from './task-parsers.js';
|
|
15
17
|
import { readTextFile } from '../shared/fs-text.js';
|
|
16
18
|
import { RESUMABLE_STATES } from './task-types.js';
|
|
19
|
+
import { RUN_END_POLICY } from './run-end.js';
|
|
17
20
|
const AUTO_FILE_RE = /^(TASK_AUTO_\d{4,})\.md$/;
|
|
18
21
|
export async function allocateAutoId(cwd) {
|
|
19
22
|
await ensureTasksDir(cwd);
|
|
@@ -52,14 +55,21 @@ export function parseDecomposeList(raw) {
|
|
|
52
55
|
}
|
|
53
56
|
return out;
|
|
54
57
|
}
|
|
58
|
+
/** The MISSING bound the coverage prompt states; a longer list is truncated
|
|
59
|
+
* rather than rejected. */
|
|
60
|
+
const MAX_MISSING_AREAS = 8;
|
|
61
|
+
/** What an `unparseable` verdict contributes to the plan's missing-area list, so
|
|
62
|
+
* the judge's own INCOMPLETE cannot be shipped as COMPLETE. */
|
|
63
|
+
export const UNNAMED_COVERAGE_GAP = 'the coverage judge ruled INCOMPLETE without naming the uncovered area';
|
|
55
64
|
/**
|
|
56
65
|
* Parse the coverage-triage child's verdict. Returns null when no COVERAGE tag is
|
|
57
|
-
* present (the model wrote prose), and the caller reads a null verdict as
|
|
58
|
-
* empty missing-list, so a malformed judgment can never block planning.
|
|
66
|
+
* present at all (the model wrote prose), and the caller reads a null verdict as
|
|
67
|
+
* an empty missing-list, so a malformed judgment can never block planning.
|
|
59
68
|
*
|
|
60
|
-
* `COVERAGE: INCOMPLETE` with no MISSING lines
|
|
61
|
-
*
|
|
62
|
-
*
|
|
69
|
+
* `COVERAGE: INCOMPLETE` with no MISSING lines is NOT null, and that distinction
|
|
70
|
+
* is the whole point: prose is no verdict, while this is a verdict of INCOMPLETE
|
|
71
|
+
* that happens to name nothing. Collapsing the two shipped a plan the judge had
|
|
72
|
+
* just ruled incomplete, logged as COMPLETE.
|
|
63
73
|
*/
|
|
64
74
|
export function parseCoverageVerdict(raw) {
|
|
65
75
|
const tag = /^\s*COVERAGE:\s*(COMPLETE|INCOMPLETE)\s*$/im.exec(raw);
|
|
@@ -72,39 +82,95 @@ export function parseCoverageVerdict(raw) {
|
|
|
72
82
|
const m = /^\s*MISSING:\s*(.+?)\s*$/i.exec(line);
|
|
73
83
|
if (m && m[1].length > 0)
|
|
74
84
|
missing.push(m[1]);
|
|
75
|
-
if (missing.length >=
|
|
85
|
+
if (missing.length >= MAX_MISSING_AREAS)
|
|
76
86
|
break;
|
|
77
87
|
}
|
|
78
|
-
|
|
79
|
-
// with — treat it like an unparseable verdict rather than looping blind.
|
|
80
|
-
return missing.length === 0 ? null : { kind: 'incomplete', missing };
|
|
88
|
+
return missing.length === 0 ? { kind: 'unparseable' } : { kind: 'incomplete', missing };
|
|
81
89
|
}
|
|
82
90
|
const CHECKBOX_RE = /^- \[([ xX])\]\s+(.+?)\s*$/;
|
|
83
|
-
|
|
91
|
+
/**
|
|
92
|
+
* The checkbox line grammar: `- [ ] P01 TASK_0006 a2 title`, where the attempts
|
|
93
|
+
* field may carry how the last attempt ended — `a2:failed`.
|
|
94
|
+
*
|
|
95
|
+
* Every field is optional and each is followed by ONE space; the title is set off
|
|
96
|
+
* by an EXTRA space. That two-space delimiter is what keeps the grammar
|
|
97
|
+
* unambiguous against prose: a title of its own may begin `TASK_0006 is broken`
|
|
98
|
+
* or `(auto) …` and is read whole, because a single space never separates a field
|
|
99
|
+
* from a title. Both legacy forms — `TASK_0006 title` and a bare `title` — are
|
|
100
|
+
* the same grammar with fields missing, so no migration pass is needed on read.
|
|
101
|
+
*
|
|
102
|
+
* The end suffix spells out the endings RUN_END_POLICY registers rather than
|
|
103
|
+
* accepting any word, so `a2:whatever ` stays prose and a renamed ending is a
|
|
104
|
+
* parse miss, never a silently mis-read field.
|
|
105
|
+
*/
|
|
106
|
+
const ENTRY_RE = new RegExp('^(?:(?:(P\\d{2,}) )?(?:(TASK_\\d{4,}) )?'
|
|
107
|
+
+ `(?:a(\\d+)(?::(${Object.keys(RUN_END_POLICY).join('|')}))? )? )?(.+)$`);
|
|
108
|
+
function parseEntryLine(line, index) {
|
|
109
|
+
const m = CHECKBOX_RE.exec(line.trim());
|
|
110
|
+
if (!m)
|
|
111
|
+
return null;
|
|
112
|
+
const f = ENTRY_RE.exec(m[2].trim());
|
|
113
|
+
if (!f)
|
|
114
|
+
return null;
|
|
115
|
+
const [, key, producedId, attempts, lastEnd, title] = f;
|
|
116
|
+
// Attempts are only ever written alongside an id (they are minted when the
|
|
117
|
+
// inner task starts), so a bare `a7 …` is a title, not a field.
|
|
118
|
+
const keyed = key !== undefined || producedId !== undefined;
|
|
119
|
+
return {
|
|
120
|
+
index,
|
|
121
|
+
done: m[1].toLowerCase() === 'x',
|
|
122
|
+
// A line carries a stamped TASK_NNNN id both when done (the completed
|
|
123
|
+
// inner task) and when merely started — an unchecked, stamped line is an
|
|
124
|
+
// in-progress entry whose inner task can be resumed.
|
|
125
|
+
...(key !== undefined && { key }),
|
|
126
|
+
...(producedId !== undefined && { producedId }),
|
|
127
|
+
...(keyed && attempts !== undefined && { attempts: parseInt(attempts, 10) }),
|
|
128
|
+
...(keyed && lastEnd !== undefined && { lastEnd: lastEnd }),
|
|
129
|
+
title: keyed || attempts === undefined ? title.trim() : m[2].trim()
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Render one entry back into the line grammar.
|
|
134
|
+
*
|
|
135
|
+
* An entry with NEITHER a key nor an id has no field to anchor an attempts count
|
|
136
|
+
* against, and `a2 title` alone re-parses as a title (see {@link ENTRY_RE}) — so
|
|
137
|
+
* the count is dropped rather than written into a line that would read it back as
|
|
138
|
+
* prose. Only pre-key legacy plans are in that state, and they predate the counter.
|
|
139
|
+
*/
|
|
140
|
+
function renderEntryLine(e) {
|
|
141
|
+
const anchored = e.key !== undefined || e.producedId !== undefined;
|
|
142
|
+
const attempts = e.attempts === undefined || !anchored ?
|
|
143
|
+
undefined
|
|
144
|
+
: `a${e.attempts}${e.lastEnd === undefined ? '' : `:${e.lastEnd}`}`;
|
|
145
|
+
const fields = [e.key, e.producedId, attempts]
|
|
146
|
+
.filter((f) => f !== undefined && f.length > 0)
|
|
147
|
+
.join(' ');
|
|
148
|
+
return `- [${e.done ? 'x' : ' '}] ${fields.length > 0 ? `${fields} ` : ''}${e.title}`;
|
|
149
|
+
}
|
|
150
|
+
/** The plan key for the `index`th entry of a freshly planned list. */
|
|
151
|
+
export function planKeyAt(index) {
|
|
152
|
+
return `P${String(index + 1).padStart(2, '0')}`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The lowest key number no entry in `entries` holds. Keys are allocated 1:1 with
|
|
156
|
+
* plan entries, so an unkeyed legacy list has implicitly spent its first N
|
|
157
|
+
* numbers — counting it in keeps a spliced repair step from minting a key an
|
|
158
|
+
* eventual migration would hand to an existing entry.
|
|
159
|
+
*/
|
|
160
|
+
export function nextPlanKey(entries) {
|
|
161
|
+
const highest = entries.reduce((max, e) => Math.max(max, e.key ? parseInt(e.key.slice(1), 10) : 0), entries.length);
|
|
162
|
+
return planKeyAt(highest);
|
|
163
|
+
}
|
|
84
164
|
/** Parse the "## tasks" checkbox list. */
|
|
85
165
|
export function parseTaskList(body) {
|
|
86
166
|
const section = extractSection(body, 'tasks');
|
|
87
167
|
if (section === null)
|
|
88
168
|
return [];
|
|
89
169
|
const entries = [];
|
|
90
|
-
let index = 0;
|
|
91
170
|
for (const line of section.split('\n')) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
const done = m[1].toLowerCase() === 'x';
|
|
96
|
-
const rest = m[2].trim();
|
|
97
|
-
// A line carries a stamped TASK_NNNN id both when done (the completed
|
|
98
|
-
// inner task) and when merely started — an unchecked, stamped line is an
|
|
99
|
-
// in-progress entry whose inner task can be resumed.
|
|
100
|
-
const idm = PRODUCED_ID_RE.exec(rest);
|
|
101
|
-
if (idm) {
|
|
102
|
-
entries.push({ index, title: idm[2].trim(), done, producedId: idm[1] });
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
entries.push({ index, title: rest, done });
|
|
106
|
-
}
|
|
107
|
-
index++;
|
|
171
|
+
const entry = parseEntryLine(line, entries.length);
|
|
172
|
+
if (entry)
|
|
173
|
+
entries.push(entry);
|
|
108
174
|
}
|
|
109
175
|
return entries;
|
|
110
176
|
}
|
|
@@ -113,24 +179,31 @@ export function parseTaskList(body) {
|
|
|
113
179
|
* cross-cutting and what stayed unowned. An empty string omits the section
|
|
114
180
|
* entirely rather than emitting a blank heading. */
|
|
115
181
|
export function buildAutoBody(feature, clarifications, titles, coverage = '') {
|
|
116
|
-
const tasks = titles
|
|
182
|
+
const tasks = titles
|
|
183
|
+
.map((t, i) => renderEntryLine({ key: planKeyAt(i), title: t, done: false }))
|
|
184
|
+
.join('\n');
|
|
117
185
|
return (`\n## feature prompt\n\n${feature.trim() || '(none)'}\n\n`
|
|
118
186
|
+ `## clarifications\n\n${clarifications.trim() || '(none)'}\n\n`
|
|
119
187
|
+ `## tasks\n\n${tasks}\n`
|
|
120
188
|
+ (coverage.trim().length > 0 ? `\n## coverage\n\n${coverage.trim()}\n` : ''));
|
|
121
189
|
}
|
|
122
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Rewrite the Nth checkbox line of the "## tasks" section in place. `render`
|
|
192
|
+
* receives the line as parsed, so a rewrite that only changes one field carries
|
|
193
|
+
* the others — a check-off must not drop the key the ownership join reads.
|
|
194
|
+
*/
|
|
123
195
|
async function rewriteTaskLine(cwd, id, index, render, label) {
|
|
124
196
|
const { body } = await readTaskFile(cwd, id);
|
|
125
197
|
const section = extractSection(body, 'tasks') ?? '';
|
|
126
198
|
const lines = section.split('\n');
|
|
127
199
|
let seen = -1;
|
|
128
200
|
for (let i = 0; i < lines.length; i++) {
|
|
129
|
-
|
|
201
|
+
const entry = parseEntryLine(lines[i], seen + 1);
|
|
202
|
+
if (!entry)
|
|
130
203
|
continue;
|
|
131
204
|
seen++;
|
|
132
205
|
if (seen === index) {
|
|
133
|
-
lines[i] = render();
|
|
206
|
+
lines[i] = render(entry);
|
|
134
207
|
break;
|
|
135
208
|
}
|
|
136
209
|
}
|
|
@@ -141,25 +214,45 @@ async function rewriteTaskLine(cwd, id, index, render, label) {
|
|
|
141
214
|
}
|
|
142
215
|
/** Check off the Nth checkbox line, stamping the produced TASK_NNNN id. */
|
|
143
216
|
export async function checkOffTask(cwd, id, index, producedId, title) {
|
|
144
|
-
await rewriteTaskLine(cwd, id, index,
|
|
217
|
+
await rewriteTaskLine(cwd, id, index, e => renderEntryLine({ ...e, done: true, title, producedId: producedId || undefined }), 'checkOffTask');
|
|
145
218
|
}
|
|
146
219
|
/**
|
|
147
220
|
* Stamp the inner TASK_NNNN id onto the Nth (still-unchecked) entry the moment
|
|
148
221
|
* the inner task is allocated. This links the AUTO entry to its in-progress
|
|
149
222
|
* inner task so /task-auto-resume can continue it from its saved phase instead
|
|
150
223
|
* of starting a brand-new task — matching how /task-resume behaves.
|
|
224
|
+
*
|
|
225
|
+
* The first stamp is also where the attempt counter is minted: an entry under way
|
|
226
|
+
* has had one attempt.
|
|
151
227
|
*/
|
|
152
228
|
export async function stampTaskInProgress(cwd, id, index, producedId, title) {
|
|
153
|
-
await rewriteTaskLine(cwd, id, index,
|
|
229
|
+
await rewriteTaskLine(cwd, id, index, e => renderEntryLine({ ...e, done: false, title, producedId }), 'stampTaskInProgress');
|
|
154
230
|
}
|
|
155
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* Count one more attempt on the `index`th entry and return the new total.
|
|
233
|
+
*
|
|
234
|
+
* Called for a fresh start AND for a resume, because both spend a run on the
|
|
235
|
+
* entry: a task that crashes in refine is re-entered from scratch every time, and
|
|
236
|
+
* a counter that only saw fresh starts would read 1 forever while the loop re-ran
|
|
237
|
+
* it without end. The previous ending is cleared here — it describes the attempt
|
|
238
|
+
* that is now over.
|
|
239
|
+
*/
|
|
240
|
+
export async function beginTaskAttempt(cwd, id, index) {
|
|
241
|
+
let attempts = 1;
|
|
242
|
+
await rewriteTaskLine(cwd, id, index, e => {
|
|
243
|
+
attempts = (e.attempts ?? 0) + 1;
|
|
244
|
+
const { lastEnd: _ended, ...rest } = e;
|
|
245
|
+
return renderEntryLine({ ...rest, attempts });
|
|
246
|
+
}, 'beginTaskAttempt');
|
|
247
|
+
return attempts;
|
|
248
|
+
}
|
|
249
|
+
/** Record how the `index`th entry's attempt ended. */
|
|
250
|
+
export async function recordTaskEnd(cwd, id, index, lastEnd) {
|
|
251
|
+
await rewriteTaskLine(cwd, id, index, e => renderEntryLine({ ...e, attempts: e.attempts ?? 1, lastEnd }), 'recordTaskEnd');
|
|
252
|
+
}
|
|
253
|
+
/** The bare title of a checkbox line (fields stripped), or null if not one. */
|
|
156
254
|
function entryTitle(line) {
|
|
157
|
-
|
|
158
|
-
if (!m)
|
|
159
|
-
return null;
|
|
160
|
-
const rest = m[2].trim();
|
|
161
|
-
const idm = PRODUCED_ID_RE.exec(rest);
|
|
162
|
-
return idm ? idm[2].trim() : rest;
|
|
255
|
+
return parseEntryLine(line, 0)?.title ?? null;
|
|
163
256
|
}
|
|
164
257
|
/**
|
|
165
258
|
* Insert a NEW unchecked entry directly after the `afterIndex`th checkbox — the
|
|
@@ -194,15 +287,15 @@ export async function insertTaskAfter(cwd, id, afterIndex, title) {
|
|
|
194
287
|
// sits relative to afterIndex — never add a second one.
|
|
195
288
|
if (lines.some(l => entryTitle(l) === clean))
|
|
196
289
|
return false;
|
|
197
|
-
|
|
290
|
+
const entries = [];
|
|
198
291
|
let insertAt = -1;
|
|
199
292
|
for (let i = 0; i < lines.length; i++) {
|
|
200
|
-
|
|
293
|
+
const entry = parseEntryLine(lines[i], entries.length);
|
|
294
|
+
if (!entry)
|
|
201
295
|
continue;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
break;
|
|
296
|
+
entries.push(entry);
|
|
297
|
+
if (entries.length - 1 <= afterIndex)
|
|
298
|
+
insertAt = i + 1;
|
|
206
299
|
}
|
|
207
300
|
// An out-of-range afterIndex is not an error: the loop above leaves `insertAt`
|
|
208
301
|
// just past the LAST checkbox, so the entry is appended rather than lost — a
|
|
@@ -213,7 +306,7 @@ export async function insertTaskAfter(cwd, id, afterIndex, title) {
|
|
|
213
306
|
// lines at all, where there is no position to splice into.
|
|
214
307
|
if (insertAt === -1)
|
|
215
308
|
return false;
|
|
216
|
-
lines.splice(insertAt, 0,
|
|
309
|
+
lines.splice(insertAt, 0, renderEntryLine({ key: nextPlanKey(entries), title: clean, done: false }));
|
|
217
310
|
await setTaskSection(cwd, id, 'tasks', lines.join('\n'));
|
|
218
311
|
return true;
|
|
219
312
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { type TaskEntry } from './auto-io.js';
|
|
3
|
+
import { expandFeatureMentions, readableMentions } from './mentions.js';
|
|
4
|
+
export { expandFeatureMentions, readableMentions };
|
|
2
5
|
import { type GateDeps } from './task-gates.js';
|
|
3
6
|
import { type FinalGateStageDeps } from './run-final-gate.js';
|
|
4
7
|
import { type RequirementEntry } from './requirements.js';
|
|
@@ -31,23 +34,6 @@ export interface AutoDeps extends GateDeps, FinalGateStageDeps {
|
|
|
31
34
|
}
|
|
32
35
|
/** Wait for every plan-debug line written so far to reach disk. Tests only. */
|
|
33
36
|
export declare function flushPlanDebug(): Promise<unknown>;
|
|
34
|
-
/**
|
|
35
|
-
* Expand any @file references in the feature text by appending each referenced
|
|
36
|
-
* file's contents, so the planning children (clarify, decompose) always see the
|
|
37
|
-
* real spec inline instead of relying on the model to open the file itself.
|
|
38
|
-
* Without this, clarify on a one-line "Implement @spec.md" tends to bail with
|
|
39
|
-
* NONE because, to the model, the request looks small and unambiguous.
|
|
40
|
-
* Unreadable mentions (typos, non-file @tokens) are left untouched; the feature
|
|
41
|
-
* is returned verbatim when nothing readable is referenced.
|
|
42
|
-
*/
|
|
43
|
-
export declare function expandFeatureMentions(cwd: string, feature: string): Promise<string>;
|
|
44
|
-
/**
|
|
45
|
-
* The @file references in the feature that point at a readable file on disk —
|
|
46
|
-
* the bare path tokens, deduped, in first-seen order. Unreadable mentions
|
|
47
|
-
* (typos, non-file @tokens) are dropped so we never advertise a missing file as
|
|
48
|
-
* an authoritative spec.
|
|
49
|
-
*/
|
|
50
|
-
export declare function readableMentions(cwd: string, feature: string): Promise<string[]>;
|
|
51
37
|
/**
|
|
52
38
|
* Thread the feature's spec references AND any per-task decisions into every
|
|
53
39
|
* decomposed task title. A title is ALL a per-task pipeline ever sees, so both the
|
|
@@ -76,11 +62,28 @@ export declare function attachSpecRefs(titles: string[], refs: string[]): string
|
|
|
76
62
|
*
|
|
77
63
|
* The plan listing strips the threaded "| decisions … | spec …" tail from each
|
|
78
64
|
* title and keeps the human-readable head, so the model reads clean step names,
|
|
79
|
-
* and it marks the current one
|
|
65
|
+
* and it marks the current one `[this]` — both confirmed by building a fence
|
|
80
66
|
* over threaded titles. The authoritative spec ref still rides on THIS step's own
|
|
81
67
|
* title via attachSpecRefs.
|
|
68
|
+
*
|
|
69
|
+
* Every sibling used to read as unbuilt, which is a lie about the first half of
|
|
70
|
+
* any plan past step one: a step told its predecessors are "done in later runs"
|
|
71
|
+
* re-scaffolds what is already in the tree. The entries carry their own done
|
|
72
|
+
* state, so the listing states it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildScopeFence(entries: readonly TaskEntry[], currentIndex: number): string;
|
|
75
|
+
/**
|
|
76
|
+
* The scope fence for step `currentIndex`, plus the REPAIR fence when that step is
|
|
77
|
+
* a queued root-cause repair. A repair title ("repair test/teardown.ts: …") reads
|
|
78
|
+
* to refine like any other feature step, and refine's job is to expand a title into
|
|
79
|
+
* a full spec — which is exactly how "repair the teardown" becomes "overhaul the
|
|
80
|
+
* test infrastructure". The extra fence pins the one editable file, and pins VERIFY
|
|
81
|
+
* to the failing command WHEN the title carries one: `extractFailingCommand` reads
|
|
82
|
+
* a backticked runner command out of the defect text, so a defect quoting
|
|
83
|
+
* `bun run test` yields it while a plain-prose defect yields undefined and only the
|
|
84
|
+
* file pin applies.
|
|
82
85
|
*/
|
|
83
|
-
export declare function
|
|
86
|
+
export declare function buildStepFence(entries: readonly TaskEntry[], currentIndex: number): string;
|
|
84
87
|
/**
|
|
85
88
|
* What ORIENT establishes about the feature before anyone is asked anything: the
|
|
86
89
|
* spec text the planning children will actually read, and the requirement ledger
|