loki-mode 11.3.9 → 11.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +315 -0
- package/autonomy/lib/engine10-push.sh +19 -1
- package/autonomy/lib/help-core.sh +164 -0
- package/autonomy/lib/json-output.sh +38 -0
- package/autonomy/lib/proof-generator.py +23 -5
- package/autonomy/lib/proof-verify.py +607 -5
- package/autonomy/lib/run-summary.sh +112 -0
- package/autonomy/lib/setup-requirements.json +35 -0
- package/autonomy/loki +337 -46
- package/autonomy/provider-offer.sh +95 -47
- package/autonomy/route.sh +302 -0
- package/autonomy/run.sh +48 -14
- package/autonomy/setup-wizard.sh +170 -0
- package/autonomy/verify.sh +62 -3
- package/bin/loki +73 -18
- package/completions/_loki +58 -1
- package/completions/loki.bash +57 -5
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-CORE-VERBS.md +112 -0
- package/docs/CLI-REFERENCE.md +9 -1
- package/docs/COMPARE-OPENCODE.md +94 -0
- package/docs/COMPONENTS.md +1 -0
- package/docs/EVALUATING.md +27 -0
- package/docs/HARNESS-INTELLIGENCE-PLAN.md +20 -0
- package/docs/INSTALLATION.md +56 -12
- package/docs/KNOWN-ISSUES.md +8 -9
- package/docs/PROOF-PASSPORT.md +114 -0
- package/docs/QUICKSTART.md +108 -0
- package/docs/alternative-installations.md +103 -0
- package/docs/cost-controls.md +139 -0
- package/docs/environment-variables.md +45 -2
- package/docs/exit-codes.md +129 -12
- package/docs/spec-first-intent.md +29 -0
- package/docs/v10/CLI-MODERN.md +11 -5
- package/docs/v10/ENGINE.md +32 -0
- package/docs/v10/GUIDE.md +270 -44
- package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
- package/loki-ts/data/model-pricing.json +1 -0
- package/loki-ts/dist/loki.js +1065 -978
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +1829 -715
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/plugins/loki-mode/README.md +41 -1
- package/plugins/loki-mode/commands/handoff.md +12 -0
- package/plugins/loki-mode/commands/loki-verify.md +9 -2
- package/plugins/loki-mode/commands/start.md +20 -0
- package/plugins/loki-mode/hooks/hooks.json +11 -0
- package/plugins/loki-mode/scripts/loki-stop-verify.sh +84 -0
- 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-Cbu3pXL8.js} +1 -1
- package/web-app/dist/assets/{Avatar-DblkLNjB.js → Avatar-DGqGGcGx.js} +1 -1
- package/web-app/dist/assets/{Badge-DIV2ny5H.js → Badge-Bhlipt6v.js} +1 -1
- package/web-app/dist/assets/{Button--rePJOlQ.js → Button-CpN2sO61.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BZlKxpGy.js → CockpitPage-BqeDJwdL.js} +1 -1
- package/web-app/dist/assets/{ComparePage-3_INSVux.js → ComparePage-DUieA2ba.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-DHG63CNr.js → ErrorBoundary-DTWsYrOm.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DFsBnLlO.js → EvidenceReceiptPanel-BU_B9X19.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-phPGah98.js → GitHubIssuesPanel-DEZoLOsP.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-T7KQqJmx.js → GitHubPRsPanel-BEsnrq7Y.js} +1 -1
- package/web-app/dist/assets/{HomePage-DFjGUF5I.js → HomePage-BoUVuq7h.js} +2 -2
- package/web-app/dist/assets/{LoginPage-CorUid_5.js → LoginPage-CmOED7vb.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DofbSxKD.js → MagicPage-DATZitrZ.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-qzrhhfLC.js → MetricsPage-DGL1v0I-.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BAZR0mMb.js → NotFoundPage-C2WgOjZd.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BOSUzG9J.js → ProjectPage-BMi1tepj.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-Ba0YLtzD.js → ProjectsPage-vc3y5YPh.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Cr10Ue2K.js → SettingsPage-kbq0Ftvi.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Czn655fe.js → ShowcasePage-Bn6Q8Kbg.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Dwjnw6Ob.js → SystemSettingsPage-Basmhs_b.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-D8nc69Be.js → TeamsPage-BTqOc4TR.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DHsaCDGv.js → TemplatesPage-2P9002VL.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DBejni2a.js → TerminalOutput-CAoSPBrg.js} +1 -1
- package/web-app/dist/assets/{activity-B2VsBl1C.js → activity-BcWVWCqU.js} +1 -1
- package/web-app/dist/assets/{bell-CT77Fq2H.js → bell-CYRqqgZ6.js} +1 -1
- package/web-app/dist/assets/{bot-DfaWgF4H.js → bot-CU7gdn3g.js} +1 -1
- package/web-app/dist/assets/{check-D1yp7Ndv.js → check-DLlCpz9V.js} +1 -1
- package/web-app/dist/assets/{chevron-left-B6Pa8IOX.js → chevron-left-CE9Cb30C.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D5jrL_Y7.js → circle-alert-uou4bjua.js} +1 -1
- package/web-app/dist/assets/{clock-DIsY_fa_.js → clock-C_dSJcDX.js} +1 -1
- package/web-app/dist/assets/{cloud-eGwLh1As.js → cloud-DZ22ITF6.js} +1 -1
- package/web-app/dist/assets/{code-xml-CGDIcZ3m.js → code-xml-BrJS0hhS.js} +1 -1
- package/web-app/dist/assets/{database-CWpdaykI.js → database-D2HOmxtl.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DZEuTCs6.js → dollar-sign-CQ1vK_6q.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BHBW6m9v.js → file-code-corner-w2vpGm5d.js} +1 -1
- package/web-app/dist/assets/{file-plus-D4h1jZSR.js → file-plus-DlF6wjkD.js} +1 -1
- package/web-app/dist/assets/{globe-CXdqKgwd.js → globe-Dy3lBJg1.js} +1 -1
- package/web-app/dist/assets/{hammer-B3YuuYI_.js → hammer-C3xIiRXN.js} +1 -1
- package/web-app/dist/assets/{index-BLEnboSv.js → index-4t-_IW2y.js} +3 -3
- package/web-app/dist/assets/{layers-Dvp5nFtw.js → layers-Y8Fs8qXI.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DodDmIZ3.js → loader-circle-CxgN1v4m.js} +1 -1
- package/web-app/dist/assets/{lock-EiuaH1Ep.js → lock-DFFNbXDs.js} +1 -1
- package/web-app/dist/assets/{package-Cx4m2hFg.js → package-5e6qCTUg.js} +1 -1
- package/web-app/dist/assets/{plus-D-Sd5rpH.js → plus-C0Go_ytj.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-C0w1cdmF.js → refresh-cw-D4i-NW3o.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-C_qk6MTT.js → rotate-ccw-YlKkiUW6.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DSxiFtkc.js → scroll-text-4qXOHTap.js} +1 -1
- package/web-app/dist/assets/{server-NTJt7I8M.js → server-BB6zXNp9.js} +1 -1
- package/web-app/dist/assets/{shield-alert-HBxMQOvH.js → shield-alert-D4tzjB1H.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DEgsHzSD.js → thumbs-up-DZiRwdFj.js} +1 -1
- package/web-app/dist/assets/{trash-2-px46FnTD.js → trash-2-Dv_LkRIK.js} +1 -1
- package/web-app/dist/assets/{trending-down-CBosVGKa.js → trending-down-BJYp9PQe.js} +1 -1
- package/web-app/dist/assets/{trending-up-Dbd7zrQj.js → trending-up-BTcXLklK.js} +1 -1
- package/web-app/dist/assets/{upload-nhdFKAs1.js → upload-fcHqwOxr.js} +1 -1
- package/web-app/dist/assets/{usePolling-BgaGdinh.js → usePolling-BeJYw_4L.js} +1 -1
- package/web-app/dist/assets/{user-CzIihUy9.js → user-BcDwUCsO.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -311,8 +311,10 @@ function verifyRunPid(runDir, runId, startOf = processStartTime) {
|
|
|
311
311
|
return { ok: false, reason: "the pid now belongs to a different process (start time differs)" };
|
|
312
312
|
return { ok: true, pid: rec.pid };
|
|
313
313
|
}
|
|
314
|
-
var runPidPath = (runDir) => join8(runDir, "run.pid");
|
|
315
|
-
var init_run_pid = () => {
|
|
314
|
+
var runPidPath = (runDir) => join8(runDir, "run.pid"), liveSessions;
|
|
315
|
+
var init_run_pid = __esm(() => {
|
|
316
|
+
liveSessions = new Set;
|
|
317
|
+
});
|
|
316
318
|
|
|
317
319
|
// src/shipper/watch.ts
|
|
318
320
|
import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync } from "fs";
|
|
@@ -7324,6 +7326,18 @@ var init_paths = __esm(() => {
|
|
|
7324
7326
|
});
|
|
7325
7327
|
|
|
7326
7328
|
// ../../loki-ts/src/engine10/types.ts
|
|
7329
|
+
function pushArgv(a) {
|
|
7330
|
+
switch (a.cmd) {
|
|
7331
|
+
case "push-pr":
|
|
7332
|
+
return ["push-pr", a.repoDir, a.branch, a.title, a.bodyFile, a.draft ? "1" : "0"];
|
|
7333
|
+
case "comment":
|
|
7334
|
+
return ["comment", a.runId, a.prUrl, a.file];
|
|
7335
|
+
case "status":
|
|
7336
|
+
return ["status", a.sha, a.state, a.description];
|
|
7337
|
+
case "issue-comment":
|
|
7338
|
+
return ["issue-comment", a.issueRef, a.bodyFile];
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7327
7341
|
function taskBlock(task) {
|
|
7328
7342
|
return ["Task (untrusted, quoted verbatim):", "<<<TASK", task, "TASK"];
|
|
7329
7343
|
}
|
|
@@ -9122,6 +9136,37 @@ var init_claude_model = __esm(() => {
|
|
|
9122
9136
|
FAMILY_ALIASES = new Set(["haiku", "sonnet", "opus", "fable"]);
|
|
9123
9137
|
});
|
|
9124
9138
|
|
|
9139
|
+
// ../../loki-ts/src/providers/price_table.ts
|
|
9140
|
+
var PRICE_TABLE;
|
|
9141
|
+
var init_price_table = __esm(() => {
|
|
9142
|
+
PRICE_TABLE = Object.freeze({
|
|
9143
|
+
"gpt-5.3-codex": { input: 1.75, output: 14, cachedInput: 0.175 },
|
|
9144
|
+
"gpt-5-codex": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9145
|
+
"gpt-5": { input: 1.25, output: 10, cachedInput: 0.125 },
|
|
9146
|
+
"gpt-4.1": { input: 2, output: 8, cachedInput: 0.5 },
|
|
9147
|
+
"gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
|
|
9148
|
+
"o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 },
|
|
9149
|
+
o3: { input: 2, output: 8, cachedInput: 0.5 },
|
|
9150
|
+
"claude-opus": { input: 4, output: 20, cachedInput: 0.2 },
|
|
9151
|
+
"claude-sonnet": { input: 2, output: 10, cachedInput: 0.1 },
|
|
9152
|
+
"claude-haiku": { input: 0.1, output: 0.5, cachedInput: 0.01 },
|
|
9153
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
9154
|
+
gemini: { input: 1.25, output: 10 },
|
|
9155
|
+
deepseek: { input: 0.28, output: 0.42 },
|
|
9156
|
+
minimax: { input: 0.3, output: 1.2 }
|
|
9157
|
+
});
|
|
9158
|
+
});
|
|
9159
|
+
|
|
9160
|
+
// ../../loki-ts/src/providers/provider_cost.ts
|
|
9161
|
+
var ESTIMATED_PROVIDERS;
|
|
9162
|
+
var init_provider_cost = __esm(() => {
|
|
9163
|
+
init_price_table();
|
|
9164
|
+
ESTIMATED_PROVIDERS = new Set(["codex", "cline", "aider"]);
|
|
9165
|
+
});
|
|
9166
|
+
|
|
9167
|
+
// ../../loki-ts/src/providers/model_routes.ts
|
|
9168
|
+
var init_model_routes = () => {};
|
|
9169
|
+
|
|
9125
9170
|
// ../../loki-ts/src/engine10/hooks.ts
|
|
9126
9171
|
var hooks;
|
|
9127
9172
|
var init_hooks = __esm(() => {
|
|
@@ -9234,6 +9279,8 @@ function sumResultCosts(lokiRoot, iterations, context = []) {
|
|
|
9234
9279
|
const unmetered = rec2["source"] === UNMETERED;
|
|
9235
9280
|
if (unmetered)
|
|
9236
9281
|
out2.unmetered = true;
|
|
9282
|
+
if (rec2["estimated"] === true)
|
|
9283
|
+
out2.estimated = true;
|
|
9237
9284
|
const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
|
|
9238
9285
|
if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
|
|
9239
9286
|
out2.missing.push(iter);
|
|
@@ -9277,6 +9324,17 @@ var init_cost = __esm(() => {
|
|
|
9277
9324
|
import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync11, readFileSync as readFileSync23, renameSync as renameSync4, writeFileSync as writeFileSync11 } from "fs";
|
|
9278
9325
|
import { dirname as dirname10, join as join31, resolve as resolve10 } from "path";
|
|
9279
9326
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9327
|
+
function loadDefaultMaxCostUsd() {
|
|
9328
|
+
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
9329
|
+
for (const rel of [["..", "..", "data"], ["..", "data"]]) {
|
|
9330
|
+
try {
|
|
9331
|
+
const j = JSON.parse(readFileSync23(resolve10(here, ...rel, "model-pricing.json"), "utf8"));
|
|
9332
|
+
if (typeof j.default_max_cost_usd === "number" && Number.isFinite(j.default_max_cost_usd) && j.default_max_cost_usd > 0)
|
|
9333
|
+
return j.default_max_cost_usd;
|
|
9334
|
+
} catch {}
|
|
9335
|
+
}
|
|
9336
|
+
return DEFAULT_FALLBACK_USD;
|
|
9337
|
+
}
|
|
9280
9338
|
function _loadPricing() {
|
|
9281
9339
|
try {
|
|
9282
9340
|
const moduleDir = dirname10(fileURLToPath2(import.meta.url));
|
|
@@ -9311,7 +9369,7 @@ function _loadPricing() {
|
|
|
9311
9369
|
return _FALLBACK_PRICING;
|
|
9312
9370
|
}
|
|
9313
9371
|
}
|
|
9314
|
-
var _FALLBACK_PRICING, PRICING;
|
|
9372
|
+
var DEFAULT_FALLBACK_USD = 100, _FALLBACK_PRICING, PRICING;
|
|
9315
9373
|
var init_budget = __esm(() => {
|
|
9316
9374
|
init_paths();
|
|
9317
9375
|
init_cost();
|
|
@@ -9359,14 +9417,47 @@ var init_session_route = __esm(() => {
|
|
|
9359
9417
|
init_unit_model();
|
|
9360
9418
|
});
|
|
9361
9419
|
|
|
9420
|
+
// ../../loki-ts/src/providers/provider_guard.ts
|
|
9421
|
+
function providerSandboxFlags(provider) {
|
|
9422
|
+
switch (provider) {
|
|
9423
|
+
case "codex":
|
|
9424
|
+
return ["--sandbox", "workspace-write"];
|
|
9425
|
+
case "aider":
|
|
9426
|
+
return ["--no-auto-commits"];
|
|
9427
|
+
default:
|
|
9428
|
+
return [];
|
|
9429
|
+
}
|
|
9430
|
+
}
|
|
9431
|
+
var E10_COMMON = "loki-ts/src/engine10/session.ts (detached spawn, killGroupWithGrace, recordCost, watchBudget), loki-ts/src/providers/provider_guard.ts, loki-ts/src/e10ext/budget_cap.ts", U = "unknown", CAPABILITIES;
|
|
9432
|
+
var init_provider_guard = __esm(() => {
|
|
9433
|
+
init_safe_git();
|
|
9434
|
+
CAPABILITIES = [
|
|
9435
|
+
{ provider: "claude", engine: "engine10", invocation: "Agent SDK query() (default) or claude CLI via loki-ts/src/runner/providers.ts", cost: "measured", budgetKill: "post-iteration", cancelProcessGroup: "yes", sandboxFlags: [], receipt: "yes", resume: "yes", guardScan: "native", killBlocking: "native", source: `${E10_COMMON}; loki-ts/src/runner/providers.ts (LOKI_E10_RESUME_SESSION, claude only)` },
|
|
9436
|
+
{ provider: "codex", engine: "engine10", invocation: "codex exec --sandbox workspace-write --skip-git-repo-check", cost: "estimated", budgetKill: "post-iteration", cancelProcessGroup: "yes", sandboxFlags: providerSandboxFlags("codex"), receipt: "yes", resume: "no", guardScan: "post-iteration", killBlocking: "none", source: `${E10_COMMON}; loki-ts/src/providers/provider_cost.ts (ESTIMATED_PROVIDERS); loki-ts/src/runner/providers.ts codexProvider` },
|
|
9437
|
+
{ provider: "cline", engine: "engine10", invocation: "cline -y", cost: "estimated", budgetKill: "post-iteration", cancelProcessGroup: "yes", sandboxFlags: providerSandboxFlags("cline"), receipt: "yes", resume: "no", guardScan: "post-iteration", killBlocking: "none", source: `${E10_COMMON}; loki-ts/src/providers/provider_cost.ts; providers/cline.sh` },
|
|
9438
|
+
{ provider: "aider", engine: "engine10", invocation: "aider --yes-always", cost: "estimated", budgetKill: "post-iteration", cancelProcessGroup: "yes", sandboxFlags: providerSandboxFlags("aider"), receipt: "yes", resume: "no", guardScan: "post-iteration", killBlocking: "none", source: `${E10_COMMON}; loki-ts/src/providers/provider_cost.ts (a reported "Cost: $X session" line is used as-is); providers/aider.sh` },
|
|
9439
|
+
{ provider: "opencode", engine: "engine10", invocation: "opencode run --auto [-m provider/model]", cost: "none", budgetKill: "none", cancelProcessGroup: "yes", sandboxFlags: providerSandboxFlags("opencode"), receipt: "yes", resume: "no", guardScan: "post-iteration", killBlocking: "none", source: `${E10_COMMON}; loki-ts/src/runner/provider_opencode.ts (costMeasured false); loki-ts/src/providers/provider_cost.ts (opencode not in ESTIMATED_PROVIDERS); docs/v10/GUIDE.md provider table` },
|
|
9440
|
+
{ provider: "claude", engine: "legacy", invocation: "claude --dangerously-skip-permissions (bash loop)", cost: U, budgetKill: U, cancelProcessGroup: U, sandboxFlags: U, receipt: U, resume: U, guardScan: U, killBlocking: U, source: "providers/claude.sh (PROVIDER_AUTONOMOUS_FLAG); autonomy/run.sh (LOKI_BUDGET_LIMIT); other fields not confirmed" },
|
|
9441
|
+
{ provider: "codex", engine: "legacy", invocation: "codex exec --sandbox workspace-write --skip-git-repo-check (bash loop)", cost: U, budgetKill: U, cancelProcessGroup: U, sandboxFlags: ["--sandbox", "workspace-write"], receipt: U, resume: U, guardScan: U, killBlocking: U, source: "providers/codex.sh (PROVIDER_AUTONOMOUS_FLAG); other fields not confirmed" },
|
|
9442
|
+
{ provider: "cline", engine: "legacy", invocation: "cline -y (bash loop)", cost: U, budgetKill: U, cancelProcessGroup: U, sandboxFlags: U, receipt: U, resume: U, guardScan: U, killBlocking: U, source: "providers/cline.sh (PROVIDER_AUTONOMOUS_FLAG); other fields not confirmed" },
|
|
9443
|
+
{ provider: "aider", engine: "legacy", invocation: "aider --yes-always (bash loop)", cost: U, budgetKill: U, cancelProcessGroup: U, sandboxFlags: U, receipt: U, resume: U, guardScan: U, killBlocking: U, source: "providers/aider.sh (PROVIDER_AUTONOMOUS_FLAG); other fields not confirmed" },
|
|
9444
|
+
{ provider: "opencode", engine: "legacy", invocation: "opencode run --auto (bash loop)", cost: U, budgetKill: U, cancelProcessGroup: U, sandboxFlags: U, receipt: U, resume: U, guardScan: U, killBlocking: U, source: "providers/opencode.sh (PROVIDER_AUTONOMOUS_FLAG); other fields not confirmed" }
|
|
9445
|
+
];
|
|
9446
|
+
});
|
|
9447
|
+
|
|
9362
9448
|
// ../../loki-ts/src/engine10/session.ts
|
|
9363
9449
|
var STDERR_TAIL_BYTES, ADVISOR_MARKED_STAGES;
|
|
9364
9450
|
var init_session = __esm(() => {
|
|
9451
|
+
init_run_pid();
|
|
9452
|
+
init_provider_cost();
|
|
9453
|
+
init_model_routes();
|
|
9365
9454
|
init_cost();
|
|
9366
9455
|
init_budget();
|
|
9367
9456
|
init_hooks();
|
|
9368
9457
|
init_session_route();
|
|
9369
9458
|
init_safe_git();
|
|
9459
|
+
init_provider_guard();
|
|
9460
|
+
init_budget();
|
|
9370
9461
|
STDERR_TAIL_BYTES = 64 * 1024;
|
|
9371
9462
|
ADVISOR_MARKED_STAGES = new Set(["plan", "fix"]);
|
|
9372
9463
|
});
|
|
@@ -10087,8 +10178,39 @@ var init_runner_errors = __esm(() => {
|
|
|
10087
10178
|
];
|
|
10088
10179
|
});
|
|
10089
10180
|
|
|
10181
|
+
// ../../loki-ts/src/engine10/testmap.ts
|
|
10182
|
+
import { basename as basename12, extname, join as join38, relative as relative3, sep as sep10 } from "path";
|
|
10183
|
+
function isTestFile(rel) {
|
|
10184
|
+
const name = basename12(rel);
|
|
10185
|
+
return JS_TEST_RE.test(name) || PY_TEST_RE.test(name) || GO_TEST_RE.test(name);
|
|
10186
|
+
}
|
|
10187
|
+
var SKIP_DIRS, JS_TEST_RE, PY_TEST_RE, GO_TEST_RE;
|
|
10188
|
+
var init_testmap = __esm(() => {
|
|
10189
|
+
init_resolve();
|
|
10190
|
+
init_scope();
|
|
10191
|
+
SKIP_DIRS = new Set([
|
|
10192
|
+
".git",
|
|
10193
|
+
"node_modules",
|
|
10194
|
+
"dist",
|
|
10195
|
+
"build",
|
|
10196
|
+
"target",
|
|
10197
|
+
"coverage",
|
|
10198
|
+
".venv",
|
|
10199
|
+
"venv",
|
|
10200
|
+
"__pycache__",
|
|
10201
|
+
".tox",
|
|
10202
|
+
".pytest_cache",
|
|
10203
|
+
".loki",
|
|
10204
|
+
".next"
|
|
10205
|
+
]);
|
|
10206
|
+
JS_TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
10207
|
+
PY_TEST_RE = /^(test_.+|.+_test)\.py$/;
|
|
10208
|
+
GO_TEST_RE = /_test\.go$/;
|
|
10209
|
+
});
|
|
10210
|
+
|
|
10090
10211
|
// ../../loki-ts/src/util/check_result.ts
|
|
10091
|
-
import {
|
|
10212
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync28, renameSync as renameSync5, writeFileSync as writeFileSync13 } from "fs";
|
|
10213
|
+
import { basename as basename13, join as join39 } from "path";
|
|
10092
10214
|
function plainTestEnv(base = process.env) {
|
|
10093
10215
|
const e = {};
|
|
10094
10216
|
for (const [k, v] of Object.entries(tokenFreeEnv(base)))
|
|
@@ -10106,7 +10228,7 @@ function ran(raw2, path, ok) {
|
|
|
10106
10228
|
if (blk) {
|
|
10107
10229
|
const c = n2(blk, /(?:#|\u2139) pass (\d+)/) + n2(blk, /(?:#|\u2139) fail (\d+)/);
|
|
10108
10230
|
const nm = out2.match(/^(?:ok \d+ - |\u2714 )(\S+\.[cm]?[jt]s)(?: \(|$)/m)?.[1];
|
|
10109
|
-
return c === 1 && nm && (!path ||
|
|
10231
|
+
return c === 1 && nm && (!path || basename13(nm) === basename13(path)) ? 0 : c;
|
|
10110
10232
|
}
|
|
10111
10233
|
const cg = cargoTrailer(out2);
|
|
10112
10234
|
if (cg.length)
|
|
@@ -10197,16 +10319,16 @@ function goRunner(cmd, args) {
|
|
|
10197
10319
|
let k = 0;
|
|
10198
10320
|
for (;k < t.length; k++) {
|
|
10199
10321
|
const x = t[k];
|
|
10200
|
-
if (/^[A-Za-z_]\w*=/.test(x) || /^(?:env|command|exec|time|nohup)$/.test(
|
|
10322
|
+
if (/^[A-Za-z_]\w*=/.test(x) || /^(?:env|command|exec|time|nohup)$/.test(basename13(x)))
|
|
10201
10323
|
continue;
|
|
10202
|
-
if (/^-/.test(x) && k > 0 &&
|
|
10324
|
+
if (/^-/.test(x) && k > 0 && basename13(t[k - 1]) === "env") {
|
|
10203
10325
|
if (x === "-u" || x === "-C" || x === "-S")
|
|
10204
10326
|
k++;
|
|
10205
10327
|
continue;
|
|
10206
10328
|
}
|
|
10207
10329
|
break;
|
|
10208
10330
|
}
|
|
10209
|
-
if (t[k] !== undefined &&
|
|
10331
|
+
if (t[k] !== undefined && basename13(t[k]) === "go")
|
|
10210
10332
|
return { runner: "go" };
|
|
10211
10333
|
}
|
|
10212
10334
|
return {};
|
|
@@ -10287,31 +10409,163 @@ function classifyCheck(i) {
|
|
|
10287
10409
|
return { result: "not_run", reason: UNMEASURED_REASON };
|
|
10288
10410
|
return { result: "pass", n: n2 };
|
|
10289
10411
|
}
|
|
10412
|
+
function summaryOf(a) {
|
|
10413
|
+
const why = a.tier === a.computed ? a.reasons.slice(0, 2).join("; ") : `computed ${a.computed}; override ${a.override?.from} to ${a.override?.to}: ${a.override?.reason}`;
|
|
10414
|
+
const def = a.checks_deferred;
|
|
10415
|
+
return `risk: ${a.tier} (${why}), ${def.length ? def.join(", ") : "no deeper checks"} not run`;
|
|
10416
|
+
}
|
|
10417
|
+
function markExecuted(a, executed) {
|
|
10418
|
+
const checks_deferred = deferredOf(a.tier, a.checks_selected, executed);
|
|
10419
|
+
return { ...a, checks_deferred, summary: summaryOf({ ...a, checks_deferred }) };
|
|
10420
|
+
}
|
|
10421
|
+
function classifyRisk(files, env = process.env) {
|
|
10422
|
+
const lines = files.reduce((n2, f) => n2 + f.added + f.deleted, 0);
|
|
10423
|
+
const sensitive = files.flatMap((f) => {
|
|
10424
|
+
const hit = SENSITIVE.find(([, re]) => re.test(f.path));
|
|
10425
|
+
return hit && !isTestFile(f.path) ? [`${f.path} (${hit[0]})`] : [];
|
|
10426
|
+
});
|
|
10427
|
+
const testFiles = files.filter((f) => isTestFile(f.path));
|
|
10428
|
+
const testsDeleted = testFiles.filter((f) => f.status === "deleted").map((f) => f.path);
|
|
10429
|
+
const testsModified = [...testFiles.filter((f) => f.status !== "added" && f.status !== "deleted").map((f) => f.path), ...files.filter((f) => TEST_CFG.test(f.path)).map((f) => f.path)];
|
|
10430
|
+
const api = files.filter((f) => API.test(f.path) && !isTestFile(f.path)).map((f) => f.path);
|
|
10431
|
+
const high = [], med = [];
|
|
10432
|
+
if (sensitive.length)
|
|
10433
|
+
high.push(`sensitive paths: ${sensitive.slice(0, 5).join(", ")}${sensitive.length > 5 ? ` (+${sensitive.length - 5})` : ""}`);
|
|
10434
|
+
if (testsDeleted.length)
|
|
10435
|
+
high.push(`test files deleted: ${testsDeleted.join(", ")}`);
|
|
10436
|
+
if (api.length)
|
|
10437
|
+
high.push(`public API surface: ${api.slice(0, 3).join(", ")}`);
|
|
10438
|
+
if (files.length >= FILES_HIGH)
|
|
10439
|
+
high.push(`${files.length} files >= ${FILES_HIGH}`);
|
|
10440
|
+
if (lines >= LINES_HIGH)
|
|
10441
|
+
high.push(`${lines} changed lines >= ${LINES_HIGH}`);
|
|
10442
|
+
if (testsModified.length)
|
|
10443
|
+
med.push(`existing tests or test config modified: ${testsModified.slice(0, 3).join(", ")}`);
|
|
10444
|
+
if (files.length >= FILES_MEDIUM)
|
|
10445
|
+
med.push(`${files.length} files >= ${FILES_MEDIUM}`);
|
|
10446
|
+
if (lines >= LINES_MEDIUM)
|
|
10447
|
+
med.push(`${lines} changed lines >= ${LINES_MEDIUM}`);
|
|
10448
|
+
const computed = high.length ? "high" : med.length ? "medium" : "low";
|
|
10449
|
+
const reasons = high.length ? [...high, ...med] : med.length ? med : [`${files.length} file${files.length === 1 ? "" : "s"}, no sensitive paths`];
|
|
10450
|
+
let tier = computed, override, ignored;
|
|
10451
|
+
const asked = (env["LOKI_RISK_TIER"] ?? "").trim().toLowerCase();
|
|
10452
|
+
if (asked in RANK2 && asked !== computed) {
|
|
10453
|
+
const reason = (env["LOKI_RISK_OVERRIDE_REASON"] ?? "").trim();
|
|
10454
|
+
if (RANK2[asked] > RANK2[computed]) {
|
|
10455
|
+
tier = asked;
|
|
10456
|
+
override = { from: computed, to: asked, reason: reason || "raised by LOKI_RISK_TIER" };
|
|
10457
|
+
} else if (reason) {
|
|
10458
|
+
tier = asked;
|
|
10459
|
+
override = { from: computed, to: asked, reason };
|
|
10460
|
+
} else
|
|
10461
|
+
ignored = `LOKI_RISK_TIER=${asked} ignored: lowering from ${computed} needs LOKI_RISK_OVERRIDE_REASON`;
|
|
10462
|
+
}
|
|
10463
|
+
const plan = PLAN[tier], checks_deferred = deferredOf(tier, plan.selected, []);
|
|
10464
|
+
const summary = summaryOf({ tier, computed, reasons, ...override ? { override } : {}, checks_deferred });
|
|
10465
|
+
return {
|
|
10466
|
+
version: 1,
|
|
10467
|
+
tier,
|
|
10468
|
+
computed,
|
|
10469
|
+
reasons,
|
|
10470
|
+
stats: { files: files.length, lines, sensitive, tests_modified: testsModified, tests_deleted: testsDeleted, api },
|
|
10471
|
+
checks_selected: plan.selected,
|
|
10472
|
+
checks_deferred,
|
|
10473
|
+
...override ? { override } : {},
|
|
10474
|
+
...ignored ? { override_ignored: ignored } : {},
|
|
10475
|
+
summary
|
|
10476
|
+
};
|
|
10477
|
+
}
|
|
10478
|
+
function changedFiles(repoDir, baseSha) {
|
|
10479
|
+
const run = (args) => safeGit(repoDir, args).split(`
|
|
10480
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
10481
|
+
const tracked = run(["diff", "--name-only", baseSha]);
|
|
10482
|
+
const untracked = run(["ls-files", "--others", "--exclude-standard"]);
|
|
10483
|
+
return [...new Set([...tracked, ...untracked])].filter((f) => !f.startsWith(".loki/"));
|
|
10484
|
+
}
|
|
10485
|
+
function diffFiles(repoDir, baseSha) {
|
|
10486
|
+
const paths = changedFiles(repoDir, baseSha), stat = new Map;
|
|
10487
|
+
for (const l of safeGit(repoDir, ["diff", "--numstat", baseSha]).split(`
|
|
10488
|
+
`)) {
|
|
10489
|
+
const [a, d, ...p] = l.split("\t"), path = p.join("\t");
|
|
10490
|
+
if (path)
|
|
10491
|
+
stat.set(path, { path, added: Number(a) || 0, deleted: Number(d) || 0 });
|
|
10492
|
+
}
|
|
10493
|
+
for (const l of safeGit(repoDir, ["diff", "--name-status", baseSha]).split(`
|
|
10494
|
+
`)) {
|
|
10495
|
+
const [s, ...p] = l.split("\t"), f = stat.get(p[p.length - 1] ?? "");
|
|
10496
|
+
if (f)
|
|
10497
|
+
f.status = s?.startsWith("A") ? "added" : s?.startsWith("D") ? "deleted" : "modified";
|
|
10498
|
+
}
|
|
10499
|
+
return paths.map((path) => {
|
|
10500
|
+
const f = stat.get(path);
|
|
10501
|
+
if (f)
|
|
10502
|
+
return f;
|
|
10503
|
+
let n2 = 0;
|
|
10504
|
+
try {
|
|
10505
|
+
n2 = readFileSync28(join39(repoDir, path), "utf8").split(`
|
|
10506
|
+
`).length;
|
|
10507
|
+
} catch {}
|
|
10508
|
+
return { path, added: n2, deleted: 0, status: "added" };
|
|
10509
|
+
});
|
|
10510
|
+
}
|
|
10511
|
+
function recordRisk(repoDir, a) {
|
|
10512
|
+
try {
|
|
10513
|
+
const dir = join39(repoDir, ".loki", "state"), p = join39(dir, "risk-tier.json");
|
|
10514
|
+
mkdirSync10(dir, { recursive: true });
|
|
10515
|
+
writeFileSync13(`${p}.tmp`, JSON.stringify(a, null, 2) + `
|
|
10516
|
+
`);
|
|
10517
|
+
renameSync5(`${p}.tmp`, p);
|
|
10518
|
+
return p;
|
|
10519
|
+
} catch {
|
|
10520
|
+
return null;
|
|
10521
|
+
}
|
|
10522
|
+
}
|
|
10290
10523
|
var NO_TESTS_REASON = "no tests executed", GO_EXIT0_REASON = "test count could not be confirmed: go test output is produced by the code under test and cannot confirm execution (a Go exit 0 is never a pass)", UNCONFIRMED_REASON = "test count could not be confirmed", FAIL_EVIDENCE, GO_FAIL_EVIDENCE, UNMEASURED_REASON = "executed count unmeasured (Loki could not parse the runner summary, harness-owned)", stripAnsi = (s) => s.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, "").replace(/(?:\u001b\[|\u009b)[0-9;?]*[ -\/]*[@-~]/g, "").replace(/\r\n?/g, `
|
|
10291
10524
|
`), tailLines = (out2) => out2.split(`
|
|
10292
|
-
`).filter((x) => x.trim()).slice(-12), GO_PKG_RE, isExecutedProof = (c) => c.result === "pass" && typeof c.n === "number" && c.n > 0, hasExecutedProof = (checks) => checks.some(isExecutedProof);
|
|
10525
|
+
`).filter((x) => x.trim()).slice(-12), GO_PKG_RE, isExecutedProof = (c) => c.result === "pass" && typeof c.n === "number" && c.n > 0, hasExecutedProof = (checks) => checks.some(isExecutedProof), SENSITIVE, API, TEST_CFG, RANK2, FILES_MEDIUM = 4, FILES_HIGH = 15, LINES_MEDIUM = 100, LINES_HIGH = 500, RAN_ALWAYS, PLAN, LOW_SKIPPED, deferredOf = (tier, selected, executed) => (tier === "low" ? LOW_SKIPPED : selected).filter((c) => !RAN_ALWAYS.includes(c) && !executed.includes(c));
|
|
10293
10526
|
var init_check_result = __esm(() => {
|
|
10527
|
+
init_testmap();
|
|
10294
10528
|
init_safe_git();
|
|
10295
10529
|
FAIL_EVIDENCE = /\[(?:build|setup) failed\]|^--- FAIL|^[\w./-]+\.go:\d+:\d+: \S|\b[1-9]\d* (?:failed|errors?)\b/m;
|
|
10296
10530
|
GO_FAIL_EVIDENCE = /^FAIL\t\S+\t[\d.]+s$|^panic: /m;
|
|
10297
10531
|
GO_PKG_RE = /^(?:ok|FAIL|\?)\s+\S+\s+(?:\(cached\)|[\d.]+s\b|\[(?:no test files|build failed|setup failed)\])/;
|
|
10532
|
+
SENSITIVE = [
|
|
10533
|
+
["auth", /(^|\/)(auth|oauth|sso|session|login|permission|rbac|acl)[\w.-]*(\/|\.|$)/i],
|
|
10534
|
+
["crypto", /(^|\/)(crypt|crypto|jwt|signing|keys?|secrets?|tokens?|credential|password)[\w.-]*(\/|\.|$)/i],
|
|
10535
|
+
["payments", /(^|\/)(payments?|billing|checkout|stripe|invoice)[\w.-]*(\/|\.|$)/i],
|
|
10536
|
+
["migration", /(^|\/)(migrations?|alembic)\/|\.sql$/i],
|
|
10537
|
+
["ci", /(^|\/)(\.github\/(workflows|actions)|\.gitlab-ci\.yml|\.circleci|Jenkinsfile|azure-pipelines)/i],
|
|
10538
|
+
["lockfile", /(^|\/)(package-lock\.json|pnpm-lock\.yaml|bun\.lockb?|go\.sum|[\w-]+\.lock)$/],
|
|
10539
|
+
["infra", /(^|\/)(Dockerfile[\w.-]*|docker-compose[\w.-]*\.ya?ml|terraform|helm|k8s|kubernetes|infra|deploy)(\/|\.|$)|\.tf$/i],
|
|
10540
|
+
["env", /(^|\/)\.env[\w.]*$/]
|
|
10541
|
+
];
|
|
10542
|
+
API = /(^|\/)(openapi|swagger)[\w.-]*\.(ya?ml|json)$|\.proto$|\.d\.ts$|(^|\/)(api|routes|public)\/|(^|\/)index\.(ts|js|py)$/i;
|
|
10543
|
+
TEST_CFG = /(^|\/)(conftest\.py|\.?pytest\.(ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+)$/;
|
|
10544
|
+
RANK2 = { low: 0, medium: 1, high: 2 };
|
|
10545
|
+
RAN_ALWAYS = ["targeted tests", "lint of changed files"];
|
|
10546
|
+
PLAN = {
|
|
10547
|
+
low: { selected: [...RAN_ALWAYS] },
|
|
10548
|
+
medium: { selected: [...RAN_ALWAYS, "full test command", "one reviewer"] },
|
|
10549
|
+
high: { selected: [...RAN_ALWAYS, "full test command", "full suite", "council", "security scan"] }
|
|
10550
|
+
};
|
|
10551
|
+
LOW_SKIPPED = ["full test command", "reviewer", "council", "security scan"];
|
|
10298
10552
|
});
|
|
10299
10553
|
|
|
10300
10554
|
// ../../loki-ts/src/runner/load_owner.ts
|
|
10301
10555
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
10302
10556
|
import { mkdtempSync as mkdtempSync5, realpathSync as realpathSync15, rmSync as rmSync8 } from "fs";
|
|
10303
10557
|
import { tmpdir as tmpdir7 } from "os";
|
|
10304
|
-
import { basename as
|
|
10558
|
+
import { basename as basename14, extname as extname2, join as join40 } from "path";
|
|
10305
10559
|
function changedFilesNamed(out2, repoDir, changed) {
|
|
10306
10560
|
return changed.filter((f) => {
|
|
10307
|
-
if (out2.includes(f) || out2.includes(
|
|
10561
|
+
if (out2.includes(f) || out2.includes(join40(repoDir, f)))
|
|
10308
10562
|
return true;
|
|
10309
10563
|
const segs = f.split("/");
|
|
10310
10564
|
if (suffixes(segs, 2).some((x) => x !== f && bounded(out2, x, "(?![\\w-])")))
|
|
10311
10565
|
return true;
|
|
10312
|
-
if (
|
|
10566
|
+
if (extname2(f) === ".go" && segs.length > 1 && suffixes(segs.slice(0, -1), 1).some((d) => new RegExp(`(?<=[\\w-])/${esc(d)}(?![\\w./-])`).test(out2)))
|
|
10313
10567
|
return true;
|
|
10314
|
-
const stem =
|
|
10568
|
+
const stem = basename14(f, extname2(f));
|
|
10315
10569
|
if (stem.length < 2 || stem === "index" || stem === "__init__")
|
|
10316
10570
|
return false;
|
|
10317
10571
|
return new RegExp(`(?<![\\w-])${esc(stem)}(?![\\w-])`).test(out2);
|
|
@@ -10415,7 +10669,7 @@ async function loadErrorIsHarnessOwned(i) {
|
|
|
10415
10669
|
return false;
|
|
10416
10670
|
if (i.args.some((a) => changed.includes(a.replace(/^\.\//, ""))))
|
|
10417
10671
|
return false;
|
|
10418
|
-
const dir = mkdtempSync5(
|
|
10672
|
+
const dir = mkdtempSync5(join40(tmpdir7(), "e10-loadown-"));
|
|
10419
10673
|
const git3 = (args) => {
|
|
10420
10674
|
safeGit(i.repoDir, args, { stdio: "ignore" });
|
|
10421
10675
|
};
|
|
@@ -10446,38 +10700,8 @@ var init_load_owner = __esm(() => {
|
|
|
10446
10700
|
init_check_result();
|
|
10447
10701
|
});
|
|
10448
10702
|
|
|
10449
|
-
// ../../loki-ts/src/engine10/testmap.ts
|
|
10450
|
-
import { basename as basename14, extname as extname2, join as join39, relative as relative3, sep as sep10 } from "path";
|
|
10451
|
-
function isTestFile(rel) {
|
|
10452
|
-
const name = basename14(rel);
|
|
10453
|
-
return JS_TEST_RE.test(name) || PY_TEST_RE.test(name) || GO_TEST_RE.test(name);
|
|
10454
|
-
}
|
|
10455
|
-
var SKIP_DIRS, JS_TEST_RE, PY_TEST_RE, GO_TEST_RE;
|
|
10456
|
-
var init_testmap = __esm(() => {
|
|
10457
|
-
init_resolve();
|
|
10458
|
-
init_scope();
|
|
10459
|
-
SKIP_DIRS = new Set([
|
|
10460
|
-
".git",
|
|
10461
|
-
"node_modules",
|
|
10462
|
-
"dist",
|
|
10463
|
-
"build",
|
|
10464
|
-
"target",
|
|
10465
|
-
"coverage",
|
|
10466
|
-
".venv",
|
|
10467
|
-
"venv",
|
|
10468
|
-
"__pycache__",
|
|
10469
|
-
".tox",
|
|
10470
|
-
".pytest_cache",
|
|
10471
|
-
".loki",
|
|
10472
|
-
".next"
|
|
10473
|
-
]);
|
|
10474
|
-
JS_TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
10475
|
-
PY_TEST_RE = /^(test_.+|.+_test)\.py$/;
|
|
10476
|
-
GO_TEST_RE = /_test\.go$/;
|
|
10477
|
-
});
|
|
10478
|
-
|
|
10479
10703
|
// ../../loki-ts/src/util/wall_owned.ts
|
|
10480
|
-
import { isAbsolute as isAbsolute4, join as
|
|
10704
|
+
import { isAbsolute as isAbsolute4, join as join41, relative as relative4 } from "path";
|
|
10481
10705
|
function failingLocations(raw2, cwd, repoDir) {
|
|
10482
10706
|
const out2 = raw2.replace(ANSI2, "").replace(/\r\n?/g, `
|
|
10483
10707
|
`), files = new Set;
|
|
@@ -10509,7 +10733,7 @@ function failingLocations(raw2, cwd, repoDir) {
|
|
|
10509
10733
|
}
|
|
10510
10734
|
if (errors2 === 0 || located !== errors2)
|
|
10511
10735
|
return null;
|
|
10512
|
-
return [...files].map((f) => relative4(repoDir, isAbsolute4(f) ? f :
|
|
10736
|
+
return [...files].map((f) => relative4(repoDir, isAbsolute4(f) ? f : join41(cwd, f)));
|
|
10513
10737
|
}
|
|
10514
10738
|
function wallOwnedFailure(raw2, cwd, repoDir, wall) {
|
|
10515
10739
|
const locs = failingLocations(raw2, cwd, repoDir);
|
|
@@ -10520,124 +10744,1236 @@ var init_wall_owned = __esm(() => {
|
|
|
10520
10744
|
ANSI2 = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
10521
10745
|
});
|
|
10522
10746
|
|
|
10523
|
-
// ../../loki-ts/src/
|
|
10524
|
-
import {
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10747
|
+
// ../../loki-ts/src/util/atomic.ts
|
|
10748
|
+
import {
|
|
10749
|
+
closeSync as closeSync6,
|
|
10750
|
+
fstatSync as fstatSync4,
|
|
10751
|
+
fsyncSync as fsyncSync2,
|
|
10752
|
+
lstatSync as lstatSync7,
|
|
10753
|
+
mkdirSync as mkdirSync11,
|
|
10754
|
+
openSync as openSync6,
|
|
10755
|
+
readSync as readSync5,
|
|
10756
|
+
renameSync as renameSync6,
|
|
10757
|
+
rmSync as rmSync9,
|
|
10758
|
+
statSync as statSync15,
|
|
10759
|
+
unlinkSync as unlinkSync3,
|
|
10760
|
+
writeSync as writeSync3
|
|
10761
|
+
} from "fs";
|
|
10762
|
+
import { dirname as dirname12 } from "path";
|
|
10763
|
+
function durableWriteThenRename(target, body) {
|
|
10764
|
+
mkdirSync11(dirname12(target), { recursive: true });
|
|
10765
|
+
const tmp = `${target}.tmp.${process.pid}.${++_tmpCounter}`;
|
|
10766
|
+
const fd = openSync6(tmp, "w");
|
|
10767
|
+
try {
|
|
10768
|
+
writeSync3(fd, body);
|
|
10769
|
+
fsyncSync2(fd);
|
|
10770
|
+
} finally {
|
|
10771
|
+
closeSync6(fd);
|
|
10772
|
+
}
|
|
10773
|
+
renameSync6(tmp, target);
|
|
10774
|
+
fsyncParentDir(target);
|
|
10539
10775
|
}
|
|
10540
|
-
function
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10776
|
+
function fsyncParentDir(target) {
|
|
10777
|
+
let dfd = null;
|
|
10778
|
+
try {
|
|
10779
|
+
dfd = openSync6(dirname12(target), "r");
|
|
10780
|
+
fsyncSync2(dfd);
|
|
10781
|
+
} catch {} finally {
|
|
10782
|
+
if (dfd !== null) {
|
|
10783
|
+
try {
|
|
10784
|
+
closeSync6(dfd);
|
|
10785
|
+
} catch {}
|
|
10545
10786
|
}
|
|
10546
|
-
case "vitest":
|
|
10547
|
-
return ["npx", ["vitest", "run", t.path]];
|
|
10548
|
-
case "jest":
|
|
10549
|
-
return ["npx", ["jest", t.path]];
|
|
10550
|
-
case "bun":
|
|
10551
|
-
return ["bun", ["test", t.path]];
|
|
10552
|
-
case "node":
|
|
10553
|
-
return ["node", ["--test", `./${t.path}`]];
|
|
10554
|
-
case "npm":
|
|
10555
|
-
return ["npm", ["test", "--silent"]];
|
|
10556
|
-
case "go":
|
|
10557
|
-
return ["go", ["test", "-json", `./${dirname12(t.path)}`]];
|
|
10558
|
-
case "cargo":
|
|
10559
|
-
return ["cargo", ["test"]];
|
|
10560
10787
|
}
|
|
10561
10788
|
}
|
|
10562
|
-
function
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
return { cwd: site?.cwd ?? repoDir, cmd, args, argv: [cmd, ...args], ...interpreter ? { interpreter } : {}, pkgRoot: site?.root ?? "." };
|
|
10566
|
-
}
|
|
10567
|
-
function changedFiles(repoDir, baseSha) {
|
|
10568
|
-
const run = (args) => safeGit(repoDir, args).split(`
|
|
10569
|
-
`).map((l) => l.trim()).filter(Boolean);
|
|
10570
|
-
const tracked = run(["diff", "--name-only", baseSha]);
|
|
10571
|
-
const untracked = run(["ls-files", "--others", "--exclude-standard"]);
|
|
10572
|
-
return [...new Set([...tracked, ...untracked])].filter((f) => !f.startsWith(".loki/"));
|
|
10573
|
-
}
|
|
10574
|
-
function testConfigChanged(repoDir, baseSha, changed) {
|
|
10575
|
-
const diff = (f) => safeGit(repoDir, ["diff", "-U0", baseSha, "--", f]);
|
|
10576
|
-
return changed.filter((f) => CFG_ALWAYS.test(f) || CFG_SHARED.test(f) && (() => {
|
|
10577
|
-
const d = diff(f);
|
|
10578
|
-
return !d || CFG_LINE.test(d);
|
|
10579
|
-
})());
|
|
10789
|
+
function atomicWriteJson(target, data) {
|
|
10790
|
+
durableWriteThenRename(target, `${JSON.stringify(data, null, 2)}
|
|
10791
|
+
`);
|
|
10580
10792
|
}
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10793
|
+
var _tmpCounter = 0, _appendChains;
|
|
10794
|
+
var init_atomic = __esm(() => {
|
|
10795
|
+
_appendChains = new Map;
|
|
10796
|
+
});
|
|
10797
|
+
|
|
10798
|
+
// ../../loki-ts/src/runner/state.ts
|
|
10799
|
+
var TERMINAL_STATUSES;
|
|
10800
|
+
var init_state = __esm(() => {
|
|
10801
|
+
init_paths();
|
|
10802
|
+
init_atomic();
|
|
10803
|
+
TERMINAL_STATUSES = new Set([
|
|
10804
|
+
"failed",
|
|
10805
|
+
"max_iterations_reached",
|
|
10806
|
+
"max_retries_exceeded",
|
|
10807
|
+
"exited",
|
|
10808
|
+
"council_approved",
|
|
10809
|
+
"council_force_approved",
|
|
10810
|
+
"completion_promise_fulfilled",
|
|
10811
|
+
"running"
|
|
10812
|
+
]);
|
|
10813
|
+
});
|
|
10814
|
+
|
|
10815
|
+
// ../../loki-ts/src/runner/repo_profile.ts
|
|
10816
|
+
import { existsSync as existsSync22, readFileSync as readFileSync29, mkdirSync as mkdirSync12, readdirSync as readdirSync12, renameSync as renameSync7, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "fs";
|
|
10817
|
+
import { basename as basename15, join as join42, resolve as resolve11, isAbsolute as isAbsolute5 } from "path";
|
|
10818
|
+
function loadProfile(repoDir) {
|
|
10819
|
+
try {
|
|
10820
|
+
const p = JSON.parse(readFileSync29(commandProfilePath(repoDir), "utf8"));
|
|
10821
|
+
return p?.version === 1 && Array.isArray(p.commands) && p.baseline ? p : null;
|
|
10822
|
+
} catch {
|
|
10823
|
+
return null;
|
|
10824
|
+
}
|
|
10609
10825
|
}
|
|
10610
|
-
function
|
|
10611
|
-
const
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
}
|
|
10826
|
+
function attribute(repoDir, baseSha, failing) {
|
|
10827
|
+
const p = loadProfile(repoDir), none = { preExistingChecks: [], preExistingIds: [], introducedChecks: failing.map((f) => f.name), line: null };
|
|
10828
|
+
if (!p || p.revision !== baseSha || !p.baseline.attributable || p.baseline.failing_ids.length === 0 || failing.length === 0)
|
|
10829
|
+
return none;
|
|
10830
|
+
const base = new Set(p.baseline.failing_ids), pre = failing.filter((f) => f.eligible && f.ids?.length && f.ids.every((i) => base.has(i)));
|
|
10831
|
+
const introduced = failing.filter((f) => !pre.includes(f)).map((f) => f.name);
|
|
10832
|
+
if (pre.length === 0)
|
|
10833
|
+
return { ...none, line: `baseline: ${base.size} known failure(s) before this run; none of the failing checks match them, ${introduced.length} introduced (blocking)` };
|
|
10834
|
+
return {
|
|
10835
|
+
preExistingChecks: pre.map((f) => f.name),
|
|
10836
|
+
preExistingIds: pre.flatMap((f) => f.ids ?? []),
|
|
10837
|
+
introducedChecks: introduced,
|
|
10838
|
+
line: `baseline: ${pre.length} pre-existing failing check(s) (not blocking), ${introduced.length} introduced${introduced.length ? " (blocking)" : ""}`
|
|
10618
10839
|
};
|
|
10619
|
-
const lines = out2.slice(-65536).split(`
|
|
10620
|
-
`).map((l2) => (l2.startsWith("{") ? goOut(l2) : l2).trim()).filter(Boolean), l = lines.find((x) => /^(FAILED\s|\u25cf\s.*\u203a|not ok\s|_{3,}\s.+\s_{3,}$)/.test(x)) ?? lines.find((x) => /^[\w./-]+\.go:\d+:\d+: \S/.test(x)) ?? lines.find((x) => /fail|error/i.test(x) && !/^(=|\u2713|ok\b|PASS)/.test(x)) ?? "";
|
|
10621
|
-
return l.replace(/\d{4}-\d\d-\d\dT[\d:.]+Z?/g, "").replace(/(^|\s)\/(?:[\w.@-]+\/)*[\w.@-]+/g, "$1<path>").replace(/:\d+(?::\d+)?/g, "").replace(/\[?\d+(?:\.\d+)?m?s\]?/g, "").replace(/\s+/g, " ").slice(0, 160);
|
|
10622
10840
|
}
|
|
10623
|
-
|
|
10624
|
-
const
|
|
10625
|
-
const
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10841
|
+
function attributeFullSuite(repoDir, baseSha, full, blocked, changed) {
|
|
10842
|
+
const bad = full.filter((c) => c.result === "fail"), none = { preExistingChecks: [], preExistingIds: [], introducedChecks: bad.map((c) => c.name), line: null };
|
|
10843
|
+
const norm3 = (s) => s.replace(/\s+--silent\b/, "").trim(), p = loadProfile(repoDir);
|
|
10844
|
+
if (!bad.length || !p || p.revision !== baseSha || !p.baseline.attributable || !p.baseline.failing_ids.length)
|
|
10845
|
+
return none;
|
|
10846
|
+
if (bad.some((c) => norm3(c.cmd) !== norm3(p.baseline.command ?? "")))
|
|
10847
|
+
return { ...none, line: `baseline: ran "${p.baseline.command}", not the full suite command, ids not comparable, full suite failures not excused` };
|
|
10848
|
+
const block = new Set(blocked);
|
|
10849
|
+
return attribute(repoDir, baseSha, bad.map((c) => ({ name: c.name, ...c.ids ? { ids: c.ids } : {}, eligible: (c.ids ?? []).every((i) => !block.has(i) && !changed.some((f) => i.includes(f))) })));
|
|
10850
|
+
}
|
|
10851
|
+
var commandProfilePath = (repoDir) => join42(repoDir, ".loki", "state", "repo-profile.json");
|
|
10852
|
+
var init_repo_profile = __esm(() => {
|
|
10853
|
+
init_failures();
|
|
10854
|
+
init_check_result();
|
|
10855
|
+
init_safe_git();
|
|
10856
|
+
init_paths();
|
|
10857
|
+
init_state();
|
|
10858
|
+
});
|
|
10859
|
+
|
|
10860
|
+
// ../../loki-ts/src/project_graph.ts
|
|
10861
|
+
import { createHash as createHash10 } from "crypto";
|
|
10862
|
+
import {
|
|
10863
|
+
existsSync as existsSync23,
|
|
10864
|
+
readdirSync as readdirSync13,
|
|
10865
|
+
readFileSync as readFileSync30,
|
|
10866
|
+
statSync as statSync16,
|
|
10867
|
+
writeFileSync as writeFileSync15,
|
|
10868
|
+
mkdirSync as mkdirSync13
|
|
10869
|
+
} from "fs";
|
|
10870
|
+
import { basename as basename16, dirname as dirname13, join as join43, resolve as resolve12 } from "path";
|
|
10871
|
+
function readManifest(path) {
|
|
10872
|
+
if (!existsSync23(path)) {
|
|
10873
|
+
return null;
|
|
10874
|
+
}
|
|
10875
|
+
let raw2;
|
|
10876
|
+
try {
|
|
10877
|
+
raw2 = readFileSync30(path, "utf8");
|
|
10878
|
+
} catch {
|
|
10879
|
+
return null;
|
|
10880
|
+
}
|
|
10881
|
+
let parsed;
|
|
10882
|
+
try {
|
|
10883
|
+
parsed = JSON.parse(raw2);
|
|
10884
|
+
} catch {
|
|
10885
|
+
return null;
|
|
10886
|
+
}
|
|
10887
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
10888
|
+
return null;
|
|
10889
|
+
}
|
|
10890
|
+
const obj = parsed;
|
|
10891
|
+
if (obj["schema_version"] !== SUPPORTED_SCHEMA_VERSION) {
|
|
10892
|
+
console.warn(`[loki:project_graph] unsupported schema_version in ${path}: ` + `${String(obj["schema_version"])} (expected ${SUPPORTED_SCHEMA_VERSION})`);
|
|
10893
|
+
return null;
|
|
10894
|
+
}
|
|
10895
|
+
const appId = obj["app_id"];
|
|
10896
|
+
if (typeof appId !== "string" || !APP_ID_REGEX.test(appId)) {
|
|
10897
|
+
console.warn(`[loki:project_graph] invalid app_id in ${path}: ${String(appId)}`);
|
|
10898
|
+
return null;
|
|
10899
|
+
}
|
|
10900
|
+
const members = Array.isArray(obj["members"]) ? obj["members"].filter((m) => typeof m === "string") : [];
|
|
10901
|
+
const sharedMemoryDir = typeof obj["shared_memory_dir"] === "string" ? obj["shared_memory_dir"] : undefined;
|
|
10902
|
+
return {
|
|
10903
|
+
schema_version: SUPPORTED_SCHEMA_VERSION,
|
|
10904
|
+
app_id: appId,
|
|
10905
|
+
members,
|
|
10906
|
+
shared_memory_dir: sharedMemoryDir
|
|
10907
|
+
};
|
|
10908
|
+
}
|
|
10909
|
+
function resolveMembers(rootDir, declared, selfBasename) {
|
|
10910
|
+
const out2 = [];
|
|
10911
|
+
const seen = new Set;
|
|
10912
|
+
let children;
|
|
10913
|
+
try {
|
|
10914
|
+
children = readdirSync13(rootDir);
|
|
10915
|
+
} catch {
|
|
10916
|
+
children = [];
|
|
10917
|
+
}
|
|
10918
|
+
const matchPattern = (pattern, name) => {
|
|
10919
|
+
if (!pattern.includes("*") && !pattern.includes("?")) {
|
|
10920
|
+
return pattern === name;
|
|
10921
|
+
}
|
|
10922
|
+
const re = new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
10923
|
+
return re.test(name);
|
|
10924
|
+
};
|
|
10925
|
+
for (const name of declared) {
|
|
10926
|
+
for (const child of children) {
|
|
10927
|
+
if (!matchPattern(name, child))
|
|
10928
|
+
continue;
|
|
10929
|
+
const full = join43(rootDir, child);
|
|
10930
|
+
if (seen.has(full))
|
|
10931
|
+
continue;
|
|
10932
|
+
let st;
|
|
10933
|
+
try {
|
|
10934
|
+
st = statSync16(full);
|
|
10935
|
+
} catch {
|
|
10936
|
+
continue;
|
|
10937
|
+
}
|
|
10938
|
+
if (!st.isDirectory())
|
|
10939
|
+
continue;
|
|
10940
|
+
seen.add(full);
|
|
10941
|
+
out2.push(full);
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
if (declared.length === 0) {
|
|
10945
|
+
for (const child of children) {
|
|
10946
|
+
const full = join43(rootDir, child);
|
|
10947
|
+
if (seen.has(full))
|
|
10948
|
+
continue;
|
|
10949
|
+
let st;
|
|
10950
|
+
try {
|
|
10951
|
+
st = statSync16(full);
|
|
10952
|
+
} catch {
|
|
10953
|
+
continue;
|
|
10954
|
+
}
|
|
10955
|
+
if (!st.isDirectory())
|
|
10956
|
+
continue;
|
|
10957
|
+
if (!existsSync23(join43(full, ".loki", "app.json")))
|
|
10958
|
+
continue;
|
|
10959
|
+
seen.add(full);
|
|
10960
|
+
out2.push(full);
|
|
10961
|
+
}
|
|
10962
|
+
}
|
|
10963
|
+
return out2;
|
|
10964
|
+
}
|
|
10965
|
+
function cacheKey(manifestPaths) {
|
|
10966
|
+
const parts = [];
|
|
10967
|
+
for (const p of manifestPaths.sort()) {
|
|
10968
|
+
let mtime = 0;
|
|
10969
|
+
try {
|
|
10970
|
+
mtime = statSync16(p).mtimeMs;
|
|
10971
|
+
} catch {}
|
|
10972
|
+
parts.push(`${p}:${mtime}`);
|
|
10973
|
+
}
|
|
10974
|
+
return createHash10("sha256").update(parts.join("|")).digest("hex");
|
|
10975
|
+
}
|
|
10976
|
+
function writeCache(targetDir, key, result) {
|
|
10977
|
+
try {
|
|
10978
|
+
const cacheDir = join43(targetDir, ".loki", "state");
|
|
10979
|
+
mkdirSync13(cacheDir, { recursive: true });
|
|
10980
|
+
const cachePath = join43(cacheDir, "project-graph.json");
|
|
10981
|
+
writeFileSync15(cachePath, JSON.stringify({ key, result }, null, 2), "utf8");
|
|
10982
|
+
} catch {}
|
|
10983
|
+
}
|
|
10984
|
+
function discoverProjectGraph(targetDir) {
|
|
10985
|
+
const absTarget = resolve12(targetDir);
|
|
10986
|
+
const targetManifest = join43(absTarget, ".loki", "app.json");
|
|
10987
|
+
const parentDir = dirname13(absTarget);
|
|
10988
|
+
const parentManifest = join43(parentDir, ".loki", "app.json");
|
|
10989
|
+
const examined = [];
|
|
10990
|
+
let chosenManifest = null;
|
|
10991
|
+
let chosenRoot = "";
|
|
10992
|
+
if (existsSync23(parentManifest) && parentDir !== absTarget) {
|
|
10993
|
+
examined.push(parentManifest);
|
|
10994
|
+
chosenManifest = readManifest(parentManifest);
|
|
10995
|
+
chosenRoot = parentDir;
|
|
10996
|
+
}
|
|
10997
|
+
if (!chosenManifest && existsSync23(targetManifest)) {
|
|
10998
|
+
examined.push(targetManifest);
|
|
10999
|
+
chosenManifest = readManifest(targetManifest);
|
|
11000
|
+
chosenRoot = absTarget;
|
|
11001
|
+
}
|
|
11002
|
+
if (!chosenManifest)
|
|
11003
|
+
return null;
|
|
11004
|
+
const declared = chosenManifest.members ?? [];
|
|
11005
|
+
const members = resolveMembers(chosenRoot, declared, basename16(absTarget));
|
|
11006
|
+
const filtered = [];
|
|
11007
|
+
for (const m of members) {
|
|
11008
|
+
const sibManifestPath = join43(m, ".loki", "app.json");
|
|
11009
|
+
if (existsSync23(sibManifestPath)) {
|
|
11010
|
+
examined.push(sibManifestPath);
|
|
11011
|
+
const sibManifest = readManifest(sibManifestPath);
|
|
11012
|
+
if (sibManifest && sibManifest.app_id !== chosenManifest.app_id) {
|
|
11013
|
+
continue;
|
|
11014
|
+
}
|
|
11015
|
+
}
|
|
11016
|
+
filtered.push(m);
|
|
11017
|
+
}
|
|
11018
|
+
const result = {
|
|
11019
|
+
appId: chosenManifest.app_id,
|
|
11020
|
+
root: chosenRoot,
|
|
11021
|
+
members: filtered,
|
|
11022
|
+
sharedMemoryDir: chosenManifest.shared_memory_dir
|
|
11023
|
+
};
|
|
11024
|
+
writeCache(absTarget, cacheKey(examined), result);
|
|
11025
|
+
return result;
|
|
11026
|
+
}
|
|
11027
|
+
var APP_ID_REGEX, SUPPORTED_SCHEMA_VERSION = 1;
|
|
11028
|
+
var init_project_graph = __esm(() => {
|
|
11029
|
+
APP_ID_REGEX = /^[a-z0-9-]{3,40}$/;
|
|
11030
|
+
});
|
|
11031
|
+
|
|
11032
|
+
// ../../loki-ts/src/util/shell.ts
|
|
11033
|
+
async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
|
|
11034
|
+
const reader = stream.getReader();
|
|
11035
|
+
const decoder = new TextDecoder;
|
|
11036
|
+
let out2 = "";
|
|
11037
|
+
let total = 0;
|
|
11038
|
+
try {
|
|
11039
|
+
while (total < maxBytes) {
|
|
11040
|
+
const { done, value } = await reader.read();
|
|
11041
|
+
if (done)
|
|
11042
|
+
break;
|
|
11043
|
+
if (!value)
|
|
11044
|
+
continue;
|
|
11045
|
+
total += value.byteLength;
|
|
11046
|
+
if (total > maxBytes) {
|
|
11047
|
+
const keep = value.byteLength - (total - maxBytes);
|
|
11048
|
+
out2 += decoder.decode(value.subarray(0, keep), { stream: true });
|
|
11049
|
+
break;
|
|
11050
|
+
}
|
|
11051
|
+
out2 += decoder.decode(value, { stream: true });
|
|
11052
|
+
}
|
|
11053
|
+
out2 += decoder.decode();
|
|
11054
|
+
} finally {
|
|
11055
|
+
try {
|
|
11056
|
+
await reader.cancel();
|
|
11057
|
+
} catch {}
|
|
11058
|
+
reader.releaseLock();
|
|
11059
|
+
}
|
|
11060
|
+
return out2;
|
|
11061
|
+
}
|
|
11062
|
+
async function run(argv, opts = {}) {
|
|
11063
|
+
const proc = Bun.spawn({
|
|
11064
|
+
cmd: [...argv],
|
|
11065
|
+
stdout: "pipe",
|
|
11066
|
+
stderr: "pipe",
|
|
11067
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
11068
|
+
cwd: opts.cwd
|
|
11069
|
+
});
|
|
11070
|
+
let timer;
|
|
11071
|
+
let killTimer;
|
|
11072
|
+
if (opts.timeoutMs && opts.timeoutMs > 0) {
|
|
11073
|
+
timer = setTimeout(() => {
|
|
11074
|
+
try {
|
|
11075
|
+
proc.kill("SIGTERM");
|
|
11076
|
+
} catch {}
|
|
11077
|
+
killTimer = setTimeout(() => {
|
|
11078
|
+
try {
|
|
11079
|
+
proc.kill("SIGKILL");
|
|
11080
|
+
} catch {}
|
|
11081
|
+
}, 2000);
|
|
11082
|
+
}, opts.timeoutMs);
|
|
11083
|
+
}
|
|
11084
|
+
try {
|
|
11085
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
11086
|
+
readStreamCapped(proc.stdout),
|
|
11087
|
+
new Response(proc.stderr).text(),
|
|
11088
|
+
proc.exited
|
|
11089
|
+
]);
|
|
11090
|
+
return { stdout, stderr, exitCode };
|
|
11091
|
+
} finally {
|
|
11092
|
+
if (timer)
|
|
11093
|
+
clearTimeout(timer);
|
|
11094
|
+
if (killTimer)
|
|
11095
|
+
clearTimeout(killTimer);
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
var MAX_STDOUT_BYTES;
|
|
11099
|
+
var init_shell = __esm(() => {
|
|
11100
|
+
MAX_STDOUT_BYTES = 16 * 1024 * 1024;
|
|
11101
|
+
});
|
|
11102
|
+
|
|
11103
|
+
// ../../loki-ts/src/e10ext/budget_cap.ts
|
|
11104
|
+
var DEFAULT_MAX_COST_USD;
|
|
11105
|
+
var init_budget_cap = __esm(() => {
|
|
11106
|
+
init_budget();
|
|
11107
|
+
DEFAULT_MAX_COST_USD = loadDefaultMaxCostUsd();
|
|
11108
|
+
});
|
|
11109
|
+
|
|
11110
|
+
// ../../loki-ts/src/runner/retry_class.ts
|
|
11111
|
+
var init_retry_class = () => {};
|
|
11112
|
+
|
|
11113
|
+
// ../../loki-ts/src/features/speed/unit_mode.ts
|
|
11114
|
+
import { closeSync as closeSync7, constants as constants2, fstatSync as fstatSync5, lstatSync as lstatSync8, openSync as openSync7, readSync as readSync6, realpathSync as realpathSync16 } from "fs";
|
|
11115
|
+
function readBounded(path) {
|
|
11116
|
+
const l = lstatSync8(path);
|
|
11117
|
+
if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
|
|
11118
|
+
return null;
|
|
11119
|
+
const fd = openSync7(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
11120
|
+
try {
|
|
11121
|
+
const st = fstatSync5(fd);
|
|
11122
|
+
if (!st.isFile() || st.size > MAX_SPEC_BYTES)
|
|
11123
|
+
return null;
|
|
11124
|
+
const buf = Buffer.alloc(st.size + 1);
|
|
11125
|
+
let n2 = 0;
|
|
11126
|
+
for (;; ) {
|
|
11127
|
+
const r = readSync6(fd, buf, n2, buf.length - n2, null);
|
|
11128
|
+
if (r <= 0)
|
|
11129
|
+
break;
|
|
11130
|
+
n2 += r;
|
|
11131
|
+
if (n2 >= buf.length)
|
|
11132
|
+
return null;
|
|
11133
|
+
}
|
|
11134
|
+
return buf.subarray(0, n2).toString("utf8");
|
|
11135
|
+
} finally {
|
|
11136
|
+
closeSync7(fd);
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
function parseText(txt) {
|
|
11140
|
+
try {
|
|
11141
|
+
const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
|
|
11142
|
+
if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
|
|
11143
|
+
return null;
|
|
11144
|
+
return { id: j.id, writeSet: ws, pack, tokenBudget: tb };
|
|
11145
|
+
} catch {
|
|
11146
|
+
return null;
|
|
11147
|
+
}
|
|
11148
|
+
}
|
|
11149
|
+
function parseSpec(path) {
|
|
11150
|
+
try {
|
|
11151
|
+
const txt = readBounded(path);
|
|
11152
|
+
return txt === null ? null : parseText(txt);
|
|
11153
|
+
} catch {
|
|
11154
|
+
return null;
|
|
11155
|
+
}
|
|
11156
|
+
}
|
|
11157
|
+
function unitSpec(env = process.env) {
|
|
11158
|
+
const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
|
|
11159
|
+
if (env["LOKI_SPEED"] === "0" || !path)
|
|
11160
|
+
return null;
|
|
11161
|
+
if (frozen !== undefined)
|
|
11162
|
+
return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
|
|
11163
|
+
if (!memo.has(path))
|
|
11164
|
+
memo.set(path, parseSpec(path));
|
|
11165
|
+
return memo.get(path) ?? null;
|
|
11166
|
+
}
|
|
11167
|
+
function unitFlags(staged, env = process.env, pre = {}) {
|
|
11168
|
+
const s = unitSpec(env);
|
|
11169
|
+
return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
|
|
11170
|
+
}
|
|
11171
|
+
var MAX_SPEC_BYTES, MAX_TOKEN_BUDGET = 1e9, norm3 = (p) => p.replace(/^\.\//, ""), CTRL, strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm3) : null, memo, inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
|
|
11172
|
+
var init_unit_mode = __esm(() => {
|
|
11173
|
+
MAX_SPEC_BYTES = 1024 * 1024;
|
|
11174
|
+
CTRL = /[\x00-\x1f\x7f]/;
|
|
11175
|
+
memo = new Map;
|
|
11176
|
+
});
|
|
11177
|
+
|
|
11178
|
+
// ../../loki-ts/src/e10ext/repomemory.ts
|
|
11179
|
+
var init_repomemory = __esm(() => {
|
|
11180
|
+
init_cache();
|
|
11181
|
+
});
|
|
11182
|
+
|
|
11183
|
+
// ../../loki-ts/src/e10ext/context.ts
|
|
11184
|
+
var FINISH_LINE = "Finish with exactly one line: LOKI_DONE, or LOKI_ALREADY_DONE: <file:line evidence>, or LOKI_SPEC_CONFLICT: <reason>.", FIXED_RULES;
|
|
11185
|
+
var init_context = __esm(() => {
|
|
11186
|
+
init_cache();
|
|
11187
|
+
init_sizing();
|
|
11188
|
+
init_unit_mode();
|
|
11189
|
+
init_repomemory();
|
|
11190
|
+
init_repo_profile();
|
|
11191
|
+
FIXED_RULES = [
|
|
11192
|
+
"You are the Loki 10 implement stage, doing the complete task described below in this repository. Do whatever a senior engineer would do to finish it correctly: read, change and test as needed across any files the task requires.",
|
|
11193
|
+
"Rules:",
|
|
11194
|
+
"- The Wall tests are read-only: do not edit or delete them. Existing test files are append-only: you may add new test functions, but never edit or delete an existing one.",
|
|
11195
|
+
"- Exception: when the task text itself states the new expected value of an existing assertion, change only that literal in that assertion; never remove, skip or loosen an assertion, and never edit one the task text does not name.",
|
|
11196
|
+
"- Run any tests you need, including the package's full suite (the Project Model commands below say how). The impacted tests named below are a starting hint, not a limit.",
|
|
11197
|
+
"- Never leave a long-lived server running.",
|
|
11198
|
+
"- Never kill processes.",
|
|
11199
|
+
"- Write no documentation unless the task explicitly asks for it.",
|
|
11200
|
+
"- Do not commit or push.",
|
|
11201
|
+
"- If you add a new third-party registry dependency, list it in .loki/supply-declared.json as a JSON array of {ecosystem,name,version_spec,registry}, leaving out workspace, path, git and local packages.",
|
|
11202
|
+
FINISH_LINE
|
|
11203
|
+
].join(`
|
|
11204
|
+
`);
|
|
11205
|
+
});
|
|
11206
|
+
|
|
11207
|
+
// ../../loki-ts/src/util/steer.ts
|
|
11208
|
+
var STEER_MAX_BYTES, STEER_STAGES;
|
|
11209
|
+
var init_steer = __esm(() => {
|
|
11210
|
+
init_context();
|
|
11211
|
+
STEER_MAX_BYTES = 1024 * 1024;
|
|
11212
|
+
STEER_STAGES = new Set(["plan", "implement"]);
|
|
11213
|
+
});
|
|
11214
|
+
|
|
11215
|
+
// ../../loki-ts/src/util/pr_lessons.ts
|
|
11216
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync31 } from "fs";
|
|
11217
|
+
import { dirname as dirname14, join as join44 } from "path";
|
|
11218
|
+
function loadLessons(repoDir) {
|
|
11219
|
+
const p = lessonsPath(repoDir);
|
|
11220
|
+
if (!existsSync24(p))
|
|
11221
|
+
return [];
|
|
11222
|
+
try {
|
|
11223
|
+
const j = JSON.parse(readFileSync31(p, "utf-8"));
|
|
11224
|
+
return Array.isArray(j.lessons) ? j.lessons : [];
|
|
11225
|
+
} catch {
|
|
11226
|
+
return [];
|
|
11227
|
+
}
|
|
11228
|
+
}
|
|
11229
|
+
function save(repoDir, lessons) {
|
|
11230
|
+
const p = lessonsPath(repoDir);
|
|
11231
|
+
mkdirSync14(dirname14(p), { recursive: true });
|
|
11232
|
+
atomicWriteJson(p, { version: 2, lessons });
|
|
11233
|
+
}
|
|
11234
|
+
function recordRunVerdict(repoDir, runId, verdict) {
|
|
11235
|
+
const lessons = loadLessons(repoDir);
|
|
11236
|
+
let dirty = false;
|
|
11237
|
+
for (const l of lessons)
|
|
11238
|
+
for (const u of l.uses)
|
|
11239
|
+
if (u.run_id === runId && u.verdict === null) {
|
|
11240
|
+
u.verdict = verdict;
|
|
11241
|
+
dirty = true;
|
|
11242
|
+
}
|
|
11243
|
+
if (dirty)
|
|
11244
|
+
save(repoDir, lessons);
|
|
11245
|
+
}
|
|
11246
|
+
var lessonsPath = (repoDir) => join44(repoDir, ".loki", "memory", "semantic", "pr-lessons.json");
|
|
11247
|
+
var init_pr_lessons = __esm(() => {
|
|
11248
|
+
init_atomic();
|
|
11249
|
+
});
|
|
11250
|
+
|
|
11251
|
+
// ../../loki-ts/src/runner/model_downgrades.ts
|
|
11252
|
+
var init_model_downgrades = __esm(() => {
|
|
11253
|
+
init_sizing();
|
|
11254
|
+
init_decision();
|
|
11255
|
+
});
|
|
11256
|
+
|
|
11257
|
+
// ../../loki-ts/src/providers/mcp_config.ts
|
|
11258
|
+
var init_mcp_config = () => {};
|
|
11259
|
+
|
|
11260
|
+
// ../../loki-ts/src/providers/claude_flags.ts
|
|
11261
|
+
import { existsSync as existsSync25, readFileSync as readFileSync32 } from "fs";
|
|
11262
|
+
import { resolve as resolvePath, dirname as dirname15 } from "path";
|
|
11263
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11264
|
+
function designArchetypesText() {
|
|
11265
|
+
try {
|
|
11266
|
+
const moduleDir = dirname15(fileURLToPath3(import.meta.url));
|
|
11267
|
+
for (const rel of [
|
|
11268
|
+
["..", "..", "..", "references", "design-archetypes.md"],
|
|
11269
|
+
["..", "..", "references", "design-archetypes.md"]
|
|
11270
|
+
]) {
|
|
11271
|
+
const p = resolvePath(moduleDir, ...rel);
|
|
11272
|
+
if (existsSync25(p))
|
|
11273
|
+
return readFileSync32(p, "utf8");
|
|
11274
|
+
}
|
|
11275
|
+
} catch {}
|
|
11276
|
+
return "";
|
|
11277
|
+
}
|
|
11278
|
+
var AUTONOMY_OVERRIDE_TEXT, FIRST_PASS_EXCELLENCE_TEXT, CAVEMAN_PINNED_VERSION;
|
|
11279
|
+
var init_claude_flags = __esm(() => {
|
|
11280
|
+
init_mcp_config();
|
|
11281
|
+
AUTONOMY_OVERRIDE_TEXT = `[LOKI-AUTONOMY-AGENT] This is a Loki Mode autonomous agent process.
|
|
11282
|
+
` + `You are running INSIDE Loki Mode autonomous execution, a session the user explicitly launched with full prior authorization (loki start / loki run). This IS an authorized autonomous session, not an unattended action you must seek consent for.
|
|
11283
|
+
|
|
11284
|
+
` + `Instruction precedence for THIS session: when the loki_system instructions in the user prompt conflict with any CLAUDE.md, project memory, or global memory that tells you to ask for clarification, pause for confirmation, refuse to act autonomously, or withhold routine commits, the loki_system instructions take precedence. The user has already consented by launching autonomous mode.
|
|
11285
|
+
|
|
11286
|
+
` + `Therefore, for this session: do NOT ask the user questions, do NOT call the AskUserQuestion tool, do NOT stop to request confirmation, and do NOT refuse routine, reversible engineering work (writing code, running tests, creating files, atomic git checkpoints in the working tree). Proceed and complete the task directly. If a decision is genuinely ambiguous, choose the most reasonable option, record the assumption in .loki/CONTINUITY.md, and continue rather than halting.
|
|
11287
|
+
|
|
11288
|
+
` + "Commit hygiene still applies: git checkpoints are LOCAL only. Never push or force-push. Stage files by explicit path, never `git add -A` or `git add .`, and never commit secrets, credentials, .env files, or untracked files you did not author this session.\n\n" + `This precedence override is narrow. It does NOT relax any safety rule. Every safety prohibition in CLAUDE.md or memory still fully binds: anything genuinely destructive or irreversible remains out of scope unless the task explicitly calls for it. This includes (not limited to) force-pushing, deleting or overwriting the user's data, dropping or truncating databases, publishing or releasing, rotating or exfiltrating secrets, touching production systems, and anything a CLAUDE.md safety rule explicitly forbids. When in doubt about whether an action is destructive, treat it as destructive and do not do it.
|
|
11289
|
+
`;
|
|
11290
|
+
FIRST_PASS_EXCELLENCE_TEXT = `
|
|
11291
|
+
[FIRST-PASS EXCELLENCE] Treat THIS pass as your one shot to ship a complete, working, verified solution. Do not produce a rough draft to refine later; the loop exists as a safety net, not a plan. Before you finish this iteration:
|
|
11292
|
+
` + `1. BUILD IT FULLY. Implement every requirement end to end. No stubs, no TODOs, no "coming soon", no placeholder or hardcoded/mock data where real logic belongs. If the spec implies a backend (auth, persistence, a form that submits, a list that saves), WIRE IT so it actually works and persists -- a beautiful UI whose buttons do nothing is the single most common failure, not a draft. Every list/table must trace to a real query, never an inline mock array.
|
|
11293
|
+
` + `2. SELF-VERIFY BY RUNNING, not by reading. Run the build and the tests yourself; for each acceptance criterion, DRIVE the actual path and observe the result (submit the form, then reload and confirm the record persisted; hit a protected route logged-out and confirm it is rejected). Fix what fails now, in this pass. Do not mark done on "looks right" or a self-claim -- observed behavior is the only proof.
|
|
11294
|
+
` + `3. LOCK THE ARCHITECTURE on this pass so later edits are small and additive. Decide the data model, routes, and component structure up front and build to them; never rewrite whole files later to patch a small thing (that is the doom loop that breaks working features).
|
|
11295
|
+
` + `4. DESIGN: commit to ONE named aesthetic direction up front (editorial, brutalist, luxury, retro-futuristic, soft/pastel, industrial, etc. -- chosen from the product domain) and hold it on every surface. Use real content (never lorem). AVOID the AI-slop tells that instantly read as machine-generated: NO indigo/blue-to-purple gradient (the #1 tell), NO Inter/Roboto/system-font headlines (pick a real display+body pairing), NO three-equal-rounded-cards-in-a-row skeleton, NO flat 1px gray card borders or colored left-border strips, NO untouched shadcn defaults, NO reflexive dark mode. Cap the palette at ~3 hues (60/30/10), tinted not pure #fff/#000, separate sections by whitespace then a slight background shift before any border. Aim for Linear/Stripe/Duolingo-tier taste: "this does not look AI-generated".
|
|
11296
|
+
` + `5. SATISFY THE GATES THAT WILL JUDGE THIS PASS. Your work is checked by deterministic gates before it can be accepted, and in measured runs EVERY extra iteration was caused by one of these rejecting the work -- never by the model failing to finish. Clear them now, in this pass:
|
|
11297
|
+
` + ` - STATIC ANALYSIS: no syntax errors, no unused/undefined symbols, no lint errors in files you touched.
|
|
11298
|
+
` + ` - TEST SUITE: the existing tests must still pass. Run them; do not assume.
|
|
11299
|
+
` + ` - MUTATION/MOCK INTEGRITY: tests must assert real behaviour. No tautological assertions (expect(true).toBe(true)), no test that passes whether or not the code works, no mocking the very unit under test, no inline mock data standing in for a real query.
|
|
11300
|
+
` + ` - CODE REVIEW: no scope creep beyond the stated task, no dead or commented-out code, no leftover debug output, and follow the conventions already in the surrounding files.
|
|
11301
|
+
` + `Deliver the finished, self-verified, genuinely-designed result in THIS pass. Additional iterations should be the exception, not the plan.
|
|
11302
|
+
` + designArchetypesText();
|
|
11303
|
+
CAVEMAN_PINNED_VERSION = process.env["LOKI_CAVEMAN_VERSION"] || "1.9.0";
|
|
11304
|
+
});
|
|
11305
|
+
|
|
11306
|
+
// ../../loki-ts/src/runner/sdk_stream_parser.ts
|
|
11307
|
+
var init_sdk_stream_parser = __esm(() => {
|
|
11308
|
+
init_cost();
|
|
11309
|
+
init_budget();
|
|
11310
|
+
});
|
|
11311
|
+
|
|
11312
|
+
// ../../loki-ts/src/runner/trim.ts
|
|
11313
|
+
var init_trim = () => {};
|
|
11314
|
+
|
|
11315
|
+
// ../../loki-ts/src/util/update_check.ts
|
|
11316
|
+
var CACHE_TTL_MS;
|
|
11317
|
+
var init_update_check = __esm(() => {
|
|
11318
|
+
init_paths();
|
|
11319
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
11320
|
+
});
|
|
11321
|
+
|
|
11322
|
+
// ../../loki-ts/src/runner/router/advisor_probe.ts
|
|
11323
|
+
var init_advisor_probe = __esm(() => {
|
|
11324
|
+
init_update_check();
|
|
11325
|
+
});
|
|
11326
|
+
|
|
11327
|
+
// ../../loki-ts/src/runner/provider_opencode.ts
|
|
11328
|
+
var init_provider_opencode = __esm(() => {
|
|
11329
|
+
init_shell();
|
|
11330
|
+
init_providers();
|
|
11331
|
+
});
|
|
11332
|
+
|
|
11333
|
+
// ../../loki-ts/src/runner/providers.ts
|
|
11334
|
+
var _installedVersionCache;
|
|
11335
|
+
var init_providers = __esm(() => {
|
|
11336
|
+
init_shell();
|
|
11337
|
+
init_paths();
|
|
11338
|
+
init_claude_flags();
|
|
11339
|
+
init_mcp_config();
|
|
11340
|
+
init_lean_prefix();
|
|
11341
|
+
init_sdk_stream_parser();
|
|
11342
|
+
init_trim();
|
|
11343
|
+
init_advisor_probe();
|
|
11344
|
+
init_claude_model();
|
|
11345
|
+
init_model_routes();
|
|
11346
|
+
init_provider_opencode();
|
|
11347
|
+
_installedVersionCache = new Map;
|
|
11348
|
+
});
|
|
11349
|
+
|
|
11350
|
+
// ../../loki-ts/src/runner/router/route_record.ts
|
|
11351
|
+
import { existsSync as existsSync26, lstatSync as lstatSync9, mkdirSync as mkdirSync15, readFileSync as readFileSync33, writeFileSync as writeFileSync16 } from "fs";
|
|
11352
|
+
import { join as join45 } from "path";
|
|
11353
|
+
function loadRouteRecord(runDir) {
|
|
11354
|
+
try {
|
|
11355
|
+
const p = join45(runDir, ROUTE_RECORD_FILE);
|
|
11356
|
+
if (!existsSync26(p) || !lstatSync9(p).isFile())
|
|
11357
|
+
return;
|
|
11358
|
+
const v = JSON.parse(readFileSync33(p, "utf8"));
|
|
11359
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
11360
|
+
} catch {
|
|
11361
|
+
return;
|
|
11362
|
+
}
|
|
11363
|
+
}
|
|
11364
|
+
var ROUTE_RECORD_FILE = "route.json";
|
|
11365
|
+
var init_route_record = () => {};
|
|
11366
|
+
|
|
11367
|
+
// ../../loki-ts/src/runner/router/history_store.ts
|
|
11368
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync34, renameSync as renameSync8, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "fs";
|
|
11369
|
+
import { resolve as resolve13 } from "path";
|
|
11370
|
+
function validRun(v) {
|
|
11371
|
+
if (typeof v !== "object" || v === null)
|
|
11372
|
+
return false;
|
|
11373
|
+
const r = v;
|
|
11374
|
+
return typeof r["shape"] === "string" && typeof r["executor"] === "string" && EXECUTORS.includes(r["executor"]) && typeof r["verdict"] === "string" && VERDICTS.includes(r["verdict"]) && (r["owner"] === null || typeof r["owner"] === "string" && OWNERS.includes(r["owner"])) && typeof r["escalated"] === "boolean" && typeof r["usd"] === "number" && Number.isFinite(r["usd"]) && typeof r["wallS"] === "number" && Number.isFinite(r["wallS"]);
|
|
11375
|
+
}
|
|
11376
|
+
function historyPath(key, cacheRoot) {
|
|
11377
|
+
return resolve13(repoCacheDir(key, cacheRoot), HISTORY_FILE);
|
|
11378
|
+
}
|
|
11379
|
+
function readRunHistory(key, cacheRoot) {
|
|
11380
|
+
const path = historyPath(key, cacheRoot);
|
|
11381
|
+
if (!existsSync27(path))
|
|
11382
|
+
return [];
|
|
11383
|
+
try {
|
|
11384
|
+
const parsed = JSON.parse(readFileSync34(path, "utf8"));
|
|
11385
|
+
const runs2 = parsed?.runs;
|
|
11386
|
+
return Array.isArray(runs2) ? runs2.filter(validRun) : [];
|
|
11387
|
+
} catch {
|
|
11388
|
+
return [];
|
|
11389
|
+
}
|
|
11390
|
+
}
|
|
11391
|
+
function appendRunOutcome(key, outcome, cacheRoot) {
|
|
11392
|
+
try {
|
|
11393
|
+
const runs2 = [...readRunHistory(key, cacheRoot), outcome].slice(-HISTORY_CAP);
|
|
11394
|
+
mkdirSync16(repoCacheDir(key, cacheRoot), { recursive: true });
|
|
11395
|
+
const target = historyPath(key, cacheRoot);
|
|
11396
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
11397
|
+
try {
|
|
11398
|
+
writeFileSync17(tmp, JSON.stringify({ runs: runs2 }));
|
|
11399
|
+
renameSync8(tmp, target);
|
|
11400
|
+
} catch (e) {
|
|
11401
|
+
rmSync11(tmp, { force: true, recursive: false });
|
|
11402
|
+
throw e;
|
|
11403
|
+
}
|
|
11404
|
+
return true;
|
|
11405
|
+
} catch {
|
|
11406
|
+
return false;
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
var HISTORY_FILE = "router_history.json", HISTORY_CAP = 200, WALL_KIND = "total-v1", EXECUTORS, VERDICTS, OWNERS;
|
|
11410
|
+
var init_history_store = __esm(() => {
|
|
11411
|
+
init_cache();
|
|
11412
|
+
EXECUTORS = ["haiku", "sonnet"];
|
|
11413
|
+
VERDICTS = ["pass", "fail", "error", "not_proven"];
|
|
11414
|
+
OWNERS = ["code", "harness", "env", "provider"];
|
|
11415
|
+
});
|
|
11416
|
+
|
|
11417
|
+
// ../../loki-ts/src/runner/router/history.ts
|
|
11418
|
+
import { dirname as dirname16, join as join46, resolve as resolve14 } from "path";
|
|
11419
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11420
|
+
function shapeKey(model) {
|
|
11421
|
+
if (!model || model.status !== "ok")
|
|
11422
|
+
return null;
|
|
11423
|
+
const runners = new Set(model.packages.map((p) => (p.runner ?? "").trim().toLowerCase() || "none"));
|
|
11424
|
+
return `${model.workspaceKind}:${[...runners].sort().join("+")}`;
|
|
11425
|
+
}
|
|
11426
|
+
function shapeKeyForRepo(repoDir) {
|
|
11427
|
+
try {
|
|
11428
|
+
const dirs = shallowDirs(repoDir);
|
|
11429
|
+
const committed = loadCommitted(repoDir);
|
|
11430
|
+
if (committed)
|
|
11431
|
+
return shapeKey({ ...committed.model, key: computeKey(repoDir, committed.model.fingerprintFiles, dirs, committed.hash) });
|
|
11432
|
+
const cached = loadCached(repoDir);
|
|
11433
|
+
if (!cached || computeKey(repoDir, cached.fingerprintFiles, dirs) !== cached.key)
|
|
11434
|
+
return null;
|
|
11435
|
+
return shapeKey(cached);
|
|
11436
|
+
} catch {
|
|
11437
|
+
return null;
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
var SHAPE_DEFAULTS_FILE;
|
|
11441
|
+
var init_history = __esm(() => {
|
|
11442
|
+
init_schema();
|
|
11443
|
+
init_history_store();
|
|
11444
|
+
init_discover2();
|
|
11445
|
+
init_gather();
|
|
11446
|
+
init_history_store();
|
|
11447
|
+
SHAPE_DEFAULTS_FILE = resolve14(dirname16(fileURLToPath4(import.meta.url)), "..", "..", "..", "data", "router-shape-defaults.json");
|
|
11448
|
+
});
|
|
11449
|
+
|
|
11450
|
+
// ../../loki-ts/src/runner/router/plan_route.ts
|
|
11451
|
+
var init_plan_route = __esm(() => {
|
|
11452
|
+
init_run_cap();
|
|
11453
|
+
init_providers();
|
|
11454
|
+
init_decision();
|
|
11455
|
+
init_advisor_probe();
|
|
11456
|
+
init_route_record();
|
|
11457
|
+
init_history();
|
|
11458
|
+
});
|
|
11459
|
+
|
|
11460
|
+
// ../../loki-ts/src/util/intent_card.ts
|
|
11461
|
+
var INTENT_HEADER = "What I think you want:", CARD_MIN_LINES = 3, CARD_MAX_LINES = 5, INTENT_CARD_INSTRUCTION;
|
|
11462
|
+
var init_intent_card = __esm(() => {
|
|
11463
|
+
INTENT_CARD_INSTRUCTION = `After the plan lines, append an intent card of ${CARD_MIN_LINES} to ${CARD_MAX_LINES} lines: a first line starting "${INTENT_HEADER}" with one sentence on what you think the user wants, then ${CARD_MIN_LINES - 1} to ${CARD_MAX_LINES - 1} lines each starting "Acceptance:" naming one check you will hold yourself to. You may add one optional line starting "Out of scope:" naming what you will NOT do; it counts toward the ${CARD_MAX_LINES} lines.`;
|
|
11464
|
+
});
|
|
11465
|
+
|
|
11466
|
+
// ../../loki-ts/src/engine10/repomap.ts
|
|
11467
|
+
var init_repomap = __esm(() => {
|
|
11468
|
+
init_safe_git();
|
|
11469
|
+
});
|
|
11470
|
+
|
|
11471
|
+
// ../../loki-ts/src/util/reviewer_brief.ts
|
|
11472
|
+
var init_reviewer_brief = __esm(() => {
|
|
11473
|
+
init_repomap();
|
|
11474
|
+
init_safe_git();
|
|
11475
|
+
});
|
|
11476
|
+
|
|
11477
|
+
// ../../loki-ts/src/engine10/stages/plan.ts
|
|
11478
|
+
var init_plan = __esm(() => {
|
|
11479
|
+
init_session();
|
|
11480
|
+
init_sizing();
|
|
11481
|
+
init_lean_prefix();
|
|
11482
|
+
init_types();
|
|
11483
|
+
init_run_cap();
|
|
11484
|
+
init_wall();
|
|
11485
|
+
init_model_downgrades();
|
|
11486
|
+
init_plan_route();
|
|
11487
|
+
init_intent_card();
|
|
11488
|
+
init_mutation_proof();
|
|
11489
|
+
init_reviewer_brief();
|
|
11490
|
+
});
|
|
11491
|
+
|
|
11492
|
+
// ../../loki-ts/src/runner/session_resume.ts
|
|
11493
|
+
var init_session_resume = __esm(() => {
|
|
11494
|
+
init_session();
|
|
11495
|
+
});
|
|
11496
|
+
|
|
11497
|
+
// ../../loki-ts/src/util/conflict_resume.ts
|
|
11498
|
+
var CONFLICT_CORRECTION, EMPTY_DONE_CORRECTION;
|
|
11499
|
+
var init_conflict_resume = __esm(() => {
|
|
11500
|
+
init_session_resume();
|
|
11501
|
+
init_context();
|
|
11502
|
+
CONFLICT_CORRECTION = "Loki imposes no file or test limits on you. If your conflict is only about Loki's own rules, finish the task. If the task itself conflicts, restate the conflict. " + FINISH_LINE;
|
|
11503
|
+
EMPTY_DONE_CORRECTION = "Your session finished with LOKI_DONE but the working tree has no change against the base. Make the change the task asks for, or if the task is already satisfied finish with LOKI_ALREADY_DONE: <file:line evidence>, or if the task itself conflicts finish with LOKI_SPEC_CONFLICT: <reason>. " + FINISH_LINE;
|
|
11504
|
+
});
|
|
11505
|
+
|
|
11506
|
+
// ../../loki-ts/src/runner/router/implement_route.ts
|
|
11507
|
+
var init_implement_route = __esm(() => {
|
|
11508
|
+
init_model_rank();
|
|
11509
|
+
init_unit_model();
|
|
11510
|
+
});
|
|
11511
|
+
|
|
11512
|
+
// ../../loki-ts/src/engine10/stages/implement.ts
|
|
11513
|
+
var init_implement = __esm(() => {
|
|
11514
|
+
init_lean_prefix();
|
|
11515
|
+
init_pr_lessons();
|
|
11516
|
+
init_context();
|
|
11517
|
+
init_model_downgrades();
|
|
11518
|
+
init_sizing();
|
|
11519
|
+
init_plan();
|
|
11520
|
+
init_verify();
|
|
11521
|
+
init_resolve();
|
|
11522
|
+
init_session();
|
|
11523
|
+
init_conflict_resume();
|
|
11524
|
+
init_session_resume();
|
|
11525
|
+
init_implement_route();
|
|
11526
|
+
init_unit_model();
|
|
11527
|
+
init_types();
|
|
11528
|
+
init_spec_file();
|
|
11529
|
+
});
|
|
11530
|
+
|
|
11531
|
+
// ../../loki-ts/src/engine10/machine.ts
|
|
11532
|
+
var init_machine = __esm(() => {
|
|
11533
|
+
init_hooks();
|
|
11534
|
+
init_retry_class();
|
|
11535
|
+
init_registry();
|
|
11536
|
+
init_run_cap();
|
|
11537
|
+
init_context();
|
|
11538
|
+
init_steer();
|
|
11539
|
+
init_implement();
|
|
11540
|
+
init_unit_model();
|
|
11541
|
+
init_types();
|
|
11542
|
+
});
|
|
11543
|
+
|
|
11544
|
+
// ../../loki-ts/src/runner/provider_failover.ts
|
|
11545
|
+
var DEFAULT_ORDER, ALLOWED;
|
|
11546
|
+
var init_provider_failover = __esm(() => {
|
|
11547
|
+
DEFAULT_ORDER = ["claude", "cline", "codex", "aider", "opencode"];
|
|
11548
|
+
ALLOWED = new Set(DEFAULT_ORDER);
|
|
11549
|
+
});
|
|
11550
|
+
|
|
11551
|
+
// ../../loki-ts/src/engine10/worker.ts
|
|
11552
|
+
function assertWorkerEnv(env = process.env) {
|
|
11553
|
+
if (env.LOKI_ALLOW_AGENT_GITHUB_TOKEN === "1")
|
|
11554
|
+
return;
|
|
11555
|
+
for (const v of GITHUB_TOKEN_VARS) {
|
|
11556
|
+
const val = env[v] ?? "";
|
|
11557
|
+
if (val !== "" && !val.startsWith(SENTINEL_PREFIX)) {
|
|
11558
|
+
throw new Error(`engine10 worker: ${v} holds a real token; the worker must run with withheld credentials`);
|
|
11559
|
+
}
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11562
|
+
var init_worker = __esm(() => {
|
|
11563
|
+
init_credential_env();
|
|
11564
|
+
init_cost();
|
|
11565
|
+
init_budget_cap();
|
|
11566
|
+
init_run_cap();
|
|
11567
|
+
init_resolve();
|
|
11568
|
+
init_budget_cap();
|
|
11569
|
+
init_machine();
|
|
11570
|
+
init_provider_failover();
|
|
11571
|
+
init_session();
|
|
11572
|
+
init_testmap();
|
|
11573
|
+
init_types();
|
|
11574
|
+
init_safe_git();
|
|
11575
|
+
});
|
|
11576
|
+
|
|
11577
|
+
// ../../loki-ts/src/project_model/package_suite.ts
|
|
11578
|
+
import { join as join47 } from "path";
|
|
11579
|
+
async function runPackageSuites(repoDir, signal, opts, checks, notProven) {
|
|
11580
|
+
const api = loadProjectApi(repoDir);
|
|
11581
|
+
if (!isMultiRoot(api))
|
|
11582
|
+
return false;
|
|
11583
|
+
const withTests = api.packages().filter((p) => p.commands.test);
|
|
11584
|
+
if (withTests.length === 0)
|
|
11585
|
+
return false;
|
|
11586
|
+
for (const pkg of withTests) {
|
|
11587
|
+
const tc = pkg.commands.test, name = `full suite: ${pkg.name}`, cmd = `(cd ${tc.cwd} && ${tc.cmd})`;
|
|
11588
|
+
const started = Date.now();
|
|
11589
|
+
const r = await run(["bash", "-c", tc.cmd], { cwd: join47(repoDir, tc.cwd), timeoutMs: opts.timeoutMs, ...opts.path ? { env: { PATH: opts.path } } : {} });
|
|
11590
|
+
const duration_s = (Date.now() - started) / 1000;
|
|
11591
|
+
if (signal.aborted) {
|
|
11592
|
+
checks.push({ name, cmd, result: "not_run", duration_s });
|
|
11593
|
+
notProven.add(`not run: ${name} (aborted)`);
|
|
11594
|
+
continue;
|
|
11595
|
+
}
|
|
11596
|
+
const out2 = `${r.stdout}
|
|
11597
|
+
${r.stderr}`;
|
|
11598
|
+
const lr = r.exitCode === 0 || !opts.baseSha ? undefined : await harnessLoadReason({ repoDir, baseSha: opts.baseSha, out: out2, cmd: "bash", args: ["-c", tc.cmd], signal, cwd: join47(repoDir, tc.cwd), ...opts.path ? { env: { PATH: opts.path } } : {} });
|
|
11599
|
+
if (lr) {
|
|
11600
|
+
checks.push({ name, cmd, result: "not_run", duration_s });
|
|
11601
|
+
notProven.add(`${lr} (${name}; harness-owned)`);
|
|
11602
|
+
continue;
|
|
11603
|
+
}
|
|
11604
|
+
const cls = classifyCheck({ kind: "test", ok: r.exitCode === 0, out: out2, ...goRunner(tc.cmd) });
|
|
11605
|
+
checks.push({ name, cmd, result: cls.result, duration_s, ...cls.result === "fail" ? { ids: failIds(out2) } : {} });
|
|
11606
|
+
if (cls.result === "not_run")
|
|
11607
|
+
notProven.add(`not run: ${name} (${cls.reason ?? NO_TESTS_REASON})`);
|
|
11608
|
+
}
|
|
11609
|
+
for (const pkg of api.packages().filter((p) => !p.commands.test))
|
|
11610
|
+
notProven.add(`full suite: package ${pkg.name} defines no test command`);
|
|
11611
|
+
return true;
|
|
11612
|
+
}
|
|
11613
|
+
var init_package_suite = __esm(() => {
|
|
11614
|
+
init_load_owner();
|
|
11615
|
+
init_check_result();
|
|
11616
|
+
init_shell();
|
|
11617
|
+
init_failures();
|
|
11618
|
+
init_resolve();
|
|
11619
|
+
});
|
|
11620
|
+
|
|
11621
|
+
// ../../loki-ts/src/engine10/stages/deep.ts
|
|
11622
|
+
var exports_deep = {};
|
|
11623
|
+
__export(exports_deep, {
|
|
11624
|
+
stage: () => stage,
|
|
11625
|
+
runFullSuite: () => runFullSuite,
|
|
11626
|
+
runDeep: () => runDeep,
|
|
11627
|
+
deepStage: () => deepStage,
|
|
11628
|
+
DEFAULT_SECRET_SCAN_SH: () => DEFAULT_SECRET_SCAN_SH,
|
|
11629
|
+
DEFAULT_PUSH_SH: () => DEFAULT_PUSH_SH
|
|
11630
|
+
});
|
|
11631
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18 } from "fs";
|
|
11632
|
+
import { join as join48 } from "path";
|
|
11633
|
+
async function runFullSuite(ctx, signal, opts, checks, notProven) {
|
|
11634
|
+
if (await runPackageSuites(ctx.repoDir, signal, { path: opts.path, timeoutMs: opts.fullSuiteTimeoutMs ?? FULL_SUITE_TIMEOUT_MS, baseSha: ctx.baseSha }, checks, notProven))
|
|
11635
|
+
return;
|
|
11636
|
+
const map = await ctx.tests.detect(ctx.repoDir);
|
|
11637
|
+
if (map.runners.length === 0) {
|
|
11638
|
+
notProven.add("full suite (no test runner detected)");
|
|
11639
|
+
return;
|
|
11640
|
+
}
|
|
11641
|
+
for (const runner of map.runners) {
|
|
11642
|
+
const spec = FULL_SUITE_CMD[runner];
|
|
11643
|
+
const name = `full suite: ${runner}`;
|
|
11644
|
+
if (!Bun.which(spec.cmd, opts.path ? { PATH: opts.path } : undefined)) {
|
|
11645
|
+
checks.push({ name, cmd: [spec.cmd, ...spec.args].join(" "), result: "not_run", duration_s: 0 });
|
|
11646
|
+
notProven.add(`not run: ${name}`);
|
|
11647
|
+
continue;
|
|
11648
|
+
}
|
|
11649
|
+
const started = Date.now();
|
|
11650
|
+
const r = await run([spec.cmd, ...spec.args], {
|
|
11651
|
+
cwd: ctx.repoDir,
|
|
11652
|
+
timeoutMs: opts.fullSuiteTimeoutMs ?? FULL_SUITE_TIMEOUT_MS,
|
|
11653
|
+
...opts.path ? { env: { PATH: opts.path } } : {}
|
|
11654
|
+
});
|
|
11655
|
+
const durationS = (Date.now() - started) / 1000;
|
|
11656
|
+
if (signal.aborted) {
|
|
11657
|
+
checks.push({ name, cmd: [spec.cmd, ...spec.args].join(" "), result: "not_run", duration_s: durationS });
|
|
11658
|
+
notProven.add(`not run: ${name} (aborted)`);
|
|
11659
|
+
continue;
|
|
11660
|
+
}
|
|
11661
|
+
const out2 = `${r.stdout}
|
|
11662
|
+
${r.stderr}`, lr = r.exitCode === 0 ? undefined : await harnessLoadReason({ repoDir: ctx.repoDir, baseSha: ctx.baseSha, out: out2, cmd: spec.cmd, args: spec.args, signal, ...opts.path ? { env: { PATH: opts.path } } : {} });
|
|
11663
|
+
if (lr) {
|
|
11664
|
+
checks.push({ name, cmd: [spec.cmd, ...spec.args].join(" "), result: "not_run", duration_s: durationS });
|
|
11665
|
+
notProven.add(`${lr} (${name}; harness-owned)`);
|
|
11666
|
+
continue;
|
|
11667
|
+
}
|
|
11668
|
+
const cls = classifyCheck({ kind: "test", ok: r.exitCode === 0, out: out2, ...goRunner(spec.cmd, spec.args) });
|
|
11669
|
+
checks.push({ name, cmd: [spec.cmd, ...spec.args].join(" "), result: cls.result, duration_s: durationS, ...cls.result === "fail" ? { ids: failIds(out2) } : {} });
|
|
11670
|
+
if (cls.result === "not_run")
|
|
11671
|
+
notProven.add(`not run: ${name} (${cls.reason ?? NO_TESTS_REASON})`);
|
|
11672
|
+
}
|
|
11673
|
+
}
|
|
11674
|
+
function runAppBoot(ctx, opts, checks, notProven) {
|
|
11675
|
+
const discover = opts.discoverGraph ?? discoverProjectGraph;
|
|
11676
|
+
let graph = null;
|
|
11677
|
+
try {
|
|
11678
|
+
graph = discover(ctx.repoDir);
|
|
11679
|
+
} catch {
|
|
11680
|
+
graph = null;
|
|
11681
|
+
}
|
|
11682
|
+
checks.push({
|
|
11683
|
+
name: "app boot",
|
|
11684
|
+
cmd: "project_graph.ts discoverProjectGraph",
|
|
11685
|
+
result: "not_run",
|
|
11686
|
+
duration_s: 0
|
|
11687
|
+
});
|
|
11688
|
+
notProven.add(graph ? `app boot not run (app graph discovered, ${graph.members.length} member(s))` : "app boot not run (no app graph discovered)");
|
|
11689
|
+
}
|
|
11690
|
+
async function runCouncilCheck(ctx, changed, baseSha, signal, opts, checks, notProven) {
|
|
11691
|
+
const codeFiles = changed.filter((f) => !f.endsWith(".md"));
|
|
11692
|
+
if (codeFiles.length === 0) {
|
|
11693
|
+
notProven.add("council (no non-doc changes to review)");
|
|
11694
|
+
return;
|
|
11695
|
+
}
|
|
11696
|
+
if (!opts.council) {
|
|
11697
|
+
notProven.add("council (not wired: see the contract-gap note in stages/deep.ts)");
|
|
11698
|
+
return;
|
|
11699
|
+
}
|
|
11700
|
+
const diffResult = await safeGitRun(ctx.repoDir, ["diff", "--no-color", baseSha, "HEAD", "--", ...codeFiles], { timeoutMs: 20000 });
|
|
11701
|
+
const diff = diffResult.exitCode === 0 ? diffResult.stdout : "";
|
|
11702
|
+
const maxBytes = opts.maxCouncilDiffBytes ?? MAX_COUNCIL_DIFF_BYTES;
|
|
11703
|
+
if (Buffer.byteLength(diff, "utf8") > maxBytes) {
|
|
11704
|
+
notProven.add("council (context oversized, refused)");
|
|
11705
|
+
return;
|
|
11706
|
+
}
|
|
11707
|
+
const started = Date.now();
|
|
11708
|
+
try {
|
|
11709
|
+
const outcome = await opts.council.run({ runId: ctx.runId, diff, files: codeFiles }, signal);
|
|
11710
|
+
checks.push({ name: "council", cmd: "dispatchClaudeAgents", result: "pass", duration_s: (Date.now() - started) / 1000 });
|
|
11711
|
+
} catch (err) {
|
|
11712
|
+
notProven.add(`council (refused: ${err.message})`);
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
11715
|
+
async function runSecretScan(ctx, changed, opts, checks, notProven) {
|
|
11716
|
+
const scriptPath = opts.secretScanShPath ?? DEFAULT_SECRET_SCAN_SH;
|
|
11717
|
+
const name = "security scan";
|
|
11718
|
+
if (!existsSync28(scriptPath)) {
|
|
11719
|
+
checks.push({ name, cmd: "secret-scan.sh", result: "not_run", duration_s: 0 });
|
|
11720
|
+
notProven.add(`not run: ${name} (secret-scan.sh not found)`);
|
|
11721
|
+
return;
|
|
11722
|
+
}
|
|
11723
|
+
if (changed.length === 0) {
|
|
11724
|
+
checks.push({ name, cmd: "secret-scan.sh", result: "pass", duration_s: 0 });
|
|
11725
|
+
return;
|
|
11726
|
+
}
|
|
11727
|
+
const inline = 'script="$1"; shift; . "$script" || exit 3; hit=0; for f in "$@"; do if _commit_scan_secret_file "$f" || _commit_path_looks_secret "$f"; then printf "%s\\n" "$f"; hit=1; fi; done; exit "$hit"';
|
|
11728
|
+
const started = Date.now();
|
|
11729
|
+
const r = await run(["bash", "-c", inline, "_", scriptPath, ...changed.map((f) => join48(ctx.repoDir, f))], { cwd: ctx.repoDir, timeoutMs: 30000 });
|
|
11730
|
+
const durationS = (Date.now() - started) / 1000;
|
|
11731
|
+
if (r.exitCode === 3) {
|
|
11732
|
+
checks.push({ name, cmd: "secret-scan.sh", result: "not_run", duration_s: durationS });
|
|
11733
|
+
notProven.add(`not run: ${name} (secret-scan.sh failed to load)`);
|
|
11734
|
+
return;
|
|
11735
|
+
}
|
|
11736
|
+
const hits = r.stdout.split(`
|
|
11737
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
11738
|
+
checks.push({
|
|
11739
|
+
name,
|
|
11740
|
+
cmd: "secret-scan.sh",
|
|
11741
|
+
result: hits.length > 0 ? "fail" : "pass",
|
|
11742
|
+
duration_s: durationS,
|
|
11743
|
+
...hits.length > 0 ? { reason: `secret match: ${hits.join(", ")}` } : {}
|
|
11744
|
+
});
|
|
11745
|
+
}
|
|
11746
|
+
function commentArgv(prNumber, bodyFile) {
|
|
11747
|
+
return ["comment", prNumber, bodyFile];
|
|
11748
|
+
}
|
|
11749
|
+
function buildCommentBody(checks, notProven, statusState) {
|
|
11750
|
+
const lines = [`## Loki deep verify: ${statusState}`, "", "### Checks"];
|
|
11751
|
+
lines.push(...checks.length ? checks.map((c) => `- ${c.result}: ${c.name}${c.reason ? ` (${c.reason})` : ""}`) : ["- none"]);
|
|
11752
|
+
lines.push("", "### NOT PROVEN", ...notProven.length ? notProven.map((n2) => `- ${n2}`) : ["- none"]);
|
|
11753
|
+
return `${lines.join(`
|
|
11754
|
+
`)}
|
|
11755
|
+
`;
|
|
11756
|
+
}
|
|
11757
|
+
async function postResults(ctx, opts, checks, notProven, statusState) {
|
|
11758
|
+
const out2 = [];
|
|
11759
|
+
const pinnedOrigin = ctx.pinnedOrigin;
|
|
11760
|
+
if (!pinnedOrigin) {
|
|
11761
|
+
out2.push("push refused: no pinned origin (Rule of Two)");
|
|
11762
|
+
return out2;
|
|
11763
|
+
}
|
|
11764
|
+
const scriptPath = opts.pushScriptPath ?? DEFAULT_PUSH_SH;
|
|
11765
|
+
const env = { ...process.env, ...opts.pushExtraEnv, _LOKI_ORIGIN_PINNED: "1", _LOKI_PINNED_ORIGIN: pinnedOrigin };
|
|
11766
|
+
const prUrl = ctx.outputs().pr?.pr_url;
|
|
11767
|
+
const prNumber = prUrl ? PR_NUMBER_RE.exec(prUrl)?.[1] : undefined;
|
|
11768
|
+
if (prNumber) {
|
|
11769
|
+
mkdirSync17(ctx.runDir, { recursive: true });
|
|
11770
|
+
const bodyFile = join48(ctx.runDir, "deep-comment.md");
|
|
11771
|
+
writeFileSync18(bodyFile, buildCommentBody(checks, notProven, statusState), "utf8");
|
|
11772
|
+
const commentResult = await run(["bash", scriptPath, ...commentArgv(prNumber, bodyFile)], { cwd: ctx.repoDir, env, timeoutMs: 30000 });
|
|
11773
|
+
if (commentResult.exitCode !== 0)
|
|
11774
|
+
out2.push(`deep comment not posted (engine10-push.sh exit ${commentResult.exitCode})`);
|
|
11775
|
+
} else {
|
|
11776
|
+
out2.push("deep comment not posted (no PR number)");
|
|
11777
|
+
}
|
|
11778
|
+
const headSha = (await safeGitRun(ctx.repoDir, ["rev-parse", "HEAD"], { timeoutMs: 1e4 })).stdout.trim();
|
|
11779
|
+
if (SHA_RE.test(headSha)) {
|
|
11780
|
+
const statusArgs = { cmd: "status", sha: headSha, state: statusState, description: `Loki 10 deep verify: ${statusState}` };
|
|
11781
|
+
const statusResult = await run(["bash", scriptPath, ...pushArgv(statusArgs)], { cwd: ctx.repoDir, env, timeoutMs: 30000 });
|
|
11782
|
+
if (statusResult.exitCode !== 0)
|
|
11783
|
+
out2.push(`loki/deep-verify status not set (engine10-push.sh exit ${statusResult.exitCode})`);
|
|
11784
|
+
} else {
|
|
11785
|
+
out2.push("loki/deep-verify status not set (HEAD sha not resolvable)");
|
|
11786
|
+
}
|
|
11787
|
+
return out2;
|
|
11788
|
+
}
|
|
11789
|
+
async function runDeep(ctx, signal, opts = {}) {
|
|
11790
|
+
if (signal.aborted)
|
|
11791
|
+
return { status: "failed", data: {}, reason: "aborted before deep verify started" };
|
|
11792
|
+
try {
|
|
11793
|
+
assertWorkerEnv(opts.env ?? process.env);
|
|
11794
|
+
} catch (err) {
|
|
11795
|
+
return { status: "failed", data: {}, reason: err.message };
|
|
11796
|
+
}
|
|
11797
|
+
let changed;
|
|
11798
|
+
try {
|
|
11799
|
+
changed = changedFiles(ctx.repoDir, ctx.baseSha);
|
|
11800
|
+
} catch (err) {
|
|
11801
|
+
return { status: "failed", data: {}, reason: `git diff against base failed: ${err.message}` };
|
|
11802
|
+
}
|
|
11803
|
+
const checks = [];
|
|
11804
|
+
const notProven = new Set;
|
|
11805
|
+
await runFullSuite(ctx, signal, opts, checks, notProven);
|
|
11806
|
+
runAppBoot(ctx, opts, checks, notProven);
|
|
11807
|
+
await runCouncilCheck(ctx, changed, ctx.baseSha, signal, opts, checks, notProven);
|
|
11808
|
+
await runSecretScan(ctx, changed, opts, checks, notProven);
|
|
11809
|
+
const statusState = checks.some((c) => c.result === "fail") ? "failure" : "success";
|
|
11810
|
+
const notProvenList = [...notProven];
|
|
11811
|
+
const seal = ctx.outputs().seal;
|
|
11812
|
+
const addendumBody = {
|
|
11813
|
+
schema: "loki.v10.receipt-addendum/1",
|
|
11814
|
+
run_id: ctx.runId,
|
|
11815
|
+
base_receipt_sha256: seal?.receipt_sha256 ?? null,
|
|
11816
|
+
checks,
|
|
11817
|
+
not_proven: notProvenList,
|
|
11818
|
+
status_state: statusState
|
|
11819
|
+
};
|
|
11820
|
+
const addendumSha256 = sha2562(canonicalJson(addendumBody));
|
|
11821
|
+
const sig = signReceipt(`${ctx.runId}-deep`, addendumSha256);
|
|
11822
|
+
const addendum = { ...addendumBody, addendum_sha256: addendumSha256, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
11823
|
+
mkdirSync17(ctx.runDir, { recursive: true });
|
|
11824
|
+
writeFileSync18(join48(ctx.runDir, "receipt-addendum-1.json"), JSON.stringify(addendum, null, 2) + `
|
|
11825
|
+
`);
|
|
11826
|
+
const pushNotProven = await postResults(ctx, opts, checks, notProvenList, statusState);
|
|
11827
|
+
const allNotProven = [...notProvenList, ...pushNotProven];
|
|
11828
|
+
ctx.emit("deep.completed", "deep", { checks, status_state: statusState, addendum_sha256: addendumSha256 });
|
|
11829
|
+
return { status: "completed", data: { checks, addendum_sha256: addendumSha256, status_state: statusState, not_proven: allNotProven } };
|
|
11830
|
+
}
|
|
11831
|
+
var DEFAULT_PUSH_SH, DEFAULT_SECRET_SCAN_SH, MAX_COUNCIL_DIFF_BYTES = 400000, FULL_SUITE_TIMEOUT_MS = 300000, PR_NUMBER_RE, SHA_RE, FULL_SUITE_CMD, deepStage, stage;
|
|
11832
|
+
var init_deep = __esm(() => {
|
|
11833
|
+
init_project_graph();
|
|
11834
|
+
init_shell();
|
|
11835
|
+
init_load_owner();
|
|
11836
|
+
init_worker();
|
|
11837
|
+
init_seal();
|
|
11838
|
+
init_verify();
|
|
11839
|
+
init_package_suite();
|
|
11840
|
+
init_check_result();
|
|
11841
|
+
init_failures();
|
|
11842
|
+
init_types();
|
|
11843
|
+
init_safe_git();
|
|
11844
|
+
DEFAULT_PUSH_SH = new URL("../../../../autonomy/lib/engine10-push.sh", import.meta.url).pathname;
|
|
11845
|
+
DEFAULT_SECRET_SCAN_SH = new URL("../../../../autonomy/lib/secret-scan.sh", import.meta.url).pathname;
|
|
11846
|
+
PR_NUMBER_RE = /\/pull\/(\d+)$/;
|
|
11847
|
+
SHA_RE = /^[0-9a-f]{40}$/;
|
|
11848
|
+
FULL_SUITE_CMD = {
|
|
11849
|
+
pytest: { cmd: "python", args: ["-m", "pytest", "-q"] },
|
|
11850
|
+
vitest: { cmd: "npx", args: ["vitest", "run"] },
|
|
11851
|
+
jest: { cmd: "npx", args: ["jest"] },
|
|
11852
|
+
bun: { cmd: "bun", args: ["test"] },
|
|
11853
|
+
node: { cmd: "node", args: ["--test"] },
|
|
11854
|
+
npm: { cmd: "npm", args: ["test", "--silent"] },
|
|
11855
|
+
go: { cmd: "go", args: ["test", "-json", "./..."] },
|
|
11856
|
+
cargo: { cmd: "cargo", args: ["test"] }
|
|
11857
|
+
};
|
|
11858
|
+
deepStage = {
|
|
11859
|
+
name: "deep",
|
|
11860
|
+
...STAGE_BUDGETS.deep,
|
|
11861
|
+
run: (ctx, signal) => runDeep(ctx, signal)
|
|
11862
|
+
};
|
|
11863
|
+
stage = deepStage;
|
|
11864
|
+
});
|
|
11865
|
+
|
|
11866
|
+
// ../../loki-ts/src/engine10/stages/verify.ts
|
|
11867
|
+
import { existsSync as existsSync29, mkdtempSync as mkdtempSync6, realpathSync as realpathSync17, rmSync as rmSync12 } from "fs";
|
|
11868
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
11869
|
+
import { dirname as dirname17, isAbsolute as isAbsolute6, join as join49, relative as relative5 } from "path";
|
|
11870
|
+
function resolveTool(repoDir, bin, fallback, lastResort = fallback) {
|
|
11871
|
+
const inRepo = (p) => {
|
|
11872
|
+
try {
|
|
11873
|
+
const r = relative5(realpathSync17(repoDir), realpathSync17(p));
|
|
11874
|
+
return r !== "" && !r.startsWith("..") && !isAbsolute6(r);
|
|
11875
|
+
} catch {
|
|
11876
|
+
return false;
|
|
11877
|
+
}
|
|
11878
|
+
};
|
|
11879
|
+
const venvEnv = process.env["VIRTUAL_ENV"];
|
|
11880
|
+
const project = [join49(repoDir, ".venv", "bin", bin), join49(repoDir, "venv", "bin", bin), ...venvEnv && inRepo(venvEnv) ? [join49(venvEnv, "bin", bin)] : []].find(existsSync29);
|
|
11881
|
+
return project ? [project, "project"] : [Bun.which(fallback) ? fallback : lastResort, "system"];
|
|
11882
|
+
}
|
|
11883
|
+
function runnerCmd(t, repoDir) {
|
|
11884
|
+
switch (t.runner) {
|
|
11885
|
+
case "pytest": {
|
|
11886
|
+
const [cmd, interpreter] = resolveTool(repoDir, "python", "python3", "python");
|
|
11887
|
+
return [cmd, ["-m", "pytest", "-q", t.path], interpreter];
|
|
11888
|
+
}
|
|
11889
|
+
case "vitest":
|
|
11890
|
+
return ["npx", ["vitest", "run", t.path]];
|
|
11891
|
+
case "jest":
|
|
11892
|
+
return ["npx", ["jest", t.path]];
|
|
11893
|
+
case "bun":
|
|
11894
|
+
return ["bun", ["test", t.path]];
|
|
11895
|
+
case "node":
|
|
11896
|
+
return ["node", ["--test", `./${t.path}`]];
|
|
11897
|
+
case "npm":
|
|
11898
|
+
return ["npm", ["test", "--silent"]];
|
|
11899
|
+
case "go":
|
|
11900
|
+
return ["go", ["test", "-json", `./${dirname17(t.path)}`]];
|
|
11901
|
+
case "cargo":
|
|
11902
|
+
return ["cargo", ["test"]];
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
function commandFor(t, repoDir, api) {
|
|
11906
|
+
const site = siteFor(api, repoDir, t.path);
|
|
11907
|
+
const [cmd, args, interpreter] = runnerCmd(site ? { runner: t.runner, path: site.file } : t, repoDir);
|
|
11908
|
+
return { cwd: site?.cwd ?? repoDir, cmd, args, argv: [cmd, ...args], ...interpreter ? { interpreter } : {}, pkgRoot: site?.root ?? "." };
|
|
11909
|
+
}
|
|
11910
|
+
function testConfigChanged(repoDir, baseSha, changed) {
|
|
11911
|
+
const diff = (f) => safeGit(repoDir, ["diff", "-U0", baseSha, "--", f]);
|
|
11912
|
+
return changed.filter((f) => CFG_ALWAYS.test(f) || CFG_SHARED.test(f) && (() => {
|
|
11913
|
+
const d = diff(f);
|
|
11914
|
+
return !d || CFG_LINE.test(d);
|
|
11915
|
+
})());
|
|
11916
|
+
}
|
|
11917
|
+
async function runOnce(cmd, args, cwd, signal, opts) {
|
|
11918
|
+
if (!Bun.which(cmd, { PATH: opts.path ?? process.env["PATH"] ?? "" }))
|
|
11919
|
+
return { ok: false, missing: true, cut: false, out: "" };
|
|
11920
|
+
const timeout = AbortSignal.timeout(opts.timeoutMs ?? CHECK_TIMEOUT_MS);
|
|
11921
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
11922
|
+
cwd,
|
|
11923
|
+
stdin: opts.stdin !== undefined ? Buffer.from(opts.stdin) : "ignore",
|
|
11924
|
+
stdout: "pipe",
|
|
11925
|
+
stderr: "pipe",
|
|
11926
|
+
signal: AbortSignal.any([signal, timeout]),
|
|
11927
|
+
env: opts.path ? { ...plainTestEnv(), PATH: opts.path } : plainTestEnv()
|
|
11928
|
+
});
|
|
11929
|
+
let tail = "";
|
|
11930
|
+
const rs = [proc.stdout, proc.stderr].map((s) => s.getReader());
|
|
11931
|
+
const pumps = rs.map(async (r) => {
|
|
11932
|
+
const d = new TextDecoder;
|
|
11933
|
+
for (;; ) {
|
|
11934
|
+
const c = await r.read().catch(() => ({ done: true, value: undefined }));
|
|
11935
|
+
if (c.done)
|
|
11936
|
+
return;
|
|
11937
|
+
tail = (tail + d.decode(c.value, { stream: true })).slice(-65536);
|
|
11938
|
+
}
|
|
11939
|
+
});
|
|
11940
|
+
const exitCode = await proc.exited;
|
|
11941
|
+
await Promise.race([Promise.all(pumps), new Promise((r) => setTimeout(r, 300))]);
|
|
11942
|
+
rs.forEach((r) => r.cancel().catch(() => {}));
|
|
11943
|
+
const cut = timeout.aborted || signal.aborted;
|
|
11944
|
+
return { ok: exitCode === 0 && !cut, missing: false, cut, out: cut ? "" : stripAnsi(tail) };
|
|
11945
|
+
}
|
|
11946
|
+
function firstError(out2) {
|
|
11947
|
+
const goOut = (l2) => {
|
|
11948
|
+
try {
|
|
11949
|
+
const e = JSON.parse(l2);
|
|
11950
|
+
return typeof e.Action === "string" ? e.Action === "output" || e.Action === "build-output" ? e.Output ?? "" : "" : l2;
|
|
11951
|
+
} catch {
|
|
11952
|
+
return l2;
|
|
11953
|
+
}
|
|
11954
|
+
};
|
|
11955
|
+
const lines = out2.slice(-65536).split(`
|
|
11956
|
+
`).map((l2) => (l2.startsWith("{") ? goOut(l2) : l2).trim()).filter(Boolean), l = lines.find((x) => /^(FAILED\s|\u25cf\s.*\u203a|not ok\s|_{3,}\s.+\s_{3,}$)/.test(x)) ?? lines.find((x) => /^[\w./-]+\.go:\d+:\d+: \S/.test(x)) ?? lines.find((x) => /fail|error/i.test(x) && !/^(=|\u2713|ok\b|PASS)/.test(x)) ?? "";
|
|
11957
|
+
return l.replace(/\d{4}-\d\d-\d\dT[\d:.]+Z?/g, "").replace(/(^|\s)\/(?:[\w.@-]+\/)*[\w.@-]+/g, "$1<path>").replace(/:\d+(?::\d+)?/g, "").replace(/\[?\d+(?:\.\d+)?m?s\]?/g, "").replace(/\s+/g, " ").slice(0, 160);
|
|
11958
|
+
}
|
|
11959
|
+
async function runCheck(ctx, name, cmd, args, signal, checks, opts = {}) {
|
|
11960
|
+
const started = Date.now();
|
|
11961
|
+
const cmdStr = [cmd, ...args].join(" ");
|
|
11962
|
+
const kind = opts.kind ?? "test";
|
|
11963
|
+
const cwd = opts.cwd ?? ctx.repoDir;
|
|
11964
|
+
const one = async (a) => {
|
|
11965
|
+
let c2 = classifyCheck({ kind, ok: a.ok, cut: a.cut, missing: a.missing, out: a.out, ...goRunner(cmd, args), path: /\.[cm]?[jt]s$/.test(args[args.length - 1] ?? "") ? args[args.length - 1] : undefined });
|
|
11966
|
+
const lr = kind === "test" && !a.ok && !a.cut && !a.missing ? await harnessLoadReason({ repoDir: ctx.repoDir, baseSha: ctx.baseSha, out: a.out, cmd, args, signal, cwd, protect: opts.protect, ...opts.path ? { env: { PATH: opts.path } } : {} }) : undefined;
|
|
11967
|
+
if (lr)
|
|
11968
|
+
c2 = { result: "not_run", reason: lr, owner: "harness" };
|
|
11969
|
+
if (!lr && kind === "static" && !a.ok && !a.cut && !a.missing && opts.wall?.size && wallOwnedFailure(a.out, cwd, ctx.repoDir, opts.wall))
|
|
11970
|
+
c2 = { result: "not_run", reason: WALL_COMPILE_REASON, owner: "harness" };
|
|
11971
|
+
const reason2 = a.missing ? `${cmd} not found on PATH` : a.cut ? signal.aborted ? "aborted" : `timed out after ${(opts.timeoutMs ?? CHECK_TIMEOUT_MS) / 1000}s` : c2.reason;
|
|
11972
|
+
return { ...c2, ...reason2 ? { reason: reason2 } : {} };
|
|
11973
|
+
};
|
|
11974
|
+
let attempt = await runOnce(cmd, args, cwd, signal, opts);
|
|
11975
|
+
let c = await one(attempt);
|
|
11976
|
+
let result = c.result;
|
|
10641
11977
|
if (c.result === "fail") {
|
|
10642
11978
|
attempt = await runOnce(cmd, args, cwd, signal, opts);
|
|
10643
11979
|
c = await one(attempt);
|
|
@@ -10659,7 +11995,7 @@ async function subtractBase(ctx, api, checks, tests, changed, wall, rel, signal)
|
|
|
10659
11995
|
const sus = pairs.filter(({ c }) => rel.has(c.name) && c.result === "pass");
|
|
10660
11996
|
if (!(un.some(({ c }) => c.result === "fail" && c.ids?.length) || sus.length) || signal.aborted)
|
|
10661
11997
|
return out2;
|
|
10662
|
-
const dir = mkdtempSync6(
|
|
11998
|
+
const dir = mkdtempSync6(join49(tmpdir8(), "e10-base-"));
|
|
10663
11999
|
const git3 = (args) => {
|
|
10664
12000
|
safeGit(ctx.repoDir, args, { stdio: "ignore", repoDrivers: true });
|
|
10665
12001
|
};
|
|
@@ -10686,7 +12022,7 @@ async function subtractBase(ctx, api, checks, tests, changed, wall, rel, signal)
|
|
|
10686
12022
|
try {
|
|
10687
12023
|
git3(["worktree", "remove", "--force", dir]);
|
|
10688
12024
|
} catch {}
|
|
10689
|
-
|
|
12025
|
+
rmSync12(dir, { recursive: true, force: true });
|
|
10690
12026
|
try {
|
|
10691
12027
|
git3(["worktree", "prune"]);
|
|
10692
12028
|
} catch {}
|
|
@@ -10694,7 +12030,7 @@ async function subtractBase(ctx, api, checks, tests, changed, wall, rel, signal)
|
|
|
10694
12030
|
return out2;
|
|
10695
12031
|
}
|
|
10696
12032
|
function isLokiModeRepo(repoDir) {
|
|
10697
|
-
return
|
|
12033
|
+
return existsSync29(join49(repoDir, "scripts", "select-tests.sh"));
|
|
10698
12034
|
}
|
|
10699
12035
|
async function runLintChecks(ctx, changed, signal, checks, opts = {}) {
|
|
10700
12036
|
const { api, ...runOpts } = opts;
|
|
@@ -10711,9 +12047,9 @@ async function runLintChecks(ctx, changed, signal, checks, opts = {}) {
|
|
|
10711
12047
|
}
|
|
10712
12048
|
for (const g of groupByPackage(api, ctx.repoDir, changed.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f)))) {
|
|
10713
12049
|
const sfx = g.root === "." ? "" : `:${g.root}`, co = g.root === "." ? opts : { ...opts, cwd: g.cwd };
|
|
10714
|
-
if (
|
|
12050
|
+
if (existsSync29(join49(g.cwd, "tsconfig.json")))
|
|
10715
12051
|
await runCheck(ctx, `lint:tsc${sfx}`, "npx", ["tsc", "--noEmit", "-p", "."], signal, checks, { ...co, kind: "static" });
|
|
10716
|
-
if (ESLINT_CONFIGS.some((f) =>
|
|
12052
|
+
if (ESLINT_CONFIGS.some((f) => existsSync29(join49(g.cwd, f))))
|
|
10717
12053
|
await runCheck(ctx, `lint:eslint${sfx}`, "npx", ["eslint", ...g.rel], signal, checks, { ...co, kind: "static" });
|
|
10718
12054
|
}
|
|
10719
12055
|
}
|
|
@@ -10730,6 +12066,8 @@ var init_verify = __esm(() => {
|
|
|
10730
12066
|
init_types();
|
|
10731
12067
|
init_resolve();
|
|
10732
12068
|
init_safe_git();
|
|
12069
|
+
init_repo_profile();
|
|
12070
|
+
init_check_result();
|
|
10733
12071
|
init_check_result();
|
|
10734
12072
|
CODE_EXT = /\.(?:[cm]?[jt]sx?|py|go)$/;
|
|
10735
12073
|
CFG_ALWAYS = /(^|\/)(conftest\.py|\.?pytest\.(?:ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+|\.mocharc[\w.]*)$/;
|
|
@@ -10754,13 +12092,22 @@ var init_verify = __esm(() => {
|
|
|
10754
12092
|
}
|
|
10755
12093
|
return { status: "failed", data: { changed_files: [] }, reason: "empty diff without an already_done marker" };
|
|
10756
12094
|
}
|
|
12095
|
+
let risk = (() => {
|
|
12096
|
+
try {
|
|
12097
|
+
const r = classifyRisk(diffFiles(ctx.repoDir, ctx.baseSha));
|
|
12098
|
+
recordRisk(ctx.repoDir, r);
|
|
12099
|
+
return r;
|
|
12100
|
+
} catch {
|
|
12101
|
+
return null;
|
|
12102
|
+
}
|
|
12103
|
+
})();
|
|
10757
12104
|
const checks = [];
|
|
10758
12105
|
const api = loadProjectApi(ctx.repoDir);
|
|
10759
12106
|
const map = await ctx.tests.detect(ctx.repoDir);
|
|
10760
12107
|
const impacted = ctx.tests.impacted(map, changed);
|
|
10761
12108
|
const changedTestFiles = map.tests.filter((t) => changed.includes(t.path));
|
|
10762
12109
|
const wall = ctx.outputs().wall ?? {};
|
|
10763
|
-
const wallPaths = new Set((wall.files ?? []).map((f) =>
|
|
12110
|
+
const wallPaths = new Set((wall.files ?? []).map((f) => isAbsolute6(f.path) ? relative5(ctx.repoDir, f.path) : f.path));
|
|
10764
12111
|
const wallTests = map.tests.filter((t) => wallPaths.has(t.path));
|
|
10765
12112
|
const intake = ctx.outputs().intake, relevant = ctx.tests.impacted(map, namedFiles(intake?.task ?? "", loadRepoMap(intake?.repomap_ref)));
|
|
10766
12113
|
const tests = dedupeTests([...impacted, ...changedTestFiles, ...wallTests, ...relevant]);
|
|
@@ -10775,6 +12122,9 @@ var init_verify = __esm(() => {
|
|
|
10775
12122
|
}
|
|
10776
12123
|
const targetNames = new Set([...wallTests, ...relevant].map((t) => `${t.runner}:${t.path}`));
|
|
10777
12124
|
const preRed = await subtractBase(ctx, api, checks, tests, changed, new Set(wallTests.map((t) => `${t.runner}:${t.path}`)), new Set(relevant.map((t) => `${t.runner}:${t.path}`)), signal);
|
|
12125
|
+
const attr = attribute(ctx.repoDir, ctx.baseSha, checks.filter((c) => c.result === "fail" && !preRed.names.includes(c.name)).map((c) => ({ name: c.name, ids: c.ids, eligible: !targetNames.has(c.name) && !changed.includes(c.name.slice(c.name.indexOf(":") + 1)) })));
|
|
12126
|
+
preRed.names.push(...attr.preExistingChecks);
|
|
12127
|
+
preRed.ids.push(...attr.preExistingIds);
|
|
10778
12128
|
if (!signal.aborted) {
|
|
10779
12129
|
await runLintChecks(ctx, changed, signal, checks, { api, wall: wallPaths });
|
|
10780
12130
|
if (isLokiModeRepo(ctx.repoDir)) {
|
|
@@ -10786,6 +12136,23 @@ var init_verify = __esm(() => {
|
|
|
10786
12136
|
});
|
|
10787
12137
|
}
|
|
10788
12138
|
}
|
|
12139
|
+
const fullNotProven = new Set;
|
|
12140
|
+
if (risk && risk.tier !== "low" && !signal.aborted) {
|
|
12141
|
+
const { runFullSuite: runFullSuite2 } = await Promise.resolve().then(() => (init_deep(), exports_deep));
|
|
12142
|
+
const full = [];
|
|
12143
|
+
await runFullSuite2(ctx, signal, {}, full, fullNotProven);
|
|
12144
|
+
for (const c of full)
|
|
12145
|
+
checks.push({ name: c.name, cmd: c.cmd, result: c.result, duration_s: c.duration_s, ...c.reason ? { reason: c.reason } : {} });
|
|
12146
|
+
const fa = attributeFullSuite(ctx.repoDir, ctx.baseSha, full, checks.filter((c) => c.result === "fail" && !preRed.names.includes(c.name)).flatMap((c) => c.ids ?? []), changed);
|
|
12147
|
+
preRed.names.push(...fa.preExistingChecks);
|
|
12148
|
+
preRed.ids.push(...fa.preExistingIds);
|
|
12149
|
+
attr.introducedChecks.push(...fa.introducedChecks);
|
|
12150
|
+
attr.line = [attr.line, fa.line].filter(Boolean).join("; ") || null;
|
|
12151
|
+
if (full.some((c) => c.result === "pass" || c.result === "fail")) {
|
|
12152
|
+
risk = markExecuted(risk, ["full test command", "full suite"]);
|
|
12153
|
+
recordRisk(ctx.repoDir, risk);
|
|
12154
|
+
}
|
|
12155
|
+
}
|
|
10789
12156
|
const cfgChanged = testConfigChanged(ctx.repoDir, ctx.baseSha, changed);
|
|
10790
12157
|
const covered = (f) => ctx.tests.impacted(map, [f]).some((t) => checks.some((c) => c.name === `${t.runner}:${t.path}` && hasExecutedProof([c])));
|
|
10791
12158
|
const uncoveredChanged = changed.filter((f) => CODE_EXT.test(f) && !isTestFile(f) && !wallPaths.has(f) && !cfgChanged.includes(f) && !covered(f));
|
|
@@ -10807,15 +12174,15 @@ var init_verify = __esm(() => {
|
|
|
10807
12174
|
return [`weakened test: ${t.path}`, ...assertDeltaNotes(ctx.repoDir, ctx.baseSha, null, t.path, intake?.task ?? "", k?.b, k?.h) ?? []];
|
|
10808
12175
|
});
|
|
10809
12176
|
const weakened = [...modifiedRel, ...cfgChanged.map((f) => `test configuration changed: ${f}`), ...preRed.weak.map((n2) => `skipped or fewer tests than base: ${n2}`)];
|
|
10810
|
-
const notProven = [...weakened, ...checks.filter((c) => c.owner === "harness").map((c) => `${c.reason} (${c.name}; harness-owned, no fix rounds)`), ...new Set(checks.filter((c) => c.interpreter === "system" && c.result !== "not_run").map((c) => c.name.startsWith("lint:") ? "lint ran on the system ruff" : "tests ran on the system interpreter"))];
|
|
10811
|
-
return { status: "completed", data: { checks, flaky, failures_grouped: failuresGrouped, changed_files: changed, not_proven: notProven, pre_red: preRed.ids, pre_red_checks: preRed.names, test_counts: testCounts, uncovered_changed: uncoveredChanged, target_checks: checks.filter((c) => c.result === "pass" && targetNames.has(c.name)).map((c) => c.name) } };
|
|
12177
|
+
const notProven = [...weakened, ...[...fullNotProven].filter((n2) => !n2.startsWith("full suite (no test runner")), ...checks.filter((c) => c.owner === "harness").map((c) => `${c.reason} (${c.name}; harness-owned, no fix rounds)`), ...new Set(checks.filter((c) => c.interpreter === "system" && c.result !== "not_run").map((c) => c.name.startsWith("lint:") ? "lint ran on the system ruff" : "tests ran on the system interpreter"))];
|
|
12178
|
+
return { status: "completed", data: { checks, flaky, failures_grouped: failuresGrouped, changed_files: changed, not_proven: notProven, pre_red: preRed.ids, pre_red_checks: preRed.names, ...attr.line ? { baseline_line: attr.line, introduced_checks: attr.introducedChecks } : {}, test_counts: testCounts, uncovered_changed: uncoveredChanged, target_checks: checks.filter((c) => c.result === "pass" && targetNames.has(c.name)).map((c) => c.name), risk } };
|
|
10812
12179
|
}
|
|
10813
12180
|
};
|
|
10814
12181
|
});
|
|
10815
12182
|
|
|
10816
12183
|
// ../../loki-ts/src/project_model/conventions.ts
|
|
10817
|
-
import { existsSync as
|
|
10818
|
-
import { dirname as
|
|
12184
|
+
import { existsSync as existsSync30, readdirSync as readdirSync14, readFileSync as readFileSync35 } from "fs";
|
|
12185
|
+
import { dirname as dirname18, join as join50 } from "path";
|
|
10819
12186
|
function parseJsonc(text3) {
|
|
10820
12187
|
let out2 = "", i = 0;
|
|
10821
12188
|
while (i < text3.length) {
|
|
@@ -10850,25 +12217,25 @@ function moduleOf(file, depth = 0) {
|
|
|
10850
12217
|
const ext = typeof j.extends === "string" ? j.extends : null;
|
|
10851
12218
|
if (!ext || !ext.startsWith("."))
|
|
10852
12219
|
return null;
|
|
10853
|
-
const next =
|
|
10854
|
-
return
|
|
12220
|
+
const next = join50(dirname18(file), ext.endsWith(".json") ? ext : `${ext}.json`);
|
|
12221
|
+
return existsSync30(next) ? moduleOf(next, depth + 1) : null;
|
|
10855
12222
|
}
|
|
10856
12223
|
function readPackageConventions(pkgDir, runner = null) {
|
|
10857
12224
|
let names = [];
|
|
10858
12225
|
try {
|
|
10859
|
-
names =
|
|
12226
|
+
names = readdirSync14(pkgDir).filter((f) => f.endsWith(".json")).sort((x, y) => x.length - y.length || x.localeCompare(y));
|
|
10860
12227
|
} catch {
|
|
10861
12228
|
return null;
|
|
10862
12229
|
}
|
|
10863
12230
|
let packageType = null, tsModule = null;
|
|
10864
12231
|
for (const n2 of names) {
|
|
10865
|
-
const j = readJson5(
|
|
12232
|
+
const j = readJson5(join50(pkgDir, n2));
|
|
10866
12233
|
if (!j)
|
|
10867
12234
|
continue;
|
|
10868
12235
|
if (packageType === null && typeof j.type === "string" && typeof j.name === "string")
|
|
10869
12236
|
packageType = j.type;
|
|
10870
12237
|
if (tsModule === null && j.compilerOptions !== undefined)
|
|
10871
|
-
tsModule = moduleOf(
|
|
12238
|
+
tsModule = moduleOf(join50(pkgDir, n2));
|
|
10872
12239
|
}
|
|
10873
12240
|
if (packageType === null && tsModule === null)
|
|
10874
12241
|
return null;
|
|
@@ -10890,7 +12257,7 @@ function conventionViolation(c, fileName, content) {
|
|
|
10890
12257
|
}
|
|
10891
12258
|
var readJson5 = (p) => {
|
|
10892
12259
|
try {
|
|
10893
|
-
const v = parseJsonc(
|
|
12260
|
+
const v = parseJsonc(readFileSync35(p, "utf8"));
|
|
10894
12261
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
10895
12262
|
} catch {
|
|
10896
12263
|
return null;
|
|
@@ -10900,15 +12267,15 @@ var init_conventions = () => {};
|
|
|
10900
12267
|
|
|
10901
12268
|
// ../../loki-ts/src/engine10/stages/wall.ts
|
|
10902
12269
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
10903
|
-
import { existsSync as
|
|
12270
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync36, readdirSync as readdirSync15, realpathSync as realpathSync18, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "fs";
|
|
10904
12271
|
import { tmpdir as tmpdir9 } from "os";
|
|
10905
|
-
import { dirname as
|
|
12272
|
+
import { dirname as dirname19, isAbsolute as isAbsolute7, join as join51, relative as relative6 } from "path";
|
|
10906
12273
|
function pytestCollectionIsRed(output, repoDir) {
|
|
10907
12274
|
if (/ModuleNotFoundError/.test(output))
|
|
10908
12275
|
return false;
|
|
10909
12276
|
const real2 = (p) => {
|
|
10910
12277
|
try {
|
|
10911
|
-
return
|
|
12278
|
+
return realpathSync18(isAbsolute7(p) ? p : join51(repoDir, p));
|
|
10912
12279
|
} catch {
|
|
10913
12280
|
return null;
|
|
10914
12281
|
}
|
|
@@ -10945,7 +12312,7 @@ function pytestCollectionIsRed(output, repoDir) {
|
|
|
10945
12312
|
}
|
|
10946
12313
|
function moduleUnderRepo(repoDir, name) {
|
|
10947
12314
|
const rel = name.replace(/\./g, "/");
|
|
10948
|
-
return
|
|
12315
|
+
return existsSync31(join51(repoDir, `${rel}.py`)) || existsSync31(join51(repoDir, rel, "__init__.py"));
|
|
10949
12316
|
}
|
|
10950
12317
|
function pytestExit1IsRed(output) {
|
|
10951
12318
|
if (/^!+ _pytest\.outcomes\.Exit\b/m.test(output))
|
|
@@ -11040,10 +12407,10 @@ ${repomapText}`] : [],
|
|
|
11040
12407
|
function loadTaskText(ctx, fromPrior) {
|
|
11041
12408
|
if (fromPrior)
|
|
11042
12409
|
return fromPrior;
|
|
11043
|
-
const issueJsonPath = process.env.LOKI_E10_ISSUE_JSON ??
|
|
11044
|
-
if (
|
|
12410
|
+
const issueJsonPath = process.env.LOKI_E10_ISSUE_JSON ?? join51(ctx.runDir, "issue.json");
|
|
12411
|
+
if (existsSync31(issueJsonPath)) {
|
|
11045
12412
|
try {
|
|
11046
|
-
const issue = JSON.parse(
|
|
12413
|
+
const issue = JSON.parse(readFileSync36(issueJsonPath, "utf8"));
|
|
11047
12414
|
const text3 = [issue.title, issue.body].filter((s) => typeof s === "string" && s.length > 0).join(`
|
|
11048
12415
|
|
|
11049
12416
|
`);
|
|
@@ -11055,7 +12422,7 @@ function loadTaskText(ctx, fromPrior) {
|
|
|
11055
12422
|
}
|
|
11056
12423
|
function wallTargetDir(repoDir, existingTests) {
|
|
11057
12424
|
const first = existingTests[0];
|
|
11058
|
-
return first ?
|
|
12425
|
+
return first ? join51(repoDir, dirname19(first.path)) : join51(repoDir, "tests");
|
|
11059
12426
|
}
|
|
11060
12427
|
function guessRunner(fileName, runners) {
|
|
11061
12428
|
if (fileName.endsWith(".py"))
|
|
@@ -11086,11 +12453,11 @@ async function wallAuthor(ctx, signal) {
|
|
|
11086
12453
|
const tree = prior.intake?.tree;
|
|
11087
12454
|
const repomapText = repoMapText(ctx.repoDir, tree, repomapRef, WALL_MAP_MAX_LINES);
|
|
11088
12455
|
const targetDir = wallTargetDir(ctx.repoDir, existingTests), pkg = loadProjectApi(ctx.repoDir)?.packageOf(`${relative6(ctx.repoDir, targetDir)}/x`);
|
|
11089
|
-
const conv2 = readPackageConventions(
|
|
11090
|
-
const cwd = mkdtempSync7(
|
|
12456
|
+
const conv2 = readPackageConventions(join51(ctx.repoDir, pkg?.root ?? "."), pkg?.runner ?? null);
|
|
12457
|
+
const cwd = mkdtempSync7(join51(tmpdir9(), "loki-e15-wall-"));
|
|
11091
12458
|
const wm = wallManifestFor(ctx.repoDir, tree, task);
|
|
11092
12459
|
for (const [n2, c] of [["task.md", task], ["repomap.txt", repomapText], ...wm ? [["wall_manifest.txt", wm.text]] : []])
|
|
11093
|
-
|
|
12460
|
+
writeFileSync19(join51(cwd, n2), c, "utf8");
|
|
11094
12461
|
const session = await ctx.sessions.run({
|
|
11095
12462
|
stage: "wall",
|
|
11096
12463
|
brief: buildWallBrief(task, repomapText, runners, !!wm, conventionsBrief(conv2)),
|
|
@@ -11102,30 +12469,30 @@ async function wallAuthor(ctx, signal) {
|
|
|
11102
12469
|
cwd
|
|
11103
12470
|
});
|
|
11104
12471
|
if (session.killed || session.exit === null) {
|
|
11105
|
-
|
|
12472
|
+
rmSync13(cwd, { recursive: true, force: true });
|
|
11106
12473
|
return done({ status: "failed", data: {}, reason: "wall session aborted, killed, or timed out", killed: true });
|
|
11107
12474
|
}
|
|
11108
|
-
const generated =
|
|
11109
|
-
const sealedDir =
|
|
12475
|
+
const generated = readdirSync15(cwd).filter((f) => f.startsWith(WALL_PREFIX));
|
|
12476
|
+
const sealedDir = join51(ctx.runDir, "wall");
|
|
11110
12477
|
if (generated.length > 0)
|
|
11111
|
-
|
|
12478
|
+
mkdirSync18(sealedDir, { recursive: true });
|
|
11112
12479
|
const contents = new Map;
|
|
11113
12480
|
const discarded = [];
|
|
11114
12481
|
for (const name of generated) {
|
|
11115
|
-
const content =
|
|
11116
|
-
|
|
12482
|
+
const content = readFileSync36(join51(cwd, name), "utf8"), bad = conventionViolation(conv2, name, content);
|
|
12483
|
+
writeFileSync19(join51(sealedDir, name), content, "utf8");
|
|
11117
12484
|
if (bad)
|
|
11118
12485
|
discarded.push({ file: name, reason: `${bad}; wall test did not compile under the package config` });
|
|
11119
12486
|
else
|
|
11120
12487
|
contents.set(name, content);
|
|
11121
12488
|
}
|
|
11122
|
-
|
|
12489
|
+
rmSync13(cwd, { recursive: true, force: true });
|
|
11123
12490
|
return { kind: "authored", task, sizeName: sz.size, runners, targetDir, generated, contents, discarded, ...wm ? { manifestSha256: wm.sha256 } : {} };
|
|
11124
12491
|
}
|
|
11125
12492
|
function installWall(ctx, a, baseDir, opts = {}) {
|
|
11126
12493
|
const { generated, contents, discarded, targetDir, runners } = a;
|
|
11127
12494
|
if (generated.length > 0)
|
|
11128
|
-
|
|
12495
|
+
mkdirSync18(targetDir, { recursive: true });
|
|
11129
12496
|
const sealedFiles = [];
|
|
11130
12497
|
const readOnlyFiles = [];
|
|
11131
12498
|
const wallTests = [];
|
|
@@ -11133,8 +12500,8 @@ function installWall(ctx, a, baseDir, opts = {}) {
|
|
|
11133
12500
|
const content = contents.get(name);
|
|
11134
12501
|
if (content === undefined)
|
|
11135
12502
|
continue;
|
|
11136
|
-
const dest =
|
|
11137
|
-
|
|
12503
|
+
const dest = join51(targetDir, name), runner = guessRunner(name, runners);
|
|
12504
|
+
writeFileSync19(dest, content, "utf8");
|
|
11138
12505
|
sealedFiles.push({ path: dest, sha256: sha2562(content) });
|
|
11139
12506
|
readOnlyFiles.push({ path: dest, content });
|
|
11140
12507
|
if (runner)
|
|
@@ -11143,13 +12510,13 @@ function installWall(ctx, a, baseDir, opts = {}) {
|
|
|
11143
12510
|
ctx.emit("wall.sealed", "wall", { files: sealedFiles, ...a.manifestSha256 ? { manifest_sha256: a.manifestSha256 } : {} });
|
|
11144
12511
|
const baseRunner = opts.baseRunner ?? new RealBaseTestRunner, baseRun = { pass: 0, fail: 0, not_run: discarded.length }, notRunFiles = [];
|
|
11145
12512
|
for (const t of wallTests) {
|
|
11146
|
-
const r = baseRunner.run(baseDir, [t]), abs =
|
|
12513
|
+
const r = baseRunner.run(baseDir, [t]), abs = join51(ctx.repoDir, t.path);
|
|
11147
12514
|
baseRun.pass += r.pass;
|
|
11148
12515
|
baseRun.fail += r.fail;
|
|
11149
12516
|
baseRun.not_run += r.not_run ?? 0;
|
|
11150
12517
|
notRunFiles.push(...baseRunner.lastNotRunFiles ?? []);
|
|
11151
12518
|
if (r.pass + r.fail === 0) {
|
|
11152
|
-
|
|
12519
|
+
rmSync13(abs, { force: true });
|
|
11153
12520
|
for (const l of [sealedFiles, readOnlyFiles])
|
|
11154
12521
|
l.splice(0, l.length, ...l.filter((f) => f.path !== abs));
|
|
11155
12522
|
}
|
|
@@ -11173,7 +12540,7 @@ async function runWall(ctx, signal, opts = {}) {
|
|
|
11173
12540
|
const a = await wallAuthor(ctx, signal);
|
|
11174
12541
|
return a.kind === "done" ? a.result : installWall(ctx, a, ctx.repoDir, opts);
|
|
11175
12542
|
}
|
|
11176
|
-
var WALL_PREFIX = "loki_wall_", BASE_RUN_TIMEOUT_MS = 60000, WALL_MAP_MAX_LINES = 200, wallStage,
|
|
12543
|
+
var WALL_PREFIX = "loki_wall_", BASE_RUN_TIMEOUT_MS = 60000, WALL_MAP_MAX_LINES = 200, wallStage, stage2;
|
|
11177
12544
|
var init_wall = __esm(() => {
|
|
11178
12545
|
init_types();
|
|
11179
12546
|
init_lean_prefix();
|
|
@@ -11190,24 +12557,24 @@ var init_wall = __esm(() => {
|
|
|
11190
12557
|
limitS: 300,
|
|
11191
12558
|
run: (ctx, signal) => runWall(ctx, signal)
|
|
11192
12559
|
};
|
|
11193
|
-
|
|
12560
|
+
stage2 = Object.assign(wallStage, { split: { author: wallAuthor, install: installWall } });
|
|
11194
12561
|
});
|
|
11195
12562
|
|
|
11196
12563
|
// ../../loki-ts/src/e10ext/discard.ts
|
|
11197
|
-
import { chmodSync as chmodSync4, closeSync as
|
|
11198
|
-
import { dirname as
|
|
12564
|
+
import { chmodSync as chmodSync4, closeSync as closeSync8, lstatSync as lstatSync10, mkdirSync as mkdirSync19, openSync as openSync8, readFileSync as readFileSync37, realpathSync as realpathSync19, rmdirSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync20 } from "fs";
|
|
12565
|
+
import { dirname as dirname20, join as join52, relative as relative7, sep as sep11 } from "path";
|
|
11199
12566
|
function safeRestore(repoDir, f, want, mode) {
|
|
11200
|
-
const root =
|
|
12567
|
+
const root = realpathSync19(repoDir), fp = join52(root, f), rel = relative7(root, dirname20(fp));
|
|
11201
12568
|
if (rel.startsWith(".."))
|
|
11202
12569
|
throw new Error("outside repo");
|
|
11203
12570
|
let cur = root;
|
|
11204
12571
|
for (const part of rel === "" ? [] : rel.split(sep11)) {
|
|
11205
|
-
cur =
|
|
12572
|
+
cur = join52(cur, part);
|
|
11206
12573
|
let st2;
|
|
11207
12574
|
try {
|
|
11208
|
-
st2 =
|
|
12575
|
+
st2 = lstatSync10(cur);
|
|
11209
12576
|
} catch {
|
|
11210
|
-
|
|
12577
|
+
mkdirSync19(cur);
|
|
11211
12578
|
continue;
|
|
11212
12579
|
}
|
|
11213
12580
|
if (!st2.isDirectory())
|
|
@@ -11215,11 +12582,11 @@ function safeRestore(repoDir, f, want, mode) {
|
|
|
11215
12582
|
}
|
|
11216
12583
|
let st;
|
|
11217
12584
|
try {
|
|
11218
|
-
st =
|
|
12585
|
+
st = lstatSync10(fp);
|
|
11219
12586
|
} catch {
|
|
11220
12587
|
st = null;
|
|
11221
12588
|
}
|
|
11222
|
-
if (st?.isFile() &&
|
|
12589
|
+
if (st?.isFile() && readFileSync37(fp).equals(want)) {
|
|
11223
12590
|
chmodSync4(fp, mode);
|
|
11224
12591
|
return;
|
|
11225
12592
|
}
|
|
@@ -11227,13 +12594,13 @@ function safeRestore(repoDir, f, want, mode) {
|
|
|
11227
12594
|
if (st.isDirectory())
|
|
11228
12595
|
rmdirSync(fp);
|
|
11229
12596
|
else
|
|
11230
|
-
|
|
12597
|
+
unlinkSync4(fp);
|
|
11231
12598
|
}
|
|
11232
|
-
const fd =
|
|
12599
|
+
const fd = openSync8(fp, "wx", mode);
|
|
11233
12600
|
try {
|
|
11234
|
-
|
|
12601
|
+
writeFileSync20(fd, want);
|
|
11235
12602
|
} finally {
|
|
11236
|
-
|
|
12603
|
+
closeSync8(fd);
|
|
11237
12604
|
}
|
|
11238
12605
|
chmodSync4(fp, mode);
|
|
11239
12606
|
}
|
|
@@ -11257,10 +12624,10 @@ async function discardIfSatisfied(git3, base, o, staged, keep, repoDir) {
|
|
|
11257
12624
|
return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
|
|
11258
12625
|
const notProven = [];
|
|
11259
12626
|
for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
|
|
11260
|
-
const fp =
|
|
12627
|
+
const fp = join52(repoDir, f);
|
|
11261
12628
|
try {
|
|
11262
12629
|
if (v.startsWith("!")) {
|
|
11263
|
-
const st =
|
|
12630
|
+
const st = lstatSync10(fp);
|
|
11264
12631
|
if (`!${st.size}:${st.mtimeMs}:${st.ctimeMs}` !== v)
|
|
11265
12632
|
throw new Error("changed, not snapshotted");
|
|
11266
12633
|
continue;
|
|
@@ -11276,7 +12643,7 @@ async function discardIfSatisfied(git3, base, o, staged, keep, repoDir) {
|
|
|
11276
12643
|
try {
|
|
11277
12644
|
let m = 420;
|
|
11278
12645
|
try {
|
|
11279
|
-
const l =
|
|
12646
|
+
const l = lstatSync10(join52(repoDir, f));
|
|
11280
12647
|
if (l.isFile())
|
|
11281
12648
|
m = l.mode & 511;
|
|
11282
12649
|
} catch {}
|
|
@@ -11309,9 +12676,9 @@ var init_preexisting_dirty = __esm(() => {
|
|
|
11309
12676
|
});
|
|
11310
12677
|
|
|
11311
12678
|
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
11312
|
-
import { dirname as
|
|
12679
|
+
import { dirname as dirname21 } from "path";
|
|
11313
12680
|
function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
|
|
11314
|
-
return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) &&
|
|
12681
|
+
return staged.concat(untouchedSinceIntake(repoDir, preexistingDirty, env).map((f) => ({ st: "L", f }))).filter(({ st, f }) => f.startsWith(".loki/") || st === "L" || /(^|\/)loki_wall_[^/]*$/.test(f) || st === "A" && !staged.some(({ f: m }) => /(^|\/)(package\.json|pyproject\.toml|requirements\.txt|Cargo\.toml|go\.mod)$/.test(m) && dirname21(m) === dirname21(f)) && /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|Cargo\.lock|go\.sum)$/.test(f));
|
|
11315
12682
|
}
|
|
11316
12683
|
var parseStaged = (out2) => out2.split("\x00").reduce((a, t, i, all) => i % 2 === 0 && t ? [...a, { st: t, f: all[i + 1] }] : a, []);
|
|
11317
12684
|
var init_commit_filter = __esm(() => {
|
|
@@ -11319,73 +12686,8 @@ var init_commit_filter = __esm(() => {
|
|
|
11319
12686
|
init_safe_git();
|
|
11320
12687
|
});
|
|
11321
12688
|
|
|
11322
|
-
// ../../loki-ts/src/features/speed/unit_mode.ts
|
|
11323
|
-
import { closeSync as closeSync7, constants as constants2, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync7, readSync as readSync5, realpathSync as realpathSync19 } from "fs";
|
|
11324
|
-
function readBounded(path) {
|
|
11325
|
-
const l = lstatSync8(path);
|
|
11326
|
-
if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
|
|
11327
|
-
return null;
|
|
11328
|
-
const fd = openSync7(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
11329
|
-
try {
|
|
11330
|
-
const st = fstatSync4(fd);
|
|
11331
|
-
if (!st.isFile() || st.size > MAX_SPEC_BYTES)
|
|
11332
|
-
return null;
|
|
11333
|
-
const buf = Buffer.alloc(st.size + 1);
|
|
11334
|
-
let n2 = 0;
|
|
11335
|
-
for (;; ) {
|
|
11336
|
-
const r = readSync5(fd, buf, n2, buf.length - n2, null);
|
|
11337
|
-
if (r <= 0)
|
|
11338
|
-
break;
|
|
11339
|
-
n2 += r;
|
|
11340
|
-
if (n2 >= buf.length)
|
|
11341
|
-
return null;
|
|
11342
|
-
}
|
|
11343
|
-
return buf.subarray(0, n2).toString("utf8");
|
|
11344
|
-
} finally {
|
|
11345
|
-
closeSync7(fd);
|
|
11346
|
-
}
|
|
11347
|
-
}
|
|
11348
|
-
function parseText(txt) {
|
|
11349
|
-
try {
|
|
11350
|
-
const j = JSON.parse(txt), ws = strs(j.writeSet), pack = strs(j.pack), tb = j.tokenBudget;
|
|
11351
|
-
if (typeof j.id !== "string" || !j.id || !ws || ws.length === 0 || !pack || typeof tb !== "number" || !Number.isFinite(tb) || !(tb > 0) || tb > MAX_TOKEN_BUDGET)
|
|
11352
|
-
return null;
|
|
11353
|
-
return { id: j.id, writeSet: ws, pack, tokenBudget: tb };
|
|
11354
|
-
} catch {
|
|
11355
|
-
return null;
|
|
11356
|
-
}
|
|
11357
|
-
}
|
|
11358
|
-
function parseSpec(path) {
|
|
11359
|
-
try {
|
|
11360
|
-
const txt = readBounded(path);
|
|
11361
|
-
return txt === null ? null : parseText(txt);
|
|
11362
|
-
} catch {
|
|
11363
|
-
return null;
|
|
11364
|
-
}
|
|
11365
|
-
}
|
|
11366
|
-
function unitSpec(env = process.env) {
|
|
11367
|
-
const path = env["LOKI_UNIT_SPEC"], frozen = env["LOKI_UNIT_SPEC_FROZEN"];
|
|
11368
|
-
if (env["LOKI_SPEED"] === "0" || !path)
|
|
11369
|
-
return null;
|
|
11370
|
-
if (frozen !== undefined)
|
|
11371
|
-
return frozen.length <= MAX_SPEC_BYTES ? parseText(frozen) : null;
|
|
11372
|
-
if (!memo.has(path))
|
|
11373
|
-
memo.set(path, parseSpec(path));
|
|
11374
|
-
return memo.get(path) ?? null;
|
|
11375
|
-
}
|
|
11376
|
-
function unitFlags(staged, env = process.env, pre = {}) {
|
|
11377
|
-
const s = unitSpec(env);
|
|
11378
|
-
return s ? staged.filter(({ f }) => !inWriteSet(s, f) && !Object.hasOwn(pre, f)).map(({ f }) => f) : null;
|
|
11379
|
-
}
|
|
11380
|
-
var MAX_SPEC_BYTES, MAX_TOKEN_BUDGET = 1e9, norm3 = (p) => p.replace(/^\.\//, ""), CTRL, strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm3) : null, memo, inWriteSet = (spec, f) => spec.writeSet.some((w) => w.endsWith("/") ? f.startsWith(w) : f === w);
|
|
11381
|
-
var init_unit_mode = __esm(() => {
|
|
11382
|
-
MAX_SPEC_BYTES = 1024 * 1024;
|
|
11383
|
-
CTRL = /[\x00-\x1f\x7f]/;
|
|
11384
|
-
memo = new Map;
|
|
11385
|
-
});
|
|
11386
|
-
|
|
11387
12689
|
// ../../loki-ts/src/e10ext/scope.ts
|
|
11388
|
-
import { basename as
|
|
12690
|
+
import { basename as basename18, dirname as dirname22 } from "path";
|
|
11389
12691
|
function contractText(o) {
|
|
11390
12692
|
const c = o.intake?.contract_snapshot?.contract;
|
|
11391
12693
|
return Array.isArray(c?.criteria) ? c.criteria.map((x) => typeof x?.text === "string" ? x.text : "").join(`
|
|
@@ -11399,8 +12701,8 @@ function outsideScope(o, staged) {
|
|
|
11399
12701
|
const text3 = `${plan}
|
|
11400
12702
|
${typeof o.intake?.task === "string" ? o.intake.task : ""}
|
|
11401
12703
|
${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
|
|
11402
|
-
const toks = surfaceTokens(text3), plannedDirs = new Set(rel.map((r) =>
|
|
11403
|
-
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text3, f) && !mentions(text3,
|
|
12704
|
+
const toks = surfaceTokens(text3), plannedDirs = new Set(rel.map((r) => dirname22(r)).filter((d) => d !== "."));
|
|
12705
|
+
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text3, f) && !mentions(text3, basename18(f)) && !plannedDirs.has(dirname22(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
|
|
11404
12706
|
}
|
|
11405
12707
|
function flagOutsideScope(o, staged) {
|
|
11406
12708
|
const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
|
|
@@ -11415,101 +12717,77 @@ var init_scope2 = __esm(() => {
|
|
|
11415
12717
|
});
|
|
11416
12718
|
|
|
11417
12719
|
// ../../loki-ts/src/util/receipt_signer.ts
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
import {
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
renameSync as renameSync5,
|
|
11430
|
-
rmSync as rmSync11,
|
|
11431
|
-
statSync as statSync15,
|
|
11432
|
-
unlinkSync as unlinkSync4,
|
|
11433
|
-
writeSync as writeSync3
|
|
11434
|
-
} from "fs";
|
|
11435
|
-
import { dirname as dirname18 } from "path";
|
|
11436
|
-
function durableWriteThenRename(target, body) {
|
|
11437
|
-
mkdirSync12(dirname18(target), { recursive: true });
|
|
11438
|
-
const tmp = `${target}.tmp.${process.pid}.${++_tmpCounter}`;
|
|
11439
|
-
const fd = openSync8(tmp, "w");
|
|
11440
|
-
try {
|
|
11441
|
-
writeSync3(fd, body);
|
|
11442
|
-
fsyncSync2(fd);
|
|
11443
|
-
} finally {
|
|
11444
|
-
closeSync8(fd);
|
|
11445
|
-
}
|
|
11446
|
-
renameSync5(tmp, target);
|
|
11447
|
-
fsyncParentDir(target);
|
|
11448
|
-
}
|
|
11449
|
-
function fsyncParentDir(target) {
|
|
11450
|
-
let dfd = null;
|
|
11451
|
-
try {
|
|
11452
|
-
dfd = openSync8(dirname18(target), "r");
|
|
11453
|
-
fsyncSync2(dfd);
|
|
11454
|
-
} catch {} finally {
|
|
11455
|
-
if (dfd !== null) {
|
|
11456
|
-
try {
|
|
11457
|
-
closeSync8(dfd);
|
|
11458
|
-
} catch {}
|
|
12720
|
+
import { createHash as createHash11, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
12721
|
+
import { chmodSync as chmodSync5, linkSync, mkdirSync as mkdirSync20, readFileSync as readFileSync38, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync21 } from "fs";
|
|
12722
|
+
import { homedir as homedir8 } from "os";
|
|
12723
|
+
import { dirname as dirname23, join as join53 } from "path";
|
|
12724
|
+
function canonicalJson(x) {
|
|
12725
|
+
const walk = (v) => {
|
|
12726
|
+
if (Array.isArray(v))
|
|
12727
|
+
return `[${v.map(walk).join(",")}]`;
|
|
12728
|
+
if (v !== null && typeof v === "object") {
|
|
12729
|
+
const o = v;
|
|
12730
|
+
return `{${Object.keys(o).filter((k) => o[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${walk(o[k])}`).join(",")}}`;
|
|
11459
12731
|
}
|
|
11460
|
-
|
|
11461
|
-
}
|
|
11462
|
-
|
|
11463
|
-
durableWriteThenRename(target, `${JSON.stringify(data, null, 2)}
|
|
11464
|
-
`);
|
|
12732
|
+
return JSON.stringify(v ?? null);
|
|
12733
|
+
};
|
|
12734
|
+
return walk(x).replace(/[\u0080-\uFFFF]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
11465
12735
|
}
|
|
11466
|
-
|
|
11467
|
-
var init_atomic = __esm(() => {
|
|
11468
|
-
_appendChains = new Map;
|
|
11469
|
-
});
|
|
11470
|
-
|
|
11471
|
-
// ../../loki-ts/src/util/pr_lessons.ts
|
|
11472
|
-
import { existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync31 } from "fs";
|
|
11473
|
-
import { dirname as dirname19, join as join45 } from "path";
|
|
11474
|
-
function loadLessons(repoDir) {
|
|
11475
|
-
const p = lessonsPath(repoDir);
|
|
11476
|
-
if (!existsSync25(p))
|
|
11477
|
-
return [];
|
|
12736
|
+
function loadSigningKey(generate = true) {
|
|
11478
12737
|
try {
|
|
11479
|
-
const
|
|
11480
|
-
|
|
12738
|
+
const inline = process.env["LOKI_RECEIPT_SIGNING_KEY"]?.trim();
|
|
12739
|
+
let pem = inline ?? "";
|
|
12740
|
+
if (!inline) {
|
|
12741
|
+
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
12742
|
+
const file = given || join53(process.env["HOME"] || homedir8(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
12743
|
+
try {
|
|
12744
|
+
pem = readFileSync38(file);
|
|
12745
|
+
if (!given) {
|
|
12746
|
+
for (const [f, m] of [[file, 384], [dirname23(file), 448]])
|
|
12747
|
+
if (statSync17(f).mode & 63)
|
|
12748
|
+
chmodSync5(f, m);
|
|
12749
|
+
}
|
|
12750
|
+
} catch (e) {
|
|
12751
|
+
if (e.code !== "ENOENT" || !generate)
|
|
12752
|
+
return null;
|
|
12753
|
+
mkdirSync20(dirname23(dirname23(file)), { recursive: true, mode: 448 });
|
|
12754
|
+
mkdirSync20(dirname23(file), { recursive: true, mode: 448 });
|
|
12755
|
+
const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
12756
|
+
writeFileSync21(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
|
|
12757
|
+
try {
|
|
12758
|
+
linkSync(tmp, file);
|
|
12759
|
+
} catch (l) {
|
|
12760
|
+
if (l.code !== "EEXIST")
|
|
12761
|
+
throw l;
|
|
12762
|
+
} finally {
|
|
12763
|
+
unlinkSync5(tmp);
|
|
12764
|
+
}
|
|
12765
|
+
pem = readFileSync38(file);
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12768
|
+
const k = createPrivateKey(pem);
|
|
12769
|
+
return k.asymmetricKeyType === "ed25519" ? k : null;
|
|
11481
12770
|
} catch {
|
|
11482
|
-
return
|
|
12771
|
+
return null;
|
|
11483
12772
|
}
|
|
11484
12773
|
}
|
|
11485
|
-
function
|
|
11486
|
-
const
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
let dirty = false;
|
|
11493
|
-
for (const l of lessons)
|
|
11494
|
-
for (const u of l.uses)
|
|
11495
|
-
if (u.run_id === runId && u.verdict === null) {
|
|
11496
|
-
u.verdict = verdict;
|
|
11497
|
-
dirty = true;
|
|
11498
|
-
}
|
|
11499
|
-
if (dirty)
|
|
11500
|
-
save(repoDir, lessons);
|
|
12774
|
+
function signReceipt(runId, hash) {
|
|
12775
|
+
const key = loadSigningKey();
|
|
12776
|
+
if (!key)
|
|
12777
|
+
return { jwt: null, kid: null };
|
|
12778
|
+
const kid = kidOf2(createPublicKey2(key));
|
|
12779
|
+
const input = `${b64u(canonicalJson({ alg: "EdDSA", typ: "JWT", kid }))}.${b64u(canonicalJson({ job_id: runId, run_id: runId, receipt_sha256: hash, iat: Math.floor(Date.now() / 1000) }))}`;
|
|
12780
|
+
return { jwt: `${input}.${b64u(sign(null, Buffer.from(input), key))}`, kid };
|
|
11501
12781
|
}
|
|
11502
|
-
var
|
|
11503
|
-
var
|
|
11504
|
-
init_atomic();
|
|
11505
|
-
});
|
|
12782
|
+
var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem", b64u = (b) => Buffer.from(b).toString("base64url"), kidOf2 = (pub) => b64u(createHash11("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest());
|
|
12783
|
+
var init_receipt_signer = () => {};
|
|
11506
12784
|
|
|
11507
12785
|
// ../../loki-ts/src/features/visual_evidence.ts
|
|
11508
12786
|
import { spawn as spawn7 } from "child_process";
|
|
11509
|
-
import { createHash as
|
|
11510
|
-
import { existsSync as
|
|
12787
|
+
import { createHash as createHash12, randomBytes as randomBytes4 } from "crypto";
|
|
12788
|
+
import { existsSync as existsSync32, lstatSync as lstatSync11, mkdirSync as mkdirSync21, readdirSync as readdirSync16, readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "fs";
|
|
11511
12789
|
import { createServer } from "net";
|
|
11512
|
-
import { dirname as
|
|
12790
|
+
import { dirname as dirname24, isAbsolute as isAbsolute8, join as join54, normalize as normalize2, relative as relative8, sep as sep12 } from "path";
|
|
11513
12791
|
function routeFor(file) {
|
|
11514
12792
|
let r = file.replace(/^(?:.*?\/)?(?:app|pages|src|public)\//, "").replace(/^(?:routes|pages)\//, "");
|
|
11515
12793
|
r = r.replace(/\.(?:html|jsx|tsx|vue|svelte)$/, "").replace(/\/?(?:page|index|\+page)$/, "");
|
|
@@ -11519,7 +12797,7 @@ function hashScreens(runDir, rels) {
|
|
|
11519
12797
|
const out2 = [];
|
|
11520
12798
|
for (const rel of rels) {
|
|
11521
12799
|
try {
|
|
11522
|
-
out2.push({ path: rel, sha256: sha256File(
|
|
12800
|
+
out2.push({ path: rel, sha256: sha256File(join54(runDir, rel)) });
|
|
11523
12801
|
} catch {}
|
|
11524
12802
|
}
|
|
11525
12803
|
return out2;
|
|
@@ -11530,16 +12808,16 @@ function checkScreens(runDir, screens) {
|
|
|
11530
12808
|
for (const s of screens) {
|
|
11531
12809
|
if (typeof s?.path !== "string" || typeof s.sha256 !== "string")
|
|
11532
12810
|
return "evidence_screens entry is malformed";
|
|
11533
|
-
if (
|
|
12811
|
+
if (isAbsolute8(s.path) || normalize2(s.path).startsWith(".."))
|
|
11534
12812
|
return `evidence screenshot path escapes the evidence dir: ${s.path}`;
|
|
11535
|
-
const file =
|
|
12813
|
+
const file = join54(runDir, s.path);
|
|
11536
12814
|
let cur = runDir;
|
|
11537
12815
|
for (const seg of normalize2(s.path).split(sep12)) {
|
|
11538
|
-
cur =
|
|
12816
|
+
cur = join54(cur, seg);
|
|
11539
12817
|
if (isSymlink(cur))
|
|
11540
12818
|
return `evidence screenshot path is a symlink: ${s.path}`;
|
|
11541
12819
|
}
|
|
11542
|
-
if (!
|
|
12820
|
+
if (!existsSync32(file))
|
|
11543
12821
|
return `evidence screenshot is missing: ${s.path}`;
|
|
11544
12822
|
if (!isRegularFile(file))
|
|
11545
12823
|
return `evidence screenshot is not a regular file: ${s.path}`;
|
|
@@ -11582,11 +12860,11 @@ async function waitUp(url, ms, signal) {
|
|
|
11582
12860
|
}
|
|
11583
12861
|
function openapiPaths(repoDir) {
|
|
11584
12862
|
for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
|
|
11585
|
-
const p =
|
|
11586
|
-
if (!
|
|
12863
|
+
const p = join54(repoDir, f);
|
|
12864
|
+
if (!existsSync32(p))
|
|
11587
12865
|
continue;
|
|
11588
12866
|
try {
|
|
11589
|
-
const txt =
|
|
12867
|
+
const txt = readFileSync39(p, "utf8");
|
|
11590
12868
|
const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
|
|
11591
12869
|
return paths.filter((x) => !x.includes("{")).slice(0, 10);
|
|
11592
12870
|
} catch {
|
|
@@ -11600,24 +12878,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
|
|
|
11600
12878
|
if (!pw || !pkg)
|
|
11601
12879
|
return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
|
|
11602
12880
|
try {
|
|
11603
|
-
const dir =
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
12881
|
+
const dir = join54(runDir, relDir, "e2e");
|
|
12882
|
+
mkdirSync21(dir, { recursive: true });
|
|
12883
|
+
writeFileSync22(join54(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
|
|
12884
|
+
writeFileSync22(join54(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
|
|
11607
12885
|
`);
|
|
11608
|
-
if (!await shoot(pw, ["test", "--config",
|
|
12886
|
+
if (!await shoot(pw, ["test", "--config", join54(dir, "playwright.config.cjs")], repoDir, ms, signal))
|
|
11609
12887
|
return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
|
|
11610
12888
|
const rels = [];
|
|
11611
12889
|
const walk = (d) => {
|
|
11612
|
-
for (const e of
|
|
11613
|
-
const full =
|
|
12890
|
+
for (const e of readdirSync16(d, { withFileTypes: true })) {
|
|
12891
|
+
const full = join54(d, e.name);
|
|
11614
12892
|
if (e.isDirectory())
|
|
11615
12893
|
walk(full);
|
|
11616
12894
|
else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
|
|
11617
|
-
rels.push(
|
|
12895
|
+
rels.push(join54(relDir, "e2e", relative8(dir, full)));
|
|
11618
12896
|
}
|
|
11619
12897
|
};
|
|
11620
|
-
walk(
|
|
12898
|
+
walk(join54(dir, "out"));
|
|
11621
12899
|
return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
|
|
11622
12900
|
} catch (e) {
|
|
11623
12901
|
return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
|
|
@@ -11686,13 +12964,13 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
11686
12964
|
if (!visualEvidenceEnabled(opts.env))
|
|
11687
12965
|
return skip("LOKI_VISUAL_EVIDENCE is 0");
|
|
11688
12966
|
const api = loadProjectApi(repoDir), boot = isMultiRoot(api) ? api.uiBoot() : null;
|
|
11689
|
-
const appDir = boot ?
|
|
11690
|
-
const pkgPath =
|
|
12967
|
+
const appDir = boot ? join54(repoDir, boot.boot.cwd) : repoDir;
|
|
12968
|
+
const pkgPath = join54(appDir, "package.json");
|
|
11691
12969
|
let script = null;
|
|
11692
12970
|
if (!boot) {
|
|
11693
|
-
if (!
|
|
12971
|
+
if (!existsSync32(pkgPath))
|
|
11694
12972
|
return skip("no package.json");
|
|
11695
|
-
script = pickScript(JSON.parse(
|
|
12973
|
+
script = pickScript(JSON.parse(readFileSync39(pkgPath, "utf8")));
|
|
11696
12974
|
if (!script)
|
|
11697
12975
|
return skip("no dev, preview or start script");
|
|
11698
12976
|
}
|
|
@@ -11719,20 +12997,20 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
11719
12997
|
}
|
|
11720
12998
|
if (!await waitUp(base, left(), signal))
|
|
11721
12999
|
return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
|
|
11722
|
-
const evRoot =
|
|
13000
|
+
const evRoot = join54(runDir, "evidence");
|
|
11723
13001
|
if (isSymlink(evRoot))
|
|
11724
13002
|
return skip("evidence dir is a symlink");
|
|
11725
|
-
|
|
13003
|
+
mkdirSync21(evRoot, { recursive: true });
|
|
11726
13004
|
if (pages.length > 0) {
|
|
11727
|
-
const rel0 =
|
|
11728
|
-
|
|
13005
|
+
const rel0 = join54("evidence", randomBytes4(6).toString("hex"));
|
|
13006
|
+
mkdirSync21(join54(runDir, rel0));
|
|
11729
13007
|
const rels = [];
|
|
11730
13008
|
for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
|
|
11731
13009
|
if (aborted() || left() <= 0)
|
|
11732
13010
|
break;
|
|
11733
|
-
const rel =
|
|
11734
|
-
const ok = await shoot(pw, ["screenshot", `${base}${route}`,
|
|
11735
|
-
if (ok && !aborted() && !isSymlink(
|
|
13011
|
+
const rel = join54(rel0, `${screenName(route)}.png`);
|
|
13012
|
+
const ok = await shoot(pw, ["screenshot", `${base}${route}`, join54(runDir, rel)], appDir, left(), signal);
|
|
13013
|
+
if (ok && !aborted() && !isSymlink(join54(runDir, rel)) && existsSync32(join54(runDir, rel)))
|
|
11736
13014
|
rels.push(rel);
|
|
11737
13015
|
}
|
|
11738
13016
|
if (aborted())
|
|
@@ -11757,9 +13035,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
11757
13035
|
}
|
|
11758
13036
|
if (aborted())
|
|
11759
13037
|
return skip("aborted");
|
|
11760
|
-
const hdir =
|
|
11761
|
-
|
|
11762
|
-
|
|
13038
|
+
const hdir = join54(evRoot, randomBytes4(6).toString("hex"));
|
|
13039
|
+
mkdirSync21(hdir);
|
|
13040
|
+
writeFileSync22(join54(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
|
|
11763
13041
|
`);
|
|
11764
13042
|
return { screens: [], http: true, skipped: null };
|
|
11765
13043
|
} catch (e) {
|
|
@@ -11782,27 +13060,27 @@ async function sealEvidence(repoDir, runDir, o, notProven, signal, emit) {
|
|
|
11782
13060
|
return ev.screens.length > 0 ? { evidence_screens: ev.screens } : {};
|
|
11783
13061
|
}
|
|
11784
13062
|
function receiptScreensProblem(receiptPath, receipt) {
|
|
11785
|
-
return receipt["evidence_screens"] === undefined ? null : checkScreens(
|
|
13063
|
+
return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname24(receiptPath), receipt["evidence_screens"]);
|
|
11786
13064
|
}
|
|
11787
|
-
var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0", PAGE_RE, isPageFile = (p) => PAGE_RE.test(p), screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index", sha256File = (p) =>
|
|
11788
|
-
const bin =
|
|
11789
|
-
return
|
|
13065
|
+
var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0", PAGE_RE, isPageFile = (p) => PAGE_RE.test(p), screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index", sha256File = (p) => createHash12("sha256").update(readFileSync39(p)).digest("hex"), playwrightBin = (repoDir) => {
|
|
13066
|
+
const bin = join54(repoDir, "node_modules", ".bin", "playwright");
|
|
13067
|
+
return existsSync32(bin) ? bin : null;
|
|
11790
13068
|
}, playwrightTestPkg = (repoDir) => {
|
|
11791
|
-
const d =
|
|
11792
|
-
return
|
|
13069
|
+
const d = join54(repoDir, "node_modules", "@playwright", "test");
|
|
13070
|
+
return existsSync32(join54(d, "package.json")) ? d : null;
|
|
11793
13071
|
}, e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
|
|
11794
13072
|
test("loki e2e walkthrough", async ({ page }) => {
|
|
11795
13073
|
for (const r of ${JSON.stringify(routes)}) { await page.goto(${JSON.stringify(base)} + r); await page.waitForLoadState("load"); }
|
|
11796
13074
|
});
|
|
11797
13075
|
`, DEFAULT_BUDGET_MS = 25000, MAX_ROUTES = 5, isSymlink = (p) => {
|
|
11798
13076
|
try {
|
|
11799
|
-
return
|
|
13077
|
+
return lstatSync11(p).isSymbolicLink();
|
|
11800
13078
|
} catch {
|
|
11801
13079
|
return false;
|
|
11802
13080
|
}
|
|
11803
13081
|
}, isRegularFile = (p) => {
|
|
11804
13082
|
try {
|
|
11805
|
-
return
|
|
13083
|
+
return lstatSync11(p).isFile();
|
|
11806
13084
|
} catch {
|
|
11807
13085
|
return false;
|
|
11808
13086
|
}
|
|
@@ -11819,83 +13097,61 @@ var init_visual_evidence = __esm(() => {
|
|
|
11819
13097
|
PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
11820
13098
|
});
|
|
11821
13099
|
|
|
11822
|
-
// ../../loki-ts/src/
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
try {
|
|
11829
|
-
while (total < maxBytes) {
|
|
11830
|
-
const { done, value } = await reader.read();
|
|
11831
|
-
if (done)
|
|
11832
|
-
break;
|
|
11833
|
-
if (!value)
|
|
11834
|
-
continue;
|
|
11835
|
-
total += value.byteLength;
|
|
11836
|
-
if (total > maxBytes) {
|
|
11837
|
-
const keep = value.byteLength - (total - maxBytes);
|
|
11838
|
-
out2 += decoder.decode(value.subarray(0, keep), { stream: true });
|
|
11839
|
-
break;
|
|
11840
|
-
}
|
|
11841
|
-
out2 += decoder.decode(value, { stream: true });
|
|
11842
|
-
}
|
|
11843
|
-
out2 += decoder.decode();
|
|
11844
|
-
} finally {
|
|
11845
|
-
try {
|
|
11846
|
-
await reader.cancel();
|
|
11847
|
-
} catch {}
|
|
11848
|
-
reader.releaseLock();
|
|
11849
|
-
}
|
|
11850
|
-
return out2;
|
|
13100
|
+
// ../../loki-ts/src/council/role_agents.ts
|
|
13101
|
+
import { existsSync as existsSync33, readdirSync as readdirSync17, readFileSync as readFileSync40 } from "fs";
|
|
13102
|
+
import { homedir as homedir9 } from "os";
|
|
13103
|
+
import { join as join55 } from "path";
|
|
13104
|
+
function agentDirs(repoDir, env = process.env) {
|
|
13105
|
+
return [env["LOKI_AGENTS_PROJECT_DIR"] || join55(repoDir, ".loki", "agents"), env["LOKI_AGENTS_USER_DIR"] || join55(env["XDG_CONFIG_HOME"] || join55(homedir9(), ".config"), "loki", "agents")];
|
|
11851
13106
|
}
|
|
11852
|
-
|
|
11853
|
-
const
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
});
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
13107
|
+
function parseAgent(file, stem2) {
|
|
13108
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(readFileSync40(file, "utf8"));
|
|
13109
|
+
if (!m)
|
|
13110
|
+
return null;
|
|
13111
|
+
const body = m[2].trim();
|
|
13112
|
+
if (!body)
|
|
13113
|
+
return null;
|
|
13114
|
+
const kv = (k) => new RegExp(`^${k}:[ \\t]*["']?([^"'\\r\\n#]*?)["']?[ \\t]*$`, "m").exec(m[1])?.[1]?.trim() ?? "";
|
|
13115
|
+
const role = (kv("role") || stem2).toLowerCase();
|
|
13116
|
+
if (!ROLES.includes(role))
|
|
13117
|
+
return null;
|
|
13118
|
+
return { name: kv("name") || stem2, role, provider: (kv("provider") || "claude").toLowerCase(), model: kv("model"), prompt: body.slice(0, 4000) };
|
|
13119
|
+
}
|
|
13120
|
+
function loadRoleAgent(repoDir, role, env = process.env) {
|
|
13121
|
+
for (const d of agentDirs(repoDir, env)) {
|
|
13122
|
+
if (!existsSync33(d))
|
|
13123
|
+
continue;
|
|
13124
|
+
let names;
|
|
13125
|
+
try {
|
|
13126
|
+
names = readdirSync17(d).filter((n2) => n2.endsWith(".md") && !n2.startsWith(".")).sort();
|
|
13127
|
+
} catch {
|
|
13128
|
+
continue;
|
|
13129
|
+
}
|
|
13130
|
+
const ordered = [...names.filter((n2) => n2 === `${role}.md`), ...names.filter((n2) => n2 !== `${role}.md`)];
|
|
13131
|
+
for (const n2 of ordered) {
|
|
11864
13132
|
try {
|
|
11865
|
-
|
|
13133
|
+
const a = parseAgent(join55(d, n2), n2.slice(0, -3));
|
|
13134
|
+
if (a && a.role === role)
|
|
13135
|
+
return a;
|
|
11866
13136
|
} catch {}
|
|
11867
|
-
|
|
11868
|
-
try {
|
|
11869
|
-
proc.kill("SIGKILL");
|
|
11870
|
-
} catch {}
|
|
11871
|
-
}, 2000);
|
|
11872
|
-
}, opts.timeoutMs);
|
|
11873
|
-
}
|
|
11874
|
-
try {
|
|
11875
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
11876
|
-
readStreamCapped(proc.stdout),
|
|
11877
|
-
new Response(proc.stderr).text(),
|
|
11878
|
-
proc.exited
|
|
11879
|
-
]);
|
|
11880
|
-
return { stdout, stderr, exitCode };
|
|
11881
|
-
} finally {
|
|
11882
|
-
if (timer)
|
|
11883
|
-
clearTimeout(timer);
|
|
11884
|
-
if (killTimer)
|
|
11885
|
-
clearTimeout(killTimer);
|
|
13137
|
+
}
|
|
11886
13138
|
}
|
|
13139
|
+
return null;
|
|
11887
13140
|
}
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
13141
|
+
function loadReviewStageAgent(repoDir, env = process.env) {
|
|
13142
|
+
return loadRoleAgent(repoDir, "reviewer", env) ?? loadRoleAgent(repoDir, "council", env);
|
|
13143
|
+
}
|
|
13144
|
+
var ROLES;
|
|
13145
|
+
var init_role_agents = __esm(() => {
|
|
13146
|
+
ROLES = ["council", "reviewer"];
|
|
11891
13147
|
});
|
|
11892
13148
|
|
|
11893
13149
|
// ../../loki-ts/src/engine10/stages/xreview.ts
|
|
11894
|
-
import { existsSync as
|
|
11895
|
-
import { join as
|
|
13150
|
+
import { existsSync as existsSync34, readFileSync as readFileSync41 } from "fs";
|
|
13151
|
+
import { join as join56 } from "path";
|
|
11896
13152
|
function minVerdict(verdict, review) {
|
|
11897
13153
|
const ceiling = review?.level === "block" ? "PARTIAL" : verdict;
|
|
11898
|
-
return
|
|
13154
|
+
return RANK3[ceiling] < RANK3[verdict] ? ceiling : verdict;
|
|
11899
13155
|
}
|
|
11900
13156
|
function explicitReview(repoDir, env) {
|
|
11901
13157
|
const flag = xvendorDefault(env);
|
|
@@ -11905,11 +13161,11 @@ function explicitReview(repoDir, env) {
|
|
|
11905
13161
|
if (flag && isOff(ev))
|
|
11906
13162
|
return "off";
|
|
11907
13163
|
for (const f of ["loki.yaml", "loki.yml"]) {
|
|
11908
|
-
const p =
|
|
11909
|
-
if (!
|
|
13164
|
+
const p = join56(repoDir, f);
|
|
13165
|
+
if (!existsSync34(p))
|
|
11910
13166
|
continue;
|
|
11911
13167
|
try {
|
|
11912
|
-
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(
|
|
13168
|
+
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync41(p, "utf8"));
|
|
11913
13169
|
const v = asProvider(m?.[1]);
|
|
11914
13170
|
if (v)
|
|
11915
13171
|
return v;
|
|
@@ -11938,9 +13194,15 @@ function reviewReceipt(builder, xr, env = process.env) {
|
|
|
11938
13194
|
const differs = !!xr && xr.level !== "not_run" && xr.provider !== builder && (builder === "claude" || builder === "codex");
|
|
11939
13195
|
return { review: { provider: xr?.provider ?? null, vendor_differs: differs }, notProven: differs ? [] : ["judge shares builder vendor or none configured"] };
|
|
11940
13196
|
}
|
|
11941
|
-
function reviewArgv(p, prompt, env = process.env) {
|
|
13197
|
+
function reviewArgv(p, prompt, env = process.env, model) {
|
|
11942
13198
|
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"];
|
|
13199
|
+
return p === "codex" ? [cli, "exec", "--sandbox", "read-only", prompt] : [cli, "-p", prompt, ...model ? ["--model", model] : [], "--allowedTools", "Read,Grep,Glob", "--disallowedTools", "Write,Edit,NotebookEdit,Bash"];
|
|
13200
|
+
}
|
|
13201
|
+
function configuredReviewer(repoDir, env = process.env) {
|
|
13202
|
+
const a = loadReviewStageAgent(repoDir, env);
|
|
13203
|
+
if (!a)
|
|
13204
|
+
return null;
|
|
13205
|
+
return { prompt: a.prompt, model: a.provider === "claude" ? a.model : "", provider: asProvider(a.provider) };
|
|
11944
13206
|
}
|
|
11945
13207
|
function parseReview(p, out2) {
|
|
11946
13208
|
const m = /^\s*VERDICT:\s*(PASS|FLAG|BLOCK)\b/im.exec(out2);
|
|
@@ -11953,7 +13215,8 @@ function parseReview(p, out2) {
|
|
|
11953
13215
|
return { provider: p, level, notes };
|
|
11954
13216
|
}
|
|
11955
13217
|
async function crossReview(ctx, verdict, head) {
|
|
11956
|
-
const
|
|
13218
|
+
const agent = configuredReviewer(ctx.repoDir);
|
|
13219
|
+
const p = reviewProvider(ctx.repoDir, process.env, ctx.provider) ?? agent?.provider ?? null;
|
|
11957
13220
|
if (!p)
|
|
11958
13221
|
return null;
|
|
11959
13222
|
if (verdict !== "VERIFIED" && verdict !== "ALREADY_SATISFIED")
|
|
@@ -11962,7 +13225,8 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11962
13225
|
if (!d || d.exitCode !== 0)
|
|
11963
13226
|
return { provider: p, level: "not_run", notes: [`cross-review not run: diff unavailable for ${p}`] };
|
|
11964
13227
|
const prompt = [
|
|
11965
|
-
|
|
13228
|
+
agent ? `${agent.prompt}
|
|
13229
|
+
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
13230
|
"Reply with a first line exactly `VERDICT: PASS`, `VERDICT: FLAG` (concerns) or `VERDICT: BLOCK` (a reproduced defect), then one `- reason` line per concern.",
|
|
11967
13231
|
"",
|
|
11968
13232
|
"```diff",
|
|
@@ -11971,7 +13235,7 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11971
13235
|
].join(`
|
|
11972
13236
|
`);
|
|
11973
13237
|
try {
|
|
11974
|
-
const r = await run(reviewArgv(p, prompt), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
13238
|
+
const r = await run(reviewArgv(p, prompt, process.env, p === "claude" ? agent?.model : undefined), { cwd: ctx.repoDir, timeoutMs: TIMEOUT_MS });
|
|
11975
13239
|
if (r.exitCode !== 0)
|
|
11976
13240
|
return { provider: p, level: "not_run", notes: [`cross-review not run: ${p} exited ${r.exitCode}`] };
|
|
11977
13241
|
return parseReview(p, r.stdout);
|
|
@@ -11979,114 +13243,15 @@ async function crossReview(ctx, verdict, head) {
|
|
|
11979
13243
|
return { provider: p, level: "not_run", notes: [`cross-review not run: ${p} CLI unavailable`] };
|
|
11980
13244
|
}
|
|
11981
13245
|
}
|
|
11982
|
-
var
|
|
13246
|
+
var RANK3, TIMEOUT_MS = 300000, MAX_DIFF = 60000, MAX_NOTES = 5, asProvider = (v) => {
|
|
11983
13247
|
const s = v?.trim().toLowerCase();
|
|
11984
13248
|
return s === "codex" || s === "claude" ? s : null;
|
|
11985
13249
|
}, 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
13250
|
var init_xreview = __esm(() => {
|
|
13251
|
+
init_role_agents();
|
|
11987
13252
|
init_shell();
|
|
11988
13253
|
init_safe_git();
|
|
11989
|
-
|
|
11990
|
-
});
|
|
11991
|
-
|
|
11992
|
-
// ../../loki-ts/src/runner/router/route_record.ts
|
|
11993
|
-
import { existsSync as existsSync28, lstatSync as lstatSync11, mkdirSync as mkdirSync15, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "fs";
|
|
11994
|
-
import { join as join48 } from "path";
|
|
11995
|
-
function loadRouteRecord(runDir) {
|
|
11996
|
-
try {
|
|
11997
|
-
const p = join48(runDir, ROUTE_RECORD_FILE);
|
|
11998
|
-
if (!existsSync28(p) || !lstatSync11(p).isFile())
|
|
11999
|
-
return;
|
|
12000
|
-
const v = JSON.parse(readFileSync34(p, "utf8"));
|
|
12001
|
-
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
12002
|
-
} catch {
|
|
12003
|
-
return;
|
|
12004
|
-
}
|
|
12005
|
-
}
|
|
12006
|
-
var ROUTE_RECORD_FILE = "route.json";
|
|
12007
|
-
var init_route_record = () => {};
|
|
12008
|
-
|
|
12009
|
-
// ../../loki-ts/src/runner/router/history_store.ts
|
|
12010
|
-
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync35, renameSync as renameSync6, rmSync as rmSync12, writeFileSync as writeFileSync17 } from "fs";
|
|
12011
|
-
import { resolve as resolve11 } from "path";
|
|
12012
|
-
function validRun(v) {
|
|
12013
|
-
if (typeof v !== "object" || v === null)
|
|
12014
|
-
return false;
|
|
12015
|
-
const r = v;
|
|
12016
|
-
return typeof r["shape"] === "string" && typeof r["executor"] === "string" && EXECUTORS.includes(r["executor"]) && typeof r["verdict"] === "string" && VERDICTS.includes(r["verdict"]) && (r["owner"] === null || typeof r["owner"] === "string" && OWNERS.includes(r["owner"])) && typeof r["escalated"] === "boolean" && typeof r["usd"] === "number" && Number.isFinite(r["usd"]) && typeof r["wallS"] === "number" && Number.isFinite(r["wallS"]);
|
|
12017
|
-
}
|
|
12018
|
-
function historyPath(key, cacheRoot) {
|
|
12019
|
-
return resolve11(repoCacheDir(key, cacheRoot), HISTORY_FILE);
|
|
12020
|
-
}
|
|
12021
|
-
function readRunHistory(key, cacheRoot) {
|
|
12022
|
-
const path = historyPath(key, cacheRoot);
|
|
12023
|
-
if (!existsSync29(path))
|
|
12024
|
-
return [];
|
|
12025
|
-
try {
|
|
12026
|
-
const parsed = JSON.parse(readFileSync35(path, "utf8"));
|
|
12027
|
-
const runs2 = parsed?.runs;
|
|
12028
|
-
return Array.isArray(runs2) ? runs2.filter(validRun) : [];
|
|
12029
|
-
} catch {
|
|
12030
|
-
return [];
|
|
12031
|
-
}
|
|
12032
|
-
}
|
|
12033
|
-
function appendRunOutcome(key, outcome, cacheRoot) {
|
|
12034
|
-
try {
|
|
12035
|
-
const runs2 = [...readRunHistory(key, cacheRoot), outcome].slice(-HISTORY_CAP);
|
|
12036
|
-
mkdirSync16(repoCacheDir(key, cacheRoot), { recursive: true });
|
|
12037
|
-
const target = historyPath(key, cacheRoot);
|
|
12038
|
-
const tmp = `${target}.${process.pid}.tmp`;
|
|
12039
|
-
try {
|
|
12040
|
-
writeFileSync17(tmp, JSON.stringify({ runs: runs2 }));
|
|
12041
|
-
renameSync6(tmp, target);
|
|
12042
|
-
} catch (e) {
|
|
12043
|
-
rmSync12(tmp, { force: true, recursive: false });
|
|
12044
|
-
throw e;
|
|
12045
|
-
}
|
|
12046
|
-
return true;
|
|
12047
|
-
} catch {
|
|
12048
|
-
return false;
|
|
12049
|
-
}
|
|
12050
|
-
}
|
|
12051
|
-
var HISTORY_FILE = "router_history.json", HISTORY_CAP = 200, WALL_KIND = "total-v1", EXECUTORS, VERDICTS, OWNERS;
|
|
12052
|
-
var init_history_store = __esm(() => {
|
|
12053
|
-
init_cache();
|
|
12054
|
-
EXECUTORS = ["haiku", "sonnet"];
|
|
12055
|
-
VERDICTS = ["pass", "fail", "error", "not_proven"];
|
|
12056
|
-
OWNERS = ["code", "harness", "env", "provider"];
|
|
12057
|
-
});
|
|
12058
|
-
|
|
12059
|
-
// ../../loki-ts/src/runner/router/history.ts
|
|
12060
|
-
import { dirname as dirname21, join as join49, resolve as resolve12 } from "path";
|
|
12061
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12062
|
-
function shapeKey(model) {
|
|
12063
|
-
if (!model || model.status !== "ok")
|
|
12064
|
-
return null;
|
|
12065
|
-
const runners = new Set(model.packages.map((p) => (p.runner ?? "").trim().toLowerCase() || "none"));
|
|
12066
|
-
return `${model.workspaceKind}:${[...runners].sort().join("+")}`;
|
|
12067
|
-
}
|
|
12068
|
-
function shapeKeyForRepo(repoDir) {
|
|
12069
|
-
try {
|
|
12070
|
-
const dirs = shallowDirs(repoDir);
|
|
12071
|
-
const committed = loadCommitted(repoDir);
|
|
12072
|
-
if (committed)
|
|
12073
|
-
return shapeKey({ ...committed.model, key: computeKey(repoDir, committed.model.fingerprintFiles, dirs, committed.hash) });
|
|
12074
|
-
const cached = loadCached(repoDir);
|
|
12075
|
-
if (!cached || computeKey(repoDir, cached.fingerprintFiles, dirs) !== cached.key)
|
|
12076
|
-
return null;
|
|
12077
|
-
return shapeKey(cached);
|
|
12078
|
-
} catch {
|
|
12079
|
-
return null;
|
|
12080
|
-
}
|
|
12081
|
-
}
|
|
12082
|
-
var SHAPE_DEFAULTS_FILE;
|
|
12083
|
-
var init_history = __esm(() => {
|
|
12084
|
-
init_schema();
|
|
12085
|
-
init_history_store();
|
|
12086
|
-
init_discover2();
|
|
12087
|
-
init_gather();
|
|
12088
|
-
init_history_store();
|
|
12089
|
-
SHAPE_DEFAULTS_FILE = resolve12(dirname21(fileURLToPath3(import.meta.url)), "..", "..", "..", "data", "router-shape-defaults.json");
|
|
13254
|
+
RANK3 = { FAILED: 0, SPEC_CONFLICT: 0, PARTIAL: 1, ALREADY_SATISFIED: 2, VERIFIED: 2 };
|
|
12090
13255
|
});
|
|
12091
13256
|
|
|
12092
13257
|
// ../../loki-ts/src/util/engine_origin.ts
|
|
@@ -12135,9 +13300,9 @@ var init_cost_preview = __esm(() => {
|
|
|
12135
13300
|
});
|
|
12136
13301
|
|
|
12137
13302
|
// ../../loki-ts/src/features/contract.ts
|
|
12138
|
-
import { existsSync as
|
|
12139
|
-
import { dirname as
|
|
12140
|
-
import { createHash as
|
|
13303
|
+
import { existsSync as existsSync35, lstatSync as lstatSync12, mkdirSync as mkdirSync22, readFileSync as readFileSync42, rmSync as rmSync14, writeFileSync as writeFileSync23 } from "fs";
|
|
13304
|
+
import { dirname as dirname25, join as join57 } from "path";
|
|
13305
|
+
import { createHash as createHash13 } from "crypto";
|
|
12141
13306
|
function contractEnabled(env = process.env) {
|
|
12142
13307
|
return env["LOKI_CONTRACT"] !== "0";
|
|
12143
13308
|
}
|
|
@@ -12168,7 +13333,7 @@ function untracedLines(trace) {
|
|
|
12168
13333
|
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
13334
|
}
|
|
12170
13335
|
function contractPath(repoDir) {
|
|
12171
|
-
return
|
|
13336
|
+
return join57(repoDir, ".loki", "contract.json");
|
|
12172
13337
|
}
|
|
12173
13338
|
function readContract(repoDir) {
|
|
12174
13339
|
const p = contractPath(repoDir);
|
|
@@ -12184,11 +13349,11 @@ function readContract(repoDir) {
|
|
|
12184
13349
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
12185
13350
|
let raw2;
|
|
12186
13351
|
try {
|
|
12187
|
-
raw2 =
|
|
13352
|
+
raw2 = readFileSync42(p);
|
|
12188
13353
|
} catch {
|
|
12189
13354
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
12190
13355
|
}
|
|
12191
|
-
const sha2563 =
|
|
13356
|
+
const sha2563 = createHash13("sha256").update(raw2).digest("hex");
|
|
12192
13357
|
let j;
|
|
12193
13358
|
try {
|
|
12194
13359
|
j = JSON.parse(raw2.toString("utf8"));
|
|
@@ -12245,16 +13410,21 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
12245
13410
|
return [`contract trace failed: ${sanitizeCriterion(e instanceof Error ? e.message : String(e))}`];
|
|
12246
13411
|
}
|
|
12247
13412
|
}
|
|
12248
|
-
var MAX_CRITERIA = 50, MAX_TEXT = 500, STOP2, MAX_CONTRACT_BYTES;
|
|
13413
|
+
var MAX_CRITERIA = 50, MAX_TEXT = 500, STOP2, MAX_CONTRACT_BYTES, REQ_MAP_REL, REQ_STOP;
|
|
12249
13414
|
var init_contract = __esm(() => {
|
|
13415
|
+
init_atomic();
|
|
13416
|
+
init_spec_file();
|
|
13417
|
+
init_paths();
|
|
12250
13418
|
init_safe_git();
|
|
12251
13419
|
STOP2 = new Set(["the", "and", "for", "with", "that", "this", "must", "should", "shall", "when", "then", "are", "was", "all", "any", "can", "not", "has", "have", "from", "into", "each", "user", "users"]);
|
|
12252
13420
|
MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
13421
|
+
REQ_MAP_REL = join57(".loki", "state", "requirements-map.json");
|
|
13422
|
+
REQ_STOP = new Set(["with", "that", "this", "from", "have", "will", "should", "must", "when", "then", "into", "each", "every", "user", "users", "page", "able", "allow", "support", "ensure", "using", "their", "there", "which", "shall"]);
|
|
12253
13423
|
});
|
|
12254
13424
|
|
|
12255
13425
|
// ../../loki-ts/src/supply/supply_guard.ts
|
|
12256
|
-
import { existsSync as
|
|
12257
|
-
import { basename as
|
|
13426
|
+
import { existsSync as existsSync36, readFileSync as readFileSync43, statSync as statSync18 } from "fs";
|
|
13427
|
+
import { basename as basename19, join as join58 } from "path";
|
|
12258
13428
|
function supplyEnabled(env) {
|
|
12259
13429
|
return env["LOKI_SUPPLY_GUARD"] !== "0";
|
|
12260
13430
|
}
|
|
@@ -12266,16 +13436,16 @@ function supplyVerdict(verdict, r) {
|
|
|
12266
13436
|
return r.blocked && verdict === "VERIFIED" ? "FAILED" : verdict;
|
|
12267
13437
|
}
|
|
12268
13438
|
function isManifestPath(path) {
|
|
12269
|
-
return MANIFESTS.test(
|
|
13439
|
+
return MANIFESTS.test(basename19(path));
|
|
12270
13440
|
}
|
|
12271
13441
|
function readDeclared(repoDir) {
|
|
12272
|
-
const p =
|
|
12273
|
-
if (!
|
|
13442
|
+
const p = join58(repoDir, ".loki", DECLARED_FILE);
|
|
13443
|
+
if (!existsSync36(p))
|
|
12274
13444
|
return { deps: [], problem: null };
|
|
12275
13445
|
try {
|
|
12276
|
-
if (
|
|
13446
|
+
if (statSync18(p).size > MAX_FILE_BYTES)
|
|
12277
13447
|
return { deps: [], problem: "declaration file too large" };
|
|
12278
|
-
const j = JSON.parse(
|
|
13448
|
+
const j = JSON.parse(readFileSync43(p, "utf8"));
|
|
12279
13449
|
if (!Array.isArray(j))
|
|
12280
13450
|
return { deps: [], problem: "declaration is not a JSON array" };
|
|
12281
13451
|
const deps = [];
|
|
@@ -12295,11 +13465,11 @@ function readDeclared(repoDir) {
|
|
|
12295
13465
|
}
|
|
12296
13466
|
}
|
|
12297
13467
|
function readAllowlist(repoDir) {
|
|
12298
|
-
const p =
|
|
12299
|
-
if (!
|
|
13468
|
+
const p = join58(repoDir, ".loki", "supply-allowlist");
|
|
13469
|
+
if (!existsSync36(p))
|
|
12300
13470
|
return new Set;
|
|
12301
13471
|
try {
|
|
12302
|
-
return new Set(
|
|
13472
|
+
return new Set(readFileSync43(p, "utf8").split(`
|
|
12303
13473
|
`).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
12304
13474
|
} catch {
|
|
12305
13475
|
return new Set;
|
|
@@ -12340,7 +13510,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12340
13510
|
if (eco === "npm") {
|
|
12341
13511
|
if (privateUrlList([env["npm_config_registry"] ?? "", env["NPM_CONFIG_REGISTRY"] ?? ""]))
|
|
12342
13512
|
return true;
|
|
12343
|
-
return files(
|
|
13513
|
+
return files(join58(repoDir, ".npmrc"), home ? join58(home, ".npmrc") : "").some((t) => t.split(`
|
|
12344
13514
|
`).some((l) => {
|
|
12345
13515
|
const m = /^\s*(?:(@[^:\s]+):)?registry\s*=\s*(\S+)/.exec(l);
|
|
12346
13516
|
return !!m && privateUrl(m[2]) && (m[1] === undefined || name.startsWith(`${m[1]}/`));
|
|
@@ -12349,7 +13519,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12349
13519
|
if (eco === "pypi") {
|
|
12350
13520
|
if (privateUrlList([env["PIP_INDEX_URL"] ?? "", env["PIP_EXTRA_INDEX_URL"] ?? ""]))
|
|
12351
13521
|
return true;
|
|
12352
|
-
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ?
|
|
13522
|
+
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join58(home, ".config", "pip", "pip.conf") : "", home ? join58(home, ".pip", "pip.conf") : "", join58(repoDir, "pip.conf")];
|
|
12353
13523
|
return files(...cfg).some((t) => t.split(`
|
|
12354
13524
|
`).some((l) => {
|
|
12355
13525
|
const m = /^\s*(?:extra-)?index-url\s*[=:]\s*(.+)$/.exec(l);
|
|
@@ -12359,7 +13529,7 @@ function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
|
12359
13529
|
if (eco === "cargo") {
|
|
12360
13530
|
if (Object.keys(env).some((k) => /^CARGO_REGISTRIES_.+_INDEX$/.test(k) || /^CARGO_SOURCE_/.test(k)))
|
|
12361
13531
|
return true;
|
|
12362
|
-
return files(
|
|
13532
|
+
return files(join58(repoDir, ".cargo", "config.toml"), join58(repoDir, ".cargo", "config"), home ? join58(home, ".cargo", "config.toml") : "", home ? join58(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
|
|
12363
13533
|
}
|
|
12364
13534
|
if (eco === "go") {
|
|
12365
13535
|
const gp = goEnv["GOPROXY"] ?? env["GOPROXY"] ?? "";
|
|
@@ -12482,7 +13652,7 @@ var MIN_AGE_DAYS = 7, DECLARED_FILE = "supply-declared.json", MAX_CHECKED = 50,
|
|
|
12482
13652
|
}
|
|
12483
13653
|
}, 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
13654
|
try {
|
|
12485
|
-
return
|
|
13655
|
+
return statSync18(p).size <= MAX_FILE_BYTES ? readFileSync43(p, "utf8") : "";
|
|
12486
13656
|
} catch {
|
|
12487
13657
|
return "";
|
|
12488
13658
|
}
|
|
@@ -12497,77 +13667,18 @@ var init_supply_guard = __esm(() => {
|
|
|
12497
13667
|
});
|
|
12498
13668
|
|
|
12499
13669
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
12500
|
-
import { createHash as
|
|
12501
|
-
import {
|
|
12502
|
-
import {
|
|
12503
|
-
import { basename as basename18, dirname as dirname23, isAbsolute as isAbsolute8, join as join52, relative as relative9 } from "path";
|
|
13670
|
+
import { createHash as createHash14 } from "crypto";
|
|
13671
|
+
import { existsSync as existsSync37, readdirSync as readdirSync18, mkdirSync as mkdirSync23, readFileSync as readFileSync44, writeFileSync as writeFileSync24 } from "fs";
|
|
13672
|
+
import { basename as basename20, isAbsolute as isAbsolute9, join as join59, relative as relative9 } from "path";
|
|
12504
13673
|
function sanitizeReason(s) {
|
|
12505
13674
|
const collapsed = s.replace(/[\x00-\x1f\x7f]+/g, " ").trim();
|
|
12506
13675
|
const capped = collapsed.length > 500 ? `${collapsed.slice(0, 500)}...` : collapsed;
|
|
12507
13676
|
return capped.replace(/`/g, "'");
|
|
12508
13677
|
}
|
|
12509
|
-
function canonicalJson(x) {
|
|
12510
|
-
const walk = (v) => {
|
|
12511
|
-
if (Array.isArray(v))
|
|
12512
|
-
return `[${v.map(walk).join(",")}]`;
|
|
12513
|
-
if (v !== null && typeof v === "object") {
|
|
12514
|
-
const o = v;
|
|
12515
|
-
return `{${Object.keys(o).filter((k) => o[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${walk(o[k])}`).join(",")}}`;
|
|
12516
|
-
}
|
|
12517
|
-
return JSON.stringify(v ?? null);
|
|
12518
|
-
};
|
|
12519
|
-
return walk(x).replace(/[\u0080-\uFFFF]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
12520
|
-
}
|
|
12521
13678
|
function receiptSha256(r) {
|
|
12522
13679
|
const { verification: _v, receipt_sha256: _h, ...body } = r;
|
|
12523
13680
|
return sha2562(canonicalJson(body));
|
|
12524
13681
|
}
|
|
12525
|
-
function loadSigningKey(generate = true) {
|
|
12526
|
-
try {
|
|
12527
|
-
const inline = process.env["LOKI_RECEIPT_SIGNING_KEY"]?.trim();
|
|
12528
|
-
let pem = inline ?? "";
|
|
12529
|
-
if (!inline) {
|
|
12530
|
-
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
12531
|
-
const file = given || join52(process.env["HOME"] || homedir8(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
12532
|
-
try {
|
|
12533
|
-
pem = readFileSync38(file);
|
|
12534
|
-
if (!given) {
|
|
12535
|
-
for (const [f, m] of [[file, 384], [dirname23(file), 448]])
|
|
12536
|
-
if (statSync17(f).mode & 63)
|
|
12537
|
-
chmodSync5(f, m);
|
|
12538
|
-
}
|
|
12539
|
-
} catch (e) {
|
|
12540
|
-
if (e.code !== "ENOENT" || !generate)
|
|
12541
|
-
return null;
|
|
12542
|
-
mkdirSync18(dirname23(dirname23(file)), { recursive: true, mode: 448 });
|
|
12543
|
-
mkdirSync18(dirname23(file), { recursive: true, mode: 448 });
|
|
12544
|
-
const tmp = `${file}.${randomBytes4(6).toString("hex")}.tmp`;
|
|
12545
|
-
writeFileSync19(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
|
|
12546
|
-
try {
|
|
12547
|
-
linkSync(tmp, file);
|
|
12548
|
-
} catch (l) {
|
|
12549
|
-
if (l.code !== "EEXIST")
|
|
12550
|
-
throw l;
|
|
12551
|
-
} finally {
|
|
12552
|
-
unlinkSync5(tmp);
|
|
12553
|
-
}
|
|
12554
|
-
pem = readFileSync38(file);
|
|
12555
|
-
}
|
|
12556
|
-
}
|
|
12557
|
-
const k = createPrivateKey(pem);
|
|
12558
|
-
return k.asymmetricKeyType === "ed25519" ? k : null;
|
|
12559
|
-
} catch {
|
|
12560
|
-
return null;
|
|
12561
|
-
}
|
|
12562
|
-
}
|
|
12563
|
-
function signReceipt(runId, hash) {
|
|
12564
|
-
const key = loadSigningKey();
|
|
12565
|
-
if (!key)
|
|
12566
|
-
return { jwt: null, kid: null };
|
|
12567
|
-
const kid = kidOf2(createPublicKey2(key));
|
|
12568
|
-
const input = `${b64u(canonicalJson({ alg: "EdDSA", typ: "JWT", kid }))}.${b64u(canonicalJson({ job_id: runId, run_id: runId, receipt_sha256: hash, iat: Math.floor(Date.now() / 1000) }))}`;
|
|
12569
|
-
return { jwt: `${input}.${b64u(sign(null, Buffer.from(input), key))}`, kid };
|
|
12570
|
-
}
|
|
12571
13682
|
async function git3(ctx, args, keep = {}) {
|
|
12572
13683
|
const r = await safeGitRun(ctx.repoDir, args, { timeoutMs: 20000, ...keep });
|
|
12573
13684
|
return { out: r.stdout, code: r.exitCode };
|
|
@@ -12632,9 +13743,9 @@ function renderReceiptMd(r) {
|
|
|
12632
13743
|
].join(`
|
|
12633
13744
|
`);
|
|
12634
13745
|
}
|
|
12635
|
-
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) =>
|
|
12636
|
-
const r = relative9(ctx.repoDir,
|
|
12637
|
-
return r.startsWith("..") ||
|
|
13746
|
+
var DEEP_NOT_PROVEN, SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)", EXCLUDE_LOKI = ":(exclude).loki", sha2562 = (s) => createHash14("sha256").update(s).digest("hex"), str3 = (v) => typeof v === "string" && v !== "" ? v : null, strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [], receiptRel = (ctx) => {
|
|
13747
|
+
const r = relative9(ctx.repoDir, join59(ctx.runDir, "receipt.json"));
|
|
13748
|
+
return r.startsWith("..") || isAbsolute9(r) ? `.loki/runs/${ctx.runId}/receipt.json` : r;
|
|
12638
13749
|
}, commitStage, sealStage;
|
|
12639
13750
|
var init_seal = __esm(() => {
|
|
12640
13751
|
init_spec_file();
|
|
@@ -12645,6 +13756,7 @@ var init_seal = __esm(() => {
|
|
|
12645
13756
|
init_discard();
|
|
12646
13757
|
init_commit_filter();
|
|
12647
13758
|
init_scope2();
|
|
13759
|
+
init_receipt_signer();
|
|
12648
13760
|
init_pr_lessons();
|
|
12649
13761
|
init_visual_evidence();
|
|
12650
13762
|
init_testmap();
|
|
@@ -12735,8 +13847,8 @@ var init_seal = __esm(() => {
|
|
|
12735
13847
|
notProven.add(`Wall test not executed: ${wallNotRun} file(s), no reason recorded`);
|
|
12736
13848
|
if (wallNotRun > 0) {
|
|
12737
13849
|
try {
|
|
12738
|
-
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) =>
|
|
12739
|
-
for (const n2 of
|
|
13850
|
+
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename20(String(f.path))));
|
|
13851
|
+
for (const n2 of readdirSync18(join59(ctx.runDir, "wall")).sort())
|
|
12740
13852
|
if (!kept.has(n2))
|
|
12741
13853
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
12742
13854
|
} catch {}
|
|
@@ -12775,6 +13887,8 @@ var init_seal = __esm(() => {
|
|
|
12775
13887
|
notProven.add(n2);
|
|
12776
13888
|
for (const id of strs2(o.verify?.pre_red))
|
|
12777
13889
|
notProven.add(`pre red: ${id}`);
|
|
13890
|
+
if (typeof o.verify?.baseline_line === "string" && strs2(o.verify?.pre_red).length)
|
|
13891
|
+
notProven.add(o.verify.baseline_line);
|
|
12778
13892
|
for (const t of strs2(o.implement?.tests_reverted))
|
|
12779
13893
|
notProven.add(`reverted test edit: ${t}`);
|
|
12780
13894
|
if (ctx.provider !== "claude")
|
|
@@ -12795,19 +13909,19 @@ var init_seal = __esm(() => {
|
|
|
12795
13909
|
for (const [s, d] of Object.entries(o))
|
|
12796
13910
|
if (typeof d?.duration_s === "number")
|
|
12797
13911
|
stages[s] = d.duration_s;
|
|
12798
|
-
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(
|
|
13912
|
+
const T = hooks.time, time = T ? T.build(ctx, stages, T.firstEventMs(join59(ctx.runDir, "events.jsonl"))) : { wall_s: Object.values(stages).reduce((a, b) => a + (b ?? 0), 0), stages }, totalS = T?.reconciled(time) ?? null;
|
|
12799
13913
|
const iterIds = Object.values(o).flatMap((d) => [...strs2(d?.iteration_ids), ...strs2([d?.iteration_id])]);
|
|
12800
13914
|
if (iterIds.length === 0)
|
|
12801
13915
|
notProven.add("cost not measured (no iteration ids recorded)");
|
|
12802
13916
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
12803
13917
|
if (cost.unmetered)
|
|
12804
13918
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
12805
|
-
const eventsPath =
|
|
13919
|
+
const eventsPath = join59(ctx.runDir, "events.jsonl");
|
|
12806
13920
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
12807
13921
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
12808
13922
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
12809
13923
|
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(
|
|
13924
|
+
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(join59(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
|
|
12811
13925
|
if (routeBlock)
|
|
12812
13926
|
for (const l of routeNotProven(routeBlock))
|
|
12813
13927
|
notProven.add(l);
|
|
@@ -12849,7 +13963,7 @@ var init_seal = __esm(() => {
|
|
|
12849
13963
|
provider: ctx.provider,
|
|
12850
13964
|
model: ctx.model,
|
|
12851
13965
|
resumed: o.intake?.resumed === true,
|
|
12852
|
-
events_sha256: sha2562(
|
|
13966
|
+
events_sha256: sha2562(existsSync37(eventsPath) ? readFileSync44(eventsPath) : ""),
|
|
12853
13967
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
12854
13968
|
log_seal: true,
|
|
12855
13969
|
...receiptBlock(process.env, cost.usd, cost.unmetered === true, totalS),
|
|
@@ -12869,11 +13983,11 @@ var init_seal = __esm(() => {
|
|
|
12869
13983
|
hash = receiptSha256(body);
|
|
12870
13984
|
}
|
|
12871
13985
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
12872
|
-
|
|
12873
|
-
const path =
|
|
12874
|
-
|
|
13986
|
+
mkdirSync23(ctx.runDir, { recursive: true });
|
|
13987
|
+
const path = join59(ctx.runDir, "receipt.json");
|
|
13988
|
+
writeFileSync24(path, JSON.stringify(receipt, null, 2) + `
|
|
12875
13989
|
`);
|
|
12876
|
-
|
|
13990
|
+
writeFileSync24(join59(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
12877
13991
|
recordRun(process.env, ctx.repoDir, ctx.model, verdict, cost.usd, cost.unmetered === true, totalS);
|
|
12878
13992
|
const signed = sig.jwt !== null;
|
|
12879
13993
|
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 +14002,7 @@ var init_seal = __esm(() => {
|
|
|
12888
14002
|
|
|
12889
14003
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
12890
14004
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
12891
|
-
import { readFileSync as
|
|
14005
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
12892
14006
|
function pae(payloadType, body) {
|
|
12893
14007
|
const t = Buffer.from(payloadType, "utf8");
|
|
12894
14008
|
return Buffer.concat([Buffer.from(`DSSEv1 ${t.length} `), t, Buffer.from(` ${body.length} `), body]);
|
|
@@ -12965,7 +14079,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
12965
14079
|
}
|
|
12966
14080
|
function runIdGuard(receiptPath, runId) {
|
|
12967
14081
|
try {
|
|
12968
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
14082
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync45(receiptPath, "utf8")), runId);
|
|
12969
14083
|
} catch {
|
|
12970
14084
|
return null;
|
|
12971
14085
|
}
|
|
@@ -12979,16 +14093,16 @@ var init_keys_cmd = __esm(() => {
|
|
|
12979
14093
|
});
|
|
12980
14094
|
|
|
12981
14095
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
12982
|
-
import { existsSync as
|
|
12983
|
-
import { dirname as
|
|
12984
|
-
import { createHash as
|
|
14096
|
+
import { existsSync as existsSync38, readdirSync as readdirSync19, readFileSync as readFileSync46, statSync as statSync20 } from "fs";
|
|
14097
|
+
import { dirname as dirname27, join as join60 } from "path";
|
|
14098
|
+
import { createHash as createHash15, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
12985
14099
|
function pubFor(kid, given) {
|
|
12986
14100
|
if (given)
|
|
12987
14101
|
return kidOf2(given) === kid ? given : undefined;
|
|
12988
14102
|
const active = loadSigningKey(false);
|
|
12989
14103
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
12990
14104
|
try {
|
|
12991
|
-
return [createPublicKey4(
|
|
14105
|
+
return [createPublicKey4(readFileSync46(f))];
|
|
12992
14106
|
} catch {
|
|
12993
14107
|
return [];
|
|
12994
14108
|
}
|
|
@@ -13018,18 +14132,18 @@ function checkAttestation(jwt, expectedHash, given) {
|
|
|
13018
14132
|
}
|
|
13019
14133
|
function checkEventLog(receiptPath, receipt) {
|
|
13020
14134
|
const bound = receipt["events_sha256"];
|
|
13021
|
-
if (typeof bound !== "string" || bound ===
|
|
14135
|
+
if (typeof bound !== "string" || bound === createHash15("sha256").digest("hex"))
|
|
13022
14136
|
return null;
|
|
13023
|
-
const path =
|
|
13024
|
-
if (!
|
|
14137
|
+
const path = join60(dirname27(receiptPath), "events.jsonl");
|
|
14138
|
+
if (!existsSync38(path))
|
|
13025
14139
|
return "events.jsonl is missing";
|
|
13026
|
-
const raw2 =
|
|
14140
|
+
const raw2 = readFileSync46(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
13027
14141
|
`).filter((l) => l.trim() !== "");
|
|
13028
14142
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
13029
14143
|
return "events.jsonl has a forged, edited or missing line";
|
|
13030
14144
|
if (events2.some((e) => e.type === "tamper.detected"))
|
|
13031
14145
|
return "the supervisor detected events.jsonl being modified during the run";
|
|
13032
|
-
const h =
|
|
14146
|
+
const h = createHash15("sha256");
|
|
13033
14147
|
for (const l of lines) {
|
|
13034
14148
|
if (h.copy().digest("hex") === bound)
|
|
13035
14149
|
return null;
|
|
@@ -13039,7 +14153,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
13039
14153
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
13040
14154
|
}
|
|
13041
14155
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
13042
|
-
const path =
|
|
14156
|
+
const path = join60(dirname27(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
13043
14157
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
13044
14158
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
13045
14159
|
if (done < 0)
|
|
@@ -13047,8 +14161,8 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13047
14161
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
13048
14162
|
if (!d)
|
|
13049
14163
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
13050
|
-
const lines =
|
|
13051
|
-
`).filter((l) => l.trim() !== ""), sha3 =
|
|
14164
|
+
const lines = readFileSync46(path, "utf8").split(`
|
|
14165
|
+
`).filter((l) => l.trim() !== ""), sha3 = createHash15("sha256").update(lines.slice(0, done + 1).join(`
|
|
13052
14166
|
`) + `
|
|
13053
14167
|
`).digest("hex");
|
|
13054
14168
|
const pub = d["kid"] === kid ? pubFor(kid, given) : undefined;
|
|
@@ -13057,11 +14171,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
13057
14171
|
return null;
|
|
13058
14172
|
}
|
|
13059
14173
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
13060
|
-
if (!
|
|
14174
|
+
if (!existsSync38(receiptPath))
|
|
13061
14175
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
13062
14176
|
let receipt;
|
|
13063
14177
|
try {
|
|
13064
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
14178
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync46(p, "utf8")))(receiptPath));
|
|
13065
14179
|
} catch {
|
|
13066
14180
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
13067
14181
|
}
|
|
@@ -13116,8 +14230,8 @@ var init_verify_cmd = __esm(() => {
|
|
|
13116
14230
|
});
|
|
13117
14231
|
|
|
13118
14232
|
// src/server/app.ts
|
|
13119
|
-
import { existsSync as
|
|
13120
|
-
import { join as
|
|
14233
|
+
import { existsSync as existsSync40, readFileSync as readFileSync49, statSync as statSync21 } from "fs";
|
|
14234
|
+
import { join as join63, resolve as resolve15, sep as sep14 } from "path";
|
|
13121
14235
|
|
|
13122
14236
|
// node_modules/hono/dist/request/constants.js
|
|
13123
14237
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -23364,9 +24478,9 @@ function mount20(ctx) {
|
|
|
23364
24478
|
}
|
|
23365
24479
|
|
|
23366
24480
|
// src/server/routes/verify.ts
|
|
23367
|
-
import { readFileSync as
|
|
24481
|
+
import { readFileSync as readFileSync47, realpathSync as realpathSync20 } from "fs";
|
|
23368
24482
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
23369
|
-
import { join as
|
|
24483
|
+
import { join as join61, sep as sep13 } from "path";
|
|
23370
24484
|
init_verify_cmd();
|
|
23371
24485
|
init_seal();
|
|
23372
24486
|
init_receipt_dsse();
|
|
@@ -23393,11 +24507,11 @@ function mount21(ctx) {
|
|
|
23393
24507
|
return done(404, "receipt not available", "not_found");
|
|
23394
24508
|
let runDir, file;
|
|
23395
24509
|
try {
|
|
23396
|
-
const root = realpathSync20(
|
|
23397
|
-
runDir = realpathSync20(
|
|
23398
|
-
if (runDir !==
|
|
24510
|
+
const root = realpathSync20(join61(repo.p, ".loki", "runs"));
|
|
24511
|
+
runDir = realpathSync20(join61(root, run2));
|
|
24512
|
+
if (runDir !== join61(root, run2))
|
|
23399
24513
|
return done(404, "receipt not found", "not_found");
|
|
23400
|
-
file = realpathSync20(
|
|
24514
|
+
file = realpathSync20(join61(runDir, "receipt.json"));
|
|
23401
24515
|
} catch {
|
|
23402
24516
|
return done(404, "receipt not found", "not_found");
|
|
23403
24517
|
}
|
|
@@ -23416,7 +24530,7 @@ function mount21(ctx) {
|
|
|
23416
24530
|
sha3 = r.receiptSha256 ?? null;
|
|
23417
24531
|
}
|
|
23418
24532
|
try {
|
|
23419
|
-
outcome = outcomeOf(JSON.parse(
|
|
24533
|
+
outcome = outcomeOf(JSON.parse(readFileSync47(file, "utf8")));
|
|
23420
24534
|
} catch {
|
|
23421
24535
|
outcome = "UNREADABLE";
|
|
23422
24536
|
}
|
|
@@ -23478,8 +24592,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
|
|
|
23478
24592
|
}
|
|
23479
24593
|
|
|
23480
24594
|
// src/server/legacy/shim.ts
|
|
23481
|
-
import { existsSync as
|
|
23482
|
-
import { join as
|
|
24595
|
+
import { existsSync as existsSync39, readFileSync as readFileSync48 } from "fs";
|
|
24596
|
+
import { join as join62 } from "path";
|
|
23483
24597
|
|
|
23484
24598
|
// src/server/legacy/routes.ts
|
|
23485
24599
|
var LEGACY_ROUTES = [
|
|
@@ -23851,10 +24965,10 @@ var logged = new Set;
|
|
|
23851
24965
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
23852
24966
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
23853
24967
|
var repoVersion = () => {
|
|
23854
|
-
for (const f of [
|
|
24968
|
+
for (const f of [join62(import.meta.dir, "../../../../../VERSION")]) {
|
|
23855
24969
|
try {
|
|
23856
|
-
if (
|
|
23857
|
-
return
|
|
24970
|
+
if (existsSync39(f))
|
|
24971
|
+
return readFileSync48(f, "utf8").trim();
|
|
23858
24972
|
} catch {}
|
|
23859
24973
|
}
|
|
23860
24974
|
return "unknown";
|
|
@@ -24036,7 +25150,7 @@ function legacyShim(opts) {
|
|
|
24036
25150
|
|
|
24037
25151
|
// src/server/app.ts
|
|
24038
25152
|
var MAX_BODY2 = 1e6;
|
|
24039
|
-
var defaultUiDir = () => [
|
|
25153
|
+
var defaultUiDir = () => [join63(import.meta.dir, "../../ui/dist"), join63(import.meta.dir, "../ui/dist")].find((d) => existsSync40(join63(d, "index.html")));
|
|
24040
25154
|
function createApp(opts) {
|
|
24041
25155
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
24042
25156
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
@@ -24065,10 +25179,10 @@ function createApp(opts) {
|
|
|
24065
25179
|
await next();
|
|
24066
25180
|
});
|
|
24067
25181
|
const readInstalled = () => {
|
|
24068
|
-
for (const f of [process.env.LOKI_VERSION_FILE,
|
|
25182
|
+
for (const f of [process.env.LOKI_VERSION_FILE, join63(import.meta.dir, "../../../../VERSION")]) {
|
|
24069
25183
|
try {
|
|
24070
25184
|
if (f)
|
|
24071
|
-
return
|
|
25185
|
+
return readFileSync49(f, "utf8").trim() || "unknown";
|
|
24072
25186
|
} catch {}
|
|
24073
25187
|
}
|
|
24074
25188
|
return "unknown";
|
|
@@ -24177,13 +25291,13 @@ function createApp(opts) {
|
|
|
24177
25291
|
}
|
|
24178
25292
|
if (!uiDir || p.startsWith("/v1/"))
|
|
24179
25293
|
return c.json({ error: "not found" }, 404);
|
|
24180
|
-
const root =
|
|
24181
|
-
const f =
|
|
24182
|
-
if (f.startsWith(root + sep14) &&
|
|
25294
|
+
const root = resolve15(uiDir);
|
|
25295
|
+
const f = resolve15(root, `.${p}`);
|
|
25296
|
+
if (f.startsWith(root + sep14) && existsSync40(f) && statSync21(f).isFile())
|
|
24183
25297
|
return new Response(Bun.file(f));
|
|
24184
25298
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
24185
25299
|
return c.json({ error: "not found" }, 404);
|
|
24186
|
-
return new Response(Bun.file(
|
|
25300
|
+
return new Response(Bun.file(join63(root, "index.html")));
|
|
24187
25301
|
});
|
|
24188
25302
|
return { app, db, close: () => {
|
|
24189
25303
|
clearInterval(reconciler);
|