@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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { spawnSync as spawnSync6 } from "node:child_process";
5
- import { existsSync as existsSync9, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
6
- import { basename as basename4, join as join12, resolve as resolve3 } from "node:path";
4
+ import { spawnSync as spawnSync7 } from "node:child_process";
5
+ import { existsSync as existsSync10, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
6
+ import { basename as basename4, join as join13, resolve as resolve3 } from "node:path";
7
7
  import { Command as Command3 } from "commander";
8
8
 
9
9
  // src/commands/hermes/types.ts
@@ -916,10 +916,100 @@ var RunCopierTemplate = class extends Command {
916
916
  }
917
917
  };
918
918
 
919
- // src/commands/hermes/WireTelegram.ts
919
+ // src/commands/hermes/UntrackHermesRuntimes.ts
920
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync3, readdirSync } from "fs";
921
+ import { join as join6 } from "path";
920
922
  import { spawnSync as spawnSync2 } from "node:child_process";
921
- import { join as join6 } from "node:path";
922
- import { existsSync as existsSync4, unlinkSync } from "node:fs";
923
+ var UntrackHermesRuntimes = class extends Command {
924
+ async invoke() {
925
+ const targetDir = this.context.targetDir;
926
+ const rolesDir = join6(targetDir, "agents", "hermes");
927
+ if (!existsSync4(rolesDir)) {
928
+ return {
929
+ success: true,
930
+ message: "No Hermes agents found (no agents/hermes directory)."
931
+ };
932
+ }
933
+ const roles = readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
934
+ if (roles.length === 0) {
935
+ return {
936
+ success: true,
937
+ message: "No Hermes agents found."
938
+ };
939
+ }
940
+ let modifiedAny = false;
941
+ const details = [];
942
+ for (const role of roles) {
943
+ const roleDir = join6("agents", "hermes", role);
944
+ const runtimePath = join6(roleDir, "runtime");
945
+ const gitignorePath = join6(roleDir, ".gitignore");
946
+ let isTracked = false;
947
+ const lsResult = spawnSync2("git", ["ls-files", "--stage", runtimePath], {
948
+ cwd: targetDir,
949
+ encoding: "utf8"
950
+ });
951
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
952
+ isTracked = true;
953
+ }
954
+ let isIgnored = false;
955
+ const fullGitignorePath = join6(targetDir, gitignorePath);
956
+ if (existsSync4(fullGitignorePath)) {
957
+ const content = readFileSync2(fullGitignorePath, "utf8");
958
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
959
+ isIgnored = lines.includes("runtime/") || lines.includes("runtime");
960
+ }
961
+ if (isTracked || !isIgnored) {
962
+ modifiedAny = true;
963
+ if (isTracked) {
964
+ details.push(`untrack agents/hermes/${role}/runtime`);
965
+ if (!this.context.dryRun) {
966
+ const rmResult = spawnSync2("git", ["rm", "--cached", "-r", runtimePath], {
967
+ cwd: targetDir,
968
+ encoding: "utf8"
969
+ });
970
+ if (rmResult.status !== 0) {
971
+ return {
972
+ success: false,
973
+ message: `Failed to untrack agents/hermes/${role}/runtime: ${rmResult.stderr}`
974
+ };
975
+ }
976
+ }
977
+ }
978
+ if (!isIgnored) {
979
+ details.push(`ignore runtime/ in agents/hermes/${role}/.gitignore`);
980
+ if (!this.context.dryRun) {
981
+ let content = "";
982
+ if (existsSync4(fullGitignorePath)) {
983
+ content = readFileSync2(fullGitignorePath, "utf8");
984
+ }
985
+ if (content && !content.endsWith("\n")) {
986
+ content += "\n";
987
+ }
988
+ content += "runtime/\n";
989
+ writeFileSync3(fullGitignorePath, content, "utf8");
990
+ }
991
+ }
992
+ }
993
+ }
994
+ if (!modifiedAny) {
995
+ return {
996
+ success: true,
997
+ message: "\u2705 All Hermes agent runtimes are already untracked and gitignored."
998
+ };
999
+ }
1000
+ const actionText = this.context.dryRun ? "Would make" : "Made";
1001
+ return {
1002
+ success: true,
1003
+ message: `${actionText} Hermes agent runtimes untracked and gitignored:
1004
+ ${details.map((d) => ` - ${d}`).join("\n")}`
1005
+ };
1006
+ }
1007
+ };
1008
+
1009
+ // src/commands/hermes/WireTelegram.ts
1010
+ import { spawnSync as spawnSync3 } from "node:child_process";
1011
+ import { join as join7 } from "node:path";
1012
+ import { existsSync as existsSync5, unlinkSync } from "node:fs";
923
1013
  import * as p3 from "@clack/prompts";
