@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.
Files changed (123) hide show
  1. package/README.md +7 -4
  2. package/dist/config/config.d.ts +61 -18
  3. package/dist/config/config.js +50 -6
  4. package/dist/config/register.js +18 -5
  5. package/dist/shared/content-tokens.d.ts +16 -0
  6. package/dist/shared/content-tokens.js +137 -0
  7. package/dist/task/accept-debt.d.ts +15 -1
  8. package/dist/task/accept-debt.js +4 -0
  9. package/dist/task/artifact-closure.js +7 -1
  10. package/dist/task/auto-io.d.ts +59 -7
  11. package/dist/task/auto-io.js +140 -47
  12. package/dist/task/auto-orchestrator.d.ts +22 -19
  13. package/dist/task/auto-orchestrator.js +126 -112
  14. package/dist/task/child-runner.d.ts +18 -0
  15. package/dist/task/child-runner.js +7 -8
  16. package/dist/task/command-run.d.ts +4 -0
  17. package/dist/task/command-run.js +4 -2
  18. package/dist/task/constraint-policy.d.ts +52 -0
  19. package/dist/task/constraint-policy.js +66 -0
  20. package/dist/task/context-silence.d.ts +5 -4
  21. package/dist/task/context-silence.js +13 -5
  22. package/dist/task/coverage-loop.d.ts +7 -4
  23. package/dist/task/coverage-loop.js +16 -138
  24. package/dist/task/debug-log.d.ts +25 -2
  25. package/dist/task/debug-log.js +64 -10
  26. package/dist/task/decompose-fidelity.d.ts +17 -4
  27. package/dist/task/decompose-fidelity.js +95 -55
  28. package/dist/task/decompose-granularity.d.ts +7 -7
  29. package/dist/task/decompose-granularity.js +8 -8
  30. package/dist/task/env-notes.d.ts +65 -19
  31. package/dist/task/env-notes.js +152 -50
  32. package/dist/task/external-context.js +25 -5
  33. package/dist/task/fix-context.d.ts +39 -0
  34. package/dist/task/fix-context.js +58 -0
  35. package/dist/task/gate-deps.d.ts +49 -0
  36. package/dist/task/gate-deps.js +199 -38
  37. package/dist/task/gate-evidence.d.ts +51 -0
  38. package/dist/task/gate-evidence.js +90 -0
  39. package/dist/task/gate-resolution.d.ts +119 -0
  40. package/dist/task/gate-resolution.js +120 -0
  41. package/dist/task/git-state-guard.js +3 -26
  42. package/dist/task/handoff.d.ts +35 -0
  43. package/dist/task/handoff.js +100 -0
  44. package/dist/task/health-baseline.d.ts +87 -0
  45. package/dist/task/health-baseline.js +158 -0
  46. package/dist/task/ledger.d.ts +10 -0
  47. package/dist/task/ledger.js +22 -11
  48. package/dist/task/lint-fix.d.ts +11 -0
  49. package/dist/task/lint-fix.js +14 -8
  50. package/dist/task/loop-detector.d.ts +52 -3
  51. package/dist/task/loop-detector.js +81 -5
  52. package/dist/task/mentions.d.ts +17 -0
  53. package/dist/task/mentions.js +86 -0
  54. package/dist/task/orchestrator.d.ts +53 -9
  55. package/dist/task/orchestrator.js +104 -28
  56. package/dist/task/orientation.d.ts +135 -29
  57. package/dist/task/orientation.js +259 -67
  58. package/dist/task/owned-freeze-reassign.d.ts +7 -3
  59. package/dist/task/owned-freeze-reassign.js +13 -5
  60. package/dist/task/parsers.d.ts +6 -1
  61. package/dist/task/parsers.js +21 -4
  62. package/dist/task/phases.d.ts +1 -1
  63. package/dist/task/phases.js +125 -153
  64. package/dist/task/plan-orchestrator.js +5 -6
  65. package/dist/task/plan-rounds.d.ts +1 -0
  66. package/dist/task/plan-rounds.js +8 -1
  67. package/dist/task/prohibition-probe.d.ts +6 -1
  68. package/dist/task/prohibition-probe.js +26 -6
  69. package/dist/task/prompts.js +13 -2
  70. package/dist/task/qa-transcript.d.ts +17 -0
  71. package/dist/task/qa-transcript.js +28 -0
  72. package/dist/task/question-source.d.ts +16 -2
  73. package/dist/task/question-source.js +21 -8
  74. package/dist/task/repo-health-check.d.ts +27 -0
  75. package/dist/task/repo-health-check.js +39 -5
  76. package/dist/task/requirements.d.ts +67 -13
  77. package/dist/task/requirements.js +129 -67
  78. package/dist/task/research-worker.d.ts +43 -2
  79. package/dist/task/research-worker.js +103 -8
  80. package/dist/task/run-bracket.d.ts +9 -4
  81. package/dist/task/run-bracket.js +9 -0
  82. package/dist/task/run-context.d.ts +152 -0
  83. package/dist/task/run-context.js +277 -0
  84. package/dist/task/spec-doc.d.ts +78 -0
  85. package/dist/task/spec-doc.js +205 -0
  86. package/dist/task/spec-model.d.ts +70 -0
  87. package/dist/task/spec-model.js +139 -0
  88. package/dist/task/stall-detector.d.ts +1 -1
  89. package/dist/task/stall-detector.js +9 -5
  90. package/dist/task/state-dir.d.ts +29 -0
  91. package/dist/task/state-dir.js +80 -0
  92. package/dist/task/suppression-probe.d.ts +67 -0
  93. package/dist/task/suppression-probe.js +78 -0
  94. package/dist/task/task-gates.d.ts +39 -57
  95. package/dist/task/task-gates.js +115 -195
  96. package/dist/task/task-io.d.ts +10 -0
  97. package/dist/task/task-io.js +69 -18
  98. package/dist/task/task-parsers.js +6 -6
  99. package/dist/task/task-types.d.ts +7 -0
  100. package/dist/task/timings.d.ts +11 -0
  101. package/dist/task/timings.js +21 -0
  102. package/dist/task/tree-hash.d.ts +8 -0
  103. package/dist/task/tree-hash.js +55 -0
  104. package/dist/task/verify-reconcile.d.ts +4 -3
  105. package/dist/task/verify-reconcile.js +7 -8
  106. package/dist/task/verify-resolution.d.ts +28 -5
  107. package/dist/task/verify-resolution.js +70 -13
  108. package/dist/task/verify-work.d.ts +98 -35
  109. package/dist/task/verify-work.js +227 -60
  110. package/dist/task/yolo.d.ts +0 -13
  111. package/dist/task/yolo.js +9 -21
  112. package/dist/workers/pi-worker-core.d.ts +20 -0
  113. package/dist/workers/pi-worker-core.js +16 -11
  114. package/dist/workers/pi-worker-docs.d.ts +6 -5
  115. package/dist/workers/pi-worker-docs.js +8 -7
  116. package/dist/workers/pi-worker-fetch.d.ts +3 -2
  117. package/dist/workers/pi-worker-fetch.js +5 -4
  118. package/dist/workers/pi-worker-search.js +5 -4
  119. package/dist/workers/research-cache.d.ts +23 -1
  120. package/dist/workers/research-cache.js +63 -3
  121. package/dist/workers/shared.d.ts +18 -0
  122. package/dist/workers/shared.js +38 -22
  123. package/package.json +1 -1
