@difflab/pi 0.7.0-rc.202609271854.6af8600 → 0.7.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.
@@ -1290,7 +1290,7 @@ function sanitize(value) {
1290
1290
  }
1291
1291
 
1292
1292
  // src/tools/plan.ts
1293
- import { join as join14 } from "node:path";
1293
+ import { join as join15 } from "node:path";
1294
1294
  import { pathToFileURL } from "node:url";
1295
1295
  import { defineTool as defineTool5 } from "@earendil-works/pi-coding-agent";
1296
1296
  import { z as z7 } from "zod";
@@ -7234,8 +7234,8 @@ function isPlanReviewDump(value) {
7234
7234
 
7235
7235
  // src/plan/store.ts
7236
7236
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
7237
- import { mkdir as mkdir6, readdir as readdir2, readFile as readFile7, realpath as realpath3, rename as rename2, rm as rm2 } from "node:fs/promises";
7238
- import { basename as basename5, dirname as dirname7, join as join13, resolve as resolve3, sep } from "node:path";
7237
+ import { mkdir as mkdir6, readdir as readdir2, readFile as readFile8, realpath as realpath3, rename as rename2, rm as rm2 } from "node:fs/promises";
7238
+ import { basename as basename5, dirname as dirname7, join as join14, resolve as resolve3, sep } from "node:path";
7239
7239
 
7240
7240
  // src/extensions/fsx.ts
7241
7241
  import { mkdir as mkdir5, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
@@ -7271,6 +7271,42 @@ function sleep(ms) {
7271
7271
  return new Promise((resolve) => setTimeout(resolve, ms));
7272
7272
  }
7273
7273
 
7274
+ // src/templates.ts
7275
+ import { readFile as readFile7 } from "node:fs/promises";
7276
+ import { homedir as homedir4 } from "node:os";
7277
+ import { join as join13, normalize } from "node:path";
7278
+ async function loadTemplate(name, options = {}) {
7279
+ const relative = templateRelativePath(name);
7280
+ const userPath = join13(options.homeDir ?? homedir4(), ".difflab", "diffpi", "templates", relative);
7281
+ const bundledPath = join13(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
7282
+ const user = await readOptionalFile(userPath);
7283
+ if (user !== undefined)
7284
+ return { name, path: userPath, source: "user", content: user };
7285
+ const bundled = await readOptionalFile(bundledPath);
7286
+ if (bundled !== undefined)
7287
+ return { name, path: bundledPath, source: "bundled", content: bundled };
7288
+ throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
7289
+ }
7290
+ function renderTemplate(content, variables) {
7291
+ return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
7292
+ }
7293
+ function templateRelativePath(name) {
7294
+ const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
7295
+ if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
7296
+ throw new Error(`Invalid template name: ${name}`);
7297
+ }
7298
+ return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
7299
+ }
7300
+ async function readOptionalFile(path) {
7301
+ try {
7302
+ return await readFile7(path, "utf8");
7303
+ } catch (error) {
7304
+ if (error.code === "ENOENT")
7305
+ return;
7306
+ throw error;
7307
+ }
7308
+ }
7309
+
7274
7310
  // src/plan/store.ts
7275
7311
  function planRecordName(shortSlug, date = new Date) {
7276
7312
  const slug = normalizeSlug(shortSlug);
@@ -7286,7 +7322,7 @@ async function resolvePlan(cwd, query, filters = {}, options = {}) {
7286
7322
  continue;
7287
7323
  if (normalizedQuery && entry.name !== normalizedQuery && stripDate(entry.name) !== normalizedQuery)
7288
7324
  continue;
7289
- const dir = join13(root, entry.name);
7325
+ const dir = join14(root, entry.name);
7290
7326
  await assertContained(root, dir);
7291
7327
  try {
7292
7328
  const record = await readRecord(dir);
@@ -7321,30 +7357,26 @@ function createPlanStore(options = {}) {
7321
7357
  async init(input) {
7322
7358
  const root = await plansDir(input.cwd, options.homeDir);
7323
7359
  const id = planRecordName(input.shortSlug, now());
7324
- const dir = join13(root, id);
7360
+ const dir = join14(root, id);
7325
7361
  await createPlanDirectory(root, dir, id);
7326
7362
  try {
7327
7363
  const timestamp = now().toISOString();
7364
+ const template = await loadTemplate("plan/PLAN", {
7365
+ homeDir: options.homeDir,
7366
+ bundledDir: options.bundledTemplatesDir
7367
+ });
7328
7368
  const title = input.title?.trim() || titleFromSlug(input.shortSlug);
7329
- const document = {
7330
- schemaVersion: 1,
7369
+ const source = renderTemplate(template.content, {
7331
7370
  id,
7332
- revision: 0,
7333
- title,
7334
7371
  branch: input.branch,
7335
- ...input.issueId?.trim() ? { issueId: input.issueId.trim() } : {},
7336
- ...input.issueUrl?.trim() ? { issueUrl: input.issueUrl.trim() } : {},
7372
+ title,
7337
7373
  intent: input.intent?.trim() || "<!-- Describe the intended outcome. -->",
7338
- requirements: [],
7339
- design: { bigIdeas: "", keyApiUpdates: "", consequences: "" },
7340
- phases: [],
7341
- references: [],
7342
- status: "draft",
7343
- createdAt: timestamp,
7344
- updatedAt: timestamp
7345
- };
7346
- const source = renderPlanDocument(document);
7347
- parsePlanDocument(source, join13(dir, "PLAN.md"));
7374
+ issue_id: input.issueId?.trim() || "",
7375
+ issue_url: input.issueUrl?.trim() || "",
7376
+ created_at: timestamp,
7377
+ updated_at: timestamp
7378
+ });
7379
+ const document = parsePlanDocument(source, join14(dir, "PLAN.md"));
7348
7380
  const authoringRequest = input.request ?? { kind: "user", text: input.intent?.trim() || `Initialize phase-less plan ${title}.` };
7349
7381
  await writeRevisionSnapshot(dir, document, source, authoringRequest, []);
7350
7382
  await writeLatestView(dir, document, source, []);
@@ -7359,14 +7391,14 @@ function createPlanStore(options = {}) {
7359
7391
  if (request.mode === "create") {
7360
7392
  const root = await plansDir(cwd, options.homeDir);
7361
7393
  const id = planRecordName(request.shortSlug, now());
7362
- const dir = join13(root, id);
7394
+ const dir = join14(root, id);
7363
7395
  await createPlanDirectory(root, dir, id);
7364
7396
  try {
7365
7397
  const timestamp = now().toISOString();
7366
7398
  const document = normalizeRevision(undefined, request, id, 0, timestamp);
7367
7399
  assertCompleteRevision(document, request.briefs);
7368
7400
  const source = renderPlanDocument(document);
7369
- parsePlanDocument(source, join13(dir, "PLAN.md"));
7401
+ parsePlanDocument(source, join14(dir, "PLAN.md"));
7370
7402
  await writeRevisionSnapshot(dir, document, source, request.request, request.briefs);
7371
7403
  await writeLatestView(dir, document, source, request.briefs);
7372
7404
  return await readRecord(dir);
@@ -7376,7 +7408,7 @@ function createPlanStore(options = {}) {
7376
7408
  }
7377
7409
  }
7378
7410
  const initial = await read(cwd, request.plan);
7379
- return withDirectoryLock(join13(initial.dir, ".lock"), async () => {
7411
+ return withDirectoryLock(join14(initial.dir, ".lock"), async () => {
7380
7412
  const current = await readRecord(initial.dir);
7381
7413
  if (current.document.revision !== request.expectedPlanRevision)
7382
7414
  throw new Error(`Expected plan revision ${request.expectedPlanRevision}, found ${current.document.revision}.`);
@@ -7389,7 +7421,7 @@ function createPlanStore(options = {}) {
7389
7421
  try {
7390
7422
  await writeLatestView(current.dir, document, source, request.briefs);
7391
7423
  } catch (error) {
7392
- await rm2(join13(current.dir, "revisions", String(revision)), { recursive: true, force: true });
7424
+ await rm2(join14(current.dir, "revisions", String(revision)), { recursive: true, force: true });
7393
7425
  throw error;
7394
7426
  }
7395
7427
  return await readRecord(current.dir);
@@ -7397,7 +7429,7 @@ function createPlanStore(options = {}) {
7397
7429
  },
7398
7430
  async mutate(cwd, query, operation, update) {
7399
7431
  const initial = await read(cwd, query);
7400
- return withDirectoryLock(join13(initial.dir, ".lock"), async () => {
7432
+ return withDirectoryLock(join14(initial.dir, ".lock"), async () => {
7401
7433
  const current = await readRecord(initial.dir);
7402
7434
  const next = await update(structuredClone(current.document));
7403
7435
  if (next.id !== current.id)
@@ -7420,16 +7452,16 @@ function createPlanStore(options = {}) {
7420
7452
  };
7421
7453
  }
7422
7454
  async function readRecord(dir) {
7423
- const planPath = join13(dir, "PLAN.md");
7424
- const logPath = join13(dir, "logs.txt");
7425
- const source = await readFile7(planPath, "utf8");
7455
+ const planPath = join14(dir, "PLAN.md");
7456
+ const logPath = join14(dir, "logs.txt");
7457
+ const source = await readFile8(planPath, "utf8");
7426
7458
  const document = parsePlanDocument(source, planPath);
7427
7459
  return {
7428
7460
  id: basename5(dir),
7429
7461
  dir,
7430
7462
  planPath,
7431
7463
  logPath,
7432
- implementationDir: join13(dir, "implementation"),
7464
+ implementationDir: join14(dir, "implementation"),
7433
7465
  document,
7434
7466
  source
7435
7467
  };
@@ -7449,17 +7481,17 @@ async function writeRevisionSnapshot(planDir, document, source, request, briefs)
7449
7481
  throw new Error("An exact non-empty authoring request is required.");
7450
7482
  if (request.kind === "annotation" && !request.response?.trim())
7451
7483
  throw new Error("Annotation requests require a non-empty response.");
7452
- const revisionsDir = join13(planDir, "revisions");
7484
+ const revisionsDir = join14(planDir, "revisions");
7453
7485
  await mkdir6(revisionsDir, { recursive: true });
7454
- const target = join13(revisionsDir, String(document.revision));
7486
+ const target = join14(revisionsDir, String(document.revision));
7455
7487
  try {
7456
- await readFile7(join13(target, "metadata.json"), "utf8");
7488
+ await readFile8(join14(target, "metadata.json"), "utf8");
7457
7489
  throw new Error(`Revision ${document.revision} already exists and is immutable.`);
7458
7490
  } catch (error) {
7459
7491
  if (error.code !== "ENOENT")
7460
7492
  throw error;
7461
7493
  }
7462
- const staging = join13(revisionsDir, `.tmp-${document.revision}-${randomUUID2()}`);
7494
+ const staging = join14(revisionsDir, `.tmp-${document.revision}-${randomUUID2()}`);
7463
7495
  await mkdir6(staging);
7464
7496
  try {
7465
7497
  const requestSource = request.kind === "annotation" ? `## Original annotation
@@ -7467,8 +7499,8 @@ ${request.text}
7467
7499
 
7468
7500
  ## LLM response
7469
7501
  ${request.response}` : request.text;
7470
- await atomicWrite(join13(staging, "request.md"), requestSource);
7471
- await atomicWrite(join13(staging, "metadata.json"), `${JSON.stringify({
7502
+ await atomicWrite(join14(staging, "request.md"), requestSource);
7503
+ await atomicWrite(join14(staging, "metadata.json"), `${JSON.stringify({
7472
7504
  schemaVersion: 1,
7473
7505
  id: document.id,
7474
7506
  revision: document.revision,
@@ -7478,7 +7510,7 @@ ${request.response}` : request.text;
7478
7510
  createdAt: document.updatedAt
7479
7511
  }, null, 2)}
7480
7512
  `);
7481
- await atomicWrite(join13(staging, "PLAN.md"), source);
7513
+ await atomicWrite(join14(staging, "PLAN.md"), source);
7482
7514
  await writeImplementationDirectory(staging, document, briefs);
7483
7515
  await rename2(staging, target);
7484
7516
  } catch (error) {
@@ -7487,10 +7519,10 @@ ${request.response}` : request.text;
7487
7519
  }
7488
7520
  }
7489
7521
  async function writeLatestView(planDir, document, source, briefs) {
7490
- const current = join13(planDir, "implementation");
7491
- const stagingRoot = join13(planDir, `.latest-${randomUUID2()}`);
7492
- const stagedImplementation = join13(stagingRoot, "implementation");
7493
- const backup = join13(planDir, `.implementation-backup-${randomUUID2()}`);
7522
+ const current = join14(planDir, "implementation");
7523
+ const stagingRoot = join14(planDir, `.latest-${randomUUID2()}`);
7524
+ const stagedImplementation = join14(stagingRoot, "implementation");
7525
+ const backup = join14(planDir, `.implementation-backup-${randomUUID2()}`);
7494
7526
  await mkdir6(stagingRoot);
7495
7527
  await writeImplementationDirectory(stagingRoot, document, briefs);
7496
7528
  let backedUp = false;
@@ -7503,7 +7535,7 @@ async function writeLatestView(planDir, document, source, briefs) {
7503
7535
  throw error;
7504
7536
  }
7505
7537
  await rename2(stagedImplementation, current);
7506
- await atomicWrite(join13(planDir, "PLAN.md"), source);
7538
+ await atomicWrite(join14(planDir, "PLAN.md"), source);
7507
7539
  if (backedUp)
7508
7540
  await rm2(backup, { recursive: true, force: true });
7509
7541
  } catch (error) {
@@ -7516,13 +7548,13 @@ async function writeLatestView(planDir, document, source, briefs) {
7516
7548
  }
7517
7549
  }
7518
7550
  async function writeImplementationDirectory(baseDir, document, briefs) {
7519
- const implementationDir = join13(baseDir, "implementation");
7551
+ const implementationDir = join14(baseDir, "implementation");
7520
7552
  await mkdir6(implementationDir, { recursive: true });
7521
7553
  for (const [index, phase] of document.phases.entries()) {
7522
7554
  const brief = briefs[index];
7523
7555
  if (!brief)
7524
7556
  throw new Error(`Missing implementation brief for phase ${phase.id}.`);
7525
- await atomicWrite(join13(implementationDir, `phase-${index + 1}.md`), renderImplementationBrief(document.revision, index + 1, phase, brief));
7557
+ await atomicWrite(join14(implementationDir, `phase-${index + 1}.md`), renderImplementationBrief(document.revision, index + 1, phase, brief));
7526
7558
  }
7527
7559
  }
7528
7560
  function normalizeRevision(current, request, id, revision, updatedAt) {
@@ -8008,7 +8040,7 @@ function createPlanTools(_pi, _modes, store = createPlanStore()) {
8008
8040
  const resolution = await store.context(workingDirectory, params.plan);
8009
8041
  if (!resolution.record)
8010
8042
  throw new Error(resolution.ambiguous ? `Plan is ambiguous: ${resolution.candidates.map((item) => item.id).join(", ")}.` : "No matching plan.");
8011
- const cliPath = join14(resolveBundledAgentsDir(), "..", "dist", "cli.js");
8043
+ const cliPath = join15(resolveBundledAgentsDir(), "..", "dist", "cli.js");
8012
8044
  const command = [
8013
8045
  process.execPath,
8014
8046
  cliPath,
@@ -8808,10 +8840,10 @@ function hasGitlabDraftNotes(input) {
8808
8840
  }
8809
8841
 
8810
8842
  // src/review/local-review-backend.ts
8811
- import { readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
8843
+ import { readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
8812
8844
 
8813
8845
  // src/extensions/tuicrx.ts
8814
- import { readFile as readFile8, realpath as realpath4 } from "node:fs/promises";
8846
+ import { readFile as readFile9, realpath as realpath4 } from "node:fs/promises";
8815
8847
  import { resolve as resolve4 } from "node:path";
8816
8848
  async function tuicrAvailable() {
8817
8849
  return Boolean(await findExecutable("tuicr"));
@@ -8908,7 +8940,7 @@ async function findMatchingSession(sessions, cwd, branch) {
8908
8940
  return;
8909
8941
  }
8910
8942
  async function readSession(path) {
8911
- const content = await readFile8(path, "utf8");
8943
+ const content = await readFile9(path, "utf8");
8912
8944
  try {
8913
8945
  return JSON.parse(content);
8914
8946
  } catch {
@@ -9029,7 +9061,7 @@ function LocalReviewBackend(options) {
9029
9061
  },
9030
9062
  async listThreads() {
9031
9063
  try {
9032
- return parseThreadArtifact(await readFile9(options.artifactPath, "utf8"));
9064
+ return parseThreadArtifact(await readFile10(options.artifactPath, "utf8"));
9033
9065
  } catch (error) {
9034
9066
  if (error.code === "ENOENT")
9035
9067
  return [];
@@ -9037,7 +9069,7 @@ function LocalReviewBackend(options) {
9037
9069
  }
9038
9070
  },
9039
9071
  async reply(input) {
9040
- const content = await readFile9(options.artifactPath, "utf8");
9072
+ const content = await readFile10(options.artifactPath, "utf8");
9041
9073
  await writeFile5(options.artifactPath, upsertThreadReply(content, input.threadId, input.body, input.question, input.resolve), "utf8");
9042
9074
  },
9043
9075
  async publish() {
@@ -9059,8 +9091,8 @@ function createLocalReviewBackend(options) {
9059
9091
  }
9060
9092
  // src/review/review-state.ts
9061
9093
  import { createHash as createHash4 } from "node:crypto";
9062
- import { readFile as readFile10, rename as rename3, writeFile as writeFile6 } from "node:fs/promises";
9063
- import { join as join15 } from "node:path";
9094
+ import { readFile as readFile11, rename as rename3, writeFile as writeFile6 } from "node:fs/promises";
9095
+ import { join as join16 } from "node:path";
9064
9096
  import { z as z10 } from "zod";
9065
9097
  var reviewPublicationStateSchema = z10.object({
9066
9098
  target: z10.string().optional(),
@@ -9084,9 +9116,9 @@ function unpublishedReviewComments(comments, knownFingerprints) {
9084
9116
  }
9085
9117
  async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
9086
9118
  const target = `${vcs.provider}:${vcs.owner}/${vcs.repo}#${number}`;
9087
- const path = join15(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
9119
+ const path = join16(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
9088
9120
  try {
9089
- const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile10(path, "utf8")));
9121
+ const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile11(path, "utf8")));
9090
9122
  return { path, state: { ...parsed, target } };
9091
9123
  } catch (error) {
9092
9124
  if (error.code === "ENOENT") {
@@ -9107,42 +9139,6 @@ async function saveReviewPublicationState(path, state) {
9107
9139
  function digest(value) {
9108
9140
  return createHash4("sha256").update(value).digest("hex");
9109
9141
  }
9110
- // src/templates.ts
9111
- import { readFile as readFile11 } from "node:fs/promises";
9112
- import { homedir as homedir4 } from "node:os";
9113
- import { join as join16, normalize } from "node:path";
9114
- async function loadTemplate(name, options = {}) {
9115
- const relative = templateRelativePath(name);
9116
- const userPath = join16(options.homeDir ?? homedir4(), ".difflab", "diffpi", "templates", relative);
9117
- const bundledPath = join16(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
9118
- const user = await readOptionalFile(userPath);
9119
- if (user !== undefined)
9120
- return { name, path: userPath, source: "user", content: user };
9121
- const bundled = await readOptionalFile(bundledPath);
9122
- if (bundled !== undefined)
9123
- return { name, path: bundledPath, source: "bundled", content: bundled };
9124
- throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
9125
- }
9126
- function renderTemplate(content, variables) {
9127
- return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
9128
- }
9129
- function templateRelativePath(name) {
9130
- const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
9131
- if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
9132
- throw new Error(`Invalid template name: ${name}`);
9133
- }
9134
- return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
9135
- }
9136
- async function readOptionalFile(path) {
9137
- try {
9138
- return await readFile11(path, "utf8");
9139
- } catch (error) {
9140
- if (error.code === "ENOENT")
9141
- return;
9142
- throw error;
9143
- }
9144
- }
9145
-
9146
9142
  // src/tools/review.ts
9147
9143
  var contextSchema = z11.object({
9148
9144
  cwd: z11.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@difflab/pi",
3
- "version": "0.7.0-rc.202609271854.6af8600",
3
+ "version": "0.7.0",
4
4
  "description": "Tools and skills for the pi coding agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: plan
3
3
  description: Create, revise, annotate, finalize, and execute durable Diffpi plans. Use for /skill:plan, /plan, or natural-language planning requests.
4
- allowed-tools: read grep find write edit symbol_search module_report read_symbol ask_user_question Agent get_subagent_result steer_subagent plan_context plan_annotate plan_start_execution plan_update_status plan_log_progress plan_run_gates plan_record_ci watch_ci
4
+ allowed-tools: read grep find symbol_search module_report read_symbol ask_user_question Agent get_subagent_result steer_subagent plan_context plan_init plan_apply_revision plan_validate plan_review plan_annotate plan_start_execution plan_update_status plan_log_progress plan_run_gates plan_record_ci watch_ci
5
5
  ---
6
6
 
7
7
  # Plan
8
8
 
9
- This skill owns the workflow. `/plan` only forwards its raw arguments to `/skill:plan`. Do not emit another command to paste or rely on a command handler to select an agent, parse flags, or inject a workflow. For NEW and UPDATE, use ordinary file tools to edit the authoritative live PLAN.md and implementation brief files. Never use file tools to alter immutable legacy snapshots, revisions, reviews, or logs; remaining references to those managed artifacts are transitional guidance pending phase 4.
9
+ This skill owns the workflow. `/plan` only forwards its raw arguments to `/skill:plan`. Do not emit another command to paste or rely on a command handler to select an agent, parse flags, or inject a workflow. Call plan tools directly; never edit managed PLAN.md, implementation briefs, revisions, reviews, or logs with file tools.
10
10
 
11
- Identify the first explicit verb (`init`, `new`, `update`, `annotate`, `finalize`, `go`, `help`) or infer one from the user's request. For NEW and UPDATE, select one unique repository-root plan by path and its plan files, and read it before acting; ask only for a material decision if the plan remains ambiguous. Read exactly the corresponding [workflow reference](references/workflows/) before acting. Preserve the user's request and intent, including visible completed task information; this is authoring input, not a managed revision transaction.
11
+ Identify the first explicit verb (`init`, `new`, `update`, `annotate`, `finalize`, `go`, `help`) or infer one from the user's request. If the verb or required plan is ambiguous, call `plan_context` and ask only for a material decision using `ask_user_question`. Read exactly the corresponding [workflow reference](references/workflows/) before acting. Preserve the exact incoming user request (and applied review feedback) as revision input; do not paraphrase it.
12
12
 
13
- `new` creates a complete plan without launching an editor; `init` creates a phase-less draft and opens it. For NEW and UPDATE content changes, write the authoritative live PLAN.md and numbered briefs directly through successive normal read/write/edit calls, then read back each file. Invoke exactly one independently verified frontier/high-thinking `diffpi-plan-reviewer` per iteration; repair actionable findings and rerun it within bounded attempts. If effective model/thinking selection cannot be verified, fail preflight with the exact observed selection and required correction; do not silently promise runtime verification. Phase/task status and execution gates remain managed-tool concerns. The remaining init/annotate/finalize/go references retain old managed-tool calls only as transitional guidance until phase 4 replaces them; they must not be presented as Planner capabilities. `go` calls `plan_start_execution` before any code change; if the plan is draft or on the wrong branch, stop.
13
+ `new` creates a complete plan without launching an editor; `init` creates a phase-less draft and opens it. For content changes, build a complete candidate and apply it **once per user request** with `plan_apply_revision`. Phase/task status and gates use their dedicated tools and do not constitute a new content revision. Strict `plan_validate` checks document and brief structure, not whether source code implements the plan. `go` calls `plan_start_execution` before any code change; if the plan is draft or on the wrong branch, stop.
14
14
 
15
15
  `--bg` requests one named background agent through `Agent` (Planner for authoring, Orchestrator for execution); pass the verb, exact arguments, cwd, and a `background: true` instruction. Remove `--bg` from the child's request and explicitly forbid redispatch. Return after launching it. A background child must not ask user questions. If `Agent` is unavailable, stop and report that background execution cannot be started; never silently execute inline. Without `--bg`, execute in the current turn: author as Planner, coordinate go as Orchestrator and delegate bounded Worker tasks; do not assume an inline mode switch occurred. Do not launch a multi-agent workflow unless the user explicitly opts into one.
16
16
 
17
- Operational references: [init](references/workflows/init.md), [new](references/workflows/new.md), [update](references/workflows/update.md), [annotate](references/workflows/annotate.md), [finalize](references/workflows/finalize.md), [go](references/workflows/go.md), [help](references/workflows/help.md). NEW and UPDATE use direct authoritative file writes. The init/annotate/finalize/go references are transitional managed-tool guidance until phase 4 replaces them.
17
+ Operational references: [init](references/workflows/init.md), [new](references/workflows/new.md), [update](references/workflows/update.md), [annotate](references/workflows/annotate.md), [finalize](references/workflows/finalize.md), [go](references/workflows/go.md), [help](references/workflows/help.md).
@@ -1,30 +1,41 @@
1
1
  # Plan authoring quality
2
2
 
3
- Build from repository evidence. Keep the result compact and useful to a Worker.
3
+ Build the design from repository evidence, not generic slogans. Keep it concise enough to scan, but never shrink a section into an uninformative one-line paragraph just to reach a word count.
4
4
 
5
- ## PLAN.md
5
+ ## Big Ideas
6
6
 
7
- - Keep intent, requirements, design, references, and numbered phases in `PLAN.md`.
8
- - Give each phase a stable ID, title, objective, and prerequisites. Prerequisites belong to phases only.
9
- - Put each discrete implementation action in a flat task checkbox with a stable task ID and title. Do not put dependency metadata, steps, file scopes, or acceptance criteria under the checkbox.
10
- - Preserve phase/task ID and order parity in every implementation brief.
7
+ Use outcome-oriented bullets. Each bullet says what will become different and why it belongs in this plan; do not repeat the phase titles. For example:
11
8
 
12
- ## Implementation brief
9
+ - Keep task status and phase ordering in `PLAN.md` so readers can see progress without reading implementation instructions.
10
+ - Write one numbered brief per phase so Workers receive concrete steps and file/API contracts.
11
+ - Reject incomplete briefs at finalization instead of handing placeholders to execution agents.
13
12
 
14
- - Give tasks ordered implementation steps. Add nested `Verify` bullets under the relevant task; verification is part of the task, not a separate task.
15
- - List exact file scopes and acceptance criteria for every task.
16
- - Put libraries and algorithms under **Implementation Constraints**.
17
- - Include exactly one fenced `text` phase-level directory tree. Label every file leaf with one of `[ADD]`, `[MODIFY]`, `[REMOVE]`, `[MOVE from: path]`, or `[VERIFY]`.
18
- - Read back each file after writing it. Normal successive write/edit calls are intentional: incomplete drafts remain visible until the plan is ready.
13
+ ## Key API Addition/Updates
19
14
 
20
- ## Review and repair
15
+ Use explanatory paragraphs and illustrative fenced examples showing the actual public call and data contract. Explain which previous call is removed, who calls the new API, what arguments/return value mean, and how errors are surfaced. Do not invent details that the repository has not established; call out a genuine unresolved choice instead. For example:
21
16
 
22
- Template metadata such as revision snapshots and managed markers is not an authoring requirement. Treat their removal as a legacy compatibility change: the existing managed parser will stop recognizing these templates until the follow-on source migration adds a replacement contract. Report that break explicitly; do not preserve metadata in the templates solely to hide it.
17
+ ```ts
18
+ await plan_apply_revision({
19
+ mode: 'amend', // use 'create' with shortSlug instead for revision 0
20
+ plan,
21
+ expectedPlanRevision,
22
+ request: { kind: 'user', text: exactRequest },
23
+ title,
24
+ intent,
25
+ requirements,
26
+ design: { bigIdeas, keyApiUpdates, consequences },
27
+ references,
28
+ phases,
29
+ briefs,
30
+ });
31
+ ```
23
32
 
24
- After writing the current `PLAN.md` and all numbered briefs, independently invoke exactly one VERIFIED frontier/high-thinking `diffpi-plan-reviewer` in read-only mode. The reviewer checks structure and parity, action labels, whole-plan quality, consistency and risk, and whether every task is executable by a lightweight Worker without guessing, in one pass. Do not replace this with cheap separate reviewers or tiered checks.
33
+ Follow the example with a paragraph describing revision creation and validation, then another covering old mutators or callers being replaced. `mode: 'create'` instead takes `shortSlug` (and optional branch/issue fields) and writes revision 0 directly; `mode: 'amend'` takes `plan` and `expectedPlanRevision` and writes the next revision. Both modes require the same `request`, overview fields, ordered `phases`, and matching `briefs`; there is no `inputs` field and no separate `complete` field. If no public API changes, say so explicitly and show the relevant internal interface or data flow instead.
25
34
 
26
- The Planner repairs actionable findings and reruns the same reviewer within bounded attempts. A passing review does not prove that source code implements the plan. `annotate` is optional human review; it does not replace the automated reviewer.
35
+ ## Consequences
27
36
 
28
- ## Contracts and consequences
37
+ Write distinct before/after paragraphs: how a user invokes the workflow, what a client or Worker reads/calls, and how failure looks. Describe concrete behavior: editor launch, status transitions, validation, snapshots, and review visibility as applicable. Include at least one trade-off, such as storage cost or compatibility break; do not substitute a generic maintainability claim.
29
38
 
30
- Describe real API or data-flow changes. If there is no public API change, say so and show the relevant internal contract. Explain before/after invocation, what readers or Workers consume, validation and failure visibility, and at least one concrete trade-off. Do not use scaffold placeholders in a plan marked ready.
39
+ ## PLAN.md versus briefs
40
+
41
+ Under each PLAN.md phase, make **each discrete implementation action a separate stable task checkbox**. Do not embed Steps, File scopes, or Acceptance criteria below its checkbox. In `implementation/phase-<ordinal>.md`, relist every stable task ID and title and provide ordered implementation steps, exact affected files, API/data-contract changes, algorithms/libraries, constraints, and acceptance criteria. A lightweight Worker must be able to implement the task from the brief without guessing. Before finalization, inspect each rendered brief and require strict validation; no scaffold comments or “define during implementation” placeholders are acceptable.
@@ -1,7 +1,7 @@
1
1
  # New
2
2
 
3
- 1. Resolve the initiating nested Git root and the requested slug/branch, then inspect for a plan collision. If a matching plan already exists, stop. Only after confirming no matching plan exists, research and create the plan; do not read a nonexistent plan. If the goal is unspecified in foreground mode, ask one material question; background work must report a blocker instead.
4
- 2. Research repository files, APIs, tests, and constraints. Follow [plan authoring quality](../design-quality.md). Draft the complete PLAN.md and one numbered brief per phase. Preserve the user's request and intent in the visible plan content, including any visible completed task information; this is authoring input, not a revision transaction.
5
- 3. Write the plan directory directly with normal `write`/`edit` calls. Write PLAN.md and briefs incrementally so incomplete files remain visible. Do not call `plan_apply_revision`, `plan_validate`, or edit managed snapshots. Include exact scopes, ordered steps, constraints, acceptance criteria, task parity, one action-labeled tree per brief, and nested verification.
6
- 4. Read back every written file. Invoke exactly one `diffpi-plan-reviewer` over PLAN.md and all numbered briefs in a single pass. Before relying on its result, verify the effective model is the explicit frontier model (prefer `openai-codex/gpt-5.6-sol`, or an available equivalent), thinking is high, and tools are read/search-only. If selection cannot be verified, fail preflight with the exact observed model/thinking/tools and required correction; do not silently promise runtime verification or use a cheap fallback.
7
- 5. Repair actionable findings directly, then rerun the same reviewer within bounded attempts. A pass checks plan quality only; it does not prove source implementation. Report plan and brief paths, review evidence, and any compatibility caveat. Policy governs the plan-file source scope; this is not a Pi sandbox guarantee.
3
+ 1. Call `plan_context` for the supplied slug and branch. If a matching plan already exists, stop rather than silently overwriting it. If the goal is unspecified in foreground mode, use `ask_user_question` for the material scope decision; in background mode, state a blocker if no safe assumption exists.
4
+ 2. Research the relevant repository files, APIs, tests, and constraints before drafting. Follow [plan authoring quality](../design-quality.md): substantive Big Ideas bullets, illustrated API contracts, explicit before/after consequences, one stable PLAN.md task per implementation action, and a fully specified numbered brief per phase. Each brief must use numbered phase files, preserve exact phase/task IDs and order parity with PLAN.md, give ordered steps, exact file scopes, API/data contracts, algorithms/libraries, constraints, and acceptance criteria; inspect every rendered brief strictly for accuracy and completeness.
5
+ 3. Preserve the exact user request as a typed revision input. Build the **complete** candidate overview, ordered phases and task IDs, and matching briefs in memory. Call `plan_apply_revision` **once** with `mode: "create"`, the short slug, `request: {kind: "user", text: <exact incoming request>}`, a full overview and phase list, and one complete brief per phase; `create` writes revision 0 directly, so `/plan new` works without `plan_init`; do not create a phase-less shell first or open an editor.
6
+ 4. Validate the result strictly and inspect the generated brief paths. If any placeholders, missing task steps, mismatched IDs, or incomplete sections remain, report the failure rather than claiming readiness. A validation result is structural, not a check that source code already implements the plan.
7
+ 5. Report the plan ID and paths. Continue discussing changes in the same conversation; a later user request is a separate revision. Offer `annotate` for human review and `finalize` when ready, without making the user paste a command merely to continue this request.
@@ -1,7 +1,7 @@
1
1
  # Update
2
2
 
3
- 1. Select exactly one unique repository-root plan by its path and plan files. Read the current PLAN.md, all numbered briefs, and the incoming request. Preserve the user's intent and all visible completed task information, including IDs, ownership, evidence, and dependencies.
4
- 2. Research affected repository files and follow [plan authoring quality](../design-quality.md). Build the entire resulting PLAN.md and numbered briefs in memory, preserving exact phase/task order parity. Do not guess among plans or rewrite historical records.
5
- 3. Apply the update through successive direct `write`/`edit` calls to the authoritative plan files. Do not call `plan_apply_revision`, `plan_validate`, or edit managed snapshots. Read back each file after writing. New files remain visible while incomplete.
6
- 4. Invoke exactly one `diffpi-plan-reviewer` over the current PLAN.md and every numbered brief in one pass. Verify its effective model is the explicit frontier model (prefer `openai-codex/gpt-5.6-sol`, or an available equivalent), thinking is high, and capabilities are read/search-only. If effective selection cannot be verified, fail preflight with the exact observed model/thinking/tools and required correction; never silently promise runtime verification or accept a cheap fallback.
7
- 5. Repair actionable findings directly and rerun the same reviewer within bounded attempts. Report changed files, review evidence, remaining blocker, and compatibility caveat. A passing review is structural/semantic plan evidence only, not proof source code implements the plan. Policy governs the plan-file source scope; this is not a Pi sandbox guarantee.
3
+ 1. Call `plan_context` for the slug/branch; never guess among multiple candidates. Call `plan_review` once for the selected current revision before interpreting the request. A review of an older revision is stale; do not reapply it.
4
+ 2. Preserve the exact incoming request in `request.text` and select `request.kind` (`user`, `annotation`, or `blocker`) according to the trigger. When applying a plan review, read its full feedback; when unblocking execution, preserve the worker's blocker in the request. For annotation requests, include the exact LLM response/outcome in `request.response` and classify every comment as applied, answered, or unresolved with a concrete reason. Do not use vague “later” or “follow-up” deferrals unless the user explicitly requested that deferral. Research affected source files. Follow [plan authoring quality](../design-quality.md), including substantive Design sections and complete per-task implementation briefs. For both new and amended content, briefs must use numbered phase files, exact phase/task ID and order parity, ordered steps, exact file scopes, API/data contracts, algorithms/libraries, constraints, and acceptance criteria; strictly inspect every rendered brief for accuracy and completeness.
5
+ 3. Build the **entire resulting plan and numbered briefs in memory**, preserving completed task IDs, ownership, evidence, and dependencies. For a single authoring request, call `plan_apply_revision` with `mode: "amend"` exactly once, passing `plan`, `expectedPlanRevision`, `request`, the entire overview, ordered phases, and matching briefs. Do not mutate overview/phase/task content with separate tools, edit managed files by hand, or create a revision per comment.
6
+ 4. In foreground mode, ask only for missing decisions that materially affect scope; background work must not ask. Strictly validate the saved plan and briefs; stop with precise errors if they are incomplete. Do not treat structural validation as evidence that implementation already meets requirements.
7
+ 5. Report the one new revision and its snapshot path, what changed, and any remaining blocker. Further chat instructions form a future separate revision; `annotate` reviews the plan and `finalize` marks it ready.
@@ -1,10 +1,13 @@
1
+ <!-- diffpi-plan: {"schemaVersion":1,"id":"{{id}}","revision":0,"branch":"{{branch}}","issueId":"{{issue_id}}","issueUrl":"{{issue_url}}","status":"draft","createdAt":"{{created_at}}","updatedAt":"{{updated_at}}"} -->
2
+
1
3
  # {{title}}
2
4
 
3
5
  - **Plan ID:** {{id}}
4
6
  - **Branch:** {{branch}}
5
- - **Status:** draft
6
7
  - **Issue:** {{issue_id}}
7
8
  - **Issue URL:** {{issue_url}}
9
+ - **Status:** draft
10
+ - **Revision:** 0
8
11
 
9
12
  ## Intent
10
13
 
@@ -12,35 +15,26 @@
12
15
 
13
16
  ## Requirements
14
17
 
15
- <!-- Add concrete requirements. -->
18
+ <!-- Add requirements. -->
16
19
 
17
20
  ## Design
18
21
 
19
22
  ### Big Ideas
20
23
 
21
- <!-- Describe the outcome-oriented design. -->
24
+ <!-- Describe the main approach. -->
22
25
 
23
26
  ### Key API Addition/Updates
24
27
 
25
- <!-- Describe public or internal contract changes. -->
28
+ <!-- Describe public API changes. -->
26
29
 
27
30
  ### Consequences
28
31
 
29
- <!-- Describe before/after behavior, risks, and trade-offs. -->
30
-
31
- ## Phases
32
-
33
- <!-- Use numbered phases. State prerequisites on the phase only; do not add task dependencies here. -->
34
-
35
- ### Phase 1: <!-- phase title -->
32
+ <!-- Describe trade-offs and limitations. -->
36
33
 
37
- - **Phase ID:** <!-- assign a stable phase ID -->
38
- - **Prerequisites:** <!-- list phase IDs, or “None” -->
39
- - **Objective:** <!-- state the outcome -->
34
+ ## Implementation
40
35
 
41
- - [ ] <!-- assign a stable task ID --> Task title
42
- - [ ] <!-- assign a stable task ID --> Another task title
36
+ <!-- Add phases with one complete plan_apply_revision request. -->
43
37
 
44
38
  ## References
45
39
 
46
- <!-- Add repository or external references. -->
40
+ <!-- Add references. -->