pi-goal-list-loop-audit 0.38.70 → 0.38.71

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.71 — Decision cards lead with the action, plaque verbs deduped (2026-09-20)
4
+
5
+ - Decision pause cards render the saved/action row before the numbered options, so Pi core tail truncation cuts late options and history instead of the resume path (UI survey finding 1, audit/UI-SURVEY-2026-09-20.md).
6
+ - Durable/defer plaque bodies strip a leading Implement/Keep from the stored fix, fixing "Implement Implement Keep ..." doubling; ledger facts untouched (UI survey finding 3).
7
+
3
8
  ## 0.38.70 — Paused action cards lead with the action (2026-09-20)
4
9
 
5
10
  - Paused cards render lifecycle/next, banner, reason, and suggested action before tally, provenance, and judgment, so Pi core tail truncation cuts history instead of the instruction (field 20260920_152744).
package/docs/INDEX.md CHANGED
@@ -16,7 +16,7 @@ Policy contracts and recent changes live in the `audit/` directory of the
16
16
  failback; v0.35.9 hardened cross-version npm tarball checks; v0.35.10
17
17
  handles multi-entry npm dry-run reports; v0.35.11 accepts both npm report
18
18
  shapes; v0.35.12 supports npm 12's keyed pack reports; v0.35.13 fixes stale-API recovery loops.
19
- v0.35.14–v0.38.70 continue through the supervisor freeze (`/glla pause`),
19
+ v0.35.14–v0.38.71 continue through the supervisor freeze (`/glla pause`),
20
20
  load hold, auditor picker parity, Windows launch fix, zombie-watchdog
21
21
  subagent carve-out, due-wait backstop, the `/glla agents` visibility panel,
22
22
  durable state-root selection, blank-until-resume auditor context, frozen
@@ -3145,6 +3145,24 @@ export function recommendDurableDeferChoice(input: DurableDeferRecommendationInp
3145
3145
  return input.durableBlocked === true ? "deferred" : "inline";
3146
3146
  }
3147
3147
 
3148
+ /** v0.38.71 (field UI-SURVEY-2026-09-20): plaque bodies compose a verb
3149
+ * around the stored fix ("Implement X now" / "Keep X as the follow-up"),
3150
+ * so a fix that already starts with that verb doubled ("Implement
3151
+ * Implement Keep ..."). Strip one leading Implement/Keep; the composed
3152
+ * verb carries the meaning either way. */
3153
+ function stripPlaqueVerb(fix: string): string {
3154
+ // Loop twice: fixes arrive as bare phrases ("pin plaque ordering") or
3155
+ // imperative stacks ("Implement Keep ...") — one pass leaves
3156
+ // "Implement Keep ..." doubled against the composed verb.
3157
+ let out = fix;
3158
+ for (let i = 0; i < 2; i++) {
3159
+ const next = out.replace(/^(implement|keep)\s+/i, "");
3160
+ if (next === out) break;
3161
+ out = next;
3162
+ }
3163
+ return out;
3164
+ }
3165
+
3148
3166
  /**
3149
3167
  * Build the ordered recommendation plaques consumed by prompt and UI
3150
3168
  * projections. The returned order is driven by DURABLE_DEFER_PLAQUE_ORDER so
@@ -3152,7 +3170,7 @@ export function recommendDurableDeferChoice(input: DurableDeferRecommendationInp
3152
3170
  */
3153
3171
  export function buildDurableDeferRecommendation(input: DurableDeferRecommendationInput): DurableDeferRecommendation {
3154
3172
  const facts = normalizeDurableDeferRecommendationInput(input);
3155
- const durableFix = facts.durableFix || "the durable root-cause fix";
3173
+ const durableFix = stripPlaqueVerb(facts.durableFix || "the durable root-cause fix");
3156
3174
  const deferCount = facts.deferRecommendations.length;
3157
3175
  const choice = recommendDurableDeferChoice(facts);
3158
3176
  const plaques: Record<DurableDeferPlaqueKind, DurableDeferPlaque> = {
@@ -2136,16 +2136,9 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
2136
2136
  lines.push(`${i === 0 ? "├─" : "│ "} ${paint(theme, reasonPaint, w)}`);
2137
2137
  });
2138
2138
  }
