@delorenj/pjangler 1.5.49 → 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
@@ -437,7 +437,7 @@ var init_executionBinding = __esm({
437
437
  });
438
438
 
439
439
  // src/project/manifestIndex.ts
440
- import { basename as basename5, dirname as dirname9, join as join12, resolve as resolve7 } from "node:path";
440
+ import { basename as basename6, dirname as dirname9, join as join13, resolve as resolve7 } from "node:path";
441
441
  function normalizeProjectId2(value) {
442
442
  try {
443
443
  return normalizeProjectId(value);
@@ -484,24 +484,24 @@ var init_manifestIndex = __esm({
484
484
  });
485
485
 
486
486
  // src/lifecycle/preflight.ts
487
- import { createHash as createHash8 } from "node:crypto";
488
- import { accessSync, constants, existsSync as existsSync9, lstatSync as lstatSync10, readFileSync as readFileSync11, readdirSync as readdirSync8, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
487
+ import { createHash as createHash9 } from "node:crypto";
488
+ import { accessSync, constants as constants2, existsSync as existsSync9, lstatSync as lstatSync11, readFileSync as readFileSync12, readdirSync as readdirSync9, realpathSync as realpathSync5, statSync as statSync3 } from "node:fs";
489
489
  import { tmpdir as tmpdir2, userInfo } from "node:os";
490
- import { basename as basename6, delimiter, dirname as dirname10, isAbsolute as isAbsolute5, join as join13, relative as relative10, resolve as resolve8 } from "node:path";
490
+ import { basename as basename7, delimiter, dirname as dirname10, isAbsolute as isAbsolute5, join as join14, relative as relative10, resolve as resolve8 } from "node:path";
491
491
  import YAML5 from "yaml";
492
492
  function containedBy(parent, candidate) {
493
493
  const rel = relative10(resolve8(parent), resolve8(candidate));
494
494
  return rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
495
495
  }
496
496
  function accountMiseShimsDir(home) {
497
- return resolve8(join13(home, ".local", "share", "mise", "shims"));
497
+ return resolve8(join14(home, ".local", "share", "mise", "shims"));
498
498
  }
499
499
  function inactiveMiseShim(candidate, shimsDir) {
500
500
  if (dirname10(candidate) !== shimsDir) return void 0;
501
501
  try {
502
- if (!lstatSync10(candidate).isSymbolicLink()) return void 0;
502
+ if (!lstatSync11(candidate).isSymbolicLink()) return void 0;
503
503
  const realPath = realpathSync5(candidate);
504
- if (basename6(realPath) !== "mise") return void 0;
504
+ if (basename7(realPath) !== "mise") return void 0;
505
505
  return { path: candidate, realPath, reason: "inactive-mise-shim" };
506
506
  } catch {
507
507
  return void 0;
@@ -513,8 +513,8 @@ function resolveCopierOnPath(env2, shimsDir) {
513
513
  const entry = rawEntry || process.cwd();
514
514
  const candidate = resolve8(entry, process.platform === "win32" ? "copier.exe" : "copier");
515
515
  try {
516
- accessSync(candidate, constants.X_OK);
517
- const stat = lstatSync10(candidate);
516
+ accessSync(candidate, constants2.X_OK);
517
+ const stat = lstatSync11(candidate);
518
518
  if (!stat.isFile() && !stat.isSymbolicLink()) continue;
519
519
  } catch {
520
520
  continue;
@@ -532,7 +532,7 @@ function describeSkipped(skipped) {
532
532
  return skipped.map((entry) => `${entry.path} -> ${entry.realPath}`).join(", ");
533
533
  }
534
534
  function sha256(path) {
535
- return createHash8("sha256").update(readFileSync11(path)).digest("base64url");
535
+ return createHash9("sha256").update(readFileSync12(path)).digest("base64url");
536
536
  }
537
537
  function fingerprint(path) {
538
538
  const absolute = resolve8(path);
@@ -570,7 +570,7 @@ function sameFingerprint(expected) {
570
570
  function consoleScriptContract(path) {
571
571
  let text;
572
572
  try {
573
- text = readFileSync11(path, "utf8").slice(0, 32 * 1024);
573
+ text = readFileSync12(path, "utf8").slice(0, 32 * 1024);
574
574
  } catch (error) {
575
575
  return { ok: false, error: `cannot read Copier launcher: ${error instanceof Error ? error.message : String(error)}` };
576
576
  }
@@ -581,7 +581,7 @@ function consoleScriptContract(path) {
581
581
  return { ok: false, error: "Copier launcher must use one absolute Python interpreter" };
582
582
  }
583
583
  const interpreterPath = resolve8(shebang[0]);
584
- const interpreter = basename6(interpreterPath);
584
+ const interpreter = basename7(interpreterPath);
585
585
  if (!/^python(?:\d+(?:\.\d+)*)?$/.test(interpreter)) {
586
586
  return { ok: false, error: "Copier launcher is not an absolute Python console script" };
587
587
  }
@@ -592,16 +592,16 @@ function consoleScriptContract(path) {
592
592
  }
593
593
  function defaultUvToolRoots(home) {
594
594
  return [
595
- join13(home, ".local", "share", "uv", "tools", "copier"),
596
- join13(home, "Library", "Application Support", "uv", "tools", "copier")
595
+ join14(home, ".local", "share", "uv", "tools", "copier"),
596
+ join14(home, "Library", "Application Support", "uv", "tools", "copier")
597
597
  ].map((path) => resolve8(path));
598
598
  }
599
599
  function locateUvSitePackages(toolRoot) {
600
- const lib = join13(toolRoot, "lib");
600
+ const lib = join14(toolRoot, "lib");
601
601
  const candidates = [];
602
- for (const entry of readdirSync8(lib, { withFileTypes: true })) {
602
+ for (const entry of readdirSync9(lib, { withFileTypes: true })) {
603
603
  if (!entry.isDirectory() || !/^python\d+(?:\.\d+)*$/.test(entry.name)) continue;
604
- const sitePackages = join13(lib, entry.name, "site-packages");
604
+ const sitePackages = join14(lib, entry.name, "site-packages");
605
605
  if (existsSync9(sitePackages)) candidates.push(sitePackages);
606
606
  }
607
607
  if (candidates.length !== 1) throw new Error(`expected one UV Copier site-packages directory, found ${candidates.length}`);
@@ -618,13 +618,13 @@ function parseRecordLine(line) {
618
618
  };
619
619
  }
620
620
  function attestUvCopier(candidate, realCandidate, home) {
621
- const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate === join13(root, "bin", "copier"));
621
+ const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate === join14(root, "bin", "copier"));
622
622
  if (!toolRoot) {
623
623
  return { ok: false, error: `refusing untrusted PATH-shadowed Copier executable: ${candidate}` };
624
624
  }
625
625
  const supportedEntries = new Set([
626
- join13(home, ".local", "bin", "copier"),
627
- join13(toolRoot, "bin", "copier")
626
+ join14(home, ".local", "bin", "copier"),
627
+ join14(toolRoot, "bin", "copier")
628
628
  ].map((path) => resolve8(path)));
629
629
  if (!supportedEntries.has(resolve8(candidate))) {
630
630
  return { ok: false, error: `refusing non-canonical UV Copier entry point: ${candidate}` };
@@ -634,46 +634,46 @@ function attestUvCopier(candidate, realCandidate, home) {
634
634
  if (!launcher.ok || !launcher.interpreter) {
635
635
  return { ...launcher, executable: realCandidate, realExecutable: realCandidate };
636
636
  }
637
- const expectedInterpreter = join13(toolRoot, "bin", basename6(launcher.interpreter));
637
+ const expectedInterpreter = join14(toolRoot, "bin", basename7(launcher.interpreter));
638
638
  if (resolve8(launcher.interpreter) !== resolve8(expectedInterpreter)) {
639
639
  return { ok: false, error: "UV Copier launcher interpreter is outside the attested tool environment" };
640
640
  }
641
641
  const interpreterReal = realpathSync5(launcher.interpreter);
642
642
  const uvPythonRoots = [
643
- join13(home, ".local", "share", "uv", "python"),
644
- join13(home, "Library", "Application Support", "uv", "python")
643
+ join14(home, ".local", "share", "uv", "python"),
644
+ join14(home, "Library", "Application Support", "uv", "python")
645
645
  ];
646
646
  if (!uvPythonRoots.some((root) => containedBy(root, interpreterReal))) {
647
647
  return { ok: false, error: "UV Copier interpreter is not managed by the canonical UV Python installation" };
648
648
  }
649
- const receiptPath = join13(toolRoot, "uv-receipt.toml");
650
- const receipt = readFileSync11(receiptPath, "utf8");
649
+ const receiptPath = join14(toolRoot, "uv-receipt.toml");
650
+ const receipt = readFileSync12(receiptPath, "utf8");
651
651
  if (!/requirements\s*=\s*\[[\s\S]*?name\s*=\s*["']copier["']/.test(receipt) || !/entrypoints\s*=\s*\[[\s\S]*?name\s*=\s*["']copier["'][\s\S]*?from\s*=\s*["']copier["']/.test(receipt)) {
652
652
  return { ok: false, error: "UV tool receipt does not bind the copier entry point to the Copier package" };
653
653
  }
654
654
  const sitePackages = locateUvSitePackages(toolRoot);
655
- const distInfos = readdirSync8(sitePackages, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^copier-[^-]+\.dist-info$/i.test(entry.name)).map((entry) => join13(sitePackages, entry.name));
655
+ const distInfos = readdirSync9(sitePackages, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^copier-[^-]+\.dist-info$/i.test(entry.name)).map((entry) => join14(sitePackages, entry.name));
656
656
  if (distInfos.length !== 1) {
657
657
  return { ok: false, error: `expected one installed Copier distribution, found ${distInfos.length}` };
658
658
  }
659
659
  const distInfo = realpathSync5(distInfos[0]);
660
- const metadataPath = join13(distInfo, "METADATA");
661
- const entryPointsPath = join13(distInfo, "entry_points.txt");
662
- const recordPath = join13(distInfo, "RECORD");
663
- const metadata = readFileSync11(metadataPath, "utf8");
660
+ const metadataPath = join14(distInfo, "METADATA");
661
+ const entryPointsPath = join14(distInfo, "entry_points.txt");
662
+ const recordPath = join14(distInfo, "RECORD");
663
+ const metadata = readFileSync12(metadataPath, "utf8");
664
664
  const name = metadata.match(/^Name:\s*(.+)$/mi)?.[1]?.trim();
665
665
  const version = metadata.match(/^Version:\s*(.+)$/mi)?.[1]?.trim();
666
666
  if (name?.toLowerCase() !== "copier" || !version || !/^9(?:\.|$)/.test(version)) {
667
667
  return { ok: false, error: "installed distribution is not a Copier 9 package" };
668
668
  }
669
- const entryPoints = readFileSync11(entryPointsPath, "utf8");
669
+ const entryPoints = readFileSync12(entryPointsPath, "utf8");
670
670
  if (!/^copier\s*=\s*copier\.__main__:CopierApp\.run\s*$/m.test(entryPoints)) {
671
671
  return { ok: false, error: "installed Copier distribution has an unexpected console entry point" };
672
672
  }
673
- const recordEntries = readFileSync11(recordPath, "utf8").split(/\r?\n/).map(parseRecordLine).filter((entry) => Boolean(entry));
673
+ const recordEntries = readFileSync12(recordPath, "utf8").split(/\r?\n/).map(parseRecordLine).filter((entry) => Boolean(entry));
674
674
  const selected = recordEntries.filter((entry) => {
675
675
  const normalized = entry.relativePath.replaceAll("\\", "/");
676
- return normalized.startsWith("copier/") || normalized === `${basename6(distInfo)}/METADATA` || normalized === `${basename6(distInfo)}/entry_points.txt` || normalized === "../../../bin/copier";
676
+ return normalized.startsWith("copier/") || normalized === `${basename7(distInfo)}/METADATA` || normalized === `${basename7(distInfo)}/entry_points.txt` || normalized === "../../../bin/copier";
677
677
  });
678
678
  if (!selected.some((entry) => entry.relativePath === "../../../bin/copier") || !selected.some((entry) => entry.relativePath.replaceAll("\\", "/") === "copier/__main__.py")) {
679
679
  return { ok: false, error: "Copier RECORD does not bind its launcher and package entry point" };
@@ -690,7 +690,7 @@ function attestUvCopier(candidate, realCandidate, home) {
690
690
  }
691
691
  attestedFiles.set(resolve8(path), fingerprint(path));
692
692
  }
693
- for (const path of [realCandidate, join13(toolRoot, "pyvenv.cfg"), receiptPath, metadataPath, entryPointsPath, recordPath]) {
693
+ for (const path of [realCandidate, join14(toolRoot, "pyvenv.cfg"), receiptPath, metadataPath, entryPointsPath, recordPath]) {
694
694
  attestedFiles.set(resolve8(path), fingerprint(path));
695
695
  }
696
696
  const identity = {
@@ -765,18 +765,18 @@ function regularContainedFile(root, path, label) {
765
765
  const rootReal = realpathSync5(root);
766
766
  const fileReal = realpathSync5(path);
767
767
  if (!containedBy(rootReal, fileReal)) return { ok: false, error: `${label} escapes its vendored template root` };
768
- if (!lstatSync10(path).isFile()) return { ok: false, error: `${label} is not a regular file` };
768
+ if (!lstatSync11(path).isFile()) return { ok: false, error: `${label} is not a regular file` };
769
769
  return { ok: true };
770
770
  } catch (error) {
771
771
  return { ok: false, error: `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}` };
772
772
  }
773
773
  }
774
774
  function parseCopierConfig(templateRoot, label) {
775
- const configPath = join13(templateRoot, "copier.yml");
775
+ const configPath = join14(templateRoot, "copier.yml");
776
776
  const file = regularContainedFile(templateRoot, configPath, `${label} copier.yml`);
777
777
  if (!file.ok) return { result: file };
778
778
  try {
779
- const parsed = YAML5.parse(readFileSync11(configPath, "utf8"));
779
+ const parsed = YAML5.parse(readFileSync12(configPath, "utf8"));
780
780
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
781
781
  return { result: { ok: false, error: `${label} copier.yml must contain a mapping` } };
782
782
  }
@@ -794,13 +794,13 @@ function parseCopierConfig(templateRoot, label) {
794
794
  }
795
795
  function requireFiles(templateRoot, files, label) {
796
796
  for (const rel of files) {
797
- const result2 = regularContainedFile(templateRoot, join13(templateRoot, rel), `${label} ${rel}`);
797
+ const result2 = regularContainedFile(templateRoot, join14(templateRoot, rel), `${label} ${rel}`);
798
798
  if (!result2.ok) return result2;
799
799
  }
800
800
  return { ok: true };
801
801
  }
802
802
  function preflightCommonProjectTemplate(pjanglerRoot) {
803
- const templateRoot = join13(resolve8(pjanglerRoot), "templates", "commonproject");
803
+ const templateRoot = join14(resolve8(pjanglerRoot), "templates", "commonproject");
804
804
  const parsed = parseCopierConfig(templateRoot, "CommonProject template");
805
805
  if (!parsed.result.ok) return parsed.result;
806
806
  const files = requireFiles(templateRoot, [
@@ -811,7 +811,7 @@ function preflightCommonProjectTemplate(pjanglerRoot) {
811
811
  "template/mise.toml.jinja"
812
812
  ], "CommonProject template");
813
813
  if (!files.ok) return files;
814
- const projectJson = readFileSync11(join13(templateRoot, "template", ".project.json.jinja"), "utf8");
814
+ const projectJson = readFileSync12(join14(templateRoot, "template", ".project.json.jinja"), "utf8");
815
815
  for (const key of ["project_name", "repo_path", "ticket_provider", "agents"]) {
816
816
  if (!projectJson.includes(`"${key}"`)) return { ok: false, error: `CommonProject projection is missing ${key}` };
817
817
  }
@@ -836,12 +836,12 @@ var init_preflight = __esm({
836
836
  });
837
837
 
838
838
  // src/project/boardUrl.ts
839
- import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
840
- import { dirname as dirname11, isAbsolute as isAbsolute6, join as join14, resolve as resolve9 } from "node:path";
839
+ import { existsSync as existsSync10, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
840
+ import { dirname as dirname11, isAbsolute as isAbsolute6, join as join15, resolve as resolve9 } from "node:path";
841
841
  function findProjectRoot(from) {
842
842
  let dir = resolve9(from);
843
843
  for (; ; ) {
844
- if (existsSync10(join14(dir, ".project.json"))) return dir;
844
+ if (existsSync10(join15(dir, ".project.json"))) return dir;
845
845
  const parent = dirname11(dir);
846
846
  if (parent === dir) return void 0;
847
847
  dir = parent;
@@ -854,8 +854,8 @@ var init_boardUrl = __esm({
854
854
  });
855
855
 
856
856
  // src/utils/version.ts
857
- import { readFileSync as readFileSync13 } from "node:fs";
858
- import { dirname as dirname12, join as join15 } from "node:path";
857
+ import { readFileSync as readFileSync14 } from "node:fs";
858
+ import { dirname as dirname12, join as join16 } from "node:path";
859
859
  import { fileURLToPath } from "node:url";
860
860
  var PJANGLER_VERSION;
861
861
  var init_version = __esm({
@@ -866,7 +866,7 @@ var init_version = __esm({
866
866
  let dir = dirname12(fileURLToPath(import.meta.url));
867
867
  for (let i = 0; i < 4; i++) {
868
868
  try {
869
- const raw = readFileSync13(join15(dir, "package.json"), "utf8");
869
+ const raw = readFileSync14(join16(dir, "package.json"), "utf8");
870
870
  return JSON.parse(raw).version ?? "0.0.0";
871
871
  } catch {
872
872
  const parent = dirname12(dir);
@@ -896,9 +896,9 @@ var init_boardQuery = __esm({
896
896
 
897
897
  // src/project/index.ts
898
898
  import { spawnSync as spawnSync6 } from "node:child_process";
899
- 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 readFileSync14, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "node:fs";
899
+ 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 readFileSync15, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync6, statSync as statSync5, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
900
900
  import { homedir as homedir5, tmpdir as tmpdir3 } from "node:os";
901
- import { basename as basename7, delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute7, join as join16, relative as relative11, resolve as resolve10, sep as sep3, win32 } from "node:path";
901
+ import { basename as basename8, delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute7, join as join17, relative as relative11, resolve as resolve10, sep as sep3, win32 } from "node:path";
902
902
  import { fileURLToPath as fileURLToPath2 } from "node:url";
903
903
  import YAML6 from "yaml";
904
904
  function normalizeProjectType(value, label = "Project type") {
@@ -908,7 +908,7 @@ function normalizeProjectType(value, label = "Project type") {
908
908
  }
909
909
  function looksLikeObsidianPlugin(dir) {
910
910
  try {
911
- const manifest = JSON.parse(readFileSync14(join16(dir, "manifest.json"), "utf8"));
911
+ const manifest = JSON.parse(readFileSync15(join17(dir, "manifest.json"), "utf8"));
912
912
  return isRecord2(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
913
913
  } catch {
914
914
  return false;
@@ -916,10 +916,10 @@ function looksLikeObsidianPlugin(dir) {
916
916
  }
917
917
  function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
918
918
  if (projectType === "base") return;
919
- const copierYml = join16(pjanglerRoot, "templates", "commonproject", "copier.yml");
919
+ const copierYml = join17(pjanglerRoot, "templates", "commonproject", "copier.yml");
920
920
  let choices;
921
921
  try {
922
- const parsed = YAML6.parse(readFileSync14(copierYml, "utf8"));
922
+ const parsed = YAML6.parse(readFileSync15(copierYml, "utf8"));
923
923
  choices = isRecord2(parsed) && isRecord2(parsed.project_type) ? parsed.project_type.choices : void 0;
924
924
  } catch {
925
925
  choices = void 0;
@@ -942,9 +942,9 @@ function carriedAutomation(automation) {
942
942
  return Object.keys(rest).length ? { automation: rest } : {};
943
943
  }
944
944
  function synchronizeCopierIdentity(manifestPath, manifest) {
945
- const answersPath = join16(dirname13(manifestPath), ".copier-answers.yml");
945
+ const answersPath = join17(dirname13(manifestPath), ".copier-answers.yml");
946
946
  if (!existsSync11(answersPath)) return [];
947
- const current = readFileSync14(answersPath, "utf8");
947
+ const current = readFileSync15(answersPath, "utf8");
948
948
  const document = YAML6.parseDocument(current);
949
949
  if (document.errors.length) return [];
950
950
  const name = String(document.get("project_name") ?? "");
@@ -954,7 +954,7 @@ function synchronizeCopierIdentity(manifestPath, manifest) {
954
954
  document.set("project_description", manifest.project_description);
955
955
  const next = String(document);
956
956
  if (next === current) return [];
957
- writeFileSync9(answersPath, next, "utf8");
957
+ writeFileSync10(answersPath, next, "utf8");
958
958
  return [answersPath];
959
959
  }
960
960
  function projectRegistryPath(env2 = process.env) {
@@ -978,7 +978,7 @@ function loadProjectRegistry(path = projectRegistryPath()) {
978
978
  return registry2;
979
979
  }
980
980
  if (!existsSync11(path)) return emptyProjectRegistry();
981
- const raw = YAML6.parse(readFileSync14(path, "utf8"));
981
+ const raw = YAML6.parse(readFileSync15(path, "utf8"));
982
982
  if (raw == null) return emptyProjectRegistry();
983
983
  if (!isRecord2(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
984
984
  const registry = raw;
@@ -1046,11 +1046,11 @@ function mergeYamlMapping(document, path, desired, ownedKeys = []) {
1046
1046
  }
1047
1047
  function fsyncDirectory(path) {
1048
1048
  try {
1049
- const fd = openSync2(path, "r");
1049
+ const fd = openSync3(path, "r");
1050
1050
  try {
1051
1051
  fsyncSync(fd);
1052
1052
  } finally {
1053
- closeSync2(fd);
1053
+ closeSync3(fd);
1054
1054
  }
1055
1055
  } catch {
1056
1056
  }
@@ -1064,14 +1064,14 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1064
1064
  return;
1065
1065
  }
1066
1066
  validateProjectRegistry(registry);
1067
- mkdirSync7(dirname13(path), { recursive: true });
1067
+ mkdirSync8(dirname13(path), { recursive: true });
1068
1068
  let text;
1069
1069
  let mode = 420;
1070
1070
  if (existsSync11(path)) {
1071
- const stat = lstatSync11(path);
1071
+ const stat = lstatSync12(path);
1072
1072
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project registry must be a regular file: ${path}`);
1073
1073
  mode = stat.mode & 511;
1074
- const current = readFileSync14(path, "utf8");
1074
+ const current = readFileSync15(path, "utf8");
1075
1075
  const document = YAML6.parseDocument(current);
1076
1076
  if (document.errors.length) throw new Error(`Project registry YAML is invalid: ${path}`);
1077
1077
  setYamlLeaf(document, ["schema_version"], registry.schema_version);
@@ -1092,22 +1092,22 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
1092
1092
  const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
1093
1093
  let fd;
1094
1094
  try {
1095
- fd = openSync2(temp, "wx", mode);
1096
- writeFileSync9(fd, text, "utf8");
1097
- fchmodSync(fd, mode);
1095
+ fd = openSync3(temp, "wx", mode);
1096
+ writeFileSync10(fd, text, "utf8");
1097
+ fchmodSync2(fd, mode);
1098
1098
  fsyncSync(fd);
1099
- closeSync2(fd);
1099
+ closeSync3(fd);
1100
1100
  fd = void 0;
1101
1101
  renameSync5(temp, path);
1102
1102
  chmodSync4(path, mode);
1103
1103
  fsyncDirectory(dirname13(path));
1104
1104
  } catch (error) {
1105
1105
  if (fd !== void 0) try {
1106
- closeSync2(fd);
1106
+ closeSync3(fd);
1107
1107
  } catch {
1108
1108
  }
1109
1109
  try {
1110
- unlinkSync6(temp);
1110
+ unlinkSync7(temp);
1111
1111
  } catch {
1112
1112
  }
1113
1113
  throw error;
@@ -1215,18 +1215,18 @@ function ticketProviderKeyVars(provider, workspace) {
1215
1215
  return ticketProviderKeyGroups(provider, workspace).flat();
1216
1216
  }
1217
1217
  function ticketProviderSecretsPath(env2 = process.env) {
1218
- const base = env2.XDG_CONFIG_HOME || join16(env2.HOME || homedir5(), ".config");
1219
- return join16(base, "zshyzsh", "secrets.zsh");
1218
+ const base = env2.XDG_CONFIG_HOME || join17(env2.HOME || homedir5(), ".config");
1219
+ return join17(base, "zshyzsh", "secrets.zsh");
1220
1220
  }
1221
1221
  function ticketProviderFleetEnvPath(env2 = process.env) {
1222
- return env2.HERMES_FLEET_ENV?.trim() || join16(env2.HOME || homedir5(), ".hermes", "fleet.env");
1222
+ return env2.HERMES_FLEET_ENV?.trim() || join17(env2.HOME || homedir5(), ".hermes", "fleet.env");
1223
1223
  }
1224
1224
  function readShellAssignments(path, keys) {
1225
1225
  const found = {};
1226
1226
  if (!existsSync11(path)) return found;
1227
1227
  let text;
1228
1228
  try {
1229
- text = readFileSync14(path, "utf8");
1229
+ text = readFileSync15(path, "utf8");
1230
1230
  } catch {
1231
1231
  return found;
1232
1232
  }
@@ -1262,7 +1262,7 @@ function resolveTicketProviderCredentials(input) {
1262
1262
  }
1263
1263
  }
1264
1264
  const candidates = [];
1265
- if (input.repoPath) candidates.push(join16(input.repoPath, ".env"));
1265
+ if (input.repoPath) candidates.push(join17(input.repoPath, ".env"));
1266
1266
  candidates.push(ticketProviderFleetEnvPath(env2));
1267
1267
  candidates.push(ticketProviderSecretsPath(env2));
1268
1268
  for (const candidate of candidates) {
@@ -1280,19 +1280,19 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
1280
1280
  const file = `${provider}.sh`;
1281
1281
  const candidates = [];
1282
1282
  const override = env2[TICKET_PROVIDER_ADAPTERS_ENV];
1283
- if (override) candidates.push(join16(override, file));
1284
- const relativeRoot = join16("krebs", "adapters", "tp");
1283
+ if (override) candidates.push(join17(override, file));
1284
+ const relativeRoot = join17("krebs", "adapters", "tp");
1285
1285
  try {
1286
1286
  let dir = dirname13(fileURLToPath2(import.meta.url));
1287
1287
  for (let depth = 0; depth < 8; depth++) {
1288
- candidates.push(join16(dir, relativeRoot, file));
1288
+ candidates.push(join17(dir, relativeRoot, file));
1289
1289
  const parent = dirname13(dir);
1290
1290
  if (parent === dir) break;
1291
1291
  dir = parent;
1292
1292
  }
1293
1293
  } catch {
1294
1294
  }
1295
- candidates.push(join16(homedir5(), "code", "33GOD", relativeRoot, file));
1295
+ candidates.push(join17(homedir5(), "code", "33GOD", relativeRoot, file));
1296
1296
  return candidates.find((candidate) => existsSync11(candidate));
1297
1297
  }
1298
1298
  function provisionTicketProviderBoard(action, env2 = process.env) {
@@ -1310,7 +1310,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1310
1310
  ok: true,
1311
1311
  skipped: true,
1312
1312
  logs: [
1313
- `ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join16(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
1313
+ `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.`
1314
1314
  ]
1315
1315
  };
1316
1316
  }
@@ -1324,12 +1324,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1324
1324
  };
1325
1325
  }
1326
1326
  const redact = (text) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text);
1327
- const staging = mkdtempSync2(join16(tmpdir3(), "pjangler-tp-"));
1327
+ const staging = mkdtempSync3(join17(tmpdir3(), "pjangler-tp-"));
1328
1328
  try {
1329
- const providersDir = join16(staging, ".tp", "adapters");
1330
- mkdirSync7(providersDir, { recursive: true });
1331
- writeFileSync9(
1332
- join16(staging, ".project.json"),
1329
+ const providersDir = join17(staging, ".tp", "adapters");
1330
+ mkdirSync8(providersDir, { recursive: true });
1331
+ writeFileSync10(
1332
+ join17(staging, ".project.json"),
1333
1333
  `${JSON.stringify(
1334
1334
  {
1335
1335
  project_name: action.boardName,
@@ -1348,7 +1348,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1348
1348
  `,
1349
1349
  "utf8"
1350
1350
  );
1351
- const staged = join16(providersDir, `${provider}.sh`);
1351
+ const staged = join17(providersDir, `${provider}.sh`);
1352
1352
  copyFileSync2(adapter, staged);
1353
1353
  const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
1354
1354
  if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
@@ -1437,7 +1437,7 @@ function prospectiveRealPath(path) {
1437
1437
  while (!existsSync11(cursor)) {
1438
1438
  const parent = dirname13(cursor);
1439
1439
  if (parent === cursor) return resolve10(path);
1440
- suffix.unshift(basename7(cursor));
1440
+ suffix.unshift(basename8(cursor));
1441
1441
  cursor = parent;
1442
1442
  }
1443
1443
  return resolve10(realpathSync6(cursor), ...suffix);
@@ -1464,7 +1464,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
1464
1464
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1465
1465
  if (override === "0" || override === "false") return false;
1466
1466
  if (override === "1" || override === "true") return true;
1467
- return !existsSync11(join16(homedir5(), ".agents", "hooks"));
1467
+ return !existsSync11(join17(homedir5(), ".agents", "hooks"));
1468
1468
  }
1469
1469
  function jsonStable(value) {
1470
1470
  return JSON.stringify(value, (_key, item) => isRecord2(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
@@ -1497,10 +1497,10 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1497
1497
  const expanded = expandHome(sourceSkill);
1498
1498
  const direct = resolve10(expanded);
1499
1499
  if (existsSync11(direct)) return direct;
1500
- const name = basename7(sourceSkill);
1500
+ const name = basename8(sourceSkill);
1501
1501
  const roots = sourceSkillRoots(env2);
1502
1502
  for (const root of roots) {
1503
- const candidate = join16(root, name);
1503
+ const candidate = join17(root, name);
1504
1504
  if (existsSync11(candidate)) return candidate;
1505
1505
  }
1506
1506
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
@@ -1510,8 +1510,8 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1510
1510
  function planProjectInit(input) {
1511
1511
  if (!input.name.trim()) throw new Error("Project name is required");
1512
1512
  const targetDir = resolve10(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
1513
- const localManifestPath = join16(targetDir, ".project.json");
1514
- const localManifestContent = existsSync11(localManifestPath) ? readFileSync14(localManifestPath, "utf8") : void 0;
1513
+ const localManifestPath = join17(targetDir, ".project.json");
1514
+ const localManifestContent = existsSync11(localManifestPath) ? readFileSync15(localManifestPath, "utf8") : void 0;
1515
1515
  const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
1516
1516
  const localId = localManifest?.project_id ?? localManifest?.project_slug;
1517
1517
  const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
@@ -1539,7 +1539,7 @@ function planProjectInit(input) {
1539
1539
  const recordedTypeValue = existing?.template?.commonproject?.project_type;
1540
1540
  const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
1541
1541
  const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
1542
- const freshRender = scaffold && localManifest === void 0 && !existsSync11(join16(targetDir, ".copier-answers.yml"));
1542
+ const freshRender = scaffold && localManifest === void 0 && !existsSync11(join17(targetDir, ".copier-answers.yml"));
1543
1543
  if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
1544
1544
  const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
1545
1545
  if (!adoptingPlugin) {
@@ -1637,7 +1637,7 @@ function planProjectInit(input) {
1637
1637
  }));
1638
1638
  }
1639
1639
  actions.push(
1640
- { kind: "project.write-manifest", path: join16(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1640
+ { kind: "project.write-manifest", path: join17(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1641
1641
  {
1642
1642
  kind: "ticket-provider.create-or-link",
1643
1643
  enabled: boardEnabled,
@@ -1692,12 +1692,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1692
1692
  plan.manifest.ticket_provider = manifestProvider;
1693
1693
  action.boardId = boardId;
1694
1694
  action.state = manifestProvider.state;
1695
- const manifestPath = join16(action.repoPath, ".project.json");
1695
+ const manifestPath = join17(action.repoPath, ".project.json");
1696
1696
  let next;
1697
1697
  if (existsSync11(manifestPath)) {
1698
1698
  let existing = {};
1699
1699
  try {
1700
- const parsed = JSON.parse(readFileSync14(manifestPath, "utf8"));
1700
+ const parsed = JSON.parse(readFileSync15(manifestPath, "utf8"));
1701
1701
  if (isRecord2(parsed)) existing = parsed;
1702
1702
  } catch {
1703
1703
  existing = {};
@@ -1705,13 +1705,13 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1705
1705
  const existingProvider = isRecord2(existing.ticket_provider) ? existing.ticket_provider : {};
1706
1706
  next = { ...existing, ticket_provider: { ...existingProvider, ...manifestProvider } };
1707
1707
  } else {
1708
- mkdirSync7(dirname13(manifestPath), { recursive: true });
1708
+ mkdirSync8(dirname13(manifestPath), { recursive: true });
1709
1709
  next = plan.manifest;
1710
1710
  }
1711
1711
  const text = `${JSON.stringify(next, null, 2)}
1712
1712
  `;
1713
- if (!existsSync11(manifestPath) || readFileSync14(manifestPath, "utf8") !== text) {
1714
- writeFileSync9(manifestPath, text, "utf8");
1713
+ if (!existsSync11(manifestPath) || readFileSync15(manifestPath, "utf8") !== text) {
1714
+ writeFileSync10(manifestPath, text, "utf8");
1715
1715
  return [manifestPath];
1716
1716
  }
1717
1717
  return [];
@@ -1739,7 +1739,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1739
1739
  logs.push(
1740
1740
  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"
1741
1741
  );
1742
- mkdirSync7(dirname13(action.targetDir), { recursive: true });
1742
+ mkdirSync8(dirname13(action.targetDir), { recursive: true });
1743
1743
  const before = snapshotTree(action.targetDir);
1744
1744
  const copierExecutable = options.trustedCopier?.executable ?? action.command[0];
1745
1745
  const copierEnv = { ...process.env };
@@ -1771,22 +1771,22 @@ async function executeProjectInitPlan(plan, options = {}) {
1771
1771
  break;
1772
1772
  }
1773
1773
  const projectType = action.data.project_type ?? "base";
1774
- const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join16(action.targetDir, rel)));
1774
+ const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync11(join17(action.targetDir, rel)));
1775
1775
  if (missing.length) {
1776
1776
  errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
1777
1777
  break;
1778
1778
  }
1779
1779
  if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
1780
1780
  } else if (action.kind === "project.write-manifest") {
1781
- if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync14(action.path, "utf8") !== action.expectedContent)) {
1781
+ if (action.expectedContent !== void 0 && (!existsSync11(action.path) || readFileSync15(action.path, "utf8") !== action.expectedContent)) {
1782
1782
  errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
1783
1783
  break;
1784
1784
  }
1785
- mkdirSync7(dirname13(action.path), { recursive: true });
1785
+ mkdirSync8(dirname13(action.path), { recursive: true });
1786
1786
  let value = action.manifest;
1787
1787
  if (existsSync11(action.path)) {
1788
1788
  try {
1789
- const currentValue = JSON.parse(readFileSync14(action.path, "utf8"));
1789
+ const currentValue = JSON.parse(readFileSync15(action.path, "utf8"));
1790
1790
  if (isRecord2(currentValue)) {
1791
1791
  const currentNotebook = isRecord2(currentValue.notebook) ? currentValue.notebook : {};
1792
1792
  const desiredNotebook = isRecord2(value.notebook) ? value.notebook : {};
@@ -1814,9 +1814,9 @@ async function executeProjectInitPlan(plan, options = {}) {
1814
1814
  value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
1815
1815
  const next = `${JSON.stringify(value, null, 2)}
1816
1816
  `;
1817
- const current = existsSync11(action.path) ? readFileSync14(action.path, "utf8") : void 0;
1817
+ const current = existsSync11(action.path) ? readFileSync15(action.path, "utf8") : void 0;
1818
1818
  if (current !== next) {
1819
- writeFileSync9(action.path, next, "utf8");
1819
+ writeFileSync10(action.path, next, "utf8");
1820
1820
  changedFiles.push(action.path);
1821
1821
  }
1822
1822
  changedFiles.push(...synchronizeCopierIdentity(action.path, action.manifest));
@@ -1897,7 +1897,7 @@ function getProject(registry, slug) {
1897
1897
  return project;
1898
1898
  }
1899
1899
  function buildCommonProjectCopierAction(input) {
1900
- const templateDir = join16(input.pjanglerRoot, "templates", "commonproject");
1900
+ const templateDir = join17(input.pjanglerRoot, "templates", "commonproject");
1901
1901
  const data = {
1902
1902
  project_name: input.projectName,
1903
1903
  project_description: input.projectDescription ?? "",
@@ -1928,7 +1928,7 @@ function buildCommonProjectCopierAction(input) {
1928
1928
  function resolvePjanglerRoot() {
1929
1929
  let dir = dirname13(fileURLToPath2(import.meta.url));
1930
1930
  while (dir !== dirname13(dir)) {
1931
- if (existsSync11(join16(dir, "package.json")) && existsSync11(join16(dir, "templates", "commonproject", "copier.yml"))) return dir;
1931
+ if (existsSync11(join17(dir, "package.json")) && existsSync11(join17(dir, "templates", "commonproject", "copier.yml"))) return dir;
1932
1932
  dir = dirname13(dir);
1933
1933
  }
1934
1934
  return resolve10(process.cwd());
@@ -2005,7 +2005,7 @@ function validateProjectRecord(project, key) {
2005
2005
  }
2006
2006
  function expandHome(path) {
2007
2007
  if (path === "~") return homedir5();
2008
- if (path.startsWith("~/")) return join16(homedir5(), path.slice(2));
2008
+ if (path.startsWith("~/")) return join17(homedir5(), path.slice(2));
2009
2009
  return path;
2010
2010
  }
2011
2011
  function isRecord2(value) {
@@ -2032,7 +2032,7 @@ var init_project = __esm({
2032
2032
  "obsidian-plugin": ["typescript", "javascript"]
2033
2033
  };
2034
2034
  PROJECT_TYPE_SENTINELS = {
2035
- "obsidian-plugin": ["manifest.json", join16("src", "main.ts")]
2035
+ "obsidian-plugin": ["manifest.json", join17("src", "main.ts")]
2036
2036
  };
2037
2037
  BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
2038
2038
  PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
@@ -2041,8 +2041,8 @@ var init_project = __esm({
2041
2041
  IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
2042
2042
  DEFAULT_SOURCE_SKILL_ROOTS = [
2043
2043
  "/home/delorenj/code/skillex/all-skills",
2044
- join16(homedir5(), ".agents", "skills"),
2045
- join16(homedir5(), ".codex", "skills")
2044
+ join17(homedir5(), ".agents", "skills"),
2045
+ join17(homedir5(), ".codex", "skills")
2046
2046
  ];
2047
2047
  PROJECT_REGISTRY_OWNED_KEYS = [
2048
2048
  "name",
@@ -2074,7 +2074,7 @@ var init_project = __esm({
2074
2074
  import { randomUUID } from "node:crypto";
2075
2075
  import { isDeepStrictEqual } from "node:util";
2076
2076
  import { isIP as isIP2 } from "node:net";
2077
- import { closeSync as closeSync3, existsSync as existsSync12, fchmodSync as fchmodSync2, fsyncSync as fsyncSync2, lstatSync as lstatSync12, openSync as openSync3, readFileSync as readFileSync15, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "node:fs";
2077
+ import { closeSync as closeSync4, existsSync as existsSync12, fchmodSync as fchmodSync3, fsyncSync as fsyncSync2, lstatSync as lstatSync13, openSync as openSync4, readFileSync as readFileSync16, realpathSync as realpathSync7, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "node:fs";
2078
2078
  import { dirname as dirname14, resolve as resolve11 } from "node:path";
2079
2079
  function isRecord3(value) {
2080
2080
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -2088,7 +2088,7 @@ function readManifest(repoPath) {
2088
2088
  if (!existsSync12(path)) return null;
2089
2089
  let parsed;
2090
2090
  try {
2091
- parsed = JSON.parse(readFileSync15(path, "utf8"));
2091
+ parsed = JSON.parse(readFileSync16(path, "utf8"));
2092
2092
  } catch {
2093
2093
  throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
2094
2094
  }
@@ -2330,7 +2330,7 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2330
2330
  const changed = [];
2331
2331
  const manifestPath = resolve11(resolved.project.repo_path, ".project.json");
2332
2332
  if (isRegistryServiceLocation(resolved.registry_path)) {
2333
- const before = existsSync12(manifestPath) ? readFileSync15(manifestPath, "utf8") : null;
2333
+ const before = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
2334
2334
  const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
2335
2335
  const nextProject = {
2336
2336
  ...resolved.project,
@@ -2342,10 +2342,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2342
2342
  resolved.registry = loadProjectRegistry(resolved.registry_path);
2343
2343
  resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
2344
2344
  resolved.manifest = readManifest(nextProject.repo_path);
2345
- const after = existsSync12(manifestPath) ? readFileSync15(manifestPath, "utf8") : null;
2345
+ const after = existsSync12(manifestPath) ? readFileSync16(manifestPath, "utf8") : null;
2346
2346
  return before === after ? [] : [manifestPath];
2347
2347
  }
2348
- const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync15(manifestPath, "utf8")) : {};
2348
+ const manifestRaw = existsSync12(manifestPath) ? JSON.parse(readFileSync16(manifestPath, "utf8")) : {};
2349
2349
  if (!isRecord3(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
2350
2350
  validateManifestNotebookSurface(manifestRaw.notebook);
2351
2351
  const manifestNotebook2 = isRecord3(manifestRaw.notebook) ? manifestRaw.notebook : {};
@@ -2378,29 +2378,29 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2378
2378
  };
2379
2379
  const manifestText = `${JSON.stringify(manifestNext, null, 2)}
2380
2380
  `;
2381
- if (!existsSync12(manifestPath) || readFileSync15(manifestPath, "utf8") !== manifestText) {
2381
+ if (!existsSync12(manifestPath) || readFileSync16(manifestPath, "utf8") !== manifestText) {
2382
2382
  let mode = 420;
2383
2383
  if (existsSync12(manifestPath)) {
2384
- const current2 = lstatSync12(manifestPath);
2384
+ const current2 = lstatSync13(manifestPath);
2385
2385
  if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
2386
2386
  mode = current2.mode & 511;
2387
2387
  }
2388
2388
  const temp = resolve11(dirname14(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
2389
- const fd = openSync3(temp, "wx", mode);
2389
+ const fd = openSync4(temp, "wx", mode);
2390
2390
  try {
2391
- writeFileSync10(fd, manifestText, "utf8");
2392
- fchmodSync2(fd, mode);
2391
+ writeFileSync11(fd, manifestText, "utf8");
2392
+ fchmodSync3(fd, mode);
2393
2393
  fsyncSync2(fd);
2394
2394
  } finally {
2395
- closeSync3(fd);
2395
+ closeSync4(fd);
2396
2396
  }
2397
2397
  renameSync6(temp, manifestPath);
2398
2398
  try {
2399
- const directory = openSync3(dirname14(manifestPath), "r");
2399
+ const directory = openSync4(dirname14(manifestPath), "r");
2400
2400
  try {
2401
2401
  fsyncSync2(directory);
2402
2402
  } finally {
2403
- closeSync3(directory);
2403
+ closeSync4(directory);
2404
2404
  }
2405
2405
  } catch {
2406
2406
  }
@@ -2409,9 +2409,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2409
2409
  const current = resolved.project.notebook;
2410
2410
  resolved.project.notebook = { ...isRecord3(current) ? current : { state: "planned" }, ...binding };
2411
2411
  const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
2412
- const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync15(resolved.registry_path, "utf8") : null;
2412
+ const registryBefore = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
2413
2413
  saveProjectRegistry(resolved.registry, resolved.registry_path);
2414
- const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync15(resolved.registry_path, "utf8") : null;
2414
+ const registryAfter = fileRegistry && existsSync12(resolved.registry_path) ? readFileSync16(resolved.registry_path, "utf8") : null;
2415
2415
  if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
2416
2416
  resolved.manifest = manifestNext;
2417
2417
  return changed;
@@ -2444,7 +2444,7 @@ var init_config = __esm({
2444
2444
  });
2445
2445
 
2446
2446
  // src/notebook/notes.ts
2447
- import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
2447
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:crypto";
2448
2448
  function canonicalJson2(value) {
2449
2449
  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");
2450
2450
  if (value === null || typeof value !== "object") {
@@ -2457,7 +2457,7 @@ function canonicalJson2(value) {
2457
2457
  return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
2458
2458
  }
2459
2459
  function sha256Hex(value) {
2460
- return createHash9("sha256").update(value).digest("hex");
2460
+ return createHash10("sha256").update(value).digest("hex");
2461
2461
  }
2462
2462
  function base64UrlEncode(value) {
2463
2463
  return Buffer.from(value, "utf8").toString("base64url");
@@ -2714,27 +2714,27 @@ var init_remote_mutation_schema = __esm({
2714
2714
  });
2715
2715
 
2716
2716
  // src/notebook/state.ts
2717
- import { createHash as createHash10, randomUUID as randomUUID3 } from "node:crypto";
2717
+ import { createHash as createHash11, randomUUID as randomUUID3 } from "node:crypto";
2718
2718
  import {
2719
- closeSync as closeSync4,
2720
- constants as constants2,
2719
+ closeSync as closeSync5,
2720
+ constants as constants3,
2721
2721
  existsSync as existsSync13,
2722
- fchmodSync as fchmodSync3,
2723
- fstatSync,
2722
+ fchmodSync as fchmodSync4,
2723
+ fstatSync as fstatSync2,
2724
2724
  fsyncSync as fsyncSync3,
2725
- lstatSync as lstatSync13,
2726
- mkdirSync as mkdirSync8,
2727
- openSync as openSync4,
2725
+ lstatSync as lstatSync14,
2726
+ mkdirSync as mkdirSync9,
2727
+ openSync as openSync5,
2728
2728
  readSync,
2729
- readdirSync as readdirSync9,
2729
+ readdirSync as readdirSync10,
2730
2730
  renameSync as renameSync7,
2731
- unlinkSync as unlinkSync7,
2732
- writeFileSync as writeFileSync11
2731
+ unlinkSync as unlinkSync8,
2732
+ writeFileSync as writeFileSync12
2733
2733
  } from "node:fs";
2734
2734
  import { homedir as homedir6 } from "node:os";
2735
- import { basename as basename8, dirname as dirname15, join as join17, parse, relative as relative12, resolve as resolve12, sep as sep4 } from "node:path";
2735
+ import { basename as basename9, dirname as dirname15, join as join18, parse, relative as relative12, resolve as resolve12, sep as sep4 } from "node:path";
2736
2736
  function notebookStateRoot(env2 = process.env) {
2737
- const base = env2.XDG_STATE_HOME || join17(env2.HOME || homedir6(), ".local", "state");
2737
+ const base = env2.XDG_STATE_HOME || join18(env2.HOME || homedir6(), ".local", "state");
2738
2738
  return resolve12(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
2739
2739
  }
2740
2740
  function assertDigest(value, label) {
@@ -2742,7 +2742,7 @@ function assertDigest(value, label) {
2742
2742
  }
2743
2743
  function projectStateDir(root, projectSlug) {
2744
2744
  if (!/^[a-z0-9][a-z0-9._-]{0,127}$/iu.test(projectSlug)) throw new NotebookError("INVALID_INPUT", "Invalid project slug for Notebook state");
2745
- return join17(resolve12(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
2745
+ return join18(resolve12(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
2746
2746
  }
2747
2747
  function assertContained(root, candidate) {
2748
2748
  const rel = relative12(resolve12(root), resolve12(candidate));
@@ -2755,33 +2755,33 @@ function openPinnedDirectory(path, root, create) {
2755
2755
  assertContained(absoluteRoot, absolute);
2756
2756
  if (!existsSync13("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
2757
2757
  const parsed = parse(absolute);
2758
- let fd = openSync4(parsed.root, DIRECTORY_OPEN_FLAGS);
2758
+ let fd = openSync5(parsed.root, DIRECTORY_OPEN_FLAGS);
2759
2759
  let cursor = parsed.root;
2760
2760
  try {
2761
2761
  for (const part of absolute.slice(parsed.root.length).split(sep4).filter(Boolean)) {
2762
2762
  const child = `/proc/self/fd/${fd}/${part}`;
2763
- cursor = join17(cursor, part);
2763
+ cursor = join18(cursor, part);
2764
2764
  let childFd;
2765
2765
  try {
2766
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
2766
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
2767
2767
  } catch (error) {
2768
2768
  if (!create || error.code !== "ENOENT") throw error;
2769
2769
  try {
2770
- mkdirSync8(child, { mode: 448 });
2770
+ mkdirSync9(child, { mode: 448 });
2771
2771
  } catch (mkdirError) {
2772
2772
  if (mkdirError.code !== "EEXIST") throw mkdirError;
2773
2773
  }
2774
- childFd = openSync4(child, DIRECTORY_OPEN_FLAGS);
2774
+ childFd = openSync5(child, DIRECTORY_OPEN_FLAGS);
2775
2775
  }
2776
- closeSync4(fd);
2776
+ closeSync5(fd);
2777
2777
  fd = childFd;
2778
- const stat = fstatSync(fd);
2778
+ const stat = fstatSync2(fd);
2779
2779
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state path component is not a directory");
2780
2780
  if (cursor === absoluteRoot || relative12(absoluteRoot, cursor).startsWith("..") === false) assertOwned(stat);
2781
2781
  }
2782
2782
  return fd;
2783
2783
  } catch (error) {
2784
- closeSync4(fd);
2784
+ closeSync5(fd);
2785
2785
  if (error.code === "ELOOP" || error.code === "ENOTDIR") {
2786
2786
  throw new NotebookError("INTERNAL_ERROR", "Notebook state path contains a symlink component");
2787
2787
  }
@@ -2789,7 +2789,7 @@ function openPinnedDirectory(path, root, create) {
2789
2789
  }
2790
2790
  }
2791
2791
  function pinnedLeaf(parentFd, path) {
2792
- return `/proc/self/fd/${parentFd}/${basename8(path)}`;
2792
+ return `/proc/self/fd/${parentFd}/${basename9(path)}`;
2793
2793
  }
2794
2794
  function assertOwned(stat) {
2795
2795
  if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
@@ -2802,13 +2802,13 @@ function ensureDirectory(path, root) {
2802
2802
  assertContained(absoluteRoot, absolute);
2803
2803
  const fd = openPinnedDirectory(absolute, absoluteRoot, true);
2804
2804
  try {
2805
- const stat = fstatSync(fd);
2805
+ const stat = fstatSync2(fd);
2806
2806
  if (!stat.isDirectory()) throw new NotebookError("INTERNAL_ERROR", "Notebook state directory is not a real directory");
2807
2807
  assertOwned(stat);
2808
- fchmodSync3(fd, 448);
2808
+ fchmodSync4(fd, 448);
2809
2809
  fsyncSync3(fd);
2810
2810
  } finally {
2811
- closeSync4(fd);
2811
+ closeSync5(fd);
2812
2812
  }
2813
2813
  }
2814
2814
  function notebookStatePaths(root, projectSlug) {
@@ -2817,18 +2817,18 @@ function notebookStatePaths(root, projectSlug) {
2817
2817
  return {
2818
2818
  root: absoluteRoot,
2819
2819
  project,
2820
- baselines: join17(project, "baselines"),
2821
- claims: join17(project, "claims"),
2822
- receipts: join17(project, "receipts"),
2823
- refusals: join17(project, "refusals"),
2824
- journals: join17(project, "journals"),
2825
- locks: join17(project, "locks")
2820
+ baselines: join18(project, "baselines"),
2821
+ claims: join18(project, "claims"),
2822
+ receipts: join18(project, "receipts"),
2823
+ refusals: join18(project, "refusals"),
2824
+ journals: join18(project, "journals"),
2825
+ locks: join18(project, "locks")
2826
2826
  };
2827
2827
  }
2828
2828
  function ensureNotebookState(root, projectSlug) {
2829
2829
  const paths = notebookStatePaths(root, projectSlug);
2830
2830
  const { root: absoluteRoot, project } = paths;
2831
- for (const path of [absoluteRoot, join17(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
2831
+ for (const path of [absoluteRoot, join18(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
2832
2832
  ensureDirectory(path, absoluteRoot);
2833
2833
  }
2834
2834
  return paths;
@@ -2844,15 +2844,15 @@ function fsyncDirectory2(path) {
2844
2844
  fsyncSync3(fd);
2845
2845
  } catch {
2846
2846
  } finally {
2847
- if (fd !== void 0) closeSync4(fd);
2847
+ if (fd !== void 0) closeSync5(fd);
2848
2848
  }
2849
2849
  }
2850
2850
  function readStateDirectory(path, root) {
2851
2851
  const fd = openPinnedDirectory(path, root, false);
2852
2852
  try {
2853
- return readdirSync9(`/proc/self/fd/${fd}`, { withFileTypes: true });
2853
+ return readdirSync10(`/proc/self/fd/${fd}`, { withFileTypes: true });
2854
2854
  } finally {
2855
- closeSync4(fd);
2855
+ closeSync5(fd);
2856
2856
  }
2857
2857
  }
2858
2858
  function readNotebookStateDirectory(path, root) {
@@ -2866,22 +2866,22 @@ function unlinkStateFile(path, root, allowMissing = false) {
2866
2866
  let fileFd;
2867
2867
  try {
2868
2868
  try {
2869
- fileFd = openSync4(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
2869
+ fileFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
2870
2870
  } catch (error) {
2871
2871
  if (allowMissing && error.code === "ENOENT") return false;
2872
2872
  throw error;
2873
2873
  }
2874
- const stat = fstatSync(fileFd);
2874
+ const stat = fstatSync2(fileFd);
2875
2875
  if (!stat.isFile() || (stat.mode & 511) !== 384) {
2876
2876
  throw new NotebookError("CONFLICT", "Refusing to remove a suspect Notebook state entry; run pj notebook audit --json");
2877
2877
  }
2878
2878
  assertOwned(stat);
2879
- unlinkSync7(target);
2879
+ unlinkSync8(target);
2880
2880
  fsyncSync3(parentFd);
2881
2881
  return true;
2882
2882
  } finally {
2883
- if (fileFd !== void 0) closeSync4(fileFd);
2884
- closeSync4(parentFd);
2883
+ if (fileFd !== void 0) closeSync5(fileFd);
2884
+ closeSync5(parentFd);
2885
2885
  }
2886
2886
  }
2887
2887
  function renameStateFile(source, target, root) {
@@ -2891,12 +2891,12 @@ function renameStateFile(source, target, root) {
2891
2891
  const targetPath = pinnedLeaf(parentFd, target);
2892
2892
  let sourceFd;
2893
2893
  try {
2894
- sourceFd = openSync4(sourcePath, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
2895
- const stat = fstatSync(sourceFd);
2894
+ sourceFd = openSync5(sourcePath, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
2895
+ const stat = fstatSync2(sourceFd);
2896
2896
  if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
2897
2897
  assertOwned(stat);
2898
2898
  try {
2899
- lstatSync13(targetPath);
2899
+ lstatSync14(targetPath);
2900
2900
  throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
2901
2901
  } catch (error) {
2902
2902
  if (error.code !== "ENOENT") throw error;
@@ -2904,8 +2904,8 @@ function renameStateFile(source, target, root) {
2904
2904
  renameSync7(sourcePath, targetPath);
2905
2905
  fsyncSync3(parentFd);
2906
2906
  } finally {
2907
- if (sourceFd !== void 0) closeSync4(sourceFd);
2908
- closeSync4(parentFd);
2907
+ if (sourceFd !== void 0) closeSync5(sourceFd);
2908
+ closeSync5(parentFd);
2909
2909
  }
2910
2910
  }
2911
2911
  function atomicWriteJson(path, value, root, afterParentPinned) {
@@ -2914,34 +2914,34 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
2914
2914
  const text = jsonLine(value);
2915
2915
  const parentFd = openPinnedDirectory(dirname15(path), root, false);
2916
2916
  const target = pinnedLeaf(parentFd, path);
2917
- const tempName = `.${basename8(path)}.${process.pid}.${randomUUID3()}.tmp`;
2917
+ const tempName = `.${basename9(path)}.${process.pid}.${randomUUID3()}.tmp`;
2918
2918
  const temp = `/proc/self/fd/${parentFd}/${tempName}`;
2919
2919
  let existingIdentity = null;
2920
2920
  try {
2921
2921
  afterParentPinned?.();
2922
2922
  try {
2923
- const existingFd = openSync4(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
2923
+ const existingFd = openSync5(target, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
2924
2924
  try {
2925
- const existing = fstatSync(existingFd);
2925
+ const existing = fstatSync2(existingFd);
2926
2926
  if (!existing.isFile()) throw new NotebookError("INTERNAL_ERROR", "Refusing to replace a non-regular Notebook state file");
2927
2927
  assertOwned(existing);
2928
2928
  if ((existing.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Refusing to replace a Notebook state file with unsafe permissions");
2929
2929
  existingIdentity = { dev: existing.dev, ino: existing.ino };
2930
2930
  } finally {
2931
- closeSync4(existingFd);
2931
+ closeSync5(existingFd);
2932
2932
  }
2933
2933
  } catch (error) {
2934
2934
  if (error.code !== "ENOENT") throw error;
2935
2935
  }
2936
- const fd = openSync4(temp, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
2936
+ const fd = openSync5(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
2937
2937
  try {
2938
- writeFileSync11(fd, text, "utf8");
2938
+ writeFileSync12(fd, text, "utf8");
2939
2939
  fsyncSync3(fd);
2940
2940
  } finally {
2941
- closeSync4(fd);
2941
+ closeSync5(fd);
2942
2942
  }
2943
2943
  try {
2944
- const current = lstatSync13(target);
2944
+ const current = lstatSync14(target);
2945
2945
  if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
2946
2946
  throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
2947
2947
  }
@@ -2952,12 +2952,12 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
2952
2952
  fsyncSync3(parentFd);
2953
2953
  } catch (error) {
2954
2954
  try {
2955
- unlinkSync7(temp);
2955
+ unlinkSync8(temp);
2956
2956
  } catch {
2957
2957
  }
2958
2958
  throw error;
2959
2959
  } finally {
2960
- closeSync4(parentFd);
2960
+ closeSync5(parentFd);
2961
2961
  }
2962
2962
  return Buffer.byteLength(text, "utf8");
2963
2963
  }
@@ -2967,22 +2967,22 @@ function exclusiveWrite(path, text, root) {
2967
2967
  const target = pinnedLeaf(parentFd, path);
2968
2968
  let fd;
2969
2969
  try {
2970
- fd = openSync4(target, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0), 384);
2970
+ fd = openSync5(target, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
2971
2971
  } catch (error) {
2972
- closeSync4(parentFd);
2972
+ closeSync5(parentFd);
2973
2973
  if (error.code === "EEXIST") return false;
2974
2974
  throw error;
2975
2975
  }
2976
2976
  try {
2977
- writeFileSync11(fd, text, "utf8");
2977
+ writeFileSync12(fd, text, "utf8");
2978
2978
  fsyncSync3(fd);
2979
2979
  } finally {
2980
- closeSync4(fd);
2980
+ closeSync5(fd);
2981
2981
  }
2982
2982
  try {
2983
2983
  fsyncSync3(parentFd);
2984
2984
  } finally {
2985
- closeSync4(parentFd);
2985
+ closeSync5(parentFd);
2986
2986
  }
2987
2987
  return true;
2988
2988
  }
@@ -3126,13 +3126,13 @@ function safeReadJson(path, maxBytes) {
3126
3126
  }
3127
3127
  let fd;
3128
3128
  try {
3129
- fd = openSync4(pinnedLeaf(parentFd, path), constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
3129
+ fd = openSync5(pinnedLeaf(parentFd, path), constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
3130
3130
  } catch (error) {
3131
- closeSync4(parentFd);
3131
+ closeSync5(parentFd);
3132
3132
  return { reason: error.code === "ELOOP" ? "non-regular" : "unreadable", bytes: 0 };
3133
3133
  }
3134
3134
  try {
3135
- const stat = fstatSync(fd);
3135
+ const stat = fstatSync2(fd);
3136
3136
  if (!stat.isFile()) return { reason: "non-regular", bytes: 0 };
3137
3137
  if ((stat.mode & 511) !== 384) return { reason: "unsafe-permissions", bytes: stat.size };
3138
3138
  try {
@@ -3152,14 +3152,14 @@ function safeReadJson(path, maxBytes) {
3152
3152
  if (total > maxBytes) return { reason: "oversize", bytes: total };
3153
3153
  chunks.push(chunk.subarray(0, count));
3154
3154
  }
3155
- const after = fstatSync(fd);
3155
+ const after = fstatSync2(fd);
3156
3156
  if (after.size !== stat.size || total !== stat.size) return { reason: "unreadable", bytes: total };
3157
3157
  return { value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, total))), bytes: total };
3158
3158
  } catch (error) {
3159
3159
  return { reason: error instanceof SyntaxError || error instanceof TypeError ? "invalid-json" : "unreadable", bytes: 0 };
3160
3160
  } finally {
3161
- closeSync4(fd);
3162
- closeSync4(parentFd);
3161
+ closeSync5(fd);
3162
+ closeSync5(parentFd);
3163
3163
  }
3164
3164
  }
3165
3165
  function safeEntryId(kind, name) {
@@ -3176,10 +3176,10 @@ function baselineReceiptByteCeiling(limits) {
3176
3176
  function readSessionBaseline(root, projectSlug, sessionKey, limits) {
3177
3177
  assertDigest(sessionKey, "session key");
3178
3178
  const paths = ensureNotebookState(root, projectSlug);
3179
- return readBaseline(join17(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3179
+ return readBaseline(join18(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3180
3180
  }
3181
3181
  function acquireLock(paths, maxWaitMs) {
3182
- const lock = join17(paths.locks, "admission.lock");
3182
+ const lock = join18(paths.locks, "admission.lock");
3183
3183
  const deadline = Date.now() + Math.max(1, maxWaitMs);
3184
3184
  const token = randomUUID3();
3185
3185
  const record3 = () => ({
@@ -3209,7 +3209,7 @@ function acquireLock(paths, maxWaitMs) {
3209
3209
  throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
3210
3210
  }
3211
3211
  if (Date.now() >= expiresAt) {
3212
- const recovery = join17(paths.locks, `recovery-${heldToken}.lock`);
3212
+ const recovery = join18(paths.locks, `recovery-${heldToken}.lock`);
3213
3213
  const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
3214
3214
  if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
3215
3215
  try {
@@ -3217,7 +3217,7 @@ function acquireLock(paths, maxWaitMs) {
3217
3217
  const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
3218
3218
  const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
3219
3219
  if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
3220
- const recovered = join17(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3220
+ const recovered = join18(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3221
3221
  renameStateFile(lock, recovered, paths.root);
3222
3222
  unlinkStateFile(recovered, paths.root);
3223
3223
  }
@@ -3297,7 +3297,7 @@ function scanAuxiliaryState(paths, limits) {
3297
3297
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3298
3298
  continue;
3299
3299
  }
3300
- const read = safeReadJson(join17(specification.dir, entry.name), specification.maxBytes);
3300
+ const read = safeReadJson(join18(specification.dir, entry.name), specification.maxBytes);
3301
3301
  if (read.reason || read.value === void 0) {
3302
3302
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3303
3303
  continue;
@@ -3322,7 +3322,7 @@ function scanAuxiliaryState(paths, limits) {
3322
3322
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3323
3323
  continue;
3324
3324
  }
3325
- const read = safeReadJson(join17(paths.journals, entry.name), limits.receipt_max_bytes);
3325
+ const read = safeReadJson(join18(paths.journals, entry.name), limits.receipt_max_bytes);
3326
3326
  if (read.reason || read.value === void 0) {
3327
3327
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3328
3328
  continue;
@@ -3351,7 +3351,7 @@ function scanAuxiliaryState(paths, limits) {
3351
3351
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3352
3352
  continue;
3353
3353
  }
3354
- const read = safeReadJson(join17(paths.locks, entry.name), 8192);
3354
+ const read = safeReadJson(join18(paths.locks, entry.name), 8192);
3355
3355
  if (read.reason || read.value === void 0) {
3356
3356
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3357
3357
  continue;
@@ -3390,7 +3390,7 @@ function scanReceipts(paths, limits) {
3390
3390
  }
3391
3391
  for (const entry of entries) {
3392
3392
  const entryId = safeEntryId("receipts", entry.name);
3393
- const path = join17(paths.receipts, entry.name);
3393
+ const path = join18(paths.receipts, entry.name);
3394
3394
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
3395
3395
  addIntegrity({ entry_id: entryId, reason: "non-regular" });
3396
3396
  continue;
@@ -3420,7 +3420,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
3420
3420
  if (!existsSync13(paths.baselines)) return { current, stale };
3421
3421
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
3422
3422
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3423
- const baseline = readBaseline(join17(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
3423
+ const baseline = readBaseline(join18(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
3424
3424
  if (!baseline || referenced.has(baseline.session_key)) continue;
3425
3425
  const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
3426
3426
  if (nowMs >= expires) stale += 1;
@@ -3434,7 +3434,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
3434
3434
  const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
3435
3435
  for (const entry of entries) {
3436
3436
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3437
- const read = safeReadJson(join17(paths.refusals, entry.name), limits.receipt_max_bytes);
3437
+ const read = safeReadJson(join18(paths.refusals, entry.name), limits.receipt_max_bytes);
3438
3438
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
3439
3439
  if (!marker || `${marker.session_key}.json` !== entry.name) continue;
3440
3440
  if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
@@ -3517,7 +3517,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
3517
3517
  function readCaptureReceipt(root, projectSlug, receiptId, limits) {
3518
3518
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
3519
3519
  const paths = notebookStatePaths(root, projectSlug);
3520
- const path = join17(paths.receipts, `${receiptId}.json`);
3520
+ const path = join18(paths.receipts, `${receiptId}.json`);
3521
3521
  if (!existsSync13(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
3522
3522
  const read = safeReadJson(path, limits.receipt_max_bytes);
3523
3523
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -3530,7 +3530,7 @@ function writeReceipt(paths, receipt, limit) {
3530
3530
  const bytes = Buffer.byteLength(text, "utf8");
3531
3531
  if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
3532
3532
  if (receipt.serialized_bytes === bytes) {
3533
- atomicWriteJson(join17(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
3533
+ atomicWriteJson(join18(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
3534
3534
  return receipt;
3535
3535
  }
3536
3536
  receipt.serialized_bytes = bytes;
@@ -3669,25 +3669,25 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
3669
3669
  const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
3670
3670
  for (const receipt of scan.receipts) {
3671
3671
  if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
3672
- const path = join17(paths.receipts, `${receipt.receipt_id}.json`);
3672
+ const path = join18(paths.receipts, `${receipt.receipt_id}.json`);
3673
3673
  unlinkStateFile(path, paths.root);
3674
- removed.push(safeEntryId("receipts", basename8(path)));
3674
+ removed.push(safeEntryId("receipts", basename9(path)));
3675
3675
  }
3676
3676
  const remaining = scanReceipts(paths, limits);
3677
3677
  const remainingAuxiliary = scanAuxiliaryState(paths, limits);
3678
3678
  const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
3679
3679
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
3680
3680
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3681
- const path = join17(paths.baselines, entry.name);
3681
+ const path = join18(paths.baselines, entry.name);
3682
3682
  const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
3683
3683
  if (!baseline || referenced.has(baseline.session_key)) continue;
3684
3684
  if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
3685
3685
  unlinkStateFile(path, paths.root);
3686
3686
  removed.push(safeEntryId("baselines", entry.name));
3687
- const claim = join17(paths.claims, `${baseline.session_key}.overview`);
3688
- if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename8(claim)));
3689
- const refusal = join17(paths.refusals, `${baseline.session_key}.json`);
3690
- if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename8(refusal)));
3687
+ const claim = join18(paths.claims, `${baseline.session_key}.overview`);
3688
+ if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename9(claim)));
3689
+ const refusal = join18(paths.refusals, `${baseline.session_key}.json`);
3690
+ if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename9(refusal)));
3691
3691
  }
3692
3692
  fsyncDirectory2(paths.project);
3693
3693
  return removed;
@@ -3733,7 +3733,7 @@ var init_state = __esm({
3733
3733
  "REMOTE_PROTOCOL_ERROR",
3734
3734
  "INTERNAL_ERROR"
3735
3735
  ]);
3736
- DIRECTORY_OPEN_FLAGS = constants2.O_RDONLY | (constants2.O_DIRECTORY ?? 0) | (constants2.O_NOFOLLOW ?? 0);
3736
+ DIRECTORY_OPEN_FLAGS = constants3.O_RDONLY | (constants3.O_DIRECTORY ?? 0) | (constants3.O_NOFOLLOW ?? 0);
3737
3737
  }
3738
3738
  });
3739
3739
 
@@ -4072,8 +4072,8 @@ var init_open_notebook_client = __esm({
4072
4072
 
4073
4073
  // src/notebook/git-evidence.ts
4074
4074
  import { spawnSync as spawnSync8 } from "node:child_process";
4075
- import { closeSync as closeSync5, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync14, openSync as openSync5, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
4076
- import { extname, join as join19, relative as relative13, resolve as resolve13, sep as sep5 } from "node:path";
4075
+ import { closeSync as closeSync6, constants as constants4, fstatSync as fstatSync3, lstatSync as lstatSync15, openSync as openSync6, readSync as readSync2, realpathSync as realpathSync8 } from "node:fs";
4076
+ import { extname, join as join20, relative as relative13, resolve as resolve13, sep as sep5 } from "node:path";
4077
4077
  function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
4078
4078
  const result2 = spawnSync8("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
4079
4079
  return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
@@ -4151,9 +4151,9 @@ function hasSymlinkComponent(root, candidate) {
4151
4151
  const rel = relative13(root, candidate);
4152
4152
  let cursor = root;
4153
4153
  for (const part of rel.split(sep5).filter(Boolean)) {
4154
- cursor = join19(cursor, part);
4154
+ cursor = join20(cursor, part);
4155
4155
  try {
4156
- if (lstatSync14(cursor).isSymbolicLink()) return true;
4156
+ if (lstatSync15(cursor).isSymbolicLink()) return true;
4157
4157
  } catch {
4158
4158
  return true;
4159
4159
  }
@@ -4165,12 +4165,12 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4165
4165
  if (!safe || hasSymlinkComponent(safe.root, safe.candidate)) return { status: "excluded", reason: "unsafe-path" };
4166
4166
  let fd;
4167
4167
  try {
4168
- fd = openSync5(safe.candidate, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
4168
+ fd = openSync6(safe.candidate, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
4169
4169
  } catch {
4170
4170
  return { status: "excluded", reason: "not-regular" };
4171
4171
  }
4172
4172
  try {
4173
- const before = fstatSync2(fd);
4173
+ const before = fstatSync3(fd);
4174
4174
  if (!before.isFile()) return { status: "excluded", reason: "not-regular" };
4175
4175
  if (before.size > maxBytes) return { status: "excluded", reason: "oversize" };
4176
4176
  let physical;
@@ -4191,7 +4191,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4191
4191
  if (total > maxBytes) return { status: "excluded", reason: "oversize" };
4192
4192
  chunks.push(chunk.subarray(0, count));
4193
4193
  }
4194
- const after = fstatSync2(fd);
4194
+ const after = fstatSync3(fd);
4195
4195
  if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || total !== before.size) return { status: "excluded", reason: "changed-during-read" };
4196
4196
  const bytes = Buffer.concat(chunks, total);
4197
4197
  if (bytes.includes(0)) return { status: "excluded", reason: "binary" };
@@ -4204,7 +4204,7 @@ function readSafeEvidenceText(repoPath, relativePath2, maxBytes) {
4204
4204
  if (looksSecret(relativePath2, content)) return { status: "excluded", reason: "secret-like" };
4205
4205
  return { status: "present", content, content_sha256: sha256Hex(bytes), bytes: total };
4206
4206
  } finally {
4207
- closeSync5(fd);
4207
+ closeSync6(fd);
4208
4208
  }
4209
4209
  }
4210
4210
  function looksGenerated(path) {
@@ -4294,10 +4294,10 @@ var init_git_evidence = __esm({
4294
4294
 
4295
4295
  // src/notebook/remote-mutation-journal.ts
4296
4296
  import { randomUUID as randomUUID4 } from "node:crypto";
4297
- import { join as join20 } from "node:path";
4297
+ import { join as join21 } from "node:path";
4298
4298
  function journalPath(root, projectSlug, operationId) {
4299
4299
  if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
4300
- return join20(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
4300
+ return join21(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
4301
4301
  }
4302
4302
  function remoteMutationJournalPath(root, projectSlug, operationId) {
4303
4303
  return journalPath(root, projectSlug, operationId);
@@ -4317,7 +4317,7 @@ function listRemoteMutationJournals(root, projectSlug) {
4317
4317
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
4318
4318
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
4319
4319
  }
4320
- const read = readNotebookStateJson(join20(paths.journals, entry.name), paths.root, 65536);
4320
+ const read = readNotebookStateJson(join21(paths.journals, entry.name), paths.root, 65536);
4321
4321
  const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
4322
4322
  if (!journal || `${journal.operation_id}.json` !== entry.name) {
4323
4323
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
@@ -4671,7 +4671,7 @@ var init_reconcile = __esm({
4671
4671
 
4672
4672
  // src/notebook/entropy.ts
4673
4673
  import { existsSync as existsSync16 } from "node:fs";
4674
- import { createHash as createHash13 } from "node:crypto";
4674
+ import { createHash as createHash14 } from "node:crypto";
4675
4675
  import { resolve as resolve16 } from "node:path";
4676
4676
  import { spawnSync as spawnSync11 } from "node:child_process";
4677
4677
  function listAllTrackedDocuments(config) {
@@ -4726,7 +4726,7 @@ async function compactSessionCaptures(input) {
4726
4726
  const toCompact = sessionHeaders.slice(maxRecent);
4727
4727
  const notesToCompact = typeof input.client.hydrateNotes === "function" ? await input.client.hydrateNotes(toCompact) : toCompact;
4728
4728
  const archiveTitle = "Session Captures Archive (Prior Sessions)";
4729
- const slugHash = createHash13("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
4729
+ const slugHash = createHash14("sha256").update(`session-capture-archive:${input.projectSlug}`).digest("hex").slice(0, 32);
4730
4730
  const archiveLogicalId = `user-note:v1:${slugHash}`;
4731
4731
  let existingArchiveContent = "";
4732
4732
  let existingArchiveId = null;
@@ -5294,7 +5294,7 @@ var init_capture = __esm({
5294
5294
 
5295
5295
  // src/mcp-server.ts
5296
5296
  import { existsSync as existsSync22, statSync as statSync8 } from "node:fs";
5297
- import { dirname as dirname20, join as join31, resolve as resolve22 } from "node:path";
5297
+ import { dirname as dirname20, join as join32, resolve as resolve22 } from "node:path";
5298
5298
  import { fileURLToPath as fileURLToPath6 } from "node:url";
5299
5299
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5300
5300
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -7274,20 +7274,20 @@ function planSkillRoots(repoRoot, options = {}) {
7274
7274
  for (const name of readdirSync6(real).sort()) {
7275
7275
  const path = join7(real, name);
7276
7276
  const label = `${alias}/${name}`;
7277
- const entryStat = lstatSync6(path);
7277
+ const entryStat2 = lstatSync6(path);
7278
7278
  const rows = tracked.get(name) ?? [];
7279
- const candidate = { alias, name, path, label, kind: entryStat.isSymbolicLink() ? "link" : entryStat.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
7279
+ const candidate = { alias, name, path, label, kind: entryStat2.isSymbolicLink() ? "link" : entryStat2.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
7280
7280
  if (name === ".git") {
7281
7281
  candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
7282
- } else if (entryStat.isSymbolicLink()) {
7282
+ } else if (entryStat2.isSymbolicLink()) {
7283
7283
  candidate.text = readlinkSync5(path);
7284
7284
  candidate.resolution = resolveIn(diskLayout, path);
7285
7285
  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`;
7286
- } else if (!entryStat.isDirectory() && !entryStat.isFile()) {
7286
+ } else if (!entryStat2.isDirectory() && !entryStat2.isFile()) {
7287
7287
  candidate.staticBlock = `${label} is a special file; refusing to relocate it`;
7288
7288
  } else {
7289
7289
  candidate.staticBlock = entryHazard(path, label);
7290
- if (!candidate.staticBlock && rootDevice !== void 0 && entryStat.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
7290
+ if (!candidate.staticBlock && rootDevice !== void 0 && entryStat2.dev !== rootDevice) candidate.staticBlock = `${label} is on a different filesystem than .agents/skills; refusing a copying move`;
7291
7291
  const linkRow = rows.find((row) => row.mode === "120000");
7292
7292
  const gitlink = rows.find((row) => row.mode === "160000");
7293
7293
  if (!candidate.staticBlock && gitlink) candidate.staticBlock = `${label} holds a tracked submodule (${gitlink.path}); refusing to relocate it`;
@@ -9194,14 +9194,324 @@ function createG33CompanionCheck() {
9194
9194
  }
9195
9195
 
9196
9196
  // src/parity/rules.ts
9197
- import { existsSync as existsSync8, lstatSync as lstatSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync10, 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";
9198
- import { basename as basename4, dirname as dirname8, join as join11, relative as relative9, resolve as resolve6 } from "node:path";
9197
+ import { existsSync as existsSync8, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync11, 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";
9198
+
9199
+ // src/bmad/setup-scripts.ts
9200
+ import { createHash as createHash8 } from "node:crypto";
9201
+ import { closeSync as closeSync2, fchmodSync, fstatSync, linkSync, lstatSync as lstatSync9, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync9, readdirSync as readdirSync7, rmdirSync as rmdirSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7, constants } from "node:fs";
9202
+ import { basename as basename4, join as join10 } from "node:path";
9203
+ function entryStat(path) {
9204
+ try {
9205
+ return lstatSync9(path);
9206
+ } catch (error) {
9207
+ if (error.code === "ENOENT") return void 0;
9208
+ throw error;
9209
+ }
9210
+ }
9211
+ var RUNTIME_SCRIPTS = ["config_utils.py", "memlog.py", "render_skill.py", "resolve_config.py", "resolve_customization.py"];
9212
+ function manifestRows(text) {
9213
+ const rows = [];
9214
+ let row = [], field2 = "", quoted = false, closed = false, inQuotes = false;
9215
+ for (let i = 0; i < text.length; i++) {
9216
+ const c = text[i];
9217
+ if (inQuotes) {
9218
+ if (c === '"' && text[i + 1] === '"') {
9219
+ field2 += '"';
9220
+ i++;
9221
+ } else if (c === '"') {
9222
+ inQuotes = false;
9223
+ closed = true;
9224
+ } else field2 += c;
9225
+ } else if (c === '"' && !field2 && !closed) {
9226
+ inQuotes = true;
9227
+ quoted = true;
9228
+ } else if (c === ",") {
9229
+ row.push(field2);
9230
+ field2 = "";
9231
+ quoted = closed = false;
9232
+ } else if (c === "\n" || c === "\r") {
9233
+ if (c === "\r" && text[i + 1] === "\n") i++;
9234
+ row.push(field2);
9235
+ if (row.length !== 1 || row[0]) rows.push(row);
9236
+ row = [];
9237
+ field2 = "";
9238
+ quoted = closed = false;
9239
+ } else if (closed || c === '"' || quoted && c === "\r") throw new Error("Malformed BMAD file manifest CSV");
9240
+ else field2 += c;
9241
+ }
9242
+ if (inQuotes) throw new Error("Unterminated BMAD file manifest CSV field");
9243
+ if (row.length || field2 || closed) {
9244
+ row.push(field2);
9245
+ rows.push(row);
9246
+ }
9247
+ if (rows.some((r) => r.length !== 5) || rows[0]?.join("\0") !== "type\0name\0module\0path\0hash") {
9248
+ throw new Error("Invalid BMAD file manifest columns");
9249
+ }
9250
+ return rows.slice(1);
9251
+ }
9252
+ function sameIdentity(a, b) {
9253
+ return !!a && a.dev === b.dev && a.ino === b.ino && a.isDirectory() && !a.isSymbolicLink();
9254
+ }
9255
+ var DeclinableError = class extends Error {
9256
+ constructor(message, kind) {
9257
+ super(message);
9258
+ this.kind = kind;
9259
+ }
9260
+ };
9261
+ var FIELD_SEP = String.fromCharCode(0);
9262
+ var MANIFEST_UNUSABLE = "Installed BMAD file manifest is missing or not a real file; cannot attest recovered scripts";
9263
+ function inspectBmadSetupScripts(repoRoot, version, installedVersion) {
9264
+ const bmadDir = join10(repoRoot, "_bmad");
9265
+ const dest = join10(bmadDir, "scripts");
9266
+ try {
9267
+ const bmad = entryStat(bmadDir);
9268
+ if (!bmad) return { state: "decline", why: "_bmad is absent", missing: [...RUNTIME_SCRIPTS] };
9269
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) return { state: "unsafe", error: "_bmad is not a real directory" };
9270
+ const destStat = entryStat(dest);
9271
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) return { state: "unsafe", error: "_bmad/scripts is not a real directory" };
9272
+ const missing = [];
9273
+ for (const name of RUNTIME_SCRIPTS) {
9274
+ const existing = destStat ? entryStat(join10(dest, name)) : void 0;
9275
+ if (!existing) missing.push(name);
9276
+ else if (!existing.isFile() || existing.isSymbolicLink()) return { state: "unsafe", error: `Existing _bmad/scripts/${name} is not a regular file; refusing overwrite` };
9277
+ }
9278
+ if (!missing.length) return { state: "present" };
9279
+ const configDir = entryStat(join10(bmadDir, "_config"));
9280
+ if (configDir && (!configDir.isDirectory() || configDir.isSymbolicLink())) return { state: "unsafe", error: MANIFEST_UNUSABLE };
9281
+ const manifestPath = join10(bmadDir, "_config", "files-manifest.csv");
9282
+ const manifestFile = configDir ? entryStat(manifestPath) : void 0;
9283
+ if (!manifestFile) return { state: "decline", why: "_bmad/_config/files-manifest.csv is absent, so recovered scripts cannot be attested", missing };
9284
+ if (!manifestFile.isFile() || manifestFile.isSymbolicLink()) return { state: "unsafe", error: MANIFEST_UNUSABLE };
9285
+ const installed = installedVersion?.();
9286
+ if (installed && installed !== version) {
9287
+ return { state: "decline", why: `this install is bmad-method ${installed}, not the pinned ${version} that recovery restores from`, missing };
9288
+ }
9289
+ const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
9290
+ const unattested = missing.filter((name) => !rows.some((row) => row[3] === `scripts/${name}`));
9291
+ if (unattested.length) {
9292
+ 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 };
9293
+ }
9294
+ return { state: "missing", missing };
9295
+ } catch (error) {
9296
+ return { state: "unsafe", error: error instanceof Error ? error.message : String(error) };
9297
+ }
9298
+ }
9299
+ function planBmadSetupScripts(repoRoot, pkg, version) {
9300
+ const dest = join10(repoRoot, "_bmad", "scripts");
9301
+ 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"}` };
9302
+ let fingerprint2;
9303
+ try {
9304
+ fingerprint2 = installerFingerprint(pkg.root);
9305
+ } catch (error) {
9306
+ return { files: [], declinable: "package", error: `Could not fingerprint pinned BMAD installer: ${String(error)}` };
9307
+ }
9308
+ if (VERIFIED_BMAD_INSTALLERS[fingerprint2] !== version) return { files: [], declinable: "package", error: `Unverified BMAD installer fingerprint ${fingerprint2}; refusing script recovery` };
9309
+ const source = join10(pkg.root, "src", "scripts");
9310
+ try {
9311
+ const bmad = lstatSync9(join10(repoRoot, "_bmad"));
9312
+ if (!bmad.isDirectory() || bmad.isSymbolicLink()) throw new Error("_bmad is not a real directory");
9313
+ const destStat = entryStat(dest);
9314
+ if (destStat && (!destStat.isDirectory() || destStat.isSymbolicLink())) throw new Error("_bmad/scripts is not a real directory");
9315
+ const names = readdirSync7(source).filter((name) => !name.startsWith(".") && !name.endsWith(".pyc") && name !== "tests" && name !== "__pycache__" && name !== ".pytest_cache").sort();
9316
+ if (names.join(FIELD_SEP) !== [...RUNTIME_SCRIPTS].sort().join(FIELD_SEP)) throw new DeclinableError("Pinned installer script inventory changed; refusing recovery", "package");
9317
+ const missing = [];
9318
+ for (const name of names) {
9319
+ const existing = entryStat(join10(dest, name));
9320
+ if (!existing) missing.push(name);
9321
+ else if (!existing.isFile() || existing.isSymbolicLink()) throw new Error(`Existing _bmad/scripts/${name} is not a regular file; refusing overwrite`);
9322
+ }
9323
+ const files = [];
9324
+ if (!missing.length) return { files, source };
9325
+ const manifestPath = join10(repoRoot, "_bmad", "_config", "files-manifest.csv");
9326
+ const manifestDir = entryStat(join10(repoRoot, "_bmad", "_config"));
9327
+ const manifestFile = entryStat(manifestPath);
9328
+ if (!manifestDir?.isDirectory() || manifestDir.isSymbolicLink() || !manifestFile?.isFile() || manifestFile.isSymbolicLink()) {
9329
+ throw new Error(MANIFEST_UNUSABLE);
9330
+ }
9331
+ const rows = manifestRows(readFileSync9(manifestPath, "utf8"));
9332
+ for (const name of missing) {
9333
+ const src = join10(source, name);
9334
+ if (!lstatSync9(src).isFile() || lstatSync9(src).isSymbolicLink()) throw new DeclinableError(`Installer script is not a regular file: ${name}`, "package");
9335
+ const matching = rows.filter((row) => row[3] === `scripts/${name}`);
9336
+ const expectedHash = createHash8("sha256").update(readFileSync9(src)).digest("hex");
9337
+ if (matching.length !== 1 || matching[0].join(FIELD_SEP) !== ["py", name.slice(0, -3), "scripts", `scripts/${name}`, expectedHash].join(FIELD_SEP)) {
9338
+ throw new DeclinableError(`Installed file manifest does not attest pinned script ${name}`, "attest");
9339
+ }
9340
+ files.push(join10(dest, name));
9341
+ }
9342
+ return { files, source };
9343
+ } catch (error) {
9344
+ return { files: [], ...error instanceof DeclinableError ? { declinable: error.kind } : {}, error: `BMAD script recovery refused: ${error instanceof Error ? error.message : String(error)}` };
9345
+ }
9346
+ }
9347
+ function applyBmadSetupScripts(repoRoot, pkg, version, afterPublish) {
9348
+ const plan = planBmadSetupScripts(repoRoot, pkg, version);
9349
+ if (plan.error || !plan.files.length) return { files: [], ...plan.error ? { error: plan.error } : {} };
9350
+ const parent = join10(repoRoot, "_bmad");
9351
+ const dest = join10(parent, "scripts");
9352
+ const expectedParent = entryStat(parent);
9353
+ const expectedDir = entryStat(dest);
9354
+ let stage;
9355
+ let stageFd;
9356
+ let parentFd;
9357
+ const created = [];
9358
+ const stagedIdentities = /* @__PURE__ */ new Map();
9359
+ const published = /* @__PURE__ */ new Map();
9360
+ let ambiguousPublication;
9361
+ const matchesPayload = (path, owned) => {
9362
+ const stat = entryStat(path);
9363
+ return !!stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino && (stat.mode & 4095) === owned.mode && readFileSync9(path).equals(owned.bytes);
9364
+ };
9365
+ let madeDir = false;
9366
+ let dirFd;
9367
+ let result2 = { files: [] };
9368
+ try {
9369
+ parentFd = openSync2(parent, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
9370
+ const pinnedParent = fstatSync(parentFd);
9371
+ if (!sameIdentity(expectedParent, pinnedParent) || !sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed before staging");
9372
+ const anchoredParent = `/proc/self/fd/${parentFd}`;
9373
+ stage = mkdtempSync2(join10(anchoredParent, ".scripts-stage-"));
9374
+ stageFd = openSync2(stage, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
9375
+ const pinnedStage = fstatSync(stageFd);
9376
+ const stageName = basename4(stage);
9377
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
9378
+ if (!sameIdentity(entryStat(join10(anchoredParent, stageName)), pinnedStage)) throw new Error("Staging directory changed during creation");
9379
+ for (const path of plan.files) {
9380
+ const name = basename4(path);
9381
+ const src = join10(plan.source, name);
9382
+ const staged = join10(anchoredStage, name);
9383
+ const fileFd = openSync2(staged, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
9384
+ try {
9385
+ const stat = fstatSync(fileFd);
9386
+ const bytes = readFileSync9(src);
9387
+ const mode = lstatSync9(src).mode & 4095;
9388
+ stagedIdentities.set(name, { dev: stat.dev, ino: stat.ino, bytes, mode });
9389
+ writeFileSync7(fileFd, bytes);
9390
+ fchmodSync(fileFd, mode);
9391
+ } finally {
9392
+ closeSync2(fileFd);
9393
+ }
9394
+ }
9395
+ if (!sameIdentity(entryStat(parent), pinnedParent)) throw new Error("BMAD parent directory changed while staging");
9396
+ const current = planBmadSetupScripts(repoRoot, pkg, version);
9397
+ if (current.error || current.files.join("\0") !== plan.files.join("\0")) throw new Error(current.error ?? "Script destination changed while staging");
9398
+ const anchoredDest = join10(anchoredParent, "scripts");
9399
+ if (!entryStat(anchoredDest)) {
9400
+ mkdirSync6(anchoredDest);
9401
+ madeDir = true;
9402
+ }
9403
+ dirFd = openSync2(anchoredDest, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
9404
+ const pinned = fstatSync(dirFd);
9405
+ if (expectedDir && !sameIdentity(expectedDir, pinned)) throw new Error("Script directory changed while staging");
9406
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
9407
+ const anchored = `/proc/self/fd/${dirFd}`;
9408
+ for (const path of plan.files) {
9409
+ const name = basename4(path);
9410
+ const staged = join10(anchoredStage, name);
9411
+ const output = join10(anchored, name);
9412
+ const owned = stagedIdentities.get(name);
9413
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
9414
+ if (!matchesPayload(staged, owned)) throw new Error(`Staged script changed before publication: ${name}`);
9415
+ if (entryStat(output)) {
9416
+ ambiguousPublication = path;
9417
+ throw new Error(`Script destination appeared before publication: ${name}`);
9418
+ }
9419
+ try {
9420
+ linkSync(staged, output);
9421
+ } catch (error) {
9422
+ if (entryStat(output)) ambiguousPublication = path;
9423
+ throw error;
9424
+ }
9425
+ published.set(path, owned);
9426
+ created.push(path);
9427
+ if (!matchesPayload(output, owned)) throw new Error(`Published script changed during publication: ${name}`);
9428
+ afterPublish?.(created.length);
9429
+ }
9430
+ if (!sameIdentity(entryStat(parent), pinnedParent) || !sameIdentity(entryStat(dest), pinned)) throw new Error("Script or parent directory changed during publication");
9431
+ for (const path of created) {
9432
+ if (!matchesPayload(join10(anchored, basename4(path)), stagedIdentities.get(basename4(path)))) throw new Error(`Published script changed before completion: ${basename4(path)}`);
9433
+ }
9434
+ result2 = { files: created };
9435
+ } catch (error) {
9436
+ const recoveryErrors = [];
9437
+ for (const [path, owned] of [...published].reverse()) {
9438
+ try {
9439
+ const anchoredPath = join10(`/proc/self/fd/${dirFd}`, basename4(path));
9440
+ const stat = entryStat(anchoredPath);
9441
+ if (matchesPayload(anchoredPath, owned)) unlinkSync5(anchoredPath);
9442
+ else if (stat) recoveryErrors.push(`Concurrent replacement or mutation retained: ${path}`);
9443
+ else recoveryErrors.push(`Published file absent from pinned directory; ownership unresolved: ${path}`);
9444
+ } catch (recoveryError) {
9445
+ recoveryErrors.push(`Could not restore ${path}: ${String(recoveryError)}`);
9446
+ }
9447
+ }
9448
+ if (ambiguousPublication) recoveryErrors.push(`Publication not confirmed; destination retained or ownership unresolved: ${ambiguousPublication}`);
9449
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
9450
+ try {
9451
+ const pinned = fstatSync(dirFd);
9452
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
9453
+ if (sameIdentity(entryStat(anchoredDest), pinned) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
9454
+ else recoveryErrors.push(`Created script directory moved or retained: ${dest}`);
9455
+ } catch (recoveryError) {
9456
+ recoveryErrors.push(`Could not remove empty script directory: ${String(recoveryError)}`);
9457
+ }
9458
+ }
9459
+ result2 = { files: [], error: `BMAD script recovery failed: ${error instanceof Error ? error.message : String(error)}${recoveryErrors.length ? `; recovery incomplete: ${recoveryErrors.join("; ")}` : ""}` };
9460
+ } finally {
9461
+ const cleanupErrors = [];
9462
+ if (stageFd !== void 0) {
9463
+ try {
9464
+ const pinnedStage = fstatSync(stageFd);
9465
+ const anchoredStage = `/proc/self/fd/${stageFd}`;
9466
+ for (const [name, owned] of stagedIdentities) {
9467
+ const staged = join10(anchoredStage, name);
9468
+ const stat = entryStat(staged);
9469
+ if (stat?.isFile() && !stat.isSymbolicLink() && stat.dev === owned.dev && stat.ino === owned.ino) unlinkSync5(staged);
9470
+ else cleanupErrors.push(`Staged file replaced, modified or absent: ${name}`);
9471
+ }
9472
+ const stagePath = join10(`/proc/self/fd/${parentFd}`, basename4(stage));
9473
+ if (sameIdentity(entryStat(stagePath), pinnedStage) && readdirSync7(anchoredStage).length === 0) rmdirSync3(stagePath);
9474
+ else cleanupErrors.push(`Staging directory moved or contains foreign entries: ${join10(parent, basename4(stage))}`);
9475
+ } catch (cleanupError) {
9476
+ cleanupErrors.push(`Could not clean owned staging directory: ${String(cleanupError)}`);
9477
+ }
9478
+ closeSync2(stageFd);
9479
+ } else if (stage) cleanupErrors.push(`Staging directory identity unavailable: ${join10(parent, basename4(stage))}`);
9480
+ if (cleanupErrors.length && result2.files.length) {
9481
+ for (const [path, owned] of [...published].reverse()) {
9482
+ try {
9483
+ const output = join10(`/proc/self/fd/${dirFd}`, basename4(path));
9484
+ if (matchesPayload(output, owned)) unlinkSync5(output);
9485
+ else cleanupErrors.push(`Concurrent replacement or mutation retained: ${path}`);
9486
+ } catch (error) {
9487
+ cleanupErrors.push(`Could not restore ${path}: ${String(error)}`);
9488
+ }
9489
+ }
9490
+ if (madeDir && dirFd !== void 0 && parentFd !== void 0) {
9491
+ try {
9492
+ const anchoredDest = join10(`/proc/self/fd/${parentFd}`, "scripts");
9493
+ if (sameIdentity(entryStat(anchoredDest), fstatSync(dirFd)) && readdirSync7(anchoredDest).length === 0) rmdirSync3(anchoredDest);
9494
+ else cleanupErrors.push(`Created script directory moved or retained: ${dest}`);
9495
+ } catch (error) {
9496
+ cleanupErrors.push(`Could not remove empty script directory: ${String(error)}`);
9497
+ }
9498
+ }
9499
+ }
9500
+ if (dirFd !== void 0) closeSync2(dirFd);
9501
+ if (parentFd !== void 0) closeSync2(parentFd);
9502
+ if (cleanupErrors.length) result2 = { files: [], error: `${result2.error ? `${result2.error}; ` : "BMAD script staging cleanup incomplete: "}recovery incomplete: ${cleanupErrors.join("; ")}` };
9503
+ }
9504
+ return result2;
9505
+ }
9506
+
9507
+ // src/parity/rules.ts
9508
+ import { basename as basename5, dirname as dirname8, join as join12, relative as relative9, resolve as resolve6 } from "node:path";
9199
9509
  import { spawnSync as spawnSync5 } from "node:child_process";
9200
9510
  import YAML4 from "yaml";
9201
9511
 
9202
9512
  // src/bmad/reserved-modules.ts
9203
- import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
9204
- import { join as join10 } from "node:path";
9513
+ import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
9514
+ import { join as join11 } from "node:path";
9205
9515
  import YAML3 from "yaml";
9206
9516
  var BMAD_RESERVED_DIR_NAMES = ["custom", "scripts", "_config", "_memory", "memory", "render"];
9207
9517
  var indentOf = (line) => line.length - line.trimStart().length;
@@ -9258,19 +9568,19 @@ function stripReservedModuleEntries(raw) {
9258
9568
  return { text: lines.filter((_, index) => !drop.has(index)).join(""), evicted };
9259
9569
  }
9260
9570
  function evictReservedModuleEntries(repoRoot, dryRun) {
9261
- const manifestPath = join10(repoRoot, "_bmad", "_config", "manifest.yaml");
9571
+ const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
9262
9572
  const none = { evicted: [], changedFiles: [], details: [] };
9263
9573
  if (!existsSync7(manifestPath)) return none;
9264
9574
  let original;
9265
9575
  try {
9266
- original = readFileSync9(manifestPath, "utf8");
9576
+ original = readFileSync10(manifestPath, "utf8");
9267
9577
  } catch {
9268
9578
  return none;
9269
9579
  }
9270
9580
  const stripped = stripReservedModuleEntries(original);
9271
9581
  if (!stripped) return none;
9272
9582
  const names = stripped.evicted.map((name) => `"${name}"`).join(", ");
9273
- if (!dryRun) writeFileSync7(manifestPath, stripped.text, "utf8");
9583
+ if (!dryRun) writeFileSync8(manifestPath, stripped.text, "utf8");
9274
9584
  return {
9275
9585
  evicted: stripped.evicted,
9276
9586
  changedFiles: [manifestPath],
@@ -9984,17 +10294,17 @@ function normalizeNewlines(value) {
9984
10294
  return value.replace(/\r\n/g, "\n");
9985
10295
  }
9986
10296
  function readText(path) {
9987
- return normalizeNewlines(readFileSync10(path, "utf8"));
10297
+ return normalizeNewlines(readFileSync11(path, "utf8"));
9988
10298
  }
9989
10299
  function safeReadText2(path) {
9990
10300
  return existsSync8(path) ? readText(path) : null;
9991
10301
  }
9992
10302
  function ensureParent(path) {
9993
- mkdirSync6(dirname8(path), { recursive: true });
10303
+ mkdirSync7(dirname8(path), { recursive: true });
9994
10304
  }
9995
10305
  function writeText(path, content) {
9996
10306
  ensureParent(path);
9997
- writeFileSync8(path, content);
10307
+ writeFileSync9(path, content);
9998
10308
  }
9999
10309
  function tryParseJson(text) {
10000
10310
  if (!text) return null;
@@ -10020,12 +10330,12 @@ function readSymlinkTarget(path) {
10020
10330
  }
10021
10331
  function ensureSymlink(path, target, dryRun) {
10022
10332
  if (existsSync8(path)) {
10023
- const stat = lstatSync9(path);
10333
+ const stat = lstatSync10(path);
10024
10334
  if (stat.isSymbolicLink()) {
10025
10335
  const current = readSymlinkTarget(path);
10026
10336
  if (current === target) return { changed: false };
10027
10337
  if (!dryRun) {
10028
- unlinkSync5(path);
10338
+ unlinkSync6(path);
10029
10339
  symlinkSync3(target, path);
10030
10340
  }
10031
10341
  return { changed: true };
@@ -10036,12 +10346,12 @@ function ensureSymlink(path, target, dryRun) {
10036
10346
  return { changed: true };
10037
10347
  }
10038
10348
  function bootstrapAgentsFile(repoRoot, dryRun) {
10039
- const agentsPath = join11(repoRoot, "AGENTS.md");
10349
+ const agentsPath = join12(repoRoot, "AGENTS.md");
10040
10350
  if (existsSync8(agentsPath)) return { changedFiles: [], details: [] };
10041
10351
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
10042
- const source = join11(repoRoot, file);
10352
+ const source = join12(repoRoot, file);
10043
10353
  if (!existsSync8(source)) continue;
10044
- const stat = lstatSync9(source);
10354
+ const stat = lstatSync10(source);
10045
10355
  if (stat.isSymbolicLink()) continue;
10046
10356
  if (stat.isFile()) {
10047
10357
  if (!dryRun) renameSync4(source, agentsPath);
@@ -10049,9 +10359,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
10049
10359
  }
10050
10360
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
10051
10361
  }
10052
- const readmePath = join11(repoRoot, "README.md");
10362
+ const readmePath = join12(repoRoot, "README.md");
10053
10363
  if (existsSync8(readmePath)) {
10054
- const stat = lstatSync9(readmePath);
10364
+ const stat = lstatSync10(readmePath);
10055
10365
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
10056
10366
  if (!dryRun) copyFileSync(readmePath, agentsPath);
10057
10367
  return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
@@ -10089,11 +10399,11 @@ function yamlGet(text, keyPath) {
10089
10399
  return "";
10090
10400
  }
10091
10401
  function discoverRoles(repoRoot) {
10092
- const rolesDir = join11(repoRoot, "agents", "hermes");
10402
+ const rolesDir = join12(repoRoot, "agents", "hermes");
10093
10403
  if (!existsSync8(rolesDir)) return [];
10094
- return readdirSync7(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
10095
- const roleDir = join11(rolesDir, entry.name);
10096
- const roleYamlPath = join11(roleDir, "role.yaml");
10404
+ return readdirSync8(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
10405
+ const roleDir = join12(rolesDir, entry.name);
10406
+ const roleYamlPath = join12(roleDir, "role.yaml");
10097
10407
  if (!existsSync8(roleYamlPath)) return null;
10098
10408
  const text = readText(roleYamlPath);
10099
10409
  const runtimeRepoRaw = yamlGet(text, "runtime.github_repo");
@@ -10126,26 +10436,26 @@ function discoverRoles(repoRoot) {
10126
10436
  }).filter((value) => Boolean(value));
10127
10437
  }
10128
10438
  function templateScript(ctx, name) {
10129
- const source = join11(ctx.pjanglerRoot, ".mise", "scripts", name);
10439
+ const source = join12(ctx.pjanglerRoot, ".mise", "scripts", name);
10130
10440
  return existsSync8(source) ? readText(source) : void 0;
10131
10441
  }
10132
10442
  function templateVersioningScript(ctx) {
10133
10443
  return templateScript(ctx, "versioning.sh");
10134
10444
  }
10135
10445
  function templateLinkAgentfilesScript(ctx) {
10136
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
10446
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
10137
10447
  return existsSync8(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
10138
10448
  }
10139
10449
  function templateMaterializeEnvScript(ctx) {
10140
- const source = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
10450
+ const source = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
10141
10451
  return existsSync8(source) ? readText(source) : void 0;
10142
10452
  }
10143
10453
  function resolveAgentHooksLayer(ctx) {
10144
10454
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
10145
10455
  if (override === "0" || override === "false") return false;
10146
10456
  if (override === "1" || override === "true") return true;
10147
- if (existsSync8(join11(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
10148
- return !existsSync8(join11(ctx.homeDir, ".agents", "hooks"));
10457
+ if (existsSync8(join12(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
10458
+ return !existsSync8(join12(ctx.homeDir, ".agents", "hooks"));
10149
10459
  }
10150
10460
  function evaluateMiseConditionals(template, agentHooksLayer) {
10151
10461
  const out = [];
@@ -10171,13 +10481,13 @@ function evaluateMiseConditionals(template, agentHooksLayer) {
10171
10481
  }
10172
10482
  function renderGeneratedProjectMiseToml(ctx, template) {
10173
10483
  const project = readProjectJson(ctx);
10174
- const projectName = String(project?.project_name ?? basename4(ctx.repoRoot) ?? "project");
10484
+ const projectName = String(project?.project_name ?? basename5(ctx.repoRoot) ?? "project");
10175
10485
  return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
10176
10486
  }
10177
10487
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
10178
- const targetPath = join11(ctx.repoRoot, "mise.toml");
10488
+ const targetPath = join12(ctx.repoRoot, "mise.toml");
10179
10489
  if (existsSync8(targetPath)) return null;
10180
- const sourcePath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
10490
+ const sourcePath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
10181
10491
  if (!existsSync8(sourcePath)) return null;
10182
10492
  const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
10183
10493
  changedFiles.push(targetPath);
@@ -10185,12 +10495,12 @@ function ensureMiseTomlFromTemplate(ctx, changedFiles) {
10185
10495
  return rendered;
10186
10496
  }
10187
10497
  function templateCommonProjectText(ctx, rel) {
10188
- const path = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
10498
+ const path = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
10189
10499
  return existsSync8(path) ? readText(path) : void 0;
10190
10500
  }
10191
10501
  function lstatIfPresent(path) {
10192
10502
  try {
10193
- return lstatSync9(path);
10503
+ return lstatSync10(path);
10194
10504
  } catch (error) {
10195
10505
  if (error.code === "ENOENT") return void 0;
10196
10506
  throw error;
@@ -10198,7 +10508,7 @@ function lstatIfPresent(path) {
10198
10508
  }
10199
10509
  function templateVersionFilesConf(ctx, repoRoot) {
10200
10510
  if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
10201
- const packageJson = join11(repoRoot, "package.json");
10511
+ const packageJson = join12(repoRoot, "package.json");
10202
10512
  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";
10203
10513
  }
10204
10514
  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";
@@ -10208,7 +10518,7 @@ function recordedProjectType(ctx) {
10208
10518
  function isObsidianPluginRepo(ctx, repoRoot) {
10209
10519
  const recorded = recordedProjectType(ctx);
10210
10520
  if (recorded !== void 0) return recorded === "obsidian-plugin";
10211
- const manifest = record2(tryParseJson(safeReadText2(join11(repoRoot, "manifest.json"))));
10521
+ const manifest = record2(tryParseJson(safeReadText2(join12(repoRoot, "manifest.json"))));
10212
10522
  return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
10213
10523
  }
10214
10524
  function unsupportedVersionTypes(conf, script) {
@@ -10669,7 +10979,7 @@ function describeRewrite(before, after) {
10669
10979
  return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
10670
10980
  }
10671
10981
  function readProjectJson(ctx) {
10672
- return tryParseJson(safeReadText2(join11(ctx.repoRoot, ".project.json")));
10982
+ return tryParseJson(safeReadText2(join12(ctx.repoRoot, ".project.json")));
10673
10983
  }
10674
10984
  function readDeclaredAgents(ctx) {
10675
10985
  const project = readProjectJson(ctx);
@@ -10686,7 +10996,7 @@ function readDeclaredAgents(ctx) {
10686
10996
  });
10687
10997
  }
10688
10998
  function readRoleYamlAt(roleDir) {
10689
- const roleYamlPath = join11(roleDir, "role.yaml");
10999
+ const roleYamlPath = join12(roleDir, "role.yaml");
10690
11000
  if (!existsSync8(roleYamlPath)) return null;
10691
11001
  const text = readText(roleYamlPath);
10692
11002
  return {
@@ -10698,7 +11008,7 @@ function readRoleYamlAt(roleDir) {
10698
11008
  }
10699
11009
  function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
10700
11010
  if (!roleDir) return true;
10701
- return !existsSync8(join11(resolve6(repoRoot, roleDir), "role.yaml"));
11011
+ return !existsSync8(join12(resolve6(repoRoot, roleDir), "role.yaml"));
10702
11012
  }
10703
11013
  function validateDeclaredAgent(ctx, declared) {
10704
11014
  const details = [];
@@ -10723,11 +11033,11 @@ function validateDeclaredAgent(ctx, declared) {
10723
11033
  details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
10724
11034
  }
10725
11035
  if (roleYaml.providerName) {
10726
- const dispatcher = join11(roleDir, ".scripts", "lib", "ticket-provider.sh");
11036
+ const dispatcher = join12(roleDir, ".scripts", "lib", "ticket-provider.sh");
10727
11037
  if (!existsSync8(dispatcher)) {
10728
11038
  details.push(`agents.${declared.agentId} provider dispatcher ${relative9(ctx.repoRoot, dispatcher)} missing`);
10729
11039
  }
10730
- const provider = join11(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
11040
+ const provider = join12(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
10731
11041
  if (!existsSync8(provider)) {
10732
11042
  details.push(`agents.${declared.agentId} provider script ${relative9(ctx.repoRoot, provider)} missing`);
10733
11043
  }
@@ -10743,7 +11053,7 @@ function repoPathNamesRoot(value, repoRoot, homeDir) {
10743
11053
  function canonicalProjectJson(ctx) {
10744
11054
  const roles = discoverRoles(ctx.repoRoot);
10745
11055
  const existing = readProjectJson(ctx) ?? {};
10746
- const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename4(ctx.repoRoot)));
11056
+ const slug = normalizeProjectId(existing.project_id ?? existing.project_slug ?? slugifyRepoName(basename5(ctx.repoRoot)));
10747
11057
  const firstRole = roles[0];
10748
11058
  const ticketProvider = {
10749
11059
  ...existing.ticket_provider,
@@ -10813,8 +11123,8 @@ function canonicalProjectJson(ctx) {
10813
11123
  };
10814
11124
  }
10815
11125
  function projectJsonFinding(ctx) {
10816
- const projectPath = join11(ctx.repoRoot, ".project.json");
10817
- const planeJsonPath = join11(ctx.repoRoot, ".plane.json");
11126
+ const projectPath = join12(ctx.repoRoot, ".project.json");
11127
+ const planeJsonPath = join12(ctx.repoRoot, ".plane.json");
10818
11128
  const details = [];
10819
11129
  const data = readProjectJson(ctx);
10820
11130
  const roles = discoverRoles(ctx.repoRoot);
@@ -10885,7 +11195,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
10885
11195
  var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
10886
11196
  var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
10887
11197
  function manifestBmadModules(repoRoot) {
10888
- const manifestPath = join11(repoRoot, "_bmad", "_config", "manifest.yaml");
11198
+ const manifestPath = join12(repoRoot, "_bmad", "_config", "manifest.yaml");
10889
11199
  const raw = safeReadText2(manifestPath);
10890
11200
  if (raw === null) return { status: "absent" };
10891
11201
  try {
@@ -10913,7 +11223,7 @@ function manifestBmadModules(repoRoot) {
10913
11223
  }
10914
11224
  }
10915
11225
  function configuredBmadModuleSelection(repoRoot) {
10916
- const raw = safeReadText2(join11(repoRoot, "_bmad", "config.toml"));
11226
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "config.toml"));
10917
11227
  if (raw === null) return void 0;
10918
11228
  const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
10919
11229
  return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
@@ -10924,24 +11234,60 @@ function selectedBmadModules(repoRoot) {
10924
11234
  if (manifest.status === "invalid") throw new Error(manifest.error);
10925
11235
  return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
10926
11236
  }
10927
- function requiredBmadSentinels(repoRoot, modules = selectedBmadModules(repoRoot)) {
10928
- return [
10929
- join11("core", "config.yaml"),
10930
- join11("config.toml"),
10931
- join11("_config", "manifest.yaml"),
10932
- ...modules.map((module) => join11(module, "config.yaml"))
10933
- ];
11237
+ var BMAD_RUNTIME_SCRIPT_SENTINELS = ["resolve_config.py", "config_utils.py"];
11238
+ function consolidatedBmadConfig(repoRoot) {
11239
+ const path = join12(repoRoot, "_bmad", "config.yaml");
11240
+ if (!existsSync8(path)) return { status: "absent" };
11241
+ let parsed;
11242
+ try {
11243
+ parsed = YAML4.parse(readText(path));
11244
+ } catch (error) {
11245
+ return { status: "unusable", reason: `could not be parsed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}` };
11246
+ }
11247
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
11248
+ return { status: "unusable", reason: "is not a YAML mapping" };
11249
+ }
11250
+ return { status: "mapping", sections: new Set(Object.keys(parsed)) };
11251
+ }
11252
+ function bmadSentinelGaps(repoRoot, modules = selectedBmadModules(repoRoot)) {
11253
+ const bmad = join12(repoRoot, "_bmad");
11254
+ const gaps = [];
11255
+ const absent = (...segments) => !existsSync8(join12(bmad, ...segments));
11256
+ if (absent("config.toml")) gaps.push({ path: "config.toml", detail: "_bmad/config.toml" });
11257
+ for (const script of BMAD_RUNTIME_SCRIPT_SENTINELS) {
11258
+ if (absent("scripts", script)) gaps.push({ path: join12("scripts", script), detail: `_bmad/scripts/${script}` });
11259
+ }
11260
+ if (absent("_config", "manifest.yaml")) {
11261
+ gaps.push({
11262
+ path: join12("_config", "manifest.yaml"),
11263
+ 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"
11264
+ });
11265
+ }
11266
+ let consolidated;
11267
+ for (const module of modules) {
11268
+ if (!absent(module, "config.yaml")) continue;
11269
+ consolidated ??= consolidatedBmadConfig(repoRoot);
11270
+ if (consolidated.status === "mapping" && consolidated.sections.has(module)) continue;
11271
+ 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)`;
11272
+ gaps.push({ path: join12(module, "config.yaml"), detail: `_bmad/${module}/config.yaml${why}` });
11273
+ }
11274
+ return gaps;
11275
+ }
11276
+ function bmadScaffoldIssues(repoRoot, modules) {
11277
+ const gaps = bmadSentinelGaps(repoRoot, modules);
11278
+ const nameIssues = bmadProjectNameIssues(repoRoot);
11279
+ return { gaps, nameIssues, details: [...gaps.map((gap) => gap.detail), ...nameIssues.details] };
10934
11280
  }
10935
11281
  function canonicalBmadProjectName(repoRoot) {
10936
11282
  const project = readProjectJson({ repoRoot });
10937
11283
  const declared = typeof project?.project_name === "string" ? project.project_name.trim() : "";
10938
- return declared || basename4(repoRoot);
11284
+ return declared || basename5(repoRoot);
10939
11285
  }
10940
11286
  function bmadProjectNameIssues(repoRoot) {
10941
11287
  const expected = canonicalBmadProjectName(repoRoot);
10942
11288
  const paths = [];
10943
11289
  const details = [];
10944
- const configToml = join11(repoRoot, "_bmad", "config.toml");
11290
+ const configToml = join12(repoRoot, "_bmad", "config.toml");
10945
11291
  const tomlText = safeReadText2(configToml);
10946
11292
  const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
10947
11293
  let tomlName;
@@ -10956,16 +11302,16 @@ function bmadProjectNameIssues(repoRoot) {
10956
11302
  paths.push(configToml);
10957
11303
  details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
10958
11304
  }
10959
- const bmadRoot = join11(repoRoot, "_bmad");
11305
+ const bmadRoot = join12(repoRoot, "_bmad");
10960
11306
  if (existsSync8(bmadRoot)) {
10961
11307
  let declared;
10962
11308
  try {
10963
11309
  declared = selectedBmadModules(repoRoot);
10964
11310
  } catch {
10965
- declared = readdirSync7(bmadRoot);
11311
+ declared = readdirSync8(bmadRoot);
10966
11312
  }
10967
11313
  for (const name of new Set(declared)) {
10968
- const configPath = join11(bmadRoot, name, "config.yaml");
11314
+ const configPath = join12(bmadRoot, name, "config.yaml");
10969
11315
  const raw = safeReadText2(configPath);
10970
11316
  if (raw === null) continue;
10971
11317
  let actual;
@@ -10985,7 +11331,7 @@ function bmadProjectNameIssues(repoRoot) {
10985
11331
  function isCurrentActivation(path, activations) {
10986
11332
  if (activations.owned.has(path)) return true;
10987
11333
  try {
10988
- if (activations.owned.has(join11(realpathSync4(dirname8(path)), basename4(path)))) return true;
11334
+ if (activations.owned.has(join12(realpathSync4(dirname8(path)), basename5(path)))) return true;
10989
11335
  return activations.targets.has(realpathSync4(path));
10990
11336
  } catch {
10991
11337
  return false;
@@ -10998,26 +11344,26 @@ async function skillActivations(ctx) {
10998
11344
  function evictLegacyBmadPackState(ctx, changedFiles, activations) {
10999
11345
  const details = [];
11000
11346
  const skillDirs = [
11001
- join11(ctx.repoRoot, ".agents", "skills"),
11002
- ...SUPPORTED_CLI_ROOTS.map((root) => join11(ctx.repoRoot, root, "skills"))
11347
+ join12(ctx.repoRoot, ".agents", "skills"),
11348
+ ...SUPPORTED_CLI_ROOTS.map((root) => join12(ctx.repoRoot, root, "skills"))
11003
11349
  ];
11004
11350
  for (const dir of skillDirs) {
11005
11351
  const dirStat = lstatIfPresent(dir);
11006
11352
  if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
11007
11353
  let names;
11008
11354
  try {
11009
- names = readdirSync7(dir);
11355
+ names = readdirSync8(dir);
11010
11356
  } catch {
11011
11357
  continue;
11012
11358
  }
11013
11359
  for (const name of names) {
11014
11360
  if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
11015
- const path = join11(dir, name);
11361
+ const path = join12(dir, name);
11016
11362
  if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
11017
11363
  if (isCurrentActivation(path, activations)) continue;
11018
11364
  changedFiles.push(path);
11019
11365
  details.push(`removed retired BMAD pack symlink ${relative9(ctx.repoRoot, path)}`);
11020
- if (!ctx.dryRun) unlinkSync5(path);
11366
+ if (!ctx.dryRun) unlinkSync6(path);
11021
11367
  }
11022
11368
  }
11023
11369
  return details;
@@ -11030,10 +11376,11 @@ function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
11030
11376
  prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
11031
11377
  };
11032
11378
  }
11033
- function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11379
+ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION, options = {}) {
11380
+ const timeoutMs = options.timeoutMs ?? 18e4;
11034
11381
  const packageAt = (dir) => {
11035
11382
  try {
11036
- const parsed = JSON.parse(readFileSync10(join11(dir, "package.json"), "utf8"));
11383
+ const parsed = JSON.parse(readFileSync11(join12(dir, "package.json"), "utf8"));
11037
11384
  return parsed.name === BMAD_NPM_PACKAGE ? typeof parsed.version === "string" ? parsed.version : "unknown" : void 0;
11038
11385
  } catch {
11039
11386
  return void 0;
@@ -11065,12 +11412,13 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11065
11412
  `node -e "process.stdout.write(require('fs').realpathSync(process.argv[1]))" "$(command -v ${BMAD_NPM_PACKAGE})"`
11066
11413
  ], {
11067
11414
  encoding: "utf8",
11068
- timeout: 18e4,
11415
+ timeout: timeoutMs,
11069
11416
  env: { ...process.env, npm_config_update_notifier: "false", npm_config_fund: "false" }
11070
11417
  });
11071
11418
  const entry = probe.stdout?.trim().split("\n").pop()?.trim();
11072
11419
  if (probe.status !== 0 || !entry) {
11073
- const detail = String(probe.stderr || probe.error?.message || `exit ${probe.status}`).split("\n").filter((line) => line.trim() && !line.startsWith("npm notice")).slice(-3).join(" ");
11420
+ const timedOut = probe.error?.code === "ETIMEDOUT";
11421
+ 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(" ");
11074
11422
  return { reason: `could not resolve ${BMAD_NPM_PACKAGE}@${version}: ${detail || "no installer path reported"}` };
11075
11423
  }
11076
11424
  const root = dirname8(dirname8(dirname8(entry)));
@@ -11078,6 +11426,34 @@ function resolveBmadInstallerPackage(version = BMAD_INSTALLER_VERSION) {
11078
11426
  if (!found) return { reason: `${entry} is not inside a ${BMAD_NPM_PACKAGE} package` };
11079
11427
  return { root, entry, version: found };
11080
11428
  }
11429
+ var BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS = 1e4;
11430
+ var BMAD_SETUP_SCRIPTS_REMEDY = "re-run the BMAD installer (npx bmad-method install) or upgrade via `pj migrate bmad.version`";
11431
+ function bmadSetupScriptsState(repoRoot) {
11432
+ const inspection = inspectBmadSetupScripts(repoRoot, BMAD_INSTALLER_VERSION, () => readInstalledBmadVersion(repoRoot));
11433
+ const decline = (why, missing, remedy2) => ({
11434
+ kind: "decline",
11435
+ summary: `Setup script recovery unavailable: ${why}${missing.length ? ` (missing from _bmad/scripts: ${missing.join(", ")})` : ""}; ${remedy2}`
11436
+ });
11437
+ switch (inspection.state) {
11438
+ case "present":
11439
+ return { kind: "present" };
11440
+ case "unsafe":
11441
+ return { kind: "refuse", error: `BMAD script recovery refused: ${inspection.error}` };
11442
+ case "decline":
11443
+ return decline(inspection.why, inspection.missing, BMAD_SETUP_SCRIPTS_REMEDY);
11444
+ case "missing":
11445
+ break;
11446
+ }
11447
+ const override = Boolean(process.env.PJ_BMAD_INSTALLER?.trim());
11448
+ const remedy = override ? "unset PJ_BMAD_INSTALLER to enable recovery from the pinned package" : `retry when npm is reachable, or ${BMAD_SETUP_SCRIPTS_REMEDY}`;
11449
+ const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION, { timeoutMs: BMAD_SETUP_SCRIPTS_PROBE_TIMEOUT_MS });
11450
+ if (!pkg.root) return decline(pkg.reason ?? "installer is not a bmad-method package", inspection.missing, remedy);
11451
+ const plan = planBmadSetupScripts(repoRoot, pkg, BMAD_INSTALLER_VERSION);
11452
+ if (plan.error) {
11453
+ 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 };
11454
+ }
11455
+ return plan.files.length ? { kind: "missing", pkg, files: plan.files } : { kind: "present" };
11456
+ }
11081
11457
  function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), extraArgs = []) {
11082
11458
  const installerModules = modules.length ? modules.join(",") : "core";
11083
11459
  return [
@@ -11096,7 +11472,7 @@ function bmadInstallerArgs(repoRoot, modules = selectedBmadModules(repoRoot), ex
11096
11472
  }
11097
11473
  function bmadUpgradeChannelArgs(repoRoot, version) {
11098
11474
  if (!/^\d+\.\d+\.\d+$/.test(version)) return [];
11099
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
11475
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
11100
11476
  if (!raw) return [];
11101
11477
  let modules;
11102
11478
  try {
@@ -11166,7 +11542,7 @@ function bmadChannelTarget(distTags) {
11166
11542
  return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
11167
11543
  }
11168
11544
  function readInstalledBmadVersion(repoRoot) {
11169
- const raw = safeReadText2(join11(repoRoot, "_bmad", "_config", "manifest.yaml"));
11545
+ const raw = safeReadText2(join12(repoRoot, "_bmad", "_config", "manifest.yaml"));
11170
11546
  if (!raw) return void 0;
11171
11547
  try {
11172
11548
  const parsed = YAML4.parse(raw);
@@ -11177,8 +11553,8 @@ function readInstalledBmadVersion(repoRoot) {
11177
11553
  }
11178
11554
  }
11179
11555
  function bmadCachePath(homeDir) {
11180
- const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join11(homeDir, ".cache");
11181
- return join11(cacheRoot, "pjangler", "bmad-dist-tags.json");
11556
+ const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join12(homeDir, ".cache");
11557
+ return join12(cacheRoot, "pjangler", "bmad-dist-tags.json");
11182
11558
  }
11183
11559
  function readBmadDistTagsCache(homeDir) {
11184
11560
  const raw = safeReadText2(bmadCachePath(homeDir));
@@ -11220,8 +11596,8 @@ function resolveBmadDistTags(homeDir) {
11220
11596
  if (fetched) {
11221
11597
  try {
11222
11598
  const path = bmadCachePath(homeDir);
11223
- mkdirSync6(dirname8(path), { recursive: true });
11224
- writeFileSync8(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
11599
+ mkdirSync7(dirname8(path), { recursive: true });
11600
+ writeFileSync9(path, JSON.stringify({ fetchedAt: Date.now(), distTags: fetched }, null, 2));
11225
11601
  } catch {
11226
11602
  }
11227
11603
  return { distTags: fetched, stale: false };
@@ -11345,8 +11721,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
11345
11721
  ".zencoder": "zencoder"
11346
11722
  };
11347
11723
  function unsupportedRootAttestation(repoRoot, rootName) {
11348
- const root = join11(repoRoot, rootName);
11349
- const stat = lstatSync9(root);
11724
+ const root = join12(repoRoot, rootName);
11725
+ const stat = lstatSync10(root);
11350
11726
  if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
11351
11727
  const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
11352
11728
  if (!installedBmadTools(repoRoot).has(installerTool)) {
@@ -11365,7 +11741,7 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
11365
11741
  function optionalTemplateScriptIssues(ctx) {
11366
11742
  const issues = [];
11367
11743
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
11368
- const path = join11(ctx.repoRoot, rel);
11744
+ const path = join12(ctx.repoRoot, rel);
11369
11745
  if (!existsSync8(path)) continue;
11370
11746
  const expected = templateCommonProjectText(ctx, rel);
11371
11747
  if (expected === void 0) continue;
@@ -11376,7 +11752,7 @@ function optionalTemplateScriptIssues(ctx) {
11376
11752
  function refreshOptionalTemplateScripts(ctx) {
11377
11753
  const changed = [];
11378
11754
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
11379
- const path = join11(ctx.repoRoot, rel);
11755
+ const path = join12(ctx.repoRoot, rel);
11380
11756
  if (!existsSync8(path)) continue;
11381
11757
  const expected = templateCommonProjectText(ctx, rel);
11382
11758
  if (expected === void 0 || safeReadText2(path) === expected) continue;
@@ -11394,13 +11770,13 @@ function createMiseChecks() {
11394
11770
  id: "mise.config-root",
11395
11771
  title: "mise config_root + AGENTS link hooks",
11396
11772
  audit: (ctx) => {
11397
- const misePath = join11(ctx.repoRoot, "mise.toml");
11773
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11398
11774
  if (!existsSync8(misePath)) {
11399
11775
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
11400
11776
  }
11401
11777
  const text = readText(misePath);
11402
11778
  const details = [];
11403
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
11779
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
11404
11780
  if (!existsSync8(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
11405
11781
  const toml = [];
11406
11782
  const manual = [];
@@ -11448,7 +11824,7 @@ function createMiseChecks() {
11448
11824
  };
11449
11825
  },
11450
11826
  migrate: (ctx, finding2) => {
11451
- const path = join11(ctx.repoRoot, "mise.toml");
11827
+ const path = join12(ctx.repoRoot, "mise.toml");
11452
11828
  const changedFiles = [];
11453
11829
  const details = [];
11454
11830
  if (!existsSync8(path)) {
@@ -11467,7 +11843,7 @@ function createMiseChecks() {
11467
11843
  if (!ctx.dryRun) writeText(path, plan.text);
11468
11844
  text = plan.text;
11469
11845
  }
11470
- const linkAgentfilesPath = join11(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
11846
+ const linkAgentfilesPath = join12(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
11471
11847
  const expectedScript = templateLinkAgentfilesScript(ctx);
11472
11848
  if (expectedScript === void 0) {
11473
11849
  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: [] };
@@ -11505,9 +11881,9 @@ function createMiseChecks() {
11505
11881
  title: "managed mise versioning block",
11506
11882
  audit: (ctx) => {
11507
11883
  const details = [];
11508
- const misePath = join11(ctx.repoRoot, "mise.toml");
11509
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
11510
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
11884
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11885
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
11886
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
11511
11887
  const text = safeReadText2(misePath);
11512
11888
  if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
11513
11889
  if (!existsSync8(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
@@ -11533,7 +11909,7 @@ function createMiseChecks() {
11533
11909
  migrate: (ctx, finding2) => {
11534
11910
  const changedFiles = [];
11535
11911
  const details = [];
11536
- const misePath = join11(ctx.repoRoot, "mise.toml");
11912
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11537
11913
  if (!existsSync8(misePath)) {
11538
11914
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
11539
11915
  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: [] };
@@ -11551,7 +11927,7 @@ function createMiseChecks() {
11551
11927
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
11552
11928
  if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
11553
11929
  }
11554
- const versioningPath = join11(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
11930
+ const versioningPath = join12(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
11555
11931
  const expectedScript = templateVersioningScript(ctx);
11556
11932
  if (expectedScript === void 0) {
11557
11933
  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: [] };
@@ -11563,7 +11939,7 @@ function createMiseChecks() {
11563
11939
  chmodSync3(versioningPath, 493);
11564
11940
  }
11565
11941
  }
11566
- const manifestPath = join11(ctx.repoRoot, ".mise", "version-files.conf");
11942
+ const manifestPath = join12(ctx.repoRoot, ".mise", "version-files.conf");
11567
11943
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
11568
11944
  if (safeReadText2(manifestPath) !== expectedManifest) {
11569
11945
  changedFiles.push(manifestPath);
@@ -11625,7 +12001,7 @@ var RETIRED_SKILL_SCRIPTS = [
11625
12001
  ".mise/scripts/unlink-project-skills-from-clis.sh"
11626
12002
  ];
11627
12003
  function retiredSkillsScripts(ctx) {
11628
- return RETIRED_SKILL_SCRIPTS.map((path) => join11(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
12004
+ return RETIRED_SKILL_SCRIPTS.map((path) => join12(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
11629
12005
  }
11630
12006
  function skillsWiringFindings(text) {
11631
12007
  if (text === null) return { fixable: [], manual: [] };
@@ -11676,7 +12052,7 @@ function createAgentHooksChecks() {
11676
12052
  title: "Skillex project skills",
11677
12053
  audit: async (ctx) => {
11678
12054
  const finding2 = await auditProjectSkills(ctx);
11679
- const mise = safeReadText2(join11(ctx.repoRoot, "mise.toml"));
12055
+ const mise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
11680
12056
  const registryField = await legacyRegistryField(ctx);
11681
12057
  const registryDrop = registryField ? await dropLegacyRegistryField({ ...ctx, dryRun: true }) : void 0;
11682
12058
  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`] : []];
@@ -11697,7 +12073,7 @@ function createAgentHooksChecks() {
11697
12073
  const changedFiles = [];
11698
12074
  const details = [];
11699
12075
  const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
11700
- let currentMise = safeReadText2(join11(ctx.repoRoot, "mise.toml"));
12076
+ let currentMise = safeReadText2(join12(ctx.repoRoot, "mise.toml"));
11701
12077
  if (currentMise === null) {
11702
12078
  const template = templateCommonProjectText(ctx, "mise.toml.jinja");
11703
12079
  if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
@@ -11712,9 +12088,9 @@ function createAgentHooksChecks() {
11712
12088
  const stat = lstatIfPresent(path);
11713
12089
  if (!stat || stat.isDirectory()) continue;
11714
12090
  changedFiles.push(path);
11715
- if (!ctx.dryRun) unlinkSync5(path);
12091
+ if (!ctx.dryRun) unlinkSync6(path);
11716
12092
  }
11717
- const misePath = join11(ctx.repoRoot, "mise.toml");
12093
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11718
12094
  const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
11719
12095
  if (safeReadText2(misePath) !== nextMise) {
11720
12096
  changedFiles.push(misePath);
@@ -11745,9 +12121,9 @@ function createAgentHooksChecks() {
11745
12121
  id: "sot.agent-symlinks",
11746
12122
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
11747
12123
  audit: (ctx) => {
11748
- const agentsPath = join11(ctx.repoRoot, "AGENTS.md");
12124
+ const agentsPath = join12(ctx.repoRoot, "AGENTS.md");
11749
12125
  if (!existsSync8(agentsPath)) {
11750
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join11(ctx.repoRoot, file)));
12126
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync8(join12(ctx.repoRoot, file)));
11751
12127
  if (fallbackSources.length === 0) {
11752
12128
  return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
11753
12129
  }
@@ -11762,7 +12138,7 @@ function createAgentHooksChecks() {
11762
12138
  }
11763
12139
  const details = [];
11764
12140
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
11765
- const full = join11(ctx.repoRoot, file);
12141
+ const full = join12(ctx.repoRoot, file);
11766
12142
  const target = readSymlinkTarget(full);
11767
12143
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
11768
12144
  }
@@ -11786,7 +12162,7 @@ function createAgentHooksChecks() {
11786
12162
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
11787
12163
  }
11788
12164
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
11789
- const full = join11(ctx.repoRoot, file);
12165
+ const full = join12(ctx.repoRoot, file);
11790
12166
  const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
11791
12167
  if (result2.blocked) blockedDetails.push(result2.blocked);
11792
12168
  if (result2.changed) changedFiles.push(full);
@@ -11813,7 +12189,7 @@ function createProjectJsonChecks() {
11813
12189
  const changedFiles = [];
11814
12190
  const blockedDetails = [];
11815
12191
  const droppedDetails = [];
11816
- const path = join11(ctx.repoRoot, ".project.json");
12192
+ const path = join12(ctx.repoRoot, ".project.json");
11817
12193
  const existing = readProjectJson(ctx) ?? {};
11818
12194
  const canonical = canonicalProjectJson(ctx);
11819
12195
  const preservedDetails = [];
@@ -11845,7 +12221,7 @@ function createProjectJsonChecks() {
11845
12221
  changedFiles.push(path);
11846
12222
  if (!ctx.dryRun) writeText(path, expected);
11847
12223
  }
11848
- const planeJson = join11(ctx.repoRoot, ".plane.json");
12224
+ const planeJson = join12(ctx.repoRoot, ".plane.json");
11849
12225
  if (existsSync8(planeJson)) {
11850
12226
  const backup = `${planeJson}.migrated-backup`;
11851
12227
  if (existsSync8(backup)) {
@@ -11877,10 +12253,10 @@ function createMiseOpInjectChecks() {
11877
12253
  const details = [];
11878
12254
  let envOpNeedsHands = false;
11879
12255
  const optedOut = envMaterializationOptedOut(ctx);
11880
- const envOpPath = join11(ctx.repoRoot, ".env.op");
12256
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
11881
12257
  const envOpExists = existsSync8(envOpPath);
11882
12258
  const envOp = envOpExists ? readText(envOpPath) : void 0;
11883
- const gitignore = safeReadText2(join11(ctx.repoRoot, ".gitignore"));
12259
+ const gitignore = safeReadText2(join12(ctx.repoRoot, ".gitignore"));
11884
12260
  if (!envOpExists) {
11885
12261
  details.push(".env.op missing");
11886
12262
  } else if (!envOp?.trim()) {
@@ -11900,7 +12276,7 @@ function createMiseOpInjectChecks() {
11900
12276
  if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
11901
12277
  if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
11902
12278
  if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
11903
- const misePath = join11(ctx.repoRoot, "mise.toml");
12279
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11904
12280
  const miseText = safeReadText2(misePath);
11905
12281
  if (!miseText) {
11906
12282
  details.push("mise.toml missing for .env materialization hook");
@@ -11923,7 +12299,7 @@ function createMiseOpInjectChecks() {
11923
12299
  else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
11924
12300
  details.push(...mise);
11925
12301
  }
11926
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
12302
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
11927
12303
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
11928
12304
  if (optedOut) {
11929
12305
  if (existsSync8(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
@@ -11931,7 +12307,7 @@ function createMiseOpInjectChecks() {
11931
12307
  details.push("pjangler package is missing the managed materialize-env.sh source");
11932
12308
  } else if (safeReadText2(materializePath) !== expectedMaterializer) {
11933
12309
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
11934
- } else if ((lstatSync9(materializePath).mode & 73) === 0) {
12310
+ } else if ((lstatSync10(materializePath).mode & 73) === 0) {
11935
12311
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
11936
12312
  }
11937
12313
  return {
@@ -11947,8 +12323,8 @@ function createMiseOpInjectChecks() {
11947
12323
  const changedFiles = [];
11948
12324
  const details = [];
11949
12325
  let envOpNeedsHands = false;
11950
- const envOpPath = join11(ctx.repoRoot, ".env.op");
11951
- const canonicalEnvOpPath = join11(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
12326
+ const envOpPath = join12(ctx.repoRoot, ".env.op");
12327
+ const canonicalEnvOpPath = join12(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
11952
12328
  if (!existsSync8(canonicalEnvOpPath)) {
11953
12329
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
11954
12330
  }
@@ -11977,7 +12353,7 @@ function createMiseOpInjectChecks() {
11977
12353
  }
11978
12354
  }
11979
12355
  }
11980
- const gitignorePath = join11(ctx.repoRoot, ".gitignore");
12356
+ const gitignorePath = join12(ctx.repoRoot, ".gitignore");
11981
12357
  const gitignore = safeReadText2(gitignorePath) ?? "";
11982
12358
  const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
11983
12359
  # staged through a mktemp file and moved into place only on success.
@@ -11990,7 +12366,7 @@ function createMiseOpInjectChecks() {
11990
12366
  changedFiles.push(gitignorePath);
11991
12367
  if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
11992
12368
  }
11993
- const misePath = join11(ctx.repoRoot, "mise.toml");
12369
+ const misePath = join12(ctx.repoRoot, "mise.toml");
11994
12370
  let currentMise = safeReadText2(misePath);
11995
12371
  if (currentMise === null) {
11996
12372
  const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
@@ -12007,7 +12383,7 @@ function createMiseOpInjectChecks() {
12007
12383
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
12008
12384
  if (!ctx.dryRun) writeText(misePath, opPlan.text);
12009
12385
  }
12010
- const materializePath = join11(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
12386
+ const materializePath = join12(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
12011
12387
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
12012
12388
  if (optedOut) {
12013
12389
  if (existsSync8(materializePath)) {
@@ -12016,7 +12392,7 @@ function createMiseOpInjectChecks() {
12016
12392
  }
12017
12393
  } else if (!expectedMaterializer) {
12018
12394
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
12019
- } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync9(materializePath).mode & 73) === 0) {
12395
+ } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync8(materializePath) && (lstatSync10(materializePath).mode & 73) === 0) {
12020
12396
  changedFiles.push(materializePath);
12021
12397
  if (!ctx.dryRun) {
12022
12398
  writeText(materializePath, expectedMaterializer);
@@ -12078,7 +12454,7 @@ function createProjectProvenanceChecks() {
12078
12454
  title: ".copier-answers.yml provenance + drift report",
12079
12455
  audit: (ctx) => {
12080
12456
  const details = [];
12081
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
12457
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
12082
12458
  const text = safeReadText2(path);
12083
12459
  const project = readProjectJson(ctx);
12084
12460
  if (!text) {
@@ -12106,11 +12482,11 @@ function createProjectProvenanceChecks() {
12106
12482
  migrate: (ctx, finding2) => {
12107
12483
  const changedFiles = [];
12108
12484
  const project = canonicalProjectJson(ctx);
12109
- const path = join11(ctx.repoRoot, ".copier-answers.yml");
12485
+ const path = join12(ctx.repoRoot, ".copier-answers.yml");
12110
12486
  const current = safeReadText2(path);
12111
12487
  const existing = current ? YAML4.parseDocument(current) : void 0;
12112
12488
  const document = existing && !existing.errors.length && YAML4.isMap(existing.contents) ? existing : new YAML4.Document({});
12113
- document.set("_src_path", join11(ctx.pjanglerRoot, "templates", "commonproject"));
12489
+ document.set("_src_path", join12(ctx.pjanglerRoot, "templates", "commonproject"));
12114
12490
  document.set("project_description", String(project.project_description));
12115
12491
  document.set("project_name", String(project.project_name));
12116
12492
  document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
@@ -12135,9 +12511,9 @@ ${body}`;
12135
12511
  }
12136
12512
  function supportedCliProjectionIssues(repoRoot, plan) {
12137
12513
  const issues = [];
12138
- const managedSkills = join11(repoRoot, ".agents", "skills");
12514
+ const managedSkills = join12(repoRoot, ".agents", "skills");
12139
12515
  for (const rootName of SUPPORTED_CLI_ROOTS) {
12140
- const root = join11(repoRoot, rootName);
12516
+ const root = join12(repoRoot, rootName);
12141
12517
  const rootStat = lstatIfPresent(root);
12142
12518
  if (!rootStat) {
12143
12519
  issues.push(`${rootName} missing`);
@@ -12147,7 +12523,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
12147
12523
  issues.push(`${rootName} must be a real configuration directory`);
12148
12524
  continue;
12149
12525
  }
12150
- const skills = join11(root, "skills");
12526
+ const skills = join12(root, "skills");
12151
12527
  const skillsStat = lstatIfPresent(skills);
12152
12528
  if (!skillsStat) {
12153
12529
  issues.push(`${rootName}/skills missing`);
@@ -12184,9 +12560,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
12184
12560
  issues.push(`${rootName}/skills is not a directory`);
12185
12561
  continue;
12186
12562
  }
12187
- const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync7(projectedSkills).some((name) => {
12188
- const skillFile = join11(projectedSkills, name, "SKILL.md");
12189
- return existsSync8(skillFile) && lstatSync9(skillFile).isFile();
12563
+ const hasGeneratedSkill = existsSync8(projectedSkills) && readdirSync8(projectedSkills).some((name) => {
12564
+ const skillFile = join12(projectedSkills, name, "SKILL.md");
12565
+ return existsSync8(skillFile) && lstatSync10(skillFile).isFile();
12190
12566
  });
12191
12567
  if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
12192
12568
  }
@@ -12212,7 +12588,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
12212
12588
  "# some CLIs get a real directory here and some get a symlink."
12213
12589
  ]);
12214
12590
  function supportedCliGitignoreIssues(repoRoot) {
12215
- const lines = (safeReadText2(join11(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
12591
+ const lines = (safeReadText2(join12(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
12216
12592
  const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
12217
12593
  return [
12218
12594
  ...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
@@ -12220,7 +12596,7 @@ function supportedCliGitignoreIssues(repoRoot) {
12220
12596
  ];
12221
12597
  }
12222
12598
  function ensureSupportedCliGitignore(ctx) {
12223
- const path = join11(ctx.repoRoot, ".gitignore");
12599
+ const path = join12(ctx.repoRoot, ".gitignore");
12224
12600
  const current = safeReadText2(path) ?? "";
12225
12601
  const kept = current.split(/\r?\n/).filter((line) => {
12226
12602
  const trimmed2 = line.trim();
@@ -12262,6 +12638,24 @@ function withInstallerSkillHandoff(check) {
12262
12638
  }
12263
12639
  };
12264
12640
  }
12641
+ function withScaffoldPostcondition(check) {
12642
+ if (check.id !== "bmad.scaffold") return check;
12643
+ return {
12644
+ ...check,
12645
+ migrate: async (ctx, finding2) => {
12646
+ const result2 = await check.migrate(ctx, finding2);
12647
+ if (ctx.dryRun || result2.status !== "applied" && result2.status !== "noop") return result2;
12648
+ const after = await check.audit(ctx);
12649
+ if (after.status === "pass" || after.status === "skip") return result2;
12650
+ return {
12651
+ ...result2,
12652
+ status: "partial",
12653
+ summary: after.summary,
12654
+ details: [...result2.details, `still failing after the install: ${after.summary}`, ...after.details]
12655
+ };
12656
+ }
12657
+ };
12658
+ }
12265
12659
  function createBmadChecks() {
12266
12660
  return [
12267
12661
  {
@@ -12279,15 +12673,8 @@ function createBmadChecks() {
12279
12673
  fixable: false
12280
12674
  };
12281
12675
  }
12282
- const targetRoot = join11(ctx.repoRoot, "_bmad");
12283
12676
  const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
12284
- const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
12285
- const missing = sentinels.filter((file) => !existsSync8(join11(targetRoot, file)));
12286
- const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
12287
- const details = [
12288
- ...missing.map((file) => `_bmad/${file}`),
12289
- ...projectNameIssues.details
12290
- ];
12677
+ const { details } = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
12291
12678
  return {
12292
12679
  id: "bmad.scaffold",
12293
12680
  title: "BMAD modules/docs scaffold",
@@ -12326,7 +12713,7 @@ function createBmadChecks() {
12326
12713
  bmadCompanion: preservation
12327
12714
  };
12328
12715
  if (ctx.dryRun) {
12329
- const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
12716
+ const issues = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
12330
12717
  const companion = installedBmad ? await runG33PreUpdateCompanion(ctx.repoRoot) : await runG33Companion(ctx.repoRoot, true, "bmad-install");
12331
12718
  if (installedBmad && ["unavailable", "conflict", "error"].includes(companion.status)) {
12332
12719
  return {
@@ -12339,8 +12726,8 @@ function createBmadChecks() {
12339
12726
  bmadCompanion: companion
12340
12727
  };
12341
12728
  }
12342
- changedFiles.push(...sentinels.map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)));
12343
- changedFiles.push(...bmadProjectNameIssues(ctx.repoRoot).paths);
12729
+ changedFiles.push(...issues.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)));
12730
+ changedFiles.push(...issues.nameIssues.paths);
12344
12731
  const reservedPreview = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, true) : void 0;
12345
12732
  changedFiles.push(...reservedPreview?.changedFiles ?? []);
12346
12733
  return {
@@ -12358,9 +12745,10 @@ function createBmadChecks() {
12358
12745
  };
12359
12746
  }
12360
12747
  if (installedBmad && g33CompanionEnabled(ctx.repoRoot)) {
12748
+ const before2 = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
12361
12749
  const expectedChangedPaths2 = [
12362
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
12363
- ...bmadProjectNameIssues(ctx.repoRoot).paths
12750
+ ...before2.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
12751
+ ...before2.nameIssues.paths
12364
12752
  ];
12365
12753
  const pkg = resolveBmadInstallerPackage(BMAD_INSTALLER_VERSION);
12366
12754
  const scope = deriveBmadUpdateScope(ctx.repoRoot, { tools: BMAD_INSTALL_TOOLS, installer: pkg, companionSurfaces: G33_OWNED_SURFACES });
@@ -12401,16 +12789,17 @@ function createBmadChecks() {
12401
12789
  bmadCompanion: update.companion
12402
12790
  };
12403
12791
  }
12792
+ const before = bmadScaffoldIssues(ctx.repoRoot, selectedModules);
12404
12793
  const expectedChangedPaths = [
12405
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join11(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync8(path)),
12406
- ...bmadProjectNameIssues(ctx.repoRoot).paths
12794
+ ...before.gaps.map((gap) => join12(ctx.repoRoot, "_bmad", gap.path)),
12795
+ ...before.nameIssues.paths
12407
12796
  ];
12408
12797
  const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
12409
12798
  const reserved = installedBmad ? evictReservedModuleEntries(ctx.repoRoot, false) : void 0;
12410
12799
  if (reserved) evicted.unshift(...reserved.details);
12411
12800
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, BMAD_INSTALLER_VERSION, channelArgs);
12412
12801
  if (!install.ok && reserved?.original !== void 0) {
12413
- writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
12802
+ writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
12414
12803
  evicted.push("restored _bmad/_config/manifest.yaml after the failed install");
12415
12804
  }
12416
12805
  if (!install.ok) {
@@ -12435,6 +12824,58 @@ function createBmadChecks() {
12435
12824
  };
12436
12825
  }
12437
12826
  },
12827
+ {
12828
+ id: "bmad.setup-scripts",
12829
+ title: "BMAD ignored installer setup scripts",
12830
+ audit: (ctx) => {
12831
+ if (!isInstalledBmadProject(ctx.repoRoot)) return {
12832
+ id: "bmad.setup-scripts",
12833
+ title: "BMAD ignored installer setup scripts",
12834
+ status: "skip",
12835
+ summary: "No installed BMAD project",
12836
+ details: [],
12837
+ fixable: false
12838
+ };
12839
+ const state = bmadSetupScriptsState(ctx.repoRoot);
12840
+ const id = "bmad.setup-scripts", title = "BMAD ignored installer setup scripts";
12841
+ switch (state.kind) {
12842
+ case "present":
12843
+ return { id, title, status: "pass", summary: "BMAD installer scripts are present", details: [], fixable: false };
12844
+ case "decline":
12845
+ return { id, title, status: "skip", summary: state.summary, details: [], fixable: false };
12846
+ case "refuse":
12847
+ return { id, title, status: "fail", summary: state.error, details: [state.error], fixable: false };
12848
+ case "missing":
12849
+ return {
12850
+ id,
12851
+ title,
12852
+ status: "fail",
12853
+ summary: "Ignored BMAD installer scripts are missing",
12854
+ details: state.files.map((path) => relative9(ctx.repoRoot, path)),
12855
+ fixable: true
12856
+ };
12857
+ }
12858
+ },
12859
+ migrate: (ctx, finding2) => {
12860
+ const title = finding2.title, id = finding2.id;
12861
+ if (finding2.status === "skip") return { id, title, status: "skipped", summary: finding2.summary, details: [], changedFiles: [] };
12862
+ const state = bmadSetupScriptsState(ctx.repoRoot);
12863
+ if (state.kind === "decline") return { id, title, status: "skipped", summary: state.summary, details: [], changedFiles: [] };
12864
+ if (state.kind === "refuse") return { id, title, status: "blocked", summary: state.error, details: [state.error], changedFiles: [] };
12865
+ if (state.kind === "present") return { id, title, status: "noop", summary: "BMAD installer scripts already present", details: [], changedFiles: [] };
12866
+ const { pkg, files } = state;
12867
+ if (ctx.dryRun) return {
12868
+ id,
12869
+ title,
12870
+ status: "applied",
12871
+ summary: "Would restore ignored scripts from verified pinned bmad-method package",
12872
+ details: [`bmad-method@${BMAD_INSTALLER_VERSION} (${pkg.root})`],
12873
+ changedFiles: files
12874
+ };
12875
+ const applied = applyBmadSetupScripts(ctx.repoRoot, pkg, BMAD_INSTALLER_VERSION);
12876
+ 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 };
12877
+ }
12878
+ },
12438
12879
  {
12439
12880
  id: "bmad.version",
12440
12881
  title: "BMAD version currency",
@@ -12445,7 +12886,7 @@ function createBmadChecks() {
12445
12886
  id: "bmad.version",
12446
12887
  title: "BMAD version currency",
12447
12888
  status: "skip",
12448
- summary: existsSync8(join11(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
12889
+ summary: existsSync8(join12(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
12449
12890
  details: [],
12450
12891
  fixable: false
12451
12892
  };
@@ -12533,7 +12974,7 @@ function createBmadChecks() {
12533
12974
  }
12534
12975
  const installed = readInstalledBmadVersion(ctx.repoRoot);
12535
12976
  const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
12536
- const manifestPath = join11(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
12977
+ const manifestPath = join12(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
12537
12978
  const manifestSelection = manifestBmadModules(ctx.repoRoot);
12538
12979
  if (manifestSelection.status === "invalid") {
12539
12980
  return {
@@ -12631,7 +13072,7 @@ function createBmadChecks() {
12631
13072
  const reserved = evictReservedModuleEntries(ctx.repoRoot, false);
12632
13073
  const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL, channelArgs);
12633
13074
  if (!install.ok) {
12634
- if (reserved.original !== void 0) writeFileSync8(reserved.changedFiles[0], reserved.original, "utf8");
13075
+ if (reserved.original !== void 0) writeFileSync9(reserved.changedFiles[0], reserved.original, "utf8");
12635
13076
  return {
12636
13077
  id: finding2.id,
12637
13078
  title: finding2.title,
@@ -12666,7 +13107,7 @@ function createBmadChecks() {
12666
13107
  title: "Supported BMAD CLI projection roots",
12667
13108
  audit: (ctx) => {
12668
13109
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
12669
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
13110
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
12670
13111
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
12671
13112
  const plan = planSkillRoots(ctx.repoRoot);
12672
13113
  const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
@@ -12688,7 +13129,7 @@ function createBmadChecks() {
12688
13129
  },
12689
13130
  migrate: (ctx, finding2) => {
12690
13131
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
12691
- const present = unsupportedNames.filter((name) => existsSync8(join11(ctx.repoRoot, name)));
13132
+ const present = unsupportedNames.filter((name) => existsSync8(join12(ctx.repoRoot, name)));
12692
13133
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
12693
13134
  const blocked = attestations.filter((entry) => !entry.safe);
12694
13135
  if (blocked.length) {
@@ -12713,7 +13154,7 @@ function createBmadChecks() {
12713
13154
  };
12714
13155
  }
12715
13156
  const gitignoreChanges = ensureSupportedCliGitignore(ctx);
12716
- const removedRoots = attestations.map((entry) => join11(ctx.repoRoot, entry.name));
13157
+ const removedRoots = attestations.map((entry) => join12(ctx.repoRoot, entry.name));
12717
13158
  if (!ctx.dryRun) for (const path of removedRoots) rmSync5(path, { recursive: true, force: true });
12718
13159
  const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
12719
13160
  return {
@@ -12726,7 +13167,7 @@ function createBmadChecks() {
12726
13167
  };
12727
13168
  }
12728
13169
  }
12729
- ].map(withInstallerSkillHandoff);
13170
+ ].map(withInstallerSkillHandoff).map(withScaffoldPostcondition);
12730
13171
  }
12731
13172
  function createProjectMomoChecks() {
12732
13173
  return [
@@ -13335,8 +13776,8 @@ var NodeRecipe = class extends Recipe {
13335
13776
  // src/recipes/ProjectRecipe.ts
13336
13777
  init_project();
13337
13778
  import { spawnSync as spawnSync13 } from "node:child_process";
13338
- import { existsSync as existsSync18, lstatSync as lstatSync16, readFileSync as readFileSync20, rmSync as rmSync8 } from "node:fs";
13339
- import { dirname as dirname17, isAbsolute as isAbsolute10, join as join23, relative as relativePath, resolve as resolve18 } from "node:path";
13779
+ import { existsSync as existsSync18, lstatSync as lstatSync17, readFileSync as readFileSync21, rmSync as rmSync8 } from "node:fs";
13780
+ import { dirname as dirname17, isAbsolute as isAbsolute10, join as join24, relative as relativePath, resolve as resolve18 } from "node:path";
13340
13781
 
13341
13782
  // src/project/ticketScope.ts
13342
13783
  function ticketScope(ticket) {
@@ -13353,8 +13794,8 @@ init_tree_diff();
13353
13794
 
13354
13795
  // src/recipes/NotebookRecipe.ts
13355
13796
  init_project();
13356
- import { existsSync as existsSync17, readFileSync as readFileSync19 } from "node:fs";
13357
- import { join as join22 } from "node:path";
13797
+ import { existsSync as existsSync17, readFileSync as readFileSync20 } from "node:fs";
13798
+ import { join as join23 } from "node:path";
13358
13799
 
13359
13800
  // src/notebook/checks.ts
13360
13801
  init_config();
@@ -13362,8 +13803,8 @@ init_notes();
13362
13803
  init_output();
13363
13804
  init_state();
13364
13805
  import { spawnSync as spawnSync7 } from "node:child_process";
13365
- import { existsSync as existsSync14, readFileSync as readFileSync17 } from "node:fs";
13366
- import { join as join18 } from "node:path";
13806
+ import { existsSync as existsSync14, readFileSync as readFileSync18 } from "node:fs";
13807
+ import { join as join19 } from "node:path";
13367
13808
  var NOTEBOOK_RULE_IDS = [
13368
13809
  "notebook.configuration",
13369
13810
  "notebook.binding",
@@ -13380,10 +13821,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
13380
13821
  return { id: check.id, title: check.title, status, summary, changedFiles, details };
13381
13822
  }
13382
13823
  function manifestNotebook(repo) {
13383
- const path = join18(repo, ".project.json");
13824
+ const path = join19(repo, ".project.json");
13384
13825
  if (!existsSync14(path)) return null;
13385
13826
  try {
13386
- const parsed = JSON.parse(readFileSync17(path, "utf8"));
13827
+ const parsed = JSON.parse(readFileSync18(path, "utf8"));
13387
13828
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
13388
13829
  const notebook = parsed.notebook;
13389
13830
  return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
@@ -13612,7 +14053,7 @@ function captureAudit(ctx) {
13612
14053
  function overviewMigrate(ctx, current) {
13613
14054
  if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
13614
14055
  if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
13615
- const pjanglerEntry = ctx.pjanglerRoot ? join18(ctx.pjanglerRoot, "dist", "index.js") : null;
14056
+ const pjanglerEntry = ctx.pjanglerRoot ? join19(ctx.pjanglerRoot, "dist", "index.js") : null;
13616
14057
  const entry = pjanglerEntry && existsSync14(pjanglerEntry) ? pjanglerEntry : "pj";
13617
14058
  const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
13618
14059
  const spawned = spawnSync7(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
@@ -13664,7 +14105,7 @@ import { resolve as resolve17 } from "node:path";
13664
14105
 
13665
14106
  // src/notebook/journal-key.ts
13666
14107
  init_types();
13667
- import { createHash as createHash11 } from "node:crypto";
14108
+ import { createHash as createHash12 } from "node:crypto";
13668
14109
  function validateJournalPeriodKey(period, key) {
13669
14110
  if (period === "monthly") {
13670
14111
  if (!/^\d{4}-(?:0[1-9]|1[0-2])$/u.test(key)) throw new NotebookError("INVALID_INPUT", "Monthly period key must be YYYY-MM");
@@ -13683,7 +14124,7 @@ function validateJournalPeriodKey(period, key) {
13683
14124
  }
13684
14125
  function journalNoteOperationId(period, key) {
13685
14126
  validateJournalPeriodKey(period, key);
13686
- const bytes = createHash11("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
14127
+ const bytes = createHash12("sha256").update(`pjangler-dev-journal-v1\0infra\0${period}\0${key}`).digest().subarray(0, 16);
13687
14128
  bytes[6] = bytes[6] & 15 | 80;
13688
14129
  bytes[8] = bytes[8] & 63 | 128;
13689
14130
  const hex = bytes.toString("hex");
@@ -13839,15 +14280,15 @@ init_notes();
13839
14280
  init_state();
13840
14281
  init_types();
13841
14282
  import { spawn, spawnSync as spawnSync10 } from "node:child_process";
13842
- import { createHash as createHash12, randomUUID as randomUUID5 } from "node:crypto";
13843
- import { chmodSync as chmodSync6, closeSync as closeSync6, constants as constants4, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync3, fsyncSync as fsyncSync4, lstatSync as lstatSync15, mkdirSync as mkdirSync9, openSync as openSync6, readFileSync as readFileSync18, 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";
13844
- import { basename as basename9, dirname as dirname16, isAbsolute as isAbsolute8, join as join21, parse as parse3, relative as relative15, resolve as resolve15, sep as sep7 } from "node:path";
14283
+ import { createHash as createHash13, randomUUID as randomUUID5 } from "node:crypto";
14284
+ import { chmodSync as chmodSync6, closeSync as closeSync7, constants as constants5, copyFileSync as copyFileSync3, existsSync as existsSync15, fstatSync as fstatSync4, fsyncSync as fsyncSync4, lstatSync as lstatSync16, mkdirSync as mkdirSync10, openSync as openSync7, readFileSync as readFileSync19, 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";
14285
+ import { basename as basename10, dirname as dirname16, isAbsolute as isAbsolute8, join as join22, parse as parse3, relative as relative15, resolve as resolve15, sep as sep7 } from "node:path";
13845
14286
  import { fileURLToPath as fileURLToPath3 } from "node:url";
13846
14287
  function bundledSkillCandidates() {
13847
14288
  const candidates = [];
13848
14289
  let cursor = dirname16(fileURLToPath3(import.meta.url));
13849
14290
  for (let depth = 0; depth < 8; depth++) {
13850
- candidates.push(join21(cursor, "dist", "assets", "project-notebook-skill"), join21(cursor, "assets", "project-notebook-skill"));
14291
+ candidates.push(join22(cursor, "dist", "assets", "project-notebook-skill"), join22(cursor, "assets", "project-notebook-skill"));
13851
14292
  const parent = dirname16(cursor);
13852
14293
  if (parent === cursor) break;
13853
14294
  cursor = parent;
@@ -13865,14 +14306,14 @@ function assertOwnedSkillTree(source) {
13865
14306
  assertNoSymlinkComponents(source);
13866
14307
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
13867
14308
  const walk = (directory) => {
13868
- const directoryStat = lstatSync15(directory);
14309
+ const directoryStat = lstatSync16(directory);
13869
14310
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
13870
14311
  if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
13871
14312
  if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
13872
- for (const entry of readdirSync10(directory, { withFileTypes: true })) {
14313
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
13873
14314
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
13874
- const path = join21(directory, entry.name);
13875
- const stat = lstatSync15(path);
14315
+ const path = join22(directory, entry.name);
14316
+ const stat = lstatSync16(path);
13876
14317
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
13877
14318
  if (entry.isDirectory()) walk(path);
13878
14319
  else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
@@ -13887,18 +14328,18 @@ function assertOwnedSkillTree(source) {
13887
14328
  function enumerateSkillPayload(source) {
13888
14329
  const result2 = [];
13889
14330
  const walk = (directory) => {
13890
- for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
14331
+ for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
13891
14332
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
13892
- const path = join21(directory, entry.name);
14333
+ const path = join22(directory, entry.name);
13893
14334
  const rel = relative15(source, path).split(sep7).join("/");
13894
14335
  if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
13895
14336
  if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
13896
- const stat = lstatSync15(path);
14337
+ const stat = lstatSync16(path);
13897
14338
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
13898
14339
  if (entry.isDirectory()) walk(path);
13899
14340
  else if (entry.isFile()) {
13900
14341
  const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
13901
- result2.push({ path: rel, sha256: createHash12("sha256").update(readFileSync18(path)).digest("hex"), mode: expectedMode });
14342
+ result2.push({ path: rel, sha256: createHash13("sha256").update(readFileSync19(path)).digest("hex"), mode: expectedMode });
13902
14343
  } else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
13903
14344
  }
13904
14345
  };
@@ -13906,9 +14347,9 @@ function enumerateSkillPayload(source) {
13906
14347
  return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
13907
14348
  }
13908
14349
  function parsePackedManifest(source) {
13909
- const manifestPath = join21(source, "export-manifest.json");
14350
+ const manifestPath = join22(source, "export-manifest.json");
13910
14351
  if (!existsSync15(manifestPath)) return null;
13911
- const value = JSON.parse(readFileSync18(manifestPath, "utf8"));
14352
+ const value = JSON.parse(readFileSync19(manifestPath, "utf8"));
13912
14353
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
13913
14354
  const manifest = value;
13914
14355
  if (manifest.schema_version !== 1 || manifest.skill !== "project-notebook" || !Array.isArray(manifest.files)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is incompatible");
@@ -13917,10 +14358,10 @@ function parsePackedManifest(source) {
13917
14358
  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");
13918
14359
  if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
13919
14360
  paths.add(entry.path);
13920
- const path = join21(source, ...entry.path.split("/"));
13921
- const stat = lstatSync15(path);
14361
+ const path = join22(source, ...entry.path.split("/"));
14362
+ const stat = lstatSync16(path);
13922
14363
  if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
13923
- const actual = createHash12("sha256").update(readFileSync18(path)).digest("hex");
14364
+ const actual = createHash13("sha256").update(readFileSync19(path)).digest("hex");
13924
14365
  if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
13925
14366
  const actualMode = stat.mode & 511;
13926
14367
  const executable = entry.mode === "0755";
@@ -13932,16 +14373,16 @@ function parsePackedManifest(source) {
13932
14373
  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");
13933
14374
  const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
13934
14375
  `;
13935
- if (!existsSync15(join21(source, "SHA256SUMS")) || readFileSync18(join21(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
14376
+ if (!existsSync15(join22(source, "SHA256SUMS")) || readFileSync19(join22(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
13936
14377
  return manifest;
13937
14378
  }
13938
14379
  function expectedPackedSkill() {
13939
14380
  for (const candidate of bundledSkillCandidates()) {
13940
- if (!existsSync15(join21(candidate, "export-manifest.json"))) continue;
14381
+ if (!existsSync15(join22(candidate, "export-manifest.json"))) continue;
13941
14382
  assertOwnedSkillTree(candidate);
13942
14383
  const manifest = parsePackedManifest(candidate);
13943
14384
  if (!manifest) continue;
13944
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
14385
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
13945
14386
  return { source: candidate, manifest, digest };
13946
14387
  }
13947
14388
  throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
@@ -14004,13 +14445,13 @@ function verifyProjectNotebookSkillExport(source) {
14004
14445
  function resolveProjectNotebookSkillSource(env2 = process.env) {
14005
14446
  if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
14006
14447
  const explicit = resolve15(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
14007
- if (!existsSync15(join21(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
14448
+ if (!existsSync15(join22(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
14008
14449
  verifyProjectNotebookSkillExport(explicit);
14009
14450
  return explicit;
14010
14451
  }
14011
14452
  if (env2.PJ_SKILLS_REGISTRY_ROOT) {
14012
14453
  const canonical = resolve15(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
14013
- if (existsSync15(join21(canonical, "SKILL.md"))) {
14454
+ if (existsSync15(join22(canonical, "SKILL.md"))) {
14014
14455
  verifyProjectNotebookSkillExport(canonical);
14015
14456
  return canonical;
14016
14457
  }
@@ -14025,7 +14466,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
14025
14466
  function probeCanonicalSkillexRootProjection(skillsRoot, link) {
14026
14467
  let rootLink;
14027
14468
  try {
14028
- rootLink = lstatSync15(skillsRoot);
14469
+ rootLink = lstatSync16(skillsRoot);
14029
14470
  } catch (error) {
14030
14471
  if (error.code === "ENOENT") return { state: "absent" };
14031
14472
  throw error;
@@ -14038,15 +14479,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
14038
14479
  if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
14039
14480
  const globalRoot = realpathSync10(skillsRoot);
14040
14481
  assertNoSymlinkComponents(globalRoot);
14041
- const globalStat = lstatSync15(globalRoot);
14482
+ const globalStat = lstatSync16(globalRoot);
14042
14483
  if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
14043
14484
  if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
14044
14485
  if (globalStat.mode & 3586) throw new Error("root-mode");
14045
14486
  const skillSetsRoot = dirname16(globalRoot);
14046
- if (basename9(globalRoot) !== "global" || basename9(skillSetsRoot) !== "skill-sets") throw new Error("layout");
14487
+ if (basename10(globalRoot) !== "global" || basename10(skillSetsRoot) !== "skill-sets") throw new Error("layout");
14047
14488
  const checkoutRoot = dirname16(skillSetsRoot);
14048
- const expectedSource = join21(checkoutRoot, "all-skills", "project-notebook");
14049
- const linkStat = lstatSync15(link);
14489
+ const expectedSource = join22(checkoutRoot, "all-skills", "project-notebook");
14490
+ const linkStat = lstatSync16(link);
14050
14491
  if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
14051
14492
  if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
14052
14493
  const projectedSource = realpathSync10(link);
@@ -14080,44 +14521,44 @@ function installPackagedProjectNotebookSkill(input = {}) {
14080
14521
  const source = input.source ?? resolveProjectNotebookSkillSource(env2);
14081
14522
  if (!source) throw new NotebookError("NOT_CONFIGURED", "Project Notebook skill source is unavailable");
14082
14523
  const manifest = verifyProjectNotebookSkillExport(source);
14083
- const digest = createHash12("sha256").update(JSON.stringify(manifest)).digest("hex");
14524
+ const digest = createHash13("sha256").update(JSON.stringify(manifest)).digest("hex");
14084
14525
  const home = env2.HOME;
14085
14526
  if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
14086
- const skillsRoot = join21(home, ".agents", "skills");
14087
- const link = join21(skillsRoot, "project-notebook");
14527
+ const skillsRoot = join22(home, ".agents", "skills");
14528
+ const link = join22(skillsRoot, "project-notebook");
14088
14529
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
14089
14530
  if (probe.state === "adopted") return { installed: false, path: link, digest };
14090
14531
  if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
14091
- const dataRoot = resolve15(env2.XDG_DATA_HOME || join21(home, ".local", "share"), "pjangler", "skills", "project-notebook");
14092
- const payload = join21(dataRoot, `${PJANGLER_VERSION}-${digest}`);
14532
+ const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
14533
+ const payload = join22(dataRoot, `${PJANGLER_VERSION}-${digest}`);
14093
14534
  assertNoSymlinkComponents(dirname16(dataRoot), true);
14094
- mkdirSync9(dataRoot, { recursive: true, mode: 448 });
14535
+ mkdirSync10(dataRoot, { recursive: true, mode: 448 });
14095
14536
  assertNoSymlinkComponents(dataRoot);
14096
- const dataStat = lstatSync15(dataRoot);
14537
+ const dataStat = lstatSync16(dataRoot);
14097
14538
  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");
14098
14539
  chmodSync6(dataRoot, 448);
14099
14540
  if (!existsSync15(payload)) {
14100
- const staging = join21(dataRoot, `.staging-${randomUUID5()}`);
14101
- mkdirSync9(staging, { recursive: false, mode: 448 });
14541
+ const staging = join22(dataRoot, `.staging-${randomUUID5()}`);
14542
+ mkdirSync10(staging, { recursive: false, mode: 448 });
14102
14543
  try {
14103
14544
  for (const entry of manifest.files) {
14104
- const destination = join21(staging, ...entry.path.split("/"));
14105
- mkdirSync9(dirname16(destination), { recursive: true, mode: 493 });
14106
- copyFileSync3(join21(source, ...entry.path.split("/")), destination);
14545
+ const destination = join22(staging, ...entry.path.split("/"));
14546
+ mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
14547
+ copyFileSync3(join22(source, ...entry.path.split("/")), destination);
14107
14548
  const mode = entry.mode === "0755" ? 493 : 420;
14108
14549
  chmodSync6(destination, mode);
14109
- const fd = openSync6(destination, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
14550
+ const fd = openSync7(destination, constants5.O_RDONLY | (constants5.O_NOFOLLOW ?? 0));
14110
14551
  try {
14111
- const current = fstatSync3(fd);
14552
+ const current = fstatSync4(fd);
14112
14553
  if (!current.isFile()) throw new NotebookError("CONFLICT", `Installed skill entry is not regular: ${entry.path}`);
14113
14554
  fsyncSync4(fd);
14114
14555
  } finally {
14115
- closeSync6(fd);
14556
+ closeSync7(fd);
14116
14557
  }
14117
14558
  }
14118
- writeFileSync12(join21(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
14559
+ writeFileSync13(join22(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
14119
14560
  `, { mode: 420, flag: "wx" });
14120
- writeFileSync12(join21(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
14561
+ writeFileSync13(join22(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
14121
14562
  `, { mode: 420, flag: "wx" });
14122
14563
  verifyProjectNotebookSkillExport(staging);
14123
14564
  renameSync8(staging, payload);
@@ -14125,17 +14566,17 @@ function installPackagedProjectNotebookSkill(input = {}) {
14125
14566
  if (existsSync15(staging)) rmSync7(staging, { recursive: true, force: true });
14126
14567
  }
14127
14568
  } else {
14128
- const stat = lstatSync15(payload);
14569
+ const stat = lstatSync16(payload);
14129
14570
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
14130
14571
  verifyProjectNotebookSkillExport(payload);
14131
14572
  }
14132
14573
  assertNoSymlinkComponents(skillsRoot, true);
14133
- mkdirSync9(skillsRoot, { recursive: true, mode: 448 });
14574
+ mkdirSync10(skillsRoot, { recursive: true, mode: 448 });
14134
14575
  assertNoSymlinkComponents(skillsRoot);
14135
14576
  let linkExists = existsSync15(link);
14136
14577
  if (!linkExists) {
14137
14578
  try {
14138
- lstatSync15(link);
14579
+ lstatSync16(link);
14139
14580
  linkExists = true;
14140
14581
  } catch {
14141
14582
  }
@@ -14152,7 +14593,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
14152
14593
  repair: REPAIR_COMMAND
14153
14594
  }
14154
14595
  });
14155
- const stat = lstatSync15(link);
14596
+ const stat = lstatSync16(link);
14156
14597
  if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
14157
14598
  const target = realpathSync10(link);
14158
14599
  if (target !== realpathSync10(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
@@ -14180,15 +14621,15 @@ function collectSupersededPayloads(dataRoot, keep) {
14180
14621
  const removed = [];
14181
14622
  let entries;
14182
14623
  try {
14183
- entries = readdirSync10(dataRoot);
14624
+ entries = readdirSync11(dataRoot);
14184
14625
  } catch {
14185
14626
  return removed;
14186
14627
  }
14187
14628
  for (const name of entries) {
14188
14629
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
14189
- const candidate = join21(dataRoot, name);
14630
+ const candidate = join22(dataRoot, name);
14190
14631
  try {
14191
- const stat = lstatSync15(candidate);
14632
+ const stat = lstatSync16(candidate);
14192
14633
  if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
14193
14634
  if (kept.has(realpathSync10(candidate))) continue;
14194
14635
  rmSync7(candidate, { recursive: true, force: true });
@@ -14202,8 +14643,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
14202
14643
  const env2 = input.env ?? process.env;
14203
14644
  const home = env2.HOME;
14204
14645
  if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
14205
- const skillsRoot = join21(home, ".agents", "skills");
14206
- const link = join21(skillsRoot, "project-notebook");
14646
+ const skillsRoot = join22(home, ".agents", "skills");
14647
+ const link = join22(skillsRoot, "project-notebook");
14207
14648
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
14208
14649
  if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
14209
14650
  if (probe.state !== "declined") {
@@ -14216,8 +14657,8 @@ function repairProjectNotebookSkillProjection(input = {}) {
14216
14657
  assertOwnedSkillTree(source);
14217
14658
  const present = [];
14218
14659
  const walk = (directory) => {
14219
- for (const entry of readdirSync10(directory, { withFileTypes: true })) {
14220
- const path = join21(directory, entry.name);
14660
+ for (const entry of readdirSync11(directory, { withFileTypes: true })) {
14661
+ const path = join22(directory, entry.name);
14221
14662
  const rel = relative15(source, path).split(sep7).join("/");
14222
14663
  if (entry.isDirectory()) {
14223
14664
  walk(path);
@@ -14231,11 +14672,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
14231
14672
  walk(source);
14232
14673
  const stale = present.filter((rel) => !wanted.has(rel)).sort();
14233
14674
  const changed = expected.manifest.files.filter((entry) => {
14234
- const path = join21(source, ...entry.path.split("/"));
14675
+ const path = join22(source, ...entry.path.split("/"));
14235
14676
  if (!existsSync15(path)) return true;
14236
- const stat = lstatSync15(path);
14677
+ const stat = lstatSync16(path);
14237
14678
  if (!stat.isFile()) return true;
14238
- if (createHash12("sha256").update(readFileSync18(path)).digest("hex") !== entry.sha256) return true;
14679
+ if (createHash13("sha256").update(readFileSync19(path)).digest("hex") !== entry.sha256) return true;
14239
14680
  const mode = stat.mode & 511;
14240
14681
  const executable = entry.mode === "0755";
14241
14682
  return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
@@ -14250,15 +14691,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
14250
14691
  summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
14251
14692
  source,
14252
14693
  drift: probe.block.details,
14253
- changed_files: affected.map((rel) => join21(source, ...rel.split("/")))
14694
+ changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
14254
14695
  };
14255
14696
  }
14256
- for (const rel of stale) rmSync7(join21(source, ...rel.split("/")), { force: true });
14697
+ for (const rel of stale) rmSync7(join22(source, ...rel.split("/")), { force: true });
14257
14698
  for (const rel of changed) {
14258
- const destination = join21(source, ...rel.split("/"));
14699
+ const destination = join22(source, ...rel.split("/"));
14259
14700
  const entry = expected.manifest.files.find((item) => item.path === rel);
14260
- mkdirSync9(dirname16(destination), { recursive: true, mode: 493 });
14261
- copyFileSync3(join21(expected.source, ...rel.split("/")), destination);
14701
+ mkdirSync10(dirname16(destination), { recursive: true, mode: 493 });
14702
+ copyFileSync3(join22(expected.source, ...rel.split("/")), destination);
14262
14703
  chmodSync6(destination, entry.mode === "0755" ? 493 : 420);
14263
14704
  }
14264
14705
  verifyProjectNotebookSkillExport(source);
@@ -14267,21 +14708,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
14267
14708
  summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
14268
14709
  source,
14269
14710
  drift: probe.block.details,
14270
- changed_files: affected.map((rel) => join21(source, ...rel.split("/")))
14711
+ changed_files: affected.map((rel) => join22(source, ...rel.split("/")))
14271
14712
  };
14272
14713
  }
14273
14714
  function inspectProjectNotebookIntegration(env2 = process.env) {
14274
14715
  const home = env2.HOME;
14275
14716
  if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
14276
- const skillsRoot = join21(home, ".agents", "skills");
14277
- const link = join21(skillsRoot, "project-notebook");
14717
+ const skillsRoot = join22(home, ".agents", "skills");
14718
+ const link = join22(skillsRoot, "project-notebook");
14278
14719
  try {
14279
14720
  const expected = expectedPackedSkill();
14280
- const dataRoot = resolve15(env2.XDG_DATA_HOME || join21(home, ".local", "share"), "pjangler", "skills", "project-notebook");
14281
- const expectedPayload = join21(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
14721
+ const dataRoot = resolve15(env2.XDG_DATA_HOME || join22(home, ".local", "share"), "pjangler", "skills", "project-notebook");
14722
+ const expectedPayload = join22(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
14282
14723
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
14283
14724
  if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
14284
- const stat = lstatSync15(link);
14725
+ const stat = lstatSync16(link);
14285
14726
  if (!stat.isSymbolicLink()) {
14286
14727
  return {
14287
14728
  skill_installed: false,
@@ -14318,8 +14759,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
14318
14759
  }
14319
14760
  }
14320
14761
  function projectorArguments(source, command, input) {
14321
- const script = join21(source, "scripts", "project-hooks.py");
14322
- const args = [script, command, "--master", join21(source, "hooks", "hooks.master.json"), "--fragment", join21(source, "hooks", "claude.settings.json"), "--target", input.target];
14762
+ const script = join22(source, "scripts", "project-hooks.py");
14763
+ const args = [script, command, "--master", join22(source, "hooks", "hooks.master.json"), "--fragment", join22(source, "hooks", "claude.settings.json"), "--target", input.target];
14323
14764
  if (command === "check") args.push("--json");
14324
14765
  else if (input.stateHome) args.push("--state-home", input.stateHome);
14325
14766
  return args;
@@ -14341,7 +14782,7 @@ function checkProjectNotebookHooks(input = {}) {
14341
14782
  verifyProjectNotebookSkillExport(source);
14342
14783
  const home = env2.HOME;
14343
14784
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
14344
- const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join21(home, ".claude", "settings.json"));
14785
+ const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
14345
14786
  const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
14346
14787
  if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
14347
14788
  try {
@@ -14359,14 +14800,14 @@ function installProjectNotebookIntegration(input = {}) {
14359
14800
  const source = realpathSync10(skill.path);
14360
14801
  const home = env2.HOME;
14361
14802
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
14362
- const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join21(home, ".claude", "settings.json"));
14363
- const stateHome = resolve15(env2.XDG_STATE_HOME || join21(home, ".local", "state"));
14803
+ const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join22(home, ".claude", "settings.json"));
14804
+ const stateHome = resolve15(env2.XDG_STATE_HOME || join22(home, ".local", "state"));
14364
14805
  const result2 = spawnSync10("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
14365
14806
  if (result2.status !== 0) {
14366
14807
  if (skill.installed) {
14367
14808
  try {
14368
- const stat = lstatSync15(skill.path);
14369
- if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync8(skill.path);
14809
+ const stat = lstatSync16(skill.path);
14810
+ if (stat.isSymbolicLink() && realpathSync10(skill.path) === source) unlinkSync9(skill.path);
14370
14811
  } catch {
14371
14812
  }
14372
14813
  }
@@ -14383,10 +14824,10 @@ function assertNoSymlinkComponents(path, allowMissing = false) {
14383
14824
  const root = parse3(absolute).root;
14384
14825
  let cursor = root;
14385
14826
  for (const component of absolute.slice(root.length).split(sep7).filter(Boolean)) {
14386
- cursor = join21(cursor, component);
14827
+ cursor = join22(cursor, component);
14387
14828
  let stat;
14388
14829
  try {
14389
- stat = lstatSync15(cursor);
14830
+ stat = lstatSync16(cursor);
14390
14831
  } catch (error) {
14391
14832
  if (allowMissing && error.code === "ENOENT") return;
14392
14833
  throw error;
@@ -14992,17 +15433,17 @@ init_remote_mutation_journal();
14992
15433
  function integrationEnvironment(module, ctx) {
14993
15434
  const env2 = { ...module.environment, HOME: ctx.homeDir };
14994
15435
  if (module.environment.HOME !== ctx.homeDir) {
14995
- env2.XDG_DATA_HOME = join22(ctx.homeDir, ".local", "share");
14996
- env2.XDG_STATE_HOME = join22(ctx.homeDir, ".local", "state");
14997
- env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join22(ctx.homeDir, ".claude", "settings.json");
15436
+ env2.XDG_DATA_HOME = join23(ctx.homeDir, ".local", "share");
15437
+ env2.XDG_STATE_HOME = join23(ctx.homeDir, ".local", "state");
15438
+ env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join23(ctx.homeDir, ".claude", "settings.json");
14998
15439
  }
14999
15440
  return env2;
15000
15441
  }
15001
15442
  function resolvedForPlan(plan) {
15002
- const manifestPath = join22(plan.project.repo_path, ".project.json");
15443
+ const manifestPath = join23(plan.project.repo_path, ".project.json");
15003
15444
  let manifest = plan.manifest;
15004
15445
  if (existsSync17(manifestPath)) {
15005
- const parsed = JSON.parse(readFileSync19(manifestPath, "utf8"));
15446
+ const parsed = JSON.parse(readFileSync20(manifestPath, "utf8"));
15006
15447
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
15007
15448
  manifest = parsed;
15008
15449
  }
@@ -15163,7 +15604,7 @@ function unsafeToRemove(targetDir) {
15163
15604
  while (!seen.has(cursor)) {
15164
15605
  seen.add(cursor);
15165
15606
  try {
15166
- const stat = lstatSync16(cursor);
15607
+ const stat = lstatSync17(cursor);
15167
15608
  if (stat.isSymbolicLink()) {
15168
15609
  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`;
15169
15610
  }
@@ -15191,12 +15632,12 @@ function publicMigration(report) {
15191
15632
  };
15192
15633
  }
15193
15634
  function hasGitRepository(runtime, targetDir) {
15194
- if (!existsSync18(join23(targetDir, ".git"))) return false;
15635
+ if (!existsSync18(join24(targetDir, ".git"))) return false;
15195
15636
  return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
15196
15637
  }
15197
15638
  function refreshPlanFromCanonicalManifest(plan) {
15198
- const manifestPath = join23(plan.project.repo_path, ".project.json");
15199
- const manifest = JSON.parse(readFileSync20(manifestPath, "utf8"));
15639
+ const manifestPath = join24(plan.project.repo_path, ".project.json");
15640
+ const manifest = JSON.parse(readFileSync21(manifestPath, "utf8"));
15200
15641
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
15201
15642
  throw new Error(`${manifestPath} must contain a JSON object`);
15202
15643
  }
@@ -15427,7 +15868,7 @@ var ProjectRecipe = class extends Recipe {
15427
15868
  if (hasGitRepository(this.runtime, targetDir)) {
15428
15869
  phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
15429
15870
  } else {
15430
- const gitPath = join23(targetDir, ".git");
15871
+ const gitPath = join24(targetDir, ".git");
15431
15872
  for (const { args, label, options } of [
15432
15873
  { args: ["init", "--initial-branch=main"], label: "git init" },
15433
15874
  { args: ["add", "-A"], label: "git add" },
@@ -15638,12 +16079,12 @@ var ProjectRecipe = class extends Recipe {
15638
16079
  };
15639
16080
 
15640
16081
  // src/recipes/registry.ts
15641
- import { readFileSync as readFileSync21 } from "node:fs";
15642
- import { join as join24 } from "node:path";
16082
+ import { readFileSync as readFileSync22 } from "node:fs";
16083
+ import { join as join25 } from "node:path";
15643
16084
  function projectWaivers(repoRoot) {
15644
16085
  let waive;
15645
16086
  try {
15646
- waive = JSON.parse(readFileSync21(join24(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
16087
+ waive = JSON.parse(readFileSync22(join25(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
15647
16088
  } catch {
15648
16089
  return /* @__PURE__ */ new Map();
15649
16090
  }
@@ -15984,8 +16425,8 @@ var recipeRegistry = new RecipeRegistry([
15984
16425
 
15985
16426
  // src/commands/AgentHooksCommands.ts
15986
16427
  import { homedir as homedir8 } from "node:os";
15987
- import { join as join25, dirname as dirname18 } from "node:path";
15988
- import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync13 } from "node:fs";
16428
+ import { join as join26, dirname as dirname18 } from "node:path";
16429
+ import { existsSync as existsSync19, cpSync, mkdirSync as mkdirSync11, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "node:fs";
15989
16430
  import { fileURLToPath as fileURLToPath4 } from "node:url";
15990
16431
  init_project();
15991
16432
  import { parse as parseToml4 } from "smol-toml";
@@ -15998,16 +16439,16 @@ function resolveTemplateRoot() {
15998
16439
  try {
15999
16440
  let dir = dirname18(fileURLToPath4(import.meta.url));
16000
16441
  for (let i = 0; i < 8; i++) {
16001
- candidates.push(join25(dir, "templates", "commonproject", "template"));
16442
+ candidates.push(join26(dir, "templates", "commonproject", "template"));
16002
16443
  const parent = dirname18(dir);
16003
16444
  if (parent === dir) break;
16004
16445
  dir = parent;
16005
16446
  }
16006
16447
  } catch {
16007
16448
  }
16008
- candidates.push(join25(homedir8(), "code", "pjangler", "templates", "commonproject", "template"));
16449
+ candidates.push(join26(homedir8(), "code", "pjangler", "templates", "commonproject", "template"));
16009
16450
  for (const c of candidates) {
16010
- if (existsSync19(join25(c, ".agents", "hooks", "hooks.master.json"))) return c;
16451
+ if (existsSync19(join26(c, ".agents", "hooks", "hooks.master.json"))) return c;
16011
16452
  }
16012
16453
  throw new Error(
16013
16454
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -16033,15 +16474,15 @@ var CopyAgentHooksTree = class extends Command {
16033
16474
  const created = [];
16034
16475
  const skipped = [];
16035
16476
  for (const { rel, dir } of items) {
16036
- const src = join25(templateRoot, rel);
16037
- const dest = join25(this.context.targetDir, rel);
16477
+ const src = join26(templateRoot, rel);
16478
+ const dest = join26(this.context.targetDir, rel);
16038
16479
  if (!existsSync19(src)) continue;
16039
16480
  if (existsSync19(dest) && !this.context.force) {
16040
16481
  skipped.push(rel);
16041
16482
  continue;
16042
16483
  }
16043
16484
  if (!this.context.dryRun) {
16044
- mkdirSync10(dirname18(dest), { recursive: true });
16485
+ mkdirSync11(dirname18(dest), { recursive: true });
16045
16486
  cpSync(src, dest, { recursive: dir, force: true });
16046
16487
  }
16047
16488
  created.push(rel);
@@ -16062,14 +16503,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
16062
16503
  if (!resolveAgentHooksLayer2()) {
16063
16504
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
16064
16505
  }
16065
- const misePath = join25(this.context.targetDir, "mise.toml");
16506
+ const misePath = join26(this.context.targetDir, "mise.toml");
16066
16507
  if (!existsSync19(misePath)) {
16067
16508
  return {
16068
16509
  success: false,
16069
16510
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
16070
16511
  };
16071
16512
  }
16072
- let content = readFileSync22(misePath, "utf8");
16513
+ let content = readFileSync23(misePath, "utf8");
16073
16514
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
16074
16515
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
16075
16516
  }
@@ -16153,7 +16594,7 @@ ${leaveBlock}`);
16153
16594
  if (!verdict.ok) {
16154
16595
  return { success: false, message: `\u26A0\uFE0F mise.toml was not rewritten: ${verdict.reason}. Add the agent-hooks tasks by hand.` };
16155
16596
  }
16156
- if (!this.context.dryRun) writeFileSync13(misePath, content);
16597
+ if (!this.context.dryRun) writeFileSync14(misePath, content);
16157
16598
  if (wiredHooks) {
16158
16599
  return { success: true, message: this.formatMessage("\u2705 Wired mise.toml ([hooks] enter/leave + tasks)") };
16159
16600
  }
@@ -16284,7 +16725,7 @@ if __name__ == "__main__":
16284
16725
 
16285
16726
  // src/commands/AddMiseCodegraphScript.ts
16286
16727
  import { chmodSync as chmodSync7 } from "fs";
16287
- import { join as join26 } from "path";
16728
+ import { join as join27 } from "path";
16288
16729
  var AddMiseCodegraphScript = class extends Command {
16289
16730
  async invoke() {
16290
16731
  const filePath = ".mise/scripts/codegraph.sh";
@@ -16338,7 +16779,7 @@ fi
16338
16779
  `;
16339
16780
  this.writeFile(filePath, content);
16340
16781
  if (!this.context.dryRun) {
16341
- chmodSync7(join26(this.context.targetDir, filePath), 493);
16782
+ chmodSync7(join27(this.context.targetDir, filePath), 493);
16342
16783
  }
16343
16784
  return {
16344
16785
  success: true,
@@ -16376,12 +16817,12 @@ SECRET_KEY=""
16376
16817
  // src/parity/index.ts
16377
16818
  import { existsSync as existsSync20 } from "node:fs";
16378
16819
  import { homedir as homedir9 } from "node:os";
16379
- import { dirname as dirname19, join as join27, resolve as resolve19 } from "node:path";
16820
+ import { dirname as dirname19, join as join28, resolve as resolve19 } from "node:path";
16380
16821
  import { fileURLToPath as fileURLToPath5 } from "node:url";
16381
16822
  function resolvePjanglerRoot2() {
16382
16823
  let dir = dirname19(fileURLToPath5(import.meta.url));
16383
16824
  while (dir !== dirname19(dir)) {
16384
- if (existsSync20(join27(dir, "package.json")) && existsSync20(join27(dir, "templates", "commonproject", "copier.yml"))) return dir;
16825
+ if (existsSync20(join28(dir, "package.json")) && existsSync20(join28(dir, "templates", "commonproject", "copier.yml"))) return dir;
16385
16826
  dir = dirname19(dir);
16386
16827
  }
16387
16828
  return resolve19(process.cwd());
@@ -16556,13 +16997,13 @@ init_version();
16556
16997
  // src/project/info.ts
16557
16998
  init_boardUrl();
16558
16999
  init_project();
16559
- import { readFileSync as readFileSync23 } from "node:fs";
16560
- import { join as join28, resolve as resolve20 } from "node:path";
17000
+ import { readFileSync as readFileSync24 } from "node:fs";
17001
+ import { join as join29, resolve as resolve20 } from "node:path";
16561
17002
  function readManifest2(root) {
16562
- const path = join28(root, ".project.json");
17003
+ const path = join29(root, ".project.json");
16563
17004
  let manifest;
16564
17005
  try {
16565
- const value = JSON.parse(readFileSync23(path, "utf8"));
17006
+ const value = JSON.parse(readFileSync24(path, "utf8"));
16566
17007
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
16567
17008
  manifest = value;
16568
17009
  } catch (error) {
@@ -16603,7 +17044,7 @@ function readProjectInfo(input = {}) {
16603
17044
  ok: true,
16604
17045
  project_id: projectId,
16605
17046
  repo_path: resolve20(root),
16606
- manifest_path: join28(resolve20(root), ".project.json"),
17047
+ manifest_path: join29(resolve20(root), ".project.json"),
16607
17048
  manifest,
16608
17049
  registry: { location, status: "indexed" }
16609
17050
  };
@@ -16631,14 +17072,14 @@ function readProjectInfo(input = {}) {
16631
17072
  }
16632
17073
 
16633
17074
  // src/describe/index.ts
16634
- import { existsSync as existsSync21, readFileSync as readFileSync24, readdirSync as readdirSync11, statSync as statSync7 } from "node:fs";
16635
- import { join as join30, resolve as resolve21 } from "node:path";
17075
+ import { existsSync as existsSync21, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync7 } from "node:fs";
17076
+ import { join as join31, resolve as resolve21 } from "node:path";
16636
17077
  init_project();
16637
17078
 
16638
17079
  // src/describe/activity.ts
16639
17080
  import { spawn as spawn2, spawnSync as spawnSync14 } from "node:child_process";
16640
17081
  import { statSync as statSync6 } from "node:fs";
16641
- import { join as join29 } from "node:path";
17082
+ import { join as join30 } from "node:path";
16642
17083
  var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
16643
17084
  var MAX_DIRTY_STATS = 500;
16644
17085
  var GIT_TIMEOUT_MS = 5e3;
@@ -16767,7 +17208,7 @@ function uncommittedSource(repo, paths) {
16767
17208
  let newest = 0;
16768
17209
  for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
16769
17210
  try {
16770
- const mtime = Math.floor(statSync6(join29(repo, path)).mtimeMs / 1e3);
17211
+ const mtime = Math.floor(statSync6(join30(repo, path)).mtimeMs / 1e3);
16771
17212
  if (mtime > newest) newest = mtime;
16772
17213
  } catch {
16773
17214
  }
@@ -16876,7 +17317,7 @@ var CONFIG_FILES = [
16876
17317
  ];
16877
17318
  function readJson(path) {
16878
17319
  try {
16879
- const parsed = JSON.parse(readFileSync24(path, "utf8"));
17320
+ const parsed = JSON.parse(readFileSync25(path, "utf8"));
16880
17321
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
16881
17322
  } catch {
16882
17323
  return void 0;
@@ -16898,23 +17339,23 @@ function describeType(repo) {
16898
17339
  const evidence = [];
16899
17340
  const note = (signal, file) => evidence.push(`${signal} (${file})`);
16900
17341
  for (const marker of LANGUAGE_MARKERS) {
16901
- if (!existsSync21(join30(repo, marker.file))) continue;
17342
+ if (!existsSync21(join31(repo, marker.file))) continue;
16902
17343
  if (!languages.includes(marker.language)) {
16903
17344
  languages.push(marker.language);
16904
17345
  note(marker.language, marker.file);
16905
17346
  }
16906
17347
  }
16907
17348
  try {
16908
- const dotnet = readdirSync11(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
17349
+ const dotnet = readdirSync12(repo).find((entry) => entry.endsWith(".csproj") || entry.endsWith(".sln"));
16909
17350
  if (dotnet && !languages.includes("dotnet")) {
16910
17351
  languages.push("dotnet");
16911
17352
  note("dotnet", dotnet);
16912
17353
  }
16913
17354
  } catch {
16914
17355
  }
16915
- const pkg = readJson(join30(repo, "package.json"));
17356
+ const pkg = readJson(join31(repo, "package.json"));
16916
17357
  if (pkg) {
16917
- if (existsSync21(join30(repo, "tsconfig.json"))) {
17358
+ if (existsSync21(join31(repo, "tsconfig.json"))) {
16918
17359
  const index = languages.indexOf("javascript");
16919
17360
  if (index >= 0) languages.splice(index, 1);
16920
17361
  if (!languages.includes("typescript")) {
@@ -16948,14 +17389,14 @@ function describeType(repo) {
16948
17389
  ["hermes-fleet-host", "agents/hermes"]
16949
17390
  ];
16950
17391
  for (const [role, marker] of roleMarkers) {
16951
- if (!existsSync21(join30(repo, marker))) continue;
17392
+ if (!existsSync21(join31(repo, marker))) continue;
16952
17393
  roles.push(role);
16953
17394
  note(role, marker);
16954
17395
  }
16955
17396
  return { primaryLanguage: languages[0], languages, roles, evidence };
16956
17397
  }
16957
17398
  function describeIdentity(repo, registryPath) {
16958
- const manifestPath = join30(repo, ".project.json");
17399
+ const manifestPath = join31(repo, ".project.json");
16959
17400
  const manifest = readJson(manifestPath);
16960
17401
  const drift = [];
16961
17402
  let record3;
@@ -17048,12 +17489,12 @@ function describeSubsystems(repo, findings) {
17048
17489
  const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
17049
17490
  const evidence = metadata.id === "notebook" ? (() => {
17050
17491
  try {
17051
- const manifest = JSON.parse(readFileSync24(join30(repo, ".project.json"), "utf8"));
17492
+ const manifest = JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8"));
17052
17493
  return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
17053
17494
  } catch {
17054
17495
  return [];
17055
17496
  }
17056
- })() : markers.filter((marker) => existsSync21(join30(repo, marker)));
17497
+ })() : markers.filter((marker) => existsSync21(join31(repo, marker)));
17057
17498
  const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
17058
17499
  id: finding2.id,
17059
17500
  title: finding2.title,
@@ -17072,7 +17513,7 @@ function describeNotebook(repo, registryPath) {
17072
17513
  try {
17073
17514
  const registry = loadProjectRegistry(registryPath);
17074
17515
  const project = Object.values(registry.projects).find((entry) => resolve21(entry.repo_path) === resolve21(repo));
17075
- const manifest = existsSync21(join30(repo, ".project.json")) ? JSON.parse(readFileSync24(join30(repo, ".project.json"), "utf8")) : void 0;
17516
+ const manifest = existsSync21(join31(repo, ".project.json")) ? JSON.parse(readFileSync25(join31(repo, ".project.json"), "utf8")) : void 0;
17076
17517
  const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
17077
17518
  if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
17078
17519
  const config = loadEffectiveNotebookConfig(repo, registryPath);
@@ -17090,7 +17531,7 @@ function describeNotebook(repo, registryPath) {
17090
17531
  }
17091
17532
  }
17092
17533
  function describeConfigFiles(repo) {
17093
- return CONFIG_FILES.filter((spec) => existsSync21(join30(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
17534
+ return CONFIG_FILES.filter((spec) => existsSync21(join31(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
17094
17535
  }
17095
17536
  function describeNextSteps(description, findings) {
17096
17537
  const steps = [];
@@ -17369,7 +17810,7 @@ function resolveTargetDir(targetDir) {
17369
17810
  function resolvePjanglerRoot3() {
17370
17811
  let dir = dirname20(fileURLToPath6(import.meta.url));
17371
17812
  while (dir !== dirname20(dir)) {
17372
- if (existsSync22(join31(dir, "package.json")) && existsSync22(join31(dir, "templates", "commonproject", "copier.yml"))) {
17813
+ if (existsSync22(join32(dir, "package.json")) && existsSync22(join32(dir, "templates", "commonproject", "copier.yml"))) {
17373
17814
  return dir;
17374
17815
  }
17375
17816
  dir = dirname20(dir);
@@ -17669,7 +18110,7 @@ server.registerTool(
17669
18110
  if (!existsSync22(parentDir) || !statSync8(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
17670
18111
  const targetDir = resolveContainedPath(
17671
18112
  parentDir,
17672
- explicitTargetDir ?? join31(parentDir, projectSlug),
18113
+ explicitTargetDir ?? join32(parentDir, projectSlug),
17673
18114
  "Bootstrap target"
17674
18115
  );
17675
18116
  const overwrite = input.overwrite ?? input.force ?? false;
@@ -17792,7 +18233,7 @@ server.registerTool(
17792
18233
  registryPath: input.registryPath,
17793
18234
  force: input.force ?? false,
17794
18235
  overwrite: input.force ?? false,
17795
- scaffold: !(input.targetDir && existsSync22(join31(resolve22(input.targetDir), ".git")))
18236
+ scaffold: !(input.targetDir && existsSync22(join32(resolve22(input.targetDir), ".git")))
17796
18237
  });
17797
18238
  if (!input.apply) return asText(publicCompositeProjectResponse(publicProjectPlan(plan), plan));
17798
18239
  const preflight = await preflightProjectApply(plan, resolvePjanglerRoot3());