From 6878b48a2a4850cea9f2a0cb6e63f164ec617832 Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:39:46 +0800 Subject: [PATCH] Name goals files -vN.md, as main's plan files did Session IDs start with a timestamp, so the last 6 characters are the distinguishing part. A new /goals new in the same session makes the next version and never overwrites (flag wx). Co-Authored-By: Claude <288921227+claudypoo@users.noreply.github.com> --- README.md | 2 +- src/index.ts | 12 ++++++++---- test/goals-loop.test.ts | 11 ++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index be137a9..e191871 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ pi-goals bundles the scheduler. Install pi-subagents yourself (0.70.1 tested); t ## Use ``` -/goals new discuss and draft .pi/goals/-.md; nothing runs yet +/goals new discuss and draft .pi/goals/-vN.md; nothing runs yet /goals review show the draft with Ready / Refine / Edit / Cancel /goals pause remove the scheduled loop; the file stays /goals resume start a new loop for the paused goals diff --git a/src/index.ts b/src/index.ts index 3777f37..fa339e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ // PI/OpenAI: one agent, one goals file, human Ready, and a stock scheduled loop. import { randomUUID } from "node:crypto"; -import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join, relative, resolve } from "node:path"; import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { Type } from "typebox"; @@ -124,11 +124,15 @@ export default function piGoals(pi: ExtensionAPI): void { } if (arg !== "new" && !arg.startsWith("new ")) throw new Error("Use /goals new [idea], review, pause, resume, clear, or judge."); if (state.file) throw new Error("Clear the current goals before starting new ones; the file is preserved."); - const file = join(ctx.cwd, ".pi/goals", `${ctx.sessionManager.getSessionId()}-${randomUUID().slice(0, 8)}.md`); - mkdirSync(dirname(file), { recursive: true }); const idea = arg.slice(3).trim(); + const dir = join(ctx.cwd, ".pi/goals"); + mkdirSync(dir, { recursive: true }); + // Session IDs start with a timestamp; the last 6 characters differ between sessions. + const suffix = ctx.sessionManager.getSessionId().slice(-6); + const taken = readdirSync(dir).map(name => Number(new RegExp(`^${suffix}-v(\\d+)\\.md$`).exec(name)?.[1] ?? 0)); + const file = join(dir, `${suffix}-v${Math.max(0, ...taken) + 1}.md`); // Slash commands skip the input hook; keep the user's opening words verbatim too. - writeFileSync(file, idea ? appendInterview("", `/goals new ${idea}`).trimStart() : ""); + writeFileSync(file, idea ? appendInterview("", `/goals new ${idea}`).trimStart() : "", { flag: "wx" }); state = { ...state, owner: ctx.sessionManager.getSessionId(), phase: "planning", file }; generation++; reviewRequested = false; resyncDue = false; persist(); refresh(ctx); diff --git a/test/goals-loop.test.ts b/test/goals-loop.test.ts index d43ad9f..1586b32 100644 --- a/test/goals-loop.test.ts +++ b/test/goals-loop.test.ts @@ -16,7 +16,7 @@ describe("planning and Ready", () => { const h = setup({ choices: ["Ready"] }); await h.commands.get("goals").handler("new plot the data", h.ctx); const file = h.branch.findLast(e => e.customType === "pi-goals-single-agent").data.file; - expect(file).toMatch(/\.pi\/goals\/sess-[\w]+\.md$/); + expect(file).toMatch(/\.pi\/goals\/sess-v1\.md$/); expect(readFileSync(file, "utf8")).toContain("> /goals new plot the data"); writeFileSync(file, GOALS); await h.hook("input", { source: "interactive", text: "yes, reuse judge_demos.py" }); @@ -32,6 +32,15 @@ describe("planning and Ready", () => { expect(await h.hook("tool_call", { toolName: "write", input: { path: "train.py" } })).toBeUndefined(); }); + it("a second /goals new in the same session makes -v2 and keeps v1", async () => { + const h = setup(); + await h.commands.get("goals").handler("new first", h.ctx); + await h.commands.get("goals").handler("clear", h.ctx); + await h.commands.get("goals").handler("new second", h.ctx); + expect(h.branch.findLast(e => e.customType === "pi-goals-single-agent").data.file).toMatch(/sess-v2\.md$/); + expect(readFileSync(`${h.cwd}/.pi/goals/sess-v1.md`, "utf8")).toContain("new first"); + }); + it("Cancel starts nothing and keeps the file", async () => { const h = setup({ choices: ["Cancel"] }); const file = await ready(h);