@intentius/behold 0.16.0 → 0.17.0

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.
package/dist/cli.js CHANGED
@@ -716,11 +716,11 @@ async function createK8sClient(options = {}) {
716
716
  }
717
717
  return void 0;
718
718
  }
719
- async function resolve10(selector, signal) {
719
+ async function resolve12(selector, signal) {
720
720
  return "apiVersion" in selector ? resolveByGvk(selector.apiVersion, selector.kind, signal) : resolveByResourceString(selector.resource, selector.group, signal);
721
721
  }
722
722
  async function resolveOrThrow(selector, signal) {
723
- const info = await resolve10(selector, signal);
723
+ const info = await resolve12(selector, signal);
724
724
  if (!info) throw new UnknownResourceError(selectorText(selector));
725
725
  return info;
726
726
  }
@@ -732,7 +732,7 @@ async function createK8sClient(options = {}) {
732
732
  return parts.join("/");
733
733
  }
734
734
  async function pathFor(ref, signal) {
735
- const info = await resolve10({ apiVersion: ref.apiVersion, kind: ref.kind }, signal);
735
+ const info = await resolve12({ apiVersion: ref.apiVersion, kind: ref.kind }, signal);
736
736
  if (!info) return void 0;
737
737
  return objectPath(info, ref.name, ref.namespace);
738
738
  }
@@ -872,7 +872,7 @@ async function createK8sClient(options = {}) {
872
872
  return {
873
873
  provenance,
874
874
  defaultNamespace,
875
- resolve: resolve10,
875
+ resolve: resolve12,
876
876
  read,
877
877
  pathFor,
878
878
  readIfPresent,
@@ -1065,8 +1065,8 @@ var init_src = __esm({
1065
1065
  });
1066
1066
 
1067
1067
  // src/cli.ts
1068
- import { resolve as resolve9, dirname as dirname7, join as join21 } from "node:path";
1069
- import { realpathSync, existsSync as existsSync14, readFileSync as readFileSync16, rmSync as rmSync2 } from "node:fs";
1068
+ import { resolve as resolve11, dirname as dirname7, join as join22 } from "node:path";
1069
+ import { realpathSync, existsSync as existsSync15, readFileSync as readFileSync17, rmSync as rmSync3 } from "node:fs";
1070
1070
  import { fileURLToPath as fileURLToPath5 } from "node:url";
1071
1071
  import { spawn as spawn6, spawnSync as spawnSync2 } from "node:child_process";
1072
1072
 
@@ -1147,8 +1147,8 @@ import { serve } from "@hono/node-server";
1147
1147
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1148
1148
  import { execFile as execFile3 } from "node:child_process";
1149
1149
  import { promisify as promisify3 } from "node:util";
1150
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
1151
- import { basename, dirname as dirname5, join as join19, relative as relative5, resolve as resolve7, sep as sep4 } from "node:path";
1150
+ import { existsSync as existsSync14, readFileSync as readFileSync15 } from "node:fs";
1151
+ import { basename, dirname as dirname5, join as join20, relative as relative5, resolve as resolve9, sep as sep6 } from "node:path";
1152
1152
 
1153
1153
  // src/recents.ts
1154
1154
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
@@ -1185,7 +1185,7 @@ import { spawn } from "node:child_process";
1185
1185
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
1186
1186
  import { createRequire } from "node:module";
1187
1187
  import { fileURLToPath } from "node:url";
1188
- import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
1188
+ import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
1189
1189
 
1190
1190
  // src/project.ts
1191
1191
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
@@ -1691,14 +1691,14 @@ function unescapeDoubleQuoted(body) {
1691
1691
 
1692
1692
  // src/carve-manifest.ts
1693
1693
  import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
1694
- import { dirname as dirname2, isAbsolute, join as join3, relative, resolve } from "node:path";
1694
+ import { dirname as dirname2, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
1695
1695
  var CARVE_MANIFEST_SUFFIX = ".carve.json";
1696
1696
  var APPLY_IS_HUMAN = "`chant carve apply` graduates ownership \u2014 it resolves the marker that makes chant the owner of a live resource. behold shows what the manifest records and echoes the command; it has no apply endpoint and no apply button, by design. Read the graduation plan, then run it yourself.";
1697
1697
  var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1698
1698
  var strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
1699
1699
  function parseCarveManifest(value) {
1700
1700
  if (!isRecord(value)) return null;
1701
- if (value.version !== 1) return null;
1701
+ if (value.version !== 1 && value.version !== 2) return null;
1702
1702
  if (typeof value.target !== "string" || !value.target) return null;
1703
1703
  return value;
1704
1704
  }
@@ -1835,17 +1835,23 @@ async function discoverCarveStates(dirs, read, io = nodeCarveIo, depth = MANIFES
1835
1835
  return byTarget;
1836
1836
  }
1837
1837
  function joinCarvedSources(ir, states, memberDir) {
1838
- const byFile = /* @__PURE__ */ new Map();
1839
- for (const s of states) for (const f of s.sourceFiles) byFile.set(resolve(f), s);
1840
- if (byFile.size === 0) return { ir, joined: 0 };
1838
+ const root = resolve(memberDir);
1839
+ const candidates = [];
1840
+ for (const s of states) {
1841
+ for (const f of s.sourceFiles) {
1842
+ const abs = resolve(f);
1843
+ if (abs === root || abs.startsWith(root + sep)) candidates.push({ abs, state: s });
1844
+ }
1845
+ }
1846
+ if (candidates.length === 0) return { ir, joined: 0 };
1841
1847
  let joined = 0;
1842
1848
  const nodes = ir.nodes.map((n) => {
1843
1849
  const file = n.sourceLoc?.file;
1844
1850
  if (!file) return n;
1845
- const abs = resolve(memberDir, file);
1846
- const state = byFile.get(abs);
1847
- if (!state) return n;
1851
+ const hit = emittedFileFor(file, root, candidates);
1852
+ if (!hit) return n;
1848
1853
  joined++;
1854
+ const { state, abs } = hit;
1849
1855
  const carve = {
1850
1856
  target: state.target,
1851
1857
  ...state.tfType ? { tfType: state.tfType } : {},
@@ -1853,12 +1859,24 @@ function joinCarvedSources(ir, states, memberDir) {
1853
1859
  graduated: state.graduated,
1854
1860
  ...state.at ? { at: state.at } : {},
1855
1861
  ...state.from ? { from: state.from } : {},
1856
- file: relative(memberDir, abs).split("\\").join("/")
1862
+ file: relative(root, abs).split(sep).join("/")
1857
1863
  };
1858
1864
  return { ...n, attrs: { ...n.attrs, carved: state.target, _carve: carve } };
1859
1865
  });
1860
1866
  return { ir: joined ? { ...ir, nodes } : ir, joined };
1861
1867
  }
1868
+ function emittedFileFor(file, root, candidates) {
1869
+ const rel = file.split("\\").join("/").replace(/^\.\//, "");
1870
+ if (isAbsolute(rel)) {
1871
+ const abs = resolve(rel);
1872
+ return candidates.find((c) => c.abs === abs);
1873
+ }
1874
+ const exact = resolve(root, rel);
1875
+ const direct = candidates.find((c) => c.abs === exact);
1876
+ if (direct) return direct;
1877
+ const suffix = candidates.filter((c) => c.abs.split(sep).join("/").endsWith("/" + rel));
1878
+ return suffix.length === 1 ? suffix[0] : void 0;
1879
+ }
1862
1880
  function emittedNodesFor(ir, states, memberDir) {
1863
1881
  const out = /* @__PURE__ */ new Map();
1864
1882
  if (!ir) return out;
@@ -1992,6 +2010,46 @@ function carveStateNote(progress, states) {
1992
2010
  return progress.detail ? `${head}, ${progress.detail}.` : `${head}.`;
1993
2011
  }
1994
2012
 
2013
+ // src/foreign.ts
2014
+ import { isAbsolute as isAbsolute2, resolve as resolve2, sep as sep2 } from "node:path";
2015
+ function dropForeignDeclarations(ir, projectDir) {
2016
+ const root = resolve2(projectDir);
2017
+ const foreign = [];
2018
+ for (const n of ir.nodes) {
2019
+ const file = n.sourceLoc?.file;
2020
+ if (!file || !isAbsolute2(file)) continue;
2021
+ const abs = resolve2(file);
2022
+ if (abs === root || abs.startsWith(root + sep2)) continue;
2023
+ foreign.push({ id: n.id, kind: n.kind, file });
2024
+ }
2025
+ if (foreign.length === 0) return ir;
2026
+ const gone = new Set(foreign.map((f) => f.id));
2027
+ const groups = {};
2028
+ for (const [axis, members] of Object.entries(ir.groups ?? {})) {
2029
+ if (!members || typeof members !== "object") continue;
2030
+ const kept = {};
2031
+ for (const [box, ids] of Object.entries(members)) {
2032
+ const left = ids.filter((id) => !gone.has(id));
2033
+ if (left.length) kept[box] = left;
2034
+ }
2035
+ groups[axis] = kept;
2036
+ }
2037
+ return {
2038
+ ...ir,
2039
+ nodes: ir.nodes.filter((n) => !gone.has(n.id)),
2040
+ edges: ir.edges.filter((e) => !gone.has(e.from) && !gone.has(e.to)),
2041
+ groups,
2042
+ foreign
2043
+ };
2044
+ }
2045
+ function foreignNote(ir) {
2046
+ const f = ir.foreign;
2047
+ if (!f || f.length === 0) return void 0;
2048
+ const named = f.slice(0, 3).map((d) => d.id).join(", ");
2049
+ const more = f.length > 3 ? ` +${f.length - 3}` : "";
2050
+ return `${f.length} declaration${f.length === 1 ? "" : "s"} outside this project dropped (${named}${more}) \u2014 chant graphs Ops from the git root (chant#2058)`;
2051
+ }
2052
+
1995
2053
  // src/chant.ts
1996
2054
  function envOverridesFor(opts) {
1997
2055
  const overrides = {};
@@ -2042,7 +2100,7 @@ function chantPackageFrom(req) {
2042
2100
  return { root, bin: join4(root, manifest.bin?.chant ?? "bin/chant"), ...manifest.version ? { version: manifest.version } : {} };
2043
2101
  }
2044
2102
  function resolveLexicons(projectDir, lexicons) {
2045
- const req = createRequire(join4(resolve2(projectDir), "noop.js"));
2103
+ const req = createRequire(join4(resolve3(projectDir), "noop.js"));
2046
2104
  return lexicons.map((lexicon) => {
2047
2105
  const pkg = `@intentius/chant-lexicon-${lexicon}`;
2048
2106
  const found = packageManifestFrom(req, pkg);
@@ -2052,7 +2110,7 @@ function resolveLexicons(projectDir, lexicons) {
2052
2110
  function resolveChant(projectDir) {
2053
2111
  const own = chantPackageFrom(createRequire(import.meta.url));
2054
2112
  if (projectDir) {
2055
- const fromProject = chantPackageFrom(createRequire(join4(resolve2(projectDir), "noop.js")));
2113
+ const fromProject = chantPackageFrom(createRequire(join4(resolve3(projectDir), "noop.js")));
2056
2114
  if (fromProject) return { source: fromProject.root === own?.root ? "behold" : "project", ...fromProject };
2057
2115
  }
2058
2116
  if (own) return { source: "behold", ...own };
@@ -2179,9 +2237,9 @@ async function graphPath(projectDir, opts = {}) {
2179
2237
  }
2180
2238
  if (info.stacks?.length) {
2181
2239
  const picked = opts.stack ? info.stacks.find((s) => s.name === opts.stack) : void 0;
2182
- return resolve2(projectDir, (picked ?? info.stacks[0]).src);
2240
+ return resolve3(projectDir, (picked ?? info.stacks[0]).src);
2183
2241
  }
2184
- if (info.sourceDir) return resolve2(projectDir, info.sourceDir);
2242
+ if (info.sourceDir) return resolve3(projectDir, info.sourceDir);
2185
2243
  return legacyGraphPath(projectDir);
2186
2244
  }
2187
2245
  function graphArgs(src, format, opts, components) {
@@ -2189,7 +2247,8 @@ function graphArgs(src, format, opts, components) {
2189
2247
  }
2190
2248
  async function graphIr(projectDir, opts = {}) {
2191
2249
  const src = await graphPath(projectDir, opts);
2192
- return runChantJson(graphArgs(src, "ir", opts, false), projectDir, envOverridesFor(opts));
2250
+ const ir = await runChantJson(graphArgs(src, "ir", opts, false), projectDir, envOverridesFor(opts));
2251
+ return dropForeignDeclarations(ir, projectDir);
2193
2252
  }
2194
2253
  async function clusterRootGraphIr(projectDir, opts = {}) {
2195
2254
  if (!existsSync3(join4(projectDir, "cluster"))) return void 0;
@@ -2230,7 +2289,21 @@ function parseCiPipeline(stdout) {
2230
2289
  const script = Array.isArray(props?.script) ? props.script.map(String) : [];
2231
2290
  return { ...j, script };
2232
2291
  });
2233
- return { stages: parsed.stages, jobs, ...typeof parsed.env === "string" && parsed.env ? { env: parsed.env } : {} };
2292
+ const fromJson = typeof parsed.env === "string" && parsed.env ? parsed.env : void 0;
2293
+ const env = fromJson ?? pipelineEnvFromYaml(doc);
2294
+ return { stages: parsed.stages, jobs, ...env ? { env } : {} };
2295
+ }
2296
+ function pipelineEnvFromYaml(doc) {
2297
+ const name = doc?.name;
2298
+ if (typeof name === "string") {
2299
+ const m = /^chant-components-(.+)$/.exec(name);
2300
+ if (m) return m[1];
2301
+ }
2302
+ const env = doc?.env;
2303
+ if (env && typeof env === "object" && typeof env.CHANT_ENV === "string") {
2304
+ return env.CHANT_ENV;
2305
+ }
2306
+ return void 0;
2234
2307
  }
2235
2308
  function ciPipeline(projectDir, opts = {}, forge = "gitlab") {
2236
2309
  const args = ciPipelineArgs(opts, forge);
@@ -2423,7 +2496,7 @@ function synthesizeHelmReleases(ir, observed, owners) {
2423
2496
  }
2424
2497
 
2425
2498
  // src/gh-run.ts
2426
- import { spawn as spawn2 } from "node:child_process";
2499
+ import { execFileSync, spawn as spawn2 } from "node:child_process";
2427
2500
  import { readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
2428
2501
  import { join as join6 } from "node:path";
2429
2502
 
@@ -2469,6 +2542,9 @@ function foldPipelineLine(state, line) {
2469
2542
  }
2470
2543
  return out;
2471
2544
  }
2545
+ function losePipelineProgress(state) {
2546
+ return { ...state, status: "lost" };
2547
+ }
2472
2548
  function finishPipelineProgress(state, exitCode) {
2473
2549
  const terminal = exitCode === 0 ? "ok" : "failed";
2474
2550
  let out = state;
@@ -2479,19 +2555,19 @@ function finishPipelineProgress(state, exitCode) {
2479
2555
  }
2480
2556
 
2481
2557
  // src/gh-run.ts
2482
- var defaultGhExec = (args) => new Promise((resolve10) => {
2558
+ var defaultGhExec = (args) => new Promise((resolve12) => {
2483
2559
  let out = "";
2484
2560
  let proc;
2485
2561
  try {
2486
2562
  proc = spawn2("gh", args, { stdio: ["ignore", "pipe", "pipe"] });
2487
2563
  } catch {
2488
- resolve10({ code: 127, out: "" });
2564
+ resolve12({ code: 127, out: "" });
2489
2565
  return;
2490
2566
  }
2491
2567
  proc.stdout.on("data", (d) => out += d);
2492
2568
  proc.stderr.on("data", (d) => out += d);
2493
- proc.on("error", () => resolve10({ code: 127, out }));
2494
- proc.on("close", (code) => resolve10({ code: code ?? 1, out }));
2569
+ proc.on("error", () => resolve12({ code: 127, out }));
2570
+ proc.on("close", (code) => resolve12({ code: code ?? 1, out }));
2495
2571
  });
2496
2572
  async function ghReady(exec = defaultGhExec) {
2497
2573
  const { code, out } = await exec(["auth", "status"]);
@@ -2512,8 +2588,16 @@ function parseWorkflow(file, text) {
2512
2588
  const dispatchable = on === "workflow_dispatch" || Array.isArray(on) && on.includes("workflow_dispatch") || !!on && typeof on === "object" && "workflow_dispatch" in on || /^\s{2,}workflow_dispatch\s*:?\s*$/m.test(text);
2513
2589
  return { file, jobIds: jobs, dispatchable, ...typeof d.name === "string" && d.name ? { name: d.name } : {} };
2514
2590
  }
2591
+ function workflowsDir(projectDir) {
2592
+ let root = projectDir;
2593
+ try {
2594
+ root = execFileSync("git", ["-C", projectDir, "rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || projectDir;
2595
+ } catch {
2596
+ }
2597
+ return join6(root, ".github", "workflows");
2598
+ }
2515
2599
  function committedWorkflows(projectDir) {
2516
- const dir = join6(projectDir, ".github", "workflows");
2600
+ const dir = workflowsDir(projectDir);
2517
2601
  let files;
2518
2602
  try {
2519
2603
  files = readdirSync3(dir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
@@ -2544,9 +2628,16 @@ function resolveWorkflow(projectDir, pipeline, designated) {
2544
2628
  if (named.length > 1) return { reason: `${named.length} committed workflows are named ${ciWorkflowName(pipeline.env)} (${named.map((w) => w.file).join(", ")}) \u2014 designate one in .behold.json's executor block` };
2545
2629
  }
2546
2630
  const wanted = new Set(pipeline.jobs.map((j) => j.jobName));
2547
- const scored = dispatchable.map((w) => ({ w, overlap: w.jobIds.filter((id) => wanted.has(id)).length })).filter((x) => x.overlap > 0).sort((a, b) => b.overlap - a.overlap);
2631
+ const otherEnv = (w) => {
2632
+ const m = w.name ? /^chant-components-(.+)$/.exec(w.name) : null;
2633
+ return !!m && !!pipeline.env && m[1] !== pipeline.env;
2634
+ };
2635
+ const scored = dispatchable.filter((w) => !otherEnv(w)).map((w) => ({ w, overlap: w.jobIds.filter((id) => wanted.has(id)).length })).filter((x) => x.overlap > 0).sort((a, b) => b.overlap - a.overlap);
2548
2636
  if (scored.length === 0) {
2549
- return { reason: "no committed workflow_dispatch workflow whose jobs match the generated pipeline \u2014 commit one (or add `workflow_dispatch:` to its `on:` block)" };
2637
+ const excluded = dispatchable.filter(otherEnv).map((w) => `${w.file} (${w.name})`);
2638
+ return {
2639
+ reason: "no committed workflow_dispatch workflow whose jobs match the generated pipeline \u2014 commit one (or add `workflow_dispatch:` to its `on:` block)" + (excluded.length ? `; ${excluded.join(", ")} ${excluded.length === 1 ? "is" : "are"} named for another env and never stand in` : "")
2640
+ };
2550
2641
  }
2551
2642
  if (scored.length > 1 && scored[1].overlap === scored[0].overlap) {
2552
2643
  const tied = scored.filter((x) => x.overlap === scored[0].overlap).map((x) => x.w.file);
@@ -2592,6 +2683,7 @@ function runViewToProgress(pipeline, view) {
2592
2683
  }
2593
2684
  return state;
2594
2685
  }
2686
+ var LOST_EXIT = 75;
2595
2687
  async function dispatchAndFollow(workflow, pipeline, ref, deps) {
2596
2688
  const exec = deps.exec ?? defaultGhExec;
2597
2689
  const pollMs = deps.pollMs ?? 5e3;
@@ -2620,10 +2712,28 @@ async function dispatchAndFollow(workflow, pipeline, ref, deps) {
2620
2712
  return 1;
2621
2713
  }
2622
2714
  deps.onLine(`\u25CF run ${runId} started \u2014 following`);
2715
+ deps.onAdopted?.({ runId });
2716
+ return followRun(runId, pipeline, deps);
2717
+ }
2718
+ async function followRun(runId, pipeline, deps) {
2719
+ const exec = deps.exec ?? defaultGhExec;
2720
+ const pollMs = deps.pollMs ?? 5e3;
2721
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
2722
+ const pollFailBudget = deps.pollFailBudget ?? 24;
2723
+ const followDeadline = Date.now() + (deps.followTimeoutMs ?? 6 * 60 * 60 * 1e3);
2623
2724
  let urlSaid = false;
2725
+ let state = pipelineProgress(pipeline);
2726
+ let consecutiveFailures = 0;
2727
+ const lose = (why) => {
2728
+ deps.onLine(`\u2717 run ${runId}: ${why} \u2014 behold stopped following; the run itself may still be live${state.url ? ` at ${state.url}` : " on the forge"} (restart behold, or POST /api/ci/readopt, to re-adopt it)`);
2729
+ deps.onProgress(losePipelineProgress(state));
2730
+ deps.onConcluded?.({ verdict: "lost" });
2731
+ return LOST_EXIT;
2732
+ };
2624
2733
  for (; ; ) {
2625
2734
  const { code, out } = await exec(["run", "view", String(runId), "--json", "status,conclusion,jobs,url"]);
2626
2735
  if (code === 0) {
2736
+ consecutiveFailures = 0;
2627
2737
  let view;
2628
2738
  try {
2629
2739
  view = JSON.parse(out);
@@ -2633,16 +2743,27 @@ async function dispatchAndFollow(workflow, pipeline, ref, deps) {
2633
2743
  if (view) {
2634
2744
  if (view.url && !urlSaid) {
2635
2745
  urlSaid = true;
2746
+ deps.onAdopted?.({ runId, url: view.url });
2636
2747
  deps.onLine(`\u25CF ${view.url} \u2014 if the workflow's environment requires an approval, it is granted there, by a GitHub identity; behold holds none that could`);
2637
2748
  }
2638
- deps.onProgress(runViewToProgress(pipeline, view));
2749
+ state = runViewToProgress(pipeline, view);
2750
+ deps.onProgress(state);
2639
2751
  if (view.status === "completed") {
2640
2752
  deps.onLine(`\u25A0 run ${runId} ${view.conclusion ?? "completed"}`);
2641
- return view.conclusion === "success" ? 0 : 1;
2753
+ const ok = view.conclusion === "success";
2754
+ deps.onConcluded?.({ verdict: ok ? "ok" : "failed" });
2755
+ return ok ? 0 : 1;
2642
2756
  }
2643
2757
  }
2644
2758
  } else {
2645
- deps.onLine(`\u26A0 gh run view ${runId} failed (exit ${code}) \u2014 retrying`);
2759
+ consecutiveFailures++;
2760
+ deps.onLine(`\u26A0 gh run view ${runId} failed (exit ${code}) \u2014 retrying (${consecutiveFailures}/${pollFailBudget})`);
2761
+ if (consecutiveFailures >= pollFailBudget) {
2762
+ return lose(`${pollFailBudget} consecutive polls failed \u2014 the stream is dead, not flaky`);
2763
+ }
2764
+ }
2765
+ if (Date.now() >= followDeadline) {
2766
+ return lose("the follow deadline passed");
2646
2767
  }
2647
2768
  await sleep(pollMs);
2648
2769
  }
@@ -2668,6 +2789,45 @@ function joinCiProgress(ir, state) {
2668
2789
  return ir;
2669
2790
  }
2670
2791
 
2792
+ // src/ci-run-store.ts
2793
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2, rmSync } from "node:fs";
2794
+ import { homedir as homedir2 } from "node:os";
2795
+ import { join as join7, resolve as resolve4 } from "node:path";
2796
+ function slug(projectDir) {
2797
+ return resolve4(projectDir).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-80);
2798
+ }
2799
+ function defaultRoot() {
2800
+ return process.env.BEHOLD_CI_RUN_DIR ?? join7(homedir2(), ".behold", "ci-runs");
2801
+ }
2802
+ function dispatchedRunPath(projectDir, root = defaultRoot()) {
2803
+ return join7(root, `${slug(projectDir)}.json`);
2804
+ }
2805
+ function saveDispatchedRun(run3, root = defaultRoot()) {
2806
+ try {
2807
+ const path = dispatchedRunPath(run3.projectDir, root);
2808
+ mkdirSync2(join7(path, ".."), { recursive: true });
2809
+ writeFileSync2(path, JSON.stringify({ version: 1, ...run3 }, null, 2) + "\n");
2810
+ } catch {
2811
+ }
2812
+ }
2813
+ function readDispatchedRun(projectDir, root) {
2814
+ try {
2815
+ const path = dispatchedRunPath(projectDir, root);
2816
+ if (!existsSync5(path)) return void 0;
2817
+ const parsed = JSON.parse(readFileSync7(path, "utf8"));
2818
+ if (parsed?.version !== 1 || typeof parsed.runId !== "number") return void 0;
2819
+ if (resolve4(parsed.projectDir) !== resolve4(projectDir)) return void 0;
2820
+ return parsed;
2821
+ } catch {
2822
+ return void 0;
2823
+ }
2824
+ }
2825
+ function concludeDispatchedRun(projectDir, runId, verdict, root) {
2826
+ const current = readDispatchedRun(projectDir, root);
2827
+ if (!current || current.runId !== runId) return;
2828
+ saveDispatchedRun({ ...current, concluded: { verdict, at: (/* @__PURE__ */ new Date()).toISOString() } }, root);
2829
+ }
2830
+
2671
2831
  // src/operator.ts
2672
2832
  var ANSI = /\x1b\[[0-9;]*m/g;
2673
2833
  var strip = (s) => s.replace(ANSI, "");
@@ -3740,26 +3900,26 @@ function applyHelmArtifacts(ir, observed) {
3740
3900
 
3741
3901
  // src/carve-discovery.ts
3742
3902
  import { readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
3743
- import { join as join8 } from "node:path";
3903
+ import { join as join9 } from "node:path";
3744
3904
 
3745
3905
  // src/member-ir.ts
3746
3906
  import { createHash } from "node:crypto";
3747
3907
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
3748
- import { join as join7, relative as relative2, resolve as resolve3 } from "node:path";
3908
+ import { join as join8, relative as relative2, resolve as resolve5 } from "node:path";
3749
3909
  var SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
3750
3910
  function memberIrCacheSize(env = process.env) {
3751
3911
  const override = Number.parseInt(env.BEHOLD_ESTATE_IR_CACHE ?? "", 10);
3752
3912
  return Number.isInteger(override) && override >= 0 ? override : 64;
3753
3913
  }
3754
3914
  function memberSourceStamp(dir) {
3755
- const root = resolve3(dir);
3915
+ const root = resolve5(dir);
3756
3916
  const h = createHash("sha1");
3757
3917
  let any = false;
3758
3918
  const walk = (at) => {
3759
3919
  const entries = readdirSync4(at, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1);
3760
3920
  for (const e of entries) {
3761
3921
  if (SKIP.has(e.name)) continue;
3762
- const path = join7(at, e.name);
3922
+ const path = join8(at, e.name);
3763
3923
  if (e.isDirectory()) {
3764
3924
  walk(path);
3765
3925
  continue;
@@ -3781,7 +3941,7 @@ function memberSourceStamp(dir) {
3781
3941
  function cacheKey(dir, opts) {
3782
3942
  const chant = resolveChant(dir);
3783
3943
  const canonical = JSON.stringify(opts, Object.keys(opts).sort());
3784
- return `${resolve3(dir)}\0${chant.bin}\0${chant.version ?? ""}\0${canonical}`;
3944
+ return `${resolve5(dir)}\0${chant.bin}\0${chant.version ?? ""}\0${canonical}`;
3785
3945
  }
3786
3946
  var cache = /* @__PURE__ */ new Map();
3787
3947
  var hits = 0;
@@ -3791,7 +3951,7 @@ function invalidateMember(dir) {
3791
3951
  cache.clear();
3792
3952
  return;
3793
3953
  }
3794
- const root = resolve3(dir);
3954
+ const root = resolve5(dir);
3795
3955
  for (const [key, entry] of cache) if (entry.dir === root) cache.delete(key);
3796
3956
  }
3797
3957
  async function memberIr(dir, opts = {}) {
@@ -3810,7 +3970,7 @@ async function memberIr(dir, opts = {}) {
3810
3970
  const ir = await graphIr(dir, opts);
3811
3971
  if (stamp === void 0 || memberSourceStamp(dir) !== stamp) return ir;
3812
3972
  cache.delete(key);
3813
- cache.set(key, { dir: resolve3(dir), stamp, ir });
3973
+ cache.set(key, { dir: resolve5(dir), stamp, ir });
3814
3974
  for (const oldest of cache.keys()) {
3815
3975
  if (cache.size <= size) break;
3816
3976
  cache.delete(oldest);
@@ -3838,7 +3998,7 @@ function hasAnyCarveManifest(dir, io = fsIo) {
3838
3998
  for (const name of entries) {
3839
3999
  if (name.endsWith(CARVE_MANIFEST_SUFFIX)) return true;
3840
4000
  if (name.startsWith(".") || SKIP2.has(name)) continue;
3841
- const full = join8(d, name);
4001
+ const full = join9(d, name);
3842
4002
  if (io.isDirectory(full) && walk(full)) return true;
3843
4003
  }
3844
4004
  return false;
@@ -3879,7 +4039,7 @@ async function carveStatesUnder(dirs) {
3879
4039
  // src/estate.ts
3880
4040
  import { statSync as statSync5 } from "node:fs";
3881
4041
  import { availableParallelism } from "node:os";
3882
- import { join as join9, resolve as resolve4, sep } from "node:path";
4042
+ import { join as join10, resolve as resolve6, sep as sep3 } from "node:path";
3883
4043
  import { composeStacks, shortStackNames } from "@intentius/pinhole";
3884
4044
 
3885
4045
  // src/zoom-notes.ts
@@ -4038,11 +4198,11 @@ function declaredSegments(path) {
4038
4198
  function pathAlignment(declared, dir, isDir = realIsDir) {
4039
4199
  const want = declaredSegments(declared);
4040
4200
  if (!want) return 0;
4041
- const root = resolve4(dir);
4042
- const have = root.split(sep).filter(Boolean);
4201
+ const root = resolve6(dir);
4202
+ const have = root.split(sep3).filter(Boolean);
4043
4203
  for (let k = Math.min(want.length, have.length); k >= 1; k--) {
4044
4204
  if (!have.slice(-k).every((s, i) => s === want[i])) continue;
4045
- if (!isDir(join9(root, ...want.slice(k)))) continue;
4205
+ if (!isDir(join10(root, ...want.slice(k)))) continue;
4046
4206
  return k;
4047
4207
  }
4048
4208
  return 0;
@@ -4072,10 +4232,10 @@ function joinNamespaceBindings(members, isDir = realIsDir) {
4072
4232
  if (!winner || tied) continue;
4073
4233
  const prev = best.get(winner.dir);
4074
4234
  if (prev === "ambiguous") continue;
4075
- const join22 = { dir: winner.dir, namespace: b.namespace, path: b.path, declaredBy: b.declaredBy };
4076
- if (!prev) best.set(winner.dir, { score: winner.score, join: join22 });
4235
+ const join23 = { dir: winner.dir, namespace: b.namespace, path: b.path, declaredBy: b.declaredBy };
4236
+ if (!prev) best.set(winner.dir, { score: winner.score, join: join23 });
4077
4237
  else if (prev.join.namespace !== b.namespace) best.set(winner.dir, "ambiguous");
4078
- else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join: join22 });
4238
+ else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join: join23 });
4079
4239
  }
4080
4240
  return [...best.values()].filter((v) => v !== "ambiguous").map((v) => v.join);
4081
4241
  }
@@ -4793,8 +4953,8 @@ function projectHelmLogical(ir) {
4793
4953
  }
4794
4954
 
4795
4955
  // src/logical-kustomize.ts
4796
- import { existsSync as existsSync5 } from "node:fs";
4797
- import { join as join10 } from "node:path";
4956
+ import { existsSync as existsSync6 } from "node:fs";
4957
+ import { join as join11 } from "node:path";
4798
4958
  function overlayBoxTitle(name) {
4799
4959
  return `overlay ${name}`;
4800
4960
  }
@@ -4814,7 +4974,7 @@ function kustomizationRoot(bases, dir, exists) {
4814
4974
  let current = dir;
4815
4975
  for (let i = 0; i < 4 && current; i++) {
4816
4976
  for (const base of bases) {
4817
- if (exists(join10(base, current, "kustomization.yaml")) || exists(join10(base, current, "kustomization.yml"))) {
4977
+ if (exists(join11(base, current, "kustomization.yaml")) || exists(join11(base, current, "kustomization.yml"))) {
4818
4978
  return current;
4819
4979
  }
4820
4980
  }
@@ -4823,7 +4983,7 @@ function kustomizationRoot(bases, dir, exists) {
4823
4983
  }
4824
4984
  return void 0;
4825
4985
  }
4826
- function projectKustomizeLogical(ir, sourceRoots, exists = existsSync5) {
4986
+ function projectKustomizeLogical(ir, sourceRoots, exists = existsSync6) {
4827
4987
  const bases = Array.isArray(sourceRoots) ? sourceRoots : [sourceRoots];
4828
4988
  const k8s = ir.nodes.filter((n) => n.lexicon === "k8s");
4829
4989
  if (k8s.length === 0) return { ir: { nodes: [], edges: [], groups: {} }, byContainer: {} };
@@ -5545,10 +5705,10 @@ import {
5545
5705
  } from "@intentius/pinhole";
5546
5706
 
5547
5707
  // src/icon-packs.ts
5548
- import { readFileSync as readFileSync7 } from "node:fs";
5549
- import { dirname as dirname4, join as join11 } from "node:path";
5708
+ import { readFileSync as readFileSync8 } from "node:fs";
5709
+ import { dirname as dirname4, join as join12 } from "node:path";
5550
5710
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5551
- var ICON_ROOT = join11(dirname4(fileURLToPath2(import.meta.url)), "..", "web", "icons");
5711
+ var ICON_ROOT = join12(dirname4(fileURLToPath2(import.meta.url)), "..", "web", "icons");
5552
5712
  var cache3 = /* @__PURE__ */ new Map();
5553
5713
  function unprefix(body) {
5554
5714
  return body.replace(/\s(?:inkscape|sodipodi):[\w-]+\s*=\s*(["'])[\s\S]*?\1/g, "").replace(/\sxlink:href\s*=/g, " href=");
@@ -5570,7 +5730,7 @@ function plate(glyph) {
5570
5730
  function loadIcon(rel) {
5571
5731
  const hit = cache3.get(rel);
5572
5732
  if (hit) return hit;
5573
- const raw = readFileSync7(join11(ICON_ROOT, `${rel}.svg`), "utf8");
5733
+ const raw = readFileSync8(join12(ICON_ROOT, `${rel}.svg`), "utf8");
5574
5734
  const open = raw.indexOf("<svg");
5575
5735
  const openEnd = raw.indexOf(">", open);
5576
5736
  const close = raw.lastIndexOf("</svg");
@@ -6735,12 +6895,12 @@ function radializeLayout(layout, groupOf, size = /* @__PURE__ */ new Map()) {
6735
6895
  }
6736
6896
 
6737
6897
  // src/carve-actions.ts
6738
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync10, readdirSync as readdirSync6, statSync as statSync6 } from "node:fs";
6739
- import { isAbsolute as isAbsolute2, join as join14, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
6898
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync6 } from "node:fs";
6899
+ import { isAbsolute as isAbsolute3, join as join15, relative as relative3, resolve as resolve7, sep as sep4 } from "node:path";
6740
6900
 
6741
6901
  // src/layout.ts
6742
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync8, renameSync, rmSync, writeFileSync as writeFileSync2, accessSync, constants } from "node:fs";
6743
- import { join as join12 } from "node:path";
6902
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3, accessSync, constants } from "node:fs";
6903
+ import { join as join13 } from "node:path";
6744
6904
  var LAYOUT_DIR = ".behold";
6745
6905
  var LAYOUT_FILE = "layout.json";
6746
6906
  var MAX_BODY_BYTES = 64 * 1024;
@@ -6750,12 +6910,12 @@ var MAX_LENSES = 64;
6750
6910
  var MAX_ID_LENGTH = 512;
6751
6911
  var MAX_LENS_LENGTH = 128;
6752
6912
  var NUM = ["dx", "dy", "dw", "dh"];
6753
- function slug(s) {
6913
+ function slug2(s) {
6754
6914
  return String(s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
6755
6915
  }
6756
6916
  function normalizeLens(raw) {
6757
6917
  if (typeof raw !== "string" || !raw.trim() || raw.length > MAX_LENS_LENGTH) return null;
6758
- const parts = raw.split("+").map((p) => slug(p)).filter((p) => p && p !== "unknown");
6918
+ const parts = raw.split("+").map((p) => slug2(p)).filter((p) => p && p !== "unknown");
6759
6919
  if (!parts.length) return null;
6760
6920
  const key = parts.join("+");
6761
6921
  return key === "__proto__" || key === "constructor" || key === "prototype" ? null : key;
@@ -6778,13 +6938,13 @@ function normalizeDeltas(raw) {
6778
6938
  function lensFromQuery(params) {
6779
6939
  const zoom = params.get("components") === "1" ? "components" : params.get("logical") === "1" ? "logical" : params.get("runtime") === "1" ? "runtime" : { 1: "composites", 2: "resources", 3: "attributes" }[Number(params.get("detail") ?? 2)] ?? "resources";
6780
6940
  const stack = params.get("stack");
6781
- return [slug(zoom), params.get("radial") === "1" ? "radial" : null, stack ? slug(`stack-${stack}`) : null].filter(Boolean).join("+");
6941
+ return [slug2(zoom), params.get("radial") === "1" ? "radial" : null, stack ? slug2(`stack-${stack}`) : null].filter(Boolean).join("+");
6782
6942
  }
6783
6943
  function layoutPath(projectDir) {
6784
- return join12(projectDir, LAYOUT_DIR, LAYOUT_FILE);
6944
+ return join13(projectDir, LAYOUT_DIR, LAYOUT_FILE);
6785
6945
  }
6786
6946
  function unwritableReason(projectDir) {
6787
- if (!projectDir || !existsSync6(projectDir)) return `no such project directory: ${projectDir || "(none)"}`;
6947
+ if (!projectDir || !existsSync7(projectDir)) return `no such project directory: ${projectDir || "(none)"}`;
6788
6948
  try {
6789
6949
  accessSync(projectDir, constants.W_OK);
6790
6950
  } catch {
@@ -6796,8 +6956,8 @@ function readLayoutFile(projectDir) {
6796
6956
  const empty = { version: 1, lenses: {} };
6797
6957
  const file = layoutPath(projectDir);
6798
6958
  try {
6799
- if (!existsSync6(file)) return empty;
6800
- const raw = readFileSync8(file, "utf8");
6959
+ if (!existsSync7(file)) return empty;
6960
+ const raw = readFileSync9(file, "utf8");
6801
6961
  if (raw.length > MAX_FILE_BYTES) return empty;
6802
6962
  const parsed = JSON.parse(raw);
6803
6963
  if (!parsed || typeof parsed !== "object" || !parsed.lenses || typeof parsed.lenses !== "object") return empty;
@@ -6832,14 +6992,14 @@ function writeLens(projectDir, lens, deltas) {
6832
6992
  if (Object.keys(lenses).length > MAX_LENSES) throw new LayoutTooLarge(`a layout sidecar holds at most ${MAX_LENSES} lenses`);
6833
6993
  const file = layoutPath(projectDir);
6834
6994
  if (!Object.keys(lenses).length) {
6835
- rmSync(file, { force: true });
6995
+ rmSync2(file, { force: true });
6836
6996
  return { lens: key, deltas: clean, bytes: 0 };
6837
6997
  }
6838
6998
  const body = JSON.stringify({ version: 1, lenses }, null, 2) + "\n";
6839
6999
  if (body.length > MAX_FILE_BYTES) throw new LayoutTooLarge(`a layout sidecar is capped at ${MAX_FILE_BYTES} bytes`);
6840
- mkdirSync2(join12(projectDir, LAYOUT_DIR), { recursive: true });
7000
+ mkdirSync3(join13(projectDir, LAYOUT_DIR), { recursive: true });
6841
7001
  const tmp = `${file}.tmp`;
6842
- writeFileSync2(tmp, body, "utf8");
7002
+ writeFileSync3(tmp, body, "utf8");
6843
7003
  renameSync(tmp, file);
6844
7004
  return { lens: key, deltas: clean, bytes: body.length };
6845
7005
  }
@@ -6922,8 +7082,8 @@ function reanchorEdges(svg, moved) {
6922
7082
 
6923
7083
  // src/carve-live.ts
6924
7084
  import { spawn as spawn3 } from "node:child_process";
6925
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "node:fs";
6926
- import { join as join13 } from "node:path";
7085
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync8 } from "node:fs";
7086
+ import { join as join14 } from "node:path";
6927
7087
 
6928
7088
  // src/scratch.ts
6929
7089
  var PROTECTED_INFRA = [
@@ -7011,9 +7171,9 @@ async function teardownScratchFloci() {
7011
7171
  await capture("docker", ["rm", "-f", LIVE_CONTAINER], process.cwd()).catch(() => void 0);
7012
7172
  }
7013
7173
  async function applyIntoFloci(fromDir, step) {
7014
- const template = join13(fromDir, "floci-override.tf.disabled");
7015
- if (!existsSync7(template)) return `no floci-override.tf.disabled in ${fromDir} \u2014 this estate wasn't authored for the live tier`;
7016
- writeFileSync3(join13(fromDir, "floci_override.tf"), armOverride(readFileSync9(template, "utf8"), LIVE_PORT));
7174
+ const template = join14(fromDir, "floci-override.tf.disabled");
7175
+ if (!existsSync8(template)) return `no floci-override.tf.disabled in ${fromDir} \u2014 this estate wasn't authored for the live tier`;
7176
+ writeFileSync4(join14(fromDir, "floci_override.tf"), armOverride(readFileSync10(template, "utf8"), LIVE_PORT));
7017
7177
  const init = await step("terraform", ["init", "-input=false", "-no-color"], fromDir);
7018
7178
  if (init !== 0) return `terraform init exited ${init} (provider downloads need network)`;
7019
7179
  const apply = await step(
@@ -7063,7 +7223,7 @@ async function runCarveObserve(demo, select) {
7063
7223
  "run `behold demo carve --live` (needs docker + terraform)"
7064
7224
  );
7065
7225
  }
7066
- if (!existsSync8(join14(demo.out, "src"))) {
7226
+ if (!existsSync9(join15(demo.out, "src"))) {
7067
7227
  return refuse3("carve-action", "nothing has been emitted yet", "run Emit first \u2014 observe reads the carveout emit writes");
7068
7228
  }
7069
7229
  const args = ["lifecycle", "diff", "prod", "--live", "--json"];
@@ -7133,8 +7293,8 @@ var refuse3 = (code, error, remedy) => ({
7133
7293
  });
7134
7294
  var BUILD_CAVEAT = "The gate shown is `chant lint`, not `chant build`. chant#1637's fold is applied now: the bucket's versioning and public-access-block sub-resources come out as native props, not just folded-in mentions. `chant build` still fails, but on one rule only \u2014 WAW042, a TLS-deny bucket policy the source Terraform never declared. That's real drift the estate inherited, not a gap in the carve. example-carve/README.md, \"Why build still fails\", has the full statement.";
7135
7295
  function insideDemo(root, p) {
7136
- const rel = relative3(resolve5(root), resolve5(p));
7137
- return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
7296
+ const rel = relative3(resolve7(root), resolve7(p));
7297
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
7138
7298
  }
7139
7299
  function selectFromReport(report, raw) {
7140
7300
  if (typeof raw !== "string" || !raw || raw.length > 512) return null;
@@ -7144,7 +7304,7 @@ function carveWriteBlock(demo) {
7144
7304
  if (!demo) {
7145
7305
  return "this server isn't running a carve demo \u2014 the carve actions only exist inside a `behold demo carve` copy";
7146
7306
  }
7147
- if (!existsSync8(demo.from)) return `the demo copy has no Terraform estate at ${demo.from}`;
7307
+ if (!existsSync9(demo.from)) return `the demo copy has no Terraform estate at ${demo.from}`;
7148
7308
  if (!insideDemo(demo.root, demo.out)) return "the carve output directory is outside the demo copy";
7149
7309
  return unwritableReason(demo.root);
7150
7310
  }
@@ -7161,7 +7321,7 @@ function readArtifacts(demo, dir, filter) {
7161
7321
  for (const name of entries) {
7162
7322
  if (out.length >= MAX_ARTIFACTS) return;
7163
7323
  if (name === "node_modules" || name.startsWith(".")) continue;
7164
- const full = join14(d, name);
7324
+ const full = join15(d, name);
7165
7325
  let st;
7166
7326
  try {
7167
7327
  st = statSync6(full);
@@ -7172,11 +7332,11 @@ function readArtifacts(demo, dir, filter) {
7172
7332
  walk(full, depth + 1);
7173
7333
  continue;
7174
7334
  }
7175
- const rel = relative3(demo.root, full).split(sep2).join("/");
7335
+ const rel = relative3(demo.root, full).split(sep4).join("/");
7176
7336
  if (!filter(rel)) continue;
7177
7337
  let text = "";
7178
7338
  try {
7179
- text = readFileSync10(full, "utf8");
7339
+ text = readFileSync11(full, "utf8");
7180
7340
  } catch {
7181
7341
  continue;
7182
7342
  }
@@ -7198,17 +7358,17 @@ function carveSlug(select) {
7198
7358
  }
7199
7359
  var clip = (s) => s.length > MAX_OUTPUT_BYTES ? s.slice(0, MAX_OUTPUT_BYTES) + "\n\u2026 (truncated)" : s;
7200
7360
  function shortenIn(arg, root) {
7201
- return arg.startsWith(root + sep2) ? arg.slice(root.length + 1) : arg === root ? "." : arg;
7361
+ return arg.startsWith(root + sep4) ? arg.slice(root.length + 1) : arg === root ? "." : arg;
7202
7362
  }
7203
7363
  function merge(r, root) {
7204
7364
  const joined = stripAnsi([r.stdout, r.stderr].filter((s) => s.trim()).join("\n").trim());
7205
- return clip(joined.split(root + sep2).join("").split(root).join("."));
7365
+ return clip(joined.split(root + sep4).join("").split(root).join("."));
7206
7366
  }
7207
7367
  async function runCarveEmit(demo, select) {
7208
7368
  const block = carveWriteBlock(demo);
7209
7369
  if (block) return refuse3("read-only", block, "Start the walkthrough with `behold demo carve`.");
7210
- mkdirSync3(demo.out, { recursive: true });
7211
- const reportFile = join14(demo.out, `${carveSlug(select)}-boundary.json`);
7370
+ mkdirSync4(demo.out, { recursive: true });
7371
+ const reportFile = join15(demo.out, `${carveSlug(select)}-boundary.json`);
7212
7372
  const args = [
7213
7373
  "carve",
7214
7374
  "emit",
@@ -7237,10 +7397,10 @@ async function runCarveEmit(demo, select) {
7237
7397
  }
7238
7398
  let boundary = null;
7239
7399
  try {
7240
- boundary = JSON.parse(readFileSync10(reportFile, "utf8"));
7400
+ boundary = JSON.parse(readFileSync11(reportFile, "utf8"));
7241
7401
  } catch {
7242
7402
  }
7243
- const lintPath = relative3(demo.project, join14(demo.out, "src")).split(sep2).join("/");
7403
+ const lintPath = relative3(demo.project, join15(demo.out, "src")).split(sep4).join("/");
7244
7404
  const lintArgs = ["lint", lintPath];
7245
7405
  const lint = await runChantRaw(lintArgs, demo.project).catch((err) => ({
7246
7406
  code: 127,
@@ -7265,7 +7425,7 @@ async function runCarveEmit(demo, select) {
7265
7425
  async function runCarveBridge(demo, select) {
7266
7426
  const block = carveWriteBlock(demo);
7267
7427
  if (block) return refuse3("read-only", block, "Start the walkthrough with `behold demo carve`.");
7268
- mkdirSync3(demo.out, { recursive: true });
7428
+ mkdirSync4(demo.out, { recursive: true });
7269
7429
  const args = [
7270
7430
  "carve",
7271
7431
  "bridge",
@@ -7290,21 +7450,21 @@ async function runCarveBridge(demo, select) {
7290
7450
  "Run the Emit step first \u2014 bridge reads the carve manifest emit leaves in the output directory."
7291
7451
  );
7292
7452
  }
7293
- const slug3 = carveSlug(select);
7453
+ const slug4 = carveSlug(select);
7294
7454
  const all = readArtifacts(demo, demo.out, (rel) => /\.(tf|md|patch)$/.test(rel));
7295
7455
  return {
7296
7456
  ok: true,
7297
7457
  select,
7298
7458
  command: `chant ${args.map((a) => shortenIn(a, demo.root)).join(" ")}`,
7299
7459
  output,
7300
- runbook: all.find((a) => a.path.endsWith(`${slug3}-runbook.md`)) ?? null,
7301
- proposals: all.filter((a) => !a.path.endsWith(`${slug3}-runbook.md`))
7460
+ runbook: all.find((a) => a.path.endsWith(`${slug4}-runbook.md`)) ?? null,
7461
+ proposals: all.filter((a) => !a.path.endsWith(`${slug4}-runbook.md`))
7302
7462
  };
7303
7463
  }
7304
7464
 
7305
7465
  // src/ops.ts
7306
- import { readdirSync as readdirSync7, readFileSync as readFileSync11, existsSync as existsSync9 } from "node:fs";
7307
- import { join as join15 } from "node:path";
7466
+ import { readdirSync as readdirSync7, readFileSync as readFileSync12, existsSync as existsSync10 } from "node:fs";
7467
+ import { join as join16 } from "node:path";
7308
7468
  var APPLY_TARGET_LEXICON = {
7309
7469
  cloudformation: "aws",
7310
7470
  kubectl: "k8s",
@@ -7326,11 +7486,11 @@ function discoverOps(projectDir) {
7326
7486
  const seen = /* @__PURE__ */ new Set();
7327
7487
  const out = [];
7328
7488
  for (const sub of ["ops", "src", "."]) {
7329
- const dir = join15(projectDir, sub);
7330
- if (!existsSync9(dir)) continue;
7489
+ const dir = join16(projectDir, sub);
7490
+ if (!existsSync10(dir)) continue;
7331
7491
  for (const f of readdirSync7(dir)) {
7332
7492
  if (!f.endsWith(".op.ts")) continue;
7333
- const content = readFileSync11(join15(dir, f), "utf8");
7493
+ const content = readFileSync12(join16(dir, f), "utf8");
7334
7494
  const name = content.match(/name:\s*["'`]([^"'`]+)["'`]/)?.[1];
7335
7495
  if (!name || seen.has(name)) continue;
7336
7496
  seen.add(name);
@@ -7346,25 +7506,25 @@ function discoverOps(projectDir) {
7346
7506
  ...gate ? { gate } : {},
7347
7507
  ...env ? { env } : {},
7348
7508
  ...substrate ? { substrate } : {},
7349
- ...existsSync9(irPath) ? { irPath } : {}
7509
+ ...existsSync10(irPath) ? { irPath } : {}
7350
7510
  });
7351
7511
  }
7352
7512
  }
7353
7513
  return out.sort((a, b) => a.name.localeCompare(b.name));
7354
7514
  }
7355
7515
  function opIrPath(projectDir, name) {
7356
- return join15(projectDir, "dist", "ops", name, "op.json");
7516
+ return join16(projectDir, "dist", "ops", name, "op.json");
7357
7517
  }
7358
7518
  function discoverOpIrs(projectDirs) {
7359
7519
  const seen = /* @__PURE__ */ new Set();
7360
7520
  const out = [];
7361
7521
  for (const dir of projectDirs) {
7362
- const root = join15(dir, "dist", "ops");
7363
- if (!existsSync9(root)) continue;
7522
+ const root = join16(dir, "dist", "ops");
7523
+ if (!existsSync10(root)) continue;
7364
7524
  for (const name of readdirSync7(root)) {
7365
7525
  if (seen.has(name)) continue;
7366
7526
  const path = opIrPath(dir, name);
7367
- if (!existsSync9(path)) continue;
7527
+ if (!existsSync10(path)) continue;
7368
7528
  seen.add(name);
7369
7529
  out.push({ name, dir, path });
7370
7530
  }
@@ -7599,11 +7759,11 @@ var RAN = { ok: "ok", fail: "failed", skipped: "skipped" };
7599
7759
  function tookLabel(ms) {
7600
7760
  return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
7601
7761
  }
7602
- function paintPlayhead(ir, join22, gate) {
7603
- if (!join22.byNode.size && !join22.at) return ir;
7762
+ function paintPlayhead(ir, join23, gate) {
7763
+ if (!join23.byNode.size && !join23.at) return ir;
7604
7764
  const nodes = ir.nodes.map((node) => {
7605
- const run3 = join22.byNode.get(node.id);
7606
- const here = join22.at === node.id;
7765
+ const run3 = join23.byNode.get(node.id);
7766
+ const here = join23.at === node.id;
7607
7767
  if (!run3 && !here) return node;
7608
7768
  const isGate = node.attrs?._step === "gate";
7609
7769
  const attrs = { ...node.attrs };
@@ -7618,7 +7778,7 @@ function paintPlayhead(ir, join22, gate) {
7618
7778
  if (here) {
7619
7779
  attrs._playhead = true;
7620
7780
  attrs._status = isGate && gate ? "warn" : "accent";
7621
- attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join22.atReason ?? "here";
7781
+ attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join23.atReason ?? "here";
7622
7782
  if (isGate && gate?.description) attrs.gateDescription = gate.description;
7623
7783
  }
7624
7784
  return { ...node, attrs };
@@ -7640,11 +7800,11 @@ function pendingGateCard(op, gate, ir) {
7640
7800
  }
7641
7801
  };
7642
7802
  }
7643
- function playheadNote(state, join22) {
7803
+ function playheadNote(state, join23) {
7644
7804
  if (!state.op) {
7645
7805
  return "No run state: nothing has been run from this behold session, so every step paints as declared.";
7646
7806
  }
7647
- const settled = join22 ? join22.byNode.size : state.records.length;
7807
+ const settled = join23 ? join23.byNode.size : state.records.length;
7648
7808
  const bits = [];
7649
7809
  const head = state.status === "idle" ? `Run state for ${state.op}: no per-step records \u2014 this behold session didn't start the run, so only the gate query answers` : `Run: ${state.op} (${state.mode === "temporal" ? "durable" : "local executor"}) \u2014 ${settled} step${settled === 1 ? "" : "s"} settled`;
7650
7810
  if (state.status === "running") {
@@ -7660,9 +7820,9 @@ function playheadNote(state, join22) {
7660
7820
  } else if (state.gateNote) {
7661
7821
  bits.push(`gate state unavailable \u2014 ${state.gateNote}`);
7662
7822
  }
7663
- if (join22?.unplaced.length) {
7823
+ if (join23?.unplaced.length) {
7664
7824
  bits.push(
7665
- `${join22.unplaced.length} record${join22.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join22.unplaced.length === 1 ? "is" : "are"} not painted`
7825
+ `${join23.unplaced.length} record${join23.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join23.unplaced.length === 1 ? "is" : "are"} not painted`
7666
7826
  );
7667
7827
  }
7668
7828
  if (!bits.length) return `${head}.`;
@@ -8328,24 +8488,24 @@ var OpRunner = class {
8328
8488
 
8329
8489
  // src/substrates.ts
8330
8490
  import { spawn as spawn4 } from "node:child_process";
8331
- import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
8332
- import { join as join16 } from "node:path";
8491
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
8492
+ import { join as join17 } from "node:path";
8333
8493
  import { platform } from "node:os";
8334
8494
  var DOCKER_DOWN_DETAIL = "docker is down";
8335
8495
  function probe(cmd, args) {
8336
- return new Promise((resolve10) => {
8496
+ return new Promise((resolve12) => {
8337
8497
  let out = "";
8338
8498
  let proc;
8339
8499
  try {
8340
8500
  proc = spawn4(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
8341
8501
  } catch {
8342
- resolve10({ code: 127, out: "" });
8502
+ resolve12({ code: 127, out: "" });
8343
8503
  return;
8344
8504
  }
8345
8505
  proc.stdout.on("data", (d) => out += d);
8346
8506
  proc.stderr.on("data", (d) => out += d);
8347
- proc.on("error", () => resolve10({ code: 127, out }));
8348
- proc.on("close", (code) => resolve10({ code: code ?? 1, out }));
8507
+ proc.on("error", () => resolve12({ code: 127, out }));
8508
+ proc.on("close", (code) => resolve12({ code: code ?? 1, out }));
8349
8509
  });
8350
8510
  }
8351
8511
  async function dockerAvailable(run3) {
@@ -8358,11 +8518,11 @@ async function dockerRunning(run3, nameFilter) {
8358
8518
  return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
8359
8519
  }
8360
8520
  function scriptBringUp(projectDir, relPath, label) {
8361
- return existsSync10(join16(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
8521
+ return existsSync11(join17(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
8362
8522
  }
8363
8523
  function projectLexicons(projectDir) {
8364
8524
  try {
8365
- const src = readFileSync12(join16(projectDir, "chant.config.ts"), "utf-8");
8525
+ const src = readFileSync13(join17(projectDir, "chant.config.ts"), "utf-8");
8366
8526
  const m = src.match(/lexicons\s*:\s*\[([^\]]*)\]/);
8367
8527
  if (!m) return [];
8368
8528
  return [...m[1].matchAll(/["']([^"']+)["']/g)].map((x) => x[1]);
@@ -8414,7 +8574,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
8414
8574
  ["forgejo", "Forgejo", ".forgejo", "test/forgejo-runtime-e2e.sh"]
8415
8575
  ];
8416
8576
  for (const [name, label, marker, script] of forges) {
8417
- if (!existsSync10(join16(projectDir, marker))) continue;
8577
+ if (!existsSync11(join17(projectDir, marker))) continue;
8418
8578
  const c = docker ? await dockerRunning(run3, name) : [];
8419
8579
  const d = dep(c.length > 0, "container up", "on-demand (pipeline run)");
8420
8580
  subs.push({
@@ -8448,7 +8608,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
8448
8608
  detail: endpoint ? `targeting ${endpoint}` : "real Fly (FLY_FLAPS_BASE_URL unset)"
8449
8609
  });
8450
8610
  }
8451
- if (existsSync10(join16(projectDir, ".github", "workflows"))) {
8611
+ if (existsSync11(join17(projectDir, ".github", "workflows"))) {
8452
8612
  const gh = await run3("gh", ["auth", "status"]);
8453
8613
  const ready = gh.code === 0;
8454
8614
  subs.push({
@@ -8461,7 +8621,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
8461
8621
  if (lexicons.includes("temporal")) {
8462
8622
  let hasProfiles = false;
8463
8623
  try {
8464
- hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(readFileSync12(join16(projectDir, "chant.config.ts"), "utf-8"));
8624
+ hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(readFileSync13(join17(projectDir, "chant.config.ts"), "utf-8"));
8465
8625
  } catch {
8466
8626
  }
8467
8627
  subs.push({
@@ -8669,8 +8829,8 @@ function addEstateMemberEdges(ir, members, isDir = realIsDir2) {
8669
8829
  }
8670
8830
 
8671
8831
  // src/events.ts
8672
- import { watch, existsSync as existsSync11 } from "node:fs";
8673
- import { join as join17 } from "node:path";
8832
+ import { watch, existsSync as existsSync12 } from "node:fs";
8833
+ import { join as join18 } from "node:path";
8674
8834
  var Broadcaster = class {
8675
8835
  listeners = /* @__PURE__ */ new Set();
8676
8836
  subscribe(fn) {
@@ -8688,7 +8848,7 @@ var Broadcaster = class {
8688
8848
  };
8689
8849
  var IGNORE = /(^|[\\/])(node_modules|dist|\.git)([\\/]|$)/;
8690
8850
  function watchSource(projectDir, onChange, debounceMs = 200) {
8691
- const dir = existsSync11(join17(projectDir, "src")) ? join17(projectDir, "src") : projectDir;
8851
+ const dir = existsSync12(join18(projectDir, "src")) ? join18(projectDir, "src") : projectDir;
8692
8852
  let timer;
8693
8853
  const watcher = watch(dir, { recursive: true }, (_event, file) => {
8694
8854
  const name = typeof file === "string" ? file : "";
@@ -8976,13 +9136,13 @@ async function emulatorDown(projectDir) {
8976
9136
  }
8977
9137
 
8978
9138
  // src/demos.ts
8979
- import { readFileSync as readFileSync13, existsSync as existsSync12, cpSync } from "node:fs";
8980
- import { join as join18, relative as relative4, resolve as resolve6, sep as sep3 } from "node:path";
9139
+ import { readFileSync as readFileSync14, existsSync as existsSync13, cpSync } from "node:fs";
9140
+ import { join as join19, relative as relative4, resolve as resolve8, sep as sep5 } from "node:path";
8981
9141
  import { spawn as spawn5, spawnSync } from "node:child_process";
8982
9142
  function loadDemoRegistry(pkgRoot2) {
8983
9143
  let raw;
8984
9144
  try {
8985
- raw = JSON.parse(readFileSync13(join18(pkgRoot2, "demos.json"), "utf8"));
9145
+ raw = JSON.parse(readFileSync14(join19(pkgRoot2, "demos.json"), "utf8"));
8986
9146
  } catch {
8987
9147
  return [];
8988
9148
  }
@@ -9022,22 +9182,22 @@ function fetchesFromNetwork(entry) {
9022
9182
  return entry.source === "git";
9023
9183
  }
9024
9184
  function demoTargetDir(entry, cwd = process.cwd()) {
9025
- const legacy = resolve6(cwd, "behold-demo");
9026
- if (entry.name === "writes" && existsSync12(legacy)) return legacy;
9027
- return resolve6(cwd, "behold-demos", entry.name);
9185
+ const legacy = resolve8(cwd, "behold-demo");
9186
+ if (entry.name === "writes" && existsSync13(legacy)) return legacy;
9187
+ return resolve8(cwd, "behold-demos", entry.name);
9028
9188
  }
9029
9189
  async function loadDemo(entry, opts) {
9030
9190
  const { pkgRoot: pkgRoot2, target } = opts;
9031
9191
  const say = (line) => opts.log?.(`behold demo ${entry.name} \u2192 ${line}`);
9032
- if (!existsSync12(target)) {
9192
+ if (!existsSync13(target)) {
9033
9193
  if (entry.source === "bundled") {
9034
- const bundled = join18(pkgRoot2, entry.dir);
9035
- if (!existsSync12(bundled)) return { ok: false, error: `this install has no bundled ${entry.dir}` };
9194
+ const bundled = join19(pkgRoot2, entry.dir);
9195
+ if (!existsSync13(bundled)) return { ok: false, error: `this install has no bundled ${entry.dir}` };
9036
9196
  say(`copying to ${target} (it's yours \u2014 edit it)`);
9037
9197
  try {
9038
9198
  cpSync(bundled, target, {
9039
9199
  recursive: true,
9040
- filter: (src) => !relative4(bundled, src).split(sep3).includes("node_modules")
9200
+ filter: (src) => !relative4(bundled, src).split(sep5).includes("node_modules")
9041
9201
  });
9042
9202
  } catch (err) {
9043
9203
  return { ok: false, error: `copy failed: ${err instanceof Error ? err.message : String(err)}` };
@@ -9051,7 +9211,7 @@ async function loadDemo(entry, opts) {
9051
9211
  } else {
9052
9212
  say(`reusing ${target}`);
9053
9213
  }
9054
- if (existsSync12(join18(target, "package.json")) && !existsSync12(join18(target, "node_modules"))) {
9214
+ if (existsSync13(join19(target, "package.json")) && !existsSync13(join19(target, "node_modules"))) {
9055
9215
  say("npm install\u2026");
9056
9216
  if (await runStep("npm", ["install"], { cwd: target, shell: process.platform === "win32" }) !== 0) {
9057
9217
  return { ok: false, error: `npm install failed in ${target}` };
@@ -9063,7 +9223,7 @@ async function loadDemo(entry, opts) {
9063
9223
  return { ok: false, error: `setup failed (${entry.setup})` };
9064
9224
  }
9065
9225
  }
9066
- return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) => join18(target, d)) : [target] };
9226
+ return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) => join19(target, d)) : [target] };
9067
9227
  }
9068
9228
  function runStep(cmd, args, opts = {}) {
9069
9229
  return new Promise((res) => {
@@ -9074,8 +9234,8 @@ function runStep(cmd, args, opts = {}) {
9074
9234
  }
9075
9235
 
9076
9236
  // src/server.ts
9077
- var pkgRoot = join19(dirname5(fileURLToPath3(import.meta.url)), "..");
9078
- var webRoot = join19(pkgRoot, "web");
9237
+ var pkgRoot = join20(dirname5(fileURLToPath3(import.meta.url)), "..");
9238
+ var webRoot = join20(pkgRoot, "web");
9079
9239
  var execFileP = async (cmd, args) => (await promisify3(execFile3)(cmd, args, { encoding: "utf8", timeout: 1e4 })).stdout;
9080
9240
  function optsFromQuery(url, tierEnvVar, projectDir) {
9081
9241
  const q = url.searchParams;
@@ -9125,7 +9285,7 @@ function tierFailure(tier, message) {
9125
9285
  function beholdVersion() {
9126
9286
  try {
9127
9287
  const here = dirname5(fileURLToPath3(import.meta.url));
9128
- return JSON.parse(readFileSync14(join19(here, "..", "package.json"), "utf8")).version ?? "unknown";
9288
+ return JSON.parse(readFileSync15(join20(here, "..", "package.json"), "utf8")).version ?? "unknown";
9129
9289
  } catch {
9130
9290
  return "unknown";
9131
9291
  }
@@ -9170,7 +9330,7 @@ async function captureFrame(projectDir, env, frames, broadcaster) {
9170
9330
  }
9171
9331
  }
9172
9332
  function carveRoutes(app, reportPath, demo) {
9173
- const load = () => readCarveReport(reportPath, (p) => readFileSync14(p, "utf8"));
9333
+ const load = () => readCarveReport(reportPath, (p) => readFileSync15(p, "utf8"));
9174
9334
  const carveDirs = [dirname5(reportPath), ...demo ? [demo.out] : []];
9175
9335
  const carveStates = () => readCarveStates(carveDirs, nodeCarveIo, 0);
9176
9336
  const emittedNodes = async (states) => {
@@ -9189,8 +9349,8 @@ function carveRoutes(app, reportPath, demo) {
9189
9349
  project: demo.project,
9190
9350
  out: demo.out,
9191
9351
  // Paths the UI can print without leaking the operator's whole home dir.
9192
- outLabel: relative5(demo.root, demo.out).split(sep4).join("/"),
9193
- fromLabel: relative5(demo.root, demo.from).split(sep4).join("/"),
9352
+ outLabel: relative5(demo.root, demo.out).split(sep6).join("/"),
9353
+ fromLabel: relative5(demo.root, demo.from).split(sep6).join("/"),
9194
9354
  runnable: !block,
9195
9355
  ...block ? { reason: block } : {},
9196
9356
  // The live tier, when this boot armed it: the scratch endpoint the
@@ -9207,7 +9367,7 @@ function carveRoutes(app, reportPath, demo) {
9207
9367
  let appGraph;
9208
9368
  const appGraphOnce = () => {
9209
9369
  if (!demo) return Promise.resolve(null);
9210
- appGraph ??= graphIr(demo.project).then((ir) => ir.nodes.length ? { ir, label: relative5(demo.root, demo.project).split(sep4).join("/") } : null).catch(() => null);
9370
+ appGraph ??= graphIr(demo.project).then((ir) => ir.nodes.length ? { ir, label: relative5(demo.root, demo.project).split(sep6).join("/") } : null).catch(() => null);
9211
9371
  return appGraph;
9212
9372
  };
9213
9373
  app.get("/api/graph", async (c) => {
@@ -9223,7 +9383,7 @@ function carveRoutes(app, reportPath, demo) {
9223
9383
  return bandGraduated(split.tf, split.graduated);
9224
9384
  };
9225
9385
  const { svg, ir } = appSide ? renderCarveEstate(tfIr, appSide.ir, {
9226
- tfTitle: `${relative5(demo.root, demo.from).split(sep4).join("/")} \u2014 terraform`,
9386
+ tfTitle: `${relative5(demo.root, demo.from).split(sep6).join("/")} \u2014 terraform`,
9227
9387
  appTitle: `${appSide.label} \u2014 chant`,
9228
9388
  carved: states,
9229
9389
  shorten,
@@ -9243,7 +9403,9 @@ function carveRoutes(app, reportPath, demo) {
9243
9403
  carve: true,
9244
9404
  // A demo whose own advisor run failed says so on the statusbar, not
9245
9405
  // only in the terminal the viewer isn't looking at.
9246
- note: carveNote(parsed.report, tfIr) + carveStateNote(carveProgress(states.values(), tfIr.nodes.length), states) + (demo?.degraded ? ` Degraded: ${demo.degraded}` : "")
9406
+ note: carveNote(parsed.report, tfIr) + carveStateNote(carveProgress(states.values(), tfIr.nodes.length), states) + (demo?.degraded ? ` Degraded: ${demo.degraded}` : "") + // chant#2058: the app box shows only the app's own declarations;
9407
+ // what the git-root Op discovery added from elsewhere is named.
9408
+ (appSide && foreignNote(appSide.ir) ? ` ${foreignNote(appSide.ir)}.` : "")
9247
9409
  }
9248
9410
  });
9249
9411
  });
@@ -9275,7 +9437,7 @@ function carveRoutes(app, reportPath, demo) {
9275
9437
  }
9276
9438
  const tfIr = carveReportToIr(parsed.report);
9277
9439
  const html = renderCarveMorph(tfIr, appSide.ir, [select], {
9278
- tfTitle: `${relative5(demo.root, demo.from).split(sep4).join("/")} \u2014 terraform`,
9440
+ tfTitle: `${relative5(demo.root, demo.from).split(sep6).join("/")} \u2014 terraform`,
9279
9441
  appTitle: `${appSide.label} \u2014 chant`,
9280
9442
  title: `carve \u2014 ${select}`,
9281
9443
  carved: carveStates(),
@@ -9369,6 +9531,36 @@ function carveRoutes(app, reportPath, demo) {
9369
9531
  app.get("/api/resources", (c) => c.json({ byComponent: {} }));
9370
9532
  app.get("/api/ci", (c) => c.json({ stages: [], jobs: [], forge: null }));
9371
9533
  }
9534
+ function followLimits(env = process.env) {
9535
+ const timeout = Number.parseInt(env.BEHOLD_CI_FOLLOW_TIMEOUT_MS ?? "", 10);
9536
+ const budget = Number.parseInt(env.BEHOLD_CI_POLL_FAIL_BUDGET ?? "", 10);
9537
+ return {
9538
+ ...Number.isInteger(timeout) && timeout >= 0 ? { followTimeoutMs: timeout } : {},
9539
+ ...Number.isInteger(budget) && budget > 0 ? { pollFailBudget: budget } : {}
9540
+ };
9541
+ }
9542
+ async function readoptDispatchedRun(cfg, runner) {
9543
+ const run3 = readDispatchedRun(cfg.projectDir);
9544
+ if (!run3 || run3.concluded && run3.concluded.verdict !== "lost") return { outcome: "none" };
9545
+ const ready = await ghReady();
9546
+ if (!ready.ok) return { outcome: "refused", reason: ready.reason ?? "gh is not ready", run: run3 };
9547
+ const pipeline = await ciPipeline(cfg.projectDir, run3.env ? { env: run3.env } : {}, "github").catch(() => void 0);
9548
+ if (!pipeline || pipeline.jobs.length === 0) {
9549
+ return { outcome: "refused", reason: "no generated GitHub pipeline to paint the run onto \u2014 `chant build --components --generate github` first", run: run3 };
9550
+ }
9551
+ const started = runner.track(`GitHub Actions ${run3.workflow} (re-adopted run ${run3.runId})`, (io) => {
9552
+ io.line(`\u25CF re-adopting run ${run3.runId} (${run3.workflow}, dispatched ${run3.dispatchedAt}) \u2014 \`gh run view\` is the durable read`);
9553
+ return followRun(run3.runId, pipeline, {
9554
+ ...followLimits(),
9555
+ onLine: io.line,
9556
+ onProgress: io.progress,
9557
+ onAdopted: ({ runId, url }) => saveDispatchedRun({ ...run3, runId, ...url ? { url } : {} }),
9558
+ onConcluded: ({ verdict }) => concludeDispatchedRun(cfg.projectDir, run3.runId, verdict)
9559
+ });
9560
+ });
9561
+ if (!started) return { outcome: "refused", reason: `busy \u2014 ${runner.running} is running`, run: run3 };
9562
+ return { outcome: "readopted", run: run3 };
9563
+ }
9372
9564
  function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffer(), runner = new OpRunner({
9373
9565
  projectDir: cfg.projectDir,
9374
9566
  broadcaster,
@@ -9486,9 +9678,9 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9486
9678
  return c.json({ started: true, name, ran: label });
9487
9679
  });
9488
9680
  app.post("/api/local/reset", (c) => {
9489
- const down = join19(cfg.projectDir, "scripts/local/local-down.sh");
9490
- const up = join19(cfg.projectDir, "scripts/local/local-up.sh");
9491
- if (!existsSync13(down) || !existsSync13(up)) {
9681
+ const down = join20(cfg.projectDir, "scripts/local/local-down.sh");
9682
+ const up = join20(cfg.projectDir, "scripts/local/local-up.sh");
9683
+ if (!existsSync14(down) || !existsSync14(up)) {
9492
9684
  return c.json({ error: "no local-down.sh / local-up.sh in scripts/local \u2014 reset is only for local emulator projects" }, 400);
9493
9685
  }
9494
9686
  if (!runner.bringUp("reset local emulator", "bash", ["-c", "bash scripts/local/local-down.sh && bash scripts/local/local-up.sh"], cfg.projectDir)) {
@@ -9509,7 +9701,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9509
9701
  const dirs = [];
9510
9702
  const declared = [];
9511
9703
  for (const f of discoverOpIrs(estateDirs)) {
9512
- const parsed = readOpIr(f.path, (p) => readFileSync14(p, "utf8"));
9704
+ const parsed = readOpIr(f.path, (p) => readFileSync15(p, "utf8"));
9513
9705
  if (!parsed.ok) continue;
9514
9706
  const mine = declaredConvergeOps([parsed.ir]);
9515
9707
  if (!mine.length) continue;
@@ -9679,9 +9871,9 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9679
9871
  app.post("/api/project/open", async (c) => {
9680
9872
  if (cfg.previewMode) return c.json({ error: "switching projects is locked in preview mode" }, 403);
9681
9873
  const body = await c.req.json().catch(() => ({}));
9682
- const dir = typeof body.dir === "string" && body.dir.trim() ? resolve7(body.dir.trim()) : "";
9683
- if (!dir || !existsSync13(dir)) return c.json({ error: `no such directory: ${dir || "(no dir given)"}` }, 400);
9684
- if (!existsSync13(join19(dir, "chant.config.ts"))) {
9874
+ const dir = typeof body.dir === "string" && body.dir.trim() ? resolve9(body.dir.trim()) : "";
9875
+ if (!dir || !existsSync14(dir)) return c.json({ error: `no such directory: ${dir || "(no dir given)"}` }, 400);
9876
+ if (!existsSync14(join20(dir, "chant.config.ts"))) {
9685
9877
  return c.json({ error: `${dir} doesn't look like a chant project \u2014 no chant.config.ts` }, 400);
9686
9878
  }
9687
9879
  switchServedProject([dir]);
@@ -9701,7 +9893,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9701
9893
  };
9702
9894
  app.post("/api/project/reveal", async (c) => {
9703
9895
  const body = await c.req.json().catch(() => ({}));
9704
- const dir = typeof body.dir === "string" && body.dir.trim() ? resolve7(body.dir.trim()) : cfg.projectDir;
9896
+ const dir = typeof body.dir === "string" && body.dir.trim() ? resolve9(body.dir.trim()) : cfg.projectDir;
9705
9897
  const known = /* @__PURE__ */ new Set([cfg.projectDir, ...cfg.projectDirs ?? [], ...listRecents().map((r) => r.dir)]);
9706
9898
  if (!known.has(dir)) return c.json({ error: "not a served or recent project directory" }, 400);
9707
9899
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
@@ -9725,7 +9917,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9725
9917
  fetches: fetchesFromNetwork(e),
9726
9918
  ...e.repo ? { repo: e.repo } : {},
9727
9919
  target,
9728
- loaded: existsSync13(target),
9920
+ loaded: existsSync14(target),
9729
9921
  satisfiable: missing.length === 0,
9730
9922
  // #254: the carve walkthrough runs fine here — it just isn't a project
9731
9923
  // to switch INTO. Carve mode claims `/api/graph` and `/api/project` at
@@ -9769,7 +9961,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9769
9961
  const loaded = await loadDemo(entry, { pkgRoot, target, log: (line) => process.stdout.write(line + "\n") });
9770
9962
  if (!loaded.ok) return c.json({ error: loaded.error }, 500);
9771
9963
  const dirs = loaded.serveDirs;
9772
- if (!existsSync13(join19(dirs[0], "chant.config.ts"))) {
9964
+ if (!existsSync14(join20(dirs[0], "chant.config.ts"))) {
9773
9965
  return c.json({ error: `${entry.name} loaded to ${target} but ${dirs[0]} has no chant.config.ts` }, 500);
9774
9966
  }
9775
9967
  if (entry.serve.local) {
@@ -9904,7 +10096,9 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9904
10096
  { method: "POST", path: "/api/rollback", desc: "open a rollback PR: ?to=<sha>" },
9905
10097
  { method: "POST", path: "/api/substrates/:name/up", desc: "bring a substrate up" },
9906
10098
  { method: "POST", path: "/api/local/reset", desc: "reset the local emulator" },
9907
- { method: "POST", path: "/api/ci/dispatch", desc: "dispatch the GitHub Actions pipeline for ?env= via the operator's gh \u2014 the designated workflow when .behold.json names one" }
10099
+ { method: "POST", path: "/api/ci/dispatch", desc: "dispatch the GitHub Actions pipeline for ?env= via the operator's gh \u2014 the designated workflow when .behold.json names one" },
10100
+ { method: "GET", path: "/api/ci/run", desc: "the persisted dispatched-run record (id, verdict) \u2014 what survives a behold restart" },
10101
+ { method: "POST", path: "/api/ci/readopt", desc: "re-adopt the persisted unconcluded run: follow it by its saved id (also attempted automatically at boot)" }
9908
10102
  ]
9909
10103
  })
9910
10104
  );
@@ -9926,7 +10120,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9926
10120
  }
9927
10121
  const parsedOps = [];
9928
10122
  for (const f of files) {
9929
- const parsed = readOpIr(f.path, (p) => readFileSync14(p, "utf8"));
10123
+ const parsed = readOpIr(f.path, (p) => readFileSync15(p, "utf8"));
9930
10124
  if (!parsed.ok) return c.json(parsed.refusal, 422);
9931
10125
  parsedOps.push(parsed.ir);
9932
10126
  }
@@ -9939,8 +10133,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9939
10133
  }
9940
10134
  const runState = runner.runState;
9941
10135
  const ranOp = runState.op ? parsedOps.find((o) => o.name === runState.op) : void 0;
9942
- const join22 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
9943
- const opsIr = join22 ? paintPlayhead(declaredIr, join22, runState.gate) : declaredIr;
10136
+ const join23 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
10137
+ const opsIr = join23 ? paintPlayhead(declaredIr, join23, runState.gate) : declaredIr;
9944
10138
  const { svg: svg2 } = renderGraph(opsIr, { boxes: "byStack" });
9945
10139
  const operator = runner.noteDeclaredConvergeOps(declaredConvergeOps(parsedOps));
9946
10140
  return c.json({
@@ -9953,7 +10147,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9953
10147
  target: null,
9954
10148
  mode: "ops",
9955
10149
  ops: parsedOps.length,
9956
- note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join22)} ${operatorNote(operator)}`,
10150
+ note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join23)} ${operatorNote(operator)}`,
9957
10151
  // The operator strip's own model: the ConvergeOps this project
9958
10152
  // declares, plus whatever the last status read said. `declared`
9959
10153
  // without a `strip` is the honest starting state — a loop exists and
@@ -10043,7 +10237,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
10043
10237
  srcCompositeEdgesAttached = 0;
10044
10238
  }
10045
10239
  }
10046
- if (!multi && !components && !logical && !opts.lens && ir.nodes.length === 0 && !existsSync13(join19(cfg.projectDir, "chant.config.ts"))) {
10240
+ if (!multi && !components && !logical && !opts.lens && ir.nodes.length === 0 && !existsSync14(join20(cfg.projectDir, "chant.config.ts"))) {
10047
10241
  return c.json(noProjectError(cfg.projectDir), 404);
10048
10242
  }
10049
10243
  const radial = new URL(c.req.url).searchParams.get("radial") === "1";
@@ -10106,13 +10300,36 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
10106
10300
  if ("reason" in resolved) return c.json({ error: resolved.reason, ...designated ? { code: "executor-forge" } : {} }, 400);
10107
10301
  const workflow = resolved.workflow;
10108
10302
  const ref = await execFileP("git", ["-C", cfg.projectDir, "branch", "--show-current"]).then((out) => out.trim() || "main").catch(() => "main");
10303
+ const base = { projectDir: cfg.projectDir, ...env ? { env } : {}, workflow: workflow.file, ref, dispatchedAt: (/* @__PURE__ */ new Date()).toISOString() };
10304
+ let adoptedId;
10109
10305
  const started = runner.track(
10110
10306
  `GitHub Actions ${workflow.file}`,
10111
- (io) => dispatchAndFollow(workflow, pipeline, ref, { onLine: io.line, onProgress: io.progress })
10307
+ (io) => dispatchAndFollow(workflow, pipeline, ref, {
10308
+ ...followLimits(),
10309
+ onLine: io.line,
10310
+ onProgress: io.progress,
10311
+ onAdopted: ({ runId, url }) => {
10312
+ adoptedId = runId;
10313
+ saveDispatchedRun({ ...base, runId, ...url ? { url } : {} });
10314
+ },
10315
+ onConcluded: ({ verdict }) => {
10316
+ if (adoptedId !== void 0) concludeDispatchedRun(cfg.projectDir, adoptedId, verdict);
10317
+ }
10318
+ })
10112
10319
  );
10113
10320
  if (!started) return c.json({ error: `busy \u2014 ${runner.running} is running` }, 409);
10114
10321
  return c.json({ started: true, workflow: workflow.file, ref, jobs: pipeline.jobs.length, ...env ? { env } : {}, designated: Boolean(designated) });
10115
10322
  });
10323
+ app.get("/api/ci/run", (c) => {
10324
+ const run3 = readDispatchedRun(cfg.projectDir);
10325
+ return c.json({ run: run3 ?? null });
10326
+ });
10327
+ app.post("/api/ci/readopt", async (c) => {
10328
+ const result = await readoptDispatchedRun(cfg, runner);
10329
+ if (result.outcome === "none") return c.json({ error: "no unconcluded dispatched run is persisted for this project" }, 404);
10330
+ if (result.outcome === "refused") return c.json({ error: result.reason, run: result.run }, 409);
10331
+ return c.json({ readopted: true, runId: result.run.runId, workflow: result.run.workflow });
10332
+ });
10116
10333
  app.get("/api/resources", async (c) => {
10117
10334
  const opts = optsFromQuery(new URL(c.req.url), tierEnvVar, cfg.projectDir);
10118
10335
  const env = opts.env ?? cfg.env;
@@ -10397,7 +10614,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
10397
10614
  });
10398
10615
  const rel = relative5(process.cwd(), webRoot) || ".";
10399
10616
  app.use("/*", serveStatic({ root: rel }));
10400
- app.get("/", serveStatic({ path: join19(rel, "index.html") }));
10617
+ app.get("/", serveStatic({ path: join20(rel, "index.html") }));
10401
10618
  return app;
10402
10619
  }
10403
10620
  async function bootLocalEmulators(dir, who) {
@@ -10426,7 +10643,7 @@ async function bootLocalEmulators(dir, who) {
10426
10643
  async function startServer(cfg) {
10427
10644
  if (cfg.local) cfg.emulators = await bootLocalEmulators(cfg.projectDir, "behold serve --local");
10428
10645
  for (const dir of cfg.projectDirs ?? [cfg.projectDir]) {
10429
- if (existsSync13(join19(dir, "chant.config.ts"))) addRecent(dir);
10646
+ if (existsSync14(join20(dir, "chant.config.ts"))) addRecent(dir);
10430
10647
  }
10431
10648
  const broadcaster = new Broadcaster();
10432
10649
  const frames = new FrameBuffer();
@@ -10437,6 +10654,17 @@ async function startServer(cfg) {
10437
10654
  });
10438
10655
  const app = createApp(cfg, broadcaster, frames, runner);
10439
10656
  const autoSync = cfg.autoSync ?? "off";
10657
+ if (!cfg.carveReport) {
10658
+ void readoptDispatchedRun(cfg, runner).then((result) => {
10659
+ if (result.outcome === "readopted") {
10660
+ process.stdout.write(` re-adopted dispatched run ${result.run.runId} (${result.run.workflow}) \u2014 following via gh
10661
+ `);
10662
+ } else if (result.outcome === "refused") {
10663
+ process.stdout.write(` a dispatched run (${result.run.runId}, ${result.run.workflow}) is persisted but not re-adopted: ${result.reason}
10664
+ `);
10665
+ }
10666
+ }).catch(() => void 0);
10667
+ }
10440
10668
  const capture2 = () => captureFrame(cfg.projectDir, cfg.env, frames, broadcaster);
10441
10669
  const onEstateChange = (memberDir) => {
10442
10670
  broadcaster.emit("changed", memberDir ?? "");
@@ -10573,8 +10801,8 @@ async function startServer(cfg) {
10573
10801
  }
10574
10802
 
10575
10803
  // src/export.ts
10576
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, copyFileSync, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "node:fs";
10577
- import { join as join20, dirname as dirname6, basename as basename2 } from "node:path";
10804
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, copyFileSync, readFileSync as readFileSync16, readdirSync as readdirSync8 } from "node:fs";
10805
+ import { join as join21, dirname as dirname6, basename as basename2 } from "node:path";
10578
10806
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10579
10807
  var LENS_PARAMS = ["components", "detail", "env", "logical", "ops", "radial", "tier"];
10580
10808
  function canonicalKey(path, params) {
@@ -10582,7 +10810,7 @@ function canonicalKey(path, params) {
10582
10810
  const q = LENS_PARAMS.filter((k) => params.has(k) && !(flat && (k === "detail" || k === "radial"))).map((k) => `${k}=${params.get(k)}`).join("&");
10583
10811
  return q ? `${path}?${q}` : path;
10584
10812
  }
10585
- function slug2(key) {
10813
+ function slug3(key) {
10586
10814
  const base = key.replace(/^\//, "").replace(/[^a-zA-Z0-9=_.-]+/g, "_").slice(0, 120);
10587
10815
  return `${base}.json`;
10588
10816
  }
@@ -10623,7 +10851,7 @@ function captureKeys(axes) {
10623
10851
  return [...keys];
10624
10852
  }
10625
10853
  function webDir() {
10626
- return join20(dirname6(fileURLToPath4(import.meta.url)), "..", "web");
10854
+ return join21(dirname6(fileURLToPath4(import.meta.url)), "..", "web");
10627
10855
  }
10628
10856
  function workerName(project, override) {
10629
10857
  const raw = override ?? `behold-${basename2(project)}`;
@@ -10634,16 +10862,16 @@ async function runExport(cfg, outDir, opts = {}) {
10634
10862
  const app = createApp({ ...cfg, layoutWrites: false });
10635
10863
  const proj = await (await app.request("/api/project")).json();
10636
10864
  const axes = { environments: proj.environments ?? [], tiers: proj.tiers ?? [], ...proj.ops ? { ops: proj.ops } : {} };
10637
- const snapDir = join20(outDir, "snapshots");
10638
- mkdirSync4(snapDir, { recursive: true });
10865
+ const snapDir = join21(outDir, "snapshots");
10866
+ mkdirSync5(snapDir, { recursive: true });
10639
10867
  const keyToFile = {};
10640
10868
  let ok = 0;
10641
10869
  let failed = 0;
10642
10870
  for (const key of captureKeys(axes)) {
10643
10871
  const res = await app.request(`${key}${key.includes("?") ? "&" : "?"}layout=1`);
10644
10872
  const body = await res.text();
10645
- const file = slug2(key);
10646
- writeFileSync4(join20(snapDir, file), body);
10873
+ const file = slug3(key);
10874
+ writeFileSync5(join21(snapDir, file), body);
10647
10875
  keyToFile[key] = `snapshots/${file}`;
10648
10876
  if (res.ok) ok++;
10649
10877
  else failed++;
@@ -10655,21 +10883,21 @@ async function runExport(cfg, outDir, opts = {}) {
10655
10883
  axes,
10656
10884
  keyToFile
10657
10885
  };
10658
- writeFileSync4(join20(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
10659
- const html = readFileSync15(join20(webDir(), "index.html"), "utf8").replace(
10886
+ writeFileSync5(join21(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
10887
+ const html = readFileSync16(join21(webDir(), "index.html"), "utf8").replace(
10660
10888
  /<\/head>/i,
10661
10889
  ` <script>window.__BEHOLD_STATIC__ = true;</script>
10662
10890
  </head>`
10663
10891
  );
10664
- writeFileSync4(join20(outDir, "index.html"), html);
10892
+ writeFileSync5(join21(outDir, "index.html"), html);
10665
10893
  for (const f of readdirSync8(webDir())) {
10666
10894
  if (f === "index.html") continue;
10667
- copyFileSync(join20(webDir(), f), join20(outDir, f));
10895
+ copyFileSync(join21(webDir(), f), join21(outDir, f));
10668
10896
  }
10669
- writeFileSync4(join20(outDir, "README.md"), BUNDLE_README);
10897
+ writeFileSync5(join21(outDir, "README.md"), BUNDLE_README);
10670
10898
  const name = workerName(cfg.projectDir, opts.name);
10671
- writeFileSync4(
10672
- join20(outDir, "wrangler.jsonc"),
10899
+ writeFileSync5(
10900
+ join21(outDir, "wrangler.jsonc"),
10673
10901
  JSON.stringify(
10674
10902
  { $schema: "node_modules/wrangler/config-schema.json", name, compatibility_date: "2025-06-01", assets: { directory: "." } },
10675
10903
  null,
@@ -10717,7 +10945,7 @@ It's just files \u2014 GitHub Pages, S3, nginx, or Cloudflare Pages
10717
10945
  `;
10718
10946
 
10719
10947
  // src/doctor.ts
10720
- import { relative as relative6, resolve as resolve8 } from "node:path";
10948
+ import { relative as relative6, resolve as resolve10 } from "node:path";
10721
10949
  var list2 = (xs) => xs.join(", ");
10722
10950
  var EMPTY_KUBECONFIG = { contexts: /* @__PURE__ */ new Map(), servers: /* @__PURE__ */ new Map() };
10723
10951
  function summarize(xs, max = 4) {
@@ -10866,7 +11094,7 @@ function opsCheck(root, ops, chantSource, estate) {
10866
11094
  return { name: "ops", status: "pass", detail: `${ops.length} committed: ${list2(names)}; ${mcp}` };
10867
11095
  }
10868
11096
  async function diagnose(dir, probes = {}) {
10869
- const root = resolve8(dir);
11097
+ const root = resolve10(dir);
10870
11098
  const shape = detectProjectShape(root);
10871
11099
  const behold = beholdVersion();
10872
11100
  if (shape.kind === "none") {
@@ -10886,7 +11114,7 @@ async function diagnose(dir, probes = {}) {
10886
11114
  };
10887
11115
  }
10888
11116
  const estate = shape.kind === "estate";
10889
- const members = (shape.members ?? []).map((m) => resolve8(root, m));
11117
+ const members = (shape.members ?? []).map((m) => resolve10(root, m));
10890
11118
  const targets = estate ? members : [root];
10891
11119
  const primary = targets[0];
10892
11120
  const projectCheck = estate ? {
@@ -11120,7 +11348,7 @@ ${USAGE}`);
11120
11348
  process.stderr.write("behold serve: --auto-sync needs --env and --poll (it acts on polled drift)\n");
11121
11349
  process.exit(2);
11122
11350
  }
11123
- const dirs = projectDirs.map((d) => resolve9(d));
11351
+ const dirs = projectDirs.map((d) => resolve11(d));
11124
11352
  for (const d of dirs) warnIfNotChantProject(d);
11125
11353
  await startServer({
11126
11354
  projectDir: dirs[0],
@@ -11139,7 +11367,7 @@ function warnIfNotChantProject(dir) {
11139
11367
  if (shape.kind === "estate") {
11140
11368
  process.stderr.write(
11141
11369
  `behold: warning \u2014 ${dir} is an estate root, not a chant project itself.
11142
- Serve its members composed: behold serve ${shape.members.map((m) => join21(dir, m)).join(" ")}
11370
+ Serve its members composed: behold serve ${shape.members.map((m) => join22(dir, m)).join(" ")}
11143
11371
  `
11144
11372
  );
11145
11373
  return;
@@ -11174,8 +11402,8 @@ async function runCarve(rest) {
11174
11402
  );
11175
11403
  process.exit(2);
11176
11404
  }
11177
- const reportPath = resolve9(fileArg);
11178
- const parsed = readCarveReport(reportPath, (p) => readFileSync16(p, "utf8"));
11405
+ const reportPath = resolve11(fileArg);
11406
+ const parsed = readCarveReport(reportPath, (p) => readFileSync17(p, "utf8"));
11179
11407
  if (!parsed.ok) {
11180
11408
  process.stderr.write(`behold carve: ${parsed.refusal.error}
11181
11409
  ${parsed.refusal.remedy}
@@ -11202,8 +11430,8 @@ async function runDoctor(rest) {
11202
11430
  }
11203
11431
  }
11204
11432
  const dir = dirArg ?? ".";
11205
- if (!existsSync14(resolve9(dir))) {
11206
- process.stderr.write(`behold doctor: no such directory: ${resolve9(dir)}
11433
+ if (!existsSync15(resolve11(dir))) {
11434
+ process.stderr.write(`behold doctor: no such directory: ${resolve11(dir)}
11207
11435
  `);
11208
11436
  process.exit(2);
11209
11437
  }
@@ -11212,7 +11440,7 @@ async function runDoctor(rest) {
11212
11440
  if (!report.ok) process.exitCode = 1;
11213
11441
  }
11214
11442
  async function runDemo(rest) {
11215
- const pkgRoot2 = join21(dirname7(fileURLToPath5(import.meta.url)), "..");
11443
+ const pkgRoot2 = join22(dirname7(fileURLToPath5(import.meta.url)), "..");
11216
11444
  const registry = loadDemoRegistry(pkgRoot2);
11217
11445
  let port = 4600;
11218
11446
  let live = false;
@@ -11266,7 +11494,7 @@ async function runDemo(rest) {
11266
11494
  `);
11267
11495
  process.exit(2);
11268
11496
  }
11269
- const target = dirArg ? resolve9(dirArg) : demoTargetDir(entry);
11497
+ const target = dirArg ? resolve11(dirArg) : demoTargetDir(entry);
11270
11498
  const loaded = await loadDemo(entry, { pkgRoot: pkgRoot2, target, log: (line) => process.stdout.write(line + "\n") });
11271
11499
  if (!loaded.ok) {
11272
11500
  process.stderr.write(`behold demo ${entry.name}: ${loaded.error}
@@ -11297,7 +11525,7 @@ function spawnStep(cmd, args, cwd) {
11297
11525
  });
11298
11526
  }
11299
11527
  async function serveCarveDemo(target, carve, port, live = false) {
11300
- const at = (rel) => resolve9(target, rel);
11528
+ const at = (rel) => resolve11(target, rel);
11301
11529
  const project = at(carve.project);
11302
11530
  const from = at(carve.from);
11303
11531
  const state = carve.state ? at(carve.state) : void 0;
@@ -11321,7 +11549,7 @@ async function serveCarveDemo(target, carve, port, live = false) {
11321
11549
  process.exit(1);
11322
11550
  }
11323
11551
  process.stdout.write("behold demo carve --live \u2192 terraform init + apply (the starred resources, into the scratch Floci)\u2026\n");
11324
- if (state) rmSync2(state, { force: true });
11552
+ if (state) rmSync3(state, { force: true });
11325
11553
  const applyErr = await applyIntoFloci(from, spawnStep);
11326
11554
  if (applyErr) {
11327
11555
  await teardownScratchFloci();
@@ -11337,7 +11565,7 @@ async function serveCarveDemo(target, carve, port, live = false) {
11337
11565
  };
11338
11566
  process.stdout.write("behold demo carve --live \u2192 the tfstate is real now; the advisor reads what terraform wrote.\n");
11339
11567
  }
11340
- if (existsSync14(join21(project, "package.json")) && !existsSync14(join21(project, "node_modules"))) {
11568
+ if (existsSync15(join22(project, "package.json")) && !existsSync15(join22(project, "node_modules"))) {
11341
11569
  process.stdout.write(`behold demo carve \u2192 npm install in ${carve.project}/ (the chant this walkthrough shells)\u2026
11342
11570
  `);
11343
11571
  const code = await spawnStep("npm", ["install"], project);
@@ -11348,7 +11576,7 @@ async function serveCarveDemo(target, carve, port, live = false) {
11348
11576
  }
11349
11577
  }
11350
11578
  let degraded;
11351
- if (!existsSync14(join21(target, "node_modules", "@cdktf", "hcl2json"))) {
11579
+ if (!existsSync15(join22(target, "node_modules", "@cdktf", "hcl2json"))) {
11352
11580
  process.stdout.write("behold demo carve \u2192 npm install @cdktf/hcl2json (chant's HCL parser, ~2MB, once)\u2026\n");
11353
11581
  const code = await spawnStep("npm", ["install", "--no-save", "--no-package-lock", "@cdktf/hcl2json"], target);
11354
11582
  if (code !== 0) degraded = "couldn't install @cdktf/hcl2json (chant's HCL parser) \u2014 no network?";
@@ -11364,7 +11592,7 @@ async function serveCarveDemo(target, carve, port, live = false) {
11364
11592
  );
11365
11593
  if (code !== 0) degraded = `chant carve advise exited ${code}`;
11366
11594
  }
11367
- const serving = !degraded && existsSync14(report) ? report : committed;
11595
+ const serving = !degraded && existsSync15(report) ? report : committed;
11368
11596
  if (degraded) {
11369
11597
  process.stderr.write(
11370
11598
  `behold demo carve: ${degraded}
@@ -11372,7 +11600,7 @@ async function serveCarveDemo(target, carve, port, live = false) {
11372
11600
  `
11373
11601
  );
11374
11602
  }
11375
- if (!existsSync14(serving)) {
11603
+ if (!existsSync15(serving)) {
11376
11604
  process.stderr.write(`behold demo carve: no carve report at ${serving}
11377
11605
  `);
11378
11606
  process.exit(2);
@@ -11417,8 +11645,8 @@ async function runPreview(rest) {
11417
11645
  process.stderr.write("behold preview: --port must be a number\n");
11418
11646
  process.exit(2);
11419
11647
  }
11420
- const projectDir = resolve9(dirArg ?? process.cwd());
11421
- if (!existsSync14(projectDir)) {
11648
+ const projectDir = resolve11(dirArg ?? process.cwd());
11649
+ if (!existsSync15(projectDir)) {
11422
11650
  process.stderr.write(`behold preview: project not found at ${projectDir}
11423
11651
  `);
11424
11652
  process.exit(2);
@@ -11438,26 +11666,26 @@ async function runPreview(rest) {
11438
11666
  await startServer({ projectDir, port, env: "local", previewMode: true });
11439
11667
  }
11440
11668
  async function runExportCmd(rest) {
11441
- let outDir = resolve9("behold-export");
11669
+ let outDir = resolve11("behold-export");
11442
11670
  let env;
11443
11671
  let name;
11444
11672
  let dirArg;
11445
11673
  let emulator = false;
11446
11674
  for (let i = 0; i < rest.length; i++) {
11447
11675
  const a = rest[i];
11448
- if (a === "--out") outDir = resolve9(rest[++i]);
11676
+ if (a === "--out") outDir = resolve11(rest[++i]);
11449
11677
  else if (a === "--env") env = rest[++i];
11450
11678
  else if (a === "--name") name = rest[++i];
11451
11679
  else if (a === "--emulator") emulator = true;
11452
11680
  else if (a === "-h" || a === "--help") return void process.stdout.write(USAGE);
11453
11681
  else if (!a.startsWith("-")) dirArg = a;
11454
11682
  }
11455
- const projectDir = resolve9(dirArg ?? process.cwd());
11683
+ const projectDir = resolve11(dirArg ?? process.cwd());
11456
11684
  if (emulator) {
11457
11685
  injectEmulatorEnv(env);
11458
11686
  env ??= "local";
11459
11687
  }
11460
- if (!existsSync14(projectDir)) {
11688
+ if (!existsSync15(projectDir)) {
11461
11689
  process.stderr.write(`behold export: project not found at ${projectDir}
11462
11690
  `);
11463
11691
  process.exit(2);