mirror of
https://github.com/wassname/pi-goals.git
synced 2026-09-11 12:43:57 +08:00
Keep goal widgets compact by removing subtask lines
Preserve tasks in plan files; update README model illustration and prompt link. 176 tests pass, typecheck and lint clean. Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -70,7 +70,7 @@ Pi/OpenAI implementation scope:
|
||||
- The supervisor may edit the plan and approve completion after inspecting actual results. It delegates implementation and must not weaken the agreed goal to accept worker output. Keep normal tools; express the division in editable prompts.
|
||||
- State the requested worker model in plan preferences; the supervisor selects it and checks the resolved model. Reuse existing usage displays before adding token-reporting code.
|
||||
- Keep all model-facing prompts in `src/prompts.ts`, in narrative order: planning/interview, Ready, supervision and plan upkeep, check-ins/messages, completion, pause/resume and solo. Make them easy for the user to review and edit.
|
||||
- Preserve useful features from `main`: plan widgets, progress/subtask visibility, plan-upkeep reminders, high-value planning questions and post-compaction plan context. Check which role needs each feature rather than copying the old supervisor runtime.
|
||||
- Preserve useful features from `main`: goal widgets, plan-upkeep reminders, high-value planning questions and post-compaction plan context. User update: omit subtasks from widgets; long task text wastes terminal space. Keep tasks in the plan. Check which role needs each feature rather than copying the old supervisor runtime.
|
||||
- Scheduled loops must be visible, editable and removable using the scheduler's own UI. Explain whether each reminder is a scheduled job or an event hook; do not advertise a second timer that does not exist.
|
||||
- Keep an explicit recoverable solo mode: confirm any worker has stopped before allowing the main thread to take over implementation and plan edits. Solo completion is self-verification, not an independent supervisor review.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Make a short list of goals in one Markdown plan file. The main chat keeps the hi
|
||||
|
||||
<img width="2513" height="1259" alt="2026-09-10_15-30-pi-goals" src="https://github.com/user-attachments/assets/35feaa15-f022-4491-bcc2-fc31cb878a9f" />
|
||||
|
||||
Abridged text from the isolated test, with approval and completion shown together. Paths are shortened; bracketed labels are annotations. Both sessions used DeepSeek in this test.
|
||||
Abridged text from the isolated test, with approval and completion shown together. Paths are shortened; bracketed labels are annotations. Model names and token counts below illustrate the intended supervisor/worker split, not measurements from this capture.
|
||||
|
||||
```text
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
@@ -29,12 +29,10 @@ Abridged text from the isolated test, with approval and completion shown togethe
|
||||
| verified-bytes-worker [goals-worker] | |
|
||||
| | |
|
||||
| > | > |
|
||||
| deepseek-v4-flash-0731 · Fireworks | deepseek-v4-flash-0731 · Fireworks |
|
||||
| astra · 50k tokens | terra · 200k tokens |
|
||||
+-----------------------------------------------------------+-----------------------------------------------------------+
|
||||
```
|
||||
|
||||
[Full captures and verification](slop/reviews/20260910_package-supervision-herdr.md).
|
||||
|
||||
The plan file looks like this:
|
||||
|
||||
```md
|
||||
@@ -126,7 +124,7 @@ worker later exits. The scheduler deletes disabled jobs on reload. [Test results
|
||||
|
||||
## Prompts
|
||||
|
||||
Planning, worker and supervisor prompts live in [`src/prompts.ts`](src/prompts.ts), in conversation order.
|
||||
You can read all the prompts in conversation order in [`src/prompts.ts`](src/prompts.ts).
|
||||
|
||||
## Develop
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ interface State {
|
||||
const initial = (): State => ({ mode: "chat", signoffs: {} });
|
||||
const digest = (text: string) => createHash("sha256").update(text).digest("hex");
|
||||
const key = (text: string) => text.trim().toLowerCase();
|
||||
const SUBTASK_LINE = /^\s+(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*(.*)$/;
|
||||
function goals(text: string) {
|
||||
return foldPlan(text).split("\n").flatMap((line, index) => {
|
||||
const match = GOAL_LINE.exec(line);
|
||||
@@ -62,17 +61,6 @@ function goals(text: string) {
|
||||
return [{ subject: match[2].trim(), status: (box === "x" ? "done" : box === "/" ? "active" : box === "-" ? "cancelled" : "open") as GoalStatus, index }];
|
||||
});
|
||||
}
|
||||
/** Open subtasks under the goal at goalLine, up to the next goal line; the widget shows these. */
|
||||
function openSubtasks(plan: string, goalLine: number): string[] {
|
||||
const lines = foldPlan(plan).split("\n");
|
||||
const out: string[] = [];
|
||||
for (let i = goalLine + 1; i < lines.length; i++) {
|
||||
if (GOAL_LINE.test(lines[i])) break;
|
||||
const m = SUBTASK_LINE.exec(lines[i]);
|
||||
if (m && (m[1] === " " || m[1] === "/")) out.push(m[2].trim());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: {} });
|
||||
|
||||
export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
@@ -126,16 +114,8 @@ export default function mainSupervisor(pi: ExtensionAPI) {
|
||||
}
|
||||
const accepted = items.filter((g) => g.status === "done" && state.signoffs[key(g.subject)]).length;
|
||||
ctx.ui.setStatus("goals", `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`);
|
||||
// Progress and subtask visibility: the widget is the task list, so the active goal shows its
|
||||
// next open subtasks without reading archived checkboxes below the fold.
|
||||
const plan = snapshot.text;
|
||||
const focus = items.find((g) => g.status === "active") ?? items.find((g) => g.status === "open" && openSubtasks(plan, g.index).length > 0);
|
||||
const mark = (status: GoalStatus, signed: boolean) => status === "done" ? (signed ? "✓" : "?") : status === "active" ? "▸" : status === "cancelled" ? "✗" : "○";
|
||||
const lines: string[] = items.map((g) => `${mark(g.status, Boolean(state.signoffs[key(g.subject)]))} ${g.subject}`);
|
||||
if (focus) {
|
||||
const muted = (s: string) => ctx.ui.theme.fg("muted", ` ◦ ${s}`);
|
||||
lines.push(...openSubtasks(plan, focus.index).slice(0, 3).map(muted));
|
||||
}
|
||||
if (items.some((g) => g.status === "done" && !state.signoffs[key(g.subject)])) lines.push("? = completion claim; parent review still required");
|
||||
ctx.ui.setWidget("goals", lines);
|
||||
}
|
||||
|
||||
+12
-2
@@ -455,7 +455,7 @@ it("cancelled goals do not prevent final cleanup, and solo writes self-verificat
|
||||
expect(text).toContain("Preserved context");
|
||||
});
|
||||
|
||||
it("lineage-only child explicitly attaches its supplied plan, restores subtasks/context, and cannot complete", async () => {
|
||||
it("lineage-only child attaches its plan with goal-only widget, retains task context, and cannot complete", async () => {
|
||||
const f = fixture(true);
|
||||
const supplied = join(f.ctx.cwd, "supplied.md");
|
||||
const text = "- [/] goal: exact file\n - [ ] verify bytes\n## Log\n - [ ] archived task\n";
|
||||
@@ -466,7 +466,7 @@ it("lineage-only child explicitly attaches its supplied plan, restores subtasks/
|
||||
await attach.execute("a", { path: "supplied.md" }, undefined, undefined, f.ctx);
|
||||
expect(f.entries.at(-1).data.plan).toBeUndefined(); // no cwd heuristics
|
||||
await attach.execute("a", { path: supplied }, undefined, undefined, f.ctx);
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).toContain("◦ verify bytes");
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1]).toEqual(["▸ exact file"]);
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).not.toContain("archived task");
|
||||
expect(readFileSync(supplied, "utf8")).toBe(text);
|
||||
f.hooks.get("session_start")({}, f.ctx);
|
||||
@@ -475,6 +475,16 @@ it("lineage-only child explicitly attaches its supplied plan, restores subtasks/
|
||||
expect(completion.content[0].text).toContain("only to the active parent");
|
||||
});
|
||||
|
||||
it.each(["solo", "supervising"])("%s widget omits long tasks without altering the plan", async mode => {
|
||||
const f = fixture(); await f.draft();
|
||||
const text = "- [/] goal: first output\n - [ ] a long task that should never take widget space\n- [ ] goal: second output\n## Log\n";
|
||||
writeFileSync(f.path, text);
|
||||
if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); }
|
||||
else await f.command("ready");
|
||||
expect(f.ctx.ui.setWidget.mock.lastCall?.[1]).toEqual(["▸ first output", "○ second output"]);
|
||||
expect(readFileSync(f.path, "utf8")).toBe(text);
|
||||
});
|
||||
|
||||
it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, resets on working-set edits, and never starts a turn", async mode => {
|
||||
const f = fixture(); await f.draft();
|
||||
if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); }
|
||||
|
||||
Reference in New Issue
Block a user