loki-mode 11.3.8 → 11.4.2

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 (103) hide show
  1. package/README.md +34 -18
  2. package/SKILL.md +3 -3
  3. package/VERSION +1 -1
  4. package/agents/custom_agents.py +280 -0
  5. package/autonomy/council-v2.sh +22 -1
  6. package/autonomy/edit.sh +242 -0
  7. package/autonomy/lib/chat.sh +312 -0
  8. package/autonomy/lib/help-core.sh +164 -0
  9. package/autonomy/lib/json-output.sh +38 -0
  10. package/autonomy/lib/run-summary.sh +96 -0
  11. package/autonomy/loki +105 -10
  12. package/autonomy/route.sh +270 -0
  13. package/autonomy/run.sh +6 -0
  14. package/autonomy/setup-wizard.sh +161 -0
  15. package/autonomy/verify.sh +22 -2
  16. package/bin/loki +73 -18
  17. package/completions/_loki +55 -0
  18. package/completions/loki.bash +49 -3
  19. package/dashboard/__init__.py +1 -1
  20. package/docs/CLI-CORE-VERBS.md +112 -0
  21. package/docs/CLI-REFERENCE.md +7 -1
  22. package/docs/COMPARE-OPENCODE.md +94 -0
  23. package/docs/COMPONENTS.md +1 -0
  24. package/docs/INSTALLATION.md +56 -12
  25. package/docs/KNOWN-ISSUES.md +2 -12
  26. package/docs/QUICKSTART.md +108 -0
  27. package/docs/environment-variables.md +9 -2
  28. package/docs/exit-codes.md +29 -12
  29. package/docs/v10/CLI-MODERN.md +10 -4
  30. package/docs/v10/FAILURE-CLASSES.md +2 -2
  31. package/docs/v10/GUIDE.md +270 -44
  32. package/docs/v10/METRICS.md +6 -0
  33. package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
  34. package/docs/v10/PROGRESS.md +7 -0
  35. package/loki-ts/dist/loki.js +968 -933
  36. package/mcp/__init__.py +1 -1
  37. package/package.json +2 -2
  38. package/packages/control-plane/dist/server.js +198 -86
  39. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  40. package/providers/claude.sh +16 -2
  41. package/providers/model_route_exec.sh +22 -0
  42. package/providers/model_routes.sh +188 -0
  43. package/skills/providers.md +13 -0
  44. package/templates/agents/planner.md +10 -0
  45. package/templates/agents/reviewer.md +10 -0
  46. package/templates/agents/tester.md +10 -0
  47. package/web-app/dist/assets/{AdminPage-D1jlQGeE.js → AdminPage-CYJ2BWRC.js} +1 -1
  48. package/web-app/dist/assets/{Avatar-QUX_aJfQ.js → Avatar-DdK4wynI.js} +1 -1
  49. package/web-app/dist/assets/{Badge-pysXUR6J.js → Badge-CrX3MI7J.js} +1 -1
  50. package/web-app/dist/assets/{Button-CWzlTw9P.js → Button-709-wwGM.js} +1 -1
  51. package/web-app/dist/assets/{CockpitPage-DLCoLdyF.js → CockpitPage-BapdTInO.js} +1 -1
  52. package/web-app/dist/assets/{ComparePage-CCENGIti.js → ComparePage-DT99pGvA.js} +1 -1
  53. package/web-app/dist/assets/{ErrorBoundary-BssCcbgv.js → ErrorBoundary-B0uJSys8.js} +1 -1
  54. package/web-app/dist/assets/{EvidenceReceiptPanel-CwmAqJ90.js → EvidenceReceiptPanel-BtMFgElP.js} +1 -1
  55. package/web-app/dist/assets/{GitHubIssuesPanel-DuSw8shb.js → GitHubIssuesPanel-DjJuU941.js} +1 -1
  56. package/web-app/dist/assets/{GitHubPRsPanel-CGvLbDjh.js → GitHubPRsPanel-HXfEX1BC.js} +1 -1
  57. package/web-app/dist/assets/{HomePage-BrBX5Gn2.js → HomePage-Cqyves-S.js} +2 -2
  58. package/web-app/dist/assets/{LoginPage-DPjr7QHU.js → LoginPage-jUbtC7_Y.js} +1 -1
  59. package/web-app/dist/assets/{MagicPage-Dxd7FWF2.js → MagicPage-DHRsqjgR.js} +1 -1
  60. package/web-app/dist/assets/{MetricsPage-BP47Urni.js → MetricsPage-B-pED05R.js} +1 -1
  61. package/web-app/dist/assets/{NotFoundPage-D7_07YoB.js → NotFoundPage-CsbUs21R.js} +1 -1
  62. package/web-app/dist/assets/{ProjectPage-SQ183efy.js → ProjectPage-BtlLEQwB.js} +3 -3
  63. package/web-app/dist/assets/{ProjectsPage-Cgr1K2Ae.js → ProjectsPage-DaRbRjSp.js} +1 -1
  64. package/web-app/dist/assets/{SettingsPage-kyYOSDt0.js → SettingsPage-DALrtP3F.js} +1 -1
  65. package/web-app/dist/assets/{ShowcasePage-CRd-H1A4.js → ShowcasePage-CoPHSNP0.js} +1 -1
  66. package/web-app/dist/assets/{SystemSettingsPage-BHBdGXod.js → SystemSettingsPage-kOuSN50O.js} +1 -1
  67. package/web-app/dist/assets/{TeamsPage-CI3Wdbjw.js → TeamsPage-DGWK0LMp.js} +1 -1
  68. package/web-app/dist/assets/{TemplatesPage-DfhCgP6i.js → TemplatesPage-C-_10CwA.js} +1 -1
  69. package/web-app/dist/assets/{TerminalOutput-Cs87YXLJ.js → TerminalOutput-Cz_vPbXL.js} +1 -1
  70. package/web-app/dist/assets/{activity-CrQx5SbK.js → activity-icIsXJVa.js} +1 -1
  71. package/web-app/dist/assets/{bell-Dde5IbnI.js → bell-DbPR2_4r.js} +1 -1
  72. package/web-app/dist/assets/{bot-trRuIJT6.js → bot-DvjP3Yh5.js} +1 -1
  73. package/web-app/dist/assets/{check-DmsthR-3.js → check-D1OBxHeU.js} +1 -1
  74. package/web-app/dist/assets/{chevron-left-C5wZb7mg.js → chevron-left-D09jDZ29.js} +1 -1
  75. package/web-app/dist/assets/{circle-alert-BCb173fK.js → circle-alert-DyZMLOcC.js} +1 -1
  76. package/web-app/dist/assets/{clock-CvcqL3Ma.js → clock-CdpdJTId.js} +1 -1
  77. package/web-app/dist/assets/{cloud-CW1r3-AN.js → cloud-CoLTlmoj.js} +1 -1
  78. package/web-app/dist/assets/{code-xml-uxBTXAfZ.js → code-xml-DrkE2ZZY.js} +1 -1
  79. package/web-app/dist/assets/{database-DBrvW0o1.js → database-CMQQuM2R.js} +1 -1
  80. package/web-app/dist/assets/{dollar-sign-WrOyoDcN.js → dollar-sign-CuioDaOG.js} +1 -1
  81. package/web-app/dist/assets/{file-code-corner-8JC8TI2_.js → file-code-corner-Cx9j9K1S.js} +1 -1
  82. package/web-app/dist/assets/{file-plus-CgUdOr2u.js → file-plus-CYD2zaaI.js} +1 -1
  83. package/web-app/dist/assets/{globe-w-0x46nt.js → globe-6w-hnQkL.js} +1 -1
  84. package/web-app/dist/assets/{hammer-Civ6_wQq.js → hammer-BpqtautL.js} +1 -1
  85. package/web-app/dist/assets/{index-wvGJotyF.js → index-Dzs-9EgJ.js} +3 -3
  86. package/web-app/dist/assets/{layers-Cc3wLG39.js → layers-D5hd7jb7.js} +1 -1
  87. package/web-app/dist/assets/{loader-circle-BedHqfS4.js → loader-circle-C6Y9x_7H.js} +1 -1
  88. package/web-app/dist/assets/{lock-CRyzk3QG.js → lock-D51L7rYh.js} +1 -1
  89. package/web-app/dist/assets/{package-DCT1v7W2.js → package-BJF8hkP8.js} +1 -1
  90. package/web-app/dist/assets/{plus-C7eerOb_.js → plus-D5kUQ79n.js} +1 -1
  91. package/web-app/dist/assets/{refresh-cw-DO753Y_B.js → refresh-cw-BKdlCa7F.js} +1 -1
  92. package/web-app/dist/assets/{rotate-ccw-ptlyGV4I.js → rotate-ccw-_bd_YI1e.js} +1 -1
  93. package/web-app/dist/assets/{scroll-text-CqPwk4yI.js → scroll-text-DbkegIxF.js} +1 -1
  94. package/web-app/dist/assets/{server-BP_VvY9m.js → server-DAwqFIsq.js} +1 -1
  95. package/web-app/dist/assets/{shield-alert-Bf0waB0a.js → shield-alert-BuR1tPHu.js} +1 -1
  96. package/web-app/dist/assets/{thumbs-up-p1roSIsw.js → thumbs-up-BF6p3mRd.js} +1 -1
  97. package/web-app/dist/assets/{trash-2-BrMaXAxl.js → trash-2-BVpnkqqz.js} +1 -1
  98. package/web-app/dist/assets/{trending-down-DaGcCn2W.js → trending-down-DpsDvRwW.js} +1 -1
  99. package/web-app/dist/assets/{trending-up-CKPupOSE.js → trending-up-mFogTjt3.js} +1 -1
  100. package/web-app/dist/assets/{upload-CR0gzibW.js → upload-4T8zpBsi.js} +1 -1
  101. package/web-app/dist/assets/{usePolling-kKzJRjFw.js → usePolling-DPXQv8WO.js} +1 -1
  102. package/web-app/dist/assets/{user-CPOXPTtD.js → user-BYGIFaTm.js} +1 -1
  103. package/web-app/dist/index.html +1 -1
