@delorenj/pjangler 1.5.49 → 1.5.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1105 -637
  2. package/dist/mcp-server.js +981 -522
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -464,7 +464,7 @@ var init_executionBinding = __esm({
464
464
  });
465
465
 
466
466
  // src/project/manifestIndex.ts
467
- import { basename as basename5, dirname as dirname10, join as join12, resolve as resolve8 } from "node:path";
467
+ import { basename as basename6, dirname as dirname10, join as join13, resolve as resolve8 } from "node:path";
468
468
  function normalizeProjectId2(value) {
469
469
  try {
470
470
  return normalizeProjectId(value);
@@ -511,12 +511,12 @@ var init_manifestIndex = __esm({
511
511
  });
512
512
 
513
513
  // src/lifecycle/preflight.ts
514
- import { createHash as createHash8 } from "node:crypto";
515
- import { accessSync, constants as constants2, existsSync as existsSync9, lstatSync as lstatSync10, readFileSync as readFileSync12, readdirSync as readdirSync8, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
516
- import { basename as basename6, delimiter, dirname as dirname11, isAbsolute as isAbsolute5, join as join13, relative as relative10, resolve as resolve9 } from "node:path";
514
+ import { createHash as createHash9 } from "node:crypto";
515
+ import { accessSync, constants as constants3, existsSync as existsSync9, lstatSync as lstatSync11, readFileSync as readFileSync13, readdirSync as readdirSync9, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
516
+ import { basename as basename7, delimiter, dirname as dirname11, isAbsolute as isAbsolute5, join as join14, relative as relative10, resolve as resolve9 } from "node:path";
517
517
  import YAML5 from "yaml";
518
518
  function sha256(path) {
519
- return createHash8("sha256").update(readFileSync12(path)).digest("base64url");
519
+ return createHash9("sha256").update(readFileSync13(path)).digest("base64url");
520
520
  }
521
521
  function fingerprint(path) {
522
522
  const absolute = resolve9(path);
@@ -570,15 +570,15 @@ var init_preflight = __esm({
570
570
  });
571
571
 
572
572
  // src/project/boardUrl.ts
573
- import { existsSync as existsSync10, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
573
+ import { existsSync as existsSync10, readFileSync as readFileSync14, statSync as statSync4 } from "node:fs";
574
574
  import { homedir as homedir5 } from "node:os";
575
- import { dirname as dirname12, isAbsolute as isAbsolute6, join as join14, resolve as resolve10 } from "node:path";
575
+ import { dirname as dirname12, isAbsolute as isAbsolute6, join as join15, resolve as resolve10 } from "node:path";
576
576
  function resolveTemplateConfigPath(env2 = process.env, home = homedir5()) {
577
577
  const fromEnv = env2.HERMES_TEMPLATE_CONFIG;
578
578
  if (fromEnv && fromEnv.trim()) return fromEnv.trim();
579
579
  const xdg = env2.XDG_CONFIG_HOME?.trim();
580
- const base = xdg && xdg.length ? xdg : join14(home, ".config");
581
- return join14(base, "hermes-agent-template", "config.toml");
580
+ const base = xdg && xdg.length ? xdg : join15(home, ".config");
581
+ return join15(base, "hermes-agent-template", "config.toml");
582
582
  }
583
583
  function readTomlScalar(text2, section2, key) {
584
584
  let inSection = false;
@@ -604,7 +604,7 @@ function readTomlScalar(text2, section2, key) {
604
604
  function readTemplateConfig(env2, home) {
605
605
  try {
606
606
  const path = resolveTemplateConfigPath(env2, home);
607
- return existsSync10(path) ? readFileSync13(path, "utf8") : void 0;
607
+ return existsSync10(path) ? readFileSync14(path, "utf8") : void 0;
608
608
  } catch {
609
609
  return void 0;
610
610
  }
@@ -668,7 +668,7 @@ function boardUrl(provider, options = {}) {
668
668
  function findProjectRoot(from) {
669
669
  let dir = resolve10(from);
670
670
  for (; ; ) {
671
- if (existsSync10(join14(dir, ".project.json"))) return dir;
671
+ if (existsSync10(join15(dir, ".project.json"))) return dir;
672
672
  const parent = dirname12(dir);
673
673
  if (parent === dir) return void 0;
674
674
  dir = parent;
@@ -676,7 +676,7 @@ function findProjectRoot(from) {
676
676
  }
677
677
  function readTicketProvider(root) {
678
678
  try {
679
- const manifest = JSON.parse(readFileSync13(join14(root, ".project.json"), "utf8"));
679
+ const manifest = JSON.parse(readFileSync14(join15(root, ".project.json"), "utf8"));
680
680
  const provider = manifest.ticket_provider;
681
681
  if (!provider || typeof provider !== "object") return void 0;
682
682
  return provider;
@@ -688,16 +688,16 @@ function currentBranch(from) {
688
688
  try {
689
689
  let dir = resolve10(from);
690
690
  for (; ; ) {
691
- const dotgit = join14(dir, ".git");
691
+ const dotgit = join15(dir, ".git");
692
692
  if (existsSync10(dotgit)) {
693
693
  let gitDir = dotgit;
694
694
  if (statSync4(dotgit).isFile()) {
695
- const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync13(dotgit, "utf8"));
695
+ const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync14(dotgit, "utf8"));
696
696
  if (!pointer) return void 0;
697
697
  const target = pointer[1].trim();
698
698
  gitDir = isAbsolute6(target) ? target : resolve10(dir, target);
699
699
  }
700
- const head = readFileSync13(join14(gitDir, "HEAD"), "utf8").trim();
700
+ const head = readFileSync14(join15(gitDir, "HEAD"), "utf8").trim();
701
701
  const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
702
702
  return ref ? ref[1].trim() : void 0;
703
703
  }
@@ -726,8 +726,8 @@ var init_boardUrl = __esm({
726
726
  });
727
727
 
728
728
  // src/utils/version.ts
729
- import { readFileSync as readFileSync14 } from "node:fs";
730
- import { dirname as dirname13, join as join15 } from "node:path";
729
+ import { readFileSync as readFileSync15 } from "node:fs";
730
+ import { dirname as dirname13, join as join16 } from "node:path";
731
731
  import { fileURLToPath } from "node:url";
732
732
  var PJANGLER_VERSION;
733
733
  var init_version = __esm({
@@ -738,7 +738,7 @@ var init_version = __esm({
738
738
  let dir = dirname13(fileURLToPath(import.meta.url));
739
739
  for (let i = 0; i < 4; i++) {
740
740
  try {
741
- const raw = readFileSync14(join15(dir, "package.json"), "utf8");
741
+ const raw = readFileSync15(join16(dir, "package.json"), "utf8");
742
742
  return JSON.parse(raw).version ?? "0.0.0";
743
743
  } catch {
744
744
  const parent = dirname13(dir);
@@ -755,17 +755,17 @@ var init_version = __esm({
755
755
 
756
756
  // src/project/boardQuery.ts
757
757
  import { spawnSync as spawnSync6 } from "node:child_process";
758
- import { readFileSync as readFileSync15 } from "node:fs";
758
+ import { readFileSync as readFileSync16 } from "node:fs";
759
759
  import { homedir as homedir6 } from "node:os";
760
- import { basename as basename7, join as join16 } from "node:path";
760
+ import { basename as basename8, join as join17 } from "node:path";
761
761
  function readProjectSlug(root) {
762
762
  try {
763
- const manifest = JSON.parse(readFileSync15(join16(root, ".project.json"), "utf8"));
763
+ const manifest = JSON.parse(readFileSync16(join17(root, ".project.json"), "utf8"));
764
764
  const slug = manifest.project_id ?? manifest.project_slug;
765
765
  if (typeof slug === "string" && slug.trim()) return slug.trim().toLowerCase();
766
766
  } catch {
767
767
  }
768
- return basename7(root).toLowerCase();
768
+ return basename8(root).toLowerCase();
769
769
  }
770
770
  function resolveProject(cwd) {
771
771
  const root = findProjectRoot(cwd);
@@ -780,12 +780,12 @@ function resolveProvider(handle) {
780
780
  const type = handle.facts?.type?.trim().toLowerCase();
781
781
  if (!type) {
782
782
  throw new BoardError(
783
- `no ticket board bound for ${handle.slug}: ${join16(handle.root, ".project.json")} has no ticket_provider.type`
783
+ `no ticket board bound for ${handle.slug}: ${join17(handle.root, ".project.json")} has no ticket_provider.type`
784
784
  );
785
785
  }
786
786
  if (!BOARD_PROVIDERS.includes(type)) {
787
787
  throw new BoardError(
788
- `unsupported ticket_provider.type "${type}" in ${join16(handle.root, ".project.json")} \u2014 expected one of ${BOARD_PROVIDERS.join(", ")}`
788
+ `unsupported ticket_provider.type "${type}" in ${join17(handle.root, ".project.json")} \u2014 expected one of ${BOARD_PROVIDERS.join(", ")}`
789
789
  );
790
790
  }
791
791
  return type;
@@ -802,7 +802,7 @@ function workspaceEnvKey(workspace) {
802
802
  function dotenvValue(path, key) {
803
803
  let text2;
804
804
  try {
805
- text2 = readFileSync15(path, "utf8");
805
+ text2 = readFileSync16(path, "utf8");
806
806
  } catch {
807
807
  return void 0;
808
808
  }
@@ -837,7 +837,7 @@ function planeApiKeyNames(workspace) {
837
837
  return ["PLANE_API_KEY", workspaceEnvKey(workspace)];
838
838
  }
839
839
  function resolvePlaneApiKey(workspace, env2 = process.env, home = homedir6()) {
840
- const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join16(home, ".hermes", "fleet.env");
840
+ const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join17(home, ".hermes", "fleet.env");
841
841
  const scoped = workspaceEnvKey(workspace);
842
842
  const candidates = [
843
843
  env2.PLANE_API_KEY,
@@ -859,7 +859,7 @@ function planeClient(ctx) {
859
859
  const board = ctx.facts.board_id?.trim();
860
860
  if (!board) {
861
861
  throw new BoardError(
862
- `no board linked for ${ctx.slug}: ${join16(ctx.root, ".project.json")} has an empty ticket_provider.board_id`
862
+ `no board linked for ${ctx.slug}: ${join17(ctx.root, ".project.json")} has an empty ticket_provider.board_id`
863
863
  );
864
864
  }
865
865
  const workspace = planeWorkspace(ctx.facts, ctx.env, ctx.home);
@@ -869,7 +869,7 @@ function planeClient(ctx) {
869
869
  const apiKey = resolvePlaneApiKey(workspace, ctx.env, ctx.home);
870
870
  if (!apiKey) {
871
871
  throw new BoardError(
872
- `no Plane API key for workspace "${workspace}": set ${planeApiKeyNames(workspace).join(" or ")}, or add one to ${ctx.env.HERMES_FLEET_ENV?.trim() || join16(ctx.home, ".hermes", "fleet.env")}`
872
+ `no Plane API key for workspace "${workspace}": set ${planeApiKeyNames(workspace).join(" or ")}, or add one to ${ctx.env.HERMES_FLEET_ENV?.trim() || join17(ctx.home, ".hermes", "fleet.env")}`
873
873
  );
874
874
  }
875
875
  return {
@@ -1036,9 +1036,9 @@ var init_boardQuery = __esm({
1036
1036
 
1037
1037
  // src/project/index.ts
1038
1038
  import { spawnSync as spawnSync7 } from "node:child_process";
1039
- import { chmodSync as chmodSync4, closeSync as closeSync2, copyFileSync as copyFileSync2, existsSync as existsSync11, fchmodSync, fsyncSync, lstatSync as lstatSync11, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync16, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
1039
+ import { chmodSync as chmodSync4, closeSync as closeSync3, copyFileSync as copyFileSync2, existsSync as existsSync11, fchmodSync as fchmodSync2, fsyncSync, lstatSync as lstatSync12, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, openSync as openSync3, readFileSync as readFileSync17, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
1040
1040
  import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
1041
- import { basename as basename8, delimiter as delimiter2, dirname as dirname14, isAbsolute as isAbsolute7, join as join17, relative as relative11, resolve as resolve11, sep as sep3, win32 } from "node:path";
1041
+ import { basename as basename9, delimiter as delimiter2, dirname as dirname14, isAbsolute as isAbsolute7, join as join18, relative as relative11, resolve as resolve11, sep as sep3, win32 } from "node:path";
1042
1042
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1043
1043
  import YAML6 from "yaml";
1044
1044
  function normalizeProjectType(value, label = "Project type") {
@@ -1048,7 +1048,7 @@ function normalizeProjectType(value, label = "Project type") {
1048
1048
  }
1049
1049
  function looksLikeObsidianPlugin(dir) {
1050
1050
  try {
1051
- const manifest = JSON.parse(readFileSync16(join17(dir, "manifest.json"), "utf8"));
1051
+ const manifest = JSON.parse(readFileSync17(join18(dir, "manifest.json"), "utf8"));
1052
1052
  return isRecord3(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
1053
1053
  } catch {
1054
1054
  return false;
@@ -1056,10 +1056,10 @@ function looksLikeObsidianPlugin(dir) {
1056
1056
  }
1057
1057
  function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
1058
1058
  if (projectType === "base") return;
1059
- const copierYml = join17(pjanglerRoot, "templates", "commonproject", "copier.yml");
1059
+ const copierYml = join18(pjanglerRoot, "templates", "commonproject", "copier.yml");
1060
1060
  let choices;
1061
1061
  try {
1062
- const parsed = YAML6.parse(readFileSync16(copierYml, "utf8"));
1062
+ const parsed = YAML6.parse(readFileSync17(copierYml, "utf8"));
1063
1063
  choices = isRecord3(parsed) && isRecord3(parsed.project_type) ? parsed.project_type.choices : void 0;
1064
1064
  } catch {
1065
1065
  choices = void 0;
@@ -1082,9 +1082,9 @@ function carriedAutomation(automation) {
1082
1082
  return Object.keys(rest).length ? { automation: rest } : {};
1083
1083
  }
1084
1084
  function synchronizeCopierIdentity(manifestPath, manifest) {
1085
- const answersPath = join17(dirname14(manifestPath), ".copier-answers.yml");
1085
+ const answersPath = join18(dirname14(manifestPath), ".copier-answers.yml");
1086
1086
  if (!existsSync11(answersPath)) return [];
1087
- const current = readFileSync16(answersPath, "utf8");
1087
+ const current = readFileSync17(answersPath, "utf8");
1088
1088
  const document = YAML6.parseDocument(current);
1089
1089
  if (document.errors.length) return [];
1090
1090
  const name = String(document.get("project_name") ?? "");
@@ -1094,7 +1094,7 @@ function synchronizeCopierIdentity(manifestPath, manifest) {
1094
1094
  document.set("project_description", manifest.project_description);
1095
1095
  const next = String(document);
1096
1096
  if (next === current) return [];
1097
- writeFileSync9(answersPath, next, "utf8");
1097
+ writeFileSync10(answersPath, next, "utf8");
1098
1098
  return [answersPath];
1099
1099
  }
1100
1100
  function projectRegistryPath(env2 = process.env) {
@@ -1118,7 +1118,7 @@ function loadProjectRegistry(path = projectRegistryPath()) {
1118
1118
  return registry2;
1119
1119
  }
1120
1120
  if (!existsSync11(path)) return emptyProjectRegistry();
1121
- const raw = YAML6.parse(readFileSync16(path, "utf8"));
1121
+ const raw = YAML6.parse(readFileSync17(path, "utf8"));
1122
1122
  if (raw == null) return emptyProjectRegistry();
1123
1123
  if (!isRecord3(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
1124
1124
  const registry = raw;
@@ -1186,11 +1186,11 @@ function mergeYamlMapping(document, path, desired, ownedKeys = []) {
1186
1186
  }
1187
1187
  function fsyncDirectory(path) {
1188
1188
  try {
1189
- const fd = openSync2(path, "r");
1189
+ const fd = openSync3(path, "r");
1190
1190
  try {
1191
1191
  fsyncSync(fd);
1192
1192
  } finally {
1193
- closeSync2(fd);
1193
+ closeSync3(fd);
1194
1194
  }
1195
1195
  } catch {
1196
1196
  }
@@ -1204,14 +1204,14 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1204
1204
  return;
1205
1205
  }
1206
1206
  validateProjectRegistry(registry);
1207
- mkdirSync7(dirname14(path), { recursive: true });
1207
+ mkdirSync8(dirname14(path), { recursive: true });
1208
1208
  let text2;
1209
1209
  let mode = 420;
1210
1210
  if (existsSync11(path)) {
1211
- const stat = lstatSync11(path);
1211
+ const stat = lstatSync12(path);
1212
1212
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project registry must be a regular file: ${path}`);
1213
1213
  mode = stat.mode & 511;
1214
- const current = readFileSync16(path, "utf8");
1214
+ const current = readFileSync17(path, "utf8");
1215
1215
  const document = YAML6.parseDocument(current);
1216
1216
  if (document.errors.length) throw new Error(`Project registry YAML is invalid: ${path}`);
1217
1217
  setYamlLeaf(document, ["schema_version"], registry.schema_version);
@@ -1232,22 +1232,22 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1232
1232
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
1233
1233
  let fd;
1234
1234
  try {
1235
- fd = openSync2(temp, "wx", mode);
1236
- writeFileSync9(fd, text2, "utf8");
1237
- fchmodSync(fd, mode);
1235
+ fd = openSync3(temp, "wx", mode);
1236
+ writeFileSync10(fd, text2, "utf8");
1237
+ fchmodSync2(fd, mode);
1238
1238
  fsyncSync(fd);
1239
- closeSync2(fd);
1239
+ closeSync3(fd);
1240
1240
  fd = void 0;
1241
1241
  renameSync5(temp, path);
1242
1242
  chmodSync4(path, mode);
1243
1243
  fsyncDirectory(dirname14(path));
1244
1244
  } catch (error) {
1245
1245
  if (fd !== void 0) try {
1246
- closeSync2(fd);
1246
+ closeSync3(fd);
1247
1247
  } catch {
1248
1248
  }
1249
1249
  try {
1250
- unlinkSync6(temp);
1250
+ unlinkSync7(temp);
1251
1251
  } catch {
1252
1252
  }
1253
1253
  throw error;
@@ -1355,18 +1355,18 @@ function ticketProviderKeyVars(provider, workspace) {
1355
1355
  return ticketProviderKeyGroups(provider, workspace).flat();
1356
1356
  }
1357
1357
  function ticketProviderSecretsPath(env2 = process.env) {
1358
- const base = env2.XDG_CONFIG_HOME || join17(env2.HOME || homedir7(), ".config");
1359
- return join17(base, "zshyzsh", "secrets.zsh");
1358
+ const base = env2.XDG_CONFIG_HOME || join18(env2.HOME || homedir7(), ".config");
1359
+ return join18(base, "zshyzsh", "secrets.zsh");
1360
1360
  }
1361
1361
  function ticketProviderFleetEnvPath(env2 = process.env) {
1362
- return env2.HERMES_FLEET_ENV?.trim() || join17(env2.HOME || homedir7(), ".hermes", "fleet.env");
1362
+ return env2.HERMES_FLEET_ENV?.trim() || join18(env2.HOME || homedir7(), ".hermes", "fleet.env");
1363
1363
  }
1364
1364
  function readShellAssignments(path, keys) {
1365
1365
  const found = {};
1366
1366
  if (!existsSync11(path)) return found;
1367
1367
  let text2;
1368
1368
  try {
1369
- text2 = readFileSync16(path, "utf8");
1369
+ text2 = readFileSync17(path, "utf8");
1370
1370
  } catch {
1371
1371
  return found;
1372
1372
  }
@@ -1402,7 +1402,7 @@ function resolveTicketProviderCredentials(input) {
1402
1402
  }
1403
1403
  }
1404
1404
  const candidates = [];
1405
- if (input.repoPath) candidates.push(join17(input.repoPath, ".env"));
1405
+ if (input.repoPath) candidates.push(join18(input.repoPath, ".env"));
1406
1406
  candidates.push(ticketProviderFleetEnvPath(env2));
1407
1407
  candidates.push(ticketProviderSecretsPath(env2));
1408
1408
  for (const candidate of candidates) {
@@ -1420,21 +1420,26 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
1420
1420
  const file = `${provider}.sh`;
1421
1421
  const candidates = [];
1422
1422
  const override = env2[TICKET_PROVIDER_ADAPTERS_ENV];
1423
- if (override) candidates.push(join17(override, file));
1424
- const relativeRoot = join17("krebs", "adapters", "tp");
1423
+ if (override) candidates.push(join18(override, file));
1424
+ const relativeRoot = join18("krebs", "adapters", "tp");
1425
1425
  try {
1426
1426
  let dir = dirname14(fileURLToPath2(import.meta.url));
1427
1427
  for (let depth = 0; depth < 8; depth++) {
1428
- candidates.push(join17(dir, relativeRoot, file));
1428
+ candidates.push(join18(dir, relativeRoot, file));
1429
1429
  const parent = dirname14(dir);
1430
1430
  if (parent === dir) break;
1431
1431
  dir = parent;
1432
1432
  }
1433
1433
  } catch {
1434
1434
  }
1435
- candidates.push(join17(homedir7(), "code", "33GOD", relativeRoot, file));
1435
+ candidates.push(join18(homedir7(), "code", "33GOD", relativeRoot, file));
1436
1436
  return candidates.find((candidate) => existsSync11(candidate));
1437
1437
  }
1438
+ function providerBoardName(provider, name) {
1439
+ if (provider !== "plane") return name;
1440
+ const cleaned = name.replace(/[^\p{L}\p{N}_\s]+/gu, " ").replace(/\s+/g, " ").trim();
1441
+ return cleaned || name;
1442
+ }
1438
1443
  function provisionTicketProviderBoard(action, env2 = process.env) {
1439
1444
  const provider = action.provider;
1440
1445
  const groups = ticketProviderKeyGroups(provider, action.workspace);
@@ -1450,7 +1455,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1450
1455
  ok: true,
1451
1456
  skipped: true,
1452
1457
  logs: [
1453
- `ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join17(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
1458
+ `ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join18(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
1454
1459
  ]
1455
1460
  };
1456
1461
  }
@@ -1464,12 +1469,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1464
1469
  };
1465
1470
  }
1466
1471
  const redact = (text2) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text2);
1467
- const staging = mkdtempSync2(join17(tmpdir2(), "pjangler-tp-"));
1472
+ const staging = mkdtempSync3(join18(tmpdir2(), "pjangler-tp-"));
1468
1473
  try {
1469
- const providersDir = join17(staging, ".tp", "adapters");
1470
- mkdirSync7(providersDir, { recursive: true });
1471
- writeFileSync9(
1472
- join17(staging, ".project.json"),
1474
+ const providersDir = join18(staging, ".tp", "adapters");
1475
+ mkdirSync8(providersDir, { recursive: true });
1476
+ writeFileSync10(
1477
+ join18(staging, ".project.json"),
1473
1478
  `${JSON.stringify(
1474
1479
  {
1475
1480
  project_name: action.boardName,
@@ -1488,7 +1493,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1488
1493
  `,
1489
1494
  "utf8"
1490
1495
  );
1491
- const staged = join17(providersDir, `${provider}.sh`);
1496
+ const staged = join18(providersDir, `${provider}.sh`);
1492
1497
  copyFileSync2(adapter, staged);
1493
1498
  const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
1494
1499
  if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
@@ -1584,7 +1589,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
1584
1589
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1585
1590
  if (override === "0" || override === "false") return false;
1586
1591
  if (override === "1" || override === "true") return true;
1587
- return !existsSync11(join17(homedir7(), ".agents", "hooks"));
1592
+ return !existsSync11(join18(homedir7(), ".agents", "hooks"));
1588
1593
  }
1589
1594
  function jsonStable(value) {
1590
1595
  return JSON.stringify(value, (_key, item) => isRecord3(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
@@ -1626,10 +1631,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1626
1631
  const expanded = expandHome(sourceSkill);
1627
1632
  const direct = resolve11(expanded);
1628
1633
  if (existsSync11(direct)) return direct;
1629
- const name = basename8(sourceSkill);
1634
+ const name = basename9(sourceSkill);
1630
1635
  const roots = sourceSkillRoots(env2);
1631
1636
  for (const root of roots) {
1632
- const candidate = join17(root, name);
1637
+ const candidate = join18(root, name);
1633
1638
  if (existsSync11(candidate)) return candidate;
1634
1639
  }
1635
1640
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
@@ -1639,8 +1644,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1639
1644
  function planProjectInit(input) {
1640
1645
  if (!input.name.trim()) throw new Error("Project name is required");
1641
1646
  const targetDir = resolve11(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
1642
- const localManifestPath = join17(targetDir, ".project.json");
1643
- const localManifestContent = existsSync11(localManifestPath) ? readFileSync16(localManifestPath, "utf8") : void 0;
1647
+ const localManifestPath = join18(targetDir, ".project.json");
1648
+ const localManifestContent = existsSync11(localManifestPath) ? readFileSync17(localManifestPath, "utf8") : void 0;
1644
1649
  const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
1645
1650
  const localId = localManifest?.project_id ?? localManifest?.project_slug;
1646
1651
  const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
@@ -1668,7 +1673,7 @@ function planProjectInit(input) {
1668
1673
  const recordedTypeValue = existing?.template?.commonproject?.project_type;
1669
1674
  const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
1670
1675
  const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
1671
- const freshRender = scaffold && localManifest === void 0 && !existsSync11(join17(targetDir, ".copier-answers.yml"));
1676
+ const freshRender = scaffold && localManifest === void 0 && !existsSync11(join18(targetDir, ".copier-answers.yml"));
1672
1677
  if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
1673
1678
  const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
1674
1679
  if (!adoptingPlugin) {
@@ -1766,7 +1771,7 @@ function planProjectInit(input) {
1766
1771
  }));
1767
1772
  }
1768
1773
  actions.push(
1769
- { kind: "project.write-manifest", path: join17(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1774
+ { kind: "project.write-manifest", path: join18(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1770
1775
  {
1771
1776
  kind: "ticket-provider.create-or-link",
1772
1777
  enabled: boardEnabled,
@@ -1775,11 +1780,11 @@ function planProjectInit(input) {
1775
1780
  workspace: project.ticket_provider.workspace ?? "33god",
1776
1781
  identifier,
1777
1782
  repoPath: targetDir,
1778
- boardName: project.name,
1783
+ boardName: providerBoardName(project.ticket_provider.type, project.name),
1779
1784
  description: project.description || `Ticket board for ${project.slug}`,
1780
1785
  boardId: project.ticket_provider.board_id ?? "",
1781
1786
  state: project.ticket_provider.board_id ? "linked" : "planned",
1782
- reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${project.name}" (${identifier}) via the ticket-provider adapter`
1787
+ reason: skipPlane ? "ticket-provider action disabled by skipPlane=true (--skip-board)" : project.ticket_provider.board_id ? "board already linked; no provider call" : !provisionTicketBoard ? "ticket-provider action requires explicit provisionTicketBoard=true" : `create or link the ${project.ticket_provider.type} board "${providerBoardName(project.ticket_provider.type, project.name)}" (${identifier}) via the ticket-provider adapter`
1783
1788
  }
1784
1789
  );
1785
1790
  return {
@@ -1821,12 +1826,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1821
1826
  plan.manifest.ticket_provider = manifestProvider;
1822
1827
  action.boardId = boardId;
1823
1828
  action.state = manifestProvider.state;
1824
- const manifestPath = join17(action.repoPath, ".project.json");
1829
+ const manifestPath = join18(action.repoPath, ".project.json");
1825
1830
  let next;
1826
1831
  if (existsSync11(manifestPath)) {
1827
1832
  let existing = {};
1828
1833
  try {
1829
- const parsed = JSON.parse(readFileSync16(manifestPath, "utf8"));
1834
+ const parsed = JSON.parse(readFileSync17(manifestPath, "utf8"));
1830
1835
  if (isRecord3(parsed)) existing = parsed;
1831
1836
  } catch {
1832
1837
  existing = {};
@@ -1834,13 +1839,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1834
1839
  const existingProvider = isRecord3(existing.ticket_provider) ? existing.ticket_provider : {};
1835
1840
  next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
1836
1841
  } else {
1837
- mkdirSync7(dirname14(manifestPath), { recursive: true });
1842
+ mkdirSync8(dirname14(manifestPath), { recursive: true });
1838
1843
  next = plan.manifest;
1839
1844
  }
1840
1845
  const text2 = `${JSON.stringify(next, null, 2)}
1841
1846
  `;
1842
- if (!existsSync11(manifestPath) || readFileSync16(manifestPath, "utf8") !== text2) {
1843
- writeFileSync9(manifestPath, text2, "utf8");
1847
+ if (!existsSync11(manifestPath) || readFileSync17(manifestPath, "utf8") !== text2) {
1848
+ writeFileSync10(manifestPath, text2, "utf8");
1844
1849
  return [manifestPath];
1845
1850
  }
1846
1851
  return [];
@@ -1868,7 +1873,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1868
1873
  logs.push(
1869
1874
  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"
1870
1875
  );
1871
- mkdirSync7(dirname14(action.targetDir), { recursive: true });
1876
+ mkdirSync8(dirname14(action.targetDir), { recursive: true });
1872
1877
  const before = snapshotTree(action.targetDir);
1873
1878
  const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
1874
1879
  const copierEnv = { ...process.env };
@@ -1900,22 +1905,22 @@ async function executeProjectInitPlan(plan, options = {}) {
1900
1905
  break;
1901
1906
  }
1902
1907
  const projectType = action.data.project_type ?? "base";
1903
- const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join17(action.targetDir, rel)));
1908
+ const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join18(action.targetDir, rel)));
1904
1909
  if (missing.length) {
1905
1910
  errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
1906
1911
  break;
1907
1912
  }
1908
1913
  if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
1909
1914
  } else if (action.kind === "project.write-manifest") {
1910
- if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync16(action.path, "utf8") !== action.expectedContent)) {
1915
+ if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync17(action.path, "utf8") !== action.expectedContent)) {
1911
1916
  errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
1912
1917
  break;
1913
1918
  }
1914
- mkdirSync7(dirname14(action.path), { recursive: true });
1919
+ mkdirSync8(dirname14(action.path), { recursive: true });
1915
1920
  let value = action.manifest;
1916
1921
  if (existsSync11(action.path)) {
1917
1922
  try {
1918
- const currentValue = JSON.parse(readFileSync16(action.path, "utf8"));
1923
+ const currentValue = JSON.parse(readFileSync17(action.path, "utf8"));
1919
1924
  if (isRecord3(currentValue)) {
1920
1925
  const currentNotebook = isRecord3(currentValue.notebook) ? currentValue.notebook : {};
1921
1926
  const desiredNotebook = isRecord3(value.notebook) ? value.notebook : {};
@@ -1943,9 +1948,9 @@ async function executeProjectInitPlan(plan, options = {}) {
1943
1948
  value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
1944
1949
  const next = `${JSON.stringify(value, null, 2)}
1945
1950
  `;
1946
- const current = existsSync11(action.path) ? readFileSync16(action.path, "utf8") : void 0;
1951
+ const current = existsSync11(action.path) ? readFileSync17(action.path, "utf8") : void 0;
1947
1952
  if (current !== next) {
1948
- writeFileSync9(action.path, next, "utf8");
1953
+ writeFileSync10(action.path, next, "utf8");
1949
1954
  changedFiles.push(action.path);
1950
1955
  }
1951
1956
  changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
@@ -2128,7 +2133,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
2128
2133
  } else if (!statSync5(project.repo_path).isDirectory()) {
2129
2134
  issues.push({ level: "error", slug: projectSlug, message: `repo_path is not a directory: ${project.repo_path}` });
2130
2135
  } else {
2131
- const manifestPath = join17(project.repo_path, ".project.json");
2136
+ const manifestPath = join18(project.repo_path, ".project.json");
2132
2137
  if (!existsSync11(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
2133
2138
  }
2134
2139
  for (const artifact of project.source_artifacts) {
@@ -2145,7 +2150,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
2145
2150
  };
2146
2151
  }
2147
2152
  function buildCommonProjectCopierAction(input) {
2148
- const templateDir = join17(input.pjanglerRoot, "templates", "commonproject");
2153
+ const templateDir = join18(input.pjanglerRoot, "templates", "commonproject");
2149
2154
  const data = {
2150
2155
  project_name: input.projectName,
2151
2156
  project_description: input.projectDescription ?? "",
@@ -2176,7 +2181,7 @@ function buildCommonProjectCopierAction(input) {
2176
2181
  function resolvePjanglerRoot() {
2177
2182
  let dir = dirname14(fileURLToPath2(import.meta.url));
2178
2183
  while (dir !== dirname14(dir)) {
2179
- if (existsSync11(join17(dir, "package.json")) && existsSync11(join17(dir, "templates", "commonproject", "copier.yml"))) return dir;
2184
+ if (existsSync11(join18(dir, "package.json")) && existsSync11(join18(dir, "templates", "commonproject", "copier.yml"))) return dir;
2180
2185
  dir = dirname14(dir);
2181
2186
  }
2182
2187
  return resolve11(process.cwd());
@@ -2253,7 +2258,7 @@ function validateProjectRecord(project, key) {
2253
2258
  }
2254
2259
  function expandHome(path) {
2255
2260
  if (path === "~") return homedir7();
2256
- if (path.startsWith("~/")) return join17(homedir7(), path.slice(2));
2261
+ if (path.startsWith("~/")) return join18(homedir7(), path.slice(2));
2257
2262
  return path;
2258
2263
  }
2259
2264
  function isRecord3(value) {
@@ -2280,7 +2285,7 @@ var init_project = __esm({
2280
2285
  "obsidian-plugin": ["typescript", "javascript"]
2281
2286
  };
2282
2287
  PROJECT_TYPE_SENTINELS = {
2283
- "obsidian-plugin": ["manifest.json", join17("src", "main.ts")]
2288
+ "obsidian-plugin": ["manifest.json", join18("src", "main.ts")]
2284
2289
  };
2285
2290
  BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
2286
2291
  PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
@@ -2289,8 +2294,8 @@ var init_project = __esm({
2289
2294
  IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
2290
2295
  DEFAULT_SOURCE_SKILL_ROOTS = [
2291
2296
  "/home/delorenj/code/skillex/all-skills",
2292
- join17(homedir7(), ".agents", "skills"),
2293
- join17(homedir7(), ".codex", "skills")
2297
+ join18(homedir7(), ".agents", "skills"),
2298
+ join18(homedir7(), ".codex", "skills")
2294
2299
  ];
2295
2300
  PROJECT_REGISTRY_OWNED_KEYS = [
2296
2301
  "name",
@@ -2322,7 +2327,7 @@ var init_project = __esm({
2322
2327
  import { randomUUID } from "node:crypto";
2323
2328
  import { isDeepStrictEqual } from "node:util";
2324
2329
  import { isIP as isIP2 } from "node:net";
2325
- import { closeSync as closeSync3, existsSync as existsSync12, fchmodSync as fchmodSync2, fsyncSync as fsyncSync2, lstatSync as lstatSync12, openSync as openSync3, readFileSync as readFileSync17, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "node:fs";
2330
+ import { closeSync as closeSync4, existsSync as existsSync12, fchmodSync as fchmodSync3, fsyncSync as fsyncSync2, lstatSync as lstatSync13, openSync as openSync4, readFileSync as readFileSync18, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "node:fs";
2326
2331
  import { dirname as dirname15, resolve as resolve12 } from "node:path";
2327
2332
  function isRecord4(value) {
2328
2333
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -2336,7 +2341,7 @@ function readManifest(repoPath) {
2336
2341
  if (!existsSync12(path)) return null;
2337
2342
  let parsed;
2338
2343
  try {
2339
- parsed = JSON.parse(readFileSync17(path, "utf8"));
2344
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
2340
2345
  } catch {
2341
2346
  throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
2342
2347
  }
@@ -2578,7 +2583,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2578
2583
  const changed = [];
2579
2584
  const manifestPath = resolve12(resolved.project.repo_path, ".project.json");
2580
2585
  if (isRegistryServiceLocation(resolved.registry_path)) {
2581
- const before = existsSync12(manifestPath) ? readFileSync17(manifestPath, "utf8") : null;
2586
+ const before = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
2582
2587
  const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
2583
2588
  const nextProject = {
2584
2589
  ...resolved.project,
@@ -2590,10 +2595,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2590
2595
  resolved.registry = loadProjectRegistry(resolved.registry_path);
2591
2596
  resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
2592
2597
  resolved.manifest = readManifest(nextProject.repo_path);
2593
- const after = existsSync12(manifestPath) ? readFileSync17(manifestPath, "utf8") : null;
2598
+ const after = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
2594
2599
  return before === after ? [] : [manifestPath];
2595
2600
  }
2596
- const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync17(manifestPath, "utf8")) : {};
2601
+ const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync18(manifestPath, "utf8")) : {};
2597
2602
  if (!isRecord4(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
2598
2603
  validateManifestNotebookSurface(manifestRaw.notebook);
2599
2604
  const manifestNotebook2 = isRecord4(manifestRaw.notebook) ? manifestRaw.notebook : {};
@@ -2626,29 +2631,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2626
2631
  };
2627
2632
  const manifestText = `${JSON.stringify(manifestNext, null, 2)}
2628
2633
  `;
2629
- if (!existsSync12(manifestPath) || readFileSync17(manifestPath, "utf8") !== manifestText) {
2634
+ if (!existsSync12(manifestPath) || readFileSync18(manifestPath, "utf8") !== manifestText) {
2630
2635
  let mode = 420;
2631
2636
  if (existsSync12(manifestPath)) {
2632
- const current2 = lstatSync12(manifestPath);
2637
+ const current2 = lstatSync13(manifestPath);
2633
2638
  if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
2634
2639
  mode = current2.mode & 511;
2635
2640
  }
2636
2641
  const temp = resolve12(dirname15(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
2637
- const fd = openSync3(temp, "wx", mode);
2642
+ const fd = openSync4(temp, "wx", mode);
2638
2643
  try {
2639
- writeFileSync10(fd, manifestText, "utf8");
2640
- fchmodSync2(fd, mode);
2644
+ writeFileSync11(fd, manifestText, "utf8");
2645
+ fchmodSync3(fd, mode);
2641
2646
  fsyncSync2(fd);
2642
2647
  } finally {
2643
- closeSync3(fd);
2648
+ closeSync4(fd);
2644
2649
  }
2645
2650
  renameSync6(temp, manifestPath);
2646
2651
  try {
2647
- const directory = openSync3(dirname15(manifestPath), "r");
2652
+ const directory = openSync4(dirname15(manifestPath), "r");
2648
2653
  try {
2649
2654
  fsyncSync2(directory);
2650
2655
  } finally {
2651
- closeSync3(directory);
2656
+ closeSync4(directory);
2652
2657
  }
2653
2658
  } catch {
2654
2659
  }
@@ -2657,9 +2662,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2657
2662
  const current = resolved.project.notebook;
2658
2663
  resolved.project.notebook = { ...isRecord4(current) ? current : { state: "planned" }, ...binding };
2659
2664
  const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
2660
- const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync17(resolved.registry_path, "utf8") : null;
2665
+ const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
2661
2666
  saveProjectRegistry(resolved.registry, resolved.registry_path);
2662
- const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync17(resolved.registry_path, "utf8") : null;
2667
+ const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
2663
2668
  if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
2664
2669
  resolved.manifest = manifestNext;
2665
2670
  return changed;
@@ -2692,7 +2697,7 @@ var init_config = __esm({
2692
2697
  });
2693
2698
 
2694
2699
  // src/notebook/notes.ts
2695
- import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
2700
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
2696
2701
  function canonicalJson2(value) {
2697
2702
  if (value === void 0 || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") throw new NotebookError("INVALID_INPUT", "Canonical JSON value is not JSON-compatible");
2698
2703
  if (value === null || typeof value !== "object") {
@@ -2705,7 +2710,7 @@ function canonicalJson2(value) {
2705
2710
  return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
2706
2711
  }
2707
2712
  function sha256Hex(value) {
2708
- return createHash9("sha256").update(value).digest("hex");
2713
+ return createHash10("sha256").update(value).digest("hex");
2709
2714
  }
2710
2715
  function base64UrlEncode(value) {
2711
2716
  return Buffer.from(value, "utf8").toString("base64url");
@@ -3256,32 +3261,32 @@ var init_remote_mutation_schema = __esm({
3256
3261
  });
3257
3262
 
3258
3263
  // src/notebook/state.ts
3259
- import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
3264
+ import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
3260
3265
  import {
3261
- closeSync as closeSync4,
3262
- constants as constants3,
3266
+ closeSync as closeSync5,
3267
+ constants as constants4,
3263
3268
  existsSync as existsSync13,
3264
- fchmodSync as fchmodSync3,
3265
- fstatSync,
3269
+ fchmodSync as fchmodSync4,
3270
+ fstatSync as fstatSync2,
3266
3271
  fsyncSync as fsyncSync3,
3267
- lstatSync as lstatSync13,
3268
- mkdirSync as mkdirSync8,
3269
- openSync as openSync4,
3272
+ lstatSync as lstatSync14,
3273
+ mkdirSync as mkdirSync9,
3274
+ openSync as openSync5,
3270
3275
  readSync,
3271
- readdirSync as readdirSync9,
3276
+ readdirSync as readdirSync10,
3272
3277
  renameSync as renameSync7,
3273
- unlinkSync as unlinkSync7,
3274
- writeFileSync as writeFileSync11
3278
+ unlinkSync as unlinkSync8,
3279
+ writeFileSync as writeFileSync12
3275
3280
  } from "node:fs";
3276
3281
  import { homedir as homedir8 } from "node:os";
3277
- import { basename as basename9, dirname as dirname16, join as join18, parse, relative as relative12, resolve as resolve13, sep as sep4 } from "node:path";
3282
+ import { basename as basename10, dirname as dirname16, join as join19, parse, relative as relative12, resolve as resolve13, sep as sep4 } from "node:path";
3278
3283
  function notebookStateRoot(env2 = process.env) {
3279
- const base = env2.XDG_STATE_HOME || join18(env2.HOME || homedir8(), ".local", "state");
3284
+ const base = env2.XDG_STATE_HOME || join19(env2.HOME || homedir8(), ".local", "state");
3280
3285
  return resolve13(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
3281
3286
  }
3282
3287
  function deriveSessionKey(projectSlug, client, clientSessionId) {
3283
3288
  if (!projectSlug || !client || !clientSessionId) throw new NotebookError("INVALID_INPUT", "project slug, client, and client session id are required");
3284
- return createHash10("sha256").update(`pjangler-session-v1\0${projectSlug}\0${client}\0${clientSessionId}`, "utf8").digest("hex");
3289
+ return createHash11("sha256").update(`pjangler-session-v1\0${projectSlug}\0${client}\0${clientSessionId}`, "utf8").digest("hex");
3285
3290
  }
3286
3291
  function deriveReceiptId(sessionKey) {
3287
3292
  assertDigest(sessionKey, "session key");
@@ -3295,7 +3300,7 @@ function assertDigest(value, label) {
3295
3300
  }
3296
3301
  function projectStateDir(root, projectSlug) {
3297
3302
  if (!/^[a-z0-9][a-z0-9._-]{0,127}$/iu.test(projectSlug)) throw new NotebookError("INVALID_INPUT", "Invalid project slug for Notebook state");
3298
- return join18(resolve13(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
3303
+ return join19(resolve13(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
3299
3304
  }
3300
3305
  function assertContained(root, candidate) {
3301
3306
  const rel = relative12(resolve13(root), resolve13(candidate));
@@ -3308,33 +3313,33 @@ function openPinnedDirectory(path, root, create) {
3308
3313
  assertContained(absoluteRoot, absolute);
3309
3314
  if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
3310
3315
  const parsed = parse(absolute);
3311
- let fd = openSync4(parsed.root, DIRECTORY_OPEN_FLAGS);
3316
+ let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
3312
3317
  let cursor = parsed.root;
3313
3318
  try {
3314
3319
  for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
3315
3320
  const child = `/proc/self/fd/${fd}/${part}`;
3316
- cursor = join18(cursor, part);
3321
+ cursor = join19(cursor, part);
3317
3322
  let childFd;
3318
3323
  try {
3319
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
3324
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
3320
3325
  } catch (error) {
3321
3326
  if (!create || error.code !== "ENOENT") throw error;
3322
3327
  try {
3323
- mkdirSync8(child, { mode: 448 });
3328
+ mkdirSync9(child, { mode: 448 });
3324
3329
  } catch (mkdirError) {
3325
3330
  if (mkdirError.code !== "EEXIST") throw mkdirError;
3326
3331
  }
3327
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
3332
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
3328
3333
  }
3329
- closeSync4(fd);
3334
+ closeSync5(fd);
3330
3335
  fd = childFd;
3331
- const stat = fstatSync(fd);
3336
+ const stat = fstatSync2(fd);
3332
3337
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
3333
3338
  if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
3334
3339
  }
3335
3340
  return fd;
3336
3341
  } catch (error) {
3337
- closeSync4(fd);
3342
+ closeSync5(fd);
3338
3343
  if (error.code === "ELOOP" || error.code === "ENOTDIR") {
3339
3344
  throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
3340
3345
  }
@@ -3342,7 +3347,7 @@ function openPinnedDirectory(path, root, create) {
3342
3347
  }
3343
3348
  }
3344
3349
  function pinnedLeaf(parentFd, path) {
3345
- return `/proc/self/fd/${parentFd}/${basename9(path)}`;
3350
+ return `/proc/self/fd/${parentFd}/${basename10(path)}`;
3346
3351
  }
3347
3352
  function assertOwned(stat) {
3348
3353
  if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
@@ -3355,13 +3360,13 @@ function ensureDirectory(path, root) {
3355
3360
  assertContained(absoluteRoot, absolute);
3356
3361
  const fd = openPinnedDirectory(absolute, absoluteRoot, true);
3357
3362
  try {
3358
- const stat = fstatSync(fd);
3363
+ const stat = fstatSync2(fd);
3359
3364
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
3360
3365
  assertOwned(stat);
3361
- fchmodSync3(fd, 448);
3366
+ fchmodSync4(fd, 448);
3362
3367
  fsyncSync3(fd);
3363
3368
  } finally {
3364
- closeSync4(fd);
3369
+ closeSync5(fd);
3365
3370
  }
3366
3371
  }
3367
3372
  function notebookStatePaths(root, projectSlug) {
@@ -3370,18 +3375,18 @@ function notebookStatePaths(root, projectSlug) {
3370
3375
  return {
3371
3376
  root: absoluteRoot,
3372
3377
  project,
3373
- baselines: join18(project, "baselines"),
3374
- claims: join18(project, "claims"),
3375
- receipts: join18(project, "receipts"),
3376
- refusals: join18(project, "refusals"),
3377
- journals: join18(project, "journals"),
3378
- locks: join18(project, "locks")
3378
+ baselines: join19(project, "baselines"),
3379
+ claims: join19(project, "claims"),
3380
+ receipts: join19(project, "receipts"),
3381
+ refusals: join19(project, "refusals"),
3382
+ journals: join19(project, "journals"),
3383
+ locks: join19(project, "locks")
3379
3384
  };
3380
3385
  }
3381
3386
  function ensureNotebookState(root, projectSlug) {
3382
3387
  const paths = notebookStatePaths(root, projectSlug);
3383
3388
  const { root: absoluteRoot, project } = paths;
3384
- for (const path of [absoluteRoot, join18(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
3389
+ for (const path of [absoluteRoot, join19(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
3385
3390
  ensureDirectory(path, absoluteRoot);
3386
3391
  }
3387
3392
  return paths;
@@ -3397,15 +3402,15 @@ function fsyncDirectory2(path) {
3397
3402
  fsyncSync3(fd);
3398
3403
  } catch {
3399
3404
  } finally {
3400
- if (fd !== void 0) closeSync4(fd);
3405
+ if (fd !== void 0) closeSync5(fd);
3401
3406
  }
3402
3407
  }
3403
3408
  function readStateDirectory(path, root) {
3404
3409
  const fd = openPinnedDirectory(path, root, false);
3405
3410
  try {
3406
- return readdirSync9(`/proc/self/fd/${fd}`, { withFileTypes: true });
3411
+ return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
3407
3412
  } finally {
3408
- closeSync4(fd);
3413
+ closeSync5(fd);
3409
3414
  }
3410
3415
  }
3411
3416
  function readNotebookStateDirectory(path, root) {
@@ -3419,22 +3424,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
3419
3424
  let fileFd;
3420
3425
  try {
3421
3426
  try {
3422
- fileFd = openSync4(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3427
+ fileFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3423
3428
  } catch (error) {
3424
3429
  if (allowMissing && error.code === "ENOENT") return false;
3425
3430
  throw error;
3426
3431
  }
3427
- const stat = fstatSync(fileFd);
3432
+ const stat = fstatSync2(fileFd);
3428
3433
  if (!stat.isFile() || (stat.mode & 511) !== 384) {
3429
3434
  throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
3430
3435
  }
3431
3436
  assertOwned(stat);
3432
- unlinkSync7(target);
3437
+ unlinkSync8(target);
3433
3438
  fsyncSync3(parentFd);
3434
3439
  return true;
3435
3440
  } finally {
3436
- if (fileFd !== void 0) closeSync4(fileFd);
3437
- closeSync4(parentFd);
3441
+ if (fileFd !== void 0) closeSync5(fileFd);
3442
+ closeSync5(parentFd);
3438
3443
  }
3439
3444
  }
3440
3445
  function renameStateFile(source, target, root) {
@@ -3444,12 +3449,12 @@ function renameStateFile(source, target, root) {
3444
3449
  const targetPath = pinnedLeaf(parentFd, target);
3445
3450
  let sourceFd;
3446
3451
  try {
3447
- sourceFd = openSync4(sourcePath, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3448
- const stat = fstatSync(sourceFd);
3452
+ sourceFd = openSync5(sourcePath, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3453
+ const stat = fstatSync2(sourceFd);
3449
3454
  if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
3450
3455
  assertOwned(stat);
3451
3456
  try {
3452
- lstatSync13(targetPath);
3457
+ lstatSync14(targetPath);
3453
3458
  throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
3454
3459
  } catch (error) {
3455
3460
  if (error.code !== "ENOENT") throw error;
@@ -3457,8 +3462,8 @@ function renameStateFile(source, target, root) {
3457
3462
  renameSync7(sourcePath, targetPath);
3458
3463
  fsyncSync3(parentFd);
3459
3464
  } finally {
3460
- if (sourceFd !== void 0) closeSync4(sourceFd);
3461
- closeSync4(parentFd);
3465
+ if (sourceFd !== void 0) closeSync5(sourceFd);
3466
+ closeSync5(parentFd);
3462
3467
  }
3463
3468
  }
3464
3469
  function atomicWriteJson(path, value, root, afterParentPinned) {
@@ -3467,34 +3472,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
3467
3472
  const text2 = jsonLine(value);
3468
3473
  const parentFd = openPinnedDirectory(dirname16(path), root, false);
3469
3474
  const target = pinnedLeaf(parentFd, path);
3470
- const tempName = `.${basename9(path)}.${process.pid}.${randomUUID3()}.tmp`;
3475
+ const tempName = `.${basename10(path)}.${process.pid}.${randomUUID3()}.tmp`;
3471
3476
  const temp = `/proc/self/fd/${parentFd}/${tempName}`;
3472
3477
  let existingIdentity = null;
3473
3478
  try {
3474
3479
  afterParentPinned?.();
3475
3480
  try {
3476
- const existingFd = openSync4(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3481
+ const existingFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3477
3482
  try {
3478
- const existing = fstatSync(existingFd);
3483
+ const existing = fstatSync2(existingFd);
3479
3484
  if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
3480
3485
  assertOwned(existing);
3481
3486
  if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
3482
3487
  existingIdentity = { dev: existing.dev, ino: existing.ino };
3483
3488
  } finally {
3484
- closeSync4(existingFd);
3489
+ closeSync5(existingFd);
3485
3490
  }
3486
3491
  } catch (error) {
3487
3492
  if (error.code !== "ENOENT") throw error;
3488
3493
  }
3489
- const fd = openSync4(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
3494
+ const fd = openSync5(temp, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
3490
3495
  try {
3491
- writeFileSync11(fd, text2, "utf8");
3496
+ writeFileSync12(fd, text2, "utf8");
3492
3497
  fsyncSync3(fd);
3493
3498
  } finally {
3494
- closeSync4(fd);
3499
+ closeSync5(fd);
3495
3500
  }
3496
3501
  try {
3497
- const current = lstatSync13(target);
3502
+ const current = lstatSync14(target);
3498
3503
  if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
3499
3504
  throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
3500
3505
  }
@@ -3505,12 +3510,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
3505
3510
  fsyncSync3(parentFd);
3506
3511
  } catch (error) {
3507
3512
  try {
3508
- unlinkSync7(temp);
3513
+ unlinkSync8(temp);
3509
3514
  } catch {
3510
3515
  }
3511
3516
  throw error;
3512
3517
  } finally {
3513
- closeSync4(parentFd);
3518
+ closeSync5(parentFd);
3514
3519
  }
3515
3520
  return Buffer.byteLength(text2, "utf8");
3516
3521
  }
@@ -3520,22 +3525,22 @@ function exclusiveWrite(path, text2, root) {
3520
3525
  const target = pinnedLeaf(parentFd, path);
3521
3526
  let fd;
3522
3527
  try {
3523
- fd = openSync4(target, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
3528
+ fd = openSync5(target, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
3524
3529
  } catch (error) {
3525
- closeSync4(parentFd);
3530
+ closeSync5(parentFd);
3526
3531
  if (error.code === "EEXIST") return false;
3527
3532
  throw error;
3528
3533
  }
3529
3534
  try {
3530
- writeFileSync11(fd, text2, "utf8");
3535
+ writeFileSync12(fd, text2, "utf8");
3531
3536
  fsyncSync3(fd);
3532
3537
  } finally {
3533
- closeSync4(fd);
3538
+ closeSync5(fd);
3534
3539
  }
3535
3540
  try {
3536
3541
  fsyncSync3(parentFd);
3537
3542
  } finally {
3538
- closeSync4(parentFd);
3543
+ closeSync5(parentFd);
3539
3544
  }
3540
3545
  return true;
3541
3546
  }
@@ -3679,13 +3684,13 @@ function safeReadJson(path, maxBytes) {
3679
3684
  }
3680
3685
  let fd;
3681
3686
  try {
3682
- fd = openSync4(pinnedLeaf(parentFd, path), constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3687
+ fd = openSync5(pinnedLeaf(parentFd, path), constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3683
3688
  } catch (error) {
3684
- closeSync4(parentFd);
3689
+ closeSync5(parentFd);
3685
3690
  return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
3686
3691
  }
3687
3692
  try {
3688
- const stat = fstatSync(fd);
3693
+ const stat = fstatSync2(fd);
3689
3694
  if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
3690
3695
  if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
3691
3696
  try {
@@ -3705,14 +3710,14 @@ function safeReadJson(path, maxBytes) {
3705
3710
  if (total > maxBytes) return { reason: "oversize", bytes: total };
3706
3711
  chunks.push(chunk.subarray(0, count));
3707
3712
  }
3708
- const after = fstatSync(fd);
3713
+ const after = fstatSync2(fd);
3709
3714
  if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
3710
3715
  return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
3711
3716
  } catch (error) {
3712
3717
  return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
3713
3718
  } finally {
3714
- closeSync4(fd);
3715
- closeSync4(parentFd);
3719
+ closeSync5(fd);
3720
+ closeSync5(parentFd);
3716
3721
  }
3717
3722
  }
3718
3723
  function safeEntryId(kind, name) {
@@ -3729,7 +3734,7 @@ function baselineReceiptByteCeiling(limits) {
3729
3734
  function createSessionBaseline(root, input) {
3730
3735
  assertDigest(input.session_key, "session key");
3731
3736
  const paths = ensureNotebookState(root, input.project_slug);
3732
- const path = join18(paths.baselines, `${input.session_key}.json`);
3737
+ const path = join19(paths.baselines, `${input.session_key}.json`);
3733
3738
  const maxBytes = baselineReceiptByteCeiling(input.limits);
3734
3739
  const existing = readBaseline(path, maxBytes);
3735
3740
  if (existing) return { baseline: existing, created: false };
@@ -3770,7 +3775,7 @@ function createSessionBaseline(root, input) {
3770
3775
  function createOverviewClaim(root, input) {
3771
3776
  assertDigest(input.session_key, "session key");
3772
3777
  const paths = ensureNotebookState(root, input.project_slug);
3773
- const path = join18(paths.claims, `${input.session_key}.overview`);
3778
+ const path = join19(paths.claims, `${input.session_key}.overview`);
3774
3779
  if (existsSync13(path)) {
3775
3780
  const read = safeReadJson(path, 65536);
3776
3781
  const existing = read.value === void 0 ? null : parseClaim(read.value);
@@ -3789,7 +3794,7 @@ function createOverviewClaim(root, input) {
3789
3794
  function readOverviewClaim(root, projectSlug, sessionKey) {
3790
3795
  assertDigest(sessionKey, "session key");
3791
3796
  const paths = notebookStatePaths(root, projectSlug);
3792
- const path = join18(paths.claims, `${sessionKey}.overview`);
3797
+ const path = join19(paths.claims, `${sessionKey}.overview`);
3793
3798
  if (!existsSync13(path)) return null;
3794
3799
  const read = safeReadJson(path, 65536);
3795
3800
  return read.value === void 0 ? null : parseClaim(read.value);
@@ -3797,10 +3802,10 @@ function readOverviewClaim(root, projectSlug, sessionKey) {
3797
3802
  function readSessionBaseline(root, projectSlug, sessionKey, limits) {
3798
3803
  assertDigest(sessionKey, "session key");
3799
3804
  const paths = ensureNotebookState(root, projectSlug);
3800
- return readBaseline(join18(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3805
+ return readBaseline(join19(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3801
3806
  }
3802
3807
  function acquireLock(paths, maxWaitMs) {
3803
- const lock = join18(paths.locks, "admission.lock");
3808
+ const lock = join19(paths.locks, "admission.lock");
3804
3809
  const deadline = Date.now() + Math.max(1, maxWaitMs);
3805
3810
  const token = randomUUID3();
3806
3811
  const record3 = () => ({
@@ -3830,7 +3835,7 @@ function acquireLock(paths, maxWaitMs) {
3830
3835
  throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
3831
3836
  }
3832
3837
  if (Date.now() >= expiresAt) {
3833
- const recovery = join18(paths.locks, `recovery-${heldToken}.lock`);
3838
+ const recovery = join19(paths.locks, `recovery-${heldToken}.lock`);
3834
3839
  const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
3835
3840
  if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
3836
3841
  try {
@@ -3838,7 +3843,7 @@ function acquireLock(paths, maxWaitMs) {
3838
3843
  const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
3839
3844
  const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
3840
3845
  if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
3841
- const recovered = join18(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3846
+ const recovered = join19(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3842
3847
  renameStateFile(lock, recovered, paths.root);
3843
3848
  unlinkStateFile(recovered, paths.root);
3844
3849
  }
@@ -3918,7 +3923,7 @@ function scanAuxiliaryState(paths, limits) {
3918
3923
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3919
3924
  continue;
3920
3925
  }
3921
- const read = safeReadJson(join18(specification.dir, entry.name), specification.maxBytes);
3926
+ const read = safeReadJson(join19(specification.dir, entry.name), specification.maxBytes);
3922
3927
  if (read.reason || read.value === void 0) {
3923
3928
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3924
3929
  continue;
@@ -3943,7 +3948,7 @@ function scanAuxiliaryState(paths, limits) {
3943
3948
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3944
3949
  continue;
3945
3950
  }
3946
- const read = safeReadJson(join18(paths.journals, entry.name), limits.receipt_max_bytes);
3951
+ const read = safeReadJson(join19(paths.journals, entry.name), limits.receipt_max_bytes);
3947
3952
  if (read.reason || read.value === void 0) {
3948
3953
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3949
3954
  continue;
@@ -3972,7 +3977,7 @@ function scanAuxiliaryState(paths, limits) {
3972
3977
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3973
3978
  continue;
3974
3979
  }
3975
- const read = safeReadJson(join18(paths.locks, entry.name), 8192);
3980
+ const read = safeReadJson(join19(paths.locks, entry.name), 8192);
3976
3981
  if (read.reason || read.value === void 0) {
3977
3982
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3978
3983
  continue;
@@ -4011,7 +4016,7 @@ function scanReceipts(paths, limits) {
4011
4016
  }
4012
4017
  for (const entry of entries) {
4013
4018
  const entryId = safeEntryId("receipts", entry.name);
4014
- const path = join18(paths.receipts, entry.name);
4019
+ const path = join19(paths.receipts, entry.name);
4015
4020
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
4016
4021
  addIntegrity({ entry_id: entryId, reason: "non-regular" });
4017
4022
  continue;
@@ -4041,7 +4046,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
4041
4046
  if (!existsSync13(paths.baselines)) return { current, stale };
4042
4047
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
4043
4048
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4044
- const baseline = readBaseline(join18(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
4049
+ const baseline = readBaseline(join19(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
4045
4050
  if (!baseline || referenced.has(baseline.session_key)) continue;
4046
4051
  const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
4047
4052
  if (nowMs >= expires) stale += 1;
@@ -4055,7 +4060,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
4055
4060
  const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
4056
4061
  for (const entry of entries) {
4057
4062
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4058
- const read = safeReadJson(join18(paths.refusals, entry.name), limits.receipt_max_bytes);
4063
+ const read = safeReadJson(join19(paths.refusals, entry.name), limits.receipt_max_bytes);
4059
4064
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
4060
4065
  if (!marker || `${marker.session_key}.json` !== entry.name) continue;
4061
4066
  if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
@@ -4151,7 +4156,7 @@ function recoveryActions(receiptId, repoPath) {
4151
4156
  ];
4152
4157
  }
4153
4158
  function removeValidRefusal(paths, sessionKey, limits) {
4154
- const path = join18(paths.refusals, `${sessionKey}.json`);
4159
+ const path = join19(paths.refusals, `${sessionKey}.json`);
4155
4160
  if (!existsSync13(path)) return;
4156
4161
  const read = safeReadJson(path, limits.receipt_max_bytes);
4157
4162
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
@@ -4165,7 +4170,7 @@ function admitCaptureReceipt(input) {
4165
4170
  const release = acquireLock(paths, Math.min(100, input.limits.hook_session_end_timeout_ms));
4166
4171
  try {
4167
4172
  const receiptId = deriveReceiptId(input.sessionKey);
4168
- const receiptPath = join18(paths.receipts, `${receiptId}.json`);
4173
+ const receiptPath = join19(paths.receipts, `${receiptId}.json`);
4169
4174
  if (existsSync13(receiptPath)) {
4170
4175
  const read = safeReadJson(receiptPath, input.limits.receipt_max_bytes);
4171
4176
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -4177,15 +4182,15 @@ function admitCaptureReceipt(input) {
4177
4182
  const scanBefore = scanReceipts(paths, input.limits);
4178
4183
  const auxiliaryBefore = scanAuxiliaryState(paths, input.limits);
4179
4184
  const referencedBefore = /* @__PURE__ */ new Set([...scanBefore.referencedSessions, ...auxiliaryBefore.referencedSessions]);
4180
- const baselinePath = join18(paths.baselines, `${input.sessionKey}.json`);
4185
+ const baselinePath = join19(paths.baselines, `${input.sessionKey}.json`);
4181
4186
  let baseline = readBaseline(baselinePath, baselineReceiptByteCeiling(input.limits));
4182
4187
  if (baseline) {
4183
4188
  const expired = now.getTime() >= Date.parse(baseline.created_at) + input.limits.receiptless_session_retention_seconds * 1e3;
4184
4189
  if (expired && !referencedBefore.has(input.sessionKey) && scanBefore.integrityCount === 0 && auxiliaryBefore.integrityCount === 0) {
4185
4190
  unlinkStateFile(baselinePath, paths.root);
4186
- const claimPath = join18(paths.claims, `${input.sessionKey}.overview`);
4191
+ const claimPath = join19(paths.claims, `${input.sessionKey}.overview`);
4187
4192
  unlinkStateFile(claimPath, paths.root, true);
4188
- const refusalPath = join18(paths.refusals, `${input.sessionKey}.json`);
4193
+ const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
4189
4194
  unlinkStateFile(refusalPath, paths.root, true);
4190
4195
  baseline = null;
4191
4196
  }
@@ -4214,7 +4219,7 @@ function admitCaptureReceipt(input) {
4214
4219
  const byteBlocked = summary.unresolved_bytes + candidate.bytes > input.limits.unresolved_receipt_max_bytes;
4215
4220
  if (countBlocked || byteBlocked) {
4216
4221
  const actions = recoveryActions(receiptId, input.repoPath);
4217
- const refusalPath = join18(paths.refusals, `${input.sessionKey}.json`);
4222
+ const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
4218
4223
  const oldRead = existsSync13(refusalPath) ? safeReadJson(refusalPath, input.limits.receipt_max_bytes) : void 0;
4219
4224
  const old = oldRead?.value === void 0 ? null : parseRefusal(oldRead.value);
4220
4225
  const marker = {
@@ -4283,7 +4288,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
4283
4288
  function readCaptureReceipt(root, projectSlug, receiptId, limits) {
4284
4289
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
4285
4290
  const paths = notebookStatePaths(root, projectSlug);
4286
- const path = join18(paths.receipts, `${receiptId}.json`);
4291
+ const path = join19(paths.receipts, `${receiptId}.json`);
4287
4292
  if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
4288
4293
  const read = safeReadJson(path, limits.receipt_max_bytes);
4289
4294
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -4296,7 +4301,7 @@ function writeReceipt(paths, receipt, limit) {
4296
4301
  const bytes = Buffer.byteLength(text2, "utf8");
4297
4302
  if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
4298
4303
  if (receipt.serialized_bytes === bytes) {
4299
- atomicWriteJson(join18(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
4304
+ atomicWriteJson(join19(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
4300
4305
  return receipt;
4301
4306
  }
4302
4307
  receipt.serialized_bytes = bytes;
@@ -4435,25 +4440,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
4435
4440
  const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
4436
4441
  for (const receipt of scan.receipts) {
4437
4442
  if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
4438
- const path = join18(paths.receipts, `${receipt.receipt_id}.json`);
4443
+ const path = join19(paths.receipts, `${receipt.receipt_id}.json`);
4439
4444
  unlinkStateFile(path, paths.root);
4440
- removed.push(safeEntryId("receipts", basename9(path)));
4445
+ removed.push(safeEntryId("receipts", basename10(path)));
4441
4446
  }
4442
4447
  const remaining = scanReceipts(paths, limits);
4443
4448
  const remainingAuxiliary = scanAuxiliaryState(paths, limits);
4444
4449
  const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
4445
4450
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
4446
4451
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4447
- const path = join18(paths.baselines, entry.name);
4452
+ const path = join19(paths.baselines, entry.name);
4448
4453
  const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
4449
4454
  if (!baseline || referenced.has(baseline.session_key)) continue;
4450
4455
  if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
4451
4456
  unlinkStateFile(path, paths.root);
4452
4457
  removed.push(safeEntryId("baselines", entry.name));
4453
- const claim = join18(paths.claims, `${baseline.session_key}.overview`);
4454
- if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename9(claim)));
4455
- const refusal = join18(paths.refusals, `${baseline.session_key}.json`);
4456
- if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename9(refusal)));
4458
+ const claim = join19(paths.claims, `${baseline.session_key}.overview`);
4459
+ if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename10(claim)));
4460
+ const refusal = join19(paths.refusals, `${baseline.session_key}.json`);
4461
+ if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename10(refusal)));
4457
4462
  }
4458
4463
  fsyncDirectory2(paths.project);
4459
4464
  return removed;
@@ -4476,7 +4481,7 @@ function currentRetentionPressure(summary) {
4476
4481
  }
4477
4482
  function statePathForReceipt(root, projectSlug, receiptId) {
4478
4483
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
4479
- return join18(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
4484
+ return join19(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
4480
4485
  }
4481
4486
  var NOTEBOOK_STATE_VERSION, SESSION_KEY_RE, RECEIPT_ID_RE, GIT_OBJECT_RE, NOTEBOOK_ERROR_CODES, DIRECTORY_OPEN_FLAGS;
4482
4487
  var init_state = __esm({
@@ -4503,7 +4508,7 @@ var init_state = __esm({
4503
4508
  "REMOTE_PROTOCOL_ERROR",
4504
4509
  "INTERNAL_ERROR"
4505
4510
  ]);
4506
- DIRECTORY_OPEN_FLAGS = constants3.O_RDONLY | (constants3.O_DIRECTORY ?? 0) | (constants3.O_NOFOLLOW ?? 0);
4511
+ DIRECTORY_OPEN_FLAGS = constants4.O_RDONLY | (constants4.O_DIRECTORY ?? 0) | (constants4.O_NOFOLLOW ?? 0);
4507
4512
  }
4508
4513
  });
4509
4514
 
@@ -4842,8 +4847,8 @@ var init_open_notebook_client = __esm({
4842
4847
 
4843
4848
  // src/notebook/git-evidence.ts
4844
4849
  import { spawnSync as spawnSync9 } from "node:child_process";
4845
- import { closeSync as closeSync5, constants as constants4, fstatSync as fstatSync2, lstatSync as lstatSync14, openSync as openSync5, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
4846
- import { extname, join as join20, relative as relative13, resolve as resolve14, sep as sep5 } from "node:path";
4850
+ import { closeSync as closeSync6, constants as constants5, fstatSync as fstatSync3, lstatSync as lstatSync15, openSync as openSync6, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
4851
+ import { extname, join as join21, relative as relative13, resolve as resolve14, sep as sep5 } from "node:path";
4847
4852
  function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
4848
4853
  const result2 = spawnSync9("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
4849
4854
  return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
@@ -4921,9 +4926,9 @@ function hasSymlinkComponent(root, candidate) {
4921
4926
  const rel = relative13(root, candidate);
4922
4927
  let cursor = root;
4923
4928
  for (const part of rel.split(sep5).filter(Boolean)) {
4924
- cursor = join20(cursor, part);
4929
+ cursor = join21(cursor, part);
4925
4930
  try {
4926
- if (lstatSync14(cursor).isSymbolicLink()) return true;
4931
+ if (lstatSync15(cursor).isSymbolicLink()) return true;
4927
4932
  } catch {
4928
4933
  return true;
4929
4934
  }
@@ -4935,12 +4940,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4935
4940
  if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
4936
4941
  let fd;
4937
4942
  try {
4938
- fd = openSync5(safe.candidate, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
4943
+ fd = openSync6(safe.candidate, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
4939
4944
  } catch {
4940
4945
  return { status: "excluded", reason: "not-regular" };
4941
4946
  }
4942
4947
  try {
4943
- const before = fstatSync2(fd);
4948
+ const before = fstatSync3(fd);
4944
4949
  if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
4945
4950
  if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
4946
4951
  let physical;
@@ -4961,7 +4966,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4961
4966
  if (total > maxBytes) return { status: "excluded", reason: "oversize" };
4962
4967
  chunks.push(chunk.subarray(0, count));
4963
4968
  }
4964
- const after = fstatSync2(fd);
4969
+ const after = fstatSync3(fd);
4965
4970
  if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
4966
4971
  const bytes = Buffer.concat(chunks, total);
4967
4972
  if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
@@ -4974,7 +4979,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4974
4979
  if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
4975
4980
  return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
4976
4981
  } finally {
4977
- closeSync5(fd);
4982
+ closeSync6(fd);
4978
4983
  }
4979
4984
  }
4980
4985
  function looksGenerated(path) {
@@ -5064,10 +5069,10 @@ var init_git_evidence = __esm({
5064
5069
 
5065
5070
  // src/notebook/remote-mutation-journal.ts
5066
5071
  import { randomUUID as randomUUID4 } from "node:crypto";
5067
- import { join as join21 } from "node:path";
5072
+ import { join as join22 } from "node:path";
5068
5073
  function journalPath(root, projectSlug, operationId) {
5069
5074
  if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
5070
- return join21(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
5075
+ return join22(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
5071
5076
  }
5072
5077
  function remoteMutationJournalPath(root, projectSlug, operationId) {
5073
5078
  return journalPath(root, projectSlug, operationId);
@@ -5087,7 +5092,7 @@ function listRemoteMutationJournals(root, projectSlug) {
5087
5092
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
5088
5093
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
5089
5094
  }
5090
- const read = readNotebookStateJson(join21(paths.journals, entry.name), paths.root, 65536);
5095
+ const read = readNotebookStateJson(join22(paths.journals, entry.name), paths.root, 65536);
5091
5096
  const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
5092
5097
  if (!journal || `${journal.operation_id}.json` !== entry.name) {
5093
5098
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
@@ -5441,7 +5446,7 @@ var init_reconcile = __esm({
5441
5446
 
5442
5447
  // src/notebook/entropy.ts
5443
5448
  import { existsSync as existsSync16 } from "node:fs";
5444
- import { createHash as createHash13 } from "node:crypto";
5449
+ import { createHash as createHash14 } from "node:crypto";
5445
5450
  import { resolve as resolve17 } from "node:path";
5446
5451
  import { spawnSync as spawnSync12 } from "node:child_process";
5447
5452
  function listAllTrackedDocuments(config) {
@@ -5496,7 +5501,7 @@ async function compactSessionCaptures(input) {
5496
5501
  const toCompact = sessionHeaders.slice(maxRecent);
5497
5502
  const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
5498
5503
  const archiveTitle = "Session Captures Archive (Prior Sessions)";
5499
- const slugHash = createHash13("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
5504
+ const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
5500
5505
  const archiveLogicalId = `user-note:v1:${slugHash}`;
5501
5506
  let existingArchiveContent = "";
5502
5507
  let existingArchiveId = null;
@@ -6064,8 +6069,8 @@ var init_capture = __esm({
6064
6069
 
6065
6070
  // src/index.ts
6066
6071
  import { spawnSync as spawnSync18 } from "node:child_process";
6067
- import { existsSync as existsSync26, readdirSync as readdirSync14, readFileSync as readFileSync32, statSync as statSync9 } from "node:fs";
6068
- import { basename as basename12, join as join37, resolve as resolve25 } from "node:path";
6072
+ import { existsSync as existsSync26, readdirSync as readdirSync15, readFileSync as readFileSync33, statSync as statSync9 } from "node:fs";
6073
+ import { basename as basename13, join as join38, resolve as resolve25 } from "node:path";
6069
6074
  import { Command as Command3, CommanderError, Option } from "commander";
6070
6075
 
6071
6076
  // src/recipes/Recipe.ts
@@ -6128,6 +6133,15 @@ function isHostScoped(finding2) {
6128
6133
  function gatesProject(finding2) {
6129
6134
  return finding2.status === "fail" && !isHostScoped(finding2);
6130
6135
  }
6136
+ var FINDING_DETAIL_LIMIT = 20;
6137
+ function describeFinding(finding2) {
6138
+ const details = (finding2.details ?? []).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
6139
+ if (details.length === 0) return `${finding2.id}: ${finding2.summary}`;
6140
+ const shown = details.slice(0, FINDING_DETAIL_LIMIT).map((line) => ` - ${line.replace(/\r?\n/g, "\n ")}`);
6141
+ const omitted = details.length - shown.length;
6142
+ if (omitted > 0) shown.push(` - (+${omitted} more; run pj audit --rules ${finding2.id} for the full list)`);
6143
+ return [`${finding2.id}: ${finding2.summary}`, ...shown].join("\n");
6144
+ }
6131
6145
 
6132
6146
  // src/recipes/Recipe.ts
6133
6147
  function commandStatus(result2, dryRun) {
@@ -6186,7 +6200,7 @@ var Recipe = class {
6186
6200
  }
6187
6201
  if (!finding2.fixable) {
6188
6202
  phases.push({ id: check.id, status: "failed", changedFiles: [], message: finding2.summary });
6189
- errors.push(`${check.id}: ${finding2.summary}`);
6203
+ errors.push(describeFinding({ id: check.id, summary: finding2.summary, details: finding2.details }));
6190
6204
  break;
6191
6205
  }
6192
6206
  const migrated = await check.migrate(ctx, finding2);
@@ -6196,7 +6210,7 @@ var Recipe = class {
6196
6210
  logs.push(`${check.id}: ${migrated.summary}`);
6197
6211
  changedFiles.push(...actualChanges);
6198
6212
  if (status === "failed") {
6199
- errors.push(`${check.id}: ${migrated.summary}`);
6213
+ errors.push(describeFinding({ id: check.id, summary: migrated.summary, details: migrated.details }));
6200
6214
  break;
6201
6215
  }
6202
6216
  if (!ctx.dryRun) {
@@ -7636,7 +7650,11 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
7636
7650
 
7637
7651
  // src/parity/skill-roots.ts
7638
7652
  var CANONICAL_CLI_SKILLS_ALIAS = "../.agents/skills";
7639
- var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.map((cli) => cli.skillsRoot);
7653
+ var SKILLEX_ONLY_ALIASES_AFTER = { ".kimi-code/skills": ".kimi/skills" };
7654
+ var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.flatMap((cli) => {
7655
+ const follower = SKILLEX_ONLY_ALIASES_AFTER[cli.skillsRoot];
7656
+ return follower ? [cli.skillsRoot, follower] : [cli.skillsRoot];
7657
+ });
7640
7658
  var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
7641
7659
  function lstatOrUndefined(path) {
7642
7660
  try {
@@ -8042,20 +8060,20 @@ function planSkillRoots(repoRoot, options = {}) {
8042
8060
  for (const name of readdirSync6(real).sort()) {
8043
8061
  const path = join7(real, name);
8044
8062
  const label = `${alias}/${name}`;
8045
- const entryStat = lstatSync6(path);
8063
+ const entryStat2 = lstatSync6(path);
8046
8064
  const rows = tracked.get(name) ?? [];
8047
- const candidate = { alias, name, path, label, kind: entryStat.isSymbolicLink() ? "link" : entryStat.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
8065
+ const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
8048
8066
  if (name === ".git") {
8049
8067
  candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
8050
- } else if (entryStat.isSymbolicLink()) {
8068
+ } else if (entryStat2.isSymbolicLink()) {
8051
8069
  candidate.text = readlinkSync5(path);
8052
8070
  candidate.resolution = resolveIn(diskLayout, path);
8053
8071
  if (rows.length) candidate.staticBlock = `${label} -> ${candidate.text} is tracked in git; a tracked link cannot be attested BMAD installer output, so it is never untracked, moved or dropped`;
8054
- } else if (!entryStat.isDirectory() && !entryStat.isFile()) {
8072
+ } else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
8055
8073
  candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
8056
8074
  } else {
8057
8075
  candidate.staticBlock = entryHazard(path, label);
8058
- if (!candidate.staticBlock && rootDevice !== void 0 && entryStat.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
8076
+ if (!candidate.staticBlock && rootDevice !== void 0 && entryStat2.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
8059
8077
  const linkRow = rows.find((row) => row.mode === "120000");
8060
8078
  const gitlink = rows.find((row) => row.mode === "160000");
8061
8079
  if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
@@ -8812,7 +8830,7 @@ function readHandoffRecord(ctx) {
8812
8830
  }
8813
8831
  function writeHandoffRecord(ctx, names) {
8814
8832
  const path = handoffRecordPath(ctx);
8815
- mkdirSync5(dirname7(path), { recursive: true });
8833
+ mkdirSync5(dirname7(path), { recursive: true, mode: 448 });
8816
8834
  const temporary = `${path}.${process.pid}.tmp`;
8817
8835
  writeFileSync5(temporary, `${JSON.stringify({ project: realpathOf(ctx.repoRoot) ?? resolve5(ctx.repoRoot), excluded: [...new Set(names)].sort() }, null, 2)}
8818
8836
  `);
@@ -10031,14 +10049,324 @@ function createG33CompanionCheck() {
10031
10049
  }
10032
10050
 
10033
10051
  // src/parity/rules.ts
10034
- import { existsSync as existsSync8, lstatSync as lstatSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readlinkSync as readlinkSync8, readdirSync as readdirSync7, realpathSync as realpathSync4, renameSync as renameSync4, symlinkSync as symlinkSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8, chmodSync as chmodSync3, copyFileSync, rmSync as rmSync5 } from "node:fs";
10035
- import { basename as basename4, dirname as dirname9, join as join11, relative as relative9, resolve as resolve7 } from "node:path";
10052
+ import { existsSync as existsSync8, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readlinkSync as readlinkSync8, readdirSync as readdirSync8, realpathSync as realpathSync4, renameSync as renameSync4, symlinkSync as symlinkSync3, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9, chmodSync as chmodSync3, copyFileSync, rmSync as rmSync5 } from "node:fs";
10053
+
10054
+ // src/bmad/setup-scripts.ts
10055
+ import { createHash as createHash8 } from "node:crypto";
10056
+ import { closeSync as closeSync2, fchmodSync, fstatSync, linkSync, lstatSync as lstatSync9, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync10, readdirSync as readdirSync7, rmdirSync as rmdirSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7, constants as constants2 } from "node:fs";
10057
+ import { basename as basename4, join as join10 } from "node:path";
10058
+ function entryStat(path) {
10059
+ try {
10060
+ return lstatSync9(path);
10061
+ } catch (error) {
10062
+ if (error.code === "ENOENT") return void 0;
10063
+ throw error;
10064
+ }
10065
+ }
10066
+ var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
10067
+ function manifestRows(text2) {
10068
+ const rows = [];
10069
+ let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
10070
+ for (let i = 0; i < text2.length; i++) {
10071
+ const c = text2[i];
10072
+ if (inQuotes) {
10073
+ if (c === '"' && text2[i + 1] === '"') {
10074
+ field2 += '"';
10075
+ i++;
10076
+ } else if (c === '"') {
10077
+ inQuotes = false;
10078
+ closed = true;
10079
+ } else field2 += c;
10080
+ } else if (c === '"' && !field2 && !closed) {
10081
+ inQuotes = true;
10082
+ quoted = true;
10083
+ } else if (c === ",") {
10084
+ row.push(field2);
10085
+ field2 = "";
10086
+ quoted = closed = false;
10087
+ } else if (c === "\n" || c === "\r") {
10088
+ if (c === "\r" && text2[i + 1] === "\n") i++;
10089
+ row.push(field2);
10090
+ if (row.length !== 1 || row[0]) rows.push(row);
10091
+ row = [];
10092
+ field2 = "";
10093
+ quoted = closed = false;
10094
+ } else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
10095
+ else field2 += c;
10096
+ }
10097
+ if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
10098
+ if (row.length || field2 || closed) {
10099
+ row.push(field2);
10100
+ rows.push(row);
10101
+ }
10102
+ if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
10103
+ throw new Error("Invalid BMAD file manifest columns");
10104
+ }
10105
+ return rows.slice(1);
10106
+ }
10107
+ function sameIdentity(a, b) {
10108
+ return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
10109
+ }
10110
+ var DeclinableError = class extends Error {
10111
+ constructor(message2, kind) {
10112
+ super(message2);
10113
+ this.kind = kind;
10114
+ }
10115
+ };
10116
+ var FIELD_SEP = String.fromCharCode(0);
10117
+ var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
10118
+ function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
10119
+ const bmadDir = join10(repoRoot, "_bmad");
10120
+ const dest = join10(bmadDir, "scripts");
10121
+ try {
10122
+ const bmad = entryStat(bmadDir);
10123
+ if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
10124
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
10125
+ const destStat = entryStat(dest);
10126
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
10127
+ const missing = [];
10128
+ for (const name of RUNTIME_SCRIPTS) {
10129
+ const existing = destStat ? entryStat(join10(dest, name)) : void 0;
10130
+ if (!existing) missing.push(name);
10131
+ else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
10132
+ }
10133
+ if (!missing.length) return { state: "present" };
10134
+ const configDir = entryStat(join10(bmadDir, "_config"));
10135
+ if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
10136
+ const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
10137
+ const manifestFile = configDir ? entryStat(manifestPath) : void 0;
10138
+ if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
10139
+ if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
10140
+ const installed = installedVersion?.();
10141
+ if (installed && installed !== version) {
10142
+ return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
10143
+ }
10144
+ const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
10145
+ const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
10146
+ if (unattested.length) {
10147
+ return { state: "decline", why: `_bmad/_config/files-manifest.csv has no row for ${unattested.map((name) => `scripts/${name}`).join(", ")}, so recovered scripts cannot be attested`, missing };
10148
+ }
10149
+ return { state: "missing", missing };
10150
+ } catch (error) {
10151
+ return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
10152
+ }
10153
+ }
10154
+ function planBmadSetupScripts(repoRoot, pkg, version) {
10155
+ const dest = join10(repoRoot, "_bmad", "scripts");
10156
+ if (!pkg.root || !pkg.entry || pkg.version !== version) return { files: [], declinable: "package", error: `Pinned bmad-method@${version} package could not be resolved: ${pkg.reason ?? "version mismatch"}` };
10157
+ let fingerprint2;
10158
+ try {
10159
+ fingerprint2 = installerFingerprint(pkg.root);
10160
+ } catch (error) {
10161
+ return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
10162
+ }
10163
+ if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
10164
+ const source = join10(pkg.root, "src", "scripts");
10165
+ try {
10166
+ const bmad = lstatSync9(join10(repoRoot, "_bmad"));
10167
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
10168
+ const destStat = entryStat(dest);
10169
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
10170
+ const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
10171
+ if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
10172
+ const missing = [];
10173
+ for (const name of names) {
10174
+ const existing = entryStat(join10(dest, name));
10175
+ if (!existing) missing.push(name);
10176
+ else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
10177
+ }
10178
+ const files = [];
10179
+ if (!missing.length) return { files, source };
10180
+ const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
10181
+ const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
10182
+ const manifestFile = entryStat(manifestPath);
10183
+ if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
10184
+ throw new Error(MANIFEST_UNUSABLE);
10185
+ }
10186
+ const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
10187
+ for (const name of missing) {
10188
+ const src = join10(source, name);
10189
+ if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
10190
+ const matching = rows.filter((row) => row[3] === `scripts/${name}`);
10191
+ const expectedHash = createHash8("sha256").update(readFileSync10(src)).digest("hex");
10192
+ if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
10193
+ throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
10194
+ }
10195
+ files.push(join10(dest, name));
10196
+ }
10197
+ return { files, source };
10198
+ } catch (error) {
10199
+ return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
10200
+ }
10201
+ }
10202
+ function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
10203
+ const plan = planBmadSetupScripts(repoRoot, pkg, version);
10204
+ if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
10205
+ const parent = join10(repoRoot, "_bmad");
10206
+ const dest = join10(parent, "scripts");
10207
+ const expectedParent = entryStat(parent);
10208
+ const expectedDir = entryStat(dest);
10209
+ let stage;
10210
+ let stageFd;
10211
+ let parentFd;
10212
+ const created = [];
10213
+ const stagedIdentities = /* @__PURE__ */ new Map();
10214
+ const published = /* @__PURE__ */ new Map();
10215
+ let ambiguousPublication;
10216
+ const matchesPayload = (path, owned) => {
10217
+ const stat = entryStat(path);
10218
+ return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync10(path).equals(owned.bytes);
10219
+ };
10220
+ let madeDir = false;
10221
+ let dirFd;
10222
+ let result2 = { files: [] };
10223
+ try {
10224
+ parentFd = openSync2(parent, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10225
+ const pinnedParent = fstatSync(parentFd);
10226
+ if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
10227
+ const anchoredParent = `/proc/self/fd/${parentFd}`;
10228
+ stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
10229
+ stageFd = openSync2(stage, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10230
+ const pinnedStage = fstatSync(stageFd);
10231
+ const stageName = basename4(stage);
10232
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
10233
+ if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
10234
+ for (const path of plan.files) {
10235
+ const name = basename4(path);
10236
+ const src = join10(plan.source, name);
10237
+ const staged = join10(anchoredStage, name);
10238
+ const fileFd = openSync2(staged, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
10239
+ try {
10240
+ const stat = fstatSync(fileFd);
10241
+ const bytes = readFileSync10(src);
10242
+ const mode = lstatSync9(src).mode & 4095;
10243
+ stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
10244
+ writeFileSync7(fileFd, bytes);
10245
+ fchmodSync(fileFd, mode);
10246
+ } finally {
10247
+ closeSync2(fileFd);
10248
+ }
10249
+ }
10250
+ if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
10251
+ const current = planBmadSetupScripts(repoRoot, pkg, version);
10252
+ if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
10253
+ const anchoredDest = join10(anchoredParent, "scripts");
10254
+ if (!entryStat(anchoredDest)) {
10255
+ mkdirSync6(anchoredDest);
10256
+ madeDir = true;
10257
+ }
10258
+ dirFd = openSync2(anchoredDest, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10259
+ const pinned = fstatSync(dirFd);
10260
+ if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
10261
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10262
+ const anchored = `/proc/self/fd/${dirFd}`;
10263
+ for (const path of plan.files) {
10264
+ const name = basename4(path);
10265
+ const staged = join10(anchoredStage, name);
10266
+ const output = join10(anchored, name);
10267
+ const owned = stagedIdentities.get(name);
10268
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10269
+ if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
10270
+ if (entryStat(output)) {
10271
+ ambiguousPublication = path;
10272
+ throw new Error(`Script destination appeared before publication: ${name}`);
10273
+ }
10274
+ try {
10275
+ linkSync(staged, output);
10276
+ } catch (error) {
10277
+ if (entryStat(output)) ambiguousPublication = path;
10278
+ throw error;
10279
+ }
10280
+ published.set(path, owned);
10281
+ created.push(path);
10282
+ if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
10283
+ afterPublish?.(created.length);
10284
+ }
10285
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10286
+ for (const path of created) {
10287
+ if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
10288
+ }
10289
+ result2 = { files: created };
10290
+ } catch (error) {
10291
+ const recoveryErrors = [];
10292
+ for (const [path, owned] of [...published].reverse()) {
10293
+ try {
10294
+ const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
10295
+ const stat = entryStat(anchoredPath);
10296
+ if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
10297
+ else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
10298
+ else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
10299
+ } catch (recoveryError) {
10300
+ recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
10301
+ }
10302
+ }
10303
+ if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
10304
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
10305
+ try {
10306
+ const pinned = fstatSync(dirFd);
10307
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
10308
+ if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
10309
+ else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
10310
+ } catch (recoveryError) {
10311
+ recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
10312
+ }
10313
+ }
10314
+ result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
10315
+ } finally {
10316
+ const cleanupErrors = [];
10317
+ if (stageFd !== void 0) {
10318
+ try {
10319
+ const pinnedStage = fstatSync(stageFd);
10320
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
10321
+ for (const [name, owned] of stagedIdentities) {
10322
+ const staged = join10(anchoredStage, name);
10323
+ const stat = entryStat(staged);
10324
+ if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
10325
+ else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
10326
+ }
10327
+ const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
10328
+ if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
10329
+ else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
10330
+ } catch (cleanupError) {
10331
+ cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
10332
+ }
10333
+ closeSync2(stageFd);
10334
+ } else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
10335
+ if (cleanupErrors.length && result2.files.length) {
10336
+ for (const [path, owned] of [...published].reverse()) {
10337
+ try {
10338
+ const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
10339
+ if (matchesPayload(output, owned)) unlinkSync5(output);
10340
+ else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
10341
+ } catch (error) {
10342
+ cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
10343
+ }
10344
+ }
10345
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
10346
+ try {
10347
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
10348
+ if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
10349
+ else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
10350
+ } catch (error) {
10351
+ cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
10352
+ }
10353
+ }
10354
+ }
10355
+ if (dirFd !== void 0) closeSync2(dirFd);
10356
+ if (parentFd !== void 0) closeSync2(parentFd);
10357
+ if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
10358
+ }
10359
+ return result2;
10360
+ }
10361
+
10362
+ // src/parity/rules.ts
10363
+ import { basename as basename5, dirname as dirname9, join as join12, relative as relative9, resolve as resolve7 } from "node:path";
10036
10364
  import { spawnSync as spawnSync5 } from "node:child_process";
10037
10365
  import YAML4 from "yaml";
10038
10366
 
10039
10367
  // src/bmad/reserved-modules.ts
10040
- import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
10041
- import { join as join10 } from "node:path";
10368
+ import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
10369
+ import { join as join11 } from "node:path";
10042
10370
  import YAML3 from "yaml";
10043
10371
  var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
10044
10372
  var indentOf = (line) => line.length - line.trimStart().length;
@@ -10095,19 +10423,19 @@ function stripReservedModuleEntries(raw) {
10095
10423
  return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
10096
10424
  }
10097
10425
  function evictReservedModuleEntries(repoRoot, dryRun) {
10098
- const manifestPath = join10(repoRoot, "_bmad", "_config", "manifest.yaml");
10426
+ const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
10099
10427
  const none = { evicted: [], changedFiles: [], details: [] };
10100
10428
  if (!existsSync7(manifestPath)) return none;
10101
10429
  let original;
10102
10430
  try {
10103
- original = readFileSync10(manifestPath, "utf8");
10431
+ original = readFileSync11(manifestPath, "utf8");
10104
10432
  } catch {
10105
10433
  return none;
10106
10434
  }
10107
10435
  const stripped = stripReservedModuleEntries(original);
10108
10436
  if (!stripped) return none;
10109
10437
  const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
10110
- if (!dryRun) writeFileSync7(manifestPath, stripped.text, "utf8");
10438
+ if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
10111
10439
  return {
10112
10440
  evicted: stripped.evicted,
10113
10441
  changedFiles: [manifestPath],
@@ -10821,17 +11149,17 @@ function normalizeNewlines(value) {
10821
11149
  return value.replace(/\r\n/g, "\n");
10822
11150
  }
10823
11151
  function readText(path) {
10824
- return normalizeNewlines(readFileSync11(path, "utf8"));
11152
+ return normalizeNewlines(readFileSync12(path, "utf8"));
10825
11153
  }
10826
11154
  function safeReadText2(path) {
10827
11155
  return existsSync8(path) ? readText(path) : null;
10828
11156
  }
10829
11157
  function ensureParent(path) {
10830
- mkdirSync6(dirname9(path), { recursive: true });
11158
+ mkdirSync7(dirname9(path), { recursive: true });
10831
11159
  }
10832
11160
  function writeText(path, content) {
10833
11161
  ensureParent(path);
10834
- writeFileSync8(path, content);
11162
+ writeFileSync9(path, content);
10835
11163
  }
10836
11164
  function tryParseJson(text2) {
10837
11165
  if (!text2) return null;
@@ -10857,12 +11185,12 @@ function readSymlinkTarget(path) {
10857
11185
  }
10858
11186
  function ensureSymlink(path, target, dryRun) {
10859
11187
  if (existsSync8(path)) {
10860
- const stat = lstatSync9(path);
11188
+ const stat = lstatSync10(path);
10861
11189
  if (stat.isSymbolicLink()) {
10862
11190
  const current = readSymlinkTarget(path);
10863
11191
  if (current === target) return { changed: false };
10864
11192
  if (!dryRun) {
10865
- unlinkSync5(path);
11193
+ unlinkSync6(path);
10866
11194
  symlinkSync3(target, path);
10867
11195
  }
10868
11196
  return { changed: true };
@@ -10873,12 +11201,12 @@ function ensureSymlink(path, target, dryRun) {
10873
11201
  return { changed: true };
10874
11202
  }
10875
11203
  function bootstrapAgentsFile(repoRoot, dryRun) {
10876
- const agentsPath = join11(repoRoot, "AGENTS.md");
11204
+ const agentsPath = join12(repoRoot, "AGENTS.md");
10877
11205
  if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
10878
11206
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
10879
- const source = join11(repoRoot, file);
11207
+ const source = join12(repoRoot, file);
10880
11208
  if (!existsSync8(source)) continue;
10881
- const stat = lstatSync9(source);
11209
+ const stat = lstatSync10(source);
10882
11210
  if (stat.isSymbolicLink()) continue;
10883
11211
  if (stat.isFile()) {
10884
11212
  if (!dryRun) renameSync4(source, agentsPath);
@@ -10886,9 +11214,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
10886
11214
  }
10887
11215
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
10888
11216
  }
10889
- const readmePath = join11(repoRoot, "README.md");
11217
+ const readmePath = join12(repoRoot, "README.md");
10890
11218
  if (existsSync8(readmePath)) {
10891
- const stat = lstatSync9(readmePath);
11219
+ const stat = lstatSync10(readmePath);
10892
11220
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
10893
11221
  if (!dryRun) copyFileSync(readmePath, agentsPath);
10894
11222
  return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
@@ -10926,11 +11254,11 @@ function yamlGet(text2, keyPath) {
10926
11254
  return "";
10927
11255
  }
10928
11256
  function discoverRoles(repoRoot) {
10929
- const rolesDir = join11(repoRoot, "agents", "hermes");
11257
+ const rolesDir = join12(repoRoot, "agents", "hermes");
10930
11258
  if (!existsSync8(rolesDir)) return [];
10931
- return readdirSync7(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
10932
- const roleDir = join11(rolesDir, entry.name);
10933
- const roleYamlPath = join11(roleDir, "role.yaml");
11259
+ return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
11260
+ const roleDir = join12(rolesDir, entry.name);
11261
+ const roleYamlPath = join12(roleDir, "role.yaml");
10934
11262
  if (!existsSync8(roleYamlPath)) return null;
10935
11263
  const text2 = readText(roleYamlPath);
10936
11264
  const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
@@ -10963,26 +11291,26 @@ function discoverRoles(repoRoot) {
10963
11291
  }).filter((value) => Boolean(value));
10964
11292
  }
10965
11293
  function templateScript(ctx, name) {
10966
- const source = join11(ctx.pjanglerRoot, ".mise", "scripts", name);
11294
+ const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
10967
11295
  return existsSync8(source) ? readText(source) : void 0;
10968
11296
  }
10969
11297
  function templateVersioningScript(ctx) {
10970
11298
  return templateScript(ctx, "versioning.sh");
10971
11299
  }
10972
11300
  function templateLinkAgentfilesScript(ctx) {
10973
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
11301
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
10974
11302
  return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
10975
11303
  }
10976
11304
  function templateMaterializeEnvScript(ctx) {
10977
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
11305
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
10978
11306
  return existsSync8(source) ? readText(source) : void 0;
10979
11307
  }
10980
11308
  function resolveAgentHooksLayer(ctx) {
10981
11309
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
10982
11310
  if (override === "0" || override === "false") return false;
10983
11311
  if (override === "1" || override === "true") return true;
10984
- if (existsSync8(join11(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
10985
- return !existsSync8(join11(ctx.homeDir, ".agents", "hooks"));
11312
+ if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
11313
+ return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
10986
11314
  }
10987
11315
  function evaluateMiseConditionals(template, agentHooksLayer) {
10988
11316
  const out = [];
@@ -11008,13 +11336,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
11008
11336
  }
11009
11337
  function renderGeneratedProjectMiseToml(ctx, template) {
11010
11338
  const project = readProjectJson(ctx);
11011
- const projectName = String(project?.project_name ?? basename4(ctx.repoRoot) ?? "project");
11339
+ const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
11012
11340
  return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
11013
11341
  }
11014
11342
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
11015
- const targetPath = join11(ctx.repoRoot, "mise.toml");
11343
+ const targetPath = join12(ctx.repoRoot, "mise.toml");
11016
11344
  if (existsSync8(targetPath)) return null;
11017
- const sourcePath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
11345
+ const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
11018
11346
  if (!existsSync8(sourcePath)) return null;
11019
11347
  const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
11020
11348
  changedFiles.push(targetPath);
@@ -11022,12 +11350,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
11022
11350
  return rendered;
11023
11351
  }
11024
11352
  function templateCommonProjectText(ctx, rel) {
11025
- const path = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
11353
+ const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
11026
11354
  return existsSync8(path) ? readText(path) : void 0;
11027
11355
  }
11028
11356
  function lstatIfPresent(path) {
11029
11357
  try {
11030
- return lstatSync9(path);
11358
+ return lstatSync10(path);
11031
11359
  } catch (error) {
11032
11360
  if (error.code === "ENOENT") return void 0;
11033
11361
  throw error;
@@ -11035,7 +11363,7 @@ function lstatIfPresent(path) {
11035
11363
  }
11036
11364
  function templateVersionFilesConf(ctx, repoRoot) {
11037
11365
  if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
11038
- const packageJson = join11(repoRoot, "package.json");
11366
+ const packageJson = join12(repoRoot, "package.json");
11039
11367
  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";
11040
11368
  }
11041
11369
  var OBSIDIAN_VERSION_FILES_CONF = "# mise-versioning manifest: <type> <path>\n# types: json toml cargo csproj gradle plain gittag obsidian\n# Obsidian plugin: bump with `mise run version:bump`, release with `npm version` (bare X.Y.Z tag).\njson package.json\nobsidian manifest.json\n";
@@ -11045,7 +11373,7 @@ function recordedProjectType(ctx) {
11045
11373
  function isObsidianPluginRepo(ctx, repoRoot) {
11046
11374
  const recorded = recordedProjectType(ctx);
11047
11375
  if (recorded !== void 0) return recorded === "obsidian-plugin";
11048
- const manifest = record2(tryParseJson(safeReadText2(join11(repoRoot, "manifest.json"))));
11376
+ const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
11049
11377
  return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
11050
11378
  }
11051
11379
  function unsupportedVersionTypes(conf, script) {
@@ -11506,7 +11834,7 @@ function describeRewrite(before, after) {
11506
11834
  return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
11507
11835
  }
11508
11836
  function readProjectJson(ctx) {
11509
- return tryParseJson(safeReadText2(join11(ctx.repoRoot, ".project.json")));
11837
+ return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
11510
11838
  }
11511
11839
  function readDeclaredAgents(ctx) {
11512
11840
  const project = readProjectJson(ctx);
@@ -11523,7 +11851,7 @@ function readDeclaredAgents(ctx) {
11523
11851
  });
11524
11852
  }
11525
11853
  function readRoleYamlAt(roleDir) {
11526
- const roleYamlPath = join11(roleDir, "role.yaml");
11854
+ const roleYamlPath = join12(roleDir, "role.yaml");
11527
11855
  if (!existsSync8(roleYamlPath)) return null;
11528
11856
  const text2 = readText(roleYamlPath);
11529
11857
  return {
@@ -11535,7 +11863,7 @@ function readRoleYamlAt(roleDir) {
11535
11863
  }
11536
11864
  function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
11537
11865
  if (!roleDir) return true;
11538
- return !existsSync8(join11(resolve7(repoRoot, roleDir), "role.yaml"));
11866
+ return !existsSync8(join12(resolve7(repoRoot, roleDir), "role.yaml"));
11539
11867
  }
11540
11868
  function validateDeclaredAgent(ctx, declared) {
11541
11869
  const details = [];
@@ -11560,11 +11888,11 @@ function validateDeclaredAgent(ctx, declared) {
11560
11888
  details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
11561
11889
  }
11562
11890
  if (roleYaml.providerName) {
11563
- const dispatcher = join11(roleDir, ".scripts", "lib", "ticket-provider.sh");
11891
+ const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
11564
11892
  if (!existsSync8(dispatcher)) {
11565
11893
  details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
11566
11894
  }
11567
- const provider = join11(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
11895
+ const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
11568
11896
  if (!existsSync8(provider)) {
11569
11897
  details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
11570
11898
  }
@@ -11580,7 +11908,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
11580
11908
  function canonicalProjectJson(ctx) {
11581
11909
  const roles = discoverRoles(ctx.repoRoot);
11582
11910
  const existing = readProjectJson(ctx) ?? {};
11583
- const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename4(ctx.repoRoot)));
11911
+ const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
11584
11912
  const firstRole = roles[0];
11585
11913
  const ticketProvider = {
11586
11914
  ...existing.ticket_provider,
@@ -11650,8 +11978,8 @@ function canonicalProjectJson(ctx) {
11650
11978
  };
11651
11979
  }
11652
11980
  function projectJsonFinding(ctx) {
11653
- const projectPath = join11(ctx.repoRoot, ".project.json");
11654
- const planeJsonPath = join11(ctx.repoRoot, ".plane.json");
11981
+ const projectPath = join12(ctx.repoRoot, ".project.json");
11982
+ const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
11655
11983
  const details = [];
11656
11984
  const data = readProjectJson(ctx);
11657
11985
  const roles = discoverRoles(ctx.repoRoot);
@@ -11722,7 +12050,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
11722
12050
  var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
11723
12051
  var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
11724
12052
  function manifestBmadModules(repoRoot) {
11725
- const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
12053
+ const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
11726
12054
  const raw = safeReadText2(manifestPath);
11727
12055
  if (raw === null) return { status: "absent" };
11728
12056
  try {
@@ -11750,7 +12078,7 @@ function manifestBmadModules(repoRoot) {
11750
12078
  }
11751
12079
  }
11752
12080
  function configuredBmadModuleSelection(repoRoot) {
11753
- const raw = safeReadText2(join11(repoRoot, "_bmad", "config.toml"));
12081
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
11754
12082
  if (raw === null) return void 0;
11755
12083
  const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
11756
12084
  return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
@@ -11761,24 +12089,60 @@ function selectedBmadModules(repoRoot) {
11761
12089
  if (manifest.status === "invalid") throw new Error(manifest.error);
11762
12090
  return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
11763
12091
  }
11764
- function requiredBmadSentinels(repoRoot, modules = selectedBmadModules(repoRoot)) {
11765
- return [
11766
- join11("core", "config.yaml"),
11767
- join11("config.toml"),
11768
- join11("_config", "manifest.yaml"),
11769
- ...modules.map((module) => join11(module, "config.yaml"))
11770
- ];
12092
+ var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
12093
+ function consolidatedBmadConfig(repoRoot) {
12094
+ const path = join12(repoRoot, "_bmad", "config.yaml");
12095
+ if (!existsSync8(path)) return { status: "absent" };
12096
+ let parsed;
12097
+ try {
12098
+ parsed = YAML4.parse(readText(path));
12099
+ } catch (error) {
12100
+ return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
12101
+ }
12102
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12103
+ return { status: "unusable", reason: "is not a YAML mapping" };
12104
+ }
12105
+ return { status: "mapping", sections: new Set(Object.keys(parsed)) };
12106
+ }
12107
+ function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
12108
+ const bmad = join12(repoRoot, "_bmad");
12109
+ const gaps = [];
12110
+ const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
12111
+ if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
12112
+ for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
12113
+ if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
12114
+ }
12115
+ if (absent("_config", "manifest.yaml")) {
12116
+ gaps.push({
12117
+ path: join12("_config", "manifest.yaml"),
12118
+ detail: existsSync8(bmad) ? "_bmad/_config/manifest.yaml (the installer's install record). BMB setup's `--also-remove _config` cleanup is the likely cause: it deletes all of _bmad/_config. Restore it with: pj migrate bmad.scaffold" : "_bmad/_config/manifest.yaml"
12119
+ });
12120
+ }
12121
+ let consolidated;
12122
+ for (const module of modules) {
12123
+ if (!absent(module, "config.yaml")) continue;
12124
+ consolidated ??= consolidatedBmadConfig(repoRoot);
12125
+ if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
12126
+ const why = consolidated.status === "absent" ? "" : consolidated.status === "unusable" ? ` (and _bmad/config.yaml ${consolidated.reason}, so it cannot supply a ${module} section)` : ` (and _bmad/config.yaml has no ${module} section)`;
12127
+ gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
12128
+ }
12129
+ return gaps;
12130
+ }
12131
+ function bmadScaffoldIssues(repoRoot, modules) {
12132
+ const gaps = bmadSentinelGaps(repoRoot, modules);
12133
+ const nameIssues = bmadProjectNameIssues(repoRoot);
12134
+ return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
11771
12135
  }
11772
12136
  function canonicalBmadProjectName(repoRoot) {
11773
12137
  const project = readProjectJson({ repoRoot });
11774
12138
  const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
11775
- return declared || basename4(repoRoot);
12139
+ return declared || basename5(repoRoot);
11776
12140
  }
11777
12141
  function bmadProjectNameIssues(repoRoot) {
11778
12142
  const expected = canonicalBmadProjectName(repoRoot);
11779
12143
  const paths = [];
11780
12144
  const details = [];
11781
- const configToml = join11(repoRoot, "_bmad", "config.toml");
12145
+ const configToml = join12(repoRoot, "_bmad", "config.toml");
11782
12146
  const tomlText = safeReadText2(configToml);
11783
12147
  const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
11784
12148
  let tomlName;
@@ -11793,16 +12157,16 @@ function bmadProjectNameIssues(repoRoot) {
11793
12157
  paths.push(configToml);
11794
12158
  details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
11795
12159
  }
11796
- const bmadRoot = join11(repoRoot, "_bmad");
12160
+ const bmadRoot = join12(repoRoot, "_bmad");
11797
12161
  if (existsSync8(bmadRoot)) {
11798
12162
  let declared;
11799
12163
  try {
11800
12164
  declared = selectedBmadModules(repoRoot);
11801
12165
  } catch {
11802
- declared = readdirSync7(bmadRoot);
12166
+ declared = readdirSync8(bmadRoot);
11803
12167
  }
11804
12168
  for (const name of new Set(declared)) {
11805
- const configPath = join11(bmadRoot, name, "config.yaml");
12169
+ const configPath = join12(bmadRoot, name, "config.yaml");
11806
12170
  const raw = safeReadText2(configPath);
11807
12171
  if (raw === null) continue;
11808
12172
  let actual;
@@ -11822,7 +12186,7 @@ function bmadProjectNameIssues(repoRoot) {
11822
12186
  function isCurrentActivation(path, activations) {
11823
12187
  if (activations.owned.has(path)) return true;
11824
12188
  try {
11825
- if (activations.owned.has(join11(realpathSync4(dirname9(path)), basename4(path)))) return true;
12189
+ if (activations.owned.has(join12(realpathSync4(dirname9(path)), basename5(path)))) return true;
11826
12190
  return activations.targets.has(realpathSync4(path));
11827
12191
  } catch {
11828
12192
  return false;
@@ -11835,26 +12199,26 @@ async function skillActivations(ctx) {
11835
12199
  function evictLegacyBmadPackState(ctx, changedFiles, activations) {
11836
12200
  const details = [];
11837
12201
  const skillDirs = [
11838
- join11(ctx.repoRoot, ".agents", "skills"),
11839
- ...SUPPORTED_CLI_ROOTS.map((root) => join11(ctx.repoRoot, root, "skills"))
12202
+ join12(ctx.repoRoot, ".agents", "skills"),
12203
+ ...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
11840
12204
  ];
11841
12205
  for (const dir of skillDirs) {
11842
12206
  const dirStat = lstatIfPresent(dir);
11843
12207
  if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
11844
12208
  let names;
11845
12209
  try {
11846
- names = readdirSync7(dir);
12210
+ names = readdirSync8(dir);
11847
12211
  } catch {
11848
12212
  continue;
11849
12213
  }
11850
12214
  for (const name of names) {
11851
12215
  if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
11852
- const path = join11(dir, name);
12216
+ const path = join12(dir, name);
11853
12217
  if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
11854
12218
  if (isCurrentActivation(path, activations)) continue;
11855
12219
  changedFiles.push(path);
11856
12220
  details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
11857
- if (!ctx.dryRun) unlinkSync5(path);
12221
+ if (!ctx.dryRun) unlinkSync6(path);
11858
12222
  }
11859
12223
  }
11860
12224
  return details;
@@ -11867,10 +12231,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
11867
12231
  prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
11868
12232
  };
11869
12233
  }
11870
- function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
12234
+ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
12235
+ const timeoutMs = options.timeoutMs ?? 18e4;
11871
12236
  const packageAt = (dir) => {
11872
12237
  try {
11873
- const parsed = JSON.parse(readFileSync11(join11(dir, "package.json"), "utf8"));
12238
+ const parsed = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8"));
11874
12239
  return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
11875
12240
  } catch {
11876
12241
  return void 0;
@@ -11902,12 +12267,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11902
12267
  `node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
11903
12268
  ], {
11904
12269
  encoding: "utf8",
11905
- timeout: 18e4,
12270
+ timeout: timeoutMs,
11906
12271
  env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
11907
12272
  });
11908
12273
  const entry = probe.stdout?.trim().split("\n").pop()?.trim();
11909
12274
  if (probe.status !== 0 || !entry) {
11910
- const detail = String(probe.stderr || probe.error?.message || `exit ${probe.status}`).split("\n").filter((line) => line.trim() && !line.startsWith("npm notice")).slice(-3).join(" ");
12275
+ const timedOut = probe.error?.code === "ETIMEDOUT";
12276
+ const detail = timedOut ? `timed out after ${Math.round(timeoutMs / 1e3)}s` : String(probe.stderr || probe.error?.message || `exit ${probe.status}`).split("\n").filter((line) => line.trim() && !line.startsWith("npm notice")).slice(-3).join(" ");
11911
12277
  return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
11912
12278
  }
11913
12279
  const root = dirname9(dirname9(dirname9(entry)));
@@ -11915,6 +12281,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11915
12281
  if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
11916
12282
  return { root, entry, version: found };
11917
12283
  }
12284
+ var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
12285
+ var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
12286
+ function bmadSetupScriptsState(repoRoot) {
12287
+ const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
12288
+ const decline = (why, missing, remedy2) => ({
12289
+ kind: "decline",
12290
+ summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
12291
+ });
12292
+ switch (inspection.state) {
12293
+ case "present":
12294
+ return { kind: "present" };
12295
+ case "unsafe":
12296
+ return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
12297
+ case "decline":
12298
+ return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
12299
+ case "missing":
12300
+ break;
12301
+ }
12302
+ const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
12303
+ const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
12304
+ const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
12305
+ if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
12306
+ const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
12307
+ if (plan.error) {
12308
+ return plan.declinable ? decline(plan.error.replace(/^BMAD script recovery refused: /, ""), inspection.missing, plan.declinable === "attest" ? BMAD_SETUP_SCRIPTS_REMEDY : remedy) : { kind: "refuse", error: plan.error };
12309
+ }
12310
+ return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
12311
+ }
11918
12312
  function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
11919
12313
  const installerModules = modules.length ? modules.join(",") : "core";
11920
12314
  return [
@@ -11933,7 +12327,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
11933
12327
  }
11934
12328
  function bmadUpgradeChannelArgs(repoRoot, version) {
11935
12329
  if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
11936
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
12330
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
11937
12331
  if (!raw) return [];
11938
12332
  let modules;
11939
12333
  try {
@@ -12003,7 +12397,7 @@ function bmadChannelTarget(distTags) {
12003
12397
  return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
12004
12398
  }
12005
12399
  function readInstalledBmadVersion(repoRoot) {
12006
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
12400
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
12007
12401
  if (!raw) return void 0;
12008
12402
  try {
12009
12403
  const parsed = YAML4.parse(raw);
@@ -12014,8 +12408,8 @@ function readInstalledBmadVersion(repoRoot) {
12014
12408
  }
12015
12409
  }
12016
12410
  function bmadCachePath(homeDir) {
12017
- const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join11(homeDir, ".cache");
12018
- return join11(cacheRoot, "pjangler", "bmad-dist-tags.json");
12411
+ const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
12412
+ return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
12019
12413
  }
12020
12414
  function readBmadDistTagsCache(homeDir) {
12021
12415
  const raw = safeReadText2(bmadCachePath(homeDir));
@@ -12057,8 +12451,8 @@ function resolveBmadDistTags(homeDir) {
12057
12451
  if (fetched) {
12058
12452
  try {
12059
12453
  const path = bmadCachePath(homeDir);
12060
- mkdirSync6(dirname9(path), { recursive: true });
12061
- writeFileSync8(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
12454
+ mkdirSync7(dirname9(path), { recursive: true });
12455
+ writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
12062
12456
  } catch {
12063
12457
  }
12064
12458
  return { distTags: fetched, stale: false };
@@ -12101,27 +12495,27 @@ function compareBmadVersions(a, b) {
12101
12495
  function discoverMomoProviderCandidates(repoRoot) {
12102
12496
  const candidates = [];
12103
12497
  const roleDirs = [];
12104
- const hermesDir = join11(repoRoot, "agents", "hermes");
12498
+ const hermesDir = join12(repoRoot, "agents", "hermes");
12105
12499
  if (existsSync8(hermesDir)) {
12106
- for (const entry of readdirSync7(hermesDir, { withFileTypes: true })) {
12107
- if (entry.isDirectory()) roleDirs.push(join11(hermesDir, entry.name));
12500
+ for (const entry of readdirSync8(hermesDir, { withFileTypes: true })) {
12501
+ if (entry.isDirectory()) roleDirs.push(join12(hermesDir, entry.name));
12108
12502
  }
12109
12503
  }
12110
12504
  for (const roleDir of roleDirs) {
12111
12505
  for (const name of ["momo", "provider", "momo-provider"]) {
12112
- const path = join11(roleDir, name);
12506
+ const path = join12(roleDir, name);
12113
12507
  if (existsSync8(path)) {
12114
12508
  const kind = path.endsWith(".py") ? "python" : "shell";
12115
12509
  candidates.push({ path, kind });
12116
12510
  }
12117
12511
  }
12118
12512
  if (existsSync8(roleDir)) {
12119
- for (const entry of readdirSync7(roleDir, { withFileTypes: true })) {
12513
+ for (const entry of readdirSync8(roleDir, { withFileTypes: true })) {
12120
12514
  if (!entry.isFile()) continue;
12121
12515
  if (!entry.name.startsWith("momo")) continue;
12122
- const path = join11(roleDir, entry.name);
12516
+ const path = join12(roleDir, entry.name);
12123
12517
  try {
12124
- if (lstatSync9(path).mode & 73) {
12518
+ if (lstatSync10(path).mode & 73) {
12125
12519
  const kind = entry.name.endsWith(".py") ? "python" : "shell";
12126
12520
  if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
12127
12521
  }
@@ -12131,7 +12525,7 @@ function discoverMomoProviderCandidates(repoRoot) {
12131
12525
  }
12132
12526
  }
12133
12527
  for (const rel of [".mise/scripts/momo-provider.sh", ".scripts/momo-provider.sh", "momo"]) {
12134
- const path = join11(repoRoot, rel);
12528
+ const path = join12(repoRoot, rel);
12135
12529
  if (existsSync8(path)) {
12136
12530
  const kind = path.endsWith(".py") ? "python" : "shell";
12137
12531
  if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
@@ -12167,7 +12561,7 @@ function runProviderLocalSmoke(repoRoot, candidate) {
12167
12561
  return { ok: true };
12168
12562
  }
12169
12563
  function attemptPlaneStateMapping(repoRoot) {
12170
- const project = tryParseJson(safeReadText2(join11(repoRoot, ".project.json")));
12564
+ const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
12171
12565
  const tp = project?.ticket_provider ?? {};
12172
12566
  if (!tp.board_id) return { ok: false, detail: "ticket_provider.board_id missing; cannot map Plane states" };
12173
12567
  return { ok: false, detail: `Plane state mapping attempted for board ${tp.board_id} (credentials required for full mapping)` };
@@ -12184,7 +12578,7 @@ function momoLifecycleFinding(section2, status, summary, details = []) {
12184
12578
  }
12185
12579
  function auditManifestRoleConsistency(repoRoot) {
12186
12580
  const details = [];
12187
- const projectPath = join11(repoRoot, ".project.json");
12581
+ const projectPath = join12(repoRoot, ".project.json");
12188
12582
  if (!existsSync8(projectPath)) {
12189
12583
  return momoLifecycleFinding("manifest-role-consistency", "fail", ".project.json missing", [".project.json missing"]);
12190
12584
  }
@@ -12206,7 +12600,7 @@ function auditManifestRoleConsistency(repoRoot) {
12206
12600
  details.push(`agents.${agentId}.role_dir does not exist: ${agent.role_dir}`);
12207
12601
  continue;
12208
12602
  }
12209
- const roleYaml = join11(roleDir, "role.yaml");
12603
+ const roleYaml = join12(roleDir, "role.yaml");
12210
12604
  if (!existsSync8(roleYaml)) {
12211
12605
  details.push(`agents.${agentId} role.yaml missing at ${agent.role_dir}/role.yaml`);
12212
12606
  continue;
@@ -12249,26 +12643,26 @@ function hasAnyLifecycleScript(repoRoot) {
12249
12643
  for (const pattern of patterns) {
12250
12644
  if (pattern.includes("*")) {
12251
12645
  const [prefix, suffix] = pattern.split("*");
12252
- const base = join11(repoRoot, prefix);
12646
+ const base = join12(repoRoot, prefix);
12253
12647
  if (!existsSync8(base)) continue;
12254
- for (const entry of readdirSync7(base, { withFileTypes: true })) {
12648
+ for (const entry of readdirSync8(base, { withFileTypes: true })) {
12255
12649
  if (!entry.isDirectory()) continue;
12256
- const candidate = join11(base, entry.name, suffix);
12650
+ const candidate = join12(base, entry.name, suffix);
12257
12651
  if (existsSync8(candidate)) return true;
12258
12652
  const parent = dirname9(candidate);
12259
- const stem = basename4(candidate);
12653
+ const stem = basename5(candidate);
12260
12654
  if (!existsSync8(parent)) continue;
12261
- for (const sibling of readdirSync7(parent, { withFileTypes: true })) {
12655
+ for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
12262
12656
  if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
12263
12657
  }
12264
12658
  }
12265
12659
  } else {
12266
- const base = join11(repoRoot, pattern);
12660
+ const base = join12(repoRoot, pattern);
12267
12661
  if (existsSync8(base)) return true;
12268
12662
  const parent = dirname9(base);
12269
- const prefix = basename4(base);
12663
+ const prefix = basename5(base);
12270
12664
  if (existsSync8(parent)) {
12271
- for (const entry of readdirSync7(parent, { withFileTypes: true })) {
12665
+ for (const entry of readdirSync8(parent, { withFileTypes: true })) {
12272
12666
  if (entry.name.startsWith(prefix)) return true;
12273
12667
  }
12274
12668
  }
@@ -12298,26 +12692,26 @@ function hasAnySentinelScript(repoRoot) {
12298
12692
  for (const pattern of patterns) {
12299
12693
  if (pattern.includes("*")) {
12300
12694
  const [prefix, suffix] = pattern.split("*");
12301
- const base = join11(repoRoot, prefix);
12695
+ const base = join12(repoRoot, prefix);
12302
12696
  if (!existsSync8(base)) continue;
12303
- for (const entry of readdirSync7(base, { withFileTypes: true })) {
12697
+ for (const entry of readdirSync8(base, { withFileTypes: true })) {
12304
12698
  if (!entry.isDirectory()) continue;
12305
- const candidate = join11(base, entry.name, suffix);
12699
+ const candidate = join12(base, entry.name, suffix);
12306
12700
  if (existsSync8(candidate)) return true;
12307
12701
  const parent = dirname9(candidate);
12308
- const stem = basename4(candidate);
12702
+ const stem = basename5(candidate);
12309
12703
  if (!existsSync8(parent)) continue;
12310
- for (const sibling of readdirSync7(parent, { withFileTypes: true })) {
12704
+ for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
12311
12705
  if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
12312
12706
  }
12313
12707
  }
12314
12708
  } else {
12315
- const base = join11(repoRoot, pattern);
12709
+ const base = join12(repoRoot, pattern);
12316
12710
  if (existsSync8(base)) return true;
12317
12711
  const parent = dirname9(base);
12318
- const prefix = basename4(base);
12712
+ const prefix = basename5(base);
12319
12713
  if (existsSync8(parent)) {
12320
- for (const entry of readdirSync7(parent, { withFileTypes: true })) {
12714
+ for (const entry of readdirSync8(parent, { withFileTypes: true })) {
12321
12715
  if (entry.name.startsWith(prefix)) return true;
12322
12716
  }
12323
12717
  }
@@ -12362,7 +12756,7 @@ function auditProviderSyntax(repoRoot) {
12362
12756
  }
12363
12757
  function auditPlaneBinding(repoRoot) {
12364
12758
  const details = [];
12365
- const project = tryParseJson(safeReadText2(join11(repoRoot, ".project.json")));
12759
+ const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
12366
12760
  const tp = project?.ticket_provider ?? {};
12367
12761
  for (const key of ["type", "workspace", "identifier", "board_id"]) {
12368
12762
  if (!tp[key]) details.push(`ticket_provider.${key} missing`);
@@ -12534,8 +12928,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
12534
12928
  ".zencoder": "zencoder"
12535
12929
  };
12536
12930
  function unsupportedRootAttestation(repoRoot, rootName) {
12537
- const root = join11(repoRoot, rootName);
12538
- const stat = lstatSync9(root);
12931
+ const root = join12(repoRoot, rootName);
12932
+ const stat = lstatSync10(root);
12539
12933
  if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
12540
12934
  const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
12541
12935
  if (!installedBmadTools(repoRoot).has(installerTool)) {
@@ -12554,7 +12948,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
12554
12948
  function optionalTemplateScriptIssues(ctx) {
12555
12949
  const issues = [];
12556
12950
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
12557
- const path = join11(ctx.repoRoot, rel);
12951
+ const path = join12(ctx.repoRoot, rel);
12558
12952
  if (!existsSync8(path)) continue;
12559
12953
  const expected = templateCommonProjectText(ctx, rel);
12560
12954
  if (expected === void 0) continue;
@@ -12565,7 +12959,7 @@ function optionalTemplateScriptIssues(ctx) {
12565
12959
  function refreshOptionalTemplateScripts(ctx) {
12566
12960
  const changed = [];
12567
12961
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
12568
- const path = join11(ctx.repoRoot, rel);
12962
+ const path = join12(ctx.repoRoot, rel);
12569
12963
  if (!existsSync8(path)) continue;
12570
12964
  const expected = templateCommonProjectText(ctx, rel);
12571
12965
  if (expected === void 0 || safeReadText2(path) === expected) continue;
@@ -12583,13 +12977,13 @@ function createMiseChecks() {
12583
12977
  id: "mise.config-root",
12584
12978
  title: "mise config_root + AGENTS link hooks",
12585
12979
  audit: (ctx) => {
12586
- const misePath = join11(ctx.repoRoot, "mise.toml");
12980
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12587
12981
  if (!existsSync8(misePath)) {
12588
12982
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
12589
12983
  }
12590
12984
  const text2 = readText(misePath);
12591
12985
  const details = [];
12592
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12986
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12593
12987
  if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
12594
12988
  const toml = [];
12595
12989
  const manual = [];
@@ -12637,7 +13031,7 @@ function createMiseChecks() {
12637
13031
  };
12638
13032
  },
12639
13033
  migrate: (ctx, finding2) => {
12640
- const path = join11(ctx.repoRoot, "mise.toml");
13034
+ const path = join12(ctx.repoRoot, "mise.toml");
12641
13035
  const changedFiles = [];
12642
13036
  const details = [];
12643
13037
  if (!existsSync8(path)) {
@@ -12656,7 +13050,7 @@ function createMiseChecks() {
12656
13050
  if (!ctx.dryRun) writeText(path, plan.text);
12657
13051
  text2 = plan.text;
12658
13052
  }
12659
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
13053
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12660
13054
  const expectedScript = templateLinkAgentfilesScript(ctx);
12661
13055
  if (expectedScript === void 0) {
12662
13056
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/link-agentfiles.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
@@ -12694,9 +13088,9 @@ function createMiseChecks() {
12694
13088
  title: "managed mise versioning block",
12695
13089
  audit: (ctx) => {
12696
13090
  const details = [];
12697
- const misePath = join11(ctx.repoRoot, "mise.toml");
12698
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
12699
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
13091
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13092
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
13093
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
12700
13094
  const text2 = safeReadText2(misePath);
12701
13095
  if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
12702
13096
  if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
@@ -12722,7 +13116,7 @@ function createMiseChecks() {
12722
13116
  migrate: (ctx, finding2) => {
12723
13117
  const changedFiles = [];
12724
13118
  const details = [];
12725
- const misePath = join11(ctx.repoRoot, "mise.toml");
13119
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12726
13120
  if (!existsSync8(misePath)) {
12727
13121
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
12728
13122
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "mise.toml missing and no generated-project mise template available to initialize from", changedFiles, details: [] };
@@ -12740,7 +13134,7 @@ function createMiseChecks() {
12740
13134
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
12741
13135
  if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
12742
13136
  }
12743
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
13137
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
12744
13138
  const expectedScript = templateVersioningScript(ctx);
12745
13139
  if (expectedScript === void 0) {
12746
13140
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler install is missing .mise/scripts/versioning.sh \u2014 update @delorenj/pjangler (broken package)", changedFiles, details: [] };
@@ -12752,7 +13146,7 @@ function createMiseChecks() {
12752
13146
  chmodSync3(versioningPath, 493);
12753
13147
  }
12754
13148
  }
12755
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
13149
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
12756
13150
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
12757
13151
  if (safeReadText2(manifestPath) !== expectedManifest) {
12758
13152
  changedFiles.push(manifestPath);
@@ -12814,7 +13208,7 @@ var RETIRED_SKILL_SCRIPTS = [
12814
13208
  ".mise/scripts/unlink-project-skills-from-clis.sh"
12815
13209
  ];
12816
13210
  function retiredSkillsScripts(ctx) {
12817
- return RETIRED_SKILL_SCRIPTS.map((path) => join11(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
13211
+ return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
12818
13212
  }
12819
13213
  function skillsWiringFindings(text2) {
12820
13214
  if (text2 === null) return { fixable: [], manual: [] };
@@ -12865,7 +13259,7 @@ function createAgentHooksChecks() {
12865
13259
  title: "Skillex project skills",
12866
13260
  audit: async (ctx) => {
12867
13261
  const finding2 = await auditProjectSkills(ctx);
12868
- const mise = safeReadText2(join11(ctx.repoRoot, "mise.toml"));
13262
+ const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
12869
13263
  const registryField = await legacyRegistryField(ctx);
12870
13264
  const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
12871
13265
  const wiring = [...skillsWiringIssues(mise), ...registryField && !registryDrop?.blocked ? [`Drop the legacy "registry" field (${JSON.stringify(registryField.value)}) from .agents/skills.json; it pins resolution to the never-fetched ~/.agents/.cache registry clone`] : []];
@@ -12886,7 +13280,7 @@ function createAgentHooksChecks() {
12886
13280
  const changedFiles = [];
12887
13281
  const details = [];
12888
13282
  const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
12889
- let currentMise = safeReadText2(join11(ctx.repoRoot, "mise.toml"));
13283
+ let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
12890
13284
  if (currentMise === null) {
12891
13285
  const template = templateCommonProjectText(ctx, "mise.toml.jinja");
12892
13286
  if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
@@ -12901,9 +13295,9 @@ function createAgentHooksChecks() {
12901
13295
  const stat = lstatIfPresent(path);
12902
13296
  if (!stat || stat.isDirectory()) continue;
12903
13297
  changedFiles.push(path);
12904
- if (!ctx.dryRun) unlinkSync5(path);
13298
+ if (!ctx.dryRun) unlinkSync6(path);
12905
13299
  }
12906
- const misePath = join11(ctx.repoRoot, "mise.toml");
13300
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12907
13301
  const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
12908
13302
  if (safeReadText2(misePath) !== nextMise) {
12909
13303
  changedFiles.push(misePath);
@@ -12934,9 +13328,9 @@ function createAgentHooksChecks() {
12934
13328
  id: "sot.agent-symlinks",
12935
13329
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
12936
13330
  audit: (ctx) => {
12937
- const agentsPath = join11(ctx.repoRoot, "AGENTS.md");
13331
+ const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
12938
13332
  if (!existsSync8(agentsPath)) {
12939
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join11(ctx.repoRoot, file)));
13333
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
12940
13334
  if (fallbackSources.length === 0) {
12941
13335
  return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
12942
13336
  }
@@ -12951,7 +13345,7 @@ function createAgentHooksChecks() {
12951
13345
  }
12952
13346
  const details = [];
12953
13347
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
12954
- const full = join11(ctx.repoRoot, file);
13348
+ const full = join12(ctx.repoRoot, file);
12955
13349
  const target = readSymlinkTarget(full);
12956
13350
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
12957
13351
  }
@@ -12975,7 +13369,7 @@ function createAgentHooksChecks() {
12975
13369
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
12976
13370
  }
12977
13371
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
12978
- const full = join11(ctx.repoRoot, file);
13372
+ const full = join12(ctx.repoRoot, file);
12979
13373
  const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
12980
13374
  if (result2.blocked) blockedDetails.push(result2.blocked);
12981
13375
  if (result2.changed) changedFiles.push(full);
@@ -13002,7 +13396,7 @@ function createProjectJsonChecks() {
13002
13396
  const changedFiles = [];
13003
13397
  const blockedDetails = [];
13004
13398
  const droppedDetails = [];
13005
- const path = join11(ctx.repoRoot, ".project.json");
13399
+ const path = join12(ctx.repoRoot, ".project.json");
13006
13400
  const existing = readProjectJson(ctx) ?? {};
13007
13401
  const canonical = canonicalProjectJson(ctx);
13008
13402
  const preservedDetails = [];
@@ -13034,7 +13428,7 @@ function createProjectJsonChecks() {
13034
13428
  changedFiles.push(path);
13035
13429
  if (!ctx.dryRun) writeText(path, expected);
13036
13430
  }
13037
- const planeJson = join11(ctx.repoRoot, ".plane.json");
13431
+ const planeJson = join12(ctx.repoRoot, ".plane.json");
13038
13432
  if (existsSync8(planeJson)) {
13039
13433
  const backup = `${planeJson}.migrated-backup`;
13040
13434
  if (existsSync8(backup)) {
@@ -13066,10 +13460,10 @@ function createMiseOpInjectChecks() {
13066
13460
  const details = [];
13067
13461
  let envOpNeedsHands = false;
13068
13462
  const optedOut = envMaterializationOptedOut(ctx);
13069
- const envOpPath = join11(ctx.repoRoot, ".env.op");
13463
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
13070
13464
  const envOpExists = existsSync8(envOpPath);
13071
13465
  const envOp = envOpExists ? readText(envOpPath) : void 0;
13072
- const gitignore = safeReadText2(join11(ctx.repoRoot, ".gitignore"));
13466
+ const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
13073
13467
  if (!envOpExists) {
13074
13468
  details.push(".env.op missing");
13075
13469
  } else if (!envOp?.trim()) {
@@ -13089,7 +13483,7 @@ function createMiseOpInjectChecks() {
13089
13483
  if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
13090
13484
  if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
13091
13485
  if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
13092
- const misePath = join11(ctx.repoRoot, "mise.toml");
13486
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13093
13487
  const miseText = safeReadText2(misePath);
13094
13488
  if (!miseText) {
13095
13489
  details.push("mise.toml missing for .env materialization hook");
@@ -13112,7 +13506,7 @@ function createMiseOpInjectChecks() {
13112
13506
  else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
13113
13507
  details.push(...mise);
13114
13508
  }
13115
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13509
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13116
13510
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
13117
13511
  if (optedOut) {
13118
13512
  if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
@@ -13120,7 +13514,7 @@ function createMiseOpInjectChecks() {
13120
13514
  details.push("pjangler package is missing the managed materialize-env.sh source");
13121
13515
  } else if (safeReadText2(materializePath) !== expectedMaterializer) {
13122
13516
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
13123
- } else if ((lstatSync9(materializePath).mode & 73) === 0) {
13517
+ } else if ((lstatSync10(materializePath).mode & 73) === 0) {
13124
13518
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
13125
13519
  }
13126
13520
  return {
@@ -13136,8 +13530,8 @@ function createMiseOpInjectChecks() {
13136
13530
  const changedFiles = [];
13137
13531
  const details = [];
13138
13532
  let envOpNeedsHands = false;
13139
- const envOpPath = join11(ctx.repoRoot, ".env.op");
13140
- const canonicalEnvOpPath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
13533
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
13534
+ const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
13141
13535
  if (!existsSync8(canonicalEnvOpPath)) {
13142
13536
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
13143
13537
  }
@@ -13166,7 +13560,7 @@ function createMiseOpInjectChecks() {
13166
13560
  }
13167
13561
  }
13168
13562
  }
13169
- const gitignorePath = join11(ctx.repoRoot, ".gitignore");
13563
+ const gitignorePath = join12(ctx.repoRoot, ".gitignore");
13170
13564
  const gitignore = safeReadText2(gitignorePath) ?? "";
13171
13565
  const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
13172
13566
  # staged through a mktemp file and moved into place only on success.
@@ -13179,7 +13573,7 @@ function createMiseOpInjectChecks() {
13179
13573
  changedFiles.push(gitignorePath);
13180
13574
  if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
13181
13575
  }
13182
- const misePath = join11(ctx.repoRoot, "mise.toml");
13576
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13183
13577
  let currentMise = safeReadText2(misePath);
13184
13578
  if (currentMise === null) {
13185
13579
  const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
@@ -13196,7 +13590,7 @@ function createMiseOpInjectChecks() {
13196
13590
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
13197
13591
  if (!ctx.dryRun) writeText(misePath, opPlan.text);
13198
13592
  }
13199
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13593
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13200
13594
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
13201
13595
  if (optedOut) {
13202
13596
  if (existsSync8(materializePath)) {
@@ -13205,7 +13599,7 @@ function createMiseOpInjectChecks() {
13205
13599
  }
13206
13600
  } else if (!expectedMaterializer) {
13207
13601
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
13208
- } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync9(materializePath).mode & 73) === 0) {
13602
+ } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
13209
13603
  changedFiles.push(materializePath);
13210
13604
  if (!ctx.dryRun) {
13211
13605
  writeText(materializePath, expectedMaterializer);
@@ -13267,7 +13661,7 @@ function createProjectProvenanceChecks() {
13267
13661
  title: ".copier-answers.yml provenance + drift report",
13268
13662
  audit: (ctx) => {
13269
13663
  const details = [];
13270
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
13664
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
13271
13665
  const text2 = safeReadText2(path);
13272
13666
  const project = readProjectJson(ctx);
13273
13667
  if (!text2) {
@@ -13295,11 +13689,11 @@ function createProjectProvenanceChecks() {
13295
13689
  migrate: (ctx, finding2) => {
13296
13690
  const changedFiles = [];
13297
13691
  const project = canonicalProjectJson(ctx);
13298
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
13692
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
13299
13693
  const current = safeReadText2(path);
13300
13694
  const existing = current ? YAML4.parseDocument(current) : void 0;
13301
13695
  const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
13302
- document.set("_src_path", join11(ctx.pjanglerRoot, "templates", "commonproject"));
13696
+ document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
13303
13697
  document.set("project_description", String(project.project_description));
13304
13698
  document.set("project_name", String(project.project_name));
13305
13699
  document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
@@ -13324,9 +13718,9 @@ ${body}`;
13324
13718
  }
13325
13719
  function supportedCliProjectionIssues(repoRoot, plan) {
13326
13720
  const issues = [];
13327
- const managedSkills = join11(repoRoot, ".agents", "skills");
13721
+ const managedSkills = join12(repoRoot, ".agents", "skills");
13328
13722
  for (const rootName of SUPPORTED_CLI_ROOTS) {
13329
- const root = join11(repoRoot, rootName);
13723
+ const root = join12(repoRoot, rootName);
13330
13724
  const rootStat = lstatIfPresent(root);
13331
13725
  if (!rootStat) {
13332
13726
  issues.push(`${rootName} missing`);
@@ -13336,7 +13730,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
13336
13730
  issues.push(`${rootName} must be a real configuration directory`);
13337
13731
  continue;
13338
13732
  }
13339
- const skills = join11(root, "skills");
13733
+ const skills = join12(root, "skills");
13340
13734
  const skillsStat = lstatIfPresent(skills);
13341
13735
  if (!skillsStat) {
13342
13736
  issues.push(`${rootName}/skills missing`);
@@ -13373,9 +13767,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
13373
13767
  issues.push(`${rootName}/skills is not a directory`);
13374
13768
  continue;
13375
13769
  }
13376
- const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync7(projectedSkills).some((name) => {
13377
- const skillFile = join11(projectedSkills, name, "SKILL.md");
13378
- return existsSync8(skillFile) && lstatSync9(skillFile).isFile();
13770
+ const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
13771
+ const skillFile = join12(projectedSkills, name, "SKILL.md");
13772
+ return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
13379
13773
  });
13380
13774
  if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
13381
13775
  }
@@ -13401,7 +13795,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
13401
13795
  "# some CLIs get a real directory here and some get a symlink."
13402
13796
  ]);
13403
13797
  function supportedCliGitignoreIssues(repoRoot) {
13404
- const lines = (safeReadText2(join11(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
13798
+ const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
13405
13799
  const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
13406
13800
  return [
13407
13801
  ...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
@@ -13409,7 +13803,7 @@ function supportedCliGitignoreIssues(repoRoot) {
13409
13803
  ];
13410
13804
  }
13411
13805
  function ensureSupportedCliGitignore(ctx) {
13412
- const path = join11(ctx.repoRoot, ".gitignore");
13806
+ const path = join12(ctx.repoRoot, ".gitignore");
13413
13807
  const current = safeReadText2(path) ?? "";
13414
13808
  const kept = current.split(/\r?\n/).filter((line) => {
13415
13809
  const trimmed2 = line.trim();
@@ -13451,6 +13845,24 @@ function withInstallerSkillHandoff(check) {
13451
13845
  }
13452
13846
  };
13453
13847
  }
13848
+ function withScaffoldPostcondition(check) {
13849
+ if (check.id !== "bmad.scaffold") return check;
13850
+ return {
13851
+ ...check,
13852
+ migrate: async (ctx, finding2) => {
13853
+ const result2 = await check.migrate(ctx, finding2);
13854
+ if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
13855
+ const after = await check.audit(ctx);
13856
+ if (after.status === "pass" || after.status === "skip") return result2;
13857
+ return {
13858
+ ...result2,
13859
+ status: "partial",
13860
+ summary: after.summary,
13861
+ details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
13862
+ };
13863
+ }
13864
+ };
13865
+ }
13454
13866
  function createBmadChecks() {
13455
13867
  return [
13456
13868
  {
@@ -13468,15 +13880,8 @@ function createBmadChecks() {
13468
13880
  fixable: false
13469
13881
  };
13470
13882
  }
13471
- const targetRoot = join11(ctx.repoRoot, "_bmad");
13472
13883
  const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
13473
- const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
13474
- const missing = sentinels.filter((file) => !existsSync8(join11(targetRoot, file)));
13475
- const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
13476
- const details = [
13477
- ...missing.map((file) => `_bmad/${file}`),
13478
- ...projectNameIssues.details
13479
- ];
13884
+ const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13480
13885
  return {
13481
13886
  id: "bmad.scaffold",
13482
13887
  title: "BMAD modules/docs scaffold",
@@ -13515,7 +13920,7 @@ function createBmadChecks() {
13515
13920
  bmadCompanion: preservation
13516
13921
  };
13517
13922
  if (ctx.dryRun) {
13518
- const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
13923
+ const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13519
13924
  const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
13520
13925
  if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
13521
13926
  return {
@@ -13528,8 +13933,8 @@ function createBmadChecks() {
13528
13933
  bmadCompanion: companion
13529
13934
  };
13530
13935
  }
13531
- changedFiles.push(...sentinels.map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)));
13532
- changedFiles.push(...bmadProjectNameIssues(ctx.repoRoot).paths);
13936
+ changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
13937
+ changedFiles.push(...issues.nameIssues.paths);
13533
13938
  const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
13534
13939
  changedFiles.push(...reservedPreview?.changedFiles ?? []);
13535
13940
  return {
@@ -13547,9 +13952,10 @@ function createBmadChecks() {
13547
13952
  };
13548
13953
  }
13549
13954
  if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
13955
+ const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13550
13956
  const expectedChangedPaths2 = [
13551
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
13552
- ...bmadProjectNameIssues(ctx.repoRoot).paths
13957
+ ...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
13958
+ ...before2.nameIssues.paths
13553
13959
  ];
13554
13960
  const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
13555
13961
  const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
@@ -13590,16 +13996,17 @@ function createBmadChecks() {
13590
13996
  bmadCompanion: update.companion
13591
13997
  };
13592
13998
  }
13999
+ const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13593
14000
  const expectedChangedPaths = [
13594
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
13595
- ...bmadProjectNameIssues(ctx.repoRoot).paths
14001
+ ...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
14002
+ ...before.nameIssues.paths
13596
14003
  ];
13597
14004
  const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
13598
14005
  const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
13599
14006
  if (reserved) evicted.unshift(...reserved.details);
13600
14007
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
13601
14008
  if (!install.ok && reserved?.original !== void 0) {
13602
- writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
14009
+ writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
13603
14010
  evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
13604
14011
  }
13605
14012
  if (!install.ok) {
@@ -13624,6 +14031,58 @@ function createBmadChecks() {
13624
14031
  };
13625
14032
  }
13626
14033
  },
14034
+ {
14035
+ id: "bmad.setup-scripts",
14036
+ title: "BMAD ignored installer setup scripts",
14037
+ audit: (ctx) => {
14038
+ if (!isInstalledBmadProject(ctx.repoRoot)) return {
14039
+ id: "bmad.setup-scripts",
14040
+ title: "BMAD ignored installer setup scripts",
14041
+ status: "skip",
14042
+ summary: "No installed BMAD project",
14043
+ details: [],
14044
+ fixable: false
14045
+ };
14046
+ const state = bmadSetupScriptsState(ctx.repoRoot);
14047
+ const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
14048
+ switch (state.kind) {
14049
+ case "present":
14050
+ return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
14051
+ case "decline":
14052
+ return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
14053
+ case "refuse":
14054
+ return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
14055
+ case "missing":
14056
+ return {
14057
+ id,
14058
+ title,
14059
+ status: "fail",
14060
+ summary: "Ignored BMAD installer scripts are missing",
14061
+ details: state.files.map((path) => relative9(ctx.repoRoot, path)),
14062
+ fixable: true
14063
+ };
14064
+ }
14065
+ },
14066
+ migrate: (ctx, finding2) => {
14067
+ const title = finding2.title, id = finding2.id;
14068
+ if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
14069
+ const state = bmadSetupScriptsState(ctx.repoRoot);
14070
+ if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
14071
+ if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
14072
+ if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
14073
+ const { pkg, files } = state;
14074
+ if (ctx.dryRun) return {
14075
+ id,
14076
+ title,
14077
+ status: "applied",
14078
+ summary: "Would restore ignored scripts from verified pinned bmad-method package",
14079
+ details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
14080
+ changedFiles: files
14081
+ };
14082
+ const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
14083
+ return applied.error ? { id, title, status: "blocked", summary: applied.error, details: [applied.error], changedFiles: [] } : { id, title, status: "applied", summary: "Restored ignored BMAD installer scripts", details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`], changedFiles: applied.files };
14084
+ }
14085
+ },
13627
14086
  {
13628
14087
  id: "bmad.version",
13629
14088
  title: "BMAD version currency",
@@ -13634,7 +14093,7 @@ function createBmadChecks() {
13634
14093
  id: "bmad.version",
13635
14094
  title: "BMAD version currency",
13636
14095
  status: "skip",
13637
- summary: existsSync8(join11(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
14096
+ summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
13638
14097
  details: [],
13639
14098
  fixable: false
13640
14099
  };
@@ -13722,7 +14181,7 @@ function createBmadChecks() {
13722
14181
  }
13723
14182
  const installed = readInstalledBmadVersion(ctx.repoRoot);
13724
14183
  const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
13725
- const manifestPath = join11(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
14184
+ const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
13726
14185
  const manifestSelection = manifestBmadModules(ctx.repoRoot);
13727
14186
  if (manifestSelection.status === "invalid") {
13728
14187
  return {
@@ -13820,7 +14279,7 @@ function createBmadChecks() {
13820
14279
  const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
13821
14280
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
13822
14281
  if (!install.ok) {
13823
- if (reserved.original !== void 0) writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
14282
+ if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
13824
14283
  return {
13825
14284
  id: finding2.id,
13826
14285
  title: finding2.title,
@@ -13855,7 +14314,7 @@ function createBmadChecks() {
13855
14314
  title: "Supported BMAD CLI projection roots",
13856
14315
  audit: (ctx) => {
13857
14316
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
13858
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
14317
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
13859
14318
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
13860
14319
  const plan = planSkillRoots(ctx.repoRoot);
13861
14320
  const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
@@ -13877,7 +14336,7 @@ function createBmadChecks() {
13877
14336
  },
13878
14337
  migrate: (ctx, finding2) => {
13879
14338
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
13880
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
14339
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
13881
14340
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
13882
14341
  const blocked = attestations.filter((entry) => !entry.safe);
13883
14342
  if (blocked.length) {
@@ -13902,7 +14361,7 @@ function createBmadChecks() {
13902
14361
  };
13903
14362
  }
13904
14363
  const gitignoreChanges = ensureSupportedCliGitignore(ctx);
13905
- const removedRoots = attestations.map((entry) => join11(ctx.repoRoot, entry.name));
14364
+ const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
13906
14365
  if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
13907
14366
  const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
13908
14367
  return {
@@ -13915,7 +14374,7 @@ function createBmadChecks() {
13915
14374
  };
13916
14375
  }
13917
14376
  }
13918
- ].map(withInstallerSkillHandoff);
14377
+ ].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
13919
14378
  }
13920
14379
  function createProjectMomoChecks() {
13921
14380
  return [
@@ -14604,8 +15063,8 @@ var NodeRecipe = class extends Recipe {
14604
15063
  // src/recipes/ProjectRecipe.ts
14605
15064
  init_project();
14606
15065
  import { spawnSync as spawnSync14 } from "node:child_process";
14607
- import { existsSync as existsSync18, lstatSync as lstatSync16, readFileSync as readFileSync22, rmSync as rmSync8 } from "node:fs";
14608
- import { dirname as dirname18, isAbsolute as isAbsolute10, join as join24, relative as relativePath, resolve as resolve19 } from "node:path";
15066
+ import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync23, rmSync as rmSync8 } from "node:fs";
15067
+ import { dirname as dirname18, isAbsolute as isAbsolute10, join as join25, relative as relativePath, resolve as resolve19 } from "node:path";
14609
15068
 
14610
15069
  // src/project/ticketScope.ts
14611
15070
  function ticketScope(ticket) {
@@ -14622,8 +15081,8 @@ init_tree_diff();
14622
15081
 
14623
15082
  // src/recipes/NotebookRecipe.ts
14624
15083
  init_project();
14625
- import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
14626
- import { join as join23 } from "node:path";
15084
+ import { existsSync as existsSync17, readFileSync as readFileSync22 } from "node:fs";
15085
+ import { join as join24 } from "node:path";
14627
15086
 
14628
15087
  // src/notebook/checks.ts
14629
15088
  init_config();
@@ -14631,8 +15090,8 @@ init_notes();
14631
15090
  init_output();
14632
15091
  init_state();
14633
15092
  import { spawnSync as spawnSync8 } from "node:child_process";
14634
- import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
14635
- import { join as join19 } from "node:path";
15093
+ import { existsSync as existsSync14, readFileSync as readFileSync20 } from "node:fs";
15094
+ import { join as join20 } from "node:path";
14636
15095
  var NOTEBOOK_RULE_IDS = [
14637
15096
  "notebook.configuration",
14638
15097
  "notebook.binding",
@@ -14649,10 +15108,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
14649
15108
  return { id: check.id, title: check.title, status, summary, changedFiles, details };
14650
15109
  }
14651
15110
  function manifestNotebook(repo) {
14652
- const path = join19(repo, ".project.json");
15111
+ const path = join20(repo, ".project.json");
14653
15112
  if (!existsSync14(path)) return null;
14654
15113
  try {
14655
- const parsed = JSON.parse(readFileSync19(path, "utf8"));
15114
+ const parsed = JSON.parse(readFileSync20(path, "utf8"));
14656
15115
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
14657
15116
  const notebook = parsed.notebook;
14658
15117
  return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
@@ -14881,7 +15340,7 @@ function captureAudit(ctx) {
14881
15340
  function overviewMigrate(ctx, current) {
14882
15341
  if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
14883
15342
  if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
14884
- const pjanglerEntry = ctx.pjanglerRoot ? join19(ctx.pjanglerRoot, "dist", "index.js") : null;
15343
+ const pjanglerEntry = ctx.pjanglerRoot ? join20(ctx.pjanglerRoot, "dist", "index.js") : null;
14885
15344
  const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
14886
15345
  const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
14887
15346
  const spawned = spawnSync8(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
@@ -14933,7 +15392,7 @@ import { resolve as resolve18 } from "node:path";
14933
15392
 
14934
15393
  // src/notebook/journal-key.ts
14935
15394
  init_types();
14936
- import { createHash as createHash11 } from "node:crypto";
15395
+ import { createHash as createHash12 } from "node:crypto";
14937
15396
  function validateJournalPeriodKey(period, key) {
14938
15397
  if (period === "monthly") {
14939
15398
  if (!/^\d{4}-(?:0[1-9]|1[0-2])$/u.test(key)) throw new NotebookError("INVALID_INPUT", "Monthly period key must be YYYY-MM");
@@ -14952,7 +15411,7 @@ function validateJournalPeriodKey(period, key) {
14952
15411
  }
14953
15412
  function journalNoteOperationId(period, key) {
14954
15413
  validateJournalPeriodKey(period, key);
14955
- const bytes = createHash11("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
15414
+ const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
14956
15415
  bytes[6] = bytes[6] & 15 | 80;
14957
15416
  bytes[8] = bytes[8] & 63 | 128;
14958
15417
  const hex = bytes.toString("hex");
@@ -15108,9 +15567,9 @@ init_notes();
15108
15567
  init_state();
15109
15568
  init_types();
15110
15569
  import { spawn as spawn2, spawnSync as spawnSync11 } from "node:child_process";
15111
- import { createHash as createHash12, randomUUID as randomUUID5 } from "node:crypto";
15112
- import { chmodSync as chmodSync6, closeSync as closeSync6, constants as constants5, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync3, fsyncSync as fsyncSync4, lstatSync as lstatSync15, mkdirSync as mkdirSync9, openSync as openSync6, readFileSync as readFileSync20, readSync as readSync3, readdirSync as readdirSync10, realpathSync as realpathSync10, renameSync as renameSync8, rmSync as rmSync7, symlinkSync as symlinkSync4, unlinkSync as unlinkSync8, writeFileSync as writeFileSync12 } from "node:fs";
15113
- import { basename as basename10, dirname as dirname17, isAbsolute as isAbsolute8, join as join22, parse as parse3, relative as relative15, resolve as resolve16, sep as sep7 } from "node:path";
15570
+ import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
15571
+ import { chmodSync as chmodSync6, closeSync as closeSync7, constants as constants6, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync4, fsyncSync as fsyncSync4, lstatSync as lstatSync16, mkdirSync as mkdirSync10, openSync as openSync7, readFileSync as readFileSync21, readSync as readSync3, readdirSync as readdirSync11, realpathSync as realpathSync10, renameSync as renameSync8, rmSync as rmSync7, symlinkSync as symlinkSync4, unlinkSync as unlinkSync9, writeFileSync as writeFileSync13 } from "node:fs";
15572
+ import { basename as basename11, dirname as dirname17, isAbsolute as isAbsolute8, join as join23, parse as parse3, relative as relative15, resolve as resolve16, sep as sep7 } from "node:path";
15114
15573
  import { fileURLToPath as fileURLToPath3 } from "node:url";
15115
15574
  function captureWorkerEnvironment(source, projectSlug) {
15116
15575
  const result2 = {
@@ -15158,7 +15617,7 @@ function bundledSkillCandidates() {
15158
15617
  const candidates = [];
15159
15618
  let cursor = dirname17(fileURLToPath3(import.meta.url));
15160
15619
  for (let depth = 0; depth < 8; depth++) {
15161
- candidates.push(join22(cursor, "dist", "assets", "project-notebook-skill"), join22(cursor, "assets", "project-notebook-skill"));
15620
+ candidates.push(join23(cursor, "dist", "assets", "project-notebook-skill"), join23(cursor, "assets", "project-notebook-skill"));
15162
15621
  const parent = dirname17(cursor);
15163
15622
  if (parent === cursor) break;
15164
15623
  cursor = parent;
@@ -15176,14 +15635,14 @@ function assertOwnedSkillTree(source) {
15176
15635
  assertNoSymlinkComponents(source);
15177
15636
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
15178
15637
  const walk = (directory) => {
15179
- const directoryStat = lstatSync15(directory);
15638
+ const directoryStat = lstatSync16(directory);
15180
15639
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
15181
15640
  if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
15182
15641
  if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
15183
- for (const entry of readdirSync10(directory, { withFileTypes: true })) {
15642
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
15184
15643
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
15185
- const path = join22(directory, entry.name);
15186
- const stat = lstatSync15(path);
15644
+ const path = join23(directory, entry.name);
15645
+ const stat = lstatSync16(path);
15187
15646
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
15188
15647
  if (entry.isDirectory()) walk(path);
15189
15648
  else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
@@ -15198,18 +15657,18 @@ function assertOwnedSkillTree(source) {
15198
15657
  function enumerateSkillPayload(source) {
15199
15658
  const result2 = [];
15200
15659
  const walk = (directory) => {
15201
- for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
15660
+ for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
15202
15661
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
15203
- const path = join22(directory, entry.name);
15662
+ const path = join23(directory, entry.name);
15204
15663
  const rel = relative15(source, path).split(sep7).join("/");
15205
15664
  if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
15206
15665
  if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
15207
- const stat = lstatSync15(path);
15666
+ const stat = lstatSync16(path);
15208
15667
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
15209
15668
  if (entry.isDirectory()) walk(path);
15210
15669
  else if (entry.isFile()) {
15211
15670
  const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
15212
- result2.push({ path: rel, sha256: createHash12("sha256").update(readFileSync20(path)).digest("hex"), mode: expectedMode });
15671
+ result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync21(path)).digest("hex"), mode: expectedMode });
15213
15672
  } else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
15214
15673
  }
15215
15674
  };
@@ -15217,9 +15676,9 @@ function enumerateSkillPayload(source) {
15217
15676
  return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
15218
15677
  }
15219
15678
  function parsePackedManifest(source) {
15220
- const manifestPath = join22(source, "export-manifest.json");
15679
+ const manifestPath = join23(source, "export-manifest.json");
15221
15680
  if (!existsSync15(manifestPath)) return null;
15222
- const value = JSON.parse(readFileSync20(manifestPath, "utf8"));
15681
+ const value = JSON.parse(readFileSync21(manifestPath, "utf8"));
15223
15682
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
15224
15683
  const manifest = value;
15225
15684
  if (manifest.schema_version !== 1 || manifest.skill !== "project-notebook" || !Array.isArray(manifest.files)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is incompatible");
@@ -15228,10 +15687,10 @@ function parsePackedManifest(source) {
15228
15687
  if (!entry || typeof entry.path !== "string" || !/^[a-f0-9]{64}$/u.test(entry.sha256) || !/^(?:0644|0755)$/u.test(entry.mode)) throw new NotebookError("CONFLICT", "Project Notebook skill export entry is invalid");
15229
15688
  if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
15230
15689
  paths.add(entry.path);
15231
- const path = join22(source, ...entry.path.split("/"));
15232
- const stat = lstatSync15(path);
15690
+ const path = join23(source, ...entry.path.split("/"));
15691
+ const stat = lstatSync16(path);
15233
15692
  if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
15234
- const actual = createHash12("sha256").update(readFileSync20(path)).digest("hex");
15693
+ const actual = createHash13("sha256").update(readFileSync21(path)).digest("hex");
15235
15694
  if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
15236
15695
  const actualMode = stat.mode & 511;
15237
15696
  const executable = entry.mode === "0755";
@@ -15243,16 +15702,16 @@ function parsePackedManifest(source) {
15243
15702
  if (JSON.stringify(actualPaths) !== JSON.stringify(manifest.files.map((entry) => entry.path))) throw new NotebookError("CONFLICT", "Project Notebook skill manifest does not exactly enumerate its payload");
15244
15703
  const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15245
15704
  `;
15246
- if (!existsSync15(join22(source, "SHA256SUMS")) || readFileSync20(join22(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
15705
+ if (!existsSync15(join23(source, "SHA256SUMS")) || readFileSync21(join23(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
15247
15706
  return manifest;
15248
15707
  }
15249
15708
  function expectedPackedSkill() {
15250
15709
  for (const candidate of bundledSkillCandidates()) {
15251
- if (!existsSync15(join22(candidate, "export-manifest.json"))) continue;
15710
+ if (!existsSync15(join23(candidate, "export-manifest.json"))) continue;
15252
15711
  assertOwnedSkillTree(candidate);
15253
15712
  const manifest = parsePackedManifest(candidate);
15254
15713
  if (!manifest) continue;
15255
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
15714
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
15256
15715
  return { source: candidate, manifest, digest };
15257
15716
  }
15258
15717
  throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
@@ -15315,13 +15774,13 @@ function verifyProjectNotebookSkillExport(source) {
15315
15774
  function resolveProjectNotebookSkillSource(env2 = process.env) {
15316
15775
  if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
15317
15776
  const explicit = resolve16(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
15318
- if (!existsSync15(join22(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
15777
+ if (!existsSync15(join23(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
15319
15778
  verifyProjectNotebookSkillExport(explicit);
15320
15779
  return explicit;
15321
15780
  }
15322
15781
  if (env2.PJ_SKILLS_REGISTRY_ROOT) {
15323
15782
  const canonical = resolve16(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
15324
- if (existsSync15(join22(canonical, "SKILL.md"))) {
15783
+ if (existsSync15(join23(canonical, "SKILL.md"))) {
15325
15784
  verifyProjectNotebookSkillExport(canonical);
15326
15785
  return canonical;
15327
15786
  }
@@ -15336,7 +15795,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
15336
15795
  function probeCanonicalSkillexRootProjection(skillsRoot, link) {
15337
15796
  let rootLink;
15338
15797
  try {
15339
- rootLink = lstatSync15(skillsRoot);
15798
+ rootLink = lstatSync16(skillsRoot);
15340
15799
  } catch (error) {
15341
15800
  if (error.code === "ENOENT") return { state: "absent" };
15342
15801
  throw error;
@@ -15349,15 +15808,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
15349
15808
  if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
15350
15809
  const globalRoot = realpathSync10(skillsRoot);
15351
15810
  assertNoSymlinkComponents(globalRoot);
15352
- const globalStat = lstatSync15(globalRoot);
15811
+ const globalStat = lstatSync16(globalRoot);
15353
15812
  if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
15354
15813
  if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
15355
15814
  if (globalStat.mode & 3586) throw new Error("root-mode");
15356
15815
  const skillSetsRoot = dirname17(globalRoot);
15357
- if (basename10(globalRoot) !== "global" || basename10(skillSetsRoot) !== "skill-sets") throw new Error("layout");
15816
+ if (basename11(globalRoot) !== "global" || basename11(skillSetsRoot) !== "skill-sets") throw new Error("layout");
15358
15817
  const checkoutRoot = dirname17(skillSetsRoot);
15359
- const expectedSource = join22(checkoutRoot, "all-skills", "project-notebook");
15360
- const linkStat = lstatSync15(link);
15818
+ const expectedSource = join23(checkoutRoot, "all-skills", "project-notebook");
15819
+ const linkStat = lstatSync16(link);
15361
15820
  if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
15362
15821
  if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
15363
15822
  const projectedSource = realpathSync10(link);
@@ -15391,44 +15850,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
15391
15850
  const source = input.source ?? resolveProjectNotebookSkillSource(env2);
15392
15851
  if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
15393
15852
  const manifest = verifyProjectNotebookSkillExport(source);
15394
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
15853
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
15395
15854
  const home = env2.HOME;
15396
15855
  if (!home || !resolve16(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
15397
- const skillsRoot = join22(home, ".agents", "skills");
15398
- const link = join22(skillsRoot, "project-notebook");
15856
+ const skillsRoot = join23(home, ".agents", "skills");
15857
+ const link = join23(skillsRoot, "project-notebook");
15399
15858
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15400
15859
  if (probe.state === "adopted") return { installed: false, path: link, digest };
15401
15860
  if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
15402
- const dataRoot = resolve16(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
15403
- const payload = join22(dataRoot, `${PJANGLER_VERSION}-${digest}`);
15861
+ const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
15862
+ const payload = join23(dataRoot, `${PJANGLER_VERSION}-${digest}`);
15404
15863
  assertNoSymlinkComponents(dirname17(dataRoot), true);
15405
- mkdirSync9(dataRoot, { recursive: true, mode: 448 });
15864
+ mkdirSync10(dataRoot, { recursive: true, mode: 448 });
15406
15865
  assertNoSymlinkComponents(dataRoot);
15407
- const dataStat = lstatSync15(dataRoot);
15866
+ const dataStat = lstatSync16(dataRoot);
15408
15867
  if (!dataStat.isDirectory() || dataStat.isSymbolicLink() || typeof process.getuid === "function" && dataStat.uid !== process.getuid()) throw new NotebookError("CONFLICT", "Project Notebook skill data root is not a current-user directory");
15409
15868
  chmodSync6(dataRoot, 448);
15410
15869
  if (!existsSync15(payload)) {
15411
- const staging = join22(dataRoot, `.staging-${randomUUID5()}`);
15412
- mkdirSync9(staging, { recursive: false, mode: 448 });
15870
+ const staging = join23(dataRoot, `.staging-${randomUUID5()}`);
15871
+ mkdirSync10(staging, { recursive: false, mode: 448 });
15413
15872
  try {
15414
15873
  for (const entry of manifest.files) {
15415
- const destination = join22(staging, ...entry.path.split("/"));
15416
- mkdirSync9(dirname17(destination), { recursive: true, mode: 493 });
15417
- copyFileSync3(join22(source, ...entry.path.split("/")), destination);
15874
+ const destination = join23(staging, ...entry.path.split("/"));
15875
+ mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
15876
+ copyFileSync3(join23(source, ...entry.path.split("/")), destination);
15418
15877
  const mode = entry.mode === "0755" ? 493 : 420;
15419
15878
  chmodSync6(destination, mode);
15420
- const fd = openSync6(destination, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
15879
+ const fd = openSync7(destination, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
15421
15880
  try {
15422
- const current = fstatSync3(fd);
15881
+ const current = fstatSync4(fd);
15423
15882
  if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
15424
15883
  fsyncSync4(fd);
15425
15884
  } finally {
15426
- closeSync6(fd);
15885
+ closeSync7(fd);
15427
15886
  }
15428
15887
  }
15429
- writeFileSync12(join22(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
15888
+ writeFileSync13(join23(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
15430
15889
  `, { mode: 420, flag: "wx" });
15431
- writeFileSync12(join22(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15890
+ writeFileSync13(join23(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15432
15891
  `, { mode: 420, flag: "wx" });
15433
15892
  verifyProjectNotebookSkillExport(staging);
15434
15893
  renameSync8(staging, payload);
@@ -15436,17 +15895,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
15436
15895
  if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
15437
15896
  }
15438
15897
  } else {
15439
- const stat = lstatSync15(payload);
15898
+ const stat = lstatSync16(payload);
15440
15899
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
15441
15900
  verifyProjectNotebookSkillExport(payload);
15442
15901
  }
15443
15902
  assertNoSymlinkComponents(skillsRoot, true);
15444
- mkdirSync9(skillsRoot, { recursive: true, mode: 448 });
15903
+ mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
15445
15904
  assertNoSymlinkComponents(skillsRoot);
15446
15905
  let linkExists = existsSync15(link);
15447
15906
  if (!linkExists) {
15448
15907
  try {
15449
- lstatSync15(link);
15908
+ lstatSync16(link);
15450
15909
  linkExists = true;
15451
15910
  } catch {
15452
15911
  }
@@ -15463,7 +15922,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
15463
15922
  repair: REPAIR_COMMAND
15464
15923
  }
15465
15924
  });
15466
- const stat = lstatSync15(link);
15925
+ const stat = lstatSync16(link);
15467
15926
  if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
15468
15927
  const target = realpathSync10(link);
15469
15928
  if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
@@ -15491,15 +15950,15 @@ function collectSupersededPayloads(dataRoot, keep) {
15491
15950
  const removed = [];
15492
15951
  let entries;
15493
15952
  try {
15494
- entries = readdirSync10(dataRoot);
15953
+ entries = readdirSync11(dataRoot);
15495
15954
  } catch {
15496
15955
  return removed;
15497
15956
  }
15498
15957
  for (const name of entries) {
15499
15958
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
15500
- const candidate = join22(dataRoot, name);
15959
+ const candidate = join23(dataRoot, name);
15501
15960
  try {
15502
- const stat = lstatSync15(candidate);
15961
+ const stat = lstatSync16(candidate);
15503
15962
  if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
15504
15963
  if (kept.has(realpathSync10(candidate))) continue;
15505
15964
  rmSync7(candidate, { recursive: true, force: true });
@@ -15513,8 +15972,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
15513
15972
  const env2 = input.env ?? process.env;
15514
15973
  const home = env2.HOME;
15515
15974
  if (!home || !resolve16(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
15516
- const skillsRoot = join22(home, ".agents", "skills");
15517
- const link = join22(skillsRoot, "project-notebook");
15975
+ const skillsRoot = join23(home, ".agents", "skills");
15976
+ const link = join23(skillsRoot, "project-notebook");
15518
15977
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15519
15978
  if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
15520
15979
  if (probe.state !== "declined") {
@@ -15527,8 +15986,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
15527
15986
  assertOwnedSkillTree(source);
15528
15987
  const present = [];
15529
15988
  const walk = (directory) => {
15530
- for (const entry of readdirSync10(directory, { withFileTypes: true })) {
15531
- const path = join22(directory, entry.name);
15989
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
15990
+ const path = join23(directory, entry.name);
15532
15991
  const rel = relative15(source, path).split(sep7).join("/");
15533
15992
  if (entry.isDirectory()) {
15534
15993
  walk(path);
@@ -15542,11 +16001,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
15542
16001
  walk(source);
15543
16002
  const stale = present.filter((rel) => !wanted.has(rel)).sort();
15544
16003
  const changed = expected.manifest.files.filter((entry) => {
15545
- const path = join22(source, ...entry.path.split("/"));
16004
+ const path = join23(source, ...entry.path.split("/"));
15546
16005
  if (!existsSync15(path)) return true;
15547
- const stat = lstatSync15(path);
16006
+ const stat = lstatSync16(path);
15548
16007
  if (!stat.isFile()) return true;
15549
- if (createHash12("sha256").update(readFileSync20(path)).digest("hex") !== entry.sha256) return true;
16008
+ if (createHash13("sha256").update(readFileSync21(path)).digest("hex") !== entry.sha256) return true;
15550
16009
  const mode = stat.mode & 511;
15551
16010
  const executable = entry.mode === "0755";
15552
16011
  return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
@@ -15561,15 +16020,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
15561
16020
  summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
15562
16021
  source,
15563
16022
  drift: probe.block.details,
15564
- changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
16023
+ changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
15565
16024
  };
15566
16025
  }
15567
- for (const rel of stale) rmSync7(join22(source, ...rel.split("/")), { force: true });
16026
+ for (const rel of stale) rmSync7(join23(source, ...rel.split("/")), { force: true });
15568
16027
  for (const rel of changed) {
15569
- const destination = join22(source, ...rel.split("/"));
16028
+ const destination = join23(source, ...rel.split("/"));
15570
16029
  const entry = expected.manifest.files.find((item) => item.path === rel);
15571
- mkdirSync9(dirname17(destination), { recursive: true, mode: 493 });
15572
- copyFileSync3(join22(expected.source, ...rel.split("/")), destination);
16030
+ mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
16031
+ copyFileSync3(join23(expected.source, ...rel.split("/")), destination);
15573
16032
  chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
15574
16033
  }
15575
16034
  verifyProjectNotebookSkillExport(source);
@@ -15578,21 +16037,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
15578
16037
  summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
15579
16038
  source,
15580
16039
  drift: probe.block.details,
15581
- changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
16040
+ changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
15582
16041
  };
15583
16042
  }
15584
16043
  function inspectProjectNotebookIntegration(env2 = process.env) {
15585
16044
  const home = env2.HOME;
15586
16045
  if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
15587
- const skillsRoot = join22(home, ".agents", "skills");
15588
- const link = join22(skillsRoot, "project-notebook");
16046
+ const skillsRoot = join23(home, ".agents", "skills");
16047
+ const link = join23(skillsRoot, "project-notebook");
15589
16048
  try {
15590
16049
  const expected = expectedPackedSkill();
15591
- const dataRoot = resolve16(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
15592
- const expectedPayload = join22(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
16050
+ const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
16051
+ const expectedPayload = join23(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
15593
16052
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15594
16053
  if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
15595
- const stat = lstatSync15(link);
16054
+ const stat = lstatSync16(link);
15596
16055
  if (!stat.isSymbolicLink()) {
15597
16056
  return {
15598
16057
  skill_installed: false,
@@ -15629,8 +16088,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
15629
16088
  }
15630
16089
  }
15631
16090
  function projectorArguments(source, command, input) {
15632
- const script = join22(source, "scripts", "project-hooks.py");
15633
- const args = [script, command, "--master", join22(source, "hooks", "hooks.master.json"), "--fragment", join22(source, "hooks", "claude.settings.json"), "--target", input.target];
16091
+ const script = join23(source, "scripts", "project-hooks.py");
16092
+ const args = [script, command, "--master", join23(source, "hooks", "hooks.master.json"), "--fragment", join23(source, "hooks", "claude.settings.json"), "--target", input.target];
15634
16093
  if (command === "check") args.push("--json");
15635
16094
  else if (input.stateHome) args.push("--state-home", input.stateHome);
15636
16095
  return args;
@@ -15652,7 +16111,7 @@ function checkProjectNotebookHooks(input = {}) {
15652
16111
  verifyProjectNotebookSkillExport(source);
15653
16112
  const home = env2.HOME;
15654
16113
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
15655
- const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
16114
+ const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
15656
16115
  const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
15657
16116
  if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
15658
16117
  try {
@@ -15670,14 +16129,14 @@ function installProjectNotebookIntegration(input = {}) {
15670
16129
  const source = realpathSync10(skill.path);
15671
16130
  const home = env2.HOME;
15672
16131
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
15673
- const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
15674
- const stateHome = resolve16(env2.XDG_STATE_HOME || join22(home, ".local", "state"));
16132
+ const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
16133
+ const stateHome = resolve16(env2.XDG_STATE_HOME || join23(home, ".local", "state"));
15675
16134
  const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
15676
16135
  if (result2.status !== 0) {
15677
16136
  if (skill.installed) {
15678
16137
  try {
15679
- const stat = lstatSync15(skill.path);
15680
- if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync8(skill.path);
16138
+ const stat = lstatSync16(skill.path);
16139
+ if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
15681
16140
  } catch {
15682
16141
  }
15683
16142
  }
@@ -15722,10 +16181,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
15722
16181
  const root = parse3(absolute).root;
15723
16182
  let cursor = root;
15724
16183
  for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
15725
- cursor = join22(cursor, component);
16184
+ cursor = join23(cursor, component);
15726
16185
  let stat;
15727
16186
  try {
15728
- stat = lstatSync15(cursor);
16187
+ stat = lstatSync16(cursor);
15729
16188
  } catch (error) {
15730
16189
  if (allowMissing && error.code === "ENOENT") return;
15731
16190
  throw error;
@@ -15744,18 +16203,18 @@ function readHookPayload(input) {
15744
16203
  if (rel === ".." || rel.startsWith(`..${sep7}`) || rel.startsWith(sep7)) throw new NotebookError("INVALID_INPUT", "Hook payload file is outside Notebook XDG state");
15745
16204
  assertNoSymlinkComponents(root);
15746
16205
  assertNoSymlinkComponents(path);
15747
- const before = lstatSync15(path);
16206
+ const before = lstatSync16(path);
15748
16207
  if (!before.isFile() || before.isSymbolicLink() || (before.mode & 511) !== 384 || before.nlink !== 1) throw new NotebookError("INVALID_INPUT", "Hook payload file must be a contained mode-0600 regular file");
15749
16208
  if (typeof process.getuid === "function" && before.uid !== process.getuid()) throw new NotebookError("INVALID_INPUT", "Hook payload file must be owned by the current user");
15750
16209
  if (before.size > input.maxBytes) throw new NotebookError("INVALID_INPUT", "Hook payload file exceeds its configured ceiling");
15751
- const fd = openSync6(path, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
16210
+ const fd = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
15752
16211
  try {
15753
- const opened = fstatSync3(fd);
16212
+ const opened = fstatSync4(fd);
15754
16213
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", "Hook payload file changed while opening");
15755
16214
  const value = readBoundedHookStdin(input.maxBytes, fd);
15756
16215
  return { payload: parsePayload(value, input.maxBytes), bytes: Buffer.byteLength(value, "utf8") };
15757
16216
  } finally {
15758
- closeSync6(fd);
16217
+ closeSync7(fd);
15759
16218
  }
15760
16219
  }
15761
16220
  var ALLOWED_CLIENT_NAMES = /* @__PURE__ */ new Set([
@@ -16482,17 +16941,17 @@ init_remote_mutation_journal();
16482
16941
  function integrationEnvironment(module, ctx) {
16483
16942
  const env2 = { ...module.environment, HOME: ctx.homeDir };
16484
16943
  if (module.environment.HOME !== ctx.homeDir) {
16485
- env2.XDG_DATA_HOME = join23(ctx.homeDir, ".local", "share");
16486
- env2.XDG_STATE_HOME = join23(ctx.homeDir, ".local", "state");
16487
- env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join23(ctx.homeDir, ".claude", "settings.json");
16944
+ env2.XDG_DATA_HOME = join24(ctx.homeDir, ".local", "share");
16945
+ env2.XDG_STATE_HOME = join24(ctx.homeDir, ".local", "state");
16946
+ env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join24(ctx.homeDir, ".claude", "settings.json");
16488
16947
  }
16489
16948
  return env2;
16490
16949
  }
16491
16950
  function resolvedForPlan(plan) {
16492
- const manifestPath = join23(plan.project.repo_path, ".project.json");
16951
+ const manifestPath = join24(plan.project.repo_path, ".project.json");
16493
16952
  let manifest = plan.manifest;
16494
16953
  if (existsSync17(manifestPath)) {
16495
- const parsed = JSON.parse(readFileSync21(manifestPath, "utf8"));
16954
+ const parsed = JSON.parse(readFileSync22(manifestPath, "utf8"));
16496
16955
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
16497
16956
  manifest = parsed;
16498
16957
  }
@@ -16669,7 +17128,7 @@ function unsafeToRemove(targetDir) {
16669
17128
  while (!seen.has(cursor)) {
16670
17129
  seen.add(cursor);
16671
17130
  try {
16672
- const stat = lstatSync16(cursor);
17131
+ const stat = lstatSync17(cursor);
16673
17132
  if (stat.isSymbolicLink()) {
16674
17133
  return cursor === absolute ? `${absolute} is a symlink; removing it would leave the tree it points at orphaned` : `${cursor} is a symlink on the path to ${absolute}; a recursive remove would traverse it`;
16675
17134
  }
@@ -16697,12 +17156,12 @@ function publicMigration(report) {
16697
17156
  };
16698
17157
  }
16699
17158
  function hasGitRepository(runtime, targetDir) {
16700
- if (!existsSync18(join24(targetDir, ".git"))) return false;
17159
+ if (!existsSync18(join25(targetDir, ".git"))) return false;
16701
17160
  return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
16702
17161
  }
16703
17162
  function refreshPlanFromCanonicalManifest(plan) {
16704
- const manifestPath = join24(plan.project.repo_path, ".project.json");
16705
- const manifest = JSON.parse(readFileSync22(manifestPath, "utf8"));
17163
+ const manifestPath = join25(plan.project.repo_path, ".project.json");
17164
+ const manifest = JSON.parse(readFileSync23(manifestPath, "utf8"));
16706
17165
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
16707
17166
  throw new Error(`${manifestPath} must contain a JSON object`);
16708
17167
  }
@@ -16916,12 +17375,12 @@ var ProjectRecipe = class extends Recipe {
16916
17375
  changedFiles: result2.status === "applied" || result2.status === "partial" ? result2.changedFiles : [],
16917
17376
  message: result2.summary
16918
17377
  })));
16919
- errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => `${result2.id}: ${result2.summary}`));
17378
+ errors.push(...migrationReport.results.filter((result2) => result2.status === "blocked" || result2.status === "partial").map((result2) => describeFinding(result2)));
16920
17379
  }
16921
17380
  const eligibilityAudit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : void 0;
16922
17381
  audit = eligibilityAudit;
16923
17382
  if (eligibilityAudit && !eligibilityAudit.ok) {
16924
- errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
17383
+ errors.push(...eligibilityAudit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
16925
17384
  }
16926
17385
  phases.push({
16927
17386
  id: "project.audit:eligibility",
@@ -16933,7 +17392,7 @@ var ProjectRecipe = class extends Recipe {
16933
17392
  if (hasGitRepository(this.runtime, targetDir)) {
16934
17393
  phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
16935
17394
  } else {
16936
- const gitPath = join24(targetDir, ".git");
17395
+ const gitPath = join25(targetDir, ".git");
16937
17396
  for (const { args, label, options } of [
16938
17397
  { args: ["init", "--initial-branch=main"], label: "git init" },
16939
17398
  { args: ["add", "-A"], label: "git add" },
@@ -17012,7 +17471,7 @@ var ProjectRecipe = class extends Recipe {
17012
17471
  transactionContext.notebookManifestBinding = projectedNotebook?.binding ? Object.freeze({ ...projectedNotebook.binding }) : null;
17013
17472
  transactionContext.notebookObservation = Object.freeze(await notebookRecipe.observeExternal(plan, notebookPlan));
17014
17473
  const candidateAudit = publicAudit(await this.registry.auditRecipes(transactionContext, ["notebook"]));
17015
- if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => `${item.id}: ${item.summary}`));
17474
+ if (!candidateAudit.ok) errors.push(...candidateAudit.rules.filter((item) => item.status === "fail" || item.status === "warn").map((item) => describeFinding(item)));
17016
17475
  }
17017
17476
  phases.push({
17018
17477
  id: "project.external:notebook",
@@ -17068,7 +17527,7 @@ var ProjectRecipe = class extends Recipe {
17068
17527
  }
17069
17528
  audit = errors.length === 0 ? publicAudit(await this.registry.auditRecipes({ ...transactionContext, dryRun: true })) : audit;
17070
17529
  if (errors.length === 0 && audit && !audit.ok) {
17071
- errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => `${finding2.id}: ${finding2.summary}`));
17530
+ errors.push(...audit.rules.filter((finding2) => gatesProject(finding2)).map((finding2) => describeFinding(finding2)));
17072
17531
  }
17073
17532
  phases.push({
17074
17533
  id: "project.audit",
@@ -17144,12 +17603,12 @@ var ProjectRecipe = class extends Recipe {
17144
17603
  };
17145
17604
 
17146
17605
  // src/recipes/registry.ts
17147
- import { readFileSync as readFileSync23 } from "node:fs";
17148
- import { join as join25 } from "node:path";
17606
+ import { readFileSync as readFileSync24 } from "node:fs";
17607
+ import { join as join26 } from "node:path";
17149
17608
  function projectWaivers(repoRoot) {
17150
17609
  let waive;
17151
17610
  try {
17152
- waive = JSON.parse(readFileSync23(join25(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
17611
+ waive = JSON.parse(readFileSync24(join26(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
17153
17612
  } catch {
17154
17613
  return /* @__PURE__ */ new Map();
17155
17614
  }
@@ -17490,8 +17949,8 @@ var recipeRegistry = new RecipeRegistry([
17490
17949
 
17491
17950
  // src/commands/AgentHooksCommands.ts
17492
17951
  import { homedir as homedir10 } from "node:os";
17493
- import { join as join26, dirname as dirname19 } from "node:path";
17494
- import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync10, readFileSync as readFileSync24, writeFileSync as writeFileSync13 } from "node:fs";
17952
+ import { join as join27, dirname as dirname19 } from "node:path";
17953
+ import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync14 } from "node:fs";
17495
17954
  import { fileURLToPath as fileURLToPath4 } from "node:url";
17496
17955
  init_project();
17497
17956
  import { parse as parseToml4 } from "smol-toml";
@@ -17504,16 +17963,16 @@ function resolveTemplateRoot() {
17504
17963
  try {
17505
17964
  let dir = dirname19(fileURLToPath4(import.meta.url));
17506
17965
  for (let i = 0; i < 8; i++) {
17507
- candidates.push(join26(dir, "templates", "commonproject", "template"));
17966
+ candidates.push(join27(dir, "templates", "commonproject", "template"));
17508
17967
  const parent = dirname19(dir);
17509
17968
  if (parent === dir) break;
17510
17969
  dir = parent;
17511
17970
  }
17512
17971
  } catch {
17513
17972
  }
17514
- candidates.push(join26(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
17973
+ candidates.push(join27(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
17515
17974
  for (const c of candidates) {
17516
- if (existsSync19(join26(c, ".agents", "hooks", "hooks.master.json"))) return c;
17975
+ if (existsSync19(join27(c, ".agents", "hooks", "hooks.master.json"))) return c;
17517
17976
  }
17518
17977
  throw new Error(
17519
17978
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -17539,15 +17998,15 @@ var CopyAgentHooksTree = class extends Command {
17539
17998
  const created = [];
17540
17999
  const skipped = [];
17541
18000
  for (const { rel, dir } of items) {
17542
- const src = join26(templateRoot, rel);
17543
- const dest = join26(this.context.targetDir, rel);
18001
+ const src = join27(templateRoot, rel);
18002
+ const dest = join27(this.context.targetDir, rel);
17544
18003
  if (!existsSync19(src)) continue;
17545
18004
  if (existsSync19(dest) && !this.context.force) {
17546
18005
  skipped.push(rel);
17547
18006
  continue;
17548
18007
  }
17549
18008
  if (!this.context.dryRun) {
17550
- mkdirSync10(dirname19(dest), { recursive: true });
18009
+ mkdirSync11(dirname19(dest), { recursive: true });
17551
18010
  cpSync(src, dest, { recursive: dir, force: true });
17552
18011
  }
17553
18012
  created.push(rel);
@@ -17568,14 +18027,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
17568
18027
  if (!resolveAgentHooksLayer2()) {
17569
18028
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
17570
18029
  }
17571
- const misePath = join26(this.context.targetDir, "mise.toml");
18030
+ const misePath = join27(this.context.targetDir, "mise.toml");
17572
18031
  if (!existsSync19(misePath)) {
17573
18032
  return {
17574
18033
  success: false,
17575
18034
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
17576
18035
  };
17577
18036
  }
17578
- let content = readFileSync24(misePath, "utf8");
18037
+ let content = readFileSync25(misePath, "utf8");
17579
18038
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
17580
18039
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
17581
18040
  }
@@ -17659,7 +18118,7 @@ ${leaveBlock}`);
17659
18118
  if (!verdict.ok) {
17660
18119
  return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
17661
18120
  }
17662
- if (!this.context.dryRun) writeFileSync13(misePath, content);
18121
+ if (!this.context.dryRun) writeFileSync14(misePath, content);
17663
18122
  if (wiredHooks) {
17664
18123
  return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
17665
18124
  }
@@ -17790,7 +18249,7 @@ if __name__ == "__main__":
17790
18249
 
17791
18250
  // src/commands/AddMiseCodegraphScript.ts
17792
18251
  import { chmodSync as chmodSync7 } from "fs";
17793
- import { join as join27 } from "path";
18252
+ import { join as join28 } from "path";
17794
18253
  var AddMiseCodegraphScript = class extends Command {
17795
18254
  async invoke() {
17796
18255
  const filePath = ".mise/scripts/codegraph.sh";
@@ -17844,7 +18303,7 @@ fi
17844
18303
  `;
17845
18304
  this.writeFile(filePath, content);
17846
18305
  if (!this.context.dryRun) {
17847
- chmodSync7(join27(this.context.targetDir, filePath), 493);
18306
+ chmodSync7(join28(this.context.targetDir, filePath), 493);
17848
18307
  }
17849
18308
  return {
17850
18309
  success: true,
@@ -17882,12 +18341,12 @@ SECRET_KEY=""
17882
18341
  // src/parity/index.ts
17883
18342
  import { existsSync as existsSync20 } from "node:fs";
17884
18343
  import { homedir as homedir11 } from "node:os";
17885
- import { dirname as dirname20, join as join28, resolve as resolve20 } from "node:path";
18344
+ import { dirname as dirname20, join as join29, resolve as resolve20 } from "node:path";
17886
18345
  import { fileURLToPath as fileURLToPath5 } from "node:url";
17887
18346
  function resolvePjanglerRoot2() {
17888
18347
  let dir = dirname20(fileURLToPath5(import.meta.url));
17889
18348
  while (dir !== dirname20(dir)) {
17890
- if (existsSync20(join28(dir, "package.json")) && existsSync20(join28(dir, "templates", "commonproject", "copier.yml"))) return dir;
18349
+ if (existsSync20(join29(dir, "package.json")) && existsSync20(join29(dir, "templates", "commonproject", "copier.yml"))) return dir;
17891
18350
  dir = dirname20(dir);
17892
18351
  }
17893
18352
  return resolve20(process.cwd());
@@ -18079,13 +18538,13 @@ init_project();
18079
18538
  // src/project/info.ts
18080
18539
  init_boardUrl();
18081
18540
  init_project();
18082
- import { readFileSync as readFileSync25 } from "node:fs";
18083
- import { join as join29, resolve as resolve21 } from "node:path";
18541
+ import { readFileSync as readFileSync26 } from "node:fs";
18542
+ import { join as join30, resolve as resolve21 } from "node:path";
18084
18543
  function readManifest2(root) {
18085
- const path = join29(root, ".project.json");
18544
+ const path = join30(root, ".project.json");
18086
18545
  let manifest;
18087
18546
  try {
18088
- const value = JSON.parse(readFileSync25(path, "utf8"));
18547
+ const value = JSON.parse(readFileSync26(path, "utf8"));
18089
18548
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
18090
18549
  manifest = value;
18091
18550
  } catch (error) {
@@ -18126,7 +18585,7 @@ function readProjectInfo(input = {}) {
18126
18585
  ok: true,
18127
18586
  project_id: projectId,
18128
18587
  repo_path: resolve21(root),
18129
- manifest_path: join29(resolve21(root), ".project.json"),
18588
+ manifest_path: join30(resolve21(root), ".project.json"),
18130
18589
  manifest,
18131
18590
  registry: { location, status: "indexed" }
18132
18591
  };
@@ -18175,20 +18634,20 @@ init_project();
18175
18634
  init_version();
18176
18635
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
18177
18636
  import {
18178
- closeSync as closeSync7,
18637
+ closeSync as closeSync8,
18179
18638
  existsSync as existsSync21,
18180
- fchmodSync as fchmodSync4,
18639
+ fchmodSync as fchmodSync5,
18181
18640
  fsyncSync as fsyncSync5,
18182
- lstatSync as lstatSync17,
18183
- mkdirSync as mkdirSync11,
18184
- openSync as openSync7,
18185
- readFileSync as readFileSync26,
18641
+ lstatSync as lstatSync18,
18642
+ mkdirSync as mkdirSync12,
18643
+ openSync as openSync8,
18644
+ readFileSync as readFileSync27,
18186
18645
  renameSync as renameSync9,
18187
- unlinkSync as unlinkSync9,
18188
- writeFileSync as writeFileSync14
18646
+ unlinkSync as unlinkSync10,
18647
+ writeFileSync as writeFileSync15
18189
18648
  } from "node:fs";
18190
18649
  import { homedir as homedir12 } from "node:os";
18191
- import { dirname as dirname21, join as join30, resolve as resolve22 } from "node:path";
18650
+ import { dirname as dirname21, join as join31, resolve as resolve22 } from "node:path";
18192
18651
  import YAML7 from "yaml";
18193
18652
  var DEAD_AGENT_IDS = ["coachingagentframework-pm"];
18194
18653
  function isRecord6(value) {
@@ -18196,7 +18655,7 @@ function isRecord6(value) {
18196
18655
  }
18197
18656
  function hermesAgentsRegistryPath(env2 = process.env, home = homedir12()) {
18198
18657
  const override = env2.HERMES_AGENTS_REGISTRY?.trim();
18199
- return override || join30(home, ".hermes", "agents-registry.yaml");
18658
+ return override || join31(home, ".hermes", "agents-registry.yaml");
18200
18659
  }
18201
18660
  function planeApiKeyFor(workspace, env2, home) {
18202
18661
  return resolvePlaneApiKey(workspace, env2, home) ?? resolvePlaneApiKey(DEFAULT_PLANE_WORKSPACE, env2, home);
@@ -18207,7 +18666,7 @@ async function fetchPlaneBoards(workspace, env2 = process.env, home = homedir12(
18207
18666
  const apiKey = planeApiKeyFor(workspace, env2, home);
18208
18667
  if (!apiKey) {
18209
18668
  throw new BoardError(
18210
- `no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join30(home, ".hermes", "fleet.env")}`
18669
+ `no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env")}`
18211
18670
  );
18212
18671
  }
18213
18672
  const base = planeBase(env2, home);
@@ -18256,7 +18715,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
18256
18715
  const apiKey = planeApiKeyFor(workspace, env2, home);
18257
18716
  if (!apiKey) {
18258
18717
  throw new BoardError(
18259
- `no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join30(home, ".hermes", "fleet.env")}`
18718
+ `no Plane API key for workspace "${workspace}": set PLANE_API_KEY, or add one to ${env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env")}`
18260
18719
  );
18261
18720
  }
18262
18721
  const base = planeBase(env2, home);
@@ -18290,7 +18749,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
18290
18749
  }
18291
18750
  }
18292
18751
  function trelloCredential(name, env2, home) {
18293
- const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join30(home, ".hermes", "fleet.env");
18752
+ const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env");
18294
18753
  const raw = env2[name]?.trim() || dotenvValue(fleetEnv, name)?.trim();
18295
18754
  return raw ? resolveSecretValue(raw) : void 0;
18296
18755
  }
@@ -18328,7 +18787,7 @@ async function fetchTrelloBoard(boardId, env2 = process.env, home = homedir12())
18328
18787
  };
18329
18788
  }
18330
18789
  function readHermesAgentBoards(path) {
18331
- const document = YAML7.parseDocument(readFileSync26(path, "utf8"));
18790
+ const document = YAML7.parseDocument(readFileSync27(path, "utf8"));
18332
18791
  if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
18333
18792
  const parsed = document.toJS();
18334
18793
  const agents = isRecord6(parsed) && isRecord6(parsed.agents) ? parsed.agents : {};
@@ -18359,11 +18818,11 @@ function matchRegistrySlug(agent, registry) {
18359
18818
  }
18360
18819
  function readManifestBoard(repoPath) {
18361
18820
  if (!repoPath) return void 0;
18362
- const path = join30(repoPath, ".project.json");
18821
+ const path = join31(repoPath, ".project.json");
18363
18822
  if (!existsSync21(path)) return void 0;
18364
18823
  let parsed;
18365
18824
  try {
18366
- parsed = JSON.parse(readFileSync26(path, "utf8"));
18825
+ parsed = JSON.parse(readFileSync27(path, "utf8"));
18367
18826
  } catch {
18368
18827
  return void 0;
18369
18828
  }
@@ -18382,32 +18841,32 @@ function yamlStyleOf(original) {
18382
18841
  return { indentSeq: !/\n([ \t]*)[^\s#][^\n]*:[ \t]*\n\1- /.test(original), lineWidth: 0 };
18383
18842
  }
18384
18843
  function atomicWrite(path, text2) {
18385
- const mode = existsSync21(path) ? lstatSync17(path).mode & 511 : 384;
18386
- mkdirSync11(dirname21(path), { recursive: true });
18844
+ const mode = existsSync21(path) ? lstatSync18(path).mode & 511 : 384;
18845
+ mkdirSync12(dirname21(path), { recursive: true });
18387
18846
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
18388
18847
  let fd;
18389
18848
  try {
18390
- fd = openSync7(temp, "wx", mode);
18391
- writeFileSync14(fd, text2, "utf8");
18392
- fchmodSync4(fd, mode);
18849
+ fd = openSync8(temp, "wx", mode);
18850
+ writeFileSync15(fd, text2, "utf8");
18851
+ fchmodSync5(fd, mode);
18393
18852
  fsyncSync5(fd);
18394
- closeSync7(fd);
18853
+ closeSync8(fd);
18395
18854
  fd = void 0;
18396
18855
  renameSync9(temp, path);
18397
18856
  } catch (error) {
18398
18857
  if (fd !== void 0) try {
18399
- closeSync7(fd);
18858
+ closeSync8(fd);
18400
18859
  } catch {
18401
18860
  }
18402
18861
  try {
18403
- unlinkSync9(temp);
18862
+ unlinkSync10(temp);
18404
18863
  } catch {
18405
18864
  }
18406
18865
  throw error;
18407
18866
  }
18408
18867
  }
18409
18868
  function applyHermesIdentifiers(path, identifiers, deletions) {
18410
- const original = readFileSync26(path, "utf8");
18869
+ const original = readFileSync27(path, "utf8");
18411
18870
  const document = YAML7.parseDocument(original);
18412
18871
  if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
18413
18872
  const before = document.toJS();
@@ -18863,11 +19322,11 @@ function formatIdentityReport(report) {
18863
19322
  return lines.join("\n");
18864
19323
  }
18865
19324
  function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
18866
- const path = join30(repoPath, ".project.json");
19325
+ const path = join31(repoPath, ".project.json");
18867
19326
  if (!existsSync21(path)) return void 0;
18868
19327
  let existing = {};
18869
19328
  try {
18870
- const parsed = JSON.parse(readFileSync26(path, "utf8"));
19329
+ const parsed = JSON.parse(readFileSync27(path, "utf8"));
18871
19330
  if (isRecord6(parsed)) existing = parsed;
18872
19331
  } catch {
18873
19332
  throw new BoardError(`${path} is not readable JSON; fix it before linking a board`);
@@ -18892,7 +19351,7 @@ function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
18892
19351
  };
18893
19352
  const text2 = `${JSON.stringify(next, null, 2)}
18894
19353
  `;
18895
- if (readFileSync26(path, "utf8") === text2) return void 0;
19354
+ if (readFileSync27(path, "utf8") === text2) return void 0;
18896
19355
  atomicWrite(path, text2);
18897
19356
  return path;
18898
19357
  }
@@ -18904,8 +19363,8 @@ async function linkProjectBoard(options) {
18904
19363
  const project = getProject(registry, options.slug);
18905
19364
  const boardId = options.boardId.trim();
18906
19365
  if (!boardId) throw new BoardError("a board id is required");
18907
- const manifestFile = join30(project.repo_path, ".project.json");
18908
- const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(readFileSync26(manifestFile, "utf8")).ticket_provider : void 0;
19366
+ const manifestFile = join31(project.repo_path, ".project.json");
19367
+ const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(readFileSync27(manifestFile, "utf8")).ticket_provider : void 0;
18909
19368
  const current = project.ticket_provider;
18910
19369
  const type = (current.type || "plane").trim().toLowerCase();
18911
19370
  const workspace = (options.workspace ?? current.workspace ?? DEFAULT_PLANE_WORKSPACE).trim();
@@ -18956,10 +19415,10 @@ async function linkProjectBoard(options) {
18956
19415
  if (apply) {
18957
19416
  registry.projects[project.slug] = { ...project, ticket_provider: { ...current, ...block }, updated_at: now };
18958
19417
  if (isRegistryServiceLocation(registryPath)) {
18959
- const beforeManifest = existsSync21(manifestFile) ? readFileSync26(manifestFile, "utf8") : null;
19418
+ const beforeManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
18960
19419
  saveProjectRegistry(registry, registryPath);
18961
19420
  manifestPath = manifestFile;
18962
- const afterManifest = existsSync21(manifestFile) ? readFileSync26(manifestFile, "utf8") : null;
19421
+ const afterManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
18963
19422
  if (beforeManifest !== afterManifest) changedFiles.push(manifestFile);
18964
19423
  } else {
18965
19424
  manifestPath = writeManifestTicketProvider(project.repo_path, block, expectedProvider);
@@ -18968,7 +19427,7 @@ async function linkProjectBoard(options) {
18968
19427
  changedFiles.push(registryPath);
18969
19428
  }
18970
19429
  } else {
18971
- manifestPath = existsSync21(join30(project.repo_path, ".project.json")) ? join30(project.repo_path, ".project.json") : void 0;
19430
+ manifestPath = existsSync21(join31(project.repo_path, ".project.json")) ? join31(project.repo_path, ".project.json") : void 0;
18972
19431
  }
18973
19432
  return {
18974
19433
  ok: true,
@@ -18992,9 +19451,9 @@ async function linkProjectBoard(options) {
18992
19451
  // src/project/rename.ts
18993
19452
  init_boardUrl();
18994
19453
  import { spawnSync as spawnSync15 } from "node:child_process";
18995
- import { existsSync as existsSync22, readdirSync as readdirSync11, readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync6 } from "node:fs";
19454
+ import { existsSync as existsSync22, readdirSync as readdirSync12, readFileSync as readFileSync28, realpathSync as realpathSync11, statSync as statSync6 } from "node:fs";
18996
19455
  import { homedir as homedir13 } from "node:os";
18997
- import { isAbsolute as isAbsolute11, join as join31, relative as relative16 } from "node:path";
19456
+ import { isAbsolute as isAbsolute11, join as join32, relative as relative16 } from "node:path";
18998
19457
  import YAML8 from "yaml";
18999
19458
  init_project();
19000
19459
  init_registryClient();
@@ -19015,7 +19474,7 @@ function message(error) {
19015
19474
  function readJsonObject(path) {
19016
19475
  let parsed;
19017
19476
  try {
19018
- parsed = JSON.parse(readFileSync27(path, "utf8"));
19477
+ parsed = JSON.parse(readFileSync28(path, "utf8"));
19019
19478
  } catch {
19020
19479
  throw new RenameError("manifest_invalid", `${path} is not readable JSON; fix it before renaming`);
19021
19480
  }
@@ -19068,7 +19527,7 @@ async function renameProject(options) {
19068
19527
  let from;
19069
19528
  if (existing && oldInput) {
19070
19529
  try {
19071
- const candidate = readJsonObject(join31(existing.repo_path, ".project.json")).renamed_from;
19530
+ const candidate = readJsonObject(join32(existing.repo_path, ".project.json")).renamed_from;
19072
19531
  if (isRecord7(candidate)) from = candidate;
19073
19532
  } catch {
19074
19533
  }
@@ -19087,7 +19546,7 @@ async function renameProject(options) {
19087
19546
  const isPlane = type === "plane";
19088
19547
  const workspace = isPlane ? String(provider.workspace ?? "").trim() || DEFAULT_PLANE_WORKSPACE : String(provider.workspace ?? "").trim();
19089
19548
  const repoPath = record3.repo_path;
19090
- const manifestPath = join31(repoPath, ".project.json");
19549
+ const manifestPath = join32(repoPath, ".project.json");
19091
19550
  const manifestExists = existsSync22(manifestPath);
19092
19551
  if (service && !manifestExists) {
19093
19552
  throw new RenameError("manifest_missing", `${manifestPath} is missing; the registry service indexes manifests, so there is nothing to rename`);
@@ -19232,7 +19691,7 @@ async function renameProject(options) {
19232
19691
  return { status: "done", detail, files: service ? [] : [registryPath] };
19233
19692
  };
19234
19693
  const roleStep = async (doApply) => {
19235
- const hermesDir = join31(repoPath, "agents", "hermes");
19694
+ const hermesDir = join32(repoPath, "agents", "hermes");
19236
19695
  if (!existsSync22(hermesDir)) return { status: "skipped", detail: "no agents/hermes directory" };
19237
19696
  if (!isPlane) return { status: "skipped", detail: "role.yaml plane.identifier is a Plane field" };
19238
19697
  const files = [];
@@ -19240,16 +19699,16 @@ async function renameProject(options) {
19240
19699
  let settled = 0;
19241
19700
  let entries;
19242
19701
  try {
19243
- entries = readdirSync11(hermesDir, { withFileTypes: true });
19702
+ entries = readdirSync12(hermesDir, { withFileTypes: true });
19244
19703
  } catch (error) {
19245
19704
  return { status: "failed", detail: `${hermesDir}: ${message(error)}` };
19246
19705
  }
19247
19706
  for (const entry of entries) {
19248
19707
  if (!entry.isDirectory()) continue;
19249
- const path = join31(hermesDir, entry.name, "role.yaml");
19708
+ const path = join32(hermesDir, entry.name, "role.yaml");
19250
19709
  if (!existsSync22(path)) continue;
19251
19710
  try {
19252
- const original = readFileSync27(path, "utf8");
19711
+ const original = readFileSync28(path, "utf8");
19253
19712
  const document = YAML8.parseDocument(original);
19254
19713
  if (document.errors.length) throw new Error("YAML is invalid");
19255
19714
  const node = document.getIn(["plane", "identifier"], true);
@@ -19307,7 +19766,7 @@ async function renameProject(options) {
19307
19766
  }
19308
19767
  };
19309
19768
  const agentsMdStep = async (doApply) => {
19310
- const link = join31(repoPath, "AGENTS.md");
19769
+ const link = join32(repoPath, "AGENTS.md");
19311
19770
  if (!existsSync22(link)) return { status: "skipped", detail: "no AGENTS.md" };
19312
19771
  try {
19313
19772
  const path = realpathSync11(link);
@@ -19315,7 +19774,7 @@ async function renameProject(options) {
19315
19774
  if (inside.startsWith("..") || isAbsolute11(inside)) {
19316
19775
  return { status: "skipped", detail: `AGENTS.md resolves outside the repo (${path}); left untouched` };
19317
19776
  }
19318
- const original = readFileSync27(path, "utf8");
19777
+ const original = readFileSync28(path, "utf8");
19319
19778
  const lines = original.split("\n");
19320
19779
  let boardLines = 0;
19321
19780
  let replaced = 0;
@@ -19345,8 +19804,8 @@ async function renameProject(options) {
19345
19804
  }
19346
19805
  };
19347
19806
  const hintsStep = async (doApply) => {
19348
- const configDir = env2.PILOT_CONFIG_DIR?.trim() || join31(env2.XDG_CONFIG_HOME?.trim() || join31(home, ".config"), "pilot");
19349
- const path = join31(configDir, "harvest", "project-hints.json");
19807
+ const configDir = env2.PILOT_CONFIG_DIR?.trim() || join32(env2.XDG_CONFIG_HOME?.trim() || join32(home, ".config"), "pilot");
19808
+ const path = join32(configDir, "harvest", "project-hints.json");
19350
19809
  if (!existsSync22(path)) return { status: "skipped", detail: `no harvest hints at ${path}` };
19351
19810
  try {
19352
19811
  const hints = readJsonObject(path);
@@ -19380,7 +19839,7 @@ async function renameProject(options) {
19380
19839
  if (oldInput === newId && steps.every((step) => step.status === "already" || step.status === "skipped")) {
19381
19840
  return { status: "already", detail: "nothing changed; projection not re-synced" };
19382
19841
  }
19383
- const cwd = join31(home, "code", "33GOD", "candystore");
19842
+ const cwd = join32(home, "code", "33GOD", "candystore");
19384
19843
  try {
19385
19844
  if (!statSync6(cwd).isDirectory()) throw new Error("not a directory");
19386
19845
  } catch {
@@ -19475,7 +19934,7 @@ init_boardQuery();
19475
19934
  // src/describe/activity.ts
19476
19935
  import { spawn as spawn3, spawnSync as spawnSync16 } from "node:child_process";
19477
19936
  import { statSync as statSync7 } from "node:fs";
19478
- import { join as join32 } from "node:path";
19937
+ import { join as join33 } from "node:path";
19479
19938
  var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
19480
19939
  var MAX_DIRTY_STATS = 500;
19481
19940
  var GIT_TIMEOUT_MS = 5e3;
@@ -19640,7 +20099,7 @@ function uncommittedSource(repo, paths) {
19640
20099
  let newest = 0;
19641
20100
  for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
19642
20101
  try {
19643
- const mtime = Math.floor(statSync7(join32(repo, path)).mtimeMs / 1e3);
20102
+ const mtime = Math.floor(statSync7(join33(repo, path)).mtimeMs / 1e3);
19644
20103
  if (mtime > newest) newest = mtime;
19645
20104
  } catch {
19646
20105
  }
@@ -19838,8 +20297,8 @@ function openUrl(url, env2 = process.env, platform = process.platform) {
19838
20297
  }
19839
20298
 
19840
20299
  // src/describe/index.ts
19841
- import { existsSync as existsSync23, readFileSync as readFileSync28, readdirSync as readdirSync12, statSync as statSync8 } from "node:fs";
19842
- import { join as join33, resolve as resolve23 } from "node:path";
20300
+ import { existsSync as existsSync23, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync8 } from "node:fs";
20301
+ import { join as join34, resolve as resolve23 } from "node:path";
19843
20302
  init_project();
19844
20303
  init_config();
19845
20304
  init_state();
@@ -19895,7 +20354,7 @@ var CONFIG_FILES = [
19895
20354
  ];
19896
20355
  function readJson(path) {
19897
20356
  try {
19898
- const parsed = JSON.parse(readFileSync28(path, "utf8"));
20357
+ const parsed = JSON.parse(readFileSync29(path, "utf8"));
19899
20358
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
19900
20359
  } catch {
19901
20360
  return void 0;
@@ -19917,23 +20376,23 @@ function describeType(repo) {
19917
20376
  const evidence = [];
19918
20377
  const note = (signal, file) => evidence.push(`${signal} (${file})`);
19919
20378
  for (const marker of LANGUAGE_MARKERS) {
19920
- if (!existsSync23(join33(repo, marker.file))) continue;
20379
+ if (!existsSync23(join34(repo, marker.file))) continue;
19921
20380
  if (!languages.includes(marker.language)) {
19922
20381
  languages.push(marker.language);
19923
20382
  note(marker.language, marker.file);
19924
20383
  }
19925
20384
  }
19926
20385
  try {
19927
- const dotnet = readdirSync12(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
20386
+ const dotnet = readdirSync13(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
19928
20387
  if (dotnet && !languages.includes("dotnet")) {
19929
20388
  languages.push("dotnet");
19930
20389
  note("dotnet", dotnet);
19931
20390
  }
19932
20391
  } catch {
19933
20392
  }
19934
- const pkg = readJson(join33(repo, "package.json"));
20393
+ const pkg = readJson(join34(repo, "package.json"));
19935
20394
  if (pkg) {
19936
- if (existsSync23(join33(repo, "tsconfig.json"))) {
20395
+ if (existsSync23(join34(repo, "tsconfig.json"))) {
19937
20396
  const index = languages.indexOf("javascript");
19938
20397
  if (index >= 0) languages.splice(index, 1);
19939
20398
  if (!languages.includes("typescript")) {
@@ -19967,14 +20426,14 @@ function describeType(repo) {
19967
20426
  ["hermes-fleet-host", "agents/hermes"]
19968
20427
  ];
19969
20428
  for (const [role, marker] of roleMarkers) {
19970
- if (!existsSync23(join33(repo, marker))) continue;
20429
+ if (!existsSync23(join34(repo, marker))) continue;
19971
20430
  roles.push(role);
19972
20431
  note(role, marker);
19973
20432
  }
19974
20433
  return { primaryLanguage: languages[0], languages, roles, evidence };
19975
20434
  }
19976
20435
  function describeIdentity(repo, registryPath) {
19977
- const manifestPath = join33(repo, ".project.json");
20436
+ const manifestPath = join34(repo, ".project.json");
19978
20437
  const manifest = readJson(manifestPath);
19979
20438
  const drift = [];
19980
20439
  let record3;
@@ -20067,12 +20526,12 @@ function describeSubsystems(repo, findings) {
20067
20526
  const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
20068
20527
  const evidence = metadata.id === "notebook" ? (() => {
20069
20528
  try {
20070
- const manifest = JSON.parse(readFileSync28(join33(repo, ".project.json"), "utf8"));
20529
+ const manifest = JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8"));
20071
20530
  return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
20072
20531
  } catch {
20073
20532
  return [];
20074
20533
  }
20075
- })() : markers.filter((marker) => existsSync23(join33(repo, marker)));
20534
+ })() : markers.filter((marker) => existsSync23(join34(repo, marker)));
20076
20535
  const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
20077
20536
  id: finding2.id,
20078
20537
  title: finding2.title,
@@ -20091,7 +20550,7 @@ function describeNotebook(repo, registryPath) {
20091
20550
  try {
20092
20551
  const registry = loadProjectRegistry(registryPath);
20093
20552
  const project = Object.values(registry.projects).find((entry) => resolve23(entry.repo_path) === resolve23(repo));
20094
- const manifest = existsSync23(join33(repo, ".project.json")) ? JSON.parse(readFileSync28(join33(repo, ".project.json"), "utf8")) : void 0;
20553
+ const manifest = existsSync23(join34(repo, ".project.json")) ? JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8")) : void 0;
20095
20554
  const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
20096
20555
  if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
20097
20556
  const config = loadEffectiveNotebookConfig(repo, registryPath);
@@ -20109,7 +20568,7 @@ function describeNotebook(repo, registryPath) {
20109
20568
  }
20110
20569
  }
20111
20570
  function describeConfigFiles(repo) {
20112
- return CONFIG_FILES.filter((spec) => existsSync23(join33(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
20571
+ return CONFIG_FILES.filter((spec) => existsSync23(join34(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
20113
20572
  }
20114
20573
  function describeNextSteps(description, findings) {
20115
20574
  const steps = [];
@@ -20533,7 +20992,7 @@ init_style();
20533
20992
  // src/notebook/cli.ts
20534
20993
  init_project();
20535
20994
  init_capture();
20536
- import { closeSync as closeSync8, constants as constants6, existsSync as existsSync24, fstatSync as fstatSync4, lstatSync as lstatSync18, openSync as openSync8, readSync as readSync4 } from "node:fs";
20995
+ import { closeSync as closeSync9, constants as constants7, existsSync as existsSync24, fstatSync as fstatSync5, lstatSync as lstatSync19, openSync as openSync9, readSync as readSync4 } from "node:fs";
20537
20996
  import { resolve as resolve24 } from "node:path";
20538
20997
  import { createInterface } from "node:readline/promises";
20539
20998
 
@@ -20676,12 +21135,12 @@ async function execute(input) {
20676
21135
  function readBoundedRegularFile(path, maxBytes) {
20677
21136
  const absolute = resolve24(path);
20678
21137
  if (!existsSync24(absolute)) throw new NotebookError("INVALID_INPUT", `File not found: ${absolute}`);
20679
- const before = lstatSync18(absolute);
21138
+ const before = lstatSync19(absolute);
20680
21139
  if (!before.isFile() || before.isSymbolicLink()) throw new NotebookError("INVALID_INPUT", `File must be a regular non-symlink: ${absolute}`);
20681
21140
  if (before.size > maxBytes) throw new NotebookError("INVALID_INPUT", `File exceeds the configured ceiling: ${absolute}`);
20682
- const fd = openSync8(absolute, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
21141
+ const fd = openSync9(absolute, constants7.O_RDONLY | (constants7.O_NOFOLLOW ?? 0));
20683
21142
  try {
20684
- const opened = fstatSync4(fd);
21143
+ const opened = fstatSync5(fd);
20685
21144
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", `File changed while opening: ${absolute}`);
20686
21145
  const chunks = [];
20687
21146
  let total = 0;
@@ -20699,7 +21158,7 @@ function readBoundedRegularFile(path, maxBytes) {
20699
21158
  throw new NotebookError("INVALID_INPUT", `File must be valid UTF-8: ${absolute}`);
20700
21159
  }
20701
21160
  } finally {
20702
- closeSync8(fd);
21161
+ closeSync9(fd);
20703
21162
  }
20704
21163
  }
20705
21164
  function textInput(options, maxBytes) {
@@ -20951,14 +21410,14 @@ async function loadScreen(relativeToDist, base) {
20951
21410
  init_boardQuery();
20952
21411
  init_style();
20953
21412
  import { spawnSync as spawnSync17 } from "node:child_process";
20954
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync30, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "node:fs";
21413
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync31, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync16 } from "node:fs";
20955
21414
  import { homedir as homedir15 } from "node:os";
20956
- import { dirname as dirname22, join as join35 } from "node:path";
21415
+ import { dirname as dirname22, join as join36 } from "node:path";
20957
21416
 
20958
21417
  // src/crank/engine.ts
20959
- import { closeSync as closeSync9, constants as constants7, fstatSync as fstatSync5, openSync as openSync9, readFileSync as readFileSync29, readSync as readSync5 } from "node:fs";
21418
+ import { closeSync as closeSync10, constants as constants8, fstatSync as fstatSync6, openSync as openSync10, readFileSync as readFileSync30, readSync as readSync5 } from "node:fs";
20960
21419
  import { homedir as homedir14 } from "node:os";
20961
- import { join as join34 } from "node:path";
21420
+ import { join as join35 } from "node:path";
20962
21421
  var ENGINE_DEFAULT_JOB_ID = "777c511a009c";
20963
21422
  var WINDOW_MS = 24 * 60 * 60 * 1e3;
20964
21423
  var TICK_MS = 5 * 60 * 1e3;
@@ -21058,9 +21517,9 @@ function parseEngineStatus(auditText, input) {
21058
21517
  }
21059
21518
  }
21060
21519
  function readTail(path, maxBytes) {
21061
- const fd = openSync9(path, constants7.O_RDONLY | constants7.O_NONBLOCK);
21520
+ const fd = openSync10(path, constants8.O_RDONLY | constants8.O_NONBLOCK);
21062
21521
  try {
21063
- const stat = fstatSync5(fd);
21522
+ const stat = fstatSync6(fd);
21064
21523
  if (!stat.isFile()) throw new Error(`${path} is not a regular file`);
21065
21524
  const size = stat.size;
21066
21525
  const length = Math.min(size, maxBytes);
@@ -21076,20 +21535,20 @@ function readTail(path, maxBytes) {
21076
21535
  const newline = text2.indexOf("\n");
21077
21536
  return newline === -1 ? "" : text2.slice(newline + 1);
21078
21537
  } finally {
21079
- closeSync9(fd);
21538
+ closeSync10(fd);
21080
21539
  }
21081
21540
  }
21082
21541
  function engineSources(env2 = process.env) {
21083
- const cronDir = join34(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
21542
+ const cronDir = join35(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
21084
21543
  return {
21085
- auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join34(cronDir, "usage_audit.jsonl"),
21086
- jobsPath: join34(cronDir, "jobs.json"),
21544
+ auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join35(cronDir, "usage_audit.jsonl"),
21545
+ jobsPath: join35(cronDir, "jobs.json"),
21087
21546
  jobId: env2.PJ_CRANK_JOB_ID?.trim() || ENGINE_DEFAULT_JOB_ID
21088
21547
  };
21089
21548
  }
21090
21549
  function readCap(jobsPath, jobId) {
21091
21550
  try {
21092
- const parsed = JSON.parse(readFileSync29(jobsPath, "utf8"));
21551
+ const parsed = JSON.parse(readFileSync30(jobsPath, "utf8"));
21093
21552
  const jobs = Array.isArray(parsed.jobs) ? parsed.jobs : [];
21094
21553
  const job = jobs.find((j) => typeof j === "object" && j !== null && String(j.id ?? "") === jobId);
21095
21554
  const economics = job?.economics;
@@ -21475,11 +21934,11 @@ function withTabOwners(tabs, panes, identify, claimed = []) {
21475
21934
  }
21476
21935
  var PANE_CWD_TTL_SECONDS = 6 * 3600;
21477
21936
  function paneCwdCachePath(env2 = process.env) {
21478
- return env2.ZCRANK_CWD_CACHE?.trim() || join35(homedir15(), ".local/state/zcrank/pane-cwds.json");
21937
+ return env2.ZCRANK_CWD_CACHE?.trim() || join36(homedir15(), ".local/state/zcrank/pane-cwds.json");
21479
21938
  }
21480
21939
  function loadCwdCache(path) {
21481
21940
  try {
21482
- const parsed = JSON.parse(readFileSync30(path, "utf8"));
21941
+ const parsed = JSON.parse(readFileSync31(path, "utf8"));
21483
21942
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
21484
21943
  } catch {
21485
21944
  return {};
@@ -21487,9 +21946,9 @@ function loadCwdCache(path) {
21487
21946
  }
21488
21947
  function saveCwdCache(path, cache) {
21489
21948
  try {
21490
- mkdirSync12(dirname22(path), { recursive: true });
21949
+ mkdirSync13(dirname22(path), { recursive: true });
21491
21950
  const tmp = `${path}.${process.pid}.tmp`;
21492
- writeFileSync15(tmp, JSON.stringify(cache));
21951
+ writeFileSync16(tmp, JSON.stringify(cache));
21493
21952
  renameSync10(tmp, path);
21494
21953
  } catch {
21495
21954
  }
@@ -21642,7 +22101,7 @@ function resolveByName(project, live, storedTabs) {
21642
22101
  function projectNameFor(handle, registryName) {
21643
22102
  if (registryName && registryName.trim()) return registryName.trim();
21644
22103
  try {
21645
- const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
22104
+ const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
21646
22105
  const name = manifest.project_name;
21647
22106
  if (typeof name === "string" && name.trim()) return name.trim();
21648
22107
  } catch {
@@ -21653,7 +22112,7 @@ function projectAgentsFor(handle, registryAgents) {
21653
22112
  const fromRecord = Object.entries(registryAgents ?? {}).map(([agentHandle, value]) => ({ handle: agentHandle, role: String(value?.role ?? "") }));
21654
22113
  if (fromRecord.length) return fromRecord;
21655
22114
  try {
21656
- const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
22115
+ const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
21657
22116
  const agents = manifest.agents;
21658
22117
  if (agents && typeof agents === "object" && !Array.isArray(agents)) {
21659
22118
  return Object.entries(agents).map(([agentHandle, value]) => ({ handle: agentHandle, role: typeof value?.role === "string" ? value.role : "" }));
@@ -21971,9 +22430,9 @@ function createCrankController(deps) {
21971
22430
  }
21972
22431
 
21973
22432
  // src/crank/panes.ts
21974
- import { readFileSync as readFileSync31, readdirSync as readdirSync13, readlinkSync as readlinkSync9 } from "node:fs";
22433
+ import { readFileSync as readFileSync32, readdirSync as readdirSync14, readlinkSync as readlinkSync9 } from "node:fs";
21975
22434
  import { homedir as homedir16 } from "node:os";
21976
- import { basename as basename11, join as join36 } from "node:path";
22435
+ import { basename as basename12, join as join37 } from "node:path";
21977
22436
  var AGENT_CLIS = [
21978
22437
  "claude",
21979
22438
  "codex",
@@ -22055,7 +22514,7 @@ function parseProcStat(text2) {
22055
22514
  }
22056
22515
  function readProcStat(pid) {
22057
22516
  try {
22058
- return parseProcStat(readFileSync31(`/proc/${pid}/stat`, "latin1"));
22517
+ return parseProcStat(readFileSync32(`/proc/${pid}/stat`, "latin1"));
22059
22518
  } catch {
22060
22519
  return null;
22061
22520
  }
@@ -22063,7 +22522,7 @@ function readProcStat(pid) {
22063
22522
  function readTag(pid) {
22064
22523
  let raw;
22065
22524
  try {
22066
- raw = readFileSync31(`/proc/${pid}/environ`);
22525
+ raw = readFileSync32(`/proc/${pid}/environ`);
22067
22526
  } catch {
22068
22527
  return null;
22069
22528
  }
@@ -22089,8 +22548,8 @@ function readTag(pid) {
22089
22548
  }
22090
22549
  function readArgv(pid) {
22091
22550
  try {
22092
- const parts = readFileSync31(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
22093
- return { argv: parts.join(" "), exe: parts[0] ? basename11(parts[0]) : "" };
22551
+ const parts = readFileSync32(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
22552
+ return { argv: parts.join(" "), exe: parts[0] ? basename12(parts[0]) : "" };
22094
22553
  } catch {
22095
22554
  return { argv: "", exe: "" };
22096
22555
  }
@@ -22130,7 +22589,7 @@ var ProcScanner = class {
22130
22589
  pids = [...this.enumerate()];
22131
22590
  } else {
22132
22591
  try {
22133
- pids = readdirSync13("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
22592
+ pids = readdirSync14("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
22134
22593
  } catch {
22135
22594
  return [];
22136
22595
  }
@@ -22166,13 +22625,13 @@ var ProcScanner = class {
22166
22625
  var PANE_STATUSES = ["busy", "idle", "shell"];
22167
22626
  function claudeSessionsDir(env2 = process.env) {
22168
22627
  const override = env2.PJ_CLAUDE_SESSIONS_DIR?.trim();
22169
- return override || join36(homedir16(), ".claude", "sessions");
22628
+ return override || join37(homedir16(), ".claude", "sessions");
22170
22629
  }
22171
22630
  function readClaudeSession(pid, stat, dir = claudeSessionsDir()) {
22172
22631
  const none = (reason) => ({ kind: null, sessionId: null, reason, status: "unknown", statusUpdatedAt: null });
22173
22632
  let raw;
22174
22633
  try {
22175
- raw = readFileSync31(join36(dir, `${pid}.json`), "utf8");
22634
+ raw = readFileSync32(join37(dir, `${pid}.json`), "utf8");
22176
22635
  } catch {
22177
22636
  return none("no-session-file");
22178
22637
  }
@@ -22633,7 +23092,7 @@ async function promptForRuleIds(rules) {
22633
23092
  function readJson2(path) {
22634
23093
  if (!existsSync26(path)) return void 0;
22635
23094
  try {
22636
- const parsed = JSON.parse(readFileSync32(path, "utf8"));
23095
+ const parsed = JSON.parse(readFileSync33(path, "utf8"));
22637
23096
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
22638
23097
  } catch {
22639
23098
  return void 0;
@@ -22650,9 +23109,9 @@ function packageNameToProjectName(value) {
22650
23109
  return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
22651
23110
  }
22652
23111
  function deriveProjectDefaults(targetDir) {
22653
- const manifest = readJson2(join37(targetDir, ".project.json"));
22654
- const pkg = readJson2(join37(targetDir, "package.json"));
22655
- const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename12(targetDir)) || "Project";
23112
+ const manifest = readJson2(join38(targetDir, ".project.json"));
23113
+ const pkg = readJson2(join38(targetDir, "package.json"));
23114
+ const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename13(targetDir)) || "Project";
22656
23115
  const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
22657
23116
  return {
22658
23117
  name,
@@ -22679,11 +23138,19 @@ async function promptTextValue(message2, initialValue) {
22679
23138
  }
22680
23139
  return value.trim();
22681
23140
  }
22682
- function reportBoardDelivery(board, slug, skipRequested) {
23141
+ function reportBoardDelivery(board, slug, skipRequested, reached = true) {
22683
23142
  if (board.confirmed) {
22684
23143
  console.log(` ${green(glyph.pass)} ${bold("Board linked")} ${dim(glyph.dot)} ${cyan(`${board.provider}/${board.workspace}/${board.identifier}`)} ${dim(board.boardId)}`);
22685
23144
  return true;
22686
23145
  }
23146
+ if (!reached && !skipRequested) {
23147
+ console.error("");
23148
+ console.error(` ${xmark2} ${bold("Board step not reached")} ${dim(glyph.dot)} ${cyan(slug)}`);
23149
+ console.error(` ${dim("project init failed before the ticket-provider phase; no board was created or looked up")}`);
23150
+ console.error(` ${dim("fix the error(s) above, then re-run the same command. The provider credential is not the cause.")}`);
23151
+ console.error("");
23152
+ return false;
23153
+ }
22687
23154
  const mark = skipRequested ? `${yellow(glyph.warn)}` : xmark2;
22688
23155
  const title = skipRequested ? "No ticket board (--skip-board)" : "No ticket board";
22689
23156
  const log = skipRequested ? console.log : console.error;
@@ -22726,7 +23193,7 @@ function actionNeedsRun(plan, kind, syncMode) {
22726
23193
  if (!action || action.kind !== "project.write-manifest") return false;
22727
23194
  const next = `${JSON.stringify(action.manifest, null, 2)}
22728
23195
  `;
22729
- return !existsSync26(action.path) || readFileSync32(action.path, "utf8") !== next;
23196
+ return !existsSync26(action.path) || readFileSync33(action.path, "utf8") !== next;
22730
23197
  }
22731
23198
  if (kind === "copier.copy.commonproject") return true;
22732
23199
  if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
@@ -22783,11 +23250,11 @@ async function resolveProjectInitTarget(name, options) {
22783
23250
  if (!targetDir && cwdGitRoot) {
22784
23251
  targetDir = cwdGitRoot;
22785
23252
  }
22786
- if (!targetDir && !name && existsSync26(join37(cwd, ".project.json"))) {
23253
+ if (!targetDir && !name && existsSync26(join38(cwd, ".project.json"))) {
22787
23254
  targetDir = cwd;
22788
23255
  }
22789
23256
  if (!targetDir && interactive) {
22790
- const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename12(cwd)));
23257
+ const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename13(cwd)));
22791
23258
  targetDir = resolve25(await promptTextValue("Project directory", projectTargetDirUnder(promptedName, cwd)));
22792
23259
  name = promptedName;
22793
23260
  }
@@ -22797,14 +23264,14 @@ async function resolveProjectInitTarget(name, options) {
22797
23264
  const targetExists = existsSync26(targetDir);
22798
23265
  if (targetExists && !statSync9(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
22799
23266
  const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
22800
- const alreadyScaffolded = targetExists && (existsSync26(join37(targetDir, ".project.json")) || existsSync26(join37(targetDir, ".copier-answers.yml")));
23267
+ const alreadyScaffolded = targetExists && (existsSync26(join38(targetDir, ".project.json")) || existsSync26(join38(targetDir, ".copier-answers.yml")));
22801
23268
  const syncMode = Boolean(
22802
23269
  targetGitRoot && resolve25(targetGitRoot) === resolve25(targetDir) || alreadyScaffolded
22803
23270
  );
22804
- if (targetExists && !syncMode && !options.force && readdirSync14(targetDir).length > 0) {
23271
+ if (targetExists && !syncMode && !options.force && readdirSync15(targetDir).length > 0) {
22805
23272
  throw new Error(`Target already exists and is not empty: ${targetDir} (pass --force to render over it)`);
22806
23273
  }
22807
- const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename12(targetDir)) ?? "Project", description: "" };
23274
+ const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename13(targetDir)) ?? "Project", description: "" };
22808
23275
  if (!name && interactive && !syncMode) {
22809
23276
  name = await promptTextValue("Project name", defaults.name);
22810
23277
  }
@@ -23075,8 +23542,9 @@ async function runProjectInit(name, options) {
23075
23542
  const board = boardDelivery({ ...result2.plan, actions: plan.actions });
23076
23543
  const skipRequested = Boolean(options.skipBoard);
23077
23544
  const boardDelivered = board.confirmed || skipRequested;
23545
+ const boardStepReached = result2.phases.some((phase) => phase.id === "project.external:ticket-provider");
23078
23546
  if (options.json) {
23079
- console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board }, null, 2));
23547
+ console.log(JSON.stringify({ ...result2, ok: result2.ok && boardDelivered, board: { ...board, reached: boardStepReached } }, null, 2));
23080
23548
  } else {
23081
23549
  console.log(formatProjectInitPlan(selectedPlan));
23082
23550
  for (const line of result2.logs) console.log(line);
@@ -23084,7 +23552,7 @@ async function runProjectInit(name, options) {
23084
23552
  if (result2.migrationReport) console.log(formatMigrationReport(result2.migrationReport));
23085
23553
  if (result2.ok && result2.changedFiles.length) console.log(` ${green(glyph.pass)} ${bold("Project synchronized")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
23086
23554
  if (result2.ok && result2.changedFiles.length === 0) console.log(` ${green(glyph.pass)} ${dim("Already in parity")} ${dim(glyph.dot)} ${cyan(plan.project.slug)}`);
23087
- reportBoardDelivery(board, plan.project.slug, skipRequested);
23555
+ reportBoardDelivery(board, plan.project.slug, skipRequested, boardStepReached || result2.ok);
23088
23556
  const rendered = selectedPlan.actions.find((action) => action.kind === "copier.copy.commonproject");
23089
23557
  if (result2.ok && rendered?.kind === "copier.copy.commonproject" && rendered.data.project_type === "obsidian-plugin") {
23090
23558
  console.log(` ${bold("Next")} ${dim(`cd ${target.targetDir}`)}`);
@@ -23319,7 +23787,7 @@ program.command("reindex").argument("[project-id]", "Canonical project ID (defau
23319
23787
  else if (options.all) result2 = registryRequest(location, "POST", "/v1/reindex", {});
23320
23788
  else {
23321
23789
  const repo = projectId ? getProject(loadProjectRegistry(location), projectId).repo_path : resolveProject(process.cwd()).root;
23322
- result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join37(repo, ".project.json") });
23790
+ result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join38(repo, ".project.json") });
23323
23791
  }
23324
23792
  const statuses = result2.__registry_status;
23325
23793
  const stale = Object.entries(statuses ?? {}).filter(([, item]) => item.status !== "ok");