pi-goal-list-loop-audit 0.17.0 → 0.18.0

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 CHANGED
@@ -41,8 +41,9 @@ Four top-level commands, that's all:
41
41
  /goal tweak "<new objective>" # edit in place (Confirm dialog)
42
42
  /goal archive # archived goals, newest first
43
43
  /glla # open the settings UI (or /glla key=value)
44
+ /list fix the login bug, add dark mode, write docs # dump it — the agent decomposes into items, one Confirm
44
45
  /list add # draft a contract (or a whole batch via items[])
45
- /list add "<objective>" # queue one directly
46
+ /list add "<objective>" # queue one directly — no interview (the /goal start of lists)
46
47
  /list add plan.md # file detected → bulk import, one Confirm
47
48
  /list add <paste a checklist> # multi-line paste → same batch flow
48
49
 
@@ -201,6 +201,35 @@ export function parseListImport(content: string): string[] {
201
201
  return items;
202
202
  }
203
203
 
204
+ /**
205
+ * Route natural-language text handed to `/list` with no subcommand verb
206
+ * (v0.18.0). The user typed a dump — "fix x, do y, write docs" — not a
207
+ * command. Flexible by detection, never a usage error:
208
+ * file path → bulk import (sisyphus/Ralph plan file)
209
+ * multi-line paste → batch add (structure is already explicit)
210
+ * has "Done when:" → one direct item (explicit contract)
211
+ * anything else → conversational decomposition (drafting session;
212
+ * the agent shapes it into items[], one Confirm)
213
+ * The explicit verb `/list add` stays the direct escape hatch (symmetric
214
+ * with `/goal start`): it skips the draft branch.
215
+ */
216
+ export type ListTextRoute =
217
+ | { kind: "file"; path: string }
218
+ | { kind: "batch"; items: string[] }
219
+ | { kind: "direct"; text: string }
220
+ | { kind: "draft"; seed: string };
221
+
222
+ export function routeListText(cwd: string, raw: string): ListTextRoute {
223
+ const importFile = resolveImportFile(cwd, raw);
224
+ if (importFile) return { kind: "file", path: importFile };
225
+ if (raw.includes("\n")) {
226
+ const pasted = parseListImport(raw);
227
+ if (pasted.length > 1) return { kind: "batch", items: pasted };
228
+ }
229
+ if (!goalArgsNeedDrafting(raw)) return { kind: "direct", text: raw };
230
+ return { kind: "draft", seed: raw };
231
+ }
232
+
204
233
  /**
205
234
  * Detect whether a `/list add` argument is a readable file (v0.8.2). File
206
235
  * detection, not a separate verb: `/list add plan.md` bulk-imports when the
@@ -128,7 +128,7 @@ function goalLines(g: Goal, state: State, audit: AuditDisplayProgress | null | u
128
128
  const next = nextPending(g);
129
129
  if (next) lines.push(`├─ next: ${truncate(next, 56)}`);
130
130
  const queue = state.list?.length ?? 0;
131
- lines.push(`└─ ${queue > 0 ? `list ${queue} · ` : ""}/goal status · /gla`);
131
+ lines.push(`└─ ${queue > 0 ? `list ${queue} · ` : ""}/goal status · /glla`);
132
132
  return lines;
133
133
  }
134
134
 
@@ -35,6 +35,7 @@ import {
35
35
  parseListImport,
36
36
  resolveImportFile,
37
37
  routeGoalArgs,
38
+ routeListText,
38
39
  sumNewAssistantTokens,
39
40
  takeAt,
40
41
  goalArgsNeedDrafting,
@@ -434,7 +435,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
434
435
  const [file, label, tool] = prompts[target]!;
435
436
  ctx.ui.notify(
436
437
  seed
437
- ? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm). Skip the interview entirely: /goal start <objective>.`
438
+ ? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm). Skip the interview entirely: ${target === "list" ? "/list add <objective>" : "/goal start <objective>"}.`
438
439
  : `${label} started. The agent will grill until the contract is concrete, then ${tool} opens a Confirm dialog. No work begins before confirmation.`,
439
440
  "info",
440
441
  );
@@ -724,7 +725,7 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
724
725
  lines.push("Active: (none)");
725
726
  }
726
727
  if (queue.length === 0) {
727
- lines.push("List: empty. /list add <objective> | /list add <file>");
728
+ lines.push("List: empty. /list <describe your tasks> | /list add <one item, no interview> | /list add <file>");
728
729
  } else {
729
730
  lines.push(`Queue (${queue.length}):`);
730
731
  const PAGE = 15;
@@ -759,8 +760,9 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
759
760
  await startDrafting(ctx, "list");
760
761
  return;
761
762
  }
762
- // Flexible by detection, not by verb: an existing file path bulk-imports,
763
- // multi-line pasted text parses as a batch, anything else is one objective.
763
+ // Explicit verb = explicit intent (the /goal start of lists): file and
764
+ // multi-line structure are still honored, but vague text adds AS ONE
765
+ // ITEM — no interview. The conversational path is bare /list <text>.
764
766
  const importFile = resolveImportFile(ctx.cwd, raw);
765
767
  if (importFile) {
766
768
  await bulkAddFromFile(ctx, importFile);
@@ -773,17 +775,7 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
773
775
  return;
774
776
  }
775
777
  }
776
- const { objective, verificationContract } = extractVerificationContract(raw);
777
- const item = { id: newGoalId(), objective, verificationContract: verificationContract || undefined, addedAt: nowIso() };
778
- state = { ...state, list: [...listQueue(), item] };
779
- persistState(ctx);
780
- appendLedger(ctx.cwd, "list_added", { id: item.id, objective: item.objective });
781
- // Nothing active → activate immediately.
782
- if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
783
- activateNextListItem(ctx);
784
- } else {
785
- ctx.ui.notify(`Queued (${listQueue().length} waiting): ${objective.slice(0, 80)}`, "info");
786
- }
778
+ addSingleItem(ctx, raw);
787
779
  return;
788
780
  }
789
781
 
@@ -828,13 +820,50 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
828
820
  return;
829
821
  }
830
822
 
831
- ctx.ui.notify("Usage: /list [show] | /list add <objective or file> | /list next | /list remove <n> | /list clear", "info");
823
+ // v0.18.0: an unknown first word isn't an error it's a natural-language
824
+ // dump. "/list fix the login bug, add dark mode, write docs" should MAKE
825
+ // a list, not print usage. Detection chain: file → batch → contract →
826
+ // conversational decomposition (drafting). The explicit verb for adding
827
+ // one item verbatim is /list add.
828
+ let raw = args.trim();
829
+ if (raw.length >= 2 && ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'")))) {
830
+ raw = raw.slice(1, -1).trim();
831
+ }
832
+ const route = routeListText(ctx.cwd, raw);
833
+ if (route.kind === "file") {
834
+ await bulkAddFromFile(ctx, route.path);
835
+ return;
836
+ }
837
+ if (route.kind === "batch") {
838
+ await bulkAddItems(ctx, route.items, "pasted text");
839
+ return;
840
+ }
841
+ if (route.kind === "direct") {
842
+ addSingleItem(ctx, route.text);
843
+ return;
844
+ }
845
+ await startDrafting(ctx, "list", route.seed);
846
+ }
847
+
848
+ /** Append one objective to the list; activate immediately when idle. */
849
+ function addSingleItem(ctx: ExtensionContext, raw: string): void {
850
+ const { objective, verificationContract } = extractVerificationContract(raw);
851
+ const item = { id: newGoalId(), objective, verificationContract: verificationContract || undefined, addedAt: nowIso() };
852
+ state = { ...state, list: [...listQueue(), item] };
853
+ persistState(ctx);
854
+ appendLedger(ctx.cwd, "list_added", { id: item.id, objective: item.objective });
855
+ // Nothing active → activate immediately.
856
+ if (!state.goal || state.goal.status === "complete" || state.goal.status === "aborted") {
857
+ activateNextListItem(ctx);
858
+ } else {
859
+ ctx.ui.notify(`Queued (${listQueue().length} waiting): ${objective.slice(0, 80)}`, "info");
860
+ }
832
861
  }
