@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.
@@ -15049,7 +15049,7 @@ function sanitize(value) {
15049
15049
  }
15050
15050
 
15051
15051
  // src/tools/plan.ts
15052
- import { join as join18 } from "node:path";
15052
+ import { join as join19 } from "node:path";
15053
15053
  import { pathToFileURL } from "node:url";
15054
15054
  import { defineTool as defineTool5 } from "@earendil-works/pi-coding-agent";
15055
15055
  import { z as z8 } from "zod";
@@ -20993,8 +20993,8 @@ function isPlanReviewDump(value) {
20993
20993
 
20994
20994
  // src/plan/store.ts
20995
20995
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
20996
- import { mkdir as mkdir6, readdir as readdir3, readFile as readFile9, realpath as realpath3, rename as rename2, rm as rm2 } from "node:fs/promises";
20997
- import { basename as basename6, dirname as dirname8, join as join17, resolve as resolve4, sep } from "node:path";
20996
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile10, realpath as realpath3, rename as rename2, rm as rm2 } from "node:fs/promises";
20997
+ import { basename as basename6, dirname as dirname8, join as join18, resolve as resolve4, sep } from "node:path";
20998
20998
 
20999
20999
  // src/extensions/fsx.ts
21000
21000
  import { mkdir as mkdir5, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
@@ -21030,6 +21030,42 @@ function sleep(ms) {
21030
21030
  return new Promise((resolve) => setTimeout(resolve, ms));
21031
21031
  }
21032
21032
 
21033
+ // src/templates.ts
21034
+ import { readFile as readFile9 } from "node:fs/promises";
21035
+ import { homedir as homedir7 } from "node:os";
21036
+ import { join as join17, normalize } from "node:path";
21037
+ async function loadTemplate(name, options = {}) {
21038
+ const relative = templateRelativePath(name);
21039
+ const userPath = join17(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
21040
+ const bundledPath = join17(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
21041
+ const user = await readOptionalFile(userPath);
21042
+ if (user !== undefined)
21043
+ return { name, path: userPath, source: "user", content: user };
21044
+ const bundled = await readOptionalFile(bundledPath);
21045
+ if (bundled !== undefined)
21046
+ return { name, path: bundledPath, source: "bundled", content: bundled };
21047
+ throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
21048
+ }
21049
+ function renderTemplate(content, variables) {
21050
+ return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
21051
+ }
21052
+ function templateRelativePath(name) {
21053
+ const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
21054
+ if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
21055
+ throw new Error(`Invalid template name: ${name}`);
21056
+ }
21057
+ return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
21058
+ }
21059
+ async function readOptionalFile(path) {
21060
+ try {
21061
+ return await readFile9(path, "utf8");
21062
+ } catch (error) {
21063
+ if (error.code === "ENOENT")
21064
+ return;
21065
+ throw error;
21066
+ }
21067
+ }
21068
+
21033
21069
  // src/plan/store.ts
21034
21070
  function planRecordName(shortSlug, date = new Date) {
21035
21071
  const slug = normalizeSlug(shortSlug);
@@ -21045,7 +21081,7 @@ async function resolvePlan(cwd, query, filters = {}, options = {}) {
21045
21081
  continue;
21046
21082
  if (normalizedQuery && entry.name !== normalizedQuery && stripDate(entry.name) !== normalizedQuery)
21047
21083
  continue;
21048
- const dir = join17(root, entry.name);
21084
+ const dir = join18(root, entry.name);
21049
21085
  await assertContained(root, dir);
21050
21086
  try {
21051
21087
  const record = await readRecord(dir);
@@ -21080,30 +21116,26 @@ function createPlanStore(options = {}) {
21080
21116
  async init(input) {
21081
21117
  const root = await plansDir(input.cwd, options.homeDir);
21082
21118
  const id = planRecordName(input.shortSlug, now());
21083
- const dir = join17(root, id);
21119
+ const dir = join18(root, id);
21084
21120
  await createPlanDirectory(root, dir, id);
21085
21121
  try {
21086
21122
  const timestamp = now().toISOString();
21123
+ const template = await loadTemplate("plan/PLAN", {
21124
+ homeDir: options.homeDir,
21125
+ bundledDir: options.bundledTemplatesDir
21126
+ });
21087
21127
  const title = input.title?.trim() || titleFromSlug(input.shortSlug);
21088
- const document = {
21089
- schemaVersion: 1,
21128
+ const source = renderTemplate(template.content, {
21090
21129
  id,
21091
- revision: 0,
21092
- title,
21093
21130
  branch: input.branch,
21094
- ...input.issueId?.trim() ? { issueId: input.issueId.trim() } : {},
21095
- ...input.issueUrl?.trim() ? { issueUrl: input.issueUrl.trim() } : {},
21131
+ title,
21096
21132
  intent: input.intent?.trim() || "<!-- Describe the intended outcome. -->",
21097
- requirements: [],
21098
- design: { bigIdeas: "", keyApiUpdates: "", consequences: "" },
21099
- phases: [],
21100
- references: [],
21101
- status: "draft",
21102
- createdAt: timestamp,
21103
- updatedAt: timestamp
21104
- };
21105
- const source = renderPlanDocument(document);
21106
- parsePlanDocument(source, join17(dir, "PLAN.md"));
21133
+ issue_id: input.issueId?.trim() || "",
21134
+ issue_url: input.issueUrl?.trim() || "",
21135
+ created_at: timestamp,
21136
+ updated_at: timestamp
21137
+ });
21138
+ const document = parsePlanDocument(source, join18(dir, "PLAN.md"));
21107
21139
  const authoringRequest = input.request ?? { kind: "user", text: input.intent?.trim() || `Initialize phase-less plan ${title}.` };
21108
21140
  await writeRevisionSnapshot(dir, document, source, authoringRequest, []);
21109
21141
  await writeLatestView(dir, document, source, []);
@@ -21118,14 +21150,14 @@ function createPlanStore(options = {}) {
21118
21150
  if (request.mode === "create") {
21119
21151
  const root = await plansDir(cwd, options.homeDir);
21120
21152
  const id = planRecordName(request.shortSlug, now());
21121
- const dir = join17(root, id);
21153
+ const dir = join18(root, id);
21122
21154
  await createPlanDirectory(root, dir, id);
21123
21155
  try {
21124
21156
  const timestamp = now().toISOString();
21125
21157
  const document = normalizeRevision(undefined, request, id, 0, timestamp);
21126
21158
  assertCompleteRevision(document, request.briefs);
21127
21159
  const source = renderPlanDocument(document);
21128
- parsePlanDocument(source, join17(dir, "PLAN.md"));
21160
+ parsePlanDocument(source, join18(dir, "PLAN.md"));
21129
21161
  await writeRevisionSnapshot(dir, document, source, request.request, request.briefs);
21130
21162
  await writeLatestView(dir, document, source, request.briefs);
21131
21163
  return await readRecord(dir);
@@ -21135,7 +21167,7 @@ function createPlanStore(options = {}) {
21135
21167
  }
21136
21168
  }
21137
21169
  const initial = await read(cwd, request.plan);
21138
- return withDirectoryLock(join17(initial.dir, ".lock"), async () => {
21170
+ return withDirectoryLock(join18(initial.dir, ".lock"), async () => {
21139
21171
  const current = await readRecord(initial.dir);
21140
21172
  if (current.document.revision !== request.expectedPlanRevision)
21141
21173
  throw new Error(`Expected plan revision ${request.expectedPlanRevision}, found ${current.document.revision}.`);
@@ -21148,7 +21180,7 @@ function createPlanStore(options = {}) {
21148
21180
  try {
21149
21181
  await writeLatestView(current.dir, document, source, request.briefs);
21150
21182
  } catch (error) {
21151
- await rm2(join17(current.dir, "revisions", String(revision)), { recursive: true, force: true });
21183
+ await rm2(join18(current.dir, "revisions", String(revision)), { recursive: true, force: true });
21152
21184
  throw error;
21153
21185
  }
21154
21186
  return await readRecord(current.dir);
@@ -21156,7 +21188,7 @@ function createPlanStore(options = {}) {
21156
21188
  },
21157
21189
  async mutate(cwd, query, operation, update) {
21158
21190
  const initial = await read(cwd, query);
21159
- return withDirectoryLock(join17(initial.dir, ".lock"), async () => {
21191
+ return withDirectoryLock(join18(initial.dir, ".lock"), async () => {
21160
21192
  const current = await readRecord(initial.dir);
21161
21193
  const next = await update(structuredClone(current.document));
21162
21194
  if (next.id !== current.id)
@@ -21179,16 +21211,16 @@ function createPlanStore(options = {}) {
21179
21211
  };
21180
21212
  }
21181
21213
  async function readRecord(dir) {
21182
- const planPath = join17(dir, "PLAN.md");
21183
- const logPath = join17(dir, "logs.txt");
21184
- const source = await readFile9(planPath, "utf8");
21214
+ const planPath = join18(dir, "PLAN.md");
21215
+ const logPath = join18(dir, "logs.txt");
21216
+ const source = await readFile10(planPath, "utf8");
21185
21217
  const document = parsePlanDocument(source, planPath);
21186
21218
  return {
21187
21219
  id: basename6(dir),
21188
21220
  dir,
21189
21221
  planPath,
21190
21222
  logPath,
21191
- implementationDir: join17(dir, "implementation"),
21223
+ implementationDir: join18(dir, "implementation"),
21192
21224
  document,
21193
21225
  source
21194
21226
  };
@@ -21208,17 +21240,17 @@ async function writeRevisionSnapshot(planDir, document, source, request, briefs)
21208
21240
  throw new Error("An exact non-empty authoring request is required.");
21209
21241
  if (request.kind === "annotation" && !request.response?.trim())
21210
21242
  throw new Error("Annotation requests require a non-empty response.");
21211
- const revisionsDir = join17(planDir, "revisions");
21243
+ const revisionsDir = join18(planDir, "revisions");
21212
21244
  await mkdir6(revisionsDir, { recursive: true });
21213
- const target = join17(revisionsDir, String(document.revision));
21245
+ const target = join18(revisionsDir, String(document.revision));
21214
21246
  try {
21215
- await readFile9(join17(target, "metadata.json"), "utf8");
21247
+ await readFile10(join18(target, "metadata.json"), "utf8");
21216
21248
  throw new Error(`Revision ${document.revision} already exists and is immutable.`);
21217
21249
  } catch (error) {
21218
21250
  if (error.code !== "ENOENT")
21219
21251
  throw error;
21220
21252
  }
21221
- const staging = join17(revisionsDir, `.tmp-${document.revision}-${randomUUID2()}`);
21253
+ const staging = join18(revisionsDir, `.tmp-${document.revision}-${randomUUID2()}`);
21222
21254
  await mkdir6(staging);
21223
21255
  try {
21224
21256
  const requestSource = request.kind === "annotation" ? `## Original annotation
@@ -21226,8 +21258,8 @@ ${request.text}
21226
21258
 
21227
21259
  ## LLM response
21228
21260
  ${request.response}` : request.text;
21229
- await atomicWrite(join17(staging, "request.md"), requestSource);
21230
- await atomicWrite(join17(staging, "metadata.json"), `${JSON.stringify({
21261
+ await atomicWrite(join18(staging, "request.md"), requestSource);
21262
+ await atomicWrite(join18(staging, "metadata.json"), `${JSON.stringify({
21231
21263
  schemaVersion: 1,
21232
21264
  id: document.id,
21233
21265
  revision: document.revision,
@@ -21237,7 +21269,7 @@ ${request.response}` : request.text;
21237
21269
  createdAt: document.updatedAt
21238
21270
  }, null, 2)}
21239
21271
  `);
21240
- await atomicWrite(join17(staging, "PLAN.md"), source);
21272
+ await atomicWrite(join18(staging, "PLAN.md"), source);
21241
21273
  await writeImplementationDirectory(staging, document, briefs);
21242
21274
  await rename2(staging, target);
21243
21275
  } catch (error) {
@@ -21246,10 +21278,10 @@ ${request.response}` : request.text;
21246
21278
  }
21247
21279
  }
21248
21280
  async function writeLatestView(planDir, document, source, briefs) {
21249
- const current = join17(planDir, "implementation");
21250
- const stagingRoot = join17(planDir, `.latest-${randomUUID2()}`);
21251
- const stagedImplementation = join17(stagingRoot, "implementation");
21252
- const backup = join17(planDir, `.implementation-backup-${randomUUID2()}`);
21281
+ const current = join18(planDir, "implementation");
21282
+ const stagingRoot = join18(planDir, `.latest-${randomUUID2()}`);
21283
+ const stagedImplementation = join18(stagingRoot, "implementation");
21284
+ const backup = join18(planDir, `.implementation-backup-${randomUUID2()}`);
21253
21285
  await mkdir6(stagingRoot);
21254
21286
  await writeImplementationDirectory(stagingRoot, document, briefs);
21255
21287
  let backedUp = false;
@@ -21262,7 +21294,7 @@ async function writeLatestView(planDir, document, source, briefs) {
21262
21294
  throw error;
21263
21295
  }
21264
21296
  await rename2(stagedImplementation, current);
21265
- await atomicWrite(join17(planDir, "PLAN.md"), source);
21297
+ await atomicWrite(join18(planDir, "PLAN.md"), source);
21266
21298
  if (backedUp)
21267
21299
  await rm2(backup, { recursive: true, force: true });
21268
21300
  } catch (error) {
@@ -21275,13 +21307,13 @@ async function writeLatestView(planDir, document, source, briefs) {
21275
21307
  }
21276
21308
  }
21277
21309
  async function writeImplementationDirectory(baseDir, document, briefs) {
21278
- const implementationDir = join17(baseDir, "implementation");
21310
+ const implementationDir = join18(baseDir, "implementation");
21279
21311
  await mkdir6(implementationDir, { recursive: true });
21280
21312
  for (const [index, phase] of document.phases.entries()) {
21281
21313
  const brief = briefs[index];
21282
21314
  if (!brief)
21283
21315
  throw new Error(`Missing implementation brief for phase ${phase.id}.`);
21284
- await atomicWrite(join17(implementationDir, `phase-${index + 1}.md`), renderImplementationBrief(document.revision, index + 1, phase, brief));
21316
+ await atomicWrite(join18(implementationDir, `phase-${index + 1}.md`), renderImplementationBrief(document.revision, index + 1, phase, brief));
21285
21317
  }
21286
21318
  }
21287
21319
  function normalizeRevision(current, request, id, revision, updatedAt) {
@@ -21767,7 +21799,7 @@ function createPlanTools(_pi, _modes, store = createPlanStore()) {
21767
21799
  const resolution = await store.context(workingDirectory, params.plan);
21768
21800
  if (!resolution.record)
21769
21801
  throw new Error(resolution.ambiguous ? `Plan is ambiguous: ${resolution.candidates.map((item) => item.id).join(", ")}.` : "No matching plan.");
21770
- const cliPath = join18(resolveBundledAgentsDir(), "..", "dist", "cli.js");
21802
+ const cliPath = join19(resolveBundledAgentsDir(), "..", "dist", "cli.js");
21771
21803
  const command = [
21772
21804
  process.execPath,
21773
21805
  cliPath,
@@ -22567,10 +22599,10 @@ function hasGitlabDraftNotes(input) {
22567
22599
  }
22568
22600
 
22569
22601
  // src/review/local-review-backend.ts
22570
- import { readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
22602
+ import { readFile as readFile12, writeFile as writeFile5 } from "node:fs/promises";
22571
22603
 
22572
22604
  // src/extensions/tuicrx.ts
22573
- import { readFile as readFile10, realpath as realpath4 } from "node:fs/promises";
22605
+ import { readFile as readFile11, realpath as realpath4 } from "node:fs/promises";
22574
22606
  import { resolve as resolve5 } from "node:path";
22575
22607
  async function tuicrAvailable() {
22576
22608
  return Boolean(await findExecutable("tuicr"));
@@ -22667,7 +22699,7 @@ async function findMatchingSession(sessions, cwd, branch) {
22667
22699
  return;
22668
22700
  }
22669
22701
  async function readSession(path) {
22670
- const content = await readFile10(path, "utf8");
22702
+ const content = await readFile11(path, "utf8");
22671
22703
  try {
22672
22704
  return JSON.parse(content);
22673
22705
  } catch {
@@ -22788,7 +22820,7 @@ function LocalReviewBackend(options) {
22788
22820
  },
22789
22821
  async listThreads() {
22790
22822
  try {
22791
- return parseThreadArtifact(await readFile11(options.artifactPath, "utf8"));
22823
+ return parseThreadArtifact(await readFile12(options.artifactPath, "utf8"));
22792
22824
  } catch (error) {
22793
22825
  if (error.code === "ENOENT")
22794
22826
  return [];
@@ -22796,7 +22828,7 @@ function LocalReviewBackend(options) {
22796
22828
  }
22797
22829
  },
22798
22830
  async reply(input) {
22799
- const content = await readFile11(options.artifactPath, "utf8");
22831
+ const content = await readFile12(options.artifactPath, "utf8");
22800
22832
  await writeFile5(options.artifactPath, upsertThreadReply(content, input.threadId, input.body, input.question, input.resolve), "utf8");
22801
22833
  },
22802
22834
  async publish() {
@@ -22818,8 +22850,8 @@ function createLocalReviewBackend(options) {
22818
22850
  }
22819
22851
  // src/review/review-state.ts
22820
22852
  import { createHash as createHash4 } from "node:crypto";
22821
- import { readFile as readFile12, rename as rename3, writeFile as writeFile6 } from "node:fs/promises";
22822
- import { join as join19 } from "node:path";
22853
+ import { readFile as readFile13, rename as rename3, writeFile as writeFile6 } from "node:fs/promises";
22854
+ import { join as join20 } from "node:path";
22823
22855
  import { z as z11 } from "zod";
22824
22856
  var reviewPublicationStateSchema = z11.object({
22825
22857
  target: z11.string().optional(),
@@ -22843,9 +22875,9 @@ function unpublishedReviewComments(comments, knownFingerprints) {
22843
22875
  }
22844
22876
  async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
22845
22877
  const target = `${vcs.provider}:${vcs.owner}/${vcs.repo}#${number}`;
22846
- const path = join19(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
22878
+ const path = join20(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
22847
22879
  try {
22848
- const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile12(path, "utf8")));
22880
+ const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile13(path, "utf8")));
22849
22881
  return { path, state: { ...parsed, target } };
22850
22882
  } catch (error) {
22851
22883
  if (error.code === "ENOENT") {
@@ -22866,42 +22898,6 @@ async function saveReviewPublicationState(path, state) {
22866
22898
  function digest(value) {
22867
22899
  return createHash4("sha256").update(value).digest("hex");
22868
22900
  }
22869
- // src/templates.ts
22870
- import { readFile as readFile13 } from "node:fs/promises";
22871
- import { homedir as homedir7 } from "node:os";
22872
- import { join as join20, normalize } from "node:path";
22873
- async function loadTemplate(name, options = {}) {
22874
- const relative = templateRelativePath(name);
22875
- const userPath = join20(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
22876
- const bundledPath = join20(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
22877
- const user = await readOptionalFile(userPath);
22878
- if (user !== undefined)
22879
- return { name, path: userPath, source: "user", content: user };
22880
- const bundled = await readOptionalFile(bundledPath);
22881
- if (bundled !== undefined)
22882
- return { name, path: bundledPath, source: "bundled", content: bundled };
22883
- throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
22884
- }
22885
- function renderTemplate(content, variables) {
22886
- return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
22887
- }
22888
- function templateRelativePath(name) {
22889
- const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
22890
- if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
22891
- throw new Error(`Invalid template name: ${name}`);
22892
- }
22893
- return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
22894
- }
22895
- async function readOptionalFile(path) {
22896
- try {
22897
- return await readFile13(path, "utf8");
22898
- } catch (error) {
22899
- if (error.code === "ENOENT")
22900
- return;
22901
- throw error;
22902
- }
22903
- }
22904
-
22905
22901
  // src/tools/review.ts
22906
22902
  var contextSchema = z12.object({
22907
22903
  cwd: z12.string().optional(),