pi-plans 0.8.0 → 0.8.2
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 +2 -2
- package/agents/execution-reviewer.md +62 -10
- package/package.json +2 -2
- package/references/pi-planning-workflow.md +16 -8
- package/references/state-and-config.md +2 -2
- package/src/auditor.ts +170 -21
- package/src/dashboard.ts +102 -19
- package/src/exec.ts +865 -107
- package/src/refine-ui.ts +21 -5
- package/src/resume-command.ts +26 -6
- package/src/review-budget.ts +290 -0
- package/src/tasks.ts +76 -0
- package/src/ui-language.ts +38 -0
- package/src/workflow-state.ts +217 -8
- package/tests/analyze-refs.test.ts +1 -1
- package/tests/auditor.test.ts +185 -1
- package/tests/dashboard.test.ts +165 -1
- package/tests/exec-review-loop.test.ts +1031 -10
- package/tests/exec.test.ts +32 -5
- package/tests/fixtures/lattice-code-blocked/plan-v2-trimmed.md +57 -0
- package/tests/fixtures/lattice-code-blocked/state.json +158 -0
- package/tests/refine-ui.test.ts +25 -2
- package/tests/resume.test.ts +45 -1
- package/tests/review-budget.test.ts +201 -0
- package/tests/tasks.test.ts +45 -0
- package/tests/workflow-state.test.ts +227 -0
- package/tools/analyze-refs.ts +17 -6
- package/tools/execute-plan.ts +12 -5
- package/tools/refine.ts +22 -3
package/src/dashboard.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { CheckItem } from "./plan.ts";
|
|
19
|
-
import {
|
|
19
|
+
import { LEGACY_REVIEW_MAX_ROUNDS, formatReviewBudget, unlimitedHardCapCeiling, type ReviewBudget } from "./review-budget.ts";
|
|
20
20
|
import { truncateToWidth, visibleWidth } from "./refine-ui-helpers.ts";
|
|
21
21
|
import {
|
|
22
22
|
allTasksTerminal,
|
|
@@ -46,6 +46,24 @@ export interface DashboardModel {
|
|
|
46
46
|
* never render `audit complete ✓` while this is set — the round-1 mis-cue
|
|
47
47
|
* showed the tick for the whole duration of a running audit. */
|
|
48
48
|
reviewRunning: boolean;
|
|
49
|
+
/** v0.9: unresolved findings from the newest committed review round
|
|
50
|
+
* (stable ids). High entries block completion; the rest are recorded. */
|
|
51
|
+
findings: Array<{ id: string; severity: string; note: string; taskIds: string[] }>;
|
|
52
|
+
/** v0.9.2: tasks that keep the review from starting — the newest failed
|
|
53
|
+
* round's rollback set intersected with the still-open tasks. Empty = the
|
|
54
|
+
* review is not blocked by open work. */
|
|
55
|
+
blockedTasks: string[];
|
|
56
|
+
/** Round that reopened those tasks (shown as `reopened by round N`). */
|
|
57
|
+
blockedRound: number | null;
|
|
58
|
+
/** v0.9.3: the per-run execution-review budget (rounds or `unlimited`).
|
|
59
|
+
* Null/absent = not chosen yet; `reviewBudgetDefaulted` marks the no-UI
|
|
60
|
+
* fallback so the expanded view can annotate it `(default)`. */
|
|
61
|
+
reviewBudget?: ReviewBudget | null;
|
|
62
|
+
/** v0.9.3: committed rounds across the whole run + granted extension —
|
|
63
|
+
* the unlimited budget's `n/50` progress. */
|
|
64
|
+
reviewRoundsTotal?: number;
|
|
65
|
+
reviewCapExtension?: number;
|
|
66
|
+
reviewBudgetDefaulted?: boolean;
|
|
49
67
|
startedAt: string;
|
|
50
68
|
usage: { inToks: number; outToks: number };
|
|
51
69
|
}
|
|
@@ -172,22 +190,45 @@ export function renderDashboardLines(model: DashboardModel, width: number, theme
|
|
|
172
190
|
// instead — the panel must look alive, never finished-then-silent.
|
|
173
191
|
const owed = model.checklist.some((item) => !item.done);
|
|
174
192
|
const nextRound = (model.auditRounds ?? 0) + 1;
|
|
193
|
+
const highCount = model.findings.filter((f) => f.severity === "high").length;
|
|
175
194
|
const auditLine = model.reviewRunning
|
|
176
|
-
? `review: round ${nextRound}/${
|
|
195
|
+
? `review: round ${nextRound}/${budgetLabelOf(model)} running — read-only reviewer verifying`
|
|
177
196
|
: model.auditFailed.length > 0
|
|
178
197
|
? `audit: ${model.auditFailed.length} check(s) failed — rollback pending`
|
|
179
|
-
:
|
|
180
|
-
? `
|
|
181
|
-
:
|
|
182
|
-
? `
|
|
183
|
-
:
|
|
184
|
-
?
|
|
185
|
-
:
|
|
198
|
+
: highCount > 0
|
|
199
|
+
? `review: round ${nextRound}/${budgetLabelOf(model)} — ${highCount} high finding(s) unresolved`
|
|
200
|
+
: model.auditUndeterminable.length > 0
|
|
201
|
+
? `audit: ${model.auditUndeterminable.length} check(s) undeterminable — verdict unreadable`
|
|
202
|
+
: owed
|
|
203
|
+
? `review: round ${nextRound}/${budgetLabelOf(model)} — verdict pending`
|
|
204
|
+
: model.auditRounds !== null
|
|
205
|
+
? "audit complete ✓"
|
|
206
|
+
: "all tasks terminal — audit pending";
|
|
186
207
|
lines.push(boxRow("│", ` ${clip(auditLine, inner - 2)}`, " ", width));
|
|
187
208
|
}
|
|
209
|
+
// v0.9.2: the blocker row renders in BOTH phases — a paused run sees it
|
|
210
|
+
// below the pause summary (which may be clipped at narrow widths), and a
|
|
211
|
+
// live run sees exactly what keeps the review from starting.
|
|
212
|
+
if (model.blockedTasks.length > 0) {
|
|
213
|
+
const provenance = model.blockedRound !== null ? ` (reopened by round ${model.blockedRound})` : "";
|
|
214
|
+
// Provenance once per row keeps the id list readable and short enough to
|
|
215
|
+
// survive a 100-column panel without dropping the instruction.
|
|
216
|
+
const rowText = narrow
|
|
217
|
+
? `⊘ blocked: ${model.blockedTasks.join(", ")}${provenance}`
|
|
218
|
+
: `⊘ blocked: ${model.blockedTasks.join(", ")}${provenance} — close with plans_update_task`;
|
|
219
|
+
lines.push(boxRow("│", ` ${clip(rowText, inner - 2)}`, " ", width));
|
|
220
|
+
}
|
|
188
221
|
if (!narrow && model.auditFailed.length > 0) {
|
|
189
222
|
lines.push(boxRow("│", ` ✗ ${clip(model.auditFailed.join(", "), inner - 3)}`, " ", width));
|
|
190
223
|
}
|
|
224
|
+
// v0.9: unresolved findings render in both phases — the fix loop's whole
|
|
225
|
+
// point is that the executor sees what it owes while repairing.
|
|
226
|
+
const highFindings = model.findings.filter((f) => f.severity === "high");
|
|
227
|
+
if (!narrow && highFindings.length > 0) {
|
|
228
|
+
lines.push(boxRow("│", ` ⚠ ${clip(`high: ${highFindings.map((f) => f.id).join(", ")}`, inner - 3)}`, " ", width));
|
|
229
|
+
} else if (!narrow && model.findings.length > 0) {
|
|
230
|
+
lines.push(boxRow("│", ` · ${clip(`findings: ${model.findings.map((f) => f.id).join(", ")}`, inner - 3)}`, " ", width));
|
|
231
|
+
}
|
|
191
232
|
if (!narrow && model.auditUndeterminable.length > 0) {
|
|
192
233
|
lines.push(boxRow("│", ` ? ${clip(model.auditUndeterminable.join(", "), inner - 3)}`, " ", width));
|
|
193
234
|
}
|
|
@@ -206,6 +247,22 @@ export function renderDashboardLines(model: DashboardModel, width: number, theme
|
|
|
206
247
|
return clampLines(painted, width);
|
|
207
248
|
}
|
|
208
249
|
|
|
250
|
+
/** v0.9.3: the budget denominator as shown in every review line (`3`, `∞`).
|
|
251
|
+
* A model without the field (legacy fixtures) reads as the legacy 5. */
|
|
252
|
+
function budgetLabelOf(model: DashboardModel): string {
|
|
253
|
+
return formatReviewBudget(model.reviewBudget ?? LEGACY_REVIEW_MAX_ROUNDS);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** v0.9.3: unlimited budgets render their run-cumulative hard-cap progress. */
|
|
257
|
+
function hardCapOf(model: DashboardModel): string | null {
|
|
258
|
+
if ((model.reviewBudget ?? null) !== "unlimited") return null;
|
|
259
|
+
const ceiling = unlimitedHardCapCeiling({
|
|
260
|
+
reviewRoundsTotal: model.reviewRoundsTotal ?? 0,
|
|
261
|
+
reviewCapExtension: model.reviewCapExtension ?? 0,
|
|
262
|
+
});
|
|
263
|
+
return `${model.reviewRoundsTotal ?? 0}/${ceiling}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
209
266
|
function progressBar(done: number, total: number, width = 12): string {
|
|
210
267
|
if (total <= 0) return "";
|
|
211
268
|
const filled = Math.round((done / total) * width);
|
|
@@ -216,7 +273,7 @@ export function deriveDashboardModel(
|
|
|
216
273
|
topic: string,
|
|
217
274
|
tasks: TaskView[],
|
|
218
275
|
checklist: CheckItem[],
|
|
219
|
-
extra?: { paused?: boolean; pausedReason?: string; auditRounds?: number | null; auditFailed?: string[]; auditUndeterminable?: string[]; reviewRunning?: boolean; startedAt?: string; usage?: { inToks: number; outToks: number } },
|
|
276
|
+
extra?: { paused?: boolean; pausedReason?: string; auditRounds?: number | null; auditFailed?: string[]; auditUndeterminable?: string[]; reviewRunning?: boolean; findings?: Array<{ id: string; severity: string; note: string; taskIds: string[] }>; blockedTasks?: string[]; blockedRound?: number | null; reviewBudget?: ReviewBudget | null; reviewRoundsTotal?: number; reviewCapExtension?: number; reviewBudgetDefaulted?: boolean; startedAt?: string; usage?: { inToks: number; outToks: number } },
|
|
220
277
|
): DashboardModel {
|
|
221
278
|
return {
|
|
222
279
|
topic,
|
|
@@ -228,6 +285,13 @@ export function deriveDashboardModel(
|
|
|
228
285
|
auditFailed: extra?.auditFailed ?? [],
|
|
229
286
|
auditUndeterminable: extra?.auditUndeterminable ?? [],
|
|
230
287
|
reviewRunning: extra?.reviewRunning ?? false,
|
|
288
|
+
findings: extra?.findings ?? [],
|
|
289
|
+
blockedTasks: extra?.blockedTasks ?? [],
|
|
290
|
+
blockedRound: extra?.blockedRound ?? null,
|
|
291
|
+
reviewBudget: extra?.reviewBudget ?? null,
|
|
292
|
+
reviewRoundsTotal: extra?.reviewRoundsTotal ?? 0,
|
|
293
|
+
reviewCapExtension: extra?.reviewCapExtension ?? 0,
|
|
294
|
+
reviewBudgetDefaulted: extra?.reviewBudgetDefaulted ?? false,
|
|
231
295
|
startedAt: extra?.startedAt ?? new Date().toISOString(),
|
|
232
296
|
usage: extra?.usage ?? { inToks: 0, outToks: 0 },
|
|
233
297
|
};
|
|
@@ -239,9 +303,15 @@ export function formatDashboardSummaryLine(model: DashboardModel): string {
|
|
|
239
303
|
const vcDone = model.checklist.filter((item) => item.done).length;
|
|
240
304
|
const cur = currentTask(model.tasks);
|
|
241
305
|
const wave = cur ? ` · wave ${cur.wave}` : "";
|
|
242
|
-
|
|
306
|
+
// v0.9: the review token carries the budget denominator and the unresolved
|
|
307
|
+
// high count — visible in BOTH phases (executing repair and verifying), so
|
|
308
|
+
// convergence is legible exactly while the executor is fixing.
|
|
309
|
+
const highs = model.findings.filter((f) => f.severity === "high").length;
|
|
310
|
+
const audit = model.auditRounds !== null ? ` · review r${model.auditRounds}/${budgetLabelOf(model)}` : "";
|
|
311
|
+
const highToken = highs > 0 ? ` · ${highs} high` : "";
|
|
243
312
|
const pause = model.paused ? " · ⏸ paused" : "";
|
|
244
|
-
|
|
313
|
+
const blocked = model.blockedTasks.length > 0 ? " · ⊘ blocked" : "";
|
|
314
|
+
return `plans: ${model.topic} ▸ tasks ${p.done}/${p.total} · VC ${vcDone}/${model.checklist.length}${wave}${audit}${highToken}${pause}${blocked}`;
|
|
245
315
|
}
|
|
246
316
|
|
|
247
317
|
/** Expanded tree view lines (Ctrl+Shift+T overlay). Wide layout from 96 cols. */
|
|
@@ -266,24 +336,37 @@ export function renderDashboardTreeLines(model: DashboardModel, width: number, t
|
|
|
266
336
|
if (task.status === "pending" && task.evidence) line += ` ↺${clip(task.evidence, 40)}`;
|
|
267
337
|
lines.push(line);
|
|
268
338
|
for (const child of task.children) row(child, depth + 1);
|
|
269
|
-
};
|
|
339
|
+
};
|
|
340
|
+
for (const task of model.tasks) row(task, 0);
|
|
270
341
|
lines.push("");
|
|
271
342
|
lines.push("Verification checks:");
|
|
272
343
|
for (const item of model.checklist) {
|
|
273
344
|
const mark = model.auditFailed.includes(item.id) ? "✗" : item.done ? "☑" : "☐";
|
|
274
345
|
lines.push(` ${mark} ${item.id}${wide ? ` ${clip(item.text.split(";")[1] ?? item.text, Math.min(80, width))}` : ""}`);
|
|
275
346
|
}
|
|
347
|
+
if (model.findings.length > 0) {
|
|
348
|
+
lines.push("");
|
|
349
|
+
lines.push("Findings (stable ids; absence from the newest round = resolved):");
|
|
350
|
+
for (const f of model.findings) {
|
|
351
|
+
const mark = f.severity === "high" ? "⚠" : f.severity === "malformed" ? "?" : "·";
|
|
352
|
+
lines.push(` ${mark} ${f.id} (${f.severity}${f.taskIds.length ? `, ${f.taskIds.join(", ")}` : ""}): ${wide ? clip(f.note, 72) : clip(f.note, 40)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const budgetNote = `${budgetLabelOf(model)}${model.reviewBudgetDefaulted ? " (default)" : ""}${hardCapOf(model) ? ` · cap ${hardCapOf(model)}` : ""}`;
|
|
276
356
|
if (model.reviewRunning) {
|
|
277
357
|
lines.push("");
|
|
278
|
-
lines.push(`Execution review: round ${(model.auditRounds ?? 0) + 1}/${
|
|
358
|
+
lines.push(`Execution review: round ${(model.auditRounds ?? 0) + 1}/${budgetNote} running`);
|
|
279
359
|
} else if (model.auditRounds !== null) {
|
|
280
360
|
lines.push("");
|
|
361
|
+
const highIds = model.findings.filter((f) => f.severity === "high").map((f) => f.id);
|
|
281
362
|
const verdict = model.auditFailed.length > 0
|
|
282
|
-
? ` — failed: ${model.auditFailed.join(", ")}`
|
|
283
|
-
:
|
|
284
|
-
? ` —
|
|
285
|
-
:
|
|
286
|
-
|
|
363
|
+
? ` — failed: ${model.auditFailed.join(", ")}${highIds.length > 0 ? `; high: ${highIds.join(", ")}` : ""}`
|
|
364
|
+
: highIds.length > 0
|
|
365
|
+
? ` — high findings unresolved: ${highIds.join(", ")}`
|
|
366
|
+
: model.auditUndeterminable.length > 0
|
|
367
|
+
? ` — undeterminable: ${model.auditUndeterminable.join(", ")}`
|
|
368
|
+
: " — passed ✓";
|
|
369
|
+
lines.push(`Execution review: round ${model.auditRounds}/${budgetNote}${verdict}`);
|
|
287
370
|
}
|
|
288
371
|
const painted = theme ? lines.map((line) => theme.fg("muted", line)) : lines;
|
|
289
372
|
return clampLines(painted, width);
|