loki-mode 10.10.1 → 10.10.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 (74) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/dashboard/__init__.py +1 -1
  4. package/docs/CLI-REFERENCE.md +1 -1
  5. package/docs/INSTALLATION.md +1 -1
  6. package/docs/v10/FAILURE-CLASSES.md +5 -2
  7. package/docs/v10/GUARDS.md +18 -0
  8. package/loki-ts/dist/loki.js +570 -570
  9. package/mcp/__init__.py +1 -1
  10. package/package.json +1 -1
  11. package/packages/control-plane/dist/server.js +364 -125
  12. package/packages/control-plane/ui/dist/assets/index-BinL6h3i.css +1 -0
  13. package/packages/control-plane/ui/dist/assets/index-BrA22FUs.js +152 -0
  14. package/packages/control-plane/ui/dist/index.html +2 -2
  15. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  16. package/web-app/dist/assets/{AdminPage-B8RjG3sy.js → AdminPage-BvbxMtZO.js} +1 -1
  17. package/web-app/dist/assets/{Avatar-Dttmvqc-.js → Avatar-Dc7Scg02.js} +1 -1
  18. package/web-app/dist/assets/{Badge-Bvyp8VrD.js → Badge-BmVuOqbM.js} +1 -1
  19. package/web-app/dist/assets/{Button-DtY5ZrZw.js → Button-DvzqhxRU.js} +1 -1
  20. package/web-app/dist/assets/{CockpitPage-B2gbOwRp.js → CockpitPage-D8AZo7Ha.js} +1 -1
  21. package/web-app/dist/assets/{ComparePage-CHi5nZTU.js → ComparePage-NmqaTXDz.js} +1 -1
  22. package/web-app/dist/assets/{ErrorBoundary-BUuTj-OQ.js → ErrorBoundary-PGl8Kzoq.js} +1 -1
  23. package/web-app/dist/assets/{EvidenceReceiptPanel-C5aOn4ES.js → EvidenceReceiptPanel-2NY4n9gv.js} +1 -1
  24. package/web-app/dist/assets/{GitHubIssuesPanel-isVxxHXb.js → GitHubIssuesPanel-DBO_LHHM.js} +1 -1
  25. package/web-app/dist/assets/{GitHubPRsPanel-DtDpxyQl.js → GitHubPRsPanel-K7m9uJ1N.js} +1 -1
  26. package/web-app/dist/assets/{HomePage-Casf-LUB.js → HomePage-qNQzAJxM.js} +2 -2
  27. package/web-app/dist/assets/{LoginPage-DvB78691.js → LoginPage-esu3OK65.js} +1 -1
  28. package/web-app/dist/assets/{MagicPage-DU6hmbnC.js → MagicPage-CCdVvXrA.js} +1 -1
  29. package/web-app/dist/assets/{MetricsPage-DcPwzoS8.js → MetricsPage-bEvugU2t.js} +1 -1
  30. package/web-app/dist/assets/{NotFoundPage-Bs6WxAZ8.js → NotFoundPage-BlEjQ1wX.js} +1 -1
  31. package/web-app/dist/assets/{ProjectPage-RBIqC-DJ.js → ProjectPage-Bnug_Yzv.js} +3 -3
  32. package/web-app/dist/assets/{ProjectsPage-CogXD7jM.js → ProjectsPage-M8r1vfpn.js} +1 -1
  33. package/web-app/dist/assets/{SettingsPage-pAyJAfPt.js → SettingsPage-NqAJpB48.js} +1 -1
  34. package/web-app/dist/assets/{ShowcasePage-D6bT2bDd.js → ShowcasePage-8FggxL1P.js} +1 -1
  35. package/web-app/dist/assets/{SystemSettingsPage-CE8xiJRh.js → SystemSettingsPage-3Sz3kFOO.js} +1 -1
  36. package/web-app/dist/assets/{TeamsPage-wMS55TDT.js → TeamsPage-nV-6vdWM.js} +1 -1
  37. package/web-app/dist/assets/{TemplatesPage-COJNVQqu.js → TemplatesPage-Brbc_VR9.js} +1 -1
  38. package/web-app/dist/assets/{TerminalOutput-Cc2XASxd.js → TerminalOutput-D1yta5ac.js} +1 -1
  39. package/web-app/dist/assets/{activity-C6eHFDjL.js → activity-oEeKuqFe.js} +1 -1
  40. package/web-app/dist/assets/{bell-BJpVYj4n.js → bell-3NJtqBRR.js} +1 -1
  41. package/web-app/dist/assets/{bot-BwRVVUQL.js → bot-CuvJZ6x1.js} +1 -1
  42. package/web-app/dist/assets/{check-CGDwoKy8.js → check-n4289IZw.js} +1 -1
  43. package/web-app/dist/assets/{chevron-left-CF2Nilj6.js → chevron-left-CmQeio7h.js} +1 -1
  44. package/web-app/dist/assets/{circle-alert-D0RyRec1.js → circle-alert-CmmVW-Pd.js} +1 -1
  45. package/web-app/dist/assets/{clock-xeF3gW0W.js → clock-QFqk7GlA.js} +1 -1
  46. package/web-app/dist/assets/{cloud-D5Sy69ZZ.js → cloud-BNkSokmq.js} +1 -1
  47. package/web-app/dist/assets/{code-xml-C6-5Ql9I.js → code-xml-nWeketqj.js} +1 -1
  48. package/web-app/dist/assets/{database-BSjG1_z0.js → database-C83MXTmJ.js} +1 -1
  49. package/web-app/dist/assets/{dollar-sign-FnDR2QUu.js → dollar-sign-Blg6TO4y.js} +1 -1
  50. package/web-app/dist/assets/{file-code-corner-BiOK9vR6.js → file-code-corner-Tk11oUzm.js} +1 -1
  51. package/web-app/dist/assets/{file-plus-3WALQDEE.js → file-plus-DF_eyA6I.js} +1 -1
  52. package/web-app/dist/assets/{globe-vUdUrQqp.js → globe-D5hDqqmn.js} +1 -1
  53. package/web-app/dist/assets/{hammer-0VwHUEpa.js → hammer-C2hm9lzl.js} +1 -1
  54. package/web-app/dist/assets/{index-BMKIwteZ.js → index-pMxOIwXq.js} +3 -3
  55. package/web-app/dist/assets/{layers-C6q8kzST.js → layers-XWQKGXg0.js} +1 -1
  56. package/web-app/dist/assets/{loader-circle-B4Tz724d.js → loader-circle-BRuvorm2.js} +1 -1
  57. package/web-app/dist/assets/{lock-yxvEA65n.js → lock-BSGvQf5v.js} +1 -1
  58. package/web-app/dist/assets/{package-BpKD0Ot0.js → package-3FJYUYgJ.js} +1 -1
  59. package/web-app/dist/assets/{plus-Ibqx_Vo9.js → plus-CeIx6g9P.js} +1 -1
  60. package/web-app/dist/assets/{refresh-cw-C9auyvi-.js → refresh-cw-ChutW42g.js} +1 -1
  61. package/web-app/dist/assets/{rotate-ccw-2cEiXm6O.js → rotate-ccw-BdSjUAk-.js} +1 -1
  62. package/web-app/dist/assets/{scroll-text-BPAWML-w.js → scroll-text-BjFqxpw3.js} +1 -1
  63. package/web-app/dist/assets/{server-CZh6B1zx.js → server-C7UEKdol.js} +1 -1
  64. package/web-app/dist/assets/{shield-alert-CivolUIx.js → shield-alert-DY8fpEs7.js} +1 -1
  65. package/web-app/dist/assets/{thumbs-up-BP9covEH.js → thumbs-up-Cz1san1g.js} +1 -1
  66. package/web-app/dist/assets/{trash-2-BCDfTcNx.js → trash-2-D2MvGYeP.js} +1 -1
  67. package/web-app/dist/assets/{trending-down-5LypivXh.js → trending-down-CxO3OYOB.js} +1 -1
  68. package/web-app/dist/assets/{trending-up-DNt4SFdk.js → trending-up-DCHKPJLe.js} +1 -1
  69. package/web-app/dist/assets/{upload-DBmHbY6y.js → upload-CdPJpSHC.js} +1 -1
  70. package/web-app/dist/assets/{usePolling-DOPZcbUL.js → usePolling-DAH6K3ST.js} +1 -1
  71. package/web-app/dist/assets/{user-BQTJJDCG.js → user-Byyt01W4.js} +1 -1
  72. package/web-app/dist/index.html +1 -1
  73. package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +0 -1
  74. package/packages/control-plane/ui/dist/assets/index-NAMkcA9N.js +0 -165
