@tangle-network/agent-app 0.60.216 → 0.60.217

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.
@@ -561,19 +561,303 @@ async function importConfig(abs) {
561
561
  }
562
562
 
563
563
  // src/signoff/run.ts
564
- import { spawnSync as spawnSync4 } from "child_process";
565
- import { existsSync as existsSync8 } from "fs";
564
+ import { spawnSync as spawnSync5 } from "child_process";
565
+ import { randomUUID as randomUUID2 } from "crypto";
566
+ import { existsSync as existsSync9 } from "fs";
566
567
  import { availableParallelism, homedir } from "os";
567
- import { basename as basename2, join as join8, resolve as resolve3 } from "path";
568
+ import { basename as basename4, join as join9, resolve as resolve4 } from "path";
569
+
570
+ // src/signoff/cache.ts
571
+ import { randomUUID } from "crypto";
572
+ import { spawnSync as spawnSync3 } from "child_process";
573
+ import {
574
+ existsSync as existsSync4,
575
+ lstatSync,
576
+ mkdirSync,
577
+ readFileSync as readFileSync3,
578
+ readdirSync,
579
+ renameSync,
580
+ rmSync as rmSync2,
581
+ readlinkSync,
582
+ realpathSync,
583
+ statSync as statSync2,
584
+ unlinkSync,
585
+ writeFileSync
586
+ } from "fs";
587
+ import { hostname } from "os";
588
+ import { basename as basename2, join as join4, relative, resolve as resolve2, sep } from "path";
589
+ import lockfile from "proper-lockfile";
590
+ var SIGNOFF_CACHE_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1e3;
591
+ var SIGNOFF_CACHE_MAX_BYTES = 4 * 1024 ** 3;
592
+ function processStart(pid) {
593
+ try {
594
+ const stat = readFileSync3(`/proc/${pid}/stat`, "utf8");
595
+ return stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19] ?? null;
596
+ } catch {
597
+ return null;
598
+ }
599
+ }
600
+ function hasRunProcesses(run) {
601
+ if (process.platform !== "linux") return true;
602
+ try {
603
+ for (const entry of readdirSync("/proc")) {
604
+ if (!/^\d+$/.test(entry)) continue;
605
+ const proc = join4("/proc", entry);
606
+ try {
607
+ const start = processStart(Number(entry));
608
+ if (run.processStart && start && BigInt(start) < BigInt(run.processStart)) continue;
609
+ const cwd = readlinkSync(join4(proc, "cwd"));
610
+ if (cwd === run.tree || cwd.startsWith(run.tree + sep)) return true;
611
+ const env = readFileSync3(join4(proc, "environ"), "utf8").split("\0");
612
+ if (env.includes(`SIGNOFF_TREE=${run.tree}`)) return true;
613
+ } catch (error) {
614
+ if (["ENOENT", "ESRCH"].includes(error.code ?? "")) continue;
615
+ return true;
616
+ }
617
+ }
618
+ return false;
619
+ } catch {
620
+ return true;
621
+ }
622
+ }
623
+ function alive(run) {
624
+ if (run.host !== hostname()) return true;
625
+ try {
626
+ process.kill(run.pid, 0);
627
+ } catch (error) {
628
+ if (error.code === "ESRCH") return hasRunProcesses(run);
629
+ return true;
630
+ }
631
+ const currentStart = processStart(run.pid);
632
+ return !run.processStart || !currentStart || currentStart === run.processStart || hasRunProcesses(run);
633
+ }
634
+ function cacheRoot(cacheDir) {
635
+ mkdirSync(cacheDir, { recursive: true });
636
+ return realpathSync(cacheDir);
637
+ }
638
+ async function withCacheLock(cacheDir, action) {
639
+ const release = await lockfile.lock(cacheRoot(cacheDir), {
640
+ realpath: false,
641
+ stale: 3e5,
642
+ update: 3e4,
643
+ retries: { retries: 100, factor: 1, minTimeout: 100, maxTimeout: 100 }
644
+ });
645
+ try {
646
+ return await action();
647
+ } finally {
648
+ await release();
649
+ }
650
+ }
651
+ function markerDir(cacheDir) {
652
+ return join4(cacheDir, "active");
653
+ }
654
+ function writeMarker(path, run) {
655
+ const temp = `${path}.${randomUUID()}.tmp`;
656
+ writeFileSync(temp, `${JSON.stringify(run)}
657
+ `, { flag: "wx" });
658
+ renameSync(temp, path);
659
+ }
660
+ function protectedPaths(cacheDir) {
661
+ const paths = /* @__PURE__ */ new Set();
662
+ const root = markerDir(cacheDir);
663
+ if (!existsSync4(root)) return { paths, unknown: false };
664
+ let unknown = false;
665
+ for (const entry of readdirSync(root)) {
666
+ if (!entry.endsWith(".json")) continue;
667
+ const file = join4(root, entry);
668
+ let run;
669
+ try {
670
+ run = JSON.parse(readFileSync3(file, "utf8"));
671
+ if (!Number.isSafeInteger(run.pid) || run.pid <= 0 || typeof run.host !== "string" || typeof run.tree !== "string" || !run.tree.startsWith(resolve2(cacheDir) + sep) || run.processStart !== null && (typeof run.processStart !== "string" || !/^\d+$/.test(run.processStart)) || run.storeKey !== null && (typeof run.storeKey !== "string" || !/^[a-f0-9]{64}$/.test(run.storeKey))) {
672
+ unknown = true;
673
+ continue;
674
+ }
675
+ } catch {
676
+ unknown = true;
677
+ continue;
678
+ }
679
+ if (!alive(run)) {
680
+ unlinkSync(file);
681
+ continue;
682
+ }
683
+ const tree = resolve2(run.tree);
684
+ if (tree.startsWith(resolve2(cacheDir) + sep)) paths.add(tree);
685
+ if (run.storeKey && /^[a-f0-9]{64}$/.test(run.storeKey)) {
686
+ paths.add(join4(cacheDir, "stores", run.storeKey));
687
+ }
688
+ }
689
+ return { paths, unknown };
690
+ }
691
+ function protectLegacyRuns(cacheDir, active) {
692
+ const trees = join4(cacheDir, "trees");
693
+ if (!existsSync4(trees)) return;
694
+ for (const entry of readdirSync(trees, { withFileTypes: true })) {
695
+ if (!entry.isDirectory()) continue;
696
+ const tree = join4(trees, entry.name);
697
+ if (active.paths.has(tree) || !existsSync4(join4(tree, ".git"))) continue;
698
+ const match = /-(\d+)$/.exec(entry.name);
699
+ if (!match) continue;
700
+ const pid = Number(match[1]);
701
+ if (alive({ pid, host: hostname(), processStart: null, tree, storeKey: null })) {
702
+ active.paths.add(tree);
703
+ active.unknown = true;
704
+ }
705
+ }
706
+ }
707
+ async function openSignoffCacheSession(cacheDir, tree) {
708
+ const root = cacheRoot(cacheDir);
709
+ const treePath = resolve2(root, relative(resolve2(cacheDir), resolve2(tree)));
710
+ const file = join4(markerDir(root), `${process.pid}-${randomUUID()}.json`);
711
+ let run = {
712
+ pid: process.pid,
713
+ host: hostname(),
714
+ processStart: processStart(process.pid),
715
+ tree: treePath,
716
+ storeKey: null
717
+ };
718
+ await withCacheLock(root, () => {
719
+ mkdirSync(markerDir(root), { recursive: true });
720
+ writeMarker(file, run);
721
+ });
722
+ return {
723
+ cacheDir: root,
724
+ protectStore: async (cacheKey) => {
725
+ if (!/^[a-f0-9]{64}$/.test(cacheKey)) throw new Error("signoff: invalid store cache key");
726
+ await withCacheLock(root, () => {
727
+ run = { ...run, storeKey: cacheKey };
728
+ writeMarker(file, run);
729
+ });
730
+ },
731
+ close: async () => {
732
+ await withCacheLock(root, () => {
733
+ if (existsSync4(file)) unlinkSync(file);
734
+ });
735
+ }
736
+ };
737
+ }
738
+ function activeStoreKeys(cacheDir) {
739
+ cacheDir = cacheRoot(cacheDir);
740
+ const active = protectedPaths(cacheDir);
741
+ protectLegacyRuns(cacheDir, active);
742
+ const { paths, unknown } = active;
743
+ if (unknown && existsSync4(join4(cacheDir, "stores"))) return new Set(readdirSync(join4(cacheDir, "stores"), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name));
744
+ return new Set([...paths].filter((path) => path.startsWith(join4(cacheDir, "stores") + sep)).map((path) => basename2(path)));
745
+ }
746
+ function allocatedBytes(stat) {
747
+ return process.platform === "win32" ? stat.size : stat.blocks * 512;
748
+ }
749
+ function collectInodes(path, keys, sizes) {
750
+ let stat;
751
+ try {
752
+ stat = lstatSync(path);
753
+ } catch (error) {
754
+ if (error.code === "ENOENT") return;
755
+ throw error;
756
+ }
757
+ const key = `${stat.dev}:${stat.ino}`;
758
+ keys.add(key);
759
+ sizes.set(key, allocatedBytes(stat));
760
+ if (!stat.isDirectory() || stat.isSymbolicLink()) return;
761
+ let children;
762
+ try {
763
+ children = readdirSync(path);
764
+ } catch (error) {
765
+ if (error.code === "ENOENT") return;
766
+ throw error;
767
+ }
768
+ for (const child of children) collectInodes(join4(path, child), keys, sizes);
769
+ }
770
+ function entries(cacheDir, protectedSet, unknown) {
771
+ const found = [];
772
+ for (const [kind, child] of [["tree", "trees"], ["store", "stores"]]) {
773
+ const root = join4(cacheDir, child);
774
+ if (!existsSync4(root)) continue;
775
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
776
+ if (!entry.isDirectory()) continue;
777
+ const path = join4(root, entry.name);
778
+ const recognized = kind === "store" ? /^[a-f0-9]{64}$/.test(entry.name) : existsSync4(join4(path, ".git"));
779
+ let mtimeMs;
780
+ try {
781
+ mtimeMs = statSync2(path).mtimeMs;
782
+ } catch (error) {
783
+ if (error.code === "ENOENT") continue;
784
+ throw error;
785
+ }
786
+ found.push({
787
+ path,
788
+ kind,
789
+ mtimeMs,
790
+ protected: unknown || protectedSet.has(path) || !recognized,
791
+ inodes: /* @__PURE__ */ new Set()
792
+ });
793
+ }
794
+ }
795
+ return found;
796
+ }
797
+ function removeEntry(entry) {
798
+ if (entry.kind === "store") {
799
+ rmSync2(entry.path, { recursive: true, force: true });
800
+ return;
801
+ }
802
+ const git2 = spawnSync3("git", ["-C", entry.path, "worktree", "remove", "--force", entry.path], { encoding: "utf8" });
803
+ if (git2.status !== 0) throw new Error(`signoff: cannot retire cached worktree ${entry.path}: ${git2.stderr.trim()}`);
804
+ }
805
+ async function pruneSignoffCache(cacheDir, options = {}) {
806
+ const root = cacheRoot(cacheDir);
807
+ const nowMs = options.nowMs ?? Date.now();
808
+ const maxAgeMs = options.maxAgeMs ?? SIGNOFF_CACHE_MAX_AGE_MS;
809
+ const maxBytes = options.maxBytes ?? SIGNOFF_CACHE_MAX_BYTES;
810
+ if (!Number.isFinite(maxAgeMs) || maxAgeMs < 0 || !Number.isFinite(maxBytes) || maxBytes < 0) {
811
+ throw new Error("signoff: cache limits must be non-negative finite numbers");
812
+ }
813
+ return withCacheLock(root, () => {
814
+ const active = protectedPaths(root);
815
+ protectLegacyRuns(root, active);
816
+ for (const path of options.keepPaths ?? []) active.paths.add(resolve2(path));
817
+ const candidates = entries(root, active.paths, active.unknown);
818
+ const sizes = /* @__PURE__ */ new Map();
819
+ const references = /* @__PURE__ */ new Map();
820
+ for (const entry of candidates) {
821
+ collectInodes(entry.path, entry.inodes, sizes);
822
+ for (const key of entry.inodes) references.set(key, (references.get(key) ?? 0) + 1);
823
+ }
824
+ let bytes = [...sizes.values()].reduce((sum, size) => sum + size, 0);
825
+ const beforeBytes = bytes;
826
+ const selected = [];
827
+ const select = (entry) => {
828
+ selected.push(entry);
829
+ for (const key of entry.inodes) {
830
+ const remaining = (references.get(key) ?? 1) - 1;
831
+ references.set(key, remaining);
832
+ if (remaining === 0) bytes -= sizes.get(key) ?? 0;
833
+ }
834
+ };
835
+ const eligible = candidates.filter((entry) => !entry.protected).sort((a, b) => a.mtimeMs - b.mtimeMs);
836
+ for (const entry of eligible) {
837
+ if (nowMs - entry.mtimeMs >= maxAgeMs) select(entry);
838
+ }
839
+ for (const entry of eligible) {
840
+ if (bytes <= maxBytes) break;
841
+ if (!selected.includes(entry)) select(entry);
842
+ }
843
+ for (const entry of selected) removeEntry(entry);
844
+ return {
845
+ beforeBytes,
846
+ afterBytes: bytes,
847
+ removed: selected.map((entry) => entry.path),
848
+ overBudgetBytes: Math.max(0, bytes - maxBytes)
849
+ };
850
+ });
851
+ }
568
852
 