833
862
 
834
863
  /**
835
864
  * Config-gated push notification: if settings.notifyCmd is set, shell out
836
865
  * with the message as $1. Fire-and-forget — a broken notify command never
837
- * blocks the loop. /gla notify='<cmd>' to configure.
866
+ * blocks the loop. /glla notify='<cmd>' to configure.
838
867
  */
839
868
  function notifyExternal(ctx: ExtensionContext, message: string): void {
840
869
  try {
@@ -1307,13 +1336,13 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1307
1336
  status: "active",
1308
1337
  auditHistory: history,
1309
1338
  pauseReason: `auditor infrastructure: ${result.error}`,
1310
- pauseSuggestedAction: "Fix the auditor model (/gla model=provider/id) and call complete_goal again — your work was NOT judged",
1339
+ pauseSuggestedAction: "Fix the auditor model (/glla model=provider/id) and call complete_goal again — your work was NOT judged",
1311
1340
  }, ctx);
1312
1341
  scheduleContinuation(ctx, true);
1313
1342
  return {
1314
1343
  content: [{
1315
1344
  type: "text",
1316
- text: `The auditor could not run (infrastructure, NOT a verdict): ${result.error}\nYour completion claim was not evaluated. Fix the auditor model with /gla model=provider/id and call complete_goal again — do not change your deliverable for this.`,
1345
+ text: `The auditor could not run (infrastructure, NOT a verdict): ${result.error}\nYour completion claim was not evaluated. Fix the auditor model with /glla model=provider/id and call complete_goal again — do not change your deliverable for this.`,
1317
1346
  }],
1318
1347
  details: {},
1319
1348
  };
