@delorenj/pjangler 1.2.12 → 1.2.13

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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/mcp-server.ts
4
- import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
5
- import { basename as basename4, dirname as dirname8, join as join12, resolve as resolve3 } from "node:path";
4
+ import { existsSync as existsSync10, statSync as statSync2 } from "node:fs";
5
+ import { basename as basename4, dirname as dirname8, join as join13, resolve as resolve3 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath5 } from "node:url";
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -902,10 +902,100 @@ var RunCopierTemplate = class extends Command {
902
902
  }
903
903
  };
904
904
 
905
- // src/commands/hermes/WireTelegram.ts
905
+ // src/commands/hermes/UntrackHermesRuntimes.ts
906
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3, readdirSync } from "fs";
907
+ import { join as join6 } from "path";
906
908
  import { spawnSync as spawnSync2 } from "node:child_process";
907
- import { join as join6 } from "node:path";
908
- import { existsSync as existsSync4, unlinkSync } from "node:fs";
909
+ var UntrackHermesRuntimes = class extends Command {
910
+ async invoke() {
911
+ const targetDir = this.context.targetDir;
912
+ const rolesDir = join6(targetDir, "agents", "hermes");
913
+ if (!existsSync4(rolesDir)) {
914
+ return {
915
+ success: true,
916
+ message: "No Hermes agents found (no agents/hermes directory)."
917
+ };
918
+ }
919
+ const roles = readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
920
+ if (roles.length === 0) {
921
+ return {
922
+ success: true,
923
+ message: "No Hermes agents found."
924
+ };
925
+ }
926
+ let modifiedAny = false;
927
+ const details = [];
928
+ for (const role of roles) {
929
+ const roleDir = join6("agents", "hermes", role);
930
+ const runtimePath = join6(roleDir, "runtime");
931
+ const gitignorePath = join6(roleDir, ".gitignore");
932
+ let isTracked = false;
933
+ const lsResult = spawnSync2("git", ["ls-files", "--stage", runtimePath], {
934
+ cwd: targetDir,
935
+ encoding: "utf8"
936
+ });
937
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
938
+ isTracked = true;
939
+ }
940
+ let isIgnored = false;
941
+ const fullGitignorePath = join6(targetDir, gitignorePath);
942
+ if (existsSync4(fullGitignorePath)) {
943
+ const content = readFileSync2(fullGitignorePath, "utf8");
944
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
945
+ isIgnored = lines.includes("runtime/") || lines.includes("runtime");
946
+ }
947
+ if (isTracked || !isIgnored) {
948
+ modifiedAny = true;
949
+ if (isTracked) {
950
+ details.push(`untrack agents/hermes/${role}/runtime`);
951
+ if (!this.context.dryRun) {
952
+ const rmResult = spawnSync2("git", ["rm", "--cached", "-r", runtimePath], {
953
+ cwd: targetDir,
954
+ encoding: "utf8"
955
+ });
956
+ if (rmResult.status !== 0) {
957
+ return {
958
+ success: false,
959
+ message: `Failed to untrack agents/hermes/${role}/runtime: ${rmResult.stderr}`
960
+ };
961
+ }
962
+ }
963
+ }
964
+ if (!isIgnored) {
965
+ details.push(`ignore runtime/ in agents/hermes/${role}/.gitignore`);
966
+ if (!this.context.dryRun) {
967
+ let content = "";
968
+ if (existsSync4(fullGitignorePath)) {
969
+ content = readFileSync2(fullGitignorePath, "utf8");
970
+ }
971
+ if (content && !content.endsWith("\n")) {
972
+ content += "\n";
973
+ }
974
+ content += "runtime/\n";
975
+ writeFileSync3(fullGitignorePath, content, "utf8");
976
+ }
977
+ }
978
+ }
979
+ }
980
+ if (!modifiedAny) {
981
+ return {
982
+ success: true,
983
+ message: "\u2705 All Hermes agent runtimes are already untracked and gitignored."
984
+ };
985
+ }
986
+ const actionText = this.context.dryRun ? "Would make" : "Made";
987
+ return {
988
+ success: true,
989
+ message: `${actionText} Hermes agent runtimes untracked and gitignored:
990
+ ${details.map((d) => ` - ${d}`).join("\n")}`
991
+ };
992
+ }
993
+ };
994
+
995
+ // src/commands/hermes/WireTelegram.ts
996
+ import { spawnSync as spawnSync3 } from "node:child_process";
997
+ import { join as join7 } from "node:path";
998
+ import { existsSync as existsSync5, unlinkSync } from "node:fs";
909
999
  import * as p3 from "@clack/prompts";