569
853
  // src/signoff/node-version.ts
570
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
571
- import { join as join5 } from "path";
854
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
855
+ import { join as join6 } from "path";
572
856
 
573
857
  // src/signoff/workflow-pin.ts
574
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync3 } from "fs";
575
- import { join as join4 } from "path";
576
- var WORKFLOW_DIR = join4(".github", "workflows");
858
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
859
+ import { join as join5 } from "path";
860
+ var WORKFLOW_DIR = join5(".github", "workflows");
577
861
  function scalarValue(raw) {
578
862
  const withoutComment = raw.replace(/\s+#.*$/, "").trim();
579
863
  const quoted = /^(['"])(.*)\1$/.exec(withoutComment);
@@ -623,24 +907,24 @@ function pinsInWorkflow(repoRoot, file, source) {
623
907
  pins.push({ file, value, via: "node-version" });
624
908
  continue;
625
909
  }
626
- const target = join4(repoRoot, value);
627
- if (!existsSync4(target)) {
910
+ const target = join5(repoRoot, value);
911
+ if (!existsSync5(target)) {
628
912
  throw new Error(
629
913
  `signoff: ${file} reads its Node pin from "${value}" (node-version-file) and that file does not exist. The workflow this gate replaces cannot itself run, so there is nothing to verify against.`
630
914
  );
631
915
  }
632
- const declared = readFileSync3(target, "utf8").split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
916
+ const declared = readFileSync4(target, "utf8").split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
633
917
  if (declared !== void 0) pins.push({ file, value: declared, via: `node-version-file ${value}` });
634
918
  }
635
919
  return pins;
636
920
  }
637
921
  function scanMergeGateNodePins(repoRoot) {
638
- const dir = join4(repoRoot, WORKFLOW_DIR);
639
- if (!existsSync4(dir)) return [];
922
+ const dir = join5(repoRoot, WORKFLOW_DIR);
923
+ if (!existsSync5(dir)) return [];
640
924
  const pins = [];
641
- const files = readdirSync(dir).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).sort();
925
+ const files = readdirSync2(dir).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).sort();
642
926
  for (const name of files) {
643
- const source = readFileSync3(join4(dir, name), "utf8");
927
+ const source = readFileSync4(join5(dir, name), "utf8");
644
928
  if (!triggersOnPullRequest(source)) continue;
645
929
  pins.push(...pinsInWorkflow(repoRoot, `${WORKFLOW_DIR}/${name}`, source));
646
930
  }
@@ -690,9 +974,9 @@ function resolveNodeRequirement(repoRoot, configured) {
690
974
  return { major, declared: configured.trim(), source: "signoff config `nodeVersion`" };
691
975
  }
692
976
  let fromNvmrc = null;
693
- const nvmrc = join5(repoRoot, ".nvmrc");
694
- if (existsSync5(nvmrc)) {
695
- const raw = readFileSync4(nvmrc, "utf8").trim();
977
+ const nvmrc = join6(repoRoot, ".nvmrc");
978
+ if (existsSync6(nvmrc)) {
979
+ const raw = readFileSync5(nvmrc, "utf8").trim();
696
980
  const major = majorOf(raw);
697
981
  if (major !== null) fromNvmrc = { major, declared: raw, source: ".nvmrc" };
698
982
  }
@@ -716,9 +1000,9 @@ function assertNodeVersion(requirement, running = process.version) {
716
1000
  }
717
1001
  function assertNodeTypesVersion(repoRoot, requirement) {
718
1002
  if (!requirement) return;
719
- const packagePath = join5(repoRoot, "package.json");
720
- if (!existsSync5(packagePath)) return;
721
- const manifest = JSON.parse(readFileSync4(packagePath, "utf8"));
1003
+ const packagePath = join6(repoRoot, "package.json");
1004
+ if (!existsSync6(packagePath)) return;
1005
+ const manifest = JSON.parse(readFileSync5(packagePath, "utf8"));
722
1006
  const declared = manifest.devDependencies?.["@types/node"] ?? manifest.dependencies?.["@types/node"] ?? manifest.optionalDependencies?.["@types/node"];
723
1007
  if (!declared) return;
724
1008
  const declaredMajor = /^[~^]?v?(\d+)(?:\.|$)/.exec(declared.trim())?.[1];
@@ -727,11 +1011,11 @@ function assertNodeTypesVersion(repoRoot, requirement) {
727
1011
  `signoff: this repo pins Node ${requirement.declared} but declares @types/node ${declared}. Use the latest @types/node ${requirement.major}.x release.`
728
1012
  );
729
1013
  }
730
- const installedPath = join5(repoRoot, "node_modules", "@types", "node", "package.json");
731
- if (!existsSync5(installedPath)) {
1014
+ const installedPath = join6(repoRoot, "node_modules", "@types", "node", "package.json");
1015
+ if (!existsSync6(installedPath)) {
732
1016
  throw new Error(`signoff: @types/node is declared but missing after install in ${repoRoot}.`);
733
1017
  }
734
- const installed = JSON.parse(readFileSync4(installedPath, "utf8"));
1018
+ const installed = JSON.parse(readFileSync5(installedPath, "utf8"));
735
1019
  const installedMajor = majorOf(installed.version ?? "");
736
1020
  if (installedMajor !== requirement.major) {
737
1021
  throw new Error(
@@ -798,7 +1082,7 @@ function runCommand(options) {
798
1082
  killGraceMs = DEFAULT_KILL_GRACE_MS,
799
1083
  onData
800
1084
  } = options;
801
- return new Promise((resolve7, reject) => {
1085
+ return new Promise((resolve8, reject) => {
802
1086
  const startedAt = Date.now();
803
1087
  const buffer = new BoundedOutput(maxOutputBytes);
804
1088
  let timedOut = false;
@@ -848,7 +1132,7 @@ function runCommand(options) {
848
1132
  });
849
1133
  child.on("close", (code, sig) => {
850
1134
  cleanup();
851
- resolve7({
1135
+ resolve8({
852
1136
  command,
853
1137
  cwd,
854
1138
  // A signalled process reports code `null`; 128+n is the shell's own
@@ -1015,8 +1299,8 @@ function planAttempts(step, seedBase, overrideRuns) {
1015
1299
 
1016
1300
  // src/signoff/store.ts
1017
1301
  import { createHash as createHash2 } from "crypto";
1018
- import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, utimesSync, writeFileSync } from "fs";
1019
- import { join as join6, relative, sep } from "path";
1302
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync3, utimesSync, writeFileSync as writeFileSync2 } from "fs";
1303
+ import { basename as basename3, join as join7, relative as relative2, sep as sep2 } from "path";
1020
1304
  var MANIFEST_FILES = [
1021
1305
  "pnpm-lock.yaml",
1022
1306
  "pnpm-workspace.yaml",
@@ -1029,12 +1313,12 @@ var MANIFEST_FILES = [
1029
1313
  ];
1030
1314
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".wrangler", ".react-router"]);
1031
1315
  function collectManifests(dir, root, out) {
1032
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
1316
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1033
1317
  if (entry.isDirectory()) {
1034
1318
  if (SKIP_DIRS.has(entry.name)) continue;
1035
- collectManifests(join6(dir, entry.name), root, out);
1319
+ collectManifests(join7(dir, entry.name), root, out);
1036
1320
  } else if (MANIFEST_FILES.includes(entry.name)) {
1037
- out.push(relative(root, join6(dir, entry.name)).split(sep).join("/"));
1321
+ out.push(relative2(root, join7(dir, entry.name)).split(sep2).join("/"));
1038
1322
  }
1039
1323
  }
1040
1324
  }
@@ -1048,26 +1332,27 @@ function manifestCacheKey(treePath, files) {
1048
1332
  for (const rel of files) {
1049
1333
  hash.update(rel);
1050
1334
  hash.update("\0");
1051
- hash.update(createHash2("sha256").update(readFileSync5(join6(treePath, rel))).digest("hex"));
1335
+ hash.update(createHash2("sha256").update(readFileSync6(join7(treePath, rel))).digest("hex"));
1052
1336
  hash.update("\n");
1053
1337
  }
1054
1338
  return hash.digest("hex");
1055
1339
  }
1056
- function pruneStores(storesRoot, keep) {
1057
- if (!existsSync6(storesRoot)) return [];
1058
- const entries = readdirSync2(storesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
1059
- const full = join6(storesRoot, entry.name);
1060
- return { full, mtimeMs: statSync2(full).mtimeMs };
1340
+ function pruneStores(storesRoot, keep, protectedKeys) {
1341
+ if (!existsSync7(storesRoot)) return [];
1342
+ const entries2 = readdirSync3(storesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^[a-f0-9]{64}$/.test(entry.name)).map((entry) => {
1343
+ const full = join7(storesRoot, entry.name);
1344
+ return { full, mtimeMs: statSync3(full).mtimeMs };
1061
1345
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
1062
1346
  const pruned = [];
1063
- for (const stale of entries.slice(keep)) {
1064
- rmSync2(stale.full, { recursive: true, force: true });
1347
+ for (const stale of entries2.slice(keep)) {
1348
+ if (protectedKeys.has(basename3(stale.full))) continue;
1349
+ rmSync3(stale.full, { recursive: true, force: true });
1065
1350
  pruned.push(stale.full);
1066
1351
  }
1067
1352
  return pruned;
1068
1353
  }
1069
1354
  function resolveStore(options) {
1070
- const { treePath, cacheDir, generations = 4 } = options;
1355
+ const { treePath, cacheDir, generations = 4, protectedStoreKeys = /* @__PURE__ */ new Set() } = options;
1071
1356
  const files = manifestFiles(treePath);
1072
1357
  if (files.length === 0) {
1073
1358
  throw new Error(
@@ -1075,25 +1360,25 @@ function resolveStore(options) {
1075
1360
  );
1076
1361
  }
1077
1362
  const cacheKey = manifestCacheKey(treePath, files);
1078
- const storesRoot = join6(cacheDir, "stores");
1079
- const storeDir = join6(storesRoot, cacheKey);
1080
- const marker = join6(storeDir, ".signoff-store.json");
1081
- const hit = existsSync6(storeDir) && readdirSync2(storeDir).some((entry) => entry !== ".signoff-store.json");
1082
- mkdirSync(storeDir, { recursive: true });
1083
- writeFileSync(marker, `${JSON.stringify({ cacheKey, keyedOn: files, usedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1363
+ const storesRoot = join7(cacheDir, "stores");
1364
+ const storeDir = join7(storesRoot, cacheKey);
1365
+ const marker = join7(storeDir, ".signoff-store.json");
1366
+ const hit = existsSync7(storeDir) && readdirSync3(storeDir).some((entry) => entry !== ".signoff-store.json");
1367
+ mkdirSync2(storeDir, { recursive: true });
1368
+ writeFileSync2(marker, `${JSON.stringify({ cacheKey, keyedOn: files, usedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
1084
1369
  `);
1085
1370
  const now = /* @__PURE__ */ new Date();
1086
1371
  utimesSync(storeDir, now, now);
1087
- return { storeDir, cacheKey, hit, keyedOn: files, pruned: pruneStores(storesRoot, generations) };
1372
+ return { storeDir, cacheKey, hit, keyedOn: files, pruned: pruneStores(storesRoot, generations, protectedStoreKeys) };
1088
1373
  }
1089
1374
 
1090
1375
  // src/signoff/workspace.ts
1091
- import { spawnSync as spawnSync3 } from "child_process";
1376
+ import { spawnSync as spawnSync4 } from "child_process";
1092
1377
  import { createHash as createHash3 } from "crypto";
1093
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
1094
- import { dirname as dirname2, isAbsolute, join as join7, resolve as resolve2 } from "path";
1378
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
1379
+ import { dirname as dirname2, isAbsolute, join as join8, resolve as resolve3 } from "path";
1095
1380
  function git(args, cwd) {
1096
- const result = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
1381
+ const result = spawnSync4("git", args, { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
1097
1382
  if (result.error) throw new Error(`signoff: git ${args.join(" ")} failed to start: ${result.error.message}`);
1098
1383
  if (result.status !== 0) {
1099
1384
  throw new Error(`signoff: git ${args.join(" ")} exited ${result.status}
@@ -1113,8 +1398,8 @@ function materializeCleanTree(options) {
1113
1398
  const head = git(["rev-parse", "HEAD"], root).trim();
1114
1399
  const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], root).trim();
1115
1400
  git(["worktree", "prune"], root);
1116
- mkdirSync2(dirname2(dest), { recursive: true });
1117
- if (existsSync7(dest)) rmSync3(dest, { recursive: true, force: true });
1401
+ mkdirSync3(dirname2(dest), { recursive: true });
1402
+ if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
1118
1403
  git(["worktree", "add", "--detach", "--quiet", dest, head], root);
1119
1404
  let diffSha256 = null;
1120
1405
  let untrackedFiles = [];
@@ -1122,29 +1407,29 @@ function materializeCleanTree(options) {
1122
1407
  const patch = git(["diff", "HEAD", "--binary", "--no-color", "--no-ext-diff"], root);
1123
1408
  if (patch.length > 0) {
1124
1409
  diffSha256 = createHash3("sha256").update(patch).digest("hex");
1125
- const patchFile = join7(dirname2(dest), `${dest.split("/").pop() ?? "tree"}.patch`);
1126
- writeFileSync2(patchFile, patch);
1410
+ const patchFile = join8(dirname2(dest), `${dest.split("/").pop() ?? "tree"}.patch`);
1411
+ writeFileSync3(patchFile, patch);
1127
1412
  git(["apply", "--binary", "--whitespace=nowarn", patchFile], dest);
1128
- rmSync3(patchFile, { force: true });
1413
+ rmSync4(patchFile, { force: true });
1129
1414
  }
1130
1415
  untrackedFiles = zsplit(git(["ls-files", "--others", "--exclude-standard", "-z"], root));
1131
1416
  for (const rel of untrackedFiles) {
1132
- const target = join7(dest, rel);
1133
- mkdirSync2(dirname2(target), { recursive: true });
1134
- copyFileSync(join7(root, rel), target);
1417
+ const target = join8(dest, rel);
1418
+ mkdirSync3(dirname2(target), { recursive: true });
1419
+ copyFileSync(join8(root, rel), target);
1135
1420
  }
1136
1421
  }
1137
1422
  const carried = [];
1138
1423
  for (const rel of carryFiles) {
1139
1424
  if (isAbsolute(rel)) throw new Error(`signoff: carryFiles must be repo-relative; got "${rel}"`);
1140
- const from = resolve2(root, rel);
1141
- if (!existsSync7(from)) {
1425
+ const from = resolve3(root, rel);
1426
+ if (!existsSync8(from)) {
1142
1427
  throw new Error(
1143
1428
  `signoff: carryFiles names "${rel}", which does not exist at ${from}. Remove it from the config or create the file \u2014 installing without it would resolve against a different registry than the one you think you are verifying.`
1144
1429
  );
1145
1430
  }
1146
- const target = join7(dest, rel);
1147
- mkdirSync2(dirname2(target), { recursive: true });
1431
+ const target = join8(dest, rel);
1432
+ mkdirSync3(dirname2(target), { recursive: true });
1148
1433
  copyFileSync(from, target);
1149
1434
  carried.push(rel);
1150
1435
  }
@@ -1165,9 +1450,9 @@ function removeCleanTree(tree) {
1165
1450
  }
1166
1451
 
1167
1452
  // src/signoff/run.ts
1168
- var DEFAULT_CACHE_DIR = join8(homedir(), ".cache", "agent-app-signoff");
1453
+ var DEFAULT_CACHE_DIR = join9(homedir(), ".cache", "agent-app-signoff");
1169
1454
  function hostFacts(treePath, requirement) {
1170
- const pm = spawnSync4("pnpm", ["--version"], { cwd: treePath, encoding: "utf8" });
1455
+ const pm = spawnSync5("pnpm", ["--version"], { cwd: treePath, encoding: "utf8" });
1171
1456
  return {
1172
1457
  node: process.version,
1173
1458
  nodePinned: requirement?.declared ?? null,
@@ -1205,17 +1490,35 @@ function withStoreDir(command, flag, storeDir) {
1205
1490
  async function runSignoff(options = {}) {
1206
1491
  const startedAt = /* @__PURE__ */ new Date();
1207
1492
  const wallStart = Date.now();
1208
- const repoDir = resolve3(options.repoDir ?? process.cwd());
1493
+ const repoDir = resolve4(options.repoDir ?? process.cwd());
1209
1494
  const repoRoot = repoRootOf(repoDir);
1210
1495
  const source = options.source ?? "working-tree";
1211
- let cacheDir = resolve3(options.cacheDir ?? DEFAULT_CACHE_DIR);
1212
- const treePath = () => join8(cacheDir, "trees", `${basename2(repoRoot)}-${process.pid}`);
1496
+ let cacheDir = resolve4(options.cacheDir ?? DEFAULT_CACHE_DIR);
1497
+ const treeName = `${basename4(repoRoot)}-${process.pid}-${randomUUID2()}`;
1498
+ const treePath = () => join9(cacheDir, "trees", treeName);
1213
1499
  let tree = null;
1500
+ const sessions = [];
1501
+ let session = await openSignoffCacheSession(cacheDir, treePath());
1502
+ sessions.push(session);
1503
+ cacheDir = session.cacheDir;
1504
+ const prune = async (root, keepPaths = []) => {
1505
+ const result = await pruneSignoffCache(root, { keepPaths });
1506
+ if (result.removed.length || result.overBudgetBytes) {
1507
+ options.onEvent?.({ kind: "cache-prune", ...result });
1508
+ }
1509
+ };
1214
1510
  try {
1511
+ await prune(cacheDir);
1215
1512
  tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath(), source });
1216
1513
  let loaded = await loadSignoffConfig({ repoRoot: tree.path, configPath: options.configPath });
1217
- cacheDir = resolve3(options.cacheDir ?? loaded.config.cacheDir ?? DEFAULT_CACHE_DIR);
1514
+ cacheDir = resolve4(options.cacheDir ?? loaded.config.cacheDir ?? DEFAULT_CACHE_DIR);
1218
1515
  if (tree.path !== treePath() || (loaded.config.carryFiles?.length ?? 0) > 0) {
1516
+ if (session.cacheDir !== cacheDir) {
1517
+ session = await openSignoffCacheSession(cacheDir, treePath());
1518
+ sessions.push(session);
1519
+ cacheDir = session.cacheDir;
1520
+ await prune(cacheDir);
1521
+ }
1219
1522
  removeCleanTree(tree);
1220
1523
  tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath(), source, carryFiles: loaded.config.carryFiles });
1221
1524
  loaded = await loadSignoffConfig({ repoRoot: tree.path, configPath: options.configPath });
@@ -1226,10 +1529,16 @@ async function runSignoff(options = {}) {
1226
1529
  const nodeRequirement = resolveNodeRequirement(tree.path, config.nodeVersion);
1227
1530
  assertNodeVersion(nodeRequirement);
1228
1531
  options.onEvent?.({ kind: "tree", path: tree.path, head: tree.head, dirty: tree.dirty });
1229
- const store = resolveStore({ treePath: tree.path, cacheDir, generations: config.storeGenerations });
1532
+ await session.protectStore(manifestCacheKey(tree.path, manifestFiles(tree.path)));
1533
+ const store = await withCacheLock(cacheDir, () => resolveStore({
1534
+ treePath: tree.path,
1535
+ cacheDir,
1536
+ generations: config.storeGenerations,
1537
+ protectedStoreKeys: activeStoreKeys(cacheDir)
1538
+ }));
1230
1539
  options.onEvent?.({ kind: "store", storeDir: store.storeDir, cacheHit: store.hit, cacheKey: store.cacheKey });
1231
1540
  const installSpec = config.install ?? {};
1232
- const installCwd = join8(tree.path, installSpec.cwd ?? ".");
1541
+ const installCwd = join9(tree.path, installSpec.cwd ?? ".");
1233
1542
  const installCommand = withStoreDir(
1234
1543
  installSpec.run ?? "pnpm install --frozen-lockfile",
1235
1544
  installSpec.storeDirFlag,
@@ -1306,7 +1615,7 @@ async function runSignoff(options = {}) {
1306
1615
  options.onEvent?.({ kind: "step-start", name: step.name, command: plan.command, seed: plan.seed });
1307
1616
  const result = await runCommand({
1308
1617
  command: plan.command,
1309
- cwd: join8(treeRoot, step.cwd ?? "."),
1618
+ cwd: join9(treeRoot, step.cwd ?? "."),
1310
1619
  env: buildEnv(sharedEnv, [step.env]),
1311
1620
  timeoutMs: step.timeoutMs,
1312
1621
  signal
@@ -1340,7 +1649,14 @@ async function runSignoff(options = {}) {
1340
1649
  options
1341
1650
  });
1342
1651
  } finally {
1343
- if (tree && !options.keepWorkspace && existsSync8(tree.path)) removeCleanTree(tree);
1652
+ try {
1653
+ if (tree && !options.keepWorkspace && existsSync9(tree.path)) removeCleanTree(tree);
1654
+ } finally {
1655
+ for (const active of sessions) {
1656
+ await active.close();
1657
+ await prune(active.cacheDir, options.keepWorkspace && tree ? [tree.path] : []);
1658
+ }
1659
+ }
1344
1660
  }
1345
1661
  }
1346
1662
  function runWorkerDefaults(treeRoot, workerConfigs, options) {
@@ -1772,8 +2088,8 @@ function round(value) {
1772
2088
  }
1773
2089
 
1774
2090
  // src/signoff/shell-alignment-cli.ts
1775
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1776
- import { dirname as dirname3, resolve as resolve4 } from "path";
2091
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2092
+ import { dirname as dirname3, resolve as resolve5 } from "path";
1777
2093
  function shellAlignmentUsage() {
1778
2094
  return [
1779
2095
  "Usage: agent-app-signoff shell-alignment --base-url <url> --route <path> [--route <path> ...] [options]",
@@ -1823,10 +2139,10 @@ function parseShellAlignmentArgs(argv) {
1823
2139
  });
1824
2140
  break;
1825
2141
  case "--storage-state":
1826
- out.storageState = resolve4(take());
2142
+ out.storageState = resolve5(take());
1827
2143
  break;
1828
2144
  case "--theme-script":
1829
- out.themeScript = readFileSync6(resolve4(take()), "utf8");
2145
+ out.themeScript = readFileSync7(resolve5(take()), "utf8");
1830
2146
  break;
1831
2147
  case "--ready":
1832
2148
  out.readySelector = take();
@@ -1838,7 +2154,7 @@ function parseShellAlignmentArgs(argv) {
1838
2154
  break;
1839
2155
  }
1840
2156
  case "--screenshots":
1841
- out.screenshotDir = resolve4(take());
2157
+ out.screenshotDir = resolve5(take());
1842
2158
  break;
1843
2159
  case "--json":
1844
2160
  out.jsonPath = take();
@@ -1868,12 +2184,12 @@ ${shellAlignmentUsage()}
1868
2184
  `);
1869
2185
  return 2;
1870
2186
  }
1871
- if (options.screenshotDir) mkdirSync3(options.screenshotDir, { recursive: true });
2187
+ if (options.screenshotDir) mkdirSync4(options.screenshotDir, { recursive: true });
1872
2188
  const report = await checkShellAlignment(options);
1873
2189
  if (options.jsonPath) {
1874
- const abs = resolve4(options.jsonPath);
1875
- mkdirSync3(dirname3(abs), { recursive: true });
1876
- writeFileSync3(abs, `${JSON.stringify(report, null, 2)}
2190
+ const abs = resolve5(options.jsonPath);
2191
+ mkdirSync4(dirname3(abs), { recursive: true });
2192
+ writeFileSync4(abs, `${JSON.stringify(report, null, 2)}
1877
2193
  `);
1878
2194
  }
1879
2195
  process.stdout.write(`${formatShellAlignmentReport(report)}
@@ -2165,8 +2481,8 @@ function formatSurfaceLadderReport(report) {
2165
2481
  }
2166
2482
 
2167
2483
  // src/signoff/surface-ladder-cli.ts
2168
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
2169
- import { dirname as dirname4, resolve as resolve5 } from "path";
2484
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
2485
+ import { dirname as dirname4, resolve as resolve6 } from "path";
2170
2486
  function surfaceLadderUsage() {
2171
2487
  return [
2172
2488
  "Usage: agent-app-signoff surface-ladder --base-url <url> --route <path> [--route <path> ...] [options]",
@@ -2224,10 +2540,10 @@ function parseSurfaceLadderArgs(argv) {
2224
2540
  });
2225
2541
  break;
2226
2542
  case "--storage-state":
2227
- out.storageState = resolve5(take());
2543
+ out.storageState = resolve6(take());
2228
2544
  break;
2229
2545
  case "--theme-script":
2230
- out.themeScript = readFileSync7(resolve5(take()), "utf8");
2546
+ out.themeScript = readFileSync8(resolve6(take()), "utf8");
2231
2547
  break;
2232
2548
  case "--ready":
2233
2549
  out.readySelector = take();
@@ -2245,10 +2561,10 @@ function parseSurfaceLadderArgs(argv) {
2245
2561
  break;
2246
2562
  }
2247
2563
  case "--inject-css":
2248
- out.injectCss = readFileSync7(resolve5(take()), "utf8");
2564
+ out.injectCss = readFileSync8(resolve6(take()), "utf8");
2249
2565
  break;
2250
2566
  case "--screenshots":
2251
- out.screenshotDir = resolve5(take());
2567
+ out.screenshotDir = resolve6(take());
2252
2568
  break;
2253
2569
  case "--json":
2254
2570
  out.jsonPath = take();
@@ -2278,12 +2594,12 @@ ${surfaceLadderUsage()}
2278
2594
  `);
2279
2595
  return 2;
2280
2596
  }
2281
- if (options.screenshotDir) mkdirSync4(options.screenshotDir, { recursive: true });
2597
+ if (options.screenshotDir) mkdirSync5(options.screenshotDir, { recursive: true });
2282
2598
  const report = await checkSurfaceLadder(options);
2283
2599
  if (options.jsonPath) {
2284
- const abs = resolve5(options.jsonPath);
2285
- mkdirSync4(dirname4(abs), { recursive: true });
2286
- writeFileSync4(abs, `${JSON.stringify(report, null, 2)}
2600
+ const abs = resolve6(options.jsonPath);
2601
+ mkdirSync5(dirname4(abs), { recursive: true });
2602
+ writeFileSync5(abs, `${JSON.stringify(report, null, 2)}
2287
2603
  `);
2288
2604
  }
2289
2605
  process.stdout.write(`${formatSurfaceLadderReport(report)}
@@ -2376,8 +2692,8 @@ function formatWorkspaceSurfaceReport(report) {
2376
2692
  }
2377
2693
 
2378
2694
  // src/signoff/workspace-surface-cli.ts
2379
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
2380
- import { dirname as dirname5, resolve as resolve6 } from "path";
2695
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
2696
+ import { dirname as dirname5, resolve as resolve7 } from "path";
2381
2697
  var USAGE = [
2382
2698
  "Usage: agent-app-signoff workspace-surface --base-url <url> --route <path> [--route <path> ...] [options]",
2383
2699
  "",
@@ -2406,12 +2722,12 @@ ${USAGE}
2406
2722
  `);
2407
2723
  return 2;
2408
2724
  }
2409
- if (options.screenshotDir) mkdirSync5(options.screenshotDir, { recursive: true });
2725
+ if (options.screenshotDir) mkdirSync6(options.screenshotDir, { recursive: true });
2410
2726
  const report = await checkWorkspaceSurface(options);
2411
2727
  if (options.jsonPath) {
2412
- const abs = resolve6(options.jsonPath);
2413
- mkdirSync5(dirname5(abs), { recursive: true });
2414
- writeFileSync5(abs, `${JSON.stringify(report, null, 2)}
2728
+ const abs = resolve7(options.jsonPath);
2729
+ mkdirSync6(dirname5(abs), { recursive: true });
2730
+ writeFileSync6(abs, `${JSON.stringify(report, null, 2)}
2415
2731
  `);
2416
2732
  }
2417
2733
  process.stdout.write(`${formatWorkspaceSurfaceReport(report)}
@@ -2469,4 +2785,4 @@ export {
2469
2785
  formatWorkspaceSurfaceReport,
2470
2786
  runWorkspaceSurfaceCli
2471
2787
  };
2472
- //# sourceMappingURL=chunk-J3N6RNMH.js.map
2788
+ //# sourceMappingURL=chunk-WDY47GBR.js.map