loki-mode 11.3.9 → 11.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -18
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/agents/custom_agents.py +280 -0
- package/autonomy/council-v2.sh +22 -1
- package/autonomy/edit.sh +242 -0
- package/autonomy/lib/chat.sh +312 -0
- package/autonomy/lib/help-core.sh +164 -0
- package/autonomy/lib/json-output.sh +38 -0
- package/autonomy/lib/run-summary.sh +96 -0
- package/autonomy/loki +105 -10
- package/autonomy/route.sh +270 -0
- package/autonomy/run.sh +6 -0
- package/autonomy/setup-wizard.sh +161 -0
- package/autonomy/verify.sh +22 -2
- package/bin/loki +73 -18
- package/completions/_loki +55 -0
- package/completions/loki.bash +49 -3
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-CORE-VERBS.md +112 -0
- package/docs/CLI-REFERENCE.md +7 -1
- package/docs/COMPARE-OPENCODE.md +94 -0
- package/docs/COMPONENTS.md +1 -0
- package/docs/INSTALLATION.md +56 -12
- package/docs/KNOWN-ISSUES.md +2 -12
- package/docs/QUICKSTART.md +108 -0
- package/docs/environment-variables.md +9 -2
- package/docs/exit-codes.md +29 -12
- package/docs/v10/CLI-MODERN.md +10 -4
- package/docs/v10/GUIDE.md +270 -44
- package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
- package/loki-ts/dist/loki.js +968 -933
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +180 -80
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +16 -2
- package/providers/model_route_exec.sh +22 -0
- package/providers/model_routes.sh +188 -0
- package/skills/providers.md +13 -0
- package/templates/agents/planner.md +10 -0
- package/templates/agents/reviewer.md +10 -0
- package/templates/agents/tester.md +10 -0
- package/web-app/dist/assets/{AdminPage-QK-i_x-O.js → AdminPage-CYJ2BWRC.js} +1 -1
- package/web-app/dist/assets/{Avatar-DblkLNjB.js → Avatar-DdK4wynI.js} +1 -1
- package/web-app/dist/assets/{Badge-DIV2ny5H.js → Badge-CrX3MI7J.js} +1 -1
- package/web-app/dist/assets/{Button--rePJOlQ.js → Button-709-wwGM.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BZlKxpGy.js → CockpitPage-BapdTInO.js} +1 -1
- package/web-app/dist/assets/{ComparePage-3_INSVux.js → ComparePage-DT99pGvA.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-DHG63CNr.js → ErrorBoundary-B0uJSys8.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DFsBnLlO.js → EvidenceReceiptPanel-BtMFgElP.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-phPGah98.js → GitHubIssuesPanel-DjJuU941.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-T7KQqJmx.js → GitHubPRsPanel-HXfEX1BC.js} +1 -1
- package/web-app/dist/assets/{HomePage-DFjGUF5I.js → HomePage-Cqyves-S.js} +2 -2
- package/web-app/dist/assets/{LoginPage-CorUid_5.js → LoginPage-jUbtC7_Y.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DofbSxKD.js → MagicPage-DHRsqjgR.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-qzrhhfLC.js → MetricsPage-B-pED05R.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BAZR0mMb.js → NotFoundPage-CsbUs21R.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BOSUzG9J.js → ProjectPage-BtlLEQwB.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-Ba0YLtzD.js → ProjectsPage-DaRbRjSp.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Cr10Ue2K.js → SettingsPage-DALrtP3F.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Czn655fe.js → ShowcasePage-CoPHSNP0.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Dwjnw6Ob.js → SystemSettingsPage-kOuSN50O.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-D8nc69Be.js → TeamsPage-DGWK0LMp.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DHsaCDGv.js → TemplatesPage-C-_10CwA.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DBejni2a.js → TerminalOutput-Cz_vPbXL.js} +1 -1
- package/web-app/dist/assets/{activity-B2VsBl1C.js → activity-icIsXJVa.js} +1 -1
- package/web-app/dist/assets/{bell-CT77Fq2H.js → bell-DbPR2_4r.js} +1 -1
- package/web-app/dist/assets/{bot-DfaWgF4H.js → bot-DvjP3Yh5.js} +1 -1
- package/web-app/dist/assets/{check-D1yp7Ndv.js → check-D1OBxHeU.js} +1 -1
- package/web-app/dist/assets/{chevron-left-B6Pa8IOX.js → chevron-left-D09jDZ29.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D5jrL_Y7.js → circle-alert-DyZMLOcC.js} +1 -1
- package/web-app/dist/assets/{clock-DIsY_fa_.js → clock-CdpdJTId.js} +1 -1
- package/web-app/dist/assets/{cloud-eGwLh1As.js → cloud-CoLTlmoj.js} +1 -1
- package/web-app/dist/assets/{code-xml-CGDIcZ3m.js → code-xml-DrkE2ZZY.js} +1 -1
- package/web-app/dist/assets/{database-CWpdaykI.js → database-CMQQuM2R.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DZEuTCs6.js → dollar-sign-CuioDaOG.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BHBW6m9v.js → file-code-corner-Cx9j9K1S.js} +1 -1
- package/web-app/dist/assets/{file-plus-D4h1jZSR.js → file-plus-CYD2zaaI.js} +1 -1
- package/web-app/dist/assets/{globe-CXdqKgwd.js → globe-6w-hnQkL.js} +1 -1
- package/web-app/dist/assets/{hammer-B3YuuYI_.js → hammer-BpqtautL.js} +1 -1
- package/web-app/dist/assets/{index-BLEnboSv.js → index-Dzs-9EgJ.js} +3 -3
- package/web-app/dist/assets/{layers-Dvp5nFtw.js → layers-D5hd7jb7.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DodDmIZ3.js → loader-circle-C6Y9x_7H.js} +1 -1
- package/web-app/dist/assets/{lock-EiuaH1Ep.js → lock-D51L7rYh.js} +1 -1
- package/web-app/dist/assets/{package-Cx4m2hFg.js → package-BJF8hkP8.js} +1 -1
- package/web-app/dist/assets/{plus-D-Sd5rpH.js → plus-D5kUQ79n.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-C0w1cdmF.js → refresh-cw-BKdlCa7F.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-C_qk6MTT.js → rotate-ccw-_bd_YI1e.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DSxiFtkc.js → scroll-text-DbkegIxF.js} +1 -1
- package/web-app/dist/assets/{server-NTJt7I8M.js → server-DAwqFIsq.js} +1 -1
- package/web-app/dist/assets/{shield-alert-HBxMQOvH.js → shield-alert-BuR1tPHu.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DEgsHzSD.js → thumbs-up-BF6p3mRd.js} +1 -1
- package/web-app/dist/assets/{trash-2-px46FnTD.js → trash-2-BVpnkqqz.js} +1 -1
- package/web-app/dist/assets/{trending-down-CBosVGKa.js → trending-down-DpsDvRwW.js} +1 -1
- package/web-app/dist/assets/{trending-up-Dbd7zrQj.js → trending-up-mFogTjt3.js} +1 -1
- package/web-app/dist/assets/{upload-nhdFKAs1.js → upload-4T8zpBsi.js} +1 -1
- package/web-app/dist/assets/{usePolling-BgaGdinh.js → usePolling-DPXQv8WO.js} +1 -1
- package/web-app/dist/assets/{user-CzIihUy9.js → user-BYGIFaTm.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -9122,6 +9122,37 @@ var init_claude_model = __esm(() => {
|
|
|
9122
9122
|
FAMILY_ALIASES = new Set(["haiku", "sonnet", "opus", "fable"]);
|
|
9123
9123
|
});
|
|
9124
9124
|
|
|
9125
|
+
// ../../loki-ts/src/providers/price_table.ts
|
|
9126
|
+
var PRICE_TABLE;
|
|
9127
|
+
var init_price_table = __esm(() => {
|
|
9128
|
+
PRICE_TABLE = Object.freeze({
|
|
9129
|
+
"gpt-5.3-codex": { input: 1.75, output: 14, cachedInput: 0.175 },
|
|
9130
|
+
"gpt-5-codex": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9131
|
+
"gpt-5": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9132
|
+
"gpt-4.1": { input: 2, output: 8, cachedInput: 0.5 },
|
|
9133
|
+
"gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
|
|
9134
|
+
"o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 },
|
|
9135
|
+
o3: { input: 2, output: 8, cachedInput: 0.5 },
|
|
9136
|
+
"claude-opus": { input: 4, output: 20, cachedInput: 0.2 },
|
|
9137
|
+
"claude-sonnet": { input: 2, output: 10, cachedInput: 0.1 },
|
|
9138
|
+
"claude-haiku": { input: 0.1, output: 0.5, cachedInput: 0.01 },
|
|
9139
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
9140
|
+
gemini: { input: 1.25, output: 10 },
|
|
9141
|
+
deepseek: { input: 0.28, output: 0.42 },
|
|
9142
|
+
minimax: { input: 0.3, output: 1.2 }
|
|
9143
|
+
});
|
|
9144
|
+
});
|
|
9145
|
+
|
|
9146
|
+
// ../../loki-ts/src/providers/provider_cost.ts
|
|
9147
|
+
var ESTIMATED_PROVIDERS;
|
|
9148
|
+
var init_provider_cost = __esm(() => {
|
|
9149
|
+
init_price_table();
|
|
9150
|
+
ESTIMATED_PROVIDERS = new Set(["codex", "cline", "aider"]);
|
|
9151
|
+
});
|
|
9152
|
+
|
|
9153
|
+
// ../../loki-ts/src/providers/model_routes.ts
|
|
9154
|
+
var init_model_routes = () => {};
|
|
9155
|
+
|
|
9125
9156
|
// ../../loki-ts/src/engine10/hooks.ts
|
|
9126
9157
|
var hooks;
|
|
9127
9158
|
var init_hooks = __esm(() => {
|
|
@@ -9234,6 +9265,8 @@ function sumResultCosts(lokiRoot, iterations, context = []) {
|
|
|
9234
9265
|
const unmetered = rec2["source"] === UNMETERED;
|
|
9235
9266
|
if (unmetered)
|
|
9236
9267
|
out2.unmetered = true;
|
|
9268
|
+
if (rec2["estimated"] === true)
|
|
9269
|
+
out2.estimated = true;
|
|
9237
9270
|
const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
|
|
9238
9271
|
if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
|
|
9239
9272
|
out2.missing.push(iter);
|
|
@@ -9359,14 +9392,23 @@ var init_session_route = __esm(() => {
|
|
|
9359
9392
|
init_unit_model();
|
|
9360
9393
|
});
|
|
9361
9394
|
|
|
9395
|
+
// ../../loki-ts/src/providers/provider_guard.ts
|
|
9396
|
+
var init_provider_guard = __esm(() => {
|
|
9397
|
+
init_safe_git();
|
|
9398
|
+
});
|
|
9399
|
+
|
|
9362
9400
|
// ../../loki-ts/src/engine10/session.ts
|
|
9363
9401
|
var STDERR_TAIL_BYTES, ADVISOR_MARKED_STAGES;
|
|
9364
9402
|
var init_session = __esm(() => {
|
|
9403
|
+
init_provider_cost();
|
|
9404
|
+
init_model_routes();
|
|
9365
9405
|
init_cost();
|
|
9366
9406
|
init_budget();
|
|
9367
9407
|
init_hooks();
|
|
9368
9408
|
init_session_route();
|
|
9369
9409
|
init_safe_git();
|
|
9410
|
+
init_provider_guard();
|
|
9411
|
+
init_budget();
|
|
9370
9412
|
STDERR_TAIL_BYTES = 64 * 1024;
|
|
9371
9413
|
ADVISOR_MARKED_STAGES = new Set(["plan", "fix"]);
|
|
9372
9414
|
});
|
|
@@ -11819,6 +11861,55 @@ var init_visual_evidence = __esm(() => {
|
|
|
11819
11861
|
PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
11820
11862
|
});
|
|
11821
11863
|
|
|
11864
|
+
// ../../loki-ts/src/council/role_agents.ts
|
|
11865
|
+
import { existsSync as existsSync27, readdirSync as readdirSync15, readFileSync as readFileSync33 } from "fs";
|
|
11866
|
+
import { homedir as homedir8 } from "os";
|
|
11867
|
+
import { join as join47 } from "path";
|
|
11868
|
+
function agentDirs(repoDir, env = process.env) {
|
|
11869
|
+
return [env["LOKI_AGENTS_PROJECT_DIR"] || join47(repoDir, ".loki", "agents"), env["LOKI_AGENTS_USER_DIR"] || join47(env["XDG_CONFIG_HOME"] || join47(homedir8(), ".config"), "loki", "agents")];
|
|
11870
|
+
}
|
|
11871
|
+
function parseAgent(file, stem2) {
|
|
11872
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(readFileSync33(file, "utf8"));
|
|
11873
|
+
if (!m)
|
|
11874
|
+
return null;
|
|
11875
|
+
const body = m[2].trim();
|
|
11876
|
+
if (!body)
|
|
11877
|
+
return null;
|
|
11878
|
+
const kv = (k) => new RegExp(`^${k}:[ \\t]*["']?([^"'\\r\\n#]*?)["']?[ \\t]*$`, "m").exec(m[1])?.[1]?.trim() ?? "";
|
|
11879
|
+
const role = (kv("role") || stem2).toLowerCase();
|
|
11880
|
+
if (!ROLES.includes(role))
|
|
11881
|
+
return null;
|
|
11882
|
+
return { name: kv("name") || stem2, role, provider: (kv("provider") || "claude").toLowerCase(), model: kv("model"), prompt: body.slice(0, 4000) };
|
|
11883
|
+
}
|
|
11884
|
+
function loadRoleAgent(repoDir, role, env = process.env) {
|
|
11885
|
+
for (const d of agentDirs(repoDir, env)) {
|
|
11886
|
+
if (!existsSync27(d))
|
|
11887
|
+
continue;
|
|
11888
|
+
let names;
|
|
11889
|
+
try {
|
|
11890
|
+
names = readdirSync15(d).filter((n2) => n2.endsWith(".md") && !n2.startsWith(".")).sort();
|
|
11891
|
+
} catch {
|
|
11892
|
+
continue;
|
|
11893
|
+
}
|
|
11894
|
+
const ordered = [...names.filter((n2) => n2 === `${role}.md`), ...names.filter((n2) => n2 !== `${role}.md`)];
|
|
11895
|
+
for (const n2 of ordered) {
|
|
11896
|
+
try {
|
|
11897
|
+
const a = parseAgent(join47(d, n2), n2.slice(0, -3));
|
|
11898
|
+
if (a && a.role === role)
|
|
11899
|
+
return a;
|
|
11900
|
+
} catch {}
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
return null;
|
|
11904
|
+
}
|
|
11905
|
+
function loadReviewStageAgent(repoDir, env = process.env) {
|
|
11906
|
+
return loadRoleAgent(repoDir, "reviewer", env) ?? loadRoleAgent(repoDir, "council", env);
|
|
11907
|
+
}
|
|
11908
|
+
var ROLES;
|
|
11909
|
+
var init_role_agents = __esm(() => {
|
|
11910
|
+
ROLES = ["council", "reviewer"];
|
|
11911
|
+
});
|
|
11912
|
+
|
|
11822
11913
|
// ../../loki-ts/src/util/shell.ts
|
|
11823
11914
|
async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
|
|
11824
11915
|
const reader = stream.getReader();
|
|
@@ -11891,8 +11982,8 @@ var init_shell = __esm(() => {
|
|
|
11891
11982
|
});
|
|
11892
11983
|
|
|
11893
11984
|
// ../../loki-ts/src/engine10/stages/xreview.ts
|
|
11894
|
-
import { existsSync as
|
|
11895
|
-
import { join as
|
|
11985
|
+
import { existsSync as existsSync28, readFileSync as readFileSync34 } from "fs";
|
|
11986
|
+
import { join as join48 } from "path";
|
|
11896
11987
|
function minVerdict(verdict, review) {
|
|
11897
11988
|
const ceiling = review?.level === "block" ? "PARTIAL" : verdict;
|
|
11898
11989
|
return RANK2[ceiling] < RANK2[verdict] ? ceiling : verdict;
|
|
@@ -11905,11 +11996,11 @@ function explicitReview(repoDir, env) {
|
|
|
11905
11996
|
if (flag && isOff(ev))
|
|
11906
11997
|
return "off";
|
|
11907
11998
|
for (const f of ["loki.yaml", "loki.yml"]) {
|
|
11908
|
-
const p =
|
|
11909
|
-
if (!
|
|
11999
|
+
const p = join48(repoDir, f);
|
|
12000
|
+
if (!existsSync28(p))
|
|
11910
12001
|
continue;
|
|
11911
12002
|
try {
|
|
11912
|
-
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(
|
|
12003
|
+
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync34(p, "utf8"));
|
|
11913
12004
|
const v = asProvider(m?.[1]);
|
|
11914
12005
|
if (v)
|
|
11915
12006
|
return v;
|
|
@@ -11938,9 +12029,15 @@ function reviewReceipt(builder, xr, env = process.env) {
|
|
|
11938
12029
|
const differs = !!xr && xr.level !== "not_run" && xr.provider !== builder && (builder === "claude" || builder === "codex");
|
|
11939
12030
|
return { review: { provider: xr?.provider ?? null, vendor_differs: differs }, notProven: differs ? [] : ["judge shares builder vendor or none configured"] };
|
|
11940
12031
|
}
|
|
11941
|
-
function reviewArgv(p, prompt, env = process.env) {
|
|
12032
|
+
function reviewArgv(p, prompt, env = process.env, model) {
|
|
11942
12033
|
const cli = reviewCli(p, env);
|
|
11943
|
-
return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
|
|
12034
|
+
return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, ...model ? ["--model", model] : [], "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
|
|
12035
|
+
}
|
|
12036
|
+
function configuredReviewer(repoDir, env = process.env) {
|
|
12037
|
+
const a = loadReviewStageAgent(repoDir, env);
|
|
12038
|
+
if (!a)
|
|
12039
|
+
return null;
|
|
12040
|
+
return { prompt: a.prompt, model: a.provider === "claude" ? a.model : "", provider: asProvider(a.provider) };
|
|
11944
12041
|
}
|
|
11945
12042
|
function parseReview(p, out2) {
|
|
11946
12043
|
const m = /^\s*VERDICT:\s*(PASS|FLAG|BLOCK)\b/im.exec(out2);
|
|
@@ -11953,7 +12050,8 @@ function parseReview(p, out2) {
|
|
|
11953
12050
|
return { provider: p, level, notes };
|
|
11954
12051
|
}
|
|
11955
12052
|
async function crossReview(ctx, verdict, head) {
|
|
11956
|
-
const
|
|
12053
|
+
const agent = configuredReviewer(ctx.repoDir);
|
|
12054
|
+
const p = reviewProvider(ctx.repoDir, process.env, ctx.provider) ?? agent?.provider ?? null;
|
|
11957
12055
|
if (!p)
|
|
11958
12056
|
return null;
|
|
11959
12057
|
if (verdict !== "VERIFIED" && verdict !== "ALREADY_SATISFIED")
|
|
@@ -11962,7 +12060,8 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11962
12060
|
if (!d || d.exitCode !== 0)
|
|
11963
12061
|
return { provider: p, level: "not_run", notes: [`cross-review not run: diff unavailable for ${p}`] };
|
|
11964
12062
|
const prompt = [
|
|
11965
|
-
|
|
12063
|
+
agent ? `${agent.prompt}
|
|
12064
|
+
Review ONLY the diff below. Do not modify files.` : "You are an independent code reviewer. Review ONLY the diff below for correctness bugs, spec violations and missing tests. Do not modify files.",
|
|
11966
12065
|
"Reply with a first line exactly `VERDICT: PASS`, `VERDICT: FLAG` (concerns) or `VERDICT: BLOCK` (a reproduced defect), then one `- reason` line per concern.",
|
|
11967
12066
|
"",
|
|
11968
12067
|
"```diff",
|
|
@@ -11971,7 +12070,7 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11971
12070
|
].join(`
|
|
11972
12071
|
`);
|
|
11973
12072
|
try {
|
|
11974
|
-
const r = await run(reviewArgv(p, prompt), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
12073
|
+
const r = await run(reviewArgv(p, prompt, process.env, p === "claude" ? agent?.model : undefined), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
11975
12074
|
if (r.exitCode !== 0)
|
|
11976
12075
|
return { provider: p, level: "not_run", notes: [`cross-review not run: ${p} exited ${r.exitCode}`] };
|
|
11977
12076
|
return parseReview(p, r.stdout);
|
|
@@ -11984,20 +12083,21 @@ var RANK2, TIMEOUT_MS = 300000, MAX_DIFF = 60000, MAX_NOTES = 5, asProvider = (v
|
|
|
11984
12083
|
return s === "codex" || s === "claude" ? s : null;
|
|
11985
12084
|
}, isOff = (v) => /^(off|none|false|0)$/i.test(v?.trim() ?? ""), xvendorDefault = (env = process.env) => /^(1|true|yes|on)$/i.test(env["LOKI_XVENDOR_DEFAULT"]?.trim() ?? ""), reviewCli = (p, env = process.env) => env[`LOKI_${p.toUpperCase()}_CLI`]?.trim() || p, clean3 = (s) => s.replace(/[\x00-\x1f\x7f`]+/g, " ").trim().slice(0, 300);
|
|
11986
12085
|
var init_xreview = __esm(() => {
|
|
12086
|
+
init_role_agents();
|
|
11987
12087
|
init_shell();
|
|
11988
12088
|
init_safe_git();
|
|
11989
12089
|
RANK2 = { FAILED: 0, SPEC_CONFLICT: 0, PARTIAL: 1, ALREADY_SATISFIED: 2, VERIFIED: 2 };
|
|
11990
12090
|
});
|
|
11991
12091
|
|
|
11992
12092
|
// ../../loki-ts/src/runner/router/route_record.ts
|
|
11993
|
-
import { existsSync as
|
|
11994
|
-
import { join as
|
|
12093
|
+
import { existsSync as existsSync29, lstatSync as lstatSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
|
|
12094
|
+
import { join as join49 } from "path";
|
|
11995
12095
|
function loadRouteRecord(runDir) {
|
|
11996
12096
|
try {
|
|
11997
|
-
const p =
|
|
11998
|
-
if (!
|
|
12097
|
+
const p = join49(runDir, ROUTE_RECORD_FILE);
|
|
12098
|
+
if (!existsSync29(p) || !lstatSync11(p).isFile())
|
|
11999
12099
|
return;
|
|
12000
|
-
const v = JSON.parse(
|
|
12100
|
+
const v = JSON.parse(readFileSync35(p, "utf8"));
|
|
12001
12101
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
12002
12102
|
} catch {
|
|
12003
12103
|
return;
|
|
@@ -12007,7 +12107,7 @@ var ROUTE_RECORD_FILE = "route.json";
|
|
|
12007
12107
|
var init_route_record = () => {};
|
|
12008
12108
|
|
|
12009
12109
|
// ../../loki-ts/src/runner/router/history_store.ts
|
|
12010
|
-
import { existsSync as
|
|
12110
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync36, renameSync as renameSync6, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
|
|
12011
12111
|
import { resolve as resolve11 } from "path";
|
|
12012
12112
|
function validRun(v) {
|
|
12013
12113
|
if (typeof v !== "object" || v === null)
|
|
@@ -12020,10 +12120,10 @@ function historyPath(key, cacheRoot) {
|
|
|
12020
12120
|
}
|
|
12021
12121
|
function readRunHistory(key, cacheRoot) {
|
|
12022
12122
|
const path = historyPath(key, cacheRoot);
|
|
12023
|
-
if (!
|
|
12123
|
+
if (!existsSync30(path))
|
|
12024
12124
|
return [];
|
|
12025
12125
|
try {
|
|
12026
|
-
const parsed = JSON.parse(
|
|
12126
|
+
const parsed = JSON.parse(readFileSync36(path, "utf8"));
|
|
12027
12127
|
const runs2 = parsed?.runs;
|
|
12028
12128
|
return Array.isArray(runs2) ? runs2.filter(validRun) : [];
|
|
12029
12129
|
} catch {
|
|
@@ -12057,7 +12157,7 @@ var init_history_store = __esm(() => {
|
|
|
12057
12157
|
});
|
|
12058
12158
|
|
|
12059
12159
|
// ../../loki-ts/src/runner/router/history.ts
|
|
12060
|
-
import { dirname as dirname21, join as
|
|
12160
|
+
import { dirname as dirname21, join as join50, resolve as resolve12 } from "path";
|
|
12061
12161
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12062
12162
|
function shapeKey(model) {
|
|
12063
12163
|
if (!model || model.status !== "ok")
|
|
@@ -12135,8 +12235,8 @@ var init_cost_preview = __esm(() => {
|
|
|
12135
12235
|
});
|
|
12136
12236
|
|
|
12137
12237
|
// ../../loki-ts/src/features/contract.ts
|
|
12138
|
-
import { existsSync as
|
|
12139
|
-
import { dirname as dirname22, join as
|
|
12238
|
+
import { existsSync as existsSync31, lstatSync as lstatSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync37, writeFileSync as writeFileSync18 } from "fs";
|
|
12239
|
+
import { dirname as dirname22, join as join51 } from "path";
|
|
12140
12240
|
import { createHash as createHash11 } from "crypto";
|
|
12141
12241
|
function contractEnabled(env = process.env) {
|
|
12142
12242
|
return env["LOKI_CONTRACT"] !== "0";
|
|
@@ -12168,7 +12268,7 @@ function untracedLines(trace) {
|
|
|
12168
12268
|
return trace.criteria.filter((c) => c.status === "no_match").map((c) => `contract ${sanitizeCriterion(c.id)} untraced (no keyword match in changed files): ${sanitizeCriterion(c.text)}`);
|
|
12169
12269
|
}
|
|
12170
12270
|
function contractPath(repoDir) {
|
|
12171
|
-
return
|
|
12271
|
+
return join51(repoDir, ".loki", "contract.json");
|
|
12172
12272
|
}
|
|
12173
12273
|
function readContract(repoDir) {
|
|
12174
12274
|
const p = contractPath(repoDir);
|
|
@@ -12184,7 +12284,7 @@ function readContract(repoDir) {
|
|
|
12184
12284
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
12185
12285
|
let raw2;
|
|
12186
12286
|
try {
|
|
12187
|
-
raw2 =
|
|
12287
|
+
raw2 = readFileSync37(p);
|
|
12188
12288
|
} catch {
|
|
12189
12289
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
12190
12290
|
}
|
|
@@ -12253,8 +12353,8 @@ var init_contract = __esm(() => {
|
|
|
12253
12353
|
});
|
|
12254
12354
|
|
|
12255
12355
|
// ../../loki-ts/src/supply/supply_guard.ts
|
|
12256
|
-
import { existsSync as
|
|
12257
|
-
import { basename as basename17, join as
|
|
12356
|
+
import { existsSync as existsSync32, readFileSync as readFileSync38, statSync as statSync16 } from "fs";
|
|
12357
|
+
import { basename as basename17, join as join52 } from "path";
|
|
12258
12358
|
function supplyEnabled(env) {
|
|
12259
12359
|
return env["LOKI_SUPPLY_GUARD"] !== "0";
|
|
12260
12360
|
}
|
|
@@ -12269,13 +12369,13 @@ function isManifestPath(path) {
|
|
|
12269
12369
|
return MANIFESTS.test(basename17(path));
|
|
12270
12370
|
}
|
|
12271
12371
|
function readDeclared(repoDir) {
|
|
12272
|
-
const p =
|
|
12273
|
-
if (!
|
|
12372
|
+
const p = join52(repoDir, ".loki", DECLARED_FILE);
|
|
12373
|
+
if (!existsSync32(p))
|
|
12274
12374
|
return { deps: [], problem: null };
|
|
12275
12375
|
try {
|
|
12276
12376
|
if (statSync16(p).size > MAX_FILE_BYTES)
|
|
12277
12377
|
return { deps: [], problem: "declaration file too large" };
|
|
12278
|
-
const j = JSON.parse(
|
|
12378
|
+
const j = JSON.parse(readFileSync38(p, "utf8"));
|
|
12279
12379
|
if (!Array.isArray(j))
|
|
12280
12380
|
return { deps: [], problem: "declaration is not a JSON array" };
|
|
12281
12381
|
const deps = [];
|
|
@@ -12295,11 +12395,11 @@ function readDeclared(repoDir) {
|
|
|
12295
12395
|
}
|
|
12296
12396
|
}
|
|
12297
12397
|
function readAllowlist(repoDir) {
|
|
12298
|
-
const p =
|
|
12299
|
-
if (!
|
|
12398
|
+
const p = join52(repoDir, ".loki", "supply-allowlist");
|
|
12399
|
+
if (!existsSync32(p))
|
|
12300
12400
|
return new Set;
|
|
12301
12401
|
try {
|
|
12302
|
-
return new Set(
|
|
12402
|
+
return new Set(readFileSync38(p, "utf8").split(`
|
|
12303
12403
|
`).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
12304
12404
|
} catch {
|
|
12305
12405
|
return new Set;
|
|
@@ -12340,7 +12440,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12340
12440
|
if (eco === "npm") {
|
|
12341
12441
|
if (privateUrlList([env["npm_config_registry"] ?? "", env["NPM_CONFIG_REGISTRY"] ?? ""]))
|
|
12342
12442
|
return true;
|
|
12343
|
-
return files(
|
|
12443
|
+
return files(join52(repoDir, ".npmrc"), home ? join52(home, ".npmrc") : "").some((t) => t.split(`
|
|
12344
12444
|
`).some((l) => {
|
|
12345
12445
|
const m = /^\s*(?:(@[^:\s]+):)?registry\s*=\s*(\S+)/.exec(l);
|
|
12346
12446
|
return !!m && privateUrl(m[2]) && (m[1] === undefined || name.startsWith(`${m[1]}/`));
|
|
@@ -12349,7 +12449,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12349
12449
|
if (eco === "pypi") {
|
|
12350
12450
|
if (privateUrlList([env["PIP_INDEX_URL"] ?? "", env["PIP_EXTRA_INDEX_URL"] ?? ""]))
|
|
12351
12451
|
return true;
|
|
12352
|
-
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ?
|
|
12452
|
+
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join52(home, ".config", "pip", "pip.conf") : "", home ? join52(home, ".pip", "pip.conf") : "", join52(repoDir, "pip.conf")];
|
|
12353
12453
|
return files(...cfg).some((t) => t.split(`
|
|
12354
12454
|
`).some((l) => {
|
|
12355
12455
|
const m = /^\s*(?:extra-)?index-url\s*[=:]\s*(.+)$/.exec(l);
|
|
@@ -12359,7 +12459,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12359
12459
|
if (eco === "cargo") {
|
|
12360
12460
|
if (Object.keys(env).some((k) => /^CARGO_REGISTRIES_.+_INDEX$/.test(k) || /^CARGO_SOURCE_/.test(k)))
|
|
12361
12461
|
return true;
|
|
12362
|
-
return files(
|
|
12462
|
+
return files(join52(repoDir, ".cargo", "config.toml"), join52(repoDir, ".cargo", "config"), home ? join52(home, ".cargo", "config.toml") : "", home ? join52(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
|
|
12363
12463
|
}
|
|
12364
12464
|
if (eco === "go") {
|
|
12365
12465
|
const gp = goEnv["GOPROXY"] ?? env["GOPROXY"] ?? "";
|
|
@@ -12482,7 +12582,7 @@ var MIN_AGE_DAYS = 7, DECLARED_FILE = "supply-declared.json", MAX_CHECKED = 50,
|
|
|
12482
12582
|
}
|
|
12483
12583
|
}, PUBLIC_HOSTS, hostOf = (u) => u.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^www\./, "").split(/[/?#\s]/)[0].replace(/^[^@]*@/, "").replace(/:\d+$/, ""), privateUrl = (u) => /[a-z0-9]/i.test(u) && !PUBLIC_HOSTS.has(hostOf(u)), readSmall = (p) => {
|
|
12484
12584
|
try {
|
|
12485
|
-
return statSync16(p).size <= MAX_FILE_BYTES ?
|
|
12585
|
+
return statSync16(p).size <= MAX_FILE_BYTES ? readFileSync38(p, "utf8") : "";
|
|
12486
12586
|
} catch {
|
|
12487
12587
|
return "";
|
|
12488
12588
|
}
|
|
@@ -12498,9 +12598,9 @@ var init_supply_guard = __esm(() => {
|
|
|
12498
12598
|
|
|
12499
12599
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
12500
12600
|
import { createHash as createHash12, randomBytes as randomBytes4, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
12501
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
12502
|
-
import { homedir as
|
|
12503
|
-
import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as
|
|
12601
|
+
import { chmodSync as chmodSync5, existsSync as existsSync33, readdirSync as readdirSync16, linkSync, mkdirSync as mkdirSync18, readFileSync as readFileSync39, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync19 } from "fs";
|
|
12602
|
+
import { homedir as homedir9 } from "os";
|
|
12603
|
+
import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as join53, relative as relative9 } from "path";
|
|
12504
12604
|
function sanitizeReason(s) {
|
|
12505
12605
|
const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
|
|
12506
12606
|
const capped = collapsed.length > 500 ? `${collapsed.slice(0, 500)}...` : collapsed;
|
|
@@ -12528,9 +12628,9 @@ function loadSigningKey(generate = true) {
|
|
|
12528
12628
|
let pem = inline ?? "";
|
|
12529
12629
|
if (!inline) {
|
|
12530
12630
|
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
12531
|
-
const file = given ||
|
|
12631
|
+
const file = given || join53(process.env["HOME"] || homedir9(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
12532
12632
|
try {
|
|
12533
|
-
pem =
|
|
12633
|
+
pem = readFileSync39(file);
|
|
12534
12634
|
if (!given) {
|
|
12535
12635
|
for (const [f, m] of [[file, 384], [dirname23(file), 448]])
|
|
12536
12636
|
if (statSync17(f).mode & 63)
|
|
@@ -12551,7 +12651,7 @@ function loadSigningKey(generate = true) {
|
|
|
12551
12651
|
} finally {
|
|
12552
12652
|
unlinkSync5(tmp);
|
|
12553
12653
|
}
|
|
12554
|
-
pem =
|
|
12654
|
+
pem = readFileSync39(file);
|
|
12555
12655
|
}
|
|
12556
12656
|
}
|
|
12557
12657
|
const k = createPrivateKey(pem);
|
|
@@ -12633,7 +12733,7 @@ function renderReceiptMd(r) {
|
|
|
12633
12733
|
`);
|
|
12634
12734
|
}
|
|
12635
12735
|
var DEEP_NOT_PROVEN, SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)", EXCLUDE_LOKI = ":(exclude).loki", sha2562 = (s) => createHash12("sha256").update(s).digest("hex"), str3 = (v) => typeof v === "string" && v !== "" ? v : null, strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [], b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash12("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest()), receiptRel = (ctx) => {
|
|
12636
|
-
const r = relative9(ctx.repoDir,
|
|
12736
|
+
const r = relative9(ctx.repoDir, join53(ctx.runDir, "receipt.json"));
|
|
12637
12737
|
return r.startsWith("..") || isAbsolute8(r) ? `.loki/runs/${ctx.runId}/receipt.json` : r;
|
|
12638
12738
|
}, commitStage, sealStage;
|
|
12639
12739
|
var init_seal = __esm(() => {
|
|
@@ -12736,7 +12836,7 @@ var init_seal = __esm(() => {
|
|
|
12736
12836
|
if (wallNotRun > 0) {
|
|
12737
12837
|
try {
|
|
12738
12838
|
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename18(String(f.path))));
|
|
12739
|
-
for (const n2 of
|
|
12839
|
+
for (const n2 of readdirSync16(join53(ctx.runDir, "wall")).sort())
|
|
12740
12840
|
if (!kept.has(n2))
|
|
12741
12841
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
12742
12842
|
} catch {}
|
|
@@ -12795,19 +12895,19 @@ var init_seal = __esm(() => {
|
|
|
12795
12895
|
for (const [s, d] of Object.entries(o))
|
|
12796
12896
|
if (typeof d?.duration_s === "number")
|
|
12797
12897
|
stages[s] = d.duration_s;
|
|
12798
|
-
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(
|
|
12898
|
+
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(join53(ctx.runDir, "events.jsonl"))) : { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages }, totalS = T?.reconciled(time) ?? null;
|
|
12799
12899
|
const iterIds = Object.values(o).flatMap((d) => [...strs2(d?.iteration_ids), ...strs2([d?.iteration_id])]);
|
|
12800
12900
|
if (iterIds.length === 0)
|
|
12801
12901
|
notProven.add("cost not measured (no iteration ids recorded)");
|
|
12802
12902
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
12803
12903
|
if (cost.unmetered)
|
|
12804
12904
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
12805
|
-
const eventsPath =
|
|
12905
|
+
const eventsPath = join53(ctx.runDir, "events.jsonl");
|
|
12806
12906
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
12807
12907
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
12808
12908
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
12809
12909
|
notProven.add("wall result not recorded by verify");
|
|
12810
|
-
const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(
|
|
12910
|
+
const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route ?? o.plan?.route_record ?? (routerEnabled(process.env) ? loadRouteRecord(ctx.runDir) : undefined), cost.router ?? (routerEnabled(process.env) ? sumResultCosts(join53(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
|
|
12811
12911
|
if (routeBlock)
|
|
12812
12912
|
for (const l of routeNotProven(routeBlock))
|
|
12813
12913
|
notProven.add(l);
|
|
@@ -12849,7 +12949,7 @@ var init_seal = __esm(() => {
|
|
|
12849
12949
|
provider: ctx.provider,
|
|
12850
12950
|
model: ctx.model,
|
|
12851
12951
|
resumed: o.intake?.resumed === true,
|
|
12852
|
-
events_sha256: sha2562(
|
|
12952
|
+
events_sha256: sha2562(existsSync33(eventsPath) ? readFileSync39(eventsPath) : ""),
|
|
12853
12953
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
12854
12954
|
log_seal: true,
|
|
12855
12955
|
...receiptBlock(process.env, cost.usd, cost.unmetered === true, totalS),
|
|
@@ -12870,10 +12970,10 @@ var init_seal = __esm(() => {
|
|
|
12870
12970
|
}
|
|
12871
12971
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
12872
12972
|
mkdirSync18(ctx.runDir, { recursive: true });
|
|
12873
|
-
const path =
|
|
12973
|
+
const path = join53(ctx.runDir, "receipt.json");
|
|
12874
12974
|
writeFileSync19(path, JSON.stringify(receipt, null, 2) + `
|
|
12875
12975
|
`);
|
|
12876
|
-
writeFileSync19(
|
|
12976
|
+
writeFileSync19(join53(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
12877
12977
|
recordRun(process.env, ctx.repoDir, ctx.model, verdict, cost.usd, cost.unmetered === true, totalS);
|
|
12878
12978
|
const signed = sig.jwt !== null;
|
|
12879
12979
|
const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...mp ? { mutation_line: mp.line } : {}, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} };
|
|
@@ -12888,7 +12988,7 @@ var init_seal = __esm(() => {
|
|
|
12888
12988
|
|
|
12889
12989
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
12890
12990
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
12891
|
-
import { readFileSync as
|
|
12991
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
12892
12992
|
function pae(payloadType, body) {
|
|
12893
12993
|
const t = Buffer.from(payloadType, "utf8");
|
|
12894
12994
|
return Buffer.concat([Buffer.from(`DSSEv1 ${t.length} `), t, Buffer.from(` ${body.length} `), body]);
|
|
@@ -12965,7 +13065,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
12965
13065
|
}
|
|
12966
13066
|
function runIdGuard(receiptPath, runId) {
|
|
12967
13067
|
try {
|
|
12968
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
13068
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync40(receiptPath, "utf8")), runId);
|
|
12969
13069
|
} catch {
|
|
12970
13070
|
return null;
|
|
12971
13071
|
}
|
|
@@ -12979,8 +13079,8 @@ var init_keys_cmd = __esm(() => {
|
|
|
12979
13079
|
});
|
|
12980
13080
|
|
|
12981
13081
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
12982
|
-
import { existsSync as
|
|
12983
|
-
import { dirname as dirname24, join as
|
|
13082
|
+
import { existsSync as existsSync34, readdirSync as readdirSync17, readFileSync as readFileSync41, statSync as statSync18 } from "fs";
|
|
13083
|
+
import { dirname as dirname24, join as join54 } from "path";
|
|
12984
13084
|
import { createHash as createHash13, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
12985
13085
|
function pubFor(kid, given) {
|
|
12986
13086
|
if (given)
|
|
@@ -12988,7 +13088,7 @@ function pubFor(kid, given) {
|
|
|
12988
13088
|
const active = loadSigningKey(false);
|
|
12989
13089
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
12990
13090
|
try {
|
|
12991
|
-
return [createPublicKey4(
|
|
13091
|
+
return [createPublicKey4(readFileSync41(f))];
|
|
12992
13092
|
} catch {
|
|
12993
13093
|
return [];
|
|
12994
13094
|
}
|
|
@@ -13020,10 +13120,10 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
13020
13120
|
const bound = receipt["events_sha256"];
|
|
13021
13121
|
if (typeof bound !== "string" || bound === createHash13("sha256").digest("hex"))
|
|
13022
13122
|
return null;
|
|
13023
|
-
const path =
|
|
13024
|
-
if (!
|
|
13123
|
+
const path = join54(dirname24(receiptPath), "events.jsonl");
|
|
13124
|
+
if (!existsSync34(path))
|
|
13025
13125
|
return "events.jsonl is missing";
|
|
13026
|
-
const raw2 =
|
|
13126
|
+
const raw2 = readFileSync41(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
13027
13127
|
`).filter((l) => l.trim() !== "");
|
|
13028
13128
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
13029
13129
|
return "events.jsonl has a forged, edited or missing line";
|
|
@@ -13039,7 +13139,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
13039
13139
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
13040
13140
|
}
|
|
13041
13141
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
13042
|
-
const path =
|
|
13142
|
+
const path = join54(dirname24(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
13043
13143
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
13044
13144
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
13045
13145
|
if (done < 0)
|
|
@@ -13047,7 +13147,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13047
13147
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
13048
13148
|
if (!d)
|
|
13049
13149
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
13050
|
-
const lines =
|
|
13150
|
+
const lines = readFileSync41(path, "utf8").split(`
|
|
13051
13151
|
`).filter((l) => l.trim() !== ""), sha3 = createHash13("sha256").update(lines.slice(0, done + 1).join(`
|
|
13052
13152
|
`) + `
|
|
13053
13153
|
`).digest("hex");
|
|
@@ -13057,11 +13157,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13057
13157
|
return null;
|
|
13058
13158
|
}
|
|
13059
13159
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
13060
|
-
if (!
|
|
13160
|
+
if (!existsSync34(receiptPath))
|
|
13061
13161
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
13062
13162
|
let receipt;
|
|
13063
13163
|
try {
|
|
13064
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
13164
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync41(p, "utf8")))(receiptPath));
|
|
13065
13165
|
} catch {
|
|
13066
13166
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
13067
13167
|
}
|
|
@@ -13116,8 +13216,8 @@ var init_verify_cmd = __esm(() => {
|
|
|
13116
13216
|
});
|
|
13117
13217
|
|
|
13118
13218
|
// src/server/app.ts
|
|
13119
|
-
import { existsSync as
|
|
13120
|
-
import { join as
|
|
13219
|
+
import { existsSync as existsSync36, readFileSync as readFileSync44, statSync as statSync19 } from "fs";
|
|
13220
|
+
import { join as join57, resolve as resolve13, sep as sep14 } from "path";
|
|
13121
13221
|
|
|
13122
13222
|
// node_modules/hono/dist/request/constants.js
|
|
13123
13223
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -23364,9 +23464,9 @@ function mount20(ctx) {
|
|
|
23364
23464
|
}
|
|
23365
23465
|
|
|
23366
23466
|
// src/server/routes/verify.ts
|
|
23367
|
-
import { readFileSync as
|
|
23467
|
+
import { readFileSync as readFileSync42, realpathSync as realpathSync20 } from "fs";
|
|
23368
23468
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
23369
|
-
import { join as
|
|
23469
|
+
import { join as join55, sep as sep13 } from "path";
|
|
23370
23470
|
init_verify_cmd();
|
|
23371
23471
|
init_seal();
|
|
23372
23472
|
init_receipt_dsse();
|
|
@@ -23393,11 +23493,11 @@ function mount21(ctx) {
|
|
|
23393
23493
|
return done(404, "receipt not available", "not_found");
|
|
23394
23494
|
let runDir, file;
|
|
23395
23495
|
try {
|
|
23396
|
-
const root = realpathSync20(
|
|
23397
|
-
runDir = realpathSync20(
|
|
23398
|
-
if (runDir !==
|
|
23496
|
+
const root = realpathSync20(join55(repo.p, ".loki", "runs"));
|
|
23497
|
+
runDir = realpathSync20(join55(root, run2));
|
|
23498
|
+
if (runDir !== join55(root, run2))
|
|
23399
23499
|
return done(404, "receipt not found", "not_found");
|
|
23400
|
-
file = realpathSync20(
|
|
23500
|
+
file = realpathSync20(join55(runDir, "receipt.json"));
|
|
23401
23501
|
} catch {
|
|
23402
23502
|
return done(404, "receipt not found", "not_found");
|
|
23403
23503
|
}
|
|
@@ -23416,7 +23516,7 @@ function mount21(ctx) {
|
|
|
23416
23516
|
sha3 = r.receiptSha256 ?? null;
|
|
23417
23517
|
}
|
|
23418
23518
|
try {
|
|
23419
|
-
outcome = outcomeOf(JSON.parse(
|
|
23519
|
+
outcome = outcomeOf(JSON.parse(readFileSync42(file, "utf8")));
|
|
23420
23520
|
} catch {
|
|
23421
23521
|
outcome = "UNREADABLE";
|
|
23422
23522
|
}
|
|
@@ -23478,8 +23578,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
|
|
|
23478
23578
|
}
|
|
23479
23579
|
|
|
23480
23580
|
// src/server/legacy/shim.ts
|
|
23481
|
-
import { existsSync as
|
|
23482
|
-
import { join as
|
|
23581
|
+
import { existsSync as existsSync35, readFileSync as readFileSync43 } from "fs";
|
|
23582
|
+
import { join as join56 } from "path";
|
|
23483
23583
|
|
|
23484
23584
|
// src/server/legacy/routes.ts
|
|
23485
23585
|
var LEGACY_ROUTES = [
|
|
@@ -23851,10 +23951,10 @@ var logged = new Set;
|
|
|
23851
23951
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
23852
23952
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
23853
23953
|
var repoVersion = () => {
|
|
23854
|
-
for (const f of [
|
|
23954
|
+
for (const f of [join56(import.meta.dir, "../../../../../VERSION")]) {
|
|
23855
23955
|
try {
|
|
23856
|
-
if (
|
|
23857
|
-
return
|
|
23956
|
+
if (existsSync35(f))
|
|
23957
|
+
return readFileSync43(f, "utf8").trim();
|
|
23858
23958
|
} catch {}
|
|
23859
23959
|
}
|
|
23860
23960
|
return "unknown";
|
|
@@ -24036,7 +24136,7 @@ function legacyShim(opts) {
|
|
|
24036
24136
|
|
|
24037
24137
|
// src/server/app.ts
|
|
24038
24138
|
var MAX_BODY2 = 1e6;
|
|
24039
|
-
var defaultUiDir = () => [
|
|
24139
|
+
var defaultUiDir = () => [join57(import.meta.dir, "../../ui/dist"), join57(import.meta.dir, "../ui/dist")].find((d) => existsSync36(join57(d, "index.html")));
|
|
24040
24140
|
function createApp(opts) {
|
|
24041
24141
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
24042
24142
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
@@ -24065,10 +24165,10 @@ function createApp(opts) {
|
|
|
24065
24165
|
await next();
|
|
24066
24166
|
});
|
|
24067
24167
|
const readInstalled = () => {
|
|
24068
|
-
for (const f of [process.env.LOKI_VERSION_FILE,
|
|
24168
|
+
for (const f of [process.env.LOKI_VERSION_FILE, join57(import.meta.dir, "../../../../VERSION")]) {
|
|
24069
24169
|
try {
|
|
24070
24170
|
if (f)
|
|
24071
|
-
return
|
|
24171
|
+
return readFileSync44(f, "utf8").trim() || "unknown";
|
|
24072
24172
|
} catch {}
|
|
24073
24173
|
}
|
|
24074
24174
|
return "unknown";
|
|
@@ -24179,11 +24279,11 @@ function createApp(opts) {
|
|
|
24179
24279
|
return c.json({ error: "not found" }, 404);
|
|
24180
24280
|
const root = resolve13(uiDir);
|
|
24181
24281
|
const f = resolve13(root, `.${p}`);
|
|
24182
|
-
if (f.startsWith(root + sep14) &&
|
|
24282
|
+
if (f.startsWith(root + sep14) && existsSync36(f) && statSync19(f).isFile())
|
|
24183
24283
|
return new Response(Bun.file(f));
|
|
24184
24284
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
24185
24285
|
return c.json({ error: "not found" }, 404);
|
|
24186
|
-
return new Response(Bun.file(
|
|
24286
|
+
return new Response(Bun.file(join57(root, "index.html")));
|
|
24187
24287
|
});
|
|
24188
24288
|
return { app, db, close: () => {
|
|
24189
24289
|
clearInterval(reconciler);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "11.
|
|
5
|
+
"version": "11.4.2",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|