@difflab/pi 0.7.1 → 0.8.0-rc.202610020810.d27e7fc

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.
Files changed (95) hide show
  1. package/README.md +22 -40
  2. package/agents/diffpi-copilot.md +2 -2
  3. package/agents/diffpi-orchestrator.md +9 -14
  4. package/agents/diffpi-plan-reviewer.md +4 -12
  5. package/agents/diffpi-planner.md +7 -13
  6. package/agents/diffpi-reviewer.md +7 -11
  7. package/agents/diffpi-tutor.md +2 -2
  8. package/agents/diffpi-worker.md +9 -12
  9. package/dist/commands/dispatch.d.ts +4 -0
  10. package/dist/commands/dispatch.d.ts.map +1 -0
  11. package/dist/commands/index.d.ts +1 -3
  12. package/dist/commands/index.d.ts.map +1 -1
  13. package/dist/commands/plan.d.ts +1 -1
  14. package/dist/commands/plan.d.ts.map +1 -1
  15. package/dist/commands/review.d.ts +1 -1
  16. package/dist/commands/review.d.ts.map +1 -1
  17. package/dist/doctor.d.ts +9 -0
  18. package/dist/doctor.d.ts.map +1 -0
  19. package/dist/environment.d.ts +0 -1
  20. package/dist/environment.d.ts.map +1 -1
  21. package/dist/extensions/index.js +1622 -3207
  22. package/dist/index.d.ts +5 -11
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +146 -1128
  25. package/dist/log.d.ts +6 -11
  26. package/dist/log.d.ts.map +1 -1
  27. package/dist/plan/verify.d.ts.map +1 -1
  28. package/dist/review/index.d.ts +2 -3
  29. package/dist/review/index.d.ts.map +1 -1
  30. package/dist/review/review-backend.d.ts +2 -3
  31. package/dist/review/review-backend.d.ts.map +1 -1
  32. package/dist/setup.d.ts +0 -2
  33. package/dist/setup.d.ts.map +1 -1
  34. package/dist/tools/ci.d.ts +1 -1
  35. package/dist/tools/doctor.d.ts +3 -0
  36. package/dist/tools/doctor.d.ts.map +1 -0
  37. package/dist/tools/index.d.ts +2 -3
  38. package/dist/tools/index.d.ts.map +1 -1
  39. package/dist/tools/index.js +1821 -3006
  40. package/dist/tools/log.d.ts.map +1 -1
  41. package/dist/tools/review.d.ts +1 -7
  42. package/dist/tools/review.d.ts.map +1 -1
  43. package/dist/tools/setup.d.ts.map +1 -1
  44. package/package.json +2 -6
  45. package/skills/diffpi-doctor/SKILL.md +10 -0
  46. package/skills/diffpi-setup/SKILL.md +4 -4
  47. package/skills/plan/SKILL.md +13 -24
  48. package/skills/plan/references/design-quality.md +3 -3
  49. package/skills/plan/references/workflows/finalize.md +10 -6
  50. package/skills/plan/references/workflows/go.md +27 -7
  51. package/skills/plan/references/workflows/help.md +19 -3
  52. package/skills/plan/references/workflows/init.md +23 -6
  53. package/skills/plan/references/workflows/inline-fallback.md +9 -0
  54. package/skills/plan/references/workflows/new.md +22 -7
  55. package/skills/plan/references/workflows/update.md +24 -6
  56. package/skills/plan/references/workflows/validate.md +31 -0
  57. package/skills/review/SKILL.md +15 -7
  58. package/skills/review/references/review-standards.md +9 -1
  59. package/skills/review/references/workflows/address.md +13 -6
  60. package/skills/review/references/workflows/auto.md +13 -5
  61. package/skills/review/references/workflows/complete.md +17 -4
  62. package/skills/review/references/workflows/edit.md +10 -2
  63. package/skills/review/references/workflows/foreground-fallback.md +7 -0
  64. package/skills/review/references/workflows/help.md +11 -10
  65. package/skills/review/references/workflows/merge.md +17 -2
  66. package/skills/review/references/workflows/new.md +18 -5
  67. package/skills/review/references/workflows/publish.md +17 -4
  68. package/skills/review/references/workflows/status.md +7 -2
  69. package/skills/review/templates/REVIEW.md +39 -0
  70. package/templates/plan/PLAN.md +9 -1
  71. package/templates/review/draft-pr.md +3 -9
  72. package/dist/cli/plan.d.ts +0 -17
  73. package/dist/cli/plan.d.ts.map +0 -1
  74. package/dist/cli.d.ts +0 -4
  75. package/dist/cli.d.ts.map +0 -1
  76. package/dist/cli.js +0 -2258
  77. package/dist/commands/mode.d.ts +0 -5
  78. package/dist/commands/mode.d.ts.map +0 -1
  79. package/dist/extensions/tuicrx.d.ts +0 -66
  80. package/dist/extensions/tuicrx.d.ts.map +0 -1
  81. package/dist/extensions/zedx.d.ts +0 -18
  82. package/dist/extensions/zedx.d.ts.map +0 -1
  83. package/dist/modes.d.ts +0 -75
  84. package/dist/modes.d.ts.map +0 -1
  85. package/dist/review/local-review-backend.d.ts +0 -3
  86. package/dist/review/local-review-backend.d.ts.map +0 -1
  87. package/dist/review/local-reviews.d.ts +0 -31
  88. package/dist/review/local-reviews.d.ts.map +0 -1
  89. package/dist/review/review-markdown.d.ts +0 -6
  90. package/dist/review/review-markdown.d.ts.map +0 -1
  91. package/dist/tools/modes.d.ts +0 -4
  92. package/dist/tools/modes.d.ts.map +0 -1
  93. package/skills/mode/SKILL.md +0 -38
  94. package/skills/plan/references/workflows/annotate.md +0 -7
  95. package/skills/review/references/workflows/open.md +0 -5