924
1014
  var WireTelegram = class extends Command {
925
1015
  async invoke() {
@@ -941,7 +1031,7 @@ var WireTelegram = class extends Command {
941
1031
  let token = process.env.TELEGRAM_BOT_TOKEN;
942
1032
  let source = token ? "env" : null;
943
1033
  if (!token) {
944
- const tryOp = spawnSync2("op", ["read", vaultRef], { encoding: "utf8" });
1034
+ const tryOp = spawnSync3("op", ["read", vaultRef], { encoding: "utf8" });
945
1035
  if (tryOp.status === 0) {
946
1036
  token = tryOp.stdout.trim();
947
1037
  source = "op";
@@ -980,7 +1070,7 @@ var WireTelegram = class extends Command {
980
1070
  initialValue: true
981
1071
  });
982
1072
  if (!p3.isCancel(persist) && persist) {
983
- const create = spawnSync2(
1073
+ const create = spawnSync3(
984
1074
  "op",
985
1075
  [
986
1076
  "item",
@@ -1007,18 +1097,18 @@ var WireTelegram = class extends Command {
1007
1097
  if (p3.isCancel(allowedAnswer)) {
1008
1098
  return { success: false, message: "\u2717 Aborted; Telegram step deferred." };
1009
1099
  }
1010
- const script = join6(roleDir, ".scripts", "30-telegram.sh");
1011
- if (!existsSync4(script)) {
1100
+ const script = join7(roleDir, ".scripts", "30-telegram.sh");
1101
+ if (!existsSync5(script)) {
1012
1102
  return {
1013
1103
  success: false,
1014
1104
  message: `\u2717 ${script} not found. Did copier finish? Re-run with --skip-runtime-repo=0 if you skipped it.`
1015
1105
  };
1016
1106
  }
1017
- const marker = join6(roleDir, ".scripts", ".done-30-telegram");
1018
- if (existsSync4(marker)) unlinkSync(marker);
1107
+ const marker = join7(roleDir, ".scripts", ".done-30-telegram");
1108
+ if (existsSync5(marker)) unlinkSync(marker);
1019
1109
  const spinner4 = p3.spinner();
1020
1110
  spinner4.start("Verifying token + wiring profile");
1021
- const result = spawnSync2("bash", [script], {
1111
+ const result = spawnSync3("bash", [script], {
1022
1112
  stdio: "inherit",
1023
1113
  env: {
1024
1114
  ...process.env,
@@ -1041,9 +1131,9 @@ function cap(s) {
1041
1131
  }
1042
1132
 
1043
1133
  // src/commands/hermes/WireEmail.ts
1044
- import { spawnSync as spawnSync3 } from "node:child_process";
1045
- import { join as join7 } from "node:path";
1046
- import { existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
1134
+ import { spawnSync as spawnSync4 } from "node:child_process";
1135
+ import { join as join8 } from "node:path";
1136
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
1047
1137
  import * as p4 from "@clack/prompts";
1048
1138
  var WireEmail = class extends Command {
1049
1139
  async invoke() {
@@ -1058,13 +1148,13 @@ var WireEmail = class extends Command {
1058
1148
  if (!targetRepo || !role || !roleDir) {
1059
1149
  return { success: false, message: "Cannot wire email: missing target_repo/role/roleDir" };
1060
1150
  }
1061
- const script = join7(roleDir, ".scripts", "50-email.sh");
1062
- if (!existsSync5(script)) {
1151
+ const script = join8(roleDir, ".scripts", "50-email.sh");
1152
+ if (!existsSync6(script)) {
1063
1153
  return { success: false, message: `\u2717 ${script} not found` };
1064
1154
  }
1065
1155
  let token = process.env.CF_EMAIL_ROUTING_TOKEN;
1066
1156
  if (!token) {
1067
- const tryOp = spawnSync3(
1157
+ const tryOp = spawnSync4(
1068
1158
  "op",
1069
1159
  ["read", "op://DeLoSecrets/Cloudflare-EmailRouting/token"],
1070
1160
  { encoding: "utf8" }
@@ -1104,7 +1194,7 @@ var WireEmail = class extends Command {
1104
1194
  initialValue: true
1105
1195
  });
1106
1196
  if (!p4.isCancel(persist) && persist) {
1107
- const create = spawnSync3(
1197
+ const create = spawnSync4(
1108
1198
  "op",
1109
1199
  [
1110
1200
  "item",
@@ -1121,11 +1211,11 @@ var WireEmail = class extends Command {
1121
1211
  }
1122
1212
  }
1123
1213
  }
1124
- const marker = join7(roleDir, ".scripts", ".done-50-email");
1125
- if (existsSync5(marker)) unlinkSync2(marker);
1214
+ const marker = join8(roleDir, ".scripts", ".done-50-email");
1215
+ if (existsSync6(marker)) unlinkSync2(marker);
1126
1216
  const spinner4 = p4.spinner();
1127
1217
  spinner4.start("Creating Cloudflare Email Routing rule");
1128
- const result = spawnSync3("bash", [script], {
1218
+ const result = spawnSync4("bash", [script], {
1129
1219
  stdio: "inherit",
1130
1220
  env: { ...process.env, SKIP_EMAIL: "0", CF_EMAIL_ROUTING_TOKEN: token },
1131
1221
  cwd: roleDir
@@ -1185,7 +1275,7 @@ var PrintHermesSummary = class extends Command {
1185
1275
  var HermesAgentRecipe = class extends Recipe {
1186
1276
  constructor(context) {
1187
1277
  super(context);
1188
- this.addIngredient(EnsureTemplateConfig).addIngredient(PromptForAgentConfig).addIngredient(RunCopierTemplate).addIngredient(WireTelegram).addIngredient(WireEmail).addIngredient(PrintHermesSummary);
1278
+ this.addIngredient(EnsureTemplateConfig).addIngredient(PromptForAgentConfig).addIngredient(RunCopierTemplate).addIngredient(UntrackHermesRuntimes).addIngredient(WireTelegram).addIngredient(WireEmail).addIngredient(PrintHermesSummary);
1189
1279
  }
1190
1280
  // Override execute() to suppress the base class's per-command logging since
1191
1281
  // our commands already render their own UI via @clack/prompts.
@@ -1209,33 +1299,33 @@ var HermesAgentRecipe = class extends Recipe {
1209
1299
 
1210
1300
  // src/commands/AgentHooksCommands.ts
1211
1301
  import { homedir as homedir4 } from "node:os";
1212
- import { join as join9, dirname as dirname5 } from "node:path";
1213
- import { existsSync as existsSync7, cpSync, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1302
+ import { join as join10, dirname as dirname5 } from "node:path";
1303
+ import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
1214
1304
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1215
1305
 
1216
1306
  // src/project/index.ts
1217
- import { spawnSync as spawnSync4 } from "node:child_process";
1218
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, renameSync, statSync, writeFileSync as writeFileSync3 } from "node:fs";
1307
+ import { spawnSync as spawnSync5 } from "node:child_process";
1308
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, statSync, writeFileSync as writeFileSync4 } from "node:fs";
1219
1309
  import { homedir as homedir3 } from "node:os";
1220
- import { basename as basename2, delimiter, dirname as dirname4, join as join8, resolve } from "node:path";
1310
+ import { basename as basename2, delimiter, dirname as dirname4, join as join9, resolve } from "node:path";
1221
1311
  import YAML from "yaml";
1222
1312
  var PROJECT_REGISTRY_ENV = "PJ_PROJECT_REGISTRY";
1223
1313
  var PROJECT_SOURCE_SKILL_ROOTS_ENV = "PJ_SOURCE_SKILL_ROOTS";
1224
1314
  var PROJECT_REGISTRY_SCHEMA_VERSION = 1;
1225
1315
  var DEFAULT_SOURCE_SKILL_ROOTS = [
1226
1316
  "/home/delorenj/code/skillex/all-skills",
1227
- join8(homedir3(), ".agents", "skills"),
1228
- join8(homedir3(), ".codex", "skills")
1317
+ join9(homedir3(), ".agents", "skills"),
1318
+ join9(homedir3(), ".codex", "skills")
1229
1319
  ];
1230
1320
  function projectRegistryPath(env2 = process.env) {
1231
- return expandHome(env2[PROJECT_REGISTRY_ENV] || join8(homedir3(), ".config", "pjangler", "projects.yaml"));
1321
+ return expandHome(env2[PROJECT_REGISTRY_ENV] || join9(homedir3(), ".config", "pjangler", "projects.yaml"));
1232
1322
  }
1233
1323
  function emptyProjectRegistry() {
1234
1324
  return { schema_version: PROJECT_REGISTRY_SCHEMA_VERSION, projects: {} };
1235
1325
  }
1236
1326
  function loadProjectRegistry(path = projectRegistryPath()) {
1237
- if (!existsSync6(path)) return emptyProjectRegistry();
1238
- const raw = YAML.parse(readFileSync2(path, "utf8"));
1327
+ if (!existsSync7(path)) return emptyProjectRegistry();
1328
+ const raw = YAML.parse(readFileSync3(path, "utf8"));
1239
1329
  if (raw == null) return emptyProjectRegistry();
1240
1330
  if (!isRecord(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
1241
1331
  const registry = raw;
@@ -1250,7 +1340,7 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1250
1340
  validateProjectRegistry(registry);
1251
1341
  mkdirSync4(dirname4(path), { recursive: true });
1252
1342
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
1253
- writeFileSync3(temp, YAML.stringify(registry, { lineWidth: 0 }), "utf8");
1343
+ writeFileSync4(temp, YAML.stringify(registry, { lineWidth: 0 }), "utf8");
1254
1344
  renameSync(temp, path);
1255
1345
  }
1256
1346
  function validateProjectRegistry(registry) {
@@ -1332,7 +1422,7 @@ function resolveAgentHooksLayer(input, env2 = process.env) {
1332
1422
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1333
1423
  if (override === "0" || override === "false") return false;
1334
1424
  if (override === "1" || override === "true") return true;
1335
- return !existsSync6(join8(homedir3(), ".agents", "hooks"));
1425
+ return !existsSync7(join9(homedir3(), ".agents", "hooks"));
1336
1426
  }
1337
1427
  function jsonStable(value) {
1338
1428
  return JSON.stringify(value);
@@ -1363,12 +1453,12 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1363
1453
  if (!sourceSkill) return void 0;
1364
1454
  const expanded = expandHome(sourceSkill);
1365
1455
  const direct = resolve(expanded);
1366
- if (existsSync6(direct)) return direct;
1456
+ if (existsSync7(direct)) return direct;
1367
1457
  const name = basename2(sourceSkill);
1368
1458
  const roots = sourceSkillRoots(env2);
1369
1459
  for (const root of roots) {
1370
- const candidate = join8(root, name);
1371
- if (existsSync6(candidate)) return candidate;
1460
+ const candidate = join9(root, name);
1461
+ if (existsSync7(candidate)) return candidate;
1372
1462
  }
1373
1463
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
1374
1464
  const hint = `${searched} Add project-specific roots with ${PROJECT_SOURCE_SKILL_ROOTS_ENV}.`;
@@ -1449,7 +1539,7 @@ function planProjectInit(input) {
1449
1539
  }));
1450
1540
  }
1451
1541
  actions.push(
1452
- { kind: "project.write-manifest", path: join8(targetDir, ".project.json"), manifest },
1542
+ { kind: "project.write-manifest", path: join9(targetDir, ".project.json"), manifest },
1453
1543
  {
1454
1544
  kind: "ticket-provider.create-or-link",
1455
1545
  enabled: live,
@@ -1490,7 +1580,7 @@ function executeProjectInitPlan(plan) {
1490
1580
  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"
1491
1581
  );
1492
1582
  mkdirSync4(dirname4(action.targetDir), { recursive: true });
1493
- const result = spawnSync4(action.command[0], action.command.slice(1), { encoding: "utf8", cwd: action.cwd });
1583
+ const result = spawnSync5(action.command[0], action.command.slice(1), { encoding: "utf8", cwd: action.cwd });
1494
1584
  if (result.stdout?.trim()) logs.push(result.stdout.trim());
1495
1585
  if (result.stderr?.trim()) logs.push(result.stderr.trim());
1496
1586
  if (result.error) {
@@ -1502,7 +1592,7 @@ function executeProjectInitPlan(plan) {
1502
1592
  }
1503
1593
  if (result.status !== 0) {
1504
1594
  errors.push(`copier exited with status ${result.status ?? "unknown"}`);
1505
- if (existsSync6(action.targetDir)) changedFiles.push(action.targetDir);
1595
+ if (existsSync7(action.targetDir)) changedFiles.push(action.targetDir);
1506
1596
  break;
1507
1597
  }
1508
1598
  changedFiles.push(action.targetDir);
@@ -1510,9 +1600,9 @@ function executeProjectInitPlan(plan) {
1510
1600
  mkdirSync4(dirname4(action.path), { recursive: true });
1511
1601
  const next = `${JSON.stringify(action.manifest, null, 2)}
1512
1602
  `;
1513
- const current = existsSync6(action.path) ? readFileSync2(action.path, "utf8") : void 0;
1603
+ const current = existsSync7(action.path) ? readFileSync3(action.path, "utf8") : void 0;
1514
1604
  if (current !== next) {
1515
- writeFileSync3(action.path, next, "utf8");
1605
+ writeFileSync4(action.path, next, "utf8");
1516
1606
  changedFiles.push(action.path);
1517
1607
  }
1518
1608
  } else if (action.kind === "registry.upsert") {
@@ -1605,16 +1695,16 @@ function doctorProjectRegistry(registryPath2 = projectRegistryPath(), slug) {
1605
1695
  const registry = loadProjectRegistry(registryPath2);
1606
1696
  const projects = slug ? [[slug, getProject(registry, slug)]] : Object.entries(registry.projects);
1607
1697
  for (const [projectSlug, project] of projects) {
1608
- if (!existsSync6(project.repo_path)) {
1698
+ if (!existsSync7(project.repo_path)) {
1609
1699
  issues.push({ level: "warn", slug: projectSlug, message: `repo_path does not exist: ${project.repo_path}` });
1610
1700
  } else if (!statSync(project.repo_path).isDirectory()) {
1611
1701
  issues.push({ level: "error", slug: projectSlug, message: `repo_path is not a directory: ${project.repo_path}` });
1612
1702
  } else {
1613
- const manifestPath = join8(project.repo_path, ".project.json");
1614
- if (!existsSync6(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
1703
+ const manifestPath = join9(project.repo_path, ".project.json");
1704
+ if (!existsSync7(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
1615
1705
  }
1616
1706
  for (const artifact of project.source_artifacts) {
1617
- if (artifact.path && !existsSync6(artifact.path)) {
1707
+ if (artifact.path && !existsSync7(artifact.path)) {
1618
1708
  issues.push({ level: "warn", slug: projectSlug, message: `source artifact missing: ${artifact.path}` });
1619
1709
  }
1620
1710
  }
@@ -1627,7 +1717,7 @@ function doctorProjectRegistry(registryPath2 = projectRegistryPath(), slug) {
1627
1717
  };
1628
1718
  }
1629
1719
  function buildCommonProjectCopierAction(input) {
1630
- const templateDir = join8(input.pjanglerRoot, "templates", "commonproject");
1720
+ const templateDir = join9(input.pjanglerRoot, "templates", "commonproject");
1631
1721
  const data = {
1632
1722
  project_name: input.projectName,
1633
1723
  project_description: input.projectDescription ?? "",
@@ -1656,7 +1746,7 @@ function buildCommonProjectCopierAction(input) {
1656
1746
  function resolvePjanglerRoot() {
1657
1747
  let dir = dirname4(new URL(import.meta.url).pathname);
1658
1748
  while (dir !== dirname4(dir)) {
1659
- if (existsSync6(join8(dir, "package.json")) && existsSync6(join8(dir, "templates", "commonproject", "copier.yml"))) return dir;
1749
+ if (existsSync7(join9(dir, "package.json")) && existsSync7(join9(dir, "templates", "commonproject", "copier.yml"))) return dir;
1660
1750
  dir = dirname4(dir);
1661
1751
  }
1662
1752
  return resolve(process.cwd());
@@ -1688,7 +1778,7 @@ function validateProjectRecord(project, key) {
1688
1778
  }
1689
1779
  function expandHome(path) {
1690
1780
  if (path === "~") return homedir3();
1691
- if (path.startsWith("~/")) return join8(homedir3(), path.slice(2));
1781
+ if (path.startsWith("~/")) return join9(homedir3(), path.slice(2));
1692
1782
  return path;
1693
1783
  }
1694
1784
  function isRecord(value) {
@@ -1705,16 +1795,16 @@ function resolveTemplateRoot() {
1705
1795
  try {
1706
1796
  let dir = dirname5(fileURLToPath2(import.meta.url));
1707
1797
  for (let i = 0; i < 8; i++) {
1708
- candidates.push(join9(dir, "templates", "commonproject", "template"));
1798
+ candidates.push(join10(dir, "templates", "commonproject", "template"));
1709
1799
  const parent = dirname5(dir);
1710
1800
  if (parent === dir) break;
1711
1801
  dir = parent;
1712
1802
  }
1713
1803
  } catch {
1714
1804
  }
1715
- candidates.push(join9(homedir4(), "code", "pjangler", "templates", "commonproject", "template"));
1805
+ candidates.push(join10(homedir4(), "code", "pjangler", "templates", "commonproject", "template"));
1716
1806
  for (const c of candidates) {
1717
- if (existsSync7(join9(c, ".agents", "hooks", "hooks.master.json"))) return c;
1807
+ if (existsSync8(join10(c, ".agents", "hooks", "hooks.master.json"))) return c;
1718
1808
  }
1719
1809
  throw new Error(
1720
1810
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -1741,10 +1831,10 @@ var CopyAgentHooksTree = class extends Command {
1741
1831
  const created = [];
1742
1832
  const skipped = [];
1743
1833
  for (const { rel, dir } of items) {
1744
- const src = join9(templateRoot, rel);
1745
- const dest = join9(this.context.targetDir, rel);
1746
- if (!existsSync7(src)) continue;
1747
- if (existsSync7(dest) && !this.context.force) {
1834
+ const src = join10(templateRoot, rel);
1835
+ const dest = join10(this.context.targetDir, rel);
1836
+ if (!existsSync8(src)) continue;
1837
+ if (existsSync8(dest) && !this.context.force) {
1748
1838
  skipped.push(rel);
1749
1839
  continue;
1750
1840
  }
@@ -1770,14 +1860,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
1770
1860
  if (!resolveAgentHooksLayer()) {
1771
1861
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
1772
1862
  }
1773
- const misePath = join9(this.context.targetDir, "mise.toml");
1774
- if (!existsSync7(misePath)) {
1863
+ const misePath = join10(this.context.targetDir, "mise.toml");
1864
+ if (!existsSync8(misePath)) {
1775
1865
  return {
1776
1866
  success: false,
1777
1867
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pjangler init mise` first, then re-run."
1778
1868
  };
1779
1869
  }
1780
- let content = readFileSync3(misePath, "utf8");
1870
+ let content = readFileSync4(misePath, "utf8");
1781
1871
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
1782
1872
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
1783
1873
  }
@@ -1853,7 +1943,7 @@ ${leaveBlock}`);
1853
1943
  ""
1854
1944
  ].join("\n");
1855
1945
  content = content.replace(/\n*$/, "\n") + appended;
1856
- if (!this.context.dryRun) writeFileSync4(misePath, content);
1946
+ if (!this.context.dryRun) writeFileSync5(misePath, content);
1857
1947
  if (wiredHooks) {
1858
1948
  return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
1859
1949
  }
@@ -2026,11 +2116,11 @@ function createRecipe(name, context) {
2026
2116
  import { cancel as cancel2, multiselect, text as text2, isCancel as isCancel5 } from "@clack/prompts";
2027
2117
 
2028
2118
  // src/parity/index.ts
2029
- import { existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync4, readlinkSync, readdirSync, renameSync as renameSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5, chmodSync as chmodSync2, copyFileSync } from "node:fs";
2030
- import { basename as basename3, dirname as dirname6, join as join10, relative, resolve as resolve2 } from "node:path";
2119
+ import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync6, readFileSync as readFileSync5, readlinkSync, readdirSync as readdirSync2, renameSync as renameSync2, symlinkSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6, chmodSync as chmodSync2, copyFileSync } from "node:fs";
2120
+ import { basename as basename3, dirname as dirname6, join as join11, relative, resolve as resolve2 } from "node:path";
2031
2121
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2032
2122
  import { homedir as homedir5 } from "node:os";
2033
- import { spawnSync as spawnSync5 } from "node:child_process";
2123
+ import { spawnSync as spawnSync6 } from "node:child_process";
2034
2124
  var LINK_AGENTFILES_BLOCK = `# This block will handle the linking of
2035
2125
  # agent files to the main AGENTS.md file.
2036
2126
  #
@@ -2101,7 +2191,7 @@ run = "{{config_root}}/.mise/scripts/versioning.sh sync"
2101
2191
  function resolvePjanglerRoot2() {
2102
2192
  let dir = dirname6(fileURLToPath3(import.meta.url));
2103
2193
  while (dir !== dirname6(dir)) {
2104
- if (existsSync8(join10(dir, "package.json")) && existsSync8(join10(dir, "templates", "commonproject", "copier.yml"))) {
2194
+ if (existsSync9(join11(dir, "package.json")) && existsSync9(join11(dir, "templates", "commonproject", "copier.yml"))) {
2105
2195
  return dir;
2106
2196
  }
2107
2197
  dir = dirname6(dir);
@@ -2112,17 +2202,17 @@ function normalizeNewlines(value) {
2112
2202
  return value.replace(/\r\n/g, "\n");
2113
2203
  }
2114
2204
  function readText(path) {
2115
- return normalizeNewlines(readFileSync4(path, "utf8"));
2205
+ return normalizeNewlines(readFileSync5(path, "utf8"));
2116
2206
  }
2117
2207
  function safeReadText(path) {
2118
- return existsSync8(path) ? readText(path) : null;
2208
+ return existsSync9(path) ? readText(path) : null;
2119
2209
  }
2120
2210
  function ensureParent(path) {
2121
2211
  mkdirSync6(dirname6(path), { recursive: true });
2122
2212
  }
2123
2213
  function writeText(path, content) {
2124
2214
  ensureParent(path);
2125
- writeFileSync5(path, content);
2215
+ writeFileSync6(path, content);
2126
2216
  }
2127
2217
  function tryParseJson(text3) {
2128
2218
  if (!text3) return null;
@@ -2139,7 +2229,7 @@ function titleCaseSlug(slug) {
2139
2229
  return slug.split(/[-_]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2140
2230
  }
2141
2231
  function readSymlinkTarget(path) {
2142
- if (!existsSync8(path)) return null;
2232
+ if (!existsSync9(path)) return null;
2143
2233
  try {
2144
2234
  return readlinkSync(path);
2145
2235
  } catch {
@@ -2147,7 +2237,7 @@ function readSymlinkTarget(path) {
2147
2237
  }
2148
2238
  }
2149
2239
  function ensureSymlink(path, target, dryRun) {
2150
- if (existsSync8(path)) {
2240
+ if (existsSync9(path)) {
2151
2241
  const stat = lstatSync(path);
2152
2242
  if (stat.isSymbolicLink()) {
2153
2243
  const current = readSymlinkTarget(path);
@@ -2164,11 +2254,11 @@ function ensureSymlink(path, target, dryRun) {
2164
2254
  return { changed: true };
2165
2255
  }
2166
2256
  function bootstrapAgentsFile(repoRoot, dryRun) {
2167
- const agentsPath = join10(repoRoot, "AGENTS.md");
2168
- if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
2257
+ const agentsPath = join11(repoRoot, "AGENTS.md");
2258
+ if (existsSync9(agentsPath)) return { changedFiles: [], details: [] };
2169
2259
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2170
- const source = join10(repoRoot, file);
2171
- if (!existsSync8(source)) continue;
2260
+ const source = join11(repoRoot, file);
2261
+ if (!existsSync9(source)) continue;
2172
2262
  const stat = lstatSync(source);
2173
2263
  if (stat.isSymbolicLink()) continue;
2174
2264
  if (stat.isFile()) {
@@ -2177,8 +2267,8 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
2177
2267
  }
2178
2268
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
2179
2269
  }
2180
- const readmePath = join10(repoRoot, "README.md");
2181
- if (existsSync8(readmePath)) {
2270
+ const readmePath = join11(repoRoot, "README.md");
2271
+ if (existsSync9(readmePath)) {
2182
2272
  const stat = lstatSync(readmePath);
2183
2273
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
2184
2274
  if (!dryRun) copyFileSync(readmePath, agentsPath);
@@ -2217,12 +2307,12 @@ function yamlGet(text3, keyPath) {
2217
2307
  return "";
2218
2308
  }
2219
2309
  function discoverRoles(repoRoot) {
2220
- const rolesDir = join10(repoRoot, "agents", "hermes");
2221
- if (!existsSync8(rolesDir)) return [];
2222
- return readdirSync(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2223
- const roleDir = join10(rolesDir, entry.name);
2224
- const roleYamlPath = join10(roleDir, "role.yaml");
2225
- if (!existsSync8(roleYamlPath)) return null;
2310
+ const rolesDir = join11(repoRoot, "agents", "hermes");
2311
+ if (!existsSync9(rolesDir)) return [];
2312
+ return readdirSync2(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2313
+ const roleDir = join11(rolesDir, entry.name);
2314
+ const roleYamlPath = join11(roleDir, "role.yaml");
2315
+ if (!existsSync9(roleYamlPath)) return null;
2226
2316
  const text3 = readText(roleYamlPath);
2227
2317
  const runtimeRepoRaw = yamlGet(text3, "runtime.github_repo");
2228
2318
  return {
@@ -2250,10 +2340,10 @@ function discoverRoles(repoRoot) {
2250
2340
  }).filter((value) => Boolean(value));
2251
2341
  }
2252
2342
  function registryPath(homeDir) {
2253
- return join10(homeDir, ".hermes", "agents-registry.yaml");
2343
+ return join11(homeDir, ".hermes", "agents-registry.yaml");
2254
2344
  }
2255
2345
  function systemctlUser(args) {
2256
- const result = spawnSync5("systemctl", ["--user", ...args], { encoding: "utf8" });
2346
+ const result = spawnSync6("systemctl", ["--user", ...args], { encoding: "utf8" });
2257
2347
  return {
2258
2348
  ok: result.status === 0,
2259
2349
  stdout: result.stdout.trim(),
@@ -2261,8 +2351,8 @@ function systemctlUser(args) {
2261
2351
  };
2262
2352
  }
2263
2353
  function templateScript(ctx, name) {
2264
- const source = join10(ctx.pjanglerRoot, ".mise", "scripts", name);
2265
- return existsSync8(source) ? readText(source) : void 0;
2354
+ const source = join11(ctx.pjanglerRoot, ".mise", "scripts", name);
2355
+ return existsSync9(source) ? readText(source) : void 0;
2266
2356
  }
2267
2357
  function templateVersioningScript(ctx) {
2268
2358
  return templateScript(ctx, "versioning.sh");
@@ -2274,8 +2364,8 @@ function resolveAgentHooksLayer2(ctx) {
2274
2364
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
2275
2365
  if (override === "0" || override === "false") return false;
2276
2366
  if (override === "1" || override === "true") return true;
2277
- if (existsSync8(join10(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
2278
- return !existsSync8(join10(ctx.homeDir, ".agents", "hooks"));
2367
+ if (existsSync9(join11(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
2368
+ return !existsSync9(join11(ctx.homeDir, ".agents", "hooks"));
2279
2369
  }
2280
2370
  function evaluateMiseConditionals(template, agentHooksLayer) {
2281
2371
  const out = [];
@@ -2305,10 +2395,10 @@ function renderGeneratedProjectMiseToml(ctx, template) {
2305
2395
  return evaluateMiseConditionals(template, resolveAgentHooksLayer2(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
2306
2396
  }
2307
2397
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
2308
- const targetPath = join10(ctx.repoRoot, "mise.toml");
2309
- if (existsSync8(targetPath)) return false;
2310
- const sourcePath = join10(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
2311
- if (!existsSync8(sourcePath)) return false;
2398
+ const targetPath = join11(ctx.repoRoot, "mise.toml");
2399
+ if (existsSync9(targetPath)) return false;
2400
+ const sourcePath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
2401
+ if (!existsSync9(sourcePath)) return false;
2312
2402
  changedFiles.push(targetPath);
2313
2403
  if (!ctx.dryRun) {
2314
2404
  writeText(targetPath, renderGeneratedProjectMiseToml(ctx, readText(sourcePath)));
@@ -2316,8 +2406,8 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
2316
2406
  return true;
2317
2407
  }
2318
2408
  function templateVersionFilesConf(ctx, repoRoot) {
2319
- const packageJson = join10(repoRoot, "package.json");
2320
- 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";
2409
+ const packageJson = join11(repoRoot, "package.json");
2410
+ 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";
2321
2411
  }
2322
2412
  function replaceOrAppendManagedBlock(text3, startMarker, block, beforePattern) {
2323
2413
  if (startMarker.test(text3)) {
@@ -2341,7 +2431,7 @@ var CONDITIONAL_HERMES_PATHS = ["agents/hermes/pm/hermes", "agent/hermes/pm/herm
2341
2431
  function requiredMisePathEntries(ctx) {
2342
2432
  const required = [...BASE_MISE_PATH_ENTRIES];
2343
2433
  for (const candidate of CONDITIONAL_HERMES_PATHS) {
2344
- if (existsSync8(join10(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
2434
+ if (existsSync9(join11(ctx.repoRoot, candidate)) && !required.includes(candidate)) required.push(candidate);
2345
2435
  }
2346
2436
  return required;
2347
2437
  }
@@ -2490,7 +2580,7 @@ function upsertLinkAgentfilesBlock(text3, ctx) {
2490
2580
  return insertTomlBlockBeforeVersioning(cleaned, LINK_AGENTFILES_WATCH_TASK_BLOCK);
2491
2581
  }
2492
2582
  function readProjectJson(ctx) {
2493
- return tryParseJson(safeReadText(join10(ctx.repoRoot, ".project.json")));
2583
+ return tryParseJson(safeReadText(join11(ctx.repoRoot, ".project.json")));
2494
2584
  }
2495
2585
  function boolSetting(value, fallback) {
2496
2586
  if (typeof value === "boolean") return value;
@@ -2565,12 +2655,12 @@ function canonicalProjectJson(ctx) {
2565
2655
  };
2566
2656
  }
2567
2657
  function projectJsonFinding(ctx) {
2568
- const projectPath = join10(ctx.repoRoot, ".project.json");
2569
- const planeJsonPath = join10(ctx.repoRoot, ".plane.json");
2658
+ const projectPath = join11(ctx.repoRoot, ".project.json");
2659
+ const planeJsonPath = join11(ctx.repoRoot, ".plane.json");
2570
2660
  const details = [];
2571
2661
  const data = readProjectJson(ctx);
2572
2662
  const roles = discoverRoles(ctx.repoRoot);
2573
- if (!existsSync8(projectPath)) {
2663
+ if (!existsSync9(projectPath)) {
2574
2664
  return { id: "sot.project-json", title: "Canonical .project.json", status: "fail", summary: ".project.json missing", details: [], fixable: true };
2575
2665
  }
2576
2666
  if (!data) {
@@ -2605,7 +2695,7 @@ function projectJsonFinding(ctx) {
2605
2695
  for (const key of ["enabled", "grace_hours", "auto_review"]) {
2606
2696
  if (!(key in reconcile)) details.push(`automation.reconcile.${key} missing`);
2607
2697
  }
2608
- if (existsSync8(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
2698
+ if (existsSync9(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
2609
2699
  return {
2610
2700
  id: "sot.project-json",
2611
2701
  title: "Canonical .project.json",
@@ -2686,17 +2776,17 @@ exec env HERMES_HOME="$HERMES_HOME" HERMES_FLEET_ENV="$FLEET_ENV" HERMES_OAUTH
2686
2776
  `.replace(/\u0010/g, "$");
2687
2777
  }
2688
2778
  function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip) {
2689
- if (!existsSync8(sourceDir)) return;
2779
+ if (!existsSync9(sourceDir)) return;
2690
2780
  mkdirSync6(targetDir, { recursive: true });
2691
- for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
2692
- const sourcePath = join10(sourceDir, entry.name);
2781
+ for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
2782
+ const sourcePath = join11(sourceDir, entry.name);
2693
2783
  if (skip?.(sourcePath)) continue;
2694
- const targetPath = join10(targetDir, entry.name);
2784
+ const targetPath = join11(targetDir, entry.name);
2695
2785
  if (entry.isDirectory()) {
2696
2786
  copyMissingRecursive(sourcePath, targetPath, changedFiles, dryRun, skip);
2697
2787
  continue;
2698
2788
  }
2699
- if (existsSync8(targetPath)) continue;
2789
+ if (existsSync9(targetPath)) continue;
2700
2790
  changedFiles.push(targetPath);
2701
2791
  if (!dryRun) {
2702
2792
  ensureParent(targetPath);
@@ -2705,7 +2795,7 @@ function copyMissingRecursive(sourceDir, targetDir, changedFiles, dryRun, skip)
2705
2795
  }
2706
2796
  }
2707
2797
  function upsertSubmodule(repoRoot, role, changedFiles, dryRun) {
2708
- const gitmodulesPath = join10(repoRoot, ".gitmodules");
2798
+ const gitmodulesPath = join11(repoRoot, ".gitmodules");
2709
2799
  const repoName = role.runtimeRepo || `agent-hm-${role.repo}-${role.role}`;
2710
2800
  const owner = role.runtimeOwner || "delorenj";
2711
2801
  const block = `[submodule "agents/hermes/${role.role}/runtime"]
@@ -2805,14 +2895,14 @@ var RULES = [
2805
2895
  id: "mise.config-root",
2806
2896
  title: "mise config_root + AGENTS link hooks",
2807
2897
  audit: (ctx) => {
2808
- const misePath = join10(ctx.repoRoot, "mise.toml");
2809
- if (!existsSync8(misePath)) {
2898
+ const misePath = join11(ctx.repoRoot, "mise.toml");
2899
+ if (!existsSync9(misePath)) {
2810
2900
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
2811
2901
  }
2812
2902
  const text3 = readText(misePath);
2813
2903
  const details = [];
2814
- const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2815
- if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
2904
+ const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2905
+ if (!existsSync9(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
2816
2906
  const pathValues = [...(text3.match(/^_\.path\s*=\s*\[([^\]]*)\]/m)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
2817
2907
  const missingPathValues = requiredMisePathEntries(ctx).filter((value) => !pathValues.includes(value));
2818
2908
  if (missingPathValues.length) details.push(`[env]._.path should include ${missingPathValues.join(", ")}`);
@@ -2830,10 +2920,10 @@ var RULES = [
2830
2920
  };
2831
2921
  },
2832
2922
  migrate: (ctx, finding) => {
2833
- const path = join10(ctx.repoRoot, "mise.toml");
2923
+ const path = join11(ctx.repoRoot, "mise.toml");
2834
2924
  const changedFiles = [];
2835
2925
  const details = [];
2836
- if (!existsSync8(path)) {
2926
+ if (!existsSync9(path)) {
2837
2927
  if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
2838
2928
  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: [] };
2839
2929
  }
@@ -2849,7 +2939,7 @@ var RULES = [
2849
2939
  if (!ctx.dryRun) writeText(path, next);
2850
2940
  text3 = next;
2851
2941
  }
2852
- const linkAgentfilesPath = join10(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2942
+ const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
2853
2943
  const expectedScript = templateLinkAgentfilesScript(ctx);
2854
2944
  if (expectedScript === void 0) {
2855
2945
  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: [] };
@@ -2876,13 +2966,13 @@ var RULES = [
2876
2966
  title: "managed mise versioning block",
2877
2967
  audit: (ctx) => {
2878
2968
  const details = [];
2879
- const misePath = join10(ctx.repoRoot, "mise.toml");
2880
- const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2881
- const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
2969
+ const misePath = join11(ctx.repoRoot, "mise.toml");
2970
+ const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2971
+ const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
2882
2972
  const text3 = safeReadText(misePath);
2883
2973
  if (!text3?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
2884
- if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
2885
- if (!existsSync8(manifestPath)) details.push(".mise/version-files.conf missing");
2974
+ if (!existsSync9(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
2975
+ if (!existsSync9(manifestPath)) details.push(".mise/version-files.conf missing");
2886
2976
  return {
2887
2977
  id: "mise.versioning",
2888
2978
  title: "managed mise versioning block",
@@ -2895,8 +2985,8 @@ var RULES = [
2895
2985
  migrate: (ctx, finding) => {
2896
2986
  const changedFiles = [];
2897
2987
  const details = [];
2898
- const misePath = join10(ctx.repoRoot, "mise.toml");
2899
- if (!existsSync8(misePath)) {
2988
+ const misePath = join11(ctx.repoRoot, "mise.toml");
2989
+ if (!existsSync9(misePath)) {
2900
2990
  if (!ensureMiseTomlFromTemplate(ctx, changedFiles)) {
2901
2991
  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: [] };
2902
2992
  }
@@ -2906,12 +2996,21 @@ var RULES = [
2906
2996
  }
2907
2997
  }
2908
2998
  const currentMise = readText(misePath);
2909
- const nextMise = replaceOrAppendManagedBlock(currentMise, /# >>> mise-versioning >>>/, VERSIONING_BLOCK, /^\[tasks\.build\]/m);
2999
+ let cleanedMise = currentMise;
3000
+ if (!currentMise.includes("# >>> mise-versioning >>>")) {
3001
+ const taskNames = ["version", "version:bump", "version:bump-patch", "version:bump-minor", "version:bump-major", "version:check", "version:sync"];
3002
+ for (const taskName of taskNames) {
3003
+ const escaped = taskName.replace(/:/g, "\\:");
3004
+ const headerPattern = new RegExp(`^\\[tasks\\.(?:"${escaped}"|'${escaped}'|${escaped})\\]$`);
3005
+ cleanedMise = removeTomlSection(cleanedMise, headerPattern);
3006
+ }
3007
+ }
3008
+ const nextMise = replaceOrAppendManagedBlock(cleanedMise, /# >>> mise-versioning >>>/, VERSIONING_BLOCK, /^\[tasks\.build\]/m);
2910
3009
  if (nextMise !== currentMise) {
2911
3010
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
2912
3011
  if (!ctx.dryRun) writeText(misePath, nextMise);
2913
3012
  }
2914
- const versioningPath = join10(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
3013
+ const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
2915
3014
  const expectedScript = templateVersioningScript(ctx);
2916
3015
  if (expectedScript === void 0) {
2917
3016
  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: [] };
@@ -2923,7 +3022,7 @@ var RULES = [
2923
3022
  chmodSync2(versioningPath, 493);
2924
3023
  }
2925
3024
  }
2926
- const manifestPath = join10(ctx.repoRoot, ".mise", "version-files.conf");
3025
+ const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
2927
3026
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
2928
3027
  if (safeReadText(manifestPath) !== expectedManifest) {
2929
3028
  changedFiles.push(manifestPath);
@@ -2943,9 +3042,9 @@ var RULES = [
2943
3042
  id: "sot.agent-symlinks",
2944
3043
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
2945
3044
  audit: (ctx) => {
2946
- const agentsPath = join10(ctx.repoRoot, "AGENTS.md");
2947
- if (!existsSync8(agentsPath)) {
2948
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join10(ctx.repoRoot, file)));
3045
+ const agentsPath = join11(ctx.repoRoot, "AGENTS.md");
3046
+ if (!existsSync9(agentsPath)) {
3047
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync9(join11(ctx.repoRoot, file)));
2949
3048
  if (fallbackSources.length === 0) {
2950
3049
  return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
2951
3050
  }
@@ -2960,7 +3059,7 @@ var RULES = [
2960
3059
  }
2961
3060
  const details = [];
2962
3061
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2963
- const full = join10(ctx.repoRoot, file);
3062
+ const full = join11(ctx.repoRoot, file);
2964
3063
  const target = readSymlinkTarget(full);
2965
3064
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
2966
3065
  }
@@ -2984,7 +3083,7 @@ var RULES = [
2984
3083
  return { id: finding.id, title: finding.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
2985
3084
  }
2986
3085
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
2987
- const full = join10(ctx.repoRoot, file);
3086
+ const full = join11(ctx.repoRoot, file);
2988
3087
  const result = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
2989
3088
  if (result.blocked) blockedDetails.push(result.blocked);
2990
3089
  if (result.changed) changedFiles.push(full);
@@ -3006,7 +3105,7 @@ var RULES = [
3006
3105
  migrate: (ctx, finding) => {
3007
3106
  const changedFiles = [];
3008
3107
  const details = [];
3009
- const path = join10(ctx.repoRoot, ".project.json");
3108
+ const path = join11(ctx.repoRoot, ".project.json");
3010
3109
  const existing = readProjectJson(ctx) ?? {};
3011
3110
  const canonical = canonicalProjectJson(ctx);
3012
3111
  const merged = { ...existing, ...canonical };
@@ -3016,10 +3115,10 @@ var RULES = [
3016
3115
  changedFiles.push(path);
3017
3116
  if (!ctx.dryRun) writeText(path, expected);
3018
3117
  }
3019
- const planeJson = join10(ctx.repoRoot, ".plane.json");
3020
- if (existsSync8(planeJson)) {
3118
+ const planeJson = join11(ctx.repoRoot, ".plane.json");
3119
+ if (existsSync9(planeJson)) {
3021
3120
  const backup = `${planeJson}.migrated-backup`;
3022
- if (existsSync8(backup)) {
3121
+ if (existsSync9(backup)) {
3023
3122
  details.push(`cannot back up .plane.json because ${relative(ctx.repoRoot, backup)} already exists`);
3024
3123
  } else {
3025
3124
  changedFiles.push(backup);
@@ -3041,8 +3140,8 @@ var RULES = [
3041
3140
  title: ".env.op + gitignore secrets contract",
3042
3141
  audit: (ctx) => {
3043
3142
  const details = [];
3044
- const envOp = safeReadText(join10(ctx.repoRoot, ".env.op"));
3045
- const gitignore = safeReadText(join10(ctx.repoRoot, ".gitignore"));
3143
+ const envOp = safeReadText(join11(ctx.repoRoot, ".env.op"));
3144
+ const gitignore = safeReadText(join11(ctx.repoRoot, ".gitignore"));
3046
3145
  if (!envOp) {
3047
3146
  details.push(".env.op missing");
3048
3147
  } else {
@@ -3068,12 +3167,12 @@ var RULES = [
3068
3167
  migrate: (ctx, finding) => {
3069
3168
  const changedFiles = [];
3070
3169
  const details = [];
3071
- const envOpPath = join10(ctx.repoRoot, ".env.op");
3072
- if (!existsSync8(envOpPath)) {
3170
+ const envOpPath = join11(ctx.repoRoot, ".env.op");
3171
+ if (!existsSync9(envOpPath)) {
3073
3172
  changedFiles.push(envOpPath);
3074
- if (!ctx.dryRun) writeText(envOpPath, readText(join10(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
3173
+ if (!ctx.dryRun) writeText(envOpPath, readText(join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op")));
3075
3174
  }
3076
- const gitignorePath = join10(ctx.repoRoot, ".gitignore");
3175
+ const gitignorePath = join11(ctx.repoRoot, ".gitignore");
3077
3176
  const gitignore = safeReadText(gitignorePath) ?? "";
3078
3177
  const requiredBlock = `# Secrets \u2014 .env is materialized by \`op inject -i .env.op > .env\` on mise enter.
3079
3178
  # NEVER commit it. .env.op holds only 1Password references or safe literals and IS committed.
@@ -3100,7 +3199,7 @@ var RULES = [
3100
3199
  title: ".copier-answers.yml provenance + drift report",
3101
3200
  audit: (ctx) => {
3102
3201
  const details = [];
3103
- const path = join10(ctx.repoRoot, ".copier-answers.yml");
3202
+ const path = join11(ctx.repoRoot, ".copier-answers.yml");
3104
3203
  const text3 = safeReadText(path);
3105
3204
  const project = readProjectJson(ctx);
3106
3205
  if (!text3) {
@@ -3131,12 +3230,12 @@ var RULES = [
3131
3230
  const changedFiles = [];
3132
3231
  const project = canonicalProjectJson(ctx);
3133
3232
  const text3 = `# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
3134
- _src_path: ${join10(ctx.pjanglerRoot, "templates", "commonproject")}
3233
+ _src_path: ${join11(ctx.pjanglerRoot, "templates", "commonproject")}
3135
3234
  project_description: ${String(project.project_description)}
3136
3235
  project_name: ${String(project.project_name)}
3137
3236
  ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3138
3237
  `;
3139
- const path = join10(ctx.repoRoot, ".copier-answers.yml");
3238
+ const path = join11(ctx.repoRoot, ".copier-answers.yml");
3140
3239
  if (safeReadText(path) !== text3) {
3141
3240
  changedFiles.push(path);
3142
3241
  if (!ctx.dryRun) writeText(path, text3);
@@ -3155,15 +3254,15 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3155
3254
  id: "bmad.scaffold",
3156
3255
  title: "BMAD modules/docs scaffold",
3157
3256
  audit: (ctx) => {
3158
- const sourceRoot = join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
3159
- const targetRoot = join10(ctx.repoRoot, "_bmad");
3257
+ const sourceRoot = join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad");
3258
+ const targetRoot = join11(ctx.repoRoot, "_bmad");
3160
3259
  const sentinels = [
3161
- join10("core", "config.yaml"),
3162
- join10("custom", "config.yaml"),
3163
- join10("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3164
- join10("bmm", "workflows", "workflow-status", "workflow.yaml")
3260
+ join11("core", "config.yaml"),
3261
+ join11("custom", "config.yaml"),
3262
+ join11("custom", "workflows", "ticket-lifecycle", "workflow.yaml"),
3263
+ join11("bmm", "workflows", "workflow-status", "workflow.yaml")
3165
3264
  ];
3166
- const missing = sentinels.filter((file) => existsSync8(join10(sourceRoot, file)) && !existsSync8(join10(targetRoot, file)));
3265
+ const missing = sentinels.filter((file) => existsSync9(join11(sourceRoot, file)) && !existsSync9(join11(targetRoot, file)));
3167
3266
  return {
3168
3267
  id: "bmad.scaffold",
3169
3268
  title: "BMAD modules/docs scaffold",
@@ -3175,7 +3274,7 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3175
3274
  },
3176
3275
  migrate: (ctx, finding) => {
3177
3276
  const changedFiles = [];
3178
- copyMissingRecursive(join10(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join10(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
3277
+ copyMissingRecursive(join11(ctx.pjanglerRoot, "templates", "commonproject", "_bmad"), join11(ctx.repoRoot, "_bmad"), changedFiles, ctx.dryRun);
3179
3278
  return {
3180
3279
  id: finding.id,
3181
3280
  title: finding.title,
@@ -3197,11 +3296,11 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3197
3296
  }
3198
3297
  const details = [];
3199
3298
  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"]) {
3200
- if (!existsSync8(join10(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join10(role.roleDir, rel))}`);
3299
+ if (!existsSync9(join11(role.roleDir, rel))) details.push(`missing ${relative(ctx.repoRoot, join11(role.roleDir, rel))}`);
3201
3300
  }
3202
- const gitmodules = safeReadText(join10(ctx.repoRoot, ".gitmodules")) ?? "";
3301
+ const gitmodules = safeReadText(join11(ctx.repoRoot, ".gitmodules")) ?? "";
3203
3302
  if (!gitmodules.includes(`agents/hermes/${role.role}/runtime`)) details.push(".gitmodules missing pm runtime submodule entry");
3204
- if (!profileMetaInheritsDefault(join10(role.roleDir, "runtime", "profile.yaml"))) {
3303
+ if (!profileMetaInheritsDefault(join11(role.roleDir, "runtime", "profile.yaml"))) {
3205
3304
  details.push("runtime/profile.yaml missing inherited default config metadata");
3206
3305
  }
3207
3306
  const registry = safeReadText(registryPath(ctx.homeDir));
@@ -3222,21 +3321,21 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3222
3321
  if (!role) {
3223
3322
  return { id: finding.id, title: finding.title, status: "blocked", summary: "No pm role present", changedFiles, details: [] };
3224
3323
  }
3225
- const templateRoleDir = join10(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
3226
- writeIfDifferent(join10(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
3227
- writeIfDifferent(join10(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
3228
- writeIfDifferent(join10(role.roleDir, ".gitignore"), readText(join10(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
3229
- copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
3230
- copyMissingRecursive(join10(templateRoleDir, ".runtime-scaffold"), join10(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
3231
- copyMissingRecursive(join10(templateRoleDir, ".scripts"), join10(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
3232
- const promptSource = join10(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
3233
- const promptTarget = join10(role.roleDir, ".scripts", "sentinel.prompt.md");
3234
- if (existsSync8(promptSource) && !existsSync8(promptTarget)) {
3324
+ const templateRoleDir = join11(ctx.pjanglerRoot, "templates", "hermes-agent", "template");
3325
+ writeIfDifferent(join11(role.roleDir, "SOUL.md"), renderSoul(role), ctx.dryRun, changedFiles);
3326
+ writeIfDifferent(join11(role.roleDir, "hermes"), renderHermesWrapper(role), ctx.dryRun, changedFiles, 493);
3327
+ writeIfDifferent(join11(role.roleDir, ".gitignore"), readText(join11(templateRoleDir, ".gitignore.jinja")).replace(/\{\{ role \}\}/g, role.role), ctx.dryRun, changedFiles);
3328
+ copyMissingRecursive(join11(templateRoleDir, ".runtime-scaffold"), join11(role.roleDir, ".runtime-scaffold"), changedFiles, ctx.dryRun);
3329
+ copyMissingRecursive(join11(templateRoleDir, ".runtime-scaffold"), join11(role.roleDir, "runtime"), changedFiles, ctx.dryRun);
3330
+ copyMissingRecursive(join11(templateRoleDir, ".scripts"), join11(role.roleDir, ".scripts"), changedFiles, ctx.dryRun, (source) => source.endsWith("sentinel.prompt.md.jinja"));
3331
+ const promptSource = join11(templateRoleDir, ".scripts", "sentinel.prompt.md.jinja");
3332
+ const promptTarget = join11(role.roleDir, ".scripts", "sentinel.prompt.md");
3333
+ if (existsSync9(promptSource) && !existsSync9(promptTarget)) {
3235
3334
  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);
3236
3335
  writeIfDifferent(promptTarget, prompt, ctx.dryRun, changedFiles);
3237
3336
  }
3238
3337
  upsertSubmodule(ctx.repoRoot, role, changedFiles, ctx.dryRun);
3239
- const profileMetaUpdated = upsertInheritedProfileMeta(join10(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
3338
+ const profileMetaUpdated = upsertInheritedProfileMeta(join11(role.roleDir, "runtime", "profile.yaml"), changedFiles, ctx.dryRun);
3240
3339
  if (profileMetaUpdated) details.push(`updated ${profileMetaUpdated}`);
3241
3340
  const registryUpdated = upsertRegistryEntry(role, ctx.homeDir, changedFiles, ctx.dryRun);
3242
3341
  if (registryUpdated) details.push(`updated ${registryUpdated}`);
@@ -3250,6 +3349,103 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3250
3349
  };
3251
3350
  }
3252
3351
  },
3352
+ {
3353
+ id: "hermes.untracked-runtimes",
3354
+ title: "Hermes agent runtimes untracked + gitignored",
3355
+ audit: (ctx) => {
3356
+ const roles = discoverRoles(ctx.repoRoot);
3357
+ if (roles.length === 0) {
3358
+ return {
3359
+ id: "hermes.untracked-runtimes",
3360
+ title: "Hermes agent runtimes untracked + gitignored",
3361
+ status: "skip",
3362
+ summary: "No Hermes roles present",
3363
+ details: [],
3364
+ fixable: false
3365
+ };
3366
+ }
3367
+ const details = [];
3368
+ for (const role of roles) {
3369
+ const roleRelDir = relative(ctx.repoRoot, role.roleDir);
3370
+ const runtimeRelPath = join11(roleRelDir, "runtime");
3371
+ const lsResult = spawnSync6("git", ["ls-files", "--stage", runtimeRelPath], {
3372
+ cwd: ctx.repoRoot,
3373
+ encoding: "utf8"
3374
+ });
3375
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
3376
+ details.push(`submodule runtime is tracked in Git index at ${runtimeRelPath}`);
3377
+ }
3378
+ const gitignorePath = join11(role.roleDir, ".gitignore");
3379
+ if (existsSync9(gitignorePath)) {
3380
+ const content = safeReadText(gitignorePath) ?? "";
3381
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
3382
+ if (!lines.includes("runtime/") && !lines.includes("runtime")) {
3383
+ details.push(`.gitignore missing runtime/ ignore entry in ${relative(ctx.repoRoot, gitignorePath)}`);
3384
+ }
3385
+ } else {
3386
+ details.push(`.gitignore is missing in ${relative(ctx.repoRoot, gitignorePath)}`);
3387
+ }
3388
+ }
3389
+ return {
3390
+ id: "hermes.untracked-runtimes",
3391
+ title: "Hermes agent runtimes untracked + gitignored",
3392
+ status: details.length === 0 ? "pass" : "fail",
3393
+ summary: details.length === 0 ? "All Hermes agent runtimes are untracked and gitignored" : `${details.length} issue(s) with untracked/ignored runtimes detected`,
3394
+ details,
3395
+ fixable: true
3396
+ };
3397
+ },
3398
+ migrate: (ctx, finding) => {
3399
+ const roles = discoverRoles(ctx.repoRoot);
3400
+ const changedFiles = [];
3401
+ const details = [];
3402
+ for (const role of roles) {
3403
+ const roleRelDir = relative(ctx.repoRoot, role.roleDir);
3404
+ const runtimeRelPath = join11(roleRelDir, "runtime");
3405
+ const lsResult = spawnSync6("git", ["ls-files", "--stage", runtimeRelPath], {
3406
+ cwd: ctx.repoRoot,
3407
+ encoding: "utf8"
3408
+ });
3409
+ if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
3410
+ details.push(`untrack ${runtimeRelPath}`);
3411
+ changedFiles.push(runtimeRelPath);
3412
+ if (!ctx.dryRun) {
3413
+ spawnSync6("git", ["rm", "--cached", "-r", runtimeRelPath], {
3414
+ cwd: ctx.repoRoot,
3415
+ encoding: "utf8"
3416
+ });
3417
+ }
3418
+ }
3419
+ const gitignorePath = join11(role.roleDir, ".gitignore");
3420
+ let content = "";
3421
+ let isIgnored = false;
3422
+ if (existsSync9(gitignorePath)) {
3423
+ content = safeReadText(gitignorePath) ?? "";
3424
+ const lines = content.split(/\r?\n/).map((line) => line.trim());
3425
+ isIgnored = lines.includes("runtime/") || lines.includes("runtime");
3426
+ }
3427
+ if (!isIgnored) {
3428
+ details.push(`ignore runtime/ in ${relative(ctx.repoRoot, gitignorePath)}`);
3429
+ changedFiles.push(gitignorePath);
3430
+ if (!ctx.dryRun) {
3431
+ if (content && !content.endsWith("\n")) {
3432
+ content += "\n";
3433
+ }
3434
+ content += "runtime/\n";
3435
+ writeText(gitignorePath, content);
3436
+ }
3437
+ }
3438
+ }
3439
+ return {
3440
+ id: finding.id,
3441
+ title: finding.title,
3442
+ status: changedFiles.length ? "applied" : "noop",
3443
+ summary: changedFiles.length ? "Hermes agent runtimes made untracked and ignored" : "No changes required",
3444
+ changedFiles,
3445
+ details
3446
+ };
3447
+ }
3448
+ },
3253
3449
  {
3254
3450
  id: "systemd.sentinel",
3255
3451
  title: "Hermes systemd/sentinel units enabled + active",
@@ -3290,9 +3486,9 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3290
3486
  return { id: finding.id, title: finding.title, status: "blocked", summary: "systemd --user unavailable on this host", changedFiles, details };
3291
3487
  }
3292
3488
  for (const role of roles) {
3293
- const sysDir = join10(ctx.homeDir, ".config", "systemd", "user");
3489
+ const sysDir = join11(ctx.homeDir, ".config", "systemd", "user");
3294
3490
  const units = [`hermes-${role.agentId}-gateway.service`, `hermes-${role.agentId}-consumer.service`, `hermes-${role.agentId}-heartbeat.timer`];
3295
- const allUnitsPresent = units.every((unit) => existsSync8(join10(sysDir, unit)));
3491
+ const allUnitsPresent = units.every((unit) => existsSync9(join11(sysDir, unit)));
3296
3492
  if (allUnitsPresent) {
3297
3493
  if (ctx.dryRun) {
3298
3494
  details.push(`would run: systemctl --user enable --now ${units.join(" ")}`);
@@ -3304,12 +3500,12 @@ ticket_provider: ${String(project.ticket_provider?.type ?? "plane")}
3304
3500
  }
3305
3501
  continue;
3306
3502
  }
3307
- for (const script of [join10(role.roleDir, ".scripts", "70-systemd.sh")]) {
3308
- if (!script || !existsSync8(script)) continue;
3503
+ for (const script of [join11(role.roleDir, ".scripts", "70-systemd.sh")]) {
3504
+ if (!script || !existsSync9(script)) continue;
3309
3505
  if (ctx.dryRun) {
3310
3506
  details.push(`would run: bash ${script}`);
3311
3507
  } else {
3312
- const result = spawnSync5("bash", [script], { cwd: role.roleDir, encoding: "utf8" });
3508
+ const result = spawnSync6("bash", [script], { cwd: role.roleDir, encoding: "utf8" });
3313
3509
  if (result.status !== 0) details.push(`script failed: ${script}: ${result.stderr.trim() || result.stdout.trim()}`);
3314
3510
  }
3315
3511
  }
@@ -3444,15 +3640,15 @@ function formatMigrationReport(report) {
3444
3640
  }
3445
3641
 
3446
3642
  // src/utils/version.ts
3447
- import { readFileSync as readFileSync5 } from "node:fs";
3448
- import { dirname as dirname7, join as join11 } from "node:path";
3643
+ import { readFileSync as readFileSync6 } from "node:fs";
3644
+ import { dirname as dirname7, join as join12 } from "node:path";
3449
3645
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3450
3646
  var PJANGLER_VERSION = (() => {
3451
3647
  try {
3452
3648
  let dir = dirname7(fileURLToPath4(import.meta.url));
3453
3649
  for (let i = 0; i < 4; i++) {
3454
3650
  try {
3455
- const raw = readFileSync5(join11(dir, "package.json"), "utf8");
3651
+ const raw = readFileSync6(join12(dir, "package.json"), "utf8");
3456
3652
  return JSON.parse(raw).version ?? "0.0.0";
3457
3653
  } catch {
3458
3654
  const parent = dirname7(dir);
@@ -3495,16 +3691,16 @@ async function promptForRuleIds(rules) {
3495
3691
  return selected;
3496
3692
  }
3497
3693
  function readJson(path) {
3498
- if (!existsSync9(path)) return void 0;
3694
+ if (!existsSync10(path)) return void 0;
3499
3695
  try {
3500
- const parsed = JSON.parse(readFileSync6(path, "utf8"));
3696
+ const parsed = JSON.parse(readFileSync7(path, "utf8"));
3501
3697
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
3502
3698
  } catch {
3503
3699
  return void 0;
3504
3700
  }
3505
3701
  }
3506
3702
  function findGitRoot(cwd) {
3507
- const result = spawnSync6("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
3703
+ const result = spawnSync7("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
3508
3704
  if (result.status !== 0) return void 0;
3509
3705
  return resolve3(result.stdout.trim());
3510
3706
  }
@@ -3514,8 +3710,8 @@ function packageNameToProjectName(value) {
3514
3710
  return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
3515
3711
  }
3516
3712
  function deriveProjectDefaults(targetDir) {
3517
- const manifest = readJson(join12(targetDir, ".project.json"));
3518
- const pkg = readJson(join12(targetDir, "package.json"));
3713
+ const manifest = readJson(join13(targetDir, ".project.json"));
3714
+ const pkg = readJson(join13(targetDir, "package.json"));
3519
3715
  const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename4(targetDir)) || "Project";
3520
3716
  const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
3521
3717
  return {
@@ -3571,7 +3767,7 @@ function actionNeedsRun(plan, kind, syncMode) {
3571
3767
  if (!action || action.kind !== "project.write-manifest") return false;
3572
3768
  const next = `${JSON.stringify(action.manifest, null, 2)}
3573
3769
  `;
3574
- return !existsSync9(action.path) || readFileSync6(action.path, "utf8") !== next;
3770
+ return !existsSync10(action.path) || readFileSync7(action.path, "utf8") !== next;
3575
3771
  }
3576
3772
  if (kind === "copier.copy.commonproject") return true;
3577
3773
  if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
@@ -3626,7 +3822,7 @@ async function resolveProjectInitTarget(name, options) {
3626
3822
  if (!targetDir && interactive) {
3627
3823
  const defaultName = name ?? basename4(cwd);
3628
3824
  const promptedName = name ?? await promptTextValue("Project name", packageNameToProjectName(defaultName));
3629
- const defaultDir = join12(cwd, promptedName.replace(/[^A-Za-z0-9._-]/g, "") || promptedName.toLowerCase().replace(/[^a-z0-9]+/g, "-"));
3825
+ const defaultDir = join13(cwd, promptedName.replace(/[^A-Za-z0-9._-]/g, "") || promptedName.toLowerCase().replace(/[^a-z0-9]+/g, "-"));
3630
3826
  targetDir = await promptTextValue("Project directory", defaultDir);
3631
3827
  name = promptedName;
3632
3828
  }
@@ -3634,7 +3830,7 @@ async function resolveProjectInitTarget(name, options) {
3634
3830
  if (!name) throw new Error("Project name or --target-dir is required when project init is not run inside a git repo");
3635
3831
  targetDir = resolve3(process.cwd(), name.replace(/[^A-Za-z0-9._-]/g, "") || name.toLowerCase().replace(/[^a-z0-9]+/g, "-"));
3636
3832
  }
3637
- const targetExists = existsSync9(targetDir);
3833
+ const targetExists = existsSync10(targetDir);
3638
3834
  if (targetExists && !statSync2(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
3639
3835
  const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
3640
3836
  const syncMode = Boolean(targetGitRoot && resolve3(targetGitRoot) === resolve3(targetDir));