@@ -7618,6 +7618,12 @@ function withheldSentinel(fresh = false) {
7618
7618
  sentinel = null;
7619
7619
  return sentinel ??= `${SENTINEL_PREFIX}${process.pid}${randomBytes2(8).toString("hex")}INVALID`;
7620
7620
  }
7621
+ function removeConfigDir() {
7622
+ if (configDir)
7623
+ try {
7624
+ rmSync4(configDir, { recursive: true, force: true });
7625
+ } catch {}
7626
+ }
7621
7627
  function emptyGhConfigDir(env = process.env) {
7622
7628
  if (configDir && isDir(configDir))
7623
7629
  return configDir;
@@ -7630,11 +7636,14 @@ function emptyGhConfigDir(env = process.env) {
7630
7636
  }
7631
7637
  if (!exitHooked) {
7632
7638
  exitHooked = true;
7633
- process.once("exit", () => {
7634
- if (configDir)
7635
- try {
7636
- rmSync4(configDir, { recursive: true, force: true });
7637
- } catch {}
7639
+ process.once("exit", removeConfigDir);
7640
+ }
7641
+ if (!sigtermHooked) {
7642
+ sigtermHooked = true;
7643
+ process.prependListener("SIGTERM", () => {
7644
+ removeConfigDir();
7645
+ if (process.listenerCount("SIGTERM") === 1)
7646
+ process.exit(143);
7638
7647
  });
7639
7648
  }
7640
7649
  return configDir;
@@ -7654,10 +7663,13 @@ function hardenCredentialEnv(env, fresh = false) {
7654
7663
  env[`GIT_CONFIG_VALUE_${n2}`] = "";
7655
7664
  env["GIT_CONFIG_COUNT"] = String(n2 + 1);
7656
7665
  }
7666
+ const params = env["GIT_CONFIG_PARAMETERS"];
7667
+ if (params && !params.trimEnd().endsWith("'credential.helper'=''"))
7668
+ env["GIT_CONFIG_PARAMETERS"] = `${params} 'credential.helper'=''`;
7657
7669
  env["GIT_TERMINAL_PROMPT"] = "0";
7658
7670
  env["GCM_INTERACTIVE"] = "never";
7659
7671
  }
7660
- var GITHUB_TOKEN_VARS, SENTINEL_PREFIX = "ghp_LOKIWITHHELDsentinel", sentinel = null, configDir = null, exitHooked = false, isDir = (p) => {
7672
+ var GITHUB_TOKEN_VARS, SENTINEL_PREFIX = "ghp_LOKIWITHHELDsentinel", sentinel = null, configDir = null, exitHooked = false, sigtermHooked = false, isDir = (p) => {
7661
7673
  try {
7662
7674
  return !lstatSync4(p).isSymbolicLink() && statSync12(p).isDirectory();
7663
7675
  } catch {
@@ -9110,6 +9122,37 @@ var init_claude_model = __esm(() => {
9110
9122
  FAMILY_ALIASES = new Set(["haiku", "sonnet", "opus", "fable"]);
9111
9123
  });
9112
9124
 
9125
+ // ../../loki-ts/src/providers/price_table.ts
9126
+ var PRICE_TABLE;
9127
+ var init_price_table = __esm(() => {
9128
+ PRICE_TABLE = Object.freeze({
9129
+ "gpt-5.3-codex": { input: 1.75, output: 14, cachedInput: 0.175 },
9130
+ "gpt-5-codex": { input: 1.25, output: 10, cachedInput: 0.125 },
9131
+ "gpt-5": { input: 1.25, output: 10, cachedInput: 0.125 },
9132
+ "gpt-4.1": { input: 2, output: 8, cachedInput: 0.5 },
9133
+ "gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
9134
+ "o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 },
9135
+ o3: { input: 2, output: 8, cachedInput: 0.5 },
9136
+ "claude-opus": { input: 4, output: 20, cachedInput: 0.2 },
9137
+ "claude-sonnet": { input: 2, output: 10, cachedInput: 0.1 },
9138
+ "claude-haiku": { input: 0.1, output: 0.5, cachedInput: 0.01 },
9139
+ "gemini-2.5-pro": { input: 1.25, output: 10 },
9140
+ gemini: { input: 1.25, output: 10 },
9141
+ deepseek: { input: 0.28, output: 0.42 },
9142
+ minimax: { input: 0.3, output: 1.2 }
9143
+ });
9144
+ });
9145
+
9146
+ // ../../loki-ts/src/providers/provider_cost.ts
9147
+ var ESTIMATED_PROVIDERS;
9148
+ var init_provider_cost = __esm(() => {
9149
+ init_price_table();
9150
+ ESTIMATED_PROVIDERS = new Set(["codex", "cline", "aider"]);
9151
+ });
9152
+
9153
+ // ../../loki-ts/src/providers/model_routes.ts
9154
+ var init_model_routes = () => {};
9155
+
9113
9156
  // ../../loki-ts/src/engine10/hooks.ts
9114
9157
  var hooks;
9115
9158
  var init_hooks = __esm(() => {
@@ -9222,6 +9265,8 @@ function sumResultCosts(lokiRoot, iterations, context = []) {
9222
9265
  const unmetered = rec2["source"] === UNMETERED;
9223
9266
  if (unmetered)
9224
9267
  out2.unmetered = true;
9268
+ if (rec2["estimated"] === true)
9269
+ out2.estimated = true;
9225
9270
  const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
9226
9271
  if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
9227
9272
  out2.missing.push(iter);
@@ -9347,14 +9392,23 @@ var init_session_route = __esm(() => {
9347
9392
  init_unit_model();
9348
9393
  });
9349
9394
 
9395
+ // ../../loki-ts/src/providers/provider_guard.ts
9396
+ var init_provider_guard = __esm(() => {
9397
+ init_safe_git();
9398
+ });
9399
+
9350
9400
  // ../../loki-ts/src/engine10/session.ts
9351
9401
  var STDERR_TAIL_BYTES, ADVISOR_MARKED_STAGES;
9352
9402
  var init_session = __esm(() => {
9403
+ init_provider_cost();
9404
+ init_model_routes();
9353
9405
  init_cost();
9354
9406
  init_budget();
9355
9407
  init_hooks();
9356
9408
  init_session_route();
9357
9409
  init_safe_git();
9410
+ init_provider_guard();
9411
+ init_budget();
9358
9412
  STDERR_TAIL_BYTES = 64 * 1024;
9359
9413
  ADVISOR_MARKED_STAGES = new Set(["plan", "fix"]);
9360
9414
  });
@@ -11807,6 +11861,55 @@ var init_visual_evidence = __esm(() => {
11807
11861
  PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
11808
11862
  });
11809
11863
 
11864
+ // ../../loki-ts/src/council/role_agents.ts
11865
+ import { existsSync as existsSync27, readdirSync as readdirSync15, readFileSync as readFileSync33 } from "fs";
11866
+ import { homedir as homedir8 } from "os";
11867
+ import { join as join47 } from "path";
11868
+ function agentDirs(repoDir, env = process.env) {
11869
+ return [env["LOKI_AGENTS_PROJECT_DIR"] || join47(repoDir, ".loki", "agents"), env["LOKI_AGENTS_USER_DIR"] || join47(env["XDG_CONFIG_HOME"] || join47(homedir8(), ".config"), "loki", "agents")];
11870
+ }
11871
+ function parseAgent(file, stem2) {
11872
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(readFileSync33(file, "utf8"));
11873
+ if (!m)
11874
+ return null;
11875
+ const body = m[2].trim();
11876
+ if (!body)
11877
+ return null;
11878
+ const kv = (k) => new RegExp(`^${k}:[ \\t]*["']?([^"'\\r\\n#]*?)["']?[ \\t]*$`, "m").exec(m[1])?.[1]?.trim() ?? "";
11879
+ const role = (kv("role") || stem2).toLowerCase();
11880
+ if (!ROLES.includes(role))
11881
+ return null;
11882
+ return { name: kv("name") || stem2, role, provider: (kv("provider") || "claude").toLowerCase(), model: kv("model"), prompt: body.slice(0, 4000) };
11883
+ }
11884
+ function loadRoleAgent(repoDir, role, env = process.env) {
11885
+ for (const d of agentDirs(repoDir, env)) {
11886
+ if (!existsSync27(d))
11887
+ continue;
11888
+ let names;
11889
+ try {
11890
+ names = readdirSync15(d).filter((n2) => n2.endsWith(".md") && !n2.startsWith(".")).sort();
11891
+ } catch {
11892
+ continue;
11893
+ }
11894
+ const ordered = [...names.filter((n2) => n2 === `${role}.md`), ...names.filter((n2) => n2 !== `${role}.md`)];
11895
+ for (const n2 of ordered) {
11896
+ try {
11897
+ const a = parseAgent(join47(d, n2), n2.slice(0, -3));
11898
+ if (a && a.role === role)
11899
+ return a;
11900
+ } catch {}
11901
+ }
11902
+ }
11903
+ return null;
11904
+ }
11905
+ function loadReviewStageAgent(repoDir, env = process.env) {
11906
+ return loadRoleAgent(repoDir, "reviewer", env) ?? loadRoleAgent(repoDir, "council", env);
11907
+ }
11908
+ var ROLES;
11909
+ var init_role_agents = __esm(() => {
11910
+ ROLES = ["council", "reviewer"];
11911
+ });
11912
+
11810
11913
  // ../../loki-ts/src/util/shell.ts
11811
11914
  async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
11812
11915
  const reader = stream.getReader();
@@ -11879,8 +11982,8 @@ var init_shell = __esm(() => {
11879
11982
  });
11880
11983
 
11881
11984
  // ../../loki-ts/src/engine10/stages/xreview.ts
11882
- import { existsSync as existsSync27, readFileSync as readFileSync33 } from "fs";
11883
- import { join as join47 } from "path";
11985
+ import { existsSync as existsSync28, readFileSync as readFileSync34 } from "fs";
11986
+ import { join as join48 } from "path";
11884
11987
  function minVerdict(verdict, review) {
11885
11988
  const ceiling = review?.level === "block" ? "PARTIAL" : verdict;
11886
11989
  return RANK2[ceiling] < RANK2[verdict] ? ceiling : verdict;
@@ -11893,11 +11996,11 @@ function explicitReview(repoDir, env) {
11893
11996
  if (flag && isOff(ev))
11894
11997
  return "off";
11895
11998
  for (const f of ["loki.yaml", "loki.yml"]) {
11896
- const p = join47(repoDir, f);
11897
- if (!existsSync27(p))
11999
+ const p = join48(repoDir, f);
12000
+ if (!existsSync28(p))
11898
12001
  continue;
11899
12002
  try {
11900
- const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync33(p, "utf8"));
12003
+ const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync34(p, "utf8"));
11901
12004
  const v = asProvider(m?.[1]);
11902
12005
  if (v)
11903
12006
  return v;
@@ -11926,9 +12029,15 @@ function reviewReceipt(builder, xr, env = process.env) {
11926
12029
  const differs = !!xr && xr.level !== "not_run" && xr.provider !== builder && (builder === "claude" || builder === "codex");
11927
12030
  return { review: { provider: xr?.provider ?? null, vendor_differs: differs }, notProven: differs ? [] : ["judge shares builder vendor or none configured"] };
11928
12031
  }
11929
- function reviewArgv(p, prompt, env = process.env) {
12032
+ function reviewArgv(p, prompt, env = process.env, model) {
11930
12033
  const cli = reviewCli(p, env);
11931
- return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
12034
+ return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, ...model ? ["--model", model] : [], "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
12035
+ }
12036
+ function configuredReviewer(repoDir, env = process.env) {
12037
+ const a = loadReviewStageAgent(repoDir, env);
12038
+ if (!a)
12039
+ return null;
12040
+ return { prompt: a.prompt, model: a.provider === "claude" ? a.model : "", provider: asProvider(a.provider) };
11932
12041
  }
11933
12042
  function parseReview(p, out2) {
11934
12043
  const m = /^\s*VERDICT:\s*(PASS|FLAG|BLOCK)\b/im.exec(out2);
@@ -11941,7 +12050,8 @@ function parseReview(p, out2) {
11941
12050
  return { provider: p, level, notes };
11942
12051
  }
11943
12052
  async function crossReview(ctx, verdict, head) {
11944
- const p = reviewProvider(ctx.repoDir, process.env, ctx.provider);
12053
+ const agent = configuredReviewer(ctx.repoDir);
12054
+ const p = reviewProvider(ctx.repoDir, process.env, ctx.provider) ?? agent?.provider ?? null;
11945
12055
  if (!p)
11946
12056
  return null;
11947
12057
  if (verdict !== "VERIFIED" && verdict !== "ALREADY_SATISFIED")
@@ -11950,7 +12060,8 @@ async function crossReview(ctx, verdict, head) {
11950
12060
  if (!d || d.exitCode !== 0)
11951
12061
  return { provider: p, level: "not_run", notes: [`cross-review not run: diff unavailable for ${p}`] };
11952
12062
  const prompt = [
11953
- "You are an independent code reviewer. Review ONLY the diff below for correctness bugs, spec violations and missing tests. Do not modify files.",
12063
+ agent ? `${agent.prompt}
12064
+ Review ONLY the diff below. Do not modify files.` : "You are an independent code reviewer. Review ONLY the diff below for correctness bugs, spec violations and missing tests. Do not modify files.",
11954
12065
  "Reply with a first line exactly `VERDICT: PASS`, `VERDICT: FLAG` (concerns) or `VERDICT: BLOCK` (a reproduced defect), then one `- reason` line per concern.",
11955
12066
  "",
11956
12067
  "```diff",
@@ -11959,7 +12070,7 @@ async function crossReview(ctx, verdict, head) {
11959
12070
  ].join(`
11960
12071
  `);
11961
12072
  try {
11962
- const r = await run(reviewArgv(p, prompt), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
12073
+ const r = await run(reviewArgv(p, prompt, process.env, p === "claude" ? agent?.model : undefined), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
11963
12074
  if (r.exitCode !== 0)
11964
12075
  return { provider: p, level: "not_run", notes: [`cross-review not run: ${p} exited ${r.exitCode}`] };
11965
12076
  return parseReview(p, r.stdout);
@@ -11972,20 +12083,21 @@ var RANK2, TIMEOUT_MS = 300000, MAX_DIFF = 60000, MAX_NOTES = 5, asProvider = (v
11972
12083
  return s === "codex" || s === "claude" ? s : null;
11973
12084
  }, isOff = (v) => /^(off|none|false|0)$/i.test(v?.trim() ?? ""), xvendorDefault = (env = process.env) => /^(1|true|yes|on)$/i.test(env["LOKI_XVENDOR_DEFAULT"]?.trim() ?? ""), reviewCli = (p, env = process.env) => env[`LOKI_${p.toUpperCase()}_CLI`]?.trim() || p, clean3 = (s) => s.replace(/[\x00-\x1f\x7f`]+/g, " ").trim().slice(0, 300);
11974
12085
  var init_xreview = __esm(() => {
12086
+ init_role_agents();
11975
12087
  init_shell();
11976
12088
  init_safe_git();
11977
12089
  RANK2 = { FAILED: 0, SPEC_CONFLICT: 0, PARTIAL: 1, ALREADY_SATISFIED: 2, VERIFIED: 2 };
11978
12090
  });
11979
12091
 
11980
12092
  // ../../loki-ts/src/runner/router/route_record.ts
11981
- import { existsSync as existsSync28, lstatSync as lstatSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "fs";
11982
- import { join as join48 } from "path";
12093
+ import { existsSync as existsSync29, lstatSync as lstatSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
12094
+ import { join as join49 } from "path";
11983
12095
  function loadRouteRecord(runDir) {
11984
12096
  try {
11985
- const p = join48(runDir, ROUTE_RECORD_FILE);
11986
- if (!existsSync28(p) || !lstatSync11(p).isFile())
12097
+ const p = join49(runDir, ROUTE_RECORD_FILE);
12098
+ if (!existsSync29(p) || !lstatSync11(p).isFile())
11987
12099
  return;
11988
- const v = JSON.parse(readFileSync34(p, "utf8"));
12100
+ const v = JSON.parse(readFileSync35(p, "utf8"));
11989
12101
  return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
11990
12102
  } catch {
11991
12103
  return;
@@ -11995,7 +12107,7 @@ var ROUTE_RECORD_FILE = "route.json";
11995
12107
  var init_route_record = () => {};
11996
12108
 
11997
12109
  // ../../loki-ts/src/runner/router/history_store.ts
11998
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync35, renameSync as renameSync6, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
12110
+ import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync36, renameSync as renameSync6, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
11999
12111
  import { resolve as resolve11 } from "path";
12000
12112
  function validRun(v) {
12001
12113
  if (typeof v !== "object" || v === null)
@@ -12008,10 +12120,10 @@ function historyPath(key, cacheRoot) {
12008
12120
  }
12009
12121
  function readRunHistory(key, cacheRoot) {
12010
12122
  const path = historyPath(key, cacheRoot);
12011
- if (!existsSync29(path))
12123
+ if (!existsSync30(path))
12012
12124
  return [];
12013
12125
  try {
12014
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
12126
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
12015
12127
  const runs2 = parsed?.runs;
12016
12128
  return Array.isArray(runs2) ? runs2.filter(validRun) : [];
12017
12129
  } catch {
@@ -12045,7 +12157,7 @@ var init_history_store = __esm(() => {
12045
12157
  });
12046
12158
 
12047
12159
  // ../../loki-ts/src/runner/router/history.ts
12048
- import { dirname as dirname21, join as join49, resolve as resolve12 } from "path";
12160
+ import { dirname as dirname21, join as join50, resolve as resolve12 } from "path";
12049
12161
  import { fileURLToPath as fileURLToPath3 } from "url";
12050
12162
  function shapeKey(model) {
12051
12163
  if (!model || model.status !== "ok")
@@ -12123,8 +12235,8 @@ var init_cost_preview = __esm(() => {
12123
12235
  });
12124
12236
 
12125
12237
  // ../../loki-ts/src/features/contract.ts
12126
- import { existsSync as existsSync30, lstatSync as lstatSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "fs";
12127
- import { dirname as dirname22, join as join50 } from "path";
12238
+ import { existsSync as existsSync31, lstatSync as lstatSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync37, writeFileSync as writeFileSync18 } from "fs";
12239
+ import { dirname as dirname22, join as join51 } from "path";
12128
12240
  import { createHash as createHash11 } from "crypto";
12129
12241
  function contractEnabled(env = process.env) {
12130
12242
  return env["LOKI_CONTRACT"] !== "0";
@@ -12156,7 +12268,7 @@ function untracedLines(trace) {
12156
12268
  return trace.criteria.filter((c) => c.status === "no_match").map((c) => `contract ${sanitizeCriterion(c.id)} untraced (no keyword match in changed files): ${sanitizeCriterion(c.text)}`);
12157
12269
  }
12158
12270
  function contractPath(repoDir) {
12159
- return join50(repoDir, ".loki", "contract.json");
12271
+ return join51(repoDir, ".loki", "contract.json");
12160
12272
  }
12161
12273
  function readContract(repoDir) {
12162
12274
  const p = contractPath(repoDir);
@@ -12172,7 +12284,7 @@ function readContract(repoDir) {
12172
12284
  return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
12173
12285
  let raw2;
12174
12286
  try {
12175
- raw2 = readFileSync36(p);
12287
+ raw2 = readFileSync37(p);
12176
12288
  } catch {
12177
12289
  return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
12178
12290
  }
@@ -12241,8 +12353,8 @@ var init_contract = __esm(() => {
12241
12353
  });
12242
12354
 
12243
12355
  // ../../loki-ts/src/supply/supply_guard.ts
12244
- import { existsSync as existsSync31, readFileSync as readFileSync37, statSync as statSync16 } from "fs";
12245
- import { basename as basename17, join as join51 } from "path";
12356
+ import { existsSync as existsSync32, readFileSync as readFileSync38, statSync as statSync16 } from "fs";
12357
+ import { basename as basename17, join as join52 } from "path";
12246
12358
  function supplyEnabled(env) {
12247
12359
  return env["LOKI_SUPPLY_GUARD"] !== "0";
12248
12360
  }
@@ -12257,13 +12369,13 @@ function isManifestPath(path) {
12257
12369
  return MANIFESTS.test(basename17(path));
12258
12370
  }
12259
12371
  function readDeclared(repoDir) {
12260
- const p = join51(repoDir, ".loki", DECLARED_FILE);
12261
- if (!existsSync31(p))
12372
+ const p = join52(repoDir, ".loki", DECLARED_FILE);
12373
+ if (!existsSync32(p))
12262
12374
  return { deps: [], problem: null };
12263
12375
  try {
12264
12376
  if (statSync16(p).size > MAX_FILE_BYTES)
12265
12377
  return { deps: [], problem: "declaration file too large" };
12266
- const j = JSON.parse(readFileSync37(p, "utf8"));
12378
+ const j = JSON.parse(readFileSync38(p, "utf8"));
12267
12379
  if (!Array.isArray(j))
12268
12380
  return { deps: [], problem: "declaration is not a JSON array" };
12269
12381
  const deps = [];
@@ -12283,11 +12395,11 @@ function readDeclared(repoDir) {
12283
12395
  }
12284
12396
  }
12285
12397
  function readAllowlist(repoDir) {
12286
- const p = join51(repoDir, ".loki", "supply-allowlist");
12287
- if (!existsSync31(p))
12398
+ const p = join52(repoDir, ".loki", "supply-allowlist");
12399
+ if (!existsSync32(p))
12288
12400
  return new Set;
12289
12401
  try {
12290
- return new Set(readFileSync37(p, "utf8").split(`
12402
+ return new Set(readFileSync38(p, "utf8").split(`
12291
12403
  `).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean));
12292
12404
  } catch {
12293
12405
  return new Set;
@@ -12328,7 +12440,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
12328
12440
  if (eco === "npm") {
12329
12441
  if (privateUrlList([env["npm_config_registry"] ?? "", env["NPM_CONFIG_REGISTRY"] ?? ""]))
12330
12442
  return true;
12331
- return files(join51(repoDir, ".npmrc"), home ? join51(home, ".npmrc") : "").some((t) => t.split(`
12443
+ return files(join52(repoDir, ".npmrc"), home ? join52(home, ".npmrc") : "").some((t) => t.split(`
12332
12444
  `).some((l) => {
12333
12445
  const m = /^\s*(?:(@[^:\s]+):)?registry\s*=\s*(\S+)/.exec(l);
12334
12446
  return !!m && privateUrl(m[2]) && (m[1] === undefined || name.startsWith(`${m[1]}/`));
@@ -12337,7 +12449,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
12337
12449
  if (eco === "pypi") {
12338
12450
  if (privateUrlList([env["PIP_INDEX_URL"] ?? "", env["PIP_EXTRA_INDEX_URL"] ?? ""]))
12339
12451
  return true;
12340
- const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join51(home, ".config", "pip", "pip.conf") : "", home ? join51(home, ".pip", "pip.conf") : "", join51(repoDir, "pip.conf")];
12452
+ const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join52(home, ".config", "pip", "pip.conf") : "", home ? join52(home, ".pip", "pip.conf") : "", join52(repoDir, "pip.conf")];
12341
12453
  return files(...cfg).some((t) => t.split(`
12342
12454
  `).some((l) => {
12343
12455
  const m = /^\s*(?:extra-)?index-url\s*[=:]\s*(.+)$/.exec(l);
@@ -12347,7 +12459,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
12347
12459
  if (eco === "cargo") {
12348
12460
  if (Object.keys(env).some((k) => /^CARGO_REGISTRIES_.+_INDEX$/.test(k) || /^CARGO_SOURCE_/.test(k)))
12349
12461
  return true;
12350
- return files(join51(repoDir, ".cargo", "config.toml"), join51(repoDir, ".cargo", "config"), home ? join51(home, ".cargo", "config.toml") : "", home ? join51(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
12462
+ return files(join52(repoDir, ".cargo", "config.toml"), join52(repoDir, ".cargo", "config"), home ? join52(home, ".cargo", "config.toml") : "", home ? join52(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
12351
12463
  }
12352
12464
  if (eco === "go") {
12353
12465
  const gp = goEnv["GOPROXY"] ?? env["GOPROXY"] ?? "";
@@ -12470,7 +12582,7 @@ var MIN_AGE_DAYS = 7, DECLARED_FILE = "supply-declared.json", MAX_CHECKED = 50,
12470
12582
  }
12471
12583
  }, PUBLIC_HOSTS, hostOf = (u) => u.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^www\./, "").split(/[/?#\s]/)[0].replace(/^[^@]*@/, "").replace(/:\d+$/, ""), privateUrl = (u) => /[a-z0-9]/i.test(u) && !PUBLIC_HOSTS.has(hostOf(u)), readSmall = (p) => {
12472
12584
  try {
12473
- return statSync16(p).size <= MAX_FILE_BYTES ? readFileSync37(p, "utf8") : "";
12585
+ return statSync16(p).size <= MAX_FILE_BYTES ? readFileSync38(p, "utf8") : "";
12474
12586
  } catch {
12475
12587
  return "";
12476
12588
  }
@@ -12486,9 +12598,9 @@ var init_supply_guard = __esm(() => {
12486
12598
 
12487
12599
  // ../../loki-ts/src/engine10/stages/seal.ts
12488
12600
  import { createHash as createHash12, randomBytes as randomBytes4, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
12489
- import { chmodSync as chmodSync5, existsSync as existsSync32, readdirSync as readdirSync15, linkSync, mkdirSync as mkdirSync18, readFileSync as readFileSync38, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
12490
- import { homedir as homedir8 } from "os";
12491
- import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as join52, relative as relative9 } from "path";
12601
+ import { chmodSync as chmodSync5, existsSync as existsSync33, readdirSync as readdirSync16, linkSync, mkdirSync as mkdirSync18, readFileSync as readFileSync39, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
12602
+ import { homedir as homedir9 } from "os";
12603
+ import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as join53, relative as relative9 } from "path";
12492
12604
  function sanitizeReason(s) {
12493
12605
  const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
12494
12606
  const capped = collapsed.length > 500 ? `${collapsed.slice(0, 500)}...` : collapsed;
@@ -12516,9 +12628,9 @@ function loadSigningKey(generate = true) {
12516
12628
  let pem = inline ?? "";
12517
12629
  if (!inline) {
12518
12630
  const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
12519
- const file = given || join52(process.env["HOME"] || homedir8(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
12631
+ const file = given || join53(process.env["HOME"] || homedir9(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
12520
12632
  try {
12521
- pem = readFileSync38(file);
12633
+ pem = readFileSync39(file);
12522
12634
  if (!given) {
12523
12635
  for (const [f, m] of [[file, 384], [dirname23(file), 448]])
12524
12636
  if (statSync17(f).mode & 63)
@@ -12539,7 +12651,7 @@ function loadSigningKey(generate = true) {
12539
12651
  } finally {
12540
12652
  unlinkSync5(tmp);
12541
12653
  }
12542
- pem = readFileSync38(file);
12654
+ pem = readFileSync39(file);
12543
12655
  }
12544
12656
  }
12545
12657
  const k = createPrivateKey(pem);
@@ -12621,7 +12733,7 @@ function renderReceiptMd(r) {
12621
12733
  `);
12622
12734
  }
12623
12735
  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) => createHash12("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 !== "") : [], b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash12("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest()), receiptRel = (ctx) => {
12624
- const r = relative9(ctx.repoDir, join52(ctx.runDir, "receipt.json"));
12736
+ const r = relative9(ctx.repoDir, join53(ctx.runDir, "receipt.json"));
12625
12737
  return r.startsWith("..") || isAbsolute8(r) ? `.loki/runs/${ctx.runId}/receipt.json` : r;
12626
12738
  }, commitStage, sealStage;
12627
12739
  var init_seal = __esm(() => {
@@ -12724,7 +12836,7 @@ var init_seal = __esm(() => {
12724
12836
  if (wallNotRun > 0) {
12725
12837
  try {
12726
12838
  const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename18(String(f.path))));
12727
- for (const n2 of readdirSync15(join52(ctx.runDir, "wall")).sort())
12839
+ for (const n2 of readdirSync16(join53(ctx.runDir, "wall")).sort())
12728
12840
  if (!kept.has(n2))
12729
12841
  notProven.add(`wall test discarded: ${n2} (not_run)`);
12730
12842
  } catch {}
@@ -12783,19 +12895,19 @@ var init_seal = __esm(() => {
12783
12895
  for (const [s, d] of Object.entries(o))
12784
12896
  if (typeof d?.duration_s === "number")
12785
12897
  stages[s] = d.duration_s;
12786
- const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(join52(ctx.runDir, "events.jsonl"))) : { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages }, totalS = T?.reconciled(time) ?? null;
12898
+ const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(join53(ctx.runDir, "events.jsonl"))) : { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages }, totalS = T?.reconciled(time) ?? null;
12787
12899
  const iterIds = Object.values(o).flatMap((d) => [...strs2(d?.iteration_ids), ...strs2([d?.iteration_id])]);
12788
12900
  if (iterIds.length === 0)
12789
12901
  notProven.add("cost not measured (no iteration ids recorded)");
12790
12902
  const cost = ctx.cost.read(ctx.repoDir, iterIds);
12791
12903
  if (cost.unmetered)
12792
12904
  notProven.add("cost unmetered (CLI invoker; recorded as 0)");
12793
- const eventsPath = join52(ctx.runDir, "events.jsonl");
12905
+ const eventsPath = join53(ctx.runDir, "events.jsonl");
12794
12906
  const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
12795
12907
  const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
12796
12908
  if (wallFiles.length > 0 && wallPassed === null)
12797
12909
  notProven.add("wall result not recorded by verify");
12798
- const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(join52(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
12910
+ const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(join53(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
12799
12911
  if (routeBlock)
12800
12912
  for (const l of routeNotProven(routeBlock))
12801
12913
  notProven.add(l);
@@ -12837,7 +12949,7 @@ var init_seal = __esm(() => {
12837
12949
  provider: ctx.provider,
12838
12950
  model: ctx.model,
12839
12951
  resumed: o.intake?.resumed === true,
12840
- events_sha256: sha2562(existsSync32(eventsPath) ? readFileSync38(eventsPath) : ""),
12952
+ events_sha256: sha2562(existsSync33(eventsPath) ? readFileSync39(eventsPath) : ""),
12841
12953
  ...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
12842
12954
  log_seal: true,
12843
12955
  ...receiptBlock(process.env, cost.usd, cost.unmetered === true, totalS),
@@ -12858,10 +12970,10 @@ var init_seal = __esm(() => {
12858
12970
  }
12859
12971
  const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
12860
12972
  mkdirSync18(ctx.runDir, { recursive: true });
12861
- const path = join52(ctx.runDir, "receipt.json");
12973
+ const path = join53(ctx.runDir, "receipt.json");
12862
12974
  writeFileSync19(path, JSON.stringify(receipt, null, 2) + `
12863
12975
  `);
12864
- writeFileSync19(join52(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
12976
+ writeFileSync19(join53(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
12865
12977
  recordRun(process.env, ctx.repoDir, ctx.model, verdict, cost.usd, cost.unmetered === true, totalS);
12866
12978
  const signed = sig.jwt !== null;
12867
12979
  const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...mp ? { mutation_line: mp.line } : {}, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} };
@@ -12876,7 +12988,7 @@ var init_seal = __esm(() => {
12876
12988
 
12877
12989
  // ../../loki-ts/src/features/receipt_dsse.ts
12878
12990
  import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
12879
- import { readFileSync as readFileSync39 } from "fs";
12991
+ import { readFileSync as readFileSync40 } from "fs";
12880
12992
  function pae(payloadType, body) {
12881
12993
  const t = Buffer.from(payloadType, "utf8");
12882
12994
  return Buffer.concat([Buffer.from(`DSSEv1 ${t.length} `), t, Buffer.from(` ${body.length} `), body]);
@@ -12953,7 +13065,7 @@ function verifyDsseReceipt(env, d) {
12953
13065
  }
12954
13066
  function runIdGuard(receiptPath, runId) {
12955
13067
  try {
12956
- return envelopeRunIdProblem(JSON.parse(readFileSync39(receiptPath, "utf8")), runId);
13068
+ return envelopeRunIdProblem(JSON.parse(readFileSync40(receiptPath, "utf8")), runId);
12957
13069
  } catch {
12958
13070
  return null;
12959
13071
  }
@@ -12967,8 +13079,8 @@ var init_keys_cmd = __esm(() => {
12967
13079
  });
12968
13080
 
12969
13081
  // ../../loki-ts/src/engine10/verify_cmd.ts
12970
- import { existsSync as existsSync33, readdirSync as readdirSync16, readFileSync as readFileSync40, statSync as statSync18 } from "fs";
12971
- import { dirname as dirname24, join as join53 } from "path";
13082
+ import { existsSync as existsSync34, readdirSync as readdirSync17, readFileSync as readFileSync41, statSync as statSync18 } from "fs";
13083
+ import { dirname as dirname24, join as join54 } from "path";
12972
13084
  import { createHash as createHash13, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
12973
13085
  function pubFor(kid, given) {
12974
13086
  if (given)
@@ -12976,7 +13088,7 @@ function pubFor(kid, given) {
12976
13088
  const active = loadSigningKey(false);
12977
13089
  const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
12978
13090
  try {
12979
- return [createPublicKey4(readFileSync40(f))];
13091
+ return [createPublicKey4(readFileSync41(f))];
12980
13092
  } catch {
12981
13093
  return [];
12982
13094
  }
@@ -13008,10 +13120,10 @@ function checkEventLog(receiptPath, receipt) {
13008
13120
  const bound = receipt["events_sha256"];
13009
13121
  if (typeof bound !== "string" || bound === createHash13("sha256").digest("hex"))
13010
13122
  return null;
13011
- const path = join53(dirname24(receiptPath), "events.jsonl");
13012
- if (!existsSync33(path))
13123
+ const path = join54(dirname24(receiptPath), "events.jsonl");
13124
+ if (!existsSync34(path))
13013
13125
  return "events.jsonl is missing";
13014
- const raw2 = readFileSync40(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
13126
+ const raw2 = readFileSync41(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
13015
13127
  `).filter((l) => l.trim() !== "");
13016
13128
  if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
13017
13129
  return "events.jsonl has a forged, edited or missing line";
@@ -13027,7 +13139,7 @@ function checkEventLog(receiptPath, receipt) {
13027
13139
  return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
13028
13140
  }
13029
13141
  function checkLogSeal(receiptPath, hash, kid, given) {
13030
- const path = join53(dirname24(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
13142
+ const path = join54(dirname24(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
13031
13143
  if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
13032
13144
  return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
13033
13145
  if (done < 0)
@@ -13035,7 +13147,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
13035
13147
  const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
13036
13148
  if (!d)
13037
13149
  return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
13038
- const lines = readFileSync40(path, "utf8").split(`
13150
+ const lines = readFileSync41(path, "utf8").split(`
13039
13151
  `).filter((l) => l.trim() !== ""), sha3 = createHash13("sha256").update(lines.slice(0, done + 1).join(`
13040
13152
  `) + `
13041
13153
  `).digest("hex");
@@ -13045,11 +13157,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
13045
13157
  return null;
13046
13158
  }
13047
13159
  async function verifyReceipt(receiptPath, deps = {}) {
13048
- if (!existsSync33(receiptPath))
13160
+ if (!existsSync34(receiptPath))
13049
13161
  return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
13050
13162
  let receipt;
13051
13163
  try {
13052
- receipt = JSON.parse((deps.read ?? ((p) => readFileSync40(p, "utf8")))(receiptPath));
13164
+ receipt = JSON.parse((deps.read ?? ((p) => readFileSync41(p, "utf8")))(receiptPath));
13053
13165
  } catch {
13054
13166
  return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
13055
13167
  }
@@ -13104,8 +13216,8 @@ var init_verify_cmd = __esm(() => {
13104
13216
  });
13105
13217
 
13106
13218
  // src/server/app.ts
13107
- import { existsSync as existsSync35, readFileSync as readFileSync43, statSync as statSync19 } from "fs";
13108
- import { join as join56, resolve as resolve13, sep as sep14 } from "path";
13219
+ import { existsSync as existsSync36, readFileSync as readFileSync44, statSync as statSync19 } from "fs";
13220
+ import { join as join57, resolve as resolve13, sep as sep14 } from "path";
13109
13221
 
13110
13222
  // node_modules/hono/dist/request/constants.js
13111
13223
  var GET_MATCH_RESULT = Symbol();
@@ -23352,9 +23464,9 @@ function mount20(ctx) {
23352
23464
  }
23353
23465
 
23354
23466
  // src/server/routes/verify.ts
23355
- import { readFileSync as readFileSync41, realpathSync as realpathSync20 } from "fs";
23467
+ import { readFileSync as readFileSync42, realpathSync as realpathSync20 } from "fs";
23356
23468
  import { createPublicKey as createPublicKey5 } from "crypto";
23357
- import { join as join54, sep as sep13 } from "path";
23469
+ import { join as join55, sep as sep13 } from "path";
23358
23470
  init_verify_cmd();
23359
23471
  init_seal();
23360
23472
  init_receipt_dsse();
@@ -23381,11 +23493,11 @@ function mount21(ctx) {
23381
23493
  return done(404, "receipt not available", "not_found");
23382
23494
  let runDir, file;
23383
23495
  try {
23384
- const root = realpathSync20(join54(repo.p, ".loki", "runs"));
23385
- runDir = realpathSync20(join54(root, run2));
23386
- if (runDir !== join54(root, run2))
23496
+ const root = realpathSync20(join55(repo.p, ".loki", "runs"));
23497
+ runDir = realpathSync20(join55(root, run2));
23498
+ if (runDir !== join55(root, run2))
23387
23499
  return done(404, "receipt not found", "not_found");
23388
- file = realpathSync20(join54(runDir, "receipt.json"));
23500
+ file = realpathSync20(join55(runDir, "receipt.json"));
23389
23501
  } catch {
23390
23502
  return done(404, "receipt not found", "not_found");
23391
23503
  }
@@ -23404,7 +23516,7 @@ function mount21(ctx) {
23404
23516
  sha3 = r.receiptSha256 ?? null;
23405
23517
  }
23406
23518
  try {
23407
- outcome = outcomeOf(JSON.parse(readFileSync41(file, "utf8")));
23519
+ outcome = outcomeOf(JSON.parse(readFileSync42(file, "utf8")));
23408
23520
  } catch {
23409
23521
  outcome = "UNREADABLE";
23410
23522
  }
@@ -23466,8 +23578,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
23466
23578
  }
23467
23579
 
23468
23580
  // src/server/legacy/shim.ts
23469
- import { existsSync as existsSync34, readFileSync as readFileSync42 } from "fs";
23470
- import { join as join55 } from "path";
23581
+ import { existsSync as existsSync35, readFileSync as readFileSync43 } from "fs";
23582
+ import { join as join56 } from "path";
23471
23583
 
23472
23584
  // src/server/legacy/routes.ts
23473
23585
  var LEGACY_ROUTES = [
@@ -23839,10 +23951,10 @@ var logged = new Set;
23839
23951
  var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
23840
23952
  var NOT_SUPPORTED = "not yet supported by the Control Plane";
23841
23953
  var repoVersion = () => {
23842
- for (const f of [join55(import.meta.dir, "../../../../../VERSION")]) {
23954
+ for (const f of [join56(import.meta.dir, "../../../../../VERSION")]) {
23843
23955
  try {
23844
- if (existsSync34(f))
23845
- return readFileSync42(f, "utf8").trim();
23956
+ if (existsSync35(f))
23957
+ return readFileSync43(f, "utf8").trim();
23846
23958
  } catch {}
23847
23959
  }
23848
23960
  return "unknown";
@@ -24024,7 +24136,7 @@ function legacyShim(opts) {
24024
24136
 
24025
24137
  // src/server/app.ts
24026
24138
  var MAX_BODY2 = 1e6;
24027
- var defaultUiDir = () => [join56(import.meta.dir, "../../ui/dist"), join56(import.meta.dir, "../ui/dist")].find((d) => existsSync35(join56(d, "index.html")));
24139
+ var defaultUiDir = () => [join57(import.meta.dir, "../../ui/dist"), join57(import.meta.dir, "../ui/dist")].find((d) => existsSync36(join57(d, "index.html")));
24028
24140
  function createApp(opts) {
24029
24141
  const uiDir = opts.uiDir ?? defaultUiDir();
24030
24142
  const { db, sqlite } = openDb(opts.dbPath);
@@ -24053,10 +24165,10 @@ function createApp(opts) {
24053
24165
  await next();
24054
24166
  });
24055
24167
  const readInstalled = () => {
24056
- for (const f of [process.env.LOKI_VERSION_FILE, join56(import.meta.dir, "../../../../VERSION")]) {
24168
+ for (const f of [process.env.LOKI_VERSION_FILE, join57(import.meta.dir, "../../../../VERSION")]) {
24057
24169
  try {
24058
24170
  if (f)
24059
- return readFileSync43(f, "utf8").trim() || "unknown";
24171
+ return readFileSync44(f, "utf8").trim() || "unknown";
24060
24172
  } catch {}
24061
24173
  }
24062
24174
  return "unknown";
@@ -24167,11 +24279,11 @@ function createApp(opts) {
24167
24279
  return c.json({ error: "not found" }, 404);
24168
24280
  const root = resolve13(uiDir);
24169
24281
  const f = resolve13(root, `.${p}`);
24170
- if (f.startsWith(root + sep14) && existsSync35(f) && statSync19(f).isFile())
24282
+ if (f.startsWith(root + sep14) && existsSync36(f) && statSync19(f).isFile())
24171
24283
  return new Response(Bun.file(f));
24172
24284
  if (/\.[a-z0-9]+$/i.test(p))
24173
24285
  return c.json({ error: "not found" }, 404);
24174
- return new Response(Bun.file(join56(root, "index.html")));
24286
+ return new Response(Bun.file(join57(root, "index.html")));
24175
24287
  });
24176
24288
  return { app, db, close: () => {
24177
24289
  clearInterval(reconciler);