package/dist/index.js CHANGED
@@ -15,15 +15,6 @@ var zx = {
15
15
 
16
16
  // src/fsx.ts
17
17
  import { readdir, readFile } from "node:fs/promises";
18
- async function readDirectoryIfExists(path) {
19
- try {
20
- return await readdir(path, { withFileTypes: true });
21
- } catch (error) {
22
- if (isMissingPath(error))
23
- return [];
24
- throw error;
25
- }
26
- }
27
18
  async function readTextIfExists(path) {
28
19
  try {
29
20
  return await readFile(path, "utf8");
@@ -104,30 +95,13 @@ function normalizeModelReference(value) {
104
95
  return value.toLowerCase().replace(/^~/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
105
96
  }
106
97
  // src/environment.ts
107
- import { readFile as readFile3 } from "node:fs/promises";
108
- import { basename, join as join5 } from "node:path";
109
-
110
- // src/assets.ts
111
- import { existsSync } from "node:fs";
112
- import { dirname, join as join2 } from "node:path";
113
- import { fileURLToPath } from "node:url";
114
- function resolveBundledAssetDir(name, moduleUrl = import.meta.url) {
115
- const moduleDir = dirname(fileURLToPath(moduleUrl));
116
- const candidates = [join2(moduleDir, name), join2(moduleDir, "..", name), join2(moduleDir, "..", "..", name)];
117
- return candidates.find((path) => existsSync(path)) ?? candidates[1];
118
- }
119
- function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
120
- return resolveBundledAssetDir("agents", moduleUrl);
121
- }
122
- function resolveBundledTemplatesDir(moduleUrl = import.meta.url) {
123
- return resolveBundledAssetDir("templates", moduleUrl);
124
- }
98
+ import { basename } from "node:path";
125
99
 
126
100
  // src/extensions/processx.ts
127
101
  import { spawn } from "node:child_process";
128
102
  import { constants } from "node:fs";
129
103
  import { access } from "node:fs/promises";
130
- import { delimiter, join as join3 } from "node:path";
104
+ import { delimiter, join as join2 } from "node:path";
131
105
  async function findExecutable(name) {
132
106
  if (name.includes("/")) {
133
107
  try {
@@ -140,7 +114,7 @@ async function findExecutable(name) {
140
114
  for (const directory of (process.env.PATH ?? "").split(delimiter)) {
141
115
  if (!directory)
142
116
  continue;
143
- const candidate = join3(directory, name);
117
+ const candidate = join2(directory, name);
144
118
  try {
145
119
  await access(candidate, constants.X_OK);
146
120
  return candidate;
@@ -204,175 +178,6 @@ function appendBounded(current, next) {
204
178
  return combined.length <= MAX_CAPTURED_OUTPUT_LENGTH ? combined : combined.slice(-MAX_CAPTURED_OUTPUT_LENGTH);
205
179
  }
206
180
 
207
- // src/extensions/zedx.ts
208
- import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
209
- import { homedir as homedir2 } from "node:os";
210
- import { dirname as dirname2, join as join4 } from "node:path";
211
- var ZED_LOCAL_REVIEW_TASK_NAME = "diffpi: tuicr local review";
212
- var ZED_REVIEW_TASK_NAME = ZED_LOCAL_REVIEW_TASK_NAME;
213
- var ZED_PR_REVIEW_TASK_NAME = "diffpi: tuicr PR review";
214
- var ZED_PLAN_ANNOTATE_TASK_NAME = "diffpi: annotate plan";
215
- var LEGACY_ZED_REVIEW_TASK_NAME = "diffpi: tuicr review";
216
- var REVIEW_KEYBINDING = "cmd-alt-r";
217
- function zedTasksPath(homeDir = homedir2()) {
218
- return join4(homeDir, ".config", "zed", "tasks.json");
219
- }
220
- function zedKeymapPath(homeDir = homedir2()) {
221
- return join4(homeDir, ".config", "zed", "keymap.json");
222
- }
223
- async function ensureZedReviewTask(homeDir = homedir2(), _command = ["tuicr", "-w", "-r", "main..HEAD"]) {
224
- const path = zedTasksPath(homeDir);
225
- const currentText = await readOptional(path);
226
- const tasks = parseJsonArray(currentText, path);
227
- const migrated = tasks.filter((task) => task.label !== LEGACY_ZED_REVIEW_TASK_NAME);
228
- const next = [...migrated];
229
- for (const task of [localReviewTask(), prReviewTask()]) {
230
- const index = next.findIndex((existing) => existing.label === task.label);
231
- if (index >= 0)
232
- next[index] = { ...next[index], ...task };
233
- else
234
- next.push(task);
235
- }
236
- const changed = JSON.stringify(tasks) !== JSON.stringify(next);
237
- if (changed)
238
- await writeJson(path, next);
239
- return { path, changed, existed: currentText !== undefined };
240
- }
241
- async function ensureZedPlanTask(packageVersion, homeDir = homedir2()) {
242
- if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageVersion)) {
243
- throw new Error(`Invalid @difflab/pi package version: ${packageVersion}.`);
244
- }
245
- const path = zedTasksPath(homeDir);
246
- const currentText = await readOptional(path);
247
- const tasks = parseJsonArray(currentText, path);
248
- const task = {
249
- label: ZED_PLAN_ANNOTATE_TASK_NAME,
250
- command: "npx",
251
- args: ["--yes", `@difflab/pi@${packageVersion}`, "plan", "annotate", "$ZED_FILE", "--cwd", "$ZED_WORKTREE_ROOT"],
252
- cwd: "$ZED_WORKTREE_ROOT",
253
- use_new_terminal: true,
254
- reveal: "always",
255
- reveal_target: "center"
256
- };
257
- const next = [...tasks];
258
- const index = next.findIndex((candidate) => candidate.label === task.label);
259
- if (index >= 0)
260
- next[index] = { ...next[index], ...task };
261
- else
262
- next.push(task);
263
- const changed = JSON.stringify(tasks) !== JSON.stringify(next);
264
- if (changed)
265
- await writeJson(path, next);
266
- return { path, changed, existed: currentText !== undefined };
267
- }
268
- async function ensureZedReviewKeybinding(homeDir = homedir2()) {
269
- const path = zedKeymapPath(homeDir);
270
- const currentText = await readOptional(path);
271
- const entries = parseJsonArray(currentText, path);
272
- const migrated = entries.map(migrateReviewKeymapEntry);
273
- const alreadyBound = migrated.some((entry) => Object.values(entry.bindings ?? {}).some((action) => Array.isArray(action) && action[0] === "task::Spawn" && bindsReviewTask(action[1])));
274
- const next = alreadyBound ? migrated : [
275
- ...migrated,
276
- {
277
- context: "Workspace",
278
- bindings: { [REVIEW_KEYBINDING]: ["task::Spawn", { task_name: ZED_LOCAL_REVIEW_TASK_NAME }] }
279
- }
280
- ];
281
- const changed = JSON.stringify(entries) !== JSON.stringify(next);
282
- if (changed)
283
- await writeJson(path, next);
284
- return { path, changed, existed: currentText !== undefined };
285
- }
286
- function zedReviewTaskName(command) {
287
- if (command.some((value, index) => value === "plan" && command[index + 1] === "annotate")) {
288
- return ZED_PLAN_ANNOTATE_TASK_NAME;
289
- }
290
- return command[0] === "tuicr" && command[1] === "pr" ? ZED_PR_REVIEW_TASK_NAME : ZED_LOCAL_REVIEW_TASK_NAME;
291
- }
292
- var LOCAL_REVIEW_SCRIPT = `set -eu
293
- remote=$(git remote get-url origin 2>/dev/null || true)
294
- branch=$(git branch --show-current)
295
- base=
296
- case "$remote" in
297
- *github.com*)
298
- base=$(gh pr list --head "$branch" --state open --limit 100 --json baseRefName,createdAt --jq 'sort_by(.createdAt) | reverse | .[0].baseRefName // empty' 2>/dev/null || true)
299
- [ -n "$base" ] || base=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || true)
300
- ;;
301
- *gitlab*)
302
- mr=$(glab mr list --source-branch "$branch" --order created_at --sort desc --per-page 100 --output json 2>/dev/null || true)
303
- base=$(printf '%s' "$mr" | node -e 'let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{try{let a=JSON.parse(s);process.stdout.write(a[0]?.target_branch||"")}catch{}})')
304
- [ -n "$base" ] || base=$(glab repo view --output json 2>/dev/null | node -e 'let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).default_branch||"")}catch{}})' || true)
305
- ;;
306
- esac
307
- [ -n "$base" ] || { echo 'diffpi: could not resolve a GitHub/GitLab review base' >&2; exit 1; }
308
- exec tuicr -w -r "$base..HEAD"`;
309
- var PR_REVIEW_SCRIPT = `set -eu
310
- remote=$(git remote get-url origin 2>/dev/null || true)
311
- branch=$(git branch --show-current)
312
- case "$remote" in
313
- *github.com*) number=$(gh pr list --head "$branch" --state open --limit 100 --json number,createdAt --jq 'sort_by(.createdAt) | reverse | .[0].number // empty' 2>/dev/null || true) ;;
314
- *gitlab*) number=$(glab mr list --source-branch "$branch" --order created_at --sort desc --per-page 100 --output json 2>/dev/null | node -e 'let s=""; process.stdin.on("data",d=>s+=d).on("end",()=>{try{let a=JSON.parse(s);process.stdout.write(String(a[0]?.iid||a[0]?.number||""))}catch{}})' || true) ;;
315
- *) number= ;;
316
- esac
317
- [ -n "$number" ] || { echo 'diffpi: could not resolve the current GitHub PR or GitLab MR' >&2; exit 1; }
318
- exec tuicr pr "$number"`;
319
- function staticReviewTask(label, script) {
320
- return {
321
- label,
322
- command: "sh",
323
- args: ["-lc", script],
324
- cwd: "$ZED_WORKTREE_ROOT",
325
- use_new_terminal: true,
326
- reveal: "always",
327
- reveal_target: "center"
328
- };
329
- }
330
- function localReviewTask() {
331
- return staticReviewTask(ZED_LOCAL_REVIEW_TASK_NAME, LOCAL_REVIEW_SCRIPT);
332
- }
333
- function prReviewTask() {
334
- return staticReviewTask(ZED_PR_REVIEW_TASK_NAME, PR_REVIEW_SCRIPT);
335
- }
336
- function bindsReviewTask(payload) {
337
- return typeof payload === "object" && payload !== null && payload.task_name === ZED_LOCAL_REVIEW_TASK_NAME;
338
- }
339
- function migrateReviewKeymapEntry(entry) {
340
- const bindings = Object.fromEntries(Object.entries(entry.bindings ?? {}).map(([key, action]) => {
341
- if (Array.isArray(action) && action[0] === "task::Spawn" && typeof action[1] === "object" && action[1] !== null && action[1].task_name === LEGACY_ZED_REVIEW_TASK_NAME) {
342
- return [key, ["task::Spawn", { ...action[1], task_name: ZED_LOCAL_REVIEW_TASK_NAME }]];
343
- }
344
- return [key, action];
345
- }));
346
- return entry.bindings ? { ...entry, bindings } : entry;
347
- }
348
- async function readOptional(path) {
349
- try {
350
- return await readFile2(path, "utf8");
351
- } catch (error) {
352
- if (error instanceof Error && "code" in error && error.code === "ENOENT")
353
- return;
354
- throw error;
355
- }
356
- }
357
- function parseJsonArray(content, path) {
358
- if (!content?.trim())
359
- return [];
360
- let value;
361
- try {
362
- value = JSON.parse(content);
363
- } catch {
364
- throw new Error(`Cannot safely edit ${path}: not strict JSON (it may contain JSONC comments).`);
365
- }
366
- if (!Array.isArray(value))
367
- throw new Error(`Expected a JSON array in ${path}.`);
368
- return value;
369
- }
370
- async function writeJson(path, value) {
371
- await mkdir(dirname2(path), { recursive: true });
372
- await writeFile(path, `${JSON.stringify(value, null, 2)}
373
- `, "utf8");
374
- }
375
-
376
181
  // src/environment.ts
377
182
  function detectIde(env = process.env) {
378
183
  const program = (env.TERM_PROGRAM ?? "").toLowerCase();
@@ -450,35 +255,6 @@ async function openFileAdjacent(path, opts) {
450
255
  }
451
256
  return { launched: false, via: "print", command: path, reason: "No supported IDE or multiplexer was available." };
452
257
  }
453
- async function openInNewTab(command, opts) {
454
- const env = opts.env ?? process.env;
455
- const name = opts.name ?? "review";
456
- const printable = command.join(" ");
457
- const mux = detectMux(env);
458
- if (mux !== "none") {
459
- const opened = await openMuxTab(mux, command, opts.cwd, name, printable);
460
- if (opened)
461
- return opened;
462
- }
463
- if (detectIde(env) === "zed") {
464
- try {
465
- const taskName = zedReviewTaskName(command);
466
- if (taskName === ZED_PLAN_ANNOTATE_TASK_NAME)
467
- await ensureZedPlanTask(await packageVersion(), opts.homeDir);
468
- else
469
- await ensureZedReviewTask(opts.homeDir, command);
470
- return {
471
- launched: false,
472
- configured: true,
473
- via: "zed-task",
474
- command: printable,
475
- taskName,
476
- instruction: `Run the Zed task "${taskName}".`
477
- };
478
- } catch {}
479
- }
480
- return { launched: false, via: "print", command: printable };
481
- }
482
258
  function screenWindowArgs(command, cwd, name) {
483
259
  return ["-X", "screen", "-t", name, "sh", "-lc", 'cd -- "$1" && shift && exec "$@"', "sh", cwd, ...command];
484
260
  }
@@ -496,17 +272,6 @@ async function resolveEditor(env) {
496
272
  return candidate;
497
273
  return;
498
274
  }
499
- async function packageVersion() {
500
- let value;
501
- try {
502
- value = JSON.parse(await readFile3(join5(resolveBundledAgentsDir(), "..", "package.json"), "utf8"));
503
- } catch (error) {
504
- throw new Error("Cannot parse the installed @difflab/pi package metadata.", { cause: error });
505
- }
506
- if (typeof value.version !== "string")
507
- throw new Error("Cannot resolve the installed @difflab/pi version.");
508
- return value.version;
509
- }
510
275
  async function openMuxTab(mux, command, cwd, name, printable, tmuxCommand) {
511
276
  if (mux === "zellij" && await findExecutable("zellij")) {
512
277
  const result = await run("zellij", ["action", "new-tab", "--cwd", cwd, "--name", name, "--", ...command]);
@@ -530,7 +295,7 @@ async function openMuxTab(mux, command, cwd, name, printable, tmuxCommand) {
530
295
  }
531
296
  // src/extensions/gitx.ts
532
297
  import { realpath } from "node:fs/promises";
533
- import { basename as basename2, isAbsolute, join as join6, resolve } from "node:path";
298
+ import { basename as basename2, isAbsolute, join as join3, resolve } from "node:path";
534
299
  async function gitToplevel(cwd) {
535
300
  const result = await run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
536
301
  const top = result.stdout.trim();
@@ -542,7 +307,7 @@ async function inspectGitRepository(cwd) {
542
307
  const remote = remoteResult.code === 0 ? remoteResult.stdout.trim() : "";
543
308
  const commonResult = await run("git", ["-C", root, "rev-parse", "--git-common-dir"]);
544
309
  const common = commonResult.stdout.trim();
545
- const commonPath = commonResult.code === 0 && common ? resolve(isAbsolute(common) ? common : join6(root, common)) : root;
310
+ const commonPath = commonResult.code === 0 && common ? resolve(isAbsolute(common) ? common : join3(root, common)) : root;
546
311
  const commonDir = await canonicalPath(commonPath);
547
312
  return {
548
313
  root,
@@ -879,9 +644,9 @@ function createForgeBackend(vcs) {
879
644
  var createVcsBackend = createForgeBackend;
880
645
  var createForge = createForgeBackend;
881
646
  // src/extensions/misex.ts
882
- import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
883
- import { homedir as homedir3 } from "node:os";
884
- import { basename as basename3, dirname as dirname3, join as join7 } from "node:path";
647
+ import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
648
+ import { homedir as homedir2 } from "node:os";
649
+ import { basename as basename3, dirname, join as join4 } from "node:path";
885
650
  var MISE_HOOK_START = "# >>> @difflab/pi mise >>>";
886
651
  var MISE_HOOK_END = "# <<< @difflab/pi mise <<<";
887
652
  var mise = {
@@ -892,11 +657,11 @@ var mise = {
892
657
  return run("mise", args, options);
893
658
  },
894
659
  async install(options = {}) {
895
- const homeDir = options.homeDir ?? homedir3();
660
+ const homeDir = options.homeDir ?? homedir2();
896
661
  const platform = options.platform ?? process.platform;
897
662
  if (platform === "win32")
898
663
  throw new Error("Automatic mise installation supports macOS and Linux only.");
899
- const installedPath = join7(homeDir, ".local", "bin", "mise");
664
+ const installedPath = join4(homeDir, ".local", "bin", "mise");
900
665
  if (options.dryRun)
901
666
  return installedPath;
902
667
  await runChecked("sh", ["-c", "curl -fsSL https://mise.run | sh"]);
@@ -906,7 +671,7 @@ var mise = {
906
671
  return executable;
907
672
  },
908
673
  async hookEnsure(executable, options = {}) {
909
- const homeDir = options.homeDir ?? homedir3();
674
+ const homeDir = options.homeDir ?? homedir2();
910
675
  const hook = getShellHook(basename3(options.shell ?? process.env.SHELL ?? ""), executable, homeDir);
911
676
  const current = await getOptionalFile(hook.path);
912
677
  if (current.includes(MISE_HOOK_START))
@@ -916,8 +681,8 @@ var mise = {
916
681
  const separator = current.length === 0 || current.endsWith(`
917
682
  `) ? "" : `
918
683
  `;
919
- await mkdir2(dirname3(hook.path), { recursive: true });
920
- await writeFile2(hook.path, `${current}${separator}${hook.content}`, "utf8");
684
+ await mkdir(dirname(hook.path), { recursive: true });
685
+ await writeFile(hook.path, `${current}${separator}${hook.content}`, "utf8");
921
686
  return { path: hook.path, changed: true, planned: false };
922
687
  },
923
688
  async toolCheckGlobal(executable, tool, minimumVersion) {
@@ -934,7 +699,7 @@ var mise = {
934
699
  async toolInstallLocal(executable, specification, cwd = process.cwd()) {
935
700
  await runChecked(executable, ["use", "--path", cwd, specification], { cwd });
936
701
  },
937
- async toolUpdateAllGlobal(executable, homeDir = homedir3()) {
702
+ async toolUpdateAllGlobal(executable, homeDir = homedir2()) {
938
703
  await runChecked(executable, ["upgrade"], { cwd: homeDir });
939
704
  }
940
705
  };
@@ -943,7 +708,7 @@ function getShellHook(shell, executable, homeDir) {
943
708
  switch (shell.toLowerCase()) {
944
709
  case "zsh":
945
710
  return {
946
- path: join7(homeDir, ".zshrc"),
711
+ path: join4(homeDir, ".zshrc"),
947
712
  content: `${MISE_HOOK_START}
948
713
  eval "$(${command} activate zsh)"
949
714
  ${MISE_HOOK_END}
@@ -951,7 +716,7 @@ ${MISE_HOOK_END}
951
716
  };
952
717
  case "fish":
953
718
  return {
954
- path: join7(homeDir, ".config", "fish", "config.fish"),
719
+ path: join4(homeDir, ".config", "fish", "config.fish"),
955
720
  content: `${MISE_HOOK_START}
956
721
  ${command} activate fish | source
957
722
  ${MISE_HOOK_END}
@@ -960,7 +725,7 @@ ${MISE_HOOK_END}
960
725
  case "nu":
961
726
  case "nushell":
962
727
  return {
963
- path: join7(homeDir, ".config", "nushell", "config.nu"),
728
+ path: join4(homeDir, ".config", "nushell", "config.nu"),
964
729
  content: `${MISE_HOOK_START}
965
730
  let mise_bin = ${command}
966
731
  let mise_path = $nu.default-config-dir | path join mise.nu
@@ -971,7 +736,7 @@ ${MISE_HOOK_END}
971
736
  };
972
737
  case "xonsh":
973
738
  return {
974
- path: join7(homeDir, ".xonshrc"),
739
+ path: join4(homeDir, ".xonshrc"),
975
740
  content: `${MISE_HOOK_START}
976
741
  execx($(${command} activate xonsh))
977
742
  ${MISE_HOOK_END}
@@ -979,7 +744,7 @@ ${MISE_HOOK_END}
979
744
  };
980
745
  case "elvish":
981
746
  return {
982
- path: join7(homeDir, ".config", "elvish", "rc.elv"),
747
+ path: join4(homeDir, ".config", "elvish", "rc.elv"),
983
748
  content: `${MISE_HOOK_START}
984
749
  var mise: = (ns [&])
985
750
  eval (${command} activate elvish | slurp) &ns=$mise: &on-end={|ns| set mise: = $ns }
@@ -990,7 +755,7 @@ ${MISE_HOOK_END}
990
755
  case "pwsh":
991
756
  case "powershell":
992
757
  return {
993
- path: join7(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
758
+ path: join4(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
994
759
  content: `${MISE_HOOK_START}
995
760
  (& ${command} activate pwsh) | Out-String | Invoke-Expression
996
761
  ${MISE_HOOK_END}
@@ -999,7 +764,7 @@ ${MISE_HOOK_END}
999
764
  case "bash":
1000
765
  default:
1001
766
  return {
1002
- path: join7(homeDir, ".bashrc"),
767
+ path: join4(homeDir, ".bashrc"),
1003
768
  content: `${MISE_HOOK_START}
1004
769
  eval "$(${command} activate bash)"
1005
770
  ${MISE_HOOK_END}
@@ -1038,7 +803,7 @@ function isVersionAtLeast(version, minimumVersion) {
1038
803
  }
1039
804
  async function getOptionalFile(path) {
1040
805
  try {
1041
- return await readFile4(path, "utf8");
806
+ return await readFile2(path, "utf8");
1042
807
  } catch (error) {
1043
808
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
1044
809
  return "";
@@ -1130,9 +895,6 @@ function yymmdd(date = new Date) {
1130
895
  function reviewRecordName(target, date = new Date) {
1131
896
  return `${yymmdd(date)}-${reviewSlug(target) || "uncommitted"}`;
1132
897
  }
1133
- function isLocalResponse(body) {
1134
- return /^<!-- diffpi(?:-local-response:[A-Za-z0-9_-]+)? -->\n/m.test(body);
1135
- }
1136
898
  function dedupeFindings(findings) {
1137
899
  const rank = { BLOCKING: 3, CONSIDER: 2, NOTE: 1 };
1138
900
  const byKey = new Map;
@@ -1177,142 +939,6 @@ function renderCommentBody(finding) {
1177
939
  > **Reference:** ${finding.reference}` : "";
1178
940
  return `${prefix}${finding.body}${reference}`;
1179
941
  }
1180
- // src/review/review-markdown.ts
1181
- import { z as z4 } from "zod";
1182
- var reviewThreadRecordSchema = z4.object({
1183
- id: z4.string().min(1),
1184
- file: z4.string().nullable().optional().transform((value) => value ?? undefined),
1185
- line: z4.number().int().positive().nullable().optional().transform((value) => value ?? undefined),
1186
- body: z4.string(),
1187
- author: z4.string().optional(),
1188
- resolved: z4.boolean(),
1189
- addressed: z4.boolean().optional(),
1190
- question: z4.boolean(),
1191
- replies: z4.array(z4.string()).optional()
1192
- });
1193
- function renderReviewDoc(input) {
1194
- const anchored = dedupeFindings(input.findings).filter((finding) => finding.line > 0);
1195
- const lines = [`# Review: ${input.title}`, "", "## Metadata"];
1196
- if (input.number !== undefined)
1197
- lines.push(`- **PR/MR**: #${input.number}${input.url ? ` — ${input.url}` : ""}`);
1198
- if (input.author)
1199
- lines.push(`- **Author**: ${input.author}`);
1200
- if (input.model)
1201
- lines.push(`- **Review agent**: ${input.model}`);
1202
- if (input.headRef && input.baseRef)
1203
- lines.push(`- **Branch**: ${input.headRef} → ${input.baseRef}`);
1204
- if (input.additions !== undefined)
1205
- lines.push(`- **Stats**: +${input.additions} -${input.deletions ?? 0} across ${input.changedFiles ?? 0} files`);
1206
- lines.push(`- **Reviewed**: ${input.timestamp ?? new Date().toISOString()}`, "");
1207
- if (input.overallIssues.length > 0) {
1208
- lines.push("## Overall issues", "");
1209
- for (const issue of input.overallIssues)
1210
- lines.push(`- ${issue}`);
1211
- lines.push("");
1212
- }
1213
- lines.push("## Verification", "");
1214
- for (const gate of input.gates)
1215
- lines.push(`- ${gate.name}: ${gate.status} — ${gate.detail}`);
1216
- lines.push("");
1217
- if (input.notVerified.length > 0) {
1218
- lines.push("## What was NOT verified", "");
1219
- for (const item of input.notVerified)
1220
- lines.push(`- ${item}`);
1221
- lines.push("");
1222
- }
1223
- lines.push("## Inline Comments", "");
1224
- for (const finding of anchored) {
1225
- lines.push(`### ${finding.file}:${finding.line} — ${finding.severity}`, "", finding.body, "");
1226
- if (finding.reference)
1227
- lines.push(`> **Reference:** ${finding.reference}`, "");
1228
- lines.push("---", "");
1229
- }
1230
- return `${lines.join(`
1231
- `).trimEnd()}
1232
- `;
1233
- }
1234
- function renderThreadArtifact(title, target, threads, options = {}) {
1235
- const records = threads.map((thread) => ({
1236
- id: thread.id,
1237
- file: thread.file,
1238
- line: thread.line,
1239
- body: thread.body,
1240
- author: thread.author,
1241
- resolved: thread.resolved,
1242
- addressed: thread.addressed,
1243
- question: thread.question,
1244
- replies: thread.replies
1245
- }));
1246
- const payload = Buffer.from(JSON.stringify(records), "utf8").toString("base64url");
1247
- const lines = [
1248
- `<!-- diffpi-threads:${payload} -->`,
1249
- `# Review threads: ${title}`,
1250
- "",
1251
- "## Metadata",
1252
- "",
1253
- `- Target: ${target}`,
1254
- `- Pulled: ${options.timestamp ?? new Date().toISOString()}`
1255
- ];
1256
- if (options.number !== undefined)
1257
- lines.push(`- PR/MR: #${options.number}${options.url ? ` — ${options.url}` : ""}`);
1258
- lines.push("", "## Replies", "");
1259
- for (const thread of threads) {
1260
- const id = Buffer.from(thread.id, "utf8").toString("base64url");
1261
- lines.push(`### ${thread.file ?? "review"}:${thread.line ?? "n/a"} (${thread.id})`, "", `<!-- diffpi-reply-start:${id} -->`, thread.reply ?? "", `<!-- diffpi-reply-end:${id} -->`, "");
1262
- }
1263
- lines.push("## Source comments", "");
1264
- for (const thread of threads) {
1265
- lines.push(`### ${thread.file ?? "review"}:${thread.line ?? "n/a"} — ${thread.author ?? "unknown"}`, "", `Thread: ${thread.id}`, `Status: ${thread.resolved ? "resolved" : thread.addressed ? "addressed" : "open"}`, "", "**Source comment**", "", thread.body, "", "**Recorded response**", "", thread.reply?.trim() || "_No response recorded._", "", "---", "");
1266
- }
1267
- return `${lines.join(`
1268
- `).trimEnd()}
1269
- `;
1270
- }
1271
- function parseThreadArtifact(content) {
1272
- const payload = content.match(/^<!-- diffpi-threads:([A-Za-z0-9_-]+) -->$/m)?.[1];
1273
- if (!payload)
1274
- throw new Error("This file is not a Diffpi thread artifact.");
1275
- let threads;
1276
- try {
1277
- const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
1278
- threads = z4.array(reviewThreadRecordSchema).parse(decoded);
1279
- } catch {
1280
- throw new Error("Cannot parse the Diffpi thread artifact payload.");
1281
- }
1282
- const replies = content.split(/^## Source comments$/m, 1)[0] ?? "";
1283
- return threads.map((thread) => {
1284
- const id = Buffer.from(thread.id, "utf8").toString("base64url");
1285
- const startMarker = `<!-- diffpi-reply-start:${id} -->
1286
- `;
1287
- const endMarker = `
1288
- <!-- diffpi-reply-end:${id} -->`;
1289
- const start = replies.indexOf(startMarker);
1290
- const end = start < 0 ? -1 : replies.indexOf(endMarker, start + startMarker.length);
1291
- const reply = start >= 0 && end >= 0 ? replies.slice(start + startMarker.length, end).trim() : "";
1292
- return reply ? { ...thread, reply } : thread;
1293
- });
1294
- }
1295
- function upsertThreadReply(content, threadId, body, question, resolved) {
1296
- const threads = parseThreadArtifact(content);
1297
- const thread = threads.find((candidate) => candidate.id === threadId);
1298
- if (!thread)
1299
- throw new Error(`Review thread ${threadId} was not found in the local artifact.`);
1300
- thread.reply = body;
1301
- thread.addressed = true;
1302
- if (question !== undefined)
1303
- thread.question = question;
1304
- if (resolved !== undefined)
1305
- thread.resolved = resolved;
1306
- const title = content.match(/^# Review threads: (.+)$/m)?.[1] ?? "review";
1307
- const target = content.match(/^- Target: (.+)$/m)?.[1] ?? "local";
1308
- const timestamp = content.match(/^- Pulled: (.+)$/m)?.[1];
1309
- const pr = content.match(/^- PR\/MR: #(\d+)(?: — (.+))?$/m);
1310
- return renderThreadArtifact(title, target, threads, {
1311
- timestamp,
1312
- number: pr ? Number.parseInt(pr[1], 10) : undefined,
1313
- url: pr?.[2]
1314
- });
1315
- }
1316
942
  // src/review/github-review-backend.ts
1317
943
  function GitHubReviewBackend(vcs, number) {
1318
944
  const pendingReview = async () => {
@@ -1692,210 +1318,6 @@ function hasGitlabDraftNotes(input) {
1692
1318
  }
1693
1319
  }
1694
1320
 
1695
- // src/review/local-review-backend.ts
1696
- import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
1697
-
1698
- // src/extensions/tuicrx.ts
1699
- import { readFile as readFile5, realpath as realpath2 } from "node:fs/promises";
1700
- import { resolve as resolve2 } from "node:path";
1701
- async function tuicrAvailable() {
1702
- return Boolean(await findExecutable("tuicr"));
1703
- }
1704
- async function listSessions(repo = ".") {
1705
- await requireTuicr();
1706
- const result = await run("tuicr", ["review", "list", "--repo", repo]);
1707
- if (result.code !== 0 || !result.stdout.trim())
1708
- return [];
1709
- try {
1710
- const raw = JSON.parse(result.stdout);
1711
- return raw.map((entry) => ({
1712
- slug: entry.slug,
1713
- kind: entry.kind,
1714
- path: entry.path,
1715
- updatedAt: entry.updated_at,
1716
- commentCount: entry.comment_count,
1717
- anchor: entry.anchor,
1718
- active: entry.active
1719
- }));
1720
- } catch {
1721
- throw new Error("Cannot parse tuicr review list output.");
1722
- }
1723
- }
1724
- async function addComment(session, body, opts = {}) {
1725
- await requireTuicr();
1726
- const args = ["review", "add", "--session", session, body];
1727
- if (opts.targetFile)
1728
- args.push("--target-file", opts.targetFile);
1729
- if (opts.line !== undefined)
1730
- args.push("--line", String(opts.line));
1731
- if (opts.side)
1732
- args.push("--side", opts.side);
1733
- if (opts.username)
1734
- args.push("--username", opts.username);
1735
- await runChecked("tuicr", args);
1736
- }
1737
- async function launch(cwd, pr, localBase) {
1738
- let command;
1739
- if (pr !== undefined)
1740
- command = ["tuicr", "pr", String(pr)];
1741
- else if (localBase)
1742
- command = ["tuicr", "-w", "-r", `${localBase}..HEAD`];
1743
- else
1744
- throw new Error("Cannot launch a local tuicr review: no base branch could be determined.");
1745
- if (!await tuicrAvailable()) {
1746
- return {
1747
- launched: false,
1748
- via: "print",
1749
- command: command.join(" "),
1750
- reason: "tuicr is not installed or is not available on PATH.",
1751
- instruction: `Install tuicr, then run: ${command.join(" ")}`
1752
- };
1753
- }
1754
- const name = diffpiLaunchName(cwd, pr === undefined ? "local review" : `PR #${pr}`);
1755
- return openInNewTab(command, { cwd, name });
1756
- }
1757
- async function resolveSession(cwd, branch) {
1758
- const sessions = await listSessions(cwd);
1759
- return findMatchingSession(sessions, cwd, branch);
1760
- }
1761
- async function resolveReviewSession(cwd, target) {
1762
- if (!target.workingTree && target.owner && target.repo && target.number !== undefined) {
1763
- return resolvePrSession(cwd, target.owner, target.repo, target.number);
1764
- }
1765
- return resolveSession(cwd, target.branch);
1766
- }
1767
- async function resolvePrSession(cwd, owner, repo, number) {
1768
- const coordinate = `${owner}/${repo}`.toLowerCase();
1769
- const sessions = await listSessions(cwd);
1770
- return sessions.find((session) => {
1771
- const slug = session.slug.toLowerCase();
1772
- return session.kind === "pr" && slug.includes(coordinate) && (slug.endsWith(`/pr/${number}`) || slug.endsWith(`/mr/${number}`));
1773
- });
1774
- }
1775
- async function findMatchingSession(sessions, cwd, branch) {
1776
- const repository = await canonicalPath2(await gitToplevel(cwd));
1777
- for (const session of sessions) {
1778
- if (session.kind !== "local")
1779
- continue;
1780
- try {
1781
- const data = await readSession(session.path);
1782
- if (data.branch_name !== branch || !data.repo_path)
1783
- continue;
1784
- if (await canonicalPath2(data.repo_path) === repository)
1785
- return session;
1786
- } catch {}
1787
- }
1788
- return;
1789
- }
1790
- async function readSession(path) {
1791
- const content = await readFile5(path, "utf8");
1792
- try {
1793
- return JSON.parse(content);
1794
- } catch {
1795
- throw new Error(`Cannot parse tuicr session JSON: ${path}`);
1796
- }
1797
- }
1798
- function toFindings(session, options = {}) {
1799
- const comments = [];
1800
- const include = (comment) => (!options.agentOnly || commentAuthor(comment)?.startsWith("Agent: ")) && (!options.excludeLocalResponses || !isLocalResponse(comment.content));
1801
- const publishableBody = (comment) => {
1802
- const author = commentAuthor(comment);
1803
- const route = author?.match(/^Agent:\s*(.+)$/)?.[1];
1804
- return route ? withRemoteProvenance(comment.content, route) : comment.content;
1805
- };
1806
- const bodyParts = (session.review_comments ?? []).flatMap((comment) => include(comment) ? [publishableBody(comment)] : []);
1807
- for (const [file, entry] of Object.entries(session.files ?? {})) {
1808
- for (const fileComment of entry.file_comments ?? []) {
1809
- if (!include(fileComment))
1810
- continue;
1811
- const comment = {
1812
- file,
1813
- body: fileComment.content,
1814
- ...fileComment.id ? { sourceCommentId: String(fileComment.id) } : {}
1815
- };
1816
- const author = commentAuthor(fileComment);
1817
- if (author)
1818
- comment.author = author;
1819
- comments.push(comment);
1820
- }
1821
- for (const [lineKey, lineComments] of Object.entries(entry.line_comments ?? {})) {
1822
- const line = Number.parseInt(lineKey, 10);
1823
- if (!Number.isFinite(line))
1824
- continue;
1825
- for (const lineComment of lineComments) {
1826
- if (!include(lineComment))
1827
- continue;
1828
- const comment = {
1829
- file,
1830
- line,
1831
- side: lineComment.side === "old" ? "LEFT" : "RIGHT",
1832
- body: lineComment.content,
1833
- ...lineComment.id ? { sourceCommentId: String(lineComment.id) } : {}
1834
- };
1835
- const author = commentAuthor(lineComment);
1836
- if (author)
1837
- comment.author = author;
1838
- comments.push(comment);
1839
- }
1840
- }
1841
- }
1842
- return { comments, body: bodyParts.join(`
1843
-
1844
- `) };
1845
- }
1846
- async function requireTuicr() {
1847
- if (!await tuicrAvailable())
1848
- throw new Error("tuicr is not installed or is not available on PATH.");
1849
- }
1850
- function commentAuthor(comment) {
1851
- return comment.username ?? comment.author;
1852
- }
1853
- async function canonicalPath2(path) {
1854
- try {
1855
- return await realpath2(path);
1856
- } catch {
1857
- return resolve2(path);
1858
- }
1859
- }
1860
-
1861
- // src/review/local-review-backend.ts
1862
- function LocalReviewBackend(options) {
1863
- return {
1864
- kind: "local",
1865
- async stage(draft) {
1866
- for (const comment of draft.comments) {
1867
- await addComment(options.session, comment.body, {
1868
- targetFile: comment.file,
1869
- line: comment.line,
1870
- side: comment.side === "LEFT" ? "old" : "new",
1871
- username: options.author
1872
- });
1873
- }
1874
- if (draft.body.trim())
1875
- await addComment(options.session, draft.body, { username: options.author });
1876
- },
1877
- async readDraft() {
1878
- return toFindings(await readSession(options.session), { excludeLocalResponses: true });
1879
- },
1880
- async listThreads() {
1881
- try {
1882
- return parseThreadArtifact(await readFile6(options.artifactPath, "utf8"));
1883
- } catch (error) {
1884
- if (error.code === "ENOENT")
1885
- return [];
1886
- throw error;
1887
- }
1888
- },
1889
- async reply(input) {
1890
- const content = await readFile6(options.artifactPath, "utf8");
1891
- await writeFile3(options.artifactPath, upsertThreadReply(content, input.threadId, input.body, input.question, input.resolve), "utf8");
1892
- },
1893
- async publish() {
1894
- throw new Error("Promote a local draft through a remote review backend before publishing it.");
1895
- }
1896
- };
1897
- }
1898
-
1899
1321
  // src/review/review-backend.ts
1900
1322
  function createRemoteReviewBackend(vcs, number) {
1901
1323
  if (vcs.provider === "github")
@@ -1904,35 +1326,32 @@ function createRemoteReviewBackend(vcs, number) {
1904
1326
  return GitLabReviewBackend(vcs, number);
1905
1327
  throw new Error("A remote review backend requires a GitHub or GitLab repository.");
1906
1328
  }
1907
- function createLocalReviewBackend(options) {
1908
- return LocalReviewBackend(options);
1909
- }
1910
1329
  // src/review/review-state.ts
1911
1330
  import { createHash as createHash2 } from "node:crypto";
1912
- import { readFile as readFile7, rename, writeFile as writeFile4 } from "node:fs/promises";
1913
- import { join as join9 } from "node:path";
1914
- import { z as z5 } from "zod";
1331
+ import { readFile as readFile3, rename, writeFile as writeFile2 } from "node:fs/promises";
1332
+ import { join as join6 } from "node:path";
1333
+ import { z as z4 } from "zod";
1915
1334
 
1916
1335
  // src/store.ts
1917
1336
  import { createHash } from "node:crypto";
1918
- import { lstat, mkdir as mkdir3, readlink, realpath as realpath3, symlink, unlink } from "node:fs/promises";
1919
- import { homedir as homedir4 } from "node:os";
1920
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join8, resolve as resolve3 } from "node:path";
1337
+ import { lstat, mkdir as mkdir2, readlink, realpath as realpath2, symlink, unlink } from "node:fs/promises";
1338
+ import { homedir as homedir3 } from "node:os";
1339
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
1921
1340
  var STORE_LINK = ".diffpi";
1922
- var LEGACY_STORE_LINK = join8(".pi", "diffpi");
1341
+ var LEGACY_STORE_LINK = join5(".pi", "diffpi");
1923
1342
  async function computeProjectSlug(cwd) {
1924
1343
  return projectSlug(await inspectGitRepository(cwd));
1925
1344
  }
1926
- function storeGlobalRoot(homeDir = homedir4()) {
1927
- return join8(homeDir, ".difflab", "diffpi", "projects");
1345
+ function storeGlobalRoot(homeDir = homedir3()) {
1346
+ return join5(homeDir, ".difflab", "diffpi", "projects");
1928
1347
  }
1929
- async function ensureStore(cwd, homeDir = homedir4()) {
1348
+ async function ensureStore(cwd, homeDir = homedir3()) {
1930
1349
  const repository = await inspectGitRepository(cwd);
1931
1350
  const root = repository.root;
1932
1351
  const slug = projectSlug(repository);
1933
- const dest = join8(storeGlobalRoot(homeDir), slug);
1934
- const link = join8(root, STORE_LINK);
1935
- await mkdir3(dest, { recursive: true });
1352
+ const dest = join5(storeGlobalRoot(homeDir), slug);
1353
+ const link = join5(root, STORE_LINK);
1354
+ await mkdir2(dest, { recursive: true });
1936
1355
  try {
1937
1356
  await assertStoreLink(link, dest);
1938
1357
  } catch (error) {
@@ -1940,29 +1359,29 @@ async function ensureStore(cwd, homeDir = homedir4()) {
1940
1359
  throw error;
1941
1360
  await symlink(dest, link);
1942
1361
  }
1943
- await removeLegacyStoreLink(join8(root, LEGACY_STORE_LINK), dest);
1362
+ await removeLegacyStoreLink(join5(root, LEGACY_STORE_LINK), dest);
1944
1363
  return { slug, root, dest, link, linked: true };
1945
1364
  }
1946
- async function storeDir(cwd, homeDir = homedir4()) {
1365
+ async function storeDir(cwd, homeDir = homedir3()) {
1947
1366
  const store = await ensureStore(cwd, homeDir);
1948
1367
  return store.dest;
1949
1368
  }
1950
- async function plansDir(cwd, homeDir = homedir4()) {
1369
+ async function plansDir(cwd, homeDir = homedir3()) {
1951
1370
  const store = await ensureStore(cwd, homeDir);
1952
- const dir = join8(store.link, "plan");
1953
- await mkdir3(dir, { recursive: true });
1371
+ const dir = join5(store.link, "plan");
1372
+ await mkdir2(dir, { recursive: true });
1954
1373
  return dir;
1955
1374
  }
1956
- async function reviewsDir(cwd, homeDir = homedir4()) {
1375
+ async function reviewsDir(cwd, homeDir = homedir3()) {
1957
1376
  const store = await ensureStore(cwd, homeDir);
1958
- const dir = join8(store.link, "review");
1959
- await mkdir3(dir, { recursive: true });
1377
+ const dir = join5(store.link, "review");
1378
+ await mkdir2(dir, { recursive: true });
1960
1379
  return dir;
1961
1380
  }
1962
- async function sessionsDir(cwd, homeDir = homedir4()) {
1381
+ async function sessionsDir(cwd, homeDir = homedir3()) {
1963
1382
  const store = await ensureStore(cwd, homeDir);
1964
- const dir = join8(store.link, "sessions");
1965
- await mkdir3(dir, { recursive: true });
1383
+ const dir = join5(store.link, "sessions");
1384
+ await mkdir2(dir, { recursive: true });
1966
1385
  return dir;
1967
1386
  }
1968
1387
  async function assertStoreLink(path, dest) {
@@ -1970,7 +1389,7 @@ async function assertStoreLink(path, dest) {
1970
1389
  if (!entry.isSymbolicLink())
1971
1390
  throw new Error(`${path} exists and is not a symlink.`);
1972
1391
  const target = await symlinkTarget(path);
1973
- if (target !== await canonicalPath3(dest))
1392
+ if (target !== await canonicalPath2(dest))
1974
1393
  throw new Error(`${path} points to ${target}, not ${dest}.`);
1975
1394
  }
1976
1395
  async function removeLegacyStoreLink(path, dest) {
@@ -1979,7 +1398,7 @@ async function removeLegacyStoreLink(path, dest) {
1979
1398
  if (!entry.isSymbolicLink())
1980
1399
  return;
1981
1400
  const target = await symlinkTarget(path);
1982
- if (target === await canonicalPath3(dest))
1401
+ if (target === await canonicalPath2(dest))
1983
1402
  await unlink(path);
1984
1403
  } catch (error) {
1985
1404
  if (error.code !== "ENOENT")
@@ -1988,13 +1407,13 @@ async function removeLegacyStoreLink(path, dest) {
1988
1407
  }
1989
1408
  async function symlinkTarget(path) {
1990
1409
  const target = await readlink(path);
1991
- return canonicalPath3(isAbsolute2(target) ? target : resolve3(dirname4(path), target));
1410
+ return canonicalPath2(isAbsolute2(target) ? target : resolve2(dirname2(path), target));
1992
1411
  }
1993
- async function canonicalPath3(path) {
1412
+ async function canonicalPath2(path) {
1994
1413
  try {
1995
- return await realpath3(path);
1414
+ return await realpath2(path);
1996
1415
  } catch {
1997
- return resolve3(path);
1416
+ return resolve2(path);
1998
1417
  }
1999
1418
  }
2000
1419
  function projectSlug(repository) {
@@ -2004,13 +1423,13 @@ function projectSlug(repository) {
2004
1423
  }
2005
1424
 
2006
1425
  // src/review/review-state.ts
2007
- var reviewPublicationStateSchema = z5.object({
2008
- target: z5.string().optional(),
2009
- bodies: z5.array(z5.string()).default([]),
2010
- comments: z5.array(z5.string()).default([]),
2011
- stagedComments: z5.array(z5.string()).default([]),
2012
- replies: z5.array(z5.string()).default([]),
2013
- overlayPath: z5.string().optional()
1426
+ var reviewPublicationStateSchema = z4.object({
1427
+ target: z4.string().optional(),
1428
+ bodies: z4.array(z4.string()).default([]),
1429
+ comments: z4.array(z4.string()).default([]),
1430
+ stagedComments: z4.array(z4.string()).default([]),
1431
+ replies: z4.array(z4.string()).default([]),
1432
+ overlayPath: z4.string().optional()
2014
1433
  });
2015
1434
  function reviewBodyFingerprint(body) {
2016
1435
  return digest(body);
@@ -2026,15 +1445,15 @@ function unpublishedReviewComments(comments, knownFingerprints) {
2026
1445
  }
2027
1446
  async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
2028
1447
  const target = `${vcs.provider}:${vcs.owner}/${vcs.repo}#${number}`;
2029
- const path = join9(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
1448
+ const path = join6(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
2030
1449
  try {
2031
- const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile7(path, "utf8")));
1450
+ const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile3(path, "utf8")));
2032
1451
  return { path, state: { ...parsed, target } };
2033
1452
  } catch (error) {
2034
1453
  if (error.code === "ENOENT") {
2035
1454
  return { path, state: { target, bodies: [], comments: [], stagedComments: [], replies: [] } };
2036
1455
  }
2037
- if (error instanceof SyntaxError || error instanceof z5.ZodError) {
1456
+ if (error instanceof SyntaxError || error instanceof z4.ZodError) {
2038
1457
  throw new Error(`Cannot parse review publication state: ${path}`);
2039
1458
  }
2040
1459
  throw error;
@@ -2042,7 +1461,7 @@ async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
2042
1461
  }
2043
1462
  async function saveReviewPublicationState(path, state) {
2044
1463
  const temp = `${path}.${process.pid}.tmp`;
2045
- await writeFile4(temp, `${JSON.stringify(state, null, 2)}
1464
+ await writeFile2(temp, `${JSON.stringify(state, null, 2)}
2046
1465
  `, "utf8");
2047
1466
  await rename(temp, path);
2048
1467
  }
@@ -2050,12 +1469,12 @@ function digest(value) {
2050
1469
  return createHash2("sha256").update(value).digest("hex");
2051
1470
  }
2052
1471
  // src/mcp.ts
2053
- import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
2054
- import { homedir as homedir5 } from "node:os";
2055
- import { dirname as dirname5, join as join10 } from "node:path";
1472
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
1473
+ import { homedir as homedir4 } from "node:os";
1474
+ import { dirname as dirname3, join as join7 } from "node:path";
2056
1475
  var mcp = {
2057
- globalConfigPath(homeDir = homedir5()) {
2058
- return join10(homeDir, ".config", "mcp", "mcp.json");
1476
+ globalConfigPath(homeDir = homedir4()) {
1477
+ return join7(homeDir, ".config", "mcp", "mcp.json");
2059
1478
  },
2060
1479
  async serversEnsure(servers, options = {}) {
2061
1480
  const path = options.path ?? mcp.globalConfigPath();
@@ -2068,8 +1487,8 @@ var mcp = {
2068
1487
  const next = { ...current, mcpServers: nextServers };
2069
1488
  const changed = JSON.stringify(current) !== JSON.stringify(next);
2070
1489
  if (changed && !options.dryRun) {
2071
- await mkdir4(dirname5(path), { recursive: true });
2072
- await writeFile5(path, `${JSON.stringify(next, null, 2)}
1490
+ await mkdir3(dirname3(path), { recursive: true });
1491
+ await writeFile3(path, `${JSON.stringify(next, null, 2)}
2073
1492
  `, "utf8");
2074
1493
  }
2075
1494
  return { path, changed, existed: currentText !== undefined, planned: changed && options.dryRun === true };
@@ -2098,7 +1517,7 @@ function getParsedConfig(content, path) {
2098
1517
  }
2099
1518
  async function getOptionalFile2(path) {
2100
1519
  try {
2101
- return await readFile8(path, "utf8");
1520
+ return await readFile4(path, "utf8");
2102
1521
  } catch (error) {
2103
1522
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
2104
1523
  return;
@@ -2108,361 +1527,10 @@ async function getOptionalFile2(path) {
2108
1527
  function isRecord(value) {
2109
1528
  return value !== null && typeof value === "object" && !Array.isArray(value);
2110
1529
  }
2111
- // src/modes.ts
2112
- import {
2113
- getAgentDir,
2114
- parseFrontmatter as parseFrontmatter2
2115
- } from "@earendil-works/pi-coding-agent";
2116
- import { readFile as readFile9 } from "node:fs/promises";
2117
- import { homedir as homedir6 } from "node:os";
2118
- import { basename as basename4, extname, join as join11 } from "node:path";
2119
- async function discoverAgentModes(options) {
2120
- const agentDir = options.agentDir ?? getAgentDir();
2121
- const homeDir = options.homeDir ?? homedir6();
2122
- const modes = new Map;
2123
- const diagnostics = [];
2124
- await loadAgentModes(options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR, "diffpi agent", modes, diagnostics);
2125
- await loadAgentModes(join11(agentDir, "agents"), "user agent", modes, diagnostics);
2126
- if (options.includeSkills) {
2127
- await loadSkillModes(join11(homeDir, ".agents", "skills"), "user skill", modes, diagnostics);
2128
- await loadSkillModes(join11(agentDir, "skills"), "pi user skill", modes, diagnostics);
2129
- }
2130
- if (options.projectTrusted === true) {
2131
- if (options.includeSkills) {
2132
- await loadSkillModes(join11(options.cwd, ".agents", "skills"), "project skill", modes, diagnostics);
2133
- await loadSkillModes(join11(options.cwd, ".pi", "skills"), "pi project skill", modes, diagnostics);
2134
- }
2135
- await loadAgentModes(join11(options.cwd, ".agents", "agents"), "project agent", modes, diagnostics);
2136
- await loadAgentModes(join11(options.cwd, ".pi", "agents"), "pi project agent", modes, diagnostics);
2137
- }
2138
- const userConfig = await loadDiffpiConfig({ homeDir });
2139
- const configuredModes = [...modes.values()].map((mode) => ({
2140
- ...mode,
2141
- modelPreferences: resolveAgentModelPreferences(mode.id, mode.modelPreferences, userConfig.config)
2142
- }));
2143
- return {
2144
- modes: configuredModes.sort((left, right) => left.id.localeCompare(right.id)),
2145
- diagnostics
2146
- };
2147
- }
2148
- function resolveAgentMode(modes, requested) {
2149
- const name = requested.trim();
2150
- if (!name)
2151
- return { ok: false, message: "Agent name is required." };
2152
- const exact = modes.find((mode) => mode.id === name);
2153
- if (exact)
2154
- return { ok: true, active: exact, message: `Active inline agent: ${exact.id}.` };
2155
- const lowerName = name.toLowerCase();
2156
- const matches = modes.filter((mode) => mode.id.toLowerCase() === lowerName);
2157
- if (matches.length === 1) {
2158
- const active = matches[0];
2159
- return { ok: true, active, message: `Active inline agent: ${active.id}.` };
2160
- }
2161
- if (matches.length > 1) {
2162
- return {
2163
- ok: false,
2164
- message: `Inline agent "${name}" is ambiguous. Use one of: ${matches.map((mode) => mode.id).join(", ")}.`
2165
- };
2166
- }
2167
- return { ok: false, message: `Unknown inline agent "${name}". Run /skill:mode or diffpi_modes_list.` };
2168
- }
2169
- function createModeController(pi, options = {}) {
2170
- let active;
2171
- let baseline;
2172
- const updateStatus = (ctx) => {
2173
- const model = ctx.model ? ` · ${ctx.model.provider}/${ctx.model.id}` : "";
2174
- const label = active?.label.toUpperCase() ?? "DEFAULT";
2175
- const badge = `AGENT ${label}${model}`;
2176
- ctx.ui.setStatus(MODE_STATUS_KEY, undefined);
2177
- ctx.ui.setWidget?.(MODE_WIDGET_KEY, [badge], { placement: "belowEditor" });
2178
- };
2179
- const list = (ctx, listOptions = {}) => discoverAgentModes({
2180
- cwd: ctx.cwd,
2181
- agentDir: options.agentDir,
2182
- bundledAgentsDir: options.bundledAgentsDir,
2183
- homeDir: options.homeDir,
2184
- projectTrusted: ctx.isProjectTrusted(),
2185
- includeSkills: listOptions.includeSkills
2186
- });
2187
- return {
2188
- list,
2189
- async set(agent, ctx) {
2190
- const catalog = await list(ctx, { includeSkills: agent.includes(":") });
2191
- const result = resolveAgentMode(catalog.modes, agent);
2192
- if (!result.ok || !result.active)
2193
- return result;
2194
- const previousActive = active;
2195
- const previousBaseline = baseline;
2196
- baseline ??= captureRuntime(pi, ctx);
2197
- if (active && baseline)
2198
- await restoreRuntime(pi, baseline, ctx);
2199
- active = result.active;
2200
- try {
2201
- const runtimeMessage = await applyModeRuntime(pi, active, ctx);
2202
- pi.appendEntry(MODE_STATE_ENTRY, { active, baseline });
2203
- updateStatus(ctx);
2204
- return { ...result, message: `${result.message} ${runtimeMessage}` };
2205
- } catch (error) {
2206
- await restoreRuntime(pi, baseline, ctx);
2207
- active = undefined;
2208
- baseline = undefined;
2209
- if (previousActive && previousBaseline) {
2210
- try {
2211
- active = previousActive;
2212
- baseline = previousBaseline;
2213
- await applyModeRuntime(pi, active, ctx);
2214
- } catch {
2215
- active = undefined;
2216
- baseline = undefined;
2217
- }
2218
- }
2219
- if (!active)
2220
- pi.appendEntry(MODE_STATE_ENTRY, { active: null });
2221
- updateStatus(ctx);
2222
- return { ok: false, message: error instanceof Error ? error.message : String(error) };
2223
- }
2224
- },
2225
- async unset(ctx) {
2226
- if (!active)
2227
- return { ok: true, message: "Inline agent is already clear." };
2228
- if (baseline)
2229
- await restoreRuntime(pi, baseline, ctx);
2230
- active = undefined;
2231
- pi.appendEntry(MODE_STATE_ENTRY, { active: null });
2232
- baseline = undefined;
2233
- updateStatus(ctx);
2234
- return { ok: true, message: "Inline agent cleared. The previous model, thinking, tools, and prompt resume." };
2235
- },
2236
- async restore(ctx) {
2237
- const previousActive = active;
2238
- const previousBaseline = baseline;
2239
- const entry = [...ctx.sessionManager.getBranch()].reverse().find((candidate) => candidate.type === "custom" && candidate.customType === MODE_STATE_ENTRY);
2240
- const restored = entry?.data?.active;
2241
- const restoredBaseline = entry?.data?.baseline;
2242
- if (isAgentModeSnapshot(restored)) {
2243
- const catalog = await list(ctx, { includeSkills: restored.id.includes(":") });
2244
- const current = catalog.modes.find((mode) => mode.id === restored.id);
2245
- active = current ?? normalizeAgentModeSnapshot(restored);
2246
- baseline = isModeBaseline(restoredBaseline) ? restoredBaseline : previousBaseline;
2247
- try {
2248
- await applyModeRuntime(pi, active, ctx);
2249
- } catch (error) {
2250
- const fallbackBaseline = previousBaseline ?? (isModeBaseline(restoredBaseline) ? restoredBaseline : undefined);
2251
- if (fallbackBaseline)
2252
- await restoreRuntime(pi, fallbackBaseline, ctx);
2253
- active = undefined;
2254
- baseline = undefined;
2255
- throw error;
2256
- }
2257
- } else {
2258
- if (previousActive && previousBaseline)
2259
- pi.setActiveTools(previousBaseline.tools);
2260
- active = undefined;
2261
- baseline = undefined;
2262
- }
2263
- updateStatus(ctx);
2264
- },
2265
- refresh(ctx) {
2266
- updateStatus(ctx);
2267
- },
2268
- apply(systemPrompt) {
2269
- if (!active)
2270
- return systemPrompt;
2271
- if (active.promptStrategy === "replace")
2272
- return active.systemPrompt;
2273
- return `${systemPrompt}
2274
-
2275
- ## Active inline agent: ${active.label}
2276
-
2277
- ${active.systemPrompt}`;
2278
- },
2279
- getActive() {
2280
- return active;
2281
- },
2282
- getStatus(ctx) {
2283
- return {
2284
- active: active?.id,
2285
- model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
2286
- thinkingLevel: pi.getThinkingLevel(),
2287
- activeTools: pi.getActiveTools(),
2288
- capabilityError: active ? validateModeRuntime(pi, active, ctx) : undefined
2289
- };
2290
- }
2291
- };
2292
- }
2293
- var MODE_STATE_ENTRY = "diffpi-mode-state";
2294
- var MODE_STATUS_KEY = "diffpi-mode";
2295
- var MODE_WIDGET_KEY = "diffpi-mode-badge";
2296
- var MODE_CONTROL_TOOLS = [
2297
- "ask_user_question",
2298
- "diffpi_modes_list",
2299
- "diffpi_modes_set",
2300
- "diffpi_modes_unset",
2301
- "diffpi_modes_status"
2302
- ];
2303
- var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
2304
- var THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
2305
- async function applyModeRuntime(pi, mode, ctx) {
2306
- let selectedModel;
2307
- if (mode.modelPreferences.length > 0) {
2308
- const scoped = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : undefined;
2309
- const availableModels = scoped ?? ctx.modelRegistry.getAvailable();
2310
- for (const preference of mode.modelPreferences) {
2311
- const model = findPreferredModel(availableModels, preference);
2312
- if (model && await pi.setModel(model)) {
2313
- selectedModel = `${model.provider}/${model.id}`;
2314
- break;
2315
- }
2316
- }
2317
- }
2318
- if (mode.thinkingLevel)
2319
- pi.setThinkingLevel(mode.thinkingLevel);
2320
- if (mode.tools.length > 0) {
2321
- const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
2322
- const selectedTools = [...new Set([...mode.tools, ...MODE_CONTROL_TOOLS])].filter((tool) => availableTools.has(tool));
2323
- if (selectedTools.length > 0)
2324
- pi.setActiveTools(selectedTools);
2325
- }
2326
- const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
2327
- const unavailableTools = mode.requiredTools.filter((tool) => !availableTools.has(tool));
2328
- const activeTools = new Set(pi.getActiveTools());
2329
- const missingTools = mode.requiredTools.filter((tool) => !activeTools.has(tool));
2330
- const forbiddenTools = mode.forbiddenTools.filter((tool) => activeTools.has(tool));
2331
- if (unavailableTools.length > 0 || missingTools.length > 0 || forbiddenTools.length > 0)
2332
- throw new Error(`Profile preflight failed: missing required tools [${[...new Set([...unavailableTools, ...missingTools])].join(", ")}]${forbiddenTools.length > 0 ? `; forbidden tools active [${forbiddenTools.join(", ")}]` : ""}`);
2333
- if (mode.requireModel && !selectedModel)
2334
- throw new Error("Profile preflight failed: requested model was not selected.");
2335
- if (mode.requireThinking && (!mode.thinkingLevel || pi.getThinkingLevel() !== mode.thinkingLevel))
2336
- throw new Error(`Profile preflight failed: thinking level ${mode.thinkingLevel ?? "unset"} was not applied.`);
2337
- const parts = [];
2338
- if (mode.modelPreferences.length > 0) {
2339
- parts.push(selectedModel ? `Model: ${selectedModel}.` : "No preferred model was available; kept the current model.");
2340
- }
2341
- if (mode.thinkingLevel)
2342
- parts.push(`Thinking: ${mode.thinkingLevel}.`);
2343
- if (mode.tools.length > 0)
2344
- parts.push("Applied the profile tool set.");
2345
- return parts.join(" ") || "The profile changes the prompt only.";
2346
- }
2347
- function validateModeRuntime(pi, mode, ctx) {
2348
- const activeTools = new Set(pi.getActiveTools());
2349
- const missingTools = mode.requiredTools.filter((tool) => !activeTools.has(tool));
2350
- const forbiddenTools = mode.forbiddenTools.filter((tool) => activeTools.has(tool));
2351
- if (missingTools.length > 0 || forbiddenTools.length > 0)
2352
- return `Profile capability check failed: missing required tools [${missingTools.join(", ")}]${forbiddenTools.length > 0 ? `; forbidden tools active [${forbiddenTools.join(", ")}]` : ""}`;
2353
- if (mode.requireModel && mode.modelPreferences.length > 0) {
2354
- const actual = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "unset";
2355
- const available = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : ctx.modelRegistry.getAvailable();
2356
- const expected = mode.modelPreferences.map((preference) => findPreferredModel(available, preference)).find(Boolean);
2357
- if (!expected || `${expected.provider}/${expected.id}` !== actual)
2358
- return `Profile capability check failed: requested model was not active (actual ${actual}).`;
2359
- }
2360
- if (mode.requireThinking && (!mode.thinkingLevel || pi.getThinkingLevel() !== mode.thinkingLevel))
2361
- return `Profile capability check failed: thinking level ${mode.thinkingLevel ?? "unset"} was not applied.`;
2362
- return;
2363
- }
2364
- async function restoreRuntime(pi, state, ctx) {
2365
- if (state.model) {
2366
- const model = ctx.modelRegistry.find(state.model.provider, state.model.id);
2367
- if (model)
2368
- await pi.setModel(model);
2369
- }
2370
- pi.setThinkingLevel(state.thinkingLevel);
2371
- pi.setActiveTools(state.tools);
2372
- const restoredTools = pi.getActiveTools();
2373
- if (state.tools.some((tool) => !restoredTools.includes(tool)))
2374
- throw new Error("Failed to restore the previous active tool set.");
2375
- }
2376
- function captureRuntime(pi, ctx) {
2377
- return {
2378
- model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
2379
- thinkingLevel: pi.getThinkingLevel(),
2380
- tools: pi.getActiveTools()
2381
- };
2382
- }
2383
- async function loadSkillModes(skillsDir, source, modes, diagnostics) {
2384
- const entries = await readDirectoryIfExists(skillsDir);
2385
- for (const entry of entries.filter((item) => item.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
2386
- await loadAgentModes(join11(skillsDir, entry.name, "agents"), `${source} ${entry.name}`, modes, diagnostics, entry.name);
2387
- }
2388
- }
2389
- async function loadAgentModes(directory, source, modes, diagnostics, skillName) {
2390
- const entries = await readDirectoryIfExists(directory);
2391
- for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
2392
- const path = join11(directory, entry.name);
2393
- try {
2394
- const content = await readFile9(path, "utf8");
2395
- const { frontmatter, body } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
2396
- if (frontmatter.enabled === false || frontmatter.inline === false)
2397
- continue;
2398
- const name = getFrontmatterText(frontmatter.name) ?? basename4(path, extname(path));
2399
- const systemPrompt = body.trim();
2400
- if (!name || name.includes(":") || !systemPrompt) {
2401
- diagnostics.push(`Skipped ${path}: agent name must not contain ":" and prompt body is required.`);
2402
- continue;
2403
- }
2404
- const id = skillName ? `${skillName}:${name}` : name;
2405
- modes.set(id, {
2406
- id,
2407
- label: getFrontmatterText(frontmatter.display_name) ?? name,
2408
- description: getFrontmatterText(frontmatter.description) ?? `Inline agent from ${basename4(path)}`,
2409
- systemPrompt,
2410
- promptStrategy: frontmatter.prompt_mode === "append" ? "append" : "replace",
2411
- modelPreferences: [
2412
- ...getFrontmatterList(frontmatter.model),
2413
- ...getFrontmatterList(frontmatter.model_fallbacks)
2414
- ],
2415
- thinkingLevel: getThinkingLevel(frontmatter.thinking),
2416
- requiredTools: getFrontmatterList(frontmatter.required_tools),
2417
- forbiddenTools: getFrontmatterList(frontmatter.forbidden_tools),
2418
- requireModel: frontmatter.required_model === true,
2419
- requireThinking: frontmatter.required_thinking === true,
2420
- tools: getFrontmatterList(frontmatter.tools),
2421
- source,
2422
- sourcePath: path
2423
- });
2424
- } catch (error) {
2425
- diagnostics.push(`Skipped ${path}: ${error instanceof Error ? error.message : String(error)}`);
2426
- }
2427
- }
2428
- }
2429
- function getFrontmatterText(value) {
2430
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
2431
- }
2432
- function getFrontmatterList(value) {
2433
- const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
2434
- return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
2435
- }
2436
- function getThinkingLevel(value) {
2437
- const level = getFrontmatterText(value);
2438
- return level && THINKING_LEVELS.has(level) ? level : undefined;
2439
- }
2440
- function normalizeAgentModeSnapshot(value) {
2441
- return {
2442
- ...value,
2443
- requiredTools: Array.isArray(value.requiredTools) ? value.requiredTools : [],
2444
- forbiddenTools: Array.isArray(value.forbiddenTools) ? value.forbiddenTools : [],
2445
- requireModel: value.requireModel === true,
2446
- requireThinking: value.requireThinking === true
2447
- };
2448
- }
2449
- function isAgentModeSnapshot(value) {
2450
- if (!value || typeof value !== "object")
2451
- return false;
2452
- const candidate = value;
2453
- return typeof candidate.id === "string" && typeof candidate.label === "string" && typeof candidate.description === "string" && typeof candidate.systemPrompt === "string" && (candidate.promptStrategy === "append" || candidate.promptStrategy === "replace") && Array.isArray(candidate.modelPreferences) && (candidate.thinkingLevel === undefined || THINKING_LEVELS.has(candidate.thinkingLevel)) && (candidate.requiredTools === undefined || Array.isArray(candidate.requiredTools)) && (candidate.forbiddenTools === undefined || Array.isArray(candidate.forbiddenTools)) && (candidate.requireModel === undefined || typeof candidate.requireModel === "boolean") && (candidate.requireThinking === undefined || typeof candidate.requireThinking === "boolean") && Array.isArray(candidate.tools) && typeof candidate.source === "string" && typeof candidate.sourcePath === "string";
2454
- }
2455
- function isModeBaseline(value) {
2456
- if (!value || typeof value !== "object")
2457
- return false;
2458
- const candidate = value;
2459
- const model = candidate.model;
2460
- return (model === undefined || typeof model.provider === "string" && typeof model.id === "string") && candidate.thinkingLevel !== undefined && THINKING_LEVELS.has(candidate.thinkingLevel) && Array.isArray(candidate.tools) && candidate.tools.every((tool) => typeof tool === "string");
2461
- }
2462
1530
  // src/pi.ts
2463
- import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
2464
- import { homedir as homedir7 } from "node:os";
2465
- import { dirname as dirname6, join as join12 } from "node:path";
1531
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
1532
+ import { homedir as homedir5 } from "node:os";
1533
+ import { dirname as dirname4, join as join8 } from "node:path";
2466
1534
  var pi = {
2467
1535
  executableCheck: findPiExecutable,
2468
1536
  packageList: listPiPackages,
@@ -2475,19 +1543,19 @@ var pi = {
2475
1543
  configEnsure: ensurePiConfig
2476
1544
  };
2477
1545
  async function ensurePiAgent(filename, content, agentDir = resolvePiAgentDir(), dryRun = false) {
2478
- const path = join12(agentDir, "agents", filename);
1546
+ const path = join8(agentDir, "agents", filename);
2479
1547
  const currentText = await readTextIfExists(path);
2480
1548
  const changed = currentText !== content;
2481
1549
  if (changed && !dryRun) {
2482
- await mkdir5(dirname6(path), { recursive: true });
2483
- await writeFile6(path, content, "utf8");
1550
+ await mkdir4(dirname4(path), { recursive: true });
1551
+ await writeFile4(path, content, "utf8");
2484
1552
  }
2485
1553
  return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
2486
1554
  }
2487
- async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir = join12(homedir7(), ".agents", "skills")) {
2488
- const roots = [join12(agentDir, "skills"), sharedSkillsDir];
1555
+ async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir = join8(homedir5(), ".agents", "skills")) {
1556
+ const roots = [join8(agentDir, "skills"), sharedSkillsDir];
2489
1557
  for (const root of roots) {
2490
- if (await readTextIfExists(join12(root, name, "SKILL.md")) !== undefined)
1558
+ if (await readTextIfExists(join8(root, name, "SKILL.md")) !== undefined)
2491
1559
  return true;
2492
1560
  }
2493
1561
  return false;
@@ -2516,8 +1584,8 @@ async function ensurePiConfig(path, update, dryRun = false) {
2516
1584
  const next = update(current);
2517
1585
  const changed = JSON.stringify(current) !== JSON.stringify(next);
2518
1586
  if (changed && !dryRun) {
2519
- await mkdir5(dirname6(path), { recursive: true });
2520
- await writeFile6(path, `${JSON.stringify(next, null, 2)}
1587
+ await mkdir4(dirname4(path), { recursive: true });
1588
+ await writeFile4(path, `${JSON.stringify(next, null, 2)}
2521
1589
  `, "utf8");
2522
1590
  }
2523
1591
  return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
@@ -2536,8 +1604,8 @@ function hasPiPackage(listOutput, source) {
2536
1604
  async function installPiPackage(executable, source) {
2537
1605
  await runChecked(executable, ["install", source]);
2538
1606
  }
2539
- function resolvePiAgentDir(homeDir = homedir7()) {
2540
- return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join12(process.env.XDG_CONFIG_HOME, "pi") : join12(homeDir, ".pi", "agent"));
1607
+ function resolvePiAgentDir(homeDir = homedir5()) {
1608
+ return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join8(process.env.XDG_CONFIG_HOME, "pi") : join8(homeDir, ".pi", "agent"));
2541
1609
  }
2542
1610
  function parseJsonObject(content, path) {
2543
1611
  if (!content?.trim())
@@ -2550,27 +1618,27 @@ function parseJsonObject(content, path) {
2550
1618
  throw new Error(`Expected valid JSON object in ${path}.`);
2551
1619
  }
2552
1620
  // src/plan/verify.ts
2553
- import { readFile as readFile10, readdir as readdir2, stat } from "node:fs/promises";
2554
- import { basename as basename5, dirname as dirname7, join as join13, resolve as resolve4 } from "node:path";
1621
+ import { lstat as lstat2, readFile as readFile5, readdir as readdir2, stat } from "node:fs/promises";
1622
+ import { basename as basename4, dirname as dirname5, join as join9, resolve as resolve3 } from "node:path";
2555
1623
  async function verifyLivePlan(requested, cwd = process.cwd()) {
2556
- const path = resolve4(cwd, requested);
1624
+ const path = resolve3(cwd, requested);
2557
1625
  let planPath = path;
2558
1626
  try {
2559
1627
  if ((await stat(path)).isDirectory())
2560
- planPath = join13(path, "PLAN.md");
1628
+ planPath = join9(path, "PLAN.md");
2561
1629
  } catch {}
2562
1630
  const issues = [];
2563
1631
  const briefPaths = [];
2564
- const planDir = dirname7(planPath);
2565
- if (basename5(planPath) !== "PLAN.md") {
1632
+ const planDir = dirname5(planPath);
1633
+ if (basename4(planPath) !== "PLAN.md") {
2566
1634
  addIssue(issues, planPath, 0, "path", "Select PLAN.md or its directory.");
2567
1635
  return { planPath, briefPaths, ok: false, issues };
2568
1636
  }
2569
- if (!/^\d{6}(?:-[a-z0-9]+)+$/.test(basename5(planDir)) || basename5(dirname7(planDir)) !== "plan" || basename5(dirname7(dirname7(planDir))) !== ".diffpi")
1637
+ if (!/^\d{6}(?:-[a-z0-9]+)+$/.test(basename4(planDir)) || basename4(dirname5(planDir)) !== "plan" || basename4(dirname5(dirname5(planDir))) !== ".diffpi")
2570
1638
  addIssue(issues, planPath, 0, "layout", "Place PLAN.md under .diffpi/plan/<YYMMDD[-ticket]-short-slug>/.");
2571
1639
  let source;
2572
1640
  try {
2573
- source = await readFile10(planPath, "utf8");
1641
+ source = await readFile5(planPath, "utf8");
2574
1642
  } catch {
2575
1643
  addIssue(issues, planPath, 0, "missing-plan", "PLAN.md is not readable.");
2576
1644
  return { planPath, briefPaths, ok: false, issues };
@@ -2584,7 +1652,7 @@ async function verifyLivePlan(requested, cwd = process.cwd()) {
2584
1652
  if (!planLines[line]?.startsWith(`### Phase ${index + 1}: `))
2585
1653
  addIssue(issues, planPath, line + 1, "phase-order", "Number phase headings from 1 without gaps.");
2586
1654
  }
2587
- const implementation = join13(planDir, "implementation");
1655
+ const implementation = join9(planDir, "implementation");
2588
1656
  let rootEntries = [];
2589
1657
  let briefEntries = [];
2590
1658
  try {
@@ -2592,19 +1660,28 @@ async function verifyLivePlan(requested, cwd = process.cwd()) {
2592
1660
  briefEntries = await readdir2(implementation);
2593
1661
  } catch {}
2594
1662
  for (const entry of rootEntries) {
2595
- if (entry !== "PLAN.md" && entry !== "implementation")
2596
- addIssue(issues, join13(planDir, entry), 0, "layout", "Only PLAN.md and implementation/ belong in the plan directory.");
1663
+ const directory = ["implementation", "revisions", "logs"].includes(entry);
1664
+ const logFile = entry === "logs.jsonl";
1665
+ let valid = entry === "PLAN.md";
1666
+ if (directory || logFile) {
1667
+ try {
1668
+ const details = await lstat2(join9(planDir, entry));
1669
+ valid = !details.isSymbolicLink() && (directory ? details.isDirectory() : details.isFile());
1670
+ } catch {}
1671
+ }
1672
+ if (!valid)
1673
+ addIssue(issues, join9(planDir, entry), 0, "layout", "Only PLAN.md, implementation/, revisions/, logs.jsonl, and historical logs/ belong in the plan directory.");
2597
1674
  }
2598
1675
  for (const entry of briefEntries) {
2599
1676
  const ordinal = /^phase-(\d+)\.md$/.exec(entry)?.[1];
2600
1677
  if (!ordinal || !Number(ordinal) || Number(ordinal) > phases.length || entry !== `phase-${Number(ordinal)}.md`)
2601
- addIssue(issues, join13(implementation, entry), 0, "extra-brief", "Only matching numbered phase briefs belong in implementation/.");
1678
+ addIssue(issues, join9(implementation, entry), 0, "extra-brief", "Only matching numbered phase briefs belong in implementation/.");
2602
1679
  }
2603
1680
  for (let index = 0;index < phases.length; index++) {
2604
- const brief = join13(implementation, `phase-${index + 1}.md`);
1681
+ const brief = join9(implementation, `phase-${index + 1}.md`);
2605
1682
  briefPaths.push(brief);
2606
1683
  try {
2607
- const lines = visibleLines(await readFile10(brief, "utf8"));
1684
+ const lines = visibleLines(await readFile5(brief, "utf8"));
2608
1685
  if (!/^# Phase \d+:\s*\S/.test(lines[0] ?? "") || !lines[0]?.startsWith(`# Phase ${index + 1}: `))
2609
1686
  addIssue(issues, brief, 1, "phase-title", `Start with # Phase ${index + 1}: <title>.`);
2610
1687
  checkHeadings(lines, brief, ["Objective", "Files Affected", "Tasks", "Implementation Constraints"], issues);
@@ -2636,20 +1713,32 @@ function checkHeadings(lines, file, names, issues) {
2636
1713
  addIssue(issues, file, found[0]?.line ?? 0, "headings", `Expected headings, in order: ${expected.join(", ")}.`);
2637
1714
  }
2638
1715
  // src/setup.ts
2639
- import { parseFrontmatter as parseFrontmatter3 } from "@earendil-works/pi-coding-agent";
2640
- import { readdir as readdir3, readFile as readFile11 } from "node:fs/promises";
2641
- import { homedir as homedir8 } from "node:os";
2642
- import { basename as basename6, join as join14 } from "node:path";
1716
+ import { parseFrontmatter as parseFrontmatter2 } from "@earendil-works/pi-coding-agent";
1717
+ import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
1718
+ import { homedir as homedir6 } from "node:os";
1719
+ import { basename as basename5, join as join11 } from "node:path";
1720
+
1721
+ // src/assets.ts
1722
+ import { existsSync } from "node:fs";
1723
+ import { dirname as dirname6, join as join10 } from "node:path";
1724
+ import { fileURLToPath } from "node:url";
1725
+ function resolveBundledAssetDir(name, moduleUrl = import.meta.url) {
1726
+ const moduleDir = dirname6(fileURLToPath(moduleUrl));
1727
+ const candidates = [join10(moduleDir, name), join10(moduleDir, "..", name), join10(moduleDir, "..", "..", name)];
1728
+ return candidates.find((path) => existsSync(path)) ?? candidates[1];
1729
+ }
1730
+ function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
1731
+ return resolveBundledAssetDir("agents", moduleUrl);
1732
+ }
1733
+ function resolveBundledTemplatesDir(moduleUrl = import.meta.url) {
1734
+ return resolveBundledAssetDir("templates", moduleUrl);
1735
+ }
1736
+
1737
+ // src/setup.ts
2643
1738
  var MISE_DEPENDENCIES = [
2644
1739
  { name: "node", tool: "node", spec: "node@22", minimumVersion: "22.19.0" },
2645
1740
  { name: "zellij", tool: "zellij", spec: "zellij@latest", minimumVersion: undefined },
2646
1741
  { name: "helix", tool: "helix", spec: "helix@latest", minimumVersion: undefined },
2647
- {
2648
- name: "tuicr",
2649
- tool: "github:agavra/tuicr",
2650
- spec: "github:agavra/tuicr@latest",
2651
- minimumVersion: undefined
2652
- },
2653
1742
  {
2654
1743
  name: "context-mode",
2655
1744
  tool: "npm:context-mode",
@@ -2672,14 +1761,14 @@ var PI_SKILL_SOURCES = [
2672
1761
  { repository: "cloudvoyant/codevoyant", skills: ["git"] }
2673
1762
  ];
2674
1763
  var MCP_ADAPTER_PACKAGE = "npm:pi-mcp-adapter";
2675
- var BUNDLED_AGENTS_DIR2 = resolveBundledAgentsDir();
1764
+ var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
2676
1765
  var FORGE_DEPENDENCIES = {
2677
1766
  github: { name: "gh", tool: "gh", spec: "gh@latest", minimumVersion: undefined },
2678
1767
  gitlab: { name: "glab", tool: "glab", spec: "glab@latest", minimumVersion: undefined }
2679
1768
  };
2680
1769
  async function ensureMise(options = {}) {
2681
- const homeDir = options.homeDir ?? homedir8();
2682
- const current = await mise.executableCheck() ?? await mise.executableCheck(join14(homeDir, ".local", "bin", "mise"));
1770
+ const homeDir = options.homeDir ?? homedir6();
1771
+ const current = await mise.executableCheck() ?? await mise.executableCheck(join11(homeDir, ".local", "bin", "mise"));
2683
1772
  if (current)
2684
1773
  return { executable: current, action: createSetupAction("mise", "ready", current) };
2685
1774
  reportProgress(options, "Installing mise");
@@ -2728,9 +1817,9 @@ async function ensureMiseDeps(miseExecutable, options = {}) {
2728
1817
  async function ensurePiPlugins(options = {}) {
2729
1818
  const actions = await ensurePiPackages(PI_PACKAGES, options);
2730
1819
  const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
2731
- const webSearch = await pi.configEnsure(join14(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
1820
+ const webSearch = await pi.configEnsure(join11(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
2732
1821
  actions.push(getConfigSetupAction("web search settings", webSearch));
2733
- const lsp = await pi.configEnsure(join14(agentDir, "pi-lsp.json"), (config) => ({
1822
+ const lsp = await pi.configEnsure(join11(agentDir, "pi-lsp.json"), (config) => ({
2734
1823
  ...config,
2735
1824
  progressive: { ...getRecord(config.progressive), enabled: true, inject: "none" }
2736
1825
  }), options.dryRun);
@@ -2739,13 +1828,13 @@ async function ensurePiPlugins(options = {}) {
2739
1828
  }
2740
1829
  async function ensurePiAgents(options = {}) {
2741
1830
  const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
2742
- const bundledAgentsDir = options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR2;
1831
+ const bundledAgentsDir = options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR;
2743
1832
  const userConfig = await loadDiffpiConfig({ homeDir: options.homeDir });
2744
1833
  const entries = (await readdir3(bundledAgentsDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.startsWith("diffpi-") && entry.name.endsWith(".md")).sort((left, right) => left.name.localeCompare(right.name));
2745
1834
  const actions = [];
2746
1835
  for (const entry of entries) {
2747
- const id = basename6(entry.name, ".md").replace(/^diffpi-/, "");
2748
- const source = await readFile11(join14(bundledAgentsDir, entry.name), "utf8");
1836
+ const id = basename5(entry.name, ".md").replace(/^diffpi-/, "");
1837
+ const source = await readFile6(join11(bundledAgentsDir, entry.name), "utf8");
2749
1838
  const content = materializeAgentModels(source, id, userConfig.config, options.availableModels);
2750
1839
  const result = await pi.agentEnsure(entry.name, content, agentDir, options.dryRun);
2751
1840
  actions.push(getConfigSetupAction(`pi agent ${id}`, result));
@@ -2754,7 +1843,7 @@ async function ensurePiAgents(options = {}) {
2754
1843
  }
2755
1844
  async function ensurePiSkills(miseExecutable, options = {}) {
2756
1845
  const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
2757
- const sharedSkillsDir = join14(options.homeDir ?? homedir8(), ".agents", "skills");
1846
+ const sharedSkillsDir = join11(options.homeDir ?? homedir6(), ".agents", "skills");
2758
1847
  const actions = [];
2759
1848
  for (const source of PI_SKILL_SOURCES) {
2760
1849
  const missing = [];
@@ -2822,8 +1911,6 @@ async function setupPi(options = {}) {
2822
1911
  actions.push(...await ensurePiAgents(options));
2823
1912
  actions.push(...await ensurePiSkills(miseResult.executable, options));
2824
1913
  actions.push(...await ensureMcpAdapters(miseResult.executable, options));
2825
- if (options.bindZedKey)
2826
- actions.push(...await ensureZedIntegration(options));
2827
1914
  return {
2828
1915
  actions,
2829
1916
  restartPi: setupRequiresRestart(actions)
@@ -2832,35 +1919,6 @@ async function setupPi(options = {}) {
2832
1919
  function setupRequiresRestart(actions) {
2833
1920
  return actions.some((item) => (item.status === "installed" || item.status === "updated") && (item.name.startsWith("pi package ") || item.name.startsWith("pi agent ") || item.name.startsWith("pi skill ") || item.name === "MCP configuration" || item.name === "web search settings" || item.name === "pi-lsp settings"));
2834
1921
  }
2835
- async function ensureZedIntegration(options = {}) {
2836
- if (options.dryRun) {
2837
- const actions = [
2838
- createSetupAction("Zed review tasks", "planned", "global static runtime-resolver tasks in tasks.json"),
2839
- createSetupAction("Zed plan task", "planned", "pinned package CLI task in tasks.json")
2840
- ];
2841
- if (options.bindZedKey)
2842
- actions.push(createSetupAction("Zed review keybinding", "planned", "keymap.json"));
2843
- return actions;
2844
- }
2845
- const actions = [];
2846
- try {
2847
- const tasks = await ensureZedReviewTask(options.homeDir);
2848
- actions.push(createSetupAction("Zed review tasks", tasks.changed ? "installed" : "ready", tasks.path));
2849
- const planTask = await ensureZedPlanTask(await packageVersion2(), options.homeDir);
2850
- actions.push(createSetupAction("Zed plan task", planTask.changed ? "installed" : "ready", planTask.path));
2851
- } catch (error) {
2852
- actions.push(createSetupAction("Zed review tasks", "skipped", error instanceof Error ? error.message : String(error)));
2853
- }
2854
- if (options.bindZedKey) {
2855
- try {
2856
- const key = await ensureZedReviewKeybinding(options.homeDir);
2857
- actions.push(createSetupAction("Zed review keybinding", key.changed ? "installed" : "ready", key.path));
2858
- } catch (error) {
2859
- actions.push(createSetupAction("Zed review keybinding", "skipped", error instanceof Error ? error.message : String(error)));
2860
- }
2861
- }
2862
- return actions;
2863
- }
2864
1922
  async function gitlabMcpHost(projectDir) {
2865
1923
  try {
2866
1924
  const vcs = await detectVcs(projectDir);
@@ -2870,7 +1928,7 @@ async function gitlabMcpHost(projectDir) {
2870
1928
  }
2871
1929
  }
2872
1930
  function materializeAgentModels(content, agentId, config, availableModels) {
2873
- const { frontmatter } = parseFrontmatter3(content.startsWith("\uFEFF") ? content.slice(1) : content);
1931
+ const { frontmatter } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
2874
1932
  const profilePreferences = [...getTextList(frontmatter.model), ...getTextList(frontmatter.model_fallbacks)];
2875
1933
  const preferences = resolveAgentModelPreferences(agentId, profilePreferences, config);
2876
1934
  let selectedIndex = availableModels === undefined && preferences.length > 0 ? 0 : -1;
@@ -2949,29 +2007,17 @@ function createSetupAction(name, status, detail) {
2949
2007
  function reportProgress(options, message) {
2950
2008
  options.onProgress?.(message);
2951
2009
  }
2952
- async function packageVersion2() {
2953
- const source = await readFile11(join14(resolveBundledAgentsDir(), "..", "package.json"), "utf8");
2954
- let value;
2955
- try {
2956
- value = JSON.parse(source);
2957
- } catch (error) {
2958
- throw new Error("Cannot parse the installed @difflab/pi package metadata.", { cause: error });
2959
- }
2960
- if (typeof value.version !== "string")
2961
- throw new Error("Cannot resolve the installed @difflab/pi version.");
2962
- return value.version;
2963
- }
2964
2010
  function getRecord(value) {
2965
2011
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
2966
2012
  }
2967
2013
  // src/templates.ts
2968
- import { readFile as readFile12 } from "node:fs/promises";
2969
- import { homedir as homedir9 } from "node:os";
2970
- import { join as join15, normalize } from "node:path";
2014
+ import { readFile as readFile7 } from "node:fs/promises";
2015
+ import { homedir as homedir7 } from "node:os";
2016
+ import { join as join12, normalize } from "node:path";
2971
2017
  async function loadTemplate(name, options = {}) {
2972
2018
  const relative = templateRelativePath(name);
2973
- const userPath = join15(options.homeDir ?? homedir9(), ".difflab", "diffpi", "templates", relative);
2974
- const bundledPath = join15(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
2019
+ const userPath = join12(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
2020
+ const bundledPath = join12(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
2975
2021
  const user = await readOptionalFile(userPath);
2976
2022
  if (user !== undefined)
2977
2023
  return { name, path: userPath, source: "user", content: user };
@@ -2992,7 +2038,7 @@ function templateRelativePath(name) {
2992
2038
  }
2993
2039
  async function readOptionalFile(path) {
2994
2040
  try {
2995
- return await readFile12(path, "utf8");
2041
+ return await readFile7(path, "utf8");
2996
2042
  } catch (error) {
2997
2043
  if (error.code === "ENOENT")
2998
2044
  return;
@@ -3003,19 +2049,12 @@ export {
3003
2049
  CONVENTIONAL_COMMIT,
3004
2050
  GitHubVcsBackend,
3005
2051
  GitLabVcsBackend,
3006
- ZED_LOCAL_REVIEW_TASK_NAME,
3007
- ZED_PLAN_ANNOTATE_TASK_NAME,
3008
- ZED_PR_REVIEW_TASK_NAME,
3009
- ZED_REVIEW_TASK_NAME,
3010
- addComment,
3011
2052
  assertGitHubMergeReady,
3012
2053
  assertReviewEventSupported,
3013
2054
  checkConventionalSubject,
3014
2055
  computeProjectSlug,
3015
2056
  createForge,
3016
2057
  createForgeBackend,
3017
- createLocalReviewBackend,
3018
- createModeController,
3019
2058
  createRemoteReviewBackend,
3020
2059
  createVcsBackend,
3021
2060
  dedupeFindings,
@@ -3025,7 +2064,6 @@ export {
3025
2064
  detectVcs,
3026
2065
  diffpiConfigPaths,
3027
2066
  diffpiLaunchName,
3028
- discoverAgentModes,
3029
2067
  ensureMcpAdapters,
3030
2068
  ensureMise,
3031
2069
  ensureMiseDeps,
@@ -3034,17 +2072,12 @@ export {
3034
2072
  ensurePiPlugins,
3035
2073
  ensurePiSkills,
3036
2074
  ensureStore,
3037
- ensureZedPlanTask,
3038
- ensureZedReviewKeybinding,
3039
- ensureZedReviewTask,
3040
2075
  findPreferredModel,
3041
2076
  findingSchema,
3042
2077
  findingsSchema,
3043
2078
  gitToplevel,
3044
2079
  githubReviewSubmissionEndpoint,
3045
2080
  hasGitlabDraftNotes,
3046
- launch,
3047
- listSessions,
3048
2081
  loadDiffpiConfig,
3049
2082
  loadReviewPublicationState,
3050
2083
  loadTemplate,
@@ -3052,21 +2085,12 @@ export {
3052
2085
  mcp,
3053
2086
  mise,
3054
2087
  openFileAdjacent,
3055
- openInNewTab,
3056
2088
  parseGitlabDiffRefs,
3057
2089
  parseRemote,
3058
- parseThreadArtifact,
3059
2090
  pi,
3060
2091
  plansDir,
3061
- readSession,
3062
- renderReviewDoc,
3063
2092
  renderTemplate,
3064
- renderThreadArtifact,
3065
- resolveAgentMode,
3066
2093
  resolveAgentModelPreferences,
3067
- resolvePrSession,
3068
- resolveReviewSession,
3069
- resolveSession,
3070
2094
  reviewBodyFingerprint,
3071
2095
  reviewCommentFingerprint,
3072
2096
  reviewRecordName,
@@ -3081,15 +2105,9 @@ export {
3081
2105
  storeDir,
3082
2106
  storeGlobalRoot,
3083
2107
  templateRelativePath,
3084
- toFindings,
3085
2108
  toReviewComments,
3086
- tuicrAvailable,
3087
2109
  unpublishedReviewComments,
3088
- upsertThreadReply,
3089
2110
  verifyLivePlan,
3090
2111
  withRemoteProvenance,
3091
- yymmdd,
3092
- zedKeymapPath,
3093
- zedReviewTaskName,
3094
- zedTasksPath
2112
+ yymmdd
3095
2113
  };