@@ -1823,7 +1852,7 @@ interface Settings {
1823
1852
  const DEFAULT_SETTINGS: Settings = {
1824
1853
  // Unset = follow the pi session thinking level (user selects thinking in
1825
1854
  // pi, auditor follows), floor "high" — the auditor is the verification
1826
- // gate, depth is worth more there than speed. /gla thinking= overrides.
1855
+ // gate, depth is worth more there than speed. /glla thinking= overrides.
1827
1856
  auditorThinkingLevel: undefined,
1828
1857
  };
1829
1858
 
@@ -1856,7 +1885,7 @@ function loadSettings(cwd: string): Settings {
1856
1885
  ) as unknown as Settings;
1857
1886
  }
1858
1887
 
1859
- /** Where each effective setting comes from (for the /gla display). */
1888
+ /** Where each effective setting comes from (for the /glla display). */
1860
1889
  function settingsProvenance(cwd: string): Record<keyof Settings, { value: unknown; source: "project" | "global" | "default" }> {
1861
1890
  const proj = readSettingsFile(projectSettingsPath(cwd));
1862
1891
  const glob = readSettingsFile(globalSettingsPath());
@@ -1905,7 +1934,7 @@ function getSessionThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high
1905
1934
  * model in pi; the auditor uses it.** The plugin never picks a model itself.
1906
1935
  *
1907
1936
  * Chain:
1908
- * 1. Explicit `/gla model=provider/id` override (rare).
1937
+ * 1. Explicit `/glla model=provider/id` override (rare).
1909
1938
  * 2. The pi session model (ctx.model) — whatever the user selected.
1910
1939
  *
1911
1940
  * If the session model's provider is extension-registered, the auditor's
@@ -2169,7 +2198,7 @@ export default function (pi: ExtensionAPI): void {
2169
2198
  handler: settingsHandler,
2170
2199
  });
2171
2200
  pi.registerCommand("list", {
2172
- description: "Loop 2: the list of audited goals — order is the default, not the law. /list add <obj or file or paste> | /list show | /list next [n] | /list remove <n> | /list clear",
2201
+ description: "Loop 2: the list of audited goals — order is the default, not the law. /list <describe your tasks> (agent decomposes into items, one Confirm) | /list add <item or file> (direct, no interview) | /list show | /list next [n] | /list remove <n> | /list clear",
2173
2202
  handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
2174
2203
  });
2175
2204
  pi.registerCommand("loop", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
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",