@delorenj/pjangler 1.5.12 → 1.5.14

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.
@@ -177,6 +177,41 @@ try {
177
177
  }
178
178
  });
179
179
 
180
+ // src/utils/tree-diff.ts
181
+ import { createHash } from "node:crypto";
182
+ import { lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs";
183
+ import { join as join2, relative } from "node:path";
184
+ function snapshotTree(root, current = root, snapshot = /* @__PURE__ */ new Map()) {
185
+ let stat;
186
+ try {
187
+ stat = lstatSync(current);
188
+ } catch (error) {
189
+ if (error.code === "ENOENT") return snapshot;
190
+ throw error;
191
+ }
192
+ const rel = relative(root, current) || ".";
193
+ if (rel === ".git" || rel.startsWith(`.git${process.platform === "win32" ? "\\" : "/"}`)) return snapshot;
194
+ if (stat.isSymbolicLink()) {
195
+ snapshot.set(rel, `link:${readlinkSync(current)}`);
196
+ } else if (stat.isFile()) {
197
+ snapshot.set(rel, `file:${createHash("sha256").update(readFileSync(current)).digest("hex")}:${stat.mode & 4095}`);
198
+ } else if (stat.isDirectory()) {
199
+ snapshot.set(rel, `dir:${stat.mode & 4095}`);
200
+ for (const name of readdirSync(current)) snapshotTree(root, join2(current, name), snapshot);
201
+ } else {
202
+ snapshot.set(rel, `other:${stat.mode}`);
203
+ }
204
+ return snapshot;
205
+ }
206
+ function changedTreePaths(root, before, after) {
207
+ return [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].filter((path) => before.get(path) !== after.get(path)).map((path) => join2(root, path)).sort();
208
+ }
209
+ var init_tree_diff = __esm({
210
+ "src/utils/tree-diff.ts"() {
211
+ "use strict";
212
+ }
213
+ });
214
+
180
215
  // src/notebook/types.ts
181
216
  function notebookCredentialMaterialPath(value) {
182
217
  const seen = /* @__PURE__ */ new WeakSet();
@@ -258,7 +293,7 @@ var init_types = __esm({
258
293
 
259
294
  // src/project/notebookSettingsValidation.ts
260
295
  import { isIP } from "node:net";
261
- import { isAbsolute as isAbsolute2 } from "node:path";
296
+ import { isAbsolute as isAbsolute3 } from "node:path";
262
297
  function isRecord(value) {
263
298
  return typeof value === "object" && value !== null && !Array.isArray(value);
264
299
  }
@@ -315,7 +350,7 @@ function validateGlobalNotebookConfig(value) {
315
350
  if (limits.lease_seconds * 1e3 <= limits.overall_timeout_ms) throw new Error("Project registry notebook lease_seconds must exceed one request timeout");
316
351
  if (isRecord(value.defaults) && value.defaults.overview_max_chars !== void 0 && Number(value.defaults.overview_max_chars) > limits.note_max_bytes) throw new Error("Project registry notebook overview_max_chars exceeds note_max_bytes");
317
352
  if (value.summarizer !== void 0) {
318
- if (!isRecord(value.summarizer) || typeof value.summarizer.executable !== "string" || !isAbsolute2(value.summarizer.executable) || value.summarizer.executable.includes("\0") || Buffer.byteLength(value.summarizer.executable, "utf8") > 1024) throw new Error("Project registry notebook.summarizer executable must be a bounded absolute path");
353
+ if (!isRecord(value.summarizer) || typeof value.summarizer.executable !== "string" || !isAbsolute3(value.summarizer.executable) || value.summarizer.executable.includes("\0") || Buffer.byteLength(value.summarizer.executable, "utf8") > 1024) throw new Error("Project registry notebook.summarizer executable must be a bounded absolute path");
319
354
  if (value.summarizer.args !== void 0 && (!Array.isArray(value.summarizer.args) || value.summarizer.args.length > 32 || value.summarizer.args.some((entry) => typeof entry !== "string" || entry.includes("\0") || Buffer.byteLength(entry, "utf8") > 1024))) throw new Error("Project registry notebook.summarizer args must be bounded strings");
320
355
  }
321
356
  }
@@ -334,7 +369,7 @@ var init_executionBinding = __esm({
334
369
  });
335
370
 
336
371
  // src/project/manifestIndex.ts
337
- import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve6 } from "node:path";
372
+ import { basename as basename4, dirname as dirname6, join as join8, resolve as resolve7 } from "node:path";
338
373
  function normalizeProjectId2(value) {
339
374
  try {
340
375
  return normalizeProjectId(value);
@@ -380,53 +415,23 @@ var init_manifestIndex = __esm({
380
415
  }
381
416
  });
382
417
 
383
- // src/utils/tree-diff.ts
384
- import { createHash as createHash3 } from "node:crypto";
385
- import { existsSync as existsSync4, lstatSync as lstatSync5, readFileSync as readFileSync4, readdirSync as readdirSync4, readlinkSync as readlinkSync4 } from "node:fs";
386
- import { join as join7, relative as relative5 } from "node:path";
387
- function snapshotTree(root, current = root, snapshot = /* @__PURE__ */ new Map()) {
388
- if (!existsSync4(current)) return snapshot;
389
- const rel = relative5(root, current) || ".";
390
- if (rel === ".git" || rel.startsWith(`.git${process.platform === "win32" ? "\\" : "/"}`)) return snapshot;
391
- const stat = lstatSync5(current);
392
- if (stat.isSymbolicLink()) {
393
- snapshot.set(rel, `link:${readlinkSync4(current)}`);
394
- } else if (stat.isFile()) {
395
- snapshot.set(rel, `file:${createHash3("sha256").update(readFileSync4(current)).digest("hex")}:${stat.mode & 511}`);
396
- } else if (stat.isDirectory()) {
397
- snapshot.set(rel, `dir:${stat.mode & 511}`);
398
- for (const name of readdirSync4(current)) snapshotTree(root, join7(current, name), snapshot);
399
- } else {
400
- snapshot.set(rel, `other:${stat.mode}`);
401
- }
402
- return snapshot;
403
- }
404
- function changedTreePaths(root, before, after) {
405
- return [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].filter((path) => path !== "." && before.get(path) !== after.get(path)).map((path) => join7(root, path)).sort();
406
- }
407
- var init_tree_diff = __esm({
408
- "src/utils/tree-diff.ts"() {
409
- "use strict";
410
- }
411
- });
412
-
413
418
  // src/lifecycle/preflight.ts
414
419
  import { createHash as createHash4 } from "node:crypto";
415
- import { accessSync, constants, existsSync as existsSync5, lstatSync as lstatSync6, readFileSync as readFileSync5, readdirSync as readdirSync5, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
420
+ import { accessSync, constants, existsSync as existsSync5, lstatSync as lstatSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, realpathSync as realpathSync4, statSync as statSync2 } from "node:fs";
416
421
  import { tmpdir, userInfo } from "node:os";
417
- import { basename as basename5, delimiter, dirname as dirname7, isAbsolute as isAbsolute3, join as join8, relative as relative6, resolve as resolve7 } from "node:path";
422
+ import { basename as basename5, delimiter, dirname as dirname7, isAbsolute as isAbsolute4, join as join9, relative as relative6, resolve as resolve8 } from "node:path";
418
423
  import YAML3 from "yaml";
419
424
  function containedBy(parent, candidate) {
420
- const rel = relative6(resolve7(parent), resolve7(candidate));
421
- return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
425
+ const rel = relative6(resolve8(parent), resolve8(candidate));
426
+ return rel === "" || !rel.startsWith("..") && !isAbsolute4(rel);
422
427
  }
423
428
  function accountMiseShimsDir(home) {
424
- return resolve7(join8(home, ".local", "share", "mise", "shims"));
429
+ return resolve8(join9(home, ".local", "share", "mise", "shims"));
425
430
  }
426
431
  function inactiveMiseShim(candidate, shimsDir) {
427
432
  if (dirname7(candidate) !== shimsDir) return void 0;
428
433
  try {
429
- if (!lstatSync6(candidate).isSymbolicLink()) return void 0;
434
+ if (!lstatSync7(candidate).isSymbolicLink()) return void 0;
430
435
  const realPath = realpathSync4(candidate);
431
436
  if (basename5(realPath) !== "mise") return void 0;
432
437
  return { path: candidate, realPath, reason: "inactive-mise-shim" };
@@ -438,10 +443,10 @@ function resolveCopierOnPath(env2, shimsDir) {
438
443
  const skipped = [];
439
444
  for (const rawEntry of (env2.PATH ?? "").split(delimiter)) {
440
445
  const entry = rawEntry || process.cwd();
441
- const candidate = resolve7(entry, process.platform === "win32" ? "copier.exe" : "copier");
446
+ const candidate = resolve8(entry, process.platform === "win32" ? "copier.exe" : "copier");
442
447
  try {
443
448
  accessSync(candidate, constants.X_OK);
444
- const stat = lstatSync6(candidate);
449
+ const stat = lstatSync7(candidate);
445
450
  if (!stat.isFile() && !stat.isSymbolicLink()) continue;
446
451
  } catch {
447
452
  continue;
@@ -459,10 +464,10 @@ function describeSkipped(skipped) {
459
464
  return skipped.map((entry) => `${entry.path} -> ${entry.realPath}`).join(", ");
460
465
  }
461
466
  function sha256(path) {
462
- return createHash4("sha256").update(readFileSync5(path)).digest("base64url");
467
+ return createHash4("sha256").update(readFileSync6(path)).digest("base64url");
463
468
  }
464
469
  function fingerprint(path) {
465
- const absolute = resolve7(path);
470
+ const absolute = resolve8(path);
466
471
  const realPath = realpathSync4(absolute);
467
472
  const stat = statSync2(realPath);
468
473
  if (!stat.isFile()) throw new Error(`${absolute} is not a regular file`);
@@ -497,17 +502,17 @@ function sameFingerprint(expected) {
497
502
  function consoleScriptContract(path) {
498
503
  let text;
499
504
  try {
500
- text = readFileSync5(path, "utf8").slice(0, 32 * 1024);
505
+ text = readFileSync6(path, "utf8").slice(0, 32 * 1024);
501
506
  } catch (error) {
502
507
  return { ok: false, error: `cannot read Copier launcher: ${error instanceof Error ? error.message : String(error)}` };
503
508
  }
504
509
  const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
505
510
  if (!firstLine.startsWith("#!")) return { ok: false, error: "Copier launcher has no executable shebang" };
506
511
  const shebang = firstLine.slice(2).trim().split(/\s+/);
507
- if (shebang.length !== 1 || !isAbsolute3(shebang[0] ?? "")) {
512
+ if (shebang.length !== 1 || !isAbsolute4(shebang[0] ?? "")) {
508
513
  return { ok: false, error: "Copier launcher must use one absolute Python interpreter" };
509
514
  }
510
- const interpreterPath = resolve7(shebang[0]);
515
+ const interpreterPath = resolve8(shebang[0]);
511
516
  const interpreter = basename5(interpreterPath);
512
517
  if (!/^python(?:\d+(?:\.\d+)*)?$/.test(interpreter)) {
513
518
  return { ok: false, error: "Copier launcher is not an absolute Python console script" };
@@ -519,16 +524,16 @@ function consoleScriptContract(path) {
519
524
  }
520
525
  function defaultUvToolRoots(home) {
521
526
  return [
522
- join8(home, ".local", "share", "uv", "tools", "copier"),
523
- join8(home, "Library", "Application Support", "uv", "tools", "copier")
524
- ].map((path) => resolve7(path));
527
+ join9(home, ".local", "share", "uv", "tools", "copier"),
528
+ join9(home, "Library", "Application Support", "uv", "tools", "copier")
529
+ ].map((path) => resolve8(path));
525
530
  }
526
531
  function locateUvSitePackages(toolRoot) {
527
- const lib = join8(toolRoot, "lib");
532
+ const lib = join9(toolRoot, "lib");
528
533
  const candidates = [];
529
534
  for (const entry of readdirSync5(lib, { withFileTypes: true })) {
530
535
  if (!entry.isDirectory() || !/^python\d+(?:\.\d+)*$/.test(entry.name)) continue;
531
- const sitePackages = join8(lib, entry.name, "site-packages");
536
+ const sitePackages = join9(lib, entry.name, "site-packages");
532
537
  if (existsSync5(sitePackages)) candidates.push(sitePackages);
533
538
  }
534
539
  if (candidates.length !== 1) throw new Error(`expected one UV Copier site-packages directory, found ${candidates.length}`);
@@ -545,15 +550,15 @@ function parseRecordLine(line) {
545
550
  };
546
551
  }
547
552
  function attestUvCopier(candidate, realCandidate, home) {
548
- const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate === join8(root, "bin", "copier"));
553
+ const toolRoot = defaultUvToolRoots(home).find((root) => realCandidate === join9(root, "bin", "copier"));
549
554
  if (!toolRoot) {
550
555
  return { ok: false, error: `refusing untrusted PATH-shadowed Copier executable: ${candidate}` };
551
556
  }
552
557
  const supportedEntries = new Set([
553
- join8(home, ".local", "bin", "copier"),
554
- join8(toolRoot, "bin", "copier")
555
- ].map((path) => resolve7(path)));
556
- if (!supportedEntries.has(resolve7(candidate))) {
558
+ join9(home, ".local", "bin", "copier"),
559
+ join9(toolRoot, "bin", "copier")
560
+ ].map((path) => resolve8(path)));
561
+ if (!supportedEntries.has(resolve8(candidate))) {
557
562
  return { ok: false, error: `refusing non-canonical UV Copier entry point: ${candidate}` };
558
563
  }
559
564
  try {
@@ -561,43 +566,43 @@ function attestUvCopier(candidate, realCandidate, home) {
561
566
  if (!launcher.ok || !launcher.interpreter) {
562
567
  return { ...launcher, executable: realCandidate, realExecutable: realCandidate };
563
568
  }
564
- const expectedInterpreter = join8(toolRoot, "bin", basename5(launcher.interpreter));
565
- if (resolve7(launcher.interpreter) !== resolve7(expectedInterpreter)) {
569
+ const expectedInterpreter = join9(toolRoot, "bin", basename5(launcher.interpreter));
570
+ if (resolve8(launcher.interpreter) !== resolve8(expectedInterpreter)) {
566
571
  return { ok: false, error: "UV Copier launcher interpreter is outside the attested tool environment" };
567
572
  }
568
573
  const interpreterReal = realpathSync4(launcher.interpreter);
569
574
  const uvPythonRoots = [
570
- join8(home, ".local", "share", "uv", "python"),
571
- join8(home, "Library", "Application Support", "uv", "python")
575
+ join9(home, ".local", "share", "uv", "python"),
576
+ join9(home, "Library", "Application Support", "uv", "python")
572
577
  ];
573
578
  if (!uvPythonRoots.some((root) => containedBy(root, interpreterReal))) {
574
579
  return { ok: false, error: "UV Copier interpreter is not managed by the canonical UV Python installation" };
575
580
  }
576
- const receiptPath = join8(toolRoot, "uv-receipt.toml");
577
- const receipt = readFileSync5(receiptPath, "utf8");
581
+ const receiptPath = join9(toolRoot, "uv-receipt.toml");
582
+ const receipt = readFileSync6(receiptPath, "utf8");
578
583
  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)) {
579
584
  return { ok: false, error: "UV tool receipt does not bind the copier entry point to the Copier package" };
580
585
  }
581
586
  const sitePackages = locateUvSitePackages(toolRoot);
582
- const distInfos = readdirSync5(sitePackages, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^copier-[^-]+\.dist-info$/i.test(entry.name)).map((entry) => join8(sitePackages, entry.name));
587
+ const distInfos = readdirSync5(sitePackages, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^copier-[^-]+\.dist-info$/i.test(entry.name)).map((entry) => join9(sitePackages, entry.name));
583
588
  if (distInfos.length !== 1) {
584
589
  return { ok: false, error: `expected one installed Copier distribution, found ${distInfos.length}` };
585
590
  }
586
591
  const distInfo = realpathSync4(distInfos[0]);
587
- const metadataPath = join8(distInfo, "METADATA");
588
- const entryPointsPath = join8(distInfo, "entry_points.txt");
589
- const recordPath = join8(distInfo, "RECORD");
590
- const metadata = readFileSync5(metadataPath, "utf8");
592
+ const metadataPath = join9(distInfo, "METADATA");
593
+ const entryPointsPath = join9(distInfo, "entry_points.txt");
594
+ const recordPath = join9(distInfo, "RECORD");
595
+ const metadata = readFileSync6(metadataPath, "utf8");
591
596
  const name = metadata.match(/^Name:\s*(.+)$/mi)?.[1]?.trim();
592
597
  const version = metadata.match(/^Version:\s*(.+)$/mi)?.[1]?.trim();
593
598
  if (name?.toLowerCase() !== "copier" || !version || !/^9(?:\.|$)/.test(version)) {
594
599
  return { ok: false, error: "installed distribution is not a Copier 9 package" };
595
600
  }
596
- const entryPoints = readFileSync5(entryPointsPath, "utf8");
601
+ const entryPoints = readFileSync6(entryPointsPath, "utf8");
597
602
  if (!/^copier\s*=\s*copier\.__main__:CopierApp\.run\s*$/m.test(entryPoints)) {
598
603
  return { ok: false, error: "installed Copier distribution has an unexpected console entry point" };
599
604
  }
600
- const recordEntries = readFileSync5(recordPath, "utf8").split(/\r?\n/).map(parseRecordLine).filter((entry) => Boolean(entry));
605
+ const recordEntries = readFileSync6(recordPath, "utf8").split(/\r?\n/).map(parseRecordLine).filter((entry) => Boolean(entry));
601
606
  const selected = recordEntries.filter((entry) => {
602
607
  const normalized = entry.relativePath.replaceAll("\\", "/");
603
608
  return normalized.startsWith("copier/") || normalized === `${basename5(distInfo)}/METADATA` || normalized === `${basename5(distInfo)}/entry_points.txt` || normalized === "../../../bin/copier";
@@ -607,7 +612,7 @@ function attestUvCopier(candidate, realCandidate, home) {
607
612
  }
608
613
  const attestedFiles = /* @__PURE__ */ new Map();
609
614
  for (const entry of selected) {
610
- const path = resolve7(sitePackages, entry.relativePath);
615
+ const path = resolve8(sitePackages, entry.relativePath);
611
616
  const allowed = path === realCandidate || containedBy(sitePackages, path);
612
617
  if (!allowed) return { ok: false, error: `Copier RECORD path escapes the tool environment: ${entry.relativePath}` };
613
618
  const actualDigest = sha256(path);
@@ -615,14 +620,14 @@ function attestUvCopier(candidate, realCandidate, home) {
615
620
  if (actualDigest !== entry.digest || actualSize !== entry.size) {
616
621
  return { ok: false, error: `Copier RECORD integrity mismatch: ${entry.relativePath}` };
617
622
  }
618
- attestedFiles.set(resolve7(path), fingerprint(path));
623
+ attestedFiles.set(resolve8(path), fingerprint(path));
619
624
  }
620
- for (const path of [realCandidate, join8(toolRoot, "pyvenv.cfg"), receiptPath, metadataPath, entryPointsPath, recordPath]) {
621
- attestedFiles.set(resolve7(path), fingerprint(path));
625
+ for (const path of [realCandidate, join9(toolRoot, "pyvenv.cfg"), receiptPath, metadataPath, entryPointsPath, recordPath]) {
626
+ attestedFiles.set(resolve8(path), fingerprint(path));
622
627
  }
623
628
  const identity = {
624
629
  executable: realCandidate,
625
- resolvedFrom: resolve7(candidate),
630
+ resolvedFrom: resolve8(candidate),
626
631
  layout: "uv-tool",
627
632
  version,
628
633
  toolRoot,
@@ -641,7 +646,7 @@ function attestUvCopier(candidate, realCandidate, home) {
641
646
  }
642
647
  }
643
648
  function verifyTrustedCopierIdentity(identity) {
644
- if (!isAbsolute3(identity.executable) || resolve7(identity.executable) !== resolve7(identity.files.find((file) => file.path === identity.executable)?.path ?? "")) {
649
+ if (!isAbsolute4(identity.executable) || resolve8(identity.executable) !== resolve8(identity.files.find((file) => file.path === identity.executable)?.path ?? "")) {
645
650
  return { ok: false, error: "trusted Copier identity has no canonical absolute launcher" };
646
651
  }
647
652
  const interpreter = sameFingerprint(identity.interpreter);
@@ -654,9 +659,9 @@ function verifyTrustedCopierIdentity(identity) {
654
659
  }
655
660
  function preflightTrustedCopier(options) {
656
661
  const env2 = options.env ?? process.env;
657
- const home = resolve7(options.homeDir ?? userInfo().homedir);
658
- const temporary = resolve7(options.temporaryDir ?? tmpdir());
659
- const target = resolve7(options.targetDir);
662
+ const home = resolve8(options.homeDir ?? userInfo().homedir);
663
+ const temporary = resolve8(options.temporaryDir ?? tmpdir());
664
+ const target = resolve8(options.targetDir);
660
665
  const { candidate, skipped } = resolveCopierOnPath(env2, accountMiseShimsDir(home));
661
666
  for (const shim of skipped) {
662
667
  for (const [label, root] of [["target", target], ["temporary", temporary]]) {
@@ -692,18 +697,18 @@ function regularContainedFile(root, path, label) {
692
697
  const rootReal = realpathSync4(root);
693
698
  const fileReal = realpathSync4(path);
694
699
  if (!containedBy(rootReal, fileReal)) return { ok: false, error: `${label} escapes its vendored template root` };
695
- if (!lstatSync6(path).isFile()) return { ok: false, error: `${label} is not a regular file` };
700
+ if (!lstatSync7(path).isFile()) return { ok: false, error: `${label} is not a regular file` };
696
701
  return { ok: true };
697
702
  } catch (error) {
698
703
  return { ok: false, error: `${label} is unavailable: ${error instanceof Error ? error.message : String(error)}` };
699
704
  }
700
705
  }
701
706
  function parseCopierConfig(templateRoot, label) {
702
- const configPath = join8(templateRoot, "copier.yml");
707
+ const configPath = join9(templateRoot, "copier.yml");
703
708
  const file = regularContainedFile(templateRoot, configPath, `${label} copier.yml`);
704
709
  if (!file.ok) return { result: file };
705
710
  try {
706
- const parsed = YAML3.parse(readFileSync5(configPath, "utf8"));
711
+ const parsed = YAML3.parse(readFileSync6(configPath, "utf8"));
707
712
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
708
713
  return { result: { ok: false, error: `${label} copier.yml must contain a mapping` } };
709
714
  }
@@ -721,13 +726,13 @@ function parseCopierConfig(templateRoot, label) {
721
726
  }
722
727
  function requireFiles(templateRoot, files, label) {
723
728
  for (const rel of files) {
724
- const result2 = regularContainedFile(templateRoot, join8(templateRoot, rel), `${label} ${rel}`);
729
+ const result2 = regularContainedFile(templateRoot, join9(templateRoot, rel), `${label} ${rel}`);
725
730
  if (!result2.ok) return result2;
726
731
  }
727
732
  return { ok: true };
728
733
  }
729
734
  function preflightCommonProjectTemplate(pjanglerRoot) {
730
- const templateRoot = join8(resolve7(pjanglerRoot), "templates", "commonproject");
735
+ const templateRoot = join9(resolve8(pjanglerRoot), "templates", "commonproject");
731
736
  const parsed = parseCopierConfig(templateRoot, "CommonProject template");
732
737
  if (!parsed.result.ok) return parsed.result;
733
738
  const files = requireFiles(templateRoot, [
@@ -738,7 +743,7 @@ function preflightCommonProjectTemplate(pjanglerRoot) {
738
743
  "template/mise.toml.jinja"
739
744
  ], "CommonProject template");
740
745
  if (!files.ok) return files;
741
- const projectJson = readFileSync5(join8(templateRoot, "template", ".project.json.jinja"), "utf8");
746
+ const projectJson = readFileSync6(join9(templateRoot, "template", ".project.json.jinja"), "utf8");
742
747
  for (const key of ["project_name", "repo_path", "ticket_provider", "agents"]) {
743
748
  if (!projectJson.includes(`"${key}"`)) return { ok: false, error: `CommonProject projection is missing ${key}` };
744
749
  }
@@ -763,12 +768,12 @@ var init_preflight = __esm({
763
768
  });
764
769
 
765
770
  // src/project/boardUrl.ts
766
- import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
767
- import { dirname as dirname8, isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "node:path";
771
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
772
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join10, resolve as resolve9 } from "node:path";
768
773
  function findProjectRoot(from) {
769
- let dir = resolve8(from);
774
+ let dir = resolve9(from);
770
775
  for (; ; ) {
771
- if (existsSync6(join9(dir, ".project.json"))) return dir;
776
+ if (existsSync6(join10(dir, ".project.json"))) return dir;
772
777
  const parent = dirname8(dir);
773
778
  if (parent === dir) return void 0;
774
779
  dir = parent;
@@ -781,8 +786,8 @@ var init_boardUrl = __esm({
781
786
  });
782
787
 
783
788
  // src/utils/version.ts
784
- import { readFileSync as readFileSync7 } from "node:fs";
785
- import { dirname as dirname9, join as join10 } from "node:path";
789
+ import { readFileSync as readFileSync8 } from "node:fs";
790
+ import { dirname as dirname9, join as join11 } from "node:path";
786
791
  import { fileURLToPath } from "node:url";
787
792
  var PJANGLER_VERSION;
788
793
  var init_version = __esm({
@@ -793,7 +798,7 @@ var init_version = __esm({
793
798
  let dir = dirname9(fileURLToPath(import.meta.url));
794
799
  for (let i = 0; i < 4; i++) {
795
800
  try {
796
- const raw = readFileSync7(join10(dir, "package.json"), "utf8");
801
+ const raw = readFileSync8(join11(dir, "package.json"), "utf8");
797
802
  return JSON.parse(raw).version ?? "0.0.0";
798
803
  } catch {
799
804
  const parent = dirname9(dir);
@@ -822,10 +827,10 @@ var init_boardQuery = __esm({
822
827
  });
823
828
 
824
829
  // src/project/index.ts
825
- import { spawnSync as spawnSync4 } from "node:child_process";
826
- import { chmodSync as chmodSync2, closeSync, copyFileSync as copyFileSync2, existsSync as existsSync7, fchmodSync, fsyncSync, lstatSync as lstatSync7, mkdirSync as mkdirSync4, mkdtempSync, openSync, readFileSync as readFileSync8, realpathSync as realpathSync5, renameSync as renameSync3, rmSync as rmSync3, statSync as statSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
830
+ import { spawnSync as spawnSync5 } from "node:child_process";
831
+ import { chmodSync as chmodSync2, closeSync, copyFileSync as copyFileSync2, existsSync as existsSync7, fchmodSync, fsyncSync, lstatSync as lstatSync8, mkdirSync as mkdirSync4, mkdtempSync, openSync, readFileSync as readFileSync9, realpathSync as realpathSync5, renameSync as renameSync3, rmSync as rmSync3, statSync as statSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "node:fs";
827
832
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
828
- import { basename as basename6, delimiter as delimiter2, dirname as dirname10, isAbsolute as isAbsolute5, join as join11, relative as relative7, resolve as resolve9, sep as sep2, win32 } from "node:path";
833
+ import { basename as basename6, delimiter as delimiter2, dirname as dirname10, isAbsolute as isAbsolute6, join as join12, relative as relative7, resolve as resolve10, sep as sep2, win32 } from "node:path";
829
834
  import { fileURLToPath as fileURLToPath2 } from "node:url";
830
835
  import YAML4 from "yaml";
831
836
  function normalizeProjectType(value, label = "Project type") {
@@ -835,7 +840,7 @@ function normalizeProjectType(value, label = "Project type") {
835
840
  }
836
841
  function looksLikeObsidianPlugin(dir) {
837
842
  try {
838
- const manifest = JSON.parse(readFileSync8(join11(dir, "manifest.json"), "utf8"));
843
+ const manifest = JSON.parse(readFileSync9(join12(dir, "manifest.json"), "utf8"));
839
844
  return isRecord2(manifest) && typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
840
845
  } catch {
841
846
  return false;
@@ -843,10 +848,10 @@ function looksLikeObsidianPlugin(dir) {
843
848
  }
844
849
  function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
845
850
  if (projectType === "base") return;
846
- const copierYml = join11(pjanglerRoot, "templates", "commonproject", "copier.yml");
851
+ const copierYml = join12(pjanglerRoot, "templates", "commonproject", "copier.yml");
847
852
  let choices;
848
853
  try {
849
- const parsed = YAML4.parse(readFileSync8(copierYml, "utf8"));
854
+ const parsed = YAML4.parse(readFileSync9(copierYml, "utf8"));
850
855
  choices = isRecord2(parsed) && isRecord2(parsed.project_type) ? parsed.project_type.choices : void 0;
851
856
  } catch {
852
857
  choices = void 0;
@@ -857,7 +862,7 @@ function assertTemplateOffersProjectType(pjanglerRoot, projectType) {
857
862
  }
858
863
  }
859
864
  function operatorDisplayName(cwd) {
860
- const result2 = spawnSync4("git", ["config", "--get", "user.name"], { cwd: existsSync7(cwd) ? cwd : void 0, encoding: "utf8", timeout: 5e3 });
865
+ const result2 = spawnSync5("git", ["config", "--get", "user.name"], { cwd: existsSync7(cwd) ? cwd : void 0, encoding: "utf8", timeout: 5e3 });
861
866
  return result2.status === 0 ? result2.stdout.trim() : "";
862
867
  }
863
868
  function providerAssignsIdentifiers(type) {
@@ -869,9 +874,9 @@ function carriedAutomation(automation) {
869
874
  return Object.keys(rest).length ? { automation: rest } : {};
870
875
  }
871
876
  function synchronizeCopierIdentity(manifestPath, manifest) {
872
- const answersPath = join11(dirname10(manifestPath), ".copier-answers.yml");
877
+ const answersPath = join12(dirname10(manifestPath), ".copier-answers.yml");
873
878
  if (!existsSync7(answersPath)) return [];
874
- const current = readFileSync8(answersPath, "utf8");
879
+ const current = readFileSync9(answersPath, "utf8");
875
880
  const document = YAML4.parseDocument(current);
876
881
  if (document.errors.length) return [];
877
882
  const name = String(document.get("project_name") ?? "");
@@ -888,12 +893,12 @@ function projectRegistryPath(env2 = process.env) {
888
893
  return resolveRegistryLocation(void 0, env2);
889
894
  }
890
895
  function createSafeRecord(entries = []) {
891
- const record2 = /* @__PURE__ */ Object.create(null);
892
- for (const [key, value] of entries) record2[key] = value;
893
- return record2;
896
+ const record3 = /* @__PURE__ */ Object.create(null);
897
+ for (const [key, value] of entries) record3[key] = value;
898
+ return record3;
894
899
  }
895
- function getOwnRecordValue(record2, key) {
896
- return Object.hasOwn(record2, key) ? record2[key] : void 0;
900
+ function getOwnRecordValue(record3, key) {
901
+ return Object.hasOwn(record3, key) ? record3[key] : void 0;
897
902
  }
898
903
  function emptyProjectRegistry() {
899
904
  return { schema_version: PROJECT_REGISTRY_SCHEMA_VERSION, projects: createSafeRecord() };
@@ -905,7 +910,7 @@ function loadProjectRegistry(path = projectRegistryPath()) {
905
910
  return registry2;
906
911
  }
907
912
  if (!existsSync7(path)) return emptyProjectRegistry();
908
- const raw = YAML4.parse(readFileSync8(path, "utf8"));
913
+ const raw = YAML4.parse(readFileSync9(path, "utf8"));
909
914
  if (raw == null) return emptyProjectRegistry();
910
915
  if (!isRecord2(raw)) throw new Error(`Project registry must be a mapping: ${path}`);
911
916
  const registry = raw;
@@ -995,10 +1000,10 @@ function saveProjectRegistry(registry, path = projectRegistryPath()) {
995
1000
  let text;
996
1001
  let mode = 420;
997
1002
  if (existsSync7(path)) {
998
- const stat = lstatSync7(path);
1003
+ const stat = lstatSync8(path);
999
1004
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Project registry must be a regular file: ${path}`);
1000
1005
  mode = stat.mode & 511;
1001
- const current = readFileSync8(path, "utf8");
1006
+ const current = readFileSync9(path, "utf8");
1002
1007
  const document = YAML4.parseDocument(current);
1003
1008
  if (document.errors.length) throw new Error(`Project registry YAML is invalid: ${path}`);
1004
1009
  setYamlLeaf(document, ["schema_version"], registry.schema_version);
@@ -1065,7 +1070,7 @@ function validateProjectRegistry(registry) {
1065
1070
  validateProjectRecord(project, slug);
1066
1071
  if (slugs.has(project.slug)) throw new Error(`Duplicate project slug: ${project.slug}`);
1067
1072
  slugs.add(project.slug);
1068
- const repoKey = resolve9(project.repo_path);
1073
+ const repoKey = resolve10(project.repo_path);
1069
1074
  const existingRepoSlug = repoPaths.get(repoKey);
1070
1075
  if (existingRepoSlug && existingRepoSlug !== slug) {
1071
1076
  throw new Error(`Duplicate project repo_path: ${project.repo_path} used by ${existingRepoSlug} and ${slug}`);
@@ -1142,18 +1147,18 @@ function ticketProviderKeyVars(provider, workspace) {
1142
1147
  return ticketProviderKeyGroups(provider, workspace).flat();
1143
1148
  }
1144
1149
  function ticketProviderSecretsPath(env2 = process.env) {
1145
- const base = env2.XDG_CONFIG_HOME || join11(env2.HOME || homedir3(), ".config");
1146
- return join11(base, "zshyzsh", "secrets.zsh");
1150
+ const base = env2.XDG_CONFIG_HOME || join12(env2.HOME || homedir3(), ".config");
1151
+ return join12(base, "zshyzsh", "secrets.zsh");
1147
1152
  }
1148
1153
  function ticketProviderFleetEnvPath(env2 = process.env) {
1149
- return env2.HERMES_FLEET_ENV?.trim() || join11(env2.HOME || homedir3(), ".hermes", "fleet.env");
1154
+ return env2.HERMES_FLEET_ENV?.trim() || join12(env2.HOME || homedir3(), ".hermes", "fleet.env");
1150
1155
  }
1151
1156
  function readShellAssignments(path, keys) {
1152
1157
  const found = {};
1153
1158
  if (!existsSync7(path)) return found;
1154
1159
  let text;
1155
1160
  try {
1156
- text = readFileSync8(path, "utf8");
1161
+ text = readFileSync9(path, "utf8");
1157
1162
  } catch {
1158
1163
  return found;
1159
1164
  }
@@ -1189,7 +1194,7 @@ function resolveTicketProviderCredentials(input) {
1189
1194
  }
1190
1195
  }
1191
1196
  const candidates = [];
1192
- if (input.repoPath) candidates.push(join11(input.repoPath, ".env"));
1197
+ if (input.repoPath) candidates.push(join12(input.repoPath, ".env"));
1193
1198
  candidates.push(ticketProviderFleetEnvPath(env2));
1194
1199
  candidates.push(ticketProviderSecretsPath(env2));
1195
1200
  for (const candidate of candidates) {
@@ -1207,19 +1212,19 @@ function resolveTicketProviderAdapter(provider, env2 = process.env) {
1207
1212
  const file = `${provider}.sh`;
1208
1213
  const candidates = [];
1209
1214
  const override = env2[TICKET_PROVIDER_ADAPTERS_ENV];
1210
- if (override) candidates.push(join11(override, file));
1211
- const relativeRoot = join11("krebs", "adapters", "tp");
1215
+ if (override) candidates.push(join12(override, file));
1216
+ const relativeRoot = join12("krebs", "adapters", "tp");
1212
1217
  try {
1213
1218
  let dir = dirname10(fileURLToPath2(import.meta.url));
1214
1219
  for (let depth = 0; depth < 8; depth++) {
1215
- candidates.push(join11(dir, relativeRoot, file));
1220
+ candidates.push(join12(dir, relativeRoot, file));
1216
1221
  const parent = dirname10(dir);
1217
1222
  if (parent === dir) break;
1218
1223
  dir = parent;
1219
1224
  }
1220
1225
  } catch {
1221
1226
  }
1222
- candidates.push(join11(homedir3(), "code", "33GOD", relativeRoot, file));
1227
+ candidates.push(join12(homedir3(), "code", "33GOD", relativeRoot, file));
1223
1228
  return candidates.find((candidate) => existsSync7(candidate));
1224
1229
  }
1225
1230
  function provisionTicketProviderBoard(action, env2 = process.env) {
@@ -1237,7 +1242,7 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1237
1242
  ok: true,
1238
1243
  skipped: true,
1239
1244
  logs: [
1240
- `ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join11(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
1245
+ `ticket-provider: ${names} not set; skipping ${provider} board creation (state stays "planned"). Set it in the environment, ${join12(action.repoPath, ".env")}, ${ticketProviderFleetEnvPath(env2)}, or ${ticketProviderSecretsPath(env2)} \u2014 or pass --board-id to link an existing board.`
1241
1246
  ]
1242
1247
  };
1243
1248
  }
@@ -1251,12 +1256,12 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1251
1256
  };
1252
1257
  }
1253
1258
  const redact = (text) => Object.values(values).reduce((acc, secret) => secret ? acc.split(secret).join("***") : acc, text);
1254
- const staging = mkdtempSync(join11(tmpdir2(), "pjangler-tp-"));
1259
+ const staging = mkdtempSync(join12(tmpdir2(), "pjangler-tp-"));
1255
1260
  try {
1256
- const providersDir = join11(staging, ".tp", "adapters");
1261
+ const providersDir = join12(staging, ".tp", "adapters");
1257
1262
  mkdirSync4(providersDir, { recursive: true });
1258
1263
  writeFileSync4(
1259
- join11(staging, ".project.json"),
1264
+ join12(staging, ".project.json"),
1260
1265
  `${JSON.stringify(
1261
1266
  {
1262
1267
  project_name: action.boardName,
@@ -1275,11 +1280,11 @@ function provisionTicketProviderBoard(action, env2 = process.env) {
1275
1280
  `,
1276
1281
  "utf8"
1277
1282
  );
1278
- const staged = join11(providersDir, `${provider}.sh`);
1283
+ const staged = join12(providersDir, `${provider}.sh`);
1279
1284
  copyFileSync2(adapter, staged);
1280
1285
  const childEnv = { ...env2, ...values, TICKET_PROVIDER: provider };
1281
1286
  if (provider === "plane" && action.workspace) childEnv.PLANE_WORKSPACE = action.workspace;
1282
- const result2 = spawnSync4("sh", [staged, "create_board", action.boardName, action.identifier, action.description], {
1287
+ const result2 = spawnSync5("sh", [staged, "create_board", action.boardName, action.identifier, action.description], {
1283
1288
  cwd: existsSync7(action.repoPath) ? action.repoPath : staging,
1284
1289
  encoding: "utf8",
1285
1290
  env: childEnv
@@ -1350,7 +1355,7 @@ function slugifyProjectName(value) {
1350
1355
  }
1351
1356
  function validateSafePathSegment(value, label) {
1352
1357
  const normalized = value.trim();
1353
- const unsafe = !normalized || normalized !== value || normalized === "." || normalized === ".." || isAbsolute5(normalized) || win32.isAbsolute(normalized) || normalized.includes("/") || normalized.includes("\\") || !SAFE_PATH_SEGMENT.test(normalized);
1358
+ const unsafe = !normalized || normalized !== value || normalized === "." || normalized === ".." || isAbsolute6(normalized) || win32.isAbsolute(normalized) || normalized.includes("/") || normalized.includes("\\") || !SAFE_PATH_SEGMENT.test(normalized);
1354
1359
  if (unsafe) {
1355
1360
  throw new Error(
1356
1361
  `${label} must be a non-empty safe single path segment using letters, numbers, dots, underscores, or hyphens (no dot segments, absolute paths, separators, or traversal)`
@@ -1359,24 +1364,24 @@ function validateSafePathSegment(value, label) {
1359
1364
  return normalized;
1360
1365
  }
1361
1366
  function prospectiveRealPath(path) {
1362
- let cursor = resolve9(path);
1367
+ let cursor = resolve10(path);
1363
1368
  const suffix = [];
1364
1369
  while (!existsSync7(cursor)) {
1365
1370
  const parent = dirname10(cursor);
1366
- if (parent === cursor) return resolve9(path);
1371
+ if (parent === cursor) return resolve10(path);
1367
1372
  suffix.unshift(basename6(cursor));
1368
1373
  cursor = parent;
1369
1374
  }
1370
- return resolve9(realpathSync5(cursor), ...suffix);
1375
+ return resolve10(realpathSync5(cursor), ...suffix);
1371
1376
  }
1372
1377
  function resolveContainedPath(parentDir, candidate, label) {
1373
1378
  const physicalParent = prospectiveRealPath(parentDir);
1374
1379
  const physicalCandidate = prospectiveRealPath(candidate);
1375
1380
  const fromParent = relative7(physicalParent, physicalCandidate);
1376
- if (!fromParent || fromParent === ".." || fromParent.startsWith(`..${sep2}`) || isAbsolute5(fromParent)) {
1377
- throw new Error(`${label} must remain contained beneath parent directory ${resolve9(parentDir)}`);
1381
+ if (!fromParent || fromParent === ".." || fromParent.startsWith(`..${sep2}`) || isAbsolute6(fromParent)) {
1382
+ throw new Error(`${label} must remain contained beneath parent directory ${resolve10(parentDir)}`);
1378
1383
  }
1379
- return resolve9(candidate);
1384
+ return resolve10(candidate);
1380
1385
  }
1381
1386
  function proposeProjectIdentifier(value) {
1382
1387
  const compact = value.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
@@ -1391,7 +1396,7 @@ function resolveAgentHooksLayer2(input, env2 = process.env) {
1391
1396
  const override = env2.PJ_AGENT_HOOKS_LAYER;
1392
1397
  if (override === "0" || override === "false") return false;
1393
1398
  if (override === "1" || override === "true") return true;
1394
- return !existsSync7(join11(homedir3(), ".agents", "hooks"));
1399
+ return !existsSync7(join12(homedir3(), ".agents", "hooks"));
1395
1400
  }
1396
1401
  function jsonStable(value) {
1397
1402
  return JSON.stringify(value, (_key, item) => isRecord2(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
@@ -1405,14 +1410,14 @@ function projectRecordEquivalent(a, b) {
1405
1410
  function defaultProjectTargetDir(name, cwd = process.cwd()) {
1406
1411
  const compactName = name.replace(/[^A-Za-z0-9._-]/g, "");
1407
1412
  const safeName = SAFE_PATH_SEGMENT.test(compactName) ? compactName : slugifyProjectName(name);
1408
- return resolve9(dirname10(resolve9(cwd)), validateSafePathSegment(safeName, "Generated project directory"));
1413
+ return resolve10(dirname10(resolve10(cwd)), validateSafePathSegment(safeName, "Generated project directory"));
1409
1414
  }
1410
1415
  function sourceSkillRoots(env2 = process.env) {
1411
1416
  const configuredRoots = (env2[PROJECT_SOURCE_SKILL_ROOTS_ENV] || "").split(delimiter2).map((root) => root.trim()).filter(Boolean);
1412
1417
  const seen = /* @__PURE__ */ new Set();
1413
1418
  const roots = [];
1414
1419
  for (const root of [...DEFAULT_SOURCE_SKILL_ROOTS, ...configuredRoots]) {
1415
- const normalized = resolve9(expandHome(root));
1420
+ const normalized = resolve10(expandHome(root));
1416
1421
  if (seen.has(normalized)) continue;
1417
1422
  seen.add(normalized);
1418
1423
  roots.push(normalized);
@@ -1422,12 +1427,12 @@ function sourceSkillRoots(env2 = process.env) {
1422
1427
  function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1423
1428
  if (!sourceSkill) return void 0;
1424
1429
  const expanded = expandHome(sourceSkill);
1425
- const direct = resolve9(expanded);
1430
+ const direct = resolve10(expanded);
1426
1431
  if (existsSync7(direct)) return direct;
1427
1432
  const name = basename6(sourceSkill);
1428
1433
  const roots = sourceSkillRoots(env2);
1429
1434
  for (const root of roots) {
1430
- const candidate = join11(root, name);
1435
+ const candidate = join12(root, name);
1431
1436
  if (existsSync7(candidate)) return candidate;
1432
1437
  }
1433
1438
  const searched = roots.length ? ` Searched roots: ${roots.join(", ")}.` : "";
@@ -1436,9 +1441,9 @@ function resolveSourceSkillPath(sourceSkill, env2 = process.env) {
1436
1441
  }
1437
1442
  function planProjectInit(input) {
1438
1443
  if (!input.name.trim()) throw new Error("Project name is required");
1439
- const targetDir = resolve9(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
1440
- const localManifestPath = join11(targetDir, ".project.json");
1441
- const localManifestContent = existsSync7(localManifestPath) ? readFileSync8(localManifestPath, "utf8") : void 0;
1444
+ const targetDir = resolve10(input.targetDir ?? defaultProjectTargetDir(input.name, input.cwd));
1445
+ const localManifestPath = join12(targetDir, ".project.json");
1446
+ const localManifestContent = existsSync7(localManifestPath) ? readFileSync9(localManifestPath, "utf8") : void 0;
1442
1447
  const localManifest = localManifestContent === void 0 ? void 0 : JSON.parse(localManifestContent);
1443
1448
  const localId = localManifest?.project_id ?? localManifest?.project_slug;
1444
1449
  const slug = normalizeProjectId(input.projectSlug ?? localId ?? slugifyProjectName(input.name));
@@ -1466,7 +1471,7 @@ function planProjectInit(input) {
1466
1471
  const recordedTypeValue = existing?.template?.commonproject?.project_type;
1467
1472
  const recordedType = recordedTypeValue === void 0 ? void 0 : normalizeProjectType(recordedTypeValue, `Recorded project type of ${slug}`);
1468
1473
  const requestedType = input.projectType === void 0 ? void 0 : normalizeProjectType(input.projectType);
1469
- const freshRender = scaffold && localManifest === void 0 && !existsSync7(join11(targetDir, ".copier-answers.yml"));
1474
+ const freshRender = scaffold && localManifest === void 0 && !existsSync7(join12(targetDir, ".copier-answers.yml"));
1470
1475
  if (requestedType && !freshRender && requestedType !== (recordedType ?? "base")) {
1471
1476
  const adoptingPlugin = !scaffold && recordedType === void 0 && requestedType === "obsidian-plugin" && looksLikeObsidianPlugin(targetDir);
1472
1477
  if (!adoptingPlugin) {
@@ -1532,7 +1537,7 @@ function planProjectInit(input) {
1532
1537
  updated_at: projectRecordEquivalent(existing, candidateProject) ? existing.updated_at : now
1533
1538
  };
1534
1539
  validateNoDuplicateProject(registry, project, overwrite);
1535
- const pjanglerRoot = resolve9(input.pjanglerRoot ?? resolvePjanglerRoot());
1540
+ const pjanglerRoot = resolve10(input.pjanglerRoot ?? resolvePjanglerRoot());
1536
1541
  const manifest = projectManifestFromRegistryProject(project);
1537
1542
  const apply = input.apply ?? false;
1538
1543
  const live = input.live ?? false;
@@ -1564,7 +1569,7 @@ function planProjectInit(input) {
1564
1569
  }));
1565
1570
  }
1566
1571
  actions.push(
1567
- { kind: "project.write-manifest", path: join11(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1572
+ { kind: "project.write-manifest", path: join12(targetDir, ".project.json"), manifest, expectedContent: localManifestContent },
1568
1573
  {
1569
1574
  kind: "ticket-provider.create-or-link",
1570
1575
  enabled: boardEnabled,
@@ -1619,12 +1624,12 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1619
1624
  plan.manifest.ticket_provider = manifestProvider;
1620
1625
  action.boardId = boardId;
1621
1626
  action.state = manifestProvider.state;
1622
- const manifestPath = join11(action.repoPath, ".project.json");
1627
+ const manifestPath = join12(action.repoPath, ".project.json");
1623
1628
  let next;
1624
1629
  if (existsSync7(manifestPath)) {
1625
1630
  let existing = {};
1626
1631
  try {
1627
- const parsed = JSON.parse(readFileSync8(manifestPath, "utf8"));
1632
+ const parsed = JSON.parse(readFileSync9(manifestPath, "utf8"));
1628
1633
  if (isRecord2(parsed)) existing = parsed;
1629
1634
  } catch {
1630
1635
  existing = {};
@@ -1637,7 +1642,7 @@ function linkTicketProviderBoard(plan, action, boardId, identifier, now = /* @__
1637
1642
  }
1638
1643
  const text = `${JSON.stringify(next, null, 2)}
1639
1644
  `;
1640
- if (!existsSync7(manifestPath) || readFileSync8(manifestPath, "utf8") !== text) {
1645
+ if (!existsSync7(manifestPath) || readFileSync9(manifestPath, "utf8") !== text) {
1641
1646
  writeFileSync4(manifestPath, text, "utf8");
1642
1647
  return [manifestPath];
1643
1648
  }
@@ -1677,7 +1682,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1677
1682
  copierEnv.PYTHONSAFEPATH = "1";
1678
1683
  }
1679
1684
  copierEnv.MISE_YES = "1";
1680
- const result2 = spawnSync4(copierExecutable, action.command.slice(1), {
1685
+ const result2 = spawnSync5(copierExecutable, action.command.slice(1), {
1681
1686
  encoding: "utf8",
1682
1687
  cwd: action.cwd,
1683
1688
  env: copierEnv
@@ -1698,14 +1703,14 @@ async function executeProjectInitPlan(plan, options = {}) {
1698
1703
  break;
1699
1704
  }
1700
1705
  const projectType = action.data.project_type ?? "base";
1701
- const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync7(join11(action.targetDir, rel)));
1706
+ const missing = (PROJECT_TYPE_SENTINELS[projectType] ?? []).filter((rel) => !existsSync7(join12(action.targetDir, rel)));
1702
1707
  if (missing.length) {
1703
1708
  errors.push(`copier rendered no ${projectType} project: ${missing.join(", ")} missing from ${action.targetDir}`);
1704
1709
  break;
1705
1710
  }
1706
1711
  if (projectType !== "base") logs.push(`commonproject: rendered as ${projectType}`);
1707
1712
  } else if (action.kind === "project.write-manifest") {
1708
- if (action.expectedContent !== void 0 && (!existsSync7(action.path) || readFileSync8(action.path, "utf8") !== action.expectedContent)) {
1713
+ if (action.expectedContent !== void 0 && (!existsSync7(action.path) || readFileSync9(action.path, "utf8") !== action.expectedContent)) {
1709
1714
  errors.push(`Manifest changed after planning: ${action.path}; re-plan before applying`);
1710
1715
  break;
1711
1716
  }
@@ -1713,7 +1718,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1713
1718
  let value = action.manifest;
1714
1719
  if (existsSync7(action.path)) {
1715
1720
  try {
1716
- const currentValue = JSON.parse(readFileSync8(action.path, "utf8"));
1721
+ const currentValue = JSON.parse(readFileSync9(action.path, "utf8"));
1717
1722
  if (isRecord2(currentValue)) {
1718
1723
  const currentNotebook = isRecord2(currentValue.notebook) ? currentValue.notebook : {};
1719
1724
  const desiredNotebook = isRecord2(value.notebook) ? value.notebook : {};
@@ -1741,7 +1746,7 @@ async function executeProjectInitPlan(plan, options = {}) {
1741
1746
  value.project_id = normalizeProjectId(value.project_id ?? action.manifest.project_id);
1742
1747
  const next = `${JSON.stringify(value, null, 2)}
1743
1748
  `;
1744
- const current = existsSync7(action.path) ? readFileSync8(action.path, "utf8") : void 0;
1749
+ const current = existsSync7(action.path) ? readFileSync9(action.path, "utf8") : void 0;
1745
1750
  if (current !== next) {
1746
1751
  writeFileSync4(action.path, next, "utf8");
1747
1752
  changedFiles.push(action.path);
@@ -1824,7 +1829,7 @@ function getProject(registry, slug) {
1824
1829
  return project;
1825
1830
  }
1826
1831
  function buildCommonProjectCopierAction(input) {
1827
- const templateDir = join11(input.pjanglerRoot, "templates", "commonproject");
1832
+ const templateDir = join12(input.pjanglerRoot, "templates", "commonproject");
1828
1833
  const data = {
1829
1834
  project_name: input.projectName,
1830
1835
  project_description: input.projectDescription ?? "",
@@ -1855,19 +1860,19 @@ function buildCommonProjectCopierAction(input) {
1855
1860
  function resolvePjanglerRoot() {
1856
1861
  let dir = dirname10(fileURLToPath2(import.meta.url));
1857
1862
  while (dir !== dirname10(dir)) {
1858
- if (existsSync7(join11(dir, "package.json")) && existsSync7(join11(dir, "templates", "commonproject", "copier.yml"))) return dir;
1863
+ if (existsSync7(join12(dir, "package.json")) && existsSync7(join12(dir, "templates", "commonproject", "copier.yml"))) return dir;
1859
1864
  dir = dirname10(dir);
1860
1865
  }
1861
- return resolve9(process.cwd());
1866
+ return resolve10(process.cwd());
1862
1867
  }
1863
1868
  function validateNoDuplicateProject(registry, project, overwrite) {
1864
1869
  const existingSameSlug = getOwnRecordValue(registry.projects, project.slug);
1865
- if (existingSameSlug && !overwrite && resolve9(existingSameSlug.repo_path) !== resolve9(project.repo_path)) {
1870
+ if (existingSameSlug && !overwrite && resolve10(existingSameSlug.repo_path) !== resolve10(project.repo_path)) {
1866
1871
  throw new Error(`Project slug already exists in registry: ${project.slug}`);
1867
1872
  }
1868
1873
  for (const [slug, existing] of Object.entries(registry.projects)) {
1869
1874
  if (slug === project.slug) continue;
1870
- if (resolve9(existing.repo_path) === resolve9(project.repo_path)) {
1875
+ if (resolve10(existing.repo_path) === resolve10(project.repo_path)) {
1871
1876
  throw new Error(`Project repo_path already registered by ${slug}: ${project.repo_path}`);
1872
1877
  }
1873
1878
  if (ticketProviderScope(existing.ticket_provider) !== ticketProviderScope(project.ticket_provider)) continue;
@@ -1932,7 +1937,7 @@ function validateProjectRecord(project, key) {
1932
1937
  }
1933
1938
  function expandHome(path) {
1934
1939
  if (path === "~") return homedir3();
1935
- if (path.startsWith("~/")) return join11(homedir3(), path.slice(2));
1940
+ if (path.startsWith("~/")) return join12(homedir3(), path.slice(2));
1936
1941
  return path;
1937
1942
  }
1938
1943
  function isRecord2(value) {
@@ -1959,7 +1964,7 @@ var init_project = __esm({
1959
1964
  "obsidian-plugin": ["typescript", "javascript"]
1960
1965
  };
1961
1966
  PROJECT_TYPE_SENTINELS = {
1962
- "obsidian-plugin": ["manifest.json", join11("src", "main.ts")]
1967
+ "obsidian-plugin": ["manifest.json", join12("src", "main.ts")]
1963
1968
  };
1964
1969
  BOARD_URL_DEPRECATION_WARNING = "boardUrl is deprecated and ignored; board URLs are derived at runtime and are never persisted.";
1965
1970
  PROJECT_IDENTIFIER_SOURCES = ["provider", "proposed"];
@@ -1968,8 +1973,8 @@ var init_project = __esm({
1968
1973
  IDENTIFIER_REPAIR_COMMAND = "pj project identity --all --apply";
1969
1974
  DEFAULT_SOURCE_SKILL_ROOTS = [
1970
1975
  "/home/delorenj/code/skillex/all-skills",
1971
- join11(homedir3(), ".agents", "skills"),
1972
- join11(homedir3(), ".codex", "skills")
1976
+ join12(homedir3(), ".agents", "skills"),
1977
+ join12(homedir3(), ".codex", "skills")
1973
1978
  ];
1974
1979
  PROJECT_REGISTRY_OWNED_KEYS = [
1975
1980
  "name",
@@ -2001,21 +2006,21 @@ var init_project = __esm({
2001
2006
  import { randomUUID } from "node:crypto";
2002
2007
  import { isDeepStrictEqual } from "node:util";
2003
2008
  import { isIP as isIP2 } from "node:net";
2004
- import { closeSync as closeSync2, existsSync as existsSync8, fchmodSync as fchmodSync2, fsyncSync as fsyncSync2, lstatSync as lstatSync8, openSync as openSync2, readFileSync as readFileSync9, realpathSync as realpathSync6, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "node:fs";
2005
- import { dirname as dirname11, resolve as resolve10 } from "node:path";
2009
+ import { closeSync as closeSync2, existsSync as existsSync8, fchmodSync as fchmodSync2, fsyncSync as fsyncSync2, lstatSync as lstatSync9, openSync as openSync2, readFileSync as readFileSync10, realpathSync as realpathSync6, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "node:fs";
2010
+ import { dirname as dirname11, resolve as resolve11 } from "node:path";
2006
2011
  function isRecord3(value) {
2007
2012
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2008
2013
  }
2009
2014
  function realOrResolved(path) {
2010
- const absolute = resolve10(path);
2015
+ const absolute = resolve11(path);
2011
2016
  return existsSync8(absolute) ? realpathSync6(absolute) : absolute;
2012
2017
  }
2013
2018
  function readManifest(repoPath) {
2014
- const path = resolve10(repoPath, ".project.json");
2019
+ const path = resolve11(repoPath, ".project.json");
2015
2020
  if (!existsSync8(path)) return null;
2016
2021
  let parsed;
2017
2022
  try {
2018
- parsed = JSON.parse(readFileSync9(path, "utf8"));
2023
+ parsed = JSON.parse(readFileSync10(path, "utf8"));
2019
2024
  } catch {
2020
2025
  throw new NotebookError("INVALID_INPUT", `${path} is not valid JSON`);
2021
2026
  }
@@ -2255,9 +2260,9 @@ function registryProjectBySlug(registry, slug) {
2255
2260
  }
2256
2261
  function persistProjectNotebookBinding(resolved, binding, policy) {
2257
2262
  const changed = [];
2258
- const manifestPath = resolve10(resolved.project.repo_path, ".project.json");
2263
+ const manifestPath = resolve11(resolved.project.repo_path, ".project.json");
2259
2264
  if (isRegistryServiceLocation(resolved.registry_path)) {
2260
- const before = existsSync8(manifestPath) ? readFileSync9(manifestPath, "utf8") : null;
2265
+ const before = existsSync8(manifestPath) ? readFileSync10(manifestPath, "utf8") : null;
2261
2266
  const declaredPolicy = resolved.manifest?.notebook?.policy ?? {};
2262
2267
  const nextProject = {
2263
2268
  ...resolved.project,
@@ -2269,10 +2274,10 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2269
2274
  resolved.registry = loadProjectRegistry(resolved.registry_path);
2270
2275
  resolved.project = registryProjectBySlug(resolved.registry, nextProject.slug);
2271
2276
  resolved.manifest = readManifest(nextProject.repo_path);
2272
- const after = existsSync8(manifestPath) ? readFileSync9(manifestPath, "utf8") : null;
2277
+ const after = existsSync8(manifestPath) ? readFileSync10(manifestPath, "utf8") : null;
2273
2278
  return before === after ? [] : [manifestPath];
2274
2279
  }
2275
- const manifestRaw = existsSync8(manifestPath) ? JSON.parse(readFileSync9(manifestPath, "utf8")) : {};
2280
+ const manifestRaw = existsSync8(manifestPath) ? JSON.parse(readFileSync10(manifestPath, "utf8")) : {};
2276
2281
  if (!isRecord3(manifestRaw)) throw new NotebookError("INVALID_INPUT", `${manifestPath} must contain a JSON object`);
2277
2282
  validateManifestNotebookSurface(manifestRaw.notebook);
2278
2283
  const manifestNotebook2 = isRecord3(manifestRaw.notebook) ? manifestRaw.notebook : {};
@@ -2305,14 +2310,14 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2305
2310
  };
2306
2311
  const manifestText = `${JSON.stringify(manifestNext, null, 2)}
2307
2312
  `;
2308
- if (!existsSync8(manifestPath) || readFileSync9(manifestPath, "utf8") !== manifestText) {
2313
+ if (!existsSync8(manifestPath) || readFileSync10(manifestPath, "utf8") !== manifestText) {
2309
2314
  let mode = 420;
2310
2315
  if (existsSync8(manifestPath)) {
2311
- const current2 = lstatSync8(manifestPath);
2316
+ const current2 = lstatSync9(manifestPath);
2312
2317
  if (!current2.isFile() || current2.isSymbolicLink()) throw new NotebookError("CONFLICT", `${manifestPath} must be a regular non-symlink file`);
2313
2318
  mode = current2.mode & 511;
2314
2319
  }
2315
- const temp = resolve10(dirname11(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
2320
+ const temp = resolve11(dirname11(manifestPath), `.${process.pid}.${randomUUID()}.project.json.tmp`);
2316
2321
  const fd = openSync2(temp, "wx", mode);
2317
2322
  try {
2318
2323
  writeFileSync5(fd, manifestText, "utf8");
@@ -2336,9 +2341,9 @@ function persistProjectNotebookBinding(resolved, binding, policy) {
2336
2341
  const current = resolved.project.notebook;
2337
2342
  resolved.project.notebook = { ...isRecord3(current) ? current : { state: "planned" }, ...binding };
2338
2343
  const fileRegistry = !isRegistryServiceLocation(resolved.registry_path);
2339
- const registryBefore = fileRegistry && existsSync8(resolved.registry_path) ? readFileSync9(resolved.registry_path, "utf8") : null;
2344
+ const registryBefore = fileRegistry && existsSync8(resolved.registry_path) ? readFileSync10(resolved.registry_path, "utf8") : null;
2340
2345
  saveProjectRegistry(resolved.registry, resolved.registry_path);
2341
- const registryAfter = fileRegistry && existsSync8(resolved.registry_path) ? readFileSync9(resolved.registry_path, "utf8") : null;
2346
+ const registryAfter = fileRegistry && existsSync8(resolved.registry_path) ? readFileSync10(resolved.registry_path, "utf8") : null;
2342
2347
  if (registryBefore !== registryAfter) changed.push(resolved.registry_path);
2343
2348
  resolved.manifest = manifestNext;
2344
2349
  return changed;
@@ -2380,8 +2385,8 @@ function canonicalJson2(value) {
2380
2385
  return encoded;
2381
2386
  }
2382
2387
  if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : canonicalJson2(item)).join(",")}]`;
2383
- const record2 = value;
2384
- return `{${Object.keys(record2).filter((key) => record2[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record2[key])}`).join(",")}}`;
2388
+ const record3 = value;
2389
+ return `{${Object.keys(record3).filter((key) => record3[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record3[key])}`).join(",")}}`;
2385
2390
  }
2386
2391
  function sha256Hex(value) {
2387
2392
  return createHash5("sha256").update(value).digest("hex");
@@ -2649,7 +2654,7 @@ import {
2649
2654
  fchmodSync as fchmodSync3,
2650
2655
  fstatSync,
2651
2656
  fsyncSync as fsyncSync3,
2652
- lstatSync as lstatSync9,
2657
+ lstatSync as lstatSync10,
2653
2658
  mkdirSync as mkdirSync5,
2654
2659
  openSync as openSync3,
2655
2660
  readSync,
@@ -2659,26 +2664,26 @@ import {
2659
2664
  writeFileSync as writeFileSync6
2660
2665
  } from "node:fs";
2661
2666
  import { homedir as homedir4 } from "node:os";
2662
- import { basename as basename7, dirname as dirname12, join as join12, parse, relative as relative8, resolve as resolve11, sep as sep3 } from "node:path";
2667
+ import { basename as basename7, dirname as dirname12, join as join13, parse, relative as relative8, resolve as resolve12, sep as sep3 } from "node:path";
2663
2668
  function notebookStateRoot(env2 = process.env) {
2664
- const base = env2.XDG_STATE_HOME || join12(env2.HOME || homedir4(), ".local", "state");
2665
- return resolve11(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
2669
+ const base = env2.XDG_STATE_HOME || join13(env2.HOME || homedir4(), ".local", "state");
2670
+ return resolve12(base, "pjangler", "notebook", NOTEBOOK_STATE_VERSION);
2666
2671
  }
2667
2672
  function assertDigest(value, label) {
2668
2673
  if (!SESSION_KEY_RE.test(value)) throw new NotebookError("INVALID_INPUT", `Invalid ${label}`);
2669
2674
  }
2670
2675
  function projectStateDir(root, projectSlug) {
2671
2676
  if (!/^[a-z0-9][a-z0-9._-]{0,127}$/iu.test(projectSlug)) throw new NotebookError("INVALID_INPUT", "Invalid project slug for Notebook state");
2672
- return join12(resolve11(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
2677
+ return join13(resolve12(root), "projects", sha256Hex(`pjangler-project-state-v1\0${projectSlug}`));
2673
2678
  }
2674
2679
  function assertContained(root, candidate) {
2675
- const rel = relative8(resolve11(root), resolve11(candidate));
2680
+ const rel = relative8(resolve12(root), resolve12(candidate));
2676
2681
  if (!rel || !rel.startsWith(`..${sep3}`) && rel !== ".." && !rel.startsWith(sep3)) return;
2677
2682
  throw new NotebookError("INTERNAL_ERROR", "Notebook state path escaped its root");
2678
2683
  }
2679
2684
  function openPinnedDirectory(path, root, create) {
2680
- const absolute = resolve11(path);
2681
- const absoluteRoot = resolve11(root);
2685
+ const absolute = resolve12(path);
2686
+ const absoluteRoot = resolve12(root);
2682
2687
  assertContained(absoluteRoot, absolute);
2683
2688
  if (!existsSync9("/proc/self/fd")) throw new NotebookError("INTERNAL_ERROR", "Descriptor-pinned Notebook state requires procfs");
2684
2689
  const parsed = parse(absolute);
@@ -2687,7 +2692,7 @@ function openPinnedDirectory(path, root, create) {
2687
2692
  try {
2688
2693
  for (const part of absolute.slice(parsed.root.length).split(sep3).filter(Boolean)) {
2689
2694
  const child = `/proc/self/fd/${fd}/${part}`;
2690
- cursor = join12(cursor, part);
2695
+ cursor = join13(cursor, part);
2691
2696
  let childFd;
2692
2697
  try {
2693
2698
  childFd = openSync3(child, DIRECTORY_OPEN_FLAGS);
@@ -2724,8 +2729,8 @@ function assertOwned(stat) {
2724
2729
  }
2725
2730
  }
2726
2731
  function ensureDirectory(path, root) {
2727
- const absoluteRoot = resolve11(root);
2728
- const absolute = resolve11(path);
2732
+ const absoluteRoot = resolve12(root);
2733
+ const absolute = resolve12(path);
2729
2734
  assertContained(absoluteRoot, absolute);
2730
2735
  const fd = openPinnedDirectory(absolute, absoluteRoot, true);
2731
2736
  try {
@@ -2739,23 +2744,23 @@ function ensureDirectory(path, root) {
2739
2744
  }
2740
2745
  }
2741
2746
  function notebookStatePaths(root, projectSlug) {
2742
- const absoluteRoot = resolve11(root);
2747
+ const absoluteRoot = resolve12(root);
2743
2748
  const project = projectStateDir(absoluteRoot, projectSlug);
2744
2749
  return {
2745
2750
  root: absoluteRoot,
2746
2751
  project,
2747
- baselines: join12(project, "baselines"),
2748
- claims: join12(project, "claims"),
2749
- receipts: join12(project, "receipts"),
2750
- refusals: join12(project, "refusals"),
2751
- journals: join12(project, "journals"),
2752
- locks: join12(project, "locks")
2752
+ baselines: join13(project, "baselines"),
2753
+ claims: join13(project, "claims"),
2754
+ receipts: join13(project, "receipts"),
2755
+ refusals: join13(project, "refusals"),
2756
+ journals: join13(project, "journals"),
2757
+ locks: join13(project, "locks")
2753
2758
  };
2754
2759
  }
2755
2760
  function ensureNotebookState(root, projectSlug) {
2756
2761
  const paths = notebookStatePaths(root, projectSlug);
2757
2762
  const { root: absoluteRoot, project } = paths;
2758
- for (const path of [absoluteRoot, join12(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
2763
+ for (const path of [absoluteRoot, join13(absoluteRoot, "projects"), project, paths.baselines, paths.claims, paths.receipts, paths.refusals, paths.journals, paths.locks]) {
2759
2764
  ensureDirectory(path, absoluteRoot);
2760
2765
  }
2761
2766
  return paths;
@@ -2823,7 +2828,7 @@ function renameStateFile(source, target, root) {
2823
2828
  if (!stat.isFile() || (stat.mode & 511) !== 384) throw new NotebookError("CONFLICT", "Notebook state rename source has an integrity finding");
2824
2829
  assertOwned(stat);
2825
2830
  try {
2826
- lstatSync9(targetPath);
2831
+ lstatSync10(targetPath);
2827
2832
  throw new NotebookError("CONFLICT", "Notebook state rename target already exists");
2828
2833
  } catch (error) {
2829
2834
  if (error.code !== "ENOENT") throw error;
@@ -2868,7 +2873,7 @@ function atomicWriteJson(path, value, root, afterParentPinned) {
2868
2873
  closeSync3(fd);
2869
2874
  }
2870
2875
  try {
2871
- const current = lstatSync9(target);
2876
+ const current = lstatSync10(target);
2872
2877
  if (!existingIdentity || !current.isFile() || current.isSymbolicLink() || current.dev !== existingIdentity.dev || current.ino !== existingIdentity.ino) {
2873
2878
  throw new NotebookError("CONFLICT", "Notebook state target changed during atomic update; preserving both entries for audit");
2874
2879
  }
@@ -3103,13 +3108,13 @@ function baselineReceiptByteCeiling(limits) {
3103
3108
  function readSessionBaseline(root, projectSlug, sessionKey, limits) {
3104
3109
  assertDigest(sessionKey, "session key");
3105
3110
  const paths = ensureNotebookState(root, projectSlug);
3106
- return readBaseline(join12(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3111
+ return readBaseline(join13(paths.baselines, `${sessionKey}.json`), baselineReceiptByteCeiling(limits));
3107
3112
  }
3108
3113
  function acquireLock(paths, maxWaitMs) {
3109
- const lock = join12(paths.locks, "admission.lock");
3114
+ const lock = join13(paths.locks, "admission.lock");
3110
3115
  const deadline = Date.now() + Math.max(1, maxWaitMs);
3111
3116
  const token = randomUUID3();
3112
- const record2 = () => ({
3117
+ const record3 = () => ({
3113
3118
  schema_version: NOTEBOOK_SCHEMA_VERSION,
3114
3119
  token,
3115
3120
  pid: process.pid,
@@ -3117,7 +3122,7 @@ function acquireLock(paths, maxWaitMs) {
3117
3122
  expires_at: new Date(Date.now() + Math.max(5e3, maxWaitMs * 20)).toISOString()
3118
3123
  });
3119
3124
  while (true) {
3120
- if (exclusiveWrite(lock, jsonLine(record2()), paths.root)) {
3125
+ if (exclusiveWrite(lock, jsonLine(record3()), paths.root)) {
3121
3126
  return () => {
3122
3127
  const current = safeReadJson(lock, 8192);
3123
3128
  if (current.value && typeof current.value === "object" && !Array.isArray(current.value) && current.value.token === token) {
@@ -3136,7 +3141,7 @@ function acquireLock(paths, maxWaitMs) {
3136
3141
  throw new NotebookError("CONFLICT", "Notebook state lock has an integrity finding; preserve it and run pj notebook audit --json");
3137
3142
  }
3138
3143
  if (Date.now() >= expiresAt) {
3139
- const recovery = join12(paths.locks, `recovery-${heldToken}.lock`);
3144
+ const recovery = join13(paths.locks, `recovery-${heldToken}.lock`);
3140
3145
  const recoveryRecord = jsonLine({ schema_version: NOTEBOOK_SCHEMA_VERSION, stale_token: heldToken, recovery_token: token, expires_at: new Date(Date.now() + 5e3).toISOString() });
3141
3146
  if (exclusiveWrite(recovery, recoveryRecord, paths.root)) {
3142
3147
  try {
@@ -3144,7 +3149,7 @@ function acquireLock(paths, maxWaitMs) {
3144
3149
  const currentToken = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? verify.value.token : null;
3145
3150
  const currentExpiry = verify.value && typeof verify.value === "object" && !Array.isArray(verify.value) ? Date.parse(String(verify.value.expires_at ?? "")) : Number.NaN;
3146
3151
  if (currentToken === heldToken && Number.isFinite(currentExpiry) && Date.now() >= currentExpiry) {
3147
- const recovered = join12(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3152
+ const recovered = join13(paths.locks, `.recovered-${heldToken}-${randomUUID3()}.json`);
3148
3153
  renameStateFile(lock, recovered, paths.root);
3149
3154
  unlinkStateFile(recovered, paths.root);
3150
3155
  }
@@ -3224,7 +3229,7 @@ function scanAuxiliaryState(paths, limits) {
3224
3229
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3225
3230
  continue;
3226
3231
  }
3227
- const read = safeReadJson(join12(specification.dir, entry.name), specification.maxBytes);
3232
+ const read = safeReadJson(join13(specification.dir, entry.name), specification.maxBytes);
3228
3233
  if (read.reason || read.value === void 0) {
3229
3234
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3230
3235
  continue;
@@ -3249,7 +3254,7 @@ function scanAuxiliaryState(paths, limits) {
3249
3254
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3250
3255
  continue;
3251
3256
  }
3252
- const read = safeReadJson(join12(paths.journals, entry.name), limits.receipt_max_bytes);
3257
+ const read = safeReadJson(join13(paths.journals, entry.name), limits.receipt_max_bytes);
3253
3258
  if (read.reason || read.value === void 0) {
3254
3259
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3255
3260
  continue;
@@ -3278,7 +3283,7 @@ function scanAuxiliaryState(paths, limits) {
3278
3283
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: "non-regular" });
3279
3284
  continue;
3280
3285
  }
3281
- const read = safeReadJson(join12(paths.locks, entry.name), 8192);
3286
+ const read = safeReadJson(join13(paths.locks, entry.name), 8192);
3282
3287
  if (read.reason || read.value === void 0) {
3283
3288
  addBoundedIntegrity(scan, limits, { entry_id: entryId, reason: read.reason ?? "invalid-json" }, read.bytes);
3284
3289
  continue;
@@ -3317,7 +3322,7 @@ function scanReceipts(paths, limits) {
3317
3322
  }
3318
3323
  for (const entry of entries) {
3319
3324
  const entryId = safeEntryId("receipts", entry.name);
3320
- const path = join12(paths.receipts, entry.name);
3325
+ const path = join13(paths.receipts, entry.name);
3321
3326
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9]{64}\.json$/u.test(entry.name)) {
3322
3327
  addIntegrity({ entry_id: entryId, reason: "non-regular" });
3323
3328
  continue;
@@ -3347,7 +3352,7 @@ function scanBaselines(paths, nowMs, limits, referenced) {
3347
3352
  if (!existsSync9(paths.baselines)) return { current, stale };
3348
3353
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
3349
3354
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3350
- const baseline = readBaseline(join12(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
3355
+ const baseline = readBaseline(join13(paths.baselines, entry.name), baselineReceiptByteCeiling(limits));
3351
3356
  if (!baseline || referenced.has(baseline.session_key)) continue;
3352
3357
  const expires = Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3;
3353
3358
  if (nowMs >= expires) stale += 1;
@@ -3361,7 +3366,7 @@ function scanRefusals(paths, nowMs, limits, scan) {
3361
3366
  const entries = readStateDirectory(paths.refusals, paths.root).sort((a, b) => a.name.localeCompare(b.name, "en")).slice(0, limits.refusal_max_entries);
3362
3367
  for (const entry of entries) {
3363
3368
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3364
- const read = safeReadJson(join12(paths.refusals, entry.name), limits.receipt_max_bytes);
3369
+ const read = safeReadJson(join13(paths.refusals, entry.name), limits.receipt_max_bytes);
3365
3370
  const marker = read.value === void 0 ? null : parseRefusal(read.value);
3366
3371
  if (!marker || `${marker.session_key}.json` !== entry.name) continue;
3367
3372
  if (nowMs >= Date.parse(marker.baseline_created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
@@ -3444,7 +3449,7 @@ function listCaptureReceipts(root, projectSlug, limits, state) {
3444
3449
  function readCaptureReceipt(root, projectSlug, receiptId, limits) {
3445
3450
  if (!RECEIPT_ID_RE.test(receiptId)) throw new NotebookError("INVALID_INPUT", "Invalid receipt ID");
3446
3451
  const paths = notebookStatePaths(root, projectSlug);
3447
- const path = join12(paths.receipts, `${receiptId}.json`);
3452
+ const path = join13(paths.receipts, `${receiptId}.json`);
3448
3453
  if (!existsSync9(path)) throw new NotebookError("NOT_FOUND", `Capture receipt not found: ${receiptId}`);
3449
3454
  const read = safeReadJson(path, limits.receipt_max_bytes);
3450
3455
  const receipt = read.value === void 0 ? null : parseReceipt(read.value);
@@ -3457,7 +3462,7 @@ function writeReceipt(paths, receipt, limit) {
3457
3462
  const bytes = Buffer.byteLength(text, "utf8");
3458
3463
  if (bytes > limit) throw new NotebookError("CONFLICT", "Receipt transition exceeds its per-receipt ceiling");
3459
3464
  if (receipt.serialized_bytes === bytes) {
3460
- atomicWriteJson(join12(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
3465
+ atomicWriteJson(join13(paths.receipts, `${receipt.receipt_id}.json`), receipt, paths.root);
3461
3466
  return receipt;
3462
3467
  }
3463
3468
  receipt.serialized_bytes = bytes;
@@ -3596,7 +3601,7 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
3596
3601
  const successCutoff = now.getTime() - limits.receipt_succeeded_retention_days * 864e5;
3597
3602
  for (const receipt of scan.receipts) {
3598
3603
  if (receipt.state !== "succeeded" || Date.parse(receipt.updated_at) > successCutoff) continue;
3599
- const path = join12(paths.receipts, `${receipt.receipt_id}.json`);
3604
+ const path = join13(paths.receipts, `${receipt.receipt_id}.json`);
3600
3605
  unlinkStateFile(path, paths.root);
3601
3606
  removed.push(safeEntryId("receipts", basename7(path)));
3602
3607
  }
@@ -3605,15 +3610,15 @@ function pruneNotebookState(root, projectSlug, limits, now = /* @__PURE__ */ new
3605
3610
  const referenced = /* @__PURE__ */ new Set([...remaining.referencedSessions, ...remainingAuxiliary.referencedSessions]);
3606
3611
  for (const entry of readStateDirectory(paths.baselines, paths.root)) {
3607
3612
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
3608
- const path = join12(paths.baselines, entry.name);
3613
+ const path = join13(paths.baselines, entry.name);
3609
3614
  const baseline = readBaseline(path, baselineReceiptByteCeiling(limits));
3610
3615
  if (!baseline || referenced.has(baseline.session_key)) continue;
3611
3616
  if (now.getTime() < Date.parse(baseline.created_at) + limits.receiptless_session_retention_seconds * 1e3) continue;
3612
3617
  unlinkStateFile(path, paths.root);
3613
3618
  removed.push(safeEntryId("baselines", entry.name));
3614
- const claim = join12(paths.claims, `${baseline.session_key}.overview`);
3619
+ const claim = join13(paths.claims, `${baseline.session_key}.overview`);
3615
3620
  if (unlinkStateFile(claim, paths.root, true)) removed.push(safeEntryId("claims", basename7(claim)));
3616
- const refusal = join12(paths.refusals, `${baseline.session_key}.json`);
3621
+ const refusal = join13(paths.refusals, `${baseline.session_key}.json`);
3617
3622
  if (unlinkStateFile(refusal, paths.root, true)) removed.push(safeEntryId("refusals", basename7(refusal)));
3618
3623
  }
3619
3624
  fsyncDirectory2(paths.project);
@@ -3998,11 +4003,11 @@ var init_open_notebook_client = __esm({
3998
4003
  });
3999
4004
 
4000
4005
  // src/notebook/git-evidence.ts
4001
- import { spawnSync as spawnSync6 } from "node:child_process";
4002
- import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync10, openSync as openSync4, readSync as readSync2, realpathSync as realpathSync7 } from "node:fs";
4003
- import { extname, join as join14, relative as relative9, resolve as resolve12, sep as sep4 } from "node:path";
4006
+ import { spawnSync as spawnSync7 } from "node:child_process";
4007
+ import { closeSync as closeSync4, constants as constants3, fstatSync as fstatSync2, lstatSync as lstatSync11, openSync as openSync4, readSync as readSync2, realpathSync as realpathSync7 } from "node:fs";
4008
+ import { extname, join as join15, relative as relative9, resolve as resolve13, sep as sep4 } from "node:path";
4004
4009
  function git(repo, args, maxBuffer = 4 * 1024 * 1024, timeout = 5e3) {
4005
- const result2 = spawnSync6("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
4010
+ const result2 = spawnSync7("git", args, { cwd: repo, encoding: "utf8", maxBuffer, timeout, shell: false });
4006
4011
  return { ok: result2.status === 0, stdout: result2.stdout ?? "" };
4007
4012
  }
4008
4013
  function statusPaths(value) {
@@ -4069,7 +4074,7 @@ function validateCommittedGitRef(repoPath, gitRef) {
4069
4074
  function safeRelative(repoPath, relativePath2) {
4070
4075
  if (!relativePath2 || relativePath2.includes("\0") || relativePath2.startsWith("/") || relativePath2.split(/[\\/]/u).includes("..")) return null;
4071
4076
  const root = realpathSync7(repoPath);
4072
- const candidate = resolve12(root, relativePath2);
4077
+ const candidate = resolve13(root, relativePath2);
4073
4078
  const rel = relative9(root, candidate);
4074
4079
  if (rel === ".." || rel.startsWith(`..${sep4}`) || rel.startsWith(sep4)) return null;
4075
4080
  return { root, candidate };
@@ -4078,9 +4083,9 @@ function hasSymlinkComponent(root, candidate) {
4078
4083
  const rel = relative9(root, candidate);
4079
4084
  let cursor = root;
4080
4085
  for (const part of rel.split(sep4).filter(Boolean)) {
4081
- cursor = join14(cursor, part);
4086
+ cursor = join15(cursor, part);
4082
4087
  try {
4083
- if (lstatSync10(cursor).isSymbolicLink()) return true;
4088
+ if (lstatSync11(cursor).isSymbolicLink()) return true;
4084
4089
  } catch {
4085
4090
  return true;
4086
4091
  }
@@ -4147,8 +4152,8 @@ function matchesSimpleGlob(path, glob) {
4147
4152
  if (glob.startsWith("**/*.")) return extname(path).toLowerCase() === `.${glob.slice(5).toLowerCase()}`;
4148
4153
  return path === glob;
4149
4154
  }
4150
- function addExclusion(record2, reason) {
4151
- record2[reason] = (record2[reason] ?? 0) + 1;
4155
+ function addExclusion(record3, reason) {
4156
+ record3[reason] = (record3[reason] ?? 0) + 1;
4152
4157
  }
4153
4158
  function selectEligibleDocuments(config, baseline, manualBaselineRef) {
4154
4159
  const repo = config.repo_path;
@@ -4221,10 +4226,10 @@ var init_git_evidence = __esm({
4221
4226
 
4222
4227
  // src/notebook/remote-mutation-journal.ts
4223
4228
  import { randomUUID as randomUUID4 } from "node:crypto";
4224
- import { join as join15 } from "node:path";
4229
+ import { join as join16 } from "node:path";
4225
4230
  function journalPath(root, projectSlug, operationId) {
4226
4231
  if (!/^[a-f0-9-]{16,64}$/iu.test(operationId)) throw new NotebookError("INVALID_INPUT", "Invalid remote mutation operation ID");
4227
- return join15(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
4232
+ return join16(ensureNotebookState(root, projectSlug).journals, `${operationId}.json`);
4228
4233
  }
4229
4234
  function remoteMutationJournalPath(root, projectSlug, operationId) {
4230
4235
  return journalPath(root, projectSlug, operationId);
@@ -4244,7 +4249,7 @@ function listRemoteMutationJournals(root, projectSlug) {
4244
4249
  if (!entry.isFile() || entry.isSymbolicLink() || !/^[a-f0-9-]{16,64}\.json$/iu.test(entry.name)) {
4245
4250
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
4246
4251
  }
4247
- const read = readNotebookStateJson(join15(paths.journals, entry.name), paths.root, 65536);
4252
+ const read = readNotebookStateJson(join16(paths.journals, entry.name), paths.root, 65536);
4248
4253
  const journal = read.value === void 0 ? null : parseRemoteMutationJournal(read.value);
4249
4254
  if (!journal || `${journal.operation_id}.json` !== entry.name) {
4250
4255
  throw new NotebookError("CONFLICT", `Remote mutation journal state-integrity finding: journals/${entry.name}`);
@@ -4599,15 +4604,15 @@ var init_reconcile = __esm({
4599
4604
  // src/notebook/entropy.ts
4600
4605
  import { existsSync as existsSync12 } from "node:fs";
4601
4606
  import { createHash as createHash9 } from "node:crypto";
4602
- import { resolve as resolve15 } from "node:path";
4603
- import { spawnSync as spawnSync9 } from "node:child_process";
4607
+ import { resolve as resolve16 } from "node:path";
4608
+ import { spawnSync as spawnSync10 } from "node:child_process";
4604
4609
  function listAllTrackedDocuments(config) {
4605
4610
  const repo = config.repo_path;
4606
4611
  const timeout = config.limits.overall_timeout_ms;
4607
- const trackedResult = spawnSync9("git", ["ls-files", "-z"], { cwd: repo, encoding: "utf8", maxBuffer: 4 * 1024 * 1024, timeout, shell: false });
4612
+ const trackedResult = spawnSync10("git", ["ls-files", "-z"], { cwd: repo, encoding: "utf8", maxBuffer: 4 * 1024 * 1024, timeout, shell: false });
4608
4613
  if (trackedResult.status !== 0) return [];
4609
4614
  const tracked = trackedResult.stdout.split("\0").filter(Boolean);
4610
- const headRev = spawnSync9("git", ["rev-parse", "HEAD"], { cwd: repo, encoding: "utf8", timeout, shell: false }).stdout?.trim() ?? "HEAD";
4615
+ const headRev = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: repo, encoding: "utf8", timeout, shell: false }).stdout?.trim() ?? "HEAD";
4611
4616
  const documents = [];
4612
4617
  for (const path of tracked) {
4613
4618
  if (!config.policy.documentation_globs.some((glob) => matchesSimpleGlob(path, glob))) continue;
@@ -4634,7 +4639,7 @@ async function pruneZombieNotes(input) {
4634
4639
  const parsed = parseNoteEnvelope(note.content);
4635
4640
  if (!parsed || parsed.envelope.kind !== "document" || parsed.envelope.project_slug !== input.projectSlug) continue;
4636
4641
  const sourcePath = parsed.envelope.source_path;
4637
- if (!sourcePath || !existsSync12(resolve15(input.repoPath, sourcePath))) {
4642
+ if (!sourcePath || !existsSync12(resolve16(input.repoPath, sourcePath))) {
4638
4643
  if (typeof input.client.deleteOwnedNote === "function") {
4639
4644
  await input.client.deleteOwnedNote(input.notebookId, note.id);
4640
4645
  } else if (typeof input.client.deleteNote === "function") {
@@ -4794,8 +4799,8 @@ var init_entropy = __esm({
4794
4799
  });
4795
4800
 
4796
4801
  // src/notebook/summarizer.ts
4797
- import { spawnSync as spawnSync10 } from "node:child_process";
4798
- import { isAbsolute as isAbsolute7 } from "node:path";
4802
+ import { spawnSync as spawnSync11 } from "node:child_process";
4803
+ import { isAbsolute as isAbsolute8 } from "node:path";
4799
4804
  function evidenceItems(evidence) {
4800
4805
  const items = [];
4801
4806
  const eligible = new Set(evidence.documents.map((item) => item.path));
@@ -4868,11 +4873,11 @@ function words(value) {
4868
4873
  }
4869
4874
  function validateClaims(value, items, noteMaxBytes) {
4870
4875
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
4871
- const record2 = value;
4872
- if (record2.schema_version !== 1 || !Array.isArray(record2.claims) || record2.claims.length < 1 || record2.claims.length > 50 || Object.keys(record2).some((key) => key !== "schema_version" && key !== "claims")) return null;
4876
+ const record3 = value;
4877
+ if (record3.schema_version !== 1 || !Array.isArray(record3.claims) || record3.claims.length < 1 || record3.claims.length > 50 || Object.keys(record3).some((key) => key !== "schema_version" && key !== "claims")) return null;
4873
4878
  const byId = new Map(items.map((item) => [item.evidence_id, item]));
4874
4879
  const rendered = ["## Session outcome"];
4875
- for (const claimValue of record2.claims) {
4880
+ for (const claimValue of record3.claims) {
4876
4881
  if (!claimValue || typeof claimValue !== "object" || Array.isArray(claimValue)) return null;
4877
4882
  const claim = claimValue;
4878
4883
  if (Object.keys(claim).some((key) => key !== "text" && key !== "evidence_ids") || typeof claim.text !== "string" || !claim.text.trim() || Buffer.byteLength(claim.text, "utf8") > 1024 || !Array.isArray(claim.evidence_ids) || claim.evidence_ids.length < 1 || claim.evidence_ids.length > 10 || claim.evidence_ids.some((id) => typeof id !== "string" || !byId.has(id))) return null;
@@ -4893,7 +4898,7 @@ function summarizeCapture(config, evidence) {
4893
4898
  const items = evidenceItems(evidence);
4894
4899
  const payload = JSON.stringify({ schema_version: 1, evidence: items });
4895
4900
  if (Buffer.byteLength(payload, "utf8") > config.limits.request_max_bytes) return { schema_version: 1, mode: "deterministic-fallback", summary: fallbackSummary };
4896
- const result2 = spawnSync10(config.summarizer.executable, config.summarizer.args, {
4901
+ const result2 = spawnSync11(config.summarizer.executable, config.summarizer.args, {
4897
4902
  cwd: config.repo_path,
4898
4903
  input: Buffer.from(payload, "utf8"),
4899
4904
  timeout: config.limits.overall_timeout_ms,
@@ -4911,7 +4916,7 @@ function summarizeCapture(config, evidence) {
4911
4916
  }
4912
4917
  }
4913
4918
  function assertSummarizerConfig(config) {
4914
- if (config.summarizer && (!isAbsolute7(config.summarizer.executable) || config.summarizer.executable.includes("\0") || config.summarizer.args.length > 32 || config.summarizer.args.some((arg) => arg.includes("\0") || Buffer.byteLength(arg, "utf8") > 1024))) {
4919
+ if (config.summarizer && (!isAbsolute8(config.summarizer.executable) || config.summarizer.executable.includes("\0") || config.summarizer.args.length > 32 || config.summarizer.args.some((arg) => arg.includes("\0") || Buffer.byteLength(arg, "utf8") > 1024))) {
4915
4920
  throw new NotebookError("NOT_CONFIGURED", "Configured Notebook summarizer command is invalid");
4916
4921
  }
4917
4922
  }
@@ -5221,7 +5226,7 @@ var init_capture = __esm({
5221
5226
 
5222
5227
  // src/mcp-server.ts
5223
5228
  import { existsSync as existsSync18, statSync as statSync7 } from "node:fs";
5224
- import { dirname as dirname17, join as join26, resolve as resolve21 } from "node:path";
5229
+ import { dirname as dirname17, join as join27, resolve as resolve22 } from "node:path";
5225
5230
  import { fileURLToPath as fileURLToPath6 } from "node:url";
5226
5231
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5227
5232
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -5332,12 +5337,12 @@ var Recipe = class {
5332
5337
  };
5333
5338
  }
5334
5339
  /** Initialize missing/drifted state using only this recipe's owned checks. */
5335
- async initializeOwnedChecks(ctx) {
5340
+ async initializeOwnedChecks(ctx, checks = this.checks) {
5336
5341
  const phases = [];
5337
5342
  const logs = [];
5338
5343
  const errors = [];
5339
5344
  const changedFiles = [];
5340
- for (const check of this.checks) {
5345
+ for (const check of checks) {
5341
5346
  const finding2 = await check.audit(ctx);
5342
5347
  if (finding2.status === "pass" || finding2.status === "skip") {
5343
5348
  phases.push({ id: check.id, status: finding2.status === "skip" ? "skipped" : "unchanged", changedFiles: [], message: finding2.summary });
@@ -5419,10 +5424,233 @@ var Recipe = class {
5419
5424
 
5420
5425
  // src/parity/rules.ts
5421
5426
  init_registryClient();
5427
+
5428
+ // src/bmad/companion.ts
5429
+ init_tree_diff();
5430
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2 } from "node:fs";
5431
+ import { spawnSync as spawnSync2 } from "node:child_process";
5432
+ import { isAbsolute, join as join3, resolve as resolve3 } from "node:path";
5433
+ import { pathToFileURL } from "node:url";
5434
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
5435
+ function optionalObject(parent, key, label) {
5436
+ if (!Object.hasOwn(parent, key)) return {};
5437
+ const value = parent[key];
5438
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object when present`);
5439
+ return value;
5440
+ }
5441
+ function select(projectRoot) {
5442
+ const path = join3(projectRoot, ".project.json");
5443
+ const manifest = existsSync2(path) ? record(JSON.parse(readFileSync2(path, "utf8"))) : {};
5444
+ const bmad = optionalObject(manifest, "bmad", "bmad");
5445
+ const config = optionalObject(bmad, "companion", "bmad.companion");
5446
+ const adapter = optionalObject(config, "adapter", "bmad.companion.adapter");
5447
+ const options = optionalObject(adapter, "options", "bmad.companion.adapter.options");
5448
+ if (config.enabled === false) return { enabled: false, reason: "g33 explicitly disabled in .project.json", options: {} };
5449
+ if (config.enabled !== void 0 && config.enabled !== true) throw new Error("bmad.companion.enabled must be boolean");
5450
+ const binding = record(manifest.ticket_provider);
5451
+ const registered = typeof manifest.project_id === "string" && /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(manifest.project_id) && manifest.project_id.length <= 128;
5452
+ const eligible = registered && binding.type === "plane" && binding.state === "linked" && typeof binding.workspace === "string" && binding.workspace.trim().toLowerCase() === "33god" && typeof binding.board_id === "string" && binding.board_id.trim() !== "";
5453
+ if (!eligible && config.enabled !== true) return { enabled: false, reason: "g33 defaults off without a registered linked 33god project or explicit opt-in", options: {} };
5454
+ const module = adapter.module ?? (process.env.PJ_G33_ADAPTER_MODULE?.trim() || void 0);
5455
+ const exportName = adapter.exportName ?? (process.env.PJ_G33_ADAPTER_EXPORT?.trim() || void 0);
5456
+ if (module !== void 0 && (typeof module !== "string" || !module.trim())) throw new Error("g33 adapter.module must be a non-empty package specifier or source path");
5457
+ if (exportName !== void 0 && (typeof exportName !== "string" || !exportName.trim())) throw new Error("g33 adapter.exportName must name the supported callable export");
5458
+ return {
5459
+ enabled: true,
5460
+ reason: eligible ? "Registered linked 33god project" : "Explicit g33 opt-in",
5461
+ module,
5462
+ exportName,
5463
+ options
5464
+ };
5465
+ }
5466
+ function g33CompanionEnabled(projectRoot) {
5467
+ return select(projectRoot).enabled;
5468
+ }
5469
+ function resolveAdapter(projectRoot, module) {
5470
+ if (module.startsWith(".") || isAbsolute(module)) {
5471
+ const path = resolve3(projectRoot, module);
5472
+ if (!existsSync2(path)) throw new Error(`Configured g33 source is unavailable: ${module}`);
5473
+ return pathToFileURL(path).href;
5474
+ }
5475
+ if (module.includes(":") || module.startsWith("/")) throw new Error("Use a local source path or installed package specifier for g33");
5476
+ const resolved = spawnSync2(process.execPath, [
5477
+ "--experimental-import-meta-resolve",
5478
+ "--input-type=module",
5479
+ "--eval",
5480
+ "process.stdout.write(import.meta.resolve(process.argv[1], process.argv[2]))",
5481
+ "--",
5482
+ module,
5483
+ pathToFileURL(join3(projectRoot, ".project.json")).href
5484
+ ], { encoding: "utf8", timeout: 1e4 });
5485
+ if (resolved.status === 0 && resolved.stdout.trim()) return resolved.stdout.trim();
5486
+ const projectError = resolved.stderr.trim() || resolved.error?.message || `resolver exit ${resolved.status}`;
5487
+ try {
5488
+ return import.meta.resolve(module);
5489
+ } catch (error) {
5490
+ throw new Error(`Project ESM resolution failed: ${projectError}
5491
+ PJangler ESM resolution failed: ${error instanceof Error ? error.message : String(error)}`);
5492
+ }
5493
+ }
5494
+ function reply(value, operation) {
5495
+ const data = record(value);
5496
+ const statuses = operation === "observe" ? ["installed", "missing", "unavailable", "conflict", "error"] : operation === "plan" ? ["planned", "unchanged", "unavailable", "conflict", "error"] : ["changed", "unchanged", "unavailable", "conflict", "error"];
5497
+ if (data.schemaVersion !== 1 || !statuses.includes(String(data.status)) || typeof data.summary !== "string" || ![data.details, data.evidence].every((field2) => Array.isArray(field2) && field2.every((item) => typeof item === "string"))) {
5498
+ throw new Error(`Configured g33 adapter returned an invalid v1 ${operation} result`);
5499
+ }
5500
+ if (data.status === "installed" && !data.evidence.some((item) => item.trim())) {
5501
+ throw new Error("g33 installed observation requires owner content/mapping evidence; metadata alone is insufficient");
5502
+ }
5503
+ return {
5504
+ schemaVersion: 1,
5505
+ status: data.status,
5506
+ summary: data.summary,
5507
+ details: [...data.details],
5508
+ evidence: [...data.evidence]
5509
+ };
5510
+ }
5511
+ async function runG33Companion(projectRoot, dryRun, reason = "recipe") {
5512
+ projectRoot = resolve3(projectRoot);
5513
+ const result2 = { schemaVersion: 1, moduleId: "g33", optional: true, status: "unavailable", summary: "g33 companion unavailable", details: [], evidence: [], changedFiles: [] };
5514
+ let before;
5515
+ try {
5516
+ const selection = select(projectRoot);
5517
+ if (!selection.enabled) return { ...result2, status: "disabled", summary: selection.reason };
5518
+ result2.details.push(selection.reason);
5519
+ if (!selection.module || !selection.exportName) return {
5520
+ ...result2,
5521
+ summary: "Optional g33 installer is not configured",
5522
+ details: [...result2.details, "Configure bmad.companion.adapter.module and exportName (or PJ_G33_ADAPTER_MODULE/PJ_G33_ADAPTER_EXPORT) to the owner's supported callable adapter. No g33 installation was attempted."]
5523
+ };
5524
+ result2.adapter = { module: selection.module, exportName: selection.exportName };
5525
+ let specifier;
5526
+ try {
5527
+ specifier = resolveAdapter(projectRoot, selection.module);
5528
+ } catch (error) {
5529
+ return { ...result2, status: "unavailable", summary: "Configured g33 adapter is unavailable", details: [...result2.details, error instanceof Error ? error.message : String(error)] };
5530
+ }
5531
+ before = snapshotTree(projectRoot);
5532
+ const exported = await import(specifier);
5533
+ if (changedTreePaths(projectRoot, before, snapshotTree(projectRoot)).length) {
5534
+ throw new Error("g33 adapter violated the no-write import contract");
5535
+ }
5536
+ const callable = exported[selection.exportName];
5537
+ if (typeof callable !== "function") throw new Error(`Configured g33 export ${selection.exportName} is not callable`);
5538
+ const invoke = async (operation) => {
5539
+ const value = reply(await callable({ schemaVersion: 1, moduleId: "g33", operation, projectRoot, reason, options: selection.options }), operation);
5540
+ if (operation !== "apply" && changedTreePaths(projectRoot, before, snapshotTree(projectRoot)).length) {
5541
+ throw new Error(`g33 adapter violated the no-write ${operation} contract`);
5542
+ }
5543
+ return value;
5544
+ };
5545
+ const observed = await invoke("observe");
5546
+ if (["unavailable", "conflict", "error"].includes(observed.status)) return { ...result2, ...observed };
5547
+ const plan = await invoke("plan");
5548
+ if (["unavailable", "conflict", "error"].includes(plan.status)) return { ...result2, ...plan };
5549
+ if (plan.status === "unchanged" && observed.status !== "installed") throw new Error("g33 plan claims convergence but owner observation does not prove installation");
5550
+ if (dryRun) return { ...result2, ...plan, evidence: observed.evidence };
5551
+ if (plan.status === "unchanged" && observed.status === "installed") return { ...result2, ...observed, status: "unchanged" };
5552
+ const applied = await invoke("apply");
5553
+ result2.changedFiles = changedTreePaths(projectRoot, before, snapshotTree(projectRoot));
5554
+ if (["unavailable", "conflict", "error"].includes(applied.status)) return { ...result2, ...applied };
5555
+ before = snapshotTree(projectRoot);
5556
+ const verified = await invoke("observe");
5557
+ if (verified.status !== "installed") return {
5558
+ ...result2,
5559
+ status: "error",
5560
+ summary: "g33 apply did not establish an installed owner observation",
5561
+ details: [verified.summary, ...verified.details],
5562
+ evidence: verified.evidence
5563
+ };
5564
+ return { ...result2, ...verified, status: result2.changedFiles.length ? "changed" : "unchanged" };
5565
+ } catch (error) {
5566
+ if (before) {
5567
+ try {
5568
+ result2.changedFiles = [.../* @__PURE__ */ new Set([...result2.changedFiles, ...changedTreePaths(projectRoot, before, snapshotTree(projectRoot))])].sort();
5569
+ } catch (snapshotError) {
5570
+ result2.details.push(`Could not inventory partial g33 writes: ${String(snapshotError)}`);
5571
+ }
5572
+ }
5573
+ return { ...result2, status: "error", summary: "Optional g33 companion could not be reconciled", details: [...result2.details, error instanceof Error ? error.message : String(error)] };
5574
+ }
5575
+ }
5576
+ function localEntryPresent(path) {
5577
+ try {
5578
+ lstatSync2(path);
5579
+ return true;
5580
+ } catch (error) {
5581
+ if (error.code === "ENOENT") return false;
5582
+ throw error;
5583
+ }
5584
+ }
5585
+ function g33BmadUpdateBoundary(projectRoot, retainedG33Declaration = false) {
5586
+ const result2 = {
5587
+ schemaVersion: 1,
5588
+ moduleId: "g33",
5589
+ optional: true,
5590
+ status: "conflict",
5591
+ summary: "BMAD update withheld pending the g33 owner pre-update preservation contract",
5592
+ details: [
5593
+ "No installer, retired-state eviction, or companion import/apply was attempted. Existing native BMAD bytes, customizations, and module mappings are retained.",
5594
+ "The owner must provide a supported pre-update capture/protection and post-update restore/verification handoff, including failure recovery. The configured v1 callable alone does not authorize a destructive BMAD update."
5595
+ ],
5596
+ evidence: [],
5597
+ changedFiles: []
5598
+ };
5599
+ try {
5600
+ const retainedG33 = retainedG33Declaration || localEntryPresent(join3(projectRoot, "_bmad", "g33"));
5601
+ const installedBmad = retainedG33 || localEntryPresent(join3(projectRoot, "_bmad", "_config", "manifest.yaml")) || localEntryPresent(join3(projectRoot, "_bmad", "core"));
5602
+ if (!installedBmad) return void 0;
5603
+ const selection = select(projectRoot);
5604
+ if (!selection.enabled && !retainedG33) return void 0;
5605
+ if (selection.module && selection.exportName) result2.adapter = { module: selection.module, exportName: selection.exportName };
5606
+ result2.details.unshift(retainedG33 ? "Retained g33 tree or module declaration requires preservation, including after opt-out." : selection.reason);
5607
+ return result2;
5608
+ } catch (error) {
5609
+ return {
5610
+ ...result2,
5611
+ status: "error",
5612
+ summary: "BMAD update withheld: companion configuration or preservation inspection failed",
5613
+ details: [...result2.details, error instanceof Error ? error.message : String(error)]
5614
+ };
5615
+ }
5616
+ }
5617
+ function companionDetails(result2) {
5618
+ return [`g33 (${result2.status}): ${result2.summary}`, ...result2.details, ...result2.evidence];
5619
+ }
5620
+ function createG33CompanionCheck() {
5621
+ return {
5622
+ id: "bmad.g33-companion",
5623
+ title: "Optional g33 companion",
5624
+ audit: async (ctx) => {
5625
+ const result2 = await runG33Companion(ctx.repoRoot, true, "audit");
5626
+ return {
5627
+ id: "bmad.g33-companion",
5628
+ title: "Optional g33 companion",
5629
+ status: result2.status === "disabled" ? "skip" : result2.status === "unchanged" && result2.evidence.length ? "pass" : "warn",
5630
+ summary: result2.summary,
5631
+ details: companionDetails(result2),
5632
+ fixable: result2.status === "planned"
5633
+ };
5634
+ },
5635
+ migrate: async (ctx, finding2) => {
5636
+ const result2 = await runG33Companion(ctx.repoRoot, ctx.dryRun, "repair");
5637
+ return {
5638
+ id: finding2.id,
5639
+ title: finding2.title,
5640
+ status: result2.status === "changed" || result2.status === "planned" ? "applied" : result2.status === "unchanged" ? "noop" : result2.status === "disabled" ? "skipped" : "partial",
5641
+ summary: result2.summary,
5642
+ details: companionDetails(result2),
5643
+ changedFiles: result2.changedFiles
5644
+ };
5645
+ }
5646
+ };
5647
+ }
5648
+
5649
+ // src/parity/rules.ts
5422
5650
  init_style();
5423
- import { existsSync as existsSync3, lstatSync as lstatSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readlinkSync as readlinkSync3, readdirSync as readdirSync3, realpathSync as realpathSync3, renameSync as renameSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3, chmodSync, copyFileSync, rmSync as rmSync2 } from "node:fs";
5424
- import { basename as basename3, dirname as dirname5, join as join5, relative as relative4, resolve as resolve5 } from "node:path";
5425
- import { spawnSync as spawnSync3 } from "node:child_process";
5651
+ import { existsSync as existsSync4, lstatSync as lstatSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readlinkSync as readlinkSync4, readdirSync as readdirSync4, realpathSync as realpathSync3, renameSync as renameSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3, chmodSync, copyFileSync, rmSync as rmSync2 } from "node:fs";
5652
+ import { basename as basename3, dirname as dirname5, join as join7, relative as relative5, resolve as resolve6 } from "node:path";
5653
+ import { spawnSync as spawnSync4 } from "node:child_process";
5426
5654
  import YAML2 from "yaml";
5427
5655
  import { parse as parseToml2 } from "smol-toml";
5428
5656
 
@@ -5441,8 +5669,8 @@ var SUPPORTED_BMAD_TOOLS = SUPPORTED_CLIS.map((cli) => cli.bmadTool);
5441
5669
  var SUPPORTED_CLI_ROOTS = SUPPORTED_CLIS.map((cli) => cli.projectRoot);
5442
5670
 
5443
5671
  // src/parity/skills.ts
5444
- import { lstatSync as lstatSync3, readlinkSync as readlinkSync2, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
5445
- import { basename as basename2, dirname as dirname4, join as join4, relative as relative3, resolve as resolve4 } from "node:path";
5672
+ import { lstatSync as lstatSync5, readlinkSync as readlinkSync3, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
5673
+ import { basename as basename2, dirname as dirname4, join as join6, relative as relative4, resolve as resolve5 } from "node:path";
5446
5674
  import {
5447
5675
  initScope,
5448
5676
  inspectStatus,
@@ -5453,15 +5681,15 @@ import {
5453
5681
  } from "@delorenj/skillex";
5454
5682
 
5455
5683
  // src/parity/skill-roots.ts
5456
- import { spawnSync as spawnSync2 } from "node:child_process";
5457
- import { createHash as createHash2 } from "node:crypto";
5684
+ import { spawnSync as spawnSync3 } from "node:child_process";
5685
+ import { createHash as createHash3 } from "node:crypto";
5458
5686
  import {
5459
5687
  appendFileSync,
5460
- lstatSync as lstatSync2,
5688
+ lstatSync as lstatSync4,
5461
5689
  mkdirSync as mkdirSync2,
5462
- readFileSync as readFileSync2,
5463
- readdirSync as readdirSync2,
5464
- readlinkSync,
5690
+ readFileSync as readFileSync4,
5691
+ readdirSync as readdirSync3,
5692
+ readlinkSync as readlinkSync2,
5465
5693
  realpathSync,
5466
5694
  renameSync,
5467
5695
  rmSync,
@@ -5471,15 +5699,15 @@ import {
5471
5699
  unlinkSync,
5472
5700
  writeFileSync as writeFileSync2
5473
5701
  } from "node:fs";
5474
- import { basename, dirname as dirname3, isAbsolute, join as join3, relative as relative2, resolve as resolve3, sep } from "node:path";
5702
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join5, relative as relative3, resolve as resolve4, sep } from "node:path";
5475
5703
 
5476
5704
  // src/parity/bmad-attestation.ts
5477
- import { createHash } from "node:crypto";
5478
- import { existsSync as existsSync2, lstatSync, readFileSync, readdirSync } from "node:fs";
5479
- import { dirname as dirname2, join as join2, relative } from "node:path";
5705
+ import { createHash as createHash2 } from "node:crypto";
5706
+ import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
5707
+ import { dirname as dirname2, join as join4, relative as relative2 } from "node:path";
5480
5708
  import YAML from "yaml";
5481
5709
  function safeReadText(path) {
5482
- return existsSync2(path) ? readFileSync(path, "utf8").replace(/\r\n/g, "\n") : null;
5710
+ return existsSync3(path) ? readFileSync3(path, "utf8").replace(/\r\n/g, "\n") : null;
5483
5711
  }
5484
5712
  function parseCsvRows(text) {
5485
5713
  const rows = [];
@@ -5523,7 +5751,7 @@ function csvObjects(text) {
5523
5751
  return rows.filter((row) => row.some(Boolean)).map((row) => Object.fromEntries(headers.map((header, index) => [header, row[index] ?? ""])));
5524
5752
  }
5525
5753
  function installedBmadTools(repoRoot) {
5526
- const raw = safeReadText(join2(repoRoot, "_bmad", "_config", "manifest.yaml"));
5754
+ const raw = safeReadText(join4(repoRoot, "_bmad", "_config", "manifest.yaml"));
5527
5755
  if (!raw) return /* @__PURE__ */ new Set();
5528
5756
  try {
5529
5757
  const parsed = YAML.parse(raw);
@@ -5533,8 +5761,8 @@ function installedBmadTools(repoRoot) {
5533
5761
  }
5534
5762
  }
5535
5763
  function bmadCliProjectionInventory(repoRoot) {
5536
- const filesText = safeReadText(join2(repoRoot, "_bmad", "_config", "files-manifest.csv"));
5537
- const skillsText = safeReadText(join2(repoRoot, "_bmad", "_config", "skill-manifest.csv"));
5764
+ const filesText = safeReadText(join4(repoRoot, "_bmad", "_config", "files-manifest.csv"));
5765
+ const skillsText = safeReadText(join4(repoRoot, "_bmad", "_config", "skill-manifest.csv"));
5538
5766
  if (!filesText || !skillsText) return { files: /* @__PURE__ */ new Map(), error: "BMAD files/skill manifests are missing" };
5539
5767
  const fileHashes = /* @__PURE__ */ new Map();
5540
5768
  for (const row of csvObjects(filesText)) {
@@ -5550,23 +5778,23 @@ function bmadCliProjectionInventory(repoRoot) {
5550
5778
  const sourceRoot = dirname2(skillPath);
5551
5779
  for (const [sourcePath, hash] of fileHashes) {
5552
5780
  if (sourcePath !== `${sourceRoot}/SKILL.md` && !sourcePath.startsWith(`${sourceRoot}/`)) continue;
5553
- const suffix = relative(sourceRoot, sourcePath);
5781
+ const suffix = relative2(sourceRoot, sourcePath);
5554
5782
  if (!suffix || suffix.startsWith("..")) continue;
5555
- projected.set(join2("skills", canonicalId, suffix), hash);
5783
+ projected.set(join4("skills", canonicalId, suffix), hash);
5556
5784
  }
5557
5785
  }
5558
5786
  return projected.size ? { files: projected } : { files: projected, error: "BMAD manifests contain no projected skill inventory" };
5559
5787
  }
5560
5788
  function inventoryFilesUnder(root, current = root) {
5561
- if (!existsSync2(current)) return { files: [], unsafe: [] };
5562
- const stat = lstatSync(current);
5563
- const rel = relative(root, current) || ".";
5789
+ if (!existsSync3(current)) return { files: [], unsafe: [] };
5790
+ const stat = lstatSync3(current);
5791
+ const rel = relative2(root, current) || ".";
5564
5792
  if (stat.isSymbolicLink()) return { files: [], unsafe: [rel] };
5565
- if (stat.isFile()) return { files: [relative(root, current)], unsafe: [] };
5793
+ if (stat.isFile()) return { files: [relative2(root, current)], unsafe: [] };
5566
5794
  if (!stat.isDirectory()) return { files: [], unsafe: [rel] };
5567
5795
  const result2 = { files: [], unsafe: [] };
5568
- for (const name of readdirSync(current)) {
5569
- const child = inventoryFilesUnder(root, join2(current, name));
5796
+ for (const name of readdirSync2(current)) {
5797
+ const child = inventoryFilesUnder(root, join4(current, name));
5570
5798
  result2.files.push(...child.files);
5571
5799
  result2.unsafe.push(...child.unsafe);
5572
5800
  }
@@ -5578,7 +5806,7 @@ function attestBmadInstallerFiles(inventory, base, files, label) {
5578
5806
  for (const rel of files) {
5579
5807
  const expectedHash = inventory.files.get(rel);
5580
5808
  if (!expectedHash) return { safe: false, reason: `${label}/${rel} is outside the BMAD generated inventory` };
5581
- const actualHash = createHash("sha256").update(readFileSync(join2(base, rel))).digest("hex");
5809
+ const actualHash = createHash2("sha256").update(readFileSync3(join4(base, rel))).digest("hex");
5582
5810
  if (actualHash !== expectedHash) return { safe: false, reason: `${label}/${rel} was locally modified after generation` };
5583
5811
  }
5584
5812
  return { safe: true, reason: `${files.length} file(s) match BMAD installer inventory and hashes` };
@@ -5590,7 +5818,7 @@ var SUPPORTED_SKILLS_ALIASES = SUPPORTED_CLIS.map((cli) => cli.skillsRoot);
5590
5818
  var SKILL_ROOTS_QUARANTINE = ".pjangler-quarantine";
5591
5819
  function lstatOrUndefined(path) {
5592
5820
  try {
5593
- return lstatSync2(path);
5821
+ return lstatSync4(path);
5594
5822
  } catch (error) {
5595
5823
  const code = error.code;
5596
5824
  if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" || code === "ENAMETOOLONG") return void 0;
@@ -5605,17 +5833,17 @@ function realpathOrUndefined(path) {
5605
5833
  }
5606
5834
  }
5607
5835
  function within(path, root) {
5608
- const part = relative2(root, path);
5609
- return part === "" || !part.startsWith(`..${sep}`) && part !== ".." && !isAbsolute(part);
5836
+ const part = relative3(root, path);
5837
+ return part === "" || !part.startsWith(`..${sep}`) && part !== ".." && !isAbsolute2(part);
5610
5838
  }
5611
5839
  function gitToplevel(directory) {
5612
- const result2 = spawnSync2("git", ["-C", directory, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
5840
+ const result2 = spawnSync3("git", ["-C", directory, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
5613
5841
  if (result2.status !== 0) return void 0;
5614
5842
  const top = result2.stdout.trim();
5615
5843
  return top ? realpathOrUndefined(top) ?? top : void 0;
5616
5844
  }
5617
5845
  function repoOwnsPath(repoRoot, path) {
5618
- const repoReal = realpathOrUndefined(repoRoot) ?? resolve3(repoRoot);
5846
+ const repoReal = realpathOrUndefined(repoRoot) ?? resolve4(repoRoot);
5619
5847
  const real = realpathOrUndefined(path);
5620
5848
  if (!real || !within(real, repoReal)) return false;
5621
5849
  let directory = real;
@@ -5627,7 +5855,7 @@ function repoOwnsPath(repoRoot, path) {
5627
5855
  return gitToplevel(directory) === gitToplevel(repoReal);
5628
5856
  }
5629
5857
  function entryOf(path, stat) {
5630
- if (stat.isSymbolicLink()) return { kind: "link", text: readlinkSync(path), stat, phys: path };
5858
+ if (stat.isSymbolicLink()) return { kind: "link", text: readlinkSync2(path), stat, phys: path };
5631
5859
  if (stat.isDirectory()) return { kind: "dir", stat, phys: path };
5632
5860
  if (stat.isFile()) return { kind: "file", stat, phys: path };
5633
5861
  return { kind: "special", stat, phys: path };
@@ -5638,16 +5866,16 @@ var diskLayout = {
5638
5866
  return stat ? entryOf(path, stat) : void 0;
5639
5867
  },
5640
5868
  readdir(path) {
5641
- return readdirSync2(path);
5869
+ return readdirSync3(path);
5642
5870
  }
5643
5871
  };
5644
5872
  function finalLayout(spec) {
5645
5873
  const underIncoming = (path) => {
5646
5874
  if (!within(path, spec.root) || path === spec.root) return void 0;
5647
- const [name, ...rest] = relative2(spec.root, path).split(sep);
5875
+ const [name, ...rest] = relative3(spec.root, path).split(sep);
5648
5876
  const occupant = spec.incoming.get(name);
5649
5877
  if (!occupant) return void 0;
5650
- if (occupant.kind === "move") return { mapped: join3(occupant.from, ...rest) };
5878
+ if (occupant.kind === "move") return { mapped: join5(occupant.from, ...rest) };
5651
5879
  return rest.length ? void 0 : { link: occupant.text };
5652
5880
  };
5653
5881
  return {
@@ -5662,8 +5890,8 @@ function finalLayout(spec) {
5662
5890
  },
5663
5891
  readdir(path) {
5664
5892
  const mapping = underIncoming(path);
5665
- if (mapping?.mapped) return readdirSync2(mapping.mapped);
5666
- const names = new Set(lstatOrUndefined(path)?.isDirectory() ? readdirSync2(path) : []);
5893
+ if (mapping?.mapped) return readdirSync3(mapping.mapped);
5894
+ const names = new Set(lstatOrUndefined(path)?.isDirectory() ? readdirSync3(path) : []);
5667
5895
  if (path === spec.root) for (const name of spec.incoming.keys()) names.add(name);
5668
5896
  for (const created of [...spec.links.keys(), ...spec.dirs]) if (dirname3(created) === path) names.add(basename(created));
5669
5897
  return [...names].sort();
@@ -5703,7 +5931,7 @@ function walkNode(layout, path, root) {
5703
5931
  if (!entry) return { t: "special" };
5704
5932
  if (entry.kind === "link") {
5705
5933
  const text = entry.text ?? "";
5706
- return { t: "link", text, escapes: !isAbsolute(text) && !within(resolve3(dirname3(path), text), root) };
5934
+ return { t: "link", text, escapes: !isAbsolute2(text) && !within(resolve4(dirname3(path), text), root) };
5707
5935
  }
5708
5936
  if (entry.kind === "file") {
5709
5937
  const stat = entry.stat;
@@ -5711,7 +5939,7 @@ function walkNode(layout, path, root) {
5711
5939
  }
5712
5940
  if (entry.kind === "dir") {
5713
5941
  const children = /* @__PURE__ */ new Map();
5714
- for (const name of layout.readdir(path).sort()) children.set(name, walkNode(layout, join3(path, name), root));
5942
+ for (const name of layout.readdir(path).sort()) children.set(name, walkNode(layout, join5(path, name), root));
5715
5943
  return { t: "dir", children };
5716
5944
  }
5717
5945
  return { t: "special" };
@@ -5731,7 +5959,7 @@ var Hasher = class {
5731
5959
  if (this.cache.has(key)) return this.cache.get(key);
5732
5960
  const path = before ? this.translate(node.phys) : node.phys;
5733
5961
  const stat = lstatOrUndefined(path);
5734
- const digest = stat && stat.ino === node.ino && stat.dev === node.dev ? createHash2("sha256").update(readFileSync2(path)).digest("hex") : void 0;
5962
+ const digest = stat && stat.ino === node.ino && stat.dev === node.dev ? createHash3("sha256").update(readFileSync4(path)).digest("hex") : void 0;
5735
5963
  this.cache.set(key, digest);
5736
5964
  return digest;
5737
5965
  }
@@ -5809,34 +6037,34 @@ function shellQuote(value) {
5809
6037
  function describeQuarantine(quarantine, label) {
5810
6038
  let runs;
5811
6039
  try {
5812
- runs = readdirSync2(quarantine).sort();
6040
+ runs = readdirSync3(quarantine).sort();
5813
6041
  } catch {
5814
6042
  return [`${label(quarantine)} exists but cannot be read; inspect it before converting any CLI skills root`];
5815
6043
  }
5816
6044
  if (!runs.length) return [`${label(quarantine)} is an empty leftover of an earlier conversion; remove it: rmdir ${shellQuote(quarantine)}`];
5817
6045
  return runs.map((run) => {
5818
- const path = join3(quarantine, run);
6046
+ const path = join5(quarantine, run);
5819
6047
  let progress;
5820
6048
  try {
5821
- progress = readFileSync2(join3(path, "progress.log"), "utf8");
6049
+ progress = readFileSync4(join5(path, "progress.log"), "utf8");
5822
6050
  } catch {
5823
6051
  }
5824
6052
  if (progress !== void 0 && /^committed$/m.test(progress)) {
5825
6053
  return `an earlier conversion completed and was verified, but its quarantine of proven duplicates remains at ${label(path)}; remove it: rm -rf ${shellQuote(path)}`;
5826
6054
  }
5827
6055
  if (progress !== void 0 && /^rolled-back$/m.test(progress)) {
5828
- return `an earlier conversion was rolled back, but ${label(join3(path, "items"))} still holds entries nothing planned to put there; inspect them, then remove ${shellQuote(path)}`;
6056
+ return `an earlier conversion was rolled back, but ${label(join5(path, "items"))} still holds entries nothing planned to put there; inspect them, then remove ${shellQuote(path)}`;
5829
6057
  }
5830
- if (progress === void 0 || !lstatOrUndefined(join3(path, "restore.sh"))) {
6058
+ if (progress === void 0 || !lstatOrUndefined(join5(path, "restore.sh"))) {
5831
6059
  return `an interrupted conversion left ${label(path)} before its first step (no journal), so nothing was changed; remove it: rm -rf ${shellQuote(path)}`;
5832
6060
  }
5833
- return `an interrupted CLI skills-root conversion left ${label(path)}; restore the repository exactly with: sh ${shellQuote(join3(path, "restore.sh"))} (it reverses every completed step listed in journal.json, then removes the quarantine)`;
6061
+ return `an interrupted CLI skills-root conversion left ${label(path)}; restore the repository exactly with: sh ${shellQuote(join5(path, "restore.sh"))} (it reverses every completed step listed in journal.json, then removes the quarantine)`;
5834
6062
  });
5835
6063
  }
5836
6064
  function indexRows(repoReal, rel) {
5837
- const probe = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], { cwd: repoReal, encoding: "utf8" });
6065
+ const probe = spawnSync3("git", ["rev-parse", "--is-inside-work-tree"], { cwd: repoReal, encoding: "utf8" });
5838
6066
  if (probe.status !== 0 || probe.stdout.trim() !== "true") return { rows: [], worktree: false };
5839
- const listed = spawnSync2("git", ["ls-files", "-s", "-z", "--", rel], { cwd: repoReal, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
6067
+ const listed = spawnSync3("git", ["ls-files", "-s", "-z", "--", rel], { cwd: repoReal, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
5840
6068
  if (listed.status !== 0) return { rows: [], worktree: true, error: `git ls-files failed for ${rel}: ${listed.stderr.trim()}` };
5841
6069
  const rows = listed.stdout.split("\0").filter(Boolean).map((line) => {
5842
6070
  const [meta, path] = line.split(" ");
@@ -5846,19 +6074,19 @@ function indexRows(repoReal, rel) {
5846
6074
  }
5847
6075
  function entryHazard(path, label) {
5848
6076
  const walk = (current, rel) => {
5849
- const stat = lstatSync2(current);
6077
+ const stat = lstatSync4(current);
5850
6078
  if (rel && basename(current) === ".git") return `${label} contains a nested git repository (${rel}); refusing to move or drop another repository's work tree`;
5851
6079
  if (stat.isSymbolicLink()) {
5852
- const text = readlinkSync(current);
5853
- if (!isAbsolute(text) && !within(resolve3(dirname3(current), text), path)) {
6080
+ const text = readlinkSync2(current);
6081
+ if (!isAbsolute2(text) && !within(resolve4(dirname3(current), text), path)) {
5854
6082
  return `${label}/${rel} -> ${text} leaves the entry; moving or dropping it would change what that link resolves to`;
5855
6083
  }
5856
6084
  return void 0;
5857
6085
  }
5858
6086
  if (stat.isFile()) return void 0;
5859
6087
  if (stat.isDirectory()) {
5860
- for (const name of readdirSync2(current).sort()) {
5861
- const found = walk(join3(current, name), rel ? `${rel}/${name}` : name);
6088
+ for (const name of readdirSync3(current).sort()) {
6089
+ const found = walk(join5(current, name), rel ? `${rel}/${name}` : name);
5862
6090
  if (found) return found;
5863
6091
  }
5864
6092
  return void 0;
@@ -5869,25 +6097,25 @@ function entryHazard(path, label) {
5869
6097
  }
5870
6098
  function planSkillRoots(repoRoot, options = {}) {
5871
6099
  const selected = options.aliases ?? SUPPORTED_SKILLS_ALIASES;
5872
- const lexicalRoot = join3(repoRoot, ".agents", "skills");
6100
+ const lexicalRoot = join5(repoRoot, ".agents", "skills");
5873
6101
  const repoReal = realpathOrUndefined(repoRoot);
5874
6102
  const empty = (rootState2, reason) => {
5875
- const aliases2 = selected.map((alias) => ({ alias, path: join3(repoRoot, alias), state: "unsupported", operations: [], blocks: [reason], summary: `${alias}: blocked (${reason})` }));
6103
+ const aliases2 = selected.map((alias) => ({ alias, path: join5(repoRoot, alias), state: "unsupported", operations: [], blocks: [reason], summary: `${alias}: blocked (${reason})` }));
5876
6104
  return { repoRoot, root: lexicalRoot, rootState: rootState2, aliases: aliases2, operations: [], blocks: [reason], clean: false, incoming: [] };
5877
6105
  };
5878
6106
  if (!repoReal) return empty("unsafe", `${repoRoot} does not resolve; refusing to plan any CLI skills root`);
5879
- const lex = (path) => within(path, repoReal) ? join3(repoRoot, relative2(repoReal, path)) : path;
5880
- const rel = (path) => relative2(repoReal, path) || ".";
5881
- const agents = join3(repoReal, ".agents");
5882
- const R = join3(agents, "skills");
6107
+ const lex = (path) => within(path, repoReal) ? join5(repoRoot, relative3(repoReal, path)) : path;
6108
+ const rel = (path) => relative3(repoReal, path) || ".";
6109
+ const agents = join5(repoReal, ".agents");
6110
+ const R = join5(agents, "skills");
5883
6111
  const agentsStat = lstatOrUndefined(agents);
5884
6112
  if (agentsStat && (agentsStat.isSymbolicLink() || !agentsStat.isDirectory())) {
5885
- const what = agentsStat.isSymbolicLink() ? `a symlink (to ${readlinkSync(agents)})` : "not a directory";
6113
+ const what = agentsStat.isSymbolicLink() ? `a symlink (to ${readlinkSync2(agents)})` : "not a directory";
5886
6114
  return empty("unsafe", `.agents is ${what}; the activation root must be a real .agents/skills directory inside the repository, so no CLI skills root is planned`);
5887
6115
  }
5888
6116
  const rootStat = agentsStat ? lstatOrUndefined(R) : void 0;
5889
6117
  const rootState = !rootStat ? "absent" : rootStat.isSymbolicLink() ? "symlink" : rootStat.isDirectory() ? "directory" : "other";
5890
- const quarantine = join3(agents, SKILL_ROOTS_QUARANTINE);
6118
+ const quarantine = join5(agents, SKILL_ROOTS_QUARANTINE);
5891
6119
  if (lstatOrUndefined(quarantine)) {
5892
6120
  const reasons = describeQuarantine(quarantine, (path) => rel(path));
5893
6121
  const plan2 = empty(rootState, reasons[0]);
@@ -5907,15 +6135,15 @@ function planSkillRoots(repoRoot, options = {}) {
5907
6135
  const repoTop = gitToplevel(repoReal);
5908
6136
  let inventory;
5909
6137
  const inventoryOf = () => inventory ??= bmadCliProjectionInventory(repoReal);
5910
- const realAliasDirs = SUPPORTED_SKILLS_ALIASES.map((alias) => join3(repoReal, alias)).filter((path) => {
6138
+ const realAliasDirs = SUPPORTED_SKILLS_ALIASES.map((alias) => join5(repoReal, alias)).filter((path) => {
5911
6139
  const stat = lstatOrUndefined(path);
5912
6140
  return Boolean(stat?.isDirectory() && !stat.isSymbolicLink() && !lstatOrUndefined(dirname3(path))?.isSymbolicLink());
5913
6141
  });
5914
6142
  const infos = selected.map((alias) => {
5915
- const real = join3(repoReal, alias);
6143
+ const real = join5(repoReal, alias);
5916
6144
  const cliRoot = dirname3(real);
5917
6145
  const cliName = rel(cliRoot);
5918
- const info = { alias, path: join3(repoRoot, alias), real, cliRoot, cliName, state: "unsupported", cliRootExists: false, blocks: [], candidates: [] };
6146
+ const info = { alias, path: join5(repoRoot, alias), real, cliRoot, cliName, state: "unsupported", cliRootExists: false, blocks: [], candidates: [] };
5919
6147
  if (rootState === "other") {
5920
6148
  info.blocks.push(`.agents/skills is not a directory; refusing to plan ${alias}`);
5921
6149
  return info;
@@ -5939,10 +6167,10 @@ function planSkillRoots(repoRoot, options = {}) {
5939
6167
  return info;
5940
6168
  }
5941
6169
  if (stat.isSymbolicLink()) {
5942
- const text = readlinkSync(real);
6170
+ const text = readlinkSync2(real);
5943
6171
  info.text = text;
5944
6172
  const reached = realpathOrUndefined(real);
5945
- if (rootReal !== void 0 && reached === rootReal || rootState === "absent" && resolve3(cliRoot, text) === R) {
6173
+ if (rootReal !== void 0 && reached === rootReal || rootState === "absent" && resolve4(cliRoot, text) === R) {
5946
6174
  info.state = "alias";
5947
6175
  return info;
5948
6176
  }
@@ -5989,16 +6217,16 @@ function planSkillRoots(repoRoot, options = {}) {
5989
6217
  const name = row.path.slice(aliasRel.length + 1).split("/")[0];
5990
6218
  tracked.set(name, [...tracked.get(name) ?? [], row]);
5991
6219
  }
5992
- for (const name of readdirSync2(real).sort()) {
5993
- const path = join3(real, name);
6220
+ for (const name of readdirSync3(real).sort()) {
6221
+ const path = join5(real, name);
5994
6222
  const label = `${alias}/${name}`;
5995
- const entryStat = lstatSync2(path);
6223
+ const entryStat = lstatSync4(path);
5996
6224
  const rows = tracked.get(name) ?? [];
5997
6225
  const candidate = { alias, name, path, label, kind: entryStat.isSymbolicLink() ? "link" : entryStat.isDirectory() ? "directory" : "file", tracked: rows.map((row) => row.path) };
5998
6226
  if (name === ".git") {
5999
6227
  candidate.staticBlock = `${label} is git metadata; refusing to relocate another repository's work tree`;
6000
6228
  } else if (entryStat.isSymbolicLink()) {
6001
- candidate.text = readlinkSync(path);
6229
+ candidate.text = readlinkSync2(path);
6002
6230
  candidate.resolution = resolveIn(diskLayout, path);
6003
6231
  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`;
6004
6232
  } else if (!entryStat.isDirectory() && !entryStat.isFile()) {
@@ -6012,10 +6240,10 @@ function planSkillRoots(repoRoot, options = {}) {
6012
6240
  if (!candidate.staticBlock && linkRow) candidate.staticBlock = `${label} holds a tracked symlink (${linkRow.path}); a tracked link cannot be attested BMAD installer output`;
6013
6241
  if (!candidate.staticBlock && rows.length) {
6014
6242
  const paths = rows.map((row) => row.path);
6015
- const missing = paths.filter((path2) => !lstatOrUndefined(join3(repoReal, path2)));
6243
+ const missing = paths.filter((path2) => !lstatOrUndefined(join5(repoReal, path2)));
6016
6244
  if (missing.length) candidate.staticBlock = `${label} has tracked path(s) missing from the work tree (${missing[0]}); restore or commit the deletion first`;
6017
6245
  else {
6018
- const verdict = attestBmadInstallerFiles(inventoryOf(), cliRoot, paths.map((path2) => relative2(cliRoot, join3(repoReal, path2))), cliName);
6246
+ const verdict = attestBmadInstallerFiles(inventoryOf(), cliRoot, paths.map((path2) => relative3(cliRoot, join5(repoReal, path2))), cliName);
6019
6247
  if (!verdict.safe) candidate.staticBlock = `${label} is tracked in git and not attested BMAD installer output: ${verdict.reason}`;
6020
6248
  }
6021
6249
  }
@@ -6064,15 +6292,15 @@ function planSkillRoots(repoRoot, options = {}) {
6064
6292
  };
6065
6293
  const finalText = (candidate) => {
6066
6294
  const text = candidate.text;
6067
- if (isAbsolute(text)) return text;
6068
- const lexical = resolve3(dirname3(candidate.path), text);
6295
+ if (isAbsolute2(text)) return text;
6296
+ const lexical = resolve4(dirname3(candidate.path), text);
6069
6297
  const home = activeDirs.find((dir) => within(lexical, dir));
6070
- const mapped = home ? join3(R, relative2(home, lexical)) : lexical;
6071
- return relative2(R, mapped) || ".";
6298
+ const mapped = home ? join5(R, relative3(home, lexical)) : lexical;
6299
+ return relative3(R, mapped) || ".";
6072
6300
  };
6073
6301
  for (const name of [...byName.keys()].sort()) {
6074
6302
  const candidates = byName.get(name);
6075
- const counterpart = join3(R, name);
6303
+ const counterpart = join5(R, name);
6076
6304
  const counterpartLabel = `.agents/skills/${name}`;
6077
6305
  const live = [];
6078
6306
  for (const candidate of candidates) {
@@ -6087,7 +6315,7 @@ function planSkillRoots(repoRoot, options = {}) {
6087
6315
  else decide(candidate, "drop-duplicate-entry", `drop ${candidate.label} (every entry already exists byte-identical in ${where})`, extra);
6088
6316
  };
6089
6317
  if (rootEntry) {
6090
- const counterpartText = rootEntry.isSymbolicLink() ? readlinkSync(counterpart) : void 0;
6318
+ const counterpartText = rootEntry.isSymbolicLink() ? readlinkSync2(counterpart) : void 0;
6091
6319
  const rootView = beforeView(counterpart);
6092
6320
  if (rootEntry.isSymbolicLink()) {
6093
6321
  if (!rootView) {
@@ -6181,7 +6409,7 @@ function planSkillRoots(repoRoot, options = {}) {
6181
6409
  const responsible = converting.filter((info) => within(path, info.real) || [...view.hops, view.real].some((hop) => within(hop, info.real)));
6182
6410
  for (const hop of [path, ...view.hops, view.real]) {
6183
6411
  if (!within(hop, R) || hop === R) continue;
6184
- const writer = incomingByName.get(relative2(R, hop).split(sep)[0]);
6412
+ const writer = incomingByName.get(relative3(R, hop).split(sep)[0]);
6185
6413
  const info = writer && converting.find((item) => item.alias === writer.alias);
6186
6414
  if (info && !responsible.includes(info)) responsible.push(info);
6187
6415
  }
@@ -6267,10 +6495,10 @@ function planSkillRoots(repoRoot, options = {}) {
6267
6495
  const after = finalLayout(spec);
6268
6496
  for (const [name, entry] of incomingByName) {
6269
6497
  if (blocked.has(entry.alias)) continue;
6270
- const resolution = resolveIn(after, join3(R, name));
6498
+ const resolution = resolveIn(after, join5(R, name));
6271
6499
  const phys = resolution ? after.lstat(resolution.real)?.phys ?? resolution.real : void 0;
6272
- const text = entry.kind !== "link" ? void 0 : entry.incoming.kind === "link" ? entry.incoming.text : readlinkSync(entry.from);
6273
- incoming.push({ name, path: join3(lexicalRoot, name), from: lex(entry.from), kind: entry.kind, ...text === void 0 ? {} : { text }, resolves: phys ? lex(phys) : void 0 });
6500
+ const text = entry.kind !== "link" ? void 0 : entry.incoming.kind === "link" ? entry.incoming.text : readlinkSync2(entry.from);
6501
+ incoming.push({ name, path: join5(lexicalRoot, name), from: lex(entry.from), kind: entry.kind, ...text === void 0 ? {} : { text }, resolves: phys ? lex(phys) : void 0 });
6274
6502
  }
6275
6503
  }
6276
6504
  const plan = { repoRoot, root: lexicalRoot, rootState, aliases, operations, blocks, clean: blocks.length === 0, incoming };
@@ -6282,26 +6510,26 @@ function planSkillRoots(repoRoot, options = {}) {
6282
6510
  agentsExisted: Boolean(agentsStat),
6283
6511
  before,
6284
6512
  affected,
6285
- converted: converted.map((entry) => join3(repoReal, entry.alias)),
6286
- created: aliases.filter((entry) => entry.operations.some((operation) => operation.kind === "create-alias" || operation.kind === "replace-dangling-alias")).map((entry) => join3(repoReal, entry.alias))
6513
+ converted: converted.map((entry) => join5(repoReal, entry.alias)),
6514
+ created: aliases.filter((entry) => entry.operations.some((operation) => operation.kind === "create-alias" || operation.kind === "replace-dangling-alias")).map((entry) => join5(repoReal, entry.alias))
6287
6515
  });
6288
6516
  return plan;
6289
6517
  }
6290
6518
  function visibleViews(repoReal, R, view) {
6291
6519
  const views = /* @__PURE__ */ new Map();
6292
6520
  const seen = /* @__PURE__ */ new Set();
6293
- for (const location of [R, ...SUPPORTED_SKILLS_ALIASES.map((alias) => join3(repoReal, alias))]) {
6521
+ for (const location of [R, ...SUPPORTED_SKILLS_ALIASES.map((alias) => join5(repoReal, alias))]) {
6294
6522
  const resolved = realpathOrUndefined(location);
6295
6523
  if (!resolved || seen.has(resolved)) continue;
6296
6524
  seen.add(resolved);
6297
6525
  let names;
6298
6526
  try {
6299
- names = readdirSync2(location);
6527
+ names = readdirSync3(location);
6300
6528
  } catch {
6301
6529
  continue;
6302
6530
  }
6303
6531
  for (const name of names.sort()) {
6304
- const path = join3(location, name);
6532
+ const path = join5(location, name);
6305
6533
  const found = view(path);
6306
6534
  if (found) views.set(path, found);
6307
6535
  }
@@ -6317,12 +6545,12 @@ function changedPaths(operation) {
6317
6545
  return operation.from ? [operation.from, operation.path] : [operation.path];
6318
6546
  }
6319
6547
  function compileSteps(plan, facts, items) {
6320
- const physical = (path) => join3(facts.repoReal, relative2(plan.repoRoot, path));
6548
+ const physical = (path) => join5(facts.repoReal, relative3(plan.repoRoot, path));
6321
6549
  const steps = [];
6322
6550
  const park = (from, kind) => {
6323
6551
  const stat = lstatOrUndefined(from);
6324
- const text = kind === "link" && stat?.isSymbolicLink() ? readlinkSync(from) : void 0;
6325
- steps.push({ step: "rename", from, to: join3(items, `${steps.length}-${basename(from)}`), park: kind, ...text === void 0 ? {} : { text } });
6552
+ const text = kind === "link" && stat?.isSymbolicLink() ? readlinkSync2(from) : void 0;
6553
+ steps.push({ step: "rename", from, to: join5(items, `${steps.length}-${basename(from)}`), park: kind, ...text === void 0 ? {} : { text } });
6326
6554
  };
6327
6555
  for (const operation of plan.operations) {
6328
6556
  const path = physical(operation.path);
@@ -6425,15 +6653,15 @@ function runStep(step) {
6425
6653
  return;
6426
6654
  }
6427
6655
  if (lstatOrUndefined(step.to)) throw new Error(`${step.to} appeared before the move`);
6428
- if (step.park === "dir" && readdirSync2(step.from).length) throw new Error(`${step.from} is not empty (${readdirSync2(step.from)[0]})`);
6429
- if (step.park === "link" && (!lstatOrUndefined(step.from)?.isSymbolicLink() || readlinkSync(step.from) !== step.text)) throw new Error(`${step.from} is no longer the link that was planned`);
6656
+ if (step.park === "dir" && readdirSync3(step.from).length) throw new Error(`${step.from} is not empty (${readdirSync3(step.from)[0]})`);
6657
+ if (step.park === "link" && (!lstatOrUndefined(step.from)?.isSymbolicLink() || readlinkSync2(step.from) !== step.text)) throw new Error(`${step.from} is no longer the link that was planned`);
6430
6658
  renameSync(step.from, step.to);
6431
6659
  }
6432
6660
  function parkedGap(step, before, affected, hasher) {
6433
6661
  const stat = lstatOrUndefined(step.to);
6434
6662
  if (!stat) return `${step.to} is missing from the quarantine`;
6435
- if (step.park === "dir") return readdirSync2(step.to).length ? `${step.to} is no longer empty` : void 0;
6436
- if (step.park === "link") return stat.isSymbolicLink() && readlinkSync(step.to) === step.text ? void 0 : `${step.to} is no longer the planned link`;
6663
+ if (step.park === "dir") return readdirSync3(step.to).length ? `${step.to} is no longer empty` : void 0;
6664
+ if (step.park === "link") return stat.isSymbolicLink() && readlinkSync2(step.to) === step.text ? void 0 : `${step.to} is no longer the planned link`;
6437
6665
  if (!before?.node) return `${step.from} had no recorded content to compare`;
6438
6666
  const parked = viewIn(diskLayout, step.to, affected);
6439
6667
  if (!parked) return `${step.to} does not resolve`;
@@ -6447,7 +6675,7 @@ function undoStep(step) {
6447
6675
  }
6448
6676
  if (step.step === "symlink") {
6449
6677
  const stat = lstatOrUndefined(step.path);
6450
- if (!stat?.isSymbolicLink() || readlinkSync(step.path) !== step.text) throw new Error(`${step.path} is not the link this run created`);
6678
+ if (!stat?.isSymbolicLink() || readlinkSync2(step.path) !== step.text) throw new Error(`${step.path} is not the link this run created`);
6451
6679
  unlinkSync(step.path);
6452
6680
  return;
6453
6681
  }
@@ -6459,7 +6687,7 @@ function translator(steps, done) {
6459
6687
  let current = path;
6460
6688
  for (let index = 0; index < done; index++) {
6461
6689
  const step = steps[index];
6462
- if (step.step === "rename" && within(current, step.from)) current = join3(step.to, relative2(step.from, current));
6690
+ if (step.step === "rename" && within(current, step.from)) current = join5(step.to, relative3(step.from, current));
6463
6691
  }
6464
6692
  return current;
6465
6693
  };
@@ -6479,10 +6707,10 @@ function applySkillRoots(repoRoot, options) {
6479
6707
  if (!plan.operations.length) return { ok: plan.clean, changedFiles: [], details: [], blocks: plan.blocks, plan };
6480
6708
  const facts = internals.get(plan);
6481
6709
  const failed = (reason) => ({ ok: false, changedFiles: [], details: [], blocks: [...plan.blocks, reason], plan });
6482
- const agents = join3(facts.repoReal, ".agents");
6483
- const quarantine = join3(agents, SKILL_ROOTS_QUARANTINE);
6484
- const run = join3(quarantine, `${Date.now()}-${process.pid}`);
6485
- const items = join3(run, "items");
6710
+ const agents = join5(facts.repoReal, ".agents");
6711
+ const quarantine = join5(agents, SKILL_ROOTS_QUARANTINE);
6712
+ const run = join5(quarantine, `${Date.now()}-${process.pid}`);
6713
+ const items = join5(run, "items");
6486
6714
  const createdAgents = !lstatOrUndefined(agents);
6487
6715
  try {
6488
6716
  if (createdAgents) mkdirSync2(agents);
@@ -6492,12 +6720,12 @@ function applySkillRoots(repoRoot, options) {
6492
6720
  return failed(`could not create the quarantine ${quarantine}: ${error instanceof Error ? error.message : String(error)}`);
6493
6721
  }
6494
6722
  const steps = compileSteps(plan, facts, items);
6495
- const log = join3(run, "progress.log");
6723
+ const log = join5(run, "progress.log");
6496
6724
  try {
6497
- writeFileSync2(join3(run, ".gitignore"), "*\n");
6498
- writeFileSync2(join3(run, "journal.json"), `${JSON.stringify({ version: 1, repo: facts.repoReal, createdAgents, steps, operations: plan.operations.map((operation) => operation.detail) }, null, 2)}
6725
+ writeFileSync2(join5(run, ".gitignore"), "*\n");
6726
+ writeFileSync2(join5(run, "journal.json"), `${JSON.stringify({ version: 1, repo: facts.repoReal, createdAgents, steps, operations: plan.operations.map((operation) => operation.detail) }, null, 2)}
6499
6727
  `);
6500
- writeFileSync2(join3(run, "restore.sh"), restoreScript(run, quarantine, createdAgents ? agents : void 0, facts.repoReal, steps), { mode: 493 });
6728
+ writeFileSync2(join5(run, "restore.sh"), restoreScript(run, quarantine, createdAgents ? agents : void 0, facts.repoReal, steps), { mode: 493 });
6501
6729
  writeFileSync2(log, "");
6502
6730
  } catch (error) {
6503
6731
  rmSync(run, { recursive: true, force: true });
@@ -6535,11 +6763,11 @@ function applySkillRoots(repoRoot, options) {
6535
6763
  appendFileSync(log, `undone ${index}
6536
6764
  `);
6537
6765
  } catch (error) {
6538
- return failed(`${reason}; rollback stopped at step ${index} (${error instanceof Error ? error.message : String(error)}): finish it exactly with: sh ${shellQuote(join3(run, "restore.sh"))}`);
6766
+ return failed(`${reason}; rollback stopped at step ${index} (${error instanceof Error ? error.message : String(error)}): finish it exactly with: sh ${shellQuote(join5(run, "restore.sh"))}`);
6539
6767
  }
6540
6768
  }
6541
6769
  appendFileSync(log, "rolled-back\n");
6542
- if (readdirSync2(items).length) return failed(`${reason}; rolled back, but ${items} is not empty \u2014 inspect it, then remove ${run}`);
6770
+ if (readdirSync3(items).length) return failed(`${reason}; rolled back, but ${items} is not empty \u2014 inspect it, then remove ${run}`);
6543
6771
  cleanup();
6544
6772
  return failed(`rolled back: ${reason}`);
6545
6773
  };
@@ -6560,12 +6788,12 @@ function applySkillRoots(repoRoot, options) {
6560
6788
  const mismatches = [];
6561
6789
  for (const [path, view] of facts.before) {
6562
6790
  const gap = viewGap(view, viewIn(diskLayout, path, facts.affected), hasher);
6563
- if (gap) mismatches.push(`${relative2(facts.repoReal, path)}: ${gap}`);
6791
+ if (gap) mismatches.push(`${relative3(facts.repoReal, path)}: ${gap}`);
6564
6792
  }
6565
6793
  const rootReal = realpathOrUndefined(facts.R);
6566
6794
  for (const alias of [...facts.converted, ...facts.created]) {
6567
- const reaches = rootReal ? realpathOrUndefined(alias) === rootReal : resolve3(dirname3(alias), readlinkSync(alias)) === facts.R;
6568
- if (!reaches) mismatches.push(`${relative2(facts.repoReal, alias)} does not resolve to .agents/skills`);
6795
+ const reaches = rootReal ? realpathOrUndefined(alias) === rootReal : resolve4(dirname3(alias), readlinkSync2(alias)) === facts.R;
6796
+ if (!reaches) mismatches.push(`${relative3(facts.repoReal, alias)} does not resolve to .agents/skills`);
6569
6797
  }
6570
6798
  for (const step of steps) {
6571
6799
  if (step.step !== "rename" || !step.park) continue;
@@ -6575,7 +6803,7 @@ function applySkillRoots(repoRoot, options) {
6575
6803
  if (mismatches.length) return rollback(mismatches.slice(0, 5).join("; "));
6576
6804
  const tracked = plan.operations.flatMap((operation) => operation.kind === "untrack" ? operation.tracked ?? [] : []);
6577
6805
  if (tracked.length) {
6578
- const removed = spawnSync2(
6806
+ const removed = spawnSync3(
6579
6807
  "git",
6580
6808
  ["rm", "-r", "--cached", "--quiet", "--pathspec-from-file=-", "--pathspec-file-nul"],
6581
6809
  { cwd: facts.repoReal, encoding: "utf8", input: `${tracked.join("\0")}\0` }
@@ -6597,13 +6825,13 @@ function shellQuotePath(path) {
6597
6825
  var PJ_SKILLS = "pj skills";
6598
6826
  function skillCoreOptions(ctx) {
6599
6827
  return {
6600
- cwd: resolve4(ctx.repoRoot),
6601
- project: resolve4(ctx.repoRoot),
6828
+ cwd: resolve5(ctx.repoRoot),
6829
+ project: resolve5(ctx.repoRoot),
6602
6830
  scope: "project",
6603
6831
  home: ctx.homeDir,
6604
6832
  env: process.env,
6605
6833
  // Preserve the documented PJangler override while using one core resolver.
6606
- ...process.env.PJ_SKILLS_REGISTRY_ROOT?.trim() ? { registryRoot: resolve4(process.env.PJ_SKILLS_REGISTRY_ROOT.trim()) } : {}
6834
+ ...process.env.PJ_SKILLS_REGISTRY_ROOT?.trim() ? { registryRoot: resolve5(process.env.PJ_SKILLS_REGISTRY_ROOT.trim()) } : {}
6607
6835
  };
6608
6836
  }
6609
6837
  function relaySkillexCommands(text) {
@@ -6616,13 +6844,13 @@ function skillDiagnostics(findings) {
6616
6844
  );
6617
6845
  }
6618
6846
  function migrationGuidance(ctx) {
6619
- const project = `--project ${shellQuotePath(resolve4(ctx.repoRoot))}`;
6847
+ const project = `--project ${shellQuotePath(resolve5(ctx.repoRoot))}`;
6620
6848
  return `Legacy selections or skill-root content need explicit review. Preview: ${PJ_SKILLS} migrate ${project}. Apply the reviewed plan: ${PJ_SKILLS} migrate ${project} --apply (add --mapping <file> only for content the preview reports as ambiguous). PJangler relocates CLI-root skill entries losslessly into .agents/skills so each root can become the alias, but never claims ownership of foreign or installer-owned skills and never deletes content that is not a proven duplicate.`;
6621
6849
  }
6622
6850
  function projectPaths(ctx, rel) {
6623
- const paths = /* @__PURE__ */ new Set([join4(resolve4(ctx.repoRoot), rel)]);
6851
+ const paths = /* @__PURE__ */ new Set([join6(resolve5(ctx.repoRoot), rel)]);
6624
6852
  try {
6625
- paths.add(join4(realpathSync2(ctx.repoRoot), rel));
6853
+ paths.add(join6(realpathSync2(ctx.repoRoot), rel));
6626
6854
  } catch {
6627
6855
  }
6628
6856
  return paths;
@@ -6632,7 +6860,7 @@ function activationConflict(ctx, findings) {
6632
6860
  if (!finding2?.path) return void 0;
6633
6861
  const aliases = new Set(SUPPORTED_SKILLS_ALIASES.flatMap((alias) => [...projectPaths(ctx, alias)]));
6634
6862
  if (aliases.has(finding2.path)) return { kind: "alias", path: finding2.path, name: basename2(dirname4(finding2.path)) };
6635
- if (projectPaths(ctx, join4(".agents", "skills")).has(dirname4(finding2.path)) && /collides with foreign activation content/.test(finding2.message)) {
6863
+ if (projectPaths(ctx, join6(".agents", "skills")).has(dirname4(finding2.path)) && /collides with foreign activation content/.test(finding2.message)) {
6636
6864
  return { kind: "root", path: finding2.path, name: basename2(finding2.path) };
6637
6865
  }
6638
6866
  return void 0;
@@ -6647,10 +6875,10 @@ async function canonicalSkillPath(ctx, name) {
6647
6875
  return (await projectBindings(ctx)).find((binding) => binding.name === name)?.path;
6648
6876
  }
6649
6877
  async function classifyRootCollision(ctx, path) {
6650
- const label = relative3(resolve4(ctx.repoRoot), path) || path;
6878
+ const label = relative4(resolve5(ctx.repoRoot), path) || path;
6651
6879
  let stat;
6652
6880
  try {
6653
- stat = lstatSync3(path);
6881
+ stat = lstatSync5(path);
6654
6882
  } catch {
6655
6883
  return { action: "block", detail: `${label} disappeared while resolving a root collision` };
6656
6884
  }
@@ -6661,7 +6889,7 @@ async function classifyRootCollision(ctx, path) {
6661
6889
  } catch {
6662
6890
  real = void 0;
6663
6891
  }
6664
- return classifyCollidingLink(ctx, basename2(path), label, readlinkSync2(path), real);
6892
+ return classifyCollidingLink(ctx, basename2(path), label, readlinkSync3(path), real);
6665
6893
  }
6666
6894
  function realEntryCollision(label, name, directory) {
6667
6895
  return { action: "block", detail: `${label} is a real ${directory ? "directory" : "entry"} that collides with the selected catalog skill ${name}; move or rename it, pjangler never removes real content` };
@@ -6700,14 +6928,14 @@ function realpathOf(path) {
6700
6928
  }
6701
6929
  }
6702
6930
  async function predictRootCollisions(ctx, incoming) {
6703
- const root = join4(resolve4(ctx.repoRoot), ".agents", "skills");
6704
- const physicalRoot = join4(realpathOf(ctx.repoRoot) ?? resolve4(ctx.repoRoot), ".agents", "skills");
6931
+ const root = join6(resolve5(ctx.repoRoot), ".agents", "skills");
6932
+ const physicalRoot = join6(realpathOf(ctx.repoRoot) ?? resolve5(ctx.repoRoot), ".agents", "skills");
6705
6933
  const planned = new Map(incoming.map((entry) => [entry.name, entry]));
6706
6934
  const owned = await receiptOwnedLinks(ctx);
6707
6935
  const predictions = [];
6708
6936
  for (const binding of await projectBindings(ctx)) {
6709
- const path = join4(root, binding.name);
6710
- const label = relative3(resolve4(ctx.repoRoot), path);
6937
+ const path = join6(root, binding.name);
6938
+ const label = relative4(resolve5(ctx.repoRoot), path);
6711
6939
  const catalog = new Set([binding.path, realpathOf(binding.path)].filter(Boolean));
6712
6940
  const entry = planned.get(binding.name);
6713
6941
  if (entry) {
@@ -6722,12 +6950,12 @@ async function predictRootCollisions(ctx, incoming) {
6722
6950
  }
6723
6951
  let stat;
6724
6952
  try {
6725
- stat = lstatSync3(path);
6953
+ stat = lstatSync5(path);
6726
6954
  } catch {
6727
6955
  continue;
6728
6956
  }
6729
6957
  if (stat.isSymbolicLink() && catalog.has(realpathOf(path))) continue;
6730
- if (owned.has(join4(physicalRoot, binding.name)) || owned.has(path)) continue;
6958
+ if (owned.has(join6(physicalRoot, binding.name)) || owned.has(path)) continue;
6731
6959
  predictions.push({ path, decision: await classifyRootCollision(ctx, path) });
6732
6960
  }
6733
6961
  return predictions;
@@ -6795,7 +7023,7 @@ async function synchronizeProjectSkills(ctx, extra = {}) {
6795
7023
  const replaceable = predictions.filter(({ decision }) => decision.action === "unlink");
6796
7024
  if (blocking.length) {
6797
7025
  details.push(...blocking.map(({ decision }) => `blocked: ${decision.detail}`));
6798
- if (replaceable.length) details.push(`left in place until the blocked collision(s) above are resolved: ${replaceable.map(({ path }) => relative3(resolve4(ctx.repoRoot), path)).join(", ")}`);
7026
+ if (replaceable.length) details.push(`left in place until the blocked collision(s) above are resolved: ${replaceable.map(({ path }) => relative4(resolve5(ctx.repoRoot), path)).join(", ")}`);
6799
7027
  details.push(migrationGuidance(ctx));
6800
7028
  return { ok: false, changedFiles: [...new Set(changedFiles)], details };
6801
7029
  }
@@ -7539,10 +7767,10 @@ function normalizeNewlines(value) {
7539
7767
  return value.replace(/\r\n/g, "\n");
7540
7768
  }
7541
7769
  function readText(path) {
7542
- return normalizeNewlines(readFileSync3(path, "utf8"));
7770
+ return normalizeNewlines(readFileSync5(path, "utf8"));
7543
7771
  }
7544
7772
  function safeReadText2(path) {
7545
- return existsSync3(path) ? readText(path) : null;
7773
+ return existsSync4(path) ? readText(path) : null;
7546
7774
  }
7547
7775
  function ensureParent(path) {
7548
7776
  mkdirSync3(dirname5(path), { recursive: true });
@@ -7566,16 +7794,16 @@ function titleCaseSlug(slug) {
7566
7794
  return slug.split(/[-_]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
7567
7795
  }
7568
7796
  function readSymlinkTarget(path) {
7569
- if (!existsSync3(path)) return null;
7797
+ if (!existsSync4(path)) return null;
7570
7798
  try {
7571
- return readlinkSync3(path);
7799
+ return readlinkSync4(path);
7572
7800
  } catch {
7573
7801
  return null;
7574
7802
  }
7575
7803
  }
7576
7804
  function ensureSymlink(path, target, dryRun) {
7577
- if (existsSync3(path)) {
7578
- const stat = lstatSync4(path);
7805
+ if (existsSync4(path)) {
7806
+ const stat = lstatSync6(path);
7579
7807
  if (stat.isSymbolicLink()) {
7580
7808
  const current = readSymlinkTarget(path);
7581
7809
  if (current === target) return { changed: false };
@@ -7585,18 +7813,18 @@ function ensureSymlink(path, target, dryRun) {
7585
7813
  }
7586
7814
  return { changed: true };
7587
7815
  }
7588
- return { changed: false, blocked: `${relative4(process.cwd(), path) || path} exists and is not a symlink` };
7816
+ return { changed: false, blocked: `${relative5(process.cwd(), path) || path} exists and is not a symlink` };
7589
7817
  }
7590
7818
  if (!dryRun) symlinkSync2(target, path);
7591
7819
  return { changed: true };
7592
7820
  }
7593
7821
  function bootstrapAgentsFile(repoRoot, dryRun) {
7594
- const agentsPath = join5(repoRoot, "AGENTS.md");
7595
- if (existsSync3(agentsPath)) return { changedFiles: [], details: [] };
7822
+ const agentsPath = join7(repoRoot, "AGENTS.md");
7823
+ if (existsSync4(agentsPath)) return { changedFiles: [], details: [] };
7596
7824
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
7597
- const source = join5(repoRoot, file);
7598
- if (!existsSync3(source)) continue;
7599
- const stat = lstatSync4(source);
7825
+ const source = join7(repoRoot, file);
7826
+ if (!existsSync4(source)) continue;
7827
+ const stat = lstatSync6(source);
7600
7828
  if (stat.isSymbolicLink()) continue;
7601
7829
  if (stat.isFile()) {
7602
7830
  if (!dryRun) renameSync2(source, agentsPath);
@@ -7604,9 +7832,9 @@ function bootstrapAgentsFile(repoRoot, dryRun) {
7604
7832
  }
7605
7833
  return { changedFiles: [], details: [], blocked: `${file} exists but is not a regular file; cannot promote to AGENTS.md` };
7606
7834
  }
7607
- const readmePath = join5(repoRoot, "README.md");
7608
- if (existsSync3(readmePath)) {
7609
- const stat = lstatSync4(readmePath);
7835
+ const readmePath = join7(repoRoot, "README.md");
7836
+ if (existsSync4(readmePath)) {
7837
+ const stat = lstatSync6(readmePath);
7610
7838
  if (!stat.isFile()) return { changedFiles: [], details: [], blocked: "README.md exists but is not a regular file; cannot copy to AGENTS.md" };
7611
7839
  if (!dryRun) copyFileSync(readmePath, agentsPath);
7612
7840
  return { changedFiles: [agentsPath], details: ["Copied README.md to AGENTS.md before wiring agent-file symlinks"] };
@@ -7644,12 +7872,12 @@ function yamlGet(text, keyPath) {
7644
7872
  return "";
7645
7873
  }
7646
7874
  function discoverRoles(repoRoot) {
7647
- const rolesDir = join5(repoRoot, "agents", "hermes");
7648
- if (!existsSync3(rolesDir)) return [];
7649
- return readdirSync3(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
7650
- const roleDir = join5(rolesDir, entry.name);
7651
- const roleYamlPath = join5(roleDir, "role.yaml");
7652
- if (!existsSync3(roleYamlPath)) return null;
7875
+ const rolesDir = join7(repoRoot, "agents", "hermes");
7876
+ if (!existsSync4(rolesDir)) return [];
7877
+ return readdirSync4(rolesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
7878
+ const roleDir = join7(rolesDir, entry.name);
7879
+ const roleYamlPath = join7(roleDir, "role.yaml");
7880
+ if (!existsSync4(roleYamlPath)) return null;
7653
7881
  const text = readText(roleYamlPath);
7654
7882
  const runtimeRepoRaw = yamlGet(text, "runtime.github_repo");
7655
7883
  return {
@@ -7681,26 +7909,26 @@ function discoverRoles(repoRoot) {
7681
7909
  }).filter((value) => Boolean(value));
7682
7910
  }
7683
7911
  function templateScript(ctx, name) {
7684
- const source = join5(ctx.pjanglerRoot, ".mise", "scripts", name);
7685
- return existsSync3(source) ? readText(source) : void 0;
7912
+ const source = join7(ctx.pjanglerRoot, ".mise", "scripts", name);
7913
+ return existsSync4(source) ? readText(source) : void 0;
7686
7914
  }
7687
7915
  function templateVersioningScript(ctx) {
7688
7916
  return templateScript(ctx, "versioning.sh");
7689
7917
  }
7690
7918
  function templateLinkAgentfilesScript(ctx) {
7691
- const source = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
7692
- return existsSync3(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
7919
+ const source = join7(ctx.pjanglerRoot, "templates", "commonproject", "template", ".mise", "scripts", "link-agentfiles.sh");
7920
+ return existsSync4(source) ? readText(source) : templateScript(ctx, "link-agentfiles.sh");
7693
7921
  }
7694
7922
  function templateMaterializeEnvScript(ctx) {
7695
- const source = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
7696
- return existsSync3(source) ? readText(source) : void 0;
7923
+ const source = join7(ctx.pjanglerRoot, "templates", "commonproject", "template", MATERIALIZE_ENV_SCRIPT_REL);
7924
+ return existsSync4(source) ? readText(source) : void 0;
7697
7925
  }
7698
7926
  function resolveAgentHooksLayer(ctx) {
7699
7927
  const override = process.env.PJ_AGENT_HOOKS_LAYER;
7700
7928
  if (override === "0" || override === "false") return false;
7701
7929
  if (override === "1" || override === "true") return true;
7702
- if (existsSync3(join5(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
7703
- return !existsSync3(join5(ctx.homeDir, ".agents", "hooks"));
7930
+ if (existsSync4(join7(ctx.repoRoot, ".agents", "hooks", "sync.py"))) return true;
7931
+ return !existsSync4(join7(ctx.homeDir, ".agents", "hooks"));
7704
7932
  }
7705
7933
  function evaluateMiseConditionals(template, agentHooksLayer) {
7706
7934
  const out = [];
@@ -7730,22 +7958,22 @@ function renderGeneratedProjectMiseToml(ctx, template) {
7730
7958
  return evaluateMiseConditionals(template, resolveAgentHooksLayer(ctx)).replace(/\{%\s*raw\s*%\}([\s\S]*?)\{%\s*endraw\s*%\}/g, "$1").replace(/\{\{\s*project_name\s*\}\}/g, projectName);
7731
7959
  }
7732
7960
  function ensureMiseTomlFromTemplate(ctx, changedFiles) {
7733
- const targetPath = join5(ctx.repoRoot, "mise.toml");
7734
- if (existsSync3(targetPath)) return null;
7735
- const sourcePath = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
7736
- if (!existsSync3(sourcePath)) return null;
7961
+ const targetPath = join7(ctx.repoRoot, "mise.toml");
7962
+ if (existsSync4(targetPath)) return null;
7963
+ const sourcePath = join7(ctx.pjanglerRoot, "templates", "commonproject", "template", "mise.toml.jinja");
7964
+ if (!existsSync4(sourcePath)) return null;
7737
7965
  const rendered = renderGeneratedProjectMiseToml(ctx, readText(sourcePath));
7738
7966
  changedFiles.push(targetPath);
7739
7967
  if (!ctx.dryRun) writeText(targetPath, rendered);
7740
7968
  return rendered;
7741
7969
  }
7742
7970
  function templateCommonProjectText(ctx, rel) {
7743
- const path = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
7744
- return existsSync3(path) ? readText(path) : void 0;
7971
+ const path = join7(ctx.pjanglerRoot, "templates", "commonproject", "template", rel);
7972
+ return existsSync4(path) ? readText(path) : void 0;
7745
7973
  }
7746
7974
  function lstatIfPresent(path) {
7747
7975
  try {
7748
- return lstatSync4(path);
7976
+ return lstatSync6(path);
7749
7977
  } catch (error) {
7750
7978
  if (error.code === "ENOENT") return void 0;
7751
7979
  throw error;
@@ -7753,17 +7981,17 @@ function lstatIfPresent(path) {
7753
7981
  }
7754
7982
  function templateVersionFilesConf(ctx, repoRoot) {
7755
7983
  if (isObsidianPluginRepo(ctx, repoRoot)) return OBSIDIAN_VERSION_FILES_CONF;
7756
- const packageJson = join5(repoRoot, "package.json");
7757
- return existsSync3(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";
7984
+ const packageJson = join7(repoRoot, "package.json");
7985
+ return existsSync4(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";
7758
7986
  }
7759
7987
  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";
7760
7988
  function recordedProjectType(ctx) {
7761
- return record(record(readProjectJson(ctx)?.template).commonproject).project_type;
7989
+ return record2(record2(readProjectJson(ctx)?.template).commonproject).project_type;
7762
7990
  }
7763
7991
  function isObsidianPluginRepo(ctx, repoRoot) {
7764
7992
  const recorded = recordedProjectType(ctx);
7765
7993
  if (recorded !== void 0) return recorded === "obsidian-plugin";
7766
- const manifest = record(tryParseJson(safeReadText2(join5(repoRoot, "manifest.json"))));
7994
+ const manifest = record2(tryParseJson(safeReadText2(join7(repoRoot, "manifest.json"))));
7767
7995
  return typeof manifest.id === "string" && typeof manifest.minAppVersion === "string";
7768
7996
  }
7769
7997
  function unsupportedVersionTypes(conf, script) {
@@ -8226,7 +8454,7 @@ function describeRewrite(before, after) {
8226
8454
  return difference ? `migrate would still rewrite ${difference} in mise.toml` : "migrate would still re-lay out mise.toml (its meaning is unchanged)";
8227
8455
  }
8228
8456
  function readProjectJson(ctx) {
8229
- return tryParseJson(safeReadText2(join5(ctx.repoRoot, ".project.json")));
8457
+ return tryParseJson(safeReadText2(join7(ctx.repoRoot, ".project.json")));
8230
8458
  }
8231
8459
  function readDeclaredAgents(ctx) {
8232
8460
  const project = readProjectJson(ctx);
@@ -8243,8 +8471,8 @@ function readDeclaredAgents(ctx) {
8243
8471
  });
8244
8472
  }
8245
8473
  function readRoleYamlAt(roleDir) {
8246
- const roleYamlPath = join5(roleDir, "role.yaml");
8247
- if (!existsSync3(roleYamlPath)) return null;
8474
+ const roleYamlPath = join7(roleDir, "role.yaml");
8475
+ if (!existsSync4(roleYamlPath)) return null;
8248
8476
  const text = readText(roleYamlPath);
8249
8477
  return {
8250
8478
  role: yamlGet(text, "role"),
@@ -8255,7 +8483,7 @@ function readRoleYamlAt(roleDir) {
8255
8483
  }
8256
8484
  function declaredRoleIsUnprovisioned(repoRoot, roleDir) {
8257
8485
  if (!roleDir) return true;
8258
- return !existsSync3(join5(resolve5(repoRoot, roleDir), "role.yaml"));
8486
+ return !existsSync4(join7(resolve6(repoRoot, roleDir), "role.yaml"));
8259
8487
  }
8260
8488
  function validateDeclaredAgent(ctx, declared) {
8261
8489
  const details = [];
@@ -8263,8 +8491,8 @@ function validateDeclaredAgent(ctx, declared) {
8263
8491
  details.push(`agents.${declared.agentId}.role_dir missing`);
8264
8492
  return { valid: false, details };
8265
8493
  }
8266
- const roleDir = resolve5(ctx.repoRoot, declared.roleDir);
8267
- if (!existsSync3(roleDir)) {
8494
+ const roleDir = resolve6(ctx.repoRoot, declared.roleDir);
8495
+ if (!existsSync4(roleDir)) {
8268
8496
  details.push(`agents.${declared.agentId}.role_dir ${declared.roleDir} does not exist`);
8269
8497
  return { valid: false, roleDir, details };
8270
8498
  }
@@ -8280,13 +8508,13 @@ function validateDeclaredAgent(ctx, declared) {
8280
8508
  details.push(`agents.${declared.agentId} should map to agent_id ${roleYaml.agentId}`);
8281
8509
  }
8282
8510
  if (roleYaml.providerName) {
8283
- const dispatcher = join5(roleDir, ".scripts", "lib", "ticket-provider.sh");
8284
- if (!existsSync3(dispatcher)) {
8285
- details.push(`agents.${declared.agentId} provider dispatcher ${relative4(ctx.repoRoot, dispatcher)} missing`);
8511
+ const dispatcher = join7(roleDir, ".scripts", "lib", "ticket-provider.sh");
8512
+ if (!existsSync4(dispatcher)) {
8513
+ details.push(`agents.${declared.agentId} provider dispatcher ${relative5(ctx.repoRoot, dispatcher)} missing`);
8286
8514
  }
8287
- const provider = join5(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
8288
- if (!existsSync3(provider)) {
8289
- details.push(`agents.${declared.agentId} provider script ${relative4(ctx.repoRoot, provider)} missing`);
8515
+ const provider = join7(roleDir, ".scripts", "providers", `${roleYaml.providerName}.sh`);
8516
+ if (!existsSync4(provider)) {
8517
+ details.push(`agents.${declared.agentId} provider script ${relative5(ctx.repoRoot, provider)} missing`);
8290
8518
  }
8291
8519
  }
8292
8520
  return { valid: details.length === 0, role: roleYaml.role, agentId: roleYaml.agentId, roleDir, details };
@@ -8312,7 +8540,7 @@ function canonicalProjectJson(ctx) {
8312
8540
  role.agentId || `${slug}-${role.role}`,
8313
8541
  {
8314
8542
  role: role.role,
8315
- role_dir: relative4(ctx.repoRoot, role.roleDir)
8543
+ role_dir: relative5(ctx.repoRoot, role.roleDir)
8316
8544
  }
8317
8545
  ])
8318
8546
  );
@@ -8344,7 +8572,7 @@ function canonicalProjectJson(ctx) {
8344
8572
  }
8345
8573
  agents[validated.agentId] = {
8346
8574
  role: validated.role,
8347
- role_dir: relative4(ctx.repoRoot, validated.roleDir),
8575
+ role_dir: relative5(ctx.repoRoot, validated.roleDir),
8348
8576
  ...declared.extras
8349
8577
  };
8350
8578
  }
@@ -8362,12 +8590,12 @@ function canonicalProjectJson(ctx) {
8362
8590
  };
8363
8591
  }
8364
8592
  function projectJsonFinding(ctx) {
8365
- const projectPath = join5(ctx.repoRoot, ".project.json");
8366
- const planeJsonPath = join5(ctx.repoRoot, ".plane.json");
8593
+ const projectPath = join7(ctx.repoRoot, ".project.json");
8594
+ const planeJsonPath = join7(ctx.repoRoot, ".plane.json");
8367
8595
  const details = [];
8368
8596
  const data = readProjectJson(ctx);
8369
8597
  const roles = discoverRoles(ctx.repoRoot);
8370
- if (!existsSync3(projectPath)) {
8598
+ if (!existsSync4(projectPath)) {
8371
8599
  return { id: "sot.project-json", title: "Canonical .project.json", status: "fail", summary: ".project.json missing", details: [], fixable: true };
8372
8600
  }
8373
8601
  if (!data) {
@@ -8387,8 +8615,8 @@ function projectJsonFinding(ctx) {
8387
8615
  continue;
8388
8616
  }
8389
8617
  if (agent.role !== role.role) details.push(`agents.${role.agentId}.role should be ${role.role}`);
8390
- if (agent.role_dir !== relative4(ctx.repoRoot, role.roleDir)) {
8391
- details.push(`agents.${role.agentId}.role_dir should be ${relative4(ctx.repoRoot, role.roleDir)}`);
8618
+ if (agent.role_dir !== relative5(ctx.repoRoot, role.roleDir)) {
8619
+ details.push(`agents.${role.agentId}.role_dir should be ${relative5(ctx.repoRoot, role.roleDir)}`);
8392
8620
  }
8393
8621
  }
8394
8622
  const declaredAgents = readDeclaredAgents(ctx);
@@ -8416,7 +8644,7 @@ function projectJsonFinding(ctx) {
8416
8644
  if ("reconcile" in automation) {
8417
8645
  details.push("automation.reconcile is vestigial and should be removed; the heartbeat reads role.yaml, never this key");
8418
8646
  }
8419
- if (existsSync3(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
8647
+ if (existsSync4(planeJsonPath)) details.push(".plane.json should not exist once .project.json is canonical");
8420
8648
  return {
8421
8649
  id: "sot.project-json",
8422
8650
  title: "Canonical .project.json",
@@ -8427,13 +8655,14 @@ function projectJsonFinding(ctx) {
8427
8655
  };
8428
8656
  }
8429
8657
  var BMAD_NPM_PACKAGE = "bmad-method";
8430
- var BMAD_INSTALLER_VERSION = "6.12.0";
8658
+ var BMAD_INSTALLER_VERSION = "6.12.1";
8431
8659
  var BMAD_TARGET_CHANNEL = "next";
8660
+ var BMAD_STABLE_CHANNEL = "latest";
8432
8661
  var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
8433
8662
  var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
8434
8663
  var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
8435
8664
  function manifestBmadModules(repoRoot) {
8436
- const manifestPath = join5(repoRoot, "_bmad", "_config", "manifest.yaml");
8665
+ const manifestPath = join7(repoRoot, "_bmad", "_config", "manifest.yaml");
8437
8666
  const raw = safeReadText2(manifestPath);
8438
8667
  if (raw === null) return { status: "absent" };
8439
8668
  try {
@@ -8442,14 +8671,16 @@ function manifestBmadModules(repoRoot) {
8442
8671
  return { status: "invalid", error: `${manifestPath} must define a modules array` };
8443
8672
  }
8444
8673
  const declared = [];
8674
+ let hasG33 = false;
8445
8675
  for (const entry of parsed.modules) {
8446
8676
  const name = typeof entry === "string" ? entry : entry && typeof entry === "object" ? entry.name : void 0;
8447
8677
  if (typeof name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) {
8448
8678
  return { status: "invalid", error: `${manifestPath} contains an invalid module entry` };
8449
8679
  }
8450
- if (name !== "core" && name !== "custom") declared.push(name);
8680
+ if (name === "g33") hasG33 = true;
8681
+ if (name !== "core" && name !== "custom" && name !== "g33") declared.push(name);
8451
8682
  }
8452
- return { status: "valid", modules: Array.from(new Set(declared)) };
8683
+ return { status: "valid", modules: Array.from(new Set(declared)), hasG33 };
8453
8684
  } catch (error) {
8454
8685
  return {
8455
8686
  status: "invalid",
@@ -8457,24 +8688,24 @@ function manifestBmadModules(repoRoot) {
8457
8688
  };
8458
8689
  }
8459
8690
  }
8460
- function configuredBmadModules(repoRoot) {
8461
- const raw = safeReadText2(join5(repoRoot, "_bmad", "config.toml"));
8691
+ function configuredBmadModuleSelection(repoRoot) {
8692
+ const raw = safeReadText2(join7(repoRoot, "_bmad", "config.toml"));
8462
8693
  if (raw === null) return void 0;
8463
8694
  const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
8464
- return Array.from(new Set(modules));
8695
+ return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
8465
8696
  }
8466
8697
  function selectedBmadModules(repoRoot) {
8467
8698
  const manifest = manifestBmadModules(repoRoot);
8468
8699
  if (manifest.status === "valid") return manifest.modules;
8469
8700
  if (manifest.status === "invalid") throw new Error(manifest.error);
8470
- return configuredBmadModules(repoRoot) ?? [...DEFAULT_BMAD_MODULES];
8701
+ return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
8471
8702
  }
8472
8703
  function requiredBmadSentinels(repoRoot, modules = selectedBmadModules(repoRoot)) {
8473
8704
  return [
8474
- join5("core", "config.yaml"),
8475
- join5("config.toml"),
8476
- join5("_config", "manifest.yaml"),
8477
- ...modules.map((module) => join5(module, "config.yaml"))
8705
+ join7("core", "config.yaml"),
8706
+ join7("config.toml"),
8707
+ join7("_config", "manifest.yaml"),
8708
+ ...modules.map((module) => join7(module, "config.yaml"))
8478
8709
  ];
8479
8710
  }
8480
8711
  function canonicalBmadProjectName(repoRoot) {
@@ -8486,7 +8717,7 @@ function bmadProjectNameIssues(repoRoot) {
8486
8717
  const expected = canonicalBmadProjectName(repoRoot);
8487
8718
  const paths = [];
8488
8719
  const details = [];
8489
- const configToml = join5(repoRoot, "_bmad", "config.toml");
8720
+ const configToml = join7(repoRoot, "_bmad", "config.toml");
8490
8721
  const tomlText = safeReadText2(configToml);
8491
8722
  const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
8492
8723
  let tomlName;
@@ -8501,16 +8732,16 @@ function bmadProjectNameIssues(repoRoot) {
8501
8732
  paths.push(configToml);
8502
8733
  details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
8503
8734
  }
8504
- const bmadRoot = join5(repoRoot, "_bmad");
8505
- if (existsSync3(bmadRoot)) {
8735
+ const bmadRoot = join7(repoRoot, "_bmad");
8736
+ if (existsSync4(bmadRoot)) {
8506
8737
  let declared;
8507
8738
  try {
8508
8739
  declared = selectedBmadModules(repoRoot);
8509
8740
  } catch {
8510
- declared = readdirSync3(bmadRoot);
8741
+ declared = readdirSync4(bmadRoot);
8511
8742
  }
8512
8743
  for (const name of new Set(declared)) {
8513
- const configPath = join5(bmadRoot, name, "config.yaml");
8744
+ const configPath = join7(bmadRoot, name, "config.yaml");
8514
8745
  const raw = safeReadText2(configPath);
8515
8746
  if (raw === null) continue;
8516
8747
  let actual;
@@ -8530,7 +8761,7 @@ function bmadProjectNameIssues(repoRoot) {
8530
8761
  function isCurrentActivation(path, activations) {
8531
8762
  if (activations.owned.has(path)) return true;
8532
8763
  try {
8533
- if (activations.owned.has(join5(realpathSync3(dirname5(path)), basename3(path)))) return true;
8764
+ if (activations.owned.has(join7(realpathSync3(dirname5(path)), basename3(path)))) return true;
8534
8765
  return activations.targets.has(realpathSync3(path));
8535
8766
  } catch {
8536
8767
  return false;
@@ -8543,25 +8774,25 @@ async function skillActivations(ctx) {
8543
8774
  function evictLegacyBmadPackState(ctx, changedFiles, activations) {
8544
8775
  const details = [];
8545
8776
  const skillDirs = [
8546
- join5(ctx.repoRoot, ".agents", "skills"),
8547
- ...SUPPORTED_CLI_ROOTS.map((root) => join5(ctx.repoRoot, root, "skills"))
8777
+ join7(ctx.repoRoot, ".agents", "skills"),
8778
+ ...SUPPORTED_CLI_ROOTS.map((root) => join7(ctx.repoRoot, root, "skills"))
8548
8779
  ];
8549
8780
  for (const dir of skillDirs) {
8550
8781
  const dirStat = lstatIfPresent(dir);
8551
8782
  if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
8552
8783
  let names;
8553
8784
  try {
8554
- names = readdirSync3(dir);
8785
+ names = readdirSync4(dir);
8555
8786
  } catch {
8556
8787
  continue;
8557
8788
  }
8558
8789
  for (const name of names) {
8559
8790
  if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
8560
- const path = join5(dir, name);
8791
+ const path = join7(dir, name);
8561
8792
  if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
8562
8793
  if (isCurrentActivation(path, activations)) continue;
8563
8794
  changedFiles.push(path);
8564
- details.push(`removed retired BMAD pack symlink ${relative4(ctx.repoRoot, path)}`);
8795
+ details.push(`removed retired BMAD pack symlink ${relative5(ctx.repoRoot, path)}`);
8565
8796
  if (!ctx.dryRun) unlinkSync3(path);
8566
8797
  }
8567
8798
  }
@@ -8569,7 +8800,7 @@ function evictLegacyBmadPackState(ctx, changedFiles, activations) {
8569
8800
  }
8570
8801
  function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
8571
8802
  const explicit = process.env.PJ_BMAD_INSTALLER?.trim();
8572
- if (explicit) return { command: resolve5(explicit), prefixArgs: [] };
8803
+ if (explicit) return { command: resolve6(explicit), prefixArgs: [] };
8573
8804
  return {
8574
8805
  command: "npx",
8575
8806
  prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
@@ -8596,7 +8827,7 @@ function bmadInstallDisplay(repoRoot, modules = selectedBmadModules(repoRoot), v
8596
8827
  }
8597
8828
  function preflightBmadLifecycle(_ctx) {
8598
8829
  const invocation = bmadInstallerInvocation();
8599
- const probe = spawnSync3(invocation.command, [...invocation.prefixArgs, "--version"], {
8830
+ const probe = spawnSync4(invocation.command, [...invocation.prefixArgs, "--version"], {
8600
8831
  encoding: "utf8",
8601
8832
  timeout: 3e4
8602
8833
  });
@@ -8618,16 +8849,26 @@ ${probe.stderr ?? ""}`;
8618
8849
  }
8619
8850
  return { ok: true };
8620
8851
  }
8621
- function runBmadInstall(repoRoot, modules = selectedBmadModules(repoRoot), version = BMAD_INSTALLER_VERSION) {
8852
+ async function runBmadInstall(repoRoot, modules = selectedBmadModules(repoRoot), version = BMAD_INSTALLER_VERSION) {
8853
+ const installedSelection = manifestBmadModules(repoRoot);
8854
+ const preservation = g33BmadUpdateBoundary(repoRoot, installedSelection.status === "valid" && installedSelection.hasG33 || configuredBmadModuleSelection(repoRoot)?.hasG33);
8855
+ if (preservation) return { ok: false, error: preservation.summary, companion: preservation };
8622
8856
  const invocation = bmadInstallerInvocation(version);
8623
- const result2 = spawnSync3(invocation.command, [...invocation.prefixArgs, ...bmadInstallerArgs(repoRoot, modules)], { encoding: "utf8" });
8857
+ const result2 = spawnSync4(invocation.command, [...invocation.prefixArgs, ...bmadInstallerArgs(repoRoot, modules)], { encoding: "utf8" });
8624
8858
  if (result2.status !== 0) {
8625
- return { ok: false, error: result2.stderr || result2.error?.message || "Unknown error" };
8859
+ const stdoutTail = result2.stdout?.trim().split("\n").slice(-12).join("\n");
8860
+ return { ok: false, error: result2.stderr?.trim() || stdoutTail || result2.error?.message || `installer exited ${result2.status}` };
8626
8861
  }
8627
- return { ok: true };
8862
+ return { ok: true, companion: await runG33Companion(repoRoot, false, "bmad-install") };
8863
+ }
8864
+ function bmadChannelTarget(distTags) {
8865
+ const next = distTags?.[BMAD_TARGET_CHANNEL];
8866
+ const stable = distTags?.[BMAD_STABLE_CHANNEL];
8867
+ if (next && stable && compareBmadVersions(stable, next) > 0) return { version: stable, channel: BMAD_STABLE_CHANNEL };
8868
+ return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
8628
8869
  }
8629
8870
  function readInstalledBmadVersion(repoRoot) {
8630
- const raw = safeReadText2(join5(repoRoot, "_bmad", "_config", "manifest.yaml"));
8871
+ const raw = safeReadText2(join7(repoRoot, "_bmad", "_config", "manifest.yaml"));
8631
8872
  if (!raw) return void 0;
8632
8873
  try {
8633
8874
  const parsed = YAML2.parse(raw);
@@ -8638,8 +8879,8 @@ function readInstalledBmadVersion(repoRoot) {
8638
8879
  }
8639
8880
  }
8640
8881
  function bmadCachePath(homeDir) {
8641
- const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join5(homeDir, ".cache");
8642
- return join5(cacheRoot, "pjangler", "bmad-dist-tags.json");
8882
+ const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join7(homeDir, ".cache");
8883
+ return join7(cacheRoot, "pjangler", "bmad-dist-tags.json");
8643
8884
  }
8644
8885
  function readBmadDistTagsCache(homeDir) {
8645
8886
  const raw = safeReadText2(bmadCachePath(homeDir));
@@ -8654,7 +8895,7 @@ function readBmadDistTagsCache(homeDir) {
8654
8895
  return void 0;
8655
8896
  }
8656
8897
  function fetchBmadDistTags() {
8657
- const result2 = spawnSync3("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
8898
+ const result2 = spawnSync4("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
8658
8899
  encoding: "utf8",
8659
8900
  timeout: 8e3
8660
8901
  });
@@ -8806,8 +9047,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
8806
9047
  ".zencoder": "zencoder"
8807
9048
  };
8808
9049
  function unsupportedRootAttestation(repoRoot, rootName) {
8809
- const root = join5(repoRoot, rootName);
8810
- const stat = lstatSync4(root);
9050
+ const root = join7(repoRoot, rootName);
9051
+ const stat = lstatSync6(root);
8811
9052
  if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
8812
9053
  const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
8813
9054
  if (!installedBmadTools(repoRoot).has(installerTool)) {
@@ -8826,8 +9067,8 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
8826
9067
  function optionalTemplateScriptIssues(ctx) {
8827
9068
  const issues = [];
8828
9069
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
8829
- const path = join5(ctx.repoRoot, rel);
8830
- if (!existsSync3(path)) continue;
9070
+ const path = join7(ctx.repoRoot, rel);
9071
+ if (!existsSync4(path)) continue;
8831
9072
  const expected = templateCommonProjectText(ctx, rel);
8832
9073
  if (expected === void 0) continue;
8833
9074
  if (safeReadText2(path) !== expected) issues.push(`${rel} is present but has drifted from the shipped template`);
@@ -8837,8 +9078,8 @@ function optionalTemplateScriptIssues(ctx) {
8837
9078
  function refreshOptionalTemplateScripts(ctx) {
8838
9079
  const changed = [];
8839
9080
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
8840
- const path = join5(ctx.repoRoot, rel);
8841
- if (!existsSync3(path)) continue;
9081
+ const path = join7(ctx.repoRoot, rel);
9082
+ if (!existsSync4(path)) continue;
8842
9083
  const expected = templateCommonProjectText(ctx, rel);
8843
9084
  if (expected === void 0 || safeReadText2(path) === expected) continue;
8844
9085
  changed.push(path);
@@ -8855,14 +9096,14 @@ function createMiseChecks() {
8855
9096
  id: "mise.config-root",
8856
9097
  title: "mise config_root + AGENTS link hooks",
8857
9098
  audit: (ctx) => {
8858
- const misePath = join5(ctx.repoRoot, "mise.toml");
8859
- if (!existsSync3(misePath)) {
9099
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9100
+ if (!existsSync4(misePath)) {
8860
9101
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
8861
9102
  }
8862
9103
  const text = readText(misePath);
8863
9104
  const details = [];
8864
- const linkAgentfilesPath = join5(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
8865
- if (!existsSync3(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
9105
+ const linkAgentfilesPath = join7(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
9106
+ if (!existsSync4(linkAgentfilesPath)) details.push(".mise/scripts/link-agentfiles.sh missing");
8866
9107
  const toml = [];
8867
9108
  const manual = [];
8868
9109
  const parsed = parsedOrUndefined(text);
@@ -8909,10 +9150,10 @@ function createMiseChecks() {
8909
9150
  };
8910
9151
  },
8911
9152
  migrate: (ctx, finding2) => {
8912
- const path = join5(ctx.repoRoot, "mise.toml");
9153
+ const path = join7(ctx.repoRoot, "mise.toml");
8913
9154
  const changedFiles = [];
8914
9155
  const details = [];
8915
- if (!existsSync3(path)) {
9156
+ if (!existsSync4(path)) {
8916
9157
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
8917
9158
  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: [] };
8918
9159
  }
@@ -8928,7 +9169,7 @@ function createMiseChecks() {
8928
9169
  if (!ctx.dryRun) writeText(path, plan.text);
8929
9170
  text = plan.text;
8930
9171
  }
8931
- const linkAgentfilesPath = join5(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
9172
+ const linkAgentfilesPath = join7(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
8932
9173
  const expectedScript = templateLinkAgentfilesScript(ctx);
8933
9174
  if (expectedScript === void 0) {
8934
9175
  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: [] };
@@ -8966,13 +9207,13 @@ function createMiseChecks() {
8966
9207
  title: "managed mise versioning block",
8967
9208
  audit: (ctx) => {
8968
9209
  const details = [];
8969
- const misePath = join5(ctx.repoRoot, "mise.toml");
8970
- const versioningPath = join5(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
8971
- const manifestPath = join5(ctx.repoRoot, ".mise", "version-files.conf");
9210
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9211
+ const versioningPath = join7(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
9212
+ const manifestPath = join7(ctx.repoRoot, ".mise", "version-files.conf");
8972
9213
  const text = safeReadText2(misePath);
8973
9214
  if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
8974
- if (!existsSync3(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
8975
- if (!existsSync3(manifestPath)) details.push(".mise/version-files.conf missing");
9215
+ if (!existsSync4(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
9216
+ if (!existsSync4(manifestPath)) details.push(".mise/version-files.conf missing");
8976
9217
  const conf = safeReadText2(manifestPath);
8977
9218
  const script = safeReadText2(versioningPath);
8978
9219
  if (conf !== null && script !== null) {
@@ -8994,8 +9235,8 @@ function createMiseChecks() {
8994
9235
  migrate: (ctx, finding2) => {
8995
9236
  const changedFiles = [];
8996
9237
  const details = [];
8997
- const misePath = join5(ctx.repoRoot, "mise.toml");
8998
- if (!existsSync3(misePath)) {
9238
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9239
+ if (!existsSync4(misePath)) {
8999
9240
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
9000
9241
  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: [] };
9001
9242
  }
@@ -9012,7 +9253,7 @@ function createMiseChecks() {
9012
9253
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
9013
9254
  if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
9014
9255
  }
9015
- const versioningPath = join5(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
9256
+ const versioningPath = join7(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
9016
9257
  const expectedScript = templateVersioningScript(ctx);
9017
9258
  if (expectedScript === void 0) {
9018
9259
  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: [] };
@@ -9024,7 +9265,7 @@ function createMiseChecks() {
9024
9265
  chmodSync(versioningPath, 493);
9025
9266
  }
9026
9267
  }
9027
- const manifestPath = join5(ctx.repoRoot, ".mise", "version-files.conf");
9268
+ const manifestPath = join7(ctx.repoRoot, ".mise", "version-files.conf");
9028
9269
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
9029
9270
  if (safeReadText2(manifestPath) !== expectedManifest) {
9030
9271
  changedFiles.push(manifestPath);
@@ -9054,7 +9295,7 @@ function planVersioningBlock(original) {
9054
9295
  let replaced = [...VERSIONING_TASK_NAMES];
9055
9296
  try {
9056
9297
  if (oldBlock) {
9057
- replaced = Object.keys(record(record(parseToml2(oldBlock)).tasks));
9298
+ replaced = Object.keys(record2(record2(parseToml2(oldBlock)).tasks));
9058
9299
  text = text.replace(blockPattern, VERSIONING_BLOCK);
9059
9300
  } else {
9060
9301
  const statements = scanToml(text);
@@ -9077,7 +9318,7 @@ function planVersioningBlock(original) {
9077
9318
  const verdict = checkRewrite(expected, text);
9078
9319
  return verdict.ok ? { text } : { text: original, refused: verdict.reason };
9079
9320
  }
9080
- var record = (value) => isTable(value) ? value : {};
9321
+ var record2 = (value) => isTable(value) ? value : {};
9081
9322
  var RETIRED_SKILL_SCRIPTS = [
9082
9323
  SYNC_SKILLS_SCRIPT_REL,
9083
9324
  PROVISION_PACKS_SCRIPT_REL,
@@ -9086,7 +9327,7 @@ var RETIRED_SKILL_SCRIPTS = [
9086
9327
  ".mise/scripts/unlink-project-skills-from-clis.sh"
9087
9328
  ];
9088
9329
  function retiredSkillsScripts(ctx) {
9089
- return RETIRED_SKILL_SCRIPTS.map((path) => join5(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
9330
+ return RETIRED_SKILL_SCRIPTS.map((path) => join7(ctx.repoRoot, path)).filter((path) => Boolean(lstatIfPresent(path)));
9090
9331
  }
9091
9332
  function skillsWiringFindings(text) {
9092
9333
  if (text === null) return { fixable: ["mise.toml is missing; add the explicit skills:sync task"], manual: [] };
@@ -9099,26 +9340,26 @@ function skillsWiringFindings(text) {
9099
9340
  parsed = baseline.parsed;
9100
9341
  fixable.push("mise.toml repeats the close of a [hooks] array (an earlier pjangler run wrote it); migrate repairs it");
9101
9342
  }
9102
- const record2 = (value) => isTable(value) ? value : {};
9103
- const tasks = record2(parsed.tasks), task = record2(tasks[SKILLS_SYNC_TASK]);
9343
+ const record3 = (value) => isTable(value) ? value : {};
9344
+ const tasks = record3(parsed.tasks), task = record3(tasks[SKILLS_SYNC_TASK]);
9104
9345
  const run = Array.isArray(task.run) && task.run.length === 1 ? task.run[0] : task.run;
9105
9346
  if (typeof run !== "string" || !/^skillex\s+sync\s+--scope\s+project\s+--project\s+(['"])\{\{config_root\}\}\1\s*$/.test(run)) {
9106
9347
  fixable.push("skills:sync must explicitly target {{config_root}} with the Node CLI");
9107
9348
  }
9108
- const pin = record2(task.tools)["npm:@delorenj/skillex"];
9109
- const pinned = typeof pin === "string" ? pin : record2(pin).version;
9349
+ const pin = record3(task.tools)["npm:@delorenj/skillex"];
9350
+ const pinned = typeof pin === "string" ? pin : record3(pin).version;
9110
9351
  if (pinned !== SKILLEX_TOOL_VERSION) {
9111
9352
  fixable.push(`skills:sync must pin @delorenj/skillex ${SKILLEX_TOOL_VERSION} in its own tools table`);
9112
9353
  } else if (typeof pin === "string") {
9113
9354
  fixable.push(`skills:sync pins @delorenj/skillex as the plain string "${pin}", which mise 2026.9 refuses to install inside its 30-day minimumPackageAge; pin { version = "${SKILLEX_TOOL_VERSION}", allow_low_downloads = true }`);
9114
- } else if (record2(pin).allow_low_downloads !== true) {
9355
+ } else if (record3(pin).allow_low_downloads !== true) {
9115
9356
  fixable.push(`skills:sync must approve @delorenj/skillex ${SKILLEX_TOOL_VERSION} with allow_low_downloads = true (mise 2026.9 refuses to install it otherwise)`);
9116
9357
  }
9117
9358
  const retired = /sync-skills\.py|provision-(?:packs|bmad-skills)\.py|skills[:\-]provision/;
9118
9359
  const renames = new Map(RETIRED_TASK_RENAMES);
9119
9360
  const replaced = /* @__PURE__ */ new Set([...RETIRED_LINK_TASKS, LINK_AGENTFILES_TASK, SKILLS_SYNC_TASK]);
9120
9361
  for (const [name, entry] of Object.entries(tasks)) {
9121
- if (!retired.test(name) && !retired.test(JSON.stringify(record2(entry).run ?? ""))) continue;
9362
+ if (!retired.test(name) && !retired.test(JSON.stringify(record3(entry).run ?? ""))) continue;
9122
9363
  if (replaced.has(renames.get(name) ?? name)) {
9123
9364
  if (!fixable.includes("Retire Python skill sync/provision tasks from mise.toml")) fixable.push("Retire Python skill sync/provision tasks from mise.toml");
9124
9365
  } else {
@@ -9146,7 +9387,7 @@ function createAgentHooksChecks() {
9146
9387
  title: "Skillex project skills",
9147
9388
  audit: async (ctx) => {
9148
9389
  const finding2 = await auditProjectSkills(ctx);
9149
- const mise = safeReadText2(join5(ctx.repoRoot, "mise.toml"));
9390
+ const mise = safeReadText2(join7(ctx.repoRoot, "mise.toml"));
9150
9391
  const wiring = skillsWiringIssues(mise);
9151
9392
  const retired = retiredSkillsScripts(ctx);
9152
9393
  const unsafe = retired.filter((path) => lstatIfPresent(path)?.isDirectory());
@@ -9163,7 +9404,7 @@ function createAgentHooksChecks() {
9163
9404
  const changedFiles = [];
9164
9405
  const details = [];
9165
9406
  const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
9166
- let currentMise = safeReadText2(join5(ctx.repoRoot, "mise.toml"));
9407
+ let currentMise = safeReadText2(join7(ctx.repoRoot, "mise.toml"));
9167
9408
  if (currentMise === null) {
9168
9409
  const template = templateCommonProjectText(ctx, "mise.toml.jinja");
9169
9410
  if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
@@ -9180,7 +9421,7 @@ function createAgentHooksChecks() {
9180
9421
  changedFiles.push(path);
9181
9422
  if (!ctx.dryRun) unlinkSync3(path);
9182
9423
  }
9183
- const misePath = join5(ctx.repoRoot, "mise.toml");
9424
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9184
9425
  const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
9185
9426
  if (safeReadText2(misePath) !== nextMise) {
9186
9427
  changedFiles.push(misePath);
@@ -9206,9 +9447,9 @@ function createAgentHooksChecks() {
9206
9447
  id: "sot.agent-symlinks",
9207
9448
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
9208
9449
  audit: (ctx) => {
9209
- const agentsPath = join5(ctx.repoRoot, "AGENTS.md");
9210
- if (!existsSync3(agentsPath)) {
9211
- const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync3(join5(ctx.repoRoot, file)));
9450
+ const agentsPath = join7(ctx.repoRoot, "AGENTS.md");
9451
+ if (!existsSync4(agentsPath)) {
9452
+ const fallbackSources = ["CLAUDE.md", "GEMINI.md", "README.md"].filter((file) => existsSync4(join7(ctx.repoRoot, file)));
9212
9453
  if (fallbackSources.length === 0) {
9213
9454
  return { id: "sot.agent-symlinks", title: "AGENTS/CLAUDE/GEMINI symlink contract", status: "skip", summary: "AGENTS.md missing; symlink contract not applicable", details: [], fixable: false };
9214
9455
  }
@@ -9223,7 +9464,7 @@ function createAgentHooksChecks() {
9223
9464
  }
9224
9465
  const details = [];
9225
9466
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
9226
- const full = join5(ctx.repoRoot, file);
9467
+ const full = join7(ctx.repoRoot, file);
9227
9468
  const target = readSymlinkTarget(full);
9228
9469
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
9229
9470
  }
@@ -9247,7 +9488,7 @@ function createAgentHooksChecks() {
9247
9488
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
9248
9489
  }
9249
9490
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
9250
- const full = join5(ctx.repoRoot, file);
9491
+ const full = join7(ctx.repoRoot, file);
9251
9492
  const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
9252
9493
  if (result2.blocked) blockedDetails.push(result2.blocked);
9253
9494
  if (result2.changed) changedFiles.push(full);
@@ -9274,7 +9515,7 @@ function createProjectJsonChecks() {
9274
9515
  const changedFiles = [];
9275
9516
  const blockedDetails = [];
9276
9517
  const droppedDetails = [];
9277
- const path = join5(ctx.repoRoot, ".project.json");
9518
+ const path = join7(ctx.repoRoot, ".project.json");
9278
9519
  const existing = readProjectJson(ctx) ?? {};
9279
9520
  const canonical = canonicalProjectJson(ctx);
9280
9521
  const preservedDetails = [];
@@ -9306,11 +9547,11 @@ function createProjectJsonChecks() {
9306
9547
  changedFiles.push(path);
9307
9548
  if (!ctx.dryRun) writeText(path, expected);
9308
9549
  }
9309
- const planeJson = join5(ctx.repoRoot, ".plane.json");
9310
- if (existsSync3(planeJson)) {
9550
+ const planeJson = join7(ctx.repoRoot, ".plane.json");
9551
+ if (existsSync4(planeJson)) {
9311
9552
  const backup = `${planeJson}.migrated-backup`;
9312
- if (existsSync3(backup)) {
9313
- blockedDetails.push(`cannot back up .plane.json because ${relative4(ctx.repoRoot, backup)} already exists`);
9553
+ if (existsSync4(backup)) {
9554
+ blockedDetails.push(`cannot back up .plane.json because ${relative5(ctx.repoRoot, backup)} already exists`);
9314
9555
  } else {
9315
9556
  changedFiles.push(backup);
9316
9557
  if (!ctx.dryRun) renameSync2(planeJson, backup);
@@ -9338,10 +9579,10 @@ function createMiseOpInjectChecks() {
9338
9579
  const details = [];
9339
9580
  let envOpNeedsHands = false;
9340
9581
  const optedOut = envMaterializationOptedOut(ctx);
9341
- const envOpPath = join5(ctx.repoRoot, ".env.op");
9342
- const envOpExists = existsSync3(envOpPath);
9582
+ const envOpPath = join7(ctx.repoRoot, ".env.op");
9583
+ const envOpExists = existsSync4(envOpPath);
9343
9584
  const envOp = envOpExists ? readText(envOpPath) : void 0;
9344
- const gitignore = safeReadText2(join5(ctx.repoRoot, ".gitignore"));
9585
+ const gitignore = safeReadText2(join7(ctx.repoRoot, ".gitignore"));
9345
9586
  if (!envOpExists) {
9346
9587
  details.push(".env.op missing");
9347
9588
  } else if (!envOp?.trim()) {
@@ -9361,7 +9602,7 @@ function createMiseOpInjectChecks() {
9361
9602
  if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
9362
9603
  if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
9363
9604
  if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
9364
- const misePath = join5(ctx.repoRoot, "mise.toml");
9605
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9365
9606
  const miseText = safeReadText2(misePath);
9366
9607
  if (!miseText) {
9367
9608
  details.push("mise.toml missing for .env materialization hook");
@@ -9384,15 +9625,15 @@ function createMiseOpInjectChecks() {
9384
9625
  else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
9385
9626
  details.push(...mise);
9386
9627
  }
9387
- const materializePath = join5(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9628
+ const materializePath = join7(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9388
9629
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
9389
9630
  if (optedOut) {
9390
- if (existsSync3(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
9631
+ if (existsSync4(materializePath)) details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is present, but .project.json declares secrets.materialize_env: false`);
9391
9632
  } else if (!expectedMaterializer) {
9392
9633
  details.push("pjangler package is missing the managed materialize-env.sh source");
9393
9634
  } else if (safeReadText2(materializePath) !== expectedMaterializer) {
9394
9635
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
9395
- } else if ((lstatSync4(materializePath).mode & 73) === 0) {
9636
+ } else if ((lstatSync6(materializePath).mode & 73) === 0) {
9396
9637
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
9397
9638
  }
9398
9639
  return {
@@ -9408,13 +9649,13 @@ function createMiseOpInjectChecks() {
9408
9649
  const changedFiles = [];
9409
9650
  const details = [];
9410
9651
  let envOpNeedsHands = false;
9411
- const envOpPath = join5(ctx.repoRoot, ".env.op");
9412
- const canonicalEnvOpPath = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
9413
- if (!existsSync3(canonicalEnvOpPath)) {
9652
+ const envOpPath = join7(ctx.repoRoot, ".env.op");
9653
+ const canonicalEnvOpPath = join7(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
9654
+ if (!existsSync4(canonicalEnvOpPath)) {
9414
9655
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
9415
9656
  }
9416
9657
  const canonicalEnvOp = readText(canonicalEnvOpPath);
9417
- if (!existsSync3(envOpPath) || !readText(envOpPath).trim()) {
9658
+ if (!existsSync4(envOpPath) || !readText(envOpPath).trim()) {
9418
9659
  changedFiles.push(envOpPath);
9419
9660
  if (!ctx.dryRun) writeText(envOpPath, canonicalEnvOp);
9420
9661
  } else {
@@ -9438,7 +9679,7 @@ function createMiseOpInjectChecks() {
9438
9679
  }
9439
9680
  }
9440
9681
  }
9441
- const gitignorePath = join5(ctx.repoRoot, ".gitignore");
9682
+ const gitignorePath = join7(ctx.repoRoot, ".gitignore");
9442
9683
  const gitignore = safeReadText2(gitignorePath) ?? "";
9443
9684
  const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
9444
9685
  # staged through a mktemp file and moved into place only on success.
@@ -9451,7 +9692,7 @@ function createMiseOpInjectChecks() {
9451
9692
  changedFiles.push(gitignorePath);
9452
9693
  if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
9453
9694
  }
9454
- const misePath = join5(ctx.repoRoot, "mise.toml");
9695
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9455
9696
  let currentMise = safeReadText2(misePath);
9456
9697
  if (currentMise === null) {
9457
9698
  const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
@@ -9468,16 +9709,16 @@ function createMiseOpInjectChecks() {
9468
9709
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
9469
9710
  if (!ctx.dryRun) writeText(misePath, opPlan.text);
9470
9711
  }
9471
- const materializePath = join5(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9712
+ const materializePath = join7(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9472
9713
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
9473
9714
  if (optedOut) {
9474
- if (existsSync3(materializePath)) {
9715
+ if (existsSync4(materializePath)) {
9475
9716
  changedFiles.push(materializePath);
9476
9717
  if (!ctx.dryRun) rmSync2(materializePath);
9477
9718
  }
9478
9719
  } else if (!expectedMaterializer) {
9479
9720
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
9480
- } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync3(materializePath) && (lstatSync4(materializePath).mode & 73) === 0) {
9721
+ } else if (safeReadText2(materializePath) !== expectedMaterializer || existsSync4(materializePath) && (lstatSync6(materializePath).mode & 73) === 0) {
9481
9722
  changedFiles.push(materializePath);
9482
9723
  if (!ctx.dryRun) {
9483
9724
  writeText(materializePath, expectedMaterializer);
@@ -9539,7 +9780,7 @@ function createProjectProvenanceChecks() {
9539
9780
  title: ".copier-answers.yml provenance + drift report",
9540
9781
  audit: (ctx) => {
9541
9782
  const details = [];
9542
- const path = join5(ctx.repoRoot, ".copier-answers.yml");
9783
+ const path = join7(ctx.repoRoot, ".copier-answers.yml");
9543
9784
  const text = safeReadText2(path);
9544
9785
  const project = readProjectJson(ctx);
9545
9786
  if (!text) {
@@ -9567,11 +9808,11 @@ function createProjectProvenanceChecks() {
9567
9808
  migrate: (ctx, finding2) => {
9568
9809
  const changedFiles = [];
9569
9810
  const project = canonicalProjectJson(ctx);
9570
- const path = join5(ctx.repoRoot, ".copier-answers.yml");
9811
+ const path = join7(ctx.repoRoot, ".copier-answers.yml");
9571
9812
  const current = safeReadText2(path);
9572
9813
  const existing = current ? YAML2.parseDocument(current) : void 0;
9573
9814
  const document = existing && !existing.errors.length && YAML2.isMap(existing.contents) ? existing : new YAML2.Document({});
9574
- document.set("_src_path", join5(ctx.pjanglerRoot, "templates", "commonproject"));
9815
+ document.set("_src_path", join7(ctx.pjanglerRoot, "templates", "commonproject"));
9575
9816
  document.set("project_description", String(project.project_description));
9576
9817
  document.set("project_name", String(project.project_name));
9577
9818
  document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
@@ -9596,9 +9837,9 @@ ${body}`;
9596
9837
  }
9597
9838
  function supportedCliProjectionIssues(repoRoot, plan) {
9598
9839
  const issues = [];
9599
- const managedSkills = join5(repoRoot, ".agents", "skills");
9840
+ const managedSkills = join7(repoRoot, ".agents", "skills");
9600
9841
  for (const rootName of SUPPORTED_CLI_ROOTS) {
9601
- const root = join5(repoRoot, rootName);
9842
+ const root = join7(repoRoot, rootName);
9602
9843
  const rootStat = lstatIfPresent(root);
9603
9844
  if (!rootStat) {
9604
9845
  issues.push(`${rootName} missing`);
@@ -9608,7 +9849,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9608
9849
  issues.push(`${rootName} must be a real configuration directory`);
9609
9850
  continue;
9610
9851
  }
9611
- const skills = join5(root, "skills");
9852
+ const skills = join7(root, "skills");
9612
9853
  const skillsStat = lstatIfPresent(skills);
9613
9854
  if (!skillsStat) {
9614
9855
  issues.push(`${rootName}/skills missing`);
@@ -9617,7 +9858,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9617
9858
  let projectedSkills = skills;
9618
9859
  if (skillsStat.isSymbolicLink()) {
9619
9860
  try {
9620
- readlinkSync3(skills);
9861
+ readlinkSync4(skills);
9621
9862
  } catch {
9622
9863
  issues.push(`${rootName}/skills is an unreadable symlink`);
9623
9864
  continue;
@@ -9645,9 +9886,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9645
9886
  issues.push(`${rootName}/skills is not a directory`);
9646
9887
  continue;
9647
9888
  }
9648
- const hasGeneratedSkill = existsSync3(projectedSkills) && readdirSync3(projectedSkills).some((name) => {
9649
- const skillFile = join5(projectedSkills, name, "SKILL.md");
9650
- return existsSync3(skillFile) && lstatSync4(skillFile).isFile();
9889
+ const hasGeneratedSkill = existsSync4(projectedSkills) && readdirSync4(projectedSkills).some((name) => {
9890
+ const skillFile = join7(projectedSkills, name, "SKILL.md");
9891
+ return existsSync4(skillFile) && lstatSync6(skillFile).isFile();
9651
9892
  });
9652
9893
  if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
9653
9894
  }
@@ -9673,7 +9914,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
9673
9914
  "# some CLIs get a real directory here and some get a symlink."
9674
9915
  ]);
9675
9916
  function supportedCliGitignoreIssues(repoRoot) {
9676
- const lines = (safeReadText2(join5(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
9917
+ const lines = (safeReadText2(join7(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
9677
9918
  const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
9678
9919
  return [
9679
9920
  ...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
@@ -9681,7 +9922,7 @@ function supportedCliGitignoreIssues(repoRoot) {
9681
9922
  ];
9682
9923
  }
9683
9924
  function ensureSupportedCliGitignore(ctx) {
9684
- const path = join5(ctx.repoRoot, ".gitignore");
9925
+ const path = join7(ctx.repoRoot, ".gitignore");
9685
9926
  const current = safeReadText2(path) ?? "";
9686
9927
  const kept = current.split(/\r?\n/).filter((line) => {
9687
9928
  const trimmed2 = line.trim();
@@ -9719,10 +9960,10 @@ function createBmadChecks() {
9719
9960
  fixable: false
9720
9961
  };
9721
9962
  }
9722
- const targetRoot = join5(ctx.repoRoot, "_bmad");
9723
- const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModules(ctx.repoRoot) ?? [...DEFAULT_BMAD_MODULES];
9963
+ const targetRoot = join7(ctx.repoRoot, "_bmad");
9964
+ const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
9724
9965
  const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
9725
- const missing = sentinels.filter((file) => !existsSync3(join5(targetRoot, file)));
9966
+ const missing = sentinels.filter((file) => !existsSync4(join7(targetRoot, file)));
9726
9967
  const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
9727
9968
  const details = [
9728
9969
  ...missing.map((file) => `_bmad/${file}`),
@@ -9750,10 +9991,21 @@ function createBmadChecks() {
9750
9991
  details: [manifestSelection.error]
9751
9992
  };
9752
9993
  }
9753
- const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModules(ctx.repoRoot) ?? [...DEFAULT_BMAD_MODULES];
9994
+ const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
9995
+ const preservation = g33BmadUpdateBoundary(ctx.repoRoot, manifestSelection.status === "valid" && manifestSelection.hasG33 || configuredBmadModuleSelection(ctx.repoRoot)?.hasG33);
9996
+ if (preservation) return {
9997
+ id: finding2.id,
9998
+ title: finding2.title,
9999
+ status: "blocked",
10000
+ summary: preservation.summary,
10001
+ changedFiles: [],
10002
+ details: companionDetails(preservation),
10003
+ bmadCompanion: preservation
10004
+ };
9754
10005
  if (ctx.dryRun) {
9755
10006
  const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
9756
- changedFiles.push(...sentinels.map((file) => join5(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync3(path)));
10007
+ const companion = await runG33Companion(ctx.repoRoot, true, "bmad-install");
10008
+ changedFiles.push(...sentinels.map((file) => join7(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync4(path)));
9757
10009
  changedFiles.push(...bmadProjectNameIssues(ctx.repoRoot).paths);
9758
10010
  return {
9759
10011
  id: finding2.id,
@@ -9762,16 +10014,18 @@ function createBmadChecks() {
9762
10014
  summary: changedFiles.length ? "Would run non-interactive bmad-method install" : "No changes required",
9763
10015
  changedFiles,
9764
10016
  details: [
9765
- `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules)}`
9766
- ]
10017
+ `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules)}`,
10018
+ ...companionDetails(companion)
10019
+ ],
10020
+ bmadCompanion: companion
9767
10021
  };
9768
10022
  }
9769
10023
  const expectedChangedPaths = [
9770
- ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join5(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync3(path)),
10024
+ ...requiredBmadSentinels(ctx.repoRoot, selectedModules).map((file) => join7(ctx.repoRoot, "_bmad", file)).filter((path) => !existsSync4(path)),
9771
10025
  ...bmadProjectNameIssues(ctx.repoRoot).paths
9772
10026
  ];
9773
10027
  const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
9774
- const install = runBmadInstall(ctx.repoRoot, selectedModules);
10028
+ const install = await runBmadInstall(ctx.repoRoot, selectedModules);
9775
10029
  if (!install.ok) {
9776
10030
  return {
9777
10031
  id: finding2.id,
@@ -9782,14 +10036,15 @@ function createBmadChecks() {
9782
10036
  details: [...evicted, install.error ?? "Unknown error"]
9783
10037
  };
9784
10038
  }
9785
- changedFiles.push(...expectedChangedPaths.filter(existsSync3));
10039
+ changedFiles.push(...expectedChangedPaths.filter(existsSync4), ...install.companion?.changedFiles ?? []);
9786
10040
  return {
9787
10041
  id: finding2.id,
9788
10042
  title: finding2.title,
9789
10043
  status: changedFiles.length ? "applied" : "noop",
9790
10044
  summary: changedFiles.length ? "Installed BMAD scaffold via bmad-method" : "No changes required",
9791
10045
  changedFiles,
9792
- details: evicted
10046
+ details: [...evicted, ...install.companion ? companionDetails(install.companion) : []],
10047
+ bmadCompanion: install.companion
9793
10048
  };
9794
10049
  }
9795
10050
  },
@@ -9803,14 +10058,16 @@ function createBmadChecks() {
9803
10058
  id: "bmad.version",
9804
10059
  title: "BMAD version currency",
9805
10060
  status: "skip",
9806
- summary: existsSync3(join5(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
10061
+ summary: existsSync4(join7(ctx.repoRoot, "_bmad")) ? "BMAD installed but version manifest unreadable" : "No BMAD install present",
9807
10062
  details: [],
9808
10063
  fixable: false
9809
10064
  };
9810
10065
  }
9811
10066
  const pinned = ctx.bmadVersionPin?.trim();
9812
10067
  const resolved = pinned ? void 0 : resolveBmadDistTags(ctx.homeDir);
9813
- const available = pinned ?? resolved?.distTags?.[BMAD_TARGET_CHANNEL];
10068
+ const channelTarget = pinned ? void 0 : bmadChannelTarget(resolved?.distTags);
10069
+ const available = pinned ?? channelTarget?.version;
10070
+ const channel = channelTarget?.channel ?? BMAD_TARGET_CHANNEL;
9814
10071
  if (!available) {
9815
10072
  return {
9816
10073
  id: "bmad.version",
@@ -9821,7 +10078,7 @@ function createBmadChecks() {
9821
10078
  fixable: false
9822
10079
  };
9823
10080
  }
9824
- const targetLabel = pinned ? `pinned ${available}` : `${BMAD_TARGET_CHANNEL} ${available}`;
10081
+ const targetLabel = pinned ? `pinned ${available}` : `${channel} ${available}`;
9825
10082
  const staleNote = resolved?.stale ? ` ${glyph.dot} cached` : "";
9826
10083
  const comparison = compareBmadVersions(installed, available);
9827
10084
  if (pinned ? comparison === 0 : comparison >= 0) {
@@ -9842,7 +10099,7 @@ function createBmadChecks() {
9842
10099
  title: "BMAD version currency",
9843
10100
  status: "skip",
9844
10101
  summary: `BMAD ${installed} installed; stable currency floor unknown (${BMAD_NPM_PACKAGE} latest unresolved)`,
9845
- details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL})`],
10102
+ details: [`installed: ${installed}`, `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`],
9846
10103
  fixable: false
9847
10104
  };
9848
10105
  }
@@ -9855,7 +10112,7 @@ function createBmadChecks() {
9855
10112
  summary: `BMAD ${installed} is at or ahead of stable ${stable ?? "unknown"}; ${targetLabel} available${staleNote}`,
9856
10113
  details: [
9857
10114
  `installed: ${installed}`,
9858
- `available: ${available} (${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL})`,
10115
+ `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
9859
10116
  stable ? `stable latest: ${stable}` : "",
9860
10117
  "run `pj migrate bmad.version` to take the prerelease"
9861
10118
  ].filter(Boolean),
@@ -9869,7 +10126,7 @@ function createBmadChecks() {
9869
10126
  summary: pinnedMismatch ? `BMAD ${installed} does not match ${targetLabel}` : `BMAD ${installed} is behind stable ${stable} \u2014 upgrade available`,
9870
10127
  details: [
9871
10128
  `installed: ${installed}`,
9872
- pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${BMAD_TARGET_CHANNEL})`,
10129
+ pinned ? `required transaction pin: ${available}` : `available: ${available} (${BMAD_NPM_PACKAGE}@${channel})`,
9873
10130
  !pinned && stable ? `stable latest: ${stable}` : "",
9874
10131
  "run `pj migrate bmad.version` to upgrade"
9875
10132
  ].filter(Boolean),
@@ -9882,14 +10139,14 @@ function createBmadChecks() {
9882
10139
  }
9883
10140
  {
9884
10141
  const current = readInstalledBmadVersion(ctx.repoRoot);
9885
- const target = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.[BMAD_TARGET_CHANNEL];
10142
+ const target = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
9886
10143
  if (!current || !target || compareBmadVersions(current, target) === 0) {
9887
10144
  return { id: finding2.id, title: finding2.title, status: "noop", summary: "BMAD already current", changedFiles: [], details: [] };
9888
10145
  }
9889
10146
  }
9890
10147
  const installed = readInstalledBmadVersion(ctx.repoRoot);
9891
- const available = ctx.bmadVersionPin?.trim() ?? resolveBmadDistTags(ctx.homeDir)?.distTags?.[BMAD_TARGET_CHANNEL];
9892
- const manifestPath = join5(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
10148
+ const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
10149
+ const manifestPath = join7(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
9893
10150
  const manifestSelection = manifestBmadModules(ctx.repoRoot);
9894
10151
  if (manifestSelection.status === "invalid") {
9895
10152
  return {
@@ -9901,8 +10158,19 @@ function createBmadChecks() {
9901
10158
  details: [manifestSelection.error]
9902
10159
  };
9903
10160
  }
9904
- const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModules(ctx.repoRoot) ?? [...DEFAULT_BMAD_MODULES];
10161
+ const selectedModules = manifestSelection.status === "valid" ? manifestSelection.modules : configuredBmadModuleSelection(ctx.repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
10162
+ const preservation = g33BmadUpdateBoundary(ctx.repoRoot, manifestSelection.status === "valid" && manifestSelection.hasG33 || configuredBmadModuleSelection(ctx.repoRoot)?.hasG33);
10163
+ if (preservation) return {
10164
+ id: finding2.id,
10165
+ title: finding2.title,
10166
+ status: "blocked",
10167
+ summary: preservation.summary,
10168
+ changedFiles: [],
10169
+ details: companionDetails(preservation),
10170
+ bmadCompanion: preservation
10171
+ };
9905
10172
  if (ctx.dryRun) {
10173
+ const companion = await runG33Companion(ctx.repoRoot, true, "bmad-install");
9906
10174
  return {
9907
10175
  id: finding2.id,
9908
10176
  title: finding2.title,
@@ -9910,11 +10178,13 @@ function createBmadChecks() {
9910
10178
  summary: `Would upgrade BMAD ${installed ?? "?"} -> ${available ?? BMAD_TARGET_CHANNEL}`,
9911
10179
  changedFiles: [manifestPath],
9912
10180
  details: [
9913
- `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL)}`
9914
- ]
10181
+ `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL)}`,
10182
+ ...companionDetails(companion)
10183
+ ],
10184
+ bmadCompanion: companion
9915
10185
  };
9916
10186
  }
9917
- const install = runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL);
10187
+ const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL);
9918
10188
  if (!install.ok) {
9919
10189
  return {
9920
10190
  id: finding2.id,
@@ -9931,7 +10201,8 @@ function createBmadChecks() {
9931
10201
  const upgraded = Boolean(nowInstalled && installed && compareBmadVersions(nowInstalled, installed) > 0);
9932
10202
  const changed = Array.from(/* @__PURE__ */ new Set([
9933
10203
  ...upgraded ? [manifestPath] : [],
9934
- ...evictedChanges
10204
+ ...evictedChanges,
10205
+ ...install.companion?.changedFiles ?? []
9935
10206
  ]));
9936
10207
  return {
9937
10208
  id: finding2.id,
@@ -9939,7 +10210,8 @@ function createBmadChecks() {
9939
10210
  status: changed.length ? "applied" : "noop",
9940
10211
  summary: upgraded ? `Upgraded BMAD ${installed} -> ${nowInstalled}` : `BMAD reinstalled (${nowInstalled ?? "?"})`,
9941
10212
  changedFiles: changed,
9942
- details: evicted
10213
+ details: [...evicted, ...install.companion ? companionDetails(install.companion) : []],
10214
+ bmadCompanion: install.companion
9943
10215
  };
9944
10216
  }
9945
10217
  },
@@ -9948,7 +10220,7 @@ function createBmadChecks() {
9948
10220
  title: "Supported BMAD CLI projection roots",
9949
10221
  audit: (ctx) => {
9950
10222
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
9951
- const present = unsupportedNames.filter((name) => existsSync3(join5(ctx.repoRoot, name)));
10223
+ const present = unsupportedNames.filter((name) => existsSync4(join7(ctx.repoRoot, name)));
9952
10224
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
9953
10225
  const plan = planSkillRoots(ctx.repoRoot);
9954
10226
  const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
@@ -9970,7 +10242,7 @@ function createBmadChecks() {
9970
10242
  },
9971
10243
  migrate: (ctx, finding2) => {
9972
10244
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
9973
- const present = unsupportedNames.filter((name) => existsSync3(join5(ctx.repoRoot, name)));
10245
+ const present = unsupportedNames.filter((name) => existsSync4(join7(ctx.repoRoot, name)));
9974
10246
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
9975
10247
  const blocked = attestations.filter((entry) => !entry.safe);
9976
10248
  if (blocked.length) {
@@ -9995,7 +10267,7 @@ function createBmadChecks() {
9995
10267
  };
9996
10268
  }
9997
10269
  const gitignoreChanges = ensureSupportedCliGitignore(ctx);
9998
- const removedRoots = attestations.map((entry) => join5(ctx.repoRoot, entry.name));
10270
+ const removedRoots = attestations.map((entry) => join7(ctx.repoRoot, entry.name));
9999
10271
  if (!ctx.dryRun) for (const path of removedRoots) rmSync2(path, { recursive: true, force: true });
10000
10272
  const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
10001
10273
  return {
@@ -10054,11 +10326,11 @@ function linkedPlaneBoard(ctx) {
10054
10326
  return { boardId, workspace };
10055
10327
  }
10056
10328
  function runPx(args, ctx) {
10057
- const probe = spawnSync3("which", ["px"], { encoding: "utf8" });
10329
+ const probe = spawnSync4("which", ["px"], { encoding: "utf8" });
10058
10330
  if (probe.status !== 0) {
10059
10331
  return { error: "px not found on PATH \u2014 install Pilot to manage board schemas" };
10060
10332
  }
10061
- const result2 = spawnSync3("px", args, {
10333
+ const result2 = spawnSync4("px", args, {
10062
10334
  cwd: ctx.repoRoot,
10063
10335
  encoding: "utf8",
10064
10336
  shell: false,
@@ -10248,11 +10520,12 @@ var AgentHooksRecipe = class extends Recipe {
10248
10520
 
10249
10521
  // src/recipes/BmadRecipe.ts
10250
10522
  var BmadRecipe = class extends Recipe {
10251
- checks = createBmadChecks();
10523
+ requiredChecks = createBmadChecks();
10524
+ checks = [...this.requiredChecks, createG33CompanionCheck()];
10252
10525
  metadata = {
10253
10526
  id: "bmad",
10254
10527
  name: "bmad",
10255
- description: "BMAD methodology and the supported CLI projections",
10528
+ description: "BMAD methodology, supported CLI projections, and the optional owner-provided g33 companion",
10256
10529
  dependencies: ["agent-hooks"],
10257
10530
  commands: [],
10258
10531
  publicRuleIds: this.checks.map((check) => check.id)
@@ -10260,8 +10533,31 @@ var BmadRecipe = class extends Recipe {
10260
10533
  constructor(context) {
10261
10534
  super(context);
10262
10535
  }
10263
- init(ctx, _input) {
10264
- return this.initializeOwnedChecks(ctx);
10536
+ async init(ctx, _input) {
10537
+ const companions = [];
10538
+ const checks = this.requiredChecks.map((check) => ({
10539
+ ...check,
10540
+ migrate: async (...args) => {
10541
+ const result2 = await check.migrate(...args);
10542
+ if (result2.bmadCompanion) companions.push(result2.bmadCompanion);
10543
+ return result2;
10544
+ }
10545
+ }));
10546
+ const required = await this.initializeOwnedChecks(ctx, checks);
10547
+ if (!required.ok) return { ...required, ...companions.length ? { bmadCompanion: companions.at(-1) } : {} };
10548
+ const companion = companions.at(-1) ?? await runG33Companion(ctx.repoRoot, ctx.dryRun);
10549
+ return {
10550
+ ...required,
10551
+ bmadCompanion: companion,
10552
+ changedFiles: [.../* @__PURE__ */ new Set([...required.changedFiles, ...companion.changedFiles])].sort(),
10553
+ logs: [...required.logs, ...companionDetails(companion)],
10554
+ phases: [...required.phases, {
10555
+ id: "bmad.g33-companion",
10556
+ status: companion.status === "planned" ? "planned" : companion.status === "changed" ? "changed" : companion.status === "unchanged" ? "unchanged" : "skipped",
10557
+ message: `Optional g33 (${companion.status}): ${companion.summary}`,
10558
+ changedFiles: companion.changedFiles
10559
+ }]
10560
+ };
10265
10561
  }
10266
10562
  printNextSteps() {
10267
10563
  console.log(`BMAD lifecycle initialized for the ${SUPPORTED_CLIS.length} supported CLIs.`);
@@ -10585,24 +10881,24 @@ var NodeRecipe = class extends Recipe {
10585
10881
 
10586
10882
  // src/recipes/ProjectRecipe.ts
10587
10883
  init_project();
10588
- import { spawnSync as spawnSync11 } from "node:child_process";
10589
- import { existsSync as existsSync14, lstatSync as lstatSync12, readFileSync as readFileSync14, rmSync as rmSync5 } from "node:fs";
10590
- import { dirname as dirname14, isAbsolute as isAbsolute8, join as join18, relative as relativePath, resolve as resolve17 } from "node:path";
10884
+ import { spawnSync as spawnSync12 } from "node:child_process";
10885
+ import { existsSync as existsSync14, lstatSync as lstatSync13, readFileSync as readFileSync15, rmSync as rmSync5 } from "node:fs";
10886
+ import { dirname as dirname14, isAbsolute as isAbsolute9, join as join19, relative as relativePath, resolve as resolve18 } from "node:path";
10591
10887
  init_tree_diff();
10592
10888
 
10593
10889
  // src/recipes/NotebookRecipe.ts
10594
10890
  init_project();
10595
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
10596
- import { join as join17 } from "node:path";
10891
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
10892
+ import { join as join18 } from "node:path";
10597
10893
 
10598
10894
  // src/notebook/checks.ts
10599
10895
  init_config();
10600
10896
  init_notes();
10601
10897
  init_output();
10602
10898
  init_state();
10603
- import { spawnSync as spawnSync5 } from "node:child_process";
10604
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
10605
- import { join as join13 } from "node:path";
10899
+ import { spawnSync as spawnSync6 } from "node:child_process";
10900
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
10901
+ import { join as join14 } from "node:path";
10606
10902
  var NOTEBOOK_RULE_IDS = [
10607
10903
  "notebook.configuration",
10608
10904
  "notebook.binding",
@@ -10619,10 +10915,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
10619
10915
  return { id: check.id, title: check.title, status, summary, changedFiles, details };
10620
10916
  }
10621
10917
  function manifestNotebook(repo) {
10622
- const path = join13(repo, ".project.json");
10918
+ const path = join14(repo, ".project.json");
10623
10919
  if (!existsSync10(path)) return null;
10624
10920
  try {
10625
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
10921
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
10626
10922
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
10627
10923
  const notebook = parsed.notebook;
10628
10924
  return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
@@ -10851,10 +11147,10 @@ function captureAudit(ctx) {
10851
11147
  function overviewMigrate(ctx, current) {
10852
11148
  if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
10853
11149
  if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
10854
- const pjanglerEntry = ctx.pjanglerRoot ? join13(ctx.pjanglerRoot, "dist", "index.js") : null;
11150
+ const pjanglerEntry = ctx.pjanglerRoot ? join14(ctx.pjanglerRoot, "dist", "index.js") : null;
10855
11151
  const entry = pjanglerEntry && existsSync10(pjanglerEntry) ? pjanglerEntry : "pj";
10856
11152
  const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
10857
- const spawned = spawnSync5(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
11153
+ const spawned = spawnSync6(cmd[0], [...cmd.slice(1), "notebook", "overview", ctx.repoRoot], {
10858
11154
  encoding: "utf8",
10859
11155
  timeout: 1e4,
10860
11156
  env: { ...process.env, ...ctx.registryPath ? { PJ_PROJECT_REGISTRY: ctx.registryPath } : {} }
@@ -10899,7 +11195,7 @@ init_project();
10899
11195
  init_config();
10900
11196
  init_open_notebook_client();
10901
11197
  init_notes();
10902
- import { resolve as resolve16 } from "node:path";
11198
+ import { resolve as resolve17 } from "node:path";
10903
11199
 
10904
11200
  // src/notebook/journal-key.ts
10905
11201
  init_types();
@@ -10933,18 +11229,18 @@ function journalNoteOperationId(period, key) {
10933
11229
  init_notes();
10934
11230
  init_git_evidence();
10935
11231
  init_types();
10936
- import { spawnSync as spawnSync7 } from "node:child_process";
11232
+ import { spawnSync as spawnSync8 } from "node:child_process";
10937
11233
  import { realpathSync as realpathSync8 } from "node:fs";
10938
- import { relative as relative10, resolve as resolve13, sep as sep5 } from "node:path";
11234
+ import { relative as relative10, resolve as resolve14, sep as sep5 } from "node:path";
10939
11235
  var DEFAULT_OVERVIEW_REFERENCES = [".project.json", "README.md", "AGENTS.md", "CLAUDE.md", "docs/architecture.md"];
10940
11236
  function git2(repo, args, timeout) {
10941
- const result2 = spawnSync7("git", args, { cwd: repo, encoding: "utf8", maxBuffer: 1024 * 1024, timeout, shell: false });
11237
+ const result2 = spawnSync8("git", args, { cwd: repo, encoding: "utf8", maxBuffer: 1024 * 1024, timeout, shell: false });
10942
11238
  return { ok: result2.status === 0, stdout: result2.stdout?.trim() ?? "" };
10943
11239
  }
10944
11240
  function normalizedReference(repo, value) {
10945
11241
  if (!value || value.includes("\0") || value.startsWith("/") || value.split(/[\\/]/u).includes("..")) throw new NotebookError("INVALID_INPUT", `Overview reference is not a contained relative path: ${value}`);
10946
11242
  const root = realpathSync8(repo);
10947
- const candidate = resolve13(root, value);
11243
+ const candidate = resolve14(root, value);
10948
11244
  const rel = relative10(root, candidate).split(sep5).join("/");
10949
11245
  if (!rel || rel === ".." || rel.startsWith("../")) throw new NotebookError("INVALID_INPUT", `Overview reference escapes the repository: ${value}`);
10950
11246
  return rel.normalize("NFC");
@@ -11077,21 +11373,21 @@ init_git_evidence();
11077
11373
  init_notes();
11078
11374
  init_state();
11079
11375
  init_types();
11080
- import { spawn, spawnSync as spawnSync8 } from "node:child_process";
11376
+ import { spawn, spawnSync as spawnSync9 } from "node:child_process";
11081
11377
  import { createHash as createHash8, randomUUID as randomUUID5 } from "node:crypto";
11082
- import { chmodSync as chmodSync4, closeSync as closeSync5, constants as constants4, copyFileSync as copyFileSync3, existsSync as existsSync11, fstatSync as fstatSync3, fsyncSync as fsyncSync4, lstatSync as lstatSync11, mkdirSync as mkdirSync6, openSync as openSync5, readFileSync as readFileSync12, readSync as readSync3, readdirSync as readdirSync7, realpathSync as realpathSync9, renameSync as renameSync6, rmSync as rmSync4, symlinkSync as symlinkSync3, unlinkSync as unlinkSync6, writeFileSync as writeFileSync7 } from "node:fs";
11083
- import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute6, join as join16, parse as parse3, relative as relative11, resolve as resolve14, sep as sep6 } from "node:path";
11378
+ import { chmodSync as chmodSync4, closeSync as closeSync5, constants as constants4, copyFileSync as copyFileSync3, existsSync as existsSync11, fstatSync as fstatSync3, fsyncSync as fsyncSync4, lstatSync as lstatSync12, mkdirSync as mkdirSync6, openSync as openSync5, readFileSync as readFileSync13, readSync as readSync3, readdirSync as readdirSync7, realpathSync as realpathSync9, renameSync as renameSync6, rmSync as rmSync4, symlinkSync as symlinkSync3, unlinkSync as unlinkSync6, writeFileSync as writeFileSync7 } from "node:fs";
11379
+ import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute7, join as join17, parse as parse3, relative as relative11, resolve as resolve15, sep as sep6 } from "node:path";
11084
11380
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11085
11381
  function bundledSkillCandidates() {
11086
11382
  const candidates = [];
11087
11383
  let cursor = dirname13(fileURLToPath3(import.meta.url));
11088
11384
  for (let depth = 0; depth < 8; depth++) {
11089
- candidates.push(join16(cursor, "dist", "assets", "project-notebook-skill"), join16(cursor, "assets", "project-notebook-skill"));
11385
+ candidates.push(join17(cursor, "dist", "assets", "project-notebook-skill"), join17(cursor, "assets", "project-notebook-skill"));
11090
11386
  const parent = dirname13(cursor);
11091
11387
  if (parent === cursor) break;
11092
11388
  cursor = parent;
11093
11389
  }
11094
- return [...new Set(candidates.map((candidate) => resolve14(candidate)))];
11390
+ return [...new Set(candidates.map((candidate) => resolve15(candidate)))];
11095
11391
  }
11096
11392
  function safeSkillRelativePath(value) {
11097
11393
  if (!value || value.startsWith("/") || value.includes("\0")) return false;
@@ -11104,14 +11400,14 @@ function assertOwnedSkillTree(source) {
11104
11400
  assertNoSymlinkComponents(source);
11105
11401
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
11106
11402
  const walk = (directory) => {
11107
- const directoryStat = lstatSync11(directory);
11403
+ const directoryStat = lstatSync12(directory);
11108
11404
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
11109
11405
  if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
11110
11406
  if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
11111
11407
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
11112
11408
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
11113
- const path = join16(directory, entry.name);
11114
- const stat = lstatSync11(path);
11409
+ const path = join17(directory, entry.name);
11410
+ const stat = lstatSync12(path);
11115
11411
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
11116
11412
  if (entry.isDirectory()) walk(path);
11117
11413
  else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
@@ -11128,16 +11424,16 @@ function enumerateSkillPayload(source) {
11128
11424
  const walk = (directory) => {
11129
11425
  for (const entry of readdirSync7(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
11130
11426
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
11131
- const path = join16(directory, entry.name);
11427
+ const path = join17(directory, entry.name);
11132
11428
  const rel = relative11(source, path).split(sep6).join("/");
11133
11429
  if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
11134
11430
  if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
11135
- const stat = lstatSync11(path);
11431
+ const stat = lstatSync12(path);
11136
11432
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
11137
11433
  if (entry.isDirectory()) walk(path);
11138
11434
  else if (entry.isFile()) {
11139
11435
  const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
11140
- result2.push({ path: rel, sha256: createHash8("sha256").update(readFileSync12(path)).digest("hex"), mode: expectedMode });
11436
+ result2.push({ path: rel, sha256: createHash8("sha256").update(readFileSync13(path)).digest("hex"), mode: expectedMode });
11141
11437
  } else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
11142
11438
  }
11143
11439
  };
@@ -11145,9 +11441,9 @@ function enumerateSkillPayload(source) {
11145
11441
  return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
11146
11442
  }
11147
11443
  function parsePackedManifest(source) {
11148
- const manifestPath = join16(source, "export-manifest.json");
11444
+ const manifestPath = join17(source, "export-manifest.json");
11149
11445
  if (!existsSync11(manifestPath)) return null;
11150
- const value = JSON.parse(readFileSync12(manifestPath, "utf8"));
11446
+ const value = JSON.parse(readFileSync13(manifestPath, "utf8"));
11151
11447
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
11152
11448
  const manifest = value;
11153
11449
  if (manifest.schema_version !== 1 || manifest.skill !== "project-notebook" || !Array.isArray(manifest.files)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is incompatible");
@@ -11156,10 +11452,10 @@ function parsePackedManifest(source) {
11156
11452
  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");
11157
11453
  if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
11158
11454
  paths.add(entry.path);
11159
- const path = join16(source, ...entry.path.split("/"));
11160
- const stat = lstatSync11(path);
11455
+ const path = join17(source, ...entry.path.split("/"));
11456
+ const stat = lstatSync12(path);
11161
11457
  if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
11162
- const actual = createHash8("sha256").update(readFileSync12(path)).digest("hex");
11458
+ const actual = createHash8("sha256").update(readFileSync13(path)).digest("hex");
11163
11459
  if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
11164
11460
  const actualMode = stat.mode & 511;
11165
11461
  const executable = entry.mode === "0755";
@@ -11171,12 +11467,12 @@ function parsePackedManifest(source) {
11171
11467
  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");
11172
11468
  const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
11173
11469
  `;
11174
- if (!existsSync11(join16(source, "SHA256SUMS")) || readFileSync12(join16(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
11470
+ if (!existsSync11(join17(source, "SHA256SUMS")) || readFileSync13(join17(source, "SHA256SUMS"), "utf8") !== sums) throw new NotebookError("CONFLICT", "Project Notebook skill SHA256SUMS is missing or stale");
11175
11471
  return manifest;
11176
11472
  }
11177
11473
  function expectedPackedSkill() {
11178
11474
  for (const candidate of bundledSkillCandidates()) {
11179
- if (!existsSync11(join16(candidate, "export-manifest.json"))) continue;
11475
+ if (!existsSync11(join17(candidate, "export-manifest.json"))) continue;
11180
11476
  assertOwnedSkillTree(candidate);
11181
11477
  const manifest = parsePackedManifest(candidate);
11182
11478
  if (!manifest) continue;
@@ -11186,7 +11482,7 @@ function expectedPackedSkill() {
11186
11482
  throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
11187
11483
  }
11188
11484
  function isCanonicalSkillexProjectionPath(path) {
11189
- return resolve14(path).split(sep6).join("/").endsWith("/all-skills/project-notebook");
11485
+ return resolve15(path).split(sep6).join("/").endsWith("/all-skills/project-notebook");
11190
11486
  }
11191
11487
  function isVerifiedCanonicalSkillexProjection(path) {
11192
11488
  if (!isCanonicalSkillexProjectionPath(path)) return false;
@@ -11206,7 +11502,7 @@ function describeProjectNotebookSkillDrift(source) {
11206
11502
  }
11207
11503
  let actual;
11208
11504
  try {
11209
- const absolute = resolve14(source);
11505
+ const absolute = resolve15(source);
11210
11506
  const packed = parsePackedManifest(absolute);
11211
11507
  actual = packed?.files ?? enumerateSkillPayload(absolute);
11212
11508
  } catch (error) {
@@ -11230,7 +11526,7 @@ function describeProjectNotebookSkillDrift(source) {
11230
11526
  return drift.length ? drift.slice(0, 20) : ["no per-file drift detected; the export enumeration itself differs"];
11231
11527
  }
11232
11528
  function verifyProjectNotebookSkillExport(source) {
11233
- const absolute = resolve14(source);
11529
+ const absolute = resolve15(source);
11234
11530
  assertOwnedSkillTree(absolute);
11235
11531
  const packed = parsePackedManifest(absolute);
11236
11532
  const actual = packed ?? { schema_version: 1, skill: "project-notebook", files: enumerateSkillPayload(absolute) };
@@ -11242,14 +11538,14 @@ function verifyProjectNotebookSkillExport(source) {
11242
11538
  }
11243
11539
  function resolveProjectNotebookSkillSource(env2 = process.env) {
11244
11540
  if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
11245
- const explicit = resolve14(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
11246
- if (!existsSync11(join16(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
11541
+ const explicit = resolve15(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
11542
+ if (!existsSync11(join17(explicit, "SKILL.md"))) throw new NotebookError("NOT_CONFIGURED", "Configured Project Notebook skill source is unavailable");
11247
11543
  verifyProjectNotebookSkillExport(explicit);
11248
11544
  return explicit;
11249
11545
  }
11250
11546
  if (env2.PJ_SKILLS_REGISTRY_ROOT) {
11251
- const canonical = resolve14(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
11252
- if (existsSync11(join16(canonical, "SKILL.md"))) {
11547
+ const canonical = resolve15(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
11548
+ if (existsSync11(join17(canonical, "SKILL.md"))) {
11253
11549
  verifyProjectNotebookSkillExport(canonical);
11254
11550
  return canonical;
11255
11551
  }
@@ -11264,7 +11560,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
11264
11560
  function probeCanonicalSkillexRootProjection(skillsRoot, link) {
11265
11561
  let rootLink;
11266
11562
  try {
11267
- rootLink = lstatSync11(skillsRoot);
11563
+ rootLink = lstatSync12(skillsRoot);
11268
11564
  } catch (error) {
11269
11565
  if (error.code === "ENOENT") return { state: "absent" };
11270
11566
  throw error;
@@ -11277,15 +11573,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
11277
11573
  if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
11278
11574
  const globalRoot = realpathSync9(skillsRoot);
11279
11575
  assertNoSymlinkComponents(globalRoot);
11280
- const globalStat = lstatSync11(globalRoot);
11576
+ const globalStat = lstatSync12(globalRoot);
11281
11577
  if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
11282
11578
  if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
11283
11579
  if (globalStat.mode & 3586) throw new Error("root-mode");
11284
11580
  const skillSetsRoot = dirname13(globalRoot);
11285
11581
  if (basename8(globalRoot) !== "global" || basename8(skillSetsRoot) !== "skill-sets") throw new Error("layout");
11286
11582
  const checkoutRoot = dirname13(skillSetsRoot);
11287
- const expectedSource = join16(checkoutRoot, "all-skills", "project-notebook");
11288
- const linkStat = lstatSync11(link);
11583
+ const expectedSource = join17(checkoutRoot, "all-skills", "project-notebook");
11584
+ const linkStat = lstatSync12(link);
11289
11585
  if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
11290
11586
  if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
11291
11587
  const projectedSource = realpathSync9(link);
@@ -11321,28 +11617,28 @@ function installPackagedProjectNotebookSkill(input = {}) {
11321
11617
  const manifest = verifyProjectNotebookSkillExport(source);
11322
11618
  const digest = createHash8("sha256").update(JSON.stringify(manifest)).digest("hex");
11323
11619
  const home = env2.HOME;
11324
- if (!home || !resolve14(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
11325
- const skillsRoot = join16(home, ".agents", "skills");
11326
- const link = join16(skillsRoot, "project-notebook");
11620
+ if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
11621
+ const skillsRoot = join17(home, ".agents", "skills");
11622
+ const link = join17(skillsRoot, "project-notebook");
11327
11623
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11328
11624
  if (probe.state === "adopted") return { installed: false, path: link, digest };
11329
11625
  if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
11330
- const dataRoot = resolve14(env2.XDG_DATA_HOME || join16(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11331
- const payload = join16(dataRoot, `${PJANGLER_VERSION}-${digest}`);
11626
+ const dataRoot = resolve15(env2.XDG_DATA_HOME || join17(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11627
+ const payload = join17(dataRoot, `${PJANGLER_VERSION}-${digest}`);
11332
11628
  assertNoSymlinkComponents(dirname13(dataRoot), true);
11333
11629
  mkdirSync6(dataRoot, { recursive: true, mode: 448 });
11334
11630
  assertNoSymlinkComponents(dataRoot);
11335
- const dataStat = lstatSync11(dataRoot);
11631
+ const dataStat = lstatSync12(dataRoot);
11336
11632
  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");
11337
11633
  chmodSync4(dataRoot, 448);
11338
11634
  if (!existsSync11(payload)) {
11339
- const staging = join16(dataRoot, `.staging-${randomUUID5()}`);
11635
+ const staging = join17(dataRoot, `.staging-${randomUUID5()}`);
11340
11636
  mkdirSync6(staging, { recursive: false, mode: 448 });
11341
11637
  try {
11342
11638
  for (const entry of manifest.files) {
11343
- const destination = join16(staging, ...entry.path.split("/"));
11639
+ const destination = join17(staging, ...entry.path.split("/"));
11344
11640
  mkdirSync6(dirname13(destination), { recursive: true, mode: 493 });
11345
- copyFileSync3(join16(source, ...entry.path.split("/")), destination);
11641
+ copyFileSync3(join17(source, ...entry.path.split("/")), destination);
11346
11642
  const mode = entry.mode === "0755" ? 493 : 420;
11347
11643
  chmodSync4(destination, mode);
11348
11644
  const fd = openSync5(destination, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
@@ -11354,9 +11650,9 @@ function installPackagedProjectNotebookSkill(input = {}) {
11354
11650
  closeSync5(fd);
11355
11651
  }
11356
11652
  }
11357
- writeFileSync7(join16(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
11653
+ writeFileSync7(join17(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
11358
11654
  `, { mode: 420, flag: "wx" });
11359
- writeFileSync7(join16(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
11655
+ writeFileSync7(join17(staging, "SHA256SUMS"), `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
11360
11656
  `, { mode: 420, flag: "wx" });
11361
11657
  verifyProjectNotebookSkillExport(staging);
11362
11658
  renameSync6(staging, payload);
@@ -11364,7 +11660,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11364
11660
  if (existsSync11(staging)) rmSync4(staging, { recursive: true, force: true });
11365
11661
  }
11366
11662
  } else {
11367
- const stat = lstatSync11(payload);
11663
+ const stat = lstatSync12(payload);
11368
11664
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
11369
11665
  verifyProjectNotebookSkillExport(payload);
11370
11666
  }
@@ -11374,7 +11670,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11374
11670
  let linkExists = existsSync11(link);
11375
11671
  if (!linkExists) {
11376
11672
  try {
11377
- lstatSync11(link);
11673
+ lstatSync12(link);
11378
11674
  linkExists = true;
11379
11675
  } catch {
11380
11676
  }
@@ -11391,7 +11687,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11391
11687
  repair: REPAIR_COMMAND
11392
11688
  }
11393
11689
  });
11394
- const stat = lstatSync11(link);
11690
+ const stat = lstatSync12(link);
11395
11691
  if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
11396
11692
  const target = realpathSync9(link);
11397
11693
  if (target !== realpathSync9(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
@@ -11425,9 +11721,9 @@ function collectSupersededPayloads(dataRoot, keep) {
11425
11721
  }
11426
11722
  for (const name of entries) {
11427
11723
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
11428
- const candidate = join16(dataRoot, name);
11724
+ const candidate = join17(dataRoot, name);
11429
11725
  try {
11430
- const stat = lstatSync11(candidate);
11726
+ const stat = lstatSync12(candidate);
11431
11727
  if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
11432
11728
  if (kept.has(realpathSync9(candidate))) continue;
11433
11729
  rmSync4(candidate, { recursive: true, force: true });
@@ -11440,9 +11736,9 @@ function collectSupersededPayloads(dataRoot, keep) {
11440
11736
  function repairProjectNotebookSkillProjection(input = {}) {
11441
11737
  const env2 = input.env ?? process.env;
11442
11738
  const home = env2.HOME;
11443
- if (!home || !resolve14(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
11444
- const skillsRoot = join16(home, ".agents", "skills");
11445
- const link = join16(skillsRoot, "project-notebook");
11739
+ if (!home || !resolve15(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
11740
+ const skillsRoot = join17(home, ".agents", "skills");
11741
+ const link = join17(skillsRoot, "project-notebook");
11446
11742
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11447
11743
  if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
11448
11744
  if (probe.state !== "declined") {
@@ -11456,7 +11752,7 @@ function repairProjectNotebookSkillProjection(input = {}) {
11456
11752
  const present = [];
11457
11753
  const walk = (directory) => {
11458
11754
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
11459
- const path = join16(directory, entry.name);
11755
+ const path = join17(directory, entry.name);
11460
11756
  const rel = relative11(source, path).split(sep6).join("/");
11461
11757
  if (entry.isDirectory()) {
11462
11758
  walk(path);
@@ -11470,11 +11766,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
11470
11766
  walk(source);
11471
11767
  const stale = present.filter((rel) => !wanted.has(rel)).sort();
11472
11768
  const changed = expected.manifest.files.filter((entry) => {
11473
- const path = join16(source, ...entry.path.split("/"));
11769
+ const path = join17(source, ...entry.path.split("/"));
11474
11770
  if (!existsSync11(path)) return true;
11475
- const stat = lstatSync11(path);
11771
+ const stat = lstatSync12(path);
11476
11772
  if (!stat.isFile()) return true;
11477
- if (createHash8("sha256").update(readFileSync12(path)).digest("hex") !== entry.sha256) return true;
11773
+ if (createHash8("sha256").update(readFileSync13(path)).digest("hex") !== entry.sha256) return true;
11478
11774
  const mode = stat.mode & 511;
11479
11775
  const executable = entry.mode === "0755";
11480
11776
  return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
@@ -11489,15 +11785,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
11489
11785
  summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
11490
11786
  source,
11491
11787
  drift: probe.block.details,
11492
- changed_files: affected.map((rel) => join16(source, ...rel.split("/")))
11788
+ changed_files: affected.map((rel) => join17(source, ...rel.split("/")))
11493
11789
  };
11494
11790
  }
11495
- for (const rel of stale) rmSync4(join16(source, ...rel.split("/")), { force: true });
11791
+ for (const rel of stale) rmSync4(join17(source, ...rel.split("/")), { force: true });
11496
11792
  for (const rel of changed) {
11497
- const destination = join16(source, ...rel.split("/"));
11793
+ const destination = join17(source, ...rel.split("/"));
11498
11794
  const entry = expected.manifest.files.find((item) => item.path === rel);
11499
11795
  mkdirSync6(dirname13(destination), { recursive: true, mode: 493 });
11500
- copyFileSync3(join16(expected.source, ...rel.split("/")), destination);
11796
+ copyFileSync3(join17(expected.source, ...rel.split("/")), destination);
11501
11797
  chmodSync4(destination, entry.mode === "0755" ? 493 : 420);
11502
11798
  }
11503
11799
  verifyProjectNotebookSkillExport(source);
@@ -11506,21 +11802,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
11506
11802
  summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
11507
11803
  source,
11508
11804
  drift: probe.block.details,
11509
- changed_files: affected.map((rel) => join16(source, ...rel.split("/")))
11805
+ changed_files: affected.map((rel) => join17(source, ...rel.split("/")))
11510
11806
  };
11511
11807
  }
11512
11808
  function inspectProjectNotebookIntegration(env2 = process.env) {
11513
11809
  const home = env2.HOME;
11514
11810
  if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
11515
- const skillsRoot = join16(home, ".agents", "skills");
11516
- const link = join16(skillsRoot, "project-notebook");
11811
+ const skillsRoot = join17(home, ".agents", "skills");
11812
+ const link = join17(skillsRoot, "project-notebook");
11517
11813
  try {
11518
11814
  const expected = expectedPackedSkill();
11519
- const dataRoot = resolve14(env2.XDG_DATA_HOME || join16(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11520
- const expectedPayload = join16(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
11815
+ const dataRoot = resolve15(env2.XDG_DATA_HOME || join17(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11816
+ const expectedPayload = join17(dataRoot, `${PJANGLER_VERSION}-${expected.digest}`);
11521
11817
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11522
11818
  if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
11523
- const stat = lstatSync11(link);
11819
+ const stat = lstatSync12(link);
11524
11820
  if (!stat.isSymbolicLink()) {
11525
11821
  return {
11526
11822
  skill_installed: false,
@@ -11557,8 +11853,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
11557
11853
  }
11558
11854
  }
11559
11855
  function projectorArguments(source, command, input) {
11560
- const script = join16(source, "scripts", "project-hooks.py");
11561
- const args = [script, command, "--master", join16(source, "hooks", "hooks.master.json"), "--fragment", join16(source, "hooks", "claude.settings.json"), "--target", input.target];
11856
+ const script = join17(source, "scripts", "project-hooks.py");
11857
+ const args = [script, command, "--master", join17(source, "hooks", "hooks.master.json"), "--fragment", join17(source, "hooks", "claude.settings.json"), "--target", input.target];
11562
11858
  if (command === "check") args.push("--json");
11563
11859
  else if (input.stateHome) args.push("--state-home", input.stateHome);
11564
11860
  return args;
@@ -11580,8 +11876,8 @@ function checkProjectNotebookHooks(input = {}) {
11580
11876
  verifyProjectNotebookSkillExport(source);
11581
11877
  const home = env2.HOME;
11582
11878
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
11583
- const target = resolve14(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join16(home, ".claude", "settings.json"));
11584
- const result2 = spawnSync8("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
11879
+ const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join17(home, ".claude", "settings.json"));
11880
+ const result2 = spawnSync9("/usr/bin/python3", ["-I", ...projectorArguments(source, "check", { target })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
11585
11881
  if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
11586
11882
  try {
11587
11883
  const parsed = JSON.parse(result2.stdout);
@@ -11598,13 +11894,13 @@ function installProjectNotebookIntegration(input = {}) {
11598
11894
  const source = realpathSync9(skill.path);
11599
11895
  const home = env2.HOME;
11600
11896
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
11601
- const target = resolve14(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join16(home, ".claude", "settings.json"));
11602
- const stateHome = resolve14(env2.XDG_STATE_HOME || join16(home, ".local", "state"));
11603
- const result2 = spawnSync8("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
11897
+ const target = resolve15(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join17(home, ".claude", "settings.json"));
11898
+ const stateHome = resolve15(env2.XDG_STATE_HOME || join17(home, ".local", "state"));
11899
+ const result2 = spawnSync9("/usr/bin/python3", ["-I", ...projectorArguments(source, "install", { target, stateHome })], { encoding: "utf8", env: projectorEnvironment(env2), timeout: 5e3, maxBuffer: 1048576 });
11604
11900
  if (result2.status !== 0) {
11605
11901
  if (skill.installed) {
11606
11902
  try {
11607
- const stat = lstatSync11(skill.path);
11903
+ const stat = lstatSync12(skill.path);
11608
11904
  if (stat.isSymbolicLink() && realpathSync9(skill.path) === source) unlinkSync6(skill.path);
11609
11905
  } catch {
11610
11906
  }
@@ -11618,14 +11914,14 @@ function boundedDiagnostic(value, max = 512) {
11618
11914
  return message.replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").slice(0, max);
11619
11915
  }
11620
11916
  function assertNoSymlinkComponents(path, allowMissing = false) {
11621
- const absolute = resolve14(path);
11917
+ const absolute = resolve15(path);
11622
11918
  const root = parse3(absolute).root;
11623
11919
  let cursor = root;
11624
11920
  for (const component of absolute.slice(root.length).split(sep6).filter(Boolean)) {
11625
- cursor = join16(cursor, component);
11921
+ cursor = join17(cursor, component);
11626
11922
  let stat;
11627
11923
  try {
11628
- stat = lstatSync11(cursor);
11924
+ stat = lstatSync12(cursor);
11629
11925
  } catch (error) {
11630
11926
  if (allowMissing && error.code === "ENOENT") return;
11631
11927
  throw error;
@@ -11825,7 +12121,7 @@ var NotebookModule = class {
11825
12121
  return {
11826
12122
  changedFiles: [
11827
12123
  ...installed.skill.installed ? [installed.skill.path] : [],
11828
- ...installed.hooksChanged ? [resolve16(env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? `${home}/.claude/settings.json`)] : []
12124
+ ...installed.hooksChanged ? [resolve17(env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? `${home}/.claude/settings.json`)] : []
11829
12125
  ],
11830
12126
  ...installed.blocked ? { blocked: installed.blocked } : {}
11831
12127
  };
@@ -12231,17 +12527,17 @@ init_remote_mutation_journal();
12231
12527
  function integrationEnvironment(module, ctx) {
12232
12528
  const env2 = { ...module.environment, HOME: ctx.homeDir };
12233
12529
  if (module.environment.HOME !== ctx.homeDir) {
12234
- env2.XDG_DATA_HOME = join17(ctx.homeDir, ".local", "share");
12235
- env2.XDG_STATE_HOME = join17(ctx.homeDir, ".local", "state");
12236
- env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join17(ctx.homeDir, ".claude", "settings.json");
12530
+ env2.XDG_DATA_HOME = join18(ctx.homeDir, ".local", "share");
12531
+ env2.XDG_STATE_HOME = join18(ctx.homeDir, ".local", "state");
12532
+ env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS = join18(ctx.homeDir, ".claude", "settings.json");
12237
12533
  }
12238
12534
  return env2;
12239
12535
  }
12240
12536
  function resolvedForPlan(plan) {
12241
- const manifestPath = join17(plan.project.repo_path, ".project.json");
12537
+ const manifestPath = join18(plan.project.repo_path, ".project.json");
12242
12538
  let manifest = plan.manifest;
12243
12539
  if (existsSync13(manifestPath)) {
12244
- const parsed = JSON.parse(readFileSync13(manifestPath, "utf8"));
12540
+ const parsed = JSON.parse(readFileSync14(manifestPath, "utf8"));
12245
12541
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
12246
12542
  manifest = parsed;
12247
12543
  }
@@ -12382,7 +12678,7 @@ var PRODUCTION_RUNTIME = {
12382
12678
  executePlan: executeProjectInitPlan,
12383
12679
  preflightBmad: preflightBmadLifecycle,
12384
12680
  runGit(cwd, args, options) {
12385
- const result2 = spawnSync11("git", [...args], {
12681
+ const result2 = spawnSync12("git", [...args], {
12386
12682
  cwd,
12387
12683
  encoding: "utf8",
12388
12684
  env: options?.env ? { ...process.env, ...options.env } : process.env
@@ -12396,13 +12692,13 @@ var PRODUCTION_RUNTIME = {
12396
12692
  }
12397
12693
  };
12398
12694
  function unsafeToRemove(targetDir) {
12399
- const absolute = resolve17(targetDir);
12695
+ const absolute = resolve18(targetDir);
12400
12696
  let cursor = absolute;
12401
12697
  const seen = /* @__PURE__ */ new Set();
12402
12698
  while (!seen.has(cursor)) {
12403
12699
  seen.add(cursor);
12404
12700
  try {
12405
- const stat = lstatSync12(cursor);
12701
+ const stat = lstatSync13(cursor);
12406
12702
  if (stat.isSymbolicLink()) {
12407
12703
  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`;
12408
12704
  }
@@ -12430,12 +12726,12 @@ function publicMigration(report) {
12430
12726
  };
12431
12727
  }
12432
12728
  function hasGitRepository(runtime, targetDir) {
12433
- if (!existsSync14(join18(targetDir, ".git"))) return false;
12729
+ if (!existsSync14(join19(targetDir, ".git"))) return false;
12434
12730
  return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
12435
12731
  }
12436
12732
  function refreshPlanFromCanonicalManifest(plan) {
12437
- const manifestPath = join18(plan.project.repo_path, ".project.json");
12438
- const manifest = JSON.parse(readFileSync14(manifestPath, "utf8"));
12733
+ const manifestPath = join19(plan.project.repo_path, ".project.json");
12734
+ const manifest = JSON.parse(readFileSync15(manifestPath, "utf8"));
12439
12735
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
12440
12736
  throw new Error(`${manifestPath} must contain a JSON object`);
12441
12737
  }
@@ -12527,6 +12823,8 @@ var ProjectRecipe = class extends Recipe {
12527
12823
  repoRoot: targetDir,
12528
12824
  bmadVersionPin: mode === "create" ? BMAD_INSTALLER_VERSION : ctx.bmadVersionPin
12529
12825
  };
12826
+ let bmadCompanion;
12827
+ let g33DeferredForEnrollment = false;
12530
12828
  let migrationReport;
12531
12829
  let audit;
12532
12830
  let notebookPlan;
@@ -12567,6 +12865,10 @@ var ProjectRecipe = class extends Recipe {
12567
12865
  message: planBlocked ? "Project plan skipped after failed preflight" : executed.ok ? "Project plan executed" : executed.errors.join("; ")
12568
12866
  });
12569
12867
  if (errors.length === 0 && executed.ok && mode === "create") {
12868
+ try {
12869
+ g33DeferredForEnrollment = !g33CompanionEnabled(targetDir);
12870
+ } catch {
12871
+ }
12570
12872
  const dependencyResult = await this.registry.initDependencies(this.metadata.id, transactionContext, normalized, ["notebook"]);
12571
12873
  logs.push(...dependencyResult.logs);
12572
12874
  errors.push(...dependencyResult.errors);
@@ -12647,7 +12949,7 @@ var ProjectRecipe = class extends Recipe {
12647
12949
  if (hasGitRepository(this.runtime, targetDir)) {
12648
12950
  phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
12649
12951
  } else {
12650
- const gitPath = join18(targetDir, ".git");
12952
+ const gitPath = join19(targetDir, ".git");
12651
12953
  for (const { args, label, options } of [
12652
12954
  { args: ["init", "--initial-branch=main"], label: "git init" },
12653
12955
  { args: ["add", "-A"], label: "git add" },
@@ -12747,6 +13049,17 @@ var ProjectRecipe = class extends Recipe {
12747
13049
  errors.push(`project manifest refresh after external effects failed: ${error instanceof Error ? error.message : String(error)}`);
12748
13050
  }
12749
13051
  }
13052
+ if (errors.length === 0 && registryFinalizerEligible && mode === "create" && g33DeferredForEnrollment && plan.project.ticket_provider.state === "linked") {
13053
+ bmadCompanion = await runG33Companion(targetDir, ctx.dryRun);
13054
+ logs.push(...companionDetails(bmadCompanion));
13055
+ changedFiles.push(...bmadCompanion.changedFiles);
13056
+ phases.push({
13057
+ id: "project.optional:g33-companion",
13058
+ status: bmadCompanion.status === "changed" ? "changed" : bmadCompanion.status === "planned" ? "planned" : bmadCompanion.status === "unchanged" ? "unchanged" : "skipped",
13059
+ changedFiles: bmadCompanion.changedFiles,
13060
+ message: `Optional g33 (${bmadCompanion.status}): ${bmadCompanion.summary}`
13061
+ });
13062
+ }
12750
13063
  if (registryFinalizerEligible && registryActions.length) {
12751
13064
  rollbackEligible = false;
12752
13065
  const registryPlan = { ...plan, actions: registryActions };
@@ -12800,8 +13113,8 @@ var ProjectRecipe = class extends Recipe {
12800
13113
  } else try {
12801
13114
  rmSync5(targetDir, { recursive: true, force: true });
12802
13115
  const insideTarget = (path) => {
12803
- const relative12 = relativePath(resolve17(targetDir), resolve17(path));
12804
- return relative12 === "" || !relative12.startsWith("..") && !isAbsolute8(relative12);
13116
+ const relative12 = relativePath(resolve18(targetDir), resolve18(path));
13117
+ return relative12 === "" || !relative12.startsWith("..") && !isAbsolute9(relative12);
12805
13118
  };
12806
13119
  const orphaned = [...new Set(changedFiles.filter((path) => !insideTarget(path)))].sort();
12807
13120
  changedFiles.length = 0;
@@ -12837,7 +13150,8 @@ var ProjectRecipe = class extends Recipe {
12837
13150
  audit,
12838
13151
  selectedOperations: normalized.selectedOperations ?? [],
12839
13152
  selectedParityRules: normalized.selectedRuleIds ?? [],
12840
- migrationReport
13153
+ migrationReport,
13154
+ ...bmadCompanion ? { bmadCompanion } : {}
12841
13155
  };
12842
13156
  }
12843
13157
  printNextSteps() {
@@ -12845,12 +13159,12 @@ var ProjectRecipe = class extends Recipe {
12845
13159
  };
12846
13160
 
12847
13161
  // src/recipes/registry.ts
12848
- import { readFileSync as readFileSync15 } from "node:fs";
12849
- import { join as join19 } from "node:path";
13162
+ import { readFileSync as readFileSync16 } from "node:fs";
13163
+ import { join as join20 } from "node:path";
12850
13164
  function projectWaivers(repoRoot) {
12851
13165
  let waive;
12852
13166
  try {
12853
- waive = JSON.parse(readFileSync15(join19(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
13167
+ waive = JSON.parse(readFileSync16(join20(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
12854
13168
  } catch {
12855
13169
  return /* @__PURE__ */ new Map();
12856
13170
  }
@@ -13191,8 +13505,8 @@ var recipeRegistry = new RecipeRegistry([
13191
13505
 
13192
13506
  // src/commands/AgentHooksCommands.ts
13193
13507
  import { homedir as homedir6 } from "node:os";
13194
- import { join as join20, dirname as dirname15 } from "node:path";
13195
- import { existsSync as existsSync15, cpSync, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "node:fs";
13508
+ import { join as join21, dirname as dirname15 } from "node:path";
13509
+ import { existsSync as existsSync15, cpSync, mkdirSync as mkdirSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "node:fs";
13196
13510
  import { fileURLToPath as fileURLToPath4 } from "node:url";
13197
13511
  init_project();
13198
13512
  import { parse as parseToml3 } from "smol-toml";
@@ -13205,16 +13519,16 @@ function resolveTemplateRoot() {
13205
13519
  try {
13206
13520
  let dir = dirname15(fileURLToPath4(import.meta.url));
13207
13521
  for (let i = 0; i < 8; i++) {
13208
- candidates.push(join20(dir, "templates", "commonproject", "template"));
13522
+ candidates.push(join21(dir, "templates", "commonproject", "template"));
13209
13523
  const parent = dirname15(dir);
13210
13524
  if (parent === dir) break;
13211
13525
  dir = parent;
13212
13526
  }
13213
13527
  } catch {
13214
13528
  }
13215
- candidates.push(join20(homedir6(), "code", "pjangler", "templates", "commonproject", "template"));
13529
+ candidates.push(join21(homedir6(), "code", "pjangler", "templates", "commonproject", "template"));
13216
13530
  for (const c of candidates) {
13217
- if (existsSync15(join20(c, ".agents", "hooks", "hooks.master.json"))) return c;
13531
+ if (existsSync15(join21(c, ".agents", "hooks", "hooks.master.json"))) return c;
13218
13532
  }
13219
13533
  throw new Error(
13220
13534
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -13240,8 +13554,8 @@ var CopyAgentHooksTree = class extends Command {
13240
13554
  const created = [];
13241
13555
  const skipped = [];
13242
13556
  for (const { rel, dir } of items) {
13243
- const src = join20(templateRoot, rel);
13244
- const dest = join20(this.context.targetDir, rel);
13557
+ const src = join21(templateRoot, rel);
13558
+ const dest = join21(this.context.targetDir, rel);
13245
13559
  if (!existsSync15(src)) continue;
13246
13560
  if (existsSync15(dest) && !this.context.force) {
13247
13561
  skipped.push(rel);
@@ -13269,14 +13583,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
13269
13583
  if (!resolveAgentHooksLayer2()) {
13270
13584
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
13271
13585
  }
13272
- const misePath = join20(this.context.targetDir, "mise.toml");
13586
+ const misePath = join21(this.context.targetDir, "mise.toml");
13273
13587
  if (!existsSync15(misePath)) {
13274
13588
  return {
13275
13589
  success: false,
13276
13590
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
13277
13591
  };
13278
13592
  }
13279
- let content = readFileSync16(misePath, "utf8");
13593
+ let content = readFileSync17(misePath, "utf8");
13280
13594
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
13281
13595
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
13282
13596
  }
@@ -13499,7 +13813,7 @@ if __name__ == "__main__":
13499
13813
 
13500
13814
  // src/commands/AddMiseCodegraphScript.ts
13501
13815
  import { chmodSync as chmodSync5 } from "fs";
13502
- import { join as join21 } from "path";
13816
+ import { join as join22 } from "path";
13503
13817
  var AddMiseCodegraphScript = class extends Command {
13504
13818
  async invoke() {
13505
13819
  const filePath = ".mise/scripts/codegraph.sh";
@@ -13553,7 +13867,7 @@ fi
13553
13867
  `;
13554
13868
  this.writeFile(filePath, content);
13555
13869
  if (!this.context.dryRun) {
13556
- chmodSync5(join21(this.context.targetDir, filePath), 493);
13870
+ chmodSync5(join22(this.context.targetDir, filePath), 493);
13557
13871
  }
13558
13872
  return {
13559
13873
  success: true,
@@ -13591,18 +13905,18 @@ SECRET_KEY=""
13591
13905
  // src/parity/index.ts
13592
13906
  import { existsSync as existsSync16 } from "node:fs";
13593
13907
  import { homedir as homedir7 } from "node:os";
13594
- import { dirname as dirname16, join as join22, resolve as resolve18 } from "node:path";
13908
+ import { dirname as dirname16, join as join23, resolve as resolve19 } from "node:path";
13595
13909
  import { fileURLToPath as fileURLToPath5 } from "node:url";
13596
13910
  function resolvePjanglerRoot2() {
13597
13911
  let dir = dirname16(fileURLToPath5(import.meta.url));
13598
13912
  while (dir !== dirname16(dir)) {
13599
- if (existsSync16(join22(dir, "package.json")) && existsSync16(join22(dir, "templates", "commonproject", "copier.yml"))) return dir;
13913
+ if (existsSync16(join23(dir, "package.json")) && existsSync16(join23(dir, "templates", "commonproject", "copier.yml"))) return dir;
13600
13914
  dir = dirname16(dir);
13601
13915
  }
13602
- return resolve18(process.cwd());
13916
+ return resolve19(process.cwd());
13603
13917
  }
13604
13918
  function lifecycleContext(repoArg, dryRun, acceptRegistryMatches = false, overrides = {}) {
13605
- const repoRoot = resolve18(repoArg ?? process.cwd());
13919
+ const repoRoot = resolve19(repoArg ?? process.cwd());
13606
13920
  return {
13607
13921
  ...overrides,
13608
13922
  targetDir: repoRoot,
@@ -13771,13 +14085,13 @@ init_version();
13771
14085
  // src/project/info.ts
13772
14086
  init_boardUrl();
13773
14087
  init_project();
13774
- import { readFileSync as readFileSync17 } from "node:fs";
13775
- import { join as join23, resolve as resolve19 } from "node:path";
14088
+ import { readFileSync as readFileSync18 } from "node:fs";
14089
+ import { join as join24, resolve as resolve20 } from "node:path";
13776
14090
  function readManifest2(root) {
13777
- const path = join23(root, ".project.json");
14091
+ const path = join24(root, ".project.json");
13778
14092
  let manifest;
13779
14093
  try {
13780
- const value = JSON.parse(readFileSync17(path, "utf8"));
14094
+ const value = JSON.parse(readFileSync18(path, "utf8"));
13781
14095
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
13782
14096
  manifest = value;
13783
14097
  } catch (error) {
@@ -13817,8 +14131,8 @@ function readProjectInfo(input = {}) {
13817
14131
  const info = {
13818
14132
  ok: true,
13819
14133
  project_id: projectId,
13820
- repo_path: resolve19(root),
13821
- manifest_path: join23(resolve19(root), ".project.json"),
14134
+ repo_path: resolve20(root),
14135
+ manifest_path: join24(resolve20(root), ".project.json"),
13822
14136
  manifest,
13823
14137
  registry: { location, status: "indexed" }
13824
14138
  };
@@ -13839,27 +14153,27 @@ function readProjectInfo(input = {}) {
13839
14153
  info.registry = { location, status: "not-indexed", message: "This project is not indexed. Run pj init --apply from its repository." };
13840
14154
  return info;
13841
14155
  }
13842
- if (resolve19(indexed.repo_path) !== resolve19(root)) {
14156
+ if (resolve20(indexed.repo_path) !== resolve20(root)) {
13843
14157
  info.registry = { location, status: "stale", message: `The registry points to ${indexed.repo_path}; this manifest is at ${root}. Reindex the repository.` };
13844
14158
  }
13845
14159
  return info;
13846
14160
  }
13847
14161
 
13848
14162
  // src/describe/index.ts
13849
- import { existsSync as existsSync17, readFileSync as readFileSync18, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
13850
- import { join as join25, resolve as resolve20 } from "node:path";
14163
+ import { existsSync as existsSync17, readFileSync as readFileSync19, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
14164
+ import { join as join26, resolve as resolve21 } from "node:path";
13851
14165
  init_project();
13852
14166
 
13853
14167
  // src/describe/activity.ts
13854
- import { spawn as spawn2, spawnSync as spawnSync12 } from "node:child_process";
14168
+ import { spawn as spawn2, spawnSync as spawnSync13 } from "node:child_process";
13855
14169
  import { statSync as statSync5 } from "node:fs";
13856
- import { join as join24 } from "node:path";
14170
+ import { join as join25 } from "node:path";
13857
14171
  var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
13858
14172
  var MAX_DIRTY_STATS = 500;
13859
14173
  var GIT_TIMEOUT_MS = 5e3;
13860
14174
  var GIT_MAX_BUFFER = 16 * 1024 * 1024;
13861
14175
  function git3(repo, args) {
13862
- const result2 = spawnSync12("git", ["-C", repo, ...args], {
14176
+ const result2 = spawnSync13("git", ["-C", repo, ...args], {
13863
14177
  encoding: "utf8",
13864
14178
  timeout: GIT_TIMEOUT_MS,
13865
14179
  maxBuffer: GIT_MAX_BUFFER
@@ -13982,7 +14296,7 @@ function uncommittedSource(repo, paths) {
13982
14296
  let newest = 0;
13983
14297
  for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
13984
14298
  try {
13985
- const mtime = Math.floor(statSync5(join24(repo, path)).mtimeMs / 1e3);
14299
+ const mtime = Math.floor(statSync5(join25(repo, path)).mtimeMs / 1e3);
13986
14300
  if (mtime > newest) newest = mtime;
13987
14301
  } catch {
13988
14302
  }
@@ -14091,7 +14405,7 @@ var CONFIG_FILES = [
14091
14405
  ];
14092
14406
  function readJson(path) {
14093
14407
  try {
14094
- const parsed = JSON.parse(readFileSync18(path, "utf8"));
14408
+ const parsed = JSON.parse(readFileSync19(path, "utf8"));
14095
14409
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
14096
14410
  } catch {
14097
14411
  return void 0;
@@ -14113,7 +14427,7 @@ function describeType(repo) {
14113
14427
  const evidence = [];
14114
14428
  const note = (signal, file) => evidence.push(`${signal} (${file})`);
14115
14429
  for (const marker of LANGUAGE_MARKERS) {
14116
- if (!existsSync17(join25(repo, marker.file))) continue;
14430
+ if (!existsSync17(join26(repo, marker.file))) continue;
14117
14431
  if (!languages.includes(marker.language)) {
14118
14432
  languages.push(marker.language);
14119
14433
  note(marker.language, marker.file);
@@ -14127,9 +14441,9 @@ function describeType(repo) {
14127
14441
  }
14128
14442
  } catch {
14129
14443
  }
14130
- const pkg = readJson(join25(repo, "package.json"));
14444
+ const pkg = readJson(join26(repo, "package.json"));
14131
14445
  if (pkg) {
14132
- if (existsSync17(join25(repo, "tsconfig.json"))) {
14446
+ if (existsSync17(join26(repo, "tsconfig.json"))) {
14133
14447
  const index = languages.indexOf("javascript");
14134
14448
  if (index >= 0) languages.splice(index, 1);
14135
14449
  if (!languages.includes("typescript")) {
@@ -14163,26 +14477,26 @@ function describeType(repo) {
14163
14477
  ["hermes-fleet-host", "agents/hermes"]
14164
14478
  ];
14165
14479
  for (const [role, marker] of roleMarkers) {
14166
- if (!existsSync17(join25(repo, marker))) continue;
14480
+ if (!existsSync17(join26(repo, marker))) continue;
14167
14481
  roles.push(role);
14168
14482
  note(role, marker);
14169
14483
  }
14170
14484
  return { primaryLanguage: languages[0], languages, roles, evidence };
14171
14485
  }
14172
14486
  function describeIdentity(repo, registryPath) {
14173
- const manifestPath = join25(repo, ".project.json");
14487
+ const manifestPath = join26(repo, ".project.json");
14174
14488
  const manifest = readJson(manifestPath);
14175
14489
  const drift = [];
14176
- let record2;
14490
+ let record3;
14177
14491
  let registryReadable = true;
14178
14492
  try {
14179
14493
  const registry = loadProjectRegistry(registryPath);
14180
14494
  const identity = manifest?.project_id ?? manifest?.project_slug;
14181
14495
  const slug = typeof identity === "string" ? identity.toLowerCase() : void 0;
14182
- const resolved = resolve20(repo);
14183
- record2 = (slug ? registry.projects[slug] : void 0) ?? Object.values(registry.projects).find((project) => resolve20(project.repo_path) === resolved);
14496
+ const resolved = resolve21(repo);
14497
+ record3 = (slug ? registry.projects[slug] : void 0) ?? Object.values(registry.projects).find((project) => resolve21(project.repo_path) === resolved);
14184
14498
  const statuses = registry.__registry_status;
14185
- const status = record2 && statuses?.[record2.slug];
14499
+ const status = record3 && statuses?.[record3.slug];
14186
14500
  if (status && status.status !== "ok") {
14187
14501
  drift.push({ note: `canonical manifest is ${status.status}; displaying the last indexed record. ${status.error ?? ""}`, command: "pjangler doctor" });
14188
14502
  }
@@ -14190,14 +14504,14 @@ function describeIdentity(repo, registryPath) {
14190
14504
  registryReadable = false;
14191
14505
  drift.push({ note: `registry unreadable: ${err instanceof Error ? err.message : String(err)}` });
14192
14506
  }
14193
- if (manifest && !record2 && registryReadable) {
14507
+ if (manifest && !record3 && registryReadable) {
14194
14508
  drift.push({ note: ".project.json exists but this repo is not in the pjangler registry" });
14195
14509
  }
14196
- if (record2 && !manifest) {
14197
- drift.push({ note: `registered as ${record2.slug} but .project.json is missing`, command: "pjangler doctor" });
14510
+ if (record3 && !manifest) {
14511
+ drift.push({ note: `registered as ${record3.slug} but .project.json is missing`, command: "pjangler doctor" });
14198
14512
  }
14199
- if (record2 && resolve20(record2.repo_path) !== resolve20(repo)) {
14200
- drift.push({ note: `registry repo_path points elsewhere: ${record2.repo_path}`, command: "pjangler doctor" });
14513
+ if (record3 && resolve21(record3.repo_path) !== resolve21(repo)) {
14514
+ drift.push({ note: `registry repo_path points elsewhere: ${record3.repo_path}`, command: "pjangler doctor" });
14201
14515
  }
14202
14516
  const manifestProvider = manifest?.ticket_provider;
14203
14517
  const provider = manifestProvider ? {
@@ -14206,13 +14520,13 @@ function describeIdentity(repo, registryPath) {
14206
14520
  identifier: manifestProvider.identifier,
14207
14521
  board_id: manifestProvider.board_id,
14208
14522
  state: manifestProvider.state
14209
- } : record2?.ticket_provider;
14210
- if (manifestProvider && record2) {
14523
+ } : record3?.ticket_provider;
14524
+ if (manifestProvider && record3) {
14211
14525
  const fields = [
14212
- ["type", provider?.type, record2.ticket_provider.type],
14213
- ["workspace", provider?.workspace, record2.ticket_provider.workspace],
14214
- ["identifier", provider?.identifier, record2.ticket_provider.identifier],
14215
- ["board_id", provider?.board_id, record2.ticket_provider.board_id]
14526
+ ["type", provider?.type, record3.ticket_provider.type],
14527
+ ["workspace", provider?.workspace, record3.ticket_provider.workspace],
14528
+ ["identifier", provider?.identifier, record3.ticket_provider.identifier],
14529
+ ["board_id", provider?.board_id, record3.ticket_provider.board_id]
14216
14530
  ];
14217
14531
  for (const [field2, fromManifest, fromRegistry] of fields) {
14218
14532
  if ((fromManifest ?? "") === (fromRegistry ?? "")) continue;
@@ -14222,7 +14536,7 @@ function describeIdentity(repo, registryPath) {
14222
14536
  }
14223
14537
  }
14224
14538
  const manifestAgents = manifest?.agents ?? {};
14225
- const agents = record2 ? Object.entries(record2.agents).map(([name, agent]) => ({
14539
+ const agents = record3 ? Object.entries(record3.agents).map(([name, agent]) => ({
14226
14540
  name,
14227
14541
  role: agent.role,
14228
14542
  provisioningState: agent.provisioning_state,
@@ -14235,11 +14549,11 @@ function describeIdentity(repo, registryPath) {
14235
14549
  }));
14236
14550
  return {
14237
14551
  manifest: Boolean(manifest),
14238
- registered: Boolean(record2),
14552
+ registered: Boolean(record3),
14239
14553
  registryPath,
14240
- slug: record2?.slug ?? (manifest?.project_id ?? manifest?.project_slug),
14241
- name: record2?.name ?? manifest?.project_name,
14242
- description: record2?.description ?? manifest?.project_description,
14554
+ slug: record3?.slug ?? (manifest?.project_id ?? manifest?.project_slug),
14555
+ name: record3?.name ?? manifest?.project_name,
14556
+ description: record3?.description ?? manifest?.project_description,
14243
14557
  ticketProvider: provider ? {
14244
14558
  type: provider.type,
14245
14559
  workspace: provider.workspace,
@@ -14263,12 +14577,12 @@ function describeSubsystems(repo, findings) {
14263
14577
  const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
14264
14578
  const evidence = metadata.id === "notebook" ? (() => {
14265
14579
  try {
14266
- const manifest = JSON.parse(readFileSync18(join25(repo, ".project.json"), "utf8"));
14580
+ const manifest = JSON.parse(readFileSync19(join26(repo, ".project.json"), "utf8"));
14267
14581
  return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
14268
14582
  } catch {
14269
14583
  return [];
14270
14584
  }
14271
- })() : markers.filter((marker) => existsSync17(join25(repo, marker)));
14585
+ })() : markers.filter((marker) => existsSync17(join26(repo, marker)));
14272
14586
  const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
14273
14587
  id: finding2.id,
14274
14588
  title: finding2.title,
@@ -14286,8 +14600,8 @@ function describeSubsystems(repo, findings) {
14286
14600
  function describeNotebook(repo, registryPath) {
14287
14601
  try {
14288
14602
  const registry = loadProjectRegistry(registryPath);
14289
- const project = Object.values(registry.projects).find((entry) => resolve20(entry.repo_path) === resolve20(repo));
14290
- const manifest = existsSync17(join25(repo, ".project.json")) ? JSON.parse(readFileSync18(join25(repo, ".project.json"), "utf8")) : void 0;
14603
+ const project = Object.values(registry.projects).find((entry) => resolve21(entry.repo_path) === resolve21(repo));
14604
+ const manifest = existsSync17(join26(repo, ".project.json")) ? JSON.parse(readFileSync19(join26(repo, ".project.json"), "utf8")) : void 0;
14291
14605
  const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
14292
14606
  if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
14293
14607
  const config = loadEffectiveNotebookConfig(repo, registryPath);
@@ -14305,7 +14619,7 @@ function describeNotebook(repo, registryPath) {
14305
14619
  }
14306
14620
  }
14307
14621
  function describeConfigFiles(repo) {
14308
- return CONFIG_FILES.filter((spec) => existsSync17(join25(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
14622
+ return CONFIG_FILES.filter((spec) => existsSync17(join26(repo, spec.path))).map((spec) => ({ path: spec.path, purpose: spec.purpose, subsystem: spec.subsystem }));
14309
14623
  }
14310
14624
  function describeNextSteps(description, findings) {
14311
14625
  const steps = [];
@@ -14383,7 +14697,7 @@ function describeNextSteps(description, findings) {
14383
14697
  return steps;
14384
14698
  }
14385
14699
  async function describeProject(input = {}) {
14386
- const repo = resolve20(input.repoArg ?? process.cwd());
14700
+ const repo = resolve21(input.repoArg ?? process.cwd());
14387
14701
  if (!existsSync17(repo)) throw new Error(`Path does not exist: ${repo}`);
14388
14702
  if (!statSync6(repo).isDirectory()) throw new Error(`Not a directory: ${repo}`);
14389
14703
  const registryPath = input.registryPath ?? projectRegistryPath();
@@ -14572,7 +14886,7 @@ function validateExternalEffectConsent(input, options) {
14572
14886
  return selected;
14573
14887
  }
14574
14888
  function resolveTargetDir(targetDir) {
14575
- const dir = resolve21(targetDir ?? process.cwd());
14889
+ const dir = resolve22(targetDir ?? process.cwd());
14576
14890
  if (!existsSync18(dir)) {
14577
14891
  throw new Error(`Target directory does not exist: ${dir}`);
14578
14892
  }
@@ -14584,7 +14898,7 @@ function resolveTargetDir(targetDir) {
14584
14898
  function resolvePjanglerRoot3() {
14585
14899
  let dir = dirname17(fileURLToPath6(import.meta.url));
14586
14900
  while (dir !== dirname17(dir)) {
14587
- if (existsSync18(join26(dir, "package.json")) && existsSync18(join26(dir, "templates", "commonproject", "copier.yml"))) {
14901
+ if (existsSync18(join27(dir, "package.json")) && existsSync18(join27(dir, "templates", "commonproject", "copier.yml"))) {
14588
14902
  return dir;
14589
14903
  }
14590
14904
  dir = dirname17(dir);
@@ -14879,12 +15193,12 @@ server.registerTool(
14879
15193
  );
14880
15194
  const pjanglerRoot = resolvePjanglerRoot3();
14881
15195
  const projectSlug = validateSafePathSegment(input.projectSlug ?? slugify(input.projectName), "Project slug");
14882
- const explicitTargetDir = input.targetDir ? resolve21(input.targetDir) : void 0;
14883
- const parentDir = resolve21(input.parentDir ?? (explicitTargetDir ? dirname17(explicitTargetDir) : process.cwd()));
15196
+ const explicitTargetDir = input.targetDir ? resolve22(input.targetDir) : void 0;
15197
+ const parentDir = resolve22(input.parentDir ?? (explicitTargetDir ? dirname17(explicitTargetDir) : process.cwd()));
14884
15198
  if (!existsSync18(parentDir) || !statSync7(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
14885
15199
  const targetDir = resolveContainedPath(
14886
15200
  parentDir,
14887
- explicitTargetDir ?? join26(parentDir, projectSlug),
15201
+ explicitTargetDir ?? join27(parentDir, projectSlug),
14888
15202
  "Bootstrap target"
14889
15203
  );
14890
15204
  const overwrite = input.overwrite ?? input.force ?? false;
@@ -15007,7 +15321,7 @@ server.registerTool(
15007
15321
  registryPath: input.registryPath,
15008
15322
  force: input.force ?? false,
15009
15323
  overwrite: input.force ?? false,
15010
- scaffold: !(input.targetDir && existsSync18(join26(resolve21(input.targetDir), ".git")))
15324
+ scaffold: !(input.targetDir && existsSync18(join27(resolve22(input.targetDir), ".git")))
15011
15325
  });
15012
15326
  if (!input.apply) return asText(publicCompositeProjectResponse(publicProjectPlan(plan), plan));
15013
15327
  const preflight = await preflightProjectApply(plan, resolvePjanglerRoot3());