@delorenj/pjangler 1.5.13 → 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",
@@ -8434,7 +8662,7 @@ var BMAD_DIST_TAGS_TTL_MS = 60 * 60 * 1e3;
8434
8662
  var DEFAULT_BMAD_MODULES = ["bmm", "bmb", "cis"];
8435
8663
  var BMAD_INSTALL_TOOLS = SUPPORTED_BMAD_TOOLS;
8436
8664
  function manifestBmadModules(repoRoot) {
8437
- const manifestPath = join5(repoRoot, "_bmad", "_config", "manifest.yaml");
8665
+ const manifestPath = join7(repoRoot, "_bmad", "_config", "manifest.yaml");
8438
8666
  const raw = safeReadText2(manifestPath);
8439
8667
  if (raw === null) return { status: "absent" };
8440
8668
  try {
@@ -8443,14 +8671,16 @@ function manifestBmadModules(repoRoot) {
8443
8671
  return { status: "invalid", error: `${manifestPath} must define a modules array` };
8444
8672
  }
8445
8673
  const declared = [];
8674
+ let hasG33 = false;
8446
8675
  for (const entry of parsed.modules) {
8447
8676
  const name = typeof entry === "string" ? entry : entry && typeof entry === "object" ? entry.name : void 0;
8448
8677
  if (typeof name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) {
8449
8678
  return { status: "invalid", error: `${manifestPath} contains an invalid module entry` };
8450
8679
  }
8451
- 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);
8452
8682
  }
8453
- return { status: "valid", modules: Array.from(new Set(declared)) };
8683
+ return { status: "valid", modules: Array.from(new Set(declared)), hasG33 };
8454
8684
  } catch (error) {
8455
8685
  return {
8456
8686
  status: "invalid",
@@ -8458,24 +8688,24 @@ function manifestBmadModules(repoRoot) {
8458
8688
  };
8459
8689
  }
8460
8690
  }