@@ -6959,8 +6959,8 @@ var require_public_api = __commonJS((exports) => {
6959
6959
  });
6960
6960
 
6961
6961
  // src/server/app.ts
6962
- import { existsSync as existsSync19, statSync as statSync13 } from "fs";
6963
- import { join as join31, resolve as resolve6, sep as sep11 } from "path";
6962
+ import { existsSync as existsSync21, statSync as statSync14 } from "fs";
6963
+ import { join as join33, resolve as resolve6, sep as sep12 } from "path";
6964
6964
 
6965
6965
  // node_modules/hono/dist/request/constants.js
6966
6966
  var GET_MATCH_RESULT = Symbol();
@@ -16448,13 +16448,13 @@ data: ${JSON.stringify({ source: k.slice(0, i), run: k.slice(i + 1) })}
16448
16448
  }
16449
16449
 
16450
16450
  // src/server/routes/verify.ts
16451
- import { readFileSync as readFileSync25, realpathSync as realpathSync12 } from "fs";
16451
+ import { readFileSync as readFileSync26, realpathSync as realpathSync13 } from "fs";
16452
16452
  import { createPublicKey as createPublicKey5 } from "crypto";
16453
- import { join as join29, sep as sep10 } from "path";
16453
+ import { join as join31, sep as sep11 } from "path";
16454
16454
 
16455
16455
  // ../../loki-ts/src/engine10/verify_cmd.ts