2139
- // v0.28.22: decision options — one numbered line each (Claude Code /
2140
- // muselinn-Ask convention), the recommended one accented and flagged.
2141
- if (kind === "decision" && g.pauseOptions && g.pauseOptions.length > 0) {
2142
- g.pauseOptions.slice(0, 6).forEach((opt, i) => {
2143
- const rec = g.pauseRecommended === i + 1;
2144
- const text = `${i + 1}. ${truncate(opt, budget - 4)}${rec ? " ◂ recommended" : ""}`;
2145
- lines.push(`│ ${paint(theme, rec ? "accent" : "dim", text)}`);
2146
- });
2147
- if (g.pauseOptions.length > 6) lines.push(`│ ${paint(theme, "dim", `… and ${g.pauseOptions.length - 6} more`)}`);
2148
- }
2139
+ const decisionOptions = kind === "decision" && g.pauseOptions && g.pauseOptions.length > 0
2140
+ ? g.pauseOptions
2141
+ : undefined;
2149
2142
  // v0.28.22: wait countdown — moved into the auto-retrying line above
2150
2143
  // (v0.34.64). The old separate `resumes X — or /goal resume now` line
2151
2144
  // implied manual rescue was the path; autoResume + the recovery-cleared
@@ -2175,14 +2168,33 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
2175
2168
  const savedLine = hasTelemetry
2176
2169
  ? `saved — ${spent.join(" · ")} · resumes exactly here`
2177
2170
  : `awaiting first turn — resumes exactly here`;
2171
+ const optionsFollow = decisionOptions !== undefined;
2178
2172
  if (g.pauseSuggestedAction) {
2179
2173
  lines.push(`├─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
2180
2174
  const wrapped = wrap(sanitizeProviderDisplayText(g.pauseSuggestedAction), budget, 3);
2181
2175
  // v0.28.22: for ACTION NEEDED pauses the action is the point — pop it.
2182
2176
  const actionPaint = kind === "error" ? "warning" : "dim";
2183
- wrapped.forEach((w, i) => lines.push(`${i === wrapped.length - 1 ? "└─" : "│ "} ${paint(theme, actionPaint, w)}`));
2177
+ // v0.38.71 (field UI-SURVEY-2026-09-20 finding 1): the action precedes
2178
+ // the options — a 7-option decision card runs 16 rows and Pi core cuts
2179
+ // the tail, which used to hold the action. Options survive in full in
2180
+ // the ask_user_question dialog; the card keeps the resume path visible.
2181
+ wrapped.forEach((w, i) => lines.push(`${i === wrapped.length - 1 && !optionsFollow ? "└─" : "│ "} ${paint(theme, actionPaint, w)}`));
2184
2182
  } else {
2185
- lines.push(`└─ ${paint(theme, "dim", truncate(savedLine, budget))}`);
2183
+ lines.push(`${optionsFollow ? "├─" : "└─"} ${paint(theme, "dim", truncate(savedLine, budget))}`);
2184
+ }
2185
+ // v0.28.22: decision options — one numbered line each (Claude Code /
2186
+ // muselinn-Ask convention), the recommended one accented and flagged.
2187
+ if (decisionOptions) {
2188
+ decisionOptions.slice(0, 6).forEach((opt, i) => {
2189
+ const rec = g.pauseRecommended === i + 1;
2190
+ const text = `${i + 1}. ${truncate(opt, budget - 4)}${rec ? " ◂ recommended" : ""}`;
2191
+ lines.push(`│ ${paint(theme, rec ? "accent" : "dim", text)}`);
2192
+ });
2193
+ if (decisionOptions.length > 6) lines.push(`│ ${paint(theme, "dim", `… and ${decisionOptions.length - 6} more`)}`);
2194
+ // Close the options block; the history helper reopens it when deferred
2195
+ // rows follow.
2196
+ const lastOpt = lines[lines.length - 1];
2197
+ if (lastOpt !== undefined) lines[lines.length - 1] = lastOpt.replace(/^│ /, "└─ ");
2186
2198
  }
2187
2199
  return closePausedWithHistory(lines, deferredHistory, theme);
2188
2200
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.38.70",
3
+ "version": "0.38.71",
4
4
  "description": "Mission control for long running pi work: interview drafted goals, an audited task queue, and metric or audit loops that run for hours. Every completion is rechecked by a detached auditor with raw evidence, while Confirm drafts, decision pauses, and consent gates keep you in charge.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "dracon",