@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.
Files changed (56) hide show
  1. package/README.md +22 -40
  2. package/agents/diffpi-copilot.md +2 -2
  3. package/agents/diffpi-orchestrator.md +9 -14
  4. package/agents/diffpi-plan-reviewer.md +4 -12
  5. package/agents/diffpi-planner.md +6 -12
  6. package/agents/diffpi-reviewer.md +6 -10
  7. package/agents/diffpi-tutor.md +2 -2
  8. package/agents/diffpi-worker.md +3 -6
  9. package/dist/commands/dispatch.d.ts +4 -0
  10. package/dist/commands/dispatch.d.ts.map +1 -0
  11. package/dist/commands/index.d.ts +1 -3
  12. package/dist/commands/index.d.ts.map +1 -1
  13. package/dist/commands/plan.d.ts +1 -1
  14. package/dist/commands/plan.d.ts.map +1 -1
  15. package/dist/commands/review.d.ts +1 -1
  16. package/dist/commands/review.d.ts.map +1 -1
  17. package/dist/extensions/index.js +365 -881
  18. package/dist/index.d.ts +0 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +40 -403
  21. package/dist/tools/index.d.ts +1 -3
  22. package/dist/tools/index.d.ts.map +1 -1
  23. package/dist/tools/index.js +95 -212
  24. package/package.json +2 -2
  25. package/skills/diffpi-setup/SKILL.md +1 -1
  26. package/skills/plan/SKILL.md +12 -22
  27. package/skills/plan/references/design-quality.md +3 -3
  28. package/skills/plan/references/workflows/annotate.md +9 -3
  29. package/skills/plan/references/workflows/finalize.md +10 -6
  30. package/skills/plan/references/workflows/go.md +25 -7
  31. package/skills/plan/references/workflows/help.md +20 -3
  32. package/skills/plan/references/workflows/init.md +23 -6
  33. package/skills/plan/references/workflows/inline-fallback.md +9 -0
  34. package/skills/plan/references/workflows/new.md +22 -7
  35. package/skills/plan/references/workflows/update.md +22 -6
  36. package/skills/plan/references/workflows/validate.md +31 -0
  37. package/skills/review/SKILL.md +14 -6
  38. package/skills/review/references/workflows/address.md +19 -6
  39. package/skills/review/references/workflows/auto.md +21 -5
  40. package/skills/review/references/workflows/complete.md +17 -4
  41. package/skills/review/references/workflows/edit.md +8 -2
  42. package/skills/review/references/workflows/foreground-fallback.md +7 -0
  43. package/skills/review/references/workflows/help.md +22 -13
  44. package/skills/review/references/workflows/merge.md +17 -2
  45. package/skills/review/references/workflows/new.md +17 -5
  46. package/skills/review/references/workflows/open.md +8 -2
  47. package/skills/review/references/workflows/publish.md +17 -4
  48. package/skills/review/references/workflows/status.md +7 -2
  49. package/templates/plan/PLAN.md +1 -1
  50. package/dist/commands/mode.d.ts +0 -5
  51. package/dist/commands/mode.d.ts.map +0 -1
  52. package/dist/modes.d.ts +0 -75
  53. package/dist/modes.d.ts.map +0 -1
  54. package/dist/tools/modes.d.ts +0 -4
  55. package/dist/tools/modes.d.ts.map +0 -1
  56. package/skills/mode/SKILL.md +0 -38
@@ -13323,524 +13323,20 @@ function rpiv_ask_user_question_default(pi) {
13323
13323
  registerAskUserQuestionReconciler(pi);
13324
13324
  }
13325
13325
 
