@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.
- package/README.md +22 -40
- package/agents/diffpi-copilot.md +2 -2
- package/agents/diffpi-orchestrator.md +9 -14
- package/agents/diffpi-plan-reviewer.md +4 -12
- package/agents/diffpi-planner.md +7 -13
- package/agents/diffpi-reviewer.md +7 -11
- package/agents/diffpi-tutor.md +2 -2
- package/agents/diffpi-worker.md +9 -12
- package/dist/commands/dispatch.d.ts +4 -0
- package/dist/commands/dispatch.d.ts.map +1 -0
- package/dist/commands/index.d.ts +1 -3
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/review.d.ts +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/environment.d.ts +0 -1
- package/dist/environment.d.ts.map +1 -1
- package/dist/extensions/index.js +1622 -3207
- package/dist/index.d.ts +5 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -1128
- package/dist/log.d.ts +6 -11
- package/dist/log.d.ts.map +1 -1
- package/dist/plan/verify.d.ts.map +1 -1
- package/dist/review/index.d.ts +2 -3
- package/dist/review/index.d.ts.map +1 -1
- package/dist/review/review-backend.d.ts +2 -3
- package/dist/review/review-backend.d.ts.map +1 -1
- package/dist/setup.d.ts +0 -2
- package/dist/setup.d.ts.map +1 -1
- package/dist/tools/ci.d.ts +1 -1
- package/dist/tools/doctor.d.ts +3 -0
- package/dist/tools/doctor.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -3
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1821 -3006
- package/dist/tools/log.d.ts.map +1 -1
- package/dist/tools/review.d.ts +1 -7
- package/dist/tools/review.d.ts.map +1 -1
- package/dist/tools/setup.d.ts.map +1 -1
- package/package.json +2 -6
- package/skills/diffpi-doctor/SKILL.md +10 -0
- package/skills/diffpi-setup/SKILL.md +4 -4
- package/skills/plan/SKILL.md +13 -24
- package/skills/plan/references/design-quality.md +3 -3
- package/skills/plan/references/workflows/finalize.md +10 -6
- package/skills/plan/references/workflows/go.md +27 -7
- package/skills/plan/references/workflows/help.md +19 -3
- package/skills/plan/references/workflows/init.md +23 -6
- package/skills/plan/references/workflows/inline-fallback.md +9 -0
- package/skills/plan/references/workflows/new.md +22 -7
- package/skills/plan/references/workflows/update.md +24 -6
- package/skills/plan/references/workflows/validate.md +31 -0
- package/skills/review/SKILL.md +15 -7
- package/skills/review/references/review-standards.md +9 -1
- package/skills/review/references/workflows/address.md +13 -6
- package/skills/review/references/workflows/auto.md +13 -5
- package/skills/review/references/workflows/complete.md +17 -4
- package/skills/review/references/workflows/edit.md +10 -2
- package/skills/review/references/workflows/foreground-fallback.md +7 -0
- package/skills/review/references/workflows/help.md +11 -10
- package/skills/review/references/workflows/merge.md +17 -2
- package/skills/review/references/workflows/new.md +18 -5
- package/skills/review/references/workflows/publish.md +17 -4
- package/skills/review/references/workflows/status.md +7 -2
- package/skills/review/templates/REVIEW.md +39 -0
- package/templates/plan/PLAN.md +9 -1
- package/templates/review/draft-pr.md +3 -9
- package/dist/cli/plan.d.ts +0 -17
- package/dist/cli/plan.d.ts.map +0 -1
- package/dist/cli.d.ts +0 -4
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2258
- package/dist/commands/mode.d.ts +0 -5
- package/dist/commands/mode.d.ts.map +0 -1
- package/dist/extensions/tuicrx.d.ts +0 -66
- package/dist/extensions/tuicrx.d.ts.map +0 -1
- package/dist/extensions/zedx.d.ts +0 -18
- package/dist/extensions/zedx.d.ts.map +0 -1
- package/dist/modes.d.ts +0 -75
- package/dist/modes.d.ts.map +0 -1
- package/dist/review/local-review-backend.d.ts +0 -3
- package/dist/review/local-review-backend.d.ts.map +0 -1
- package/dist/review/local-reviews.d.ts +0 -31
- package/dist/review/local-reviews.d.ts.map +0 -1
- package/dist/review/review-markdown.d.ts +0 -6
- package/dist/review/review-markdown.d.ts.map +0 -1
- package/dist/tools/modes.d.ts +0 -4
- package/dist/tools/modes.d.ts.map +0 -1
- package/skills/mode/SKILL.md +0 -38
- package/skills/plan/references/workflows/annotate.md +0 -7
- 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 {
|
|
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
|
|
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 =
|
|
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
|
|
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 :
|
|
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
|
|
883
|
-
import { homedir as
|
|
884
|
-
import { basename as basename3, dirname
|
|
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 ??
|
|
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 =
|
|
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 ??
|
|
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
|
|
920
|
-
await
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
1913
|
-
import { join as
|
|
1914
|
-
import { z as
|
|
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
|
|
1919
|
-
import { homedir as
|
|
1920
|
-
import { dirname as
|
|
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 =
|
|
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 =
|
|
1927
|
-
return
|
|
1345
|
+
function storeGlobalRoot(homeDir = homedir3()) {
|
|
1346
|
+
return join5(homeDir, ".difflab", "diffpi", "projects");
|
|
1928
1347
|
}
|
|
1929
|
-
async function ensureStore(cwd, homeDir =
|
|
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 =
|
|
1934
|
-
const link =
|
|
1935
|
-
await
|
|
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(
|
|
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 =
|
|
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 =
|
|
1369
|
+
async function plansDir(cwd, homeDir = homedir3()) {
|
|
1951
1370
|
const store = await ensureStore(cwd, homeDir);
|
|
1952
|
-
const dir =
|
|
1953
|
-
await
|
|
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 =
|
|
1375
|
+
async function reviewsDir(cwd, homeDir = homedir3()) {
|
|
1957
1376
|
const store = await ensureStore(cwd, homeDir);
|
|
1958
|
-
const dir =
|
|
1959
|
-
await
|
|
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 =
|
|
1381
|
+
async function sessionsDir(cwd, homeDir = homedir3()) {
|
|
1963
1382
|
const store = await ensureStore(cwd, homeDir);
|
|
1964
|
-
const dir =
|
|
1965
|
-
await
|
|
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
|
|
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
|
|
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
|
|
1410
|
+
return canonicalPath2(isAbsolute2(target) ? target : resolve2(dirname2(path), target));
|
|
1992
1411
|
}
|
|
1993
|
-
async function
|
|
1412
|
+
async function canonicalPath2(path) {
|
|
1994
1413
|
try {
|
|
1995
|
-
return await
|
|
1414
|
+
return await realpath2(path);
|
|
1996
1415
|
} catch {
|
|
1997
|
-
return
|
|
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 =
|
|
2008
|
-
target:
|
|
2009
|
-
bodies:
|
|
2010
|
-
comments:
|
|
2011
|
-
stagedComments:
|
|
2012
|
-
replies:
|
|
2013
|
-
overlayPath:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2054
|
-
import { homedir as
|
|
2055
|
-
import { dirname as
|
|
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 =
|
|
2058
|
-
return
|
|
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
|
|
2072
|
-
await
|
|
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
|
|
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
|
|
2464
|
-
import { homedir as
|
|
2465
|
-
import { dirname as
|
|
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 =
|
|
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
|
|
2483
|
-
await
|
|
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 =
|
|
2488
|
-
const roots = [
|
|
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(
|
|
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
|
|
2520
|
-
await
|
|
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 =
|
|
2540
|
-
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ?
|
|
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
|
|
2554
|
-
import { basename as
|
|
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 =
|
|
1624
|
+
const path = resolve3(cwd, requested);
|
|
2557
1625
|
let planPath = path;
|
|
2558
1626
|
try {
|
|
2559
1627
|
if ((await stat(path)).isDirectory())
|
|
2560
|
-
planPath =
|
|
1628
|
+
planPath = join9(path, "PLAN.md");
|
|
2561
1629
|
} catch {}
|
|
2562
1630
|
const issues = [];
|
|
2563
1631
|
const briefPaths = [];
|
|
2564
|
-
const planDir =
|
|
2565
|
-
if (
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
2596
|
-
|
|
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,
|
|
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 =
|
|
1681
|
+
const brief = join9(implementation, `phase-${index + 1}.md`);
|
|
2605
1682
|
briefPaths.push(brief);
|
|
2606
1683
|
try {
|
|
2607
|
-
const lines = visibleLines(await
|
|
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
|
|
2640
|
-
import { readdir as readdir3, readFile as
|
|
2641
|
-
import { homedir as
|
|
2642
|
-
import { basename as
|
|
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
|
|
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 ??
|
|
2682
|
-
const current = await mise.executableCheck() ?? await mise.executableCheck(
|
|
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(
|
|
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(
|
|
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 ??
|
|
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 =
|
|
2748
|
-
const source = await
|
|
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 =
|
|
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 } =
|
|
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
|
|
2969
|
-
import { homedir as
|
|
2970
|
-
import { join as
|
|
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 =
|
|
2974
|
-
const bundledPath =
|
|
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
|
|
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
|
};
|