@@ -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 an
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 also returns null, deliberately:
61
- * it names nothing to reprompt with, so treating it as a verdict would loop
62
- * blind. Confirmed by running both shapes.
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 >= 8)
85
+ if (missing.length >= MAX_MISSING_AREAS)
76
86
  break;
77
87
  }
78
- // INCOMPLETE with no MISSING lines carries no actionable signal to reprompt
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
- const PRODUCED_ID_RE = /^(TASK_\d{4,})\s{2,}(.+)$/;
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 m = CHECKBOX_RE.exec(line.trim());
93
- if (!m)
94
- continue;
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.map(t => `- [ ] ${t}`).join('\n');
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
- /** Rewrite the Nth checkbox line of the "## tasks" section in place. */
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
- if (!CHECKBOX_RE.test(lines[i].trim()))
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, () => (producedId ? `- [x] ${producedId} ${title}` : `- [x] ${title}`), 'checkOffTask');
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, () => `- [ ] ${producedId} ${title}`, 'stampTaskInProgress');
229
+ await rewriteTaskLine(cwd, id, index, e => renderEntryLine({ ...e, done: false, title, producedId }), 'stampTaskInProgress');
154
230
  }
155
- /** The bare title of a checkbox line (id stamp stripped), or null if not one. */
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
- const m = CHECKBOX_RE.exec(line.trim());
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
- let seen = -1;
290
+ const entries = [];
198
291
  let insertAt = -1;
199
292
  for (let i = 0; i < lines.length; i++) {
200
- if (!CHECKBOX_RE.test(lines[i].trim()))
293
+ const entry = parseEntryLine(lines[i], entries.length);
294
+ if (!entry)
201
295
  continue;
202
- seen++;
203
- insertAt = i + 1;
204
- if (seen === afterIndex)
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, `- [ ] ${clean}`);
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 "(THIS STEP)" — both confirmed by building a fence
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 buildScopeFence(titles: string[], currentIndex: number): string;
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