@delorenj/pjangler 1.5.48 → 1.5.50

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 +1065 -624
  2. package/dist/mcp-server.js +953 -512
  3. package/package.json +2 -1
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,19 +1420,19 @@ 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
1438
  function provisionTicketProviderBoard(action, env2 = process.env) {
@@ -1450,7 +1450,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1450
1450
  ok: true,
1451
1451
  skipped: true,
1452
1452
  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.`
1453
+ `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
1454
  ]
1455
1455
  };
1456
1456
  }
@@ -1464,12 +1464,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1464
1464
  };
1465
1465
  }
1466
1466
  const redact = (text2) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text2);
1467
- const staging = mkdtempSync2(join17(tmpdir2(), "pjangler-tp-"));
1467
+ const staging = mkdtempSync3(join18(tmpdir2(), "pjangler-tp-"));
1468
1468
  try {
1469
- const providersDir = join17(staging, ".tp", "adapters");
1470
- mkdirSync7(providersDir, { recursive: true });
1471
- writeFileSync9(
1472
- join17(staging, ".project.json"),
1469
+ const providersDir = join18(staging, ".tp", "adapters");
1470
+ mkdirSync8(providersDir, { recursive: true });
1471
+ writeFileSync10(
1472
+ join18(staging, ".project.json"),
1473
1473
  `${JSON.stringify(
1474
1474
  {
1475
1475
  project_name: action.boardName,
@@ -1488,7 +1488,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1488
1488
  `,
1489
1489
  "utf8"
1490
1490
  );
1491
- const staged = join17(providersDir, `${provider}.sh`);
1491
+ const staged = join18(providersDir, `${provider}.sh`);
1492
1492
  copyFileSync2(adapter, staged);
1493
1493
  const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
1494
1494
  if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
@@ -1584,7 +1584,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
1584
1584
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1585
1585
  if (override === "0" || override === "false") return false;
1586
1586
  if (override === "1" || override === "true") return true;
1587
- return !existsSync11(join17(homedir7(), ".agents", "hooks"));
1587
+ return !existsSync11(join18(homedir7(), ".agents", "hooks"));
1588
1588
  }
1589
1589
  function jsonStable(value) {
1590
1590
  return JSON.stringify(value, (_key, item) => isRecord3(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
@@ -1626,10 +1626,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1626
1626
  const expanded = expandHome(sourceSkill);
1627
1627
  const direct = resolve11(expanded);
1628
1628
  if (existsSync11(direct)) return direct;
1629
- const name = basename8(sourceSkill);
1629
+ const name = basename9(sourceSkill);
1630
1630
  const roots = sourceSkillRoots(env2);
1631
1631
  for (const root of roots) {
1632
- const candidate = join17(root, name);
1632
+ const candidate = join18(root, name);
1633
1633
  if (existsSync11(candidate)) return candidate;
1634
1634
  }
1635
1635
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
@@ -1639,8 +1639,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1639
1639
  function planProjectInit(input) {
1640
1640
  if (!input.name.trim()) throw new Error("Project name is required");
1641
1641
  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;
1642
+ const localManifestPath = join18(targetDir, ".project.json");
1643
+ const localManifestContent = existsSync11(localManifestPath) ? readFileSync17(localManifestPath, "utf8") : void 0;
1644
1644
  const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
1645
1645
  const localId = localManifest?.project_id ?? localManifest?.project_slug;
1646
1646
  const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
@@ -1668,7 +1668,7 @@ function planProjectInit(input) {
1668
1668
  const recordedTypeValue = existing?.template?.commonproject?.project_type;
1669
1669
  const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
1670
1670
  const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
1671
- const freshRender = scaffold && localManifest === void 0 && !existsSync11(join17(targetDir, ".copier-answers.yml"));
1671
+ const freshRender = scaffold && localManifest === void 0 && !existsSync11(join18(targetDir, ".copier-answers.yml"));
1672
1672
  if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
1673
1673
  const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
1674
1674
  if (!adoptingPlugin) {
@@ -1766,7 +1766,7 @@ function planProjectInit(input) {
1766
1766
  }));
1767
1767
  }
1768
1768
  actions.push(
1769
- { kind: "project.write-manifest", path: join17(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1769
+ { kind: "project.write-manifest", path: join18(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1770
1770
  {
1771
1771
  kind: "ticket-provider.create-or-link",
1772
1772
  enabled: boardEnabled,
@@ -1821,12 +1821,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1821
1821
  plan.manifest.ticket_provider = manifestProvider;
1822
1822
  action.boardId = boardId;
1823
1823
  action.state = manifestProvider.state;
1824
- const manifestPath = join17(action.repoPath, ".project.json");
1824
+ const manifestPath = join18(action.repoPath, ".project.json");
1825
1825
  let next;
1826
1826
  if (existsSync11(manifestPath)) {
1827
1827
  let existing = {};
1828
1828
  try {
1829
- const parsed = JSON.parse(readFileSync16(manifestPath, "utf8"));
1829
+ const parsed = JSON.parse(readFileSync17(manifestPath, "utf8"));
1830
1830
  if (isRecord3(parsed)) existing = parsed;
1831
1831
  } catch {
1832
1832
  existing = {};
@@ -1834,13 +1834,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1834
1834
  const existingProvider = isRecord3(existing.ticket_provider) ? existing.ticket_provider : {};
1835
1835
  next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
1836
1836
  } else {
1837
- mkdirSync7(dirname14(manifestPath), { recursive: true });
1837
+ mkdirSync8(dirname14(manifestPath), { recursive: true });
1838
1838
  next = plan.manifest;
1839
1839
  }
1840
1840
  const text2 = `${JSON.stringify(next, null, 2)}
1841
1841
  `;
1842
- if (!existsSync11(manifestPath) || readFileSync16(manifestPath, "utf8") !== text2) {
1843
- writeFileSync9(manifestPath, text2, "utf8");
1842
+ if (!existsSync11(manifestPath) || readFileSync17(manifestPath, "utf8") !== text2) {
1843
+ writeFileSync10(manifestPath, text2, "utf8");
1844
1844
  return [manifestPath];
1845
1845
  }
1846
1846
  return [];
@@ -1868,7 +1868,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1868
1868
  logs.push(
1869
1869
  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
1870
  );
1871
- mkdirSync7(dirname14(action.targetDir), { recursive: true });
1871
+ mkdirSync8(dirname14(action.targetDir), { recursive: true });
1872
1872
  const before = snapshotTree(action.targetDir);
1873
1873
  const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
1874
1874
  const copierEnv = { ...process.env };
@@ -1900,22 +1900,22 @@ async function executeProjectInitPlan(plan, options = {}) {
1900
1900
  break;
1901
1901
  }
1902
1902
  const projectType = action.data.project_type ?? "base";
1903
- const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join17(action.targetDir, rel)));
1903
+ const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join18(action.targetDir, rel)));
1904
1904
  if (missing.length) {
1905
1905
  errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
1906
1906
  break;
1907
1907
  }
1908
1908
  if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
1909
1909
  } else if (action.kind === "project.write-manifest") {
1910
- if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync16(action.path, "utf8") !== action.expectedContent)) {
1910
+ if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync17(action.path, "utf8") !== action.expectedContent)) {
1911
1911
  errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
1912
1912
  break;
1913
1913
  }
1914
- mkdirSync7(dirname14(action.path), { recursive: true });
1914
+ mkdirSync8(dirname14(action.path), { recursive: true });
1915
1915
  let value = action.manifest;
1916
1916
  if (existsSync11(action.path)) {
1917
1917
  try {
1918
- const currentValue = JSON.parse(readFileSync16(action.path, "utf8"));
1918
+ const currentValue = JSON.parse(readFileSync17(action.path, "utf8"));
1919
1919
  if (isRecord3(currentValue)) {
1920
1920
  const currentNotebook = isRecord3(currentValue.notebook) ? currentValue.notebook : {};
1921
1921
  const desiredNotebook = isRecord3(value.notebook) ? value.notebook : {};
@@ -1943,9 +1943,9 @@ async function executeProjectInitPlan(plan, options = {}) {
1943
1943
  value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
1944
1944
  const next = `${JSON.stringify(value, null, 2)}
1945
1945
  `;
1946
- const current = existsSync11(action.path) ? readFileSync16(action.path, "utf8") : void 0;
1946
+ const current = existsSync11(action.path) ? readFileSync17(action.path, "utf8") : void 0;
1947
1947
  if (current !== next) {
1948
- writeFileSync9(action.path, next, "utf8");
1948
+ writeFileSync10(action.path, next, "utf8");
1949
1949
  changedFiles.push(action.path);
1950
1950
  }
1951
1951
  changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
@@ -2128,7 +2128,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
2128
2128
  } else if (!statSync5(project.repo_path).isDirectory()) {
2129
2129
  issues.push({ level: "error", slug: projectSlug, message: `repo_path is not a directory: ${project.repo_path}` });
2130
2130
  } else {
2131
- const manifestPath = join17(project.repo_path, ".project.json");
2131
+ const manifestPath = join18(project.repo_path, ".project.json");
2132
2132
  if (!existsSync11(manifestPath)) issues.push({ level: "warn", slug: projectSlug, message: ".project.json is missing" });
2133
2133
  }
2134
2134
  for (const artifact of project.source_artifacts) {
@@ -2145,7 +2145,7 @@ function doctorProjectRegistry(registryPath = projectRegistryPath(), slug) {
2145
2145
  };
2146
2146
  }
2147
2147
  function buildCommonProjectCopierAction(input) {
2148
- const templateDir = join17(input.pjanglerRoot, "templates", "commonproject");
2148
+ const templateDir = join18(input.pjanglerRoot, "templates", "commonproject");
2149
2149
  const data = {
2150
2150
  project_name: input.projectName,
2151
2151
  project_description: input.projectDescription ?? "",
@@ -2176,7 +2176,7 @@ function buildCommonProjectCopierAction(input) {
2176
2176
  function resolvePjanglerRoot() {
2177
2177
  let dir = dirname14(fileURLToPath2(import.meta.url));
2178
2178
  while (dir !== dirname14(dir)) {
2179
- if (existsSync11(join17(dir, "package.json")) && existsSync11(join17(dir, "templates", "commonproject", "copier.yml"))) return dir;
2179
+ if (existsSync11(join18(dir, "package.json")) && existsSync11(join18(dir, "templates", "commonproject", "copier.yml"))) return dir;
2180
2180
  dir = dirname14(dir);
2181
2181
  }
2182
2182
  return resolve11(process.cwd());
@@ -2253,7 +2253,7 @@ function validateProjectRecord(project, key) {
2253
2253
  }
2254
2254
  function expandHome(path) {
2255
2255
  if (path === "~") return homedir7();
2256
- if (path.startsWith("~/")) return join17(homedir7(), path.slice(2));
2256
+ if (path.startsWith("~/")) return join18(homedir7(), path.slice(2));
2257
2257
  return path;
2258
2258
  }
2259
2259
  function isRecord3(value) {
@@ -2280,7 +2280,7 @@ var init_project = __esm({
2280
2280
  "obsidian-plugin": ["typescript", "javascript"]
2281
2281
  };
2282
2282
  PROJECT_TYPE_SENTINELS = {
2283
- "obsidian-plugin": ["manifest.json", join17("src", "main.ts")]
2283
+ "obsidian-plugin": ["manifest.json", join18("src", "main.ts")]
2284
2284
  };
2285
2285
  BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
2286
2286
  PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
@@ -2289,8 +2289,8 @@ var init_project = __esm({
2289
2289
  IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
2290
2290
  DEFAULT_SOURCE_SKILL_ROOTS = [
2291
2291
  "/home/delorenj/code/skillex/all-skills",
2292
- join17(homedir7(), ".agents", "skills"),
2293
- join17(homedir7(), ".codex", "skills")
2292
+ join18(homedir7(), ".agents", "skills"),
2293
+ join18(homedir7(), ".codex", "skills")
2294
2294
  ];
2295
2295
  PROJECT_REGISTRY_OWNED_KEYS = [
2296
2296
  "name",
@@ -2322,7 +2322,7 @@ var init_project = __esm({
2322
2322
  import { randomUUID } from "node:crypto";
2323
2323
  import { isDeepStrictEqual } from "node:util";
2324
2324
  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";
2325
+ 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
2326
  import { dirname as dirname15, resolve as resolve12 } from "node:path";
2327
2327
  function isRecord4(value) {
2328
2328
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -2336,7 +2336,7 @@ function readManifest(repoPath) {
2336
2336
  if (!existsSync12(path)) return null;
2337
2337
  let parsed;
2338
2338
  try {
2339
- parsed = JSON.parse(readFileSync17(path, "utf8"));
2339
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
2340
2340
  } catch {
2341
2341
  throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
2342
2342
  }
@@ -2578,7 +2578,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2578
2578
  const changed = [];
2579
2579
  const manifestPath = resolve12(resolved.project.repo_path, ".project.json");
2580
2580
  if (isRegistryServiceLocation(resolved.registry_path)) {
2581
- const before = existsSync12(manifestPath) ? readFileSync17(manifestPath, "utf8") : null;
2581
+ const before = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
2582
2582
  const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
2583
2583
  const nextProject = {
2584
2584
  ...resolved.project,
@@ -2590,10 +2590,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2590
2590
  resolved.registry = loadProjectRegistry(resolved.registry_path);
2591
2591
  resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
2592
2592
  resolved.manifest = readManifest(nextProject.repo_path);
2593
- const after = existsSync12(manifestPath) ? readFileSync17(manifestPath, "utf8") : null;
2593
+ const after = existsSync12(manifestPath) ? readFileSync18(manifestPath, "utf8") : null;
2594
2594
  return before === after ? [] : [manifestPath];
2595
2595
  }
2596
- const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync17(manifestPath, "utf8")) : {};
2596
+ const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync18(manifestPath, "utf8")) : {};
2597
2597
  if (!isRecord4(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
2598
2598
  validateManifestNotebookSurface(manifestRaw.notebook);
2599
2599
  const manifestNotebook2 = isRecord4(manifestRaw.notebook) ? manifestRaw.notebook : {};
@@ -2626,29 +2626,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2626
2626
  };
2627
2627
  const manifestText = `${JSON.stringify(manifestNext, null, 2)}
2628
2628
  `;
2629
- if (!existsSync12(manifestPath) || readFileSync17(manifestPath, "utf8") !== manifestText) {
2629
+ if (!existsSync12(manifestPath) || readFileSync18(manifestPath, "utf8") !== manifestText) {
2630
2630
  let mode = 420;
2631
2631
  if (existsSync12(manifestPath)) {
2632
- const current2 = lstatSync12(manifestPath);
2632
+ const current2 = lstatSync13(manifestPath);
2633
2633
  if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
2634
2634
  mode = current2.mode & 511;
2635
2635
  }
2636
2636
  const temp = resolve12(dirname15(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
2637
- const fd = openSync3(temp, "wx", mode);
2637
+ const fd = openSync4(temp, "wx", mode);
2638
2638
  try {
2639
- writeFileSync10(fd, manifestText, "utf8");
2640
- fchmodSync2(fd, mode);
2639
+ writeFileSync11(fd, manifestText, "utf8");
2640
+ fchmodSync3(fd, mode);
2641
2641
  fsyncSync2(fd);
2642
2642
  } finally {
2643
- closeSync3(fd);
2643
+ closeSync4(fd);
2644
2644
  }
2645
2645
  renameSync6(temp, manifestPath);
2646
2646
  try {
2647
- const directory = openSync3(dirname15(manifestPath), "r");
2647
+ const directory = openSync4(dirname15(manifestPath), "r");
2648
2648
  try {
2649
2649
  fsyncSync2(directory);
2650
2650
  } finally {
2651
- closeSync3(directory);
2651
+ closeSync4(directory);
2652
2652
  }
2653
2653
  } catch {
2654
2654
  }
@@ -2657,9 +2657,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2657
2657
  const current = resolved.project.notebook;
2658
2658
  resolved.project.notebook = { ...isRecord4(current) ? current : { state: "planned" }, ...binding };
2659
2659
  const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
2660
- const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync17(resolved.registry_path, "utf8") : null;
2660
+ const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
2661
2661
  saveProjectRegistry(resolved.registry, resolved.registry_path);
2662
- const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync17(resolved.registry_path, "utf8") : null;
2662
+ const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync18(resolved.registry_path, "utf8") : null;
2663
2663
  if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
2664
2664
  resolved.manifest = manifestNext;
2665
2665
  return changed;
@@ -2692,7 +2692,7 @@ var init_config = __esm({
2692
2692
  });
2693
2693
 
2694
2694
  // src/notebook/notes.ts
2695
- import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
2695
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
2696
2696
  function canonicalJson2(value) {
2697
2697
  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
2698
  if (value === null || typeof value !== "object") {
@@ -2705,7 +2705,7 @@ function canonicalJson2(value) {
2705
2705
  return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
2706
2706
  }
2707
2707
  function sha256Hex(value) {
2708
- return createHash9("sha256").update(value).digest("hex");
2708
+ return createHash10("sha256").update(value).digest("hex");
2709
2709
  }
2710
2710
  function base64UrlEncode(value) {
2711
2711
  return Buffer.from(value, "utf8").toString("base64url");
@@ -3256,32 +3256,32 @@ var init_remote_mutation_schema = __esm({
3256
3256
  });
3257
3257
 
3258
3258
  // src/notebook/state.ts
3259
- import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
3259
+ import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
3260
3260
  import {
3261
- closeSync as closeSync4,
3262
- constants as constants3,
3261
+ closeSync as closeSync5,
3262
+ constants as constants4,
3263
3263
  existsSync as existsSync13,
3264
- fchmodSync as fchmodSync3,
3265
- fstatSync,
3264
+ fchmodSync as fchmodSync4,
3265
+ fstatSync as fstatSync2,
3266
3266
  fsyncSync as fsyncSync3,
3267
- lstatSync as lstatSync13,
3268
- mkdirSync as mkdirSync8,
3269
- openSync as openSync4,
3267
+ lstatSync as lstatSync14,
3268
+ mkdirSync as mkdirSync9,
3269
+ openSync as openSync5,
3270
3270
  readSync,
3271
- readdirSync as readdirSync9,
3271
+ readdirSync as readdirSync10,
3272
3272
  renameSync as renameSync7,
3273
- unlinkSync as unlinkSync7,
3274
- writeFileSync as writeFileSync11
3273
+ unlinkSync as unlinkSync8,
3274
+ writeFileSync as writeFileSync12
3275
3275
  } from "node:fs";
3276
3276
  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";
3277
+ import { basename as basename10, dirname as dirname16, join as join19, parse, relative as relative12, resolve as resolve13, sep as sep4 } from "node:path";
3278
3278
  function notebookStateRoot(env2 = process.env) {
3279
- const base = env2.XDG_STATE_HOME || join18(env2.HOME || homedir8(), ".local", "state");
3279
+ const base = env2.XDG_STATE_HOME || join19(env2.HOME || homedir8(), ".local", "state");
3280
3280
  return resolve13(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
3281
3281
  }
3282
3282
  function deriveSessionKey(projectSlug, client, clientSessionId) {
3283
3283
  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");
3284
+ return createHash11("sha256").update(`pjangler-session-v1\0${projectSlug}\0${client}\0${clientSessionId}`, "utf8").digest("hex");
3285
3285
  }
3286
3286
  function deriveReceiptId(sessionKey) {
3287
3287
  assertDigest(sessionKey, "session key");
@@ -3295,7 +3295,7 @@ function assertDigest(value, label) {
3295
3295
  }
3296
3296
  function projectStateDir(root, projectSlug) {
3297
3297
  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}`));
3298
+ return join19(resolve13(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
3299
3299
  }
3300
3300
  function assertContained(root, candidate) {
3301
3301
  const rel = relative12(resolve13(root), resolve13(candidate));
@@ -3308,33 +3308,33 @@ function openPinnedDirectory(path, root, create) {
3308
3308
  assertContained(absoluteRoot, absolute);
3309
3309
  if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
3310
3310
  const parsed = parse(absolute);
3311
- let fd = openSync4(parsed.root, DIRECTORY_OPEN_FLAGS);
3311
+ let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
3312
3312
  let cursor = parsed.root;
3313
3313
  try {
3314
3314
  for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
3315
3315
  const child = `/proc/self/fd/${fd}/${part}`;
3316
- cursor = join18(cursor, part);
3316
+ cursor = join19(cursor, part);
3317
3317
  let childFd;
3318
3318
  try {
3319
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
3319
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
3320
3320
  } catch (error) {
3321
3321
  if (!create || error.code !== "ENOENT") throw error;
3322
3322
  try {
3323
- mkdirSync8(child, { mode: 448 });
3323
+ mkdirSync9(child, { mode: 448 });
3324
3324
  } catch (mkdirError) {
3325
3325
  if (mkdirError.code !== "EEXIST") throw mkdirError;
3326
3326
  }
3327
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
3327
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
3328
3328
  }
3329
- closeSync4(fd);
3329
+ closeSync5(fd);
3330
3330
  fd = childFd;
3331
- const stat = fstatSync(fd);
3331
+ const stat = fstatSync2(fd);
3332
3332
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
3333
3333
  if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
3334
3334
  }
3335
3335
  return fd;
3336
3336
  } catch (error) {
3337
- closeSync4(fd);
3337
+ closeSync5(fd);
3338
3338
  if (error.code === "ELOOP" || error.code === "ENOTDIR") {
3339
3339
  throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
3340
3340
  }
@@ -3342,7 +3342,7 @@ function openPinnedDirectory(path, root, create) {
3342
3342
  }
3343
3343
  }
3344
3344
  function pinnedLeaf(parentFd, path) {
3345
- return `/proc/self/fd/${parentFd}/${basename9(path)}`;
3345
+ return `/proc/self/fd/${parentFd}/${basename10(path)}`;
3346
3346
  }
3347
3347
  function assertOwned(stat) {
3348
3348
  if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
@@ -3355,13 +3355,13 @@ function ensureDirectory(path, root) {
3355
3355
  assertContained(absoluteRoot, absolute);
3356
3356
  const fd = openPinnedDirectory(absolute, absoluteRoot, true);
3357
3357
  try {
3358
- const stat = fstatSync(fd);
3358
+ const stat = fstatSync2(fd);
3359
3359
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
3360
3360
  assertOwned(stat);
3361
- fchmodSync3(fd, 448);
3361
+ fchmodSync4(fd, 448);
3362
3362
  fsyncSync3(fd);
3363
3363
  } finally {
3364
- closeSync4(fd);
3364
+ closeSync5(fd);
3365
3365
  }
3366
3366
  }
3367
3367
  function notebookStatePaths(root, projectSlug) {
@@ -3370,18 +3370,18 @@ function notebookStatePaths(root, projectSlug) {
3370
3370
  return {
3371
3371
  root: absoluteRoot,
3372
3372
  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")
3373
+ baselines: join19(project, "baselines"),
3374
+ claims: join19(project, "claims"),
3375
+ receipts: join19(project, "receipts"),
3376
+ refusals: join19(project, "refusals"),
3377
+ journals: join19(project, "journals"),
3378
+ locks: join19(project, "locks")
3379
3379
  };
3380
3380
  }
3381
3381
  function ensureNotebookState(root, projectSlug) {
3382
3382
  const paths = notebookStatePaths(root, projectSlug);
3383
3383
  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]) {
3384
+ for (const path of [absoluteRoot, join19(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
3385
3385
  ensureDirectory(path, absoluteRoot);
3386
3386
  }
3387
3387
  return paths;
@@ -3397,15 +3397,15 @@ function fsyncDirectory2(path) {
3397
3397
  fsyncSync3(fd);
3398
3398
  } catch {
3399
3399
  } finally {
3400
- if (fd !== void 0) closeSync4(fd);
3400
+ if (fd !== void 0) closeSync5(fd);
3401
3401
  }
3402
3402
  }
3403
3403
  function readStateDirectory(path, root) {
3404
3404
  const fd = openPinnedDirectory(path, root, false);
3405
3405
  try {
3406
- return readdirSync9(`/proc/self/fd/${fd}`, { withFileTypes: true });
3406
+ return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
3407
3407
  } finally {
3408
- closeSync4(fd);
3408
+ closeSync5(fd);
3409
3409
  }
3410
3410
  }
3411
3411
  function readNotebookStateDirectory(path, root) {
@@ -3419,22 +3419,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
3419
3419
  let fileFd;
3420
3420
  try {
3421
3421
  try {
3422
- fileFd = openSync4(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3422
+ fileFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3423
3423
  } catch (error) {
3424
3424
  if (allowMissing && error.code === "ENOENT") return false;
3425
3425
  throw error;
3426
3426
  }
3427
- const stat = fstatSync(fileFd);
3427
+ const stat = fstatSync2(fileFd);
3428
3428
  if (!stat.isFile() || (stat.mode & 511) !== 384) {
3429
3429
  throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
3430
3430
  }
3431
3431
  assertOwned(stat);
3432
- unlinkSync7(target);
3432
+ unlinkSync8(target);
3433
3433
  fsyncSync3(parentFd);
3434
3434
  return true;
3435
3435
  } finally {
3436
- if (fileFd !== void 0) closeSync4(fileFd);
3437
- closeSync4(parentFd);
3436
+ if (fileFd !== void 0) closeSync5(fileFd);
3437
+ closeSync5(parentFd);
3438
3438
  }
3439
3439
  }
3440
3440
  function renameStateFile(source, target, root) {
@@ -3444,12 +3444,12 @@ function renameStateFile(source, target, root) {
3444
3444
  const targetPath = pinnedLeaf(parentFd, target);
3445
3445
  let sourceFd;
3446
3446
  try {
3447
- sourceFd = openSync4(sourcePath, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3448
- const stat = fstatSync(sourceFd);
3447
+ sourceFd = openSync5(sourcePath, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3448
+ const stat = fstatSync2(sourceFd);
3449
3449
  if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
3450
3450
  assertOwned(stat);
3451
3451
  try {
3452
- lstatSync13(targetPath);
3452
+ lstatSync14(targetPath);
3453
3453
  throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
3454
3454
  } catch (error) {
3455
3455
  if (error.code !== "ENOENT") throw error;
@@ -3457,8 +3457,8 @@ function renameStateFile(source, target, root) {
3457
3457
  renameSync7(sourcePath, targetPath);
3458
3458
  fsyncSync3(parentFd);
3459
3459
  } finally {
3460
- if (sourceFd !== void 0) closeSync4(sourceFd);
3461
- closeSync4(parentFd);
3460
+ if (sourceFd !== void 0) closeSync5(sourceFd);
3461
+ closeSync5(parentFd);
3462
3462
  }
3463
3463
  }
3464
3464
  function atomicWriteJson(path, value, root, afterParentPinned) {
@@ -3467,34 +3467,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
3467
3467
  const text2 = jsonLine(value);
3468
3468
  const parentFd = openPinnedDirectory(dirname16(path), root, false);
3469
3469
  const target = pinnedLeaf(parentFd, path);
3470
- const tempName = `.${basename9(path)}.${process.pid}.${randomUUID3()}.tmp`;
3470
+ const tempName = `.${basename10(path)}.${process.pid}.${randomUUID3()}.tmp`;
3471
3471
  const temp = `/proc/self/fd/${parentFd}/${tempName}`;
3472
3472
  let existingIdentity = null;
3473
3473
  try {
3474
3474
  afterParentPinned?.();
3475
3475
  try {
3476
- const existingFd = openSync4(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3476
+ const existingFd = openSync5(target, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3477
3477
  try {
3478
- const existing = fstatSync(existingFd);
3478
+ const existing = fstatSync2(existingFd);
3479
3479
  if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
3480
3480
  assertOwned(existing);
3481
3481
  if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
3482
3482
  existingIdentity = { dev: existing.dev, ino: existing.ino };
3483
3483
  } finally {
3484
- closeSync4(existingFd);
3484
+ closeSync5(existingFd);
3485
3485
  }
3486
3486
  } catch (error) {
3487
3487
  if (error.code !== "ENOENT") throw error;
3488
3488
  }
3489
- const fd = openSync4(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
3489
+ const fd = openSync5(temp, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
3490
3490
  try {
3491
- writeFileSync11(fd, text2, "utf8");
3491
+ writeFileSync12(fd, text2, "utf8");
3492
3492
  fsyncSync3(fd);
3493
3493
  } finally {
3494
- closeSync4(fd);
3494
+ closeSync5(fd);
3495
3495
  }
3496
3496
  try {
3497
- const current = lstatSync13(target);
3497
+ const current = lstatSync14(target);
3498
3498
  if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
3499
3499
  throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
3500
3500
  }
@@ -3505,12 +3505,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
3505
3505
  fsyncSync3(parentFd);
3506
3506
  } catch (error) {
3507
3507
  try {
3508
- unlinkSync7(temp);
3508
+ unlinkSync8(temp);
3509
3509
  } catch {
3510
3510
  }
3511
3511
  throw error;
3512
3512
  } finally {
3513
- closeSync4(parentFd);
3513
+ closeSync5(parentFd);
3514
3514
  }
3515
3515
  return Buffer.byteLength(text2, "utf8");
3516
3516
  }
@@ -3520,22 +3520,22 @@ function exclusiveWrite(path, text2, root) {
3520
3520
  const target = pinnedLeaf(parentFd, path);
3521
3521
  let fd;
3522
3522
  try {
3523
- fd = openSync4(target, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
3523
+ fd = openSync5(target, constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | (constants4.O_NOFOLLOW ?? 0), 384);
3524
3524
  } catch (error) {
3525
- closeSync4(parentFd);
3525
+ closeSync5(parentFd);
3526
3526
  if (error.code === "EEXIST") return false;
3527
3527
  throw error;
3528
3528
  }
3529
3529
  try {
3530
- writeFileSync11(fd, text2, "utf8");
3530
+ writeFileSync12(fd, text2, "utf8");
3531
3531
  fsyncSync3(fd);
3532
3532
  } finally {
3533
- closeSync4(fd);
3533
+ closeSync5(fd);
3534
3534
  }
3535
3535
  try {
3536
3536
  fsyncSync3(parentFd);
3537
3537
  } finally {
3538
- closeSync4(parentFd);
3538
+ closeSync5(parentFd);
3539
3539
  }
3540
3540
  return true;
3541
3541
  }
@@ -3679,13 +3679,13 @@ function safeReadJson(path, maxBytes) {
3679
3679
  }
3680
3680
  let fd;
3681
3681
  try {
3682
- fd = openSync4(pinnedLeaf(parentFd, path), constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3682
+ fd = openSync5(pinnedLeaf(parentFd, path), constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3683
3683
  } catch (error) {
3684
- closeSync4(parentFd);
3684
+ closeSync5(parentFd);
3685
3685
  return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
3686
3686
  }
3687
3687
  try {
3688
- const stat = fstatSync(fd);
3688
+ const stat = fstatSync2(fd);
3689
3689
  if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
3690
3690
  if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
3691
3691
  try {
@@ -3705,14 +3705,14 @@ function safeReadJson(path, maxBytes) {
3705
3705
  if (total > maxBytes) return { reason: "oversize", bytes: total };
3706
3706
  chunks.push(chunk.subarray(0, count));
3707
3707
  }
3708
- const after = fstatSync(fd);
3708
+ const after = fstatSync2(fd);
3709
3709
  if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
3710
3710
  return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
3711
3711
  } catch (error) {
3712
3712
  return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
3713
3713
  } finally {
3714
- closeSync4(fd);
3715
- closeSync4(parentFd);
3714
+ closeSync5(fd);
3715
+ closeSync5(parentFd);
3716
3716
  }
3717
3717
  }
3718
3718
  function safeEntryId(kind, name) {
@@ -3729,7 +3729,7 @@ function baselineReceiptByteCeiling(limits) {
3729
3729
  function createSessionBaseline(root, input) {
3730
3730
  assertDigest(input.session_key, "session key");
3731
3731
  const paths = ensureNotebookState(root, input.project_slug);
3732
- const path = join18(paths.baselines, `${input.session_key}.json`);
3732
+ const path = join19(paths.baselines, `${input.session_key}.json`);
3733
3733
  const maxBytes = baselineReceiptByteCeiling(input.limits);
3734
3734
  const existing = readBaseline(path, maxBytes);
3735
3735
  if (existing) return { baseline: existing, created: false };
@@ -3770,7 +3770,7 @@ function createSessionBaseline(root, input) {
3770
3770
  function createOverviewClaim(root, input) {
3771
3771
  assertDigest(input.session_key, "session key");
3772
3772
  const paths = ensureNotebookState(root, input.project_slug);
3773
- const path = join18(paths.claims, `${input.session_key}.overview`);
3773
+ const path = join19(paths.claims, `${input.session_key}.overview`);
3774
3774
  if (existsSync13(path)) {
3775
3775
  const read = safeReadJson(path, 65536);
3776
3776
  const existing = read.value === void 0 ? null : parseClaim(read.value);
@@ -3789,7 +3789,7 @@ function createOverviewClaim(root, input) {
3789
3789
  function readOverviewClaim(root, projectSlug, sessionKey) {
3790
3790
  assertDigest(sessionKey, "session key");
3791
3791
  const paths = notebookStatePaths(root, projectSlug);
3792
- const path = join18(paths.claims, `${sessionKey}.overview`);
3792
+ const path = join19(paths.claims, `${sessionKey}.overview`);
3793
3793
  if (!existsSync13(path)) return null;
3794
3794
  const read = safeReadJson(path, 65536);
3795
3795
  return read.value === void 0 ? null : parseClaim(read.value);
@@ -3797,10 +3797,10 @@ function readOverviewClaim(root, projectSlug, sessionKey) {
3797
3797
  function readSessionBaseline(root, projectSlug, sessionKey, limits) {
3798
3798
  assertDigest(sessionKey, "session key");
3799
3799
  const paths = ensureNotebookState(root, projectSlug);
3800
- return readBaseline(join18(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3800
+ return readBaseline(join19(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3801
3801
  }
3802
3802
  function acquireLock(paths, maxWaitMs) {
3803
- const lock = join18(paths.locks, "admission.lock");
3803
+ const lock = join19(paths.locks, "admission.lock");
3804
3804
  const deadline = Date.now() + Math.max(1, maxWaitMs);
3805
3805
  const token = randomUUID3();
3806
3806
  const record3 = () => ({
@@ -3830,7 +3830,7 @@ function acquireLock(paths, maxWaitMs) {
3830
3830
  throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
3831
3831
  }
3832
3832
  if (Date.now() >= expiresAt) {
3833
- const recovery = join18(paths.locks, `recovery-${heldToken}.lock`);
3833
+ const recovery = join19(paths.locks, `recovery-${heldToken}.lock`);
3834
3834
  const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
3835
3835
  if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
3836
3836
  try {
@@ -3838,7 +3838,7 @@ function acquireLock(paths, maxWaitMs) {
3838
3838
  const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
3839
3839
  const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
3840
3840
  if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
3841
- const recovered = join18(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3841
+ const recovered = join19(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3842
3842
  renameStateFile(lock, recovered, paths.root);
3843
3843
  unlinkStateFile(recovered, paths.root);
3844
3844
  }
@@ -3918,7 +3918,7 @@ function scanAuxiliaryState(paths, limits) {
3918
3918
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3919
3919
  continue;
3920
3920
  }
3921
- const read = safeReadJson(join18(specification.dir, entry.name), specification.maxBytes);
3921
+ const read = safeReadJson(join19(specification.dir, entry.name), specification.maxBytes);
3922
3922
  if (read.reason || read.value === void 0) {
3923
3923
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3924
3924
  continue;
@@ -3943,7 +3943,7 @@ function scanAuxiliaryState(paths, limits) {
3943
3943
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3944
3944
  continue;
3945
3945
  }
3946
- const read = safeReadJson(join18(paths.journals, entry.name), limits.receipt_max_bytes);
3946
+ const read = safeReadJson(join19(paths.journals, entry.name), limits.receipt_max_bytes);
3947
3947
  if (read.reason || read.value === void 0) {
3948
3948
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3949
3949
  continue;
@@ -3972,7 +3972,7 @@ function scanAuxiliaryState(paths, limits) {
3972
3972
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3973
3973
  continue;
3974
3974
  }
3975
- const read = safeReadJson(join18(paths.locks, entry.name), 8192);
3975
+ const read = safeReadJson(join19(paths.locks, entry.name), 8192);
3976
3976
  if (read.reason || read.value === void 0) {
3977
3977
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3978
3978
  continue;
@@ -4011,7 +4011,7 @@ function scanReceipts(paths, limits) {
4011
4011
  }
4012
4012
  for (const entry of entries) {
4013
4013
  const entryId = safeEntryId("receipts", entry.name);
4014
- const path = join18(paths.receipts, entry.name);
4014
+ const path = join19(paths.receipts, entry.name);
4015
4015
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
4016
4016
  addIntegrity({ entry_id: entryId, reason: "non-regular" });
4017
4017
  continue;
@@ -4041,7 +4041,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
4041
4041
  if (!existsSync13(paths.baselines)) return { current, stale };
4042
4042
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
4043
4043
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4044
- const baseline = readBaseline(join18(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
4044
+ const baseline = readBaseline(join19(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
4045
4045
  if (!baseline || referenced.has(baseline.session_key)) continue;
4046
4046
  const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
4047
4047
  if (nowMs >= expires) stale += 1;
@@ -4055,7 +4055,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
4055
4055
  const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
4056
4056
  for (const entry of entries) {
4057
4057
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4058
- const read = safeReadJson(join18(paths.refusals, entry.name), limits.receipt_max_bytes);
4058
+ const read = safeReadJson(join19(paths.refusals, entry.name), limits.receipt_max_bytes);
4059
4059
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
4060
4060
  if (!marker || `${marker.session_key}.json` !== entry.name) continue;
4061
4061
  if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
@@ -4151,7 +4151,7 @@ function recoveryActions(receiptId, repoPath) {
4151
4151
  ];
4152
4152
  }
4153
4153
  function removeValidRefusal(paths, sessionKey, limits) {
4154
- const path = join18(paths.refusals, `${sessionKey}.json`);
4154
+ const path = join19(paths.refusals, `${sessionKey}.json`);
4155
4155
  if (!existsSync13(path)) return;
4156
4156
  const read = safeReadJson(path, limits.receipt_max_bytes);
4157
4157
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
@@ -4165,7 +4165,7 @@ function admitCaptureReceipt(input) {
4165
4165
  const release = acquireLock(paths, Math.min(100, input.limits.hook_session_end_timeout_ms));
4166
4166
  try {
4167
4167
  const receiptId = deriveReceiptId(input.sessionKey);
4168
- const receiptPath = join18(paths.receipts, `${receiptId}.json`);
4168
+ const receiptPath = join19(paths.receipts, `${receiptId}.json`);
4169
4169
  if (existsSync13(receiptPath)) {
4170
4170
  const read = safeReadJson(receiptPath, input.limits.receipt_max_bytes);
4171
4171
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -4177,15 +4177,15 @@ function admitCaptureReceipt(input) {
4177
4177
  const scanBefore = scanReceipts(paths, input.limits);
4178
4178
  const auxiliaryBefore = scanAuxiliaryState(paths, input.limits);
4179
4179
  const referencedBefore = /* @__PURE__ */ new Set([...scanBefore.referencedSessions, ...auxiliaryBefore.referencedSessions]);
4180
- const baselinePath = join18(paths.baselines, `${input.sessionKey}.json`);
4180
+ const baselinePath = join19(paths.baselines, `${input.sessionKey}.json`);
4181
4181
  let baseline = readBaseline(baselinePath, baselineReceiptByteCeiling(input.limits));
4182
4182
  if (baseline) {
4183
4183
  const expired = now.getTime() >= Date.parse(baseline.created_at) + input.limits.receiptless_session_retention_seconds * 1e3;
4184
4184
  if (expired && !referencedBefore.has(input.sessionKey) && scanBefore.integrityCount === 0 && auxiliaryBefore.integrityCount === 0) {
4185
4185
  unlinkStateFile(baselinePath, paths.root);
4186
- const claimPath = join18(paths.claims, `${input.sessionKey}.overview`);
4186
+ const claimPath = join19(paths.claims, `${input.sessionKey}.overview`);
4187
4187
  unlinkStateFile(claimPath, paths.root, true);
4188
- const refusalPath = join18(paths.refusals, `${input.sessionKey}.json`);
4188
+ const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
4189
4189
  unlinkStateFile(refusalPath, paths.root, true);
4190
4190
  baseline = null;
4191
4191
  }
@@ -4214,7 +4214,7 @@ function admitCaptureReceipt(input) {
4214
4214
  const byteBlocked = summary.unresolved_bytes + candidate.bytes > input.limits.unresolved_receipt_max_bytes;
4215
4215
  if (countBlocked || byteBlocked) {
4216
4216
  const actions = recoveryActions(receiptId, input.repoPath);
4217
- const refusalPath = join18(paths.refusals, `${input.sessionKey}.json`);
4217
+ const refusalPath = join19(paths.refusals, `${input.sessionKey}.json`);
4218
4218
  const oldRead = existsSync13(refusalPath) ? safeReadJson(refusalPath, input.limits.receipt_max_bytes) : void 0;
4219
4219
  const old = oldRead?.value === void 0 ? null : parseRefusal(oldRead.value);
4220
4220
  const marker = {
@@ -4283,7 +4283,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
4283
4283
  function readCaptureReceipt(root, projectSlug, receiptId, limits) {
4284
4284
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
4285
4285
  const paths = notebookStatePaths(root, projectSlug);
4286
- const path = join18(paths.receipts, `${receiptId}.json`);
4286
+ const path = join19(paths.receipts, `${receiptId}.json`);
4287
4287
  if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
4288
4288
  const read = safeReadJson(path, limits.receipt_max_bytes);
4289
4289
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -4296,7 +4296,7 @@ function writeReceipt(paths, receipt, limit) {
4296
4296
  const bytes = Buffer.byteLength(text2, "utf8");
4297
4297
  if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
4298
4298
  if (receipt.serialized_bytes === bytes) {
4299
- atomicWriteJson(join18(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
4299
+ atomicWriteJson(join19(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
4300
4300
  return receipt;
4301
4301
  }
4302
4302
  receipt.serialized_bytes = bytes;
@@ -4435,25 +4435,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
4435
4435
  const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
4436
4436
  for (const receipt of scan.receipts) {
4437
4437
  if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
4438
- const path = join18(paths.receipts, `${receipt.receipt_id}.json`);
4438
+ const path = join19(paths.receipts, `${receipt.receipt_id}.json`);
4439
4439
  unlinkStateFile(path, paths.root);
4440
- removed.push(safeEntryId("receipts", basename9(path)));
4440
+ removed.push(safeEntryId("receipts", basename10(path)));
4441
4441
  }
4442
4442
  const remaining = scanReceipts(paths, limits);
4443
4443
  const remainingAuxiliary = scanAuxiliaryState(paths, limits);
4444
4444
  const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
4445
4445
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
4446
4446
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
4447
- const path = join18(paths.baselines, entry.name);
4447
+ const path = join19(paths.baselines, entry.name);
4448
4448
  const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
4449
4449
  if (!baseline || referenced.has(baseline.session_key)) continue;
4450
4450
  if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
4451
4451
  unlinkStateFile(path, paths.root);
4452
4452
  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)));
4453
+ const claim = join19(paths.claims, `${baseline.session_key}.overview`);
4454
+ if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename10(claim)));
4455
+ const refusal = join19(paths.refusals, `${baseline.session_key}.json`);
4456
+ if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename10(refusal)));
4457
4457
  }
4458
4458
  fsyncDirectory2(paths.project);
4459
4459
  return removed;
@@ -4476,7 +4476,7 @@ function currentRetentionPressure(summary) {
4476
4476
  }
4477
4477
  function statePathForReceipt(root, projectSlug, receiptId) {
4478
4478
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
4479
- return join18(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
4479
+ return join19(projectStateDir(root, projectSlug), "receipts", `${receiptId}.json`);
4480
4480
  }
4481
4481
  var NOTEBOOK_STATE_VERSION, SESSION_KEY_RE, RECEIPT_ID_RE, GIT_OBJECT_RE, NOTEBOOK_ERROR_CODES, DIRECTORY_OPEN_FLAGS;
4482
4482
  var init_state = __esm({
@@ -4503,7 +4503,7 @@ var init_state = __esm({
4503
4503
  "REMOTE_PROTOCOL_ERROR",
4504
4504
  "INTERNAL_ERROR"
4505
4505
  ]);
4506
- DIRECTORY_OPEN_FLAGS = constants3.O_RDONLY | (constants3.O_DIRECTORY ?? 0) | (constants3.O_NOFOLLOW ?? 0);
4506
+ DIRECTORY_OPEN_FLAGS = constants4.O_RDONLY | (constants4.O_DIRECTORY ?? 0) | (constants4.O_NOFOLLOW ?? 0);
4507
4507
  }
4508
4508
  });
4509
4509
 
@@ -4842,8 +4842,8 @@ var init_open_notebook_client = __esm({
4842
4842
 
4843
4843
  // src/notebook/git-evidence.ts
4844
4844
  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";
4845
+ 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";
4846
+ import { extname, join as join21, relative as relative13, resolve as resolve14, sep as sep5 } from "node:path";
4847
4847
  function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
4848
4848
  const result2 = spawnSync9("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
4849
4849
  return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
@@ -4921,9 +4921,9 @@ function hasSymlinkComponent(root, candidate) {
4921
4921
  const rel = relative13(root, candidate);
4922
4922
  let cursor = root;
4923
4923
  for (const part of rel.split(sep5).filter(Boolean)) {
4924
- cursor = join20(cursor, part);
4924
+ cursor = join21(cursor, part);
4925
4925
  try {
4926
- if (lstatSync14(cursor).isSymbolicLink()) return true;
4926
+ if (lstatSync15(cursor).isSymbolicLink()) return true;
4927
4927
  } catch {
4928
4928
  return true;
4929
4929
  }
@@ -4935,12 +4935,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4935
4935
  if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
4936
4936
  let fd;
4937
4937
  try {
4938
- fd = openSync5(safe.candidate, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
4938
+ fd = openSync6(safe.candidate, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
4939
4939
  } catch {
4940
4940
  return { status: "excluded", reason: "not-regular" };
4941
4941
  }
4942
4942
  try {
4943
- const before = fstatSync2(fd);
4943
+ const before = fstatSync3(fd);
4944
4944
  if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
4945
4945
  if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
4946
4946
  let physical;
@@ -4961,7 +4961,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4961
4961
  if (total > maxBytes) return { status: "excluded", reason: "oversize" };
4962
4962
  chunks.push(chunk.subarray(0, count));
4963
4963
  }
4964
- const after = fstatSync2(fd);
4964
+ const after = fstatSync3(fd);
4965
4965
  if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
4966
4966
  const bytes = Buffer.concat(chunks, total);
4967
4967
  if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
@@ -4974,7 +4974,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4974
4974
  if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
4975
4975
  return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
4976
4976
  } finally {
4977
- closeSync5(fd);
4977
+ closeSync6(fd);
4978
4978
  }
4979
4979
  }
4980
4980
  function looksGenerated(path) {
@@ -5064,10 +5064,10 @@ var init_git_evidence = __esm({
5064
5064
 
5065
5065
  // src/notebook/remote-mutation-journal.ts
5066
5066
  import { randomUUID as randomUUID4 } from "node:crypto";
5067
- import { join as join21 } from "node:path";
5067
+ import { join as join22 } from "node:path";
5068
5068
  function journalPath(root, projectSlug, operationId) {
5069
5069
  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`);
5070
+ return join22(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
5071
5071
  }
5072
5072
  function remoteMutationJournalPath(root, projectSlug, operationId) {
5073
5073
  return journalPath(root, projectSlug, operationId);
@@ -5087,7 +5087,7 @@ function listRemoteMutationJournals(root, projectSlug) {
5087
5087
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
5088
5088
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
5089
5089
  }
5090
- const read = readNotebookStateJson(join21(paths.journals, entry.name), paths.root, 65536);
5090
+ const read = readNotebookStateJson(join22(paths.journals, entry.name), paths.root, 65536);
5091
5091
  const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
5092
5092
  if (!journal || `${journal.operation_id}.json` !== entry.name) {
5093
5093
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
@@ -5441,7 +5441,7 @@ var init_reconcile = __esm({
5441
5441
 
5442
5442
  // src/notebook/entropy.ts
5443
5443
  import { existsSync as existsSync16 } from "node:fs";
5444
- import { createHash as createHash13 } from "node:crypto";
5444
+ import { createHash as createHash14 } from "node:crypto";
5445
5445
  import { resolve as resolve17 } from "node:path";
5446
5446
  import { spawnSync as spawnSync12 } from "node:child_process";
5447
5447
  function listAllTrackedDocuments(config) {
@@ -5496,7 +5496,7 @@ async function compactSessionCaptures(input) {
5496
5496
  const toCompact = sessionHeaders.slice(maxRecent);
5497
5497
  const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
5498
5498
  const archiveTitle = "Session Captures Archive (Prior Sessions)";
5499
- const slugHash = createHash13("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
5499
+ const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
5500
5500
  const archiveLogicalId = `user-note:v1:${slugHash}`;
5501
5501
  let existingArchiveContent = "";
5502
5502
  let existingArchiveId = null;
@@ -6064,8 +6064,8 @@ var init_capture = __esm({
6064
6064
 
6065
6065
  // src/index.ts
6066
6066
  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";
6067
+ import { existsSync as existsSync26, readdirSync as readdirSync15, readFileSync as readFileSync33, statSync as statSync9 } from "node:fs";
6068
+ import { basename as basename13, join as join38, resolve as resolve25 } from "node:path";
6069
6069
  import { Command as Command3, CommanderError, Option } from "commander";
6070
6070
 
6071
6071
  // src/recipes/Recipe.ts
@@ -8042,20 +8042,20 @@ function planSkillRoots(repoRoot, options = {}) {
8042
8042
  for (const name of readdirSync6(real).sort()) {
8043
8043
  const path = join7(real, name);
8044
8044
  const label = `${alias}/${name}`;
8045
- const entryStat = lstatSync6(path);
8045
+ const entryStat2 = lstatSync6(path);
8046
8046
  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) };
8047
+ const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
8048
8048
  if (name === ".git") {
8049
8049
  candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
8050
- } else if (entryStat.isSymbolicLink()) {
8050
+ } else if (entryStat2.isSymbolicLink()) {
8051
8051
  candidate.text = readlinkSync5(path);
8052
8052
  candidate.resolution = resolveIn(diskLayout, path);
8053
8053
  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()) {
8054
+ } else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
8055
8055
  candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
8056
8056
  } else {
8057
8057
  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`;
8058
+ 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
8059
  const linkRow = rows.find((row) => row.mode === "120000");
8060
8060
  const gitlink = rows.find((row) => row.mode === "160000");
8061
8061
  if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
@@ -10031,14 +10031,324 @@ function createG33CompanionCheck() {
10031
10031
  }
10032
10032
 
10033
10033
  // 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";
10034
+ 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";
10035
+
10036
+ // src/bmad/setup-scripts.ts
10037
+ import { createHash as createHash8 } from "node:crypto";
10038
+ 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";
10039
+ import { basename as basename4, join as join10 } from "node:path";
10040
+ function entryStat(path) {
10041
+ try {
10042
+ return lstatSync9(path);
10043
+ } catch (error) {
10044
+ if (error.code === "ENOENT") return void 0;
10045
+ throw error;
10046
+ }
10047
+ }
10048
+ var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
10049
+ function manifestRows(text2) {
10050
+ const rows = [];
10051
+ let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
10052
+ for (let i = 0; i < text2.length; i++) {
10053
+ const c = text2[i];
10054
+ if (inQuotes) {
10055
+ if (c === '"' && text2[i + 1] === '"') {
10056
+ field2 += '"';
10057
+ i++;
10058
+ } else if (c === '"') {
10059
+ inQuotes = false;
10060
+ closed = true;
10061
+ } else field2 += c;
10062
+ } else if (c === '"' && !field2 && !closed) {
10063
+ inQuotes = true;
10064
+ quoted = true;
10065
+ } else if (c === ",") {
10066
+ row.push(field2);
10067
+ field2 = "";
10068
+ quoted = closed = false;
10069
+ } else if (c === "\n" || c === "\r") {
10070
+ if (c === "\r" && text2[i + 1] === "\n") i++;
10071
+ row.push(field2);
10072
+ if (row.length !== 1 || row[0]) rows.push(row);
10073
+ row = [];
10074
+ field2 = "";
10075
+ quoted = closed = false;
10076
+ } else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
10077
+ else field2 += c;
10078
+ }
10079
+ if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
10080
+ if (row.length || field2 || closed) {
10081
+ row.push(field2);
10082
+ rows.push(row);
10083
+ }
10084
+ if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
10085
+ throw new Error("Invalid BMAD file manifest columns");
10086
+ }
10087
+ return rows.slice(1);
10088
+ }
10089
+ function sameIdentity(a, b) {
10090
+ return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
10091
+ }
10092
+ var DeclinableError = class extends Error {
10093
+ constructor(message2, kind) {
10094
+ super(message2);
10095
+ this.kind = kind;
10096
+ }
10097
+ };
10098
+ var FIELD_SEP = String.fromCharCode(0);
10099
+ var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
10100
+ function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
10101
+ const bmadDir = join10(repoRoot, "_bmad");
10102
+ const dest = join10(bmadDir, "scripts");
10103
+ try {
10104
+ const bmad = entryStat(bmadDir);
10105
+ if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
10106
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
10107
+ const destStat = entryStat(dest);
10108
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
10109
+ const missing = [];
10110
+ for (const name of RUNTIME_SCRIPTS) {
10111
+ const existing = destStat ? entryStat(join10(dest, name)) : void 0;
10112
+ if (!existing) missing.push(name);
10113
+ else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
10114
+ }
10115
+ if (!missing.length) return { state: "present" };
10116
+ const configDir = entryStat(join10(bmadDir, "_config"));
10117
+ if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
10118
+ const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
10119
+ const manifestFile = configDir ? entryStat(manifestPath) : void 0;
10120
+ if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
10121
+ if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
10122
+ const installed = installedVersion?.();
10123
+ if (installed && installed !== version) {
10124
+ return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
10125
+ }
10126
+ const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
10127
+ const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
10128
+ if (unattested.length) {
10129
+ 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 };
10130
+ }
10131
+ return { state: "missing", missing };
10132
+ } catch (error) {
10133
+ return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
10134
+ }
10135
+ }
10136
+ function planBmadSetupScripts(repoRoot, pkg, version) {
10137
+ const dest = join10(repoRoot, "_bmad", "scripts");
10138
+ 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"}` };
10139
+ let fingerprint2;
10140
+ try {
10141
+ fingerprint2 = installerFingerprint(pkg.root);
10142
+ } catch (error) {
10143
+ return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
10144
+ }
10145
+ if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
10146
+ const source = join10(pkg.root, "src", "scripts");
10147
+ try {
10148
+ const bmad = lstatSync9(join10(repoRoot, "_bmad"));
10149
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
10150
+ const destStat = entryStat(dest);
10151
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
10152
+ const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
10153
+ if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
10154
+ const missing = [];
10155
+ for (const name of names) {
10156
+ const existing = entryStat(join10(dest, name));
10157
+ if (!existing) missing.push(name);
10158
+ else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
10159
+ }
10160
+ const files = [];
10161
+ if (!missing.length) return { files, source };
10162
+ const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
10163
+ const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
10164
+ const manifestFile = entryStat(manifestPath);
10165
+ if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
10166
+ throw new Error(MANIFEST_UNUSABLE);
10167
+ }
10168
+ const rows = manifestRows(readFileSync10(manifestPath, "utf8"));
10169
+ for (const name of missing) {
10170
+ const src = join10(source, name);
10171
+ if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
10172
+ const matching = rows.filter((row) => row[3] === `scripts/${name}`);
10173
+ const expectedHash = createHash8("sha256").update(readFileSync10(src)).digest("hex");
10174
+ if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
10175
+ throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
10176
+ }
10177
+ files.push(join10(dest, name));
10178
+ }
10179
+ return { files, source };
10180
+ } catch (error) {
10181
+ return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
10182
+ }
10183
+ }
10184
+ function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
10185
+ const plan = planBmadSetupScripts(repoRoot, pkg, version);
10186
+ if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
10187
+ const parent = join10(repoRoot, "_bmad");
10188
+ const dest = join10(parent, "scripts");
10189
+ const expectedParent = entryStat(parent);
10190
+ const expectedDir = entryStat(dest);
10191
+ let stage;
10192
+ let stageFd;
10193
+ let parentFd;
10194
+ const created = [];
10195
+ const stagedIdentities = /* @__PURE__ */ new Map();
10196
+ const published = /* @__PURE__ */ new Map();
10197
+ let ambiguousPublication;
10198
+ const matchesPayload = (path, owned) => {
10199
+ const stat = entryStat(path);
10200
+ return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync10(path).equals(owned.bytes);
10201
+ };
10202
+ let madeDir = false;
10203
+ let dirFd;
10204
+ let result2 = { files: [] };
10205
+ try {
10206
+ parentFd = openSync2(parent, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10207
+ const pinnedParent = fstatSync(parentFd);
10208
+ if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
10209
+ const anchoredParent = `/proc/self/fd/${parentFd}`;
10210
+ stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
10211
+ stageFd = openSync2(stage, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10212
+ const pinnedStage = fstatSync(stageFd);
10213
+ const stageName = basename4(stage);
10214
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
10215
+ if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
10216
+ for (const path of plan.files) {
10217
+ const name = basename4(path);
10218
+ const src = join10(plan.source, name);
10219
+ const staged = join10(anchoredStage, name);
10220
+ const fileFd = openSync2(staged, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
10221
+ try {
10222
+ const stat = fstatSync(fileFd);
10223
+ const bytes = readFileSync10(src);
10224
+ const mode = lstatSync9(src).mode & 4095;
10225
+ stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
10226
+ writeFileSync7(fileFd, bytes);
10227
+ fchmodSync(fileFd, mode);
10228
+ } finally {
10229
+ closeSync2(fileFd);
10230
+ }
10231
+ }
10232
+ if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
10233
+ const current = planBmadSetupScripts(repoRoot, pkg, version);
10234
+ if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
10235
+ const anchoredDest = join10(anchoredParent, "scripts");
10236
+ if (!entryStat(anchoredDest)) {
10237
+ mkdirSync6(anchoredDest);
10238
+ madeDir = true;
10239
+ }
10240
+ dirFd = openSync2(anchoredDest, constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW);
10241
+ const pinned = fstatSync(dirFd);
10242
+ if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
10243
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10244
+ const anchored = `/proc/self/fd/${dirFd}`;
10245
+ for (const path of plan.files) {
10246
+ const name = basename4(path);
10247
+ const staged = join10(anchoredStage, name);
10248
+ const output = join10(anchored, name);
10249
+ const owned = stagedIdentities.get(name);
10250
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10251
+ if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
10252
+ if (entryStat(output)) {
10253
+ ambiguousPublication = path;
10254
+ throw new Error(`Script destination appeared before publication: ${name}`);
10255
+ }
10256
+ try {
10257
+ linkSync(staged, output);
10258
+ } catch (error) {
10259
+ if (entryStat(output)) ambiguousPublication = path;
10260
+ throw error;
10261
+ }
10262
+ published.set(path, owned);
10263
+ created.push(path);
10264
+ if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
10265
+ afterPublish?.(created.length);
10266
+ }
10267
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
10268
+ for (const path of created) {
10269
+ if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
10270
+ }
10271
+ result2 = { files: created };
10272
+ } catch (error) {
10273
+ const recoveryErrors = [];
10274
+ for (const [path, owned] of [...published].reverse()) {
10275
+ try {
10276
+ const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
10277
+ const stat = entryStat(anchoredPath);
10278
+ if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
10279
+ else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
10280
+ else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
10281
+ } catch (recoveryError) {
10282
+ recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
10283
+ }
10284
+ }
10285
+ if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
10286
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
10287
+ try {
10288
+ const pinned = fstatSync(dirFd);
10289
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
10290
+ if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
10291
+ else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
10292
+ } catch (recoveryError) {
10293
+ recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
10294
+ }
10295
+ }
10296
+ result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
10297
+ } finally {
10298
+ const cleanupErrors = [];
10299
+ if (stageFd !== void 0) {
10300
+ try {
10301
+ const pinnedStage = fstatSync(stageFd);
10302
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
10303
+ for (const [name, owned] of stagedIdentities) {
10304
+ const staged = join10(anchoredStage, name);
10305
+ const stat = entryStat(staged);
10306
+ if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
10307
+ else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
10308
+ }
10309
+ const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
10310
+ if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
10311
+ else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
10312
+ } catch (cleanupError) {
10313
+ cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
10314
+ }
10315
+ closeSync2(stageFd);
10316
+ } else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
10317
+ if (cleanupErrors.length && result2.files.length) {
10318
+ for (const [path, owned] of [...published].reverse()) {
10319
+ try {
10320
+ const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
10321
+ if (matchesPayload(output, owned)) unlinkSync5(output);
10322
+ else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
10323
+ } catch (error) {
10324
+ cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
10325
+ }
10326
+ }
10327
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
10328
+ try {
10329
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
10330
+ if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
10331
+ else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
10332
+ } catch (error) {
10333
+ cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
10334
+ }
10335
+ }
10336
+ }
10337
+ if (dirFd !== void 0) closeSync2(dirFd);
10338
+ if (parentFd !== void 0) closeSync2(parentFd);
10339
+ if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
10340
+ }
10341
+ return result2;
10342
+ }
10343
+
10344
+ // src/parity/rules.ts
10345
+ import { basename as basename5, dirname as dirname9, join as join12, relative as relative9, resolve as resolve7 } from "node:path";
10036
10346
  import { spawnSync as spawnSync5 } from "node:child_process";
10037
10347
  import YAML4 from "yaml";
10038
10348
 
10039
10349
  // 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";
10350
+ import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "node:fs";
10351
+ import { join as join11 } from "node:path";
10042
10352
  import YAML3 from "yaml";
10043
10353
  var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
10044
10354
  var indentOf = (line) => line.length - line.trimStart().length;
@@ -10095,19 +10405,19 @@ function stripReservedModuleEntries(raw) {
10095
10405
  return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
10096
10406
  }
10097
10407
  function evictReservedModuleEntries(repoRoot, dryRun) {
10098
- const manifestPath = join10(repoRoot, "_bmad", "_config", "manifest.yaml");
10408
+ const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
10099
10409
  const none = { evicted: [], changedFiles: [], details: [] };
10100
10410
  if (!existsSync7(manifestPath)) return none;
10101
10411
  let original;
10102
10412
  try {
10103
- original = readFileSync10(manifestPath, "utf8");
10413
+ original = readFileSync11(manifestPath, "utf8");
10104
10414
  } catch {
10105
10415
  return none;
10106
10416
  }
10107
10417
  const stripped = stripReservedModuleEntries(original);
10108
10418
  if (!stripped) return none;
10109
10419
  const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
10110
- if (!dryRun) writeFileSync7(manifestPath, stripped.text, "utf8");
10420
+ if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
10111
10421
  return {
10112
10422
  evicted: stripped.evicted,
10113
10423
  changedFiles: [manifestPath],
@@ -10821,17 +11131,17 @@ function normalizeNewlines(value) {
10821
11131
  return value.replace(/\r\n/g, "\n");
10822
11132
  }
10823
11133
  function readText(path) {
10824
- return normalizeNewlines(readFileSync11(path, "utf8"));
11134
+ return normalizeNewlines(readFileSync12(path, "utf8"));
10825
11135
  }
10826
11136
  function safeReadText2(path) {
10827
11137
  return existsSync8(path) ? readText(path) : null;
10828
11138
  }
10829
11139
  function ensureParent(path) {
10830
- mkdirSync6(dirname9(path), { recursive: true });
11140
+ mkdirSync7(dirname9(path), { recursive: true });
10831
11141
  }
10832
11142
  function writeText(path, content) {
10833
11143
  ensureParent(path);
10834
- writeFileSync8(path, content);
11144
+ writeFileSync9(path, content);
10835
11145
  }
10836
11146
  function tryParseJson(text2) {
10837
11147
  if (!text2) return null;
@@ -10857,12 +11167,12 @@ function readSymlinkTarget(path) {
10857
11167
  }
10858
11168
  function ensureSymlink(path, target, dryRun) {
10859
11169
  if (existsSync8(path)) {
10860
- const stat = lstatSync9(path);
11170
+ const stat = lstatSync10(path);
10861
11171
  if (stat.isSymbolicLink()) {
10862
11172
  const current = readSymlinkTarget(path);
10863
11173
  if (current === target) return { changed: false };
10864
11174
  if (!dryRun) {
10865
- unlinkSync5(path);
11175
+ unlinkSync6(path);
10866
11176
  symlinkSync3(target, path);
10867
11177
  }
10868
11178
  return { changed: true };
@@ -10873,12 +11183,12 @@ function ensureSymlink(path, target, dryRun) {
10873
11183
  return { changed: true };
10874
11184
  }
10875
11185
  function bootstrapAgentsFile(repoRoot, dryRun) {
10876
- const agentsPath = join11(repoRoot, "AGENTS.md");
11186
+ const agentsPath = join12(repoRoot, "AGENTS.md");
10877
11187
  if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
10878
11188
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
10879
- const source = join11(repoRoot, file);
11189
+ const source = join12(repoRoot, file);
10880
11190
  if (!existsSync8(source)) continue;
10881
- const stat = lstatSync9(source);
11191
+ const stat = lstatSync10(source);
10882
11192
  if (stat.isSymbolicLink()) continue;
10883
11193
  if (stat.isFile()) {
10884
11194
  if (!dryRun) renameSync4(source, agentsPath);
@@ -10886,9 +11196,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
10886
11196
  }
10887
11197
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
10888
11198
  }
10889
- const readmePath = join11(repoRoot, "README.md");
11199
+ const readmePath = join12(repoRoot, "README.md");
10890
11200
  if (existsSync8(readmePath)) {
10891
- const stat = lstatSync9(readmePath);
11201
+ const stat = lstatSync10(readmePath);
10892
11202
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
10893
11203
  if (!dryRun) copyFileSync(readmePath, agentsPath);
10894
11204
  return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
@@ -10926,11 +11236,11 @@ function yamlGet(text2, keyPath) {
10926
11236
  return "";
10927
11237
  }
10928
11238
  function discoverRoles(repoRoot) {
10929
- const rolesDir = join11(repoRoot, "agents", "hermes");
11239
+ const rolesDir = join12(repoRoot, "agents", "hermes");
10930
11240
  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");
11241
+ return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
11242
+ const roleDir = join12(rolesDir, entry.name);
11243
+ const roleYamlPath = join12(roleDir, "role.yaml");
10934
11244
  if (!existsSync8(roleYamlPath)) return null;
10935
11245
  const text2 = readText(roleYamlPath);
10936
11246
  const runtimeRepoRaw = yamlGet(text2, "runtime.github_repo");
@@ -10963,26 +11273,26 @@ function discoverRoles(repoRoot) {
10963
11273
  }).filter((value) => Boolean(value));
10964
11274
  }
10965
11275
  function templateScript(ctx, name) {
10966
- const source = join11(ctx.pjanglerRoot, ".mise", "scripts", name);
11276
+ const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
10967
11277
  return existsSync8(source) ? readText(source) : void 0;
10968
11278
  }
10969
11279
  function templateVersioningScript(ctx) {
10970
11280
  return templateScript(ctx, "versioning.sh");
10971
11281
  }
10972
11282
  function templateLinkAgentfilesScript(ctx) {
10973
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
11283
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
10974
11284
  return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
10975
11285
  }
10976
11286
  function templateMaterializeEnvScript(ctx) {
10977
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
11287
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
10978
11288
  return existsSync8(source) ? readText(source) : void 0;
10979
11289
  }
10980
11290
  function resolveAgentHooksLayer(ctx) {
10981
11291
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
10982
11292
  if (override === "0" || override === "false") return false;
10983
11293
  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"));
11294
+ if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
11295
+ return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
10986
11296
  }
10987
11297
  function evaluateMiseConditionals(template, agentHooksLayer) {
10988
11298
  const out = [];
@@ -11008,13 +11318,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
11008
11318
  }
11009
11319
  function renderGeneratedProjectMiseToml(ctx, template) {
11010
11320
  const project = readProjectJson(ctx);
11011
- const projectName = String(project?.project_name ?? basename4(ctx.repoRoot) ?? "project");
11321
+ const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
11012
11322
  return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
11013
11323
  }
11014
11324
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
11015
- const targetPath = join11(ctx.repoRoot, "mise.toml");
11325
+ const targetPath = join12(ctx.repoRoot, "mise.toml");
11016
11326
  if (existsSync8(targetPath)) return null;
11017
- const sourcePath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
11327
+ const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
11018
11328
  if (!existsSync8(sourcePath)) return null;
11019
11329
  const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
11020
11330
  changedFiles.push(targetPath);
@@ -11022,12 +11332,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
11022
11332
  return rendered;
11023
11333
  }
11024
11334
  function templateCommonProjectText(ctx, rel) {
11025
- const path = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
11335
+ const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
11026
11336
  return existsSync8(path) ? readText(path) : void 0;
11027
11337
  }
11028
11338
  function lstatIfPresent(path) {
11029
11339
  try {
11030
- return lstatSync9(path);
11340
+ return lstatSync10(path);
11031
11341
  } catch (error) {
11032
11342
  if (error.code === "ENOENT") return void 0;
11033
11343
  throw error;
@@ -11035,7 +11345,7 @@ function lstatIfPresent(path) {
11035
11345
  }
11036
11346
  function templateVersionFilesConf(ctx, repoRoot) {
11037
11347
  if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
11038
- const packageJson = join11(repoRoot, "package.json");
11348
+ const packageJson = join12(repoRoot, "package.json");
11039
11349
  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
11350
  }
11041
11351
  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 +11355,7 @@ function recordedProjectType(ctx) {
11045
11355
  function isObsidianPluginRepo(ctx, repoRoot) {
11046
11356
  const recorded = recordedProjectType(ctx);
11047
11357
  if (recorded !== void 0) return recorded === "obsidian-plugin";
11048
- const manifest = record2(tryParseJson(safeReadText2(join11(repoRoot, "manifest.json"))));
11358
+ const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
11049
11359
  return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
11050
11360
  }
11051
11361
  function unsupportedVersionTypes(conf, script) {
@@ -11506,7 +11816,7 @@ function describeRewrite(before, after) {
11506
11816
  return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
11507
11817
  }
11508
11818
  function readProjectJson(ctx) {
11509
- return tryParseJson(safeReadText2(join11(ctx.repoRoot, ".project.json")));
11819
+ return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
11510
11820
  }
11511
11821
  function readDeclaredAgents(ctx) {
11512
11822
  const project = readProjectJson(ctx);
@@ -11523,7 +11833,7 @@ function readDeclaredAgents(ctx) {
11523
11833
  });
11524
11834
  }
11525
11835
  function readRoleYamlAt(roleDir) {
11526
- const roleYamlPath = join11(roleDir, "role.yaml");
11836
+ const roleYamlPath = join12(roleDir, "role.yaml");
11527
11837
  if (!existsSync8(roleYamlPath)) return null;
11528
11838
  const text2 = readText(roleYamlPath);
11529
11839
  return {
@@ -11535,7 +11845,7 @@ function readRoleYamlAt(roleDir) {
11535
11845
  }
11536
11846
  function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
11537
11847
  if (!roleDir) return true;
11538
- return !existsSync8(join11(resolve7(repoRoot, roleDir), "role.yaml"));
11848
+ return !existsSync8(join12(resolve7(repoRoot, roleDir), "role.yaml"));
11539
11849
  }
11540
11850
  function validateDeclaredAgent(ctx, declared) {
11541
11851
  const details = [];
@@ -11560,11 +11870,11 @@ function validateDeclaredAgent(ctx, declared) {
11560
11870
  details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
11561
11871
  }
11562
11872
  if (roleYaml.providerName) {
11563
- const dispatcher = join11(roleDir, ".scripts", "lib", "ticket-provider.sh");
11873
+ const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
11564
11874
  if (!existsSync8(dispatcher)) {
11565
11875
  details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
11566
11876
  }
11567
- const provider = join11(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
11877
+ const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
11568
11878
  if (!existsSync8(provider)) {
11569
11879
  details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
11570
11880
  }
@@ -11580,7 +11890,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
11580
11890
  function canonicalProjectJson(ctx) {
11581
11891
  const roles = discoverRoles(ctx.repoRoot);
11582
11892
  const existing = readProjectJson(ctx) ?? {};
11583
- const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename4(ctx.repoRoot)));
11893
+ const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
11584
11894
  const firstRole = roles[0];
11585
11895
  const ticketProvider = {
11586
11896
  ...existing.ticket_provider,
@@ -11650,8 +11960,8 @@ function canonicalProjectJson(ctx) {
11650
11960
  };
11651
11961
  }
11652
11962
  function projectJsonFinding(ctx) {
11653
- const projectPath = join11(ctx.repoRoot, ".project.json");
11654
- const planeJsonPath = join11(ctx.repoRoot, ".plane.json");
11963
+ const projectPath = join12(ctx.repoRoot, ".project.json");
11964
+ const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
11655
11965
  const details = [];
11656
11966
  const data = readProjectJson(ctx);
11657
11967
  const roles = discoverRoles(ctx.repoRoot);
@@ -11722,7 +12032,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
11722
12032
  var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
11723
12033
  var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
11724
12034
  function manifestBmadModules(repoRoot) {
11725
- const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
12035
+ const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
11726
12036
  const raw = safeReadText2(manifestPath);
11727
12037
  if (raw === null) return { status: "absent" };
11728
12038
  try {
@@ -11750,7 +12060,7 @@ function manifestBmadModules(repoRoot) {
11750
12060
  }
11751
12061
  }
11752
12062
  function configuredBmadModuleSelection(repoRoot) {
11753
- const raw = safeReadText2(join11(repoRoot, "_bmad", "config.toml"));
12063
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
11754
12064
  if (raw === null) return void 0;
11755
12065
  const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
11756
12066
  return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
@@ -11761,24 +12071,60 @@ function selectedBmadModules(repoRoot) {
11761
12071
  if (manifest.status === "invalid") throw new Error(manifest.error);
11762
12072
  return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
11763
12073
  }
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
- ];
12074
+ var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
12075
+ function consolidatedBmadConfig(repoRoot) {
12076
+ const path = join12(repoRoot, "_bmad", "config.yaml");
12077
+ if (!existsSync8(path)) return { status: "absent" };
12078
+ let parsed;
12079
+ try {
12080
+ parsed = YAML4.parse(readText(path));
12081
+ } catch (error) {
12082
+ return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
12083
+ }
12084
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12085
+ return { status: "unusable", reason: "is not a YAML mapping" };
12086
+ }
12087
+ return { status: "mapping", sections: new Set(Object.keys(parsed)) };
12088
+ }
12089
+ function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
12090
+ const bmad = join12(repoRoot, "_bmad");
12091
+ const gaps = [];
12092
+ const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
12093
+ if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
12094
+ for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
12095
+ if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
12096
+ }
12097
+ if (absent("_config", "manifest.yaml")) {
12098
+ gaps.push({
12099
+ path: join12("_config", "manifest.yaml"),
12100
+ 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"
12101
+ });
12102
+ }
12103
+ let consolidated;
12104
+ for (const module of modules) {
12105
+ if (!absent(module, "config.yaml")) continue;
12106
+ consolidated ??= consolidatedBmadConfig(repoRoot);
12107
+ if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
12108
+ 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)`;
12109
+ gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
12110
+ }
12111
+ return gaps;
12112
+ }
12113
+ function bmadScaffoldIssues(repoRoot, modules) {
12114
+ const gaps = bmadSentinelGaps(repoRoot, modules);
12115
+ const nameIssues = bmadProjectNameIssues(repoRoot);
12116
+ return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
11771
12117
  }
11772
12118
  function canonicalBmadProjectName(repoRoot) {
11773
12119
  const project = readProjectJson({ repoRoot });
11774
12120
  const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
11775
- return declared || basename4(repoRoot);
12121
+ return declared || basename5(repoRoot);
11776
12122
  }
11777
12123
  function bmadProjectNameIssues(repoRoot) {
11778
12124
  const expected = canonicalBmadProjectName(repoRoot);
11779
12125
  const paths = [];
11780
12126
  const details = [];
11781
- const configToml = join11(repoRoot, "_bmad", "config.toml");
12127
+ const configToml = join12(repoRoot, "_bmad", "config.toml");
11782
12128
  const tomlText = safeReadText2(configToml);
11783
12129
  const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
11784
12130
  let tomlName;
@@ -11793,16 +12139,16 @@ function bmadProjectNameIssues(repoRoot) {
11793
12139
  paths.push(configToml);
11794
12140
  details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
11795
12141
  }
11796
- const bmadRoot = join11(repoRoot, "_bmad");
12142
+ const bmadRoot = join12(repoRoot, "_bmad");
11797
12143
  if (existsSync8(bmadRoot)) {
11798
12144
  let declared;
11799
12145
  try {
11800
12146
  declared = selectedBmadModules(repoRoot);
11801
12147
  } catch {
11802
- declared = readdirSync7(bmadRoot);
12148
+ declared = readdirSync8(bmadRoot);
11803
12149
  }
11804
12150
  for (const name of new Set(declared)) {
11805
- const configPath = join11(bmadRoot, name, "config.yaml");
12151
+ const configPath = join12(bmadRoot, name, "config.yaml");
11806
12152
  const raw = safeReadText2(configPath);
11807
12153
  if (raw === null) continue;
11808
12154
  let actual;
@@ -11822,7 +12168,7 @@ function bmadProjectNameIssues(repoRoot) {
11822
12168
  function isCurrentActivation(path, activations) {
11823
12169
  if (activations.owned.has(path)) return true;
11824
12170
  try {
11825
- if (activations.owned.has(join11(realpathSync4(dirname9(path)), basename4(path)))) return true;
12171
+ if (activations.owned.has(join12(realpathSync4(dirname9(path)), basename5(path)))) return true;
11826
12172
  return activations.targets.has(realpathSync4(path));
11827
12173
  } catch {
11828
12174
  return false;
@@ -11835,26 +12181,26 @@ async function skillActivations(ctx) {
11835
12181
  function evictLegacyBmadPackState(ctx, changedFiles, activations) {
11836
12182
  const details = [];
11837
12183
  const skillDirs = [
11838
- join11(ctx.repoRoot, ".agents", "skills"),
11839
- ...SUPPORTED_CLI_ROOTS.map((root) => join11(ctx.repoRoot, root, "skills"))
12184
+ join12(ctx.repoRoot, ".agents", "skills"),
12185
+ ...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
11840
12186
  ];
11841
12187
  for (const dir of skillDirs) {
11842
12188
  const dirStat = lstatIfPresent(dir);
11843
12189
  if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
11844
12190
  let names;
11845
12191
  try {
11846
- names = readdirSync7(dir);
12192
+ names = readdirSync8(dir);
11847
12193
  } catch {
11848
12194
  continue;
11849
12195
  }
11850
12196
  for (const name of names) {
11851
12197
  if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
11852
- const path = join11(dir, name);
12198
+ const path = join12(dir, name);
11853
12199
  if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
11854
12200
  if (isCurrentActivation(path, activations)) continue;
11855
12201
  changedFiles.push(path);
11856
12202
  details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
11857
- if (!ctx.dryRun) unlinkSync5(path);
12203
+ if (!ctx.dryRun) unlinkSync6(path);
11858
12204
  }
11859
12205
  }
11860
12206
  return details;
@@ -11867,10 +12213,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
11867
12213
  prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
11868
12214
  };
11869
12215
  }
11870
- function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
12216
+ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
12217
+ const timeoutMs = options.timeoutMs ?? 18e4;
11871
12218
  const packageAt = (dir) => {
11872
12219
  try {
11873
- const parsed = JSON.parse(readFileSync11(join11(dir, "package.json"), "utf8"));
12220
+ const parsed = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8"));
11874
12221
  return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
11875
12222
  } catch {
11876
12223
  return void 0;
@@ -11902,12 +12249,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11902
12249
  `node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
11903
12250
  ], {
11904
12251
  encoding: "utf8",
11905
- timeout: 18e4,
12252
+ timeout: timeoutMs,
11906
12253
  env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
11907
12254
  });
11908
12255
  const entry = probe.stdout?.trim().split("\n").pop()?.trim();
11909
12256
  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(" ");
12257
+ const timedOut = probe.error?.code === "ETIMEDOUT";
12258
+ 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
12259
  return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
11912
12260
  }
11913
12261
  const root = dirname9(dirname9(dirname9(entry)));
@@ -11915,6 +12263,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11915
12263
  if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
11916
12264
  return { root, entry, version: found };
11917
12265
  }
12266
+ var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
12267
+ var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
12268
+ function bmadSetupScriptsState(repoRoot) {
12269
+ const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
12270
+ const decline = (why, missing, remedy2) => ({
12271
+ kind: "decline",
12272
+ summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
12273
+ });
12274
+ switch (inspection.state) {
12275
+ case "present":
12276
+ return { kind: "present" };
12277
+ case "unsafe":
12278
+ return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
12279
+ case "decline":
12280
+ return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
12281
+ case "missing":
12282
+ break;
12283
+ }
12284
+ const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
12285
+ const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
12286
+ const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
12287
+ if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
12288
+ const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
12289
+ if (plan.error) {
12290
+ 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 };
12291
+ }
12292
+ return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
12293
+ }
11918
12294
  function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
11919
12295
  const installerModules = modules.length ? modules.join(",") : "core";
11920
12296
  return [
@@ -11933,7 +12309,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
11933
12309
  }
11934
12310
  function bmadUpgradeChannelArgs(repoRoot, version) {
11935
12311
  if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
11936
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
12312
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
11937
12313
  if (!raw) return [];
11938
12314
  let modules;
11939
12315
  try {
@@ -12003,7 +12379,7 @@ function bmadChannelTarget(distTags) {
12003
12379
  return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
12004
12380
  }
12005
12381
  function readInstalledBmadVersion(repoRoot) {
12006
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
12382
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
12007
12383
  if (!raw) return void 0;
12008
12384
  try {
12009
12385
  const parsed = YAML4.parse(raw);
@@ -12014,8 +12390,8 @@ function readInstalledBmadVersion(repoRoot) {
12014
12390
  }
12015
12391
  }
12016
12392
  function bmadCachePath(homeDir) {
12017
- const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join11(homeDir, ".cache");
12018
- return join11(cacheRoot, "pjangler", "bmad-dist-tags.json");
12393
+ const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
12394
+ return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
12019
12395
  }
12020
12396
  function readBmadDistTagsCache(homeDir) {
12021
12397
  const raw = safeReadText2(bmadCachePath(homeDir));
@@ -12057,8 +12433,8 @@ function resolveBmadDistTags(homeDir) {
12057
12433
  if (fetched) {
12058
12434
  try {
12059
12435
  const path = bmadCachePath(homeDir);
12060
- mkdirSync6(dirname9(path), { recursive: true });
12061
- writeFileSync8(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
12436
+ mkdirSync7(dirname9(path), { recursive: true });
12437
+ writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
12062
12438
  } catch {
12063
12439
  }
12064
12440
  return { distTags: fetched, stale: false };
@@ -12101,27 +12477,27 @@ function compareBmadVersions(a, b) {
12101
12477
  function discoverMomoProviderCandidates(repoRoot) {
12102
12478
  const candidates = [];
12103
12479
  const roleDirs = [];
12104
- const hermesDir = join11(repoRoot, "agents", "hermes");
12480
+ const hermesDir = join12(repoRoot, "agents", "hermes");
12105
12481
  if (existsSync8(hermesDir)) {
12106
- for (const entry of readdirSync7(hermesDir, { withFileTypes: true })) {
12107
- if (entry.isDirectory()) roleDirs.push(join11(hermesDir, entry.name));
12482
+ for (const entry of readdirSync8(hermesDir, { withFileTypes: true })) {
12483
+ if (entry.isDirectory()) roleDirs.push(join12(hermesDir, entry.name));
12108
12484
  }
12109
12485
  }
12110
12486
  for (const roleDir of roleDirs) {
12111
12487
  for (const name of ["momo", "provider", "momo-provider"]) {
12112
- const path = join11(roleDir, name);
12488
+ const path = join12(roleDir, name);
12113
12489
  if (existsSync8(path)) {
12114
12490
  const kind = path.endsWith(".py") ? "python" : "shell";
12115
12491
  candidates.push({ path, kind });
12116
12492
  }
12117
12493
  }
12118
12494
  if (existsSync8(roleDir)) {
12119
- for (const entry of readdirSync7(roleDir, { withFileTypes: true })) {
12495
+ for (const entry of readdirSync8(roleDir, { withFileTypes: true })) {
12120
12496
  if (!entry.isFile()) continue;
12121
12497
  if (!entry.name.startsWith("momo")) continue;
12122
- const path = join11(roleDir, entry.name);
12498
+ const path = join12(roleDir, entry.name);
12123
12499
  try {
12124
- if (lstatSync9(path).mode & 73) {
12500
+ if (lstatSync10(path).mode & 73) {
12125
12501
  const kind = entry.name.endsWith(".py") ? "python" : "shell";
12126
12502
  if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
12127
12503
  }
@@ -12131,7 +12507,7 @@ function discoverMomoProviderCandidates(repoRoot) {
12131
12507
  }
12132
12508
  }
12133
12509
  for (const rel of [".mise/scripts/momo-provider.sh", ".scripts/momo-provider.sh", "momo"]) {
12134
- const path = join11(repoRoot, rel);
12510
+ const path = join12(repoRoot, rel);
12135
12511
  if (existsSync8(path)) {
12136
12512
  const kind = path.endsWith(".py") ? "python" : "shell";
12137
12513
  if (!candidates.some((c) => c.path === path)) candidates.push({ path, kind });
@@ -12167,7 +12543,7 @@ function runProviderLocalSmoke(repoRoot, candidate) {
12167
12543
  return { ok: true };
12168
12544
  }
12169
12545
  function attemptPlaneStateMapping(repoRoot) {
12170
- const project = tryParseJson(safeReadText2(join11(repoRoot, ".project.json")));
12546
+ const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
12171
12547
  const tp = project?.ticket_provider ?? {};
12172
12548
  if (!tp.board_id) return { ok: false, detail: "ticket_provider.board_id missing; cannot map Plane states" };
12173
12549
  return { ok: false, detail: `Plane state mapping attempted for board ${tp.board_id} (credentials required for full mapping)` };
@@ -12184,7 +12560,7 @@ function momoLifecycleFinding(section2, status, summary, details = []) {
12184
12560
  }
12185
12561
  function auditManifestRoleConsistency(repoRoot) {
12186
12562
  const details = [];
12187
- const projectPath = join11(repoRoot, ".project.json");
12563
+ const projectPath = join12(repoRoot, ".project.json");
12188
12564
  if (!existsSync8(projectPath)) {
12189
12565
  return momoLifecycleFinding("manifest-role-consistency", "fail", ".project.json missing", [".project.json missing"]);
12190
12566
  }
@@ -12206,7 +12582,7 @@ function auditManifestRoleConsistency(repoRoot) {
12206
12582
  details.push(`agents.${agentId}.role_dir does not exist: ${agent.role_dir}`);
12207
12583
  continue;
12208
12584
  }
12209
- const roleYaml = join11(roleDir, "role.yaml");
12585
+ const roleYaml = join12(roleDir, "role.yaml");
12210
12586
  if (!existsSync8(roleYaml)) {
12211
12587
  details.push(`agents.${agentId} role.yaml missing at ${agent.role_dir}/role.yaml`);
12212
12588
  continue;
@@ -12249,26 +12625,26 @@ function hasAnyLifecycleScript(repoRoot) {
12249
12625
  for (const pattern of patterns) {
12250
12626
  if (pattern.includes("*")) {
12251
12627
  const [prefix, suffix] = pattern.split("*");
12252
- const base = join11(repoRoot, prefix);
12628
+ const base = join12(repoRoot, prefix);
12253
12629
  if (!existsSync8(base)) continue;
12254
- for (const entry of readdirSync7(base, { withFileTypes: true })) {
12630
+ for (const entry of readdirSync8(base, { withFileTypes: true })) {
12255
12631
  if (!entry.isDirectory()) continue;
12256
- const candidate = join11(base, entry.name, suffix);
12632
+ const candidate = join12(base, entry.name, suffix);
12257
12633
  if (existsSync8(candidate)) return true;
12258
12634
  const parent = dirname9(candidate);
12259
- const stem = basename4(candidate);
12635
+ const stem = basename5(candidate);
12260
12636
  if (!existsSync8(parent)) continue;
12261
- for (const sibling of readdirSync7(parent, { withFileTypes: true })) {
12637
+ for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
12262
12638
  if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
12263
12639
  }
12264
12640
  }
12265
12641
  } else {
12266
- const base = join11(repoRoot, pattern);
12642
+ const base = join12(repoRoot, pattern);
12267
12643
  if (existsSync8(base)) return true;
12268
12644
  const parent = dirname9(base);
12269
- const prefix = basename4(base);
12645
+ const prefix = basename5(base);
12270
12646
  if (existsSync8(parent)) {
12271
- for (const entry of readdirSync7(parent, { withFileTypes: true })) {
12647
+ for (const entry of readdirSync8(parent, { withFileTypes: true })) {
12272
12648
  if (entry.name.startsWith(prefix)) return true;
12273
12649
  }
12274
12650
  }
@@ -12298,26 +12674,26 @@ function hasAnySentinelScript(repoRoot) {
12298
12674
  for (const pattern of patterns) {
12299
12675
  if (pattern.includes("*")) {
12300
12676
  const [prefix, suffix] = pattern.split("*");
12301
- const base = join11(repoRoot, prefix);
12677
+ const base = join12(repoRoot, prefix);
12302
12678
  if (!existsSync8(base)) continue;
12303
- for (const entry of readdirSync7(base, { withFileTypes: true })) {
12679
+ for (const entry of readdirSync8(base, { withFileTypes: true })) {
12304
12680
  if (!entry.isDirectory()) continue;
12305
- const candidate = join11(base, entry.name, suffix);
12681
+ const candidate = join12(base, entry.name, suffix);
12306
12682
  if (existsSync8(candidate)) return true;
12307
12683
  const parent = dirname9(candidate);
12308
- const stem = basename4(candidate);
12684
+ const stem = basename5(candidate);
12309
12685
  if (!existsSync8(parent)) continue;
12310
- for (const sibling of readdirSync7(parent, { withFileTypes: true })) {
12686
+ for (const sibling of readdirSync8(parent, { withFileTypes: true })) {
12311
12687
  if (sibling.isFile() && sibling.name.startsWith(stem)) return true;
12312
12688
  }
12313
12689
  }
12314
12690
  } else {
12315
- const base = join11(repoRoot, pattern);
12691
+ const base = join12(repoRoot, pattern);
12316
12692
  if (existsSync8(base)) return true;
12317
12693
  const parent = dirname9(base);
12318
- const prefix = basename4(base);
12694
+ const prefix = basename5(base);
12319
12695
  if (existsSync8(parent)) {
12320
- for (const entry of readdirSync7(parent, { withFileTypes: true })) {
12696
+ for (const entry of readdirSync8(parent, { withFileTypes: true })) {
12321
12697
  if (entry.name.startsWith(prefix)) return true;
12322
12698
  }
12323
12699
  }
@@ -12362,7 +12738,7 @@ function auditProviderSyntax(repoRoot) {
12362
12738
  }
12363
12739
  function auditPlaneBinding(repoRoot) {
12364
12740
  const details = [];
12365
- const project = tryParseJson(safeReadText2(join11(repoRoot, ".project.json")));
12741
+ const project = tryParseJson(safeReadText2(join12(repoRoot, ".project.json")));
12366
12742
  const tp = project?.ticket_provider ?? {};
12367
12743
  for (const key of ["type", "workspace", "identifier", "board_id"]) {
12368
12744
  if (!tp[key]) details.push(`ticket_provider.${key} missing`);
@@ -12534,8 +12910,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
12534
12910
  ".zencoder": "zencoder"
12535
12911
  };
12536
12912
  function unsupportedRootAttestation(repoRoot, rootName) {
12537
- const root = join11(repoRoot, rootName);
12538
- const stat = lstatSync9(root);
12913
+ const root = join12(repoRoot, rootName);
12914
+ const stat = lstatSync10(root);
12539
12915
  if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
12540
12916
  const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
12541
12917
  if (!installedBmadTools(repoRoot).has(installerTool)) {
@@ -12554,7 +12930,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
12554
12930
  function optionalTemplateScriptIssues(ctx) {
12555
12931
  const issues = [];
12556
12932
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
12557
- const path = join11(ctx.repoRoot, rel);
12933
+ const path = join12(ctx.repoRoot, rel);
12558
12934
  if (!existsSync8(path)) continue;
12559
12935
  const expected = templateCommonProjectText(ctx, rel);
12560
12936
  if (expected === void 0) continue;
@@ -12565,7 +12941,7 @@ function optionalTemplateScriptIssues(ctx) {
12565
12941
  function refreshOptionalTemplateScripts(ctx) {
12566
12942
  const changed = [];
12567
12943
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
12568
- const path = join11(ctx.repoRoot, rel);
12944
+ const path = join12(ctx.repoRoot, rel);
12569
12945
  if (!existsSync8(path)) continue;
12570
12946
  const expected = templateCommonProjectText(ctx, rel);
12571
12947
  if (expected === void 0 || safeReadText2(path) === expected) continue;
@@ -12583,13 +12959,13 @@ function createMiseChecks() {
12583
12959
  id: "mise.config-root",
12584
12960
  title: "mise config_root + AGENTS link hooks",
12585
12961
  audit: (ctx) => {
12586
- const misePath = join11(ctx.repoRoot, "mise.toml");
12962
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12587
12963
  if (!existsSync8(misePath)) {
12588
12964
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
12589
12965
  }
12590
12966
  const text2 = readText(misePath);
12591
12967
  const details = [];
12592
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12968
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12593
12969
  if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
12594
12970
  const toml = [];
12595
12971
  const manual = [];
@@ -12637,7 +13013,7 @@ function createMiseChecks() {
12637
13013
  };
12638
13014
  },
12639
13015
  migrate: (ctx, finding2) => {
12640
- const path = join11(ctx.repoRoot, "mise.toml");
13016
+ const path = join12(ctx.repoRoot, "mise.toml");
12641
13017
  const changedFiles = [];
12642
13018
  const details = [];
12643
13019
  if (!existsSync8(path)) {
@@ -12656,7 +13032,7 @@ function createMiseChecks() {
12656
13032
  if (!ctx.dryRun) writeText(path, plan.text);
12657
13033
  text2 = plan.text;
12658
13034
  }
12659
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
13035
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
12660
13036
  const expectedScript = templateLinkAgentfilesScript(ctx);
12661
13037
  if (expectedScript === void 0) {
12662
13038
  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 +13070,9 @@ function createMiseChecks() {
12694
13070
  title: "managed mise versioning block",
12695
13071
  audit: (ctx) => {
12696
13072
  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");
13073
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13074
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
13075
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
12700
13076
  const text2 = safeReadText2(misePath);
12701
13077
  if (!text2?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
12702
13078
  if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
@@ -12722,7 +13098,7 @@ function createMiseChecks() {
12722
13098
  migrate: (ctx, finding2) => {
12723
13099
  const changedFiles = [];
12724
13100
  const details = [];
12725
- const misePath = join11(ctx.repoRoot, "mise.toml");
13101
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12726
13102
  if (!existsSync8(misePath)) {
12727
13103
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
12728
13104
  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 +13116,7 @@ function createMiseChecks() {
12740
13116
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
12741
13117
  if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
12742
13118
  }
12743
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
13119
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
12744
13120
  const expectedScript = templateVersioningScript(ctx);
12745
13121
  if (expectedScript === void 0) {
12746
13122
  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 +13128,7 @@ function createMiseChecks() {
12752
13128
  chmodSync3(versioningPath, 493);
12753
13129
  }
12754
13130
  }
12755
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
13131
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
12756
13132
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
12757
13133
  if (safeReadText2(manifestPath) !== expectedManifest) {
12758
13134
  changedFiles.push(manifestPath);
@@ -12814,7 +13190,7 @@ var RETIRED_SKILL_SCRIPTS = [
12814
13190
  ".mise/scripts/unlink-project-skills-from-clis.sh"
12815
13191
  ];
12816
13192
  function retiredSkillsScripts(ctx) {
12817
- return RETIRED_SKILL_SCRIPTS.map((path) => join11(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
13193
+ return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
12818
13194
  }
12819
13195
  function skillsWiringFindings(text2) {
12820
13196
  if (text2 === null) return { fixable: [], manual: [] };
@@ -12865,7 +13241,7 @@ function createAgentHooksChecks() {
12865
13241
  title: "Skillex project skills",
12866
13242
  audit: async (ctx) => {
12867
13243
  const finding2 = await auditProjectSkills(ctx);
12868
- const mise = safeReadText2(join11(ctx.repoRoot, "mise.toml"));
13244
+ const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
12869
13245
  const registryField = await legacyRegistryField(ctx);
12870
13246
  const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
12871
13247
  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 +13262,7 @@ function createAgentHooksChecks() {
12886
13262
  const changedFiles = [];
12887
13263
  const details = [];
12888
13264
  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"));
13265
+ let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
12890
13266
  if (currentMise === null) {
12891
13267
  const template = templateCommonProjectText(ctx, "mise.toml.jinja");
12892
13268
  if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
@@ -12901,9 +13277,9 @@ function createAgentHooksChecks() {
12901
13277
  const stat = lstatIfPresent(path);
12902
13278
  if (!stat || stat.isDirectory()) continue;
12903
13279
  changedFiles.push(path);
12904
- if (!ctx.dryRun) unlinkSync5(path);
13280
+ if (!ctx.dryRun) unlinkSync6(path);
12905
13281
  }
12906
- const misePath = join11(ctx.repoRoot, "mise.toml");
13282
+ const misePath = join12(ctx.repoRoot, "mise.toml");
12907
13283
  const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
12908
13284
  if (safeReadText2(misePath) !== nextMise) {
12909
13285
  changedFiles.push(misePath);
@@ -12934,9 +13310,9 @@ function createAgentHooksChecks() {
12934
13310
  id: "sot.agent-symlinks",
12935
13311
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
12936
13312
  audit: (ctx) => {
12937
- const agentsPath = join11(ctx.repoRoot, "AGENTS.md");
13313
+ const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
12938
13314
  if (!existsSync8(agentsPath)) {
12939
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join11(ctx.repoRoot, file)));
13315
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
12940
13316
  if (fallbackSources.length === 0) {
12941
13317
  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
13318
  }
@@ -12951,7 +13327,7 @@ function createAgentHooksChecks() {
12951
13327
  }
12952
13328
  const details = [];
12953
13329
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
12954
- const full = join11(ctx.repoRoot, file);
13330
+ const full = join12(ctx.repoRoot, file);
12955
13331
  const target = readSymlinkTarget(full);
12956
13332
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
12957
13333
  }
@@ -12975,7 +13351,7 @@ function createAgentHooksChecks() {
12975
13351
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
12976
13352
  }
12977
13353
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
12978
- const full = join11(ctx.repoRoot, file);
13354
+ const full = join12(ctx.repoRoot, file);
12979
13355
  const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
12980
13356
  if (result2.blocked) blockedDetails.push(result2.blocked);
12981
13357
  if (result2.changed) changedFiles.push(full);
@@ -13002,7 +13378,7 @@ function createProjectJsonChecks() {
13002
13378
  const changedFiles = [];
13003
13379
  const blockedDetails = [];
13004
13380
  const droppedDetails = [];
13005
- const path = join11(ctx.repoRoot, ".project.json");
13381
+ const path = join12(ctx.repoRoot, ".project.json");
13006
13382
  const existing = readProjectJson(ctx) ?? {};
13007
13383
  const canonical = canonicalProjectJson(ctx);
13008
13384
  const preservedDetails = [];
@@ -13034,7 +13410,7 @@ function createProjectJsonChecks() {
13034
13410
  changedFiles.push(path);
13035
13411
  if (!ctx.dryRun) writeText(path, expected);
13036
13412
  }
13037
- const planeJson = join11(ctx.repoRoot, ".plane.json");
13413
+ const planeJson = join12(ctx.repoRoot, ".plane.json");
13038
13414
  if (existsSync8(planeJson)) {
13039
13415
  const backup = `${planeJson}.migrated-backup`;
13040
13416
  if (existsSync8(backup)) {
@@ -13066,10 +13442,10 @@ function createMiseOpInjectChecks() {
13066
13442
  const details = [];
13067
13443
  let envOpNeedsHands = false;
13068
13444
  const optedOut = envMaterializationOptedOut(ctx);
13069
- const envOpPath = join11(ctx.repoRoot, ".env.op");
13445
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
13070
13446
  const envOpExists = existsSync8(envOpPath);
13071
13447
  const envOp = envOpExists ? readText(envOpPath) : void 0;
13072
- const gitignore = safeReadText2(join11(ctx.repoRoot, ".gitignore"));
13448
+ const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
13073
13449
  if (!envOpExists) {
13074
13450
  details.push(".env.op missing");
13075
13451
  } else if (!envOp?.trim()) {
@@ -13089,7 +13465,7 @@ function createMiseOpInjectChecks() {
13089
13465
  if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
13090
13466
  if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
13091
13467
  if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
13092
- const misePath = join11(ctx.repoRoot, "mise.toml");
13468
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13093
13469
  const miseText = safeReadText2(misePath);
13094
13470
  if (!miseText) {
13095
13471
  details.push("mise.toml missing for .env materialization hook");
@@ -13112,7 +13488,7 @@ function createMiseOpInjectChecks() {
13112
13488
  else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
13113
13489
  details.push(...mise);
13114
13490
  }
13115
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13491
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13116
13492
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
13117
13493
  if (optedOut) {
13118
13494
  if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
@@ -13120,7 +13496,7 @@ function createMiseOpInjectChecks() {
13120
13496
  details.push("pjangler package is missing the managed materialize-env.sh source");
13121
13497
  } else if (safeReadText2(materializePath) !== expectedMaterializer) {
13122
13498
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
13123
- } else if ((lstatSync9(materializePath).mode & 73) === 0) {
13499
+ } else if ((lstatSync10(materializePath).mode & 73) === 0) {
13124
13500
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
13125
13501
  }
13126
13502
  return {
@@ -13136,8 +13512,8 @@ function createMiseOpInjectChecks() {
13136
13512
  const changedFiles = [];
13137
13513
  const details = [];
13138
13514
  let envOpNeedsHands = false;
13139
- const envOpPath = join11(ctx.repoRoot, ".env.op");
13140
- const canonicalEnvOpPath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
13515
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
13516
+ const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
13141
13517
  if (!existsSync8(canonicalEnvOpPath)) {
13142
13518
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
13143
13519
  }
@@ -13166,7 +13542,7 @@ function createMiseOpInjectChecks() {
13166
13542
  }
13167
13543
  }
13168
13544
  }
13169
- const gitignorePath = join11(ctx.repoRoot, ".gitignore");
13545
+ const gitignorePath = join12(ctx.repoRoot, ".gitignore");
13170
13546
  const gitignore = safeReadText2(gitignorePath) ?? "";
13171
13547
  const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
13172
13548
  # staged through a mktemp file and moved into place only on success.
@@ -13179,7 +13555,7 @@ function createMiseOpInjectChecks() {
13179
13555
  changedFiles.push(gitignorePath);
13180
13556
  if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
13181
13557
  }
13182
- const misePath = join11(ctx.repoRoot, "mise.toml");
13558
+ const misePath = join12(ctx.repoRoot, "mise.toml");
13183
13559
  let currentMise = safeReadText2(misePath);
13184
13560
  if (currentMise === null) {
13185
13561
  const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
@@ -13196,7 +13572,7 @@ function createMiseOpInjectChecks() {
13196
13572
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
13197
13573
  if (!ctx.dryRun) writeText(misePath, opPlan.text);
13198
13574
  }
13199
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13575
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
13200
13576
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
13201
13577
  if (optedOut) {
13202
13578
  if (existsSync8(materializePath)) {
@@ -13205,7 +13581,7 @@ function createMiseOpInjectChecks() {
13205
13581
  }
13206
13582
  } else if (!expectedMaterializer) {
13207
13583
  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) {
13584
+ } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
13209
13585
  changedFiles.push(materializePath);
13210
13586
  if (!ctx.dryRun) {
13211
13587
  writeText(materializePath, expectedMaterializer);
@@ -13267,7 +13643,7 @@ function createProjectProvenanceChecks() {
13267
13643
  title: ".copier-answers.yml provenance + drift report",
13268
13644
  audit: (ctx) => {
13269
13645
  const details = [];
13270
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
13646
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
13271
13647
  const text2 = safeReadText2(path);
13272
13648
  const project = readProjectJson(ctx);
13273
13649
  if (!text2) {
@@ -13295,11 +13671,11 @@ function createProjectProvenanceChecks() {
13295
13671
  migrate: (ctx, finding2) => {
13296
13672
  const changedFiles = [];
13297
13673
  const project = canonicalProjectJson(ctx);
13298
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
13674
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
13299
13675
  const current = safeReadText2(path);
13300
13676
  const existing = current ? YAML4.parseDocument(current) : void 0;
13301
13677
  const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
13302
- document.set("_src_path", join11(ctx.pjanglerRoot, "templates", "commonproject"));
13678
+ document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
13303
13679
  document.set("project_description", String(project.project_description));
13304
13680
  document.set("project_name", String(project.project_name));
13305
13681
  document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
@@ -13324,9 +13700,9 @@ ${body}`;
13324
13700
  }
13325
13701
  function supportedCliProjectionIssues(repoRoot, plan) {
13326
13702
  const issues = [];
13327
- const managedSkills = join11(repoRoot, ".agents", "skills");
13703
+ const managedSkills = join12(repoRoot, ".agents", "skills");
13328
13704
  for (const rootName of SUPPORTED_CLI_ROOTS) {
13329
- const root = join11(repoRoot, rootName);
13705
+ const root = join12(repoRoot, rootName);
13330
13706
  const rootStat = lstatIfPresent(root);
13331
13707
  if (!rootStat) {
13332
13708
  issues.push(`${rootName} missing`);
@@ -13336,7 +13712,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
13336
13712
  issues.push(`${rootName} must be a real configuration directory`);
13337
13713
  continue;
13338
13714
  }
13339
- const skills = join11(root, "skills");
13715
+ const skills = join12(root, "skills");
13340
13716
  const skillsStat = lstatIfPresent(skills);
13341
13717
  if (!skillsStat) {
13342
13718
  issues.push(`${rootName}/skills missing`);
@@ -13373,9 +13749,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
13373
13749
  issues.push(`${rootName}/skills is not a directory`);
13374
13750
  continue;
13375
13751
  }
13376
- const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync7(projectedSkills).some((name) => {
13377
- const skillFile = join11(projectedSkills, name, "SKILL.md");
13378
- return existsSync8(skillFile) && lstatSync9(skillFile).isFile();
13752
+ const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
13753
+ const skillFile = join12(projectedSkills, name, "SKILL.md");
13754
+ return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
13379
13755
  });
13380
13756
  if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
13381
13757
  }
@@ -13401,7 +13777,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
13401
13777
  "# some CLIs get a real directory here and some get a symlink."
13402
13778
  ]);
13403
13779
  function supportedCliGitignoreIssues(repoRoot) {
13404
- const lines = (safeReadText2(join11(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
13780
+ const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
13405
13781
  const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
13406
13782
  return [
13407
13783
  ...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
@@ -13409,7 +13785,7 @@ function supportedCliGitignoreIssues(repoRoot) {
13409
13785
  ];
13410
13786
  }
13411
13787
  function ensureSupportedCliGitignore(ctx) {
13412
- const path = join11(ctx.repoRoot, ".gitignore");
13788
+ const path = join12(ctx.repoRoot, ".gitignore");
13413
13789
  const current = safeReadText2(path) ?? "";
13414
13790
  const kept = current.split(/\r?\n/).filter((line) => {
13415
13791
  const trimmed2 = line.trim();
@@ -13451,6 +13827,24 @@ function withInstallerSkillHandoff(check) {
13451
13827
  }
13452
13828
  };
13453
13829
  }
13830
+ function withScaffoldPostcondition(check) {
13831
+ if (check.id !== "bmad.scaffold") return check;
13832
+ return {
13833
+ ...check,
13834
+ migrate: async (ctx, finding2) => {
13835
+ const result2 = await check.migrate(ctx, finding2);
13836
+ if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
13837
+ const after = await check.audit(ctx);
13838
+ if (after.status === "pass" || after.status === "skip") return result2;
13839
+ return {
13840
+ ...result2,
13841
+ status: "partial",
13842
+ summary: after.summary,
13843
+ details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
13844
+ };
13845
+ }
13846
+ };
13847
+ }
13454
13848
  function createBmadChecks() {
13455
13849
  return [
13456
13850
  {
@@ -13468,15 +13862,8 @@ function createBmadChecks() {
13468
13862
  fixable: false
13469
13863
  };
13470
13864
  }
13471
- const targetRoot = join11(ctx.repoRoot, "_bmad");
13472
13865
  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
- ];
13866
+ const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13480
13867
  return {
13481
13868
  id: "bmad.scaffold",
13482
13869
  title: "BMAD modules/docs scaffold",
@@ -13515,7 +13902,7 @@ function createBmadChecks() {
13515
13902
  bmadCompanion: preservation
13516
13903
  };
13517
13904
  if (ctx.dryRun) {
13518
- const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
13905
+ const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13519
13906
  const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
13520
13907
  if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
13521
13908
  return {
@@ -13528,8 +13915,8 @@ function createBmadChecks() {
13528
13915
  bmadCompanion: companion
13529
13916
  };
13530
13917
  }
13531
- changedFiles.push(...sentinels.map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)));
13532
- changedFiles.push(...bmadProjectNameIssues(ctx.repoRoot).paths);
13918
+ changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
13919
+ changedFiles.push(...issues.nameIssues.paths);
13533
13920
  const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
13534
13921
  changedFiles.push(...reservedPreview?.changedFiles ?? []);
13535
13922
  return {
@@ -13547,9 +13934,10 @@ function createBmadChecks() {
13547
13934
  };
13548
13935
  }
13549
13936
  if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
13937
+ const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13550
13938
  const expectedChangedPaths2 = [
13551
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
13552
- ...bmadProjectNameIssues(ctx.repoRoot).paths
13939
+ ...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
13940
+ ...before2.nameIssues.paths
13553
13941
  ];
13554
13942
  const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
13555
13943
  const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
@@ -13590,16 +13978,17 @@ function createBmadChecks() {
13590
13978
  bmadCompanion: update.companion
13591
13979
  };
13592
13980
  }
13981
+ const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
13593
13982
  const expectedChangedPaths = [
13594
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
13595
- ...bmadProjectNameIssues(ctx.repoRoot).paths
13983
+ ...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
13984
+ ...before.nameIssues.paths
13596
13985
  ];
13597
13986
  const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
13598
13987
  const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
13599
13988
  if (reserved) evicted.unshift(...reserved.details);
13600
13989
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
13601
13990
  if (!install.ok && reserved?.original !== void 0) {
13602
- writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
13991
+ writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
13603
13992
  evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
13604
13993
  }
13605
13994
  if (!install.ok) {
@@ -13624,6 +14013,58 @@ function createBmadChecks() {
13624
14013
  };
13625
14014
  }
13626
14015
  },
14016
+ {
14017
+ id: "bmad.setup-scripts",
14018
+ title: "BMAD ignored installer setup scripts",
14019
+ audit: (ctx) => {
14020
+ if (!isInstalledBmadProject(ctx.repoRoot)) return {
14021
+ id: "bmad.setup-scripts",
14022
+ title: "BMAD ignored installer setup scripts",
14023
+ status: "skip",
14024
+ summary: "No installed BMAD project",
14025
+ details: [],
14026
+ fixable: false
14027
+ };
14028
+ const state = bmadSetupScriptsState(ctx.repoRoot);
14029
+ const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
14030
+ switch (state.kind) {
14031
+ case "present":
14032
+ return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
14033
+ case "decline":
14034
+ return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
14035
+ case "refuse":
14036
+ return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
14037
+ case "missing":
14038
+ return {
14039
+ id,
14040
+ title,
14041
+ status: "fail",
14042
+ summary: "Ignored BMAD installer scripts are missing",
14043
+ details: state.files.map((path) => relative9(ctx.repoRoot, path)),
14044
+ fixable: true
14045
+ };
14046
+ }
14047
+ },
14048
+ migrate: (ctx, finding2) => {
14049
+ const title = finding2.title, id = finding2.id;
14050
+ if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
14051
+ const state = bmadSetupScriptsState(ctx.repoRoot);
14052
+ if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
14053
+ if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
14054
+ if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
14055
+ const { pkg, files } = state;
14056
+ if (ctx.dryRun) return {
14057
+ id,
14058
+ title,
14059
+ status: "applied",
14060
+ summary: "Would restore ignored scripts from verified pinned bmad-method package",
14061
+ details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
14062
+ changedFiles: files
14063
+ };
14064
+ const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
14065
+ 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 };
14066
+ }
14067
+ },
13627
14068
  {
13628
14069
  id: "bmad.version",
13629
14070
  title: "BMAD version currency",
@@ -13634,7 +14075,7 @@ function createBmadChecks() {
13634
14075
  id: "bmad.version",
13635
14076
  title: "BMAD version currency",
13636
14077
  status: "skip",
13637
- summary: existsSync8(join11(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
14078
+ summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
13638
14079
  details: [],
13639
14080
  fixable: false
13640
14081
  };
@@ -13722,7 +14163,7 @@ function createBmadChecks() {
13722
14163
  }
13723
14164
  const installed = readInstalledBmadVersion(ctx.repoRoot);
13724
14165
  const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
13725
- const manifestPath = join11(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
14166
+ const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
13726
14167
  const manifestSelection = manifestBmadModules(ctx.repoRoot);
13727
14168
  if (manifestSelection.status === "invalid") {
13728
14169
  return {
@@ -13820,7 +14261,7 @@ function createBmadChecks() {
13820
14261
  const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
13821
14262
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
13822
14263
  if (!install.ok) {
13823
- if (reserved.original !== void 0) writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
14264
+ if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
13824
14265
  return {
13825
14266
  id: finding2.id,
13826
14267
  title: finding2.title,
@@ -13855,7 +14296,7 @@ function createBmadChecks() {
13855
14296
  title: "Supported BMAD CLI projection roots",
13856
14297
  audit: (ctx) => {
13857
14298
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
13858
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
14299
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
13859
14300
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
13860
14301
  const plan = planSkillRoots(ctx.repoRoot);
13861
14302
  const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
@@ -13877,7 +14318,7 @@ function createBmadChecks() {
13877
14318
  },
13878
14319
  migrate: (ctx, finding2) => {
13879
14320
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
13880
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
14321
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
13881
14322
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
13882
14323
  const blocked = attestations.filter((entry) => !entry.safe);
13883
14324
  if (blocked.length) {
@@ -13902,7 +14343,7 @@ function createBmadChecks() {
13902
14343
  };
13903
14344
  }
13904
14345
  const gitignoreChanges = ensureSupportedCliGitignore(ctx);
13905
- const removedRoots = attestations.map((entry) => join11(ctx.repoRoot, entry.name));
14346
+ const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
13906
14347
  if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
13907
14348
  const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
13908
14349
  return {
@@ -13915,7 +14356,7 @@ function createBmadChecks() {
13915
14356
  };
13916
14357
  }
13917
14358
  }
13918
- ].map(withInstallerSkillHandoff);
14359
+ ].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
13919
14360
  }
13920
14361
  function createProjectMomoChecks() {
13921
14362
  return [
@@ -14604,8 +15045,8 @@ var NodeRecipe = class extends Recipe {
14604
15045
  // src/recipes/ProjectRecipe.ts
14605
15046
  init_project();
14606
15047
  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";
15048
+ import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync23, rmSync as rmSync8 } from "node:fs";
15049
+ import { dirname as dirname18, isAbsolute as isAbsolute10, join as join25, relative as relativePath, resolve as resolve19 } from "node:path";
14609
15050
 
14610
15051
  // src/project/ticketScope.ts
14611
15052
  function ticketScope(ticket) {
@@ -14622,8 +15063,8 @@ init_tree_diff();
14622
15063
 
14623
15064
  // src/recipes/NotebookRecipe.ts
14624
15065
  init_project();
14625
- import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
14626
- import { join as join23 } from "node:path";
15066
+ import { existsSync as existsSync17, readFileSync as readFileSync22 } from "node:fs";
15067
+ import { join as join24 } from "node:path";
14627
15068
 
14628
15069
  // src/notebook/checks.ts
14629
15070
  init_config();
@@ -14631,8 +15072,8 @@ init_notes();
14631
15072
  init_output();
14632
15073
  init_state();
14633
15074
  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";
15075
+ import { existsSync as existsSync14, readFileSync as readFileSync20 } from "node:fs";
15076
+ import { join as join20 } from "node:path";
14636
15077
  var NOTEBOOK_RULE_IDS = [
14637
15078
  "notebook.configuration",
14638
15079
  "notebook.binding",
@@ -14649,10 +15090,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
14649
15090
  return { id: check.id, title: check.title, status, summary, changedFiles, details };
14650
15091
  }
14651
15092
  function manifestNotebook(repo) {
14652
- const path = join19(repo, ".project.json");
15093
+ const path = join20(repo, ".project.json");
14653
15094
  if (!existsSync14(path)) return null;
14654
15095
  try {
14655
- const parsed = JSON.parse(readFileSync19(path, "utf8"));
15096
+ const parsed = JSON.parse(readFileSync20(path, "utf8"));
14656
15097
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
14657
15098
  const notebook = parsed.notebook;
14658
15099
  return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
@@ -14881,7 +15322,7 @@ function captureAudit(ctx) {
14881
15322
  function overviewMigrate(ctx, current) {
14882
15323
  if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
14883
15324
  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;
15325
+ const pjanglerEntry = ctx.pjanglerRoot ? join20(ctx.pjanglerRoot, "dist", "index.js") : null;
14885
15326
  const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
14886
15327
  const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
14887
15328
  const spawned = spawnSync8(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
@@ -14933,7 +15374,7 @@ import { resolve as resolve18 } from "node:path";
14933
15374
 
14934
15375
  // src/notebook/journal-key.ts
14935
15376
  init_types();
14936
- import { createHash as createHash11 } from "node:crypto";
15377
+ import { createHash as createHash12 } from "node:crypto";
14937
15378
  function validateJournalPeriodKey(period, key) {
14938
15379
  if (period === "monthly") {
14939
15380
  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 +15393,7 @@ function validateJournalPeriodKey(period, key) {
14952
15393
  }
14953
15394
  function journalNoteOperationId(period, key) {
14954
15395
  validateJournalPeriodKey(period, key);
14955
- const bytes = createHash11("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
15396
+ const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
14956
15397
  bytes[6] = bytes[6] & 15 | 80;
14957
15398
  bytes[8] = bytes[8] & 63 | 128;
14958
15399
  const hex = bytes.toString("hex");
@@ -15108,9 +15549,9 @@ init_notes();
15108
15549
  init_state();
15109
15550
  init_types();
15110
15551
  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";
15552
+ import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
15553
+ 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";
15554
+ 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
15555
  import { fileURLToPath as fileURLToPath3 } from "node:url";
15115
15556
  function captureWorkerEnvironment(source, projectSlug) {
15116
15557
  const result2 = {
@@ -15158,7 +15599,7 @@ function bundledSkillCandidates() {
15158
15599
  const candidates = [];
15159
15600
  let cursor = dirname17(fileURLToPath3(import.meta.url));
15160
15601
  for (let depth = 0; depth < 8; depth++) {
15161
- candidates.push(join22(cursor, "dist", "assets", "project-notebook-skill"), join22(cursor, "assets", "project-notebook-skill"));
15602
+ candidates.push(join23(cursor, "dist", "assets", "project-notebook-skill"), join23(cursor, "assets", "project-notebook-skill"));
15162
15603
  const parent = dirname17(cursor);
15163
15604
  if (parent === cursor) break;
15164
15605
  cursor = parent;
@@ -15176,14 +15617,14 @@ function assertOwnedSkillTree(source) {
15176
15617
  assertNoSymlinkComponents(source);
15177
15618
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
15178
15619
  const walk = (directory) => {
15179
- const directoryStat = lstatSync15(directory);
15620
+ const directoryStat = lstatSync16(directory);
15180
15621
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
15181
15622
  if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
15182
15623
  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 })) {
15624
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
15184
15625
  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);
15626
+ const path = join23(directory, entry.name);
15627
+ const stat = lstatSync16(path);
15187
15628
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
15188
15629
  if (entry.isDirectory()) walk(path);
15189
15630
  else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
@@ -15198,18 +15639,18 @@ function assertOwnedSkillTree(source) {
15198
15639
  function enumerateSkillPayload(source) {
15199
15640
  const result2 = [];
15200
15641
  const walk = (directory) => {
15201
- for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
15642
+ for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
15202
15643
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
15203
- const path = join22(directory, entry.name);
15644
+ const path = join23(directory, entry.name);
15204
15645
  const rel = relative15(source, path).split(sep7).join("/");
15205
15646
  if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
15206
15647
  if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
15207
- const stat = lstatSync15(path);
15648
+ const stat = lstatSync16(path);
15208
15649
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
15209
15650
  if (entry.isDirectory()) walk(path);
15210
15651
  else if (entry.isFile()) {
15211
15652
  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 });
15653
+ result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync21(path)).digest("hex"), mode: expectedMode });
15213
15654
  } else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
15214
15655
  }
15215
15656
  };
@@ -15217,9 +15658,9 @@ function enumerateSkillPayload(source) {
15217
15658
  return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
15218
15659
  }
15219
15660
  function parsePackedManifest(source) {
15220
- const manifestPath = join22(source, "export-manifest.json");
15661
+ const manifestPath = join23(source, "export-manifest.json");
15221
15662
  if (!existsSync15(manifestPath)) return null;
15222
- const value = JSON.parse(readFileSync20(manifestPath, "utf8"));
15663
+ const value = JSON.parse(readFileSync21(manifestPath, "utf8"));
15223
15664
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
15224
15665
  const manifest = value;
15225
15666
  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 +15669,10 @@ function parsePackedManifest(source) {
15228
15669
  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
15670
  if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
15230
15671
  paths.add(entry.path);
15231
- const path = join22(source, ...entry.path.split("/"));
15232
- const stat = lstatSync15(path);
15672
+ const path = join23(source, ...entry.path.split("/"));
15673
+ const stat = lstatSync16(path);
15233
15674
  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");
15675
+ const actual = createHash13("sha256").update(readFileSync21(path)).digest("hex");
15235
15676
  if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
15236
15677
  const actualMode = stat.mode & 511;
15237
15678
  const executable = entry.mode === "0755";
@@ -15243,16 +15684,16 @@ function parsePackedManifest(source) {
15243
15684
  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
15685
  const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15245
15686
  `;
15246
- if (!existsSync15(join22(source, "SHA256SUMS")) || readFileSync20(join22(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
15687
+ if (!existsSync15(join23(source, "SHA256SUMS")) || readFileSync21(join23(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
15247
15688
  return manifest;
15248
15689
  }
15249
15690
  function expectedPackedSkill() {
15250
15691
  for (const candidate of bundledSkillCandidates()) {
15251
- if (!existsSync15(join22(candidate, "export-manifest.json"))) continue;
15692
+ if (!existsSync15(join23(candidate, "export-manifest.json"))) continue;
15252
15693
  assertOwnedSkillTree(candidate);
15253
15694
  const manifest = parsePackedManifest(candidate);
15254
15695
  if (!manifest) continue;
15255
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
15696
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
15256
15697
  return { source: candidate, manifest, digest };
15257
15698
  }
15258
15699
  throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
@@ -15315,13 +15756,13 @@ function verifyProjectNotebookSkillExport(source) {
15315
15756
  function resolveProjectNotebookSkillSource(env2 = process.env) {
15316
15757
  if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
15317
15758
  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");
15759
+ if (!existsSync15(join23(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
15319
15760
  verifyProjectNotebookSkillExport(explicit);
15320
15761
  return explicit;
15321
15762
  }
15322
15763
  if (env2.PJ_SKILLS_REGISTRY_ROOT) {
15323
15764
  const canonical = resolve16(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
15324
- if (existsSync15(join22(canonical, "SKILL.md"))) {
15765
+ if (existsSync15(join23(canonical, "SKILL.md"))) {
15325
15766
  verifyProjectNotebookSkillExport(canonical);
15326
15767
  return canonical;
15327
15768
  }
@@ -15336,7 +15777,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
15336
15777
  function probeCanonicalSkillexRootProjection(skillsRoot, link) {
15337
15778
  let rootLink;
15338
15779
  try {
15339
- rootLink = lstatSync15(skillsRoot);
15780
+ rootLink = lstatSync16(skillsRoot);
15340
15781
  } catch (error) {
15341
15782
  if (error.code === "ENOENT") return { state: "absent" };
15342
15783
  throw error;
@@ -15349,15 +15790,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
15349
15790
  if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
15350
15791
  const globalRoot = realpathSync10(skillsRoot);
15351
15792
  assertNoSymlinkComponents(globalRoot);
15352
- const globalStat = lstatSync15(globalRoot);
15793
+ const globalStat = lstatSync16(globalRoot);
15353
15794
  if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
15354
15795
  if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
15355
15796
  if (globalStat.mode & 3586) throw new Error("root-mode");
15356
15797
  const skillSetsRoot = dirname17(globalRoot);
15357
- if (basename10(globalRoot) !== "global" || basename10(skillSetsRoot) !== "skill-sets") throw new Error("layout");
15798
+ if (basename11(globalRoot) !== "global" || basename11(skillSetsRoot) !== "skill-sets") throw new Error("layout");
15358
15799
  const checkoutRoot = dirname17(skillSetsRoot);
15359
- const expectedSource = join22(checkoutRoot, "all-skills", "project-notebook");
15360
- const linkStat = lstatSync15(link);
15800
+ const expectedSource = join23(checkoutRoot, "all-skills", "project-notebook");
15801
+ const linkStat = lstatSync16(link);
15361
15802
  if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
15362
15803
  if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
15363
15804
  const projectedSource = realpathSync10(link);
@@ -15391,44 +15832,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
15391
15832
  const source = input.source ?? resolveProjectNotebookSkillSource(env2);
15392
15833
  if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
15393
15834
  const manifest = verifyProjectNotebookSkillExport(source);
15394
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
15835
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
15395
15836
  const home = env2.HOME;
15396
15837
  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");
15838
+ const skillsRoot = join23(home, ".agents", "skills");
15839
+ const link = join23(skillsRoot, "project-notebook");
15399
15840
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15400
15841
  if (probe.state === "adopted") return { installed: false, path: link, digest };
15401
15842
  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}`);
15843
+ const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
15844
+ const payload = join23(dataRoot, `${PJANGLER_VERSION}-${digest}`);
15404
15845
  assertNoSymlinkComponents(dirname17(dataRoot), true);
15405
- mkdirSync9(dataRoot, { recursive: true, mode: 448 });
15846
+ mkdirSync10(dataRoot, { recursive: true, mode: 448 });
15406
15847
  assertNoSymlinkComponents(dataRoot);
15407
- const dataStat = lstatSync15(dataRoot);
15848
+ const dataStat = lstatSync16(dataRoot);
15408
15849
  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
15850
  chmodSync6(dataRoot, 448);
15410
15851
  if (!existsSync15(payload)) {
15411
- const staging = join22(dataRoot, `.staging-${randomUUID5()}`);
15412
- mkdirSync9(staging, { recursive: false, mode: 448 });
15852
+ const staging = join23(dataRoot, `.staging-${randomUUID5()}`);
15853
+ mkdirSync10(staging, { recursive: false, mode: 448 });
15413
15854
  try {
15414
15855
  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);
15856
+ const destination = join23(staging, ...entry.path.split("/"));
15857
+ mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
15858
+ copyFileSync3(join23(source, ...entry.path.split("/")), destination);
15418
15859
  const mode = entry.mode === "0755" ? 493 : 420;
15419
15860
  chmodSync6(destination, mode);
15420
- const fd = openSync6(destination, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
15861
+ const fd = openSync7(destination, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
15421
15862
  try {
15422
- const current = fstatSync3(fd);
15863
+ const current = fstatSync4(fd);
15423
15864
  if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
15424
15865
  fsyncSync4(fd);
15425
15866
  } finally {
15426
- closeSync6(fd);
15867
+ closeSync7(fd);
15427
15868
  }
15428
15869
  }
15429
- writeFileSync12(join22(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
15870
+ writeFileSync13(join23(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
15430
15871
  `, { mode: 420, flag: "wx" });
15431
- writeFileSync12(join22(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15872
+ writeFileSync13(join23(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
15432
15873
  `, { mode: 420, flag: "wx" });
15433
15874
  verifyProjectNotebookSkillExport(staging);
15434
15875
  renameSync8(staging, payload);
@@ -15436,17 +15877,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
15436
15877
  if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
15437
15878
  }
15438
15879
  } else {
15439
- const stat = lstatSync15(payload);
15880
+ const stat = lstatSync16(payload);
15440
15881
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
15441
15882
  verifyProjectNotebookSkillExport(payload);
15442
15883
  }
15443
15884
  assertNoSymlinkComponents(skillsRoot, true);
15444
- mkdirSync9(skillsRoot, { recursive: true, mode: 448 });
15885
+ mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
15445
15886
  assertNoSymlinkComponents(skillsRoot);
15446
15887
  let linkExists = existsSync15(link);
15447
15888
  if (!linkExists) {
15448
15889
  try {
15449
- lstatSync15(link);
15890
+ lstatSync16(link);
15450
15891
  linkExists = true;
15451
15892
  } catch {
15452
15893
  }
@@ -15463,7 +15904,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
15463
15904
  repair: REPAIR_COMMAND
15464
15905
  }
15465
15906
  });
15466
- const stat = lstatSync15(link);
15907
+ const stat = lstatSync16(link);
15467
15908
  if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
15468
15909
  const target = realpathSync10(link);
15469
15910
  if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
@@ -15491,15 +15932,15 @@ function collectSupersededPayloads(dataRoot, keep) {
15491
15932
  const removed = [];
15492
15933
  let entries;
15493
15934
  try {
15494
- entries = readdirSync10(dataRoot);
15935
+ entries = readdirSync11(dataRoot);
15495
15936
  } catch {
15496
15937
  return removed;
15497
15938
  }
15498
15939
  for (const name of entries) {
15499
15940
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
15500
- const candidate = join22(dataRoot, name);
15941
+ const candidate = join23(dataRoot, name);
15501
15942
  try {
15502
- const stat = lstatSync15(candidate);
15943
+ const stat = lstatSync16(candidate);
15503
15944
  if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
15504
15945
  if (kept.has(realpathSync10(candidate))) continue;
15505
15946
  rmSync7(candidate, { recursive: true, force: true });
@@ -15513,8 +15954,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
15513
15954
  const env2 = input.env ?? process.env;
15514
15955
  const home = env2.HOME;
15515
15956
  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");
15957
+ const skillsRoot = join23(home, ".agents", "skills");
15958
+ const link = join23(skillsRoot, "project-notebook");
15518
15959
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15519
15960
  if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
15520
15961
  if (probe.state !== "declined") {
@@ -15527,8 +15968,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
15527
15968
  assertOwnedSkillTree(source);
15528
15969
  const present = [];
15529
15970
  const walk = (directory) => {
15530
- for (const entry of readdirSync10(directory, { withFileTypes: true })) {
15531
- const path = join22(directory, entry.name);
15971
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
15972
+ const path = join23(directory, entry.name);
15532
15973
  const rel = relative15(source, path).split(sep7).join("/");
15533
15974
  if (entry.isDirectory()) {
15534
15975
  walk(path);
@@ -15542,11 +15983,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
15542
15983
  walk(source);
15543
15984
  const stale = present.filter((rel) => !wanted.has(rel)).sort();
15544
15985
  const changed = expected.manifest.files.filter((entry) => {
15545
- const path = join22(source, ...entry.path.split("/"));
15986
+ const path = join23(source, ...entry.path.split("/"));
15546
15987
  if (!existsSync15(path)) return true;
15547
- const stat = lstatSync15(path);
15988
+ const stat = lstatSync16(path);
15548
15989
  if (!stat.isFile()) return true;
15549
- if (createHash12("sha256").update(readFileSync20(path)).digest("hex") !== entry.sha256) return true;
15990
+ if (createHash13("sha256").update(readFileSync21(path)).digest("hex") !== entry.sha256) return true;
15550
15991
  const mode = stat.mode & 511;
15551
15992
  const executable = entry.mode === "0755";
15552
15993
  return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
@@ -15561,15 +16002,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
15561
16002
  summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
15562
16003
  source,
15563
16004
  drift: probe.block.details,
15564
- changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
16005
+ changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
15565
16006
  };
15566
16007
  }
15567
- for (const rel of stale) rmSync7(join22(source, ...rel.split("/")), { force: true });
16008
+ for (const rel of stale) rmSync7(join23(source, ...rel.split("/")), { force: true });
15568
16009
  for (const rel of changed) {
15569
- const destination = join22(source, ...rel.split("/"));
16010
+ const destination = join23(source, ...rel.split("/"));
15570
16011
  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);
16012
+ mkdirSync10(dirname17(destination), { recursive: true, mode: 493 });
16013
+ copyFileSync3(join23(expected.source, ...rel.split("/")), destination);
15573
16014
  chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
15574
16015
  }
15575
16016
  verifyProjectNotebookSkillExport(source);
@@ -15578,21 +16019,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
15578
16019
  summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
15579
16020
  source,
15580
16021
  drift: probe.block.details,
15581
- changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
16022
+ changed_files: affected.map((rel) => join23(source, ...rel.split("/")))
15582
16023
  };
15583
16024
  }
15584
16025
  function inspectProjectNotebookIntegration(env2 = process.env) {
15585
16026
  const home = env2.HOME;
15586
16027
  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");
16028
+ const skillsRoot = join23(home, ".agents", "skills");
16029
+ const link = join23(skillsRoot, "project-notebook");
15589
16030
  try {
15590
16031
  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}`);
16032
+ const dataRoot = resolve16(env2.XDG_DATA_HOME || join23(home, ".local", "share"), "pjangler", "skills", "project-notebook");
16033
+ const expectedPayload = join23(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
15593
16034
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
15594
16035
  if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
15595
- const stat = lstatSync15(link);
16036
+ const stat = lstatSync16(link);
15596
16037
  if (!stat.isSymbolicLink()) {
15597
16038
  return {
15598
16039
  skill_installed: false,
@@ -15629,8 +16070,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
15629
16070
  }
15630
16071
  }
15631
16072
  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];
16073
+ const script = join23(source, "scripts", "project-hooks.py");
16074
+ const args = [script, command, "--master", join23(source, "hooks", "hooks.master.json"), "--fragment", join23(source, "hooks", "claude.settings.json"), "--target", input.target];
15634
16075
  if (command === "check") args.push("--json");
15635
16076
  else if (input.stateHome) args.push("--state-home", input.stateHome);
15636
16077
  return args;
@@ -15652,7 +16093,7 @@ function checkProjectNotebookHooks(input = {}) {
15652
16093
  verifyProjectNotebookSkillExport(source);
15653
16094
  const home = env2.HOME;
15654
16095
  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"));
16096
+ const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
15656
16097
  const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
15657
16098
  if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
15658
16099
  try {
@@ -15670,14 +16111,14 @@ function installProjectNotebookIntegration(input = {}) {
15670
16111
  const source = realpathSync10(skill.path);
15671
16112
  const home = env2.HOME;
15672
16113
  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"));
16114
+ const target = resolve16(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join23(home, ".claude", "settings.json"));
16115
+ const stateHome = resolve16(env2.XDG_STATE_HOME || join23(home, ".local", "state"));
15675
16116
  const result2 = spawnSync11("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
15676
16117
  if (result2.status !== 0) {
15677
16118
  if (skill.installed) {
15678
16119
  try {
15679
- const stat = lstatSync15(skill.path);
15680
- if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync8(skill.path);
16120
+ const stat = lstatSync16(skill.path);
16121
+ if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
15681
16122
  } catch {
15682
16123
  }
15683
16124
  }
@@ -15722,10 +16163,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
15722
16163
  const root = parse3(absolute).root;
15723
16164
  let cursor = root;
15724
16165
  for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
15725
- cursor = join22(cursor, component);
16166
+ cursor = join23(cursor, component);
15726
16167
  let stat;
15727
16168
  try {
15728
- stat = lstatSync15(cursor);
16169
+ stat = lstatSync16(cursor);
15729
16170
  } catch (error) {
15730
16171
  if (allowMissing && error.code === "ENOENT") return;
15731
16172
  throw error;
@@ -15744,18 +16185,18 @@ function readHookPayload(input) {
15744
16185
  if (rel === ".." || rel.startsWith(`..${sep7}`) || rel.startsWith(sep7)) throw new NotebookError("INVALID_INPUT", "Hook payload file is outside Notebook XDG state");
15745
16186
  assertNoSymlinkComponents(root);
15746
16187
  assertNoSymlinkComponents(path);
15747
- const before = lstatSync15(path);
16188
+ const before = lstatSync16(path);
15748
16189
  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
16190
  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
16191
  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));
16192
+ const fd = openSync7(path, constants6.O_RDONLY | (constants6.O_NOFOLLOW ?? 0));
15752
16193
  try {
15753
- const opened = fstatSync3(fd);
16194
+ const opened = fstatSync4(fd);
15754
16195
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", "Hook payload file changed while opening");
15755
16196
  const value = readBoundedHookStdin(input.maxBytes, fd);
15756
16197
  return { payload: parsePayload(value, input.maxBytes), bytes: Buffer.byteLength(value, "utf8") };
15757
16198
  } finally {
15758
- closeSync6(fd);
16199
+ closeSync7(fd);
15759
16200
  }
15760
16201
  }
15761
16202
  var ALLOWED_CLIENT_NAMES = /* @__PURE__ */ new Set([
@@ -16482,17 +16923,17 @@ init_remote_mutation_journal();
16482
16923
  function integrationEnvironment(module, ctx) {
16483
16924
  const env2 = { ...module.environment, HOME: ctx.homeDir };
16484
16925
  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");
16926
+ env2.XDG_DATA_HOME = join24(ctx.homeDir, ".local", "share");
16927
+ env2.XDG_STATE_HOME = join24(ctx.homeDir, ".local", "state");
16928
+ env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join24(ctx.homeDir, ".claude", "settings.json");
16488
16929
  }
16489
16930
  return env2;
16490
16931
  }
16491
16932
  function resolvedForPlan(plan) {
16492
- const manifestPath = join23(plan.project.repo_path, ".project.json");
16933
+ const manifestPath = join24(plan.project.repo_path, ".project.json");
16493
16934
  let manifest = plan.manifest;
16494
16935
  if (existsSync17(manifestPath)) {
16495
- const parsed = JSON.parse(readFileSync21(manifestPath, "utf8"));
16936
+ const parsed = JSON.parse(readFileSync22(manifestPath, "utf8"));
16496
16937
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
16497
16938
  manifest = parsed;
16498
16939
  }
@@ -16669,7 +17110,7 @@ function unsafeToRemove(targetDir) {
16669
17110
  while (!seen.has(cursor)) {
16670
17111
  seen.add(cursor);
16671
17112
  try {
16672
- const stat = lstatSync16(cursor);
17113
+ const stat = lstatSync17(cursor);
16673
17114
  if (stat.isSymbolicLink()) {
16674
17115
  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
17116
  }
@@ -16697,12 +17138,12 @@ function publicMigration(report) {
16697
17138
  };
16698
17139
  }
16699
17140
  function hasGitRepository(runtime, targetDir) {
16700
- if (!existsSync18(join24(targetDir, ".git"))) return false;
17141
+ if (!existsSync18(join25(targetDir, ".git"))) return false;
16701
17142
  return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
16702
17143
  }
16703
17144
  function refreshPlanFromCanonicalManifest(plan) {
16704
- const manifestPath = join24(plan.project.repo_path, ".project.json");
16705
- const manifest = JSON.parse(readFileSync22(manifestPath, "utf8"));
17145
+ const manifestPath = join25(plan.project.repo_path, ".project.json");
17146
+ const manifest = JSON.parse(readFileSync23(manifestPath, "utf8"));
16706
17147
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
16707
17148
  throw new Error(`${manifestPath} must contain a JSON object`);
16708
17149
  }
@@ -16933,7 +17374,7 @@ var ProjectRecipe = class extends Recipe {
16933
17374
  if (hasGitRepository(this.runtime, targetDir)) {
16934
17375
  phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
16935
17376
  } else {
16936
- const gitPath = join24(targetDir, ".git");
17377
+ const gitPath = join25(targetDir, ".git");
16937
17378
  for (const { args, label, options } of [
16938
17379
  { args: ["init", "--initial-branch=main"], label: "git init" },
16939
17380
  { args: ["add", "-A"], label: "git add" },
@@ -17144,12 +17585,12 @@ var ProjectRecipe = class extends Recipe {
17144
17585
  };
17145
17586
 
17146
17587
  // src/recipes/registry.ts
17147
- import { readFileSync as readFileSync23 } from "node:fs";
17148
- import { join as join25 } from "node:path";
17588
+ import { readFileSync as readFileSync24 } from "node:fs";
17589
+ import { join as join26 } from "node:path";
17149
17590
  function projectWaivers(repoRoot) {
17150
17591
  let waive;
17151
17592
  try {
17152
- waive = JSON.parse(readFileSync23(join25(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
17593
+ waive = JSON.parse(readFileSync24(join26(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
17153
17594
  } catch {
17154
17595
  return /* @__PURE__ */ new Map();
17155
17596
  }
@@ -17490,8 +17931,8 @@ var recipeRegistry = new RecipeRegistry([
17490
17931
 
17491
17932
  // src/commands/AgentHooksCommands.ts
17492
17933
  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";
17934
+ import { join as join27, dirname as dirname19 } from "node:path";
17935
+ import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync14 } from "node:fs";
17495
17936
  import { fileURLToPath as fileURLToPath4 } from "node:url";
17496
17937
  init_project();
17497
17938
  import { parse as parseToml4 } from "smol-toml";
@@ -17504,16 +17945,16 @@ function resolveTemplateRoot() {
17504
17945
  try {
17505
17946
  let dir = dirname19(fileURLToPath4(import.meta.url));
17506
17947
  for (let i = 0; i < 8; i++) {
17507
- candidates.push(join26(dir, "templates", "commonproject", "template"));
17948
+ candidates.push(join27(dir, "templates", "commonproject", "template"));
17508
17949
  const parent = dirname19(dir);
17509
17950
  if (parent === dir) break;
17510
17951
  dir = parent;
17511
17952
  }
17512
17953
  } catch {
17513
17954
  }
17514
- candidates.push(join26(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
17955
+ candidates.push(join27(homedir10(), "code", "pjangler", "templates", "commonproject", "template"));
17515
17956
  for (const c of candidates) {
17516
- if (existsSync19(join26(c, ".agents", "hooks", "hooks.master.json"))) return c;
17957
+ if (existsSync19(join27(c, ".agents", "hooks", "hooks.master.json"))) return c;
17517
17958
  }
17518
17959
  throw new Error(
17519
17960
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -17539,15 +17980,15 @@ var CopyAgentHooksTree = class extends Command {
17539
17980
  const created = [];
17540
17981
  const skipped = [];
17541
17982
  for (const { rel, dir } of items) {
17542
- const src = join26(templateRoot, rel);
17543
- const dest = join26(this.context.targetDir, rel);
17983
+ const src = join27(templateRoot, rel);
17984
+ const dest = join27(this.context.targetDir, rel);
17544
17985
  if (!existsSync19(src)) continue;
17545
17986
  if (existsSync19(dest) && !this.context.force) {
17546
17987
  skipped.push(rel);
17547
17988
  continue;
17548
17989
  }
17549
17990
  if (!this.context.dryRun) {
17550
- mkdirSync10(dirname19(dest), { recursive: true });
17991
+ mkdirSync11(dirname19(dest), { recursive: true });
17551
17992
  cpSync(src, dest, { recursive: dir, force: true });
17552
17993
  }
17553
17994
  created.push(rel);
@@ -17568,14 +18009,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
17568
18009
  if (!resolveAgentHooksLayer2()) {
17569
18010
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
17570
18011
  }
17571
- const misePath = join26(this.context.targetDir, "mise.toml");
18012
+ const misePath = join27(this.context.targetDir, "mise.toml");
17572
18013
  if (!existsSync19(misePath)) {
17573
18014
  return {
17574
18015
  success: false,
17575
18016
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
17576
18017
  };
17577
18018
  }
17578
- let content = readFileSync24(misePath, "utf8");
18019
+ let content = readFileSync25(misePath, "utf8");
17579
18020
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
17580
18021
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
17581
18022
  }
@@ -17659,7 +18100,7 @@ ${leaveBlock}`);
17659
18100
  if (!verdict.ok) {
17660
18101
  return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
17661
18102
  }
17662
- if (!this.context.dryRun) writeFileSync13(misePath, content);
18103
+ if (!this.context.dryRun) writeFileSync14(misePath, content);
17663
18104
  if (wiredHooks) {
17664
18105
  return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
17665
18106
  }
@@ -17790,7 +18231,7 @@ if __name__ == "__main__":
17790
18231
 
17791
18232
  // src/commands/AddMiseCodegraphScript.ts
17792
18233
  import { chmodSync as chmodSync7 } from "fs";
17793
- import { join as join27 } from "path";
18234
+ import { join as join28 } from "path";
17794
18235
  var AddMiseCodegraphScript = class extends Command {
17795
18236
  async invoke() {
17796
18237
  const filePath = ".mise/scripts/codegraph.sh";
@@ -17844,7 +18285,7 @@ fi
17844
18285
  `;
17845
18286
  this.writeFile(filePath, content);
17846
18287
  if (!this.context.dryRun) {
17847
- chmodSync7(join27(this.context.targetDir, filePath), 493);
18288
+ chmodSync7(join28(this.context.targetDir, filePath), 493);
17848
18289
  }
17849
18290
  return {
17850
18291
  success: true,
@@ -17882,12 +18323,12 @@ SECRET_KEY=""
17882
18323
  // src/parity/index.ts
17883
18324
  import { existsSync as existsSync20 } from "node:fs";
17884
18325
  import { homedir as homedir11 } from "node:os";
17885
- import { dirname as dirname20, join as join28, resolve as resolve20 } from "node:path";
18326
+ import { dirname as dirname20, join as join29, resolve as resolve20 } from "node:path";
17886
18327
  import { fileURLToPath as fileURLToPath5 } from "node:url";
17887
18328
  function resolvePjanglerRoot2() {
17888
18329
  let dir = dirname20(fileURLToPath5(import.meta.url));
17889
18330
  while (dir !== dirname20(dir)) {
17890
- if (existsSync20(join28(dir, "package.json")) && existsSync20(join28(dir, "templates", "commonproject", "copier.yml"))) return dir;
18331
+ if (existsSync20(join29(dir, "package.json")) && existsSync20(join29(dir, "templates", "commonproject", "copier.yml"))) return dir;
17891
18332
  dir = dirname20(dir);
17892
18333
  }
17893
18334
  return resolve20(process.cwd());
@@ -18079,13 +18520,13 @@ init_project();
18079
18520
  // src/project/info.ts
18080
18521
  init_boardUrl();
18081
18522
  init_project();
18082
- import { readFileSync as readFileSync25 } from "node:fs";
18083
- import { join as join29, resolve as resolve21 } from "node:path";
18523
+ import { readFileSync as readFileSync26 } from "node:fs";
18524
+ import { join as join30, resolve as resolve21 } from "node:path";
18084
18525
  function readManifest2(root) {
18085
- const path = join29(root, ".project.json");
18526
+ const path = join30(root, ".project.json");
18086
18527
  let manifest;
18087
18528
  try {
18088
- const value = JSON.parse(readFileSync25(path, "utf8"));
18529
+ const value = JSON.parse(readFileSync26(path, "utf8"));
18089
18530
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
18090
18531
  manifest = value;
18091
18532
  } catch (error) {
@@ -18126,7 +18567,7 @@ function readProjectInfo(input = {}) {
18126
18567
  ok: true,
18127
18568
  project_id: projectId,
18128
18569
  repo_path: resolve21(root),
18129
- manifest_path: join29(resolve21(root), ".project.json"),
18570
+ manifest_path: join30(resolve21(root), ".project.json"),
18130
18571
  manifest,
18131
18572
  registry: { location, status: "indexed" }
18132
18573
  };
@@ -18175,20 +18616,20 @@ init_project();
18175
18616
  init_version();
18176
18617
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
18177
18618
  import {
18178
- closeSync as closeSync7,
18619
+ closeSync as closeSync8,
18179
18620
  existsSync as existsSync21,
18180
- fchmodSync as fchmodSync4,
18621
+ fchmodSync as fchmodSync5,
18181
18622
  fsyncSync as fsyncSync5,
18182
- lstatSync as lstatSync17,
18183
- mkdirSync as mkdirSync11,
18184
- openSync as openSync7,
18185
- readFileSync as readFileSync26,
18623
+ lstatSync as lstatSync18,
18624
+ mkdirSync as mkdirSync12,
18625
+ openSync as openSync8,
18626
+ readFileSync as readFileSync27,
18186
18627
  renameSync as renameSync9,
18187
- unlinkSync as unlinkSync9,
18188
- writeFileSync as writeFileSync14
18628
+ unlinkSync as unlinkSync10,
18629
+ writeFileSync as writeFileSync15
18189
18630
  } from "node:fs";
18190
18631
  import { homedir as homedir12 } from "node:os";
18191
- import { dirname as dirname21, join as join30, resolve as resolve22 } from "node:path";
18632
+ import { dirname as dirname21, join as join31, resolve as resolve22 } from "node:path";
18192
18633
  import YAML7 from "yaml";
18193
18634
  var DEAD_AGENT_IDS = ["coachingagentframework-pm"];
18194
18635
  function isRecord6(value) {
@@ -18196,7 +18637,7 @@ function isRecord6(value) {
18196
18637
  }
18197
18638
  function hermesAgentsRegistryPath(env2 = process.env, home = homedir12()) {
18198
18639
  const override = env2.HERMES_AGENTS_REGISTRY?.trim();
18199
- return override || join30(home, ".hermes", "agents-registry.yaml");
18640
+ return override || join31(home, ".hermes", "agents-registry.yaml");
18200
18641
  }
18201
18642
  function planeApiKeyFor(workspace, env2, home) {
18202
18643
  return resolvePlaneApiKey(workspace, env2, home) ?? resolvePlaneApiKey(DEFAULT_PLANE_WORKSPACE, env2, home);
@@ -18207,7 +18648,7 @@ async function fetchPlaneBoards(workspace, env2 = process.env, home = homedir12(
18207
18648
  const apiKey = planeApiKeyFor(workspace, env2, home);
18208
18649
  if (!apiKey) {
18209
18650
  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")}`
18651
+ `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
18652
  );
18212
18653
  }
18213
18654
  const base = planeBase(env2, home);
@@ -18256,7 +18697,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
18256
18697
  const apiKey = planeApiKeyFor(workspace, env2, home);
18257
18698
  if (!apiKey) {
18258
18699
  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")}`
18700
+ `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
18701
  );
18261
18702
  }
18262
18703
  const base = planeBase(env2, home);
@@ -18290,7 +18731,7 @@ async function patchPlaneBoardIdentifier(workspace, boardId, identifier, env2 =
18290
18731
  }
18291
18732
  }
18292
18733
  function trelloCredential(name, env2, home) {
18293
- const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join30(home, ".hermes", "fleet.env");
18734
+ const fleetEnv = env2.HERMES_FLEET_ENV?.trim() || join31(home, ".hermes", "fleet.env");
18294
18735
  const raw = env2[name]?.trim() || dotenvValue(fleetEnv, name)?.trim();
18295
18736
  return raw ? resolveSecretValue(raw) : void 0;
18296
18737
  }
@@ -18328,7 +18769,7 @@ async function fetchTrelloBoard(boardId, env2 = process.env, home = homedir12())
18328
18769
  };
18329
18770
  }
18330
18771
  function readHermesAgentBoards(path) {
18331
- const document = YAML7.parseDocument(readFileSync26(path, "utf8"));
18772
+ const document = YAML7.parseDocument(readFileSync27(path, "utf8"));
18332
18773
  if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
18333
18774
  const parsed = document.toJS();
18334
18775
  const agents = isRecord6(parsed) && isRecord6(parsed.agents) ? parsed.agents : {};
@@ -18359,11 +18800,11 @@ function matchRegistrySlug(agent, registry) {
18359
18800
  }
18360
18801
  function readManifestBoard(repoPath) {
18361
18802
  if (!repoPath) return void 0;
18362
- const path = join30(repoPath, ".project.json");
18803
+ const path = join31(repoPath, ".project.json");
18363
18804
  if (!existsSync21(path)) return void 0;
18364
18805
  let parsed;
18365
18806
  try {
18366
- parsed = JSON.parse(readFileSync26(path, "utf8"));
18807
+ parsed = JSON.parse(readFileSync27(path, "utf8"));
18367
18808
  } catch {
18368
18809
  return void 0;
18369
18810
  }
@@ -18382,32 +18823,32 @@ function yamlStyleOf(original) {
18382
18823
  return { indentSeq: !/\n([ \t]*)[^\s#][^\n]*:[ \t]*\n\1- /.test(original), lineWidth: 0 };
18383
18824
  }
18384
18825
  function atomicWrite(path, text2) {
18385
- const mode = existsSync21(path) ? lstatSync17(path).mode & 511 : 384;
18386
- mkdirSync11(dirname21(path), { recursive: true });
18826
+ const mode = existsSync21(path) ? lstatSync18(path).mode & 511 : 384;
18827
+ mkdirSync12(dirname21(path), { recursive: true });
18387
18828
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
18388
18829
  let fd;
18389
18830
  try {
18390
- fd = openSync7(temp, "wx", mode);
18391
- writeFileSync14(fd, text2, "utf8");
18392
- fchmodSync4(fd, mode);
18831
+ fd = openSync8(temp, "wx", mode);
18832
+ writeFileSync15(fd, text2, "utf8");
18833
+ fchmodSync5(fd, mode);
18393
18834
  fsyncSync5(fd);
18394
- closeSync7(fd);
18835
+ closeSync8(fd);
18395
18836
  fd = void 0;
18396
18837
  renameSync9(temp, path);
18397
18838
  } catch (error) {
18398
18839
  if (fd !== void 0) try {
18399
- closeSync7(fd);
18840
+ closeSync8(fd);
18400
18841
  } catch {
18401
18842
  }
18402
18843
  try {
18403
- unlinkSync9(temp);
18844
+ unlinkSync10(temp);
18404
18845
  } catch {
18405
18846
  }
18406
18847
  throw error;
18407
18848
  }
18408
18849
  }
18409
18850
  function applyHermesIdentifiers(path, identifiers, deletions) {
18410
- const original = readFileSync26(path, "utf8");
18851
+ const original = readFileSync27(path, "utf8");
18411
18852
  const document = YAML7.parseDocument(original);
18412
18853
  if (document.errors.length) throw new Error(`Hermes agents registry YAML is invalid: ${path}`);
18413
18854
  const before = document.toJS();
@@ -18863,11 +19304,11 @@ function formatIdentityReport(report) {
18863
19304
  return lines.join("\n");
18864
19305
  }
18865
19306
  function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
18866
- const path = join30(repoPath, ".project.json");
19307
+ const path = join31(repoPath, ".project.json");
18867
19308
  if (!existsSync21(path)) return void 0;
18868
19309
  let existing = {};
18869
19310
  try {
18870
- const parsed = JSON.parse(readFileSync26(path, "utf8"));
19311
+ const parsed = JSON.parse(readFileSync27(path, "utf8"));
18871
19312
  if (isRecord6(parsed)) existing = parsed;
18872
19313
  } catch {
18873
19314
  throw new BoardError(`${path} is not readable JSON; fix it before linking a board`);
@@ -18892,7 +19333,7 @@ function writeManifestTicketProvider(repoPath, provider, expectedProvider) {
18892
19333
  };
18893
19334
  const text2 = `${JSON.stringify(next, null, 2)}
18894
19335
  `;
18895
- if (readFileSync26(path, "utf8") === text2) return void 0;
19336
+ if (readFileSync27(path, "utf8") === text2) return void 0;
18896
19337
  atomicWrite(path, text2);
18897
19338
  return path;
18898
19339
  }
@@ -18904,8 +19345,8 @@ async function linkProjectBoard(options) {
18904
19345
  const project = getProject(registry, options.slug);
18905
19346
  const boardId = options.boardId.trim();
18906
19347
  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;
19348
+ const manifestFile = join31(project.repo_path, ".project.json");
19349
+ const expectedProvider = !isRegistryServiceLocation(registryPath) && existsSync21(manifestFile) ? JSON.parse(readFileSync27(manifestFile, "utf8")).ticket_provider : void 0;
18909
19350
  const current = project.ticket_provider;
18910
19351
  const type = (current.type || "plane").trim().toLowerCase();
18911
19352
  const workspace = (options.workspace ?? current.workspace ?? DEFAULT_PLANE_WORKSPACE).trim();
@@ -18956,10 +19397,10 @@ async function linkProjectBoard(options) {
18956
19397
  if (apply) {
18957
19398
  registry.projects[project.slug] = { ...project, ticket_provider: { ...current, ...block }, updated_at: now };
18958
19399
  if (isRegistryServiceLocation(registryPath)) {
18959
- const beforeManifest = existsSync21(manifestFile) ? readFileSync26(manifestFile, "utf8") : null;
19400
+ const beforeManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
18960
19401
  saveProjectRegistry(registry, registryPath);
18961
19402
  manifestPath = manifestFile;
18962
- const afterManifest = existsSync21(manifestFile) ? readFileSync26(manifestFile, "utf8") : null;
19403
+ const afterManifest = existsSync21(manifestFile) ? readFileSync27(manifestFile, "utf8") : null;
18963
19404
  if (beforeManifest !== afterManifest) changedFiles.push(manifestFile);
18964
19405
  } else {
18965
19406
  manifestPath = writeManifestTicketProvider(project.repo_path, block, expectedProvider);
@@ -18968,7 +19409,7 @@ async function linkProjectBoard(options) {
18968
19409
  changedFiles.push(registryPath);
18969
19410
  }
18970
19411
  } else {
18971
- manifestPath = existsSync21(join30(project.repo_path, ".project.json")) ? join30(project.repo_path, ".project.json") : void 0;
19412
+ manifestPath = existsSync21(join31(project.repo_path, ".project.json")) ? join31(project.repo_path, ".project.json") : void 0;
18972
19413
  }
18973
19414
  return {
18974
19415
  ok: true,
@@ -18992,9 +19433,9 @@ async function linkProjectBoard(options) {
18992
19433
  // src/project/rename.ts
18993
19434
  init_boardUrl();
18994
19435
  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";
19436
+ import { existsSync as existsSync22, readdirSync as readdirSync12, readFileSync as readFileSync28, realpathSync as realpathSync11, statSync as statSync6 } from "node:fs";
18996
19437
  import { homedir as homedir13 } from "node:os";
18997
- import { isAbsolute as isAbsolute11, join as join31, relative as relative16 } from "node:path";
19438
+ import { isAbsolute as isAbsolute11, join as join32, relative as relative16 } from "node:path";
18998
19439
  import YAML8 from "yaml";
18999
19440
  init_project();
19000
19441
  init_registryClient();
@@ -19015,7 +19456,7 @@ function message(error) {
19015
19456
  function readJsonObject(path) {
19016
19457
  let parsed;
19017
19458
  try {
19018
- parsed = JSON.parse(readFileSync27(path, "utf8"));
19459
+ parsed = JSON.parse(readFileSync28(path, "utf8"));
19019
19460
  } catch {
19020
19461
  throw new RenameError("manifest_invalid", `${path} is not readable JSON; fix it before renaming`);
19021
19462
  }
@@ -19068,7 +19509,7 @@ async function renameProject(options) {
19068
19509
  let from;
19069
19510
  if (existing && oldInput) {
19070
19511
  try {
19071
- const candidate = readJsonObject(join31(existing.repo_path, ".project.json")).renamed_from;
19512
+ const candidate = readJsonObject(join32(existing.repo_path, ".project.json")).renamed_from;
19072
19513
  if (isRecord7(candidate)) from = candidate;
19073
19514
  } catch {
19074
19515
  }
@@ -19087,7 +19528,7 @@ async function renameProject(options) {
19087
19528
  const isPlane = type === "plane";
19088
19529
  const workspace = isPlane ? String(provider.workspace ?? "").trim() || DEFAULT_PLANE_WORKSPACE : String(provider.workspace ?? "").trim();
19089
19530
  const repoPath = record3.repo_path;
19090
- const manifestPath = join31(repoPath, ".project.json");
19531
+ const manifestPath = join32(repoPath, ".project.json");
19091
19532
  const manifestExists = existsSync22(manifestPath);
19092
19533
  if (service && !manifestExists) {
19093
19534
  throw new RenameError("manifest_missing", `${manifestPath} is missing; the registry service indexes manifests, so there is nothing to rename`);
@@ -19232,7 +19673,7 @@ async function renameProject(options) {
19232
19673
  return { status: "done", detail, files: service ? [] : [registryPath] };
19233
19674
  };
19234
19675
  const roleStep = async (doApply) => {
19235
- const hermesDir = join31(repoPath, "agents", "hermes");
19676
+ const hermesDir = join32(repoPath, "agents", "hermes");
19236
19677
  if (!existsSync22(hermesDir)) return { status: "skipped", detail: "no agents/hermes directory" };
19237
19678
  if (!isPlane) return { status: "skipped", detail: "role.yaml plane.identifier is a Plane field" };
19238
19679
  const files = [];
@@ -19240,16 +19681,16 @@ async function renameProject(options) {
19240
19681
  let settled = 0;
19241
19682
  let entries;
19242
19683
  try {
19243
- entries = readdirSync11(hermesDir, { withFileTypes: true });
19684
+ entries = readdirSync12(hermesDir, { withFileTypes: true });
19244
19685
  } catch (error) {
19245
19686
  return { status: "failed", detail: `${hermesDir}: ${message(error)}` };
19246
19687
  }
19247
19688
  for (const entry of entries) {
19248
19689
  if (!entry.isDirectory()) continue;
19249
- const path = join31(hermesDir, entry.name, "role.yaml");
19690
+ const path = join32(hermesDir, entry.name, "role.yaml");
19250
19691
  if (!existsSync22(path)) continue;
19251
19692
  try {
19252
- const original = readFileSync27(path, "utf8");
19693
+ const original = readFileSync28(path, "utf8");
19253
19694
  const document = YAML8.parseDocument(original);
19254
19695
  if (document.errors.length) throw new Error("YAML is invalid");
19255
19696
  const node = document.getIn(["plane", "identifier"], true);
@@ -19307,7 +19748,7 @@ async function renameProject(options) {
19307
19748
  }
19308
19749
  };
19309
19750
  const agentsMdStep = async (doApply) => {
19310
- const link = join31(repoPath, "AGENTS.md");
19751
+ const link = join32(repoPath, "AGENTS.md");
19311
19752
  if (!existsSync22(link)) return { status: "skipped", detail: "no AGENTS.md" };
19312
19753
  try {
19313
19754
  const path = realpathSync11(link);
@@ -19315,7 +19756,7 @@ async function renameProject(options) {
19315
19756
  if (inside.startsWith("..") || isAbsolute11(inside)) {
19316
19757
  return { status: "skipped", detail: `AGENTS.md resolves outside the repo (${path}); left untouched` };
19317
19758
  }
19318
- const original = readFileSync27(path, "utf8");
19759
+ const original = readFileSync28(path, "utf8");
19319
19760
  const lines = original.split("\n");
19320
19761
  let boardLines = 0;
19321
19762
  let replaced = 0;
@@ -19345,8 +19786,8 @@ async function renameProject(options) {
19345
19786
  }
19346
19787
  };
19347
19788
  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");
19789
+ const configDir = env2.PILOT_CONFIG_DIR?.trim() || join32(env2.XDG_CONFIG_HOME?.trim() || join32(home, ".config"), "pilot");
19790
+ const path = join32(configDir, "harvest", "project-hints.json");
19350
19791
  if (!existsSync22(path)) return { status: "skipped", detail: `no harvest hints at ${path}` };
19351
19792
  try {
19352
19793
  const hints = readJsonObject(path);
@@ -19380,7 +19821,7 @@ async function renameProject(options) {
19380
19821
  if (oldInput === newId && steps.every((step) => step.status === "already" || step.status === "skipped")) {
19381
19822
  return { status: "already", detail: "nothing changed; projection not re-synced" };
19382
19823
  }
19383
- const cwd = join31(home, "code", "33GOD", "candystore");
19824
+ const cwd = join32(home, "code", "33GOD", "candystore");
19384
19825
  try {
19385
19826
  if (!statSync6(cwd).isDirectory()) throw new Error("not a directory");
19386
19827
  } catch {
@@ -19475,7 +19916,7 @@ init_boardQuery();
19475
19916
  // src/describe/activity.ts
19476
19917
  import { spawn as spawn3, spawnSync as spawnSync16 } from "node:child_process";
19477
19918
  import { statSync as statSync7 } from "node:fs";
19478
- import { join as join32 } from "node:path";
19919
+ import { join as join33 } from "node:path";
19479
19920
  var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
19480
19921
  var MAX_DIRTY_STATS = 500;
19481
19922
  var GIT_TIMEOUT_MS = 5e3;
@@ -19640,7 +20081,7 @@ function uncommittedSource(repo, paths) {
19640
20081
  let newest = 0;
19641
20082
  for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
19642
20083
  try {
19643
- const mtime = Math.floor(statSync7(join32(repo, path)).mtimeMs / 1e3);
20084
+ const mtime = Math.floor(statSync7(join33(repo, path)).mtimeMs / 1e3);
19644
20085
  if (mtime > newest) newest = mtime;
19645
20086
  } catch {
19646
20087
  }
@@ -19838,8 +20279,8 @@ function openUrl(url, env2 = process.env, platform = process.platform) {
19838
20279
  }
19839
20280
 
19840
20281
  // 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";
20282
+ import { existsSync as existsSync23, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync8 } from "node:fs";
20283
+ import { join as join34, resolve as resolve23 } from "node:path";
19843
20284
  init_project();
19844
20285
  init_config();
19845
20286
  init_state();
@@ -19895,7 +20336,7 @@ var CONFIG_FILES = [
19895
20336
  ];
19896
20337
  function readJson(path) {
19897
20338
  try {
19898
- const parsed = JSON.parse(readFileSync28(path, "utf8"));
20339
+ const parsed = JSON.parse(readFileSync29(path, "utf8"));
19899
20340
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
19900
20341
  } catch {
19901
20342
  return void 0;
@@ -19917,23 +20358,23 @@ function describeType(repo) {
19917
20358
  const evidence = [];
19918
20359
  const note = (signal, file) => evidence.push(`${signal} (${file})`);
19919
20360
  for (const marker of LANGUAGE_MARKERS) {
19920
- if (!existsSync23(join33(repo, marker.file))) continue;
20361
+ if (!existsSync23(join34(repo, marker.file))) continue;
19921
20362
  if (!languages.includes(marker.language)) {
19922
20363
  languages.push(marker.language);
19923
20364
  note(marker.language, marker.file);
19924
20365
  }
19925
20366
  }
19926
20367
  try {
19927
- const dotnet = readdirSync12(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
20368
+ const dotnet = readdirSync13(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
19928
20369
  if (dotnet && !languages.includes("dotnet")) {
19929
20370
  languages.push("dotnet");
19930
20371
  note("dotnet", dotnet);
19931
20372
  }
19932
20373
  } catch {
19933
20374
  }
19934
- const pkg = readJson(join33(repo, "package.json"));
20375
+ const pkg = readJson(join34(repo, "package.json"));
19935
20376
  if (pkg) {
19936
- if (existsSync23(join33(repo, "tsconfig.json"))) {
20377
+ if (existsSync23(join34(repo, "tsconfig.json"))) {
19937
20378
  const index = languages.indexOf("javascript");
19938
20379
  if (index >= 0) languages.splice(index, 1);
19939
20380
  if (!languages.includes("typescript")) {
@@ -19967,14 +20408,14 @@ function describeType(repo) {
19967
20408
  ["hermes-fleet-host", "agents/hermes"]
19968
20409
  ];
19969
20410
  for (const [role, marker] of roleMarkers) {
19970
- if (!existsSync23(join33(repo, marker))) continue;
20411
+ if (!existsSync23(join34(repo, marker))) continue;
19971
20412
  roles.push(role);
19972
20413
  note(role, marker);
19973
20414
  }
19974
20415
  return { primaryLanguage: languages[0], languages, roles, evidence };
19975
20416
  }
19976
20417
  function describeIdentity(repo, registryPath) {
19977
- const manifestPath = join33(repo, ".project.json");
20418
+ const manifestPath = join34(repo, ".project.json");
19978
20419
  const manifest = readJson(manifestPath);
19979
20420
  const drift = [];
19980
20421
  let record3;
@@ -20067,12 +20508,12 @@ function describeSubsystems(repo, findings) {
20067
20508
  const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
20068
20509
  const evidence = metadata.id === "notebook" ? (() => {
20069
20510
  try {
20070
- const manifest = JSON.parse(readFileSync28(join33(repo, ".project.json"), "utf8"));
20511
+ const manifest = JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8"));
20071
20512
  return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
20072
20513
  } catch {
20073
20514
  return [];
20074
20515
  }
20075
- })() : markers.filter((marker) => existsSync23(join33(repo, marker)));
20516
+ })() : markers.filter((marker) => existsSync23(join34(repo, marker)));
20076
20517
  const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
20077
20518
  id: finding2.id,
20078
20519
  title: finding2.title,
@@ -20091,7 +20532,7 @@ function describeNotebook(repo, registryPath) {
20091
20532
  try {
20092
20533
  const registry = loadProjectRegistry(registryPath);
20093
20534
  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;
20535
+ const manifest = existsSync23(join34(repo, ".project.json")) ? JSON.parse(readFileSync29(join34(repo, ".project.json"), "utf8")) : void 0;
20095
20536
  const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
20096
20537
  if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
20097
20538
  const config = loadEffectiveNotebookConfig(repo, registryPath);
@@ -20109,7 +20550,7 @@ function describeNotebook(repo, registryPath) {
20109
20550
  }
20110
20551
  }
20111
20552
  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 }));
20553
+ return CONFIG_FILES.filter((spec) => existsSync23(join34(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
20113
20554
  }
20114
20555
  function describeNextSteps(description, findings) {
20115
20556
  const steps = [];
@@ -20533,7 +20974,7 @@ init_style();
20533
20974
  // src/notebook/cli.ts
20534
20975
  init_project();
20535
20976
  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";
20977
+ 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
20978
  import { resolve as resolve24 } from "node:path";
20538
20979
  import { createInterface } from "node:readline/promises";
20539
20980
 
@@ -20676,12 +21117,12 @@ async function execute(input) {
20676
21117
  function readBoundedRegularFile(path, maxBytes) {
20677
21118
  const absolute = resolve24(path);
20678
21119
  if (!existsSync24(absolute)) throw new NotebookError("INVALID_INPUT", `File not found: ${absolute}`);
20679
- const before = lstatSync18(absolute);
21120
+ const before = lstatSync19(absolute);
20680
21121
  if (!before.isFile() || before.isSymbolicLink()) throw new NotebookError("INVALID_INPUT", `File must be a regular non-symlink: ${absolute}`);
20681
21122
  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));
21123
+ const fd = openSync9(absolute, constants7.O_RDONLY | (constants7.O_NOFOLLOW ?? 0));
20683
21124
  try {
20684
- const opened = fstatSync4(fd);
21125
+ const opened = fstatSync5(fd);
20685
21126
  if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) throw new NotebookError("INVALID_INPUT", `File changed while opening: ${absolute}`);
20686
21127
  const chunks = [];
20687
21128
  let total = 0;
@@ -20699,7 +21140,7 @@ function readBoundedRegularFile(path, maxBytes) {
20699
21140
  throw new NotebookError("INVALID_INPUT", `File must be valid UTF-8: ${absolute}`);
20700
21141
  }
20701
21142
  } finally {
20702
- closeSync8(fd);
21143
+ closeSync9(fd);
20703
21144
  }
20704
21145
  }
20705
21146
  function textInput(options, maxBytes) {
@@ -20951,14 +21392,14 @@ async function loadScreen(relativeToDist, base) {
20951
21392
  init_boardQuery();
20952
21393
  init_style();
20953
21394
  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";
21395
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync31, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync16 } from "node:fs";
20955
21396
  import { homedir as homedir15 } from "node:os";
20956
- import { dirname as dirname22, join as join35 } from "node:path";
21397
+ import { dirname as dirname22, join as join36 } from "node:path";
20957
21398
 
20958
21399
  // 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";
21400
+ import { closeSync as closeSync10, constants as constants8, fstatSync as fstatSync6, openSync as openSync10, readFileSync as readFileSync30, readSync as readSync5 } from "node:fs";
20960
21401
  import { homedir as homedir14 } from "node:os";
20961
- import { join as join34 } from "node:path";
21402
+ import { join as join35 } from "node:path";
20962
21403
  var ENGINE_DEFAULT_JOB_ID = "777c511a009c";
20963
21404
  var WINDOW_MS = 24 * 60 * 60 * 1e3;
20964
21405
  var TICK_MS = 5 * 60 * 1e3;
@@ -21058,9 +21499,9 @@ function parseEngineStatus(auditText, input) {
21058
21499
  }
21059
21500
  }
21060
21501
  function readTail(path, maxBytes) {
21061
- const fd = openSync9(path, constants7.O_RDONLY | constants7.O_NONBLOCK);
21502
+ const fd = openSync10(path, constants8.O_RDONLY | constants8.O_NONBLOCK);
21062
21503
  try {
21063
- const stat = fstatSync5(fd);
21504
+ const stat = fstatSync6(fd);
21064
21505
  if (!stat.isFile()) throw new Error(`${path} is not a regular file`);
21065
21506
  const size = stat.size;
21066
21507
  const length = Math.min(size, maxBytes);
@@ -21076,20 +21517,20 @@ function readTail(path, maxBytes) {
21076
21517
  const newline = text2.indexOf("\n");
21077
21518
  return newline === -1 ? "" : text2.slice(newline + 1);
21078
21519
  } finally {
21079
- closeSync9(fd);
21520
+ closeSync10(fd);
21080
21521
  }
21081
21522
  }
21082
21523
  function engineSources(env2 = process.env) {
21083
- const cronDir = join34(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
21524
+ const cronDir = join35(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
21084
21525
  return {
21085
- auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join34(cronDir, "usage_audit.jsonl"),
21086
- jobsPath: join34(cronDir, "jobs.json"),
21526
+ auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join35(cronDir, "usage_audit.jsonl"),
21527
+ jobsPath: join35(cronDir, "jobs.json"),
21087
21528
  jobId: env2.PJ_CRANK_JOB_ID?.trim() || ENGINE_DEFAULT_JOB_ID
21088
21529
  };
21089
21530
  }
21090
21531
  function readCap(jobsPath, jobId) {
21091
21532
  try {
21092
- const parsed = JSON.parse(readFileSync29(jobsPath, "utf8"));
21533
+ const parsed = JSON.parse(readFileSync30(jobsPath, "utf8"));
21093
21534
  const jobs = Array.isArray(parsed.jobs) ? parsed.jobs : [];
21094
21535
  const job = jobs.find((j) => typeof j === "object" && j !== null && String(j.id ?? "") === jobId);
21095
21536
  const economics = job?.economics;
@@ -21475,11 +21916,11 @@ function withTabOwners(tabs, panes, identify, claimed = []) {
21475
21916
  }
21476
21917
  var PANE_CWD_TTL_SECONDS = 6 * 3600;
21477
21918
  function paneCwdCachePath(env2 = process.env) {
21478
- return env2.ZCRANK_CWD_CACHE?.trim() || join35(homedir15(), ".local/state/zcrank/pane-cwds.json");
21919
+ return env2.ZCRANK_CWD_CACHE?.trim() || join36(homedir15(), ".local/state/zcrank/pane-cwds.json");
21479
21920
  }
21480
21921
  function loadCwdCache(path) {
21481
21922
  try {
21482
- const parsed = JSON.parse(readFileSync30(path, "utf8"));
21923
+ const parsed = JSON.parse(readFileSync31(path, "utf8"));
21483
21924
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
21484
21925
  } catch {
21485
21926
  return {};
@@ -21487,9 +21928,9 @@ function loadCwdCache(path) {
21487
21928
  }
21488
21929
  function saveCwdCache(path, cache) {
21489
21930
  try {
21490
- mkdirSync12(dirname22(path), { recursive: true });
21931
+ mkdirSync13(dirname22(path), { recursive: true });
21491
21932
  const tmp = `${path}.${process.pid}.tmp`;
21492
- writeFileSync15(tmp, JSON.stringify(cache));
21933
+ writeFileSync16(tmp, JSON.stringify(cache));
21493
21934
  renameSync10(tmp, path);
21494
21935
  } catch {
21495
21936
  }
@@ -21642,7 +22083,7 @@ function resolveByName(project, live, storedTabs) {
21642
22083
  function projectNameFor(handle, registryName) {
21643
22084
  if (registryName && registryName.trim()) return registryName.trim();
21644
22085
  try {
21645
- const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
22086
+ const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
21646
22087
  const name = manifest.project_name;
21647
22088
  if (typeof name === "string" && name.trim()) return name.trim();
21648
22089
  } catch {
@@ -21653,7 +22094,7 @@ function projectAgentsFor(handle, registryAgents) {
21653
22094
  const fromRecord = Object.entries(registryAgents ?? {}).map(([agentHandle, value]) => ({ handle: agentHandle, role: String(value?.role ?? "") }));
21654
22095
  if (fromRecord.length) return fromRecord;
21655
22096
  try {
21656
- const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
22097
+ const manifest = JSON.parse(readFileSync31(join36(handle.root, ".project.json"), "utf8"));
21657
22098
  const agents = manifest.agents;
21658
22099
  if (agents && typeof agents === "object" && !Array.isArray(agents)) {
21659
22100
  return Object.entries(agents).map(([agentHandle, value]) => ({ handle: agentHandle, role: typeof value?.role === "string" ? value.role : "" }));
@@ -21971,9 +22412,9 @@ function createCrankController(deps) {
21971
22412
  }
21972
22413
 
21973
22414
  // src/crank/panes.ts
21974
- import { readFileSync as readFileSync31, readdirSync as readdirSync13, readlinkSync as readlinkSync9 } from "node:fs";
22415
+ import { readFileSync as readFileSync32, readdirSync as readdirSync14, readlinkSync as readlinkSync9 } from "node:fs";
21975
22416
  import { homedir as homedir16 } from "node:os";
21976
- import { basename as basename11, join as join36 } from "node:path";
22417
+ import { basename as basename12, join as join37 } from "node:path";
21977
22418
  var AGENT_CLIS = [
21978
22419
  "claude",
21979
22420
  "codex",
@@ -22055,7 +22496,7 @@ function parseProcStat(text2) {
22055
22496
  }
22056
22497
  function readProcStat(pid) {
22057
22498
  try {
22058
- return parseProcStat(readFileSync31(`/proc/${pid}/stat`, "latin1"));
22499
+ return parseProcStat(readFileSync32(`/proc/${pid}/stat`, "latin1"));
22059
22500
  } catch {
22060
22501
  return null;
22061
22502
  }
@@ -22063,7 +22504,7 @@ function readProcStat(pid) {
22063
22504
  function readTag(pid) {
22064
22505
  let raw;
22065
22506
  try {
22066
- raw = readFileSync31(`/proc/${pid}/environ`);
22507
+ raw = readFileSync32(`/proc/${pid}/environ`);
22067
22508
  } catch {
22068
22509
  return null;
22069
22510
  }
@@ -22089,8 +22530,8 @@ function readTag(pid) {
22089
22530
  }
22090
22531
  function readArgv(pid) {
22091
22532
  try {
22092
- const parts = readFileSync31(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
22093
- return { argv: parts.join(" "), exe: parts[0] ? basename11(parts[0]) : "" };
22533
+ const parts = readFileSync32(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
22534
+ return { argv: parts.join(" "), exe: parts[0] ? basename12(parts[0]) : "" };
22094
22535
  } catch {
22095
22536
  return { argv: "", exe: "" };
22096
22537
  }
@@ -22130,7 +22571,7 @@ var ProcScanner = class {
22130
22571
  pids = [...this.enumerate()];
22131
22572
  } else {
22132
22573
  try {
22133
- pids = readdirSync13("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
22574
+ pids = readdirSync14("/proc").filter((name) => /^\d+$/.test(name)).map(Number);
22134
22575
  } catch {
22135
22576
  return [];
22136
22577
  }
@@ -22166,13 +22607,13 @@ var ProcScanner = class {
22166
22607
  var PANE_STATUSES = ["busy", "idle", "shell"];
22167
22608
  function claudeSessionsDir(env2 = process.env) {
22168
22609
  const override = env2.PJ_CLAUDE_SESSIONS_DIR?.trim();
22169
- return override || join36(homedir16(), ".claude", "sessions");
22610
+ return override || join37(homedir16(), ".claude", "sessions");
22170
22611
  }
22171
22612
  function readClaudeSession(pid, stat, dir = claudeSessionsDir()) {
22172
22613
  const none = (reason) => ({ kind: null, sessionId: null, reason, status: "unknown", statusUpdatedAt: null });
22173
22614
  let raw;
22174
22615
  try {
22175
- raw = readFileSync31(join36(dir, `${pid}.json`), "utf8");
22616
+ raw = readFileSync32(join37(dir, `${pid}.json`), "utf8");
22176
22617
  } catch {
22177
22618
  return none("no-session-file");
22178
22619
  }
@@ -22633,7 +23074,7 @@ async function promptForRuleIds(rules) {
22633
23074
  function readJson2(path) {
22634
23075
  if (!existsSync26(path)) return void 0;
22635
23076
  try {
22636
- const parsed = JSON.parse(readFileSync32(path, "utf8"));
23077
+ const parsed = JSON.parse(readFileSync33(path, "utf8"));
22637
23078
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
22638
23079
  } catch {
22639
23080
  return void 0;
@@ -22650,9 +23091,9 @@ function packageNameToProjectName(value) {
22650
23091
  return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
22651
23092
  }
22652
23093
  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";
23094
+ const manifest = readJson2(join38(targetDir, ".project.json"));
23095
+ const pkg = readJson2(join38(targetDir, "package.json"));
23096
+ const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename13(targetDir)) || "Project";
22656
23097
  const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
22657
23098
  return {
22658
23099
  name,
@@ -22726,7 +23167,7 @@ function actionNeedsRun(plan, kind, syncMode) {
22726
23167
  if (!action || action.kind !== "project.write-manifest") return false;
22727
23168
  const next = `${JSON.stringify(action.manifest, null, 2)}
22728
23169
  `;
22729
- return !existsSync26(action.path) || readFileSync32(action.path, "utf8") !== next;
23170
+ return !existsSync26(action.path) || readFileSync33(action.path, "utf8") !== next;
22730
23171
  }
22731
23172
  if (kind === "copier.copy.commonproject") return true;
22732
23173
  if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
@@ -22783,11 +23224,11 @@ async function resolveProjectInitTarget(name, options) {
22783
23224
  if (!targetDir && cwdGitRoot) {
22784
23225
  targetDir = cwdGitRoot;
22785
23226
  }
22786
- if (!targetDir && !name && existsSync26(join37(cwd, ".project.json"))) {
23227
+ if (!targetDir && !name && existsSync26(join38(cwd, ".project.json"))) {
22787
23228
  targetDir = cwd;
22788
23229
  }
22789
23230
  if (!targetDir && interactive) {
22790
- const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename12(cwd)));
23231
+ const promptedName = await promptTextValue("Project name", packageNameToProjectName(basename13(cwd)));
22791
23232
  targetDir = resolve25(await promptTextValue("Project directory", projectTargetDirUnder(promptedName, cwd)));
22792
23233
  name = promptedName;
22793
23234
  }
@@ -22797,14 +23238,14 @@ async function resolveProjectInitTarget(name, options) {
22797
23238
  const targetExists = existsSync26(targetDir);
22798
23239
  if (targetExists && !statSync9(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
22799
23240
  const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
22800
- const alreadyScaffolded = targetExists && (existsSync26(join37(targetDir, ".project.json")) || existsSync26(join37(targetDir, ".copier-answers.yml")));
23241
+ const alreadyScaffolded = targetExists && (existsSync26(join38(targetDir, ".project.json")) || existsSync26(join38(targetDir, ".copier-answers.yml")));
22801
23242
  const syncMode = Boolean(
22802
23243
  targetGitRoot && resolve25(targetGitRoot) === resolve25(targetDir) || alreadyScaffolded
22803
23244
  );
22804
- if (targetExists && !syncMode && !options.force && readdirSync14(targetDir).length > 0) {
23245
+ if (targetExists && !syncMode && !options.force && readdirSync15(targetDir).length > 0) {
22805
23246
  throw new Error(`Target already exists and is not empty: ${targetDir} (pass --force to render over it)`);
22806
23247
  }
22807
- const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename12(targetDir)) ?? "Project", description: "" };
23248
+ const defaults = targetExists ? deriveProjectDefaults(targetDir) : { name: packageNameToProjectName(basename13(targetDir)) ?? "Project", description: "" };
22808
23249
  if (!name && interactive && !syncMode) {
22809
23250
  name = await promptTextValue("Project name", defaults.name);
22810
23251
  }
@@ -23319,7 +23760,7 @@ program.command("reindex").argument("[project-id]", "Canonical project ID (defau
23319
23760
  else if (options.all) result2 = registryRequest(location, "POST", "/v1/reindex", {});
23320
23761
  else {
23321
23762
  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") });
23763
+ result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join38(repo, ".project.json") });
23323
23764
  }
23324
23765
  const statuses = result2.__registry_status;
23325
23766
  const stale = Object.entries(statuses ?? {}).filter(([, item]) => item.status !== "ok");