pi-goal-list-loop-audit 0.18.0 → 0.19.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 +10 -10
- package/extensions/goal-loop-core.ts +15 -0
- package/extensions/loops/goal.ts +32 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,11 +41,11 @@ 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
|
|
45
|
-
/list
|
|
46
|
-
/list
|
|
47
|
-
/list
|
|
48
|
-
/list add
|
|
44
|
+
/list fix the login bug, add dark mode, write docs # dump it — the agent shapes it into items, one Confirm
|
|
45
|
+
/list plan.md # file detected → bulk import, one Confirm (sisyphus/Ralph style)
|
|
46
|
+
/list <paste a checklist> # multi-line paste → same batch flow
|
|
47
|
+
/list "fix the flaky test. Done when: npm test green" # explicit contract → queues directly, no interview
|
|
48
|
+
/list # show the list (add/import are optional no-op aliases — detection routes everything)
|
|
49
49
|
|
|
50
50
|
(Or just say it: "queue these 10 things…" — the agent manages the list too.)
|
|
51
51
|
|
|
@@ -82,9 +82,9 @@ ONE confirmed batch, not 50 dialogs.
|
|
|
82
82
|
Note: every queue item is audited individually, so at hundreds of items the
|
|
83
83
|
audit cost per item is the thing to think about.
|
|
84
84
|
|
|
85
|
-
**Drafting is the default for long-running things.** `/goal
|
|
86
|
-
`/loop` with no arguments
|
|
87
|
-
dialog. For `/loop` specifically, the orchestrator **test-runs the proposed
|
|
85
|
+
**Drafting is the default for long-running things.** `/goal` and
|
|
86
|
+
`/loop` with no arguments — and any vague `/list` dump — all start a
|
|
87
|
+
grilling turn that ends in a Confirm dialog. For `/loop` specifically, the orchestrator **test-runs the proposed
|
|
88
88
|
measure command once** and shows the real number in the dialog — you validate
|
|
89
89
|
the metric before a single iteration burns tokens.
|
|
90
90
|
|
|
@@ -132,8 +132,8 @@ redirect you to `/goal`.
|
|
|
132
132
|
| Loop | Command | Status |
|
|
133
133
|
|---|---|---|
|
|
134
134
|
| 1. Single ordered goal | `/goal "<objective>"` | **shipped v0.1.0** |
|
|
135
|
-
| 2. Queue of goals | `/list
|
|
136
|
-
| 3.
|
|
135
|
+
| 2. Queue of goals | `/list [show\|next\|remove\|clear]` | **shipped v0.2.0** |
|
|
136
|
+
| 3. Metric-driven process loop | `/loop start\|status\|stop` | **shipped v0.3.0** |
|
|
137
137
|
|
|
138
138
|
Each loop is a different policy class on the same status machine.
|
|
139
139
|
|
|
@@ -201,6 +201,21 @@ export function parseListImport(content: string): string[] {
|
|
|
201
201
|
return items;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* During a LIST drafting session the agent must not queue items one by one
|
|
206
|
+
* with list_add/list_activate — that bypasses the user's Confirm gate
|
|
207
|
+
* (observed in the wild: the agent decomposed a dump and ACTIVATED the first
|
|
208
|
+
* item with zero confirmation). The batch path is propose_goal_draft's
|
|
209
|
+
* items[]: one Confirm for the whole list. User commands (/list add) are
|
|
210
|
+
* unaffected — only the agent tools are gated.
|
|
211
|
+
*/
|
|
212
|
+
export function listMutationBlocked(draftingTarget: string | null): boolean {
|
|
213
|
+
return draftingTarget === "list";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const LIST_DRAFTING_BLOCK_MESSAGE =
|
|
217
|
+
"LIST DRAFTING IN PROGRESS — do not queue items one by one. Decompose the request into an items[] array and call propose_goal_draft ONCE: the user confirms the whole batch in a single dialog. list_add / list_activate work again after the drafting session ends.";
|
|
218
|
+
|
|
204
219
|
/**
|
|
205
220
|
* Route natural-language text handed to `/list` with no subcommand verb
|
|
206
221
|
* (v0.18.0). The user typed a dump — "fix x, do y, write docs" — not a
|
package/extensions/loops/goal.ts
CHANGED
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
DEFAULT_TOKEN_LIMIT,
|
|
34
34
|
mergeSettings,
|
|
35
35
|
parseListImport,
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
routeGoalArgs,
|
|
38
38
|
routeListText,
|
|
39
|
+
listMutationBlocked,
|
|
40
|
+
LIST_DRAFTING_BLOCK_MESSAGE,
|
|
39
41
|
sumNewAssistantTokens,
|
|
40
42
|
takeAt,
|
|
41
43
|
goalArgsNeedDrafting,
|
|
@@ -435,7 +437,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
|
|
|
435
437
|
const [file, label, tool] = prompts[target]!;
|
|
436
438
|
ctx.ui.notify(
|
|
437
439
|
seed
|
|
438
|
-
? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm).
|
|
440
|
+
? `${label}: the objective has no "Done when:" clause — the agent will grill you about it first (nothing activates until you confirm). ${target === "list" ? "Add directly instead: include a \"Done when:\" clause." : "Skip the interview entirely: /goal start <objective>."}`
|
|
439
441
|
: `${label} started. The agent will grill until the contract is concrete, then ${tool} opens a Confirm dialog. No work begins before confirmation.`,
|
|
440
442
|
"info",
|
|
441
443
|
);
|
|
@@ -725,7 +727,7 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
725
727
|
lines.push("Active: (none)");
|
|
726
728
|
}
|
|
727
729
|
if (queue.length === 0) {
|
|
728
|
-
lines.push("List: empty. /list <describe your tasks>
|
|
730
|
+
lines.push("List: empty. /list <describe your tasks, or a plan file> — the agent shapes dumps into items, files import directly.");
|
|
729
731
|
} else {
|
|
730
732
|
lines.push(`Queue (${queue.length}):`);
|
|
731
733
|
const PAGE = 15;
|
|
@@ -739,43 +741,33 @@ async function cmdList(args: string, ctx: ExtensionContext): Promise<void> {
|
|
|
739
741
|
}
|
|
740
742
|
|
|
741
743
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
744
|
+
// v0.19.0: `add` and `import` are pure no-op aliases — the verb changes
|
|
745
|
+
// nothing, detection routes everything. `/list plan.md` and
|
|
746
|
+
// `/list add plan.md` both import; `/list fix x, do y` and
|
|
747
|
+
// `/list add fix x, do y` both draft. Rationale: a list item activates
|
|
748
|
+
// RAW when it reaches the head, so the drafting interview is the only
|
|
749
|
+
// quality gate an item ever gets — a verb whose only job was skipping
|
|
750
|
+
// that gate was a leak, not an escape hatch. The direct path is an
|
|
751
|
+
// explicit "Done when:" clause (user already did the contract work).
|
|
752
|
+
if (sub === "add" || sub === "import") {
|
|
753
|
+
if (!rest) {
|
|
754
|
+
await startDrafting(ctx, "list");
|
|
747
755
|
return;
|
|
748
756
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (sub === "add") {
|
|
754
|
-
let raw = rest;
|
|
755
|
-
if (raw.length >= 2 && ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'")))) {
|
|
756
|
-
raw = raw.slice(1, -1).trim();
|
|
757
|
-
}
|
|
758
|
-
if (!raw) {
|
|
759
|
-
// /list add with no args → draft a confirmed contract INTO THE QUEUE.
|
|
760
|
-
await startDrafting(ctx, "list");
|
|
757
|
+
const aliased = routeListText(ctx.cwd, rest.replace(/^["']|["']$/g, ""));
|
|
758
|
+
if (aliased.kind === "file") {
|
|
759
|
+
await bulkAddFromFile(ctx, aliased.path);
|
|
761
760
|
return;
|
|
762
761
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// ITEM — no interview. The conversational path is bare /list <text>.
|
|
766
|
-
const importFile = resolveImportFile(ctx.cwd, raw);
|
|
767
|
-
if (importFile) {
|
|
768
|
-
await bulkAddFromFile(ctx, importFile);
|
|
762
|
+
if (aliased.kind === "batch") {
|
|
763
|
+
await bulkAddItems(ctx, aliased.items, "pasted text");
|
|
769
764
|
return;
|
|
770
765
|
}
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
await bulkAddItems(ctx, pasted, "pasted text");
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
766
|
+
if (aliased.kind === "direct") {
|
|
767
|
+
addSingleItem(ctx, aliased.text);
|
|
768
|
+
return;
|
|
777
769
|
}
|
|
778
|
-
|
|
770
|
+
await startDrafting(ctx, "list", aliased.seed);
|
|
779
771
|
return;
|
|
780
772
|
}
|
|
781
773
|
|
|
@@ -1715,6 +1707,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1715
1707
|
}),
|
|
1716
1708
|
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
1717
1709
|
const p = params as { items: string[] };
|
|
1710
|
+
if (listMutationBlocked(draftingTarget)) {
|
|
1711
|
+
return { content: [{ type: "text", text: LIST_DRAFTING_BLOCK_MESSAGE }], details: {} };
|
|
1712
|
+
}
|
|
1718
1713
|
if (!Array.isArray(p.items) || p.items.length === 0) {
|
|
1719
1714
|
return { content: [{ type: "text", text: "No items given." }], details: {} };
|
|
1720
1715
|
}
|
|
@@ -1746,6 +1741,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
|
|
|
1746
1741
|
}),
|
|
1747
1742
|
async execute(_id, params, _signal, _onUpdate, execCtx) {
|
|
1748
1743
|
const p = params as { n: number };
|
|
1744
|
+
if (listMutationBlocked(draftingTarget)) {
|
|
1745
|
+
return { content: [{ type: "text", text: LIST_DRAFTING_BLOCK_MESSAGE }], details: {} };
|
|
1746
|
+
}
|
|
1749
1747
|
const n = Math.floor(p.n);
|
|
1750
1748
|
if (!Number.isInteger(n) || n < 1) {
|
|
1751
1749
|
return { content: [{ type: "text", text: "n must be a positive integer (1-based position)." }], details: {} };
|
|
@@ -2198,7 +2196,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
2198
2196
|
handler: settingsHandler,
|
|
2199
2197
|
});
|
|
2200
2198
|
pi.registerCommand("list", {
|
|
2201
|
-
description: "Loop 2: the list of audited goals — order is the default, not the law. /list <describe
|
|
2199
|
+
description: "Loop 2: the list of audited goals — order is the default, not the law. /list <describe tasks or name a plan file> (dumps get shaped into items, files import, 'Done when:' adds directly) | /list show | /list next [n] | /list remove <n> | /list clear",
|
|
2202
2200
|
handler: (args: string, ctx: ExtensionContext) => { rememberCtx(ctx); return cmdList(args, ctx); },
|
|
2203
2201
|
});
|
|
2204
2202
|
pi.registerCommand("loop", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-list-loop-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|