pi-goal-list-loop-audit 0.28.27 → 0.28.28

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.
@@ -185,6 +185,10 @@ export interface Goal {
185
185
  * the retry resolves. Only consumed while paused with an "auditor quota:"
186
186
  * reason, so a stale value is unreachable by construction. */
187
187
  pendingCompletion?: { completionSummary?: string; verificationSummary?: string; at: string };
188
+ /** v0.28.28: provenance — who created this goal ("user", "list-cascade",
189
+ * "draft-confirmed", "draft-autoaccepted"). Ledgered on goal_created so
190
+ * "where did this come from" is answerable after the fact. */
191
+ createdVia?: string;
188
192
  /** v0.25.0 (contract item 22): auditor objections extracted as TODOs when
189
193
  * aggressiveMode keeps the goal active past the disapproval cap. Rendered
190
194
  * into every continuation prompt until the next audit clears them. */
@@ -895,18 +895,19 @@ function notifyPersistenceState(ctx: ExtensionContext): void {
895
895
  }
896
896
  }
897
897
 
898
- function setGoal(goal: Goal, ctx: ExtensionContext): void {
898
+ function setGoal(goal: Goal, ctx: ExtensionContext, via = "user"): void {
899
899
  // v0.28.14: never silently orphan a live goal — a paused/active goal
900
900
  // being replaced is archived honestly first (the old behavior left it in
901
901
  // goals/ but untracked: "older goals lying around leading to confusion").
902
902
  if (state.goal && state.goal.id !== goal.id && (state.goal.status === "active" || state.goal.status === "paused")) {
903
903
  archiveCurrentGoal(ctx, "aborted", `replaced by goal ${goal.id}`);
904
904
  }
905
+ goal.createdVia = via; // v0.28.28: provenance — answerable from the ledger + /glla log
905
906
  state = { ...state, goal }; // preserve list AND loop (v0.28.14: the bare reconstruction used to nuke a held/active loop whenever a goal was set)
906
907
  const file = writeGoalMd(ctx.cwd, goal);
907
908
  state.goal!.activePath = path.relative(ctx.cwd, file) || file;
908
909
  persistState(ctx);
909
- appendLedger(ctx.cwd, "goal_created", { goalId: goal.id, objective: goal.objective, policy: goal.policy });
910
+ appendLedger(ctx.cwd, "goal_created", { goalId: goal.id, objective: goal.objective, policy: goal.policy, via });
910
911
  }
911
912
 
912
913
  function updateGoal(patch: Partial<Goal>, ctx: ExtensionContext): void {
@@ -1142,7 +1143,7 @@ function fireReviewer(
1142
1143
  manual: opts.manual,
1143
1144
  ledgerEntries,
1144
1145
  sources,
1145
- enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer"),
1146
+ enqueueListItems: (objectives) => enqueueItems(ctx, objectives, "reviewer", { autoActivate: loadSettings(ctx.cwd).autoResume === true }),
1146
1147
  proposeGoal: (objective, reason) => {
1147
1148
  try {
1148
1149
  extensionApi?.sendUserMessage(
@@ -1212,7 +1213,7 @@ function activateNextListItem(ctx: ExtensionContext, n = 1): boolean {
1212
1213
  state = { ...state, list: rest };
1213
1214
  const goal = createGoal(next.objective, ctx, "list");
1214
1215
  if (next.verificationContract) goal.verificationContract = next.verificationContract;
1215
- setGoal(goal, ctx);
1216
+ setGoal(goal, ctx, "list-cascade");
1216
1217
  iterationCounter = 0;
1217
1218
  consecutiveErrorIterations = 0;
1218
1219
  consecutiveAbortIterations = 0;
@@ -1631,7 +1632,7 @@ async function cmdTweak(args: string, ctx: ExtensionContext): Promise<void> {
1631
1632
  * contract extraction) → appended to the queue → persisted → first item
1632
1633
  * activated when nothing is running. Returns the count enqueued.
1633
1634
  */
1634
- function enqueueItems(ctx: ExtensionContext, texts: string[], source: string): number {
1635
+ function enqueueItems(ctx: ExtensionContext, texts: string[], source: string, opts?: { autoActivate?: boolean }): number {
1635
1636
  const items = texts.map((text) => {
1636
1637
  const extracted = extractVerificationContract(text);
1637
1638
  return { id: newGoalId(), objective: extracted.objective, verificationContract: extracted.verificationContract || undefined, addedAt: nowIso() };
@@ -1640,7 +1641,16 @@ function enqueueItems(ctx: ExtensionContext, texts: string[], source: string): n
1640
1641
  persistState(ctx);
1641
1642
  appendLedger(ctx.cwd, "list_imported", { source, count: items.length });
1642
1643
  if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
1643
- activateNextListItem(ctx);
1644
+ // v0.28.28: unsolicited sources (the reviewer) do NOT auto-start the
1645
+ // head unless autoResume is on — "I cancelled a goal and the next one
1646
+ // started itself" was the field complaint. User-driven imports keep
1647
+ // the immediate-start behavior (opts default true).
1648
+ if (opts?.autoActivate === false) {
1649
+ ctx.ui.notify(`Queued ${items.length} item(s) from ${source} — /list next when ready (auto-start is opt-in: /glla autoresume=on).`, "info");
1650
+ appendLedger(ctx.cwd, "list_autoactivation_held", { source, count: items.length });
1651
+ } else {
1652
+ activateNextListItem(ctx);
1653
+ }
1644
1654
  }
1645
1655
  return items.length;
1646
1656
  }
@@ -3167,13 +3177,34 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
3167
3177
  persistState(liveCtx);
3168
3178
  appendLedger(liveCtx.cwd, "list_added", { id: item.id, objective: item.objective, drafted: true });
3169
3179
  if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
3180
+ // v0.28.28: an AUTO-ACCEPTED draft does not auto-start unless
3181
+ // autoResume is on — accepting a draft is not consent to start.
3182
+ if (autoAccept && loadSettings(liveCtx.cwd).autoResume !== true) {
3183
+ liveCtx.ui.notify(`Auto-accepted and QUEUED (autoResume off — not auto-started): ${extracted.objective.slice(0, 80)} — /list next when ready.`, "info");
3184
+ appendLedger(liveCtx.cwd, "list_autoactivation_held", { source: "draft-autoaccepted", count: 1 });
3185
+ return { content: [{ type: "text", text: "Draft accepted and added to the list, but NOT started (the user's autoResume setting is off — auto-accepted drafts queue, they don't auto-start). Do NOT begin work. Tell the user: /list next starts it." }], details: {} };
3186
+ }
3170
3187
  activateNextListItem(liveCtx);
3171
3188
  return { content: [{ type: "text", text: "Confirmed and activated (list was empty). Begin work now." }], details: {} };
3172
3189
  }
3173
3190
  return { content: [{ type: "text", text: `Confirmed and added to the list (${listQueue().length} waiting). It activates when the current goal completes.` }], details: {} };
3174
3191
  }
3175
3192
  const goal = createGoal(full, liveCtx);
3176
- setGoal(goal, liveCtx);
3193
+ setGoal(goal, liveCtx, autoAccept ? "draft-autoaccepted" : "draft-confirmed");
3194
+ // v0.28.28: auto-accepted goal drafts are created HELD when autoResume
3195
+ // is off — auto-accept delegates the Confirm click, not the decision
3196
+ // to start. Explicit user-confirmed drafts still start immediately.
3197
+ if (autoAccept && loadSettings(liveCtx.cwd).autoResume !== true) {
3198
+ updateGoal({
3199
+ status: "paused",
3200
+ pauseKind: "blocked",
3201
+ pauseReason: "auto-accepted draft — held for the user's go-ahead (autoResume off)",
3202
+ pauseSuggestedAction: "/goal resume to start · /goal cancel to drop · /glla autoresume=on starts auto-accepted drafts automatically",
3203
+ }, liveCtx);
3204
+ appendLedger(liveCtx.cwd, "draft_held", { goalId: goal.id, reason: "autoaccept-autoresume-off" });
3205
+ liveCtx.ui.notify(`Draft auto-accepted and HELD (autoResume off): ${goal.objective.slice(0, 80)} — /goal resume to start, /goal cancel to drop.`, "info");
3206
+ return { content: [{ type: "text", text: "Goal accepted but HELD (the user's autoResume setting is off — auto-accepted drafts do not auto-start). Do NOT begin work. Tell the user: /goal resume starts it, /goal cancel drops it." }], details: {} };
3207
+ }
3177
3208
  iterationCounter = 0;
3178
3209
  consecutiveErrorIterations = 0;
3179
3210
  consecutiveAbortIterations = 0;
@@ -4085,6 +4116,42 @@ function cmdStats(args: string, ctx: ExtensionContext): void {
4085
4116
  * log (.pi-glla/audits.jsonl). Default: last 10 verdicts, one line each.
4086
4117
  * "full" prints the latest report in full.
4087
4118
  */
4119
+ /**
4120
+ * v0.28.28: /glla log [N] — human-readable tail of the event ledger (the
4121
+ * forensic trail: who created/resumed/paused goals, from where). Skips the
4122
+ * high-frequency noise entries (state snapshots, re-arm internals) unless
4123
+ * "all" is passed. N defaults to 15.
4124
+ */
4125
+ const LOG_NOISE = new Set(["state", "send_rearm_start", "heartbeat_suppressed_tick"]);
4126
+ function cmdLog(args: string, ctx: ExtensionContext): void {
4127
+ const all = /\ball\b/.test(args);
4128
+ const nMatch = args.match(/\b(\d+)\b/);
4129
+ const n = Math.min(Math.max(parseInt(nMatch?.[1] ?? "15", 10) || 15, 1), 100);
4130
+ let entries: Array<{ type: string; at?: string; value?: any }> = [];
4131
+ try {
4132
+ entries = parseLedgerEntries(fs.readFileSync(ledgerPath(ctx.cwd), "utf-8"));
4133
+ } catch {
4134
+ ctx.ui.notify("No ledger yet — .pi-glla/active.jsonl doesn't exist.", "info");
4135
+ return;
4136
+ }
4137
+ const visible = all ? entries : entries.filter((e) => !LOG_NOISE.has(e.type));
4138
+ const tail = visible.slice(-n);
4139
+ if (tail.length === 0) {
4140
+ ctx.ui.notify("Ledger is empty (no non-noise events yet).", "info");
4141
+ return;
4142
+ }
4143
+ const lines = tail.map((e) => {
4144
+ const t = (e.at ?? "").slice(11, 19);
4145
+ const v = e.value ?? {};
4146
+ const detail = Object.entries(v)
4147
+ .filter(([k]) => k !== "goalId" && k !== "report")
4148
+ .map(([k, val]) => `${k}=${typeof val === "string" ? val.slice(0, 60) : JSON.stringify(val)?.slice(0, 60)}`)
4149
+ .join(" ");
4150
+ return `${t} ${e.type}${detail ? ` ${detail}` : ""}`;
4151
+ });
4152
+ ctx.ui.notify(`Ledger tail (last ${tail.length}${all ? "" : " non-noise"} events — /glla log <N> for more, /glla log all to include noise):\n${lines.join("\n")}`, "info");
4153
+ }
4154
+
4088
4155
  function cmdAudits(args: string, ctx: ExtensionContext): void {
4089
4156
  const full = /\bfull\b/.test(args);
4090
4157
  const all = /\b(?:all|global|log)\b/.test(args);
@@ -4136,6 +4203,12 @@ async function cmdSettings(args: string, ctx: ExtensionContext): Promise<void> {
4136
4203
  cmdAudits(trimmed.slice("audits".length).trim(), ctx);
4137
4204
  return;
4138
4205
  }
4206
+ // v0.28.28: /glla log [N] — the raw event trail, human-readable. "Log it
4207
+ // so we can look back and see where we are doing things wrong."
4208
+ if (/^log\b/.test(trimmed)) {
4209
+ cmdLog(trimmed.slice("log".length).trim(), ctx);
4210
+ return;
4211
+ }
4139
4212
  if (/^reviewer\b/.test(trimmed)) {
4140
4213
  await cmdReviewerSettings(ctx);
4141
4214
  return;
@@ -4526,6 +4599,7 @@ export default function (pi: ExtensionAPI): void {
4526
4599
  ["autoresume=", "default: hold when a session is loaded, auto-resume on reload/fork; on: always auto-resume; off: never"],
4527
4600
  ["decisionpopup=", "on|off: decision pauses pop the select() picker (default on; the widget card always lists the options, /goal decide reopens the picker)"],
4528
4601
  ["auditcap=", "N: pause goal after N consecutive auditor disapprovals (default 5, 0 = unlimited)"],
4602
+ ["log", "event-trail tail: /glla log [N] — who created/resumed/paused what, from where (v0.28.28)"],
4529
4603
  ["auditfeedbackchars=", "cap on executor-visible disapproval report chars (0 = full report, the default)"],
4530
4604
  ["aggressivemode=", "on: keep-going defaults — autoResume, cap 10, stuck 10, wedge off, quota auto-retry, cap→TODOs"],
4531
4605
  ["quotaretryminutes=", "N: minutes before auto-retrying a quota-exhausted auditor (default 60)"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.28.27",
3
+ "version": "0.28.28",
4
4
  "description": "Goal. Loop. Audit. Done. \u2014 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 \u2014 only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",
@@ -54,6 +54,7 @@
54
54
  "interruptedReason": { "type": "string" },
55
55
  "auditInfraStreak": { "type": "number" },
56
56
  "pendingCompletion": { "type": "object" },
57
+ "createdVia": { "type": "string" },
57
58
  "activePath": { "type": "string" },
58
59
  "archivedPath": { "type": "string" },
59
60
  "usage": {