@difflab/pi 0.7.1 → 0.7.2
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 +6 -12
- package/agents/diffpi-reviewer.md +6 -10
- package/agents/diffpi-tutor.md +2 -2
- package/agents/diffpi-worker.md +3 -6
- 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/extensions/index.js +365 -881
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -403
- package/dist/tools/index.d.ts +1 -3
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +95 -212
- package/package.json +2 -2
- package/skills/diffpi-setup/SKILL.md +1 -1
- package/skills/plan/SKILL.md +12 -22
- package/skills/plan/references/design-quality.md +3 -3
- package/skills/plan/references/workflows/annotate.md +9 -3
- package/skills/plan/references/workflows/finalize.md +10 -6
- package/skills/plan/references/workflows/go.md +25 -7
- package/skills/plan/references/workflows/help.md +20 -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 +22 -6
- package/skills/plan/references/workflows/validate.md +31 -0
- package/skills/review/SKILL.md +14 -6
- package/skills/review/references/workflows/address.md +19 -6
- package/skills/review/references/workflows/auto.md +21 -5
- package/skills/review/references/workflows/complete.md +17 -4
- package/skills/review/references/workflows/edit.md +8 -2
- package/skills/review/references/workflows/foreground-fallback.md +7 -0
- package/skills/review/references/workflows/help.md +22 -13
- package/skills/review/references/workflows/merge.md +17 -2
- package/skills/review/references/workflows/new.md +17 -5
- package/skills/review/references/workflows/open.md +8 -2
- package/skills/review/references/workflows/publish.md +17 -4
- package/skills/review/references/workflows/status.md +7 -2
- package/templates/plan/PLAN.md +1 -1
- package/dist/commands/mode.d.ts +0 -5
- package/dist/commands/mode.d.ts.map +0 -1
- package/dist/modes.d.ts +0 -75
- package/dist/modes.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/dist/extensions/index.js
CHANGED
|
@@ -13323,524 +13323,20 @@ function rpiv_ask_user_question_default(pi) {
|
|
|
13323
13323
|
registerAskUserQuestionReconciler(pi);
|
|
13324
13324
|
}
|
|
13325
13325
|
|
|
13326
|
-
// src/
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
13332
|
-
import { homedir as homedir3 } from "node:os";
|
|
13333
|
-
import { basename, extname, join as join5 } from "node:path";
|
|
13334
|
-
|
|
13335
|
-
// src/assets.ts
|
|
13336
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
13337
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
13338
|
-
import { fileURLToPath } from "node:url";
|
|
13339
|
-
function resolveBundledAssetDir(name, moduleUrl = import.meta.url) {
|
|
13340
|
-
const moduleDir = dirname2(fileURLToPath(moduleUrl));
|
|
13341
|
-
const candidates = [join3(moduleDir, name), join3(moduleDir, "..", name), join3(moduleDir, "..", "..", name)];
|
|
13342
|
-
return candidates.find((path) => existsSync2(path)) ?? candidates[1];
|
|
13343
|
-
}
|
|
13344
|
-
function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
|
|
13345
|
-
return resolveBundledAssetDir("agents", moduleUrl);
|
|
13346
|
-
}
|
|
13347
|
-
function resolveBundledTemplatesDir(moduleUrl = import.meta.url) {
|
|
13348
|
-
return resolveBundledAssetDir("templates", moduleUrl);
|
|
13349
|
-
}
|
|
13350
|
-
|
|
13351
|
-
// src/config.ts
|
|
13352
|
-
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
13353
|
-
import { z as z2 } from "zod";
|
|
13354
|
-
import { homedir as homedir2 } from "node:os";
|
|
13355
|
-
import { join as join4 } from "node:path";
|
|
13356
|
-
|
|
13357
|
-
// src/extensions/zodx.ts
|
|
13358
|
-
import { z } from "zod";
|
|
13359
|
-
var zx = {
|
|
13360
|
-
cwd: z.string().optional(),
|
|
13361
|
-
id: z.string().min(1).max(80).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
13362
|
-
revision: z.number().int().nonnegative(),
|
|
13363
|
-
text: z.string().trim().min(1)
|
|
13364
|
-
};
|
|
13365
|
-
|
|
13366
|
-
// src/fsx.ts
|
|
13367
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
13368
|
-
async function readDirectoryIfExists(path) {
|
|
13369
|
-
try {
|
|
13370
|
-
return await readdir(path, { withFileTypes: true });
|
|
13371
|
-
} catch (error) {
|
|
13372
|
-
if (isMissingPath(error))
|
|
13373
|
-
return [];
|
|
13374
|
-
throw error;
|
|
13375
|
-
}
|
|
13376
|
-
}
|
|
13377
|
-
async function readTextIfExists(path) {
|
|
13378
|
-
try {
|
|
13379
|
-
return await readFile(path, "utf8");
|
|
13380
|
-
} catch (error) {
|
|
13381
|
-
if (isMissingPath(error))
|
|
13382
|
-
return;
|
|
13383
|
-
throw error;
|
|
13384
|
-
}
|
|
13385
|
-
}
|
|
13386
|
-
function isMissingPath(error) {
|
|
13387
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
13388
|
-
}
|
|
13389
|
-
|
|
13390
|
-
// src/config.ts
|
|
13391
|
-
var modelReferenceSchema = zx.text;
|
|
13392
|
-
var agentConfigSchema = z2.object({
|
|
13393
|
-
models: z2.array(modelReferenceSchema).optional()
|
|
13394
|
-
}).strict();
|
|
13395
|
-
var diffpiConfigSchema = z2.object({
|
|
13396
|
-
agents: z2.record(z2.string(), agentConfigSchema).optional()
|
|
13397
|
-
}).strict();
|
|
13398
|
-
function diffpiConfigPaths(homeDir = homedir2()) {
|
|
13399
|
-
const directory = join4(homeDir, ".difflab", "diffpi");
|
|
13400
|
-
return {
|
|
13401
|
-
yaml: join4(directory, "config.yaml"),
|
|
13402
|
-
json: join4(directory, "config.json")
|
|
13403
|
-
};
|
|
13404
|
-
}
|
|
13405
|
-
async function loadDiffpiConfig(options = {}) {
|
|
13406
|
-
const paths = diffpiConfigPaths(options.homeDir);
|
|
13407
|
-
for (const [format, path] of [
|
|
13408
|
-
["yaml", paths.yaml],
|
|
13409
|
-
["json", paths.json]
|
|
13410
|
-
]) {
|
|
13411
|
-
const content = await readTextIfExists(path);
|
|
13412
|
-
if (content === undefined)
|
|
13413
|
-
continue;
|
|
13414
|
-
try {
|
|
13415
|
-
const value = format === "yaml" ? parseYamlConfig(content) : JSON.parse(content);
|
|
13416
|
-
return { config: diffpiConfigSchema.parse(value ?? {}), path };
|
|
13417
|
-
} catch (error) {
|
|
13418
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
13419
|
-
throw new Error(`Invalid Diffpi config at ${path}: ${reason}`, { cause: error });
|
|
13420
|
-
}
|
|
13421
|
-
}
|
|
13422
|
-
return { config: {} };
|
|
13423
|
-
}
|
|
13424
|
-
function resolveAgentModelPreferences(agentId, profilePreferences, config) {
|
|
13425
|
-
const override = config.agents?.[agentId];
|
|
13426
|
-
if (override && Object.hasOwn(override, "models"))
|
|
13427
|
-
return [...override.models ?? []];
|
|
13428
|
-
return [...profilePreferences];
|
|
13429
|
-
}
|
|
13430
|
-
function findPreferredModel(models, preference) {
|
|
13431
|
-
const normalizedPreference = normalizeModelReference(preference);
|
|
13432
|
-
const exactReference = models.find((model) => normalizeModelReference(`${model.provider}/${model.id}`) === normalizedPreference);
|
|
13433
|
-
if (exactReference)
|
|
13434
|
-
return exactReference;
|
|
13435
|
-
const idPreference = preference.includes("/") ? preference.slice(preference.indexOf("/") + 1) : preference;
|
|
13436
|
-
const normalizedIdPreference = normalizeModelReference(idPreference);
|
|
13437
|
-
const exactId = models.find((model) => normalizeModelReference(model.id) === normalizedIdPreference);
|
|
13438
|
-
if (exactId)
|
|
13439
|
-
return exactId;
|
|
13440
|
-
const preferenceTokens = normalizedIdPreference.split("-").filter(Boolean);
|
|
13441
|
-
return models.find((model) => {
|
|
13442
|
-
const modelTokens = new Set(normalizeModelReference(model.id).split("-").filter(Boolean));
|
|
13443
|
-
return preferenceTokens.every((token) => modelTokens.has(token));
|
|
13326
|
+
// src/commands/dispatch.ts
|
|
13327
|
+
function forwardWorkflow(pi, workflow, args) {
|
|
13328
|
+
pi.sendUserMessage(`/skill:${workflow}${args ? ` ${args}` : ""}`, {
|
|
13329
|
+
deliverAs: "followUp",
|
|
13330
|
+
expandPromptTemplates: true
|
|
13444
13331
|
});
|
|
13445
13332
|
}
|
|
13446
|
-
function parseYamlConfig(content) {
|
|
13447
|
-
const document = content.replace(/^\uFEFF/, "").replace(/^---[^\S\r\n]*(?:#.*)?(?:\r?\n|$)/, "");
|
|
13448
|
-
return parseFrontmatter(`---
|
|
13449
|
-
${document}
|
|
13450
|
-
---
|
|
13451
|
-
`).frontmatter;
|
|
13452
|
-
}
|
|
13453
|
-
function normalizeModelReference(value) {
|
|
13454
|
-
return value.toLowerCase().replace(/^~/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
13455
|
-
}
|
|
13456
|
-
|
|
13457
|
-
// src/modes.ts
|
|
13458
|
-
async function discoverAgentModes(options) {
|
|
13459
|
-
const agentDir = options.agentDir ?? getAgentDir();
|
|
13460
|
-
const homeDir = options.homeDir ?? homedir3();
|
|
13461
|
-
const modes = new Map;
|
|
13462
|
-
const diagnostics = [];
|
|
13463
|
-
await loadAgentModes(options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR, "diffpi agent", modes, diagnostics);
|
|
13464
|
-
await loadAgentModes(join5(agentDir, "agents"), "user agent", modes, diagnostics);
|
|
13465
|
-
if (options.includeSkills) {
|
|
13466
|
-
await loadSkillModes(join5(homeDir, ".agents", "skills"), "user skill", modes, diagnostics);
|
|
13467
|
-
await loadSkillModes(join5(agentDir, "skills"), "pi user skill", modes, diagnostics);
|
|
13468
|
-
}
|
|
13469
|
-
if (options.projectTrusted === true) {
|
|
13470
|
-
if (options.includeSkills) {
|
|
13471
|
-
await loadSkillModes(join5(options.cwd, ".agents", "skills"), "project skill", modes, diagnostics);
|
|
13472
|
-
await loadSkillModes(join5(options.cwd, ".pi", "skills"), "pi project skill", modes, diagnostics);
|
|
13473
|
-
}
|
|
13474
|
-
await loadAgentModes(join5(options.cwd, ".agents", "agents"), "project agent", modes, diagnostics);
|
|
13475
|
-
await loadAgentModes(join5(options.cwd, ".pi", "agents"), "pi project agent", modes, diagnostics);
|
|
13476
|
-
}
|
|
13477
|
-
const userConfig = await loadDiffpiConfig({ homeDir });
|
|
13478
|
-
const configuredModes = [...modes.values()].map((mode) => ({
|
|
13479
|
-
...mode,
|
|
13480
|
-
modelPreferences: resolveAgentModelPreferences(mode.id, mode.modelPreferences, userConfig.config)
|
|
13481
|
-
}));
|
|
13482
|
-
return {
|
|
13483
|
-
modes: configuredModes.sort((left, right) => left.id.localeCompare(right.id)),
|
|
13484
|
-
diagnostics
|
|
13485
|
-
};
|
|
13486
|
-
}
|
|
13487
|
-
function resolveAgentMode(modes, requested) {
|
|
13488
|
-
const name = requested.trim();
|
|
13489
|
-
if (!name)
|
|
13490
|
-
return { ok: false, message: "Agent name is required." };
|
|
13491
|
-
const exact = modes.find((mode) => mode.id === name);
|
|
13492
|
-
if (exact)
|
|
13493
|
-
return { ok: true, active: exact, message: `Active inline agent: ${exact.id}.` };
|
|
13494
|
-
const lowerName = name.toLowerCase();
|
|
13495
|
-
const matches = modes.filter((mode) => mode.id.toLowerCase() === lowerName);
|
|
13496
|
-
if (matches.length === 1) {
|
|
13497
|
-
const active = matches[0];
|
|
13498
|
-
return { ok: true, active, message: `Active inline agent: ${active.id}.` };
|
|
13499
|
-
}
|
|
13500
|
-
if (matches.length > 1) {
|
|
13501
|
-
return {
|
|
13502
|
-
ok: false,
|
|
13503
|
-
message: `Inline agent "${name}" is ambiguous. Use one of: ${matches.map((mode) => mode.id).join(", ")}.`
|
|
13504
|
-
};
|
|
13505
|
-
}
|
|
13506
|
-
return { ok: false, message: `Unknown inline agent "${name}". Run /skill:mode or diffpi_modes_list.` };
|
|
13507
|
-
}
|
|
13508
|
-
function createModeController(pi, options = {}) {
|
|
13509
|
-
let active;
|
|
13510
|
-
let baseline;
|
|
13511
|
-
const updateStatus = (ctx) => {
|
|
13512
|
-
const model = ctx.model ? ` · ${ctx.model.provider}/${ctx.model.id}` : "";
|
|
13513
|
-
const label = active?.label.toUpperCase() ?? "DEFAULT";
|
|
13514
|
-
const badge = `AGENT ${label}${model}`;
|
|
13515
|
-
ctx.ui.setStatus(MODE_STATUS_KEY, undefined);
|
|
13516
|
-
ctx.ui.setWidget?.(MODE_WIDGET_KEY, [badge], { placement: "belowEditor" });
|
|
13517
|
-
};
|
|
13518
|
-
const list = (ctx, listOptions = {}) => discoverAgentModes({
|
|
13519
|
-
cwd: ctx.cwd,
|
|
13520
|
-
agentDir: options.agentDir,
|
|
13521
|
-
bundledAgentsDir: options.bundledAgentsDir,
|
|
13522
|
-
homeDir: options.homeDir,
|
|
13523
|
-
projectTrusted: ctx.isProjectTrusted(),
|
|
13524
|
-
includeSkills: listOptions.includeSkills
|
|
13525
|
-
});
|
|
13526
|
-
return {
|
|
13527
|
-
list,
|
|
13528
|
-
async set(agent, ctx) {
|
|
13529
|
-
const catalog = await list(ctx, { includeSkills: agent.includes(":") });
|
|
13530
|
-
const result = resolveAgentMode(catalog.modes, agent);
|
|
13531
|
-
if (!result.ok || !result.active)
|
|
13532
|
-
return result;
|
|
13533
|
-
const previousActive = active;
|
|
13534
|
-
const previousBaseline = baseline;
|
|
13535
|
-
baseline ??= captureRuntime(pi, ctx);
|
|
13536
|
-
if (active && baseline)
|
|
13537
|
-
await restoreRuntime(pi, baseline, ctx);
|
|
13538
|
-
active = result.active;
|
|
13539
|
-
try {
|
|
13540
|
-
const runtimeMessage = await applyModeRuntime(pi, active, ctx);
|
|
13541
|
-
pi.appendEntry(MODE_STATE_ENTRY, { active, baseline });
|
|
13542
|
-
updateStatus(ctx);
|
|
13543
|
-
return { ...result, message: `${result.message} ${runtimeMessage}` };
|
|
13544
|
-
} catch (error) {
|
|
13545
|
-
await restoreRuntime(pi, baseline, ctx);
|
|
13546
|
-
active = undefined;
|
|
13547
|
-
baseline = undefined;
|
|
13548
|
-
if (previousActive && previousBaseline) {
|
|
13549
|
-
try {
|
|
13550
|
-
active = previousActive;
|
|
13551
|
-
baseline = previousBaseline;
|
|
13552
|
-
await applyModeRuntime(pi, active, ctx);
|
|
13553
|
-
} catch {
|
|
13554
|
-
active = undefined;
|
|
13555
|
-
baseline = undefined;
|
|
13556
|
-
}
|
|
13557
|
-
}
|
|
13558
|
-
if (!active)
|
|
13559
|
-
pi.appendEntry(MODE_STATE_ENTRY, { active: null });
|
|
13560
|
-
updateStatus(ctx);
|
|
13561
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
13562
|
-
}
|
|
13563
|
-
},
|
|
13564
|
-
async unset(ctx) {
|
|
13565
|
-
if (!active)
|
|
13566
|
-
return { ok: true, message: "Inline agent is already clear." };
|
|
13567
|
-
if (baseline)
|
|
13568
|
-
await restoreRuntime(pi, baseline, ctx);
|
|
13569
|
-
active = undefined;
|
|
13570
|
-
pi.appendEntry(MODE_STATE_ENTRY, { active: null });
|
|
13571
|
-
baseline = undefined;
|
|
13572
|
-
updateStatus(ctx);
|
|
13573
|
-
return { ok: true, message: "Inline agent cleared. The previous model, thinking, tools, and prompt resume." };
|
|
13574
|
-
},
|
|
13575
|
-
async restore(ctx) {
|
|
13576
|
-
const previousActive = active;
|
|
13577
|
-
const previousBaseline = baseline;
|
|
13578
|
-
const entry = [...ctx.sessionManager.getBranch()].reverse().find((candidate) => candidate.type === "custom" && candidate.customType === MODE_STATE_ENTRY);
|
|
13579
|
-
const restored = entry?.data?.active;
|
|
13580
|
-
const restoredBaseline = entry?.data?.baseline;
|
|
13581
|
-
if (isAgentModeSnapshot(restored)) {
|
|
13582
|
-
const catalog = await list(ctx, { includeSkills: restored.id.includes(":") });
|
|
13583
|
-
const current = catalog.modes.find((mode) => mode.id === restored.id);
|
|
13584
|
-
active = current ?? normalizeAgentModeSnapshot(restored);
|
|
13585
|
-
baseline = isModeBaseline(restoredBaseline) ? restoredBaseline : previousBaseline;
|
|
13586
|
-
try {
|
|
13587
|
-
await applyModeRuntime(pi, active, ctx);
|
|
13588
|
-
} catch (error) {
|
|
13589
|
-
const fallbackBaseline = previousBaseline ?? (isModeBaseline(restoredBaseline) ? restoredBaseline : undefined);
|
|
13590
|
-
if (fallbackBaseline)
|
|
13591
|
-
await restoreRuntime(pi, fallbackBaseline, ctx);
|
|
13592
|
-
active = undefined;
|
|
13593
|
-
baseline = undefined;
|
|
13594
|
-
throw error;
|
|
13595
|
-
}
|
|
13596
|
-
} else {
|
|
13597
|
-
if (previousActive && previousBaseline)
|
|
13598
|
-
pi.setActiveTools(previousBaseline.tools);
|
|
13599
|
-
active = undefined;
|
|
13600
|
-
baseline = undefined;
|
|
13601
|
-
}
|
|
13602
|
-
updateStatus(ctx);
|
|
13603
|
-
},
|
|
13604
|
-
refresh(ctx) {
|
|
13605
|
-
updateStatus(ctx);
|
|
13606
|
-
},
|
|
13607
|
-
apply(systemPrompt) {
|
|
13608
|
-
if (!active)
|
|
13609
|
-
return systemPrompt;
|
|
13610
|
-
if (active.promptStrategy === "replace")
|
|
13611
|
-
return active.systemPrompt;
|
|
13612
|
-
return `${systemPrompt}
|
|
13613
|
-
|
|
13614
|
-
## Active inline agent: ${active.label}
|
|
13615
|
-
|
|
13616
|
-
${active.systemPrompt}`;
|
|
13617
|
-
},
|
|
13618
|
-
getActive() {
|
|
13619
|
-
return active;
|
|
13620
|
-
},
|
|
13621
|
-
getStatus(ctx) {
|
|
13622
|
-
return {
|
|
13623
|
-
active: active?.id,
|
|
13624
|
-
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
13625
|
-
thinkingLevel: pi.getThinkingLevel(),
|
|
13626
|
-
activeTools: pi.getActiveTools(),
|
|
13627
|
-
capabilityError: active ? validateModeRuntime(pi, active, ctx) : undefined
|
|
13628
|
-
};
|
|
13629
|
-
}
|
|
13630
|
-
};
|
|
13631
|
-
}
|
|
13632
|
-
var MODE_STATE_ENTRY = "diffpi-mode-state";
|
|
13633
|
-
var MODE_STATUS_KEY = "diffpi-mode";
|
|
13634
|
-
var MODE_WIDGET_KEY = "diffpi-mode-badge";
|
|
13635
|
-
var MODE_CONTROL_TOOLS = [
|
|
13636
|
-
"ask_user_question",
|
|
13637
|
-
"diffpi_modes_list",
|
|
13638
|
-
"diffpi_modes_set",
|
|
13639
|
-
"diffpi_modes_unset",
|
|
13640
|
-
"diffpi_modes_status"
|
|
13641
|
-
];
|
|
13642
|
-
var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
|
|
13643
|
-
var THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
13644
|
-
async function applyModeRuntime(pi, mode, ctx) {
|
|
13645
|
-
let selectedModel;
|
|
13646
|
-
if (mode.modelPreferences.length > 0) {
|
|
13647
|
-
const scoped = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : undefined;
|
|
13648
|
-
const availableModels = scoped ?? ctx.modelRegistry.getAvailable();
|
|
13649
|
-
for (const preference of mode.modelPreferences) {
|
|
13650
|
-
const model = findPreferredModel(availableModels, preference);
|
|
13651
|
-
if (model && await pi.setModel(model)) {
|
|
13652
|
-
selectedModel = `${model.provider}/${model.id}`;
|
|
13653
|
-
break;
|
|
13654
|
-
}
|
|
13655
|
-
}
|
|
13656
|
-
}
|
|
13657
|
-
if (mode.thinkingLevel)
|
|
13658
|
-
pi.setThinkingLevel(mode.thinkingLevel);
|
|
13659
|
-
if (mode.tools.length > 0) {
|
|
13660
|
-
const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
13661
|
-
const selectedTools = [...new Set([...mode.tools, ...MODE_CONTROL_TOOLS])].filter((tool) => availableTools.has(tool));
|
|
13662
|
-
if (selectedTools.length > 0)
|
|
13663
|
-
pi.setActiveTools(selectedTools);
|
|
13664
|
-
}
|
|
13665
|
-
const availableTools = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
13666
|
-
const unavailableTools = mode.requiredTools.filter((tool) => !availableTools.has(tool));
|
|
13667
|
-
const activeTools = new Set(pi.getActiveTools());
|
|
13668
|
-
const missingTools = mode.requiredTools.filter((tool) => !activeTools.has(tool));
|
|
13669
|
-
const forbiddenTools = mode.forbiddenTools.filter((tool) => activeTools.has(tool));
|
|
13670
|
-
if (unavailableTools.length > 0 || missingTools.length > 0 || forbiddenTools.length > 0)
|
|
13671
|
-
throw new Error(`Profile preflight failed: missing required tools [${[...new Set([...unavailableTools, ...missingTools])].join(", ")}]${forbiddenTools.length > 0 ? `; forbidden tools active [${forbiddenTools.join(", ")}]` : ""}`);
|
|
13672
|
-
if (mode.requireModel && !selectedModel)
|
|
13673
|
-
throw new Error("Profile preflight failed: requested model was not selected.");
|
|
13674
|
-
if (mode.requireThinking && (!mode.thinkingLevel || pi.getThinkingLevel() !== mode.thinkingLevel))
|
|
13675
|
-
throw new Error(`Profile preflight failed: thinking level ${mode.thinkingLevel ?? "unset"} was not applied.`);
|
|
13676
|
-
const parts = [];
|
|
13677
|
-
if (mode.modelPreferences.length > 0) {
|
|
13678
|
-
parts.push(selectedModel ? `Model: ${selectedModel}.` : "No preferred model was available; kept the current model.");
|
|
13679
|
-
}
|
|
13680
|
-
if (mode.thinkingLevel)
|
|
13681
|
-
parts.push(`Thinking: ${mode.thinkingLevel}.`);
|
|
13682
|
-
if (mode.tools.length > 0)
|
|
13683
|
-
parts.push("Applied the profile tool set.");
|
|
13684
|
-
return parts.join(" ") || "The profile changes the prompt only.";
|
|
13685
|
-
}
|
|
13686
|
-
function validateModeRuntime(pi, mode, ctx) {
|
|
13687
|
-
const activeTools = new Set(pi.getActiveTools());
|
|
13688
|
-
const missingTools = mode.requiredTools.filter((tool) => !activeTools.has(tool));
|
|
13689
|
-
const forbiddenTools = mode.forbiddenTools.filter((tool) => activeTools.has(tool));
|
|
13690
|
-
if (missingTools.length > 0 || forbiddenTools.length > 0)
|
|
13691
|
-
return `Profile capability check failed: missing required tools [${missingTools.join(", ")}]${forbiddenTools.length > 0 ? `; forbidden tools active [${forbiddenTools.join(", ")}]` : ""}`;
|
|
13692
|
-
if (mode.requireModel && mode.modelPreferences.length > 0) {
|
|
13693
|
-
const actual = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "unset";
|
|
13694
|
-
const available = ctx.scopedModels.length > 0 ? ctx.scopedModels.map((entry) => entry.model) : ctx.modelRegistry.getAvailable();
|
|
13695
|
-
const expected = mode.modelPreferences.map((preference) => findPreferredModel(available, preference)).find(Boolean);
|
|
13696
|
-
if (!expected || `${expected.provider}/${expected.id}` !== actual)
|
|
13697
|
-
return `Profile capability check failed: requested model was not active (actual ${actual}).`;
|
|
13698
|
-
}
|
|
13699
|
-
if (mode.requireThinking && (!mode.thinkingLevel || pi.getThinkingLevel() !== mode.thinkingLevel))
|
|
13700
|
-
return `Profile capability check failed: thinking level ${mode.thinkingLevel ?? "unset"} was not applied.`;
|
|
13701
|
-
return;
|
|
13702
|
-
}
|
|
13703
|
-
async function restoreRuntime(pi, state, ctx) {
|
|
13704
|
-
if (state.model) {
|
|
13705
|
-
const model = ctx.modelRegistry.find(state.model.provider, state.model.id);
|
|
13706
|
-
if (model)
|
|
13707
|
-
await pi.setModel(model);
|
|
13708
|
-
}
|
|
13709
|
-
pi.setThinkingLevel(state.thinkingLevel);
|
|
13710
|
-
pi.setActiveTools(state.tools);
|
|
13711
|
-
const restoredTools = pi.getActiveTools();
|
|
13712
|
-
if (state.tools.some((tool) => !restoredTools.includes(tool)))
|
|
13713
|
-
throw new Error("Failed to restore the previous active tool set.");
|
|
13714
|
-
}
|
|
13715
|
-
function captureRuntime(pi, ctx) {
|
|
13716
|
-
return {
|
|
13717
|
-
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
13718
|
-
thinkingLevel: pi.getThinkingLevel(),
|
|
13719
|
-
tools: pi.getActiveTools()
|
|
13720
|
-
};
|
|
13721
|
-
}
|
|
13722
|
-
async function loadSkillModes(skillsDir, source, modes, diagnostics) {
|
|
13723
|
-
const entries = await readDirectoryIfExists(skillsDir);
|
|
13724
|
-
for (const entry of entries.filter((item) => item.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13725
|
-
await loadAgentModes(join5(skillsDir, entry.name, "agents"), `${source} ${entry.name}`, modes, diagnostics, entry.name);
|
|
13726
|
-
}
|
|
13727
|
-
}
|
|
13728
|
-
async function loadAgentModes(directory, source, modes, diagnostics, skillName) {
|
|
13729
|
-
const entries = await readDirectoryIfExists(directory);
|
|
13730
|
-
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".md")).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
13731
|
-
const path = join5(directory, entry.name);
|
|
13732
|
-
try {
|
|
13733
|
-
const content = await readFile2(path, "utf8");
|
|
13734
|
-
const { frontmatter, body } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
13735
|
-
if (frontmatter.enabled === false || frontmatter.inline === false)
|
|
13736
|
-
continue;
|
|
13737
|
-
const name = getFrontmatterText(frontmatter.name) ?? basename(path, extname(path));
|
|
13738
|
-
const systemPrompt = body.trim();
|
|
13739
|
-
if (!name || name.includes(":") || !systemPrompt) {
|
|
13740
|
-
diagnostics.push(`Skipped ${path}: agent name must not contain ":" and prompt body is required.`);
|
|
13741
|
-
continue;
|
|
13742
|
-
}
|
|
13743
|
-
const id = skillName ? `${skillName}:${name}` : name;
|
|
13744
|
-
modes.set(id, {
|
|
13745
|
-
id,
|
|
13746
|
-
label: getFrontmatterText(frontmatter.display_name) ?? name,
|
|
13747
|
-
description: getFrontmatterText(frontmatter.description) ?? `Inline agent from ${basename(path)}`,
|
|
13748
|
-
systemPrompt,
|
|
13749
|
-
promptStrategy: frontmatter.prompt_mode === "append" ? "append" : "replace",
|
|
13750
|
-
modelPreferences: [
|
|
13751
|
-
...getFrontmatterList(frontmatter.model),
|
|
13752
|
-
...getFrontmatterList(frontmatter.model_fallbacks)
|
|
13753
|
-
],
|
|
13754
|
-
thinkingLevel: getThinkingLevel(frontmatter.thinking),
|
|
13755
|
-
requiredTools: getFrontmatterList(frontmatter.required_tools),
|
|
13756
|
-
forbiddenTools: getFrontmatterList(frontmatter.forbidden_tools),
|
|
13757
|
-
requireModel: frontmatter.required_model === true,
|
|
13758
|
-
requireThinking: frontmatter.required_thinking === true,
|
|
13759
|
-
tools: getFrontmatterList(frontmatter.tools),
|
|
13760
|
-
source,
|
|
13761
|
-
sourcePath: path
|
|
13762
|
-
});
|
|
13763
|
-
} catch (error) {
|
|
13764
|
-
diagnostics.push(`Skipped ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13765
|
-
}
|
|
13766
|
-
}
|
|
13767
|
-
}
|
|
13768
|
-
function getFrontmatterText(value) {
|
|
13769
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
13770
|
-
}
|
|
13771
|
-
function getFrontmatterList(value) {
|
|
13772
|
-
const values = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
13773
|
-
return values.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
13774
|
-
}
|
|
13775
|
-
function getThinkingLevel(value) {
|
|
13776
|
-
const level = getFrontmatterText(value);
|
|
13777
|
-
return level && THINKING_LEVELS.has(level) ? level : undefined;
|
|
13778
|
-
}
|
|
13779
|
-
function normalizeAgentModeSnapshot(value) {
|
|
13780
|
-
return {
|
|
13781
|
-
...value,
|
|
13782
|
-
requiredTools: Array.isArray(value.requiredTools) ? value.requiredTools : [],
|
|
13783
|
-
forbiddenTools: Array.isArray(value.forbiddenTools) ? value.forbiddenTools : [],
|
|
13784
|
-
requireModel: value.requireModel === true,
|
|
13785
|
-
requireThinking: value.requireThinking === true
|
|
13786
|
-
};
|
|
13787
|
-
}
|
|
13788
|
-
function isAgentModeSnapshot(value) {
|
|
13789
|
-
if (!value || typeof value !== "object")
|
|
13790
|
-
return false;
|
|
13791
|
-
const candidate = value;
|
|
13792
|
-
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";
|
|
13793
|
-
}
|
|
13794
|
-
function isModeBaseline(value) {
|
|
13795
|
-
if (!value || typeof value !== "object")
|
|
13796
|
-
return false;
|
|
13797
|
-
const candidate = value;
|
|
13798
|
-
const model = candidate.model;
|
|
13799
|
-
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");
|
|
13800
|
-
}
|
|
13801
|
-
|
|
13802
|
-
// src/commands/mode.ts
|
|
13803
|
-
var MODE_USAGE = `/mode [agent|default|clear|reset]
|
|
13804
|
-
|
|
13805
|
-
Use /mode to pick a profile. Available profiles: copilot, orchestrator, reviewer, tutor, worker.`;
|
|
13806
|
-
function registerModeCommand(pi, modes) {
|
|
13807
|
-
pi.registerCommand("mode", {
|
|
13808
|
-
description: "Select or clear a Diffpi inline agent profile",
|
|
13809
|
-
handler: async (args, ctx) => handleModeCommand(args, ctx, modes)
|
|
13810
|
-
});
|
|
13811
|
-
}
|
|
13812
|
-
async function handleModeCommand(args, ctx, modes) {
|
|
13813
|
-
const requested = args.trim();
|
|
13814
|
-
if (requested === "help" || requested === "-h" || requested === "--help") {
|
|
13815
|
-
ctx.ui.notify(MODE_USAGE, "info");
|
|
13816
|
-
return;
|
|
13817
|
-
}
|
|
13818
|
-
if (!requested) {
|
|
13819
|
-
const catalog = await modes.list(ctx);
|
|
13820
|
-
const selected = await ctx.ui.select("Diffpi mode", ["default", ...catalog.modes.map((mode) => mode.id)]);
|
|
13821
|
-
if (!selected)
|
|
13822
|
-
return;
|
|
13823
|
-
const result = selected === "default" ? await modes.unset(ctx) : await modes.set(selected, ctx);
|
|
13824
|
-
ctx.ui.notify(`${result.message} Changes apply on the next turn.`, result.ok ? "info" : "error");
|
|
13825
|
-
return;
|
|
13826
|
-
}
|
|
13827
|
-
if (/\s/.test(requested)) {
|
|
13828
|
-
ctx.ui.notify(MODE_USAGE, "error");
|
|
13829
|
-
return;
|
|
13830
|
-
}
|
|
13831
|
-
const result = requested === "default" || requested === "clear" || requested === "reset" ? await modes.unset(ctx) : await modes.set(requested, ctx);
|
|
13832
|
-
ctx.ui.notify(`${result.message} Changes apply on the next turn.`, result.ok ? "info" : "error");
|
|
13833
|
-
}
|
|
13834
13333
|
|
|
13835
13334
|
// src/commands/plan.ts
|
|
13836
13335
|
function registerPlanCommand(pi) {
|
|
13837
13336
|
pi.registerCommand("plan", {
|
|
13838
|
-
description: "
|
|
13337
|
+
description: "Open the plan skill",
|
|
13839
13338
|
handler: async (args) => {
|
|
13840
|
-
pi
|
|
13841
|
-
deliverAs: "followUp",
|
|
13842
|
-
expandPromptTemplates: true
|
|
13843
|
-
});
|
|
13339
|
+
forwardWorkflow(pi, "plan", args);
|
|
13844
13340
|
}
|
|
13845
13341
|
});
|
|
13846
13342
|
}
|
|
@@ -13858,37 +13354,49 @@ function registerReloadCommand(pi) {
|
|
|
13858
13354
|
// src/commands/review.ts
|
|
13859
13355
|
function registerReviewCommand(pi) {
|
|
13860
13356
|
pi.registerCommand("review", {
|
|
13861
|
-
description: "
|
|
13357
|
+
description: "Open the review skill",
|
|
13862
13358
|
handler: async (args) => {
|
|
13863
|
-
pi
|
|
13864
|
-
deliverAs: "followUp",
|
|
13865
|
-
expandPromptTemplates: true
|
|
13866
|
-
});
|
|
13359
|
+
forwardWorkflow(pi, "review", args);
|
|
13867
13360
|
}
|
|
13868
13361
|
});
|
|
13869
13362
|
}
|
|
13870
13363
|
|
|
13871
13364
|
// src/commands/index.ts
|
|
13872
|
-
function registerCommands(pi
|
|
13365
|
+
function registerCommands(pi) {
|
|
13873
13366
|
registerReloadCommand(pi);
|
|
13874
|
-
registerModeCommand(pi, modes);
|
|
13875
13367
|
registerReviewCommand(pi);
|
|
13876
13368
|
registerPlanCommand(pi);
|
|
13877
13369
|
}
|
|
13878
13370
|
|
|
13879
13371
|
// src/tools/ci.ts
|
|
13880
13372
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
13881
|
-
import { z as
|
|
13373
|
+
import { z as z2 } from "zod";
|
|
13882
13374
|
|
|
13883
13375
|
// src/environment.ts
|
|
13884
|
-
import { readFile as
|
|
13885
|
-
import { basename
|
|
13376
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
13377
|
+
import { basename, join as join6 } from "node:path";
|
|
13378
|
+
|
|
13379
|
+
// src/assets.ts
|
|
13380
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
13381
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
13382
|
+
import { fileURLToPath } from "node:url";
|
|
13383
|
+
function resolveBundledAssetDir(name, moduleUrl = import.meta.url) {
|
|
13384
|
+
const moduleDir = dirname2(fileURLToPath(moduleUrl));
|
|
13385
|
+
const candidates = [join3(moduleDir, name), join3(moduleDir, "..", name), join3(moduleDir, "..", "..", name)];
|
|
13386
|
+
return candidates.find((path) => existsSync2(path)) ?? candidates[1];
|
|
13387
|
+
}
|
|
13388
|
+
function resolveBundledAgentsDir(moduleUrl = import.meta.url) {
|
|
13389
|
+
return resolveBundledAssetDir("agents", moduleUrl);
|
|
13390
|
+
}
|
|
13391
|
+
function resolveBundledTemplatesDir(moduleUrl = import.meta.url) {
|
|
13392
|
+
return resolveBundledAssetDir("templates", moduleUrl);
|
|
13393
|
+
}
|
|
13886
13394
|
|
|
13887
13395
|
// src/extensions/processx.ts
|
|
13888
13396
|
import { spawn as spawn2 } from "node:child_process";
|
|
13889
13397
|
import { constants } from "node:fs";
|
|
13890
13398
|
import { access } from "node:fs/promises";
|
|
13891
|
-
import { delimiter, join as
|
|
13399
|
+
import { delimiter, join as join4 } from "node:path";
|
|
13892
13400
|
async function findExecutable(name) {
|
|
13893
13401
|
if (name.includes("/")) {
|
|
13894
13402
|
try {
|
|
@@ -13901,7 +13409,7 @@ async function findExecutable(name) {
|
|
|
13901
13409
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
13902
13410
|
if (!directory)
|
|
13903
13411
|
continue;
|
|
13904
|
-
const candidate =
|
|
13412
|
+
const candidate = join4(directory, name);
|
|
13905
13413
|
try {
|
|
13906
13414
|
await access(candidate, constants.X_OK);
|
|
13907
13415
|
return candidate;
|
|
@@ -13966,21 +13474,21 @@ function appendBounded(current, next) {
|
|
|
13966
13474
|
}
|
|
13967
13475
|
|
|
13968
13476
|
// src/extensions/zedx.ts
|
|
13969
|
-
import { mkdir, readFile
|
|
13970
|
-
import { homedir as
|
|
13971
|
-
import { dirname as dirname3, join as
|
|
13477
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13478
|
+
import { homedir as homedir2 } from "node:os";
|
|
13479
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
13972
13480
|
var ZED_LOCAL_REVIEW_TASK_NAME = "diffpi: tuicr local review";
|
|
13973
13481
|
var ZED_PR_REVIEW_TASK_NAME = "diffpi: tuicr PR review";
|
|
13974
13482
|
var ZED_PLAN_ANNOTATE_TASK_NAME = "diffpi: annotate plan";
|
|
13975
13483
|
var LEGACY_ZED_REVIEW_TASK_NAME = "diffpi: tuicr review";
|
|
13976
13484
|
var REVIEW_KEYBINDING = "cmd-alt-r";
|
|
13977
|
-
function zedTasksPath(homeDir =
|
|
13978
|
-
return
|
|
13485
|
+
function zedTasksPath(homeDir = homedir2()) {
|
|
13486
|
+
return join5(homeDir, ".config", "zed", "tasks.json");
|
|
13979
13487
|
}
|
|
13980
|
-
function zedKeymapPath(homeDir =
|
|
13981
|
-
return
|
|
13488
|
+
function zedKeymapPath(homeDir = homedir2()) {
|
|
13489
|
+
return join5(homeDir, ".config", "zed", "keymap.json");
|
|
13982
13490
|
}
|
|
13983
|
-
async function ensureZedReviewTask(homeDir =
|
|
13491
|
+
async function ensureZedReviewTask(homeDir = homedir2(), _command = ["tuicr", "-w", "-r", "main..HEAD"]) {
|
|
13984
13492
|
const path = zedTasksPath(homeDir);
|
|
13985
13493
|
const currentText = await readOptional(path);
|
|
13986
13494
|
const tasks = parseJsonArray(currentText, path);
|
|
@@ -13998,7 +13506,7 @@ async function ensureZedReviewTask(homeDir = homedir4(), _command = ["tuicr", "-
|
|
|
13998
13506
|
await writeJson(path, next);
|
|
13999
13507
|
return { path, changed, existed: currentText !== undefined };
|
|
14000
13508
|
}
|
|
14001
|
-
async function ensureZedPlanTask(packageVersion, homeDir =
|
|
13509
|
+
async function ensureZedPlanTask(packageVersion, homeDir = homedir2()) {
|
|
14002
13510
|
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageVersion)) {
|
|
14003
13511
|
throw new Error(`Invalid @difflab/pi package version: ${packageVersion}.`);
|
|
14004
13512
|
}
|
|
@@ -14025,7 +13533,7 @@ async function ensureZedPlanTask(packageVersion, homeDir = homedir4()) {
|
|
|
14025
13533
|
await writeJson(path, next);
|
|
14026
13534
|
return { path, changed, existed: currentText !== undefined };
|
|
14027
13535
|
}
|
|
14028
|
-
async function ensureZedReviewKeybinding(homeDir =
|
|
13536
|
+
async function ensureZedReviewKeybinding(homeDir = homedir2()) {
|
|
14029
13537
|
const path = zedKeymapPath(homeDir);
|
|
14030
13538
|
const currentText = await readOptional(path);
|
|
14031
13539
|
const entries = parseJsonArray(currentText, path);
|
|
@@ -14107,7 +13615,7 @@ function migrateReviewKeymapEntry(entry) {
|
|
|
14107
13615
|
}
|
|
14108
13616
|
async function readOptional(path) {
|
|
14109
13617
|
try {
|
|
14110
|
-
return await
|
|
13618
|
+
return await readFile(path, "utf8");
|
|
14111
13619
|
} catch (error) {
|
|
14112
13620
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
14113
13621
|
return;
|
|
@@ -14158,7 +13666,7 @@ function detectMux(env = process.env) {
|
|
|
14158
13666
|
return "none";
|
|
14159
13667
|
}
|
|
14160
13668
|
function detectShell(env = process.env) {
|
|
14161
|
-
return env.SHELL ?
|
|
13669
|
+
return env.SHELL ? basename(env.SHELL) : "unknown";
|
|
14162
13670
|
}
|
|
14163
13671
|
async function detectVcs(cwd) {
|
|
14164
13672
|
const root = (await run("git", ["-C", cwd, "rev-parse", "--show-toplevel"])).stdout.trim() || cwd;
|
|
@@ -14185,7 +13693,7 @@ function parseRemote(remote) {
|
|
|
14185
13693
|
return { provider, host, owner, repo };
|
|
14186
13694
|
}
|
|
14187
13695
|
function diffpiLaunchName(cwd, workflow) {
|
|
14188
|
-
return `diffpi: ${
|
|
13696
|
+
return `diffpi: ${basename(cwd)} / ${workflow}`;
|
|
14189
13697
|
}
|
|
14190
13698
|
async function openInNewTab(command, opts) {
|
|
14191
13699
|
const env = opts.env ?? process.env;
|
|
@@ -14222,7 +13730,7 @@ function screenWindowArgs(command, cwd, name) {
|
|
|
14222
13730
|
async function packageVersion() {
|
|
14223
13731
|
let value;
|
|
14224
13732
|
try {
|
|
14225
|
-
value = JSON.parse(await
|
|
13733
|
+
value = JSON.parse(await readFile2(join6(resolveBundledAgentsDir(), "..", "package.json"), "utf8"));
|
|
14226
13734
|
} catch (error) {
|
|
14227
13735
|
throw new Error("Cannot parse the installed @difflab/pi package metadata.", { cause: error });
|
|
14228
13736
|
}
|
|
@@ -14252,6 +13760,15 @@ async function openMuxTab(mux, command, cwd, name, printable, tmuxCommand) {
|
|
|
14252
13760
|
return;
|
|
14253
13761
|
}
|
|
14254
13762
|
|
|
13763
|
+
// src/extensions/zodx.ts
|
|
13764
|
+
import { z } from "zod";
|
|
13765
|
+
var zx = {
|
|
13766
|
+
cwd: z.string().optional(),
|
|
13767
|
+
id: z.string().min(1).max(80).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
|
13768
|
+
revision: z.number().int().nonnegative(),
|
|
13769
|
+
text: z.string().trim().min(1)
|
|
13770
|
+
};
|
|
13771
|
+
|
|
14255
13772
|
// src/extensions/ghx.ts
|
|
14256
13773
|
function gh(args, options) {
|
|
14257
13774
|
return run("gh", args, options);
|
|
@@ -14564,17 +14081,17 @@ function createForgeBackend(vcs) {
|
|
|
14564
14081
|
}
|
|
14565
14082
|
var createVcsBackend = createForgeBackend;
|
|
14566
14083
|
// src/tools/ci.ts
|
|
14567
|
-
var watchCiParametersSchema =
|
|
14084
|
+
var watchCiParametersSchema = z2.object({
|
|
14568
14085
|
cwd: zx.cwd,
|
|
14569
|
-
sha:
|
|
14570
|
-
timeoutSeconds:
|
|
14571
|
-
pollSeconds:
|
|
14086
|
+
sha: z2.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i),
|
|
14087
|
+
timeoutSeconds: z2.number().int().min(30).max(7200).default(1800),
|
|
14088
|
+
pollSeconds: z2.number().int().min(2).max(60).default(10)
|
|
14572
14089
|
}).strict();
|
|
14573
14090
|
var watchCiTool = defineTool({
|
|
14574
14091
|
name: "watch_ci",
|
|
14575
14092
|
label: "watch CI",
|
|
14576
14093
|
description: "Wait for hosted CI on an exact commit SHA without changing plan or review state.",
|
|
14577
|
-
parameters:
|
|
14094
|
+
parameters: z2.toJSONSchema(watchCiParametersSchema, { io: "input" }),
|
|
14578
14095
|
executionMode: "parallel",
|
|
14579
14096
|
async execute(_id, input, signal) {
|
|
14580
14097
|
const params = watchCiParametersSchema.parse(input);
|
|
@@ -14610,12 +14127,12 @@ function result(text, status, detail = text) {
|
|
|
14610
14127
|
|
|
14611
14128
|
// src/tools/log.ts
|
|
14612
14129
|
import { defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
|
|
14613
|
-
import { join as
|
|
14614
|
-
import { z as
|
|
14130
|
+
import { join as join9 } from "node:path";
|
|
14131
|
+
import { z as z3 } from "zod";
|
|
14615
14132
|
|
|
14616
14133
|
// src/log.ts
|
|
14617
14134
|
import { randomUUID } from "node:crypto";
|
|
14618
|
-
import { appendFile, readFile as
|
|
14135
|
+
import { appendFile, readFile as readFile3 } from "node:fs/promises";
|
|
14619
14136
|
async function appendLogEntry(logPath, event) {
|
|
14620
14137
|
const entry = {
|
|
14621
14138
|
...event,
|
|
@@ -14631,12 +14148,12 @@ async function appendLogEntry(logPath, event) {
|
|
|
14631
14148
|
// src/store.ts
|
|
14632
14149
|
import { createHash } from "node:crypto";
|
|
14633
14150
|
import { lstat, mkdir as mkdir2, readlink, realpath as realpath2, symlink, unlink } from "node:fs/promises";
|
|
14634
|
-
import { homedir as
|
|
14635
|
-
import { dirname as dirname4, isAbsolute as isAbsolute3, join as
|
|
14151
|
+
import { homedir as homedir3 } from "node:os";
|
|
14152
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join8, resolve as resolve3 } from "node:path";
|
|
14636
14153
|
|
|
14637
14154
|
// src/extensions/gitx.ts
|
|
14638
14155
|
import { realpath } from "node:fs/promises";
|
|
14639
|
-
import { basename as
|
|
14156
|
+
import { basename as basename2, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
|
|
14640
14157
|
async function gitToplevel(cwd) {
|
|
14641
14158
|
const result = await run("git", ["-C", cwd, "rev-parse", "--show-toplevel"]);
|
|
14642
14159
|
const top = result.stdout.trim();
|
|
@@ -14648,13 +14165,13 @@ async function inspectGitRepository(cwd) {
|
|
|
14648
14165
|
const remote = remoteResult.code === 0 ? remoteResult.stdout.trim() : "";
|
|
14649
14166
|
const commonResult = await run("git", ["-C", root, "rev-parse", "--git-common-dir"]);
|
|
14650
14167
|
const common = commonResult.stdout.trim();
|
|
14651
|
-
const commonPath = commonResult.code === 0 && common ? resolve2(isAbsolute2(common) ? common :
|
|
14168
|
+
const commonPath = commonResult.code === 0 && common ? resolve2(isAbsolute2(common) ? common : join7(root, common)) : root;
|
|
14652
14169
|
const commonDir = await canonicalPath(commonPath);
|
|
14653
14170
|
return {
|
|
14654
14171
|
root,
|
|
14655
14172
|
commonDir,
|
|
14656
14173
|
...remote ? { remote } : {},
|
|
14657
|
-
name: remote ? repositoryName(remote) :
|
|
14174
|
+
name: remote ? repositoryName(remote) : basename2(resolve2(commonDir, "..")) || basename2(root),
|
|
14658
14175
|
identity: remote ? `remote:${normalizeRemote(remote)}` : `git-common-dir:${commonDir}`
|
|
14659
14176
|
};
|
|
14660
14177
|
}
|
|
@@ -14675,16 +14192,16 @@ async function canonicalPath(path) {
|
|
|
14675
14192
|
|
|
14676
14193
|
// src/store.ts
|
|
14677
14194
|
var STORE_LINK = ".diffpi";
|
|
14678
|
-
var LEGACY_STORE_LINK =
|
|
14679
|
-
function storeGlobalRoot(homeDir =
|
|
14680
|
-
return
|
|
14195
|
+
var LEGACY_STORE_LINK = join8(".pi", "diffpi");
|
|
14196
|
+
function storeGlobalRoot(homeDir = homedir3()) {
|
|
14197
|
+
return join8(homeDir, ".difflab", "diffpi", "projects");
|
|
14681
14198
|
}
|
|
14682
|
-
async function ensureStore(cwd, homeDir =
|
|
14199
|
+
async function ensureStore(cwd, homeDir = homedir3()) {
|
|
14683
14200
|
const repository = await inspectGitRepository(cwd);
|
|
14684
14201
|
const root = repository.root;
|
|
14685
14202
|
const slug = projectSlug(repository);
|
|
14686
|
-
const dest =
|
|
14687
|
-
const link =
|
|
14203
|
+
const dest = join8(storeGlobalRoot(homeDir), slug);
|
|
14204
|
+
const link = join8(root, STORE_LINK);
|
|
14688
14205
|
await mkdir2(dest, { recursive: true });
|
|
14689
14206
|
try {
|
|
14690
14207
|
await assertStoreLink(link, dest);
|
|
@@ -14693,30 +14210,30 @@ async function ensureStore(cwd, homeDir = homedir5()) {
|
|
|
14693
14210
|
throw error;
|
|
14694
14211
|
await symlink(dest, link);
|
|
14695
14212
|
}
|
|
14696
|
-
await removeLegacyStoreLink(
|
|
14213
|
+
await removeLegacyStoreLink(join8(root, LEGACY_STORE_LINK), dest);
|
|
14697
14214
|
return { slug, root, dest, link, linked: true };
|
|
14698
14215
|
}
|
|
14699
|
-
async function logsDir(cwd, homeDir =
|
|
14216
|
+
async function logsDir(cwd, homeDir = homedir3()) {
|
|
14700
14217
|
const store = await ensureStore(cwd, homeDir);
|
|
14701
|
-
const dir =
|
|
14218
|
+
const dir = join8(store.link, "logs");
|
|
14702
14219
|
await mkdir2(dir, { recursive: true });
|
|
14703
14220
|
return dir;
|
|
14704
14221
|
}
|
|
14705
|
-
async function reviewsDir(cwd, homeDir =
|
|
14222
|
+
async function reviewsDir(cwd, homeDir = homedir3()) {
|
|
14706
14223
|
const store = await ensureStore(cwd, homeDir);
|
|
14707
|
-
const dir =
|
|
14224
|
+
const dir = join8(store.link, "review");
|
|
14708
14225
|
await mkdir2(dir, { recursive: true });
|
|
14709
14226
|
return dir;
|
|
14710
14227
|
}
|
|
14711
|
-
async function completedReviewsDir(cwd, homeDir =
|
|
14228
|
+
async function completedReviewsDir(cwd, homeDir = homedir3()) {
|
|
14712
14229
|
const store = await ensureStore(cwd, homeDir);
|
|
14713
|
-
const dir =
|
|
14230
|
+
const dir = join8(store.link, "reviews");
|
|
14714
14231
|
await mkdir2(dir, { recursive: true });
|
|
14715
14232
|
return dir;
|
|
14716
14233
|
}
|
|
14717
|
-
async function sessionsDir(cwd, homeDir =
|
|
14234
|
+
async function sessionsDir(cwd, homeDir = homedir3()) {
|
|
14718
14235
|
const store = await ensureStore(cwd, homeDir);
|
|
14719
|
-
const dir =
|
|
14236
|
+
const dir = join8(store.link, "sessions");
|
|
14720
14237
|
await mkdir2(dir, { recursive: true });
|
|
14721
14238
|
return dir;
|
|
14722
14239
|
}
|
|
@@ -14759,15 +14276,15 @@ function projectSlug(repository) {
|
|
|
14759
14276
|
}
|
|
14760
14277
|
|
|
14761
14278
|
// src/tools/log.ts
|
|
14762
|
-
var schema7 =
|
|
14279
|
+
var schema7 = z3.object({
|
|
14763
14280
|
cwd: zx.cwd,
|
|
14764
14281
|
channel: zx.id,
|
|
14765
|
-
kind:
|
|
14282
|
+
kind: z3.enum(["progress", "issue", "deviation"]),
|
|
14766
14283
|
actor: zx.text,
|
|
14767
14284
|
message: zx.text,
|
|
14768
|
-
correlation:
|
|
14769
|
-
evidence:
|
|
14770
|
-
data:
|
|
14285
|
+
correlation: z3.record(z3.string(), z3.string()).optional(),
|
|
14286
|
+
evidence: z3.array(zx.text).optional(),
|
|
14287
|
+
data: z3.record(z3.string(), z3.unknown()).optional()
|
|
14771
14288
|
}).strict();
|
|
14772
14289
|
var diffpiLogTool = defineTool2({
|
|
14773
14290
|
name: "diffpi_log",
|
|
@@ -14778,11 +14295,11 @@ var diffpiLogTool = defineTool2({
|
|
|
14778
14295
|
"Use stable channel names shared by one workflow, such as plan, flow, or session.",
|
|
14779
14296
|
"Keep plan status and task checkboxes in live PLAN.md files; diffpi_log is a general activity log, not another task system."
|
|
14780
14297
|
],
|
|
14781
|
-
parameters:
|
|
14298
|
+
parameters: z3.toJSONSchema(schema7, { io: "input" }),
|
|
14782
14299
|
executionMode: "sequential",
|
|
14783
14300
|
async execute(_id, input) {
|
|
14784
14301
|
const params = schema7.parse(input);
|
|
14785
|
-
const entry = await appendLogEntry(
|
|
14302
|
+
const entry = await appendLogEntry(join9(await logsDir(params.cwd ?? process.cwd()), `${params.channel}.jsonl`), {
|
|
14786
14303
|
kind: params.kind,
|
|
14787
14304
|
actor: params.actor,
|
|
14788
14305
|
message: params.message,
|
|
@@ -14799,30 +14316,30 @@ var diffpiLogTool = defineTool2({
|
|
|
14799
14316
|
|
|
14800
14317
|
// src/tools/plan.ts
|
|
14801
14318
|
import { defineTool as defineTool3 } from "@earendil-works/pi-coding-agent";
|
|
14802
|
-
import { z as
|
|
14319
|
+
import { z as z4 } from "zod";
|
|
14803
14320
|
|
|
14804
14321
|
// src/plan/verify.ts
|
|
14805
|
-
import { readFile as
|
|
14806
|
-
import { basename as
|
|
14322
|
+
import { readFile as readFile4, readdir, stat } from "node:fs/promises";
|
|
14323
|
+
import { basename as basename3, dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
|
|
14807
14324
|
async function verifyLivePlan(requested, cwd = process.cwd()) {
|
|
14808
14325
|
const path = resolve4(cwd, requested);
|
|
14809
14326
|
let planPath = path;
|
|
14810
14327
|
try {
|
|
14811
14328
|
if ((await stat(path)).isDirectory())
|
|
14812
|
-
planPath =
|
|
14329
|
+
planPath = join10(path, "PLAN.md");
|
|
14813
14330
|
} catch {}
|
|
14814
14331
|
const issues = [];
|
|
14815
14332
|
const briefPaths = [];
|
|
14816
14333
|
const planDir = dirname5(planPath);
|
|
14817
|
-
if (
|
|
14334
|
+
if (basename3(planPath) !== "PLAN.md") {
|
|
14818
14335
|
addIssue(issues, planPath, 0, "path", "Select PLAN.md or its directory.");
|
|
14819
14336
|
return { planPath, briefPaths, ok: false, issues };
|
|
14820
14337
|
}
|
|
14821
|
-
if (!/^\d{6}(?:-[a-z0-9]+)+$/.test(
|
|
14338
|
+
if (!/^\d{6}(?:-[a-z0-9]+)+$/.test(basename3(planDir)) || basename3(dirname5(planDir)) !== "plan" || basename3(dirname5(dirname5(planDir))) !== ".diffpi")
|
|
14822
14339
|
addIssue(issues, planPath, 0, "layout", "Place PLAN.md under .diffpi/plan/<YYMMDD[-ticket]-short-slug>/.");
|
|
14823
14340
|
let source;
|
|
14824
14341
|
try {
|
|
14825
|
-
source = await
|
|
14342
|
+
source = await readFile4(planPath, "utf8");
|
|
14826
14343
|
} catch {
|
|
14827
14344
|
addIssue(issues, planPath, 0, "missing-plan", "PLAN.md is not readable.");
|
|
14828
14345
|
return { planPath, briefPaths, ok: false, issues };
|
|
@@ -14836,27 +14353,27 @@ async function verifyLivePlan(requested, cwd = process.cwd()) {
|
|
|
14836
14353
|
if (!planLines[line]?.startsWith(`### Phase ${index + 1}: `))
|
|
14837
14354
|
addIssue(issues, planPath, line + 1, "phase-order", "Number phase headings from 1 without gaps.");
|
|
14838
14355
|
}
|
|
14839
|
-
const implementation =
|
|
14356
|
+
const implementation = join10(planDir, "implementation");
|
|
14840
14357
|
let rootEntries = [];
|
|
14841
14358
|
let briefEntries = [];
|
|
14842
14359
|
try {
|
|
14843
|
-
rootEntries = await
|
|
14844
|
-
briefEntries = await
|
|
14360
|
+
rootEntries = await readdir(planDir);
|
|
14361
|
+
briefEntries = await readdir(implementation);
|
|
14845
14362
|
} catch {}
|
|
14846
14363
|
for (const entry of rootEntries) {
|
|
14847
14364
|
if (entry !== "PLAN.md" && entry !== "implementation")
|
|
14848
|
-
addIssue(issues,
|
|
14365
|
+
addIssue(issues, join10(planDir, entry), 0, "layout", "Only PLAN.md and implementation/ belong in the plan directory.");
|
|
14849
14366
|
}
|
|
14850
14367
|
for (const entry of briefEntries) {
|
|
14851
14368
|
const ordinal = /^phase-(\d+)\.md$/.exec(entry)?.[1];
|
|
14852
14369
|
if (!ordinal || !Number(ordinal) || Number(ordinal) > phases.length || entry !== `phase-${Number(ordinal)}.md`)
|
|
14853
|
-
addIssue(issues,
|
|
14370
|
+
addIssue(issues, join10(implementation, entry), 0, "extra-brief", "Only matching numbered phase briefs belong in implementation/.");
|
|
14854
14371
|
}
|
|
14855
14372
|
for (let index = 0;index < phases.length; index++) {
|
|
14856
|
-
const brief =
|
|
14373
|
+
const brief = join10(implementation, `phase-${index + 1}.md`);
|
|
14857
14374
|
briefPaths.push(brief);
|
|
14858
14375
|
try {
|
|
14859
|
-
const lines = visibleLines(await
|
|
14376
|
+
const lines = visibleLines(await readFile4(brief, "utf8"));
|
|
14860
14377
|
if (!/^# Phase \d+:\s*\S/.test(lines[0] ?? "") || !lines[0]?.startsWith(`# Phase ${index + 1}: `))
|
|
14861
14378
|
addIssue(issues, brief, 1, "phase-title", `Start with # Phase ${index + 1}: <title>.`);
|
|
14862
14379
|
checkHeadings(lines, brief, ["Objective", "Files Affected", "Tasks", "Implementation Constraints"], issues);
|
|
@@ -14889,7 +14406,7 @@ function checkHeadings(lines, file, names, issues) {
|
|
|
14889
14406
|
}
|
|
14890
14407
|
|
|
14891
14408
|
// src/tools/plan.ts
|
|
14892
|
-
var schema8 =
|
|
14409
|
+
var schema8 = z4.object({ plan: z4.string().min(1), cwd: z4.string().optional() }).strict();
|
|
14893
14410
|
var planVerifyTool = defineTool3({
|
|
14894
14411
|
name: "plan_verify",
|
|
14895
14412
|
label: "plan verify",
|
|
@@ -14898,7 +14415,7 @@ var planVerifyTool = defineTool3({
|
|
|
14898
14415
|
promptGuidelines: [
|
|
14899
14416
|
"A PASS checks structure only. It never replaces the independent high-tier Plan Reviewer or approves execution."
|
|
14900
14417
|
],
|
|
14901
|
-
parameters:
|
|
14418
|
+
parameters: z4.toJSONSchema(schema8, { io: "input" }),
|
|
14902
14419
|
executionMode: "parallel",
|
|
14903
14420
|
async execute(_id, input) {
|
|
14904
14421
|
const params = schema8.parse(input);
|
|
@@ -14911,9 +14428,9 @@ var planVerifyTool = defineTool3({
|
|
|
14911
14428
|
|
|
14912
14429
|
// src/tools/reload.ts
|
|
14913
14430
|
import { defineTool as defineTool4 } from "@earendil-works/pi-coding-agent";
|
|
14914
|
-
import { z as
|
|
14915
|
-
var reloadParametersSchema =
|
|
14916
|
-
var reloadParameters =
|
|
14431
|
+
import { z as z5 } from "zod";
|
|
14432
|
+
var reloadParametersSchema = z5.object({});
|
|
14433
|
+
var reloadParameters = z5.toJSONSchema(reloadParametersSchema, { io: "input" });
|
|
14917
14434
|
function createDiffpiReloadTool(pi) {
|
|
14918
14435
|
return defineTool4({
|
|
14919
14436
|
name: "diffpi_reload",
|
|
@@ -14936,132 +14453,17 @@ function createDiffpiReloadTool(pi) {
|
|
|
14936
14453
|
});
|
|
14937
14454
|
}
|
|
14938
14455
|
|
|
14939
|
-
// src/tools/modes.ts
|
|
14940
|
-
import { defineTool as defineTool5 } from "@earendil-works/pi-coding-agent";
|
|
14941
|
-
import { z as z7 } from "zod";
|
|
14942
|
-
var emptyParametersSchema = z7.object({});
|
|
14943
|
-
var emptyParameters = z7.toJSONSchema(emptyParametersSchema, { io: "input" });
|
|
14944
|
-
var listParametersSchema = z7.object({
|
|
14945
|
-
includeSkills: z7.boolean().optional().describe("Include skill-owned agents using skill:agent ids.")
|
|
14946
|
-
});
|
|
14947
|
-
var listParameters = z7.toJSONSchema(listParametersSchema, { io: "input" });
|
|
14948
|
-
var setParametersSchema = z7.object({
|
|
14949
|
-
agent: zx.text.describe("Inline agent id from diffpi_modes_list.")
|
|
14950
|
-
});
|
|
14951
|
-
var setParameters = z7.toJSONSchema(setParametersSchema, { io: "input" });
|
|
14952
|
-
function createModeTools(controller) {
|
|
14953
|
-
return [
|
|
14954
|
-
defineTool5({
|
|
14955
|
-
name: "diffpi_modes_status",
|
|
14956
|
-
label: "diffpi modes status",
|
|
14957
|
-
description: "Report the actual runtime model, thinking level, and active tools for capability inspection.",
|
|
14958
|
-
promptSnippet: "Inspect actual post-compaction inline mode capabilities before claiming them.",
|
|
14959
|
-
parameters: emptyParameters,
|
|
14960
|
-
executionMode: "parallel",
|
|
14961
|
-
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
14962
|
-
emptyParametersSchema.parse(input);
|
|
14963
|
-
const status = controller.getStatus(ctx);
|
|
14964
|
-
return {
|
|
14965
|
-
content: [{ type: "text", text: JSON.stringify(status) }],
|
|
14966
|
-
details: status
|
|
14967
|
-
};
|
|
14968
|
-
}
|
|
14969
|
-
}),
|
|
14970
|
-
defineTool5({
|
|
14971
|
-
name: "diffpi_modes_list",
|
|
14972
|
-
label: "diffpi modes list",
|
|
14973
|
-
description: "List inline agents shared with the subagent plugin, optionally including skill-owned agents.",
|
|
14974
|
-
promptSnippet: "List inline agents before selecting one when the requested agent is unclear",
|
|
14975
|
-
promptGuidelines: [
|
|
14976
|
-
"Call diffpi_modes_list when the user asks which inline agents are available.",
|
|
14977
|
-
"Set includeSkills to true only when the user asks for skill agents or runs /skill:mode --include-skills.",
|
|
14978
|
-
"Inline mode applies the profile prompt, first available preferred model, thinking level, and available tools."
|
|
14979
|
-
],
|
|
14980
|
-
parameters: listParameters,
|
|
14981
|
-
executionMode: "parallel",
|
|
14982
|
-
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
14983
|
-
const params = listParametersSchema.parse(input);
|
|
14984
|
-
const catalog = await controller.list(ctx, { includeSkills: params.includeSkills });
|
|
14985
|
-
return {
|
|
14986
|
-
content: [{ type: "text", text: formatCatalog(catalog, controller.getActive()?.id) }],
|
|
14987
|
-
details: { active: controller.getActive()?.id, catalog }
|
|
14988
|
-
};
|
|
14989
|
-
}
|
|
14990
|
-
}),
|
|
14991
|
-
defineTool5({
|
|
14992
|
-
name: "diffpi_modes_set",
|
|
14993
|
-
label: "diffpi modes set",
|
|
14994
|
-
description: "Set a validated available agent as the inline behavioral agent for subsequent chat turns.",
|
|
14995
|
-
promptSnippet: "Set the inline behavioral agent only after the user chooses one",
|
|
14996
|
-
promptGuidelines: [
|
|
14997
|
-
"Call diffpi_modes_set only after the user explicitly selects an agent.",
|
|
14998
|
-
"Use the exact skill:agent id for a skill-owned agent.",
|
|
14999
|
-
"The selected prompt takes effect on the next model turn."
|
|
15000
|
-
],
|
|
15001
|
-
parameters: setParameters,
|
|
15002
|
-
executionMode: "sequential",
|
|
15003
|
-
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
15004
|
-
const params = setParametersSchema.parse(input);
|
|
15005
|
-
const result = await controller.set(params.agent, ctx);
|
|
15006
|
-
if (!result.ok)
|
|
15007
|
-
throw new Error(result.message);
|
|
15008
|
-
return {
|
|
15009
|
-
content: [{ type: "text", text: `${result.message} The prompt takes effect on the next turn.` }],
|
|
15010
|
-
details: { active: result.active }
|
|
15011
|
-
};
|
|
15012
|
-
}
|
|
15013
|
-
}),
|
|
15014
|
-
defineTool5({
|
|
15015
|
-
name: "diffpi_modes_unset",
|
|
15016
|
-
label: "diffpi modes unset",
|
|
15017
|
-
description: "Clear the inline behavioral agent and restore default Pi prompting for subsequent turns.",
|
|
15018
|
-
promptSnippet: "Clear the inline agent when the user asks for default behavior",
|
|
15019
|
-
promptGuidelines: [
|
|
15020
|
-
"Call diffpi_modes_unset only when the user explicitly asks to clear the active inline agent."
|
|
15021
|
-
],
|
|
15022
|
-
parameters: emptyParameters,
|
|
15023
|
-
executionMode: "sequential",
|
|
15024
|
-
async execute(_toolCallId, input, _signal, _onUpdate, ctx) {
|
|
15025
|
-
emptyParametersSchema.parse(input);
|
|
15026
|
-
const result = await controller.unset(ctx);
|
|
15027
|
-
return {
|
|
15028
|
-
content: [{ type: "text", text: result.message }],
|
|
15029
|
-
details: { active: controller.getActive()?.id }
|
|
15030
|
-
};
|
|
15031
|
-
}
|
|
15032
|
-
})
|
|
15033
|
-
];
|
|
15034
|
-
}
|
|
15035
|
-
function formatCatalog(catalog, active) {
|
|
15036
|
-
const lines = [`Active inline agent: ${active ?? "default"}.`, "", "Available inline agents:"];
|
|
15037
|
-
for (const mode of catalog.modes) {
|
|
15038
|
-
const runtime = [mode.modelPreferences[0], mode.thinkingLevel].filter(Boolean).join(", ");
|
|
15039
|
-
lines.push(`- ${mode.id} [${mode.promptStrategy}${runtime ? `; ${runtime}` : ""}] — ${sanitize(mode.description)} (${mode.source})`);
|
|
15040
|
-
}
|
|
15041
|
-
if (catalog.diagnostics.length > 0) {
|
|
15042
|
-
lines.push("", "Skipped agent files:");
|
|
15043
|
-
for (const diagnostic of catalog.diagnostics)
|
|
15044
|
-
lines.push(`- ${sanitize(diagnostic)}`);
|
|
15045
|
-
}
|
|
15046
|
-
lines.push("", "Inline mode applies the profile prompt, preferred available model, thinking level, and tool set.");
|
|
15047
|
-
return lines.join(`
|
|
15048
|
-
`);
|
|
15049
|
-
}
|
|
15050
|
-
function sanitize(value) {
|
|
15051
|
-
return value.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
|
|
15052
|
-
}
|
|
15053
|
-
|
|
15054
14456
|
// src/tools/review.ts
|
|
15055
14457
|
import { existsSync as existsSync3 } from "node:fs";
|
|
15056
|
-
import { mkdir as mkdir4, readdir as
|
|
15057
|
-
import { join as
|
|
15058
|
-
import { defineTool as
|
|
15059
|
-
import { z as
|
|
14458
|
+
import { mkdir as mkdir4, readdir as readdir2, readFile as readFile10, rename as rename2, stat as stat2, unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
|
|
14459
|
+
import { join as join14 } from "node:path";
|
|
14460
|
+
import { defineTool as defineTool5 } from "@earendil-works/pi-coding-agent";
|
|
14461
|
+
import { z as z9 } from "zod";
|
|
15060
14462
|
|
|
15061
14463
|
// src/extensions/misex.ts
|
|
15062
|
-
import { mkdir as mkdir3, readFile as
|
|
15063
|
-
import { homedir as
|
|
15064
|
-
import { basename as
|
|
14464
|
+
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "node:fs/promises";
|
|
14465
|
+
import { homedir as homedir4 } from "node:os";
|
|
14466
|
+
import { basename as basename4, dirname as dirname6, join as join11 } from "node:path";
|
|
15065
14467
|
var MISE_HOOK_START = "# >>> @difflab/pi mise >>>";
|
|
15066
14468
|
var MISE_HOOK_END = "# <<< @difflab/pi mise <<<";
|
|
15067
14469
|
var mise = {
|
|
@@ -15072,11 +14474,11 @@ var mise = {
|
|
|
15072
14474
|
return run("mise", args, options);
|
|
15073
14475
|
},
|
|
15074
14476
|
async install(options = {}) {
|
|
15075
|
-
const homeDir = options.homeDir ??
|
|
14477
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
15076
14478
|
const platform = options.platform ?? process.platform;
|
|
15077
14479
|
if (platform === "win32")
|
|
15078
14480
|
throw new Error("Automatic mise installation supports macOS and Linux only.");
|
|
15079
|
-
const installedPath =
|
|
14481
|
+
const installedPath = join11(homeDir, ".local", "bin", "mise");
|
|
15080
14482
|
if (options.dryRun)
|
|
15081
14483
|
return installedPath;
|
|
15082
14484
|
await runChecked("sh", ["-c", "curl -fsSL https://mise.run | sh"]);
|
|
@@ -15086,8 +14488,8 @@ var mise = {
|
|
|
15086
14488
|
return executable;
|
|
15087
14489
|
},
|
|
15088
14490
|
async hookEnsure(executable, options = {}) {
|
|
15089
|
-
const homeDir = options.homeDir ??
|
|
15090
|
-
const hook = getShellHook(
|
|
14491
|
+
const homeDir = options.homeDir ?? homedir4();
|
|
14492
|
+
const hook = getShellHook(basename4(options.shell ?? process.env.SHELL ?? ""), executable, homeDir);
|
|
15091
14493
|
const current = await getOptionalFile(hook.path);
|
|
15092
14494
|
if (current.includes(MISE_HOOK_START))
|
|
15093
14495
|
return { path: hook.path, changed: false, planned: false };
|
|
@@ -15114,7 +14516,7 @@ var mise = {
|
|
|
15114
14516
|
async toolInstallLocal(executable, specification, cwd = process.cwd()) {
|
|
15115
14517
|
await runChecked(executable, ["use", "--path", cwd, specification], { cwd });
|
|
15116
14518
|
},
|
|
15117
|
-
async toolUpdateAllGlobal(executable, homeDir =
|
|
14519
|
+
async toolUpdateAllGlobal(executable, homeDir = homedir4()) {
|
|
15118
14520
|
await runChecked(executable, ["upgrade"], { cwd: homeDir });
|
|
15119
14521
|
}
|
|
15120
14522
|
};
|
|
@@ -15123,7 +14525,7 @@ function getShellHook(shell, executable, homeDir) {
|
|
|
15123
14525
|
switch (shell.toLowerCase()) {
|
|
15124
14526
|
case "zsh":
|
|
15125
14527
|
return {
|
|
15126
|
-
path:
|
|
14528
|
+
path: join11(homeDir, ".zshrc"),
|
|
15127
14529
|
content: `${MISE_HOOK_START}
|
|
15128
14530
|
eval "$(${command} activate zsh)"
|
|
15129
14531
|
${MISE_HOOK_END}
|
|
@@ -15131,7 +14533,7 @@ ${MISE_HOOK_END}
|
|
|
15131
14533
|
};
|
|
15132
14534
|
case "fish":
|
|
15133
14535
|
return {
|
|
15134
|
-
path:
|
|
14536
|
+
path: join11(homeDir, ".config", "fish", "config.fish"),
|
|
15135
14537
|
content: `${MISE_HOOK_START}
|
|
15136
14538
|
${command} activate fish | source
|
|
15137
14539
|
${MISE_HOOK_END}
|
|
@@ -15140,7 +14542,7 @@ ${MISE_HOOK_END}
|
|
|
15140
14542
|
case "nu":
|
|
15141
14543
|
case "nushell":
|
|
15142
14544
|
return {
|
|
15143
|
-
path:
|
|
14545
|
+
path: join11(homeDir, ".config", "nushell", "config.nu"),
|
|
15144
14546
|
content: `${MISE_HOOK_START}
|
|
15145
14547
|
let mise_bin = ${command}
|
|
15146
14548
|
let mise_path = $nu.default-config-dir | path join mise.nu
|
|
@@ -15151,7 +14553,7 @@ ${MISE_HOOK_END}
|
|
|
15151
14553
|
};
|
|
15152
14554
|
case "xonsh":
|
|
15153
14555
|
return {
|
|
15154
|
-
path:
|
|
14556
|
+
path: join11(homeDir, ".xonshrc"),
|
|
15155
14557
|
content: `${MISE_HOOK_START}
|
|
15156
14558
|
execx($(${command} activate xonsh))
|
|
15157
14559
|
${MISE_HOOK_END}
|
|
@@ -15159,7 +14561,7 @@ ${MISE_HOOK_END}
|
|
|
15159
14561
|
};
|
|
15160
14562
|
case "elvish":
|
|
15161
14563
|
return {
|
|
15162
|
-
path:
|
|
14564
|
+
path: join11(homeDir, ".config", "elvish", "rc.elv"),
|
|
15163
14565
|
content: `${MISE_HOOK_START}
|
|
15164
14566
|
var mise: = (ns [&])
|
|
15165
14567
|
eval (${command} activate elvish | slurp) &ns=$mise: &on-end={|ns| set mise: = $ns }
|
|
@@ -15170,7 +14572,7 @@ ${MISE_HOOK_END}
|
|
|
15170
14572
|
case "pwsh":
|
|
15171
14573
|
case "powershell":
|
|
15172
14574
|
return {
|
|
15173
|
-
path:
|
|
14575
|
+
path: join11(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
|
|
15174
14576
|
content: `${MISE_HOOK_START}
|
|
15175
14577
|
(& ${command} activate pwsh) | Out-String | Invoke-Expression
|
|
15176
14578
|
${MISE_HOOK_END}
|
|
@@ -15179,7 +14581,7 @@ ${MISE_HOOK_END}
|
|
|
15179
14581
|
case "bash":
|
|
15180
14582
|
default:
|
|
15181
14583
|
return {
|
|
15182
|
-
path:
|
|
14584
|
+
path: join11(homeDir, ".bashrc"),
|
|
15183
14585
|
content: `${MISE_HOOK_START}
|
|
15184
14586
|
eval "$(${command} activate bash)"
|
|
15185
14587
|
${MISE_HOOK_END}
|
|
@@ -15218,7 +14620,7 @@ function isVersionAtLeast(version, minimumVersion) {
|
|
|
15218
14620
|
}
|
|
15219
14621
|
async function getOptionalFile(path) {
|
|
15220
14622
|
try {
|
|
15221
|
-
return await
|
|
14623
|
+
return await readFile5(path, "utf8");
|
|
15222
14624
|
} catch (error) {
|
|
15223
14625
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
15224
14626
|
return "";
|
|
@@ -15289,16 +14691,16 @@ async function discoverMiseTasks(cwd) {
|
|
|
15289
14691
|
}
|
|
15290
14692
|
|
|
15291
14693
|
// src/review/types.ts
|
|
15292
|
-
import { z as
|
|
15293
|
-
var severitySchema =
|
|
15294
|
-
var findingSchema =
|
|
15295
|
-
file:
|
|
15296
|
-
line:
|
|
14694
|
+
import { z as z6 } from "zod";
|
|
14695
|
+
var severitySchema = z6.enum(["BLOCKING", "CONSIDER", "NOTE"]);
|
|
14696
|
+
var findingSchema = z6.object({
|
|
14697
|
+
file: z6.string().min(1),
|
|
14698
|
+
line: z6.number().int().nonnegative(),
|
|
15297
14699
|
severity: severitySchema,
|
|
15298
|
-
body:
|
|
15299
|
-
reference:
|
|
14700
|
+
body: z6.string().min(1),
|
|
14701
|
+
reference: z6.string().optional().default("")
|
|
15300
14702
|
});
|
|
15301
|
-
var findingsSchema =
|
|
14703
|
+
var findingsSchema = z6.array(findingSchema);
|
|
15302
14704
|
function reviewSlug(input) {
|
|
15303
14705
|
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
15304
14706
|
}
|
|
@@ -15371,17 +14773,17 @@ function renderCommentBody(finding) {
|
|
|
15371
14773
|
return `${prefix}${finding.body}${reference}`;
|
|
15372
14774
|
}
|
|
15373
14775
|
// src/review/review-markdown.ts
|
|
15374
|
-
import { z as
|
|
15375
|
-
var reviewThreadRecordSchema =
|
|
15376
|
-
id:
|
|
15377
|
-
file:
|
|
15378
|
-
line:
|
|
15379
|
-
body:
|
|
15380
|
-
author:
|
|
15381
|
-
resolved:
|
|
15382
|
-
addressed:
|
|
15383
|
-
question:
|
|
15384
|
-
replies:
|
|
14776
|
+
import { z as z7 } from "zod";
|
|
14777
|
+
var reviewThreadRecordSchema = z7.object({
|
|
14778
|
+
id: z7.string().min(1),
|
|
14779
|
+
file: z7.string().nullable().optional().transform((value) => value ?? undefined),
|
|
14780
|
+
line: z7.number().int().positive().nullable().optional().transform((value) => value ?? undefined),
|
|
14781
|
+
body: z7.string(),
|
|
14782
|
+
author: z7.string().optional(),
|
|
14783
|
+
resolved: z7.boolean(),
|
|
14784
|
+
addressed: z7.boolean().optional(),
|
|
14785
|
+
question: z7.boolean(),
|
|
14786
|
+
replies: z7.array(z7.string()).optional()
|
|
15385
14787
|
});
|
|
15386
14788
|
function renderReviewDoc(input) {
|
|
15387
14789
|
const anchored = dedupeFindings(input.findings).filter((finding) => finding.line > 0);
|
|
@@ -15468,7 +14870,7 @@ function parseThreadArtifact(content) {
|
|
|
15468
14870
|
let threads;
|
|
15469
14871
|
try {
|
|
15470
14872
|
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
15471
|
-
threads =
|
|
14873
|
+
threads = z7.array(reviewThreadRecordSchema).parse(decoded);
|
|
15472
14874
|
} catch {
|
|
15473
14875
|
throw new Error("Cannot parse the Diffpi thread artifact payload.");
|
|
15474
14876
|
}
|
|
@@ -15886,10 +15288,10 @@ function hasGitlabDraftNotes(input) {
|
|
|
15886
15288
|
}
|
|
15887
15289
|
|
|
15888
15290
|
// src/review/local-review-backend.ts
|
|
15889
|
-
import { readFile as
|
|
15291
|
+
import { readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
|
|
15890
15292
|
|
|
15891
15293
|
// src/extensions/tuicrx.ts
|
|
15892
|
-
import { readFile as
|
|
15294
|
+
import { readFile as readFile6, realpath as realpath3 } from "node:fs/promises";
|
|
15893
15295
|
import { resolve as resolve5 } from "node:path";
|
|
15894
15296
|
async function tuicrAvailable() {
|
|
15895
15297
|
return Boolean(await findExecutable("tuicr"));
|
|
@@ -15986,7 +15388,7 @@ async function findMatchingSession(sessions, cwd, branch) {
|
|
|
15986
15388
|
return;
|
|
15987
15389
|
}
|
|
15988
15390
|
async function readSession(path) {
|
|
15989
|
-
const content = await
|
|
15391
|
+
const content = await readFile6(path, "utf8");
|
|
15990
15392
|
try {
|
|
15991
15393
|
return JSON.parse(content);
|
|
15992
15394
|
} catch {
|
|
@@ -16107,7 +15509,7 @@ function LocalReviewBackend(options) {
|
|
|
16107
15509
|
},
|
|
16108
15510
|
async listThreads() {
|
|
16109
15511
|
try {
|
|
16110
|
-
return parseThreadArtifact(await
|
|
15512
|
+
return parseThreadArtifact(await readFile7(options.artifactPath, "utf8"));
|
|
16111
15513
|
} catch (error) {
|
|
16112
15514
|
if (error.code === "ENOENT")
|
|
16113
15515
|
return [];
|
|
@@ -16115,7 +15517,7 @@ function LocalReviewBackend(options) {
|
|
|
16115
15517
|
}
|
|
16116
15518
|
},
|
|
16117
15519
|
async reply(input) {
|
|
16118
|
-
const content = await
|
|
15520
|
+
const content = await readFile7(options.artifactPath, "utf8");
|
|
16119
15521
|
await writeFile3(options.artifactPath, upsertThreadReply(content, input.threadId, input.body, input.question, input.resolve), "utf8");
|
|
16120
15522
|
},
|
|
16121
15523
|
async publish() {
|
|
@@ -16137,16 +15539,16 @@ function createLocalReviewBackend(options) {
|
|
|
16137
15539
|
}
|
|
16138
15540
|
// src/review/review-state.ts
|
|
16139
15541
|
import { createHash as createHash2 } from "node:crypto";
|
|
16140
|
-
import { readFile as
|
|
16141
|
-
import { join as
|
|
16142
|
-
import { z as
|
|
16143
|
-
var reviewPublicationStateSchema =
|
|
16144
|
-
target:
|
|
16145
|
-
bodies:
|
|
16146
|
-
comments:
|
|
16147
|
-
stagedComments:
|
|
16148
|
-
replies:
|
|
16149
|
-
overlayPath:
|
|
15542
|
+
import { readFile as readFile8, rename, writeFile as writeFile4 } from "node:fs/promises";
|
|
15543
|
+
import { join as join12 } from "node:path";
|
|
15544
|
+
import { z as z8 } from "zod";
|
|
15545
|
+
var reviewPublicationStateSchema = z8.object({
|
|
15546
|
+
target: z8.string().optional(),
|
|
15547
|
+
bodies: z8.array(z8.string()).default([]),
|
|
15548
|
+
comments: z8.array(z8.string()).default([]),
|
|
15549
|
+
stagedComments: z8.array(z8.string()).default([]),
|
|
15550
|
+
replies: z8.array(z8.string()).default([]),
|
|
15551
|
+
overlayPath: z8.string().optional()
|
|
16150
15552
|
});
|
|
16151
15553
|
function reviewBodyFingerprint(body) {
|
|
16152
15554
|
return digest(body);
|
|
@@ -16162,15 +15564,15 @@ function unpublishedReviewComments(comments, knownFingerprints) {
|
|
|
16162
15564
|
}
|
|
16163
15565
|
async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
|
|
16164
15566
|
const target = `${vcs.provider}:${vcs.owner}/${vcs.repo}#${number}`;
|
|
16165
|
-
const path =
|
|
15567
|
+
const path = join12(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
|
|
16166
15568
|
try {
|
|
16167
|
-
const parsed = reviewPublicationStateSchema.parse(JSON.parse(await
|
|
15569
|
+
const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile8(path, "utf8")));
|
|
16168
15570
|
return { path, state: { ...parsed, target } };
|
|
16169
15571
|
} catch (error) {
|
|
16170
15572
|
if (error.code === "ENOENT") {
|
|
16171
15573
|
return { path, state: { target, bodies: [], comments: [], stagedComments: [], replies: [] } };
|
|
16172
15574
|
}
|
|
16173
|
-
if (error instanceof SyntaxError || error instanceof
|
|
15575
|
+
if (error instanceof SyntaxError || error instanceof z8.ZodError) {
|
|
16174
15576
|
throw new Error(`Cannot parse review publication state: ${path}`);
|
|
16175
15577
|
}
|
|
16176
15578
|
throw error;
|
|
@@ -16186,13 +15588,13 @@ function digest(value) {
|
|
|
16186
15588
|
return createHash2("sha256").update(value).digest("hex");
|
|
16187
15589
|
}
|
|
16188
15590
|
// src/templates.ts
|
|
16189
|
-
import { readFile as
|
|
16190
|
-
import { homedir as
|
|
16191
|
-
import { join as
|
|
15591
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
15592
|
+
import { homedir as homedir5 } from "node:os";
|
|
15593
|
+
import { join as join13, normalize } from "node:path";
|
|
16192
15594
|
async function loadTemplate(name, options = {}) {
|
|
16193
15595
|
const relative = templateRelativePath(name);
|
|
16194
|
-
const userPath =
|
|
16195
|
-
const bundledPath =
|
|
15596
|
+
const userPath = join13(options.homeDir ?? homedir5(), ".difflab", "diffpi", "templates", relative);
|
|
15597
|
+
const bundledPath = join13(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
|
|
16196
15598
|
const user = await readOptionalFile(userPath);
|
|
16197
15599
|
if (user !== undefined)
|
|
16198
15600
|
return { name, path: userPath, source: "user", content: user };
|
|
@@ -16213,7 +15615,7 @@ function templateRelativePath(name) {
|
|
|
16213
15615
|
}
|
|
16214
15616
|
async function readOptionalFile(path) {
|
|
16215
15617
|
try {
|
|
16216
|
-
return await
|
|
15618
|
+
return await readFile9(path, "utf8");
|
|
16217
15619
|
} catch (error) {
|
|
16218
15620
|
if (error.code === "ENOENT")
|
|
16219
15621
|
return;
|
|
@@ -16222,41 +15624,41 @@ async function readOptionalFile(path) {
|
|
|
16222
15624
|
}
|
|
16223
15625
|
|
|
16224
15626
|
// src/tools/review.ts
|
|
16225
|
-
var contextSchema =
|
|
16226
|
-
cwd:
|
|
16227
|
-
target:
|
|
16228
|
-
workingTree:
|
|
15627
|
+
var contextSchema = z9.object({
|
|
15628
|
+
cwd: z9.string().optional(),
|
|
15629
|
+
target: z9.string().optional(),
|
|
15630
|
+
workingTree: z9.boolean().optional()
|
|
16229
15631
|
});
|
|
16230
|
-
var localSchema = contextSchema.extend({ local:
|
|
15632
|
+
var localSchema = contextSchema.extend({ local: z9.boolean().optional() });
|
|
16231
15633
|
var openSchema = localSchema.extend({
|
|
16232
|
-
title:
|
|
16233
|
-
intent:
|
|
16234
|
-
issueUrl:
|
|
16235
|
-
base:
|
|
15634
|
+
title: z9.string().optional(),
|
|
15635
|
+
intent: z9.string().optional(),
|
|
15636
|
+
issueUrl: z9.string().url().optional(),
|
|
15637
|
+
base: z9.string().optional()
|
|
16236
15638
|
});
|
|
16237
15639
|
var submitSchema = localSchema.extend({
|
|
16238
15640
|
findings: findingsSchema,
|
|
16239
|
-
overallIssues:
|
|
16240
|
-
notVerified:
|
|
16241
|
-
title:
|
|
15641
|
+
overallIssues: z9.array(z9.string()).optional(),
|
|
15642
|
+
notVerified: z9.array(z9.string()).optional(),
|
|
15643
|
+
title: z9.string().optional()
|
|
16242
15644
|
});
|
|
16243
15645
|
var addCommentSchema = localSchema.extend({
|
|
16244
|
-
body:
|
|
16245
|
-
file:
|
|
16246
|
-
line:
|
|
16247
|
-
side:
|
|
15646
|
+
body: z9.string().min(1),
|
|
15647
|
+
file: z9.string().min(1),
|
|
15648
|
+
line: z9.number().int().positive(),
|
|
15649
|
+
side: z9.enum(["LEFT", "RIGHT"]).optional()
|
|
16248
15650
|
});
|
|
16249
15651
|
var respondSchema = localSchema.extend({
|
|
16250
|
-
threadId:
|
|
16251
|
-
body:
|
|
16252
|
-
question:
|
|
16253
|
-
resolve:
|
|
15652
|
+
threadId: z9.string().min(1),
|
|
15653
|
+
body: z9.string().min(1),
|
|
15654
|
+
question: z9.boolean().optional(),
|
|
15655
|
+
resolve: z9.boolean().optional()
|
|
16254
15656
|
});
|
|
16255
15657
|
var publishSchema = localSchema.extend({
|
|
16256
|
-
status:
|
|
15658
|
+
status: z9.enum(["COMMENT", "APPROVE", "REQUEST_CHANGES", "CLOSE"]).optional()
|
|
16257
15659
|
});
|
|
16258
15660
|
var completeSchema = localSchema.extend({
|
|
16259
|
-
action:
|
|
15661
|
+
action: z9.enum(["approve", "reject", "abandon"]).optional()
|
|
16260
15662
|
});
|
|
16261
15663
|
function hasReviewDraft(comments, body) {
|
|
16262
15664
|
return comments.length > 0 || body.trim().length > 0;
|
|
@@ -16330,7 +15732,7 @@ async function workingTreeDiff(cwd) {
|
|
|
16330
15732
|
}
|
|
16331
15733
|
function createReviewTools() {
|
|
16332
15734
|
return [
|
|
16333
|
-
|
|
15735
|
+
defineTool5({
|
|
16334
15736
|
name: "review_context",
|
|
16335
15737
|
label: "review context",
|
|
16336
15738
|
description: "Orient to the target, backend, forge, environment, shared store, PR/MR, and tuicr session.",
|
|
@@ -16369,7 +15771,7 @@ function createReviewTools() {
|
|
|
16369
15771
|
});
|
|
16370
15772
|
}
|
|
16371
15773
|
}),
|
|
16372
|
-
|
|
15774
|
+
defineTool5({
|
|
16373
15775
|
name: "review_status",
|
|
16374
15776
|
label: "review status",
|
|
16375
15777
|
description: "Report local and remote review lifecycle status without requiring a forge or tuicr.",
|
|
@@ -16384,7 +15786,7 @@ function createReviewTools() {
|
|
|
16384
15786
|
return result2(status.text, status.details);
|
|
16385
15787
|
}
|
|
16386
15788
|
}),
|
|
16387
|
-
|
|
15789
|
+
defineTool5({
|
|
16388
15790
|
name: "review_new",
|
|
16389
15791
|
label: "review new",
|
|
16390
15792
|
description: "Create a new local tuicr review or a remote draft PR/MR and open it in tuicr.",
|
|
@@ -16427,7 +15829,7 @@ ${openMessage}`, {
|
|
|
16427
15829
|
});
|
|
16428
15830
|
}
|
|
16429
15831
|
}),
|
|
16430
|
-
|
|
15832
|
+
defineTool5({
|
|
16431
15833
|
name: "review_open",
|
|
16432
15834
|
label: "review open",
|
|
16433
15835
|
description: "Open an existing remote PR/MR in the system browser; never creates one.",
|
|
@@ -16452,7 +15854,7 @@ ${openMessage}`, {
|
|
|
16452
15854
|
});
|
|
16453
15855
|
}
|
|
16454
15856
|
}),
|
|
16455
|
-
|
|
15857
|
+
defineTool5({
|
|
16456
15858
|
name: "review_edit",
|
|
16457
15859
|
label: "review edit",
|
|
16458
15860
|
description: "Open an existing local tuicr session or remote PR/MR in tuicr without generating findings.",
|
|
@@ -16476,7 +15878,7 @@ ${openMessage}`, {
|
|
|
16476
15878
|
return launchLocalReview(review, false);
|
|
16477
15879
|
}
|
|
16478
15880
|
}),
|
|
16479
|
-
|
|
15881
|
+
defineTool5({
|
|
16480
15882
|
name: "review_diff",
|
|
16481
15883
|
label: "review diff",
|
|
16482
15884
|
description: "Fetch the target PR/MR diff or auto-detected local working-tree diff.",
|
|
@@ -16499,7 +15901,7 @@ ${openMessage}`, {
|
|
|
16499
15901
|
return result2(diff || "Empty diff.", { diff, pr: review.pr });
|
|
16500
15902
|
}
|
|
16501
15903
|
}),
|
|
16502
|
-
|
|
15904
|
+
defineTool5({
|
|
16503
15905
|
name: "review_gates",
|
|
16504
15906
|
label: "review gates",
|
|
16505
15907
|
description: "Run format, lint, test, conventional-subject, and available CI checks.",
|
|
@@ -16529,7 +15931,7 @@ ${openMessage}`, {
|
|
|
16529
15931
|
});
|
|
16530
15932
|
}
|
|
16531
15933
|
}),
|
|
16532
|
-
|
|
15934
|
+
defineTool5({
|
|
16533
15935
|
name: "review_submit",
|
|
16534
15936
|
label: "review submit",
|
|
16535
15937
|
description: "Write the review artifact and stage comments in the selected local or remote backend.",
|
|
@@ -16592,7 +15994,7 @@ ${openMessage}`, {
|
|
|
16592
15994
|
});
|
|
16593
15995
|
}
|
|
16594
15996
|
}),
|
|
16595
|
-
|
|
15997
|
+
defineTool5({
|
|
16596
15998
|
name: "review_add_comment",
|
|
16597
15999
|
label: "review add comment",
|
|
16598
16000
|
description: "Add one provenance-marked comment through tuicr or the remote pending-review backend.",
|
|
@@ -16631,7 +16033,7 @@ ${openMessage}`, {
|
|
|
16631
16033
|
return result2(`Draft comment added to #${review.pr.number}.`, { pr: review.pr, comment });
|
|
16632
16034
|
}
|
|
16633
16035
|
}),
|
|
16634
|
-
|
|
16036
|
+
defineTool5({
|
|
16635
16037
|
name: "review_comments",
|
|
16636
16038
|
label: "review comments",
|
|
16637
16039
|
description: "Pull review threads or local tuicr comments and write a target-named artifact.",
|
|
@@ -16662,7 +16064,7 @@ ${openMessage}`, {
|
|
|
16662
16064
|
`) || "No comments.", { artifact, threads });
|
|
16663
16065
|
}
|
|
16664
16066
|
}),
|
|
16665
|
-
|
|
16067
|
+
defineTool5({
|
|
16666
16068
|
name: "review_respond",
|
|
16667
16069
|
label: "review respond",
|
|
16668
16070
|
description: "Post local question replies to tuicr and record their publish overlay, or reply to remote threads.",
|
|
@@ -16723,7 +16125,7 @@ ${params.body}`, {
|
|
|
16723
16125
|
});
|
|
16724
16126
|
}
|
|
16725
16127
|
}),
|
|
16726
|
-
|
|
16128
|
+
defineTool5({
|
|
16727
16129
|
name: "review_publish",
|
|
16728
16130
|
label: "review publish",
|
|
16729
16131
|
description: "Publish pending remote draft PR/MR review comments and status, or promote a local tuicr draft before publishing.",
|
|
@@ -16739,7 +16141,7 @@ ${params.body}`, {
|
|
|
16739
16141
|
return publishResolvedReview(review, params, modelRoute(ctx));
|
|
16740
16142
|
}
|
|
16741
16143
|
}),
|
|
16742
|
-
|
|
16144
|
+
defineTool5({
|
|
16743
16145
|
name: "review_complete",
|
|
16744
16146
|
label: "review complete",
|
|
16745
16147
|
description: "Approve, reject, or abandon a remote review, or archive a local review artifact.",
|
|
@@ -16773,7 +16175,7 @@ ${params.body}`, {
|
|
|
16773
16175
|
return publishResolvedReview(review, { ...params, status }, modelRoute(ctx));
|
|
16774
16176
|
}
|
|
16775
16177
|
}),
|
|
16776
|
-
|
|
16178
|
+
defineTool5({
|
|
16777
16179
|
name: "review_merge",
|
|
16778
16180
|
label: "review merge",
|
|
16779
16181
|
description: "Squash-merge an open, ready GitHub PR through gh after checking its conventional subject.",
|
|
@@ -16782,10 +16184,10 @@ ${params.body}`, {
|
|
|
16782
16184
|
"Do not query GitHub through MCP or duplicate readiness checks before calling review_merge.",
|
|
16783
16185
|
"This is intentionally GitHub-only until GitLab merge support is added."
|
|
16784
16186
|
],
|
|
16785
|
-
parameters: parameters5(contextSchema.extend({ subject:
|
|
16187
|
+
parameters: parameters5(contextSchema.extend({ subject: z9.string().optional() })),
|
|
16786
16188
|
executionMode: "sequential",
|
|
16787
16189
|
async execute(_id, input) {
|
|
16788
|
-
const params = contextSchema.extend({ subject:
|
|
16190
|
+
const params = contextSchema.extend({ subject: z9.string().optional() }).parse(input);
|
|
16789
16191
|
const review = await resolveReviewContext(resolveWorkingDirectory(params), params.target);
|
|
16790
16192
|
if (review.vcs.provider !== "github" || !review.forge)
|
|
16791
16193
|
return result2("review_merge currently supports GitHub only.");
|
|
@@ -16799,7 +16201,7 @@ ${params.body}`, {
|
|
|
16799
16201
|
return result2(`Merged #${review.pr.number} with subject: ${subject}.`, { pr: review.pr, guard });
|
|
16800
16202
|
}
|
|
16801
16203
|
}),
|
|
16802
|
-
|
|
16204
|
+
defineTool5({
|
|
16803
16205
|
name: "review_launch_ui",
|
|
16804
16206
|
label: "review launch UI",
|
|
16805
16207
|
description: "Open a tuicr UI in a mux tab, configure Zed, or return the command to run.",
|
|
@@ -16821,7 +16223,7 @@ ${params.body}`, {
|
|
|
16821
16223
|
async function syncLocalThreadArtifact(path, title, sessionSlug, threads) {
|
|
16822
16224
|
let previous = [];
|
|
16823
16225
|
try {
|
|
16824
|
-
previous = parseThreadArtifact(await
|
|
16226
|
+
previous = parseThreadArtifact(await readFile10(path, "utf8"));
|
|
16825
16227
|
} catch (error) {
|
|
16826
16228
|
if (error.code !== "ENOENT")
|
|
16827
16229
|
throw error;
|
|
@@ -16843,7 +16245,7 @@ async function syncLocalThreadArtifact(path, title, sessionSlug, threads) {
|
|
|
16843
16245
|
return merged;
|
|
16844
16246
|
}
|
|
16845
16247
|
function parameters5(schema) {
|
|
16846
|
-
return
|
|
16248
|
+
return z9.toJSONSchema(schema, { io: "input" });
|
|
16847
16249
|
}
|
|
16848
16250
|
function resolveWorkingDirectory(params) {
|
|
16849
16251
|
return params.cwd ?? process.cwd();
|
|
@@ -17129,11 +16531,11 @@ async function newReviewArtifactPath(cwd, target) {
|
|
|
17129
16531
|
}
|
|
17130
16532
|
async function localThreadArtifactPath(cwd, sessionSlug) {
|
|
17131
16533
|
const dir = await reviewsDir(cwd);
|
|
17132
|
-
return
|
|
16534
|
+
return join14(dir, `${reviewSlug(sessionSlug) || "local-review"}.md`);
|
|
17133
16535
|
}
|
|
17134
16536
|
async function readThreadArtifact(path) {
|
|
17135
16537
|
try {
|
|
17136
|
-
return parseThreadArtifact(await
|
|
16538
|
+
return parseThreadArtifact(await readFile10(path, "utf8"));
|
|
17137
16539
|
} catch (error) {
|
|
17138
16540
|
if (error.code === "ENOENT") {
|
|
17139
16541
|
throw new Error(`Expected local reply overlay is missing: ${path}`);
|
|
@@ -17157,9 +16559,9 @@ async function findLatestThreadArtifact(cwd, review, target) {
|
|
|
17157
16559
|
return [];
|
|
17158
16560
|
return [
|
|
17159
16561
|
(async () => {
|
|
17160
|
-
const path =
|
|
16562
|
+
const path = join14(dir, name);
|
|
17161
16563
|
const info = await stat2(path);
|
|
17162
|
-
return { path, name, content: await
|
|
16564
|
+
return { path, name, content: await readFile10(path, "utf8"), modified: info.mtimeMs };
|
|
17163
16565
|
})()
|
|
17164
16566
|
];
|
|
17165
16567
|
}));
|
|
@@ -17173,7 +16575,7 @@ async function findLatestThreadArtifact(cwd, review, target) {
|
|
|
17173
16575
|
}
|
|
17174
16576
|
async function listReviewArtifactNames(dir) {
|
|
17175
16577
|
try {
|
|
17176
|
-
return await
|
|
16578
|
+
return await readdir2(dir);
|
|
17177
16579
|
} catch (error) {
|
|
17178
16580
|
throw new Error(`Cannot read review artifacts in ${dir}.`, { cause: error });
|
|
17179
16581
|
}
|
|
@@ -17182,10 +16584,10 @@ function deriveTitle(branch) {
|
|
|
17182
16584
|
return branch.replace(/^(feature|feat|fix|bug|chore)\//, "").replace(/^eng-\d+-/i, "").replace(/[-_]+/g, " ").replace(/^\w/, (char) => char.toUpperCase());
|
|
17183
16585
|
}
|
|
17184
16586
|
function uniqueRecordPath(dir, base) {
|
|
17185
|
-
let path =
|
|
16587
|
+
let path = join14(dir, `${base}.md`);
|
|
17186
16588
|
let count = 2;
|
|
17187
16589
|
while (existsSync3(path))
|
|
17188
|
-
path =
|
|
16590
|
+
path = join14(dir, `${base}-${count++}.md`);
|
|
17189
16591
|
return path;
|
|
17190
16592
|
}
|
|
17191
16593
|
function artifactNameMatches(name, suffix) {
|
|
@@ -17201,22 +16603,110 @@ function artifactNameMatches(name, suffix) {
|
|
|
17201
16603
|
}
|
|
17202
16604
|
|
|
17203
16605
|
// src/tools/setup.ts
|
|
17204
|
-
import { defineTool as
|
|
17205
|
-
import { z as
|
|
16606
|
+
import { defineTool as defineTool6 } from "@earendil-works/pi-coding-agent";
|
|
16607
|
+
import { z as z11 } from "zod";
|
|
17206
16608
|
|
|
17207
16609
|
// src/setup.ts
|
|
17208
|
-
import { parseFrontmatter as
|
|
17209
|
-
import { readdir as readdir4, readFile as
|
|
17210
|
-
import { homedir as
|
|
17211
|
-
import { basename as
|
|
16610
|
+
import { parseFrontmatter as parseFrontmatter2 } from "@earendil-works/pi-coding-agent";
|
|
16611
|
+
import { readdir as readdir4, readFile as readFile13 } from "node:fs/promises";
|
|
16612
|
+
import { homedir as homedir9 } from "node:os";
|
|
16613
|
+
import { basename as basename5, join as join18 } from "node:path";
|
|
16614
|
+
|
|
16615
|
+
// src/config.ts
|
|
16616
|
+
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
16617
|
+
import { z as z10 } from "zod";
|
|
16618
|
+
import { homedir as homedir6 } from "node:os";
|
|
16619
|
+
import { join as join15 } from "node:path";
|
|
16620
|
+
|
|
16621
|
+
// src/fsx.ts
|
|
16622
|
+
import { readdir as readdir3, readFile as readFile11 } from "node:fs/promises";
|
|
16623
|
+
async function readTextIfExists(path) {
|
|
16624
|
+
try {
|
|
16625
|
+
return await readFile11(path, "utf8");
|
|
16626
|
+
} catch (error) {
|
|
16627
|
+
if (isMissingPath(error))
|
|
16628
|
+
return;
|
|
16629
|
+
throw error;
|
|
16630
|
+
}
|
|
16631
|
+
}
|
|
16632
|
+
function isMissingPath(error) {
|
|
16633
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
16634
|
+
}
|
|
16635
|
+
|
|
16636
|
+
// src/config.ts
|
|
16637
|
+
var modelReferenceSchema = zx.text;
|
|
16638
|
+
var agentConfigSchema = z10.object({
|
|
16639
|
+
models: z10.array(modelReferenceSchema).optional()
|
|
16640
|
+
}).strict();
|
|
16641
|
+
var diffpiConfigSchema = z10.object({
|
|
16642
|
+
agents: z10.record(z10.string(), agentConfigSchema).optional()
|
|
16643
|
+
}).strict();
|
|
16644
|
+
function diffpiConfigPaths(homeDir = homedir6()) {
|
|
16645
|
+
const directory = join15(homeDir, ".difflab", "diffpi");
|
|
16646
|
+
return {
|
|
16647
|
+
yaml: join15(directory, "config.yaml"),
|
|
16648
|
+
json: join15(directory, "config.json")
|
|
16649
|
+
};
|
|
16650
|
+
}
|
|
16651
|
+
async function loadDiffpiConfig(options = {}) {
|
|
16652
|
+
const paths = diffpiConfigPaths(options.homeDir);
|
|
16653
|
+
for (const [format, path] of [
|
|
16654
|
+
["yaml", paths.yaml],
|
|
16655
|
+
["json", paths.json]
|
|
16656
|
+
]) {
|
|
16657
|
+
const content = await readTextIfExists(path);
|
|
16658
|
+
if (content === undefined)
|
|
16659
|
+
continue;
|
|
16660
|
+
try {
|
|
16661
|
+
const value = format === "yaml" ? parseYamlConfig(content) : JSON.parse(content);
|
|
16662
|
+
return { config: diffpiConfigSchema.parse(value ?? {}), path };
|
|
16663
|
+
} catch (error) {
|
|
16664
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
16665
|
+
throw new Error(`Invalid Diffpi config at ${path}: ${reason}`, { cause: error });
|
|
16666
|
+
}
|
|
16667
|
+
}
|
|
16668
|
+
return { config: {} };
|
|
16669
|
+
}
|
|
16670
|
+
function resolveAgentModelPreferences(agentId, profilePreferences, config) {
|
|
16671
|
+
const override = config.agents?.[agentId];
|
|
16672
|
+
if (override && Object.hasOwn(override, "models"))
|
|
16673
|
+
return [...override.models ?? []];
|
|
16674
|
+
return [...profilePreferences];
|
|
16675
|
+
}
|
|
16676
|
+
function findPreferredModel(models, preference) {
|
|
16677
|
+
const normalizedPreference = normalizeModelReference(preference);
|
|
16678
|
+
const exactReference = models.find((model) => normalizeModelReference(`${model.provider}/${model.id}`) === normalizedPreference);
|
|
16679
|
+
if (exactReference)
|
|
16680
|
+
return exactReference;
|
|
16681
|
+
const idPreference = preference.includes("/") ? preference.slice(preference.indexOf("/") + 1) : preference;
|
|
16682
|
+
const normalizedIdPreference = normalizeModelReference(idPreference);
|
|
16683
|
+
const exactId = models.find((model) => normalizeModelReference(model.id) === normalizedIdPreference);
|
|
16684
|
+
if (exactId)
|
|
16685
|
+
return exactId;
|
|
16686
|
+
const preferenceTokens = normalizedIdPreference.split("-").filter(Boolean);
|
|
16687
|
+
return models.find((model) => {
|
|
16688
|
+
const modelTokens = new Set(normalizeModelReference(model.id).split("-").filter(Boolean));
|
|
16689
|
+
return preferenceTokens.every((token) => modelTokens.has(token));
|
|
16690
|
+
});
|
|
16691
|
+
}
|
|
16692
|
+
function parseYamlConfig(content) {
|
|
16693
|
+
const document = content.replace(/^\uFEFF/, "").replace(/^---[^\S\r\n]*(?:#.*)?(?:\r?\n|$)/, "");
|
|
16694
|
+
return parseFrontmatter(`---
|
|
16695
|
+
${document}
|
|
16696
|
+
---
|
|
16697
|
+
`).frontmatter;
|
|
16698
|
+
}
|
|
16699
|
+
function normalizeModelReference(value) {
|
|
16700
|
+
return value.toLowerCase().replace(/^~/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
16701
|
+
}
|
|
17212
16702
|
|
|
17213
16703
|
// src/mcp.ts
|
|
17214
|
-
import { mkdir as mkdir5, readFile as
|
|
17215
|
-
import { homedir as
|
|
17216
|
-
import { dirname as dirname7, join as
|
|
16704
|
+
import { mkdir as mkdir5, readFile as readFile12, writeFile as writeFile6 } from "node:fs/promises";
|
|
16705
|
+
import { homedir as homedir7 } from "node:os";
|
|
16706
|
+
import { dirname as dirname7, join as join16 } from "node:path";
|
|
17217
16707
|
var mcp = {
|
|
17218
|
-
globalConfigPath(homeDir =
|
|
17219
|
-
return
|
|
16708
|
+
globalConfigPath(homeDir = homedir7()) {
|
|
16709
|
+
return join16(homeDir, ".config", "mcp", "mcp.json");
|
|
17220
16710
|
},
|
|
17221
16711
|
async serversEnsure(servers, options = {}) {
|
|
17222
16712
|
const path = options.path ?? mcp.globalConfigPath();
|
|
@@ -17259,7 +16749,7 @@ function getParsedConfig(content, path) {
|
|
|
17259
16749
|
}
|
|
17260
16750
|
async function getOptionalFile2(path) {
|
|
17261
16751
|
try {
|
|
17262
|
-
return await
|
|
16752
|
+
return await readFile12(path, "utf8");
|
|
17263
16753
|
} catch (error) {
|
|
17264
16754
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
17265
16755
|
return;
|
|
@@ -17272,8 +16762,8 @@ function isRecord(value) {
|
|
|
17272
16762
|
|
|
17273
16763
|
// src/pi.ts
|
|
17274
16764
|
import { mkdir as mkdir6, writeFile as writeFile7 } from "node:fs/promises";
|
|
17275
|
-
import { homedir as
|
|
17276
|
-
import { dirname as dirname8, join as
|
|
16765
|
+
import { homedir as homedir8 } from "node:os";
|
|
16766
|
+
import { dirname as dirname8, join as join17 } from "node:path";
|
|
17277
16767
|
var pi = {
|
|
17278
16768
|
executableCheck: findPiExecutable,
|
|
17279
16769
|
packageList: listPiPackages,
|
|
@@ -17286,7 +16776,7 @@ var pi = {
|
|
|
17286
16776
|
configEnsure: ensurePiConfig
|
|
17287
16777
|
};
|
|
17288
16778
|
async function ensurePiAgent(filename, content, agentDir = resolvePiAgentDir(), dryRun = false) {
|
|
17289
|
-
const path =
|
|
16779
|
+
const path = join17(agentDir, "agents", filename);
|
|
17290
16780
|
const currentText = await readTextIfExists(path);
|
|
17291
16781
|
const changed = currentText !== content;
|
|
17292
16782
|
if (changed && !dryRun) {
|
|
@@ -17295,10 +16785,10 @@ async function ensurePiAgent(filename, content, agentDir = resolvePiAgentDir(),
|
|
|
17295
16785
|
}
|
|
17296
16786
|
return { path, changed, existed: currentText !== undefined, planned: changed && dryRun };
|
|
17297
16787
|
}
|
|
17298
|
-
async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir =
|
|
17299
|
-
const roots = [
|
|
16788
|
+
async function checkGlobalPiSkill(name, agentDir = resolvePiAgentDir(), sharedSkillsDir = join17(homedir8(), ".agents", "skills")) {
|
|
16789
|
+
const roots = [join17(agentDir, "skills"), sharedSkillsDir];
|
|
17300
16790
|
for (const root of roots) {
|
|
17301
|
-
if (await readTextIfExists(
|
|
16791
|
+
if (await readTextIfExists(join17(root, name, "SKILL.md")) !== undefined)
|
|
17302
16792
|
return true;
|
|
17303
16793
|
}
|
|
17304
16794
|
return false;
|
|
@@ -17347,8 +16837,8 @@ function hasPiPackage(listOutput, source) {
|
|
|
17347
16837
|
async function installPiPackage(executable, source) {
|
|
17348
16838
|
await runChecked(executable, ["install", source]);
|
|
17349
16839
|
}
|
|
17350
|
-
function resolvePiAgentDir(homeDir =
|
|
17351
|
-
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ?
|
|
16840
|
+
function resolvePiAgentDir(homeDir = homedir8()) {
|
|
16841
|
+
return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join17(process.env.XDG_CONFIG_HOME, "pi") : join17(homeDir, ".pi", "agent"));
|
|
17352
16842
|
}
|
|
17353
16843
|
function parseJsonObject(content, path) {
|
|
17354
16844
|
if (!content?.trim())
|
|
@@ -17394,14 +16884,14 @@ var PI_SKILL_SOURCES = [
|
|
|
17394
16884
|
{ repository: "cloudvoyant/codevoyant", skills: ["git"] }
|
|
17395
16885
|
];
|
|
17396
16886
|
var MCP_ADAPTER_PACKAGE = "npm:pi-mcp-adapter";
|
|
17397
|
-
var
|
|
16887
|
+
var BUNDLED_AGENTS_DIR = resolveBundledAgentsDir();
|
|
17398
16888
|
var FORGE_DEPENDENCIES = {
|
|
17399
16889
|
github: { name: "gh", tool: "gh", spec: "gh@latest", minimumVersion: undefined },
|
|
17400
16890
|
gitlab: { name: "glab", tool: "glab", spec: "glab@latest", minimumVersion: undefined }
|
|
17401
16891
|
};
|
|
17402
16892
|
async function ensureMise(options = {}) {
|
|
17403
|
-
const homeDir = options.homeDir ??
|
|
17404
|
-
const current = await mise.executableCheck() ?? await mise.executableCheck(
|
|
16893
|
+
const homeDir = options.homeDir ?? homedir9();
|
|
16894
|
+
const current = await mise.executableCheck() ?? await mise.executableCheck(join18(homeDir, ".local", "bin", "mise"));
|
|
17405
16895
|
if (current)
|
|
17406
16896
|
return { executable: current, action: createSetupAction("mise", "ready", current) };
|
|
17407
16897
|
reportProgress(options, "Installing mise");
|
|
@@ -17450,9 +16940,9 @@ async function ensureMiseDeps(miseExecutable, options = {}) {
|
|
|
17450
16940
|
async function ensurePiPlugins(options = {}) {
|
|
17451
16941
|
const actions = await ensurePiPackages(PI_PACKAGES, options);
|
|
17452
16942
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
17453
|
-
const webSearch = await pi.configEnsure(
|
|
16943
|
+
const webSearch = await pi.configEnsure(join18(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
|
|
17454
16944
|
actions.push(getConfigSetupAction("web search settings", webSearch));
|
|
17455
|
-
const lsp = await pi.configEnsure(
|
|
16945
|
+
const lsp = await pi.configEnsure(join18(agentDir, "pi-lsp.json"), (config) => ({
|
|
17456
16946
|
...config,
|
|
17457
16947
|
progressive: { ...getRecord(config.progressive), enabled: true, inject: "none" }
|
|
17458
16948
|
}), options.dryRun);
|
|
@@ -17461,13 +16951,13 @@ async function ensurePiPlugins(options = {}) {
|
|
|
17461
16951
|
}
|
|
17462
16952
|
async function ensurePiAgents(options = {}) {
|
|
17463
16953
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
17464
|
-
const bundledAgentsDir = options.bundledAgentsDir ??
|
|
16954
|
+
const bundledAgentsDir = options.bundledAgentsDir ?? BUNDLED_AGENTS_DIR;
|
|
17465
16955
|
const userConfig = await loadDiffpiConfig({ homeDir: options.homeDir });
|
|
17466
16956
|
const entries = (await readdir4(bundledAgentsDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.startsWith("diffpi-") && entry.name.endsWith(".md")).sort((left, right) => left.name.localeCompare(right.name));
|
|
17467
16957
|
const actions = [];
|
|
17468
16958
|
for (const entry of entries) {
|
|
17469
|
-
const id =
|
|
17470
|
-
const source = await
|
|
16959
|
+
const id = basename5(entry.name, ".md").replace(/^diffpi-/, "");
|
|
16960
|
+
const source = await readFile13(join18(bundledAgentsDir, entry.name), "utf8");
|
|
17471
16961
|
const content = materializeAgentModels(source, id, userConfig.config, options.availableModels);
|
|
17472
16962
|
const result = await pi.agentEnsure(entry.name, content, agentDir, options.dryRun);
|
|
17473
16963
|
actions.push(getConfigSetupAction(`pi agent ${id}`, result));
|
|
@@ -17476,7 +16966,7 @@ async function ensurePiAgents(options = {}) {
|
|
|
17476
16966
|
}
|
|
17477
16967
|
async function ensurePiSkills(miseExecutable, options = {}) {
|
|
17478
16968
|
const agentDir = options.agentDir ?? pi.agentDir(options.homeDir);
|
|
17479
|
-
const sharedSkillsDir =
|
|
16969
|
+
const sharedSkillsDir = join18(options.homeDir ?? homedir9(), ".agents", "skills");
|
|
17480
16970
|
const actions = [];
|
|
17481
16971
|
for (const source of PI_SKILL_SOURCES) {
|
|
17482
16972
|
const missing = [];
|
|
@@ -17592,7 +17082,7 @@ async function gitlabMcpHost(projectDir) {
|
|
|
17592
17082
|
}
|
|
17593
17083
|
}
|
|
17594
17084
|
function materializeAgentModels(content, agentId, config, availableModels) {
|
|
17595
|
-
const { frontmatter } =
|
|
17085
|
+
const { frontmatter } = parseFrontmatter2(content.startsWith("\uFEFF") ? content.slice(1) : content);
|
|
17596
17086
|
const profilePreferences = [...getTextList(frontmatter.model), ...getTextList(frontmatter.model_fallbacks)];
|
|
17597
17087
|
const preferences = resolveAgentModelPreferences(agentId, profilePreferences, config);
|
|
17598
17088
|
let selectedIndex = availableModels === undefined && preferences.length > 0 ? 0 : -1;
|
|
@@ -17672,7 +17162,7 @@ function reportProgress(options, message) {
|
|
|
17672
17162
|
options.onProgress?.(message);
|
|
17673
17163
|
}
|
|
17674
17164
|
async function packageVersion2() {
|
|
17675
|
-
const source = await
|
|
17165
|
+
const source = await readFile13(join18(resolveBundledAgentsDir(), "..", "package.json"), "utf8");
|
|
17676
17166
|
let value;
|
|
17677
17167
|
try {
|
|
17678
17168
|
value = JSON.parse(source);
|
|
@@ -17688,14 +17178,14 @@ function getRecord(value) {
|
|
|
17688
17178
|
}
|
|
17689
17179
|
|
|
17690
17180
|
// src/tools/setup.ts
|
|
17691
|
-
var setupParametersSchema =
|
|
17692
|
-
issueTracker:
|
|
17693
|
-
forges:
|
|
17694
|
-
forge:
|
|
17695
|
-
bindZedKey:
|
|
17696
|
-
});
|
|
17697
|
-
var setupParameters =
|
|
17698
|
-
var diffpiSetupTool =
|
|
17181
|
+
var setupParametersSchema = z11.object({
|
|
17182
|
+
issueTracker: z11.enum(["none", "linear", "jira"]).default("none").describe("Issue tracker MCP server to configure. Use none unless the user explicitly selects Linear or Jira."),
|
|
17183
|
+
forges: z11.array(z11.enum(["github", "gitlab"])).default([]).describe("Hosted VCS MCP servers to configure. Installs matching CLIs and registers each MCP server."),
|
|
17184
|
+
forge: z11.enum(["none", "github", "gitlab"]).optional().describe("Deprecated single-host alias. Prefer forges for one or more VCS MCP servers."),
|
|
17185
|
+
bindZedKey: z11.boolean().default(false).describe("Opt in to a Zed keybinding for the tuicr review task.")
|
|
17186
|
+
});
|
|
17187
|
+
var setupParameters = z11.toJSONSchema(setupParametersSchema, { io: "input" });
|
|
17188
|
+
var diffpiSetupTool = defineTool6({
|
|
17699
17189
|
name: "diffpi_setup",
|
|
17700
17190
|
label: "diffpi setup",
|
|
17701
17191
|
description: "Install or repair the @difflab/pi environment. This mutates user-level tool installations and configuration files.",
|
|
@@ -17724,7 +17214,7 @@ var diffpiSetupTool = defineTool7({
|
|
|
17724
17214
|
return formatResult(result, "Setup complete.");
|
|
17725
17215
|
}
|
|
17726
17216
|
});
|
|
17727
|
-
var diffpiValidateTool =
|
|
17217
|
+
var diffpiValidateTool = defineTool6({
|
|
17728
17218
|
name: "diffpi_validate",
|
|
17729
17219
|
label: "diffpi validate",
|
|
17730
17220
|
description: "Inspect the @difflab/pi environment without installing software or changing configuration files.",
|
|
@@ -17766,20 +17256,20 @@ ${lines.join(`
|
|
|
17766
17256
|
}
|
|
17767
17257
|
|
|
17768
17258
|
// src/tools/templates.ts
|
|
17769
|
-
import { defineTool as
|
|
17770
|
-
import { z as
|
|
17771
|
-
var templateSchema =
|
|
17772
|
-
name:
|
|
17773
|
-
variables:
|
|
17774
|
-
homeDir:
|
|
17775
|
-
});
|
|
17776
|
-
var diffpiTemplateTool =
|
|
17259
|
+
import { defineTool as defineTool7 } from "@earendil-works/pi-coding-agent";
|
|
17260
|
+
import { z as z12 } from "zod";
|
|
17261
|
+
var templateSchema = z12.object({
|
|
17262
|
+
name: z12.string().min(1),
|
|
17263
|
+
variables: z12.record(z12.string(), z12.string()).optional(),
|
|
17264
|
+
homeDir: z12.string().optional()
|
|
17265
|
+
});
|
|
17266
|
+
var diffpiTemplateTool = defineTool7({
|
|
17777
17267
|
name: "diffpi_template",
|
|
17778
17268
|
label: "diffpi template",
|
|
17779
17269
|
description: "Load a bundled Diffpi template or a user override and render named variables.",
|
|
17780
17270
|
promptSnippet: "Use diffpi_template for package workflow templates",
|
|
17781
17271
|
promptGuidelines: ["User overrides live under ~/.difflab/diffpi/templates."],
|
|
17782
|
-
parameters:
|
|
17272
|
+
parameters: z12.toJSONSchema(templateSchema, { io: "input" }),
|
|
17783
17273
|
executionMode: "parallel",
|
|
17784
17274
|
async execute(_id, input) {
|
|
17785
17275
|
const params = templateSchema.parse(input);
|
|
@@ -17793,7 +17283,7 @@ var diffpiTemplateTool = defineTool8({
|
|
|
17793
17283
|
});
|
|
17794
17284
|
|
|
17795
17285
|
// src/tools/index.ts
|
|
17796
|
-
function createPiTools(pi
|
|
17286
|
+
function createPiTools(pi) {
|
|
17797
17287
|
return [
|
|
17798
17288
|
diffpiSetupTool,
|
|
17799
17289
|
diffpiValidateTool,
|
|
@@ -17802,7 +17292,6 @@ function createPiTools(pi, modes) {
|
|
|
17802
17292
|
diffpiTemplateTool,
|
|
17803
17293
|
planVerifyTool,
|
|
17804
17294
|
watchCiTool,
|
|
17805
|
-
...createModeTools(modes),
|
|
17806
17295
|
...createReviewTools()
|
|
17807
17296
|
];
|
|
17808
17297
|
}
|
|
@@ -17815,23 +17304,18 @@ Use fetch-url for a one-time read that does not belong in the documentation inde
|
|
|
17815
17304
|
Use the context-mode skill for commands, tests, builds, logs, API responses, and other output that can be large.
|
|
17816
17305
|
Use ctx_execute or ctx_execute_file to analyze that output, and use ctx_fetch_and_index with ctx_search for external documentation.`;
|
|
17817
17306
|
function difflabPiExtension(pi) {
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
for (const tool of createPiTools(pi, modes))
|
|
17307
|
+
registerCommands(pi);
|
|
17308
|
+
for (const tool of createPiTools(pi))
|
|
17821
17309
|
pi.registerTool(tool);
|
|
17822
|
-
pi.on("session_start",
|
|
17310
|
+
pi.on("session_start", () => {
|
|
17823
17311
|
if (!pi.getAllTools().some((tool) => tool.name === "ask_user_question"))
|
|
17824
17312
|
rpiv_ask_user_question_default(pi);
|
|
17825
|
-
await modes.restore(ctx);
|
|
17826
17313
|
});
|
|
17827
|
-
pi.on("
|
|
17828
|
-
|
|
17829
|
-
pi.on("before_agent_start", (event) => {
|
|
17830
|
-
const defaultPrompt = `${event.systemPrompt}
|
|
17314
|
+
pi.on("before_agent_start", (event) => ({
|
|
17315
|
+
systemPrompt: `${event.systemPrompt}
|
|
17831
17316
|
|
|
17832
|
-
${SKILL_ROUTING_GUIDANCE}
|
|
17833
|
-
|
|
17834
|
-
});
|
|
17317
|
+
${SKILL_ROUTING_GUIDANCE}`
|
|
17318
|
+
}));
|
|
17835
17319
|
}
|
|
17836
17320
|
export {
|
|
17837
17321
|
difflabPiExtension as default
|