13326
- // src/modes.ts
13327
- import {
13328
- getAgentDir,
13329
- parseFrontmatter as parseFrontmatter2
13330
- } from "@earendil-works/pi-coding-agent";
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: "Durable planning (delegated to the plan skill)",
13337
+ description: "Open the plan skill",
13839
13338
  handler: async (args) => {
13840
- pi.sendUserMessage(`/skill:plan${args ? ` ${args}` : ""}`, {
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: "Code review (delegated to the review skill)",
13357
+ description: "Open the review skill",
13862
13358
  handler: async (args) => {
13863
- pi.sendUserMessage(`/skill:review${args ? ` ${args}` : ""}`, {
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, modes) {
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 z3 } from "zod";
13373
+ import { z as z2 } from "zod";
13882
13374
 
13883
13375
  // src/environment.ts
13884
- import { readFile as readFile4 } from "node:fs/promises";
13885
- import { basename as basename2, join as join8 } from "node:path";
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 join6 } from "node:path";
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 = join6(directory, name);
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 as readFile3, writeFile } from "node:fs/promises";
13970
- import { homedir as homedir4 } from "node:os";
13971
- import { dirname as dirname3, join as join7 } from "node:path";
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 = homedir4()) {
13978
- return join7(homeDir, ".config", "zed", "tasks.json");
13485
+ function zedTasksPath(homeDir = homedir2()) {
13486
+ return join5(homeDir, ".config", "zed", "tasks.json");
13979
13487
  }
13980
- function zedKeymapPath(homeDir = homedir4()) {
13981
- return join7(homeDir, ".config", "zed", "keymap.json");
13488
+ function zedKeymapPath(homeDir = homedir2()) {
13489
+ return join5(homeDir, ".config", "zed", "keymap.json");
13982
13490
  }
13983
- async function ensureZedReviewTask(homeDir = homedir4(), _command = ["tuicr", "-w", "-r", "main..HEAD"]) {
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 = homedir4()) {
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 = homedir4()) {
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 readFile3(path, "utf8");
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 ? basename2(env.SHELL) : "unknown";
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: ${basename2(cwd)} / ${workflow}`;
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 readFile4(join8(resolveBundledAgentsDir(), "..", "package.json"), "utf8"));
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 = z3.object({
14084
+ var watchCiParametersSchema = z2.object({
14568
14085
  cwd: zx.cwd,
14569
- sha: z3.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i),
14570
- timeoutSeconds: z3.number().int().min(30).max(7200).default(1800),
14571
- pollSeconds: z3.number().int().min(2).max(60).default(10)
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: z3.toJSONSchema(watchCiParametersSchema, { io: "input" }),
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 join11 } from "node:path";
14614
- import { z as z4 } from "zod";
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 readFile5 } from "node:fs/promises";
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 homedir5 } from "node:os";
14635
- import { dirname as dirname4, isAbsolute as isAbsolute3, join as join10, resolve as resolve3 } from "node:path";
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 basename3, isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "node:path";
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 : join9(root, common)) : root;
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) : basename3(resolve2(commonDir, "..")) || basename3(root),
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 = join10(".pi", "diffpi");
14679
- function storeGlobalRoot(homeDir = homedir5()) {
14680
- return join10(homeDir, ".difflab", "diffpi", "projects");
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 = homedir5()) {
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 = join10(storeGlobalRoot(homeDir), slug);
14687
- const link = join10(root, STORE_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(join10(root, LEGACY_STORE_LINK), dest);
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 = homedir5()) {
14216
+ async function logsDir(cwd, homeDir = homedir3()) {
14700
14217
  const store = await ensureStore(cwd, homeDir);
14701
- const dir = join10(store.link, "logs");
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 = homedir5()) {
14222
+ async function reviewsDir(cwd, homeDir = homedir3()) {
14706
14223
  const store = await ensureStore(cwd, homeDir);
14707
- const dir = join10(store.link, "review");
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 = homedir5()) {
14228
+ async function completedReviewsDir(cwd, homeDir = homedir3()) {
14712
14229
  const store = await ensureStore(cwd, homeDir);
14713
- const dir = join10(store.link, "reviews");
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 = homedir5()) {
14234
+ async function sessionsDir(cwd, homeDir = homedir3()) {
14718
14235
  const store = await ensureStore(cwd, homeDir);
14719
- const dir = join10(store.link, "sessions");
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 = z4.object({
14279
+ var schema7 = z3.object({
14763
14280
  cwd: zx.cwd,
14764
14281
  channel: zx.id,
14765
- kind: z4.enum(["progress", "issue", "deviation"]),
14282
+ kind: z3.enum(["progress", "issue", "deviation"]),
14766
14283
  actor: zx.text,
14767
14284
  message: zx.text,
14768
- correlation: z4.record(z4.string(), z4.string()).optional(),
14769
- evidence: z4.array(zx.text).optional(),
14770
- data: z4.record(z4.string(), z4.unknown()).optional()
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: z4.toJSONSchema(schema7, { io: "input" }),
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(join11(await logsDir(params.cwd ?? process.cwd()), `${params.channel}.jsonl`), {
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 z5 } from "zod";
14319
+ import { z as z4 } from "zod";
14803
14320
 
14804
14321
  // src/plan/verify.ts
14805
- import { readFile as readFile6, readdir as readdir2, stat } from "node:fs/promises";
14806
- import { basename as basename4, dirname as dirname5, join as join12, resolve as resolve4 } from "node:path";
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 = join12(path, "PLAN.md");
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 (basename4(planPath) !== "PLAN.md") {
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(basename4(planDir)) || basename4(dirname5(planDir)) !== "plan" || basename4(dirname5(dirname5(planDir))) !== ".diffpi")
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 readFile6(planPath, "utf8");
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 = join12(planDir, "implementation");
14356
+ const implementation = join10(planDir, "implementation");
14840
14357
  let rootEntries = [];
14841
14358
  let briefEntries = [];
14842
14359
  try {
14843
- rootEntries = await readdir2(planDir);
14844
- briefEntries = await readdir2(implementation);
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, join12(planDir, entry), 0, "layout", "Only PLAN.md and implementation/ belong in the plan directory.");
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, join12(implementation, entry), 0, "extra-brief", "Only matching numbered phase briefs belong in implementation/.");
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 = join12(implementation, `phase-${index + 1}.md`);
14373
+ const brief = join10(implementation, `phase-${index + 1}.md`);
14857
14374
  briefPaths.push(brief);
14858
14375
  try {
14859
- const lines = visibleLines(await readFile6(brief, "utf8"));
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 = z5.object({ plan: z5.string().min(1), cwd: z5.string().optional() }).strict();
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: z5.toJSONSchema(schema8, { io: "input" }),
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 z6 } from "zod";
14915
- var reloadParametersSchema = z6.object({});
14916
- var reloadParameters = z6.toJSONSchema(reloadParametersSchema, { io: "input" });
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 readdir3, readFile as readFile12, rename as rename2, stat as stat2, unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
15057
- import { join as join16 } from "node:path";
15058
- import { defineTool as defineTool6 } from "@earendil-works/pi-coding-agent";
15059
- import { z as z11 } from "zod";
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 readFile7, writeFile as writeFile2 } from "node:fs/promises";
15063
- import { homedir as homedir6 } from "node:os";
15064
- import { basename as basename5, dirname as dirname6, join as join13 } from "node:path";
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 ?? homedir6();
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 = join13(homeDir, ".local", "bin", "mise");
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 ?? homedir6();
15090
- const hook = getShellHook(basename5(options.shell ?? process.env.SHELL ?? ""), executable, homeDir);
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 = homedir6()) {
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: join13(homeDir, ".zshrc"),
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: join13(homeDir, ".config", "fish", "config.fish"),
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: join13(homeDir, ".config", "nushell", "config.nu"),
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: join13(homeDir, ".xonshrc"),
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: join13(homeDir, ".config", "elvish", "rc.elv"),
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: join13(homeDir, ".config", "powershell", "Microsoft.PowerShell_profile.ps1"),
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: join13(homeDir, ".bashrc"),
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 readFile7(path, "utf8");
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 z8 } from "zod";
15293
- var severitySchema = z8.enum(["BLOCKING", "CONSIDER", "NOTE"]);
15294
- var findingSchema = z8.object({
15295
- file: z8.string().min(1),
15296
- line: z8.number().int().nonnegative(),
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: z8.string().min(1),
15299
- reference: z8.string().optional().default("")
14700
+ body: z6.string().min(1),
14701
+ reference: z6.string().optional().default("")
15300
14702
  });
15301
- var findingsSchema = z8.array(findingSchema);
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 z9 } from "zod";
15375
- var reviewThreadRecordSchema = z9.object({
15376
- id: z9.string().min(1),
15377
- file: z9.string().nullable().optional().transform((value) => value ?? undefined),
15378
- line: z9.number().int().positive().nullable().optional().transform((value) => value ?? undefined),
15379
- body: z9.string(),
15380
- author: z9.string().optional(),
15381
- resolved: z9.boolean(),
15382
- addressed: z9.boolean().optional(),
15383
- question: z9.boolean(),
15384
- replies: z9.array(z9.string()).optional()
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 = z9.array(reviewThreadRecordSchema).parse(decoded);
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 readFile9, writeFile as writeFile3 } from "node:fs/promises";
15291
+ import { readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
15890
15292
 
15891
15293
  // src/extensions/tuicrx.ts
15892
- import { readFile as readFile8, realpath as realpath3 } from "node:fs/promises";
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 readFile8(path, "utf8");
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 readFile9(options.artifactPath, "utf8"));
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 readFile9(options.artifactPath, "utf8");
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 readFile10, rename, writeFile as writeFile4 } from "node:fs/promises";
16141
- import { join as join14 } from "node:path";
16142
- import { z as z10 } from "zod";
16143
- var reviewPublicationStateSchema = z10.object({
16144
- target: z10.string().optional(),
16145
- bodies: z10.array(z10.string()).default([]),
16146
- comments: z10.array(z10.string()).default([]),
16147
- stagedComments: z10.array(z10.string()).default([]),
16148
- replies: z10.array(z10.string()).default([]),
16149
- overlayPath: z10.string().optional()
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 = join14(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
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 readFile10(path, "utf8")));
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 z10.ZodError) {
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 readFile11 } from "node:fs/promises";
16190
- import { homedir as homedir7 } from "node:os";
16191
- import { join as join15, normalize } from "node:path";
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 = join15(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
16195
- const bundledPath = join15(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
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 readFile11(path, "utf8");
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 = z11.object({
16226
- cwd: z11.string().optional(),
16227
- target: z11.string().optional(),
16228
- workingTree: z11.boolean().optional()
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: z11.boolean().optional() });
15632
+ var localSchema = contextSchema.extend({ local: z9.boolean().optional() });
16231
15633
  var openSchema = localSchema.extend({
16232
- title: z11.string().optional(),
16233
- intent: z11.string().optional(),
16234
- issueUrl: z11.string().url().optional(),
16235
- base: z11.string().optional()
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: z11.array(z11.string()).optional(),
16240
- notVerified: z11.array(z11.string()).optional(),
16241
- title: z11.string().optional()
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: z11.string().min(1),
16245
- file: z11.string().min(1),
16246
- line: z11.number().int().positive(),
16247
- side: z11.enum(["LEFT", "RIGHT"]).optional()
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: z11.string().min(1),
16251
- body: z11.string().min(1),
16252
- question: z11.boolean().optional(),
16253
- resolve: z11.boolean().optional()
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: z11.enum(["COMMENT", "APPROVE", "REQUEST_CHANGES", "CLOSE"]).optional()
15658
+ status: z9.enum(["COMMENT", "APPROVE", "REQUEST_CHANGES", "CLOSE"]).optional()
16257
15659
  });
16258
15660
  var completeSchema = localSchema.extend({
16259
- action: z11.enum(["approve", "reject", "abandon"]).optional()
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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
- defineTool6({
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: z11.string().optional() })),
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: z11.string().optional() }).parse(input);
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
- defineTool6({
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 readFile12(path, "utf8"));
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 z11.toJSONSchema(schema, { io: "input" });
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 join16(dir, `${reviewSlug(sessionSlug) || "local-review"}.md`);
16534
+ return join14(dir, `${reviewSlug(sessionSlug) || "local-review"}.md`);
17133
16535
  }
17134
16536
  async function readThreadArtifact(path) {
17135
16537
  try {
17136
- return parseThreadArtifact(await readFile12(path, "utf8"));
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 = join16(dir, name);
16562
+ const path = join14(dir, name);
17161
16563
  const info = await stat2(path);
17162
- return { path, name, content: await readFile12(path, "utf8"), modified: info.mtimeMs };
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 readdir3(dir);
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 = join16(dir, `${base}.md`);
16587
+ let path = join14(dir, `${base}.md`);
17186
16588
  let count = 2;
17187
16589
  while (existsSync3(path))
17188
- path = join16(dir, `${base}-${count++}.md`);
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 defineTool7 } from "@earendil-works/pi-coding-agent";
17205
- import { z as z12 } from "zod";
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 parseFrontmatter3 } from "@earendil-works/pi-coding-agent";
17209
- import { readdir as readdir4, readFile as readFile14 } from "node:fs/promises";
17210
- import { homedir as homedir10 } from "node:os";
17211
- import { basename as basename6, join as join19 } from "node:path";
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 readFile13, writeFile as writeFile6 } from "node:fs/promises";
17215
- import { homedir as homedir8 } from "node:os";
17216
- import { dirname as dirname7, join as join17 } from "node:path";
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 = homedir8()) {
17219
- return join17(homeDir, ".config", "mcp", "mcp.json");
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 readFile13(path, "utf8");
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 homedir9 } from "node:os";
17276
- import { dirname as dirname8, join as join18 } from "node:path";
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 = join18(agentDir, "agents", filename);
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 = join18(homedir9(), ".agents", "skills")) {
17299
- const roots = [join18(agentDir, "skills"), sharedSkillsDir];
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(join18(root, name, "SKILL.md")) !== undefined)
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 = homedir9()) {
17351
- return process.env.PI_CODING_AGENT_DIR ?? (process.env.XDG_CONFIG_HOME ? join18(process.env.XDG_CONFIG_HOME, "pi") : join18(homeDir, ".pi", "agent"));
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 BUNDLED_AGENTS_DIR2 = resolveBundledAgentsDir();
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 ?? homedir10();
17404
- const current = await mise.executableCheck() ?? await mise.executableCheck(join19(homeDir, ".local", "bin", "mise"));
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(join19(agentDir, "web-search.json"), (config) => ({ ...config, workflow: "auto-summary" }), options.dryRun);
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(join19(agentDir, "pi-lsp.json"), (config) => ({
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 ?? BUNDLED_AGENTS_DIR2;
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 = basename6(entry.name, ".md").replace(/^diffpi-/, "");
17470
- const source = await readFile14(join19(bundledAgentsDir, entry.name), "utf8");
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 = join19(options.homeDir ?? homedir10(), ".agents", "skills");
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 } = parseFrontmatter3(content.startsWith("\uFEFF") ? content.slice(1) : content);
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 readFile14(join19(resolveBundledAgentsDir(), "..", "package.json"), "utf8");
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 = z12.object({
17692
- issueTracker: z12.enum(["none", "linear", "jira"]).default("none").describe("Issue tracker MCP server to configure. Use none unless the user explicitly selects Linear or Jira."),
17693
- forges: z12.array(z12.enum(["github", "gitlab"])).default([]).describe("Hosted VCS MCP servers to configure. Installs matching CLIs and registers each MCP server."),
17694
- forge: z12.enum(["none", "github", "gitlab"]).optional().describe("Deprecated single-host alias. Prefer forges for one or more VCS MCP servers."),
17695
- bindZedKey: z12.boolean().default(false).describe("Opt in to a Zed keybinding for the tuicr review task.")
17696
- });
17697
- var setupParameters = z12.toJSONSchema(setupParametersSchema, { io: "input" });
17698
- var diffpiSetupTool = defineTool7({
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 = defineTool7({
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 defineTool8 } from "@earendil-works/pi-coding-agent";
17770
- import { z as z13 } from "zod";
17771
- var templateSchema = z13.object({
17772
- name: z13.string().min(1),
17773
- variables: z13.record(z13.string(), z13.string()).optional(),
17774
- homeDir: z13.string().optional()
17775
- });
17776
- var diffpiTemplateTool = defineTool8({
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: z13.toJSONSchema(templateSchema, { io: "input" }),
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, modes) {
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
- const modes = createModeController(pi);
17819
- registerCommands(pi, modes);
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", async (_event, ctx) => {
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("session_tree", async (_event, ctx) => modes.restore(ctx));
17828
- pi.on("model_select", (_event, ctx) => modes.refresh(ctx));
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
- return { systemPrompt: modes.apply(defaultPrompt) };
17834
- });
17317
+ ${SKILL_ROUTING_GUIDANCE}`
17318
+ }));
17835
17319
  }
17836
17320
  export {
17837
17321
  difflabPiExtension as default