pi-goal-list-loop-audit 0.23.6 → 0.23.7

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.
@@ -300,7 +300,10 @@ export interface ListItem {
300
300
  export function goalArgsNeedDrafting(args: string): boolean {
301
301
  const t = args.trim();
302
302
  if (!t) return false; // no-args is already the drafting path
303
- return !/\bdone\s+when\s*:/i.test(t);
303
+ // v0.23.7: any "done when" phrase counts — requiring the colon to
304
+ // immediately follow made "Done when ALL of the following are true:"
305
+ // route to the interview even though the user wrote a contract.
306
+ return !/\bdone\s+when\b/i.test(t);
304
307
  }
305
308
 
306
309
  /**
@@ -627,3 +630,43 @@ export function normalizeDraftContract(raw: string): string {
627
630
  export function draftContractItemCount(normalized: string): number {
628
631
  return normalized.split("\n").filter((l) => /^\d+\.\s/.test(l)).length;
629
632
  }
633
+
634
+ /**
635
+ * Split raw objective text into { objective, verificationContract } at the
636
+ * first "Done when…:"-family marker (line-start preferred, inline fallback
637
+ * for one-liners). v0.23.7: the marker family accepts ANY text between the
638
+ * keyword and the colon ("Done when ALL of the following are true:") —
639
+ * the shield's contractItems already drops such introducer lines
640
+ * (v0.23.4), and goalArgsNeedDrafting recognizes the same phrase, so the
641
+ * three "done when" parsers can no longer drift apart. Lives in the pure
642
+ * module so tests exercise THIS function, not a copy (the pre-0.23.7 test
643
+ * re-implemented it and silently went stale).
644
+ */
645
+ export function extractVerificationContract(raw: string): { objective: string; verificationContract: string } {
646
+ // Line-based first: a marker at line start begins the contract block.
647
+ const lines = raw.split("\n");
648
+ let mode: "obj" | "verify" = "obj";
649
+ const objParts: string[] = [];
650
+ const verifyParts: string[] = [];
651
+ for (const line of lines) {
652
+ if (line.match(/^\s*(?:done when|verified when|verify|verification|done)\b[^:]*:/i)) {
653
+ mode = "verify";
654
+ }
655
+ if (mode === "obj") objParts.push(line);
656
+ else verifyParts.push(line);
657
+ }
658
+ let objective = objParts.join("\n").trim();
659
+ let verificationContract = verifyParts.join("\n").trim();
660
+
661
+ // Inline fallback: users write one-liners like
662
+ // "Create x.txt. Done when: grep -q ok x.txt"
663
+ // where the marker is mid-line. Split at the first inline marker.
664
+ if (!verificationContract) {
665
+ const m = raw.match(/^(.*?)(?:\.|;)??\s+(?:done when|verified when|verify|verification)\b[^:]*:\s*(.+)$/is);
666
+ if (m) {
667
+ objective = (m[1] ?? "").trim().replace(/[.;]\s*$/, "");
668
+ verificationContract = (m[2] ?? "").trim();
669
+ }
670
+ }
671
+ return { objective, verificationContract };
672
+ }
@@ -55,6 +55,7 @@ import {
55
55
  piGlaDir,
56
56
  normalizeDraftContract,
57
57
  draftContractItemCount,
58
+ extractVerificationContract,
58
59
  readState,
59
60
  renderGoalMarkdown,
60
61
  shouldAutoResumeOnSessionStart,
@@ -354,36 +355,6 @@ function createGoal(objective: string, ctx: ExtensionContext, policy: "goal" | "
354
355
  return goal;
355
356
  }
356
357
 
357
- function extractVerificationContract(raw: string): { objective: string; verificationContract: string } {
358
- // Line-based first: a marker at line start begins the contract block.
359
- const lines = raw.split("\n");
360
- let mode: "obj" | "verify" = "obj";
361
- const objParts: string[] = [];
362
- const verifyParts: string[] = [];
363
- for (const line of lines) {
364
- const lower = line.toLowerCase();
365
- if (lower.match(/^\s*(?:done when|verify|verified when|verification|done):/)) {
366
- mode = "verify";
367
- }
368
- if (mode === "obj") objParts.push(line);
369
- else verifyParts.push(line);
370
- }
371
- let objective = objParts.join("\n").trim();
372
- let verificationContract = verifyParts.join("\n").trim();
373
-
374
- // Inline fallback: users write one-liners like
375
- // "Create x.txt. Done when: grep -q ok x.txt"
376
- // where the marker is mid-line. Split at the first inline marker.
377
- if (!verificationContract) {
378
- const m = raw.match(/^(.*?)(?:\.|;)??\s+(done when|verified when|verify|verification)\s*:\s*(.+)$/is);
379
- if (m) {
380
- objective = (m[1] ?? "").trim().replace(/[.;]\s*$/, "");
381
- verificationContract = (m[3] ?? "").trim();
382
- }
383
- }
384
- return { objective, verificationContract };
385
- }
386
-
387
358
  function persistState(ctx: ExtensionContext): void {
388
359
  appendLedger(ctx.cwd, "state", { goal: state.goal, list: state.list ?? [], loop: state.loop ?? null });
389
360
  refreshUI(ctx); // every state transition flows through here → the TUI is always current
@@ -1607,13 +1578,15 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1607
1578
  const liveCtx = (execCtx as ExtensionContext | undefined) ?? ctx;
1608
1579
  // Multi-item list draft: one Confirm for the whole batch.
1609
1580
  if (p.items && p.items.length > 0) {
1610
- const preview = p.items.slice(0, 6).map((t, i) => ` ${i + 1}. ${t.slice(0, 60)}`).join("\n");
1581
+ // v0.23.7: show ALL items in full the user approves the whole
1582
+ // batch; hidden items would be approved blind.
1583
+ const preview = p.items.map((t, i) => ` ${i + 1}. ${t}`).join("\n");
1611
1584
  const batchActivates = !state.goal || state.goal.status === "complete" || state.goal.status === "aborted";
1612
1585
  let batchConfirmed = false;
1613
1586
  try {
1614
1587
  batchConfirmed = await liveCtx.ui.confirm(
1615
1588
  "Confirm queue batch",
1616
- `${p.items.length} items:\n${preview}${p.items.length > 6 ? `\n … and ${p.items.length - 6} more` : ""}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
1589
+ `${p.items.length} items:\n${preview}${batchActivates ? "\n\n(List is empty — confirming ACTIVATES item 1 immediately as the active goal.)" : ""}`,
1617
1590
  );
1618
1591
  } catch {
1619
1592
  batchConfirmed = false;
@@ -2181,7 +2154,7 @@ async function openSettingsUI(ctx: ExtensionContext): Promise<void> {
2181
2154
  else ctx.ui.notify(`Not a non-negative integer: ${v}`, "warning");
2182
2155
  }
2183
2156
  } else if (choice.startsWith("Wedge alert")) {
2184
- const v = await ctx.ui.input("Wedge alert threshold (minutes)", "non-negative integer; 0 = off, empty = default 45");
2157
+ const v = await ctx.ui.input("Wedge alert threshold (minutes)", "non-negative integer; 0 = off, empty = default 30");
2185
2158
  if (v !== undefined) {
2186
2159
  const n = Number.parseInt(v.trim(), 10);
2187
2160
  if (Number.isFinite(n) && n >= 0) saveSettings("global", ctx.cwd, { wedgeAlertMinutes: n });
@@ -2270,7 +2243,7 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
2270
2243
  } else {
2271
2244
  const n = Number.parseInt(value, 10);
2272
2245
  if (Number.isFinite(n) && n >= 0) {
2273
- patch.wedgeAlertMinutes = n; // 0 = off; unset = default 45
2246
+ patch.wedgeAlertMinutes = n; // 0 = off; unset = default 30
2274
2247
  changed = true;
2275
2248
  } else {
2276
2249
  ctx.ui.notify(`wedgealert must be a non-negative integer (minutes, 0 = off), got: ${value}`, "warning");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.23.6",
3
+ "version": "0.23.7",
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",
@@ -38,10 +38,12 @@ stops it.
38
38
  3. Clarify the **direction**: is lower better (min) or higher better (max)?
39
39
  (Skip this for a metricless loop — there is no direction without a metric.)
40
40
  4. Optional tuning: `window` (plateau stop after N non-improving iterations,
41
- default 5 — meaningless for metricless), `max` (iteration cap, default 50;
42
- `max=0` = truly unbounded). Suggest smaller values for expensive measures.
43
- For metricless loops, ALWAYS discuss bounds an unbounded metricless loop
44
- is a deliberate furnace.
41
+ default 5 — meaningless for metricless), `max` (iteration cap: default 50
42
+ for METRIC loops; default UNBOUNDED (`max=0`) for metricless loops,
43
+ v0.23.6 a bare infinite loop is the point of the metricless form).
44
+ Suggest smaller values for expensive measures. For metricless loops,
45
+ ALWAYS discuss bounds — an unbounded metricless loop is a deliberate
46
+ furnace.
45
47
  5. When concrete, call `propose_loop_draft` with `target`, `measureCmd` (or
46
48
  omit/`"none"` for metricless), `direction` (measured only), and optional
47
49
  `window`/`max`/`time`/`tokens`.