loki-mode 11.6.0 → 11.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +19 -2
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/lib/backlog.py +11 -2
  5. package/autonomy/lib/chat.sh +57 -1
  6. package/autonomy/lib/loki_yaml.py +8 -2
  7. package/autonomy/lib/workspace.py +267 -11
  8. package/autonomy/loki +315 -21
  9. package/autonomy/trigger-server.py +185 -2
  10. package/autonomy/verify.sh +4 -0
  11. package/completions/_loki +4 -0
  12. package/completions/loki.bash +20 -2
  13. package/dashboard/__init__.py +1 -1
  14. package/docs/CLI-REFERENCE.md +2 -1
  15. package/docs/DEPLOY-PLAN.md +17 -8
  16. package/docs/EVALUATING.md +8 -0
  17. package/docs/INSTALLATION.md +1 -1
  18. package/docs/WORKSPACES.md +15 -2
  19. package/docs/dev/architecture-reference.md +8 -0
  20. package/docs/sentry-intake.md +11 -0
  21. package/docs/v10/CLI-MODERN.md +1 -1
  22. package/loki-ts/dist/loki.js +1009 -975
  23. package/mcp/__init__.py +1 -1
  24. package/package.json +2 -2
  25. package/packages/control-plane/dist/server.js +618 -469
  26. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  27. package/schemas/loki-yaml.schema.json +5 -2
  28. package/src/policies/engine.js +41 -2
  29. package/src/policies/types.js +49 -0
  30. package/web-app/dist/assets/{AdminPage-DhXdJNQk.js → AdminPage-S3BYeapm.js} +1 -1
  31. package/web-app/dist/assets/{Avatar-ixVj76fM.js → Avatar-C1rHwLgn.js} +1 -1
  32. package/web-app/dist/assets/{Badge-BGMSp22s.js → Badge-CzlnN5aQ.js} +1 -1
  33. package/web-app/dist/assets/{Button-pYJc32tG.js → Button-B1w5AeGC.js} +1 -1
  34. package/web-app/dist/assets/{CockpitPage-C1JBBouA.js → CockpitPage-Dyeqjfkc.js} +1 -1
  35. package/web-app/dist/assets/{ComparePage-qD0NV4RK.js → ComparePage-C6y2GkGR.js} +1 -1
  36. package/web-app/dist/assets/{ErrorBoundary-C-yxO58j.js → ErrorBoundary-Dg2fcGkR.js} +1 -1
  37. package/web-app/dist/assets/{EvidenceReceiptPanel-ByPzuE1S.js → EvidenceReceiptPanel-CxbyjvTj.js} +1 -1
  38. package/web-app/dist/assets/{GitHubIssuesPanel-BNv_2BEJ.js → GitHubIssuesPanel-Dpys1xDb.js} +1 -1
  39. package/web-app/dist/assets/{GitHubPRsPanel-CuLZQ9CS.js → GitHubPRsPanel-CRVjWoFl.js} +1 -1
  40. package/web-app/dist/assets/{HomePage-CS_FW8Je.js → HomePage-BKnv-oYw.js} +2 -2
  41. package/web-app/dist/assets/{LoginPage-DwiNf1Wm.js → LoginPage-BGksmFRd.js} +1 -1
  42. package/web-app/dist/assets/{MagicPage-CmQybLPZ.js → MagicPage-OCvhp16M.js} +1 -1
  43. package/web-app/dist/assets/{MetricsPage-D9ERdpv8.js → MetricsPage-CJOJpXWC.js} +1 -1
  44. package/web-app/dist/assets/{NotFoundPage-D9ccgdbF.js → NotFoundPage-C04p3QvN.js} +1 -1
  45. package/web-app/dist/assets/{ProjectPage-B9Jslmgv.js → ProjectPage-BRB8txsY.js} +3 -3
  46. package/web-app/dist/assets/{ProjectsPage-DnU99Go2.js → ProjectsPage-CoUYeQEh.js} +1 -1
  47. package/web-app/dist/assets/{SettingsPage-DcTyx6wo.js → SettingsPage-BkjmJGfM.js} +1 -1
  48. package/web-app/dist/assets/{ShowcasePage-jEY9ghSr.js → ShowcasePage-B4vU9TjQ.js} +1 -1
  49. package/web-app/dist/assets/{SystemSettingsPage-CSGltW-8.js → SystemSettingsPage-Z2jiVsni.js} +1 -1
  50. package/web-app/dist/assets/{TeamsPage-gUuJXb9l.js → TeamsPage-Ulm8SISq.js} +1 -1
  51. package/web-app/dist/assets/{TemplatesPage-37Zhe--v.js → TemplatesPage-C_7MM6Ey.js} +1 -1
  52. package/web-app/dist/assets/{TerminalOutput-B09hRwaR.js → TerminalOutput-Cumd7Tgs.js} +1 -1
  53. package/web-app/dist/assets/{activity-q_4MY9J0.js → activity-CZAUkbiY.js} +1 -1
  54. package/web-app/dist/assets/{bell-X8OC_PFP.js → bell-BOvSXpEr.js} +1 -1
  55. package/web-app/dist/assets/{bot-C-N1PUOD.js → bot-DOTZxBYj.js} +1 -1
  56. package/web-app/dist/assets/{check-B2c76Jmp.js → check-BxpE0B5u.js} +1 -1
  57. package/web-app/dist/assets/{chevron-left-BF76YLNw.js → chevron-left-D6KPvBOA.js} +1 -1
  58. package/web-app/dist/assets/{circle-alert-AeFKBD8h.js → circle-alert-Bajq86oe.js} +1 -1
  59. package/web-app/dist/assets/{clock-CiXRf-FH.js → clock-CnlHAVJc.js} +1 -1
  60. package/web-app/dist/assets/{cloud-DKQ8SRJ5.js → cloud-DjaymjwM.js} +1 -1
  61. package/web-app/dist/assets/{code-xml-DEb6jXWP.js → code-xml-BADEw2Ld.js} +1 -1
  62. package/web-app/dist/assets/{database-6W7e-E9c.js → database-B4aKb5Cq.js} +1 -1
  63. package/web-app/dist/assets/{dollar-sign-Ds2GOuyW.js → dollar-sign-DrldXpja.js} +1 -1
  64. package/web-app/dist/assets/{file-code-corner-CZU0jg52.js → file-code-corner-DVebDVR-.js} +1 -1
  65. package/web-app/dist/assets/{file-plus-DTVYnXCN.js → file-plus-BD-N_-Tl.js} +1 -1
  66. package/web-app/dist/assets/{globe-0mUzKYDe.js → globe-C7ieInC-.js} +1 -1
  67. package/web-app/dist/assets/{hammer-BMHZg3YX.js → hammer-BSugKIji.js} +1 -1
  68. package/web-app/dist/assets/{index-BvOTvjax.js → index-nLQgfvOn.js} +3 -3
  69. package/web-app/dist/assets/{layers-Df7TdcWB.js → layers-CHXxU2on.js} +1 -1
  70. package/web-app/dist/assets/{loader-circle-kPsVfrSY.js → loader-circle-BEEz0LbD.js} +1 -1
  71. package/web-app/dist/assets/{lock-CXYcbEc5.js → lock-CzQGfcnE.js} +1 -1
  72. package/web-app/dist/assets/{package-Cqhua1ID.js → package-CcHYQLGd.js} +1 -1
  73. package/web-app/dist/assets/{plus-lseXhsn-.js → plus-DdzgFEXn.js} +1 -1
  74. package/web-app/dist/assets/{refresh-cw-OZriI6gD.js → refresh-cw-7x0HbH9Y.js} +1 -1
  75. package/web-app/dist/assets/{rotate-ccw-CKCfjPg8.js → rotate-ccw-DBahX1Sp.js} +1 -1
  76. package/web-app/dist/assets/{scroll-text-DoVyyfKb.js → scroll-text-BB8y8a9Z.js} +1 -1
  77. package/web-app/dist/assets/{server-DGy7TflW.js → server-CfkbW2MH.js} +1 -1
  78. package/web-app/dist/assets/{shield-alert-BCzRcMXi.js → shield-alert-DUgD8rBF.js} +1 -1
  79. package/web-app/dist/assets/{thumbs-up-C_znJd5g.js → thumbs-up-DGoyY6QC.js} +1 -1
  80. package/web-app/dist/assets/{trash-2-BosCbeeA.js → trash-2-B6xrSUMr.js} +1 -1
  81. package/web-app/dist/assets/{trending-down-CkR5zDkV.js → trending-down-B6RGKTOx.js} +1 -1
  82. package/web-app/dist/assets/{trending-up-DpUIfOH4.js → trending-up-B44DCrt1.js} +1 -1
  83. package/web-app/dist/assets/{upload-CH9aKc8L.js → upload-4kWm33ZY.js} +1 -1
  84. package/web-app/dist/assets/{usePolling-pzxrHJeg.js → usePolling-zkUtRQG6.js} +1 -1
  85. package/web-app/dist/assets/{user-B7OHa-VQ.js → user-04cEVIe2.js} +1 -1
  86. package/web-app/dist/index.html +1 -1
@@ -7674,6 +7674,74 @@ var init_mutation_proof = __esm(() => {
7674
7674
  DEP_DIRS = ["node_modules", ".venv", "venv"];
7675
7675
  });
7676
7676
 
