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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/FAILURE-CLASSES.md +5 -2
- package/docs/v10/GUARDS.md +18 -0
- package/loki-ts/dist/loki.js +570 -570
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/packages/control-plane/dist/server.js +364 -125
- package/packages/control-plane/ui/dist/assets/index-BinL6h3i.css +1 -0
- package/packages/control-plane/ui/dist/assets/index-BrA22FUs.js +152 -0
- package/packages/control-plane/ui/dist/index.html +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-B8RjG3sy.js → AdminPage-BvbxMtZO.js} +1 -1
- package/web-app/dist/assets/{Avatar-Dttmvqc-.js → Avatar-Dc7Scg02.js} +1 -1
- package/web-app/dist/assets/{Badge-Bvyp8VrD.js → Badge-BmVuOqbM.js} +1 -1
- package/web-app/dist/assets/{Button-DtY5ZrZw.js → Button-DvzqhxRU.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-B2gbOwRp.js → CockpitPage-D8AZo7Ha.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CHi5nZTU.js → ComparePage-NmqaTXDz.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BUuTj-OQ.js → ErrorBoundary-PGl8Kzoq.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-C5aOn4ES.js → EvidenceReceiptPanel-2NY4n9gv.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-isVxxHXb.js → GitHubIssuesPanel-DBO_LHHM.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-DtDpxyQl.js → GitHubPRsPanel-K7m9uJ1N.js} +1 -1
- package/web-app/dist/assets/{HomePage-Casf-LUB.js → HomePage-qNQzAJxM.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DvB78691.js → LoginPage-esu3OK65.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DU6hmbnC.js → MagicPage-CCdVvXrA.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DcPwzoS8.js → MetricsPage-bEvugU2t.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-Bs6WxAZ8.js → NotFoundPage-BlEjQ1wX.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-RBIqC-DJ.js → ProjectPage-Bnug_Yzv.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CogXD7jM.js → ProjectsPage-M8r1vfpn.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-pAyJAfPt.js → SettingsPage-NqAJpB48.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-D6bT2bDd.js → ShowcasePage-8FggxL1P.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CE8xiJRh.js → SystemSettingsPage-3Sz3kFOO.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-wMS55TDT.js → TeamsPage-nV-6vdWM.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-COJNVQqu.js → TemplatesPage-Brbc_VR9.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-Cc2XASxd.js → TerminalOutput-D1yta5ac.js} +1 -1
- package/web-app/dist/assets/{activity-C6eHFDjL.js → activity-oEeKuqFe.js} +1 -1
- package/web-app/dist/assets/{bell-BJpVYj4n.js → bell-3NJtqBRR.js} +1 -1
- package/web-app/dist/assets/{bot-BwRVVUQL.js → bot-CuvJZ6x1.js} +1 -1
- package/web-app/dist/assets/{check-CGDwoKy8.js → check-n4289IZw.js} +1 -1
- package/web-app/dist/assets/{chevron-left-CF2Nilj6.js → chevron-left-CmQeio7h.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D0RyRec1.js → circle-alert-CmmVW-Pd.js} +1 -1
- package/web-app/dist/assets/{clock-xeF3gW0W.js → clock-QFqk7GlA.js} +1 -1
- package/web-app/dist/assets/{cloud-D5Sy69ZZ.js → cloud-BNkSokmq.js} +1 -1
- package/web-app/dist/assets/{code-xml-C6-5Ql9I.js → code-xml-nWeketqj.js} +1 -1
- package/web-app/dist/assets/{database-BSjG1_z0.js → database-C83MXTmJ.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-FnDR2QUu.js → dollar-sign-Blg6TO4y.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BiOK9vR6.js → file-code-corner-Tk11oUzm.js} +1 -1
- package/web-app/dist/assets/{file-plus-3WALQDEE.js → file-plus-DF_eyA6I.js} +1 -1
- package/web-app/dist/assets/{globe-vUdUrQqp.js → globe-D5hDqqmn.js} +1 -1
- package/web-app/dist/assets/{hammer-0VwHUEpa.js → hammer-C2hm9lzl.js} +1 -1
- package/web-app/dist/assets/{index-BMKIwteZ.js → index-pMxOIwXq.js} +3 -3
- package/web-app/dist/assets/{layers-C6q8kzST.js → layers-XWQKGXg0.js} +1 -1
- package/web-app/dist/assets/{loader-circle-B4Tz724d.js → loader-circle-BRuvorm2.js} +1 -1
- package/web-app/dist/assets/{lock-yxvEA65n.js → lock-BSGvQf5v.js} +1 -1
- package/web-app/dist/assets/{package-BpKD0Ot0.js → package-3FJYUYgJ.js} +1 -1
- package/web-app/dist/assets/{plus-Ibqx_Vo9.js → plus-CeIx6g9P.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-C9auyvi-.js → refresh-cw-ChutW42g.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-2cEiXm6O.js → rotate-ccw-BdSjUAk-.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BPAWML-w.js → scroll-text-BjFqxpw3.js} +1 -1
- package/web-app/dist/assets/{server-CZh6B1zx.js → server-C7UEKdol.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CivolUIx.js → shield-alert-DY8fpEs7.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BP9covEH.js → thumbs-up-Cz1san1g.js} +1 -1
- package/web-app/dist/assets/{trash-2-BCDfTcNx.js → trash-2-D2MvGYeP.js} +1 -1
- package/web-app/dist/assets/{trending-down-5LypivXh.js → trending-down-CxO3OYOB.js} +1 -1
- package/web-app/dist/assets/{trending-up-DNt4SFdk.js → trending-up-DCHKPJLe.js} +1 -1
- package/web-app/dist/assets/{upload-DBmHbY6y.js → upload-CdPJpSHC.js} +1 -1
- package/web-app/dist/assets/{usePolling-DOPZcbUL.js → usePolling-DAH6K3ST.js} +1 -1
- package/web-app/dist/assets/{user-BQTJJDCG.js → user-Byyt01W4.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +0 -1
- 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
|
|
6963
|
-
import { join as
|
|
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
|
|
16451
|
+
import { readFileSync as readFileSync26, realpathSync as realpathSync13 } from "fs";
|
|
16452
16452
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
16453
|
-
import { join as
|
|
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
|
|
16457
|
-
import { dirname as dirname12, join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 (
|
|
17283
|
+
if (isAbsolute2(s.path) || normalize2(s.path).startsWith(".."))
|
|
17037
17284
|
return `evidence screenshot path escapes the evidence dir: ${s.path}`;
|
|
17038
|
-
const file =
|
|
17285
|
+
const file = join26(runDir, s.path);
|
|
17039
17286
|
let cur = runDir;
|
|
17040
|
-
for (const seg of
|
|
17041
|
-
cur =
|
|
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 (!
|
|
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 =
|
|
17088
|
-
return
|
|
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 =
|
|
17093
|
-
if (!
|
|
17339
|
+
const p = join26(repoDir, f);
|
|
17340
|
+
if (!existsSync15(p))
|
|
17094
17341
|
continue;
|
|
17095
17342
|
try {
|
|
17096
|
-
const txt =
|
|
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 =
|
|
17107
|
-
return
|
|
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 =
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
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",
|
|
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 =
|
|
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(
|
|
17380
|
+
rels.push(join26(relDir, "e2e", relative3(dir, full)));
|
|
17134
17381
|
}
|
|
17135
17382
|
};
|
|
17136
|
-
walk(
|
|
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
|
|
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
|
|
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
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
17233
|
-
if (!
|
|
17234
|
-
|
|
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
|
-
|
|
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 =
|
|
17510
|
+
const evRoot = join26(runDir, "evidence");
|
|
17258
17511
|
if (isSymlink(evRoot))
|
|
17259
17512
|
return skip("evidence dir is a symlink");
|
|
17260
|
-
|
|
17513
|
+
mkdirSync7(evRoot, { recursive: true });
|
|
17261
17514
|
if (pages.length > 0) {
|
|
17262
|
-
const rel0 =
|
|
17263
|
-
|
|
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 =
|
|
17269
|
-
const ok = await shoot(pw, ["screenshot", `${base}${route}`,
|
|
17270
|
-
if (ok && !aborted() && !isSymlink(
|
|
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(
|
|
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 =
|
|
17296
|
-
|
|
17297
|
-
|
|
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
|
|
17339
|
-
import { dirname as dirname9, join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
17456
|
-
import { dirname as dirname10, join as
|
|
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) =>
|
|
17702
|
+
var unitsDir = (runDir) => join28(runDir, "group", "units");
|
|
17464
17703
|
var readJson3 = (p) => {
|
|
17465
17704
|
try {
|
|
17466
|
-
return JSON.parse(
|
|
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 =
|
|
17480
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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 (!
|
|
17805
|
+
if (!statSync11(dir).isDirectory())
|
|
17567
17806
|
return bad(`unit ${id} is not a directory`);
|
|
17568
|
-
bytes =
|
|
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 =
|
|
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 ||
|
|
17868
|
+
const file = given || join29(process.env["HOME"] || homedir6(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
17630
17869
|
try {
|
|
17631
|
-
pem =
|
|
17870
|
+
pem = readFileSync23(file);
|
|
17632
17871
|
if (!given) {
|
|
17633
17872
|
for (const [f, m] of [[file, 384], [dirname11(file), 448]])
|
|
17634
|
-
if (
|
|
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
|
-
|
|
17641
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
17890
|
-
const path =
|
|
17891
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
18031
|
-
if (!
|
|
18269
|
+
const path = join30(dirname12(receiptPath), "events.jsonl");
|
|
18270
|
+
if (!existsSync19(path))
|
|
18032
18271
|
return "events.jsonl is missing";
|
|
18033
|
-
const raw2 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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) =>
|
|
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 =
|
|
18140
|
-
runDir =
|
|
18141
|
-
if (runDir !==
|
|
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 =
|
|
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 +
|
|
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(
|
|
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
|
|
18225
|
-
import { join as
|
|
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 [
|
|
18836
|
+
for (const f of [join32(import.meta.dir, "../../../../../VERSION")]) {
|
|
18598
18837
|
try {
|
|
18599
|
-
if (
|
|
18600
|
-
return
|
|
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 = () => [
|
|
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 +
|
|
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(
|
|
19151
|
+
return new Response(Bun.file(join33(root, "index.html")));
|
|
18913
19152
|
});
|
|
18914
19153
|
return { app, db, close: () => sqlite.close() };
|
|
18915
19154
|
}
|