910
1000
  var WireTelegram = class extends Command {
911
1001
  async invoke() {
@@ -927,7 +1017,7 @@ var WireTelegram = class extends Command {
927
1017
  let token = process.env.TELEGRAM_BOT_TOKEN;
928
1018
  let source = token ? "env" : null;
929
1019
  if (!token) {
930
- const tryOp = spawnSync2("op", ["read", vaultRef], { encoding: "utf8" });
1020
+ const tryOp = spawnSync3("op", ["read", vaultRef], { encoding: "utf8" });
931
1021
  if (tryOp.status === 0) {
932
1022
  token = tryOp.stdout.trim();
933
1023
  source = "op";
@@ -966,7 +1056,7 @@ var WireTelegram = class extends Command {
966
1056
  initialValue: true
967
1057
  });
968
1058
  if (!p3.isCancel(persist) && persist) {
969
- const create = spawnSync2(
1059
+ const create = spawnSync3(
970
1060
  "op",
971
1061
  [
972
1062
  "item",
@@ -993,18 +1083,18 @@ var WireTelegram = class extends Command {
993
1083
  if (p3.isCancel(allowedAnswer)) {
994
1084
  return { success: false, message: "\u2717 Aborted; Telegram step deferred." };
995
1085
  }
996
- const script = join6(roleDir, ".scripts", "30-telegram.sh");
997
- if (!existsSync4(script)) {
1086
+ const script = join7(roleDir, ".scripts", "30-telegram.sh");
1087
+ if (!existsSync5(script)) {
998
1088
  return {
999
1089
  success: false,
1000
1090
  message: `\u2717 ${script} not found. Did copier finish? Re-run with --skip-runtime-repo=0 if you skipped it.`
1001
1091
  };
1002
1092
  }
1003
- const marker = join6(roleDir, ".scripts", ".done-30-telegram");
1004
- if (existsSync4(marker)) unlinkSync(marker);
1093
+ const marker = join7(roleDir, ".scripts", ".done-30-telegram");
1094
+ if (existsSync5(marker)) unlinkSync(marker);
1005
1095
  const spinner4 = p3.spinner();
1006
1096
  spinner4.start("Verifying token + wiring profile");
1007
- const result = spawnSync2("bash", [script], {
1097
+ const result = spawnSync3("bash", [script], {
1008
1098
  stdio: "inherit",
1009
1099
  env: {
1010
1100
  ...process.env,
@@ -1027,9 +1117,9 @@ function cap(s) {
1027
1117
  }
1028
1118
 
1029
1119
  // src/commands/hermes/WireEmail.ts
1030
- import { spawnSync as spawnSync3 } from "node:child_process";
1031
- import { join as join7 } from "node:path";
1032
- import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
1120
+ import { spawnSync as spawnSync4 } from "node:child_process";
1121
+ import { join as join8 } from "node:path";
1122
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
1033
1123
  import * as p4 from "@clack/prompts";
1034
1124
  var WireEmail = class extends Command {
1035
1125
  async invoke() {
@@ -1044,13 +1134,13 @@ var WireEmail = class extends Command {
1044
1134
  if (!targetRepo || !role || !roleDir) {
1045
1135
  return { success: false, message: "Cannot wire email: missing target_repo/role/roleDir" };
1046
1136
  }
1047
- const script = join7(roleDir, ".scripts", "50-email.sh");
1048
- if (!existsSync5(script)) {
1137
+ const script = join8(roleDir, ".scripts", "50-email.sh");
1138
+ if (!existsSync6(script)) {
1049
1139
  return { success: false, message: `\u2717 ${script} not found` };
1050
1140
  }
1051
1141
  let token = process.env.CF_EMAIL_ROUTING_TOKEN;
1052
1142
  if (!token) {
1053
- const tryOp = spawnSync3(
1143
+ const tryOp = spawnSync4(
1054
1144
  "op",
1055
1145
  ["read", "op://DeLoSecrets/Cloudflare-EmailRouting/token"],
1056
1146
  { encoding: "utf8" }
@@ -1090,7 +1180,7 @@ var WireEmail = class extends Command {
1090
1180
  initialValue: true
1091
1181
  });
1092
1182
  if (!p4.isCancel(persist) && persist) {
1093
- const create = spawnSync3(
1183
+ const create = spawnSync4(
1094
1184
  "op",
1095
1185
  [
1096
1186
  "item",
@@ -1107,11 +1197,11 @@ var WireEmail = class extends Command {
1107
1197
  }
1108
1198
  }
1109
1199
  }
1110
- const marker = join7(roleDir, ".scripts", ".done-50-email");
1111
- if (existsSync5(marker)) unlinkSync2(marker);
1200
+ const marker = join8(roleDir, ".scripts", ".done-50-email");
1201
+ if (existsSync6(marker)) unlinkSync2(marker);
1112
1202
  const spinner4 = p4.spinner();
1113
1203
  spinner4.start("Creating Cloudflare Email Routing rule");
1114
- const result = spawnSync3("bash", [script], {
1204
+ const result = spawnSync4("bash", [script], {
1115
1205
  stdio: "inherit",
1116
1206
  env: { ...process.env, SKIP_EMAIL: "0", CF_EMAIL_ROUTING_TOKEN: token },
1117
1207
  cwd: roleDir
@@ -1171,7 +1261,7 @@ var PrintHermesSummary = class extends Command {
1171
1261
  var HermesAgentRecipe = class extends Recipe {
1172
1262
  constructor(context) {
1173
1263
  super(context);
1174
- this.addIngredient(EnsureTemplateConfig).addIngredient(PromptForAgentConfig).addIngredient(RunCopierTemplate).addIngredient(WireTelegram).addIngredient(WireEmail).addIngredient(PrintHermesSummary);
1264
+ this.addIngredient(EnsureTemplateConfig).addIngredient(PromptForAgentConfig).addIngredient(RunCopierTemplate).addIngredient(UntrackHermesRuntimes).addIngredient(WireTelegram).addIngredient(WireEmail).addIngredient(PrintHermesSummary);
1175
1265
  }
1176
1266
  // Override execute() to suppress the base class's per-command logging since
1177
1267
  // our commands already render their own UI via @clack/prompts.
@@ -1195,33 +1285,33 @@ var HermesAgentRecipe = class extends Recipe {
1195
1285
 
1196
1286
  // src/commands/AgentHooksCommands.ts
1197
1287
  import { homedir as homedir4 } from "node:os";
1198
- import { join as join9, dirname as dirname5 } from "node:path";
1199
- import { existsSync as existsSync7, cpSync, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1288
+ import { join as join10, dirname as dirname5 } from "node:path";
1289
+ import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
1200
1290
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1201
1291
 
1202
1292
  // src/project/index.ts
1203
- import { spawnSync as spawnSync4 } from "node:child_process";
1204
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
1293
+ import { spawnSync as spawnSync5 } from "node:child_process";
1294
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, statSync, writeFileSync as writeFileSync4 } from "node:fs";
1205
1295
  import { homedir as homedir3 } from "node:os";
1206
- import { basename as basename2, delimiter, dirname as dirname4, join as join8, resolve } from "node:path";
1296
+ import { basename as basename2, delimiter, dirname as dirname4, join as join9, resolve } from "node:path";
1207
1297
  import YAML from "yaml";
1208
1298
  var PROJECT_REGISTRY_ENV = "PJ_PROJECT_REGISTRY";
1209
1299
  var PROJECT_SOURCE_SKILL_ROOTS_ENV = "PJ_SOURCE_SKILL_ROOTS";
1210
1300
  var PROJECT_REGISTRY_SCHEMA_VERSION = 1;
1211
1301
  var DEFAULT_SOURCE_SKILL_ROOTS = [
1212
1302
  "/home/delorenj/code/skillex/all-skills",
1213
- join8(homedir3(), ".agents", "skills"),
1214
- join8(homedir3(), ".codex", "skills")
1303
+ join9(homedir3(), ".agents", "skills"),
1304
+ join9(homedir3(), ".codex", "skills")
1215
1305
  ];
1216
1306
  function projectRegistryPath(env2 = process.env) {
1217
- return expandHome(env2[PROJECT_REGISTRY_ENV] || join8(homedir3(), ".config", "pjangler", "projects.yaml"));
1307
+ return expandHome(env2[PROJECT_REGISTRY_ENV] || join9(homedir3(), ".config", "pjangler", "projects.yaml"));
1218
1308
  }
1219
1309
  function emptyProjectRegistry() {
1220
1310
  return { schema_version: PROJECT_REGISTRY_SCHEMA_VERSION, projects: {} };
1221
1311
  }
1222
1312
  function loadProjectRegistry(path = projectRegistryPath()) {
1223
- if (!existsSync6(path)) return emptyProjectRegistry();
1224
- const raw = YAML.parse(readFileSync2(path, "utf8"));
1313
+ if (!existsSync7(path)) return emptyProjectRegistry();
1314
+ const raw = YAML.parse(readFileSync3(path, "utf8"));
1225
1315
  if (raw == null) return emptyProjectRegistry();
1226
1316
  if (!isRecord(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
1227
1317
  const registry = raw;
@@ -1236,7 +1326,7 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1236
1326
  validateProjectRegistry(registry);
1237
1327
  mkdirSync4(dirname4(path), { recursive: true });
1238
1328
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
1239
- writeFileSync3(temp, YAML.stringify(registry, { lineWidth: 0 }), "utf8");
1329
+ writeFileSync4(temp, YAML.stringify(registry, { lineWidth: 0 }), "utf8");
1240
1330
  renameSync(temp, path);
1241
1331
  }
1242
1332
  function validateProjectRegistry(registry) {
@@ -1318,7 +1408,7 @@ function resolveAgentHooksLayer(input, env2 = process.env) {
1318
1408
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1319
1409
  if (override === "0" || override === "false") return false;
1320
1410
  if (override === "1" || override === "true") return true;
1321
- return !existsSync6(join8(homedir3(), ".agents", "hooks"));
1411
+ return !existsSync7(join9(homedir3(), ".agents", "hooks"));
1322
1412
  }
1323
1413
  function jsonStable(value) {
1324
1414
  return JSON.stringify(value);
@@ -1349,12 +1439,12 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1349
1439
  if (!sourceSkill) return void 0;
1350
1440
  const expanded = expandHome(sourceSkill);
1351
1441
  const direct = resolve(expanded);
1352
- if (existsSync6(direct)) return direct;
1442
+ if (existsSync7(direct)) return direct;
1353
1443
  const name = basename2(sourceSkill);
1354
1444
  const roots = sourceSkillRoots(env2);
1355
1445
  for (const root of roots) {
1356
- const candidate = join8(root, name);
1357
- if (existsSync6(candidate)) return candidate;
1446
+ const candidate = join9(root, name);
1447
+ if (existsSync7(candidate)) return candidate;
1358
1448
  }
1359
1449
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
1360
1450
  const hint = `${searched} Add project-specific roots with ${PROJECT_SOURCE_SKILL_ROOTS_ENV}.`;
@@ -1435,7 +1525,7 @@ function planProjectInit(input) {
1435
1525
  }));
1436
1526
  }
1437
1527
  actions.push(
1438
- { kind: "project.write-manifest", path: join8(targetDir, ".project.json"), manifest },
1528
+ { kind: "project.write-manifest", path: join9(targetDir, ".project.json"), manifest },
1439
1529
  {
1440
1530
  kind: "ticket-provider.create-or-link",
1441
1531
  enabled: live,
@@ -1476,7 +1566,7 @@ function executeProjectInitPlan(plan) {
1476
1566
  action.data.agent_hooks_layer === "false" ? "commonproject: agent-hooks layer skipped (global ~/.agents/hooks detected \u2014 no per-user CLI injection)" : "commonproject: agent-hooks layer included"
1477
1567
  );
1478
1568
  mkdirSync4(dirname4(action.targetDir), { recursive: true });
1479
- const result = spawnSync4(action.command[0], action.command.slice(1), { encoding: "utf8", cwd: action.cwd });
1569
+ const result = spawnSync5(action.command[0], action.command.slice(1), { encoding: "utf8", cwd: action.cwd });
1480
1570
  if (result.stdout?.trim()) logs.push(result.stdout.trim());
1481
1571
  if (result.stderr?.trim()) logs.push(result.stderr.trim());
1482
1572
  if (result.error) {
@@ -1488,7 +1578,7 @@ function executeProjectInitPlan(plan) {
1488
1578
  }
1489
1579
  if (result.status !== 0) {
1490
1580
  errors.push(`copier exited with status ${result.status ?? "unknown"}`);
1491
- if (existsSync6(action.targetDir)) changedFiles.push(action.targetDir);
1581
+ if (existsSync7(action.targetDir)) changedFiles.push(action.targetDir);
1492
1582
  break;
1493
1583
  }
1494
1584
  changedFiles.push(action.targetDir);
@@ -1496,9 +1586,9 @@ function executeProjectInitPlan(plan) {
1496
1586
  mkdirSync4(dirname4(action.path), { recursive: true });
1497
1587
  const next = `${JSON.stringify(action.manifest, null, 2)}
1498
1588
  `;
1499
- const current = existsSync6(action.path) ? readFileSync2(action.path, "utf8") : void 0;
1589
+ const current = existsSync7(action.path) ? readFileSync3(action.path, "utf8") : void 0;
1500
1590
  if (current !== next) {
1501
- writeFileSync3(action.path, next, "utf8");
1591
+ writeFileSync4(action.path, next, "utf8");
1502
1592
  changedFiles.push(action.path);
1503
1593
  }
1504
1594
  } else if (action.kind === "registry.upsert") {
@@ -1551,7 +1641,7 @@ function getProject(registry, slug) {
1551
1641
  return project;
1552
1642
  }
1553
1643
  function buildCommonProjectCopierAction(input) {
1554
- const templateDir = join8(input.pjanglerRoot, "templates", "commonproject");
1644
+ const templateDir = join9(input.pjanglerRoot, "templates", "commonproject");
1555
1645
  const data = {
1556
1646
  project_name: input.projectName,
1557
1647
  project_description: input.projectDescription ?? "",
@@ -1580,7 +1670,7 @@ function buildCommonProjectCopierAction(input) {
1580
1670
  function resolvePjanglerRoot() {
1581
1671
  let dir = dirname4(new URL(import.meta.url).pathname);
1582
1672
  while (dir !== dirname4(dir)) {
1583
- if (existsSync6(join8(dir, "package.json")) && existsSync6(join8(dir, "templates", "commonproject", "copier.yml"))) return dir;
1673
+ if (existsSync7(join9(dir, "package.json")) && existsSync7(join9(dir, "templates", "commonproject", "copier.yml"))) return dir;
1584
1674
  dir = dirname4(dir);
1585
1675
  }
1586
1676
  return resolve(process.cwd());
@@ -1612,7 +1702,7 @@ function validateProjectRecord(project, key) {
1612
1702
  }
1613
1703
  function expandHome(path) {
1614
1704
  if (path === "~") return homedir3();
1615
- if (path.startsWith("~/")) return join8(homedir3(), path.slice(2));
1705
+ if (path.startsWith("~/")) return join9(homedir3(), path.slice(2));
1616
1706
  return path;
1617
1707
  }
1618
1708
  function isRecord(value) {
@@ -1629,16 +1719,16 @@ function resolveTemplateRoot() {
1629
1719
  try {
1630
1720
  let dir = dirname5(fileURLToPath2(import.meta.url));
1631
1721
  for (let i = 0; i < 8; i++) {
1632
- candidates.push(join9(dir, "templates", "commonproject", "template"));
1722
+ candidates.push(join10(dir, "templates", "commonproject", "template"));
1633
1723
  const parent = dirname5(dir);
1634
1724
  if (parent === dir) break;
1635
1725
  dir = parent;
1636
1726
  }
1637
1727
  } catch {
1638
1728
  }
1639
- candidates.push(join9(homedir4(), "code", "pjangler", "templates", "commonproject", "template"));
1729
+ candidates.push(join10(homedir4(), "code", "pjangler", "templates", "commonproject", "template"));
1640
1730
  for (const c of candidates) {
1641
- if (existsSync7(join9(c, ".agents", "hooks", "hooks.master.json"))) return c;
1731
+ if (existsSync8(join10(c, ".agents", "hooks", "hooks.master.json"))) return c;
1642
1732
  }
1643
1733
  throw new Error(
1644
1734
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -1665,10 +1755,10 @@ var CopyAgentHooksTree = class extends Command {
1665
1755
  const created = [];
1666
1756
  const skipped = [];
1667
1757
  for (const { rel, dir } of items) {
1668
- const src = join9(templateRoot, rel);
1669
- const dest = join9(this.context.targetDir, rel);
1670
- if (!existsSync7(src)) continue;
1671
- if (existsSync7(dest) && !this.context.force) {
1758
+ const src = join10(templateRoot, rel);
1759
+ const dest = join10(this.context.targetDir, rel);
1760
+ if (!existsSync8(src)) continue;
1761
+ if (existsSync8(dest) && !this.context.force) {
1672
1762
  skipped.push(rel);
1673
1763
  continue;
1674
1764
  }
@@ -1694,14 +1784,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
1694
1784
  if (!resolveAgentHooksLayer()) {
1695
1785
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
1696
1786
  }
1697
- const misePath = join9(this.context.targetDir, "mise.toml");
1698
- if (!existsSync7(misePath)) {
1787
+ const misePath = join10(this.context.targetDir, "mise.toml");
1788
+ if (!existsSync8(misePath)) {
1699
1789
  return {
1700
1790
  success: false,
1701
1791
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pjangler init mise` first, then re-run."
1702
1792
  };
1703
1793
  }
1704
- let content = readFileSync3(misePath, "utf8");
1794
+ let content = readFileSync4(misePath, "utf8");
1705
1795
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
1706
1796
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
1707
1797
  }
@@ -1777,7 +1867,7 @@ ${leaveBlock}`);
1777
1867
  ""
1778
1868
  ].join("\n");
1779
1869
  content = content.replace(/\n*$/, "\n") + appended;
1780
- if (!this.context.dryRun) writeFileSync4(misePath, content);
1870
+ if (!this.context.dryRun) writeFileSync5(misePath, content);
1781
1871
  if (wiredHooks) {
1782
1872
  return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
1783
1873
  }
@@ -1931,15 +2021,15 @@ function createRecipe(name, context) {
1931
2021
  }
1932
2022
 
1933
2023
  // src/utils/version.ts
1934
- import { readFileSync as readFileSync4 } from "node:fs";
1935
- import { dirname as dirname6, join as join10 } from "node:path";
2024
+ import { readFileSync as readFileSync5 } from "node:fs";
2025
+ import { dirname as dirname6, join as join11 } from "node:path";
1936
2026
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1937
2027
  var PJANGLER_VERSION = (() => {
1938
2028
  try {
1939
2029
  let dir = dirname6(fileURLToPath3(import.meta.url));
1940
2030
  for (let i = 0; i < 4; i++) {
1941
2031
  try {
1942
- const raw = readFileSync4(join10(dir, "package.json"), "utf8");
2032
+ const raw = readFileSync5(join11(dir, "package.json"), "utf8");
1943
2033
  return JSON.parse(raw).version ?? "0.0.0";
1944
2034
  } catch {
1945
2035
  const parent = dirname6(dir);
@@ -1953,11 +2043,11 @@ var PJANGLER_VERSION = (() => {
1953
2043
  })();
1954
2044
 
1955
2045
  // src/parity/index.ts
1956
- import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readlinkSync, readdirSync, renameSync as renameSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5, chmodSync as chmodSync2, copyFileSync } from "node:fs";
1957
- import { basename as basename3, dirname as dirname7, join as join11, relative, resolve as resolve2 } from "node:path";
2046
+ import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readlinkSync, readdirSync as readdirSync2, renameSync as renameSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6, chmodSync as chmodSync2, copyFileSync } from "node:fs";
2047
+ import { basename as basename3, dirname as dirname7, join as join12, relative, resolve as resolve2 } from "node:path";
1958
2048
  import { fileURLToPath as fileURLToPath4 } from "node:url";
1959
2049
  import { homedir as homedir5 } from "node:os";
1960
- import { spawnSync as spawnSync5 } from "node:child_process";
2050
+ import { spawnSync as spawnSync6 } from "node:child_process";
1961
2051
  var LINK_AGENTFILES_BLOCK = `# This block will handle the linking of
1962
2052
  # agent files to the main AGENTS.md file.
1963
2053
  #
@@ -2028,7 +2118,7 @@ run = "{{config_root}}/.mise/scripts/versioning.sh sync"
2028
2118
  function resolvePjanglerRoot2() {
2029
2119
  let dir = dirname7(fileURLToPath4(import.meta.url));
2030
2120
  while (dir !== dirname7(dir)) {
2031
- if (existsSync8(join11(dir, "package.json")) && existsSync8(join11(dir, "templates", "commonproject", "copier.yml"))) {
2121
+ if (existsSync9(join12(dir, "package.json")) && existsSync9(join12(dir, "templates", "commonproject", "copier.yml"))) {
2032
2122
  return dir;
2033
2123
  }
2034
2124
  dir = dirname7(dir);
@@ -2039,17 +2129,17 @@ function normalizeNewlines(value) {
2039
2129
  return value.replace(/\r\n/g, "\n");
2040
2130
  }
2041
2131
  function readText(path) {
2042
- return normalizeNewlines(readFileSync5(path, "utf8"));
2132
+ return normalizeNewlines(readFileSync6(path, "utf8"));
2043
2133
  }
2044
2134
  function safeReadText(path) {
2045
- return existsSync8(path) ? readText(path) : null;
2135
+ return existsSync9(path) ? readText(path) : null;
2046
2136
  }
2047
2137
  function ensureParent(path) {
2048
2138
  mkdirSync6(dirname7(path), { recursive: true });
2049
2139
  }
2050
2140
  function writeText(path, content) {
2051
2141
  ensureParent(path);
2052
- writeFileSync5(path, content);
2142
+ writeFileSync6(path, content);
2053
2143
  }
2054
2144
  function tryParseJson(text2) {
2055
2145
  if (!text2) return null;
@@ -2066,7 +2156,7 @@ function titleCaseSlug(slug) {
2066
2156
  return slug.split(/[-_]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2067
2157
  }
2068
2158
  function readSymlinkTarget(path) {
2069
- if (!existsSync8(path)) return null;
2159
+ if (!existsSync9(path)) return null;
2070
2160
  try {
2071
2161
  return readlinkSync(path);
2072
2162
  } catch {
@@ -2074,7 +2164,7 @@ function readSymlinkTarget(path) {
2074
2164
  }
2075
2165
  }
2076
2166
  function ensureSymlink(path, target, dryRun) {
2077
- if (existsSync8(path)) {
2167
+ if (existsSync9(path)) {
2078
2168
  const stat = lstatSync(path);
2079
2169
  if (stat.isSymbolicLink()) {
2080
2170
  const current = readSymlinkTarget(path);
@@ -2091,11 +2181,11 @@ function ensureSymlink(path, target, dryRun) {
2091
2181
  return { changed: true };
2092
2182
  }
2093
2183
  function bootstrapAgentsFile(repoRoot, dryRun) {
2094
- const agentsPath = join11(repoRoot, "AGENTS.md");
2095
- if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
2184
+ const agentsPath = join12(repoRoot, "AGENTS.md");
2185
+ if (existsSync9(agentsPath)) return { changedFiles: [], details: [] };
2096
2186
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2097
- const source = join11(repoRoot, file);
2098
- if (!existsSync8(source)) continue;
2187
+ const source = join12(repoRoot, file);
2188
+ if (!existsSync9(source)) continue;
2099
2189
  const stat = lstatSync(source);
2100
2190
  if (stat.isSymbolicLink()) continue;
2101
2191
  if (stat.isFile()) {
@@ -2104,8 +2194,8 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
2104
2194
  }
2105
2195
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
2106
2196
  }
2107
- const readmePath = join11(repoRoot, "README.md");
2108
- if (existsSync8(readmePath)) {
2197
+ const readmePath = join12(repoRoot, "README.md");
2198
+ if (existsSync9(readmePath)) {
2109
2199
  const stat = lstatSync(readmePath);
2110
2200
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
2111
2201
  if (!dryRun) copyFileSync(readmePath, agentsPath);
@@ -2144,12 +2234,12 @@ function yamlGet(text2, keyPath) {
2144
2234
  return "";
2145
2235
  }
2146
2236
  function discoverRoles(repoRoot) {
2147
- const rolesDir = join11(repoRoot, "agents", "hermes");
2148
- if (!existsSync8(rolesDir)) return [];
2149
- return readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2150
- const roleDir = join11(rolesDir, entry.name);
2151
- const roleYamlPath = join11(roleDir, "role.yaml");
2152
- if (!existsSync8(roleYamlPath)) return null;
2237
+ const rolesDir = join12(repoRoot, "agents", "hermes");
2238
+ if (!existsSync9(rolesDir)) return [];
2239
+ return readdirSync2(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2240
+ const roleDir = join12(rolesDir, entry.name);
2241
+ const roleYamlPath = join12(roleDir, "role.yaml");
2242
+ if (!existsSync9(roleYamlPath)) return null;
2153
2243
  const text2 = readText(roleYamlPath);
2154
2244
  const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
2155
2245
  return {
@@ -2177,10 +2267,10 @@ function discoverRoles(repoRoot) {
2177
2267
  }).filter((value) => Boolean(value));
2178
2268
  }
2179
2269
  function registryPath(homeDir) {
2180
- return join11(homeDir, ".hermes", "agents-registry.yaml");
2270
+ return join12(homeDir, ".hermes", "agents-registry.yaml");
2181
2271
  }
2182
2272
  function systemctlUser(args) {
2183
- const result = spawnSync5("systemctl", ["--user", ...args], { encoding: "utf8" });
2273
+ const result = spawnSync6("systemctl", ["--user", ...args], { encoding: "utf8" });
2184
2274
  return {
2185
2275
  ok: result.status === 0,
2186
2276
  stdout: result.stdout.trim(),
@@ -2188,8 +2278,8 @@ function systemctlUser(args) {
2188
2278
  };
2189
2279
  }
2190
2280
  function templateScript(ctx, name) {
2191
- const source = join11(ctx.pjanglerRoot, ".mise", "scripts", name);
2192
- return existsSync8(source) ? readText(source) : void 0;
2281
+ const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
2282
+ return existsSync9(source) ? readText(source) : void 0;
2193
2283
  }
2194
2284
  function templateVersioningScript(ctx) {
2195
2285
  return templateScript(ctx, "versioning.sh");
@@ -2201,8 +2291,8 @@ function resolveAgentHooksLayer2(ctx) {
2201
2291
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
2202
2292
  if (override === "0" || override === "false") return false;
2203
2293
  if (override === "1" || override === "true") return true;
2204
- if (existsSync8(join11(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
2205
- return !existsSync8(join11(ctx.homeDir, ".agents", "hooks"));
2294
+ if (existsSync9(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
2295
+ return !existsSync9(join12(ctx.homeDir, ".agents", "hooks"));
2206
2296
  }
2207
2297
  function evaluateMiseConditionals(template, agentHooksLayer) {
2208
2298
  const out = [];
@@ -2232,10 +2322,10 @@ function renderGeneratedProjectMiseToml(ctx, template) {
2232
2322
  return evaluateMiseConditionals(template, resolveAgentHooksLayer2(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
2233
2323
  }
2234
2324
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
2235
- const targetPath = join11(ctx.repoRoot, "mise.toml");
2236
- if (existsSync8(targetPath)) return false;
2237
- const sourcePath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
2238
- if (!existsSync8(sourcePath)) return false;
2325
+ const targetPath = join12(ctx.repoRoot, "mise.toml");
2326
+ if (existsSync9(targetPath)) return false;
2327
+ const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
2328
+ if (!existsSync9(sourcePath)) return false;
2239
2329
  changedFiles.push(targetPath);
2240
2330
  if (!ctx.dryRun) {
2241
2331
  writeText(targetPath, renderGeneratedProjectMiseToml(ctx, readText(sourcePath)));
@@ -2243,8 +2333,8 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
2243
2333
  return true;
2244
2334
  }
2245
2335
  function templateVersionFilesConf(ctx, repoRoot) {
2246
- const packageJson = join11(repoRoot, "package.json");
2247
- return existsSync8(packageJson) ? "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\njson package.json\ngittag .\n" : "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\ngittag .\n";
2336
+ const packageJson = join12(repoRoot, "package.json");
2337
+ return existsSync9(packageJson) ? "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\njson package.json\ngittag .\n" : "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag\ngittag .\n";
2248
2338
  }
2249
2339
  function replaceOrAppendManagedBlock(text2, startMarker, block, beforePattern) {
2250
2340
  if (startMarker.test(text2)) {
@@ -2268,7 +2358,7 @@ var CONDITIONAL_HERMES_PATHS = ["agents/hermes/pm/hermes", "agent/hermes/pm/herm
2268
2358
  function requiredMisePathEntries(ctx) {
2269
2359
  const required = [...BASE_MISE_PATH_ENTRIES];
2270
2360
  for (const candidate of CONDITIONAL_HERMES_PATHS) {
2271
- if (existsSync8(join11(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
2361
+ if (existsSync9(join12(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
2272
2362
  }
2273
2363
  return required;
2274
2364
  }
@@ -2417,7 +2507,7 @@ function upsertLinkAgentfilesBlock(text2, ctx) {
2417
2507
  return insertTomlBlockBeforeVersioning(cleaned, LINK_AGENTFILES_WATCH_TASK_BLOCK);
2418
2508
  }
2419
2509
  function readProjectJson(ctx) {
2420
- return tryParseJson(safeReadText(join11(ctx.repoRoot, ".project.json")));
2510
+ return tryParseJson(safeReadText(join12(ctx.repoRoot, ".project.json")));
2421
2511
  }
2422
2512
  function boolSetting(value, fallback) {
2423
2513
  if (typeof value === "boolean") return value;
@@ -2492,12 +2582,12 @@ function canonicalProjectJson(ctx) {
2492
2582
  };
2493
2583
  }
2494
2584
  function projectJsonFinding(ctx) {
2495
- const projectPath = join11(ctx.repoRoot, ".project.json");
2496
- const planeJsonPath = join11(ctx.repoRoot, ".plane.json");
2585
+ const projectPath = join12(ctx.repoRoot, ".project.json");
2586
+ const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
2497
2587
  const details = [];
2498
2588
  const data = readProjectJson(ctx);
2499
2589
  const roles = discoverRoles(ctx.repoRoot);
2500
- if (!existsSync8(projectPath)) {
2590
+ if (!existsSync9(projectPath)) {
2501
2591
  return { id: "sot.project-json", title: "Canonical .project.json", status: "fail", summary: ".project.json missing", details: [], fixable: true };
2502
2592
  }
2503
2593
  if (!data) {
@@ -2532,7 +2622,7 @@ function projectJsonFinding(ctx) {
2532
2622
  for (const key of ["enabled", "grace_hours", "auto_review"]) {
2533
2623
  if (!(key in reconcile)) details.push(`automation.reconcile.${key} missing`);
2534
2624
  }
2535
- if (existsSync8(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
2625
+ if (existsSync9(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
2536
2626
  return {
2537
2627
  id: "sot.project-json",
2538
2628
  title: "Canonical .project.json",
@@ -2613,17 +2703,17 @@ exec env HERMES_HOME="$HERMES_HOME" HERMES_FLEET_ENV="$FLEET_ENV" HERMES_OAUTH
2613
2703
  `.replace(/\u0010/g, "$");
2614
2704
  }
2615
2705
  function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip) {
2616
- if (!existsSync8(sourceDir)) return;
2706
+ if (!existsSync9(sourceDir)) return;
2617
2707
  mkdirSync6(targetDir, { recursive: true });
2618
- for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
2619
- const sourcePath = join11(sourceDir, entry.name);
2708
+ for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
2709
+ const sourcePath = join12(sourceDir, entry.name);
2620
2710
  if (skip?.(sourcePath)) continue;
2621
- const targetPath = join11(targetDir, entry.name);
2711
+ const targetPath = join12(targetDir, entry.name);
2622
2712
  if (entry.isDirectory()) {
2623
2713
  copyMissingRecursive(sourcePath, targetPath, changedFiles, dryRun, skip);
2624
2714
  continue;
2625
2715
  }
2626
- if (existsSync8(targetPath)) continue;
2716
+ if (existsSync9(targetPath)) continue;
2627
2717
  changedFiles.push(targetPath);
2628
2718
  if (!dryRun) {
2629
2719
  ensureParent(targetPath);
@@ -2632,7 +2722,7 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
2632
2722
  }
2633
2723
  }
2634
2724
  function upsertSubmodule(repoRoot, role, changedFiles, dryRun) {
2635
- const gitmodulesPath = join11(repoRoot, ".gitmodules");
2725
+ const gitmodulesPath = join12(repoRoot, ".gitmodules");
2636
2726
  const repoName = role.runtimeRepo || `agent-hm-${role.repo}-${role.role}`;
2637
2727
  const owner = role.runtimeOwner || "delorenj";
2638
2728
  const block = `[submodule "agents/hermes/${role.role}/runtime"]
@@ -2732,14 +2822,14 @@ var RULES = [
2732
2822
  id: "mise.config-root",
2733
2823
  title: "mise config_root + AGENTS link hooks",
2734
2824
  audit: (ctx) => {
2735
- const misePath = join11(ctx.repoRoot, "mise.toml");
2736
- if (!existsSync8(misePath)) {
2825
+ const misePath = join12(ctx.repoRoot, "mise.toml");
2826
+ if (!existsSync9(misePath)) {
2737
2827
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
2738
2828
  }
2739
2829
  const text2 = readText(misePath);
2740
2830
  const details = [];
2741
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2742
- if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
2831
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2832
+ if (!existsSync9(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
2743
2833
  const pathValues = [...(text2.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
2744
2834
  const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
2745
2835
  if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
@@ -2757,10 +2847,10 @@ var RULES = [
2757
2847
  };
2758
2848
  },
2759
2849
  migrate: (ctx, finding) => {
2760
- const path = join11(ctx.repoRoot, "mise.toml");
2850
+ const path = join12(ctx.repoRoot, "mise.toml");
2761
2851
  const changedFiles = [];
2762
2852
  const details = [];
2763
- if (!existsSync8(path)) {
2853
+ if (!existsSync9(path)) {
2764
2854
  if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
2765
2855
  return { id: finding.id, title: finding.title, status: "blocked", summary: "mise.toml missing and no generated-project mise template available to initialize from", changedFiles, details: [] };
2766
2856
  }
@@ -2776,7 +2866,7 @@ var RULES = [
2776
2866
  if (!ctx.dryRun) writeText(path, next);
2777
2867
  text2 = next;
2778
2868
  }
2779
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2869
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2780
2870
  const expectedScript = templateLinkAgentfilesScript(ctx);
2781
2871
  if (expectedScript === void 0) {
2782
2872
  return { id: finding.id, title: finding.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/link-agentfiles.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
@@ -2803,13 +2893,13 @@ var RULES = [
2803
2893
  title: "managed mise versioning block",
2804
2894
  audit: (ctx) => {
2805
2895
  const details = [];
2806
- const misePath = join11(ctx.repoRoot, "mise.toml");
2807
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2808
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
2896
+ const misePath = join12(ctx.repoRoot, "mise.toml");
2897
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2898
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
2809
2899
  const text2 = safeReadText(misePath);
2810
2900
  if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
2811
- if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
2812
- if (!existsSync8(manifestPath)) details.push(".mise/version-files.conf missing");
2901
+ if (!existsSync9(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
2902
+ if (!existsSync9(manifestPath)) details.push(".mise/version-files.conf missing");
2813
2903
  return {
2814
2904
  id: "mise.versioning",
2815
2905
  title: "managed mise versioning block",
@@ -2822,8 +2912,8 @@ var RULES = [
2822
2912
  migrate: (ctx, finding) => {
2823
2913
  const changedFiles = [];
2824
2914
  const details = [];
2825
- const misePath = join11(ctx.repoRoot, "mise.toml");
2826
- if (!existsSync8(misePath)) {
2915
+ const misePath = join12(ctx.repoRoot, "mise.toml");
2916
+ if (!existsSync9(misePath)) {
2827
2917
  if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
2828
2918
  return { id: finding.id, title: finding.title, status: "blocked", summary: "mise.toml missing and no generated-project mise template available to initialize from", changedFiles, details: [] };
2829
2919
  }
@@ -2833,12 +2923,21 @@ var RULES = [
2833
2923
  }
2834
2924
  }
2835
2925
  const currentMise = readText(misePath);
2836
- const nextMise = replaceOrAppendManagedBlock(currentMise, /# >>> mise-versioning >>>/, VERSIONING_BLOCK, /^\[tasks\.build\]/m);
2926
+ let cleanedMise = currentMise;
2927
+ if (!currentMise.includes("# >>> mise-versioning >>>")) {
2928
+ const taskNames = ["version", "version:bump", "version:bump-patch", "version:bump-minor", "version:bump-major", "version:check", "version:sync"];
2929
+ for (const taskName of taskNames) {
2930
+ const escaped = taskName.replace(/:/g, "\\:");
2931
+ const headerPattern = new RegExp(`^\\[tasks\\.(?:"${escaped}"|'${escaped}'|${escaped})\\]$`);
2932
+ cleanedMise = removeTomlSection(cleanedMise, headerPattern);
2933
+ }
2934
+ }
2935
+ const nextMise = replaceOrAppendManagedBlock(cleanedMise, /# >>> mise-versioning >>>/, VERSIONING_BLOCK, /^\[tasks\.build\]/m);
2837
2936
  if (nextMise !== currentMise) {
2838
2937
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
2839
2938
  if (!ctx.dryRun) writeText(misePath, nextMise);
2840
2939
  }
2841
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2940
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2842
2941
  const expectedScript = templateVersioningScript(ctx);
2843
2942
  if (expectedScript === void 0) {
2844
2943
  return { id: finding.id, title: finding.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/versioning.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
@@ -2850,7 +2949,7 @@ var RULES = [
2850
2949
  chmodSync2(versioningPath, 493);
2851
2950
  }
2852
2951
  }
2853
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
2952
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
2854
2953
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
2855
2954
  if (safeReadText(manifestPath) !== expectedManifest) {
2856
2955
  changedFiles.push(manifestPath);
@@ -2870,9 +2969,9 @@ var RULES = [
2870
2969
  id: "sot.agent-symlinks",
2871
2970
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
2872
2971
  audit: (ctx) => {
2873
- const agentsPath = join11(ctx.repoRoot, "AGENTS.md");
2874
- if (!existsSync8(agentsPath)) {
2875
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join11(ctx.repoRoot, file)));
2972
+ const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
2973
+ if (!existsSync9(agentsPath)) {
2974
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync9(join12(ctx.repoRoot, file)));
2876
2975
  if (fallbackSources.length === 0) {
2877
2976
  return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
2878
2977
  }
@@ -2887,7 +2986,7 @@ var RULES = [
2887
2986
  }
2888
2987
  const details = [];
2889
2988
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2890
- const full = join11(ctx.repoRoot, file);
2989
+ const full = join12(ctx.repoRoot, file);
2891
2990
  const target = readSymlinkTarget(full);
2892
2991
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
2893
2992
  }
@@ -2911,7 +3010,7 @@ var RULES = [
2911
3010
  return { id: finding.id, title: finding.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
2912
3011
  }
2913
3012
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2914
- const full = join11(ctx.repoRoot, file);
3013
+ const full = join12(ctx.repoRoot, file);
2915
3014
  const result = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
2916
3015
  if (result.blocked) blockedDetails.push(result.blocked);
2917
3016
  if (result.changed) changedFiles.push(full);
@@ -2933,7 +3032,7 @@ var RULES = [
2933
3032
  migrate: (ctx, finding) => {
2934
3033
  const changedFiles = [];
2935
3034
  const details = [];
2936
- const path = join11(ctx.repoRoot, ".project.json");
3035
+ const path = join12(ctx.repoRoot, ".project.json");
2937
3036
  const existing = readProjectJson(ctx) ?? {};
2938
3037
  const canonical = canonicalProjectJson(ctx);
2939
3038
  const merged = { ...existing, ...canonical };
@@ -2943,10 +3042,10 @@ var RULES = [
2943
3042
  changedFiles.push(path);
2944
3043
  if (!ctx.dryRun) writeText(path, expected);
2945
3044
  }
2946
- const planeJson = join11(ctx.repoRoot, ".plane.json");
2947
- if (existsSync8(planeJson)) {
3045
+ const planeJson = join12(ctx.repoRoot, ".plane.json");
3046
+ if (existsSync9(planeJson)) {
2948
3047
  const backup = `${planeJson}.migrated-backup`;
2949
- if (existsSync8(backup)) {
3048
+ if (existsSync9(backup)) {
2950
3049
  details.push(`cannot back up .plane.json because ${relative(ctx.repoRoot, backup)} already exists`);
2951
3050
  } else {
2952
3051
  changedFiles.push(backup);
@@ -2968,8 +3067,8 @@ var RULES = [
2968
3067
  title: ".env.op + gitignore secrets contract",
2969
3068
  audit: (ctx) => {
2970
3069
  const details = [];
2971
- const envOp = safeReadText(join11(ctx.repoRoot, ".env.op"));
2972
- const gitignore = safeReadText(join11(ctx.repoRoot, ".gitignore"));
3070
+ const envOp = safeReadText(join12(ctx.repoRoot, ".env.op"));
3071
+ const gitignore = safeReadText(join12(ctx.repoRoot, ".gitignore"));
2973
3072
  if (!envOp) {
2974
3073
  details.push(".env.op missing");
2975
3074
  } else {
@@ -2995,12 +3094,12 @@ var RULES = [
2995
3094
  migrate: (ctx, finding) => {
2996
3095
  const changedFiles = [];
2997
3096
  const details = [];
2998
- const envOpPath = join11(ctx.repoRoot, ".env.op");
2999
- if (!existsSync8(envOpPath)) {
3097
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
3098
+ if (!existsSync9(envOpPath)) {
3000
3099
  changedFiles.push(envOpPath);
3001
- if (!ctx.dryRun) writeText(envOpPath, readText(join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
3100
+ if (!ctx.dryRun) writeText(envOpPath, readText(join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
3002
3101
  }
3003
- const gitignorePath = join11(ctx.repoRoot, ".gitignore");
3102
+ const gitignorePath = join12(ctx.repoRoot, ".gitignore");
3004
3103
  const gitignore = safeReadText(gitignorePath) ?? "";
3005
3104
  const requiredBlock = `# Secrets \u2014 .env is materialized by \`op inject -i .env.op > .env\` on mise enter.
3006
3105
  # NEVER commit it. .env.op holds only 1Password references or safe literals and IS committed.
@@ -3027,7 +3126,7 @@ var RULES = [
3027
3126
  title: ".copier-answers.yml provenance + drift report",
3028
3127
  audit: (ctx) => {
3029
3128
  const details = [];
3030
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
3129
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
3031
3130
  const text2 = safeReadText(path);
3032
3131
  const project = readProjectJson(ctx);
3033
3132
  if (!text2) {
@@ -3058,12 +3157,12 @@ var RULES = [
3058
3157
  const changedFiles = [];
3059
3158
  const project = canonicalProjectJson(ctx);
3060
3159
  const text2 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
3061
- _src_path: ${join11(ctx.pjanglerRoot, "templates", "commonproject")}
3160
+ _src_path: ${join12(ctx.pjanglerRoot, "templates", "commonproject")}
3062
3161
  project_description: ${String(project.project_description)}
3063
3162
  project_name: ${String(project.project_name)}
3064
3163
  ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3065
3164
  `;
3066
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
3165
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
3067
3166
  if (safeReadText(path) !== text2) {
3068
3167
  changedFiles.push(path);
3069
3168
  if (!ctx.dryRun) writeText(path, text2);
@@ -3082,15 +3181,15 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3082
3181
  id: "bmad.scaffold",
3083
3182
  title: "BMAD modules/docs scaffold",
3084
3183
  audit: (ctx) => {
3085
- const sourceRoot = join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
3086
- const targetRoot = join11(ctx.repoRoot, "_bmad");
3184
+ const sourceRoot = join12(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
3185
+ const targetRoot = join12(ctx.repoRoot, "_bmad");
3087
3186
  const sentinels = [
3088
- join11("core", "config.yaml"),
3089
- join11("custom", "config.yaml"),
3090
- join11("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3091
- join11("bmm", "workflows", "workflow-status", "workflow.yaml")
3187
+ join12("core", "config.yaml"),
3188
+ join12("custom", "config.yaml"),
3189
+ join12("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3190
+ join12("bmm", "workflows", "workflow-status", "workflow.yaml")
3092
3191
  ];
3093
- const missing = sentinels.filter((file) => existsSync8(join11(sourceRoot, file)) && !existsSync8(join11(targetRoot, file)));
3192
+ const missing = sentinels.filter((file) => existsSync9(join12(sourceRoot, file)) && !existsSync9(join12(targetRoot, file)));
3094
3193
  return {
3095
3194
  id: "bmad.scaffold",
3096
3195
  title: "BMAD modules/docs scaffold",
@@ -3102,7 +3201,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3102
3201
  },
3103
3202
  migrate: (ctx, finding) => {
3104
3203
  const changedFiles = [];
3105
- copyMissingRecursive(join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join11(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
3204
+ copyMissingRecursive(join12(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join12(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
3106
3205
  return {
3107
3206
  id: finding.id,
3108
3207
  title: finding.title,
@@ -3124,11 +3223,11 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3124
3223
  }
3125
3224
  const details = [];
3126
3225
  for (const rel of ["role.yaml", "SOUL.md", "hermes", ".gitignore", ".scripts/70-systemd.sh", ".scripts/heartbeat.sh", ".scripts/checkpoint.sh", ".runtime-scaffold/README.md", "runtime/memories/MEMORY.md", "runtime/bloodbank-consumer.py"]) {
3127
- if (!existsSync8(join11(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join11(role.roleDir, rel))}`);
3226
+ if (!existsSync9(join12(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join12(role.roleDir, rel))}`);
3128
3227
  }
3129
- const gitmodules = safeReadText(join11(ctx.repoRoot, ".gitmodules")) ?? "";
3228
+ const gitmodules = safeReadText(join12(ctx.repoRoot, ".gitmodules")) ?? "";
3130
3229
  if (!gitmodules.includes(`agents/hermes/${role.role}/runtime`)) details.push(".gitmodules missing pm runtime submodule entry");
3131
- if (!profileMetaInheritsDefault(join11(role.roleDir, "runtime", "profile.yaml"))) {
3230
+ if (!profileMetaInheritsDefault(join12(role.roleDir, "runtime", "profile.yaml"))) {
3132
3231
  details.push("runtime/profile.yaml missing inherited default config metadata");
3133
3232
  }
3134
3233
  const registry = safeReadText(registryPath(ctx.homeDir));
@@ -3149,21 +3248,21 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3149
3248
  if (!role) {
3150
3249
  return { id: finding.id, title: finding.title, status: "blocked", summary: "No pm role present", changedFiles, details: [] };
3151
3250
  }
3152
- const templateRoleDir = join11(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
3153
- writeIfDifferent(join11(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
3154
- writeIfDifferent(join11(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
3155
- writeIfDifferent(join11(role.roleDir, ".gitignore"), readText(join11(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
3156
- copyMissingRecursive(join11(templateRoleDir, ".runtime-scaffold"), join11(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
3157
- copyMissingRecursive(join11(templateRoleDir, ".runtime-scaffold"), join11(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
3158
- copyMissingRecursive(join11(templateRoleDir, ".scripts"), join11(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
3159
- const promptSource = join11(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
3160
- const promptTarget = join11(role.roleDir, ".scripts", "sentinel.prompt.md");
3161
- if (existsSync8(promptSource) && !existsSync8(promptTarget)) {
3251
+ const templateRoleDir = join12(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
3252
+ writeIfDifferent(join12(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
3253
+ writeIfDifferent(join12(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
3254
+ writeIfDifferent(join12(role.roleDir, ".gitignore"), readText(join12(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
3255
+ copyMissingRecursive(join12(templateRoleDir, ".runtime-scaffold"), join12(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
3256
+ copyMissingRecursive(join12(templateRoleDir, ".runtime-scaffold"), join12(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
3257
+ copyMissingRecursive(join12(templateRoleDir, ".scripts"), join12(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
3258
+ const promptSource = join12(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
3259
+ const promptTarget = join12(role.roleDir, ".scripts", "sentinel.prompt.md");
3260
+ if (existsSync9(promptSource) && !existsSync9(promptTarget)) {
3162
3261
  const prompt = readText(promptSource).replace(/\{\{ agent_id \}\}/g, role.agentId).replace(/\{\{ role \}\}/g, role.role).replace(/\{\{ target_repo \}\}/g, role.repo).replace(/\{\{ display_name \}\}/g, role.displayName || role.agentId);
3163
3262
  writeIfDifferent(promptTarget, prompt, ctx.dryRun, changedFiles);
3164
3263
  }
3165
3264
  upsertSubmodule(ctx.repoRoot, role, changedFiles, ctx.dryRun);
3166
- const profileMetaUpdated = upsertInheritedProfileMeta(join11(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
3265
+ const profileMetaUpdated = upsertInheritedProfileMeta(join12(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
3167
3266
  if (profileMetaUpdated) details.push(`updated ${profileMetaUpdated}`);
3168
3267
  const registryUpdated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
3169
3268
  if (registryUpdated) details.push(`updated ${registryUpdated}`);
@@ -3177,6 +3276,103 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3177
3276
  };
3178
3277
  }
3179
3278
  },
3279
+ {
3280
+ id: "hermes.untracked-runtimes",
3281
+ title: "Hermes agent runtimes untracked + gitignored",
3282
+ audit: (ctx) => {
3283
+ const roles = discoverRoles(ctx.repoRoot);
3284
+ if (roles.length === 0) {
3285
+ return {
3286
+ id: "hermes.untracked-runtimes",
3287
+ title: "Hermes agent runtimes untracked + gitignored",
3288
+ status: "skip",
3289
+ summary: "No Hermes roles present",
3290
+ details: [],
3291
+ fixable: false
3292
+ };
3293
+ }
3294
+ const details = [];
3295
+ for (const role of roles) {
3296
+ const roleRelDir = relative(ctx.repoRoot, role.roleDir);
3297
+ const runtimeRelPath = join12(roleRelDir, "runtime");
3298
+ const lsResult = spawnSync6("git", ["ls-files", "--stage", runtimeRelPath], {
3299
+ cwd: ctx.repoRoot,
3300
+ encoding: "utf8"
3301
+ });
3302
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
3303
+ details.push(`submodule runtime is tracked in Git index at ${runtimeRelPath}`);
3304
+ }
3305
+ const gitignorePath = join12(role.roleDir, ".gitignore");
3306
+ if (existsSync9(gitignorePath)) {
3307
+ const content = safeReadText(gitignorePath) ?? "";
3308
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
3309
+ if (!lines.includes("runtime/") && !lines.includes("runtime")) {
3310
+ details.push(`.gitignore missing runtime/ ignore entry in ${relative(ctx.repoRoot, gitignorePath)}`);
3311
+ }
3312
+ } else {
3313
+ details.push(`.gitignore is missing in ${relative(ctx.repoRoot, gitignorePath)}`);
3314
+ }
3315
+ }
3316
+ return {
3317
+ id: "hermes.untracked-runtimes",
3318
+ title: "Hermes agent runtimes untracked + gitignored",
3319
+ status: details.length === 0 ? "pass" : "fail",
3320
+ summary: details.length === 0 ? "All Hermes agent runtimes are untracked and gitignored" : `${details.length} issue(s) with untracked/ignored runtimes detected`,
3321
+ details,
3322
+ fixable: true
3323
+ };
3324
+ },
3325
+ migrate: (ctx, finding) => {
3326
+ const roles = discoverRoles(ctx.repoRoot);
3327
+ const changedFiles = [];
3328
+ const details = [];
3329
+ for (const role of roles) {
3330
+ const roleRelDir = relative(ctx.repoRoot, role.roleDir);
3331
+ const runtimeRelPath = join12(roleRelDir, "runtime");
3332
+ const lsResult = spawnSync6("git", ["ls-files", "--stage", runtimeRelPath], {
3333
+ cwd: ctx.repoRoot,
3334
+ encoding: "utf8"
3335
+ });
3336
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
3337
+ details.push(`untrack ${runtimeRelPath}`);
3338
+ changedFiles.push(runtimeRelPath);
3339
+ if (!ctx.dryRun) {
3340
+ spawnSync6("git", ["rm", "--cached", "-r", runtimeRelPath], {
3341
+ cwd: ctx.repoRoot,
3342
+ encoding: "utf8"
3343
+ });
3344
+ }
3345
+ }
3346
+ const gitignorePath = join12(role.roleDir, ".gitignore");
3347
+ let content = "";
3348
+ let isIgnored = false;
3349
+ if (existsSync9(gitignorePath)) {
3350
+ content = safeReadText(gitignorePath) ?? "";
3351
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
3352
+ isIgnored = lines.includes("runtime/") || lines.includes("runtime");
3353
+ }
3354
+ if (!isIgnored) {
3355
+ details.push(`ignore runtime/ in ${relative(ctx.repoRoot, gitignorePath)}`);
3356
+ changedFiles.push(gitignorePath);
3357
+ if (!ctx.dryRun) {
3358
+ if (content && !content.endsWith("\n")) {
3359
+ content += "\n";
3360
+ }
3361
+ content += "runtime/\n";
3362
+ writeText(gitignorePath, content);
3363
+ }
3364
+ }
3365
+ }
3366
+ return {
3367
+ id: finding.id,
3368
+ title: finding.title,
3369
+ status: changedFiles.length ? "applied" : "noop",
3370
+ summary: changedFiles.length ? "Hermes agent runtimes made untracked and ignored" : "No changes required",
3371
+ changedFiles,
3372
+ details
3373
+ };
3374
+ }
3375
+ },
3180
3376
  {
3181
3377
  id: "systemd.sentinel",
3182
3378
  title: "Hermes systemd/sentinel units enabled + active",
@@ -3217,9 +3413,9 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3217
3413
  return { id: finding.id, title: finding.title, status: "blocked", summary: "systemd --user unavailable on this host", changedFiles, details };
3218
3414
  }
3219
3415
  for (const role of roles) {
3220
- const sysDir = join11(ctx.homeDir, ".config", "systemd", "user");
3416
+ const sysDir = join12(ctx.homeDir, ".config", "systemd", "user");
3221
3417
  const units = [`hermes-${role.agentId}-gateway.service`, `hermes-${role.agentId}-consumer.service`, `hermes-${role.agentId}-heartbeat.timer`];
3222
- const allUnitsPresent = units.every((unit) => existsSync8(join11(sysDir, unit)));
3418
+ const allUnitsPresent = units.every((unit) => existsSync9(join12(sysDir, unit)));
3223
3419
  if (allUnitsPresent) {
3224
3420
  if (ctx.dryRun) {
3225
3421
  details.push(`would run: systemctl --user enable --now ${units.join(" ")}`);
@@ -3231,12 +3427,12 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3231
3427
  }
3232
3428
  continue;
3233
3429
  }
3234
- for (const script of [join11(role.roleDir, ".scripts", "70-systemd.sh")]) {
3235
- if (!script || !existsSync8(script)) continue;
3430
+ for (const script of [join12(role.roleDir, ".scripts", "70-systemd.sh")]) {
3431
+ if (!script || !existsSync9(script)) continue;
3236
3432
  if (ctx.dryRun) {
3237
3433
  details.push(`would run: bash ${script}`);
3238
3434
  } else {
3239
- const result = spawnSync5("bash", [script], { cwd: role.roleDir, encoding: "utf8" });
3435
+ const result = spawnSync6("bash", [script], { cwd: role.roleDir, encoding: "utf8" });
3240
3436
  if (result.status !== 0) details.push(`script failed: ${script}: ${result.stderr.trim() || result.stdout.trim()}`);
3241
3437
  }
3242
3438
  }
@@ -3356,7 +3552,7 @@ var server = new McpServer({
3356
3552
  var TICKET_PROVIDER_SCHEMA = z.enum(["plane", "trello"]);
3357
3553
  function resolveTargetDir(targetDir) {
3358
3554
  const dir = resolve3(targetDir ?? process.cwd());
3359
- if (!existsSync9(dir)) {
3555
+ if (!existsSync10(dir)) {
3360
3556
  throw new Error(`Target directory does not exist: ${dir}`);
3361
3557
  }
3362
3558
  if (!statSync2(dir).isDirectory()) {
@@ -3367,7 +3563,7 @@ function resolveTargetDir(targetDir) {
3367
3563
  function resolvePjanglerRoot3() {
3368
3564
  let dir = dirname8(fileURLToPath5(import.meta.url));
3369
3565
  while (dir !== dirname8(dir)) {
3370
- if (existsSync9(join12(dir, "package.json")) && existsSync9(join12(dir, "templates", "commonproject", "copier.yml"))) {
3566
+ if (existsSync10(join13(dir, "package.json")) && existsSync10(join13(dir, "templates", "commonproject", "copier.yml"))) {
3371
3567
  return dir;
3372
3568
  }
3373
3569
  dir = dirname8(dir);
@@ -3569,8 +3765,8 @@ server.registerTool(
3569
3765
  const pjanglerRoot = resolvePjanglerRoot3();
3570
3766
  const projectSlug = input.projectSlug ?? slugify(input.projectName);
3571
3767
  const parentDir = resolve3(input.parentDir ?? process.cwd());
3572
- if (!existsSync9(parentDir) || !statSync2(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
3573
- const targetDir = resolve3(input.targetDir ?? join12(parentDir, projectSlug));
3768
+ if (!existsSync10(parentDir) || !statSync2(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
3769
+ const targetDir = resolve3(input.targetDir ?? join13(parentDir, projectSlug));
3574
3770
  const overwrite = input.overwrite ?? input.force ?? false;
3575
3771
  const dryRun = input.dryRun ?? true;
3576
3772
  const local = input.local ?? true;
@@ -3580,7 +3776,7 @@ server.registerTool(
3580
3776
  if (!skipPlane && ticketProvider === "plane" && !boardId) {
3581
3777
  throw new Error("boardId or planeProjectId is required when skipPlane=false for Plane; keep skipPlane=true for safe local bootstrap");
3582
3778
  }
3583
- if (!dryRun && existsSync9(targetDir) && !overwrite) throw new Error(`Target already exists: ${targetDir} (set force/overwrite=true to re-render)`);
3779
+ if (!dryRun && existsSync10(targetDir) && !overwrite) throw new Error(`Target already exists: ${targetDir} (set force/overwrite=true to re-render)`);
3584
3780
  const plan = planProjectInit({
3585
3781
  name: input.projectName,
3586
3782
  description: input.projectDescription,