16456
- import { existsSync as existsSync17, readdirSync as readdirSync12, readFileSync as readFileSync24, statSync as statSync12 } from "fs";
16457
- import { dirname as dirname12, join as join28 } from "path";
16456
+ import { existsSync as existsSync19, readdirSync as readdirSync12, readFileSync as readFileSync25, statSync as statSync13 } from "fs";
16457
+ import { dirname as dirname12, join as join30 } from "path";
16458
16458
  import { createHash as createHash10, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
16459
16459
 
16460
16460
  // ../../loki-ts/src/util/paths.ts
@@ -16479,9 +16479,9 @@ var REPO_ROOT = findRepoRoot();
16479
16479
 
16480
16480
  // ../../loki-ts/src/engine10/stages/seal.ts
16481
16481
  import { createHash as createHash9, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
16482
- import { chmodSync as chmodSync4, existsSync as existsSync16, readdirSync as readdirSync11, linkSync, mkdirSync as mkdirSync8, readFileSync as readFileSync22, statSync as statSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync9 } from "fs";
16482
+ import { chmodSync as chmodSync4, existsSync as existsSync18, readdirSync as readdirSync11, linkSync, mkdirSync as mkdirSync9, readFileSync as readFileSync23, statSync as statSync12, unlinkSync as unlinkSync4, writeFileSync as writeFileSync10 } from "fs";
16483
16483
  import { homedir as homedir6 } from "os";
16484
- import { basename as basename9, dirname as dirname11, join as join27 } from "path";
16484
+ import { basename as basename9, dirname as dirname11, join as join29 } from "path";
16485
16485
 
16486
16486
  // ../../loki-ts/src/e10ext/assert_delta.ts
16487
16487
  import { execFileSync as execFileSync3 } from "child_process";
@@ -17004,10 +17004,257 @@ async function run(argv, opts = {}) {
17004
17004
 
17005
17005
  // ../../loki-ts/src/features/visual_evidence.ts
17006
17006
  import { spawn as spawn5 } from "child_process";
17007
+
17008
+ // ../../loki-ts/src/project_model/discover.ts
17009
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync19, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
17010
+ import { join as join25 } from "path";
17011
+
17012
+ // ../../loki-ts/src/engine10/types.ts
17013
+ var STAGE_BUDGETS = {
17014
+ intake: { targetS: 15, limitS: 60 },
17015
+ plan: { targetS: 45, limitS: 90 },
17016
+ wall: { targetS: 45, limitS: 90 },
17017
+ implement: { targetS: 180, limitS: 480 },
17018
+ verify: { targetS: 60, limitS: 120 },
17019
+ fix: { targetS: 90, limitS: 180 },
17020
+ commit: { targetS: 5, limitS: 30 },
17021
+ seal: { targetS: 15, limitS: 60 },
17022
+ pr: { targetS: 15, limitS: 60 },
17023
+ deep: { targetS: null, limitS: 2700 }
17024
+ };
17025
+
17026
+ // ../../loki-ts/src/project_model/schema.ts
17027
+ import { existsSync as existsSync13, lstatSync as lstatSync5, realpathSync as realpathSync12, statSync as statSync10 } from "fs";
17028
+ import { isAbsolute, join as join24, normalize, sep as sep9 } from "path";
17029
+ var PROJECT_MODEL_SCHEMA = "loki.v10.project/1";
17030
+ var COMMAND_KINDS = ["test", "lint", "build", "start"];
17031
+ var isRec = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
17032
+ var isStr = (v) => typeof v === "string" && v.trim() !== "";
17033
+ function safeRel(p) {
17034
+ const n2 = normalize(p.trim()).split(sep9).join("/").replace(/\/$/, "");
17035
+ if (n2 === "" || isAbsolute(n2) || n2 === ".." || n2.startsWith("../"))
17036
+ return null;
17037
+ return n2;
17038
+ }
17039
+ function insideRepo(repoDir, rel) {
17040
+ try {
17041
+ const real2 = realpathSync12(join24(repoDir, rel));
17042
+ const base = realpathSync12(repoDir);
17043
+ return real2 === base || real2.startsWith(base + sep9);
17044
+ } catch {
17045
+ return false;
17046
+ }
17047
+ }
17048
+ function checkCitation(repoDir, raw2) {
17049
+ if (!isStr(raw2))
17050
+ return "citation is not a non-empty string";
17051
+ const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
17052
+ if (rel === null)
17053
+ return `citation "${raw2}" is not a repo-relative path`;
17054
+ const abs = join24(repoDir, rel);
17055
+ if (!existsSync13(abs) || !statSync10(abs).isFile() || !insideRepo(repoDir, rel))
17056
+ return `citation "${raw2}" names a file that does not exist`;
17057
+ return null;
17058
+ }
17059
+ function checkFingerprint(repoDir, raw2) {
17060
+ if (!isStr(raw2))
17061
+ return "fingerprint entry is not a non-empty string";
17062
+ const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
17063
+ if (rel === null)
17064
+ return `fingerprint "${raw2}" is not a repo-relative path`;
17065
+ try {
17066
+ if (!lstatSync5(join24(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
17067
+ return `fingerprint "${raw2}" is not a regular file inside the repo`;
17068
+ } catch {
17069
+ return `fingerprint "${raw2}" names a file that does not exist`;
17070
+ }
17071
+ return null;
17072
+ }
17073
+ function checkCites(repoDir, v, where, errs) {
17074
+ if (!Array.isArray(v) || v.length === 0) {
17075
+ errs.push(`${where}: needs at least one citation (a repo-relative file path)`);
17076
+ return [];
17077
+ }
17078
+ const out2 = [];
17079
+ for (const c of v) {
17080
+ const e = checkCitation(repoDir, c);
17081
+ if (e)
17082
+ errs.push(`${where}: ${e}`);
17083
+ else
17084
+ out2.push(String(c));
17085
+ }
17086
+ return out2;
17087
+ }
17088
+ function checkDir(repoDir, v, where, errs) {
17089
+ const rel = isStr(v) ? safeRel(v) : null;
17090
+ if (rel === null) {
17091
+ errs.push(`${where}: must be a repo-relative directory ("." for the repo root)`);
17092
+ return ".";
17093
+ }
17094
+ const abs = join24(repoDir, rel);
17095
+ if (!existsSync13(abs) || !statSync10(abs).isDirectory() || !insideRepo(repoDir, rel))
17096
+ errs.push(`${where}: directory "${rel}" does not exist`);
17097
+ return rel;
17098
+ }
17099
+ function checkCommand(repoDir, v, where, errs) {
17100
+ if (v === null || v === undefined)
17101
+ return null;
17102
+ if (!isRec(v) || !isStr(v.cmd)) {
17103
+ errs.push(`${where}: must be null or {cmd, cwd, cite}`);
17104
+ return null;
17105
+ }
17106
+ const cwd = checkDir(repoDir, v.cwd, `${where}.cwd`, errs);
17107
+ return { cmd: v.cmd.trim(), cwd, cite: checkCites(repoDir, v.cite, `${where}.cite`, errs) };
17108
+ }
17109
+ function validateAnswer(repoDir, raw2) {
17110
+ const errs = [];
17111
+ if (!isRec(raw2))
17112
+ return { ok: false, errors: ["answer must be a JSON object"] };
17113
+ if (!isStr(raw2.workspaceKind))
17114
+ errs.push("workspaceKind: required non-empty string");
17115
+ const workspaceCite = checkCites(repoDir, raw2.workspaceCite, "workspaceCite", errs);
17116
+ if (!Array.isArray(raw2.packages))
17117
+ errs.push("packages: required array");
17118
+ const packages = [];
17119
+ for (const [i, p] of (Array.isArray(raw2.packages) ? raw2.packages : []).entries()) {
17120
+ const w = `packages[${i}]`;
17121
+ if (!isRec(p) || !isStr(p.name)) {
17122
+ errs.push(`${w}: must be an object with a name`);
17123
+ continue;
17124
+ }
17125
+ const root = checkDir(repoDir, p.root, `${w}.root`, errs);
17126
+ const cmds = isRec(p.commands) ? p.commands : {};
17127
+ if (!isRec(p.commands))
17128
+ errs.push(`${w}.commands: required object with keys ${COMMAND_KINDS.join(", ")} (each null or a command)`);
17129
+ const commands = {};
17130
+ for (const k of COMMAND_KINDS)
17131
+ commands[k] = checkCommand(repoDir, cmds[k], `${w}.commands.${k}`, errs);
17132
+ const ui = isRec(p.ui) ? p.ui : {};
17133
+ if (!isRec(p.ui) || typeof ui.present !== "boolean")
17134
+ errs.push(`${w}.ui: required {present: boolean, boot: command|null, cite}`);
17135
+ packages.push({
17136
+ name: p.name.trim(),
17137
+ root,
17138
+ runner: isStr(p.runner) ? p.runner.trim() : null,
17139
+ commands,
17140
+ ui: { present: ui.present === true, boot: checkCommand(repoDir, ui.boot, `${w}.ui.boot`, errs), cite: checkCites(repoDir, ui.cite, `${w}.ui.cite`, errs) },
17141
+ cite: checkCites(repoDir, p.cite, `${w}.cite`, errs)
17142
+ });
17143
+ }
17144
+ if (!Array.isArray(raw2.fingerprintFiles))
17145
+ errs.push("fingerprintFiles: required array of the manifest and lockfile paths");
17146
+ const fingerprintFiles = (Array.isArray(raw2.fingerprintFiles) ? raw2.fingerprintFiles : []).filter((f) => {
17147
+ const e = checkFingerprint(repoDir, f);
17148
+ if (e)
17149
+ errs.push(`fingerprintFiles: ${e}`);
17150
+ return e === null;
17151
+ }).map((f) => String(f).replace(/:\d+(?:-\d+)?$/, ""));
17152
+ if (errs.length > 0)
17153
+ return { ok: false, errors: errs };
17154
+ return { ok: true, model: { schema: PROJECT_MODEL_SCHEMA, status: "ok", workspaceKind: String(raw2.workspaceKind).trim(), workspaceCite, packages, fingerprintFiles } };
17155
+ }
17156
+ function unknownModel(key, reason, expiresAt) {
17157
+ return { schema: PROJECT_MODEL_SCHEMA, status: "unknown", key, workspaceKind: "unknown", workspaceCite: [], packages: [], fingerprintFiles: [], reason, ...expiresAt === undefined ? {} : { expiresAt } };
17158
+ }
17159
+ function parseCached(repoDir, raw2, now) {
17160
+ if (!isRec(raw2) || raw2.schema !== PROJECT_MODEL_SCHEMA || typeof raw2.key !== "string")
17161
+ return null;
17162
+ if (raw2.status === "unknown")
17163
+ return typeof raw2.expiresAt === "number" && raw2.expiresAt > now ? unknownModel(raw2.key, typeof raw2.reason === "string" ? raw2.reason : "cached unknown", raw2.expiresAt) : null;
17164
+ if (raw2.status !== "ok")
17165
+ return null;
17166
+ const v = validateAnswer(repoDir, raw2);
17167
+ return v.ok ? { ...v.model, key: raw2.key } : null;
17168
+ }
17169
+
17170
+ // ../../loki-ts/src/project_model/gather.ts
17171
+ var GATHER_CAPS = {
17172
+ maxListFiles: 20000,
17173
+ maxDepth: 3,
17174
+ maxFileBytes: 16 * 1024,
17175
+ maxFiles: 60,
17176
+ maxTotalBytes: 96 * 1024,
17177
+ maxTreeLines: 300,
17178
+ maxHashBytes: 16 * 1024 * 1024
17179
+ };
17180
+
17181
+ // ../../loki-ts/src/project_model/discover.ts
17182
+ function projectModelEnabled(env = process.env) {
17183
+ return env["LOKI_E10_PROJECT_MODEL"] !== "0";
17184
+ }
17185
+ var PROJECT_FILE = ".loki/project.json";
17186
+ var UNKNOWN_TTL_MS = 60 * 60 * 1000;
17187
+ function loadCached(repoDir, now = Date.now()) {
17188
+ try {
17189
+ return parseCached(repoDir, JSON.parse(readFileSync19(join25(repoDir, PROJECT_FILE), "utf8")), now);
17190
+ } catch {
17191
+ return null;
17192
+ }
17193
+ }
17194
+
17195
+ // ../../loki-ts/src/project_model/api.ts
17196
+ import { posix } from "path";
17197
+ var clean = (p) => typeof p === "string" ? posix.normalize(p.replace(/\\/g, "/")).replace(/^\.\//, "") : "";
17198
+ var isRec2 = (v) => typeof v === "object" && v !== null;
17199
+ var wellFormed = (p) => isRec2(p) && typeof p.root === "string" && typeof p.name === "string";
17200
+ function projectApi(model) {
17201
+ const known = isRec2(model) && model.status === "ok";
17202
+ const packages = known && Array.isArray(model.packages) ? model.packages.filter(wellFormed) : [];
17203
+ const packageOf = (file) => {
17204
+ const f = clean(file);
17205
+ if (f === "")
17206
+ return null;
17207
+ let best = null;
17208
+ for (const p of packages) {
17209
+ const hit = p.root === "." || f === p.root || f.startsWith(`${p.root}/`);
17210
+ if (hit && (best === null || p.root !== "." && (best.root === "." || p.root.length > best.root.length)))
17211
+ best = p;
17212
+ }
17213
+ return best;
17214
+ };
17215
+ return {
17216
+ model,
17217
+ known: () => known,
17218
+ packages: () => packages,
17219
+ workspaceKind: () => isRec2(model) && typeof model.workspaceKind === "string" ? model.workspaceKind : "unknown",
17220
+ packageRootOf: (file) => packageOf(file)?.root ?? null,
17221
+ packageOf,
17222
+ relativeToRoot: (file) => {
17223
+ const p = packageOf(file);
17224
+ return p ? p.root === "." ? clean(file) : clean(file).slice(p.root.length + 1) : null;
17225
+ },
17226
+ commandFor: (pkg, kind) => (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.commands?.[kind] ?? null,
17227
+ hasUI: () => packages.some((p) => isRec2(p.ui) && p.ui.present === true),
17228
+ uiBoot: () => {
17229
+ for (const p of packages)
17230
+ if (isRec2(p.ui) && p.ui.present === true && isRec2(p.ui.boot))
17231
+ return { pkg: p, boot: p.ui.boot };
17232
+ return null;
17233
+ }
17234
+ };
17235
+ }
17236
+
17237
+ // ../../loki-ts/src/project_model/resolve.ts
17238
+ function loadProjectApi(repoDir, env = process.env) {
17239
+ if (!projectModelEnabled(env))
17240
+ return null;
17241
+ try {
17242
+ const model = loadCached(repoDir);
17243
+ const api = model ? projectApi(model) : null;
17244
+ return api && api.known() ? api : null;
17245
+ } catch {
17246
+ return null;
17247
+ }
17248
+ }
17249
+ function isMultiRoot(api) {
17250
+ return !!api && api.known() && api.packages().some((p) => p.root !== ".");
17251
+ }
17252
+
17253
+ // ../../loki-ts/src/features/visual_evidence.ts
17007
17254
  import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
17008
- import { existsSync as existsSync13, lstatSync as lstatSync5, mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync7 } from "fs";
17255
+ import { existsSync as existsSync15, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readdirSync as readdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
17009
17256
  import { createServer } from "net";
17010
- import { dirname as dirname8, isAbsolute, join as join24, normalize, relative as relative3, sep as sep9 } from "path";
17257
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join26, normalize as normalize2, relative as relative3, sep as sep10 } from "path";
17011
17258
  var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0";
17012
17259
  var PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
17013
17260
  var isPageFile = (p) => PAGE_RE.test(p);
@@ -17017,12 +17264,12 @@ function routeFor(file) {
17017
17264
  return `/${r}`.replace(/\/+/g, "/");
17018
17265
  }
17019
17266
  var screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index";
17020
- var sha256File = (p) => createHash6("sha256").update(readFileSync19(p)).digest("hex");
17267
+ var sha256File = (p) => createHash6("sha256").update(readFileSync20(p)).digest("hex");
17021
17268
  function hashScreens(runDir, rels) {
17022
17269
  const out2 = [];
17023
17270
  for (const rel of rels) {
17024
17271
  try {
17025
- out2.push({ path: rel, sha256: sha256File(join24(runDir, rel)) });
17272
+ out2.push({ path: rel, sha256: sha256File(join26(runDir, rel)) });
17026
17273
  } catch {}
17027
17274
  }
17028
17275
  return out2;
@@ -17033,16 +17280,16 @@ function checkScreens(runDir, screens) {
17033
17280
  for (const s of screens) {
17034
17281
  if (typeof s?.path !== "string" || typeof s.sha256 !== "string")
17035
17282
  return "evidence_screens entry is malformed";
17036
- if (isAbsolute(s.path) || normalize(s.path).startsWith(".."))
17283
+ if (isAbsolute2(s.path) || normalize2(s.path).startsWith(".."))
17037
17284
  return `evidence screenshot path escapes the evidence dir: ${s.path}`;
17038
- const file = join24(runDir, s.path);
17285
+ const file = join26(runDir, s.path);
17039
17286
  let cur = runDir;
17040
- for (const seg of normalize(s.path).split(sep9)) {
17041
- cur = join24(cur, seg);
17287
+ for (const seg of normalize2(s.path).split(sep10)) {
17288
+ cur = join26(cur, seg);
17042
17289
  if (isSymlink(cur))
17043
17290
  return `evidence screenshot path is a symlink: ${s.path}`;
17044
17291
  }
17045
- if (!existsSync13(file))
17292
+ if (!existsSync15(file))
17046
17293
  return `evidence screenshot is missing: ${s.path}`;
17047
17294
  if (!isRegularFile(file))
17048
17295
  return `evidence screenshot is not a regular file: ${s.path}`;
@@ -17084,16 +17331,16 @@ async function waitUp(url, ms, signal) {
17084
17331
  return false;
17085
17332
  }
17086
17333
  var playwrightBin = (repoDir) => {
17087
- const bin = join24(repoDir, "node_modules", ".bin", "playwright");
17088
- return existsSync13(bin) ? bin : null;
17334
+ const bin = join26(repoDir, "node_modules", ".bin", "playwright");
17335
+ return existsSync15(bin) ? bin : null;
17089
17336
  };
17090
17337
  function openapiPaths(repoDir) {
17091
17338
  for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
17092
- const p = join24(repoDir, f);
17093
- if (!existsSync13(p))
17339
+ const p = join26(repoDir, f);
17340
+ if (!existsSync15(p))
17094
17341
  continue;
17095
17342
  try {
17096
- const txt = readFileSync19(p, "utf8");
17343
+ const txt = readFileSync20(p, "utf8");
17097
17344
  const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
17098
17345
  return paths.filter((x) => !x.includes("{")).slice(0, 10);
17099
17346
  } catch {
@@ -17103,8 +17350,8 @@ function openapiPaths(repoDir) {
17103
17350
  return [];
17104
17351
  }
17105
17352
  var playwrightTestPkg = (repoDir) => {
17106
- const d = join24(repoDir, "node_modules", "@playwright", "test");
17107
- return existsSync13(join24(d, "package.json")) ? d : null;
17353
+ const d = join26(repoDir, "node_modules", "@playwright", "test");
17354
+ return existsSync15(join26(d, "package.json")) ? d : null;
17108
17355
  };
17109
17356
  var e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
17110
17357
  test("loki e2e walkthrough", async ({ page }) => {
@@ -17116,24 +17363,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
17116
17363
  if (!pw || !pkg)
17117
17364
  return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
17118
17365
  try {
17119
- const dir = join24(runDir, relDir, "e2e");
17120
- mkdirSync6(dir, { recursive: true });
17121
- writeFileSync7(join24(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
17122
- writeFileSync7(join24(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
17366
+ const dir = join26(runDir, relDir, "e2e");
17367
+ mkdirSync7(dir, { recursive: true });
17368
+ writeFileSync8(join26(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
17369
+ writeFileSync8(join26(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
17123
17370
  `);
17124
- if (!await shoot(pw, ["test", "--config", join24(dir, "playwright.config.cjs")], repoDir, ms, signal))
17371
+ if (!await shoot(pw, ["test", "--config", join26(dir, "playwright.config.cjs")], repoDir, ms, signal))
17125
17372
  return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
17126
17373
  const rels = [];
17127
17374
  const walk = (d) => {
17128
17375
  for (const e of readdirSync9(d, { withFileTypes: true })) {
17129
- const full = join24(d, e.name);
17376
+ const full = join26(d, e.name);
17130
17377
  if (e.isDirectory())
17131
17378
  walk(full);
17132
17379
  else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
17133
- rels.push(join24(relDir, "e2e", relative3(dir, full)));
17380
+ rels.push(join26(relDir, "e2e", relative3(dir, full)));
17134
17381
  }
17135
17382
  };
17136
- walk(join24(dir, "out"));
17383
+ walk(join26(dir, "out"));
17137
17384
  return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
17138
17385
  } catch (e) {
17139
17386
  return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
@@ -17171,14 +17418,14 @@ function shoot(pw, args, cwd, ms, signal) {
17171
17418
  }
17172
17419
  var isSymlink = (p) => {
17173
17420
  try {
17174
- return lstatSync5(p).isSymbolicLink();
17421
+ return lstatSync6(p).isSymbolicLink();
17175
17422
  } catch {
17176
17423
  return false;
17177
17424
  }
17178
17425
  };
17179
17426
  var isRegularFile = (p) => {
17180
17427
  try {
17181
- return lstatSync5(p).isFile();
17428
+ return lstatSync6(p).isFile();
17182
17429
  } catch {
17183
17430
  return false;
17184
17431
  }
@@ -17226,24 +17473,30 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
17226
17473
  try {
17227
17474
  if (!visualEvidenceEnabled(opts.env))
17228
17475
  return skip("LOKI_VISUAL_EVIDENCE is 0");
17229
- const pkgPath = join24(repoDir, "package.json");
17230
- if (!existsSync13(pkgPath))
17231
- return skip("no package.json");
17232
- const script = pickScript(JSON.parse(readFileSync19(pkgPath, "utf8")));
17233
- if (!script)
17234
- return skip("no dev, preview or start script");
17476
+ const api = loadProjectApi(repoDir), boot = isMultiRoot(api) ? api.uiBoot() : null;
17477
+ const appDir = boot ? join26(repoDir, boot.boot.cwd) : repoDir;
17478
+ const pkgPath = join26(appDir, "package.json");
17479
+ let script = null;
17480
+ if (!boot) {
17481
+ if (!existsSync15(pkgPath))
17482
+ return skip("no package.json");
17483
+ script = pickScript(JSON.parse(readFileSync20(pkgPath, "utf8")));
17484
+ if (!script)
17485
+ return skip("no dev, preview or start script");
17486
+ }
17235
17487
  const pages = changed.filter(isPageFile);
17236
17488
  const explicitOn = (opts.env ?? process.env)["LOKI_VISUAL_EVIDENCE"] === "1";
17237
17489
  const apiPaths = pages.length === 0 && explicitOn ? openapiPaths(repoDir) : [];
17238
17490
  if (pages.length === 0 && apiPaths.length === 0)
17239
17491
  return skip(explicitOn ? "no changed page files and no openapi routes" : "no changed page files (set LOKI_VISUAL_EVIDENCE=1 for the API transcript)");
17240
- const pw = pages.length > 0 ? playwrightBin(repoDir) : null;
17492
+ const pw = pages.length > 0 ? playwrightBin(appDir) ?? playwrightBin(repoDir) : null;
17241
17493
  if (pages.length > 0 && !pw)
17242
17494
  return skip("playwright not resolvable in the repo");
17243
17495
  const budget = opts.budgetMs ?? DEFAULT_BUDGET_MS, end = Date.now() + budget, left = () => end - Date.now();
17244
17496
  const aborted = () => signal?.aborted === true;
17245
17497
  const port = await freePort(), base = `http://127.0.0.1:${port}`;
17246
- child = spawn5("npm", ["run", script, "--silent"], { cwd: repoDir, detached: true, env: { ...opts.env ?? process.env, PORT: String(port), HOST: "127.0.0.1", BROWSER: "none" }, stdio: "ignore" });
17498
+ const [bc, ba] = boot ? ["bash", ["-c", boot.boot.cmd]] : ["npm", ["run", script, "--silent"]];
17499
+ child = spawn5(bc, [...ba], { cwd: appDir, detached: true, env: { ...opts.env ?? process.env, PORT: String(port), HOST: "127.0.0.1", BROWSER: "none" }, stdio: "ignore" });
17247
17500
  child.on("error", () => {
17248
17501
  return;
17249
17502
  });
@@ -17254,26 +17507,26 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
17254
17507
  }
17255
17508
  if (!await waitUp(base, left(), signal))
17256
17509
  return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
17257
- const evRoot = join24(runDir, "evidence");
17510
+ const evRoot = join26(runDir, "evidence");
17258
17511
  if (isSymlink(evRoot))
17259
17512
  return skip("evidence dir is a symlink");
17260
- mkdirSync6(evRoot, { recursive: true });
17513
+ mkdirSync7(evRoot, { recursive: true });
17261
17514
  if (pages.length > 0) {
17262
- const rel0 = join24("evidence", randomBytes2(6).toString("hex"));
17263
- mkdirSync6(join24(runDir, rel0));
17515
+ const rel0 = join26("evidence", randomBytes2(6).toString("hex"));
17516
+ mkdirSync7(join26(runDir, rel0));
17264
17517
  const rels = [];
17265
17518
  for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
17266
17519
  if (aborted() || left() <= 0)
17267
17520
  break;
17268
- const rel = join24(rel0, `${screenName(route)}.png`);
17269
- const ok = await shoot(pw, ["screenshot", `${base}${route}`, join24(runDir, rel)], repoDir, left(), signal);
17270
- if (ok && !aborted() && !isSymlink(join24(runDir, rel)) && existsSync13(join24(runDir, rel)))
17521
+ const rel = join26(rel0, `${screenName(route)}.png`);
17522
+ const ok = await shoot(pw, ["screenshot", `${base}${route}`, join26(runDir, rel)], appDir, left(), signal);
17523
+ if (ok && !aborted() && !isSymlink(join26(runDir, rel)) && existsSync15(join26(runDir, rel)))
17271
17524
  rels.push(rel);
17272
17525
  }
17273
17526
  if (aborted())
17274
17527
  return skip("aborted");
17275
17528
  const routes = [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES);
17276
- const media = rels.length > 0 && left() > 0 ? await recordE2eMedia(repoDir, runDir, rel0, base, routes, left(), signal) : { rels: [], skipped: null };
17529
+ const media = rels.length > 0 && left() > 0 ? await recordE2eMedia(appDir, runDir, rel0, base, routes, left(), signal) : { rels: [], skipped: null };
17277
17530
  if (aborted())
17278
17531
  return skip("aborted");
17279
17532
  const screens = hashScreens(runDir, [...rels, ...media.rels]);
@@ -17292,9 +17545,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
17292
17545
  }
17293
17546
  if (aborted())
17294
17547
  return skip("aborted");
17295
- const hdir = join24(evRoot, randomBytes2(6).toString("hex"));
17296
- mkdirSync6(hdir);
17297
- writeFileSync7(join24(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
17548
+ const hdir = join26(evRoot, randomBytes2(6).toString("hex"));
17549
+ mkdirSync7(hdir);
17550
+ writeFileSync8(join26(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
17298
17551
  `);
17299
17552
  return { screens: [], http: true, skipped: null };
17300
17553
  } catch (e) {
@@ -17320,23 +17573,9 @@ function receiptScreensProblem(receiptPath, receipt) {
17320
17573
  return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname8(receiptPath), receipt["evidence_screens"]);
17321
17574
  }
17322
17575
 
17323
- // ../../loki-ts/src/engine10/types.ts
17324
- var STAGE_BUDGETS = {
17325
- intake: { targetS: 15, limitS: 60 },
17326
- plan: { targetS: 45, limitS: 90 },
17327
- wall: { targetS: 45, limitS: 90 },
17328
- implement: { targetS: 180, limitS: 480 },
17329
- verify: { targetS: 60, limitS: 120 },
17330
- fix: { targetS: 90, limitS: 180 },
17331
- commit: { targetS: 5, limitS: 30 },
17332
- seal: { targetS: 15, limitS: 60 },
17333
- pr: { targetS: 15, limitS: 60 },
17334
- deep: { targetS: null, limitS: 2700 }
17335
- };
17336
-
17337
17576
  // ../../loki-ts/src/features/contract.ts
17338
- import { existsSync as existsSync14, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
17339
- import { dirname as dirname9, join as join25 } from "path";
17577
+ import { existsSync as existsSync16, lstatSync as lstatSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync9 } from "fs";
17578
+ import { dirname as dirname9, join as join27 } from "path";
17340
17579
  import { createHash as createHash7 } from "crypto";
17341
17580
  var MAX_CRITERIA = 50;
17342
17581
  var MAX_TEXT = 500;
@@ -17371,14 +17610,14 @@ function untracedLines(trace) {
17371
17610
  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)}`);
17372
17611
  }
17373
17612
  function contractPath(repoDir) {
17374
- return join25(repoDir, ".loki", "contract.json");
17613
+ return join27(repoDir, ".loki", "contract.json");
17375
17614
  }
17376
17615
  var MAX_CONTRACT_BYTES = 1024 * 1024;
17377
17616
  function readContract(repoDir) {
17378
17617
  const p = contractPath(repoDir);
17379
17618
  let st;
17380
17619
  try {
17381
- st = lstatSync6(p);
17620
+ st = lstatSync7(p);
17382
17621
  } catch {
17383
17622
  return { contract: null, notes: [], sha256: null };
17384
17623
  }
@@ -17388,7 +17627,7 @@ function readContract(repoDir) {
17388
17627
  return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
17389
17628
  let raw2;
17390
17629
  try {
17391
- raw2 = readFileSync20(p);
17630
+ raw2 = readFileSync21(p);
17392
17631
  } catch {
17393
17632
  return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
17394
17633
  }
@@ -17423,7 +17662,7 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
17423
17662
  const live = readContract(repoDir);
17424
17663
  let exists2 = true;
17425
17664
  try {
17426
- lstatSync6(contractPath(repoDir));
17665
+ lstatSync7(contractPath(repoDir));
17427
17666
  } catch {
17428
17667
  exists2 = false;
17429
17668
  }
@@ -17452,18 +17691,18 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
17452
17691
 
17453
17692
  // ../../loki-ts/src/features/speed/seal_group.ts
17454
17693
  import { createHash as createHash8 } from "crypto";
17455
- import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync21, statSync as statSync10 } from "fs";
17456
- import { dirname as dirname10, join as join26 } from "path";
17694
+ import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync22, statSync as statSync11 } from "fs";
17695
+ import { dirname as dirname10, join as join28 } from "path";
17457
17696
  var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
17458
17697
  var PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
17459
17698
  var sha2 = (b) => createHash8("sha256").update(b).digest("hex");
17460
17699
  var isObj9 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
17461
17700
  var text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "";
17462
17701
  var num2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
17463
- var unitsDir = (runDir) => join26(runDir, "group", "units");
17702
+ var unitsDir = (runDir) => join28(runDir, "group", "units");
17464
17703
  var readJson3 = (p) => {
17465
17704
  try {
17466
- return JSON.parse(readFileSync21(p, "utf8"));
17705
+ return JSON.parse(readFileSync22(p, "utf8"));
17467
17706
  } catch {
17468
17707
  return;
17469
17708
  }
@@ -17476,8 +17715,8 @@ var dirNames = (d) => {
17476
17715
  }
17477
17716
  };
17478
17717
  function sealGroup(runDir, rh) {
17479
- const manifestPath = join26(runDir, "group", "manifest.json");
17480
- if (!existsSync15(manifestPath))
17718
+ const manifestPath = join28(runDir, "group", "manifest.json");
17719
+ if (!existsSync17(manifestPath))
17481
17720
  return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
17482
17721
  const notProven = [];
17483
17722
  let problems = 0;
@@ -17501,10 +17740,10 @@ function sealGroup(runDir, rh) {
17501
17740
  return fail(`unit ${index2} has an invalid, duplicate or case-colliding id`);
17502
17741
  seen.add(id);
17503
17742
  seenLower.add(id.toLowerCase());
17504
- const dir = join26(unitsDir(runDir), id), rp = join26(dir, "receipt.json"), ep = join26(dir, "events.jsonl");
17743
+ const dir = join28(unitsDir(runDir), id), rp = join28(dir, "receipt.json"), ep = join28(dir, "events.jsonl");
17505
17744
  let bytes;
17506
17745
  try {
17507
- bytes = readFileSync21(rp);
17746
+ bytes = readFileSync22(rp);
17508
17747
  } catch {
17509
17748
  return fail(`unit ${id} sub-receipt missing`);
17510
17749
  }
@@ -17521,7 +17760,7 @@ function sealGroup(runDir, rh) {
17521
17760
  unit_id: id,
17522
17761
  sub_receipt_sha256: sha2(bytes),
17523
17762
  receipt_sha256: sub["receipt_sha256"],
17524
- events_file_sha256: existsSync15(ep) ? sha2(readFileSync21(ep)) : null,
17763
+ events_file_sha256: existsSync17(ep) ? sha2(readFileSync22(ep)) : null,
17525
17764
  verdict,
17526
17765
  goal: text2(u["goal"], 200),
17527
17766
  files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
@@ -17546,7 +17785,7 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
17546
17785
  const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
17547
17786
  const g = receipt["group"];
17548
17787
  if (g === undefined)
17549
- return existsSync15(join26(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
17788
+ return existsSync17(join28(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
17550
17789
  if (!isObj9(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
17551
17790
  return bad("group section malformed");
17552
17791
  const entries = g["units"];
@@ -17558,20 +17797,20 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
17558
17797
  return bad("unit directories do not match the receipt (missing or extra unit)");
17559
17798
  let unchecked = null;
17560
17799
  for (let i = 0;i < entries.length; i++) {
17561
- const e = entries[i], id = ids[i], dir = join26(unitsDir(runDir), id), rp = join26(dir, "receipt.json"), ep = join26(dir, "events.jsonl");
17800
+ const e = entries[i], id = ids[i], dir = join28(unitsDir(runDir), id), rp = join28(dir, "receipt.json"), ep = join28(dir, "events.jsonl");
17562
17801
  if (e["index"] !== i)
17563
17802
  return bad(`unit ${id} is out of order (index ${String(e["index"])} at position ${i})`);
17564
17803
  let bytes;
17565
17804
  try {
17566
- if (!statSync10(dir).isDirectory())
17805
+ if (!statSync11(dir).isDirectory())
17567
17806
  return bad(`unit ${id} is not a directory`);
17568
- bytes = readFileSync21(rp);
17807
+ bytes = readFileSync22(rp);
17569
17808
  } catch {
17570
17809
  return bad(`unit ${id} sub-receipt missing`);
17571
17810
  }
17572
17811
  if (sha2(bytes) !== e["sub_receipt_sha256"])
17573
17812
  return bad(`unit ${id} sub-receipt does not match its recorded hash`);
17574
- const evSha = existsSync15(ep) ? sha2(readFileSync21(ep)) : null;
17813
+ const evSha = existsSync17(ep) ? sha2(readFileSync22(ep)) : null;
17575
17814
  if (evSha !== (e["events_file_sha256"] ?? null))
17576
17815
  return bad(`unit ${id} events.jsonl does not match its recorded hash`);
17577
17816
  const sub = readJson3(rp);
@@ -17626,21 +17865,21 @@ function loadSigningKey(generate = true) {
17626
17865
  let pem = inline ?? "";
17627
17866
  if (!inline) {
17628
17867
  const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
17629
- const file = given || join27(process.env["HOME"] || homedir6(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
17868
+ const file = given || join29(process.env["HOME"] || homedir6(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
17630
17869
  try {
17631
- pem = readFileSync22(file);
17870
+ pem = readFileSync23(file);
17632
17871
  if (!given) {
17633
17872
  for (const [f, m] of [[file, 384], [dirname11(file), 448]])
17634
- if (statSync11(f).mode & 63)
17873
+ if (statSync12(f).mode & 63)
17635
17874
  chmodSync4(f, m);
17636
17875
  }
17637
17876
  } catch (e) {
17638
17877
  if (e.code !== "ENOENT" || !generate)
17639
17878
  return null;
17640
- mkdirSync8(dirname11(dirname11(file)), { recursive: true, mode: 448 });
17641
- mkdirSync8(dirname11(file), { recursive: true, mode: 448 });
17879
+ mkdirSync9(dirname11(dirname11(file)), { recursive: true, mode: 448 });
17880
+ mkdirSync9(dirname11(file), { recursive: true, mode: 448 });
17642
17881
  const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
17643
- writeFileSync9(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
17882
+ writeFileSync10(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
17644
17883
  try {
17645
17884
  linkSync(tmp, file);
17646
17885
  } catch (l) {
@@ -17649,7 +17888,7 @@ function loadSigningKey(generate = true) {
17649
17888
  } finally {
17650
17889
  unlinkSync4(tmp);
17651
17890
  }
17652
- pem = readFileSync22(file);
17891
+ pem = readFileSync23(file);
17653
17892
  }
17654
17893
  }
17655
17894
  const k = createPrivateKey(pem);
@@ -17776,7 +18015,7 @@ var sealStage = {
17776
18015
  if (wallNotRun > 0) {
17777
18016
  try {
17778
18017
  const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename9(String(f.path))));
17779
- for (const n2 of readdirSync11(join27(ctx.runDir, "wall")).sort())
18018
+ for (const n2 of readdirSync11(join29(ctx.runDir, "wall")).sort())
17780
18019
  if (!kept.has(n2))
17781
18020
  notProven.add(`wall test discarded: ${n2} (not_run)`);
17782
18021
  } catch {}
@@ -17836,7 +18075,7 @@ var sealStage = {
17836
18075
  const cost = ctx.cost.read(ctx.repoDir, iterIds);
17837
18076
  if (cost.unmetered)
17838
18077
  notProven.add("cost unmetered (CLI invoker; recorded as 0)");
17839
- const eventsPath = join27(ctx.runDir, "events.jsonl");
18078
+ const eventsPath = join29(ctx.runDir, "events.jsonl");
17840
18079
  const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
17841
18080
  const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
17842
18081
  if (wallFiles.length > 0 && wallPassed === null)
@@ -17872,7 +18111,7 @@ var sealStage = {
17872
18111
  provider: ctx.provider,
17873
18112
  model: ctx.model,
17874
18113
  resumed: o.intake?.resumed === true,
17875
- events_sha256: sha2562(existsSync16(eventsPath) ? readFileSync22(eventsPath) : ""),
18114
+ events_sha256: sha2562(existsSync18(eventsPath) ? readFileSync23(eventsPath) : ""),
17876
18115
  ...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
17877
18116
  log_seal: true
17878
18117
  };
@@ -17886,11 +18125,11 @@ var sealStage = {
17886
18125
  hash = receiptSha256(body);
17887
18126
  }
17888
18127
  const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
17889
- mkdirSync8(ctx.runDir, { recursive: true });
17890
- const path = join27(ctx.runDir, "receipt.json");
17891
- writeFileSync9(path, JSON.stringify(receipt, null, 2) + `
18128
+ mkdirSync9(ctx.runDir, { recursive: true });
18129
+ const path = join29(ctx.runDir, "receipt.json");
18130
+ writeFileSync10(path, JSON.stringify(receipt, null, 2) + `
17892
18131
  `);
17893
- writeFileSync9(join27(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
18132
+ writeFileSync10(join29(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
17894
18133
  const signed = sig.jwt !== null;
17895
18134
  const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven };
17896
18135
  ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven });
@@ -17900,7 +18139,7 @@ var sealStage = {
17900
18139
 
17901
18140
  // ../../loki-ts/src/features/receipt_dsse.ts
17902
18141
  import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
17903
- import { readFileSync as readFileSync23 } from "fs";
18142
+ import { readFileSync as readFileSync24 } from "fs";
17904
18143
  var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
17905
18144
  var STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
17906
18145
  var PREDICATE_TYPE = "https://autonomi.dev/loki/receipt/v10";
@@ -17981,7 +18220,7 @@ function verifyDsseReceipt(env, d) {
17981
18220
  }
17982
18221
  function runIdGuard(receiptPath, runId) {
17983
18222
  try {
17984
- return envelopeRunIdProblem(JSON.parse(readFileSync23(receiptPath, "utf8")), runId);
18223
+ return envelopeRunIdProblem(JSON.parse(readFileSync24(receiptPath, "utf8")), runId);
17985
18224
  } catch {
17986
18225
  return null;
17987
18226
  }
@@ -17995,7 +18234,7 @@ function pubFor(kid, given) {
17995
18234
  const active = loadSigningKey(false);
17996
18235
  const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
17997
18236
  try {
17998
- return [createPublicKey4(readFileSync24(f))];
18237
+ return [createPublicKey4(readFileSync25(f))];
17999
18238
  } catch {
18000
18239
  return [];
18001
18240
  }
@@ -18027,10 +18266,10 @@ function checkEventLog(receiptPath, receipt) {
18027
18266
  const bound = receipt["events_sha256"];
18028
18267
  if (typeof bound !== "string" || bound === createHash10("sha256").digest("hex"))
18029
18268
  return null;
18030
- const path = join28(dirname12(receiptPath), "events.jsonl");
18031
- if (!existsSync17(path))
18269
+ const path = join30(dirname12(receiptPath), "events.jsonl");
18270
+ if (!existsSync19(path))
18032
18271
  return "events.jsonl is missing";
18033
- const raw2 = readFileSync24(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
18272
+ const raw2 = readFileSync25(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
18034
18273
  `).filter((l) => l.trim() !== "");
18035
18274
  if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
18036
18275
  return "events.jsonl has a forged, edited or missing line";
@@ -18046,7 +18285,7 @@ function checkEventLog(receiptPath, receipt) {
18046
18285
  return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
18047
18286
  }
18048
18287
  function checkLogSeal(receiptPath, hash, kid, given) {
18049
- const path = join28(dirname12(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
18288
+ const path = join30(dirname12(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
18050
18289
  if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
18051
18290
  return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
18052
18291
  if (done < 0)
@@ -18054,7 +18293,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
18054
18293
  const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
18055
18294
  if (!d)
18056
18295
  return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
18057
- const lines = readFileSync24(path, "utf8").split(`
18296
+ const lines = readFileSync25(path, "utf8").split(`
18058
18297
  `).filter((l) => l.trim() !== ""), sha3 = createHash10("sha256").update(lines.slice(0, done + 1).join(`
18059
18298
  `) + `
18060
18299
  `).digest("hex");
@@ -18064,11 +18303,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
18064
18303
  return null;
18065
18304
  }
18066
18305
  async function verifyReceipt(receiptPath, deps = {}) {
18067
- if (!existsSync17(receiptPath))
18306
+ if (!existsSync19(receiptPath))
18068
18307
  return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
18069
18308
  let receipt;
18070
18309
  try {
18071
- receipt = JSON.parse((deps.read ?? ((p) => readFileSync24(p, "utf8")))(receiptPath));
18310
+ receipt = JSON.parse((deps.read ?? ((p) => readFileSync25(p, "utf8")))(receiptPath));
18072
18311
  } catch {
18073
18312
  return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
18074
18313
  }
@@ -18136,15 +18375,15 @@ function mount19(ctx) {
18136
18375
  return done(404, "receipt not available", "not_found");
18137
18376
  let runDir, file;
18138
18377
  try {
18139
- const root = realpathSync12(join29(repo.p, ".loki", "runs"));
18140
- runDir = realpathSync12(join29(root, run2));
18141
- if (runDir !== join29(root, run2))
18378
+ const root = realpathSync13(join31(repo.p, ".loki", "runs"));
18379
+ runDir = realpathSync13(join31(root, run2));
18380
+ if (runDir !== join31(root, run2))
18142
18381
  return done(404, "receipt not found", "not_found");
18143
- file = realpathSync12(join29(runDir, "receipt.json"));
18382
+ file = realpathSync13(join31(runDir, "receipt.json"));
18144
18383
  } catch {
18145
18384
  return done(404, "receipt not found", "not_found");
18146
18385
  }
18147
- if (!file.startsWith(runDir + sep10))
18386
+ if (!file.startsWith(runDir + sep11))
18148
18387
  return done(404, "receipt not found", "not_found");
18149
18388
  let integrity, reasons, sha3 = null, outcome = "UNREADABLE";
18150
18389
  try {
@@ -18159,7 +18398,7 @@ function mount19(ctx) {
18159
18398
  sha3 = r.receiptSha256 ?? null;
18160
18399
  }
18161
18400
  try {
18162
- outcome = outcomeOf(JSON.parse(readFileSync25(file, "utf8")));
18401
+ outcome = outcomeOf(JSON.parse(readFileSync26(file, "utf8")));
18163
18402
  } catch {
18164
18403
  outcome = "UNREADABLE";
18165
18404
  }
@@ -18221,8 +18460,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now = Date.now) {
18221
18460
  }
18222
18461
 
18223
18462
  // src/server/legacy/shim.ts
18224
- import { existsSync as existsSync18, readFileSync as readFileSync26 } from "fs";
18225
- import { join as join30 } from "path";
18463
+ import { existsSync as existsSync20, readFileSync as readFileSync27 } from "fs";
18464
+ import { join as join32 } from "path";
18226
18465
 
18227
18466
  // src/server/legacy/routes.ts
18228
18467
  var LEGACY_ROUTES = [
@@ -18594,10 +18833,10 @@ var logged = new Set;
18594
18833
  var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
18595
18834
  var NOT_SUPPORTED = "not yet supported by the Control Plane";
18596
18835
  var repoVersion = () => {
18597
- for (const f of [join30(import.meta.dir, "../../../../../VERSION")]) {
18836
+ for (const f of [join32(import.meta.dir, "../../../../../VERSION")]) {
18598
18837
  try {
18599
- if (existsSync18(f))
18600
- return readFileSync26(f, "utf8").trim();
18838
+ if (existsSync20(f))
18839
+ return readFileSync27(f, "utf8").trim();
18601
18840
  } catch {}
18602
18841
  }
18603
18842
  return "unknown";
@@ -18779,7 +19018,7 @@ function legacyShim(opts) {
18779
19018
 
18780
19019
  // src/server/app.ts
18781
19020
  var MAX_BODY2 = 1e6;
18782
- var defaultUiDir = () => [join31(import.meta.dir, "../../ui/dist"), join31(import.meta.dir, "../ui/dist")].find((d) => existsSync19(join31(d, "index.html")));
19021
+ var defaultUiDir = () => [join33(import.meta.dir, "../../ui/dist"), join33(import.meta.dir, "../ui/dist")].find((d) => existsSync21(join33(d, "index.html")));
18783
19022
  function createApp(opts) {
18784
19023
  const uiDir = opts.uiDir ?? defaultUiDir();
18785
19024
  const { db, sqlite } = openDb(opts.dbPath);
@@ -18905,11 +19144,11 @@ function createApp(opts) {
18905
19144
  return c.json({ error: "not found" }, 404);
18906
19145
  const root = resolve6(uiDir);
18907
19146
  const f = resolve6(root, `.${p}`);
18908
- if (f.startsWith(root + sep11) && existsSync19(f) && statSync13(f).isFile())
19147
+ if (f.startsWith(root + sep12) && existsSync21(f) && statSync14(f).isFile())
18909
19148
  return new Response(Bun.file(f));
18910
19149
  if (/\.[a-z0-9]+$/i.test(p))
18911
19150
  return c.json({ error: "not found" }, 404);
18912
- return new Response(Bun.file(join31(root, "index.html")));
19151
+ return new Response(Bun.file(join33(root, "index.html")));
18913
19152
  });
18914
19153
  return { app, db, close: () => sqlite.close() };
18915
19154
  }