8461
- function configuredBmadModules(repoRoot) {
8462
- const raw = safeReadText2(join5(repoRoot, "_bmad", "config.toml"));
8691
+ function configuredBmadModuleSelection(repoRoot) {
8692
+ const raw = safeReadText2(join7(repoRoot, "_bmad", "config.toml"));
8463
8693
  if (raw === null) return void 0;
8464
8694
  const modules = [...raw.matchAll(/^\[modules\.([A-Za-z0-9][A-Za-z0-9_-]*)\]\s*$/gm)].map((match) => match[1]);
8465
- return Array.from(new Set(modules));
8695
+ return { modules: Array.from(new Set(modules)).filter((name) => name !== "g33"), hasG33: modules.includes("g33") };
8466
8696
  }
8467
8697
  function selectedBmadModules(repoRoot) {
8468
8698
  const manifest = manifestBmadModules(repoRoot);
8469
8699
  if (manifest.status === "valid") return manifest.modules;
8470
8700
  if (manifest.status === "invalid") throw new Error(manifest.error);
8471
- return configuredBmadModules(repoRoot) ?? [...DEFAULT_BMAD_MODULES];
8701
+ return configuredBmadModuleSelection(repoRoot)?.modules ?? [...DEFAULT_BMAD_MODULES];
8472
8702
  }
8473
8703
  function requiredBmadSentinels(repoRoot, modules = selectedBmadModules(repoRoot)) {
8474
8704
  return [
8475
- join5("core", "config.yaml"),
8476
- join5("config.toml"),
8477
- join5("_config", "manifest.yaml"),
8478
- ...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"))
8479
8709
  ];
8480
8710
  }
8481
8711
  function canonicalBmadProjectName(repoRoot) {
@@ -8487,7 +8717,7 @@ function bmadProjectNameIssues(repoRoot) {
8487
8717
  const expected = canonicalBmadProjectName(repoRoot);
8488
8718
  const paths = [];
8489
8719
  const details = [];
8490
- const configToml = join5(repoRoot, "_bmad", "config.toml");
8720
+ const configToml = join7(repoRoot, "_bmad", "config.toml");
8491
8721
  const tomlText = safeReadText2(configToml);
8492
8722
  const tomlMatch = tomlText?.match(/^project_name\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m);
8493
8723
  let tomlName;
@@ -8502,16 +8732,16 @@ function bmadProjectNameIssues(repoRoot) {
8502
8732
  paths.push(configToml);
8503
8733
  details.push(`_bmad/config.toml project_name must be ${JSON.stringify(expected)}`);
8504
8734
  }
8505
- const bmadRoot = join5(repoRoot, "_bmad");
8506
- if (existsSync3(bmadRoot)) {
8735
+ const bmadRoot = join7(repoRoot, "_bmad");
8736
+ if (existsSync4(bmadRoot)) {
8507
8737
  let declared;
8508
8738
  try {
8509
8739
  declared = selectedBmadModules(repoRoot);
8510
8740
  } catch {
8511
- declared = readdirSync3(bmadRoot);
8741
+ declared = readdirSync4(bmadRoot);
8512
8742
  }
8513
8743
  for (const name of new Set(declared)) {
8514
- const configPath = join5(bmadRoot, name, "config.yaml");
8744
+ const configPath = join7(bmadRoot, name, "config.yaml");
8515
8745
  const raw = safeReadText2(configPath);
8516
8746
  if (raw === null) continue;
8517
8747
  let actual;
@@ -8531,7 +8761,7 @@ function bmadProjectNameIssues(repoRoot) {
8531
8761
  function isCurrentActivation(path, activations) {
8532
8762
  if (activations.owned.has(path)) return true;
8533
8763
  try {
8534
- 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;
8535
8765
  return activations.targets.has(realpathSync3(path));
8536
8766
  } catch {
8537
8767
  return false;
@@ -8544,25 +8774,25 @@ async function skillActivations(ctx) {
8544
8774
  function evictLegacyBmadPackState(ctx, changedFiles, activations) {
8545
8775
  const details = [];
8546
8776
  const skillDirs = [
8547
- join5(ctx.repoRoot, ".agents", "skills"),
8548
- ...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"))
8549
8779
  ];
8550
8780
  for (const dir of skillDirs) {
8551
8781
  const dirStat = lstatIfPresent(dir);
8552
8782
  if (!dirStat || dirStat.isSymbolicLink() || !dirStat.isDirectory()) continue;
8553
8783
  let names;
8554
8784
  try {
8555
- names = readdirSync3(dir);
8785
+ names = readdirSync4(dir);
8556
8786
  } catch {
8557
8787
  continue;
8558
8788
  }
8559
8789
  for (const name of names) {
8560
8790
  if (!name.startsWith(BMAD_SKILL_NAME_PREFIX)) continue;
8561
- const path = join5(dir, name);
8791
+ const path = join7(dir, name);
8562
8792
  if (!lstatIfPresent(path)?.isSymbolicLink()) continue;
8563
8793
  if (isCurrentActivation(path, activations)) continue;
8564
8794
  changedFiles.push(path);
8565
- details.push(`removed retired BMAD pack symlink ${relative4(ctx.repoRoot, path)}`);
8795
+ details.push(`removed retired BMAD pack symlink ${relative5(ctx.repoRoot, path)}`);
8566
8796
  if (!ctx.dryRun) unlinkSync3(path);
8567
8797
  }
8568
8798
  }
@@ -8570,7 +8800,7 @@ function evictLegacyBmadPackState(ctx, changedFiles, activations) {
8570
8800
  }
8571
8801
  function bmadInstallerInvocation(version = BMAD_INSTALLER_VERSION) {
8572
8802
  const explicit = process.env.PJ_BMAD_INSTALLER?.trim();
8573
- if (explicit) return { command: resolve5(explicit), prefixArgs: [] };
8803
+ if (explicit) return { command: resolve6(explicit), prefixArgs: [] };
8574
8804
  return {
8575
8805
  command: "npx",
8576
8806
  prefixArgs: ["-y", `${BMAD_NPM_PACKAGE}@${version}`]
@@ -8597,7 +8827,7 @@ function bmadInstallDisplay(repoRoot, modules = selectedBmadModules(repoRoot), v
8597
8827
  }
8598
8828
  function preflightBmadLifecycle(_ctx) {
8599
8829
  const invocation = bmadInstallerInvocation();
8600
- const probe = spawnSync3(invocation.command, [...invocation.prefixArgs, "--version"], {
8830
+ const probe = spawnSync4(invocation.command, [...invocation.prefixArgs, "--version"], {
8601
8831
  encoding: "utf8",
8602
8832
  timeout: 3e4
8603
8833
  });
@@ -8619,14 +8849,17 @@ ${probe.stderr ?? ""}`;
8619
8849
  }
8620
8850
  return { ok: true };
8621
8851
  }
8622
- 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 };
8623
8856
  const invocation = bmadInstallerInvocation(version);
8624
- 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" });
8625
8858
  if (result2.status !== 0) {
8626
8859
  const stdoutTail = result2.stdout?.trim().split("\n").slice(-12).join("\n");
8627
8860
  return { ok: false, error: result2.stderr?.trim() || stdoutTail || result2.error?.message || `installer exited ${result2.status}` };
8628
8861
  }
8629
- return { ok: true };
8862
+ return { ok: true, companion: await runG33Companion(repoRoot, false, "bmad-install") };
8630
8863
  }
8631
8864
  function bmadChannelTarget(distTags) {
8632
8865
  const next = distTags?.[BMAD_TARGET_CHANNEL];
@@ -8635,7 +8868,7 @@ function bmadChannelTarget(distTags) {
8635
8868
  return next ? { version: next, channel: BMAD_TARGET_CHANNEL } : void 0;
8636
8869
  }
8637
8870
  function readInstalledBmadVersion(repoRoot) {
8638
- const raw = safeReadText2(join5(repoRoot, "_bmad", "_config", "manifest.yaml"));
8871
+ const raw = safeReadText2(join7(repoRoot, "_bmad", "_config", "manifest.yaml"));
8639
8872
  if (!raw) return void 0;
8640
8873
  try {
8641
8874
  const parsed = YAML2.parse(raw);
@@ -8646,8 +8879,8 @@ function readInstalledBmadVersion(repoRoot) {
8646
8879
  }
8647
8880
  }
8648
8881
  function bmadCachePath(homeDir) {
8649
- const cacheRoot = process.env.XDG_CACHE_HOME?.trim() || join5(homeDir, ".cache");
8650
- 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");
8651
8884
  }
8652
8885
  function readBmadDistTagsCache(homeDir) {
8653
8886
  const raw = safeReadText2(bmadCachePath(homeDir));
@@ -8662,7 +8895,7 @@ function readBmadDistTagsCache(homeDir) {
8662
8895
  return void 0;
8663
8896
  }
8664
8897
  function fetchBmadDistTags() {
8665
- const result2 = spawnSync3("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
8898
+ const result2 = spawnSync4("npm", ["view", BMAD_NPM_PACKAGE, "dist-tags", "--json"], {
8666
8899
  encoding: "utf8",
8667
8900
  timeout: 8e3
8668
8901
  });
@@ -8814,8 +9047,8 @@ var UNSUPPORTED_BMAD_ROOTS = {
8814
9047
  ".zencoder": "zencoder"
8815
9048
  };
8816
9049
  function unsupportedRootAttestation(repoRoot, rootName) {
8817
- const root = join5(repoRoot, rootName);
8818
- const stat = lstatSync4(root);
9050
+ const root = join7(repoRoot, rootName);
9051
+ const stat = lstatSync6(root);
8819
9052
  if (!stat.isDirectory() || stat.isSymbolicLink()) return { safe: false, reason: `${rootName} is not a regular generated directory` };
8820
9053
  const installerTool = UNSUPPORTED_BMAD_ROOTS[rootName];
8821
9054
  if (!installedBmadTools(repoRoot).has(installerTool)) {
@@ -8834,8 +9067,8 @@ var OPTIONAL_TEMPLATE_SCRIPTS = [
8834
9067
  function optionalTemplateScriptIssues(ctx) {
8835
9068
  const issues = [];
8836
9069
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
8837
- const path = join5(ctx.repoRoot, rel);
8838
- if (!existsSync3(path)) continue;
9070
+ const path = join7(ctx.repoRoot, rel);
9071
+ if (!existsSync4(path)) continue;
8839
9072
  const expected = templateCommonProjectText(ctx, rel);
8840
9073
  if (expected === void 0) continue;
8841
9074
  if (safeReadText2(path) !== expected) issues.push(`${rel} is present but has drifted from the shipped template`);
@@ -8845,8 +9078,8 @@ function optionalTemplateScriptIssues(ctx) {
8845
9078
  function refreshOptionalTemplateScripts(ctx) {
8846
9079
  const changed = [];
8847
9080
  for (const rel of OPTIONAL_TEMPLATE_SCRIPTS) {
8848
- const path = join5(ctx.repoRoot, rel);
8849
- if (!existsSync3(path)) continue;
9081
+ const path = join7(ctx.repoRoot, rel);
9082
+ if (!existsSync4(path)) continue;
8850
9083
  const expected = templateCommonProjectText(ctx, rel);
8851
9084
  if (expected === void 0 || safeReadText2(path) === expected) continue;
8852
9085
  changed.push(path);
@@ -8863,14 +9096,14 @@ function createMiseChecks() {
8863
9096
  id: "mise.config-root",
8864
9097
  title: "mise config_root + AGENTS link hooks",
8865
9098
  audit: (ctx) => {
8866
- const misePath = join5(ctx.repoRoot, "mise.toml");
8867
- if (!existsSync3(misePath)) {
9099
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9100
+ if (!existsSync4(misePath)) {
8868
9101
  return { id: "mise.config-root", title: "mise config_root + AGENTS link hooks", status: "fail", summary: "mise.toml missing", details: [], fixable: true };
8869
9102
  }
8870
9103
  const text = readText(misePath);
8871
9104
  const details = [];
8872
- const linkAgentfilesPath = join5(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
8873
- 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");
8874
9107
  const toml = [];
8875
9108
  const manual = [];
8876
9109
  const parsed = parsedOrUndefined(text);
@@ -8917,10 +9150,10 @@ function createMiseChecks() {
8917
9150
  };
8918
9151
  },
8919
9152
  migrate: (ctx, finding2) => {
8920
- const path = join5(ctx.repoRoot, "mise.toml");
9153
+ const path = join7(ctx.repoRoot, "mise.toml");
8921
9154
  const changedFiles = [];
8922
9155
  const details = [];
8923
- if (!existsSync3(path)) {
9156
+ if (!existsSync4(path)) {
8924
9157
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
8925
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: [] };
8926
9159
  }
@@ -8936,7 +9169,7 @@ function createMiseChecks() {
8936
9169
  if (!ctx.dryRun) writeText(path, plan.text);
8937
9170
  text = plan.text;
8938
9171
  }
8939
- const linkAgentfilesPath = join5(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
9172
+ const linkAgentfilesPath = join7(ctx.repoRoot, ".mise", "scripts", "link-agentfiles.sh");
8940
9173
  const expectedScript = templateLinkAgentfilesScript(ctx);
8941
9174
  if (expectedScript === void 0) {
8942
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: [] };
@@ -8974,13 +9207,13 @@ function createMiseChecks() {
8974
9207
  title: "managed mise versioning block",
8975
9208
  audit: (ctx) => {
8976
9209
  const details = [];
8977
- const misePath = join5(ctx.repoRoot, "mise.toml");
8978
- const versioningPath = join5(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
8979
- 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");
8980
9213
  const text = safeReadText2(misePath);
8981
9214
  if (!text?.includes("# >>> mise-versioning >>>")) details.push("mise versioning managed block missing");
8982
- if (!existsSync3(versioningPath)) details.push(".mise/scripts/versioning.sh missing");
8983
- 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");
8984
9217
  const conf = safeReadText2(manifestPath);
8985
9218
  const script = safeReadText2(versioningPath);
8986
9219
  if (conf !== null && script !== null) {
@@ -9002,8 +9235,8 @@ function createMiseChecks() {
9002
9235
  migrate: (ctx, finding2) => {
9003
9236
  const changedFiles = [];
9004
9237
  const details = [];
9005
- const misePath = join5(ctx.repoRoot, "mise.toml");
9006
- if (!existsSync3(misePath)) {
9238
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9239
+ if (!existsSync4(misePath)) {
9007
9240
  if (ensureMiseTomlFromTemplate(ctx, changedFiles) === null) {
9008
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: [] };
9009
9242
  }
@@ -9020,7 +9253,7 @@ function createMiseChecks() {
9020
9253
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
9021
9254
  if (!ctx.dryRun) writeText(misePath, versioningPlan.text);
9022
9255
  }
9023
- const versioningPath = join5(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
9256
+ const versioningPath = join7(ctx.repoRoot, ".mise", "scripts", "versioning.sh");
9024
9257
  const expectedScript = templateVersioningScript(ctx);
9025
9258
  if (expectedScript === void 0) {
9026
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: [] };
@@ -9032,7 +9265,7 @@ function createMiseChecks() {
9032
9265
  chmodSync(versioningPath, 493);
9033
9266
  }
9034
9267
  }
9035
- const manifestPath = join5(ctx.repoRoot, ".mise", "version-files.conf");
9268
+ const manifestPath = join7(ctx.repoRoot, ".mise", "version-files.conf");
9036
9269
  const expectedManifest = templateVersionFilesConf(ctx, ctx.repoRoot);
9037
9270
  if (safeReadText2(manifestPath) !== expectedManifest) {
9038
9271
  changedFiles.push(manifestPath);
@@ -9062,7 +9295,7 @@ function planVersioningBlock(original) {
9062
9295
  let replaced = [...VERSIONING_TASK_NAMES];
9063
9296
  try {
9064
9297
  if (oldBlock) {
9065
- replaced = Object.keys(record(record(parseToml2(oldBlock)).tasks));
9298
+ replaced = Object.keys(record2(record2(parseToml2(oldBlock)).tasks));
9066
9299
  text = text.replace(blockPattern, VERSIONING_BLOCK);
9067
9300
  } else {
9068
9301
  const statements = scanToml(text);
@@ -9085,7 +9318,7 @@ function planVersioningBlock(original) {
9085
9318
  const verdict = checkRewrite(expected, text);
9086
9319
  return verdict.ok ? { text } : { text: original, refused: verdict.reason };
9087
9320
  }
9088
- var record = (value) => isTable(value) ? value : {};
9321
+ var record2 = (value) => isTable(value) ? value : {};
9089
9322
  var RETIRED_SKILL_SCRIPTS = [
9090
9323
  SYNC_SKILLS_SCRIPT_REL,
9091
9324
  PROVISION_PACKS_SCRIPT_REL,
@@ -9094,7 +9327,7 @@ var RETIRED_SKILL_SCRIPTS = [
9094
9327
  ".mise/scripts/unlink-project-skills-from-clis.sh"
9095
9328
  ];
9096
9329
  function retiredSkillsScripts(ctx) {
9097
- 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)));
9098
9331
  }
9099
9332
  function skillsWiringFindings(text) {
9100
9333
  if (text === null) return { fixable: ["mise.toml is missing; add the explicit skills:sync task"], manual: [] };
@@ -9107,26 +9340,26 @@ function skillsWiringFindings(text) {
9107
9340
  parsed = baseline.parsed;
9108
9341
  fixable.push("mise.toml repeats the close of a [hooks] array (an earlier pjangler run wrote it); migrate repairs it");
9109
9342
  }
9110
- const record2 = (value) => isTable(value) ? value : {};
9111
- 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]);
9112
9345
  const run = Array.isArray(task.run) && task.run.length === 1 ? task.run[0] : task.run;
9113
9346
  if (typeof run !== "string" || !/^skillex\s+sync\s+--scope\s+project\s+--project\s+(['"])\{\{config_root\}\}\1\s*$/.test(run)) {
9114
9347
  fixable.push("skills:sync must explicitly target {{config_root}} with the Node CLI");
9115
9348
  }
9116
- const pin = record2(task.tools)["npm:@delorenj/skillex"];
9117
- 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;
9118
9351
  if (pinned !== SKILLEX_TOOL_VERSION) {
9119
9352
  fixable.push(`skills:sync must pin @delorenj/skillex ${SKILLEX_TOOL_VERSION} in its own tools table`);
9120
9353
  } else if (typeof pin === "string") {
9121
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 }`);
9122
- } else if (record2(pin).allow_low_downloads !== true) {
9355
+ } else if (record3(pin).allow_low_downloads !== true) {
9123
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)`);
9124
9357
  }
9125
9358
  const retired = /sync-skills\.py|provision-(?:packs|bmad-skills)\.py|skills[:\-]provision/;
9126
9359
  const renames = new Map(RETIRED_TASK_RENAMES);
9127
9360
  const replaced = /* @__PURE__ */ new Set([...RETIRED_LINK_TASKS, LINK_AGENTFILES_TASK, SKILLS_SYNC_TASK]);
9128
9361
  for (const [name, entry] of Object.entries(tasks)) {
9129
- 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;
9130
9363
  if (replaced.has(renames.get(name) ?? name)) {
9131
9364
  if (!fixable.includes("Retire Python skill sync/provision tasks from mise.toml")) fixable.push("Retire Python skill sync/provision tasks from mise.toml");
9132
9365
  } else {
@@ -9154,7 +9387,7 @@ function createAgentHooksChecks() {
9154
9387
  title: "Skillex project skills",
9155
9388
  audit: async (ctx) => {
9156
9389
  const finding2 = await auditProjectSkills(ctx);
9157
- const mise = safeReadText2(join5(ctx.repoRoot, "mise.toml"));
9390
+ const mise = safeReadText2(join7(ctx.repoRoot, "mise.toml"));
9158
9391
  const wiring = skillsWiringIssues(mise);
9159
9392
  const retired = retiredSkillsScripts(ctx);
9160
9393
  const unsafe = retired.filter((path) => lstatIfPresent(path)?.isDirectory());
@@ -9171,7 +9404,7 @@ function createAgentHooksChecks() {
9171
9404
  const changedFiles = [];
9172
9405
  const details = [];
9173
9406
  const result2 = (status, summary) => ({ id: finding2.id, title: finding2.title, status, summary, changedFiles: [...new Set(changedFiles)], details });
9174
- let currentMise = safeReadText2(join5(ctx.repoRoot, "mise.toml"));
9407
+ let currentMise = safeReadText2(join7(ctx.repoRoot, "mise.toml"));
9175
9408
  if (currentMise === null) {
9176
9409
  const template = templateCommonProjectText(ctx, "mise.toml.jinja");
9177
9410
  if (template === void 0) return result2("blocked", "Generated-project mise template is unavailable");
@@ -9188,7 +9421,7 @@ function createAgentHooksChecks() {
9188
9421
  changedFiles.push(path);
9189
9422
  if (!ctx.dryRun) unlinkSync3(path);
9190
9423
  }
9191
- const misePath = join5(ctx.repoRoot, "mise.toml");
9424
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9192
9425
  const nextMise = upsertLinkAgentfilesBlock(currentMise, ctx);
9193
9426
  if (safeReadText2(misePath) !== nextMise) {
9194
9427
  changedFiles.push(misePath);
@@ -9214,9 +9447,9 @@ function createAgentHooksChecks() {
9214
9447
  id: "sot.agent-symlinks",
9215
9448
  title: "AGENTS/CLAUDE/GEMINI symlink contract",
9216
9449
  audit: (ctx) => {
9217
- const agentsPath = join5(ctx.repoRoot, "AGENTS.md");
9218
- if (!existsSync3(agentsPath)) {
9219
- 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)));
9220
9453
  if (fallbackSources.length === 0) {
9221
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 };
9222
9455
  }
@@ -9231,7 +9464,7 @@ function createAgentHooksChecks() {
9231
9464
  }
9232
9465
  const details = [];
9233
9466
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
9234
- const full = join5(ctx.repoRoot, file);
9467
+ const full = join7(ctx.repoRoot, file);
9235
9468
  const target = readSymlinkTarget(full);
9236
9469
  if (target !== "AGENTS.md") details.push(`${file} should be a symlink to AGENTS.md`);
9237
9470
  }
@@ -9255,7 +9488,7 @@ function createAgentHooksChecks() {
9255
9488
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "AGENTS.md missing; cannot derive canonical agent file", changedFiles, details: [bootstrap.blocked] };
9256
9489
  }
9257
9490
  for (const file of ["CLAUDE.md", "GEMINI.md"]) {
9258
- const full = join5(ctx.repoRoot, file);
9491
+ const full = join7(ctx.repoRoot, file);
9259
9492
  const result2 = ensureSymlink(full, "AGENTS.md", ctx.dryRun);
9260
9493
  if (result2.blocked) blockedDetails.push(result2.blocked);
9261
9494
  if (result2.changed) changedFiles.push(full);
@@ -9282,7 +9515,7 @@ function createProjectJsonChecks() {
9282
9515
  const changedFiles = [];
9283
9516
  const blockedDetails = [];
9284
9517
  const droppedDetails = [];
9285
- const path = join5(ctx.repoRoot, ".project.json");
9518
+ const path = join7(ctx.repoRoot, ".project.json");
9286
9519
  const existing = readProjectJson(ctx) ?? {};
9287
9520
  const canonical = canonicalProjectJson(ctx);
9288
9521
  const preservedDetails = [];
@@ -9314,11 +9547,11 @@ function createProjectJsonChecks() {
9314
9547
  changedFiles.push(path);
9315
9548
  if (!ctx.dryRun) writeText(path, expected);
9316
9549
  }
9317
- const planeJson = join5(ctx.repoRoot, ".plane.json");
9318
- if (existsSync3(planeJson)) {
9550
+ const planeJson = join7(ctx.repoRoot, ".plane.json");
9551
+ if (existsSync4(planeJson)) {
9319
9552
  const backup = `${planeJson}.migrated-backup`;
9320
- if (existsSync3(backup)) {
9321
- 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`);
9322
9555
  } else {
9323
9556
  changedFiles.push(backup);
9324
9557
  if (!ctx.dryRun) renameSync2(planeJson, backup);
@@ -9346,10 +9579,10 @@ function createMiseOpInjectChecks() {
9346
9579
  const details = [];
9347
9580
  let envOpNeedsHands = false;
9348
9581
  const optedOut = envMaterializationOptedOut(ctx);
9349
- const envOpPath = join5(ctx.repoRoot, ".env.op");
9350
- const envOpExists = existsSync3(envOpPath);
9582
+ const envOpPath = join7(ctx.repoRoot, ".env.op");
9583
+ const envOpExists = existsSync4(envOpPath);
9351
9584
  const envOp = envOpExists ? readText(envOpPath) : void 0;
9352
- const gitignore = safeReadText2(join5(ctx.repoRoot, ".gitignore"));
9585
+ const gitignore = safeReadText2(join7(ctx.repoRoot, ".gitignore"));
9353
9586
  if (!envOpExists) {
9354
9587
  details.push(".env.op missing");
9355
9588
  } else if (!envOp?.trim()) {
@@ -9369,7 +9602,7 @@ function createMiseOpInjectChecks() {
9369
9602
  if (!gitignore?.includes(".env\n") && !gitignore?.includes(".env\r\n")) details.push(".gitignore should ignore .env");
9370
9603
  if (!gitignore?.includes(".env.*")) details.push(".gitignore should ignore .env.*");
9371
9604
  if (!gitignore?.includes("!.env.op")) details.push(".gitignore should unignore .env.op");
9372
- const misePath = join5(ctx.repoRoot, "mise.toml");
9605
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9373
9606
  const miseText = safeReadText2(misePath);
9374
9607
  if (!miseText) {
9375
9608
  details.push("mise.toml missing for .env materialization hook");
@@ -9392,15 +9625,15 @@ function createMiseOpInjectChecks() {
9392
9625
  else if (plan.text !== miseText && !mise.length) mise.push(describeRewrite(miseText, plan.text));
9393
9626
  details.push(...mise);
9394
9627
  }
9395
- const materializePath = join5(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9628
+ const materializePath = join7(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9396
9629
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
9397
9630
  if (optedOut) {
9398
- 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`);
9399
9632
  } else if (!expectedMaterializer) {
9400
9633
  details.push("pjangler package is missing the managed materialize-env.sh source");
9401
9634
  } else if (safeReadText2(materializePath) !== expectedMaterializer) {
9402
9635
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} missing or drifted`);
9403
- } else if ((lstatSync4(materializePath).mode & 73) === 0) {
9636
+ } else if ((lstatSync6(materializePath).mode & 73) === 0) {
9404
9637
  details.push(`${MATERIALIZE_ENV_SCRIPT_REL} is not executable`);
9405
9638
  }
9406
9639
  return {
@@ -9416,13 +9649,13 @@ function createMiseOpInjectChecks() {
9416
9649
  const changedFiles = [];
9417
9650
  const details = [];
9418
9651
  let envOpNeedsHands = false;
9419
- const envOpPath = join5(ctx.repoRoot, ".env.op");
9420
- const canonicalEnvOpPath = join5(ctx.pjanglerRoot, "templates", "commonproject", "template", ".env.op");
9421
- 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)) {
9422
9655
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing the neutral .env.op template", changedFiles: [], details: [] };
9423
9656
  }
9424
9657
  const canonicalEnvOp = readText(canonicalEnvOpPath);
9425
- if (!existsSync3(envOpPath) || !readText(envOpPath).trim()) {
9658
+ if (!existsSync4(envOpPath) || !readText(envOpPath).trim()) {
9426
9659
  changedFiles.push(envOpPath);
9427
9660
  if (!ctx.dryRun) writeText(envOpPath, canonicalEnvOp);
9428
9661
  } else {
@@ -9446,7 +9679,7 @@ function createMiseOpInjectChecks() {
9446
9679
  }
9447
9680
  }
9448
9681
  }
9449
- const gitignorePath = join5(ctx.repoRoot, ".gitignore");
9682
+ const gitignorePath = join7(ctx.repoRoot, ".gitignore");
9450
9683
  const gitignore = safeReadText2(gitignorePath) ?? "";
9451
9684
  const requiredBlock = `# Secrets \u2014 .env is materialized from .env.op by \`op inject\` on mise enter,
9452
9685
  # staged through a mktemp file and moved into place only on success.
@@ -9459,7 +9692,7 @@ function createMiseOpInjectChecks() {
9459
9692
  changedFiles.push(gitignorePath);
9460
9693
  if (!ctx.dryRun) writeText(gitignorePath, `${gitignore.replace(/\s*$/, "")}${gitignore.trim() ? "\n\n" : ""}${requiredBlock}`);
9461
9694
  }
9462
- const misePath = join5(ctx.repoRoot, "mise.toml");
9695
+ const misePath = join7(ctx.repoRoot, "mise.toml");
9463
9696
  let currentMise = safeReadText2(misePath);
9464
9697
  if (currentMise === null) {
9465
9698
  const initialized = ensureMiseTomlFromTemplate(ctx, changedFiles);
@@ -9476,16 +9709,16 @@ function createMiseOpInjectChecks() {
9476
9709
  if (!changedFiles.includes(misePath)) changedFiles.push(misePath);
9477
9710
  if (!ctx.dryRun) writeText(misePath, opPlan.text);
9478
9711
  }
9479
- const materializePath = join5(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9712
+ const materializePath = join7(ctx.repoRoot, MATERIALIZE_ENV_SCRIPT_REL);
9480
9713
  const expectedMaterializer = templateMaterializeEnvScript(ctx);
9481
9714
  if (optedOut) {
9482
- if (existsSync3(materializePath)) {
9715
+ if (existsSync4(materializePath)) {
9483
9716
  changedFiles.push(materializePath);
9484
9717
  if (!ctx.dryRun) rmSync2(materializePath);
9485
9718
  }
9486
9719
  } else if (!expectedMaterializer) {
9487
9720
  return { id: finding2.id, title: finding2.title, status: "blocked", summary: "pjangler package is missing materialize-env.sh", changedFiles: [], details: [] };
9488
- } 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) {
9489
9722
  changedFiles.push(materializePath);
9490
9723
  if (!ctx.dryRun) {
9491
9724
  writeText(materializePath, expectedMaterializer);
@@ -9547,7 +9780,7 @@ function createProjectProvenanceChecks() {
9547
9780
  title: ".copier-answers.yml provenance + drift report",
9548
9781
  audit: (ctx) => {
9549
9782
  const details = [];
9550
- const path = join5(ctx.repoRoot, ".copier-answers.yml");
9783
+ const path = join7(ctx.repoRoot, ".copier-answers.yml");
9551
9784
  const text = safeReadText2(path);
9552
9785
  const project = readProjectJson(ctx);
9553
9786
  if (!text) {
@@ -9575,11 +9808,11 @@ function createProjectProvenanceChecks() {
9575
9808
  migrate: (ctx, finding2) => {
9576
9809
  const changedFiles = [];
9577
9810
  const project = canonicalProjectJson(ctx);
9578
- const path = join5(ctx.repoRoot, ".copier-answers.yml");
9811
+ const path = join7(ctx.repoRoot, ".copier-answers.yml");
9579
9812
  const current = safeReadText2(path);
9580
9813
  const existing = current ? YAML2.parseDocument(current) : void 0;
9581
9814
  const document = existing && !existing.errors.length && YAML2.isMap(existing.contents) ? existing : new YAML2.Document({});
9582
- document.set("_src_path", join5(ctx.pjanglerRoot, "templates", "commonproject"));
9815
+ document.set("_src_path", join7(ctx.pjanglerRoot, "templates", "commonproject"));
9583
9816
  document.set("project_description", String(project.project_description));
9584
9817
  document.set("project_name", String(project.project_name));
9585
9818
  document.set("ticket_provider", String(project.ticket_provider?.type ?? "plane"));
@@ -9604,9 +9837,9 @@ ${body}`;
9604
9837
  }
9605
9838
  function supportedCliProjectionIssues(repoRoot, plan) {
9606
9839
  const issues = [];
9607
- const managedSkills = join5(repoRoot, ".agents", "skills");
9840
+ const managedSkills = join7(repoRoot, ".agents", "skills");
9608
9841
  for (const rootName of SUPPORTED_CLI_ROOTS) {
9609
- const root = join5(repoRoot, rootName);
9842
+ const root = join7(repoRoot, rootName);
9610
9843
  const rootStat = lstatIfPresent(root);
9611
9844
  if (!rootStat) {
9612
9845
  issues.push(`${rootName} missing`);
@@ -9616,7 +9849,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9616
9849
  issues.push(`${rootName} must be a real configuration directory`);
9617
9850
  continue;
9618
9851
  }
9619
- const skills = join5(root, "skills");
9852
+ const skills = join7(root, "skills");
9620
9853
  const skillsStat = lstatIfPresent(skills);
9621
9854
  if (!skillsStat) {
9622
9855
  issues.push(`${rootName}/skills missing`);
@@ -9625,7 +9858,7 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9625
9858
  let projectedSkills = skills;
9626
9859
  if (skillsStat.isSymbolicLink()) {
9627
9860
  try {
9628
- readlinkSync3(skills);
9861
+ readlinkSync4(skills);
9629
9862
  } catch {
9630
9863
  issues.push(`${rootName}/skills is an unreadable symlink`);
9631
9864
  continue;
@@ -9653,9 +9886,9 @@ function supportedCliProjectionIssues(repoRoot, plan) {
9653
9886
  issues.push(`${rootName}/skills is not a directory`);
9654
9887
  continue;
9655
9888
  }
9656
- const hasGeneratedSkill = existsSync3(projectedSkills) && readdirSync3(projectedSkills).some((name) => {
9657
- const skillFile = join5(projectedSkills, name, "SKILL.md");
9658
- 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();
9659
9892
  });
9660
9893
  if (!hasGeneratedSkill) issues.push(`${rootName}/skills contains no BMAD skill configuration`);
9661
9894
  }
@@ -9681,7 +9914,7 @@ var LEGACY_CLI_GITIGNORE_LINES = /* @__PURE__ */ new Set([
9681
9914
  "# some CLIs get a real directory here and some get a symlink."
9682
9915
  ]);
9683
9916
  function supportedCliGitignoreIssues(repoRoot) {
9684
- const lines = (safeReadText2(join5(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
9917
+ const lines = (safeReadText2(join7(repoRoot, ".gitignore")) ?? "").split(/\r?\n/);
9685
9918
  const legacy = lines.filter((line) => LEGACY_CLI_GITIGNORE_LINES.has(line.trim()));
9686
9919
  return [
9687
9920
  ...legacy.length ? [`.gitignore contains ${legacy.length} legacy client-root override line(s); only .agents is canonical`] : [],
@@ -9689,7 +9922,7 @@ function supportedCliGitignoreIssues(repoRoot) {
9689
9922
  ];
9690
9923
  }
9691
9924
  function ensureSupportedCliGitignore(ctx) {
9692
- const path = join5(ctx.repoRoot, ".gitignore");
9925
+ const path = join7(ctx.repoRoot, ".gitignore");
9693
9926
  const current = safeReadText2(path) ?? "";
9694
9927
  const kept = current.split(/\r?\n/).filter((line) => {
9695
9928
  const trimmed2 = line.trim();
@@ -9727,10 +9960,10 @@ function createBmadChecks() {
9727
9960
  fixable: false
9728
9961
  };
9729
9962
  }
9730
- const targetRoot = join5(ctx.repoRoot, "_bmad");
9731
- 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];
9732
9965
  const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
9733
- const missing = sentinels.filter((file) => !existsSync3(join5(targetRoot, file)));
9966
+ const missing = sentinels.filter((file) => !existsSync4(join7(targetRoot, file)));
9734
9967
  const projectNameIssues = bmadProjectNameIssues(ctx.repoRoot);
9735
9968
  const details = [
9736
9969
  ...missing.map((file) => `_bmad/${file}`),
@@ -9758,10 +9991,21 @@ function createBmadChecks() {
9758
9991
  details: [manifestSelection.error]
9759
9992
  };
9760
9993
  }
9761
- 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
+ };
9762
10005
  if (ctx.dryRun) {
9763
10006
  const sentinels = requiredBmadSentinels(ctx.repoRoot, selectedModules);
9764
- 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)));
9765
10009
  changedFiles.push(...bmadProjectNameIssues(ctx.repoRoot).paths);
9766
10010
  return {
9767
10011
  id: finding2.id,
@@ -9770,16 +10014,18 @@ function createBmadChecks() {
9770
10014
  summary: changedFiles.length ? "Would run non-interactive bmad-method install" : "No changes required",
9771
10015
  changedFiles,
9772
10016
  details: [
9773
- `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules)}`
9774
- ]
10017
+ `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules)}`,
10018
+ ...companionDetails(companion)
10019
+ ],
10020
+ bmadCompanion: companion
9775
10021
  };
9776
10022
  }
9777
10023
  const expectedChangedPaths = [
9778
- ...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)),
9779
10025
  ...bmadProjectNameIssues(ctx.repoRoot).paths
9780
10026
  ];
9781
10027
  const evicted = evictLegacyBmadPackState(ctx, changedFiles, await skillActivations(ctx));
9782
- const install = runBmadInstall(ctx.repoRoot, selectedModules);
10028
+ const install = await runBmadInstall(ctx.repoRoot, selectedModules);
9783
10029
  if (!install.ok) {
9784
10030
  return {
9785
10031
  id: finding2.id,
@@ -9790,14 +10036,15 @@ function createBmadChecks() {
9790
10036
  details: [...evicted, install.error ?? "Unknown error"]
9791
10037
  };
9792
10038
  }
9793
- changedFiles.push(...expectedChangedPaths.filter(existsSync3));
10039
+ changedFiles.push(...expectedChangedPaths.filter(existsSync4), ...install.companion?.changedFiles ?? []);
9794
10040
  return {
9795
10041
  id: finding2.id,
9796
10042
  title: finding2.title,
9797
10043
  status: changedFiles.length ? "applied" : "noop",
9798
10044
  summary: changedFiles.length ? "Installed BMAD scaffold via bmad-method" : "No changes required",
9799
10045
  changedFiles,
9800
- details: evicted
10046
+ details: [...evicted, ...install.companion ? companionDetails(install.companion) : []],
10047
+ bmadCompanion: install.companion
9801
10048
  };
9802
10049
  }
9803
10050
  },
@@ -9811,7 +10058,7 @@ function createBmadChecks() {
9811
10058
  id: "bmad.version",
9812
10059
  title: "BMAD version currency",
9813
10060
  status: "skip",
9814
- 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",
9815
10062
  details: [],
9816
10063
  fixable: false
9817
10064
  };
@@ -9899,7 +10146,7 @@ function createBmadChecks() {
9899
10146
  }
9900
10147
  const installed = readInstalledBmadVersion(ctx.repoRoot);
9901
10148
  const available = ctx.bmadVersionPin?.trim() ?? bmadChannelTarget(resolveBmadDistTags(ctx.homeDir)?.distTags)?.version;
9902
- const manifestPath = join5(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
10149
+ const manifestPath = join7(ctx.repoRoot, "_bmad", "_config", "manifest.yaml");
9903
10150
  const manifestSelection = manifestBmadModules(ctx.repoRoot);
9904
10151
  if (manifestSelection.status === "invalid") {
9905
10152
  return {
@@ -9911,8 +10158,19 @@ function createBmadChecks() {
9911
10158
  details: [manifestSelection.error]
9912
10159
  };
9913
10160
  }
9914
- 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
+ };
9915
10172
  if (ctx.dryRun) {
10173
+ const companion = await runG33Companion(ctx.repoRoot, true, "bmad-install");
9916
10174
  return {
9917
10175
  id: finding2.id,
9918
10176
  title: finding2.title,
@@ -9920,11 +10178,13 @@ function createBmadChecks() {
9920
10178
  summary: `Would upgrade BMAD ${installed ?? "?"} -> ${available ?? BMAD_TARGET_CHANNEL}`,
9921
10179
  changedFiles: [manifestPath],
9922
10180
  details: [
9923
- `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL)}`
9924
- ]
10181
+ `Would run: ${bmadInstallDisplay(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL)}`,
10182
+ ...companionDetails(companion)
10183
+ ],
10184
+ bmadCompanion: companion
9925
10185
  };
9926
10186
  }
9927
- const install = runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL);
10187
+ const install = await runBmadInstall(ctx.repoRoot, selectedModules, available ?? BMAD_TARGET_CHANNEL);
9928
10188
  if (!install.ok) {
9929
10189
  return {
9930
10190
  id: finding2.id,
@@ -9941,7 +10201,8 @@ function createBmadChecks() {
9941
10201
  const upgraded = Boolean(nowInstalled && installed && compareBmadVersions(nowInstalled, installed) > 0);
9942
10202
  const changed = Array.from(/* @__PURE__ */ new Set([
9943
10203
  ...upgraded ? [manifestPath] : [],
9944
- ...evictedChanges
10204
+ ...evictedChanges,
10205
+ ...install.companion?.changedFiles ?? []
9945
10206
  ]));
9946
10207
  return {
9947
10208
  id: finding2.id,
@@ -9949,7 +10210,8 @@ function createBmadChecks() {
9949
10210
  status: changed.length ? "applied" : "noop",
9950
10211
  summary: upgraded ? `Upgraded BMAD ${installed} -> ${nowInstalled}` : `BMAD reinstalled (${nowInstalled ?? "?"})`,
9951
10212
  changedFiles: changed,
9952
- details: evicted
10213
+ details: [...evicted, ...install.companion ? companionDetails(install.companion) : []],
10214
+ bmadCompanion: install.companion
9953
10215
  };
9954
10216
  }
9955
10217
  },
@@ -9958,7 +10220,7 @@ function createBmadChecks() {
9958
10220
  title: "Supported BMAD CLI projection roots",
9959
10221
  audit: (ctx) => {
9960
10222
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
9961
- const present = unsupportedNames.filter((name) => existsSync3(join5(ctx.repoRoot, name)));
10223
+ const present = unsupportedNames.filter((name) => existsSync4(join7(ctx.repoRoot, name)));
9962
10224
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
9963
10225
  const plan = planSkillRoots(ctx.repoRoot);
9964
10226
  const supportedIssues = supportedCliProjectionIssues(ctx.repoRoot, plan);
@@ -9980,7 +10242,7 @@ function createBmadChecks() {
9980
10242
  },
9981
10243
  migrate: (ctx, finding2) => {
9982
10244
  const unsupportedNames = Object.keys(UNSUPPORTED_BMAD_ROOTS);
9983
- const present = unsupportedNames.filter((name) => existsSync3(join5(ctx.repoRoot, name)));
10245
+ const present = unsupportedNames.filter((name) => existsSync4(join7(ctx.repoRoot, name)));
9984
10246
  const attestations = present.map((name) => ({ name, ...unsupportedRootAttestation(ctx.repoRoot, name) }));
9985
10247
  const blocked = attestations.filter((entry) => !entry.safe);
9986
10248
  if (blocked.length) {
@@ -10005,7 +10267,7 @@ function createBmadChecks() {
10005
10267
  };
10006
10268
  }
10007
10269
  const gitignoreChanges = ensureSupportedCliGitignore(ctx);
10008
- const removedRoots = attestations.map((entry) => join5(ctx.repoRoot, entry.name));
10270
+ const removedRoots = attestations.map((entry) => join7(ctx.repoRoot, entry.name));
10009
10271
  if (!ctx.dryRun) for (const path of removedRoots) rmSync2(path, { recursive: true, force: true });
10010
10272
  const changedFiles = [.../* @__PURE__ */ new Set([...projectionResult.changedFiles, ...gitignoreChanges, ...removedRoots])].sort();
10011
10273
  return {
@@ -10064,11 +10326,11 @@ function linkedPlaneBoard(ctx) {
10064
10326
  return { boardId, workspace };
10065
10327
  }
10066
10328
  function runPx(args, ctx) {
10067
- const probe = spawnSync3("which", ["px"], { encoding: "utf8" });
10329
+ const probe = spawnSync4("which", ["px"], { encoding: "utf8" });
10068
10330
  if (probe.status !== 0) {
10069
10331
  return { error: "px not found on PATH \u2014 install Pilot to manage board schemas" };
10070
10332
  }
10071
- const result2 = spawnSync3("px", args, {
10333
+ const result2 = spawnSync4("px", args, {
10072
10334
  cwd: ctx.repoRoot,
10073
10335
  encoding: "utf8",
10074
10336
  shell: false,
@@ -10258,11 +10520,12 @@ var AgentHooksRecipe = class extends Recipe {
10258
10520
 
10259
10521
  // src/recipes/BmadRecipe.ts
10260
10522
  var BmadRecipe = class extends Recipe {
10261
- checks = createBmadChecks();
10523
+ requiredChecks = createBmadChecks();
10524
+ checks = [...this.requiredChecks, createG33CompanionCheck()];
10262
10525
  metadata = {
10263
10526
  id: "bmad",
10264
10527
  name: "bmad",
10265
- description: "BMAD methodology and the supported CLI projections",
10528
+ description: "BMAD methodology, supported CLI projections, and the optional owner-provided g33 companion",
10266
10529
  dependencies: ["agent-hooks"],
10267
10530
  commands: [],
10268
10531
  publicRuleIds: this.checks.map((check) => check.id)
@@ -10270,8 +10533,31 @@ var BmadRecipe = class extends Recipe {
10270
10533
  constructor(context) {
10271
10534
  super(context);
10272
10535
  }
10273
- init(ctx, _input) {
10274
- 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
+ };
10275
10561
  }
10276
10562
  printNextSteps() {
10277
10563
  console.log(`BMAD lifecycle initialized for the ${SUPPORTED_CLIS.length} supported CLIs.`);
@@ -10595,24 +10881,24 @@ var NodeRecipe = class extends Recipe {
10595
10881
 
10596
10882
  // src/recipes/ProjectRecipe.ts
10597
10883
  init_project();
10598
- import { spawnSync as spawnSync11 } from "node:child_process";
10599
- import { existsSync as existsSync14, lstatSync as lstatSync12, readFileSync as readFileSync14, rmSync as rmSync5 } from "node:fs";
10600
- 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";
10601
10887
  init_tree_diff();
10602
10888
 
10603
10889
  // src/recipes/NotebookRecipe.ts
10604
10890
  init_project();
10605
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
10606
- 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";
10607
10893
 
10608
10894
  // src/notebook/checks.ts
10609
10895
  init_config();
10610
10896
  init_notes();
10611
10897
  init_output();
10612
10898
  init_state();
10613
- import { spawnSync as spawnSync5 } from "node:child_process";
10614
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
10615
- 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";
10616
10902
  var NOTEBOOK_RULE_IDS = [
10617
10903
  "notebook.configuration",
10618
10904
  "notebook.binding",
@@ -10629,10 +10915,10 @@ function result(check, status, summary, changedFiles = [], details = []) {
10629
10915
  return { id: check.id, title: check.title, status, summary, changedFiles, details };
10630
10916
  }
10631
10917
  function manifestNotebook(repo) {
10632
- const path = join13(repo, ".project.json");
10918
+ const path = join14(repo, ".project.json");
10633
10919
  if (!existsSync10(path)) return null;
10634
10920
  try {
10635
- const parsed = JSON.parse(readFileSync11(path, "utf8"));
10921
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
10636
10922
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
10637
10923
  const notebook = parsed.notebook;
10638
10924
  return notebook && typeof notebook === "object" && !Array.isArray(notebook) ? notebook : null;
@@ -10861,10 +11147,10 @@ function captureAudit(ctx) {
10861
11147
  function overviewMigrate(ctx, current) {
10862
11148
  if (ctx.dryRun) return result(current, "applied", "Would reconcile drifted Project Overview note");
10863
11149
  if (!ctx.live) return result(current, "blocked", "Reconciling the remote Overview note requires --live");
10864
- const pjanglerEntry = ctx.pjanglerRoot ? join13(ctx.pjanglerRoot, "dist", "index.js") : null;
11150
+ const pjanglerEntry = ctx.pjanglerRoot ? join14(ctx.pjanglerRoot, "dist", "index.js") : null;
10865
11151
  const entry = pjanglerEntry && existsSync10(pjanglerEntry) ? pjanglerEntry : "pj";
10866
11152
  const cmd = entry === "pj" ? ["pj"] : [process.execPath, entry];
10867
- 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], {
10868
11154
  encoding: "utf8",
10869
11155
  timeout: 1e4,
10870
11156
  env: { ...process.env, ...ctx.registryPath ? { PJ_PROJECT_REGISTRY: ctx.registryPath } : {} }
@@ -10909,7 +11195,7 @@ init_project();
10909
11195
  init_config();
10910
11196
  init_open_notebook_client();
10911
11197
  init_notes();
10912
- import { resolve as resolve16 } from "node:path";
11198
+ import { resolve as resolve17 } from "node:path";
10913
11199
 
10914
11200
  // src/notebook/journal-key.ts
10915
11201
  init_types();
@@ -10943,18 +11229,18 @@ function journalNoteOperationId(period, key) {
10943
11229
  init_notes();
10944
11230
  init_git_evidence();
10945
11231
  init_types();
10946
- import { spawnSync as spawnSync7 } from "node:child_process";
11232
+ import { spawnSync as spawnSync8 } from "node:child_process";
10947
11233
  import { realpathSync as realpathSync8 } from "node:fs";
10948
- 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";
10949
11235
  var DEFAULT_OVERVIEW_REFERENCES = [".project.json", "README.md", "AGENTS.md", "CLAUDE.md", "docs/architecture.md"];
10950
11236
  function git2(repo, args, timeout) {
10951
- 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 });
10952
11238
  return { ok: result2.status === 0, stdout: result2.stdout?.trim() ?? "" };
10953
11239
  }
10954
11240
  function normalizedReference(repo, value) {
10955
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}`);
10956
11242
  const root = realpathSync8(repo);
10957
- const candidate = resolve13(root, value);
11243
+ const candidate = resolve14(root, value);
10958
11244
  const rel = relative10(root, candidate).split(sep5).join("/");
10959
11245
  if (!rel || rel === ".." || rel.startsWith("../")) throw new NotebookError("INVALID_INPUT", `Overview reference escapes the repository: ${value}`);
10960
11246
  return rel.normalize("NFC");
@@ -11087,21 +11373,21 @@ init_git_evidence();
11087
11373
  init_notes();
11088
11374
  init_state();
11089
11375
  init_types();
11090
- import { spawn, spawnSync as spawnSync8 } from "node:child_process";
11376
+ import { spawn, spawnSync as spawnSync9 } from "node:child_process";
11091
11377
  import { createHash as createHash8, randomUUID as randomUUID5 } from "node:crypto";
11092
- 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";
11093
- 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";
11094
11380
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11095
11381
  function bundledSkillCandidates() {
11096
11382
  const candidates = [];
11097
11383
  let cursor = dirname13(fileURLToPath3(import.meta.url));
11098
11384
  for (let depth = 0; depth < 8; depth++) {
11099
- 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"));
11100
11386
  const parent = dirname13(cursor);
11101
11387
  if (parent === cursor) break;
11102
11388
  cursor = parent;
11103
11389
  }
11104
- return [...new Set(candidates.map((candidate) => resolve14(candidate)))];
11390
+ return [...new Set(candidates.map((candidate) => resolve15(candidate)))];
11105
11391
  }
11106
11392
  function safeSkillRelativePath(value) {
11107
11393
  if (!value || value.startsWith("/") || value.includes("\0")) return false;
@@ -11114,14 +11400,14 @@ function assertOwnedSkillTree(source) {
11114
11400
  assertNoSymlinkComponents(source);
11115
11401
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
11116
11402
  const walk = (directory) => {
11117
- const directoryStat = lstatSync11(directory);
11403
+ const directoryStat = lstatSync12(directory);
11118
11404
  if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-directory component");
11119
11405
  if (uid !== void 0 && directoryStat.uid !== uid) throw new NotebookError("CONFLICT", "Project Notebook skill source is not owned by the current user");
11120
11406
  if (directoryStat.mode & 3586) throw new NotebookError("CONFLICT", "Project Notebook skill source has unsafe directory mode bits");
11121
11407
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
11122
11408
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
11123
- const path = join16(directory, entry.name);
11124
- const stat = lstatSync11(path);
11409
+ const path = join17(directory, entry.name);
11410
+ const stat = lstatSync12(path);
11125
11411
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a symlink");
11126
11412
  if (entry.isDirectory()) walk(path);
11127
11413
  else if (!entry.isFile()) throw new NotebookError("CONFLICT", "Project Notebook skill source contains a non-regular entry");
@@ -11138,16 +11424,16 @@ function enumerateSkillPayload(source) {
11138
11424
  const walk = (directory) => {
11139
11425
  for (const entry of readdirSync7(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
11140
11426
  if (entry.isDirectory() && (entry.name === "__pycache__" || entry.name === ".git" || entry.name === "node_modules")) continue;
11141
- const path = join16(directory, entry.name);
11427
+ const path = join17(directory, entry.name);
11142
11428
  const rel = relative11(source, path).split(sep6).join("/");
11143
11429
  if (!rel.includes("/") && (rel === "export-manifest.json" || rel === "SHA256SUMS" || rel === ".source.yaml")) continue;
11144
11430
  if (!safeSkillRelativePath(rel)) throw new NotebookError("CONFLICT", `Project Notebook skill export path is unsafe: ${rel}`);
11145
- const stat = lstatSync11(path);
11431
+ const stat = lstatSync12(path);
11146
11432
  if (stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a symlink");
11147
11433
  if (entry.isDirectory()) walk(path);
11148
11434
  else if (entry.isFile()) {
11149
11435
  const expectedMode = rel.endsWith(".sh") || rel.startsWith("scripts/") ? "0755" : "0644";
11150
- 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 });
11151
11437
  } else throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
11152
11438
  }
11153
11439
  };
@@ -11155,9 +11441,9 @@ function enumerateSkillPayload(source) {
11155
11441
  return result2.sort((a, b) => a.path.localeCompare(b.path, "en"));
11156
11442
  }
11157
11443
  function parsePackedManifest(source) {
11158
- const manifestPath = join16(source, "export-manifest.json");
11444
+ const manifestPath = join17(source, "export-manifest.json");
11159
11445
  if (!existsSync11(manifestPath)) return null;
11160
- const value = JSON.parse(readFileSync12(manifestPath, "utf8"));
11446
+ const value = JSON.parse(readFileSync13(manifestPath, "utf8"));
11161
11447
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new NotebookError("CONFLICT", "Project Notebook skill export manifest is invalid");
11162
11448
  const manifest = value;
11163
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");
@@ -11166,10 +11452,10 @@ function parsePackedManifest(source) {
11166
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");
11167
11453
  if (!safeSkillRelativePath(entry.path) || paths.has(entry.path)) throw new NotebookError("CONFLICT", "Project Notebook skill export path is unsafe or duplicated");
11168
11454
  paths.add(entry.path);
11169
- const path = join16(source, ...entry.path.split("/"));
11170
- const stat = lstatSync11(path);
11455
+ const path = join17(source, ...entry.path.split("/"));
11456
+ const stat = lstatSync12(path);
11171
11457
  if (!stat.isFile() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Project Notebook skill export contains a non-regular entry");
11172
- const actual = createHash8("sha256").update(readFileSync12(path)).digest("hex");
11458
+ const actual = createHash8("sha256").update(readFileSync13(path)).digest("hex");
11173
11459
  if (actual !== entry.sha256) throw new NotebookError("CONFLICT", `Project Notebook skill digest mismatch: ${entry.path}`);
11174
11460
  const actualMode = stat.mode & 511;
11175
11461
  const executable = entry.mode === "0755";
@@ -11181,12 +11467,12 @@ function parsePackedManifest(source) {
11181
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");
11182
11468
  const sums = `${manifest.files.map((entry) => `${entry.sha256} ${entry.path}`).join("\n")}
11183
11469
  `;
11184
- 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");
11185
11471
  return manifest;
11186
11472
  }
11187
11473
  function expectedPackedSkill() {
11188
11474
  for (const candidate of bundledSkillCandidates()) {
11189
- if (!existsSync11(join16(candidate, "export-manifest.json"))) continue;
11475
+ if (!existsSync11(join17(candidate, "export-manifest.json"))) continue;
11190
11476
  assertOwnedSkillTree(candidate);
11191
11477
  const manifest = parsePackedManifest(candidate);
11192
11478
  if (!manifest) continue;
@@ -11196,7 +11482,7 @@ function expectedPackedSkill() {
11196
11482
  throw new NotebookError("NOT_CONFIGURED", "PJ\xE1ngler package has no verified Project Notebook skill export");
11197
11483
  }
11198
11484
  function isCanonicalSkillexProjectionPath(path) {
11199
- return resolve14(path).split(sep6).join("/").endsWith("/all-skills/project-notebook");
11485
+ return resolve15(path).split(sep6).join("/").endsWith("/all-skills/project-notebook");
11200
11486
  }
11201
11487
  function isVerifiedCanonicalSkillexProjection(path) {
11202
11488
  if (!isCanonicalSkillexProjectionPath(path)) return false;
@@ -11216,7 +11502,7 @@ function describeProjectNotebookSkillDrift(source) {
11216
11502
  }
11217
11503
  let actual;
11218
11504
  try {
11219
- const absolute = resolve14(source);
11505
+ const absolute = resolve15(source);
11220
11506
  const packed = parsePackedManifest(absolute);
11221
11507
  actual = packed?.files ?? enumerateSkillPayload(absolute);
11222
11508
  } catch (error) {
@@ -11240,7 +11526,7 @@ function describeProjectNotebookSkillDrift(source) {
11240
11526
  return drift.length ? drift.slice(0, 20) : ["no per-file drift detected; the export enumeration itself differs"];
11241
11527
  }
11242
11528
  function verifyProjectNotebookSkillExport(source) {
11243
- const absolute = resolve14(source);
11529
+ const absolute = resolve15(source);
11244
11530
  assertOwnedSkillTree(absolute);
11245
11531
  const packed = parsePackedManifest(absolute);
11246
11532
  const actual = packed ?? { schema_version: 1, skill: "project-notebook", files: enumerateSkillPayload(absolute) };
@@ -11252,14 +11538,14 @@ function verifyProjectNotebookSkillExport(source) {
11252
11538
  }
11253
11539
  function resolveProjectNotebookSkillSource(env2 = process.env) {
11254
11540
  if (env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT) {
11255
- const explicit = resolve14(env2.PJ_PROJECT_NOTEBOOK_SKILL_ROOT);
11256
- 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");
11257
11543
  verifyProjectNotebookSkillExport(explicit);
11258
11544
  return explicit;
11259
11545
  }
11260
11546
  if (env2.PJ_SKILLS_REGISTRY_ROOT) {
11261
- const canonical = resolve14(env2.PJ_SKILLS_REGISTRY_ROOT, "all-skills", "project-notebook");
11262
- 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"))) {
11263
11549
  verifyProjectNotebookSkillExport(canonical);
11264
11550
  return canonical;
11265
11551
  }
@@ -11274,7 +11560,7 @@ var REPAIR_COMMAND = "pj notebook skill --apply";
11274
11560
  function probeCanonicalSkillexRootProjection(skillsRoot, link) {
11275
11561
  let rootLink;
11276
11562
  try {
11277
- rootLink = lstatSync11(skillsRoot);
11563
+ rootLink = lstatSync12(skillsRoot);
11278
11564
  } catch (error) {
11279
11565
  if (error.code === "ENOENT") return { state: "absent" };
11280
11566
  throw error;
@@ -11287,15 +11573,15 @@ function probeCanonicalSkillexRootProjection(skillsRoot, link) {
11287
11573
  if (uid !== void 0 && rootLink.uid !== uid) throw new Error("owner");
11288
11574
  const globalRoot = realpathSync9(skillsRoot);
11289
11575
  assertNoSymlinkComponents(globalRoot);
11290
- const globalStat = lstatSync11(globalRoot);
11576
+ const globalStat = lstatSync12(globalRoot);
11291
11577
  if (!globalStat.isDirectory() || globalStat.isSymbolicLink()) throw new Error("root-type");
11292
11578
  if (uid !== void 0 && globalStat.uid !== uid) throw new Error("root-owner");
11293
11579
  if (globalStat.mode & 3586) throw new Error("root-mode");
11294
11580
  const skillSetsRoot = dirname13(globalRoot);
11295
11581
  if (basename8(globalRoot) !== "global" || basename8(skillSetsRoot) !== "skill-sets") throw new Error("layout");
11296
11582
  const checkoutRoot = dirname13(skillSetsRoot);
11297
- const expectedSource = join16(checkoutRoot, "all-skills", "project-notebook");
11298
- const linkStat = lstatSync11(link);
11583
+ const expectedSource = join17(checkoutRoot, "all-skills", "project-notebook");
11584
+ const linkStat = lstatSync12(link);
11299
11585
  if (!linkStat.isSymbolicLink()) throw new Error("projection-type");
11300
11586
  if (uid !== void 0 && linkStat.uid !== uid) throw new Error("projection-owner");
11301
11587
  const projectedSource = realpathSync9(link);
@@ -11331,28 +11617,28 @@ function installPackagedProjectNotebookSkill(input = {}) {
11331
11617
  const manifest = verifyProjectNotebookSkillExport(source);
11332
11618
  const digest = createHash8("sha256").update(JSON.stringify(manifest)).digest("hex");
11333
11619
  const home = env2.HOME;
11334
- if (!home || !resolve14(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to install the Project Notebook skill");
11335
- const skillsRoot = join16(home, ".agents", "skills");
11336
- 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");
11337
11623
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11338
11624
  if (probe.state === "adopted") return { installed: false, path: link, digest };
11339
11625
  if (probe.state === "declined") return { installed: false, path: link, digest, blocked: probe.block };
11340
- const dataRoot = resolve14(env2.XDG_DATA_HOME || join16(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11341
- 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}`);
11342
11628
  assertNoSymlinkComponents(dirname13(dataRoot), true);
11343
11629
  mkdirSync6(dataRoot, { recursive: true, mode: 448 });
11344
11630
  assertNoSymlinkComponents(dataRoot);
11345
- const dataStat = lstatSync11(dataRoot);
11631
+ const dataStat = lstatSync12(dataRoot);
11346
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");
11347
11633
  chmodSync4(dataRoot, 448);
11348
11634
  if (!existsSync11(payload)) {
11349
- const staging = join16(dataRoot, `.staging-${randomUUID5()}`);
11635
+ const staging = join17(dataRoot, `.staging-${randomUUID5()}`);
11350
11636
  mkdirSync6(staging, { recursive: false, mode: 448 });
11351
11637
  try {
11352
11638
  for (const entry of manifest.files) {
11353
- const destination = join16(staging, ...entry.path.split("/"));
11639
+ const destination = join17(staging, ...entry.path.split("/"));
11354
11640
  mkdirSync6(dirname13(destination), { recursive: true, mode: 493 });
11355
- copyFileSync3(join16(source, ...entry.path.split("/")), destination);
11641
+ copyFileSync3(join17(source, ...entry.path.split("/")), destination);
11356
11642
  const mode = entry.mode === "0755" ? 493 : 420;
11357
11643
  chmodSync4(destination, mode);
11358
11644
  const fd = openSync5(destination, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
@@ -11364,9 +11650,9 @@ function installPackagedProjectNotebookSkill(input = {}) {
11364
11650
  closeSync5(fd);
11365
11651
  }
11366
11652
  }
11367
- writeFileSync7(join16(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
11653
+ writeFileSync7(join17(staging, "export-manifest.json"), `${JSON.stringify(manifest, null, 2)}
11368
11654
  `, { mode: 420, flag: "wx" });
11369
- 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")}
11370
11656
  `, { mode: 420, flag: "wx" });
11371
11657
  verifyProjectNotebookSkillExport(staging);
11372
11658
  renameSync6(staging, payload);
@@ -11374,7 +11660,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11374
11660
  if (existsSync11(staging)) rmSync4(staging, { recursive: true, force: true });
11375
11661
  }
11376
11662
  } else {
11377
- const stat = lstatSync11(payload);
11663
+ const stat = lstatSync12(payload);
11378
11664
  if (!stat.isDirectory() || stat.isSymbolicLink()) throw new NotebookError("CONFLICT", "Installed Project Notebook payload is not a real directory");
11379
11665
  verifyProjectNotebookSkillExport(payload);
11380
11666
  }
@@ -11384,7 +11670,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11384
11670
  let linkExists = existsSync11(link);
11385
11671
  if (!linkExists) {
11386
11672
  try {
11387
- lstatSync11(link);
11673
+ lstatSync12(link);
11388
11674
  linkExists = true;
11389
11675
  } catch {
11390
11676
  }
@@ -11401,7 +11687,7 @@ function installPackagedProjectNotebookSkill(input = {}) {
11401
11687
  repair: REPAIR_COMMAND
11402
11688
  }
11403
11689
  });
11404
- const stat = lstatSync11(link);
11690
+ const stat = lstatSync12(link);
11405
11691
  if (!stat.isSymbolicLink()) return foreign(["the path is a real file or directory, not a PJ\xE1ngler-owned link"]);
11406
11692
  const target = realpathSync9(link);
11407
11693
  if (target !== realpathSync9(payload) && !isVerifiedCanonicalSkillexProjection(target)) {
@@ -11435,9 +11721,9 @@ function collectSupersededPayloads(dataRoot, keep) {
11435
11721
  }
11436
11722
  for (const name of entries) {
11437
11723
  if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?-[0-9a-f]{64}$/u.test(name)) continue;
11438
- const candidate = join16(dataRoot, name);
11724
+ const candidate = join17(dataRoot, name);
11439
11725
  try {
11440
- const stat = lstatSync11(candidate);
11726
+ const stat = lstatSync12(candidate);
11441
11727
  if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
11442
11728
  if (kept.has(realpathSync9(candidate))) continue;
11443
11729
  rmSync4(candidate, { recursive: true, force: true });
@@ -11450,9 +11736,9 @@ function collectSupersededPayloads(dataRoot, keep) {
11450
11736
  function repairProjectNotebookSkillProjection(input = {}) {
11451
11737
  const env2 = input.env ?? process.env;
11452
11738
  const home = env2.HOME;
11453
- if (!home || !resolve14(home).startsWith("/")) throw new NotebookError("NOT_CONFIGURED", "A trusted HOME is required to repair the Project Notebook skill projection");
11454
- const skillsRoot = join16(home, ".agents", "skills");
11455
- 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");
11456
11742
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11457
11743
  if (probe.state === "adopted") return { status: "clean", summary: "Canonical Skillex projection already matches the version-pinned export", source: probe.source, drift: [], changed_files: [] };
11458
11744
  if (probe.state !== "declined") {
@@ -11466,7 +11752,7 @@ function repairProjectNotebookSkillProjection(input = {}) {
11466
11752
  const present = [];
11467
11753
  const walk = (directory) => {
11468
11754
  for (const entry of readdirSync7(directory, { withFileTypes: true })) {
11469
- const path = join16(directory, entry.name);
11755
+ const path = join17(directory, entry.name);
11470
11756
  const rel = relative11(source, path).split(sep6).join("/");
11471
11757
  if (entry.isDirectory()) {
11472
11758
  walk(path);
@@ -11480,11 +11766,11 @@ function repairProjectNotebookSkillProjection(input = {}) {
11480
11766
  walk(source);
11481
11767
  const stale = present.filter((rel) => !wanted.has(rel)).sort();
11482
11768
  const changed = expected.manifest.files.filter((entry) => {
11483
- const path = join16(source, ...entry.path.split("/"));
11769
+ const path = join17(source, ...entry.path.split("/"));
11484
11770
  if (!existsSync11(path)) return true;
11485
- const stat = lstatSync11(path);
11771
+ const stat = lstatSync12(path);
11486
11772
  if (!stat.isFile()) return true;
11487
- 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;
11488
11774
  const mode = stat.mode & 511;
11489
11775
  const executable = entry.mode === "0755";
11490
11776
  return executable && (mode & 64) === 0 || !executable && (mode & 73) !== 0;
@@ -11499,15 +11785,15 @@ function repairProjectNotebookSkillProjection(input = {}) {
11499
11785
  summary: `Would restore ${affected.length} file(s) in ${source} from the version-pinned export`,
11500
11786
  source,
11501
11787
  drift: probe.block.details,
11502
- changed_files: affected.map((rel) => join16(source, ...rel.split("/")))
11788
+ changed_files: affected.map((rel) => join17(source, ...rel.split("/")))
11503
11789
  };
11504
11790
  }
11505
- 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 });
11506
11792
  for (const rel of changed) {
11507
- const destination = join16(source, ...rel.split("/"));
11793
+ const destination = join17(source, ...rel.split("/"));
11508
11794
  const entry = expected.manifest.files.find((item) => item.path === rel);
11509
11795
  mkdirSync6(dirname13(destination), { recursive: true, mode: 493 });
11510
- copyFileSync3(join16(expected.source, ...rel.split("/")), destination);
11796
+ copyFileSync3(join17(expected.source, ...rel.split("/")), destination);
11511
11797
  chmodSync4(destination, entry.mode === "0755" ? 493 : 420);
11512
11798
  }
11513
11799
  verifyProjectNotebookSkillExport(source);
@@ -11516,21 +11802,21 @@ function repairProjectNotebookSkillProjection(input = {}) {
11516
11802
  summary: `Restored ${affected.length} file(s) in ${source} from the version-pinned export; commit the change in the Skillex checkout`,
11517
11803
  source,
11518
11804
  drift: probe.block.details,
11519
- changed_files: affected.map((rel) => join16(source, ...rel.split("/")))
11805
+ changed_files: affected.map((rel) => join17(source, ...rel.split("/")))
11520
11806
  };
11521
11807
  }
11522
11808
  function inspectProjectNotebookIntegration(env2 = process.env) {
11523
11809
  const home = env2.HOME;
11524
11810
  if (!home) return { skill_installed: false, hooks_projected: false, details: ["HOME is unavailable"] };
11525
- const skillsRoot = join16(home, ".agents", "skills");
11526
- const link = join16(skillsRoot, "project-notebook");
11811
+ const skillsRoot = join17(home, ".agents", "skills");
11812
+ const link = join17(skillsRoot, "project-notebook");
11527
11813
  try {
11528
11814
  const expected = expectedPackedSkill();
11529
- const dataRoot = resolve14(env2.XDG_DATA_HOME || join16(home, ".local", "share"), "pjangler", "skills", "project-notebook");
11530
- 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}`);
11531
11817
  const probe = probeCanonicalSkillexRootProjection(skillsRoot, link);
11532
11818
  if (probe.state === "declined") return { skill_installed: false, hooks_projected: false, details: probe.block.details, blocked: probe.block };
11533
- const stat = lstatSync11(link);
11819
+ const stat = lstatSync12(link);
11534
11820
  if (!stat.isSymbolicLink()) {
11535
11821
  return {
11536
11822
  skill_installed: false,
@@ -11567,8 +11853,8 @@ function inspectProjectNotebookIntegration(env2 = process.env) {
11567
11853
  }
11568
11854
  }
11569
11855
  function projectorArguments(source, command, input) {
11570
- const script = join16(source, "scripts", "project-hooks.py");
11571
- 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];
11572
11858
  if (command === "check") args.push("--json");
11573
11859
  else if (input.stateHome) args.push("--state-home", input.stateHome);
11574
11860
  return args;
@@ -11590,8 +11876,8 @@ function checkProjectNotebookHooks(input = {}) {
11590
11876
  verifyProjectNotebookSkillExport(source);
11591
11877
  const home = env2.HOME;
11592
11878
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to check Project Notebook hooks");
11593
- const target = resolve14(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join16(home, ".claude", "settings.json"));
11594
- 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 });
11595
11881
  if (result2.status !== 0 && result2.status !== 1) throw new NotebookError("CONFLICT", (result2.stderr || "Project Notebook projector check failed").trim().slice(0, 512));
11596
11882
  try {
11597
11883
  const parsed = JSON.parse(result2.stdout);
@@ -11608,13 +11894,13 @@ function installProjectNotebookIntegration(input = {}) {
11608
11894
  const source = realpathSync9(skill.path);
11609
11895
  const home = env2.HOME;
11610
11896
  if (!home) throw new NotebookError("NOT_CONFIGURED", "HOME is required to install Project Notebook hooks");
11611
- const target = resolve14(input.target ?? env2.PJ_PROJECT_NOTEBOOK_CLAUDE_SETTINGS ?? join16(home, ".claude", "settings.json"));
11612
- const stateHome = resolve14(env2.XDG_STATE_HOME || join16(home, ".local", "state"));
11613
- 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 });
11614
11900
  if (result2.status !== 0) {
11615
11901
  if (skill.installed) {
11616
11902
  try {
11617
- const stat = lstatSync11(skill.path);
11903
+ const stat = lstatSync12(skill.path);
11618
11904
  if (stat.isSymbolicLink() && realpathSync9(skill.path) === source) unlinkSync6(skill.path);
11619
11905
  } catch {
11620
11906
  }
@@ -11628,14 +11914,14 @@ function boundedDiagnostic(value, max = 512) {
11628
11914
  return message.replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").slice(0, max);
11629
11915
  }
11630
11916
  function assertNoSymlinkComponents(path, allowMissing = false) {
11631
- const absolute = resolve14(path);
11917
+ const absolute = resolve15(path);
11632
11918
  const root = parse3(absolute).root;
11633
11919
  let cursor = root;
11634
11920
  for (const component of absolute.slice(root.length).split(sep6).filter(Boolean)) {
11635
- cursor = join16(cursor, component);
11921
+ cursor = join17(cursor, component);
11636
11922
  let stat;
11637
11923
  try {
11638
- stat = lstatSync11(cursor);
11924
+ stat = lstatSync12(cursor);
11639
11925
  } catch (error) {
11640
11926
  if (allowMissing && error.code === "ENOENT") return;
11641
11927
  throw error;
@@ -11835,7 +12121,7 @@ var NotebookModule = class {
11835
12121
  return {
11836
12122
  changedFiles: [
11837
12123
  ...installed.skill.installed ? [installed.skill.path] : [],
11838
- ...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`)] : []
11839
12125
  ],
11840
12126
  ...installed.blocked ? { blocked: installed.blocked } : {}
11841
12127
  };
@@ -12241,17 +12527,17 @@ init_remote_mutation_journal();
12241
12527
  function integrationEnvironment(module, ctx) {
12242
12528
  const env2 = { ...module.environment, HOME: ctx.homeDir };
12243
12529
  if (module.environment.HOME !== ctx.homeDir) {
12244
- env2.XDG_DATA_HOME = join17(ctx.homeDir, ".local", "share");
12245
- env2.XDG_STATE_HOME = join17(ctx.homeDir, ".local", "state");
12246
- 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");
12247
12533
  }
12248
12534
  return env2;
12249
12535
  }
12250
12536
  function resolvedForPlan(plan) {
12251
- const manifestPath = join17(plan.project.repo_path, ".project.json");
12537
+ const manifestPath = join18(plan.project.repo_path, ".project.json");
12252
12538
  let manifest = plan.manifest;
12253
12539
  if (existsSync13(manifestPath)) {
12254
- const parsed = JSON.parse(readFileSync13(manifestPath, "utf8"));
12540
+ const parsed = JSON.parse(readFileSync14(manifestPath, "utf8"));
12255
12541
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${manifestPath} must contain a JSON object`);
12256
12542
  manifest = parsed;
12257
12543
  }
@@ -12392,7 +12678,7 @@ var PRODUCTION_RUNTIME = {
12392
12678
  executePlan: executeProjectInitPlan,
12393
12679
  preflightBmad: preflightBmadLifecycle,
12394
12680
  runGit(cwd, args, options) {
12395
- const result2 = spawnSync11("git", [...args], {
12681
+ const result2 = spawnSync12("git", [...args], {
12396
12682
  cwd,
12397
12683
  encoding: "utf8",
12398
12684
  env: options?.env ? { ...process.env, ...options.env } : process.env
@@ -12406,13 +12692,13 @@ var PRODUCTION_RUNTIME = {
12406
12692
  }
12407
12693
  };
12408
12694
  function unsafeToRemove(targetDir) {
12409
- const absolute = resolve17(targetDir);
12695
+ const absolute = resolve18(targetDir);
12410
12696
  let cursor = absolute;
12411
12697
  const seen = /* @__PURE__ */ new Set();
12412
12698
  while (!seen.has(cursor)) {
12413
12699
  seen.add(cursor);
12414
12700
  try {
12415
- const stat = lstatSync12(cursor);
12701
+ const stat = lstatSync13(cursor);
12416
12702
  if (stat.isSymbolicLink()) {
12417
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`;
12418
12704
  }
@@ -12440,12 +12726,12 @@ function publicMigration(report) {
12440
12726
  };
12441
12727
  }
12442
12728
  function hasGitRepository(runtime, targetDir) {
12443
- if (!existsSync14(join18(targetDir, ".git"))) return false;
12729
+ if (!existsSync14(join19(targetDir, ".git"))) return false;
12444
12730
  return runtime.runGit(targetDir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
12445
12731
  }
12446
12732
  function refreshPlanFromCanonicalManifest(plan) {
12447
- const manifestPath = join18(plan.project.repo_path, ".project.json");
12448
- 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"));
12449
12735
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
12450
12736
  throw new Error(`${manifestPath} must contain a JSON object`);
12451
12737
  }
@@ -12537,6 +12823,8 @@ var ProjectRecipe = class extends Recipe {
12537
12823
  repoRoot: targetDir,
12538
12824
  bmadVersionPin: mode === "create" ? BMAD_INSTALLER_VERSION : ctx.bmadVersionPin
12539
12825
  };
12826
+ let bmadCompanion;
12827
+ let g33DeferredForEnrollment = false;
12540
12828
  let migrationReport;
12541
12829
  let audit;
12542
12830
  let notebookPlan;
@@ -12577,6 +12865,10 @@ var ProjectRecipe = class extends Recipe {
12577
12865
  message: planBlocked ? "Project plan skipped after failed preflight" : executed.ok ? "Project plan executed" : executed.errors.join("; ")
12578
12866
  });
12579
12867
  if (errors.length === 0 && executed.ok && mode === "create") {
12868
+ try {
12869
+ g33DeferredForEnrollment = !g33CompanionEnabled(targetDir);
12870
+ } catch {
12871
+ }
12580
12872
  const dependencyResult = await this.registry.initDependencies(this.metadata.id, transactionContext, normalized, ["notebook"]);
12581
12873
  logs.push(...dependencyResult.logs);
12582
12874
  errors.push(...dependencyResult.errors);
@@ -12657,7 +12949,7 @@ var ProjectRecipe = class extends Recipe {
12657
12949
  if (hasGitRepository(this.runtime, targetDir)) {
12658
12950
  phases.push({ id: "project.git", status: "unchanged", changedFiles: [], message: "Git repository already initialized" });
12659
12951
  } else {
12660
- const gitPath = join18(targetDir, ".git");
12952
+ const gitPath = join19(targetDir, ".git");
12661
12953
  for (const { args, label, options } of [
12662
12954
  { args: ["init", "--initial-branch=main"], label: "git init" },
12663
12955
  { args: ["add", "-A"], label: "git add" },
@@ -12757,6 +13049,17 @@ var ProjectRecipe = class extends Recipe {
12757
13049
  errors.push(`project manifest refresh after external effects failed: ${error instanceof Error ? error.message : String(error)}`);
12758
13050
  }
12759
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
+ }
12760
13063
  if (registryFinalizerEligible && registryActions.length) {
12761
13064
  rollbackEligible = false;
12762
13065
  const registryPlan = { ...plan, actions: registryActions };
@@ -12810,8 +13113,8 @@ var ProjectRecipe = class extends Recipe {
12810
13113
  } else try {
12811
13114
  rmSync5(targetDir, { recursive: true, force: true });
12812
13115
  const insideTarget = (path) => {
12813
- const relative12 = relativePath(resolve17(targetDir), resolve17(path));
12814
- return relative12 === "" || !relative12.startsWith("..") && !isAbsolute8(relative12);
13116
+ const relative12 = relativePath(resolve18(targetDir), resolve18(path));
13117
+ return relative12 === "" || !relative12.startsWith("..") && !isAbsolute9(relative12);
12815
13118
  };
12816
13119
  const orphaned = [...new Set(changedFiles.filter((path) => !insideTarget(path)))].sort();
12817
13120
  changedFiles.length = 0;
@@ -12847,7 +13150,8 @@ var ProjectRecipe = class extends Recipe {
12847
13150
  audit,
12848
13151
  selectedOperations: normalized.selectedOperations ?? [],
12849
13152
  selectedParityRules: normalized.selectedRuleIds ?? [],
12850
- migrationReport
13153
+ migrationReport,
13154
+ ...bmadCompanion ? { bmadCompanion } : {}
12851
13155
  };
12852
13156
  }
12853
13157
  printNextSteps() {
@@ -12855,12 +13159,12 @@ var ProjectRecipe = class extends Recipe {
12855
13159
  };
12856
13160
 
12857
13161
  // src/recipes/registry.ts
12858
- import { readFileSync as readFileSync15 } from "node:fs";
12859
- import { join as join19 } from "node:path";
13162
+ import { readFileSync as readFileSync16 } from "node:fs";
13163
+ import { join as join20 } from "node:path";
12860
13164
  function projectWaivers(repoRoot) {
12861
13165
  let waive;
12862
13166
  try {
12863
- waive = JSON.parse(readFileSync15(join19(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
13167
+ waive = JSON.parse(readFileSync16(join20(repoRoot, ".project.json"), "utf8"))?.parity?.waive;
12864
13168
  } catch {
12865
13169
  return /* @__PURE__ */ new Map();
12866
13170
  }
@@ -13201,8 +13505,8 @@ var recipeRegistry = new RecipeRegistry([
13201
13505
 
13202
13506
  // src/commands/AgentHooksCommands.ts
13203
13507
  import { homedir as homedir6 } from "node:os";
13204
- import { join as join20, dirname as dirname15 } from "node:path";
13205
- 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";
13206
13510
  import { fileURLToPath as fileURLToPath4 } from "node:url";
13207
13511
  init_project();
13208
13512
  import { parse as parseToml3 } from "smol-toml";
@@ -13215,16 +13519,16 @@ function resolveTemplateRoot() {
13215
13519
  try {
13216
13520
  let dir = dirname15(fileURLToPath4(import.meta.url));
13217
13521
  for (let i = 0; i < 8; i++) {
13218
- candidates.push(join20(dir, "templates", "commonproject", "template"));
13522
+ candidates.push(join21(dir, "templates", "commonproject", "template"));
13219
13523
  const parent = dirname15(dir);
13220
13524
  if (parent === dir) break;
13221
13525
  dir = parent;
13222
13526
  }
13223
13527
  } catch {
13224
13528
  }
13225
- candidates.push(join20(homedir6(), "code", "pjangler", "templates", "commonproject", "template"));
13529
+ candidates.push(join21(homedir6(), "code", "pjangler", "templates", "commonproject", "template"));
13226
13530
  for (const c of candidates) {
13227
- if (existsSync15(join20(c, ".agents", "hooks", "hooks.master.json"))) return c;
13531
+ if (existsSync15(join21(c, ".agents", "hooks", "hooks.master.json"))) return c;
13228
13532
  }
13229
13533
  throw new Error(
13230
13534
  "Could not locate the CommonProject template. Set PJANGLER_COMMONPROJECT_TEMPLATE to <repo>/templates/commonproject/template."
@@ -13250,8 +13554,8 @@ var CopyAgentHooksTree = class extends Command {
13250
13554
  const created = [];
13251
13555
  const skipped = [];
13252
13556
  for (const { rel, dir } of items) {
13253
- const src = join20(templateRoot, rel);
13254
- const dest = join20(this.context.targetDir, rel);
13557
+ const src = join21(templateRoot, rel);
13558
+ const dest = join21(this.context.targetDir, rel);
13255
13559
  if (!existsSync15(src)) continue;
13256
13560
  if (existsSync15(dest) && !this.context.force) {
13257
13561
  skipped.push(rel);
@@ -13279,14 +13583,14 @@ var WireMiseAgentHooks = class _WireMiseAgentHooks extends Command {
13279
13583
  if (!resolveAgentHooksLayer2()) {
13280
13584
  return { success: true, message: this.formatMessage(AGENT_HOOKS_SKIP_MESSAGE) };
13281
13585
  }
13282
- const misePath = join20(this.context.targetDir, "mise.toml");
13586
+ const misePath = join21(this.context.targetDir, "mise.toml");
13283
13587
  if (!existsSync15(misePath)) {
13284
13588
  return {
13285
13589
  success: false,
13286
13590
  message: "\u26A0\uFE0F No mise.toml found \u2014 run `pj add mise` first, then re-run."
13287
13591
  };
13288
13592
  }
13289
- let content = readFileSync16(misePath, "utf8");
13593
+ let content = readFileSync17(misePath, "utf8");
13290
13594
  if (content.includes(_WireMiseAgentHooks.MARKER)) {
13291
13595
  return { success: true, message: this.formatMessage("\u2713 mise.toml already wired for agent-hooks") };
13292
13596
  }
@@ -13509,7 +13813,7 @@ if __name__ == "__main__":
13509
13813
 
13510
13814
  // src/commands/AddMiseCodegraphScript.ts
13511
13815
  import { chmodSync as chmodSync5 } from "fs";
13512
- import { join as join21 } from "path";
13816
+ import { join as join22 } from "path";
13513
13817
  var AddMiseCodegraphScript = class extends Command {
13514
13818
  async invoke() {
13515
13819
  const filePath = ".mise/scripts/codegraph.sh";
@@ -13563,7 +13867,7 @@ fi
13563
13867
  `;
13564
13868
  this.writeFile(filePath, content);
13565
13869
  if (!this.context.dryRun) {
13566
- chmodSync5(join21(this.context.targetDir, filePath), 493);
13870
+ chmodSync5(join22(this.context.targetDir, filePath), 493);
13567
13871
  }
13568
13872
  return {
13569
13873
  success: true,
@@ -13601,18 +13905,18 @@ SECRET_KEY=""
13601
13905
  // src/parity/index.ts
13602
13906
  import { existsSync as existsSync16 } from "node:fs";
13603
13907
  import { homedir as homedir7 } from "node:os";
13604
- 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";
13605
13909
  import { fileURLToPath as fileURLToPath5 } from "node:url";
13606
13910
  function resolvePjanglerRoot2() {
13607
13911
  let dir = dirname16(fileURLToPath5(import.meta.url));
13608
13912
  while (dir !== dirname16(dir)) {
13609
- 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;
13610
13914
  dir = dirname16(dir);
13611
13915
  }
13612
- return resolve18(process.cwd());
13916
+ return resolve19(process.cwd());
13613
13917
  }
13614
13918
  function lifecycleContext(repoArg, dryRun, acceptRegistryMatches = false, overrides = {}) {
13615
- const repoRoot = resolve18(repoArg ?? process.cwd());
13919
+ const repoRoot = resolve19(repoArg ?? process.cwd());
13616
13920
  return {
13617
13921
  ...overrides,
13618
13922
  targetDir: repoRoot,
@@ -13781,13 +14085,13 @@ init_version();
13781
14085
  // src/project/info.ts
13782
14086
  init_boardUrl();
13783
14087
  init_project();
13784
- import { readFileSync as readFileSync17 } from "node:fs";
13785
- 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";
13786
14090
  function readManifest2(root) {
13787
- const path = join23(root, ".project.json");
14091
+ const path = join24(root, ".project.json");
13788
14092
  let manifest;
13789
14093
  try {
13790
- const value = JSON.parse(readFileSync17(path, "utf8"));
14094
+ const value = JSON.parse(readFileSync18(path, "utf8"));
13791
14095
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
13792
14096
  manifest = value;
13793
14097
  } catch (error) {
@@ -13827,8 +14131,8 @@ function readProjectInfo(input = {}) {
13827
14131
  const info = {
13828
14132
  ok: true,
13829
14133
  project_id: projectId,
13830
- repo_path: resolve19(root),
13831
- manifest_path: join23(resolve19(root), ".project.json"),
14134
+ repo_path: resolve20(root),
14135
+ manifest_path: join24(resolve20(root), ".project.json"),
13832
14136
  manifest,
13833
14137
  registry: { location, status: "indexed" }
13834
14138
  };
@@ -13849,27 +14153,27 @@ function readProjectInfo(input = {}) {
13849
14153
  info.registry = { location, status: "not-indexed", message: "This project is not indexed. Run pj init --apply from its repository." };
13850
14154
  return info;
13851
14155
  }
13852
- if (resolve19(indexed.repo_path) !== resolve19(root)) {
14156
+ if (resolve20(indexed.repo_path) !== resolve20(root)) {
13853
14157
  info.registry = { location, status: "stale", message: `The registry points to ${indexed.repo_path}; this manifest is at ${root}. Reindex the repository.` };
13854
14158
  }
13855
14159
  return info;
13856
14160
  }
13857
14161
 
13858
14162
  // src/describe/index.ts
13859
- import { existsSync as existsSync17, readFileSync as readFileSync18, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
13860
- 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";
13861
14165
  init_project();
13862
14166
 
13863
14167
  // src/describe/activity.ts
13864
- import { spawn as spawn2, spawnSync as spawnSync12 } from "node:child_process";
14168
+ import { spawn as spawn2, spawnSync as spawnSync13 } from "node:child_process";
13865
14169
  import { statSync as statSync5 } from "node:fs";
13866
- import { join as join24 } from "node:path";
14170
+ import { join as join25 } from "node:path";
13867
14171
  var ACTIVE_WINDOW_SECONDS = 24 * 60 * 60;
13868
14172
  var MAX_DIRTY_STATS = 500;
13869
14173
  var GIT_TIMEOUT_MS = 5e3;
13870
14174
  var GIT_MAX_BUFFER = 16 * 1024 * 1024;
13871
14175
  function git3(repo, args) {
13872
- const result2 = spawnSync12("git", ["-C", repo, ...args], {
14176
+ const result2 = spawnSync13("git", ["-C", repo, ...args], {
13873
14177
  encoding: "utf8",
13874
14178
  timeout: GIT_TIMEOUT_MS,
13875
14179
  maxBuffer: GIT_MAX_BUFFER
@@ -13992,7 +14296,7 @@ function uncommittedSource(repo, paths) {
13992
14296
  let newest = 0;
13993
14297
  for (const path of paths.slice(0, MAX_DIRTY_STATS)) {
13994
14298
  try {
13995
- const mtime = Math.floor(statSync5(join24(repo, path)).mtimeMs / 1e3);
14299
+ const mtime = Math.floor(statSync5(join25(repo, path)).mtimeMs / 1e3);
13996
14300
  if (mtime > newest) newest = mtime;
13997
14301
  } catch {
13998
14302
  }
@@ -14101,7 +14405,7 @@ var CONFIG_FILES = [
14101
14405
  ];
14102
14406
  function readJson(path) {
14103
14407
  try {
14104
- const parsed = JSON.parse(readFileSync18(path, "utf8"));
14408
+ const parsed = JSON.parse(readFileSync19(path, "utf8"));
14105
14409
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
14106
14410
  } catch {
14107
14411
  return void 0;
@@ -14123,7 +14427,7 @@ function describeType(repo) {
14123
14427
  const evidence = [];
14124
14428
  const note = (signal, file) => evidence.push(`${signal} (${file})`);
14125
14429
  for (const marker of LANGUAGE_MARKERS) {
14126
- if (!existsSync17(join25(repo, marker.file))) continue;
14430
+ if (!existsSync17(join26(repo, marker.file))) continue;
14127
14431
  if (!languages.includes(marker.language)) {
14128
14432
  languages.push(marker.language);
14129
14433
  note(marker.language, marker.file);
@@ -14137,9 +14441,9 @@ function describeType(repo) {
14137
14441
  }
14138
14442
  } catch {
14139
14443
  }
14140
- const pkg = readJson(join25(repo, "package.json"));
14444
+ const pkg = readJson(join26(repo, "package.json"));
14141
14445
  if (pkg) {
14142
- if (existsSync17(join25(repo, "tsconfig.json"))) {
14446
+ if (existsSync17(join26(repo, "tsconfig.json"))) {
14143
14447
  const index = languages.indexOf("javascript");
14144
14448
  if (index >= 0) languages.splice(index, 1);
14145
14449
  if (!languages.includes("typescript")) {
@@ -14173,26 +14477,26 @@ function describeType(repo) {
14173
14477
  ["hermes-fleet-host", "agents/hermes"]
14174
14478
  ];
14175
14479
  for (const [role, marker] of roleMarkers) {
14176
- if (!existsSync17(join25(repo, marker))) continue;
14480
+ if (!existsSync17(join26(repo, marker))) continue;
14177
14481
  roles.push(role);
14178
14482
  note(role, marker);
14179
14483
  }
14180
14484
  return { primaryLanguage: languages[0], languages, roles, evidence };
14181
14485
  }
14182
14486
  function describeIdentity(repo, registryPath) {
14183
- const manifestPath = join25(repo, ".project.json");
14487
+ const manifestPath = join26(repo, ".project.json");
14184
14488
  const manifest = readJson(manifestPath);
14185
14489
  const drift = [];
14186
- let record2;
14490
+ let record3;
14187
14491
  let registryReadable = true;
14188
14492
  try {
14189
14493
  const registry = loadProjectRegistry(registryPath);
14190
14494
  const identity = manifest?.project_id ?? manifest?.project_slug;
14191
14495
  const slug = typeof identity === "string" ? identity.toLowerCase() : void 0;
14192
- const resolved = resolve20(repo);
14193
- 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);
14194
14498
  const statuses = registry.__registry_status;
14195
- const status = record2 && statuses?.[record2.slug];
14499
+ const status = record3 && statuses?.[record3.slug];
14196
14500
  if (status && status.status !== "ok") {
14197
14501
  drift.push({ note: `canonical manifest is ${status.status}; displaying the last indexed record. ${status.error ?? ""}`, command: "pjangler doctor" });
14198
14502
  }
@@ -14200,14 +14504,14 @@ function describeIdentity(repo, registryPath) {
14200
14504
  registryReadable = false;
14201
14505
  drift.push({ note: `registry unreadable: ${err instanceof Error ? err.message : String(err)}` });
14202
14506
  }
14203
- if (manifest && !record2 && registryReadable) {
14507
+ if (manifest && !record3 && registryReadable) {
14204
14508
  drift.push({ note: ".project.json exists but this repo is not in the pjangler registry" });
14205
14509
  }
14206
- if (record2 && !manifest) {
14207
- 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" });
14208
14512
  }
14209
- if (record2 && resolve20(record2.repo_path) !== resolve20(repo)) {
14210
- 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" });
14211
14515
  }
14212
14516
  const manifestProvider = manifest?.ticket_provider;
14213
14517
  const provider = manifestProvider ? {
@@ -14216,13 +14520,13 @@ function describeIdentity(repo, registryPath) {
14216
14520
  identifier: manifestProvider.identifier,
14217
14521
  board_id: manifestProvider.board_id,
14218
14522
  state: manifestProvider.state
14219
- } : record2?.ticket_provider;
14220
- if (manifestProvider && record2) {
14523
+ } : record3?.ticket_provider;
14524
+ if (manifestProvider && record3) {
14221
14525
  const fields = [
14222
- ["type", provider?.type, record2.ticket_provider.type],
14223
- ["workspace", provider?.workspace, record2.ticket_provider.workspace],
14224
- ["identifier", provider?.identifier, record2.ticket_provider.identifier],
14225
- ["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]
14226
14530
  ];
14227
14531
  for (const [field2, fromManifest, fromRegistry] of fields) {
14228
14532
  if ((fromManifest ?? "") === (fromRegistry ?? "")) continue;
@@ -14232,7 +14536,7 @@ function describeIdentity(repo, registryPath) {
14232
14536
  }
14233
14537
  }
14234
14538
  const manifestAgents = manifest?.agents ?? {};
14235
- const agents = record2 ? Object.entries(record2.agents).map(([name, agent]) => ({
14539
+ const agents = record3 ? Object.entries(record3.agents).map(([name, agent]) => ({
14236
14540
  name,
14237
14541
  role: agent.role,
14238
14542
  provisioningState: agent.provisioning_state,
@@ -14245,11 +14549,11 @@ function describeIdentity(repo, registryPath) {
14245
14549
  }));
14246
14550
  return {
14247
14551
  manifest: Boolean(manifest),
14248
- registered: Boolean(record2),
14552
+ registered: Boolean(record3),
14249
14553
  registryPath,
14250
- slug: record2?.slug ?? (manifest?.project_id ?? manifest?.project_slug),
14251
- name: record2?.name ?? manifest?.project_name,
14252
- 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,
14253
14557
  ticketProvider: provider ? {
14254
14558
  type: provider.type,
14255
14559
  workspace: provider.workspace,
@@ -14273,12 +14577,12 @@ function describeSubsystems(repo, findings) {
14273
14577
  const markers = SUBSYSTEM_MARKERS[metadata.id] ?? [];
14274
14578
  const evidence = metadata.id === "notebook" ? (() => {
14275
14579
  try {
14276
- const manifest = JSON.parse(readFileSync18(join25(repo, ".project.json"), "utf8"));
14580
+ const manifest = JSON.parse(readFileSync19(join26(repo, ".project.json"), "utf8"));
14277
14581
  return manifest.notebook && typeof manifest.notebook === "object" ? [".project.json#notebook"] : [];
14278
14582
  } catch {
14279
14583
  return [];
14280
14584
  }
14281
- })() : markers.filter((marker) => existsSync17(join25(repo, marker)));
14585
+ })() : markers.filter((marker) => existsSync17(join26(repo, marker)));
14282
14586
  const rules = (byRecipe.get(metadata.id) ?? []).map((finding2) => ({
14283
14587
  id: finding2.id,
14284
14588
  title: finding2.title,
@@ -14296,8 +14600,8 @@ function describeSubsystems(repo, findings) {
14296
14600
  function describeNotebook(repo, registryPath) {
14297
14601
  try {
14298
14602
  const registry = loadProjectRegistry(registryPath);
14299
- const project = Object.values(registry.projects).find((entry) => resolve20(entry.repo_path) === resolve20(repo));
14300
- 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;
14301
14605
  const declared = Boolean(project?.notebook || manifest?.notebook && typeof manifest.notebook === "object");
14302
14606
  if (!declared) return { declared: false, bindingState: null, notebookId: null, overviewNoteId: null, health: null, remoteCheck: "skip", captureAdmission: null };
14303
14607
  const config = loadEffectiveNotebookConfig(repo, registryPath);
@@ -14315,7 +14619,7 @@ function describeNotebook(repo, registryPath) {
14315
14619
  }
14316
14620
  }
14317
14621
  function describeConfigFiles(repo) {
14318
- 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 }));
14319
14623
  }
14320
14624
  function describeNextSteps(description, findings) {
14321
14625
  const steps = [];
@@ -14393,7 +14697,7 @@ function describeNextSteps(description, findings) {
14393
14697
  return steps;
14394
14698
  }
14395
14699
  async function describeProject(input = {}) {
14396
- const repo = resolve20(input.repoArg ?? process.cwd());
14700
+ const repo = resolve21(input.repoArg ?? process.cwd());
14397
14701
  if (!existsSync17(repo)) throw new Error(`Path does not exist: ${repo}`);
14398
14702
  if (!statSync6(repo).isDirectory()) throw new Error(`Not a directory: ${repo}`);
14399
14703
  const registryPath = input.registryPath ?? projectRegistryPath();
@@ -14582,7 +14886,7 @@ function validateExternalEffectConsent(input, options) {
14582
14886
  return selected;
14583
14887
  }
14584
14888
  function resolveTargetDir(targetDir) {
14585
- const dir = resolve21(targetDir ?? process.cwd());
14889
+ const dir = resolve22(targetDir ?? process.cwd());
14586
14890
  if (!existsSync18(dir)) {
14587
14891
  throw new Error(`Target directory does not exist: ${dir}`);
14588
14892
  }
@@ -14594,7 +14898,7 @@ function resolveTargetDir(targetDir) {
14594
14898
  function resolvePjanglerRoot3() {
14595
14899
  let dir = dirname17(fileURLToPath6(import.meta.url));
14596
14900
  while (dir !== dirname17(dir)) {
14597
- 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"))) {
14598
14902
  return dir;
14599
14903
  }
14600
14904
  dir = dirname17(dir);
@@ -14889,12 +15193,12 @@ server.registerTool(
14889
15193
  );
14890
15194
  const pjanglerRoot = resolvePjanglerRoot3();
14891
15195
  const projectSlug = validateSafePathSegment(input.projectSlug ?? slugify(input.projectName), "Project slug");
14892
- const explicitTargetDir = input.targetDir ? resolve21(input.targetDir) : void 0;
14893
- 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()));
14894
15198
  if (!existsSync18(parentDir) || !statSync7(parentDir).isDirectory()) throw new Error(`Parent directory does not exist: ${parentDir}`);
14895
15199
  const targetDir = resolveContainedPath(
14896
15200
  parentDir,
14897
- explicitTargetDir ?? join26(parentDir, projectSlug),
15201
+ explicitTargetDir ?? join27(parentDir, projectSlug),
14898
15202
  "Bootstrap target"
14899
15203
  );
14900
15204
  const overwrite = input.overwrite ?? input.force ?? false;
@@ -15017,7 +15321,7 @@ server.registerTool(
15017
15321
  registryPath: input.registryPath,
15018
15322
  force: input.force ?? false,
15019
15323
  overwrite: input.force ?? false,
15020
- scaffold: !(input.targetDir && existsSync18(join26(resolve21(input.targetDir), ".git")))
15324
+ scaffold: !(input.targetDir && existsSync18(join27(resolve22(input.targetDir), ".git")))
15021
15325
  });
15022
15326
  if (!input.apply) return asText(publicCompositeProjectResponse(publicProjectPlan(plan), plan));
15023
15327
  const preflight = await preflightProjectApply(plan, resolvePjanglerRoot3());