pi-goal-list-loop-audit 0.27.0 → 0.27.1

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.
@@ -37,6 +37,36 @@ export function truncate(s: string, max: number): string {
37
37
  return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…";
38
38
  }
39
39
 
40
+ /**
41
+ * Word-wrap to `width`, capped at `maxLines` (v0.27.1). A pause is the one
42
+ * state where the FULL text matters — the reason often carries a decision
43
+ * the user must make (dedup choices, impossible-verdict narrowing), and a
44
+ * 60-char truncate hid it. Over-long words are hard-split; when the cap
45
+ * cuts content the last line ends with "…" (the pause-time notification
46
+ * and /goal status always carry the full text).
47
+ */
48
+ export function wrap(s: string, width: number, maxLines: number): string[] {
49
+ const norm = s.replace(/\s+/g, " ").trim();
50
+ const words = norm.split(" ").filter(Boolean);
51
+ const all: string[] = [];
52
+ let cur = "";
53
+ for (let w of words) {
54
+ const next = cur ? `${cur} ${w}` : w;
55
+ if (next.length <= width) { cur = next; continue; }
56
+ if (cur) all.push(cur);
57
+ while (w.length > width) { all.push(w.slice(0, width)); w = w.slice(width); }
58
+ cur = w;
59
+ }
60
+ if (cur) all.push(cur);
61
+ if (all.length === 0) all.push("");
62
+ if (all.length <= maxLines) return all;
63
+ const out = all.slice(0, maxLines);
64
+ // The last kept line already fits within width — truncate() would leave it
65
+ // unmarked, so force the ellipsis to signal "more in /goal status".
66
+ out[maxLines - 1] = out[maxLines - 1]!.slice(0, Math.max(0, width - 1)) + "…";
67
+ return out;
68
+ }
69
+
40
70
  /**
41
71
  * Width-aware truncation budget (v0.22.2). The hardcoded caps are FLOORS for
42
72
  * narrow terminals; when the terminal is wider, lines may use the available
@@ -191,8 +221,29 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
191
221
  return lines;
192
222
  }
193
223
  if (g.status === "paused" && g.pauseReason) {
194
- lines.push(`├─ ${paint(theme, pauseIsError(g) ? "error" : "warning", truncate(g.pauseReason, budgetFor(width, 3, 60)))}`);
195
- if (g.pauseSuggestedAction) lines.push(`└─ ${paint(theme, "dim", truncate(g.pauseSuggestedAction, budgetFor(width, 3, 60)))}`);
224
+ const isErr = pauseIsError(g);
225
+ const budget = budgetFor(width, 3, 60);
226
+ // v0.27.1: wrap reason + suggested action over up to 3 lines each
227
+ // (see wrap()); before, both were truncated at ~60 chars and the actual
228
+ // question in a decision-pause never reached the user.
229
+ wrap(g.pauseReason, budget, 3).forEach((w, i) => {
230
+ lines.push(`${i === 0 ? "├─" : "│ "} ${paint(theme, isErr ? "error" : "warning", w)}`);
231
+ });
232
+ // v0.27.1: what survives the pause — the first question at a pause is
233
+ // "did I lose the work?". Answer it on the card.
234
+ const spent: string[] = [];
235
+ const tokUsed = g.usage?.tokensUsed ?? 0;
236
+ if (tokUsed > 0) spent.push(`${fmtTokens(tokUsed)} tok spent`);
237
+ const audits = g.auditHistory?.length ?? 0;
238
+ if (audits > 0) spent.push(`${audits} audit${audits === 1 ? "" : "s"}`);
239
+ const savedLine = `saved${spent.length > 0 ? ` — ${spent.join(" · ")}` : ""} · resumes exactly here`;
240
+ if (g.pauseSuggestedAction) {
241
+ lines.push(`├─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
242
+ const wrapped = wrap(g.pauseSuggestedAction, budget, 3);
243
+ wrapped.forEach((w, i) => lines.push(`${i === wrapped.length - 1 ? "└─" : "│ "} ${paint(theme, "dim", w)}`));
244
+ } else {
245
+ lines.push(`└─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
246
+ }
196
247
  return lines;
197
248
  }
198
249
  const next = nextPending(g);
@@ -2254,8 +2254,12 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
2254
2254
  pauseReason: p.reason,
2255
2255
  pauseSuggestedAction: p.suggestedAction,
2256
2256
  }, ctx);
2257
- ctx.ui.notify(`Goal paused: ${p.reason}`, "info");
2258
- notifyExternal(ctx, `Goal paused: ${p.reason.slice(0, 120)}`);
2257
+ // v0.27.1: surface the FULL pause contract — reason AND suggested
2258
+ // action. Before, the action only appeared in /goal status and the
2259
+ // widget truncated both at ~60 chars, so decision-pauses ("choose a
2260
+ // or b") reached the user as an unreadable fragment.
2261
+ ctx.ui.notify(`Goal paused: ${p.reason}${p.suggestedAction ? `\n\n→ ${p.suggestedAction}` : ""}`, "info");
2262
+ notifyExternal(ctx, `Goal paused: ${(p.suggestedAction ? `${p.reason} → ${p.suggestedAction}` : p.reason).slice(0, 200)}`);
2259
2263
  return { content: [{ type: "text", text: "Goal paused. /goal resume to continue." }], details: {} };
2260
2264
  },
2261
2265
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
4
4
  "description": "Goal. Loop. Audit. Done. — a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor — only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",