7677
+ // ../../loki-ts/src/features/speed/unit_mode.ts
7678
+ import { closeSync as closeSync5, constants, fstatSync as fstatSync4, lstatSync as lstatSync5, openSync as openSync5, readSync as readSync4, realpathSync as realpathSync14 } from "fs";
7679
+ function readBounded(path) {
7680
+ const l = lstatSync5(path);
7681
+ if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
7682
+ return null;
7683
+ const fd = openSync5(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
7684
+ try {
7685
+ const st = fstatSync4(fd);
7686
+ if (!st.isFile() || st.size > MAX_SPEC_BYTES)
7687
+ return null;
7688
+ const buf = Buffer.alloc(st.size + 1);
7689
+ let n2 = 0;
7690
+ for (;; ) {
7691
+ const r = readSync4(fd, buf, n2, buf.length - n2, null);
7692
+ if (r <= 0)
7693
+ break;
7694
+ n2 += r;
7695
+ if (n2 >= buf.length)
7696
+ return null;
7697
+ }
7698
+ return buf.subarray(0, n2).toString("utf8");
7699
+ } finally {
7700
+ closeSync5(fd);
7701
+ }
7702
+ }
7703
+ function parseText(txt) {
7704
+ try {
7705
+ const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
7706
+ if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
7707
+ return null;
7708
+ const acc = j.acceptance === undefined ? [] : strs(j.acceptance);
7709
+ if (acc === null)
7710
+ return null;
7711
+ return { id: j.id, writeSet: ws, pack, tokenBudget: tb, ...acc.length ? { acceptance: acc.map((a) => a.slice(0, 500)).slice(0, 50) } : {} };
7712
+ } catch {
7713
+ return null;
7714
+ }
7715
+ }
7716
+ function parseSpec(path) {
7717
+ try {
7718
+ const txt = readBounded(path);
7719
+ return txt === null ? null : parseText(txt);
7720
+ } catch {
7721
+ return null;
7722
+ }
7723
+ }
7724
+ function unitSpec(env = process.env) {
7725
+ const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
7726
+ if (env["LOKI_SPEED"] === "0" || !path)
7727
+ return null;
7728
+ if (frozen !== undefined)
7729
+ return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
7730
+ if (!memo.has(path))
7731
+ memo.set(path, parseSpec(path));
7732
+ return memo.get(path) ?? null;
7733
+ }
7734
+ function unitFlags(staged, env = process.env, pre = {}) {
7735
+ const s = unitSpec(env);
7736
+ return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
7737
+ }
7738
+ var MAX_SPEC_BYTES, MAX_TOKEN_BUDGET = 1e9, norm2 = (p) => p.replace(/^\.\//, ""), CTRL, strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null, memo, inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
7739
+ var init_unit_mode = __esm(() => {
7740
+ MAX_SPEC_BYTES = 1024 * 1024;
7741
+ CTRL = /[\x00-\x1f\x7f]/;
7742
+ memo = new Map;
7743
+ });
7744
+
7677
7745
  // ../../loki-ts/src/features/lean_prefix.ts
7678
7746
  var LEAN_PREFIX, STAGE_PREFIX, withStagePrefix = (brief) => process.env["LOKI_SPEED"] !== "0" ? `${STAGE_PREFIX}
7679
7747
 
@@ -7683,10 +7751,216 @@ var init_lean_prefix = __esm(() => {
7683
7751
  STAGE_PREFIX = "You are Loki, an autonomous coding agent working one stage of a run. " + "Follow the stage prompt exactly, including the role it gives you and the rules it sets, " + "and report only what you did. " + "Stage-specific instructions and the task follow this block.";
7684
7752
  });
7685
7753
 
7686
- // ../../loki-ts/src/features/wall_manifest.ts
7754
+ // ../../loki-ts/src/util/shell.ts
7755
+ async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
7756
+ const reader = stream.getReader();
7757
+ const decoder = new TextDecoder;
7758
+ let out2 = "";
7759
+ let total = 0;
7760
+ try {
7761
+ while (total < maxBytes) {
7762
+ const { done, value } = await reader.read();
7763
+ if (done)
7764
+ break;
7765
+ if (!value)
7766
+ continue;
7767
+ total += value.byteLength;
7768
+ if (total > maxBytes) {
7769
+ const keep = value.byteLength - (total - maxBytes);
7770
+ out2 += decoder.decode(value.subarray(0, keep), { stream: true });
7771
+ break;
7772
+ }
7773
+ out2 += decoder.decode(value, { stream: true });
7774
+ }
7775
+ out2 += decoder.decode();
7776
+ } finally {
7777
+ try {
7778
+ await reader.cancel();
7779
+ } catch {}
7780
+ reader.releaseLock();
7781
+ }
7782
+ return out2;
7783
+ }
7784
+ async function run(argv, opts = {}) {
7785
+ const proc = Bun.spawn({
7786
+ cmd: [...argv],
7787
+ stdout: "pipe",
7788
+ stderr: "pipe",
7789
+ env: opts.env ? { ...process.env, ...opts.env } : process.env,
7790
+ cwd: opts.cwd
7791
+ });
7792
+ let timer;
7793
+ let killTimer;
7794
+ if (opts.timeoutMs && opts.timeoutMs > 0) {
7795
+ timer = setTimeout(() => {
7796
+ try {
7797
+ proc.kill("SIGTERM");
7798
+ } catch {}
7799
+ killTimer = setTimeout(() => {
7800
+ try {
7801
+ proc.kill("SIGKILL");
7802
+ } catch {}
7803
+ }, 2000);
7804
+ }, opts.timeoutMs);
7805
+ }
7806
+ try {
7807
+ const [stdout, stderr, exitCode] = await Promise.all([
7808
+ readStreamCapped(proc.stdout),
7809
+ new Response(proc.stderr).text(),
7810
+ proc.exited
7811
+ ]);
7812
+ return { stdout, stderr, exitCode };
7813
+ } finally {
7814
+ if (timer)
7815
+ clearTimeout(timer);
7816
+ if (killTimer)
7817
+ clearTimeout(killTimer);
7818
+ }
7819
+ }
7820
+ var MAX_STDOUT_BYTES;
7821
+ var init_shell = __esm(() => {
7822
+ MAX_STDOUT_BYTES = 16 * 1024 * 1024;
7823
+ });
7824
+
7825
+ // ../../loki-ts/src/util/python.ts
7826
+ import { accessSync, constants as constants2, existsSync as existsSync16, statSync as statSync12 } from "fs";
7687
7827
  import { spawnSync as spawnSync2 } from "child_process";
7688
- import { accessSync, constants, statSync as statSync12 } from "fs";
7689
7828
  import { tmpdir as tmpdir5 } from "os";
7829
+ function resolvePython3() {
7830
+ for (const dir of (process.env.PATH ?? "").split(":")) {
7831
+ if (!dir || !dir.startsWith("/"))
7832
+ continue;
7833
+ const cand = `${dir}/python3`;
7834
+ try {
7835
+ accessSync(cand, constants2.X_OK);
7836
+ if (statSync12(cand).isFile())
7837
+ return cand;
7838
+ } catch {}
7839
+ }
7840
+ return null;
7841
+ }
7842
+ function pySignatures(src) {
7843
+ try {
7844
+ const py = resolvePython3();
7845
+ if (!py)
7846
+ return [];
7847
+ const r = spawnSync2(py, ["-I", "-S", "-c", PY_SCRIPT], {
7848
+ input: src.replace(/\r\n?/g, `
7849
+ `),
7850
+ cwd: tmpdir5(),
7851
+ timeout: PY_TIMEOUT_MS,
7852
+ encoding: "utf8",
7853
+ env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
7854
+ maxBuffer: 8 * 1024 * 1024,
7855
+ stdio: ["pipe", "pipe", "ignore"]
7856
+ });
7857
+ if (r.error || r.status !== 0)
7858
+ return [];
7859
+ const parsed = JSON.parse(r.stdout);
7860
+ return Array.isArray(parsed) && parsed.every((l) => typeof l === "string") ? parsed : [];
7861
+ } catch {
7862
+ return [];
7863
+ }
7864
+ }
7865
+ var PY_TIMEOUT_MS = 5000, PY_SCRIPT = `
7866
+ import ast, sys, json, re
7867
+ def u(n): return ast.unparse(n)
7868
+ def chain(n):
7869
+ if isinstance(n, ast.Name): return n.id
7870
+ if isinstance(n, ast.Attribute):
7871
+ c = chain(n.value)
7872
+ return None if c is None else c + "." + n.attr
7873
+ return None
7874
+ def deco(d):
7875
+ if isinstance(d, ast.Call):
7876
+ c = chain(d.func)
7877
+ return "@" + c + "(...)" if c else "@..."
7878
+ c = chain(d)
7879
+ return "@" + c if c else "@..."
7880
+ def lit(n):
7881
+ if isinstance(n, ast.Constant): return u(n)
7882
+ if isinstance(n, ast.UnaryOp) and isinstance(n.op, ast.USub) and isinstance(n.operand, ast.Constant): return u(n)
7883
+ if isinstance(n, ast.Tuple):
7884
+ p = [lit(e) for e in n.elts]
7885
+ return None if None in p else ", ".join(p)
7886
+ return chain(n)
7887
+ def ann(n):
7888
+ if isinstance(n, (ast.Name, ast.Attribute)): return chain(n)
7889
+ if isinstance(n, ast.Constant): return u(n) if (n.value is None or n.value is Ellipsis) else None
7890
+ if isinstance(n, ast.BinOp) and isinstance(n.op, ast.BitOr):
7891
+ l, r = ann(n.left), ann(n.right)
7892
+ return None if l is None or r is None else l + " | " + r
7893
+ if isinstance(n, ast.List):
7894
+ p = [ann(e) for e in n.elts]
7895
+ return None if None in p else "[" + ", ".join(p) + "]"
7896
+ if isinstance(n, ast.Tuple):
7897
+ p = [ann(e) for e in n.elts]
7898
+ return None if None in p else ", ".join(p)
7899
+ if isinstance(n, ast.Subscript):
7900
+ v = chain(n.value)
7901
+ if v is None: return None
7902
+ last = v.split(".")[-1]
7903
+ if last == "Literal":
7904
+ s = lit(n.slice)
7905
+ return None if s is None else v + "[" + s + "]"
7906
+ if last == "Annotated" and isinstance(n.slice, ast.Tuple) and n.slice.elts:
7907
+ first = ann(n.slice.elts[0])
7908
+ return None if first is None else v + "[" + first + ", ...]"
7909
+ s = ann(n.slice)
7910
+ return None if s is None else v + "[" + s + "]"
7911
+ return None
7912
+ def a_or_dots(n):
7913
+ r = ann(n)
7914
+ return "..." if r is None else r
7915
+ def clean(n):
7916
+ r = ann(n)
7917
+ return "..." if r is None else r
7918
+ def arg(a, has_default):
7919
+ t = a.arg
7920
+ if a.annotation is not None: t += ": " + a_or_dots(a.annotation)
7921
+ if has_default: t += " = ..." if a.annotation is not None else "=..."
7922
+ return t
7923
+ def args(a):
7924
+ pos = list(a.posonlyargs) + list(a.args)
7925
+ first_default = len(pos) - len(a.defaults)
7926
+ out = []
7927
+ for i, x in enumerate(pos):
7928
+ out.append(arg(x, i >= first_default))
7929
+ if a.posonlyargs and i == len(a.posonlyargs) - 1: out.append("/")
7930
+ if a.vararg is not None: out.append("*" + arg(a.vararg, False))
7931
+ elif a.kwonlyargs: out.append("*")
7932
+ for x, d in zip(a.kwonlyargs, a.kw_defaults): out.append(arg(x, d is not None))
7933
+ if a.kwarg is not None: out.append("**" + arg(a.kwarg, False))
7934
+ return ", ".join(out)
7935
+ def fn(n, ind):
7936
+ r = [ind + deco(d) for d in n.decorator_list]
7937
+ h = ind + ("async def " if isinstance(n, ast.AsyncFunctionDef) else "def ") + n.name + "(" + args(n.args) + ")"
7938
+ if n.returns is not None: h += " -> " + a_or_dots(n.returns)
7939
+ return r + [h + ":"]
7940
+ def cls(n):
7941
+ r = [deco(d) for d in n.decorator_list]
7942
+ parts = [clean(b) for b in n.bases] + [(k.arg + "=" + clean(k.value)) if k.arg else "**..." for k in n.keywords]
7943
+ return r + ["class " + n.name + ("(" + ", ".join(parts) + ")" if parts else "") + ":"]
7944
+ tree = ast.parse(sys.stdin.read())
7945
+ out = []
7946
+ for n in tree.body:
7947
+ if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and not n.name.startswith("_"):
7948
+ out += fn(n, "")
7949
+ elif isinstance(n, ast.ClassDef) and not n.name.startswith("_"):
7950
+ out += cls(n)
7951
+ for m in n.body:
7952
+ if isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and (not m.name.startswith("_") or re.fullmatch(r"__\\w+__", m.name)):
7953
+ out += fn(m, " ")
7954
+ else:
7955
+ t = n.targets[0] if isinstance(n, ast.Assign) and len(n.targets) == 1 else (n.target if isinstance(n, ast.AnnAssign) else None)
7956
+ if isinstance(t, ast.Name) and re.fullmatch(r"[A-Z][A-Z0-9_]*", t.id): out.append(t.id + " = ...")
7957
+ print(json.dumps(out))
7958
+ `;
7959
+ var init_python = __esm(() => {
7960
+ init_shell();
7961
+ });
7962
+
7963
+ // ../../loki-ts/src/features/wall_manifest.ts
7690
7964
  function templateEnd(s, i) {
7691
7965
  for (let j = i + 1;j < s.length; ) {
7692
7966
  const c = s[j];
@@ -8412,41 +8686,6 @@ function tsSignatures(src, jsx) {
8412
8686
  }
8413
8687
  return out2;
8414
8688
  }
8415
- function resolvePython3() {
8416
- for (const dir of (process.env.PATH ?? "").split(":")) {
8417
- if (!dir || !dir.startsWith("/"))
8418
- continue;
8419
- const cand = `${dir}/python3`;
8420
- try {
8421
- accessSync(cand, constants.X_OK);
8422
- if (statSync12(cand).isFile())
8423
- return cand;
8424
- } catch {}
8425
- }
8426
- return null;
8427
- }
8428
- function pySignatures(src) {
8429
- try {
8430
- const py = resolvePython3();
8431
- if (!py)
8432
- return [];
8433
- const r = spawnSync2(py, ["-I", "-S", "-c", PY_SCRIPT], {
8434
- input: norm2(src),
8435
- cwd: tmpdir5(),
8436
- timeout: PY_TIMEOUT_MS,
8437
- encoding: "utf8",
8438
- env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
8439
- maxBuffer: 8 * 1024 * 1024,
8440
- stdio: ["pipe", "pipe", "ignore"]
8441
- });
8442
- if (r.error || r.status !== 0)
8443
- return [];
8444
- const parsed = JSON.parse(r.stdout);
8445
- return Array.isArray(parsed) && parsed.every((l) => typeof l === "string") ? parsed : [];
8446
- } catch {
8447
- return [];
8448
- }
8449
- }
8450
8689
  function packageRunner(content) {
8451
8690
  try {
8452
8691
  const pkg = JSON.parse(content);
@@ -8459,7 +8698,7 @@ function packageRunner(content) {
8459
8698
  }
8460
8699
  }
8461
8700
  function pytestSection(content, header) {
8462
- const lines = norm2(content).split(`
8701
+ const lines = norm3(content).split(`
8463
8702
  `);
8464
8703
  const at = lines.findIndex((l) => header.test(l));
8465
8704
  if (at < 0)
@@ -8476,10 +8715,10 @@ function runnerSection(files) {
8476
8715
  if (js)
8477
8716
  out2.push(`runner: ${js}`, "config: package.json (scripts.test only)");
8478
8717
  for (const f of get(/^(bunfig\.toml|(vitest|jest)\.config\.[cm]?[jt]s)$/))
8479
- out2.push(`config: ${f.path}`, ...norm2(f.content).split(`
8718
+ out2.push(`config: ${f.path}`, ...norm3(f.content).split(`
8480
8719
  `).slice(0, 20));
8481
8720
  const py = [
8482
- ...get(/^pytest\.ini$/).map((f) => ({ f, lines: norm2(f.content).split(`
8721
+ ...get(/^pytest\.ini$/).map((f) => ({ f, lines: norm3(f.content).split(`
8483
8722
  `).filter((l) => l.trim()) })),
8484
8723
  ...get(/^pyproject\.toml$/).map((f) => ({ f, lines: pytestSection(f.content, /^\[tool\.pytest/) })),
8485
8724
  ...get(/^(setup\.cfg|tox\.ini)$/).map((f) => ({ f, lines: pytestSection(f.content, /^\[(tool:)?pytest\]/) }))
@@ -8553,185 +8792,93 @@ function importsNamed(path, raw2, stems, importStems = stems, hits = () => false
8553
8792
  if (specs.some((s) => s.trim().startsWith(".") && hits(resolveRel(dir, s.trim()))))
8554
8793
  return true;
8555
8794
  for (const py of views)
8556
- for (const m of py.matchAll(/(?:^|:)[ \t\f]*from[ \t\f]+(\.+)([\w.]*?)[ \t\f]*\bimport\b[ \t\f]*(?:\(([^)]*)\)|([^\n]*))/gm)) {
8557
- let base = dir;
8558
- for (let i = 1;i < m[1].length; i++)
8559
- base = dirOf(base);
8560
- const r = resolveRel(base, (m[2] ?? "").replace(/\./g, "/"));
8561
- if (hits(r) || names(m[3] ?? m[4] ?? "").some((n2) => hits(resolveRel(r, n2))))
8562
- return true;
8563
- }
8564
- const lastSeg = (s) => normStem((s.trim().split(/[\\/]/).pop() ?? "").replace(/\.([cm]?[jt]sx?|py)$/i, "").replace(/^[#@~]+/, ""));
8565
- return specs.some((s) => importStems.has(lastSeg(s)) || s.trim().replace(/\.(ts|js|py)$/, "").split(/[./\\]/).some((seg) => importStems.has(normStem(seg))));
8566
- }
8567
- function safeTs(content, path) {
8568
- const jsx = /\.[jt]sx$/.test(path);
8569
- try {
8570
- return tsAmbiguous(norm2(content), jsx) ? [] : tsSignatures(content, jsx);
8571
- } catch {
8572
- return [];
8573
- }
8574
- }
8575
- function buildWallManifest(files, taskModules, alsoNamed = []) {
8576
- const all = [...files].sort(byPath).map((f) => ({ path: f.path.replace(/^\.\//, ""), content: norm2(f.content) }));
8577
- const mods = [...new Set(taskModules.map((m) => m.replace(/^\.\//, "")))].sort();
8578
- const excl = [...new Set([...mods, ...alsoNamed.map((m) => m.replace(/^\.\//, ""))])];
8579
- const stems = new Set(excl.flatMap(stemsOfTarget));
8580
- const tests = all.filter((f) => TEST_PATH.test(f.path));
8581
- const out2 = ["# wall manifest (D77): signatures only, from the base tree", "", "## runner", ...runnerSection(all)];
8582
- out2.push("", "## test layout", ...tests.slice(0, MAX_LAYOUT).map((f) => f.path));
8583
- if (tests.length > MAX_LAYOUT)
8584
- out2.push(`... ${tests.length - MAX_LAYOUT} more test files`);
8585
- for (const path of mods) {
8586
- const f = all.find((x) => x.path === path);
8587
- if (!f)
8588
- continue;
8589
- out2.push("", `## signatures: ${path}`, ...path.endsWith(".py") ? pySignatures(f.content) : safeTs(f.content, path));
8590
- }
8591
- out2.push("", "## style examples");
8592
- const named = new Set(excl.flatMap((m) => [baseOf(m).toLowerCase(), ...stemsOfTarget(m)]));
8593
- const importStems = new Set(excl.flatMap((m) => {
8594
- const s = stemsOfTarget(m);
8595
- return s.length > 1 ? s.slice(1) : s;
8596
- }));
8597
- const info = excl.map((m) => ({ noext: m.replace(/\.[^./]*$/, "").toLowerCase(), dir: dirOf(m).toLowerCase(), mod: !!dirOf(m) && ["index", "__init__", "mod", "main"].includes(stemOf(m).toLowerCase()), src: !TEST_FILE.test(m) }));
8598
- const hits = (r) => {
8599
- const low = r.toLowerCase().replace(/\.(?:[cm]?[jt]sx?|py)$/, "");
8600
- return [low, low.replace(/\/(?:index|__init__|mod|main)$/, "")].some((v) => info.some((t) => v === t.noext || t.mod && v === t.dir || v === "" || t.noext.startsWith(`${v}/`)));
8601
- };
8602
- const inTargetArea = (p) => {
8603
- const d = dirOf(p).toLowerCase();
8604
- return info.some((t) => t.src && (d === t.dir || t.mod && d.startsWith(`${t.dir}/`)));
8605
- };
8606
- const backstop = [...stems].filter((s) => !GENERIC_STEMS.includes(s));
8607
- const eligible = (t) => TEST_FILE.test(t.path) && EXAMPLE_EXT.test(t.path) && !named.has(baseOf(t.path).toLowerCase()) && !named.has(normStem(stemOf(t.path))) && !inTargetArea(t.path) && !importsNamed(t.path, t.content, stems, importStems, hits) && !mentionsStem(t.content, backstop);
8608
- for (const f of tests.filter(eligible).slice(0, MAX_EXAMPLES)) {
8609
- out2.push(`--- example: ${f.path}`, ...f.content.split(`
8610
- `).slice(0, EXAMPLE_LINES));
8611
- }
8612
- const lines = out2.join(`
8613
- `).split(`
8614
- `);
8615
- if (lines.length <= MANIFEST_MAX_LINES)
8616
- return lines.join(`
8617
- `);
8618
- const keep = MANIFEST_MAX_LINES - 1;
8619
- return [...lines.slice(0, keep), `... truncated: ${lines.length - keep} lines omitted`].join(`
8620
- `);
8621
- }
8622
- var MANIFEST_MAX_LINES = 400, MAX_EXAMPLES = 2, EXAMPLE_LINES = 40, MAX_LAYOUT = 60, TEST_PATH, TEST_FILE, EXAMPLE_EXT, baseOf = (p) => p.split("/").pop() ?? p, normStem = (s) => s.toLowerCase().replace(/[._-]+/g, ".").replace(/^\.|\.$/g, ""), GENERIC_STEMS, mentionsStem = (content, stems) => {
8623
- const c = content.toLowerCase().replace(/[._-]+/g, ".");
8624
- return stems.some((s) => new RegExp(`(?<![a-z0-9])${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![a-z0-9])`).test(c));
8625
- }, dirOf = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "", resolveRel = (dir, spec) => {
8626
- const out2 = dir ? dir.split("/") : [];
8627
- for (const seg of spec.split("/")) {
8628
- if (seg === "" || seg === ".")
8629
- continue;
8630
- if (seg === "..")
8631
- out2.pop();
8632
- else
8633
- out2.push(seg);
8634
- }
8635
- return out2.join("/");
8636
- }, stemsOfTarget = (m) => {
8637
- const raw2 = stemOf(m).toLowerCase(), parent = m.split("/").slice(-2, -1)[0];
8638
- return [normStem(raw2), ...["index", "__init__", "mod", "main"].includes(raw2) && parent ? [normStem(parent)] : []];
8639
- }, norm2 = (s) => s.replace(/\r\n?/g, `
8640
- `), stemOf = (p) => (p.split("/").pop() ?? p).replace(/\.[^.]*$/, ""), byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : a.content < b.content ? -1 : a.content > b.content ? 1 : 0, REGEX_PREV = "(,=:[!&|?{;+-*%~^", isComment = (s, i) => s[i] === "/" && (s[i + 1] === "/" || s[i + 1] === "*"), MODS = "(?:(?:public|private|protected|static|readonly|abstract|async|get|set|declare|override)\\s+)*", MEMBER_GRAMMAR, NOT_MEMBER, RESERVED_AFTER_DECORATOR, PY_TIMEOUT_MS = 5000, PY_SCRIPT = `
8641
- import ast, sys, json, re
8642
- def u(n): return ast.unparse(n)
8643
- def chain(n):
8644
- if isinstance(n, ast.Name): return n.id
8645
- if isinstance(n, ast.Attribute):
8646
- c = chain(n.value)
8647
- return None if c is None else c + "." + n.attr
8648
- return None
8649
- def deco(d):
8650
- if isinstance(d, ast.Call):
8651
- c = chain(d.func)
8652
- return "@" + c + "(...)" if c else "@..."
8653
- c = chain(d)
8654
- return "@" + c if c else "@..."
8655
- def lit(n):
8656
- if isinstance(n, ast.Constant): return u(n)
8657
- if isinstance(n, ast.UnaryOp) and isinstance(n.op, ast.USub) and isinstance(n.operand, ast.Constant): return u(n)
8658
- if isinstance(n, ast.Tuple):
8659
- p = [lit(e) for e in n.elts]
8660
- return None if None in p else ", ".join(p)
8661
- return chain(n)
8662
- def ann(n):
8663
- if isinstance(n, (ast.Name, ast.Attribute)): return chain(n)
8664
- if isinstance(n, ast.Constant): return u(n) if (n.value is None or n.value is Ellipsis) else None
8665
- if isinstance(n, ast.BinOp) and isinstance(n.op, ast.BitOr):
8666
- l, r = ann(n.left), ann(n.right)
8667
- return None if l is None or r is None else l + " | " + r
8668
- if isinstance(n, ast.List):
8669
- p = [ann(e) for e in n.elts]
8670
- return None if None in p else "[" + ", ".join(p) + "]"
8671
- if isinstance(n, ast.Tuple):
8672
- p = [ann(e) for e in n.elts]
8673
- return None if None in p else ", ".join(p)
8674
- if isinstance(n, ast.Subscript):
8675
- v = chain(n.value)
8676
- if v is None: return None
8677
- last = v.split(".")[-1]
8678
- if last == "Literal":
8679
- s = lit(n.slice)
8680
- return None if s is None else v + "[" + s + "]"
8681
- if last == "Annotated" and isinstance(n.slice, ast.Tuple) and n.slice.elts:
8682
- first = ann(n.slice.elts[0])
8683
- return None if first is None else v + "[" + first + ", ...]"
8684
- s = ann(n.slice)
8685
- return None if s is None else v + "[" + s + "]"
8686
- return None
8687
- def a_or_dots(n):
8688
- r = ann(n)
8689
- return "..." if r is None else r
8690
- def clean(n):
8691
- r = ann(n)
8692
- return "..." if r is None else r
8693
- def arg(a, has_default):
8694
- t = a.arg
8695
- if a.annotation is not None: t += ": " + a_or_dots(a.annotation)
8696
- if has_default: t += " = ..." if a.annotation is not None else "=..."
8697
- return t
8698
- def args(a):
8699
- pos = list(a.posonlyargs) + list(a.args)
8700
- first_default = len(pos) - len(a.defaults)
8701
- out = []
8702
- for i, x in enumerate(pos):
8703
- out.append(arg(x, i >= first_default))
8704
- if a.posonlyargs and i == len(a.posonlyargs) - 1: out.append("/")
8705
- if a.vararg is not None: out.append("*" + arg(a.vararg, False))
8706
- elif a.kwonlyargs: out.append("*")
8707
- for x, d in zip(a.kwonlyargs, a.kw_defaults): out.append(arg(x, d is not None))
8708
- if a.kwarg is not None: out.append("**" + arg(a.kwarg, False))
8709
- return ", ".join(out)
8710
- def fn(n, ind):
8711
- r = [ind + deco(d) for d in n.decorator_list]
8712
- h = ind + ("async def " if isinstance(n, ast.AsyncFunctionDef) else "def ") + n.name + "(" + args(n.args) + ")"
8713
- if n.returns is not None: h += " -> " + a_or_dots(n.returns)
8714
- return r + [h + ":"]
8715
- def cls(n):
8716
- r = [deco(d) for d in n.decorator_list]
8717
- parts = [clean(b) for b in n.bases] + [(k.arg + "=" + clean(k.value)) if k.arg else "**..." for k in n.keywords]
8718
- return r + ["class " + n.name + ("(" + ", ".join(parts) + ")" if parts else "") + ":"]
8719
- tree = ast.parse(sys.stdin.read())
8720
- out = []
8721
- for n in tree.body:
8722
- if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and not n.name.startswith("_"):
8723
- out += fn(n, "")
8724
- elif isinstance(n, ast.ClassDef) and not n.name.startswith("_"):
8725
- out += cls(n)
8726
- for m in n.body:
8727
- if isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and (not m.name.startswith("_") or re.fullmatch(r"__\\w+__", m.name)):
8728
- out += fn(m, " ")
8729
- else:
8730
- t = n.targets[0] if isinstance(n, ast.Assign) and len(n.targets) == 1 else (n.target if isinstance(n, ast.AnnAssign) else None)
8731
- if isinstance(t, ast.Name) and re.fullmatch(r"[A-Z][A-Z0-9_]*", t.id): out.append(t.id + " = ...")
8732
- print(json.dumps(out))
8733
- `;
8795
+ for (const m of py.matchAll(/(?:^|:)[ \t\f]*from[ \t\f]+(\.+)([\w.]*?)[ \t\f]*\bimport\b[ \t\f]*(?:\(([^)]*)\)|([^\n]*))/gm)) {
8796
+ let base = dir;
8797
+ for (let i = 1;i < m[1].length; i++)
8798
+ base = dirOf(base);
8799
+ const r = resolveRel(base, (m[2] ?? "").replace(/\./g, "/"));
8800
+ if (hits(r) || names(m[3] ?? m[4] ?? "").some((n2) => hits(resolveRel(r, n2))))
8801
+ return true;
8802
+ }
8803
+ const lastSeg = (s) => normStem((s.trim().split(/[\\/]/).pop() ?? "").replace(/\.([cm]?[jt]sx?|py)$/i, "").replace(/^[#@~]+/, ""));
8804
+ return specs.some((s) => importStems.has(lastSeg(s)) || s.trim().replace(/\.(ts|js|py)$/, "").split(/[./\\]/).some((seg) => importStems.has(normStem(seg))));
8805
+ }
8806
+ function safeTs(content, path) {
8807
+ const jsx = /\.[jt]sx$/.test(path);
8808
+ try {
8809
+ return tsAmbiguous(norm3(content), jsx) ? [] : tsSignatures(content, jsx);
8810
+ } catch {
8811
+ return [];
8812
+ }
8813
+ }
8814
+ function buildWallManifest(files, taskModules, alsoNamed = []) {
8815
+ const all = [...files].sort(byPath).map((f) => ({ path: f.path.replace(/^\.\//, ""), content: norm3(f.content) }));
8816
+ const mods = [...new Set(taskModules.map((m) => m.replace(/^\.\//, "")))].sort();
8817
+ const excl = [...new Set([...mods, ...alsoNamed.map((m) => m.replace(/^\.\//, ""))])];
8818
+ const stems = new Set(excl.flatMap(stemsOfTarget));
8819
+ const tests = all.filter((f) => TEST_PATH.test(f.path));
8820
+ const out2 = ["# wall manifest (D77): signatures only, from the base tree", "", "## runner", ...runnerSection(all)];
8821
+ out2.push("", "## test layout", ...tests.slice(0, MAX_LAYOUT).map((f) => f.path));
8822
+ if (tests.length > MAX_LAYOUT)
8823
+ out2.push(`... ${tests.length - MAX_LAYOUT} more test files`);
8824
+ for (const path of mods) {
8825
+ const f = all.find((x) => x.path === path);
8826
+ if (!f)
8827
+ continue;
8828
+ out2.push("", `## signatures: ${path}`, ...path.endsWith(".py") ? pySignatures(f.content) : safeTs(f.content, path));
8829
+ }
8830
+ out2.push("", "## style examples");
8831
+ const named = new Set(excl.flatMap((m) => [baseOf(m).toLowerCase(), ...stemsOfTarget(m)]));
8832
+ const importStems = new Set(excl.flatMap((m) => {
8833
+ const s = stemsOfTarget(m);
8834
+ return s.length > 1 ? s.slice(1) : s;
8835
+ }));
8836
+ const info = excl.map((m) => ({ noext: m.replace(/\.[^./]*$/, "").toLowerCase(), dir: dirOf(m).toLowerCase(), mod: !!dirOf(m) && ["index", "__init__", "mod", "main"].includes(stemOf(m).toLowerCase()), src: !TEST_FILE.test(m) }));
8837
+ const hits = (r) => {
8838
+ const low = r.toLowerCase().replace(/\.(?:[cm]?[jt]sx?|py)$/, "");
8839
+ return [low, low.replace(/\/(?:index|__init__|mod|main)$/, "")].some((v) => info.some((t) => v === t.noext || t.mod && v === t.dir || v === "" || t.noext.startsWith(`${v}/`)));
8840
+ };
8841
+ const inTargetArea = (p) => {
8842
+ const d = dirOf(p).toLowerCase();
8843
+ return info.some((t) => t.src && (d === t.dir || t.mod && d.startsWith(`${t.dir}/`)));
8844
+ };
8845
+ const backstop = [...stems].filter((s) => !GENERIC_STEMS.includes(s));
8846
+ const eligible = (t) => TEST_FILE.test(t.path) && EXAMPLE_EXT.test(t.path) && !named.has(baseOf(t.path).toLowerCase()) && !named.has(normStem(stemOf(t.path))) && !inTargetArea(t.path) && !importsNamed(t.path, t.content, stems, importStems, hits) && !mentionsStem(t.content, backstop);
8847
+ for (const f of tests.filter(eligible).slice(0, MAX_EXAMPLES)) {
8848
+ out2.push(`--- example: ${f.path}`, ...f.content.split(`
8849
+ `).slice(0, EXAMPLE_LINES));
8850
+ }
8851
+ const lines = out2.join(`
8852
+ `).split(`
8853
+ `);
8854
+ if (lines.length <= MANIFEST_MAX_LINES)
8855
+ return lines.join(`
8856
+ `);
8857
+ const keep = MANIFEST_MAX_LINES - 1;
8858
+ return [...lines.slice(0, keep), `... truncated: ${lines.length - keep} lines omitted`].join(`
8859
+ `);
8860
+ }
8861
+ var MANIFEST_MAX_LINES = 400, MAX_EXAMPLES = 2, EXAMPLE_LINES = 40, MAX_LAYOUT = 60, TEST_PATH, TEST_FILE, EXAMPLE_EXT, baseOf = (p) => p.split("/").pop() ?? p, normStem = (s) => s.toLowerCase().replace(/[._-]+/g, ".").replace(/^\.|\.$/g, ""), GENERIC_STEMS, mentionsStem = (content, stems) => {
8862
+ const c = content.toLowerCase().replace(/[._-]+/g, ".");
8863
+ return stems.some((s) => new RegExp(`(?<![a-z0-9])${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![a-z0-9])`).test(c));
8864
+ }, dirOf = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "", resolveRel = (dir, spec) => {
8865
+ const out2 = dir ? dir.split("/") : [];
8866
+ for (const seg of spec.split("/")) {
8867
+ if (seg === "" || seg === ".")
8868
+ continue;
8869
+ if (seg === "..")
8870
+ out2.pop();
8871
+ else
8872
+ out2.push(seg);
8873
+ }
8874
+ return out2.join("/");
8875
+ }, stemsOfTarget = (m) => {
8876
+ const raw2 = stemOf(m).toLowerCase(), parent = m.split("/").slice(-2, -1)[0];
8877
+ return [normStem(raw2), ...["index", "__init__", "mod", "main"].includes(raw2) && parent ? [normStem(parent)] : []];
8878
+ }, norm3 = (s) => s.replace(/\r\n?/g, `
8879
+ `), stemOf = (p) => (p.split("/").pop() ?? p).replace(/\.[^.]*$/, ""), byPath = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : a.content < b.content ? -1 : a.content > b.content ? 1 : 0, REGEX_PREV = "(,=:[!&|?{;+-*%~^", isComment = (s, i) => s[i] === "/" && (s[i + 1] === "/" || s[i + 1] === "*"), MODS = "(?:(?:public|private|protected|static|readonly|abstract|async|get|set|declare|override)\\s+)*", MEMBER_GRAMMAR, NOT_MEMBER, RESERVED_AFTER_DECORATOR;
8734
8880
  var init_wall_manifest = __esm(() => {
8881
+ init_python();
8735
8882
  TEST_PATH = /(^|\/)(tests?|__tests__|spec)\/|\.(test|spec)\.[jt]sx?$|(^|\/)test_[^/]*\.py$|_test\.(py|go)$/;
8736
8883
  TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)test_[^/]*\.py$|_test\.py$/;
8737
8884
  EXAMPLE_EXT = /\.(py|[cm]?[jt]sx?)$/;
@@ -8835,7 +8982,7 @@ Example: test('name', () => { assert.strictEqual(fn(1), 2); }); Run: node --tes
8835
8982
 
8836
8983
  // ../../loki-ts/src/engine10/cache.ts
8837
8984
  import { createHash as createHash7 } from "crypto";
8838
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
8985
+ import { existsSync as existsSync17, mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
8839
8986
  import { resolve as resolve8 } from "path";
8840
8987
  function repoKey(originUrl, repoDir) {
8841
8988
  const basis = originUrl && originUrl.trim() !== "" ? originUrl : resolve8(repoDir);
@@ -8848,7 +8995,7 @@ function repoCacheDir(key, cacheRoot = defaultCacheRoot()) {
8848
8995
  return resolve8(cacheRoot, key);
8849
8996
  }
8850
8997
  function readJson3(path, isValid = (_v) => true) {
8851
- if (!existsSync16(path))
8998
+ if (!existsSync17(path))
8852
8999
  return null;
8853
9000
  try {
8854
9001
  const parsed = JSON.parse(readFileSync19(path, "utf8"));
@@ -8966,7 +9113,7 @@ function routerMode(env = process.env) {
8966
9113
  var routerEnabled = (env = process.env) => routerMode(env) === "on";
8967
9114
 
8968
9115
  // ../../loki-ts/src/engine10/cost.ts
8969
- import { mkdirSync as mkdirSync7, readdirSync as readdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
9116
+ import { readdirSync as readdirSync9, readFileSync as readFileSync21 } from "fs";
8970
9117
  import { join as join29 } from "path";
8971
9118
  function routerTelemetryOn(env = process.env) {
8972
9119
  return routerEnabled(env);
@@ -9110,17 +9257,19 @@ function sumResultCosts(lokiRoot, iterations, context = []) {
9110
9257
  var UNMETERED = "cli-invoker-unmetered";
9111
9258
  var init_cost = __esm(() => {
9112
9259
  init_hooks();
9260
+ init_provider_cost();
9261
+ init_provider_cost();
9113
9262
  });
9114
9263
 
9115
9264
  // ../../loki-ts/src/util/atomic.ts
9116
9265
  import {
9117
- closeSync as closeSync5,
9118
- fstatSync as fstatSync4,
9266
+ closeSync as closeSync6,
9267
+ fstatSync as fstatSync5,
9119
9268
  fsyncSync as fsyncSync2,
9120
- lstatSync as lstatSync5,
9269
+ lstatSync as lstatSync6,
9121
9270
  mkdirSync as mkdirSync8,
9122
- openSync as openSync5,
9123
- readSync as readSync4,
9271
+ openSync as openSync6,
9272
+ readSync as readSync5,
9124
9273
  renameSync as renameSync4,
9125
9274
  rmSync as rmSync6,
9126
9275
  statSync as statSync13,
@@ -9132,12 +9281,12 @@ import { dirname as dirname9 } from "path";
9132
9281
  function durableWriteThenRename(target, body) {
9133
9282
  mkdirSync8(dirname9(target), { recursive: true });
9134
9283
  const tmp = tmpPathFor(target);
9135
- const fd = openSync5(tmp, "w");
9284
+ const fd = openSync6(tmp, "w");
9136
9285
  try {
9137
9286
  writeSync3(fd, body);
9138
9287
  fsyncSync2(fd);
9139
9288
  } finally {
9140
- closeSync5(fd);
9289
+ closeSync6(fd);
9141
9290
  }
9142
9291
  renameSync4(tmp, target);
9143
9292
  fsyncParentDir(target);
@@ -9145,12 +9294,12 @@ function durableWriteThenRename(target, body) {
9145
9294
  function fsyncParentDir(target) {
9146
9295
  let dfd = null;
9147
9296
  try {
9148
- dfd = openSync5(dirname9(target), "r");
9297
+ dfd = openSync6(dirname9(target), "r");
9149
9298
  fsyncSync2(dfd);
9150
9299
  } catch {} finally {
9151
9300
  if (dfd !== null) {
9152
9301
  try {
9153
- closeSync5(dfd);
9302
+ closeSync6(dfd);
9154
9303
  } catch {}
9155
9304
  }
9156
9305
  }
@@ -9168,7 +9317,7 @@ var init_atomic = __esm(() => {
9168
9317
  });
9169
9318
 
9170
9319
  // ../../loki-ts/src/runner/budget.ts
9171
- import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
9320
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
9172
9321
  import { dirname as dirname10, join as join30, resolve as resolve10 } from "path";
9173
9322
  import { fileURLToPath as fileURLToPath2 } from "url";
9174
9323
  function loadDefaultMaxCostUsd() {
@@ -9186,7 +9335,7 @@ function _loadPricing() {
9186
9335
  try {
9187
9336
  const moduleDir = dirname10(fileURLToPath2(import.meta.url));
9188
9337
  const jsonPath = resolve10(moduleDir, "..", "..", "data", "model-pricing.json");
9189
- if (!existsSync17(jsonPath))
9338
+ if (!existsSync18(jsonPath))
9190
9339
  return _FALLBACK_PRICING;
9191
9340
  const raw2 = JSON.parse(readFileSync22(jsonPath, "utf8"));
9192
9341
  const pricing2 = raw2["pricing"];
@@ -9318,7 +9467,7 @@ var init_model_rank = __esm(() => {
9318
9467
  });
9319
9468
 
9320
9469
  // ../../loki-ts/src/engine10/sizing.ts
9321
- import { existsSync as existsSync18, readFileSync as readFileSync23 } from "fs";
9470
+ import { existsSync as existsSync19, readFileSync as readFileSync23 } from "fs";
9322
9471
  import { basename as basename10 } from "path";
9323
9472
  function namedFiles(task, map) {
9324
9473
  return (map?.files ?? []).filter((f) => task.toLowerCase().includes(basename10(f).toLowerCase()));
@@ -9337,7 +9486,7 @@ function sizeTask(task, map, tests) {
9337
9486
  return why.length ? { size: "normal", reasons: why } : { size: "small", reasons: [`task ${task.length} chars, names ${named} files`] };
9338
9487
  }
9339
9488
  function loadRepoMap(repomapRef) {
9340
- if (!repomapRef || !existsSync18(repomapRef))
9489
+ if (!repomapRef || !existsSync19(repomapRef))
9341
9490
  return null;
9342
9491
  try {
9343
9492
  return JSON.parse(readFileSync23(repomapRef, "utf8"));
@@ -9371,7 +9520,7 @@ var init_sizing = __esm(() => {
9371
9520
  });
9372
9521
 
9373
9522
  // ../../loki-ts/src/project_model/schema.ts
9374
- import { existsSync as existsSync19, lstatSync as lstatSync6, realpathSync as realpathSync14, statSync as statSync14 } from "fs";
9523
+ import { existsSync as existsSync20, lstatSync as lstatSync7, realpathSync as realpathSync15, statSync as statSync14 } from "fs";
9375
9524
  import { isAbsolute as isAbsolute3, join as join32, normalize, sep as sep9 } from "path";
9376
9525
  function safeRel(p) {
9377
9526
  const n2 = normalize(p.trim()).split(sep9).join("/").replace(/\/$/, "");
@@ -9381,8 +9530,8 @@ function safeRel(p) {
9381
9530
  }
9382
9531
  function insideRepo(repoDir, rel) {
9383
9532
  try {
9384
- const real2 = realpathSync14(join32(repoDir, rel));
9385
- const base = realpathSync14(repoDir);
9533
+ const real2 = realpathSync15(join32(repoDir, rel));
9534
+ const base = realpathSync15(repoDir);
9386
9535
  return real2 === base || real2.startsWith(base + sep9);
9387
9536
  } catch {
9388
9537
  return false;
@@ -9395,7 +9544,7 @@ function checkCitation(repoDir, raw2) {
9395
9544
  if (rel === null)
9396
9545
  return `citation "${raw2}" is not a repo-relative path`;
9397
9546
  const abs = join32(repoDir, rel);
9398
- if (!existsSync19(abs) || !statSync14(abs).isFile() || !insideRepo(repoDir, rel))
9547
+ if (!existsSync20(abs) || !statSync14(abs).isFile() || !insideRepo(repoDir, rel))
9399
9548
  return `citation "${raw2}" names a file that does not exist`;
9400
9549
  return null;
9401
9550
  }
@@ -9406,7 +9555,7 @@ function checkFingerprint(repoDir, raw2) {
9406
9555
  if (rel === null)
9407
9556
  return `fingerprint "${raw2}" is not a repo-relative path`;
9408
9557
  try {
9409
- if (!lstatSync6(join32(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
9558
+ if (!lstatSync7(join32(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
9410
9559
  return `fingerprint "${raw2}" is not a regular file inside the repo`;
9411
9560
  } catch {
9412
9561
  return `fingerprint "${raw2}" names a file that does not exist`;
@@ -9435,7 +9584,7 @@ function checkDir(repoDir, v, where, errs) {
9435
9584
  return ".";
9436
9585
  }
9437
9586
  const abs = join32(repoDir, rel);
9438
- if (!existsSync19(abs) || !statSync14(abs).isDirectory() || !insideRepo(repoDir, rel))
9587
+ if (!existsSync20(abs) || !statSync14(abs).isDirectory() || !insideRepo(repoDir, rel))
9439
9588
  errs.push(`${where}: directory "${rel}" does not exist`);
9440
9589
  return rel;
9441
9590
  }
@@ -9538,7 +9687,7 @@ var init_schema = __esm(() => {
9538
9687
 
9539
9688
  // ../../loki-ts/src/project_model/gather.ts
9540
9689
  import { createHash as createHash8 } from "crypto";
9541
- import { closeSync as closeSync6, lstatSync as lstatSync7, openSync as openSync6, readFileSync as readFileSync24, readSync as readSync5 } from "fs";
9690
+ import { closeSync as closeSync7, lstatSync as lstatSync8, openSync as openSync7, readFileSync as readFileSync24, readSync as readSync6 } from "fs";
9542
9691
  import { dirname as dirname11, join as join33 } from "path";
9543
9692
  function listShallow(repoDir) {
9544
9693
  try {
@@ -9566,7 +9715,7 @@ function computeKey(repoDir, fingerprintFiles, dirs, committedHash = null) {
9566
9715
  h.update("invalid");
9567
9716
  else {
9568
9717
  const abs = join33(repoDir, f);
9569
- const size = lstatSync7(abs).size;
9718
+ const size = lstatSync8(abs).size;
9570
9719
  h.update(size <= GATHER_CAPS.maxHashBytes ? readFileSync24(abs) : `too-large:${size}`);
9571
9720
  }
9572
9721
  } catch {
@@ -9610,7 +9759,7 @@ var init_gather = __esm(() => {
9610
9759
  });
9611
9760
 
9612
9761
  // ../../loki-ts/src/project_model/discover.ts
9613
- import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync25, rmSync as rmSync7, writeFileSync as writeFileSync13 } from "fs";
9762
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync25, rmSync as rmSync7, writeFileSync as writeFileSync13 } from "fs";
9614
9763
  import { join as join34 } from "path";
9615
9764
  function projectModelEnabled(env = process.env) {
9616
9765
  return env["LOKI_E10_PROJECT_MODEL"] !== "0";
@@ -10324,10 +10473,10 @@ function classifyRisk(files, env = process.env) {
10324
10473
  };
10325
10474
  }
10326
10475
  function changedFiles(repoDir, baseSha) {
10327
- const run = (args) => safeGit(repoDir, args).split(`
10476
+ const run2 = (args) => safeGit(repoDir, args).split(`
10328
10477
  `).map((l) => l.trim()).filter(Boolean);
10329
- const tracked = run(["diff", "--name-only", baseSha]);
10330
- const untracked = run(["ls-files", "--others", "--exclude-standard"]);
10478
+ const tracked = run2(["diff", "--name-only", baseSha]);
10479
+ const untracked = run2(["ls-files", "--others", "--exclude-standard"]);
10331
10480
  return [...new Set([...tracked, ...untracked])].filter((f) => !f.startsWith(".loki/"));
10332
10481
  }
10333
10482
  function diffFiles(repoDir, baseSha) {
@@ -10401,7 +10550,7 @@ var init_check_result = __esm(() => {
10401
10550
 
10402
10551
  // ../../loki-ts/src/runner/load_owner.ts
10403
10552
  import { spawnSync as spawnSync3 } from "child_process";
10404
- import { mkdtempSync as mkdtempSync5, realpathSync as realpathSync15, rmSync as rmSync9 } from "fs";
10553
+ import { mkdtempSync as mkdtempSync5, realpathSync as realpathSync16, rmSync as rmSync9 } from "fs";
10405
10554
  import { tmpdir as tmpdir7 } from "os";
10406
10555
  import { basename as basename14, extname as extname2, join as join39 } from "path";
10407
10556
  function changedFilesNamed(out2, repoDir, changed) {
@@ -10423,7 +10572,7 @@ function loadSignature(out2, roots) {
10423
10572
  let o = out2;
10424
10573
  for (const r of roots.flatMap((x) => {
10425
10574
  try {
10426
- return [x, realpathSync15(x)];
10575
+ return [x, realpathSync16(x)];
10427
10576
  } catch {
10428
10577
  return [x];
10429
10578
  }
@@ -10610,7 +10759,7 @@ var init_state = __esm(() => {
10610
10759
  });
10611
10760
 
10612
10761
  // ../../loki-ts/src/runner/repo_profile.ts
10613
- import { existsSync as existsSync21, readFileSync as readFileSync28, mkdirSync as mkdirSync12, readdirSync as readdirSync11, renameSync as renameSync6, rmSync as rmSync10, writeFileSync as writeFileSync15 } from "fs";
10762
+ import { existsSync as existsSync22, readFileSync as readFileSync28, mkdirSync as mkdirSync12, readdirSync as readdirSync11, renameSync as renameSync6, rmSync as rmSync10, writeFileSync as writeFileSync15 } from "fs";
10614
10763
  import { basename as basename15, join as join41, resolve as resolve11, isAbsolute as isAbsolute5 } from "path";
10615
10764
  function loadProfile(repoDir) {
10616
10765
  try {
@@ -10637,10 +10786,10 @@ function attribute(repoDir, baseSha, failing) {
10637
10786
  }
10638
10787
  function attributeFullSuite(repoDir, baseSha, full, blocked, changed) {
10639
10788
  const bad = full.filter((c) => c.result === "fail"), none = { preExistingChecks: [], preExistingIds: [], introducedChecks: bad.map((c) => c.name), line: null };
10640
- const norm3 = (s) => s.replace(/\s+--silent\b/, "").trim(), p = loadProfile(repoDir);
10789
+ const norm4 = (s) => s.replace(/\s+--silent\b/, "").trim(), p = loadProfile(repoDir);
10641
10790
  if (!bad.length || !p || p.revision !== baseSha || !p.baseline.attributable || !p.baseline.failing_ids.length)
10642
10791
  return none;
10643
- if (bad.some((c) => norm3(c.cmd) !== norm3(p.baseline.command ?? "")))
10792
+ if (bad.some((c) => norm4(c.cmd) !== norm4(p.baseline.command ?? "")))
10644
10793
  return { ...none, line: `baseline: ran "${p.baseline.command}", not the full suite command, ids not comparable, full suite failures not excused` };
10645
10794
  const block = new Set(blocked);
10646
10795
  return attribute(repoDir, baseSha, bad.map((c) => ({ name: c.name, ...c.ids ? { ids: c.ids } : {}, eligible: (c.ids ?? []).every((i) => !block.has(i) && !changed.some((f) => i.includes(f))) })));
@@ -10657,7 +10806,7 @@ var init_repo_profile = __esm(() => {
10657
10806
  // ../../loki-ts/src/project_graph.ts
10658
10807
  import { createHash as createHash9 } from "crypto";
10659
10808
  import {
10660
- existsSync as existsSync22,
10809
+ existsSync as existsSync23,
10661
10810
  readdirSync as readdirSync12,
10662
10811
  readFileSync as readFileSync29,
10663
10812
  statSync as statSync15,
@@ -10666,7 +10815,7 @@ import {
10666
10815
  } from "fs";
10667
10816
  import { basename as basename16, dirname as dirname12, join as join42, resolve as resolve12 } from "path";
10668
10817
  function readManifest(path) {
10669
- if (!existsSync22(path)) {
10818
+ if (!existsSync23(path)) {
10670
10819
  return null;
10671
10820
  }
10672
10821
  let raw2;
@@ -10751,7 +10900,7 @@ function resolveMembers(rootDir, declared, selfBasename) {
10751
10900
  }
10752
10901
  if (!st.isDirectory())
10753
10902
  continue;
10754
- if (!existsSync22(join42(full, ".loki", "app.json")))
10903
+ if (!existsSync23(join42(full, ".loki", "app.json")))
10755
10904
  continue;
10756
10905
  seen.add(full);
10757
10906
  out2.push(full);
@@ -10786,12 +10935,12 @@ function discoverProjectGraph(targetDir) {
10786
10935
  const examined = [];
10787
10936
  let chosenManifest = null;
10788
10937
  let chosenRoot = "";
10789
- if (existsSync22(parentManifest) && parentDir !== absTarget) {
10938
+ if (existsSync23(parentManifest) && parentDir !== absTarget) {
10790
10939
  examined.push(parentManifest);
10791
10940
  chosenManifest = readManifest(parentManifest);
10792
10941
  chosenRoot = parentDir;
10793
10942
  }
10794
- if (!chosenManifest && existsSync22(targetManifest)) {
10943
+ if (!chosenManifest && existsSync23(targetManifest)) {
10795
10944
  examined.push(targetManifest);
10796
10945
  chosenManifest = readManifest(targetManifest);
10797
10946
  chosenRoot = absTarget;
@@ -10803,7 +10952,7 @@ function discoverProjectGraph(targetDir) {
10803
10952
  const filtered = [];
10804
10953
  for (const m of members) {
10805
10954
  const sibManifestPath = join42(m, ".loki", "app.json");
10806
- if (existsSync22(sibManifestPath)) {
10955
+ if (existsSync23(sibManifestPath)) {
10807
10956
  examined.push(sibManifestPath);
10808
10957
  const sibManifest = readManifest(sibManifestPath);
10809
10958
  if (sibManifest && sibManifest.app_id !== chosenManifest.app_id) {
@@ -10826,77 +10975,6 @@ var init_project_graph = __esm(() => {
10826
10975
  APP_ID_REGEX = /^[a-z0-9-]{3,40}$/;
10827
10976
  });
10828
10977
 
10829
- // ../../loki-ts/src/util/shell.ts
10830
- async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
10831
- const reader = stream.getReader();
10832
- const decoder = new TextDecoder;
10833
- let out2 = "";
10834
- let total = 0;
10835
- try {
10836
- while (total < maxBytes) {
10837
- const { done, value } = await reader.read();
10838
- if (done)
10839
- break;
10840
- if (!value)
10841
- continue;
10842
- total += value.byteLength;
10843
- if (total > maxBytes) {
10844
- const keep = value.byteLength - (total - maxBytes);
10845
- out2 += decoder.decode(value.subarray(0, keep), { stream: true });
10846
- break;
10847
- }
10848
- out2 += decoder.decode(value, { stream: true });
10849
- }
10850
- out2 += decoder.decode();
10851
- } finally {
10852
- try {
10853
- await reader.cancel();
10854
- } catch {}
10855
- reader.releaseLock();
10856
- }
10857
- return out2;
10858
- }
10859
- async function run(argv, opts = {}) {
10860
- const proc = Bun.spawn({
10861
- cmd: [...argv],
10862
- stdout: "pipe",
10863
- stderr: "pipe",
10864
- env: opts.env ? { ...process.env, ...opts.env } : process.env,
10865
- cwd: opts.cwd
10866
- });
10867
- let timer;
10868
- let killTimer;
10869
- if (opts.timeoutMs && opts.timeoutMs > 0) {
10870
- timer = setTimeout(() => {
10871
- try {
10872
- proc.kill("SIGTERM");
10873
- } catch {}
10874
- killTimer = setTimeout(() => {
10875
- try {
10876
- proc.kill("SIGKILL");
10877
- } catch {}
10878
- }, 2000);
10879
- }, opts.timeoutMs);
10880
- }
10881
- try {
10882
- const [stdout, stderr, exitCode] = await Promise.all([
10883
- readStreamCapped(proc.stdout),
10884
- new Response(proc.stderr).text(),
10885
- proc.exited
10886
- ]);
10887
- return { stdout, stderr, exitCode };
10888
- } finally {
10889
- if (timer)
10890
- clearTimeout(timer);
10891
- if (killTimer)
10892
- clearTimeout(killTimer);
10893
- }
10894
- }
10895
- var MAX_STDOUT_BYTES;
10896
- var init_shell = __esm(() => {
10897
- MAX_STDOUT_BYTES = 16 * 1024 * 1024;
10898
- });
10899
-
10900
10978
  // ../../loki-ts/src/e10ext/budget_cap.ts
10901
10979
  var DEFAULT_MAX_COST_USD;
10902
10980
  var init_budget_cap = __esm(() => {
@@ -10908,71 +10986,6 @@ var init_budget_cap = __esm(() => {
10908
10986
  // ../../loki-ts/src/runner/retry_class.ts
10909
10987
  var init_retry_class = () => {};
10910
10988
 
10911
- // ../../loki-ts/src/features/speed/unit_mode.ts
10912
- import { closeSync as closeSync7, constants as constants2, fstatSync as fstatSync5, lstatSync as lstatSync8, openSync as openSync7, readSync as readSync6, realpathSync as realpathSync16 } from "fs";
10913
- function readBounded(path) {
10914
- const l = lstatSync8(path);
10915
- if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
10916
- return null;
10917
- const fd = openSync7(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
10918
- try {
10919
- const st = fstatSync5(fd);
10920
- if (!st.isFile() || st.size > MAX_SPEC_BYTES)
10921
- return null;
10922
- const buf = Buffer.alloc(st.size + 1);
10923
- let n2 = 0;
10924
- for (;; ) {
10925
- const r = readSync6(fd, buf, n2, buf.length - n2, null);
10926
- if (r <= 0)
10927
- break;
10928
- n2 += r;
10929
- if (n2 >= buf.length)
10930
- return null;
10931
- }
10932
- return buf.subarray(0, n2).toString("utf8");
10933
- } finally {
10934
- closeSync7(fd);
10935
- }
10936
- }
10937
- function parseText(txt) {
10938
- try {
10939
- const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
10940
- if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
10941
- return null;
10942
- return { id: j.id, writeSet: ws, pack, tokenBudget: tb };
10943
- } catch {
10944
- return null;
10945
- }
10946
- }
10947
- function parseSpec(path) {
10948
- try {
10949
- const txt = readBounded(path);
10950
- return txt === null ? null : parseText(txt);
10951
- } catch {
10952
- return null;
10953
- }
10954
- }
10955
- function unitSpec(env = process.env) {
10956
- const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
10957
- if (env["LOKI_SPEED"] === "0" || !path)
10958
- return null;
10959
- if (frozen !== undefined)
10960
- return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
10961
- if (!memo.has(path))
10962
- memo.set(path, parseSpec(path));
10963
- return memo.get(path) ?? null;
10964
- }
10965
- function unitFlags(staged, env = process.env, pre = {}) {
10966
- const s = unitSpec(env);
10967
- return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
10968
- }
10969
- var MAX_SPEC_BYTES, MAX_TOKEN_BUDGET = 1e9, norm3 = (p) => p.replace(/^\.\//, ""), CTRL, strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm3) : null, memo, inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
10970
- var init_unit_mode = __esm(() => {
10971
- MAX_SPEC_BYTES = 1024 * 1024;
10972
- CTRL = /[\x00-\x1f\x7f]/;
10973
- memo = new Map;
10974
- });
10975
-
10976
10989
  // ../../loki-ts/src/e10ext/repomemory.ts
10977
10990
  var init_repomemory = __esm(() => {
10978
10991
  init_cache();
@@ -11011,11 +11024,12 @@ var init_steer = __esm(() => {
11011
11024
  });
11012
11025
 
11013
11026
  // ../../loki-ts/src/util/pr_lessons.ts
11014
- import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync30 } from "fs";
11027
+ import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync30 } from "fs";
11015
11028
  import { dirname as dirname13, join as join43 } from "path";
11029
+ import { createHash as createHash10 } from "crypto";
11016
11030
  function loadLessons(repoDir) {
11017
11031
  const p = lessonsPath(repoDir);
11018
- if (!existsSync23(p))
11032
+ if (!existsSync24(p))
11019
11033
  return [];
11020
11034
  try {
11021
11035
  const j = JSON.parse(readFileSync30(p, "utf-8"));
@@ -11029,6 +11043,25 @@ function save(repoDir, lessons) {
11029
11043
  mkdirSync14(dirname13(p), { recursive: true });
11030
11044
  atomicWriteJson(p, { version: 2, lessons });
11031
11045
  }
11046
+ function appendReceiptFact(repoDir, f, now2 = () => new Date) {
11047
+ const id = `rcp-${createHash10("sha256").update(f.runId).digest("hex").slice(0, 12)}`;
11048
+ const lessons = loadLessons(repoDir);
11049
+ if (lessons.some((l) => l.id === id))
11050
+ return false;
11051
+ const cited = f.receiptSha256 !== null;
11052
+ const title = f.title.replace(/\s+/g, " ").trim().slice(0, 200);
11053
+ lessons.push({
11054
+ id,
11055
+ kind: "receipt",
11056
+ text: `Run ${f.runId} sealed ${f.verdict}: ${title}`,
11057
+ source: { pr: `run:${f.runId}`, pr_url: "", comment_url: "", kind: "receipt", author: "loki" },
11058
+ learned_at: now2().toISOString(),
11059
+ uses: [],
11060
+ justified_by: { run_id: f.runId, receipt_sha256: f.receiptSha256, verdict: cited ? f.verdict : "unverified" }
11061
+ });
11062
+ save(repoDir, lessons);
11063
+ return true;
11064
+ }
11032
11065
  function recordRunVerdict(repoDir, runId, verdict) {
11033
11066
  const lessons = loadLessons(repoDir);
11034
11067
  let dirty = false;
@@ -11041,9 +11074,10 @@ function recordRunVerdict(repoDir, runId, verdict) {
11041
11074
  if (dirty)
11042
11075
  save(repoDir, lessons);
11043
11076
  }
11044
- var lessonsPath = (repoDir) => join43(repoDir, ".loki", "memory", "semantic", "pr-lessons.json");
11077
+ var lessonsPath = (repoDir) => join43(repoDir, ".loki", "memory", "semantic", "pr-lessons.json"), KINDS;
11045
11078
  var init_pr_lessons = __esm(() => {
11046
11079
  init_atomic();
11080
+ KINDS = new Set(["pr", "receipt", "ticket", "rejected"]);
11047
11081
  });
11048
11082
 
11049
11083
  // ../../loki-ts/src/runner/model_downgrades.ts
@@ -11056,7 +11090,7 @@ var init_model_downgrades = __esm(() => {
11056
11090
  var init_mcp_config = () => {};
11057
11091
 
11058
11092
  // ../../loki-ts/src/providers/claude_flags.ts
11059
- import { existsSync as existsSync24, readFileSync as readFileSync31 } from "fs";
11093
+ import { existsSync as existsSync25, readFileSync as readFileSync31 } from "fs";
11060
11094
  import { resolve as resolvePath, dirname as dirname14 } from "path";
11061
11095
  import { fileURLToPath as fileURLToPath3 } from "url";
11062
11096
  function designArchetypesText() {
@@ -11067,7 +11101,7 @@ function designArchetypesText() {
11067
11101
  ["..", "..", "references", "design-archetypes.md"]
11068
11102
  ]) {
11069
11103
  const p = resolvePath(moduleDir, ...rel);
11070
- if (existsSync24(p))
11104
+ if (existsSync25(p))
11071
11105
  return readFileSync31(p, "utf8");
11072
11106
  }
11073
11107
  } catch {}
@@ -11146,12 +11180,12 @@ var init_providers = __esm(() => {
11146
11180
  });
11147
11181
 
11148
11182
  // ../../loki-ts/src/runner/router/route_record.ts
11149
- import { existsSync as existsSync25, lstatSync as lstatSync9, mkdirSync as mkdirSync15, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
11183
+ import { existsSync as existsSync26, lstatSync as lstatSync9, mkdirSync as mkdirSync15, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
11150
11184
  import { join as join44 } from "path";
11151
11185
  function loadRouteRecord(runDir) {
11152
11186
  try {
11153
11187
  const p = join44(runDir, ROUTE_RECORD_FILE);
11154
- if (!existsSync25(p) || !lstatSync9(p).isFile())
11188
+ if (!existsSync26(p) || !lstatSync9(p).isFile())
11155
11189
  return;
11156
11190
  const v = JSON.parse(readFileSync32(p, "utf8"));
11157
11191
  return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
@@ -11163,7 +11197,7 @@ var ROUTE_RECORD_FILE = "route.json";
11163
11197
  var init_route_record = () => {};
11164
11198
 
11165
11199
  // ../../loki-ts/src/runner/router/history_store.ts
11166
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync33, renameSync as renameSync7, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
11200
+ import { existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync33, renameSync as renameSync7, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
11167
11201
  import { resolve as resolve13 } from "path";
11168
11202
  function validRun(v) {
11169
11203
  if (typeof v !== "object" || v === null)
@@ -11176,7 +11210,7 @@ function historyPath(key, cacheRoot) {
11176
11210
  }
11177
11211
  function readRunHistory(key, cacheRoot) {
11178
11212
  const path = historyPath(key, cacheRoot);
11179
- if (!existsSync26(path))
11213
+ if (!existsSync27(path))
11180
11214
  return [];
11181
11215
  try {
11182
11216
  const parsed = JSON.parse(readFileSync33(path, "utf8"));
@@ -11426,7 +11460,7 @@ __export(exports_deep, {
11426
11460
  DEFAULT_SECRET_SCAN_SH: () => DEFAULT_SECRET_SCAN_SH,
11427
11461
  DEFAULT_PUSH_SH: () => DEFAULT_PUSH_SH
11428
11462
  });
11429
- import { existsSync as existsSync27, mkdirSync as mkdirSync17, writeFileSync as writeFileSync19 } from "fs";
11463
+ import { existsSync as existsSync28, mkdirSync as mkdirSync17, writeFileSync as writeFileSync19 } from "fs";
11430
11464
  import { join as join47 } from "path";
11431
11465
  async function runFullSuite(ctx, signal, opts, checks, notProven) {
11432
11466
  if (await runPackageSuites(ctx.repoDir, signal, { path: opts.path, timeoutMs: opts.fullSuiteTimeoutMs ?? FULL_SUITE_TIMEOUT_MS, baseSha: ctx.baseSha }, checks, notProven))
@@ -11513,7 +11547,7 @@ async function runCouncilCheck(ctx, changed, baseSha, signal, opts, checks, notP
11513
11547
  async function runSecretScan(ctx, changed, opts, checks, notProven) {
11514
11548
  const scriptPath = opts.secretScanShPath ?? DEFAULT_SECRET_SCAN_SH;
11515
11549
  const name = "security scan";
11516
- if (!existsSync27(scriptPath)) {
11550
+ if (!existsSync28(scriptPath)) {
11517
11551
  checks.push({ name, cmd: "secret-scan.sh", result: "not_run", duration_s: 0 });
11518
11552
  notProven.add(`not run: ${name} (secret-scan.sh not found)`);
11519
11553
  return;
@@ -11662,7 +11696,7 @@ var init_deep = __esm(() => {
11662
11696
  });
11663
11697
 
11664
11698
  // ../../loki-ts/src/engine10/stages/verify.ts
11665
- import { existsSync as existsSync28, mkdtempSync as mkdtempSync6, realpathSync as realpathSync17, rmSync as rmSync12 } from "fs";
11699
+ import { existsSync as existsSync29, mkdtempSync as mkdtempSync6, realpathSync as realpathSync17, rmSync as rmSync12 } from "fs";
11666
11700
  import { tmpdir as tmpdir8 } from "os";
11667
11701
  import { dirname as dirname16, isAbsolute as isAbsolute6, join as join48, relative as relative5 } from "path";
11668
11702
  function resolveTool(repoDir, bin, fallback, lastResort = fallback) {
@@ -11675,7 +11709,7 @@ function resolveTool(repoDir, bin, fallback, lastResort = fallback) {
11675
11709
  }
11676
11710
  };
11677
11711
  const venvEnv = process.env["VIRTUAL_ENV"];
11678
- const project = [join48(repoDir, ".venv", "bin", bin), join48(repoDir, "venv", "bin", bin), ...venvEnv && inRepo(venvEnv) ? [join48(venvEnv, "bin", bin)] : []].find(existsSync28);
11712
+ const project = [join48(repoDir, ".venv", "bin", bin), join48(repoDir, "venv", "bin", bin), ...venvEnv && inRepo(venvEnv) ? [join48(venvEnv, "bin", bin)] : []].find(existsSync29);
11679
11713
  return project ? [project, "project"] : [Bun.which(fallback) ? fallback : lastResort, "system"];
11680
11714
  }
11681
11715
  function runnerCmd(t, repoDir) {
@@ -11828,7 +11862,7 @@ async function subtractBase(ctx, api, checks, tests, changed, wall, rel, signal)
11828
11862
  return out2;
11829
11863
  }
11830
11864
  function isLokiModeRepo(repoDir) {
11831
- return existsSync28(join48(repoDir, "scripts", "select-tests.sh"));
11865
+ return existsSync29(join48(repoDir, "scripts", "select-tests.sh"));
11832
11866
  }
11833
11867
  async function runLintChecks(ctx, changed, signal, checks, opts = {}) {
11834
11868
  const { api, ...runOpts } = opts;
@@ -11845,9 +11879,9 @@ async function runLintChecks(ctx, changed, signal, checks, opts = {}) {
11845
11879
  }
11846
11880
  for (const g of groupByPackage(api, ctx.repoDir, changed.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f)))) {
11847
11881
  const sfx = g.root === "." ? "" : `:${g.root}`, co = g.root === "." ? opts : { ...opts, cwd: g.cwd };
11848
- if (existsSync28(join48(g.cwd, "tsconfig.json")))
11882
+ if (existsSync29(join48(g.cwd, "tsconfig.json")))
11849
11883
  await runCheck(ctx, `lint:tsc${sfx}`, "npx", ["tsc", "--noEmit", "-p", "."], signal, checks, { ...co, kind: "static" });
11850
- if (ESLINT_CONFIGS.some((f) => existsSync28(join48(g.cwd, f))))
11884
+ if (ESLINT_CONFIGS.some((f) => existsSync29(join48(g.cwd, f))))
11851
11885
  await runCheck(ctx, `lint:eslint${sfx}`, "npx", ["eslint", ...g.rel], signal, checks, { ...co, kind: "static" });
11852
11886
  }
11853
11887
  }
@@ -11979,7 +12013,7 @@ var init_verify = __esm(() => {
11979
12013
  });
11980
12014
 
11981
12015
  // ../../loki-ts/src/project_model/conventions.ts
11982
- import { existsSync as existsSync29, readdirSync as readdirSync13, readFileSync as readFileSync34 } from "fs";
12016
+ import { existsSync as existsSync30, readdirSync as readdirSync13, readFileSync as readFileSync34 } from "fs";
11983
12017
  import { dirname as dirname17, join as join49 } from "path";
11984
12018
  function parseJsonc(text2) {
11985
12019
  let out2 = "", i = 0;
@@ -12016,7 +12050,7 @@ function moduleOf(file, depth = 0) {
12016
12050
  if (!ext || !ext.startsWith("."))
12017
12051
  return null;
12018
12052
  const next = join49(dirname17(file), ext.endsWith(".json") ? ext : `${ext}.json`);
12019
- return existsSync29(next) ? moduleOf(next, depth + 1) : null;
12053
+ return existsSync30(next) ? moduleOf(next, depth + 1) : null;
12020
12054
  }
12021
12055
  function readPackageConventions(pkgDir, runner = null) {
12022
12056
  let names = [];
@@ -12065,7 +12099,7 @@ var init_conventions = () => {};
12065
12099
 
12066
12100
  // ../../loki-ts/src/engine10/stages/wall.ts
12067
12101
  import { spawnSync as spawnSync4 } from "child_process";
12068
- import { existsSync as existsSync30, mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync35, readdirSync as readdirSync14, realpathSync as realpathSync18, rmSync as rmSync13, writeFileSync as writeFileSync20 } from "fs";
12102
+ import { existsSync as existsSync31, mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync35, readdirSync as readdirSync14, realpathSync as realpathSync18, rmSync as rmSync13, writeFileSync as writeFileSync20 } from "fs";
12069
12103
  import { tmpdir as tmpdir9 } from "os";
12070
12104
  import { dirname as dirname18, isAbsolute as isAbsolute7, join as join50, relative as relative6 } from "path";
12071
12105
  function pytestCollectionIsRed(output, repoDir) {
@@ -12110,7 +12144,7 @@ function pytestCollectionIsRed(output, repoDir) {
12110
12144
  }
12111
12145
  function moduleUnderRepo(repoDir, name) {
12112
12146
  const rel = name.replace(/\./g, "/");
12113
- return existsSync30(join50(repoDir, `${rel}.py`)) || existsSync30(join50(repoDir, rel, "__init__.py"));
12147
+ return existsSync31(join50(repoDir, `${rel}.py`)) || existsSync31(join50(repoDir, rel, "__init__.py"));
12114
12148
  }
12115
12149
  function pytestExit1IsRed(output) {
12116
12150
  if (/^!+ _pytest\.outcomes\.Exit\b/m.test(output))
@@ -12187,13 +12221,16 @@ ${r.stderr ?? ""}`), repoDir, interpreter);
12187
12221
  return { pass, fail, not_run };
12188
12222
  }
12189
12223
  }
12190
- function buildWallBrief(task, repomapText = "", runners = [], hasManifest = false, conventions = "") {
12224
+ function buildWallBrief(task, repomapText = "", runners = [], hasManifest = false, conventions = "", acceptance = []) {
12191
12225
  return withStagePrefix([
12192
12226
  "You are the Loki 10 Wall author.",
12193
12227
  `You cannot see the repository. This directory holds only task.md and repomap.txt${hasManifest ? " and wall_manifest.txt (signatures, runner config, test layout and test-style examples)" : ""}.`,
12194
12228
  ...repomapText ? [`Repository paths (repomap.txt):
12195
12229
  ${repomapText}`] : [],
12196
12230
  ...taskBlock(task),
12231
+ ...acceptance.length ? [`Acceptance criteria frozen for this unit (each must be covered by a test):
12232
+ ${acceptance.map((a) => `Acceptance: ${a}`).join(`
12233
+ `)}`] : [],
12197
12234
  "Write behavioral acceptance tests that prove the task is done. A test that errors on import, uses another framework's globals, or fails for a reason unrelated to the task is discarded.",
12198
12235
  `Test runner: ${runners.map((r) => RUNNER_HINT[r]).find(Boolean) ?? "the framework named in repomap.txt"}`,
12199
12236
  ...conventions ? [conventions] : [],
@@ -12206,7 +12243,7 @@ function loadTaskText(ctx, fromPrior) {
12206
12243
  if (fromPrior)
12207
12244
  return fromPrior;
12208
12245
  const issueJsonPath = process.env.LOKI_E10_ISSUE_JSON ?? join50(ctx.runDir, "issue.json");
12209
- if (existsSync30(issueJsonPath)) {
12246
+ if (existsSync31(issueJsonPath)) {
12210
12247
  try {
12211
12248
  const issue = JSON.parse(readFileSync35(issueJsonPath, "utf8"));
12212
12249
  const text2 = [issue.title, issue.body].filter((s) => typeof s === "string" && s.length > 0).join(`
@@ -12258,7 +12295,7 @@ async function wallAuthor(ctx, signal) {
12258
12295
  writeFileSync20(join50(cwd, n2), c, "utf8");
12259
12296
  const session = await ctx.sessions.run({
12260
12297
  stage: "wall",
12261
- brief: buildWallBrief(task, repomapText, runners, !!wm, conventionsBrief(conv2)),
12298
+ brief: buildWallBrief(task, repomapText, runners, !!wm, conventionsBrief(conv2), unitSpec()?.acceptance ?? []),
12262
12299
  tier: "development",
12263
12300
  model: wallModel(),
12264
12301
  iterationId: `${ctx.runId}-wall`,
@@ -12341,6 +12378,7 @@ async function runWall(ctx, signal, opts = {}) {
12341
12378
  var WALL_PREFIX = "loki_wall_", BASE_RUN_TIMEOUT_MS = 60000, WALL_MAP_MAX_LINES = 200, wallStage, stage2;
12342
12379
  var init_wall = __esm(() => {
12343
12380
  init_types();
12381
+ init_unit_mode();
12344
12382
  init_lean_prefix();
12345
12383
  init_wall_hints();
12346
12384
  init_sizing();
@@ -12515,7 +12553,7 @@ var init_scope2 = __esm(() => {
12515
12553
  });
12516
12554
 
12517
12555
  // ../../loki-ts/src/util/receipt_signer.ts
12518
- import { createHash as createHash10, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
12556
+ import { createHash as createHash11, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
12519
12557
  import { chmodSync as chmodSync5, linkSync, mkdirSync as mkdirSync20, readFileSync as readFileSync37, statSync as statSync16, unlinkSync as unlinkSync5, writeFileSync as writeFileSync22 } from "fs";
12520
12558
  import { homedir as homedir8 } from "os";
12521
12559
  import { dirname as dirname22, join as join52 } from "path";
@@ -12577,13 +12615,13 @@ function signReceipt(runId, hash) {
12577
12615
  const input = `${b64u(canonicalJson({ alg: "EdDSA", typ: "JWT", kid }))}.${b64u(canonicalJson({ job_id: runId, run_id: runId, receipt_sha256: hash, iat: Math.floor(Date.now() / 1000) }))}`;
12578
12616
  return { jwt: `${input}.${b64u(sign(null, Buffer.from(input), key))}`, kid };
12579
12617
  }
12580
- var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem", b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash10("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest());
12618
+ var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem", b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash11("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest());
12581
12619
  var init_receipt_signer = () => {};
12582
12620
 
12583
12621
  // ../../loki-ts/src/features/visual_evidence.ts
12584
12622
  import { spawn as spawn7 } from "child_process";
12585
- import { createHash as createHash11, randomBytes as randomBytes4 } from "crypto";
12586
- import { existsSync as existsSync31, lstatSync as lstatSync11, mkdirSync as mkdirSync21, readdirSync as readdirSync15, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
12623
+ import { createHash as createHash12, randomBytes as randomBytes4 } from "crypto";
12624
+ import { existsSync as existsSync32, lstatSync as lstatSync11, mkdirSync as mkdirSync21, readdirSync as readdirSync15, readFileSync as readFileSync38, writeFileSync as writeFileSync23 } from "fs";
12587
12625
  import { createServer } from "net";
12588
12626
  import { dirname as dirname23, isAbsolute as isAbsolute8, join as join53, normalize as normalize2, relative as relative8, sep as sep12 } from "path";
12589
12627
  function routeFor(file) {
@@ -12615,7 +12653,7 @@ function checkScreens(runDir, screens) {
12615
12653
  if (isSymlink(cur))
12616
12654
  return `evidence screenshot path is a symlink: ${s.path}`;
12617
12655
  }
12618
- if (!existsSync31(file))
12656
+ if (!existsSync32(file))
12619
12657
  return `evidence screenshot is missing: ${s.path}`;
12620
12658
  if (!isRegularFile(file))
12621
12659
  return `evidence screenshot is not a regular file: ${s.path}`;
@@ -12659,7 +12697,7 @@ async function waitUp(url, ms, signal) {
12659
12697
  function openapiPaths(repoDir) {
12660
12698
  for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
12661
12699
  const p = join53(repoDir, f);
12662
- if (!existsSync31(p))
12700
+ if (!existsSync32(p))
12663
12701
  continue;
12664
12702
  try {
12665
12703
  const txt = readFileSync38(p, "utf8");
@@ -12766,7 +12804,7 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
12766
12804
  const pkgPath = join53(appDir, "package.json");
12767
12805
  let script = null;
12768
12806
  if (!boot) {
12769
- if (!existsSync31(pkgPath))
12807
+ if (!existsSync32(pkgPath))
12770
12808
  return skip("no package.json");
12771
12809
  script = pickScript(JSON.parse(readFileSync38(pkgPath, "utf8")));
12772
12810
  if (!script)
@@ -12808,7 +12846,7 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
12808
12846
  break;
12809
12847
  const rel = join53(rel0, `${screenName(route)}.png`);
12810
12848
  const ok = await shoot(pw, ["screenshot", `${base}${route}`, join53(runDir, rel)], appDir, left(), signal);
12811
- if (ok && !aborted() && !isSymlink(join53(runDir, rel)) && existsSync31(join53(runDir, rel)))
12849
+ if (ok && !aborted() && !isSymlink(join53(runDir, rel)) && existsSync32(join53(runDir, rel)))
12812
12850
  rels.push(rel);
12813
12851
  }
12814
12852
  if (aborted())
@@ -12860,12 +12898,12 @@ async function sealEvidence(repoDir, runDir, o, notProven, signal, emit) {
12860
12898
  function receiptScreensProblem(receiptPath, receipt) {
12861
12899
  return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname23(receiptPath), receipt["evidence_screens"]);
12862
12900
  }
12863
- var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0", PAGE_RE, isPageFile = (p) => PAGE_RE.test(p), screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index", sha256File = (p) => createHash11("sha256").update(readFileSync38(p)).digest("hex"), playwrightBin = (repoDir) => {
12901
+ var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0", PAGE_RE, isPageFile = (p) => PAGE_RE.test(p), screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index", sha256File = (p) => createHash12("sha256").update(readFileSync38(p)).digest("hex"), playwrightBin = (repoDir) => {
12864
12902
  const bin = join53(repoDir, "node_modules", ".bin", "playwright");
12865
- return existsSync31(bin) ? bin : null;
12903
+ return existsSync32(bin) ? bin : null;
12866
12904
  }, playwrightTestPkg = (repoDir) => {
12867
12905
  const d = join53(repoDir, "node_modules", "@playwright", "test");
12868
- return existsSync31(join53(d, "package.json")) ? d : null;
12906
+ return existsSync32(join53(d, "package.json")) ? d : null;
12869
12907
  }, e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
12870
12908
  test("loki e2e walkthrough", async ({ page }) => {
12871
12909
  for (const r of ${JSON.stringify(routes)}) { await page.goto(${JSON.stringify(base)} + r); await page.waitForLoadState("load"); }
@@ -12896,7 +12934,7 @@ var init_visual_evidence = __esm(() => {
12896
12934
  });
12897
12935
 
12898
12936
  // ../../loki-ts/src/council/role_agents.ts
12899
- import { existsSync as existsSync32, readdirSync as readdirSync16, readFileSync as readFileSync39 } from "fs";
12937
+ import { existsSync as existsSync33, readdirSync as readdirSync16, readFileSync as readFileSync39 } from "fs";
12900
12938
  import { homedir as homedir9 } from "os";
12901
12939
  import { join as join54 } from "path";
12902
12940
  function agentDirs(repoDir, env = process.env) {
@@ -12917,7 +12955,7 @@ function parseAgent(file, stem2) {
12917
12955
  }
12918
12956
  function loadRoleAgent(repoDir, role, env = process.env) {
12919
12957
  for (const d of agentDirs(repoDir, env)) {
12920
- if (!existsSync32(d))
12958
+ if (!existsSync33(d))
12921
12959
  continue;
12922
12960
  let names;
12923
12961
  try {
@@ -12945,7 +12983,7 @@ var init_role_agents = __esm(() => {
12945
12983
  });
12946
12984
 
12947
12985
  // ../../loki-ts/src/engine10/stages/xreview.ts
12948
- import { existsSync as existsSync33, readFileSync as readFileSync40 } from "fs";
12986
+ import { existsSync as existsSync34, readFileSync as readFileSync40 } from "fs";
12949
12987
  import { join as join55 } from "path";
12950
12988
  function minVerdict(verdict, review) {
12951
12989
  const ceiling = review?.level === "block" ? "PARTIAL" : verdict;
@@ -12960,7 +12998,7 @@ function explicitReview(repoDir, env) {
12960
12998
  return "off";
12961
12999
  for (const f of ["loki.yaml", "loki.yml"]) {
12962
13000
  const p = join55(repoDir, f);
12963
- if (!existsSync33(p))
13001
+ if (!existsSync34(p))
12964
13002
  continue;
12965
13003
  try {
12966
13004
  const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync40(p, "utf8"));
@@ -13181,9 +13219,9 @@ var init_cost_preview = __esm(() => {
13181
13219
  });
13182
13220
 
13183
13221
  // ../../loki-ts/src/features/contract.ts
13184
- import { existsSync as existsSync34, lstatSync as lstatSync12, mkdirSync as mkdirSync22, readFileSync as readFileSync41, rmSync as rmSync14, writeFileSync as writeFileSync24 } from "fs";
13222
+ import { existsSync as existsSync35, lstatSync as lstatSync12, mkdirSync as mkdirSync22, readFileSync as readFileSync41, rmSync as rmSync14, writeFileSync as writeFileSync24 } from "fs";
13185
13223
  import { dirname as dirname24, join as join56 } from "path";
13186
- import { createHash as createHash12 } from "crypto";
13224
+ import { createHash as createHash13 } from "crypto";
13187
13225
  function contractEnabled(env = process.env) {
13188
13226
  return env["LOKI_CONTRACT"] !== "0";
13189
13227
  }
@@ -13234,7 +13272,7 @@ function readContract(repoDir) {
13234
13272
  } catch {
13235
13273
  return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
13236
13274
  }
13237
- const sha2563 = createHash12("sha256").update(raw2).digest("hex");
13275
+ const sha2563 = createHash13("sha256").update(raw2).digest("hex");
13238
13276
  let j;
13239
13277
  try {
13240
13278
  j = JSON.parse(raw2.toString("utf8"));
@@ -13304,12 +13342,12 @@ var init_contract = __esm(() => {
13304
13342
  });
13305
13343
 
13306
13344
  // ../../loki-ts/src/features/speed/seal_group.ts
13307
- import { createHash as createHash13 } from "crypto";
13308
- import { existsSync as existsSync35, readdirSync as readdirSync17, readFileSync as readFileSync42, statSync as statSync17 } from "fs";
13309
- import { dirname as dirname25, join as join57 } from "path";
13345
+ import { createHash as createHash14 } from "crypto";
13346
+ import { existsSync as existsSync36, readdirSync as readdirSync17, readFileSync as readFileSync42, statSync as statSync17 } from "fs";
13347
+ import { basename as basename19, dirname as dirname25, join as join57 } from "path";
13310
13348
  function sealGroup(runDir, rh) {
13311
13349
  const manifestPath = join57(runDir, "group", "manifest.json");
13312
- if (!existsSync35(manifestPath))
13350
+ if (!existsSync36(manifestPath))
13313
13351
  return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
13314
13352
  const notProven = [];
13315
13353
  let problems = 0;
@@ -13353,7 +13391,7 @@ function sealGroup(runDir, rh) {
13353
13391
  unit_id: id,
13354
13392
  sub_receipt_sha256: sha2(bytes),
13355
13393
  receipt_sha256: sub["receipt_sha256"],
13356
- events_file_sha256: existsSync35(ep) ? sha2(readFileSync42(ep)) : null,
13394
+ events_file_sha256: existsSync36(ep) ? sha2(readFileSync42(ep)) : null,
13357
13395
  verdict,
13358
13396
  goal: text2(u["goal"], 200),
13359
13397
  files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
@@ -13378,7 +13416,7 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
13378
13416
  const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
13379
13417
  const g = receipt["group"];
13380
13418
  if (g === undefined)
13381
- return existsSync35(join57(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
13419
+ return existsSync36(join57(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
13382
13420
  if (!isObj10(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
13383
13421
  return bad("group section malformed");
13384
13422
  const entries = g["units"];
@@ -13403,7 +13441,7 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
13403
13441
  }
13404
13442
  if (sha2(bytes) !== e["sub_receipt_sha256"])
13405
13443
  return bad(`unit ${id} sub-receipt does not match its recorded hash`);
13406
- const evSha = existsSync35(ep) ? sha2(readFileSync42(ep)) : null;
13444
+ const evSha = existsSync36(ep) ? sha2(readFileSync42(ep)) : null;
13407
13445
  if (evSha !== (e["events_file_sha256"] ?? null))
13408
13446
  return bad(`unit ${id} events.jsonl does not match its recorded hash`);
13409
13447
  const sub = readJson5(rp);
@@ -13421,7 +13459,83 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
13421
13459
  }
13422
13460
  return unchecked ? { verdict: "UNCHECKED", reason: `group: ${unchecked}` } : null;
13423
13461
  }
13424
- var ID_RE, PASSING, sha2 = (b) => createHash13("sha256").update(b).digest("hex"), isObj10 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "", num4 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0, unitsDir = (runDir) => join57(runDir, "group", "units"), readJson5 = (p) => {
13462
+ function compositeVerdict(childVerdicts) {
13463
+ const base = childVerdicts.includes("SPEC_CONFLICT") ? "SPEC_CONFLICT" : childVerdicts.length > 0 && childVerdicts.every((v) => v === "ALREADY_SATISFIED") ? "ALREADY_SATISFIED" : "VERIFIED";
13464
+ const failed = childVerdicts.filter((v) => v === "FAILED" || !["VERIFIED", "ALREADY_SATISFIED", "PARTIAL", "SPEC_CONFLICT"].includes(v)).length;
13465
+ return capGroupVerdict(base, { section: undefined, notProven: [], problems: failed, allUnitsPass: childVerdicts.every((v) => PASSING.has(v)) });
13466
+ }
13467
+ async function verifyComposite(receiptPath, receipt, rh, verifyOne, deps = {}) {
13468
+ const runDir = dirname25(receiptPath);
13469
+ const signed = isObj10(receipt["verification"]) && typeof receipt["verification"]["jwt"] === "string";
13470
+ const bad = (reason) => ({ problem: { verdict: "TAMPERED", reason: `composite: ${reason}` }, verdict: "FAILED" });
13471
+ const kids = receipt["children"];
13472
+ if (!Array.isArray(kids) || kids.length === 0 || !ID_RE.test(String(receipt["run_id"] ?? "")))
13473
+ return bad("body malformed");
13474
+ let ibytes;
13475
+ try {
13476
+ ibytes = readFileSync42(join57(runDir, "integration.json"));
13477
+ } catch {
13478
+ return bad("integration.json missing");
13479
+ }
13480
+ if (sha2(ibytes) !== receipt["integration_sha256"])
13481
+ return bad("integration.json does not match its recorded hash");
13482
+ const integ = readJson5(join57(runDir, "integration.json"));
13483
+ if (!isObj10(integ) || !isObj10(integ["outcomes"]))
13484
+ return bad("integration.json has no outcomes");
13485
+ const heads = isObj10(integ["heads"]) ? integ["heads"] : {}, outcomes = integ["outcomes"];
13486
+ const order2 = Array.isArray(integ["order"]) ? integ["order"].map(String) : Object.keys(outcomes).sort();
13487
+ const kidRepos = kids.map((k) => isObj10(k) ? String(k["repo"] ?? "") : "");
13488
+ if (new Set(kidRepos).size !== kidRepos.length)
13489
+ return bad("children list a repo more than once");
13490
+ if (kidRepos.length !== order2.length || kidRepos.some((r, i) => r !== order2[i]))
13491
+ return bad("children do not cover the integrated repos in order (a repo is missing or extra)");
13492
+ let unchecked = null;
13493
+ const verdicts = [];
13494
+ for (const raw2 of kids) {
13495
+ const c = isObj10(raw2) ? raw2 : {}, repo = String(c["repo"] ?? ""), rel = String(c["receipt_path"] ?? "");
13496
+ if (!repo || !relOk(rel) || !rel.startsWith(`worktrees/${slugOf(repo)}/.loki/runs/`))
13497
+ return bad(`child ${text2(repo, 80) || "?"} has an invalid receipt path`);
13498
+ if (outcomes[repo] !== "ok")
13499
+ return bad(`child ${text2(repo, 80)} did not finish ok in integration.json`);
13500
+ const rp = join57(runDir, rel);
13501
+ let bytes;
13502
+ try {
13503
+ bytes = readFileSync42(rp);
13504
+ } catch {
13505
+ return bad(`child ${repo} receipt missing`);
13506
+ }
13507
+ if (sha2(bytes) !== c["receipt_file_sha256"])
13508
+ return bad(`child ${repo} receipt does not match its recorded hash (swapped or edited)`);
13509
+ const sub = readJson5(rp);
13510
+ if (!isObj10(sub) || sub["receipt_sha256"] !== c["receipt_sha256"] || rh(sub) !== c["receipt_sha256"])
13511
+ return bad(`child ${repo} receipt hash does not recompute`);
13512
+ if (String(sub["verdict"] ?? "") !== c["verdict"])
13513
+ return bad(`child ${repo} verdict differs from the composite`);
13514
+ if (String(sub["head_sha"] ?? "") !== c["head_sha"] || !c["head_sha"])
13515
+ return bad(`child ${repo} head_sha differs from the composite`);
13516
+ verdicts.push(String(c["verdict"]));
13517
+ if (typeof heads[repo] !== "string" || heads[repo] === "")
13518
+ unchecked ??= `child ${repo} has no integrated head to compare (freshness not checked)`;
13519
+ else if (heads[repo] !== c["head_sha"])
13520
+ unchecked ??= `child ${repo} head ${String(c["head_sha"])} is not the integrated head ${String(heads[repo])}`;
13521
+ const r = await verifyOne(rp, deps);
13522
+ if (r.verdict === "TAMPERED")
13523
+ return bad(`child ${repo} failed verify: ${r.reasons.join("; ")}`);
13524
+ if (r.verdict === "UNSIGNED" && signed)
13525
+ unchecked ??= `child ${repo} receipt is unsigned under a signed composite`;
13526
+ if (r.verdict === "UNCHECKED")
13527
+ unchecked ??= `child ${repo} not checked: ${r.reasons.join("; ")}`;
13528
+ }
13529
+ const expect = compositeVerdict(verdicts);
13530
+ if (receipt["verdict"] !== expect)
13531
+ return bad(`recorded verdict ${String(receipt["verdict"])} differs from the children (${expect})`);
13532
+ return { problem: unchecked ? { verdict: "UNCHECKED", reason: `composite: ${unchecked}` } : null, verdict: expect };
13533
+ }
13534
+ function compositeHash(r) {
13535
+ const { composite_sha256: _c, verification: _v, ...body } = r;
13536
+ return sha2(canonicalJson(body));
13537
+ }
13538
+ var ID_RE, PASSING, sha2 = (b) => createHash14("sha256").update(b).digest("hex"), isObj10 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "", num4 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0, unitsDir = (runDir) => join57(runDir, "group", "units"), readJson5 = (p) => {
13425
13539
  try {
13426
13540
  return JSON.parse(readFileSync42(p, "utf8"));
13427
13541
  } catch {
@@ -13433,15 +13547,16 @@ var ID_RE, PASSING, sha2 = (b) => createHash13("sha256").update(b).digest("hex")
13433
13547
  } catch {
13434
13548
  return null;
13435
13549
  }
13436
- };
13550
+ }, COMPOSITE_SCHEMA = "loki.v10.composite/1", slugOf = (repo) => repo.replace(/\//g, "__"), isComposite = (r) => r["schema"] === COMPOSITE_SCHEMA, relOk = (p) => p !== "" && !p.startsWith("/") && !p.split("/").includes("..");
13437
13551
  var init_seal_group = __esm(() => {
13552
+ init_receipt_signer();
13438
13553
  ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
13439
13554
  PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
13440
13555
  });
13441
13556
 
13442
13557
  // ../../loki-ts/src/supply/supply_guard.ts
13443
- import { existsSync as existsSync36, readFileSync as readFileSync43, statSync as statSync18 } from "fs";
13444
- import { basename as basename19, join as join58 } from "path";
13558
+ import { existsSync as existsSync37, readFileSync as readFileSync43, statSync as statSync18 } from "fs";
13559
+ import { basename as basename20, join as join58 } from "path";
13445
13560
  function supplyEnabled(env) {
13446
13561
  return env["LOKI_SUPPLY_GUARD"] !== "0";
13447
13562
  }
@@ -13453,11 +13568,11 @@ function supplyVerdict(verdict, r) {
13453
13568
  return r.blocked && verdict === "VERIFIED" ? "FAILED" : verdict;
13454
13569
  }
13455
13570
  function isManifestPath(path) {
13456
- return MANIFESTS.test(basename19(path));
13571
+ return MANIFESTS.test(basename20(path));
13457
13572
  }
13458
13573
  function readDeclared(repoDir) {
13459
13574
  const p = join58(repoDir, ".loki", DECLARED_FILE);
13460
- if (!existsSync36(p))
13575
+ if (!existsSync37(p))
13461
13576
  return { deps: [], problem: null };
13462
13577
  try {
13463
13578
  if (statSync18(p).size > MAX_FILE_BYTES)
@@ -13483,7 +13598,7 @@ function readDeclared(repoDir) {
13483
13598
  }
13484
13599
  function readAllowlist(repoDir) {
13485
13600
  const p = join58(repoDir, ".loki", "supply-allowlist");
13486
- if (!existsSync36(p))
13601
+ if (!existsSync37(p))
13487
13602
  return new Set;
13488
13603
  try {
13489
13604
  return new Set(readFileSync43(p, "utf8").split(`
@@ -13684,9 +13799,9 @@ var init_supply_guard = __esm(() => {
13684
13799
  });
13685
13800
 
13686
13801
  // ../../loki-ts/src/engine10/stages/seal.ts
13687
- import { createHash as createHash14 } from "crypto";
13688
- import { existsSync as existsSync37, readdirSync as readdirSync18, mkdirSync as mkdirSync23, readFileSync as readFileSync44, writeFileSync as writeFileSync25 } from "fs";
13689
- import { basename as basename20, isAbsolute as isAbsolute9, join as join59, relative as relative9 } from "path";
13802
+ import { createHash as createHash15 } from "crypto";
13803
+ import { existsSync as existsSync38, readdirSync as readdirSync18, mkdirSync as mkdirSync23, readFileSync as readFileSync44, writeFileSync as writeFileSync25 } from "fs";
13804
+ import { basename as basename21, isAbsolute as isAbsolute9, join as join59, relative as relative9 } from "path";
13690
13805
  function sanitizeReason(s) {
13691
13806
  const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
13692
13807
  const capped = collapsed.length > 500 ? `${collapsed.slice(0, 500)}...` : collapsed;
@@ -13760,7 +13875,7 @@ function renderReceiptMd(r) {
13760
13875
  ].join(`
13761
13876
  `);
13762
13877
  }
13763
- var DEEP_NOT_PROVEN, SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)", EXCLUDE_LOKI = ":(exclude).loki", sha2562 = (s) => createHash14("sha256").update(s).digest("hex"), str3 = (v) => typeof v === "string" && v !== "" ? v : null, strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [], receiptRel = (ctx) => {
13878
+ var DEEP_NOT_PROVEN, SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)", EXCLUDE_LOKI = ":(exclude).loki", sha2562 = (s) => createHash15("sha256").update(s).digest("hex"), str3 = (v) => typeof v === "string" && v !== "" ? v : null, strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [], receiptRel = (ctx) => {
13764
13879
  const r = relative9(ctx.repoDir, join59(ctx.runDir, "receipt.json"));
13765
13880
  return r.startsWith("..") || isAbsolute9(r) ? `.loki/runs/${ctx.runId}/receipt.json` : r;
13766
13881
  }, commitStage, sealStage;
@@ -13863,7 +13978,7 @@ var init_seal = __esm(() => {
13863
13978
  notProven.add(`Wall test not executed: ${wallNotRun} file(s), no reason recorded`);
13864
13979
  if (wallNotRun > 0) {
13865
13980
  try {
13866
- const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename20(String(f.path))));
13981
+ const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename21(String(f.path))));
13867
13982
  for (const n2 of readdirSync18(join59(ctx.runDir, "wall")).sort())
13868
13983
  if (!kept.has(n2))
13869
13984
  notProven.add(`wall test discarded: ${n2} (not_run)`);
@@ -13979,7 +14094,7 @@ var init_seal = __esm(() => {
13979
14094
  provider: ctx.provider,
13980
14095
  model: ctx.model,
13981
14096
  resumed: o.intake?.resumed === true,
13982
- events_sha256: sha2562(existsSync37(eventsPath) ? readFileSync44(eventsPath) : ""),
14097
+ events_sha256: sha2562(existsSync38(eventsPath) ? readFileSync44(eventsPath) : ""),
13983
14098
  ...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
13984
14099
  log_seal: true,
13985
14100
  ...receiptBlock(process.env, cost.usd, cost.unmetered === true, totalS),
@@ -14010,6 +14125,11 @@ var init_seal = __esm(() => {
14010
14125
  try {
14011
14126
  recordRunVerdict(ctx.repoDir, ctx.runId, verdict);
14012
14127
  } catch {}
14128
+ if (process.env["LOKI_AUTO_LEARN_PR"] !== "0") {
14129
+ try {
14130
+ appendReceiptFact(ctx.repoDir, { runId: ctx.runId, verdict, receiptSha256: signed ? hash : null, title: str3(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`, headSha: receipt.head_sha });
14131
+ } catch {}
14132
+ }
14013
14133
  ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} });
14014
14134
  return { status: "completed", data: { ...data, summary: `${verdict} receipt ${hash.slice(0, 12)} ${signed ? `SIGNED kid ${sig.kid}` : "UNSIGNED"}` } };
14015
14135
  }
@@ -14109,9 +14229,9 @@ var init_keys_cmd = __esm(() => {
14109
14229
  });
14110
14230
 
14111
14231
  // ../../loki-ts/src/engine10/verify_cmd.ts
14112
- import { existsSync as existsSync38, readFileSync as readFileSync46, statSync as statSync20 } from "fs";
14113
- import { dirname as dirname27, join as join60 } from "path";
14114
- import { createHash as createHash15, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
14232
+ import { existsSync as existsSync39, readFileSync as readFileSync46, renameSync as renameSync8, statSync as statSync20, writeFileSync as writeFileSync26 } from "fs";
14233
+ import { basename as basename22, dirname as dirname27, join as join60 } from "path";
14234
+ import { createHash as createHash16, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
14115
14235
  function pubFor(kid, given) {
14116
14236
  if (given)
14117
14237
  return kidOf2(given) === kid ? given : undefined;
@@ -14148,10 +14268,10 @@ function checkAttestation(jwt, expectedHash, given) {
14148
14268
  }
14149
14269
  function checkEventLog(receiptPath, receipt) {
14150
14270
  const bound = receipt["events_sha256"];
14151
- if (typeof bound !== "string" || bound === createHash15("sha256").digest("hex"))
14271
+ if (typeof bound !== "string" || bound === createHash16("sha256").digest("hex"))
14152
14272
  return null;
14153
14273
  const path = join60(dirname27(receiptPath), "events.jsonl");
14154
- if (!existsSync38(path))
14274
+ if (!existsSync39(path))
14155
14275
  return "events.jsonl is missing";
14156
14276
  const raw2 = readFileSync46(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
14157
14277
  `).filter((l) => l.trim() !== "");
@@ -14159,7 +14279,7 @@ function checkEventLog(receiptPath, receipt) {
14159
14279
  return "events.jsonl has a forged, edited or missing line";
14160
14280
  if (events2.some((e) => e.type === "tamper.detected"))
14161
14281
  return "the supervisor detected events.jsonl being modified during the run";
14162
- const h = createHash15("sha256");
14282
+ const h = createHash16("sha256");
14163
14283
  for (const l of lines) {
14164
14284
  if (h.copy().digest("hex") === bound)
14165
14285
  return null;
@@ -14178,7 +14298,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
14178
14298
  if (!d)
14179
14299
  return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
14180
14300
  const lines = readFileSync46(path, "utf8").split(`
14181
- `).filter((l) => l.trim() !== ""), sha3 = createHash15("sha256").update(lines.slice(0, done + 1).join(`
14301
+ `).filter((l) => l.trim() !== ""), sha3 = createHash16("sha256").update(lines.slice(0, done + 1).join(`
14182
14302
  `) + `
14183
14303
  `).digest("hex");
14184
14304
  const pub = d["kid"] === kid ? pubFor(kid, given) : undefined;
@@ -14187,7 +14307,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
14187
14307
  return null;
14188
14308
  }
14189
14309
  async function verifyReceipt(receiptPath, deps = {}) {
14190
- if (!existsSync38(receiptPath))
14310
+ if (!existsSync39(receiptPath))
14191
14311
  return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
14192
14312
  let receipt;
14193
14313
  try {
@@ -14197,6 +14317,8 @@ async function verifyReceipt(receiptPath, deps = {}) {
14197
14317
  }
14198
14318
  if (isEnvelope(receipt))
14199
14319
  return verifyDsseReceipt(receipt, { pubFor: (kid) => pubFor(kid, deps.pubkey), hash: computeReceiptHash, attest: (j, h) => checkAttestation(j, h, deps.pubkey) });
14320
+ if (isComposite(receipt))
14321
+ return verifyCompositeFile(receiptPath, receipt, deps);
14200
14322
  const recorded = receipt["receipt_sha256"];
14201
14323
  const computed = computeReceiptHash(receipt);
14202
14324
  if (typeof recorded !== "string" || recorded !== computed) {
@@ -14234,6 +14356,30 @@ async function verifyReceipt(receiptPath, deps = {}) {
14234
14356
  return { verdict: gp.verdict, reasons: [gp.reason] };
14235
14357
  return { verdict: "VERIFIED", reasons: [], receiptSha256: computed };
14236
14358
  }
14359
+ async function verifyCompositeFile(receiptPath, receipt, deps) {
14360
+ const computed = compositeHash(receipt);
14361
+ if (receipt["composite_sha256"] !== computed)
14362
+ return { verdict: "TAMPERED", reasons: [`composite_sha256 mismatch: recorded ${JSON.stringify(receipt["composite_sha256"])}, computed ${computed}`] };
14363
+ const c = await verifyComposite(receiptPath, receipt, computeReceiptHash, verifyReceipt, deps);
14364
+ if (c.problem?.verdict === "TAMPERED")
14365
+ return { verdict: "TAMPERED", reasons: [c.problem.reason] };
14366
+ const jwt = (receipt["verification"] ?? {}).jwt ?? null;
14367
+ if (jwt !== null && typeof jwt !== "string")
14368
+ return { verdict: "UNCHECKED", reasons: ["verification.jwt is not a string"] };
14369
+ if (!jwt)
14370
+ return c.problem ? { verdict: c.problem.verdict, reasons: [c.problem.reason] } : { verdict: "UNSIGNED", reasons: [], receiptSha256: computed };
14371
+ const outcome = checkAttestation(jwt, computed, deps.pubkey);
14372
+ if (outcome.status === "unchecked")
14373
+ return { verdict: "UNCHECKED", reasons: [outcome.reason ?? "attestation not checked"] };
14374
+ if (outcome.status === "tampered")
14375
+ return { verdict: "TAMPERED", reasons: [outcome.reason ?? "attestation invalid"] };
14376
+ const shown = receipt["verification"].kid, signed = JSON.parse(Buffer.from(jwt.split(".")[0], "base64url").toString()).kid;
14377
+ if (shown !== undefined && shown !== signed)
14378
+ return { verdict: "TAMPERED", reasons: ["verification.kid differs from the signing kid in the attestation header"] };
14379
+ if (c.problem)
14380
+ return { verdict: c.problem.verdict, reasons: [c.problem.reason] };
14381
+ return { verdict: "VERIFIED", reasons: [], receiptSha256: computed };
14382
+ }
14237
14383
  var computeReceiptHash = (receipt) => receiptSha256(receipt);
14238
14384
  var init_verify_cmd = __esm(() => {
14239
14385
  init_paths();
@@ -14247,7 +14393,7 @@ var init_verify_cmd = __esm(() => {
14247
14393
  });
14248
14394
 
14249
14395
  // src/server/app.ts
14250
- import { existsSync as existsSync40, readFileSync as readFileSync49, statSync as statSync21 } from "fs";
14396
+ import { existsSync as existsSync41, readFileSync as readFileSync49, statSync as statSync21 } from "fs";
14251
14397
  import { join as join63, resolve as resolve15, sep as sep14 } from "path";
14252
14398
 
14253
14399
  // node_modules/hono/dist/request/constants.js
@@ -22455,7 +22601,7 @@ var loki_yaml_schema_default = {
22455
22601
  items: { type: "string", minLength: 1 }
22456
22602
  },
22457
22603
  workspaces: {
22458
- description: "Named groups of repos for cross-repo runs (`loki workspace run`). Accepted unconditionally; runtime behavior is gated by LOKI_WORKSPACES=1.",
22604
+ description: "Named groups of repos for cross-repo runs (`loki workspace run`). On by default; LOKI_WORKSPACES=0 disables. Unknown or mistyped keys fail validation.",
22459
22605
  type: "object",
22460
22606
  additionalProperties: {
22461
22607
  type: "object",
@@ -22479,9 +22625,12 @@ var loki_yaml_schema_default = {
22479
22625
  additionalProperties: false,
22480
22626
  properties: {
22481
22627
  command: { type: "string", minLength: 1 },
22482
- timeout_s: { type: "integer", minimum: 1 }
22628
+ timeout_s: { type: "integer", minimum: 1 },
22629
+ per_stage: { type: "boolean" }
22483
22630
  }
22484
22631
  },
22632
+ concurrency: { type: "integer", minimum: 1 },
22633
+ plan: { type: "boolean" },
22485
22634
  integration_files: { type: "array", items: { type: "string", minLength: 1 } }
22486
22635
  }
22487
22636
  }
@@ -24609,7 +24758,7 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
24609
24758
  }
24610
24759
 
24611
24760
  // src/server/legacy/shim.ts
24612
- import { existsSync as existsSync39, readFileSync as readFileSync48 } from "fs";
24761
+ import { existsSync as existsSync40, readFileSync as readFileSync48 } from "fs";
24613
24762
  import { join as join62 } from "path";
24614
24763
 
24615
24764
  // src/server/legacy/routes.ts
@@ -24984,7 +25133,7 @@ var NOT_SUPPORTED = "not yet supported by the Control Plane";
24984
25133
  var repoVersion = () => {
24985
25134
  for (const f of [join62(import.meta.dir, "../../../../../VERSION")]) {
24986
25135
  try {
24987
- if (existsSync39(f))
25136
+ if (existsSync40(f))
24988
25137
  return readFileSync48(f, "utf8").trim();
24989
25138
  } catch {}
24990
25139
  }
@@ -25167,7 +25316,7 @@ function legacyShim(opts) {
25167
25316
 
25168
25317
  // src/server/app.ts
25169
25318
  var MAX_BODY2 = 1e6;
25170
- var defaultUiDir = () => [join63(import.meta.dir, "../../ui/dist"), join63(import.meta.dir, "../ui/dist")].find((d) => existsSync40(join63(d, "index.html")));
25319
+ var defaultUiDir = () => [join63(import.meta.dir, "../../ui/dist"), join63(import.meta.dir, "../ui/dist")].find((d) => existsSync41(join63(d, "index.html")));
25171
25320
  function createApp(opts) {
25172
25321
  const uiDir = opts.uiDir ?? defaultUiDir();
25173
25322
  const { db, sqlite } = openDb(opts.dbPath);
@@ -25310,7 +25459,7 @@ function createApp(opts) {
25310
25459
  return c.json({ error: "not found" }, 404);
25311
25460
  const root = resolve15(uiDir);
25312
25461
  const f = resolve15(root, `.${p}`);
25313
- if (f.startsWith(root + sep14) && existsSync40(f) && statSync21(f).isFile())
25462
+ if (f.startsWith(root + sep14) && existsSync41(f) && statSync21(f).isFile())
25314
25463
  return new Response(Bun.file(f));
25315
25464
  if (/\.[a-z0-9]+$/i.test(p))
25316
25465
  return c.json({ error: "not found" }, 404);