loki-mode 11.3.0 → 11.3.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 +68 -650
- package/SKILL.md +4 -4
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +2 -2
- package/autonomy/context-tracker.py +4 -4
- package/autonomy/council-v2.sh +1 -1
- package/autonomy/grill.sh +1 -1
- package/autonomy/lib/done-recognition.sh +1 -1
- package/autonomy/lib/prd-enrich.sh +1 -1
- package/autonomy/lib/voter-agents.sh +1 -1
- package/autonomy/loki +99 -75
- package/autonomy/run.sh +30 -18
- package/autonomy/sandbox.sh +1 -1
- package/autonomy/tui.sh +6 -5
- package/autonomy/verify.sh +1 -1
- package/bin/loki +166 -59
- package/completions/_loki +2 -0
- package/completions/loki.bash +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +25 -31
- package/docs/ARCHITECTURE.md +337 -0
- package/docs/BRANDING.md +124 -0
- package/docs/CLI-REFERENCE.md +7 -1
- package/docs/COMPONENTS.md +447 -0
- package/docs/DEPLOYMENT.md +9 -9
- package/docs/DOCKER.md +469 -0
- package/docs/INSTALLATION.md +67 -1045
- package/docs/KNOWN-ISSUES.md +11 -35
- package/docs/TESTING.md +161 -0
- package/docs/air-gapped.md +1 -2
- package/docs/alternative-installations.md +14 -24
- package/docs/architecture/DECISIONS.md +333 -0
- package/docs/audit-logging.md +7 -7
- package/docs/authentication.md +3 -3
- package/docs/authorization.md +1 -1
- package/docs/certification/04-production-deployment/lab.md +6 -6
- package/docs/certification/04-production-deployment/lesson.md +4 -4
- package/docs/certification/README.md +1 -1
- package/docs/control-plane-container.md +2 -2
- package/docs/dev/real-run.md +34 -0
- package/docs/dev/release-checklist.md +2 -2
- package/docs/exit-codes.md +3 -3
- package/docs/git-workflow.md +3 -3
- package/docs/metrics.md +37 -55
- package/docs/network-security.md +1 -1
- package/docs/siem-integration.md +5 -5
- package/docs/v10/CLI-MODERN.md +4 -3
- package/docs/v10/DECISIONS.md +44 -0
- package/docs/v10/DEPS.md +22 -22
- package/docs/v10/ENGINE.md +1 -1
- package/docs/v10/FAILURE-CLASSES.md +118 -1
- package/docs/v10/FOUNDER-QUEUE.md +1 -0
- package/docs/v10/GUARDS.md +13 -0
- package/docs/v10/LEGACY-REMOVAL.md +4 -6
- package/docs/v10/METRICS.md +2 -2
- package/docs/v10/MODERNIZE.md +1 -1
- package/docs/v10/OPERATING-MODEL.md +2 -1
- package/docs/v10/PROGRESS.md +29 -0
- package/docs/v11/COST-HALF.md +352 -0
- package/docs/v11/MARKET-SLICES.md +315 -0
- package/docs/v11/REGISTRIES.md +730 -0
- package/loki-ts/data/model-pricing.json +52 -24
- package/loki-ts/dist/loki.js +1036 -957
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/packages/control-plane/dist/server.js +2467 -736
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/src/protocols/mcp-client.js +6 -2
- package/src/protocols/mcp-server.js +13 -3
- package/tools/audit-docs.py +1 -1
- package/web-app/dist/assets/{AdminPage-D_e45uTw.js → AdminPage-CMkHNJbF.js} +1 -1
- package/web-app/dist/assets/{Avatar-T3nIpoaf.js → Avatar-BJyjV7Zi.js} +1 -1
- package/web-app/dist/assets/{Badge-BKqb59pP.js → Badge-D572l0uq.js} +1 -1
- package/web-app/dist/assets/{Button-DjVlkh8_.js → Button-T4BnOZgl.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-DXJgLGmh.js → CockpitPage-Cj_GFsNn.js} +1 -1
- package/web-app/dist/assets/{ComparePage-BRP_eVc_.js → ComparePage-A8m4ZXak.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-CLjpGLjC.js → ErrorBoundary-hugCg5yg.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DX-vb4Xy.js → EvidenceReceiptPanel-CuJKDC2G.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-214HVHtX.js → GitHubIssuesPanel-BYGKjarC.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BEiZKc4t.js → GitHubPRsPanel-c7sFIJlf.js} +1 -1
- package/web-app/dist/assets/{HomePage-VvMHS5ga.js → HomePage-A1MIkG2P.js} +2 -2
- package/web-app/dist/assets/{LoginPage-5WD-v7n3.js → LoginPage-8KQMRhtR.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DUQT_Jye.js → MagicPage-C05QW2Lx.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-lga-z79v.js → MetricsPage-Oukv5hme.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CGhBX3G1.js → NotFoundPage-DKIwsawz.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-CdzF2GzZ.js → ProjectPage-DwFRPLCG.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DRG64N8s.js → ProjectsPage-CgY4YYDX.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-5fzW9Frb.js → SettingsPage-BpfcSjUA.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DrTdxO4y.js → ShowcasePage-BqBakC4K.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-C9Mqs6Rd.js → SystemSettingsPage-CZLu5VB0.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CUGPdSWo.js → TeamsPage-D55S1jKz.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CNraDY0F.js → TemplatesPage-D3SydvxN.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BIu_6Pea.js → TerminalOutput-hNg8G1-e.js} +1 -1
- package/web-app/dist/assets/{activity-Bk-kgqaf.js → activity-D2C0VagO.js} +1 -1
- package/web-app/dist/assets/{bell-BXmUdmo8.js → bell-CMvYi36w.js} +1 -1
- package/web-app/dist/assets/{bot-CiuzXxUR.js → bot-BvtQ6lmo.js} +1 -1
- package/web-app/dist/assets/{check-BEzjXppd.js → check-B13xkXed.js} +1 -1
- package/web-app/dist/assets/{chevron-left-AYWCXHf8.js → chevron-left-emu6r49E.js} +1 -1
- package/web-app/dist/assets/{circle-alert-BNg2uSRq.js → circle-alert-CExlwjuy.js} +1 -1
- package/web-app/dist/assets/{clock-DTj0kkGT.js → clock-D7Uas3oL.js} +1 -1
- package/web-app/dist/assets/{cloud-x4nTDBLT.js → cloud-CkB4S86C.js} +1 -1
- package/web-app/dist/assets/{code-xml-BVh_MLrk.js → code-xml-SL9IIWIn.js} +1 -1
- package/web-app/dist/assets/{database-DdfBaowG.js → database-BQFkd7t1.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-XuYFmAOY.js → dollar-sign-qHmnNYsU.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-D4cRnmeL.js → file-code-corner-C2YUDP0K.js} +1 -1
- package/web-app/dist/assets/{file-plus-CyoVlHc_.js → file-plus-C_ooIkDe.js} +1 -1
- package/web-app/dist/assets/{globe-Y6IK2RPW.js → globe-CQ-GL485.js} +1 -1
- package/web-app/dist/assets/{hammer-BKzr9VZI.js → hammer-U05L_D7V.js} +1 -1
- package/web-app/dist/assets/{index-B20cSx5G.js → index-CooHd7I-.js} +3 -3
- package/web-app/dist/assets/{layers-DebNOhGS.js → layers-Buo17rjW.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DyGbrEgd.js → loader-circle-15vK3oV1.js} +1 -1
- package/web-app/dist/assets/{lock-BKPpyt20.js → lock-BeXLebuJ.js} +1 -1
- package/web-app/dist/assets/{package-egF5Wi4y.js → package-MVh_6-6R.js} +1 -1
- package/web-app/dist/assets/{plus-OiEgKIc-.js → plus-Dh_nd9vS.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CzKvrONq.js → refresh-cw-C9cBJEAT.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Bu2mII0p.js → rotate-ccw-D3PAtzST.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CSkiyax8.js → scroll-text-DZCoXQjo.js} +1 -1
- package/web-app/dist/assets/{server-DlPthJFw.js → server-OCOLI5J_.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CbpHWQQX.js → shield-alert-oVhmf1_h.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DgGpZe-Q.js → thumbs-up-B-kbX4vN.js} +1 -1
- package/web-app/dist/assets/{trash-2-BV3GlCB-.js → trash-2-EL6moveW.js} +1 -1
- package/web-app/dist/assets/{trending-down-Y4TJtXhD.js → trending-down-Cl3kjpsx.js} +1 -1
- package/web-app/dist/assets/{trending-up-DsDNoHZY.js → trending-up-DST7I6M2.js} +1 -1
- package/web-app/dist/assets/{upload-gkThrc_I.js → upload-CUOT780G.js} +1 -1
- package/web-app/dist/assets/{usePolling-jpNSeNAe.js → usePolling-Djmt90eb.js} +1 -1
- package/web-app/dist/assets/{user-3Zez1WOK.js → user-BRBohA6J.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/docs/ARCHITECTURE-OVERVIEW.md +0 -209
- package/docs/AUTONOMI-ECOSYSTEM.md +0 -107
- package/docs/COCKPIT-SPEC.md +0 -72
- package/docs/COCKPIT-UX-MANDATE.md +0 -53
- package/docs/COMPETITIVE-ANALYSIS.md +0 -303
- package/docs/COMPETITIVE-NEXT-10.md +0 -316
- package/docs/COMPETITIVE-RALPH-ZEROSHOT-2026-07-08.md +0 -81
- package/docs/DASHBOARD-9.12-EVIDENCE.md +0 -97
- package/docs/DASHBOARD-ARCHITECTURE.md +0 -425
- package/docs/DASHBOARD_V2_CHECKLIST.md +0 -243
- package/docs/DEMOS.md +0 -63
- package/docs/ENTERPRISE-IDENTITY-ROADMAP.md +0 -206
- package/docs/EVIDENCE-RECEIPT-BUILD-APPLICABILITY.md +0 -79
- package/docs/FEAT-PRDREUSE-DOCKER-PLAN.md +0 -144
- package/docs/GATE-FAILURE-TRIAGE.md +0 -254
- package/docs/HANDOFF-2026-08-03.md +0 -439
- package/docs/LOOP-CANDIDATE-PROPOSAL-v1.md +0 -167
- package/docs/LOOP-HARNESS-AUDIT.md +0 -684
- package/docs/MERGE-DEDUP-MAP.md +0 -420
- package/docs/MERGE-ROUTE-MAP.md +0 -139
- package/docs/MODEL-REGISTRY-PLAN.md +0 -155
- package/docs/OUTCOME-FRONTIER.md +0 -536
- package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +0 -192
- package/docs/PLANS-INDEX.md +0 -32
- package/docs/PRD-REUSE-DONE-RECOGNITION-PLAN.md +0 -536
- package/docs/PROMPT-ABLATION-RESULT.md +0 -97
- package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +0 -127
- package/docs/R5-AUTO-WIKI-DESIGN.md +0 -137
- package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +0 -107
- package/docs/R7-ZERO-CONFIG-FIRST-RUN-PLAN.md +0 -137
- package/docs/R8-SHAREABLE-TEAM-ASSETS-PLAN.md +0 -129
- package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +0 -113
- package/docs/RARV-C-100X-PLAN.md +0 -286
- package/docs/RARV-C-LOOP-EFFICIENCY-PLAN.md +0 -470
- package/docs/SHAREABLE-PROOF-PLAN.md +0 -194
- package/docs/SONNET5-DEFAULT-PLAN.md +0 -157
- package/docs/SPEED-AND-FIRST-PASS-PLAN.md +0 -281
- package/docs/STALE-STATE-AUDIT.md +0 -174
- package/docs/SYNERGY-TASKS.md +0 -143
- package/docs/TASKLIST-ACCURACY-PLAN.md +0 -355
- package/docs/TOP-100-BACKLOG.md +0 -159
- package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +0 -114
- package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +0 -45
- package/docs/V8-MAJOR-RELEASE-PLAN.md +0 -137
- package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +0 -82
- package/docs/V8-SDK-RESEARCH-RAW.md +0 -129
- package/docs/WELCOME-OPENER-PLAN.md +0 -106
- package/docs/adoption-baseline-2026-07-31.md +0 -84
- package/docs/architecture/bmad-integration-epic.md +0 -271
- package/docs/architecture/bmad-integration-review.md +0 -86
- package/docs/architecture/bmad-integration-validation.md +0 -249
- package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +0 -61
- package/docs/auto-claude-comparison.md +0 -250
- package/docs/bug-fixes/agent-01-cli-fixes.md +0 -101
- package/docs/bug-fixes/agent-02-purplelab-fixes.md +0 -88
- package/docs/bug-fixes/agent-03-dashboard-fixes.md +0 -119
- package/docs/bug-fixes/agent-04-memory-fixes.md +0 -105
- package/docs/bug-fixes/agent-05-provider-fixes.md +0 -86
- package/docs/bug-fixes/agent-06-integration-fixes.md +0 -101
- package/docs/bug-fixes/agent-07-dash-run-fixes.md +0 -101
- package/docs/bug-fixes/agent-08-docker-fixes.md +0 -164
- package/docs/bug-fixes/agent-09-e2e-build-fixes.md +0 -69
- package/docs/bug-fixes/agent-10-e2e-fullstack-fixes.md +0 -102
- package/docs/bug-fixes/agent-11-e2e-session-fixes.md +0 -70
- package/docs/bug-fixes/agent-12-scenario-fixes.md +0 -120
- package/docs/bug-fixes/agent-13-enterprise-fixes.md +0 -143
- package/docs/bug-fixes/agent-14-uat-newuser-fixes.md +0 -88
- package/docs/bug-fixes/agent-15-uat-poweruser-fixes.md +0 -132
- package/docs/bug-fixes/agent-19-code-review.md +0 -316
- package/docs/bug-fixes/agent-20-architecture-review.md +0 -331
- package/docs/competitive/bolt-new-analysis.md +0 -579
- package/docs/competitive/emergence-others-analysis.md +0 -605
- package/docs/competitive/replit-lovable-analysis.md +0 -622
- package/docs/dashboard-guide.md +0 -487
- package/docs/enterprise/migration.md +0 -265
- package/docs/history/one-run-audit.md +0 -304
- package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +0 -312
- package/docs/plans/ULTRAPLAN-FORGE-BAAS.md +0 -612
- package/docs/plans/UT2-6-LSP-DIAGNOSTIC-BROADCAST.md +0 -394
- package/docs/prd-purple-lab-ide.md +0 -152
- package/docs/prd-purple-lab-platform-v2.md +0 -223
- package/docs/prd-purple-lab-platform.md +0 -319
- package/docs/retrospectives/v7.5.15-fleet-postmortem.md +0 -325
- package/docs/retrospectives/v7.5.15-honesty-audit.md +0 -136
- package/docs/retrospectives/v7.5.15-llm-failure-modes.md +0 -49
- package/docs/show-hn-post.md +0 -46
- package/docs/test-scenarios/enterprise-scenarios.md +0 -732
- package/docs/thick2thin.md +0 -173
|
@@ -14,6 +14,7 @@ var __export = (target, all) => {
|
|
|
14
14
|
set: __exportSetter.bind(all, name)
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
18
|
var __require = import.meta.require;
|
|
18
19
|
|
|
19
20
|
// node_modules/yaml/dist/nodes/identity.js
|
|
@@ -6958,9 +6959,292 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6958
6959
|
exports.stringify = stringify;
|
|
6959
6960
|
});
|
|
6960
6961
|
|
|
6962
|
+
// ../../loki-ts/src/util/paths.ts
|
|
6963
|
+
import { resolve as resolve7, dirname as dirname6 } from "path";
|
|
6964
|
+
import { fileURLToPath } from "url";
|
|
6965
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6966
|
+
import { homedir as homedir7 } from "os";
|
|
6967
|
+
function findRepoRoot() {
|
|
6968
|
+
let dir = HERE;
|
|
6969
|
+
for (let i = 0;i < 6; i++) {
|
|
6970
|
+
if (existsSync14(resolve7(dir, "VERSION")) && existsSync14(resolve7(dir, "autonomy/run.sh"))) {
|
|
6971
|
+
return dir;
|
|
6972
|
+
}
|
|
6973
|
+
const parent = dirname6(dir);
|
|
6974
|
+
if (parent === dir)
|
|
6975
|
+
break;
|
|
6976
|
+
dir = parent;
|
|
6977
|
+
}
|
|
6978
|
+
return resolve7(HERE, "..", "..", "..");
|
|
6979
|
+
}
|
|
6980
|
+
function homeLokiDir() {
|
|
6981
|
+
return resolve7(homedir7(), ".loki");
|
|
6982
|
+
}
|
|
6983
|
+
var HERE, REPO_ROOT;
|
|
6984
|
+
var init_paths = __esm(() => {
|
|
6985
|
+
HERE = dirname6(fileURLToPath(import.meta.url));
|
|
6986
|
+
REPO_ROOT = findRepoRoot();
|
|
6987
|
+
});
|
|
6988
|
+
|
|
6989
|
+
// ../../loki-ts/src/features/lean_prefix.ts
|
|
6990
|
+
var LEAN_PREFIX, STAGE_PREFIX;
|
|
6991
|
+
var init_lean_prefix = __esm(() => {
|
|
6992
|
+
LEAN_PREFIX = "You are Loki, an autonomous coding agent. Follow the task prompt exactly " + "and report only what you did.";
|
|
6993
|
+
STAGE_PREFIX = "You are Loki, an autonomous coding agent working one stage of a run. " + "Follow the stage prompt exactly, including the role it gives you and the rules it sets, " + "and report only what you did. " + "Stage-specific instructions and the task follow this block.";
|
|
6994
|
+
});
|
|
6995
|
+
|
|
6996
|
+
// ../../loki-ts/src/util/claude_model.ts
|
|
6997
|
+
var FAMILY_ALIASES;
|
|
6998
|
+
var init_claude_model = __esm(() => {
|
|
6999
|
+
init_paths();
|
|
7000
|
+
FAMILY_ALIASES = new Set(["haiku", "sonnet", "opus", "fable"]);
|
|
7001
|
+
});
|
|
7002
|
+
|
|
7003
|
+
// ../../loki-ts/src/runner/router/flag.ts
|
|
7004
|
+
function routerMode(env = process.env) {
|
|
7005
|
+
const v = (env.LOKI_ROUTER ?? "").trim().toLowerCase();
|
|
7006
|
+
if (v === "0" || v === "false" || v === "off")
|
|
7007
|
+
return "opt-out";
|
|
7008
|
+
if (v === "1" || v === "true" || v === "on")
|
|
7009
|
+
return "on";
|
|
7010
|
+
return "off";
|
|
7011
|
+
}
|
|
7012
|
+
var routerEnabled = (env = process.env) => routerMode(env) === "on";
|
|
7013
|
+
|
|
7014
|
+
// ../../loki-ts/src/engine10/cost.ts
|
|
7015
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
|
|
7016
|
+
import { join as join27 } from "path";
|
|
7017
|
+
function routerTelemetryOn(env = process.env) {
|
|
7018
|
+
return routerEnabled(env);
|
|
7019
|
+
}
|
|
7020
|
+
function num3(v) {
|
|
7021
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
7022
|
+
}
|
|
7023
|
+
function resultCostPath(lokiRoot, iteration) {
|
|
7024
|
+
return join27(lokiRoot, "metrics", `result-cost-${iteration}.json`);
|
|
7025
|
+
}
|
|
7026
|
+
function sumResultCosts(lokiRoot, iterations) {
|
|
7027
|
+
const out2 = {
|
|
7028
|
+
usd: null,
|
|
7029
|
+
partialUsd: 0,
|
|
7030
|
+
measuredCount: 0,
|
|
7031
|
+
totalCount: iterations.length,
|
|
7032
|
+
input_tokens: 0,
|
|
7033
|
+
output_tokens: 0,
|
|
7034
|
+
cache_read_tokens: 0,
|
|
7035
|
+
cache_creation_tokens: 0,
|
|
7036
|
+
...routerTelemetryOn() ? { router: {
|
|
7037
|
+
requests_total: 0,
|
|
7038
|
+
requests_over_100k: 0,
|
|
7039
|
+
over_100k_input_tokens: 0,
|
|
7040
|
+
over_100k_output_tokens: 0,
|
|
7041
|
+
advisor_calls: 0,
|
|
7042
|
+
advisor_input_tokens: 0,
|
|
7043
|
+
advisor_output_tokens: 0
|
|
7044
|
+
} } : {},
|
|
7045
|
+
model: null,
|
|
7046
|
+
source: "",
|
|
7047
|
+
missing: []
|
|
7048
|
+
};
|
|
7049
|
+
const sources2 = [];
|
|
7050
|
+
let usd = 0;
|
|
7051
|
+
for (const iter of iterations) {
|
|
7052
|
+
const path = resultCostPath(lokiRoot, iter);
|
|
7053
|
+
let rec;
|
|
7054
|
+
try {
|
|
7055
|
+
rec = JSON.parse(readFileSync19(path, "utf8"));
|
|
7056
|
+
} catch {
|
|
7057
|
+
out2.missing.push(iter);
|
|
7058
|
+
continue;
|
|
7059
|
+
}
|
|
7060
|
+
const inTok = num3(rec["input_tokens"]);
|
|
7061
|
+
const outTok = num3(rec["output_tokens"]);
|
|
7062
|
+
const cacheR = num3(rec["cache_read_tokens"]);
|
|
7063
|
+
const cacheC = num3(rec["cache_creation_tokens"]);
|
|
7064
|
+
out2.input_tokens += inTok;
|
|
7065
|
+
out2.output_tokens += outTok;
|
|
7066
|
+
out2.cache_read_tokens += cacheR;
|
|
7067
|
+
out2.cache_creation_tokens += cacheC;
|
|
7068
|
+
if (out2.router)
|
|
7069
|
+
out2.router["requests_total"] += num3(rec["requests_total"]);
|
|
7070
|
+
if (out2.router)
|
|
7071
|
+
out2.router["requests_over_100k"] += num3(rec["requests_over_100k"]);
|
|
7072
|
+
if (out2.router)
|
|
7073
|
+
out2.router["over_100k_input_tokens"] += num3(rec["over_100k_input_tokens"]);
|
|
7074
|
+
if (out2.router)
|
|
7075
|
+
out2.router["over_100k_output_tokens"] += num3(rec["over_100k_output_tokens"]);
|
|
7076
|
+
if (out2.router)
|
|
7077
|
+
out2.router["advisor_calls"] += num3(rec["advisor_calls"]);
|
|
7078
|
+
if (out2.router)
|
|
7079
|
+
out2.router["advisor_input_tokens"] += num3(rec["advisor_input_tokens"]);
|
|
7080
|
+
if (out2.router)
|
|
7081
|
+
out2.router["advisor_output_tokens"] += num3(rec["advisor_output_tokens"]);
|
|
7082
|
+
if (typeof rec["model"] === "string" && rec["model"])
|
|
7083
|
+
out2.model = rec["model"];
|
|
7084
|
+
sources2.push(path);
|
|
7085
|
+
const c = rec["total_cost_usd"];
|
|
7086
|
+
const unmetered = rec["source"] === UNMETERED;
|
|
7087
|
+
if (unmetered)
|
|
7088
|
+
out2.unmetered = true;
|
|
7089
|
+
const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
|
|
7090
|
+
if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
|
|
7091
|
+
out2.missing.push(iter);
|
|
7092
|
+
continue;
|
|
7093
|
+
}
|
|
7094
|
+
usd += c;
|
|
7095
|
+
out2.partialUsd += c;
|
|
7096
|
+
out2.measuredCount++;
|
|
7097
|
+
}
|
|
7098
|
+
out2.source = sources2.join(",");
|
|
7099
|
+
if (iterations.length > 0 && out2.missing.length === 0)
|
|
7100
|
+
out2.usd = usd;
|
|
7101
|
+
return out2;
|
|
7102
|
+
}
|
|
7103
|
+
var UNMETERED = "cli-invoker-unmetered";
|
|
7104
|
+
var init_cost = () => {};
|
|
7105
|
+
|
|
7106
|
+
// ../../loki-ts/src/runner/budget.ts
|
|
7107
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync10, readFileSync as readFileSync20, renameSync as renameSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
7108
|
+
import { dirname as dirname8, join as join28, resolve as resolve9 } from "path";
|
|
7109
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7110
|
+
function _loadPricing() {
|
|
7111
|
+
try {
|
|
7112
|
+
const moduleDir = dirname8(fileURLToPath2(import.meta.url));
|
|
7113
|
+
const jsonPath = resolve9(moduleDir, "..", "..", "data", "model-pricing.json");
|
|
7114
|
+
if (!existsSync16(jsonPath))
|
|
7115
|
+
return _FALLBACK_PRICING;
|
|
7116
|
+
const raw2 = JSON.parse(readFileSync20(jsonPath, "utf8"));
|
|
7117
|
+
const pricing2 = raw2["pricing"];
|
|
7118
|
+
if (!pricing2 || typeof pricing2 !== "object")
|
|
7119
|
+
return _FALLBACK_PRICING;
|
|
7120
|
+
const out2 = {};
|
|
7121
|
+
for (const [key, value] of Object.entries(pricing2)) {
|
|
7122
|
+
if (value !== null && typeof value === "object" && typeof value.input === "number" && typeof value.output === "number") {
|
|
7123
|
+
const _e = value;
|
|
7124
|
+
out2[key] = {
|
|
7125
|
+
input: _e.input,
|
|
7126
|
+
output: _e.output,
|
|
7127
|
+
...typeof _e.cache_read === "number" ? { cache_read: _e.cache_read } : {},
|
|
7128
|
+
...typeof _e.cache_write === "number" ? { cache_write: _e.cache_write } : {},
|
|
7129
|
+
...typeof _e.cache_write_5m === "number" ? { cache_write_5m: _e.cache_write_5m } : {},
|
|
7130
|
+
...typeof _e.cache_write_1h === "number" ? { cache_write_1h: _e.cache_write_1h } : {},
|
|
7131
|
+
..._e.over_100k && typeof _e.over_100k.input === "number" && typeof _e.over_100k.output === "number" ? { over_100k: _e.over_100k } : {}
|
|
7132
|
+
};
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
for (const k of Object.keys(_FALLBACK_PRICING)) {
|
|
7136
|
+
if (!(k in out2))
|
|
7137
|
+
return _FALLBACK_PRICING;
|
|
7138
|
+
}
|
|
7139
|
+
return out2;
|
|
7140
|
+
} catch {
|
|
7141
|
+
return _FALLBACK_PRICING;
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
var _FALLBACK_PRICING, PRICING;
|
|
7145
|
+
var init_budget = __esm(() => {
|
|
7146
|
+
init_paths();
|
|
7147
|
+
init_cost();
|
|
7148
|
+
_FALLBACK_PRICING = {
|
|
7149
|
+
fable: { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5, cache_write_5m: 12.5, cache_write_1h: 20 },
|
|
7150
|
+
opus: { input: 4, output: 20, cache_read: 0.2, cache_write: 5, cache_write_5m: 5, cache_write_1h: 8 },
|
|
7151
|
+
sonnet: { input: 2, output: 10, cache_read: 0.1, cache_write: 2.5, cache_write_5m: 2.5, cache_write_1h: 4 },
|
|
7152
|
+
haiku: {
|
|
7153
|
+
input: 0.1,
|
|
7154
|
+
output: 0.5,
|
|
7155
|
+
cache_read: 0.01,
|
|
7156
|
+
cache_write: 0.125,
|
|
7157
|
+
cache_write_5m: 0.125,
|
|
7158
|
+
cache_write_1h: 0.2,
|
|
7159
|
+
over_100k: { input: 0.5, output: 2.5, cache_read: 0.05, cache_write: 0.625, cache_write_5m: 0.625, cache_write_1h: 1 }
|
|
7160
|
+
},
|
|
7161
|
+
"claude-haiku-4-5": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25, cache_write_5m: 1.25, cache_write_1h: 2 },
|
|
7162
|
+
"claude-haiku-5-5": {
|
|
7163
|
+
input: 0.1,
|
|
7164
|
+
output: 0.5,
|
|
7165
|
+
cache_read: 0.01,
|
|
7166
|
+
cache_write: 0.125,
|
|
7167
|
+
over_100k: { input: 0.5, output: 2.5, cache_read: 0.05, cache_write: 0.625 }
|
|
7168
|
+
},
|
|
7169
|
+
"gpt-5.3-codex": { input: 1.75, output: 14, cache_read: 0.175, cache_write: 2.1875 }
|
|
7170
|
+
};
|
|
7171
|
+
PRICING = Object.freeze(_loadPricing());
|
|
7172
|
+
});
|
|
7173
|
+
|
|
7174
|
+
// ../../loki-ts/src/util/shell.ts
|
|
7175
|
+
async function readStreamCapped(stream, maxBytes = MAX_STDOUT_BYTES) {
|
|
7176
|
+
const reader = stream.getReader();
|
|
7177
|
+
const decoder = new TextDecoder;
|
|
7178
|
+
let out2 = "";
|
|
7179
|
+
let total = 0;
|
|
7180
|
+
try {
|
|
7181
|
+
while (total < maxBytes) {
|
|
7182
|
+
const { done, value } = await reader.read();
|
|
7183
|
+
if (done)
|
|
7184
|
+
break;
|
|
7185
|
+
if (!value)
|
|
7186
|
+
continue;
|
|
7187
|
+
total += value.byteLength;
|
|
7188
|
+
if (total > maxBytes) {
|
|
7189
|
+
const keep = value.byteLength - (total - maxBytes);
|
|
7190
|
+
out2 += decoder.decode(value.subarray(0, keep), { stream: true });
|
|
7191
|
+
break;
|
|
7192
|
+
}
|
|
7193
|
+
out2 += decoder.decode(value, { stream: true });
|
|
7194
|
+
}
|
|
7195
|
+
out2 += decoder.decode();
|
|
7196
|
+
} finally {
|
|
7197
|
+
try {
|
|
7198
|
+
await reader.cancel();
|
|
7199
|
+
} catch {}
|
|
7200
|
+
reader.releaseLock();
|
|
7201
|
+
}
|
|
7202
|
+
return out2;
|
|
7203
|
+
}
|
|
7204
|
+
async function run(argv, opts = {}) {
|
|
7205
|
+
const proc = Bun.spawn({
|
|
7206
|
+
cmd: [...argv],
|
|
7207
|
+
stdout: "pipe",
|
|
7208
|
+
stderr: "pipe",
|
|
7209
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
7210
|
+
cwd: opts.cwd
|
|
7211
|
+
});
|
|
7212
|
+
let timer;
|
|
7213
|
+
let killTimer;
|
|
7214
|
+
if (opts.timeoutMs && opts.timeoutMs > 0) {
|
|
7215
|
+
timer = setTimeout(() => {
|
|
7216
|
+
try {
|
|
7217
|
+
proc.kill("SIGTERM");
|
|
7218
|
+
} catch {}
|
|
7219
|
+
killTimer = setTimeout(() => {
|
|
7220
|
+
try {
|
|
7221
|
+
proc.kill("SIGKILL");
|
|
7222
|
+
} catch {}
|
|
7223
|
+
}, 2000);
|
|
7224
|
+
}, opts.timeoutMs);
|
|
7225
|
+
}
|
|
7226
|
+
try {
|
|
7227
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
7228
|
+
readStreamCapped(proc.stdout),
|
|
7229
|
+
new Response(proc.stderr).text(),
|
|
7230
|
+
proc.exited
|
|
7231
|
+
]);
|
|
7232
|
+
return { stdout, stderr, exitCode };
|
|
7233
|
+
} finally {
|
|
7234
|
+
if (timer)
|
|
7235
|
+
clearTimeout(timer);
|
|
7236
|
+
if (killTimer)
|
|
7237
|
+
clearTimeout(killTimer);
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
var MAX_STDOUT_BYTES;
|
|
7241
|
+
var init_shell = __esm(() => {
|
|
7242
|
+
MAX_STDOUT_BYTES = 16 * 1024 * 1024;
|
|
7243
|
+
});
|
|
7244
|
+
|
|
6961
7245
|
// src/server/app.ts
|
|
6962
|
-
import { existsSync as
|
|
6963
|
-
import { join as
|
|
7246
|
+
import { existsSync as existsSync33, readFileSync as readFileSync40, statSync as statSync17 } from "fs";
|
|
7247
|
+
import { join as join53, resolve as resolve12, sep as sep14 } from "path";
|
|
6964
7248
|
|
|
6965
7249
|
// node_modules/hono/dist/request/constants.js
|
|
6966
7250
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -17519,365 +17803,39 @@ function mount20(ctx) {
|
|
|
17519
17803
|
}
|
|
17520
17804
|
|
|
17521
17805
|
// src/server/routes/verify.ts
|
|
17522
|
-
import { readFileSync as
|
|
17806
|
+
import { readFileSync as readFileSync38, realpathSync as realpathSync20 } from "fs";
|
|
17523
17807
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
17524
|
-
import { join as
|
|
17808
|
+
import { join as join51, sep as sep13 } from "path";
|
|
17525
17809
|
|
|
17526
17810
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
17527
|
-
|
|
17528
|
-
import {
|
|
17529
|
-
import {
|
|
17530
|
-
|
|
17531
|
-
// ../../loki-ts/src/util/paths.ts
|
|
17532
|
-
import { resolve as resolve7, dirname as dirname6 } from "path";
|
|
17533
|
-
import { fileURLToPath } from "url";
|
|
17534
|
-
import { existsSync as existsSync14 } from "fs";
|
|
17535
|
-
var HERE = dirname6(fileURLToPath(import.meta.url));
|
|
17536
|
-
function findRepoRoot() {
|
|
17537
|
-
let dir = HERE;
|
|
17538
|
-
for (let i = 0;i < 6; i++) {
|
|
17539
|
-
if (existsSync14(resolve7(dir, "VERSION")) && existsSync14(resolve7(dir, "autonomy/run.sh"))) {
|
|
17540
|
-
return dir;
|
|
17541
|
-
}
|
|
17542
|
-
const parent = dirname6(dir);
|
|
17543
|
-
if (parent === dir)
|
|
17544
|
-
break;
|
|
17545
|
-
dir = parent;
|
|
17546
|
-
}
|
|
17547
|
-
return resolve7(HERE, "..", "..", "..");
|
|
17548
|
-
}
|
|
17549
|
-
var REPO_ROOT = findRepoRoot();
|
|
17811
|
+
init_paths();
|
|
17812
|
+
import { existsSync as existsSync31, readdirSync as readdirSync15, readFileSync as readFileSync37, statSync as statSync16 } from "fs";
|
|
17813
|
+
import { dirname as dirname22, join as join50 } from "path";
|
|
17814
|
+
import { createHash as createHash12, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
17550
17815
|
|
|
17551
17816
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
17552
|
-
import { createHash as
|
|
17553
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
17554
|
-
import { homedir as
|
|
17555
|
-
import { basename as
|
|
17817
|
+
import { createHash as createHash11, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
17818
|
+
import { chmodSync as chmodSync5, existsSync as existsSync30, readdirSync as readdirSync14, linkSync, mkdirSync as mkdirSync17, readFileSync as readFileSync35, statSync as statSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "fs";
|
|
17819
|
+
import { homedir as homedir8 } from "os";
|
|
17820
|
+
import { basename as basename17, dirname as dirname21, join as join49 } from "path";
|
|
17556
17821
|
|
|
17557
|
-
// ../../loki-ts/src/
|
|
17558
|
-
import {
|
|
17559
|
-
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, rmSync as rmSync4 } from "fs";
|
|
17822
|
+
// ../../loki-ts/src/util/mutation_proof.ts
|
|
17823
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, rmSync as rmSync4, symlinkSync, writeFileSync as writeFileSync8 } from "fs";
|
|
17560
17824
|
import { tmpdir as tmpdir3 } from "os";
|
|
17561
|
-
import { join as join26 } from "path";
|
|
17562
|
-
import { basename as basename8 } from "path";
|
|
17563
|
-
var WEAKENED = { verdict: "weakened", changes: [] };
|
|
17564
|
-
var PY = String.raw`
|
|
17565
|
-
import ast, copy, io, json, re, sys, tokenize
|
|
17566
|
-
d = json.load(sys.stdin)
|
|
17567
|
-
bs, hs, task = d["base"], d["head"], d["task"]
|
|
17568
|
-
def out(v, it=()):
|
|
17569
|
-
print(json.dumps({"verdict": v, "items": list(it)}))
|
|
17570
|
-
sys.exit(0)
|
|
17571
|
-
if "\r" in bs or "\r" in hs or "\t" in bs or "\t" in hs:
|
|
17572
|
-
out("weakened")
|
|
17573
|
-
bt, ht = ast.parse(bs), ast.parse(hs)
|
|
17574
|
-
# every byte outside a literal token (comments, blank lines, spacing, other code) must be identical
|
|
17575
|
-
def template(src):
|
|
17576
|
-
lines = src.split("\n")
|
|
17577
|
-
starts, o = [], 0
|
|
17578
|
-
for ln in lines:
|
|
17579
|
-
starts.append(o)
|
|
17580
|
-
o += len(ln) + 1
|
|
17581
|
-
spans = [(starts[t.start[0] - 1] + t.start[1], starts[t.end[0] - 1] + t.end[1])
|
|
17582
|
-
for t in tokenize.generate_tokens(io.StringIO(src).readline) if t.type in (tokenize.STRING, tokenize.NUMBER)]
|
|
17583
|
-
r, pos = [], 0
|
|
17584
|
-
for a, b in spans:
|
|
17585
|
-
r.append(src[pos:a] + "\0")
|
|
17586
|
-
pos = b
|
|
17587
|
-
return "".join(r) + src[pos:]
|
|
17588
|
-
if template(bs) != template(hs):
|
|
17589
|
-
out("weakened")
|
|
17590
|
-
class Norm(ast.NodeTransformer):
|
|
17591
|
-
def visit_Constant(self, n):
|
|
17592
|
-
return ast.copy_location(ast.Constant(value="<" + type(n.value).__name__ + ">"), n)
|
|
17593
|
-
def norm(t):
|
|
17594
|
-
return ast.dump(Norm().visit(copy.deepcopy(t)))
|
|
17595
|
-
# only what pytest collects: module-level test functions and methods of Test* classes without __init__
|
|
17596
|
-
def tests(t):
|
|
17597
|
-
r = []
|
|
17598
|
-
for n in t.body:
|
|
17599
|
-
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name.startswith("test"):
|
|
17600
|
-
r.append(n)
|
|
17601
|
-
elif isinstance(n, ast.ClassDef) and n.name.startswith("Test") \
|
|
17602
|
-
and not any(isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and m.name == "__init__" for m in n.body):
|
|
17603
|
-
r += [m for m in n.body if isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and m.name.startswith("test")]
|
|
17604
|
-
return r
|
|
17605
|
-
def direct(f):
|
|
17606
|
-
return [a for a in f.body if isinstance(a, ast.Assert)]
|
|
17607
|
-
def tables(t):
|
|
17608
|
-
return [n.args[1] for n in ast.walk(t) if isinstance(n, ast.Call) and getattr(n.func, "attr", "") == "parametrize"
|
|
17609
|
-
and len(n.args) > 1 and isinstance(n.args[1], (ast.List, ast.Tuple))]
|
|
17610
|
-
def stripped(t):
|
|
17611
|
-
c = copy.deepcopy(t)
|
|
17612
|
-
for tb in tables(c):
|
|
17613
|
-
tb.elts = []
|
|
17614
|
-
return c
|
|
17615
|
-
def consts(t):
|
|
17616
|
-
return [n for n in ast.walk(t) if isinstance(n, ast.Constant)]
|
|
17617
|
-
def shadowed(t):
|
|
17618
|
-
for scope in [t] + [n for n in t.body if isinstance(n, ast.ClassDef)]:
|
|
17619
|
-
names = [n.name for n in scope.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name.startswith("test")]
|
|
17620
|
-
if len(names) != len(set(names)):
|
|
17621
|
-
return True
|
|
17622
|
-
return False
|
|
17623
|
-
if shadowed(bt) or shadowed(ht):
|
|
17624
|
-
out("weakened")
|
|
17625
|
-
sb, sh = stripped(bt), stripped(ht)
|
|
17626
|
-
if not tests(ht) or norm(sb) != norm(sh):
|
|
17627
|
-
out("weakened")
|
|
17628
|
-
for t in (bt, ht):
|
|
17629
|
-
if any(isinstance(n, ast.Call) and isinstance(n.func, ast.Name) and n.func.id == "round" for n in ast.walk(t)):
|
|
17630
|
-
out("weakened")
|
|
17631
|
-
tb, th = tables(bt), tables(ht)
|
|
17632
|
-
if any(len(x.elts) != len(y.elts) for x, y in zip(tb, th)):
|
|
17633
|
-
out("weakened")
|
|
17634
|
-
if any(norm(r) != norm(q) for x, y in zip(tb, th) for r, q in zip(x.elts, y.elts)):
|
|
17635
|
-
out("weakened")
|
|
17636
|
-
owner = {}
|
|
17637
|
-
for f in tests(sh):
|
|
17638
|
-
for n in ast.walk(f):
|
|
17639
|
-
owner[id(n)] = f
|
|
17640
|
-
pairs = [(b, h, owner.get(id(h))) for b, h in zip(consts(sb), consts(sh))]
|
|
17641
|
-
fh = {}
|
|
17642
|
-
for f in tests(ht):
|
|
17643
|
-
for t in tables(f):
|
|
17644
|
-
fh[id(t)] = f
|
|
17645
|
-
for x, y in zip(tb, th):
|
|
17646
|
-
for r, q in zip(x.elts, y.elts):
|
|
17647
|
-
pairs += [(b, h, fh.get(id(y))) for b, h in zip(consts(r), consts(q))]
|
|
17648
|
-
def eqs(f):
|
|
17649
|
-
return [a.test for a in direct(f) if isinstance(a.test, ast.Compare) and len(a.test.ops) == 1 and isinstance(a.test.ops[0], ast.Eq)]
|
|
17650
|
-
def dups(t):
|
|
17651
|
-
r = {}
|
|
17652
|
-
for f in tests(t):
|
|
17653
|
-
ds = [ast.dump(a) for a in direct(f)]
|
|
17654
|
-
r[f.name] = len(ds) - len(set(ds))
|
|
17655
|
-
return r
|
|
17656
|
-
dh, db = dups(ht), dups(bt)
|
|
17657
|
-
ok = not any(v > db.get(k, 0) for k, v in dh.items())
|
|
17658
|
-
# expected side = the right operand of a direct "assert X == Y"
|
|
17659
|
-
exp = set()
|
|
17660
|
-
for f in tests(sh):
|
|
17661
|
-
exp |= {id(c.comparators[0]) for c in eqs(f) if isinstance(c.comparators[0], ast.Constant)}
|
|
17662
|
-
for f in tests(ht):
|
|
17663
|
-
rights = {id(c.comparators[0]) for c in eqs(f) if isinstance(c.comparators[0], ast.Name)}
|
|
17664
|
-
for c in f.decorator_list:
|
|
17665
|
-
if isinstance(c, ast.Call) and getattr(c.func, "attr", "") == "parametrize" and len(c.args) > 1 \
|
|
17666
|
-
and isinstance(c.args[0], ast.Constant) and isinstance(c.args[0].value, str) and isinstance(c.args[1], (ast.List, ast.Tuple)):
|
|
17667
|
-
cols = [x.strip() for x in c.args[0].value.split(",")]
|
|
17668
|
-
# a column counts only if every use of its name in the body is the right operand of a direct eq assert
|
|
17669
|
-
if any(kw.arg == "indirect" for kw in c.keywords):
|
|
17670
|
-
continue
|
|
17671
|
-
deco = {id(m) for dd in f.decorator_list for m in ast.walk(dd)}
|
|
17672
|
-
uses = {k: [n for n in ast.walk(f) if isinstance(n, ast.Name) and n.id == k and id(n) not in deco] for k in cols}
|
|
17673
|
-
# the column must be read at least once, and only as the right operand of a direct eq assert
|
|
17674
|
-
solo = {k for k, u in uses.items() if u and all(id(n) in rights for n in u)}
|
|
17675
|
-
for r in c.args[1].elts:
|
|
17676
|
-
for i, e in enumerate(r.elts if isinstance(r, (ast.Tuple, ast.List)) else [r]):
|
|
17677
|
-
if isinstance(e, ast.Constant) and i < len(cols) and cols[i] in solo:
|
|
17678
|
-
exp.add(id(e))
|
|
17679
|
-
def callees(f):
|
|
17680
|
-
r = set()
|
|
17681
|
-
for c in eqs(f):
|
|
17682
|
-
for n in ast.walk(c.left):
|
|
17683
|
-
if isinstance(n, ast.Call):
|
|
17684
|
-
k = n.func.id if isinstance(n.func, ast.Name) else getattr(n.func, "attr", "")
|
|
17685
|
-
if k:
|
|
17686
|
-
r.add(k)
|
|
17687
|
-
return r
|
|
17688
|
-
def generic(v):
|
|
17689
|
-
return v is None or isinstance(v, (bool, bytes)) or (isinstance(v, str) and len(v) <= 3) \
|
|
17690
|
-
or (isinstance(v, (int, float)) and abs(v) < 10)
|
|
17691
|
-
def in_task(v, f):
|
|
17692
|
-
task0 = d["task"]
|
|
17693
|
-
t = v if isinstance(v, str) else repr(v)
|
|
17694
|
-
if t == "":
|
|
17695
|
-
return False
|
|
17696
|
-
# bounded by whitespace, string ends or ,;:()[]{}"' only; never glued to - + or a digit group separator
|
|
17697
|
-
tok = r"(?<![^\s,;:()\[\]{}\"'])(?<!\d,)" + re.escape(t) + r"(?:(?=[\s,;:()\[\]{}\"']|$)(?!,\d)|\.(?=\s|$))"
|
|
17698
|
-
task = re.sub(r"(?<=\d),(?=\d{3}\b)", "", task0)
|
|
17699
|
-
if not generic(v):
|
|
17700
|
-
return re.search(tok, task) is not None
|
|
17701
|
-
# a generic value counts only right next to the identifier the test calls
|
|
17702
|
-
return any(re.search(r"\b" + re.escape(k) + r"\b[^\n]{0,40}?" + tok, task) for k in (callees(f) if f else ()))
|
|
17703
|
-
items = []
|
|
17704
|
-
for b, h, f in pairs:
|
|
17705
|
-
if (type(b.value), repr(b.value)) != (type(h.value), repr(h.value)):
|
|
17706
|
-
hit = in_task(h.value, f)
|
|
17707
|
-
ok = ok and id(h) in exp and hit
|
|
17708
|
-
items.append({"line": h.lineno, "old": repr(b.value), "new": repr(h.value), "inTask": hit, "matched": "new"})
|
|
17709
|
-
out("value-change" if ok and items else "weakened", items if ok else [])
|
|
17710
|
-
`;
|
|
17711
|
-
var okStr = (v) => typeof v === "string" && v.length <= 200 && !/[\x00-\x1f\x7f]/.test(v);
|
|
17712
|
-
function classifyAssertDelta(inp) {
|
|
17713
|
-
const f = basename8(inp.path);
|
|
17714
|
-
if (!/^(test_.*|.*_test)\.py$/.test(f))
|
|
17715
|
-
return WEAKENED;
|
|
17716
|
-
if (inp.headCounts.run !== inp.baseCounts.run || inp.headCounts.skipped !== inp.baseCounts.skipped)
|
|
17717
|
-
return WEAKENED;
|
|
17718
|
-
let cwd = "";
|
|
17719
|
-
try {
|
|
17720
|
-
cwd = mkdtempSync2(join26(tmpdir3(), "loki-ad-"));
|
|
17721
|
-
const raw2 = execFileSync4("python3", ["-I", "-c", PY], {
|
|
17722
|
-
input: JSON.stringify({ path: inp.path, base: inp.base, head: inp.head, task: inp.task }),
|
|
17723
|
-
cwd,
|
|
17724
|
-
env: process.env,
|
|
17725
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
17726
|
-
encoding: "utf8",
|
|
17727
|
-
timeout: 20000
|
|
17728
|
-
});
|
|
17729
|
-
const r = JSON.parse(raw2);
|
|
17730
|
-
if (r.verdict !== "value-change" || !Array.isArray(r.items) || r.items.length === 0)
|
|
17731
|
-
return WEAKENED;
|
|
17732
|
-
const items = [];
|
|
17733
|
-
for (const i of r.items) {
|
|
17734
|
-
if (!i || typeof i !== "object" || !Number.isInteger(i["line"]) || i["line"] < 1 || !okStr(i["old"]) || !okStr(i["new"]) || i["inTask"] !== true || i["matched"] !== "new")
|
|
17735
|
-
return WEAKENED;
|
|
17736
|
-
items.push({ line: i["line"], old: i["old"], new: i["new"], matched: "new" });
|
|
17737
|
-
}
|
|
17738
|
-
return { verdict: "value-change", changes: items.map((i) => `${inp.path}:${i.line} ${i.old} -> ${i.new}`), items };
|
|
17739
|
-
} catch {
|
|
17740
|
-
return WEAKENED;
|
|
17741
|
-
} finally {
|
|
17742
|
-
if (cwd)
|
|
17743
|
-
rmSync4(cwd, { recursive: true, force: true });
|
|
17744
|
-
}
|
|
17745
|
-
}
|
|
17746
|
-
function assertDeltaNotes(repoDir, baseSha, headRef, path, task, baseCounts, headCounts) {
|
|
17747
|
-
if (!baseCounts || !headCounts)
|
|
17748
|
-
return null;
|
|
17749
|
-
try {
|
|
17750
|
-
const show = (ref) => execFileSync4("git", ["show", `${ref}:${path}`], { cwd: repoDir, encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "ignore"] });
|
|
17751
|
-
const head = headRef ? show(headRef) : readFileSync18(join26(repoDir, path), "utf8");
|
|
17752
|
-
const r = classifyTestEdit(path, show(baseSha), head, task, baseCounts, headCounts);
|
|
17753
|
-
return r[0]?.kind === "literal-only" ? r.map((e) => `assertion value changed (not shown to be required by the task): ${e.file}:${e.line} ${e.old} -> ${e.new}`) : null;
|
|
17754
|
-
} catch {
|
|
17755
|
-
return null;
|
|
17756
|
-
}
|
|
17757
|
-
}
|
|
17758
|
-
function classifyTestEdit(file, oldText, newText, taskText, baseCounts, headCounts) {
|
|
17759
|
-
const weak = [{ kind: "weakened", file, line: 0, old: "", new: "", inTask: false }];
|
|
17760
|
-
const r = classifyAssertDelta({ path: file, base: oldText, head: newText, task: taskText, baseCounts, headCounts });
|
|
17761
|
-
if (r.verdict !== "value-change" || !r.items?.length)
|
|
17762
|
-
return weak;
|
|
17763
|
-
return r.items.map((i) => ({ kind: "literal-only", file, line: i.line, old: i.old, new: i.new, inTask: true }));
|
|
17764
|
-
}
|
|
17765
|
-
|
|
17766
|
-
// ../../loki-ts/src/e10ext/discard.ts
|
|
17767
|
-
import { execFileSync as execFileSync5 } from "child_process";
|
|
17768
|
-
import { chmodSync as chmodSync4, closeSync as closeSync5, lstatSync as lstatSync4, mkdirSync as mkdirSync5, openSync as openSync5, readFileSync as readFileSync19, realpathSync as realpathSync14, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
17769
|
-
import { dirname as dirname7, join as join27, relative, sep as sep9 } from "path";
|
|
17770
|
-
function safeRestore(repoDir, f, want, mode) {
|
|
17771
|
-
const root = realpathSync14(repoDir), fp = join27(root, f), rel = relative(root, dirname7(fp));
|
|
17772
|
-
if (rel.startsWith(".."))
|
|
17773
|
-
throw new Error("outside repo");
|
|
17774
|
-
let cur = root;
|
|
17775
|
-
for (const part of rel === "" ? [] : rel.split(sep9)) {
|
|
17776
|
-
cur = join27(cur, part);
|
|
17777
|
-
let st2;
|
|
17778
|
-
try {
|
|
17779
|
-
st2 = lstatSync4(cur);
|
|
17780
|
-
} catch {
|
|
17781
|
-
mkdirSync5(cur);
|
|
17782
|
-
continue;
|
|
17783
|
-
}
|
|
17784
|
-
if (!st2.isDirectory())
|
|
17785
|
-
throw new Error("parent is not a real directory");
|
|
17786
|
-
}
|
|
17787
|
-
let st;
|
|
17788
|
-
try {
|
|
17789
|
-
st = lstatSync4(fp);
|
|
17790
|
-
} catch {
|
|
17791
|
-
st = null;
|
|
17792
|
-
}
|
|
17793
|
-
if (st?.isFile() && readFileSync19(fp).equals(want)) {
|
|
17794
|
-
chmodSync4(fp, mode);
|
|
17795
|
-
return;
|
|
17796
|
-
}
|
|
17797
|
-
if (st) {
|
|
17798
|
-
if (st.isDirectory())
|
|
17799
|
-
rmdirSync(fp);
|
|
17800
|
-
else
|
|
17801
|
-
unlinkSync3(fp);
|
|
17802
|
-
}
|
|
17803
|
-
const fd = openSync5(fp, "wx", mode);
|
|
17804
|
-
try {
|
|
17805
|
-
writeFileSync8(fd, want);
|
|
17806
|
-
} finally {
|
|
17807
|
-
closeSync5(fd);
|
|
17808
|
-
}
|
|
17809
|
-
chmodSync4(fp, mode);
|
|
17810
|
-
}
|
|
17811
|
-
function alreadySatisfied(o) {
|
|
17812
|
-
const b = o.wall?.base_run ?? {};
|
|
17813
|
-
return o.intake?.already_satisfied === true || o.implement?.exit === "already_done" || typeof b.pass === "number" && b.pass > 0 && b.fail === 0 && (b.not_run ?? 0) === 0;
|
|
17814
|
-
}
|
|
17815
|
-
async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
17816
|
-
if (!alreadySatisfied(o))
|
|
17817
|
-
return null;
|
|
17818
|
-
const pre = Object.assign(Object.create(null), o.intake?.preexisting_dirty ?? {}), own = new Set([...Array.isArray(o.intake?.preexisting_untracked) ? o.intake.preexisting_untracked : [], ...Object.keys(o.intake?.preexisting_untracked_blobs ?? {})]);
|
|
17819
|
-
const gone = staged.filter(({ f }) => !keep.has(f) && !own.has(f) && !Object.hasOwn(pre, f) && !f.startsWith(".loki/")), lit = ["--literal-pathspecs"];
|
|
17820
|
-
const del = gone.filter(({ st }) => st === "A").map(({ f }) => f), back = gone.filter(({ st }) => st !== "A").map(({ f }) => f);
|
|
17821
|
-
if (del.length > 0 && (await git([...lit, "rm", "-q", "-f", "--", ...del])).code !== 0 || back.length > 0 && (await git([...lit, "restore", `--source=${base}`, "--staged", "--worktree", "--", ...back])).code !== 0 || (await git(["reset", "-q", base])).code !== 0)
|
|
17822
|
-
return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
|
|
17823
|
-
const notProven = [];
|
|
17824
|
-
for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
|
|
17825
|
-
const fp = join27(repoDir, f);
|
|
17826
|
-
try {
|
|
17827
|
-
if (v.startsWith("!")) {
|
|
17828
|
-
const st = lstatSync4(fp);
|
|
17829
|
-
if (`!${st.size}:${st.mtimeMs}:${st.ctimeMs}` !== v)
|
|
17830
|
-
throw new Error("changed, not snapshotted");
|
|
17831
|
-
continue;
|
|
17832
|
-
}
|
|
17833
|
-
const [sha2, mode] = v.split(" "), want = execFileSync5("git", ["cat-file", "blob", sha2], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 });
|
|
17834
|
-
safeRestore(repoDir, f, want, parseInt(mode ?? "644", 8));
|
|
17835
|
-
} catch {
|
|
17836
|
-
notProven.push(f);
|
|
17837
|
-
}
|
|
17838
|
-
}
|
|
17839
|
-
for (const f of Object.keys(pre))
|
|
17840
|
-
if (staged.some((s) => s.f === f) && !keep.has(f)) {
|
|
17841
|
-
try {
|
|
17842
|
-
let m = 420;
|
|
17843
|
-
try {
|
|
17844
|
-
const l = lstatSync4(join27(repoDir, f));
|
|
17845
|
-
if (l.isFile())
|
|
17846
|
-
m = l.mode & 511;
|
|
17847
|
-
} catch {}
|
|
17848
|
-
safeRestore(repoDir, f, execFileSync5("git", ["cat-file", "blob", pre[f]], { cwd: repoDir, env: process.env, maxBuffer: 1 << 28 }), m);
|
|
17849
|
-
} catch {
|
|
17850
|
-
notProven.push(f);
|
|
17851
|
-
}
|
|
17852
|
-
}
|
|
17853
|
-
return { status: "completed", data: { committed: false, discarded: gone.length, ...notProven.length > 0 ? { not_proven: notProven.map((f) => `pre-existing file not restored: ${f}`) } : {} } };
|
|
17854
|
-
}
|
|
17855
|
-
|
|
17856
|
-
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
17857
|
-
import { dirname as dirname8 } from "path";
|
|
17858
|
-
|
|
17859
|
-
// ../../loki-ts/src/e10ext/preexisting_dirty.ts
|
|
17860
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
17861
|
-
var blob2 = (repoDir, path, env = process.env) => execFileSync6("git", ["hash-object", "-w", "--no-filters", "--", path], { cwd: repoDir, encoding: "utf8", env, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
17862
|
-
function untouchedSinceIntake(repoDir, recorded, env = process.env) {
|
|
17863
|
-
if (!recorded || typeof recorded !== "object")
|
|
17864
|
-
return [];
|
|
17865
|
-
return Object.entries(recorded).filter(([p, h]) => {
|
|
17866
|
-
try {
|
|
17867
|
-
return blob2(repoDir, p, env) === h;
|
|
17868
|
-
} catch {
|
|
17869
|
-
return false;
|
|
17870
|
-
}
|
|
17871
|
-
}).map(([p]) => p);
|
|
17872
|
-
}
|
|
17825
|
+
import { basename as basename8, dirname as dirname7, isAbsolute, join as join26, relative } from "path";
|
|
17873
17826
|
|
|
17874
17827
|
// ../../loki-ts/src/util/safe_git.ts
|
|
17875
|
-
import { execFileSync as
|
|
17828
|
+
import { execFileSync as execFileSync4, spawnSync } from "child_process";
|
|
17876
17829
|
var SAFE_GIT_CONFIG = ["-c", "core.fsmonitor=", "-c", "core.hooksPath=/dev/null", "-c", "core.sshCommand=", "-c", "protocol.ext.allow=never", "-c", "credential.helper="];
|
|
17877
17830
|
var CREDENTIAL_CONFIG = ["-c", "core.fsmonitor=", "-c", "core.hooksPath=/dev/null", "-c", "protocol.ext.allow=never"];
|
|
17878
17831
|
var DRIVER_CONFIG = ["-c", "diff.external=", "-c", "commit.gpgSign=false", "-c", "core.attributesFile=/dev/null"];
|
|
17832
|
+
var ATTRS_OFF = "core.attributesFile=/dev/null";
|
|
17833
|
+
var HOOKS_OFF = "core.hooksPath=/dev/null";
|
|
17834
|
+
var SIGN_OFF = "commit.gpgSign=false";
|
|
17879
17835
|
var REPO_SCOPES = new Set(["local", "worktree", "command"]);
|
|
17880
|
-
var DRIVER_KEY_RE = "^(filter\\..*\\.(clean|smudge|process)|diff\\..*\\.textconv)$";
|
|
17836
|
+
var DRIVER_KEY_RE = "^(filter\\..*\\.(clean|smudge|process)|diff\\..*\\.(textconv|command))$";
|
|
17837
|
+
var PATCH_CMDS = new Set(["diff", "show", "log", "whatchanged"]);
|
|
17838
|
+
var OPT_WITH_VALUE = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env", "--attr-source"]);
|
|
17881
17839
|
var SECRET_VARS = ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN", "SSH_AUTH_SOCK"];
|
|
17882
17840
|
function tokenFreeEnv(env = process.env) {
|
|
17883
17841
|
const out2 = { ...env };
|
|
@@ -17888,6 +17846,7 @@ function tokenFreeEnv(env = process.env) {
|
|
|
17888
17846
|
var safeGitEnv = (base = process.env, allowToken = false) => {
|
|
17889
17847
|
const env = { ...allowToken ? base : tokenFreeEnv(base), GIT_CONFIG_NOSYSTEM: "1" };
|
|
17890
17848
|
delete env.GIT_ATTR_SOURCE;
|
|
17849
|
+
delete env.GIT_EXTERNAL_DIFF;
|
|
17891
17850
|
return env;
|
|
17892
17851
|
};
|
|
17893
17852
|
function driverKeys(repoDir) {
|
|
@@ -17907,62 +17866,253 @@ function driverKeys(repoDir) {
|
|
|
17907
17866
|
}
|
|
17908
17867
|
return [...new Set(keys)];
|
|
17909
17868
|
}
|
|
17910
|
-
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
...
|
|
17914
|
-
...
|
|
17915
|
-
];
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17869
|
+
function safeGitArgs(args, allowToken = false, repoDir, keep = {}) {
|
|
17870
|
+
if (keep.userHooks && allowToken)
|
|
17871
|
+
throw new Error("safeGit: userHooks with allowToken is refused (a hook would run holding the credential)");
|
|
17872
|
+
const off = new Set([...keep.repoDrivers ? [ATTRS_OFF] : [], ...keep.userHooks ? [HOOKS_OFF, SIGN_OFF] : []]);
|
|
17873
|
+
const cfg = [...allowToken ? CREDENTIAL_CONFIG : SAFE_GIT_CONFIG, ...DRIVER_CONFIG];
|
|
17874
|
+
const flags = cfg.flatMap((v, i) => i % 2 === 1 && !off.has(v) ? [cfg[i - 1], v] : []);
|
|
17875
|
+
return [...flags, ...repoDir && !keep.repoDrivers ? driverKeys(repoDir).flatMap((k) => ["-c", `${k}=`]) : [], ...noExtDiff(args)];
|
|
17876
|
+
}
|
|
17877
|
+
function noExtDiff(args) {
|
|
17878
|
+
let i = 0;
|
|
17879
|
+
while (i < args.length && args[i].startsWith("-"))
|
|
17880
|
+
i += OPT_WITH_VALUE.has(args[i]) ? 2 : 1;
|
|
17881
|
+
const cmd = args[i];
|
|
17882
|
+
if (cmd === undefined || !PATCH_CMDS.has(cmd))
|
|
17883
|
+
return [...args];
|
|
17884
|
+
const rest = args.slice(i + 1);
|
|
17885
|
+
const end = rest.indexOf("--");
|
|
17886
|
+
const opts = (end < 0 ? rest : rest.slice(0, end)).filter((a) => a !== "--ext-diff" && a !== "--textconv");
|
|
17887
|
+
return [...args.slice(0, i + 1), "--no-ext-diff", "--no-textconv", ...opts, ...end < 0 ? [] : rest.slice(end)];
|
|
17888
|
+
}
|
|
17889
|
+
function safeGit(repoDir, args, opts = {}) {
|
|
17890
|
+
const { env, allowToken, repoDrivers, userHooks, ...rest } = opts;
|
|
17891
|
+
const out2 = execFileSync4("git", safeGitArgs(args, allowToken, repoDir, { repoDrivers, userHooks }), { stdio: ["ignore", "pipe", "ignore"], ...rest, cwd: repoDir, env: safeGitEnv(env ?? process.env, allowToken), encoding: "utf8" });
|
|
17892
|
+
return typeof out2 === "string" ? out2 : "";
|
|
17893
|
+
}
|
|
17894
|
+
function safeGitSpawn(repoDir, args, opts = {}) {
|
|
17895
|
+
const { env, allowToken, repoDrivers, userHooks, ...rest } = opts;
|
|
17896
|
+
let argv;
|
|
17897
|
+
try {
|
|
17898
|
+
argv = safeGitArgs(args, allowToken, repoDir, { repoDrivers, userHooks });
|
|
17899
|
+
} catch (e) {
|
|
17900
|
+
return { pid: 0, output: [], stdout: "", stderr: "", status: null, signal: null, error: e };
|
|
17901
|
+
}
|
|
17902
|
+
return spawnSync("git", argv, { ...rest, cwd: repoDir, env: safeGitEnv(env ?? process.env, allowToken) });
|
|
17922
17903
|
}
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
17928
|
-
|
|
17929
|
-
|
|
17930
|
-
|
|
17931
|
-
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17904
|
+
async function safeGitRun(repoDir, args, opts = {}) {
|
|
17905
|
+
let argv;
|
|
17906
|
+
try {
|
|
17907
|
+
argv = safeGitArgs(args, opts.allowToken, repoDir, { repoDrivers: opts.repoDrivers, userHooks: opts.userHooks });
|
|
17908
|
+
} catch (e) {
|
|
17909
|
+
return { stdout: "", stderr: String(e), exitCode: 128 };
|
|
17910
|
+
}
|
|
17911
|
+
let proc;
|
|
17912
|
+
try {
|
|
17913
|
+
proc = Bun.spawn({ cmd: ["git", ...argv], cwd: repoDir, env: safeGitEnv(opts.env ?? process.env, opts.allowToken), stdin: "ignore", stdout: "pipe", stderr: "pipe", signal: opts.signal });
|
|
17914
|
+
} catch (e) {
|
|
17915
|
+
return { stdout: "", stderr: String(e), exitCode: 127 };
|
|
17916
|
+
}
|
|
17917
|
+
let timer;
|
|
17918
|
+
if (opts.timeoutMs && opts.timeoutMs > 0)
|
|
17919
|
+
timer = setTimeout(() => {
|
|
17920
|
+
try {
|
|
17921
|
+
proc.kill("SIGKILL");
|
|
17922
|
+
} catch {}
|
|
17923
|
+
}, opts.timeoutMs);
|
|
17924
|
+
try {
|
|
17925
|
+
const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
17926
|
+
return { stdout, stderr, exitCode };
|
|
17927
|
+
} finally {
|
|
17928
|
+
if (timer)
|
|
17929
|
+
clearTimeout(timer);
|
|
17930
|
+
}
|
|
17931
|
+
}
|
|
17932
|
+
|
|
17933
|
+
// ../../loki-ts/src/engine10/types.ts
|
|
17934
|
+
var STAGE_BUDGETS = {
|
|
17935
|
+
intake: { targetS: 15, limitS: 60 },
|
|
17936
|
+
plan: { targetS: 45, limitS: 90 },
|
|
17937
|
+
wall: { targetS: 45, limitS: 90 },
|
|
17938
|
+
implement: { targetS: 180, limitS: 480 },
|
|
17939
|
+
verify: { targetS: 60, limitS: 120 },
|
|
17940
|
+
fix: { targetS: 90, limitS: 180 },
|
|
17941
|
+
commit: { targetS: 5, limitS: 30 },
|
|
17943
17942
|
seal: { targetS: 15, limitS: 60 },
|
|
17944
17943
|
pr: { targetS: 15, limitS: 60 },
|
|
17945
17944
|
deep: { targetS: null, limitS: 2700 }
|
|
17946
17945
|
};
|
|
17947
17946
|
|
|
17947
|
+
// ../../loki-ts/src/util/run_cap.ts
|
|
17948
|
+
var MAX_SCOPE_BYTES = 256 * 1024;
|
|
17949
|
+
|
|
17950
|
+
// ../../loki-ts/src/util/mutation_proof.ts
|
|
17951
|
+
var mutationStrict = (env = process.env) => (env["LOKI_MUTATION_STRICT"] ?? "").trim() === "1";
|
|
17952
|
+
var mutationEnabled = (env = process.env) => (env["LOKI_MUTATION_PROOF"] ?? "").trim() !== "0";
|
|
17953
|
+
var LABEL = "test fails without the fix: ";
|
|
17954
|
+
var np = (reason) => ({ outcome: "inconclusive", line: `${LABEL}inconclusive (${reason})` });
|
|
17955
|
+
var MAX_TOTAL_S = 40;
|
|
17956
|
+
var DEP_DIRS = ["node_modules", ".venv", "venv"];
|
|
17957
|
+
var git = (cwd, args, timeout = 30000) => {
|
|
17958
|
+
try {
|
|
17959
|
+
safeGit(cwd, args, { timeout });
|
|
17960
|
+
return { status: 0 };
|
|
17961
|
+
} catch {
|
|
17962
|
+
return { status: 1 };
|
|
17963
|
+
}
|
|
17964
|
+
};
|
|
17965
|
+
function mutationProof(i) {
|
|
17966
|
+
const rels = i.wallFiles.map((f) => isAbsolute(f.path) ? relative(i.repoDir, f.path) : f.path);
|
|
17967
|
+
if (rels.length === 0)
|
|
17968
|
+
return np("no Wall tests");
|
|
17969
|
+
const tests = rels.flatMap((rel) => {
|
|
17970
|
+
const c = i.checks.find((k) => k.name.endsWith(`:${rel}`));
|
|
17971
|
+
const idx = c ? c.name.indexOf(":") : -1;
|
|
17972
|
+
return c && idx > 0 ? [{ runner: c.name.slice(0, idx), path: rel }] : [];
|
|
17973
|
+
});
|
|
17974
|
+
if (tests.length === 0)
|
|
17975
|
+
return np("no recorded Wall test command");
|
|
17976
|
+
if (tests.length !== rels.length)
|
|
17977
|
+
return np("a Wall file has no recorded test command");
|
|
17978
|
+
const budgetS = Math.min(i.timeoutS ?? (Number((i.env ?? process.env)["LOKI_MUTATION_PROOF_TIMEOUT_S"] ?? 30) || 30), MAX_TOTAL_S);
|
|
17979
|
+
const deadline = Date.now() + budgetS * 1000, remaining = () => deadline - Date.now();
|
|
17980
|
+
let parent = null, wt = null, added = false;
|
|
17981
|
+
try {
|
|
17982
|
+
parent = mkdtempSync2(join26(tmpdir3(), "loki-mutproof-"));
|
|
17983
|
+
wt = join26(parent, "wt");
|
|
17984
|
+
const add2 = git(i.repoDir, ["worktree", "add", "--detach", wt, i.baseSha], Math.max(1000, Math.min(30000, remaining())));
|
|
17985
|
+
added = existsSync15(wt) || add2.status === 0;
|
|
17986
|
+
if (add2.status !== 0)
|
|
17987
|
+
return np("could not create the base worktree");
|
|
17988
|
+
for (const rel of rels) {
|
|
17989
|
+
const sealed = join26(i.runDir, "wall", basename8(rel)), src = existsSync15(sealed) ? sealed : join26(i.repoDir, rel);
|
|
17990
|
+
if (!existsSync15(src))
|
|
17991
|
+
return np(`Wall file missing: ${rel}`);
|
|
17992
|
+
mkdirSync5(dirname7(join26(wt, rel)), { recursive: true });
|
|
17993
|
+
writeFileSync8(join26(wt, rel), readFileSync18(src));
|
|
17994
|
+
}
|
|
17995
|
+
for (const rel of rels) {
|
|
17996
|
+
const parts = rel.split("/").slice(0, -1);
|
|
17997
|
+
for (let n2 = 0;n2 <= parts.length; n2++) {
|
|
17998
|
+
const anc = parts.slice(0, n2).join("/");
|
|
17999
|
+
for (const d of DEP_DIRS) {
|
|
18000
|
+
const src = join26(i.repoDir, anc, d), dst = join26(wt, anc, d);
|
|
18001
|
+
if (existsSync15(src) && !existsSync15(dst)) {
|
|
18002
|
+
try {
|
|
18003
|
+
symlinkSync(src, dst);
|
|
18004
|
+
} catch {}
|
|
18005
|
+
}
|
|
18006
|
+
}
|
|
18007
|
+
}
|
|
18008
|
+
}
|
|
18009
|
+
let pass = 0, fail = 0, notRun = 0;
|
|
18010
|
+
for (const t of tests) {
|
|
18011
|
+
if (remaining() <= 0)
|
|
18012
|
+
return np("timeout");
|
|
18013
|
+
const r = (typeof i.runner === "function" ? i.runner(remaining()) : i.runner).run(wt, [t]);
|
|
18014
|
+
pass += r.pass;
|
|
18015
|
+
fail += r.fail;
|
|
18016
|
+
notRun += r.not_run ?? 0;
|
|
18017
|
+
}
|
|
18018
|
+
if (remaining() <= 0 && fail === 0)
|
|
18019
|
+
return np("timeout");
|
|
18020
|
+
if (fail > 0)
|
|
18021
|
+
return { outcome: "yes", line: `${LABEL}yes` };
|
|
18022
|
+
if (pass > 0 && notRun === 0)
|
|
18023
|
+
return { outcome: "no", line: `${LABEL}no (warning: the Wall tests also pass on the old code)` };
|
|
18024
|
+
return np("the Wall command did not execute to a result");
|
|
18025
|
+
} catch (e) {
|
|
18026
|
+
return np(`error: ${String(e?.message ?? e).replace(/\s+/g, " ").slice(0, 120)}`);
|
|
18027
|
+
} finally {
|
|
18028
|
+
if (wt && added)
|
|
18029
|
+
git(i.repoDir, ["worktree", "remove", "--force", wt]);
|
|
18030
|
+
if (parent)
|
|
18031
|
+
rmSync4(parent, { recursive: true, force: true });
|
|
18032
|
+
}
|
|
18033
|
+
}
|
|
18034
|
+
|
|
18035
|
+
// ../../loki-ts/src/engine10/stages/wall.ts
|
|
18036
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
18037
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync6, readFileSync as readFileSync25, readdirSync as readdirSync11, realpathSync as realpathSync17, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "fs";
|
|
18038
|
+
import { dirname as dirname11, isAbsolute as isAbsolute5, join as join39, relative as relative5 } from "path";
|
|
18039
|
+
init_lean_prefix();
|
|
18040
|
+
|
|
18041
|
+
// ../../loki-ts/src/features/wall_manifest.ts
|
|
18042
|
+
var MODS = "(?:(?:public|private|protected|static|readonly|abstract|async|get|set|declare|override)\\s+)*";
|
|
18043
|
+
var MEMBER_GRAMMAR = new RegExp(`^(?:@[\\w$.]+(?:\\([^)]*\\))?\\s*)*${MODS}\\*?\\s*(?:[A-Za-z_$][\\w$]*|#[A-Za-z_$][\\w$]*|\\[[^\\]]*\\])\\s*[?!]?\\s*(?:[(<:=]|$)`);
|
|
18044
|
+
// ../../loki-ts/src/engine10/sizing.ts
|
|
18045
|
+
import { existsSync as existsSync17, readFileSync as readFileSync21 } from "fs";
|
|
18046
|
+
import { basename as basename9 } from "path";
|
|
18047
|
+
|
|
18048
|
+
// ../../loki-ts/src/engine10/cache.ts
|
|
18049
|
+
import { createHash as createHash6 } from "crypto";
|
|
18050
|
+
init_paths();
|
|
18051
|
+
import { resolve as resolve8 } from "path";
|
|
18052
|
+
function repoKey(originUrl, repoDir) {
|
|
18053
|
+
const basis = originUrl && originUrl.trim() !== "" ? originUrl : resolve8(repoDir);
|
|
18054
|
+
return createHash6("sha256").update(basis).digest("hex");
|
|
18055
|
+
}
|
|
18056
|
+
function defaultCacheRoot() {
|
|
18057
|
+
return resolve8(homeLokiDir(), "cache", "v10");
|
|
18058
|
+
}
|
|
18059
|
+
function repoCacheDir(key, cacheRoot = defaultCacheRoot()) {
|
|
18060
|
+
return resolve8(cacheRoot, key);
|
|
18061
|
+
}
|
|
18062
|
+
|
|
18063
|
+
// ../../loki-ts/src/engine10/sizing.ts
|
|
18064
|
+
init_claude_model();
|
|
18065
|
+
|
|
18066
|
+
// ../../loki-ts/src/engine10/session.ts
|
|
18067
|
+
init_cost();
|
|
18068
|
+
init_budget();
|
|
18069
|
+
var STDERR_TAIL_BYTES = 64 * 1024;
|
|
18070
|
+
var ADVISOR_MARKED_STAGES = new Set(["plan", "fix"]);
|
|
18071
|
+
|
|
18072
|
+
// ../../loki-ts/src/engine10/sizing.ts
|
|
18073
|
+
function namedFiles(task, map) {
|
|
18074
|
+
return (map?.files ?? []).filter((f) => task.toLowerCase().includes(basename9(f).toLowerCase()));
|
|
18075
|
+
}
|
|
18076
|
+
function loadRepoMap(repomapRef) {
|
|
18077
|
+
if (!repomapRef || !existsSync17(repomapRef))
|
|
18078
|
+
return null;
|
|
18079
|
+
try {
|
|
18080
|
+
return JSON.parse(readFileSync21(repomapRef, "utf8"));
|
|
18081
|
+
} catch {
|
|
18082
|
+
return null;
|
|
18083
|
+
}
|
|
18084
|
+
}
|
|
18085
|
+
|
|
18086
|
+
// ../../loki-ts/src/project_model/resolve.ts
|
|
18087
|
+
import { join as join33 } from "path";
|
|
18088
|
+
|
|
18089
|
+
// ../../loki-ts/src/project_model/discover.ts
|
|
18090
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync23, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "fs";
|
|
18091
|
+
import { join as join32 } from "path";
|
|
18092
|
+
|
|
18093
|
+
// ../../loki-ts/src/project_model/gather.ts
|
|
18094
|
+
import { createHash as createHash7 } from "crypto";
|
|
18095
|
+
import { closeSync as closeSync5, lstatSync as lstatSync5, openSync as openSync5, readFileSync as readFileSync22, readSync as readSync4 } from "fs";
|
|
18096
|
+
import { dirname as dirname9, join as join31 } from "path";
|
|
18097
|
+
|
|
17948
18098
|
// ../../loki-ts/src/project_model/schema.ts
|
|
17949
|
-
import { existsSync as
|
|
17950
|
-
import { isAbsolute, join as
|
|
18099
|
+
import { existsSync as existsSync18, lstatSync as lstatSync4, realpathSync as realpathSync14, statSync as statSync11 } from "fs";
|
|
18100
|
+
import { isAbsolute as isAbsolute2, join as join30, normalize, sep as sep9 } from "path";
|
|
17951
18101
|
var PROJECT_MODEL_SCHEMA = "loki.v10.project/1";
|
|
17952
18102
|
var COMMAND_KINDS = ["test", "lint", "build", "start"];
|
|
17953
18103
|
var isRec = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17954
18104
|
var isStr = (v) => typeof v === "string" && v.trim() !== "";
|
|
17955
18105
|
function safeRel(p) {
|
|
17956
|
-
const n2 = normalize(p.trim()).split(
|
|
17957
|
-
if (n2 === "" ||
|
|
18106
|
+
const n2 = normalize(p.trim()).split(sep9).join("/").replace(/\/$/, "");
|
|
18107
|
+
if (n2 === "" || isAbsolute2(n2) || n2 === ".." || n2.startsWith("../"))
|
|
17958
18108
|
return null;
|
|
17959
18109
|
return n2;
|
|
17960
18110
|
}
|
|
17961
18111
|
function insideRepo(repoDir, rel) {
|
|
17962
18112
|
try {
|
|
17963
|
-
const real2 =
|
|
17964
|
-
const base =
|
|
17965
|
-
return real2 === base || real2.startsWith(base +
|
|
18113
|
+
const real2 = realpathSync14(join30(repoDir, rel));
|
|
18114
|
+
const base = realpathSync14(repoDir);
|
|
18115
|
+
return real2 === base || real2.startsWith(base + sep9);
|
|
17966
18116
|
} catch {
|
|
17967
18117
|
return false;
|
|
17968
18118
|
}
|
|
@@ -17973,8 +18123,8 @@ function checkCitation(repoDir, raw2) {
|
|
|
17973
18123
|
const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
|
|
17974
18124
|
if (rel === null)
|
|
17975
18125
|
return `citation "${raw2}" is not a repo-relative path`;
|
|
17976
|
-
const abs =
|
|
17977
|
-
if (!
|
|
18126
|
+
const abs = join30(repoDir, rel);
|
|
18127
|
+
if (!existsSync18(abs) || !statSync11(abs).isFile() || !insideRepo(repoDir, rel))
|
|
17978
18128
|
return `citation "${raw2}" names a file that does not exist`;
|
|
17979
18129
|
return null;
|
|
17980
18130
|
}
|
|
@@ -17985,7 +18135,7 @@ function checkFingerprint(repoDir, raw2) {
|
|
|
17985
18135
|
if (rel === null)
|
|
17986
18136
|
return `fingerprint "${raw2}" is not a repo-relative path`;
|
|
17987
18137
|
try {
|
|
17988
|
-
if (!
|
|
18138
|
+
if (!lstatSync4(join30(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
|
|
17989
18139
|
return `fingerprint "${raw2}" is not a regular file inside the repo`;
|
|
17990
18140
|
} catch {
|
|
17991
18141
|
return `fingerprint "${raw2}" names a file that does not exist`;
|
|
@@ -18013,8 +18163,8 @@ function checkDir(repoDir, v, where, errs) {
|
|
|
18013
18163
|
errs.push(`${where}: must be a repo-relative directory ("." for the repo root)`);
|
|
18014
18164
|
return ".";
|
|
18015
18165
|
}
|
|
18016
|
-
const abs =
|
|
18017
|
-
if (!
|
|
18166
|
+
const abs = join30(repoDir, rel);
|
|
18167
|
+
if (!existsSync18(abs) || !statSync11(abs).isDirectory() || !insideRepo(repoDir, rel))
|
|
18018
18168
|
errs.push(`${where}: directory "${rel}" does not exist`);
|
|
18019
18169
|
return rel;
|
|
18020
18170
|
}
|
|
@@ -18121,6 +18271,62 @@ var GATHER_CAPS = {
|
|
|
18121
18271
|
maxTreeLines: 300,
|
|
18122
18272
|
maxHashBytes: 16 * 1024 * 1024
|
|
18123
18273
|
};
|
|
18274
|
+
var depthOf = (p) => p.split("/").length;
|
|
18275
|
+
function listShallow(repoDir) {
|
|
18276
|
+
try {
|
|
18277
|
+
const out2 = safeGit(repoDir, ["ls-files", "-z"], { maxBuffer: 256 * 1024 * 1024 });
|
|
18278
|
+
return out2.split("\x00").filter((p) => p !== "").slice(0, GATHER_CAPS.maxListFiles).filter((p) => depthOf(p) <= GATHER_CAPS.maxDepth);
|
|
18279
|
+
} catch {
|
|
18280
|
+
return [];
|
|
18281
|
+
}
|
|
18282
|
+
}
|
|
18283
|
+
function shallowDirs(repoDir) {
|
|
18284
|
+
return [...new Set(listShallow(repoDir).map((p) => dirname9(p)))].sort();
|
|
18285
|
+
}
|
|
18286
|
+
var MODEL_REV = "model-rev:2";
|
|
18287
|
+
function computeKey(repoDir, fingerprintFiles, dirs, committedHash = null) {
|
|
18288
|
+
const h = createHash7("sha256");
|
|
18289
|
+
h.update(`${MODEL_REV}
|
|
18290
|
+
`);
|
|
18291
|
+
if (committedHash !== null)
|
|
18292
|
+
h.update(`committed:${committedHash}
|
|
18293
|
+
`);
|
|
18294
|
+
for (const f of [...fingerprintFiles].sort()) {
|
|
18295
|
+
h.update(`file:${f}
|
|
18296
|
+
`);
|
|
18297
|
+
try {
|
|
18298
|
+
if (checkFingerprint(repoDir, f) !== null)
|
|
18299
|
+
h.update("invalid");
|
|
18300
|
+
else {
|
|
18301
|
+
const abs = join31(repoDir, f);
|
|
18302
|
+
const size = lstatSync5(abs).size;
|
|
18303
|
+
h.update(size <= GATHER_CAPS.maxHashBytes ? readFileSync22(abs) : `too-large:${size}`);
|
|
18304
|
+
}
|
|
18305
|
+
} catch {
|
|
18306
|
+
h.update("missing");
|
|
18307
|
+
}
|
|
18308
|
+
h.update(`
|
|
18309
|
+
`);
|
|
18310
|
+
}
|
|
18311
|
+
for (const d of dirs)
|
|
18312
|
+
h.update(`dir:${d}
|
|
18313
|
+
`);
|
|
18314
|
+
return h.digest("hex");
|
|
18315
|
+
}
|
|
18316
|
+
function isGitTracked(repoDir, relPath) {
|
|
18317
|
+
try {
|
|
18318
|
+
const out2 = safeGit(repoDir, ["ls-files", "--error-unmatch", "--", relPath], { timeout: 1e4 });
|
|
18319
|
+
return out2.trim() !== "";
|
|
18320
|
+
} catch {
|
|
18321
|
+
return false;
|
|
18322
|
+
}
|
|
18323
|
+
}
|
|
18324
|
+
function committedModelHash(raw2) {
|
|
18325
|
+
if (typeof raw2 !== "object" || raw2 === null || Array.isArray(raw2))
|
|
18326
|
+
return null;
|
|
18327
|
+
const { key: _key, expiresAt: _exp, ...rest } = raw2;
|
|
18328
|
+
return createHash7("sha256").update(JSON.stringify(rest)).digest("hex");
|
|
18329
|
+
}
|
|
18124
18330
|
|
|
18125
18331
|
// ../../loki-ts/src/project_model/discover.ts
|
|
18126
18332
|
function projectModelEnabled(env = process.env) {
|
|
@@ -18130,11 +18336,36 @@ var PROJECT_FILE = ".loki/project.json";
|
|
|
18130
18336
|
var UNKNOWN_TTL_MS = 60 * 60 * 1000;
|
|
18131
18337
|
function loadCached(repoDir, now2 = Date.now()) {
|
|
18132
18338
|
try {
|
|
18133
|
-
return parseCached(repoDir, JSON.parse(
|
|
18339
|
+
return parseCached(repoDir, JSON.parse(readFileSync23(join32(repoDir, PROJECT_FILE), "utf8")), now2);
|
|
18134
18340
|
} catch {
|
|
18135
18341
|
return null;
|
|
18136
18342
|
}
|
|
18137
18343
|
}
|
|
18344
|
+
function loadCommitted(repoDir) {
|
|
18345
|
+
if (!isGitTracked(repoDir, PROJECT_FILE))
|
|
18346
|
+
return null;
|
|
18347
|
+
const ignore = (why) => {
|
|
18348
|
+
process.stderr.write(`loki: ignoring committed ${PROJECT_FILE}: ${why}; falling back to discovery
|
|
18349
|
+
`);
|
|
18350
|
+
return null;
|
|
18351
|
+
};
|
|
18352
|
+
let raw2;
|
|
18353
|
+
try {
|
|
18354
|
+
raw2 = JSON.parse(readFileSync23(join32(repoDir, PROJECT_FILE), "utf8"));
|
|
18355
|
+
} catch (e) {
|
|
18356
|
+
return ignore(`unreadable JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
18357
|
+
}
|
|
18358
|
+
const hash = committedModelHash(raw2);
|
|
18359
|
+
if (hash === null)
|
|
18360
|
+
return ignore("not a JSON object");
|
|
18361
|
+
const r = raw2;
|
|
18362
|
+
if (r["schema"] !== PROJECT_MODEL_SCHEMA || r["status"] !== "ok")
|
|
18363
|
+
return ignore(`needs schema "${PROJECT_MODEL_SCHEMA}" and status "ok"`);
|
|
18364
|
+
const v = validateAnswer(repoDir, raw2);
|
|
18365
|
+
if (!v.ok)
|
|
18366
|
+
return ignore(v.errors.slice(0, 3).join("; "));
|
|
18367
|
+
return { model: { ...v.model, key: "" }, hash };
|
|
18368
|
+
}
|
|
18138
18369
|
|
|
18139
18370
|
// ../../loki-ts/src/project_model/api.ts
|
|
18140
18371
|
import { posix } from "path";
|
|
@@ -18201,48 +18432,1235 @@ function loadProjectApi(repoDir, env = process.env) {
|
|
|
18201
18432
|
function isMultiRoot(api) {
|
|
18202
18433
|
return !!api && api.known() && api.packages().some((p) => p.root !== ".");
|
|
18203
18434
|
}
|
|
18435
|
+
function siteFor(api, repoDir, file) {
|
|
18436
|
+
if (!isMultiRoot(api))
|
|
18437
|
+
return null;
|
|
18438
|
+
const pkg = api.packageOf(file);
|
|
18439
|
+
const rel = api.relativeToRoot(file);
|
|
18440
|
+
if (!pkg || pkg.root === "." || rel === null)
|
|
18441
|
+
return null;
|
|
18442
|
+
return { pkg, root: pkg.root, cwd: join33(repoDir, pkg.root), file: rel };
|
|
18443
|
+
}
|
|
18444
|
+
function groupByPackage(api, repoDir, files) {
|
|
18445
|
+
const out2 = new Map;
|
|
18446
|
+
for (const f of files) {
|
|
18447
|
+
const s = siteFor(api, repoDir, f);
|
|
18448
|
+
const root = s?.root ?? ".";
|
|
18449
|
+
const g = out2.get(root) ?? { root, cwd: s?.cwd ?? repoDir, files: [], rel: [] };
|
|
18450
|
+
g.files.push(f);
|
|
18451
|
+
g.rel.push(s?.file ?? f);
|
|
18452
|
+
out2.set(root, g);
|
|
18453
|
+
}
|
|
18454
|
+
return [...out2.values()];
|
|
18455
|
+
}
|
|
18204
18456
|
|
|
18205
18457
|
// ../../loki-ts/src/project_model/scope.ts
|
|
18206
18458
|
var apis = new WeakMap;
|
|
18207
18459
|
var imports = new WeakMap;
|
|
18208
18460
|
var dropped = new WeakMap;
|
|
18461
|
+
function scopedOutOf(map) {
|
|
18462
|
+
return [...dropped.get(map) ?? []].sort();
|
|
18463
|
+
}
|
|
18209
18464
|
|
|
18210
|
-
// ../../loki-ts/src/engine10/
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
]
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18465
|
+
// ../../loki-ts/src/engine10/stages/verify.ts
|
|
18466
|
+
import { existsSync as existsSync20, mkdtempSync as mkdtempSync5, realpathSync as realpathSync16, rmSync as rmSync8 } from "fs";
|
|
18467
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
18468
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, join as join38, relative as relative4 } from "path";
|
|
18469
|
+
|
|
18470
|
+
// ../../loki-ts/src/e10ext/assert_delta.ts
|
|
18471
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
18472
|
+
import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync24, rmSync as rmSync6 } from "fs";
|
|
18473
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
18474
|
+
import { join as join34 } from "path";
|
|
18475
|
+
import { basename as basename10 } from "path";
|
|
18476
|
+
var WEAKENED = { verdict: "weakened", changes: [] };
|
|
18477
|
+
var PY = String.raw`
|
|
18478
|
+
import ast, copy, io, json, re, sys, tokenize
|
|
18479
|
+
d = json.load(sys.stdin)
|
|
18480
|
+
bs, hs, task = d["base"], d["head"], d["task"]
|
|
18481
|
+
def out(v, it=()):
|
|
18482
|
+
print(json.dumps({"verdict": v, "items": list(it)}))
|
|
18483
|
+
sys.exit(0)
|
|
18484
|
+
if "\r" in bs or "\r" in hs or "\t" in bs or "\t" in hs:
|
|
18485
|
+
out("weakened")
|
|
18486
|
+
bt, ht = ast.parse(bs), ast.parse(hs)
|
|
18487
|
+
# every byte outside a literal token (comments, blank lines, spacing, other code) must be identical
|
|
18488
|
+
def template(src):
|
|
18489
|
+
lines = src.split("\n")
|
|
18490
|
+
starts, o = [], 0
|
|
18491
|
+
for ln in lines:
|
|
18492
|
+
starts.append(o)
|
|
18493
|
+
o += len(ln) + 1
|
|
18494
|
+
spans = [(starts[t.start[0] - 1] + t.start[1], starts[t.end[0] - 1] + t.end[1])
|
|
18495
|
+
for t in tokenize.generate_tokens(io.StringIO(src).readline) if t.type in (tokenize.STRING, tokenize.NUMBER)]
|
|
18496
|
+
r, pos = [], 0
|
|
18497
|
+
for a, b in spans:
|
|
18498
|
+
r.append(src[pos:a] + "\0")
|
|
18499
|
+
pos = b
|
|
18500
|
+
return "".join(r) + src[pos:]
|
|
18501
|
+
if template(bs) != template(hs):
|
|
18502
|
+
out("weakened")
|
|
18503
|
+
class Norm(ast.NodeTransformer):
|
|
18504
|
+
def visit_Constant(self, n):
|
|
18505
|
+
return ast.copy_location(ast.Constant(value="<" + type(n.value).__name__ + ">"), n)
|
|
18506
|
+
def norm(t):
|
|
18507
|
+
return ast.dump(Norm().visit(copy.deepcopy(t)))
|
|
18508
|
+
# only what pytest collects: module-level test functions and methods of Test* classes without __init__
|
|
18509
|
+
def tests(t):
|
|
18510
|
+
r = []
|
|
18511
|
+
for n in t.body:
|
|
18512
|
+
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name.startswith("test"):
|
|
18513
|
+
r.append(n)
|
|
18514
|
+
elif isinstance(n, ast.ClassDef) and n.name.startswith("Test") \
|
|
18515
|
+
and not any(isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and m.name == "__init__" for m in n.body):
|
|
18516
|
+
r += [m for m in n.body if isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef)) and m.name.startswith("test")]
|
|
18517
|
+
return r
|
|
18518
|
+
def direct(f):
|
|
18519
|
+
return [a for a in f.body if isinstance(a, ast.Assert)]
|
|
18520
|
+
def tables(t):
|
|
18521
|
+
return [n.args[1] for n in ast.walk(t) if isinstance(n, ast.Call) and getattr(n.func, "attr", "") == "parametrize"
|
|
18522
|
+
and len(n.args) > 1 and isinstance(n.args[1], (ast.List, ast.Tuple))]
|
|
18523
|
+
def stripped(t):
|
|
18524
|
+
c = copy.deepcopy(t)
|
|
18525
|
+
for tb in tables(c):
|
|
18526
|
+
tb.elts = []
|
|
18527
|
+
return c
|
|
18528
|
+
def consts(t):
|
|
18529
|
+
return [n for n in ast.walk(t) if isinstance(n, ast.Constant)]
|
|
18530
|
+
def shadowed(t):
|
|
18531
|
+
for scope in [t] + [n for n in t.body if isinstance(n, ast.ClassDef)]:
|
|
18532
|
+
names = [n.name for n in scope.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name.startswith("test")]
|
|
18533
|
+
if len(names) != len(set(names)):
|
|
18534
|
+
return True
|
|
18535
|
+
return False
|
|
18536
|
+
if shadowed(bt) or shadowed(ht):
|
|
18537
|
+
out("weakened")
|
|
18538
|
+
sb, sh = stripped(bt), stripped(ht)
|
|
18539
|
+
if not tests(ht) or norm(sb) != norm(sh):
|
|
18540
|
+
out("weakened")
|
|
18541
|
+
for t in (bt, ht):
|
|
18542
|
+
if any(isinstance(n, ast.Call) and isinstance(n.func, ast.Name) and n.func.id == "round" for n in ast.walk(t)):
|
|
18543
|
+
out("weakened")
|
|
18544
|
+
tb, th = tables(bt), tables(ht)
|
|
18545
|
+
if any(len(x.elts) != len(y.elts) for x, y in zip(tb, th)):
|
|
18546
|
+
out("weakened")
|
|
18547
|
+
if any(norm(r) != norm(q) for x, y in zip(tb, th) for r, q in zip(x.elts, y.elts)):
|
|
18548
|
+
out("weakened")
|
|
18549
|
+
owner = {}
|
|
18550
|
+
for f in tests(sh):
|
|
18551
|
+
for n in ast.walk(f):
|
|
18552
|
+
owner[id(n)] = f
|
|
18553
|
+
pairs = [(b, h, owner.get(id(h))) for b, h in zip(consts(sb), consts(sh))]
|
|
18554
|
+
fh = {}
|
|
18555
|
+
for f in tests(ht):
|
|
18556
|
+
for t in tables(f):
|
|
18557
|
+
fh[id(t)] = f
|
|
18558
|
+
for x, y in zip(tb, th):
|
|
18559
|
+
for r, q in zip(x.elts, y.elts):
|
|
18560
|
+
pairs += [(b, h, fh.get(id(y))) for b, h in zip(consts(r), consts(q))]
|
|
18561
|
+
def eqs(f):
|
|
18562
|
+
return [a.test for a in direct(f) if isinstance(a.test, ast.Compare) and len(a.test.ops) == 1 and isinstance(a.test.ops[0], ast.Eq)]
|
|
18563
|
+
def dups(t):
|
|
18564
|
+
r = {}
|
|
18565
|
+
for f in tests(t):
|
|
18566
|
+
ds = [ast.dump(a) for a in direct(f)]
|
|
18567
|
+
r[f.name] = len(ds) - len(set(ds))
|
|
18568
|
+
return r
|
|
18569
|
+
dh, db = dups(ht), dups(bt)
|
|
18570
|
+
ok = not any(v > db.get(k, 0) for k, v in dh.items())
|
|
18571
|
+
# expected side = the right operand of a direct "assert X == Y"
|
|
18572
|
+
exp = set()
|
|
18573
|
+
for f in tests(sh):
|
|
18574
|
+
exp |= {id(c.comparators[0]) for c in eqs(f) if isinstance(c.comparators[0], ast.Constant)}
|
|
18575
|
+
for f in tests(ht):
|
|
18576
|
+
rights = {id(c.comparators[0]) for c in eqs(f) if isinstance(c.comparators[0], ast.Name)}
|
|
18577
|
+
for c in f.decorator_list:
|
|
18578
|
+
if isinstance(c, ast.Call) and getattr(c.func, "attr", "") == "parametrize" and len(c.args) > 1 \
|
|
18579
|
+
and isinstance(c.args[0], ast.Constant) and isinstance(c.args[0].value, str) and isinstance(c.args[1], (ast.List, ast.Tuple)):
|
|
18580
|
+
cols = [x.strip() for x in c.args[0].value.split(",")]
|
|
18581
|
+
# a column counts only if every use of its name in the body is the right operand of a direct eq assert
|
|
18582
|
+
if any(kw.arg == "indirect" for kw in c.keywords):
|
|
18583
|
+
continue
|
|
18584
|
+
deco = {id(m) for dd in f.decorator_list for m in ast.walk(dd)}
|
|
18585
|
+
uses = {k: [n for n in ast.walk(f) if isinstance(n, ast.Name) and n.id == k and id(n) not in deco] for k in cols}
|
|
18586
|
+
# the column must be read at least once, and only as the right operand of a direct eq assert
|
|
18587
|
+
solo = {k for k, u in uses.items() if u and all(id(n) in rights for n in u)}
|
|
18588
|
+
for r in c.args[1].elts:
|
|
18589
|
+
for i, e in enumerate(r.elts if isinstance(r, (ast.Tuple, ast.List)) else [r]):
|
|
18590
|
+
if isinstance(e, ast.Constant) and i < len(cols) and cols[i] in solo:
|
|
18591
|
+
exp.add(id(e))
|
|
18592
|
+
def callees(f):
|
|
18593
|
+
r = set()
|
|
18594
|
+
for c in eqs(f):
|
|
18595
|
+
for n in ast.walk(c.left):
|
|
18596
|
+
if isinstance(n, ast.Call):
|
|
18597
|
+
k = n.func.id if isinstance(n.func, ast.Name) else getattr(n.func, "attr", "")
|
|
18598
|
+
if k:
|
|
18599
|
+
r.add(k)
|
|
18600
|
+
return r
|
|
18601
|
+
def generic(v):
|
|
18602
|
+
return v is None or isinstance(v, (bool, bytes)) or (isinstance(v, str) and len(v) <= 3) \
|
|
18603
|
+
or (isinstance(v, (int, float)) and abs(v) < 10)
|
|
18604
|
+
def in_task(v, f):
|
|
18605
|
+
task0 = d["task"]
|
|
18606
|
+
t = v if isinstance(v, str) else repr(v)
|
|
18607
|
+
if t == "":
|
|
18608
|
+
return False
|
|
18609
|
+
# bounded by whitespace, string ends or ,;:()[]{}"' only; never glued to - + or a digit group separator
|
|
18610
|
+
tok = r"(?<![^\s,;:()\[\]{}\"'])(?<!\d,)" + re.escape(t) + r"(?:(?=[\s,;:()\[\]{}\"']|$)(?!,\d)|\.(?=\s|$))"
|
|
18611
|
+
task = re.sub(r"(?<=\d),(?=\d{3}\b)", "", task0)
|
|
18612
|
+
if not generic(v):
|
|
18613
|
+
return re.search(tok, task) is not None
|
|
18614
|
+
# a generic value counts only right next to the identifier the test calls
|
|
18615
|
+
return any(re.search(r"\b" + re.escape(k) + r"\b[^\n]{0,40}?" + tok, task) for k in (callees(f) if f else ()))
|
|
18616
|
+
items = []
|
|
18617
|
+
for b, h, f in pairs:
|
|
18618
|
+
if (type(b.value), repr(b.value)) != (type(h.value), repr(h.value)):
|
|
18619
|
+
hit = in_task(h.value, f)
|
|
18620
|
+
ok = ok and id(h) in exp and hit
|
|
18621
|
+
items.append({"line": h.lineno, "old": repr(b.value), "new": repr(h.value), "inTask": hit, "matched": "new"})
|
|
18622
|
+
out("value-change" if ok and items else "weakened", items if ok else [])
|
|
18623
|
+
`;
|
|
18624
|
+
var okStr = (v) => typeof v === "string" && v.length <= 200 && !/[\x00-\x1f\x7f]/.test(v);
|
|
18625
|
+
function classifyAssertDelta(inp) {
|
|
18626
|
+
const f = basename10(inp.path);
|
|
18627
|
+
if (!/^(test_.*|.*_test)\.py$/.test(f))
|
|
18628
|
+
return WEAKENED;
|
|
18629
|
+
if (inp.headCounts.run !== inp.baseCounts.run || inp.headCounts.skipped !== inp.baseCounts.skipped)
|
|
18630
|
+
return WEAKENED;
|
|
18631
|
+
let cwd = "";
|
|
18632
|
+
try {
|
|
18633
|
+
cwd = mkdtempSync3(join34(tmpdir4(), "loki-ad-"));
|
|
18634
|
+
const raw2 = execFileSync5("python3", ["-I", "-c", PY], {
|
|
18635
|
+
input: JSON.stringify({ path: inp.path, base: inp.base, head: inp.head, task: inp.task }),
|
|
18636
|
+
cwd,
|
|
18637
|
+
env: process.env,
|
|
18638
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
18639
|
+
encoding: "utf8",
|
|
18640
|
+
timeout: 20000
|
|
18641
|
+
});
|
|
18642
|
+
const r = JSON.parse(raw2);
|
|
18643
|
+
if (r.verdict !== "value-change" || !Array.isArray(r.items) || r.items.length === 0)
|
|
18644
|
+
return WEAKENED;
|
|
18645
|
+
const items = [];
|
|
18646
|
+
for (const i of r.items) {
|
|
18647
|
+
if (!i || typeof i !== "object" || !Number.isInteger(i["line"]) || i["line"] < 1 || !okStr(i["old"]) || !okStr(i["new"]) || i["inTask"] !== true || i["matched"] !== "new")
|
|
18648
|
+
return WEAKENED;
|
|
18649
|
+
items.push({ line: i["line"], old: i["old"], new: i["new"], matched: "new" });
|
|
18650
|
+
}
|
|
18651
|
+
return { verdict: "value-change", changes: items.map((i) => `${inp.path}:${i.line} ${i.old} -> ${i.new}`), items };
|
|
18652
|
+
} catch {
|
|
18653
|
+
return WEAKENED;
|
|
18654
|
+
} finally {
|
|
18655
|
+
if (cwd)
|
|
18656
|
+
rmSync6(cwd, { recursive: true, force: true });
|
|
18657
|
+
}
|
|
18658
|
+
}
|
|
18659
|
+
function assertDeltaNotes(repoDir, baseSha, headRef, path, task, baseCounts, headCounts) {
|
|
18660
|
+
if (!baseCounts || !headCounts)
|
|
18661
|
+
return null;
|
|
18662
|
+
try {
|
|
18663
|
+
const show = (ref) => safeGit(repoDir, ["show", `${ref}:${path}`]);
|
|
18664
|
+
const head = headRef ? show(headRef) : readFileSync24(join34(repoDir, path), "utf8");
|
|
18665
|
+
const r = classifyTestEdit(path, show(baseSha), head, task, baseCounts, headCounts);
|
|
18666
|
+
return r[0]?.kind === "literal-only" ? r.map((e) => `assertion value changed (not shown to be required by the task): ${e.file}:${e.line} ${e.old} -> ${e.new}`) : null;
|
|
18667
|
+
} catch {
|
|
18668
|
+
return null;
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
function classifyTestEdit(file, oldText, newText, taskText, baseCounts, headCounts) {
|
|
18672
|
+
const weak = [{ kind: "weakened", file, line: 0, old: "", new: "", inTask: false }];
|
|
18673
|
+
const r = classifyAssertDelta({ path: file, base: oldText, head: newText, task: taskText, baseCounts, headCounts });
|
|
18674
|
+
if (r.verdict !== "value-change" || !r.items?.length)
|
|
18675
|
+
return weak;
|
|
18676
|
+
return r.items.map((i) => ({ kind: "literal-only", file, line: i.line, old: i.old, new: i.new, inTask: true }));
|
|
18677
|
+
}
|
|
18678
|
+
|
|
18679
|
+
// ../../loki-ts/src/engine10/failures.ts
|
|
18680
|
+
var ID_LINE = /^(?:(?:FAILED|ERROR)\s+(\S+::\S*)(?:\s.*)?|\u25cf\s+(?!Console|Test suite failed)(.+)|not ok \d+ - (.+?)(?:\s+#.*)?|\u2716\s+(?!failing tests:)(.+?)(?:\s+\([\d.]+ms\))?|(?:FAIL|\u00d7)\s+(.+))$/;
|
|
18681
|
+
function failCount(out2) {
|
|
18682
|
+
const n2 = (s, re) => +(s.match(re)?.[1] ?? 0);
|
|
18683
|
+
const l = out2.split(`
|
|
18684
|
+
`).reverse().map((x) => x.trim()).find((x) => /^(?:=+ )?\d+ \w+.* in [\d.]+s|^Tests?:?\s+\d/.test(x)), m = /^(?:#|\u2139) fail (\d+)$/m.exec(out2);
|
|
18685
|
+
return l ? n2(l, /(\d+) failed/) + n2(l, /(\d+) errors?/) : m ? +m[1] : null;
|
|
18686
|
+
}
|
|
18687
|
+
function failIds(output) {
|
|
18688
|
+
const ids = [...new Set(output.split(`
|
|
18689
|
+
`).map((l) => ID_LINE.exec(l.trim())).flatMap((m) => m ? [(m.slice(1).find(Boolean) ?? "").trim()] : []).filter(Boolean))];
|
|
18690
|
+
const c = failCount(output);
|
|
18691
|
+
return c && ids.length >= c ? ids : [];
|
|
18692
|
+
}
|
|
18693
|
+
|
|
18694
|
+
// ../../loki-ts/src/runner/load_owner.ts
|
|
18695
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
18696
|
+
import { mkdtempSync as mkdtempSync4, realpathSync as realpathSync15, rmSync as rmSync7 } from "fs";
|
|
18697
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
18698
|
+
import { basename as basename11, extname, join as join35 } from "path";
|
|
18699
|
+
|
|
18700
|
+
// ../../loki-ts/src/runner/runner_errors.ts
|
|
18701
|
+
function hasRealTestFailure(out2) {
|
|
18702
|
+
return /^\s*Tests:?\s+.*\b[1-9]\d* failed\b/m.test(out2) || /^(?:=+ )?.*\b[1-9]\d* failed\b.* in [\d.]+s/m.test(out2) || /^FAILED \S+::/m.test(out2) || /^(?:#|\u2139) fail [1-9]/m.test(out2) || /^\s*[1-9]\d*\s+fail\s*$/m.test(out2) || /^\s*(?:FAIL|\u00D7)\s+\S.*\s>\s/m.test(out2) || /\bAssertionError\b/.test(out2);
|
|
18703
|
+
}
|
|
18704
|
+
var LOAD_PATTERNS = [
|
|
18705
|
+
[/Failed Suites\s+[1-9]/, "test suite failed to load"],
|
|
18706
|
+
[/Test suite failed to run/, "test suite failed to run"],
|
|
18707
|
+
[/Failed to load (?:url|config)|failed to load config|Failed to resolve (?:import|entry)/i, "runner config or import could not be loaded"],
|
|
18708
|
+
[/^\s*(?:\w*Error: )?Cannot find (?:module|package) '[^']+'/m, "module not found while loading tests"],
|
|
18709
|
+
[/ERR_MODULE_NOT_FOUND/, "module not found while loading tests"],
|
|
18710
|
+
[/Interrupted: \d+ errors? during collection/, "collection error"],
|
|
18711
|
+
[/^_+ ERROR collecting /m, "collection error"],
|
|
18712
|
+
[/ImportError while importing test module/, "test module import error"],
|
|
18713
|
+
[/^ERROR: (?:file or directory not found|not found:)/m, "test path not found by the runner"],
|
|
18714
|
+
[/No test files found, exiting with code [1-9]/, "runner found no test files (wrong cwd or path)"],
|
|
18715
|
+
[/\[setup failed\]|cannot find package|no required module provides package/, "go package could not be set up"],
|
|
18716
|
+
[/could not determine executable to run/i, "test runner executable not found"],
|
|
18717
|
+
[/^(?:\S+: )?(?:line \d+: )?[\w.-]+: command not found$/m, "test runner executable not found"]
|
|
18718
|
+
];
|
|
18719
|
+
function firstLoadLine(out2) {
|
|
18720
|
+
const lines = out2.split(`
|
|
18721
|
+
`).map((l2) => l2.trim()).filter(Boolean);
|
|
18722
|
+
const l = lines.find((x) => /^(?:E\s+)?(?:Error|\w*Error|ERROR|Failed|FAIL)\b|Cannot find|Interrupted:|No test files found/.test(x)) ?? lines[0] ?? "";
|
|
18723
|
+
return l.replace(/\s+/g, " ").slice(0, 160);
|
|
18724
|
+
}
|
|
18725
|
+
function classifyRunnerOutput(out2) {
|
|
18726
|
+
if (!hasRealTestFailure(out2)) {
|
|
18727
|
+
for (const [re, why] of LOAD_PATTERNS) {
|
|
18728
|
+
if (re.test(out2))
|
|
18729
|
+
return { kind: "load_error", owner: "harness", reason: `runner could not load: ${why}: ${firstLoadLine(out2)}` };
|
|
18730
|
+
}
|
|
18731
|
+
}
|
|
18732
|
+
return { kind: "test_failure", owner: "code" };
|
|
18733
|
+
}
|
|
18734
|
+
|
|
18735
|
+
// ../../loki-ts/src/runner/load_owner.ts
|
|
18736
|
+
var esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18737
|
+
var suffixes = (segs, min) => segs.map((_, k) => segs.slice(k).join("/")).filter((x) => x.split("/").length >= min);
|
|
18738
|
+
var bounded = (out2, tok, tail) => new RegExp(`(?<![\\w.-])${esc(tok)}${tail}`).test(out2);
|
|
18739
|
+
function changedFilesNamed(out2, repoDir, changed) {
|
|
18740
|
+
return changed.filter((f) => {
|
|
18741
|
+
if (out2.includes(f) || out2.includes(join35(repoDir, f)))
|
|
18742
|
+
return true;
|
|
18743
|
+
const segs = f.split("/");
|
|
18744
|
+
if (suffixes(segs, 2).some((x) => x !== f && bounded(out2, x, "(?![\\w-])")))
|
|
18745
|
+
return true;
|
|
18746
|
+
if (extname(f) === ".go" && segs.length > 1 && suffixes(segs.slice(0, -1), 1).some((d) => new RegExp(`(?<=[\\w-])/${esc(d)}(?![\\w./-])`).test(out2)))
|
|
18747
|
+
return true;
|
|
18748
|
+
const stem = basename11(f, extname(f));
|
|
18749
|
+
if (stem.length < 2 || stem === "index" || stem === "__init__")
|
|
18750
|
+
return false;
|
|
18751
|
+
return new RegExp(`(?<![\\w-])${esc(stem)}(?![\\w-])`).test(out2);
|
|
18752
|
+
});
|
|
18753
|
+
}
|
|
18754
|
+
function loadSignature(out2, roots) {
|
|
18755
|
+
let o = out2;
|
|
18756
|
+
for (const r of roots.flatMap((x) => {
|
|
18757
|
+
try {
|
|
18758
|
+
return [x, realpathSync15(x)];
|
|
18759
|
+
} catch {
|
|
18760
|
+
return [x];
|
|
18761
|
+
}
|
|
18762
|
+
}).sort((a, b) => b.length - a.length))
|
|
18763
|
+
o = o.split(r).join("<repo>");
|
|
18764
|
+
const keys = new Set;
|
|
18765
|
+
for (const m of o.matchAll(/Cannot find (?:module|package) '([^']+)'/g))
|
|
18766
|
+
keys.add(`mod:${m[1]}`);
|
|
18767
|
+
for (const m of o.matchAll(/([\w.-]+): command not found/g))
|
|
18768
|
+
keys.add(`cmd:${m[1]}`);
|
|
18769
|
+
return { keys, reason: (classifyRunnerOutput(o).reason ?? "").replace(/:\d+(?::\d+)?/g, "") };
|
|
18770
|
+
}
|
|
18771
|
+
function sameLoadError(head, base, headRoots, baseRoots) {
|
|
18772
|
+
const h = loadSignature(head, headRoots), b = loadSignature(base, baseRoots);
|
|
18773
|
+
if (b.keys.size)
|
|
18774
|
+
return h.keys.size > 0 && [...b.keys].every((k) => h.keys.has(k));
|
|
18775
|
+
return h.keys.size === 0 && h.reason === b.reason;
|
|
18776
|
+
}
|
|
18777
|
+
function killTree(pid) {
|
|
18778
|
+
const all = [pid];
|
|
18779
|
+
for (let k = 0;k < all.length; k++) {
|
|
18780
|
+
const kids = spawnSync2("pgrep", ["-P", String(all[k])], { encoding: "utf8", env: process.env }).stdout.split(/\s+/).filter(Boolean).map(Number);
|
|
18781
|
+
all.push(...kids.filter((x) => Number.isInteger(x) && !all.includes(x)));
|
|
18782
|
+
}
|
|
18783
|
+
try {
|
|
18784
|
+
if (Number.isInteger(pid) && pid > 1)
|
|
18785
|
+
process.kill(-pid, "SIGKILL");
|
|
18786
|
+
} catch {}
|
|
18787
|
+
for (const p of all.reverse()) {
|
|
18788
|
+
try {
|
|
18789
|
+
process.kill(p, "SIGKILL");
|
|
18790
|
+
} catch {}
|
|
18791
|
+
}
|
|
18792
|
+
}
|
|
18793
|
+
async function runOnBase(i, dir) {
|
|
18794
|
+
const sub = (s) => s.split(i.repoDir).join(dir);
|
|
18795
|
+
const stop = AbortSignal.any([i.signal, AbortSignal.timeout(i.timeoutMs ?? 60000)]);
|
|
18796
|
+
try {
|
|
18797
|
+
const proc = Bun.spawn([sub(i.cmd), ...i.args.map(sub)], {
|
|
18798
|
+
cwd: sub(i.cwd ?? i.repoDir),
|
|
18799
|
+
stdin: "ignore",
|
|
18800
|
+
stdout: "pipe",
|
|
18801
|
+
stderr: "pipe",
|
|
18802
|
+
detached: true,
|
|
18803
|
+
env: { ...process.env, ...i.env ?? {} }
|
|
18804
|
+
});
|
|
18805
|
+
let tail = "";
|
|
18806
|
+
const rs = [proc.stdout, proc.stderr].map((x) => x.getReader());
|
|
18807
|
+
const pumps = rs.map(async (r) => {
|
|
18808
|
+
const d = new TextDecoder;
|
|
18809
|
+
for (;; ) {
|
|
18810
|
+
const c = await r.read().catch(() => ({ done: true, value: undefined }));
|
|
18811
|
+
if (c.done)
|
|
18812
|
+
return;
|
|
18813
|
+
tail = (tail + d.decode(c.value, { stream: true })).slice(-65536);
|
|
18814
|
+
}
|
|
18815
|
+
});
|
|
18816
|
+
const cut = new Promise((res) => {
|
|
18817
|
+
if (stop.aborted)
|
|
18818
|
+
res(null);
|
|
18819
|
+
else
|
|
18820
|
+
stop.addEventListener("abort", () => res(null), { once: true });
|
|
18821
|
+
});
|
|
18822
|
+
const code = await Promise.race([proc.exited, cut]);
|
|
18823
|
+
if (code === null) {
|
|
18824
|
+
killTree(proc.pid);
|
|
18825
|
+
return null;
|
|
18826
|
+
}
|
|
18827
|
+
await Promise.race([Promise.all(pumps), new Promise((r) => setTimeout(r, 300))]);
|
|
18828
|
+
rs.forEach((r) => r.cancel().catch(() => {}));
|
|
18829
|
+
return stop.aborted || code === 0 ? null : tail;
|
|
18830
|
+
} catch {
|
|
18831
|
+
return null;
|
|
18832
|
+
}
|
|
18833
|
+
}
|
|
18834
|
+
function changedSince(repoDir, baseSha) {
|
|
18835
|
+
const g = (a) => safeGit(repoDir, a).split(`
|
|
18836
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
18837
|
+
return [...new Set([...g(["diff", "--name-only", baseSha]), ...g(["ls-files", "--others", "--exclude-standard"])])].filter((f) => !f.startsWith(".loki/"));
|
|
18838
|
+
}
|
|
18839
|
+
async function loadErrorIsHarnessOwned(i) {
|
|
18840
|
+
if (i.protect || !i.baseSha || i.signal.aborted)
|
|
18841
|
+
return false;
|
|
18842
|
+
let changed;
|
|
18843
|
+
try {
|
|
18844
|
+
changed = changedSince(i.repoDir, i.baseSha);
|
|
18845
|
+
} catch {
|
|
18846
|
+
return false;
|
|
18847
|
+
}
|
|
18848
|
+
if (changedFilesNamed(i.out, i.repoDir, changed).length > 0)
|
|
18849
|
+
return false;
|
|
18850
|
+
if (i.args.some((a) => changed.includes(a.replace(/^\.\//, ""))))
|
|
18851
|
+
return false;
|
|
18852
|
+
const dir = mkdtempSync4(join35(tmpdir5(), "e10-loadown-"));
|
|
18853
|
+
const git2 = (args) => {
|
|
18854
|
+
safeGit(i.repoDir, args, { stdio: "ignore" });
|
|
18855
|
+
};
|
|
18856
|
+
try {
|
|
18857
|
+
git2(["worktree", "add", "--detach", dir, i.baseSha]);
|
|
18858
|
+
const base = await runOnBase(i, dir);
|
|
18859
|
+
return base !== null && classifyRunnerOutput(base).kind === "load_error" && sameLoadError(i.out, base, [i.repoDir, ...i.cwd ? [i.cwd] : []], [dir]);
|
|
18860
|
+
} catch {
|
|
18861
|
+
return false;
|
|
18862
|
+
} finally {
|
|
18863
|
+
try {
|
|
18864
|
+
git2(["worktree", "remove", "--force", dir]);
|
|
18865
|
+
} catch {}
|
|
18866
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
18867
|
+
try {
|
|
18868
|
+
git2(["worktree", "prune"]);
|
|
18869
|
+
} catch {}
|
|
18870
|
+
}
|
|
18871
|
+
}
|
|
18872
|
+
async function harnessLoadReason(i) {
|
|
18873
|
+
const c = classifyRunnerOutput(i.out);
|
|
18874
|
+
return c.kind === "load_error" && await loadErrorIsHarnessOwned(i) ? c.reason : undefined;
|
|
18875
|
+
}
|
|
18876
|
+
|
|
18877
|
+
// ../../loki-ts/src/engine10/testmap.ts
|
|
18878
|
+
import { basename as basename12, extname as extname2, join as join36, relative as relative2, sep as sep10 } from "path";
|
|
18879
|
+
var SKIP_DIRS = new Set([
|
|
18880
|
+
".git",
|
|
18881
|
+
"node_modules",
|
|
18882
|
+
"dist",
|
|
18883
|
+
"build",
|
|
18884
|
+
"target",
|
|
18885
|
+
"coverage",
|
|
18886
|
+
".venv",
|
|
18887
|
+
"venv",
|
|
18888
|
+
"__pycache__",
|
|
18889
|
+
".tox",
|
|
18890
|
+
".pytest_cache",
|
|
18891
|
+
".loki",
|
|
18892
|
+
".next"
|
|
18893
|
+
]);
|
|
18894
|
+
var JS_TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
18895
|
+
var PY_TEST_RE = /^(test_.+|.+_test)\.py$/;
|
|
18896
|
+
var GO_TEST_RE = /_test\.go$/;
|
|
18897
|
+
function isTestFile(rel) {
|
|
18898
|
+
const name = basename12(rel);
|
|
18899
|
+
return JS_TEST_RE.test(name) || PY_TEST_RE.test(name) || GO_TEST_RE.test(name);
|
|
18900
|
+
}
|
|
18901
|
+
|
|
18902
|
+
// ../../loki-ts/src/util/check_result.ts
|
|
18903
|
+
import { basename as basename13 } from "path";
|
|
18904
|
+
var NO_TESTS_REASON = "no tests executed";
|
|
18905
|
+
var 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)";
|
|
18906
|
+
var UNCONFIRMED_REASON = "test count could not be confirmed";
|
|
18907
|
+
var FAIL_EVIDENCE = /\[(?:build|setup) failed\]|^--- FAIL|^[\w./-]+\.go:\d+:\d+: \S|\b[1-9]\d* (?:failed|errors?)\b/m;
|
|
18908
|
+
var GO_FAIL_EVIDENCE = /^FAIL\t\S+\t[\d.]+s$|^panic: /m;
|
|
18909
|
+
var UNMEASURED_REASON = "executed count unmeasured (Loki could not parse the runner summary, harness-owned)";
|
|
18910
|
+
function ran(raw2, path, ok) {
|
|
18911
|
+
const out2 = stripAnsi(raw2);
|
|
18912
|
+
const n2 = (s, re) => +(s.match(re)?.[1] ?? 0);
|
|
18913
|
+
const blk = out2.trimEnd().match(/(?:^|\n)((?:(?:#|\u2139) \w+ [\d.]+(?:\n|$)){5,})$/)?.[1];
|
|
18914
|
+
if (blk) {
|
|
18915
|
+
const c = n2(blk, /(?:#|\u2139) pass (\d+)/) + n2(blk, /(?:#|\u2139) fail (\d+)/);
|
|
18916
|
+
const nm = out2.match(/^(?:ok \d+ - |\u2714 )(\S+\.[cm]?[jt]s)(?: \(|$)/m)?.[1];
|
|
18917
|
+
return c === 1 && nm && (!path || basename13(nm) === basename13(path)) ? 0 : c;
|
|
18918
|
+
}
|
|
18919
|
+
const cg = cargoTrailer(out2);
|
|
18920
|
+
if (cg.length)
|
|
18921
|
+
return cg.reduce((t, l2) => t + n2(l2, /(\d+) passed/) + n2(l2, /(\d+) failed/), 0);
|
|
18922
|
+
const g = goCount(out2, ok);
|
|
18923
|
+
if (g !== undefined)
|
|
18924
|
+
return g;
|
|
18925
|
+
const tail = tailLines(out2);
|
|
18926
|
+
const l = tail.filter((x) => /^(?:=+ )?(?:\d+ \w+.*|no tests ran) in [\d.]+s|^\s*Tests?:?\s+\d|^No tests found/.test(x) && (!/^\s*Tests?:?\s+\d/.test(x) || tail.some((y) => /^\s*Test (?:Files|Suites):?\s+\d/.test(y)))).pop();
|
|
18927
|
+
if (!l)
|
|
18928
|
+
return null;
|
|
18929
|
+
return /^(?:=+ )?no tests (?:ran|found)|^No tests found|skipped/i.test(l) || /\d+ (?:passed|failed|errors?)/.test(l) ? n2(l, /(\d+) passed/) + n2(l, /(\d+) failed/) + n2(l, /(\d+) errors?/) : null;
|
|
18930
|
+
}
|
|
18931
|
+
var stripAnsi = (s) => s.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, "").replace(/(?:\u001b\[|\u009b)[0-9;?]*[ -\/]*[@-~]/g, "").replace(/\r\n?/g, `
|
|
18932
|
+
`);
|
|
18933
|
+
var tailLines = (out2) => out2.split(`
|
|
18934
|
+
`).filter((x) => x.trim()).slice(-12);
|
|
18935
|
+
function cargoTrailer(out2) {
|
|
18936
|
+
const ls = out2.split(`
|
|
18937
|
+
`).filter((x) => x.trim());
|
|
18938
|
+
while (ls.length && /^error: /.test(ls[ls.length - 1]))
|
|
18939
|
+
ls.pop();
|
|
18940
|
+
const segs = [];
|
|
18941
|
+
for (const l of ls) {
|
|
18942
|
+
if (/^running \d+ tests?\b/.test(l))
|
|
18943
|
+
segs.push([]);
|
|
18944
|
+
else if (segs.length && !/^\s*(?:Running |Doc-tests |Doctest)/.test(l))
|
|
18945
|
+
segs[segs.length - 1].push(l);
|
|
18946
|
+
}
|
|
18947
|
+
const lastOf = (g) => g[g.length - 1]?.startsWith("test result: ") ? g[g.length - 1] : undefined;
|
|
18948
|
+
if (!segs.length) {
|
|
18949
|
+
const g = [];
|
|
18950
|
+
for (let k = ls.length - 1;k >= 0 && ls[k].startsWith("test result: "); k--)
|
|
18951
|
+
g.push(ls[k]);
|
|
18952
|
+
return g;
|
|
18953
|
+
}
|
|
18954
|
+
if (!lastOf(segs[segs.length - 1]))
|
|
18955
|
+
return [];
|
|
18956
|
+
return segs.flatMap((g) => lastOf(g) ?? []);
|
|
18957
|
+
}
|
|
18958
|
+
function bunCount(out2) {
|
|
18959
|
+
const ls = out2.split(`
|
|
18960
|
+
`).filter((x) => x.trim());
|
|
18961
|
+
if (!/^Ran \d+ tests? across \d+ files?\./.test(ls[ls.length - 1] ?? ""))
|
|
18962
|
+
return null;
|
|
18963
|
+
const blk = ls.slice(-9, -1);
|
|
18964
|
+
const last = (k) => +(blk.map((x) => x.match(new RegExp(`^\\s*(\\d+) ${k}\\s*$`))?.[1]).filter(Boolean).pop() ?? 0);
|
|
18965
|
+
return last("pass") + last("fail");
|
|
18966
|
+
}
|
|
18967
|
+
function unittestCount(out2, ok) {
|
|
18968
|
+
const ls = out2.split(`
|
|
18969
|
+
`).filter((x) => x.trim()), v = ls[ls.length - 1] ?? "", r = ls[ls.length - 2]?.match(/^Ran (\d+) tests? in [\d.]+s$/);
|
|
18970
|
+
if (!r || !/^(?:OK|FAILED)\b/.test(v))
|
|
18971
|
+
return null;
|
|
18972
|
+
if (ls.filter((x) => /^Ran \d+ tests? in [\d.]+s$/.test(x)).length > 1)
|
|
18973
|
+
return null;
|
|
18974
|
+
if (ok === true && v.startsWith("FAILED") || ok === false && v.startsWith("OK"))
|
|
18975
|
+
return null;
|
|
18976
|
+
return Math.max(0, +r[1] - +(v.match(/skipped=(\d+)/)?.[1] ?? 0) - +(v.match(/expected failures=(\d+)/)?.[1] ?? 0));
|
|
18977
|
+
}
|
|
18978
|
+
var GO_PKG_RE = /^(?:ok|FAIL|\?)\s+\S+\s+(?:\(cached\)|[\d.]+s\b|\[(?:no test files|build failed|setup failed)\])/;
|
|
18979
|
+
function goCount(out2, ok) {
|
|
18980
|
+
const lines = out2.split(`
|
|
18981
|
+
`);
|
|
18982
|
+
if (!lines.some((x) => GO_PKG_RE.test(x)))
|
|
18983
|
+
return;
|
|
18984
|
+
const last = new Map, seen = new Set, skip = new Set, failPkg = ok !== true && lines.some((x) => /^FAIL\s+\S+\s+[\d.]+s\b/.test(x));
|
|
18985
|
+
for (const x of lines) {
|
|
18986
|
+
const r = x.match(/^=== RUN\s+(\S+)/), m = x.match(/^--- (PASS|FAIL|SKIP): (\S+)/);
|
|
18987
|
+
if (r)
|
|
18988
|
+
seen.add(r[1]);
|
|
18989
|
+
else if (m && (seen.has(m[2]) || m[1] === "FAIL" && failPkg)) {
|
|
18990
|
+
last.set(m[2], m[1]);
|
|
18991
|
+
if (m[1] === "SKIP")
|
|
18992
|
+
skip.add(m[2]);
|
|
18993
|
+
}
|
|
18994
|
+
}
|
|
18995
|
+
for (const k of skip)
|
|
18996
|
+
last.set(k, "SKIP");
|
|
18997
|
+
if (ok === true && [...last.values()].includes("FAIL"))
|
|
18998
|
+
return null;
|
|
18999
|
+
const t = [...last.values()].filter((v) => v !== "SKIP").length;
|
|
19000
|
+
if (t > 0)
|
|
19001
|
+
return t;
|
|
19002
|
+
if (lines.some((x) => /^(?:=== RUN|PASS$|FAIL$|testing: warning: no tests to run)/.test(x)))
|
|
19003
|
+
return 0;
|
|
19004
|
+
return lines.filter((x) => GO_PKG_RE.test(x)).every((x) => /\[no tests? (?:files|to run)\]/.test(x)) ? 0 : null;
|
|
19005
|
+
}
|
|
19006
|
+
function goRunner(cmd, args) {
|
|
19007
|
+
const segs = args ? [[cmd, ...args]] : cmd.split(/&&|\|\||[;|\n]/).map((x) => x.trim().split(/\s+/).filter(Boolean));
|
|
19008
|
+
for (const t0 of segs) {
|
|
19009
|
+
const t = t0.map((x) => x.replace(/^["']|["']$/g, ""));
|
|
19010
|
+
let k = 0;
|
|
19011
|
+
for (;k < t.length; k++) {
|
|
19012
|
+
const x = t[k];
|
|
19013
|
+
if (/^[A-Za-z_]\w*=/.test(x) || /^(?:env|command|exec|time|nohup)$/.test(basename13(x)))
|
|
19014
|
+
continue;
|
|
19015
|
+
if (/^-/.test(x) && k > 0 && basename13(t[k - 1]) === "env") {
|
|
19016
|
+
if (x === "-u" || x === "-C" || x === "-S")
|
|
19017
|
+
k++;
|
|
19018
|
+
continue;
|
|
19019
|
+
}
|
|
19020
|
+
break;
|
|
19021
|
+
}
|
|
19022
|
+
if (t[k] !== undefined && basename13(t[k]) === "go")
|
|
19023
|
+
return { runner: "go" };
|
|
19024
|
+
}
|
|
19025
|
+
return {};
|
|
19026
|
+
}
|
|
19027
|
+
function goJson(out2) {
|
|
19028
|
+
let any = false, failed = false;
|
|
19029
|
+
for (const x of out2.split(`
|
|
19030
|
+
`)) {
|
|
19031
|
+
if (!x.trim() || /^go: /.test(x))
|
|
19032
|
+
continue;
|
|
19033
|
+
let e;
|
|
19034
|
+
try {
|
|
19035
|
+
e = JSON.parse(x);
|
|
19036
|
+
} catch {
|
|
19037
|
+
return null;
|
|
19038
|
+
}
|
|
19039
|
+
if (!e || typeof e !== "object" || typeof e.Action !== "string")
|
|
19040
|
+
return null;
|
|
19041
|
+
any = true;
|
|
19042
|
+
if (e.Action === "fail")
|
|
19043
|
+
failed = true;
|
|
19044
|
+
}
|
|
19045
|
+
return any ? { failed } : null;
|
|
19046
|
+
}
|
|
19047
|
+
function skipped(raw2) {
|
|
19048
|
+
return stripAnsi(raw2).split(`
|
|
19049
|
+
`).filter((l) => /^(?:=+ )?\d+ \w+.* in [\d.]+s|^\s*Tests?:?\s+\d|^(?:#|\u2139) skipped \d/.test(l.trim())).reduce((t, l) => t + [...l.matchAll(/(\d+) (?:skipped|deselected|xfailed)|skipped (\d+)/g)].reduce((u, m) => u + +(m[1] ?? m[2]), 0), 0);
|
|
19050
|
+
}
|
|
19051
|
+
function testCount(raw2, path, ok) {
|
|
19052
|
+
const out2 = stripAnsi(raw2);
|
|
19053
|
+
const bu = bunCount(out2), ut = unittestCount(out2, ok);
|
|
19054
|
+
if (bu !== null)
|
|
19055
|
+
return bu;
|
|
19056
|
+
if (ut !== null)
|
|
19057
|
+
return ut;
|
|
19058
|
+
if (/^\s*(?:Test Files\s+0\b|No test files found)/m.test(out2) && !/^\s*Tests?\s+\d/m.test(out2))
|
|
19059
|
+
return 0;
|
|
19060
|
+
const r = ran(out2, path, ok);
|
|
19061
|
+
if (r !== null)
|
|
19062
|
+
return r;
|
|
19063
|
+
const tl = tailLines(out2);
|
|
19064
|
+
const pw = tl.filter((x) => /^\s*\d+ (?:passed|failed|flaky)\b/.test(x));
|
|
19065
|
+
if (pw.length && tl.some((x) => /^\s*\d+ passed \([\d.]+m?s\)/.test(x) || /^\s*\d+ failed$/.test(x)))
|
|
19066
|
+
return pw.reduce((t, x) => t + +x.trim().split(" ")[0], 0);
|
|
19067
|
+
const lastNum = (re) => {
|
|
19068
|
+
const m = [...out2.matchAll(re)].pop();
|
|
19069
|
+
return m ? +m[1] : null;
|
|
19070
|
+
};
|
|
19071
|
+
const mp = lastNum(/^\s*(\d+) passing\b/gm), mf = lastNum(/^\s*(\d+) failing\b/gm);
|
|
19072
|
+
if (mp !== null || mf !== null)
|
|
19073
|
+
return (mp ?? 0) + (mf ?? 0);
|
|
19074
|
+
return null;
|
|
19075
|
+
}
|
|
19076
|
+
function classifyCheck(i) {
|
|
19077
|
+
if (i.missing)
|
|
19078
|
+
return { result: "not_run", reason: "tool not found on PATH" };
|
|
19079
|
+
if (i.cut)
|
|
19080
|
+
return { result: "not_run", reason: "timed out or aborted" };
|
|
19081
|
+
if (i.kind === "static")
|
|
19082
|
+
return { result: i.ok ? "pass" : "fail" };
|
|
19083
|
+
const text2 = stripAnsi(i.out);
|
|
19084
|
+
if (i.runner === "go") {
|
|
19085
|
+
if (i.ok)
|
|
19086
|
+
return { result: "not_run", reason: GO_EXIT0_REASON };
|
|
19087
|
+
const j = goJson(text2);
|
|
19088
|
+
if (j)
|
|
19089
|
+
return j.failed ? { result: "fail" } : { result: "not_run", reason: UNCONFIRMED_REASON };
|
|
19090
|
+
return FAIL_EVIDENCE.test(text2) || GO_FAIL_EVIDENCE.test(text2) ? { result: "fail" } : { result: "not_run", reason: UNCONFIRMED_REASON };
|
|
19091
|
+
}
|
|
19092
|
+
const n2 = testCount(i.out, i.path, i.ok);
|
|
19093
|
+
if (!i.ok && FAIL_EVIDENCE.test(text2))
|
|
19094
|
+
return { result: "fail", ...n2 !== null ? { n: n2 } : {} };
|
|
19095
|
+
if (n2 === 0)
|
|
19096
|
+
return { result: "not_run", n: 0, reason: `${NO_TESTS_REASON} (ran 0 tests, empty or all skipped)` };
|
|
19097
|
+
if (!i.ok)
|
|
19098
|
+
return { result: "fail", ...n2 !== null ? { n: n2 } : {} };
|
|
19099
|
+
if (n2 === null)
|
|
19100
|
+
return { result: "not_run", reason: UNMEASURED_REASON };
|
|
19101
|
+
return { result: "pass", n: n2 };
|
|
19102
|
+
}
|
|
19103
|
+
var isExecutedProof = (c) => c.result === "pass" && typeof c.n === "number" && c.n > 0;
|
|
19104
|
+
var hasExecutedProof = (checks) => checks.some(isExecutedProof);
|
|
19105
|
+
|
|
19106
|
+
// ../../loki-ts/src/util/wall_owned.ts
|
|
19107
|
+
import { isAbsolute as isAbsolute3, join as join37, relative as relative3 } from "path";
|
|
19108
|
+
var WALL_COMPILE_REASON = "wall test did not compile under the package config";
|
|
19109
|
+
var ANSI2 = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
19110
|
+
function failingLocations(raw2, cwd, repoDir) {
|
|
19111
|
+
const out2 = raw2.replace(ANSI2, "").replace(/\r\n?/g, `
|
|
19112
|
+
`), files = new Set;
|
|
19113
|
+
let located = 0, errors2 = 0, header = null;
|
|
19114
|
+
for (const line2 of out2.split(`
|
|
19115
|
+
`)) {
|
|
19116
|
+
const m = /^(.+?)\(\d+,\d+\): error TS\d+:/.exec(line2) ?? /^(.+?):\d+:\d+ - error TS\d+:/.exec(line2) ?? /^(.+?):\d+:\d+: .*\berror\b/.exec(line2);
|
|
19117
|
+
if (m) {
|
|
19118
|
+
files.add(m[1]);
|
|
19119
|
+
located++;
|
|
19120
|
+
errors2++;
|
|
19121
|
+
continue;
|
|
19122
|
+
}
|
|
19123
|
+
if (/\berror TS\d+:/.test(line2)) {
|
|
19124
|
+
errors2++;
|
|
19125
|
+
continue;
|
|
19126
|
+
}
|
|
19127
|
+
if (/^\S/.test(line2) && /^[^\s:]+\.[cm]?[jt]sx?$/.test(line2.trim())) {
|
|
19128
|
+
header = line2.trim();
|
|
19129
|
+
continue;
|
|
19130
|
+
}
|
|
19131
|
+
if (/^\s+\d+:\d+\s+error\b/.test(line2)) {
|
|
19132
|
+
errors2++;
|
|
19133
|
+
if (header) {
|
|
19134
|
+
files.add(header);
|
|
19135
|
+
located++;
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
}
|
|
19139
|
+
if (errors2 === 0 || located !== errors2)
|
|
19140
|
+
return null;
|
|
19141
|
+
return [...files].map((f) => relative3(repoDir, isAbsolute3(f) ? f : join37(cwd, f)));
|
|
19142
|
+
}
|
|
19143
|
+
function wallOwnedFailure(raw2, cwd, repoDir, wall) {
|
|
19144
|
+
const locs = failingLocations(raw2, cwd, repoDir);
|
|
19145
|
+
return !!locs && locs.length > 0 && locs.every((l) => wall.has(l));
|
|
19146
|
+
}
|
|
19147
|
+
|
|
19148
|
+
// ../../loki-ts/src/engine10/stages/verify.ts
|
|
19149
|
+
var CHECK_TIMEOUT_MS = 60000;
|
|
19150
|
+
function resolveTool(repoDir, bin, fallback, lastResort = fallback) {
|
|
19151
|
+
const inRepo = (p) => {
|
|
19152
|
+
try {
|
|
19153
|
+
const r = relative4(realpathSync16(repoDir), realpathSync16(p));
|
|
19154
|
+
return r !== "" && !r.startsWith("..") && !isAbsolute4(r);
|
|
19155
|
+
} catch {
|
|
19156
|
+
return false;
|
|
19157
|
+
}
|
|
19158
|
+
};
|
|
19159
|
+
const venvEnv = process.env["VIRTUAL_ENV"];
|
|
19160
|
+
const project = [join38(repoDir, ".venv", "bin", bin), join38(repoDir, "venv", "bin", bin), ...venvEnv && inRepo(venvEnv) ? [join38(venvEnv, "bin", bin)] : []].find(existsSync20);
|
|
19161
|
+
return project ? [project, "project"] : [Bun.which(fallback) ? fallback : lastResort, "system"];
|
|
19162
|
+
}
|
|
19163
|
+
function runnerCmd(t, repoDir) {
|
|
19164
|
+
switch (t.runner) {
|
|
19165
|
+
case "pytest": {
|
|
19166
|
+
const [cmd, interpreter] = resolveTool(repoDir, "python", "python3", "python");
|
|
19167
|
+
return [cmd, ["-m", "pytest", "-q", t.path], interpreter];
|
|
19168
|
+
}
|
|
19169
|
+
case "vitest":
|
|
19170
|
+
return ["npx", ["vitest", "run", t.path]];
|
|
19171
|
+
case "jest":
|
|
19172
|
+
return ["npx", ["jest", t.path]];
|
|
19173
|
+
case "bun":
|
|
19174
|
+
return ["bun", ["test", t.path]];
|
|
19175
|
+
case "node":
|
|
19176
|
+
return ["node", ["--test", `./${t.path}`]];
|
|
19177
|
+
case "npm":
|
|
19178
|
+
return ["npm", ["test", "--silent"]];
|
|
19179
|
+
case "go":
|
|
19180
|
+
return ["go", ["test", "-json", `./${dirname10(t.path)}`]];
|
|
19181
|
+
case "cargo":
|
|
19182
|
+
return ["cargo", ["test"]];
|
|
19183
|
+
}
|
|
19184
|
+
}
|
|
19185
|
+
function commandFor(t, repoDir, api) {
|
|
19186
|
+
const site = siteFor(api, repoDir, t.path);
|
|
19187
|
+
const [cmd, args, interpreter] = runnerCmd(site ? { runner: t.runner, path: site.file } : t, repoDir);
|
|
19188
|
+
return { cwd: site?.cwd ?? repoDir, cmd, args, argv: [cmd, ...args], ...interpreter ? { interpreter } : {}, pkgRoot: site?.root ?? "." };
|
|
19189
|
+
}
|
|
19190
|
+
var dedupeTests = (tests) => [...new Map(tests.map((t) => [`${t.runner}:${t.path}`, t])).values()];
|
|
19191
|
+
function changedFiles(repoDir, baseSha) {
|
|
19192
|
+
const run = (args) => safeGit(repoDir, args).split(`
|
|
19193
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
19194
|
+
const tracked = run(["diff", "--name-only", baseSha]);
|
|
19195
|
+
const untracked = run(["ls-files", "--others", "--exclude-standard"]);
|
|
19196
|
+
return [...new Set([...tracked, ...untracked])].filter((f) => !f.startsWith(".loki/"));
|
|
19197
|
+
}
|
|
19198
|
+
var CODE_EXT = /\.(?:[cm]?[jt]sx?|py|go)$/;
|
|
19199
|
+
var CFG_ALWAYS = /(^|\/)(conftest\.py|\.?pytest\.(?:ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+|\.mocharc[\w.]*)$/;
|
|
19200
|
+
var CFG_SHARED = /(^|\/)(setup\.cfg|pyproject\.toml|package\.json)$/;
|
|
19201
|
+
var CFG_LINE = /^[+-].*(pytest|jest|mocha|vitest|"test"\s*:|addopts|testpaths)/im;
|
|
19202
|
+
function testConfigChanged(repoDir, baseSha, changed) {
|
|
19203
|
+
const diff = (f) => safeGit(repoDir, ["diff", "-U0", baseSha, "--", f]);
|
|
19204
|
+
return changed.filter((f) => CFG_ALWAYS.test(f) || CFG_SHARED.test(f) && (() => {
|
|
19205
|
+
const d = diff(f);
|
|
19206
|
+
return !d || CFG_LINE.test(d);
|
|
19207
|
+
})());
|
|
19208
|
+
}
|
|
19209
|
+
async function runOnce(cmd, args, cwd, signal, opts) {
|
|
19210
|
+
if (!Bun.which(cmd, { PATH: opts.path ?? process.env["PATH"] ?? "" }))
|
|
19211
|
+
return { ok: false, missing: true, cut: false, out: "" };
|
|
19212
|
+
const timeout = AbortSignal.timeout(opts.timeoutMs ?? CHECK_TIMEOUT_MS);
|
|
19213
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
19214
|
+
cwd,
|
|
19215
|
+
stdin: opts.stdin !== undefined ? Buffer.from(opts.stdin) : "ignore",
|
|
19216
|
+
stdout: "pipe",
|
|
19217
|
+
stderr: "pipe",
|
|
19218
|
+
signal: AbortSignal.any([signal, timeout]),
|
|
19219
|
+
env: opts.path ? { ...process.env, PATH: opts.path } : process.env
|
|
19220
|
+
});
|
|
19221
|
+
let tail = "";
|
|
19222
|
+
const rs = [proc.stdout, proc.stderr].map((s) => s.getReader());
|
|
19223
|
+
const pumps = rs.map(async (r) => {
|
|
19224
|
+
const d = new TextDecoder;
|
|
19225
|
+
for (;; ) {
|
|
19226
|
+
const c = await r.read().catch(() => ({ done: true, value: undefined }));
|
|
19227
|
+
if (c.done)
|
|
19228
|
+
return;
|
|
19229
|
+
tail = (tail + d.decode(c.value, { stream: true })).slice(-65536);
|
|
19230
|
+
}
|
|
19231
|
+
});
|
|
19232
|
+
const exitCode = await proc.exited;
|
|
19233
|
+
await Promise.race([Promise.all(pumps), new Promise((r) => setTimeout(r, 300))]);
|
|
19234
|
+
rs.forEach((r) => r.cancel().catch(() => {}));
|
|
19235
|
+
const cut = timeout.aborted || signal.aborted;
|
|
19236
|
+
return { ok: exitCode === 0 && !cut, missing: false, cut, out: cut ? "" : tail };
|
|
19237
|
+
}
|
|
19238
|
+
function firstError(out2) {
|
|
19239
|
+
const goOut = (l2) => {
|
|
19240
|
+
try {
|
|
19241
|
+
const e = JSON.parse(l2);
|
|
19242
|
+
return typeof e.Action === "string" ? e.Action === "output" || e.Action === "build-output" ? e.Output ?? "" : "" : l2;
|
|
19243
|
+
} catch {
|
|
19244
|
+
return l2;
|
|
19245
|
+
}
|
|
19246
|
+
};
|
|
19247
|
+
const lines = out2.slice(-65536).split(`
|
|
19248
|
+
`).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)) ?? "";
|
|
19249
|
+
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);
|
|
19250
|
+
}
|
|
19251
|
+
async function runCheck(ctx, name, cmd, args, signal, checks, opts = {}) {
|
|
19252
|
+
const started = Date.now();
|
|
19253
|
+
const cmdStr = [cmd, ...args].join(" ");
|
|
19254
|
+
const kind = opts.kind ?? "test";
|
|
19255
|
+
const cwd = opts.cwd ?? ctx.repoDir;
|
|
19256
|
+
const one = async (a) => {
|
|
19257
|
+
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 });
|
|
19258
|
+
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;
|
|
19259
|
+
if (lr)
|
|
19260
|
+
c2 = { result: "not_run", reason: lr, owner: "harness" };
|
|
19261
|
+
if (!lr && kind === "static" && !a.ok && !a.cut && !a.missing && opts.wall?.size && wallOwnedFailure(a.out, cwd, ctx.repoDir, opts.wall))
|
|
19262
|
+
c2 = { result: "not_run", reason: WALL_COMPILE_REASON, owner: "harness" };
|
|
19263
|
+
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;
|
|
19264
|
+
return { ...c2, ...reason2 ? { reason: reason2 } : {} };
|
|
19265
|
+
};
|
|
19266
|
+
let attempt = await runOnce(cmd, args, cwd, signal, opts);
|
|
19267
|
+
let c = await one(attempt);
|
|
19268
|
+
let result = c.result;
|
|
19269
|
+
if (c.result === "fail") {
|
|
19270
|
+
attempt = await runOnce(cmd, args, cwd, signal, opts);
|
|
19271
|
+
c = await one(attempt);
|
|
19272
|
+
result = c.result === "not_run" ? "not_run" : c.result === "pass" ? "flaky" : "fail";
|
|
19273
|
+
}
|
|
19274
|
+
const reason = c.reason;
|
|
19275
|
+
const check = { name, cmd: cmdStr, result, duration_s: (Date.now() - started) / 1000, ...result === "fail" ? { first_error: firstError(attempt.out), ids: failIds(attempt.out) } : result === "pass" && kind === "test" ? { n: c.n ?? 0, sk: skipped(attempt.out) } : {}, ...reason ? { reason } : {}, ...c.owner ? { owner: "harness" } : {}, ...opts.interpreter ? { interpreter: opts.interpreter } : {} };
|
|
19276
|
+
checks.push(check);
|
|
19277
|
+
ctx.emit("test.result", "verify", { ...check });
|
|
19278
|
+
return check;
|
|
19279
|
+
}
|
|
19280
|
+
async function subtractBase(ctx, api, checks, tests, changed, wall, rel, signal) {
|
|
19281
|
+
const out2 = { ids: [], names: [], weak: [], cnt: {} };
|
|
19282
|
+
const pairs = checks.flatMap((c) => {
|
|
19283
|
+
const t = tests.find((x) => `${x.runner}:${x.path}` === c.name);
|
|
19284
|
+
return t ? [{ c, t }] : [];
|
|
19285
|
+
});
|
|
19286
|
+
const un = pairs.filter(({ t }) => !changed.includes(t.path));
|
|
19287
|
+
const sus = pairs.filter(({ c }) => rel.has(c.name) && c.result === "pass");
|
|
19288
|
+
if (!(un.some(({ c }) => c.result === "fail" && c.ids?.length) || sus.length) || signal.aborted)
|
|
19289
|
+
return out2;
|
|
19290
|
+
const dir = mkdtempSync5(join38(tmpdir6(), "e10-base-"));
|
|
19291
|
+
const git2 = (args) => {
|
|
19292
|
+
safeGit(ctx.repoDir, args, { stdio: "ignore", repoDrivers: true });
|
|
19293
|
+
};
|
|
19294
|
+
try {
|
|
19295
|
+
git2(["-c", "core.hooksPath=/dev/null", "worktree", "add", "--detach", dir, ctx.baseSha]);
|
|
19296
|
+
const base = new Map;
|
|
19297
|
+
for (const { c, t } of new Set([...un, ...sus])) {
|
|
19298
|
+
const bc = commandFor(t, dir, api), b = await runOnce(bc.cmd, bc.args, bc.cwd, signal, {});
|
|
19299
|
+
base.set(c, { red: !b.ok && !b.cut ? failIds(b.out) : [], n: ran(b.out) ?? ["pass", "fail"].reduce((t2, k) => t2 + +([...b.out.matchAll(new RegExp(`^(?:#|\\u2139) ${k} (\\d+)$`, "gm"))].pop()?.[1] ?? 0), 0), sk: skipped(b.out) });
|
|
19300
|
+
}
|
|
19301
|
+
out2.weak = sus.filter(({ c }) => (c.sk ?? 0) > base.get(c).sk || (c.n ?? 0) < base.get(c).n).map(({ c }) => c.name);
|
|
19302
|
+
for (const { c } of sus)
|
|
19303
|
+
out2.cnt[c.name] = { b: { run: base.get(c).n, skipped: base.get(c).sk }, h: { run: c.n ?? 0, skipped: c.sk ?? 0 } };
|
|
19304
|
+
const progress = checks.some((c) => wall.has(c.name) && c.result === "pass") || sus.some(({ c }) => !out2.weak.includes(c.name) && base.get(c).red.length > 0 && (c.n ?? 0) >= base.get(c).n);
|
|
19305
|
+
if (progress) {
|
|
19306
|
+
for (const { c } of un) {
|
|
19307
|
+
if (!rel.has(c.name) && c.result === "fail" && c.ids?.length && c.ids.every((i) => base.get(c).red.includes(i))) {
|
|
19308
|
+
out2.names.push(c.name);
|
|
19309
|
+
out2.ids.push(...c.ids);
|
|
19310
|
+
}
|
|
19311
|
+
}
|
|
19312
|
+
}
|
|
19313
|
+
} catch {} finally {
|
|
19314
|
+
try {
|
|
19315
|
+
git2(["worktree", "remove", "--force", dir]);
|
|
19316
|
+
} catch {}
|
|
19317
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
19318
|
+
try {
|
|
19319
|
+
git2(["worktree", "prune"]);
|
|
19320
|
+
} catch {}
|
|
19321
|
+
}
|
|
19322
|
+
return out2;
|
|
19323
|
+
}
|
|
19324
|
+
function isLokiModeRepo(repoDir) {
|
|
19325
|
+
return existsSync20(join38(repoDir, "scripts", "select-tests.sh"));
|
|
19326
|
+
}
|
|
19327
|
+
var ESLINT_CONFIGS = [".eslintrc", ".eslintrc.json", ".eslintrc.js", ".eslintrc.cjs", "eslint.config.js", "eslint.config.mjs", "eslint.config.cjs"];
|
|
19328
|
+
async function runLintChecks(ctx, changed, signal, checks, opts = {}) {
|
|
19329
|
+
const { api, ...runOpts } = opts;
|
|
19330
|
+
opts = runOpts;
|
|
19331
|
+
const py = changed.filter((f) => f.endsWith(".py"));
|
|
19332
|
+
if (py.length) {
|
|
19333
|
+
const [ruffCmd, interpreter] = resolveTool(ctx.repoDir, "ruff", "ruff");
|
|
19334
|
+
await runCheck(ctx, "lint:ruff", ruffCmd, ["check", ...py], signal, checks, { ...opts, interpreter, kind: "static" });
|
|
19335
|
+
}
|
|
19336
|
+
const sh = changed.filter((f) => f.endsWith(".sh"));
|
|
19337
|
+
if (sh.length) {
|
|
19338
|
+
await runCheck(ctx, "lint:bash-n", "bash", ["-c", 'for f in "$@"; do bash -n "$f" || exit 1; done', "_", ...sh], signal, checks, { ...opts, kind: "static" });
|
|
19339
|
+
await runCheck(ctx, "lint:shellcheck", "shellcheck", sh, signal, checks, { ...opts, kind: "static" });
|
|
19340
|
+
}
|
|
19341
|
+
for (const g of groupByPackage(api, ctx.repoDir, changed.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f)))) {
|
|
19342
|
+
const sfx = g.root === "." ? "" : `:${g.root}`, co = g.root === "." ? opts : { ...opts, cwd: g.cwd };
|
|
19343
|
+
if (existsSync20(join38(g.cwd, "tsconfig.json")))
|
|
19344
|
+
await runCheck(ctx, `lint:tsc${sfx}`, "npx", ["tsc", "--noEmit", "-p", "."], signal, checks, { ...co, kind: "static" });
|
|
19345
|
+
if (ESLINT_CONFIGS.some((f) => existsSync20(join38(g.cwd, f))))
|
|
19346
|
+
await runCheck(ctx, `lint:eslint${sfx}`, "npx", ["eslint", ...g.rel], signal, checks, { ...co, kind: "static" });
|
|
19347
|
+
}
|
|
19348
|
+
}
|
|
19349
|
+
var verifyStage = {
|
|
19350
|
+
name: "verify",
|
|
19351
|
+
targetS: STAGE_BUDGETS.verify.targetS,
|
|
19352
|
+
limitS: STAGE_BUDGETS.verify.limitS,
|
|
19353
|
+
async run(ctx, signal) {
|
|
19354
|
+
let changed;
|
|
19355
|
+
try {
|
|
19356
|
+
changed = changedFiles(ctx.repoDir, ctx.baseSha);
|
|
19357
|
+
} catch (err) {
|
|
19358
|
+
return { status: "failed", data: {}, reason: `git diff against base failed: ${err.message}` };
|
|
19359
|
+
}
|
|
19360
|
+
if (changed.length === 0) {
|
|
19361
|
+
const implementExit = ctx.outputs().implement?.exit;
|
|
19362
|
+
if (implementExit === "already_done") {
|
|
19363
|
+
return { status: "completed", data: { already_satisfied: true, checks: [], flaky: [], failures_grouped: [], changed_files: [] } };
|
|
19364
|
+
}
|
|
19365
|
+
return { status: "failed", data: { changed_files: [] }, reason: "empty diff without an already_done marker" };
|
|
19366
|
+
}
|
|
19367
|
+
const checks = [];
|
|
19368
|
+
const api = loadProjectApi(ctx.repoDir);
|
|
19369
|
+
const map = await ctx.tests.detect(ctx.repoDir);
|
|
19370
|
+
const impacted = ctx.tests.impacted(map, changed);
|
|
19371
|
+
const changedTestFiles = map.tests.filter((t) => changed.includes(t.path));
|
|
19372
|
+
const wall = ctx.outputs().wall ?? {};
|
|
19373
|
+
const wallPaths = new Set((wall.files ?? []).map((f) => isAbsolute4(f.path) ? relative4(ctx.repoDir, f.path) : f.path));
|
|
19374
|
+
const wallTests = map.tests.filter((t) => wallPaths.has(t.path));
|
|
19375
|
+
const intake = ctx.outputs().intake, relevant = ctx.tests.impacted(map, namedFiles(intake?.task ?? "", loadRepoMap(intake?.repomap_ref)));
|
|
19376
|
+
const tests = dedupeTests([...impacted, ...changedTestFiles, ...wallTests, ...relevant]);
|
|
19377
|
+
const scopedOut = scopedOutOf(map);
|
|
19378
|
+
if (scopedOut.length)
|
|
19379
|
+
ctx.emit("test.scoped_out", "verify", { tests: scopedOut });
|
|
19380
|
+
for (const t of tests) {
|
|
19381
|
+
if (signal.aborted)
|
|
19382
|
+
break;
|
|
19383
|
+
const tc = commandFor(t, ctx.repoDir, api);
|
|
19384
|
+
await runCheck(ctx, `${t.runner}:${t.path}`, tc.cmd, tc.args, signal, checks, { ...tc.interpreter ? { interpreter: tc.interpreter } : {}, protect: [...wallTests, ...relevant].includes(t), ...tc.pkgRoot !== "." ? { cwd: tc.cwd } : {} });
|
|
19385
|
+
}
|
|
19386
|
+
const targetNames = new Set([...wallTests, ...relevant].map((t) => `${t.runner}:${t.path}`));
|
|
19387
|
+
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);
|
|
19388
|
+
if (!signal.aborted) {
|
|
19389
|
+
await runLintChecks(ctx, changed, signal, checks, { api, wall: wallPaths });
|
|
19390
|
+
if (isLokiModeRepo(ctx.repoDir)) {
|
|
19391
|
+
await runCheck(ctx, "select-tests", "bash", ["scripts/select-tests.sh", "--files", "-", "--run"], signal, checks, {
|
|
19392
|
+
kind: "static",
|
|
19393
|
+
stdin: changed.join(`
|
|
19394
|
+
`) + `
|
|
19395
|
+
`
|
|
19396
|
+
});
|
|
19397
|
+
}
|
|
19398
|
+
}
|
|
19399
|
+
const cfgChanged = testConfigChanged(ctx.repoDir, ctx.baseSha, changed);
|
|
19400
|
+
const covered = (f) => ctx.tests.impacted(map, [f]).some((t) => checks.some((c) => c.name === `${t.runner}:${t.path}` && hasExecutedProof([c])));
|
|
19401
|
+
const uncoveredChanged = changed.filter((f) => CODE_EXT.test(f) && !isTestFile(f) && !wallPaths.has(f) && !cfgChanged.includes(f) && !covered(f));
|
|
19402
|
+
const flaky = checks.filter((c) => c.result === "flaky").map((c) => c.name);
|
|
19403
|
+
const failuresGrouped = checks.filter((c) => c.result === "fail" && !preRed.names.includes(c.name)).map((c) => ({ signature: c.first_error ? `${c.name} ${c.first_error}` : c.name, count: 1, sample: c.cmd }));
|
|
19404
|
+
const inBase = (f) => {
|
|
19405
|
+
try {
|
|
19406
|
+
safeGit(ctx.repoDir, ["cat-file", "-e", `${ctx.baseSha}:${f}`], { stdio: "ignore" });
|
|
19407
|
+
return true;
|
|
19408
|
+
} catch {
|
|
19409
|
+
return false;
|
|
19410
|
+
}
|
|
19411
|
+
};
|
|
19412
|
+
const testCounts = {};
|
|
19413
|
+
const modifiedRel = relevant.filter((t) => changed.includes(t.path) && inBase(t.path)).flatMap((t) => {
|
|
19414
|
+
const k = preRed.cnt[`${t.runner}:${t.path}`];
|
|
19415
|
+
if (k)
|
|
19416
|
+
testCounts[t.path] = k;
|
|
19417
|
+
return [`weakened test: ${t.path}`, ...assertDeltaNotes(ctx.repoDir, ctx.baseSha, null, t.path, intake?.task ?? "", k?.b, k?.h) ?? []];
|
|
19418
|
+
});
|
|
19419
|
+
const weakened = [...modifiedRel, ...cfgChanged.map((f) => `test configuration changed: ${f}`), ...preRed.weak.map((n2) => `skipped or fewer tests than base: ${n2}`)];
|
|
19420
|
+
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"))];
|
|
19421
|
+
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) } };
|
|
19422
|
+
}
|
|
19423
|
+
};
|
|
19424
|
+
|
|
19425
|
+
// ../../loki-ts/src/engine10/stages/wall.ts
|
|
19426
|
+
var BASE_RUN_TIMEOUT_MS = 60000;
|
|
19427
|
+
function pytestCollectionIsRed(output, repoDir) {
|
|
19428
|
+
if (/ModuleNotFoundError/.test(output))
|
|
19429
|
+
return false;
|
|
19430
|
+
const real2 = (p) => {
|
|
19431
|
+
try {
|
|
19432
|
+
return realpathSync17(isAbsolute5(p) ? p : join39(repoDir, p));
|
|
19433
|
+
} catch {
|
|
19434
|
+
return null;
|
|
19435
|
+
}
|
|
19436
|
+
};
|
|
19437
|
+
const repoReal = real2(repoDir);
|
|
19438
|
+
const under = (p) => {
|
|
19439
|
+
const r = real2(p);
|
|
19440
|
+
return !!repoReal && !!r && (r === repoReal || r.startsWith(`${repoReal}/`));
|
|
19441
|
+
};
|
|
19442
|
+
const imp = /ImportError: cannot import name .* from ['"][\w.]+['"] \(([^)]+)\)/.exec(output);
|
|
19443
|
+
if (imp)
|
|
19444
|
+
return under(imp[1]);
|
|
19445
|
+
const hdr = [...output.matchAll(/^=+ short test summary info =+$/gm)].pop();
|
|
19446
|
+
if (!hdr)
|
|
19447
|
+
return false;
|
|
19448
|
+
const cls = [...output.slice(hdr.index).matchAll(/^ERROR \S+ - (\w+Error): /gm)].pop()?.[1];
|
|
19449
|
+
if (cls !== "AttributeError" && cls !== "NameError")
|
|
19450
|
+
return false;
|
|
19451
|
+
const start = output.search(/^_+ ERROR collecting /m);
|
|
19452
|
+
if (start < 0 || start > hdr.index)
|
|
19453
|
+
return false;
|
|
19454
|
+
const block = output.slice(start, hdr.index).split(/^-+ Captured .*$/m)[0];
|
|
19455
|
+
const eLines = [...block.matchAll(new RegExp(`^E {3}${cls}: (.*)$`, "gm"))];
|
|
19456
|
+
const eLine = eLines[eLines.length - 1];
|
|
19457
|
+
if (!eLine)
|
|
19458
|
+
return false;
|
|
19459
|
+
if (cls === "AttributeError") {
|
|
19460
|
+
const m = /^module ['"]([\w.]+)['"] has no attribute/.exec(eLine[1]);
|
|
19461
|
+
return !!m && moduleUnderRepo(repoDir, m[1]);
|
|
19462
|
+
}
|
|
19463
|
+
const frames = [...block.slice(0, eLine.index).matchAll(/^(.+?):\d+: in \S+\n\s*(.*)$/gm)];
|
|
19464
|
+
const last = frames[frames.length - 1];
|
|
19465
|
+
return !!last && under(last[1]) && !/^\s*(import\s|from\s\S+\s+import\b)/.test(last[2] ?? "");
|
|
19466
|
+
}
|
|
19467
|
+
function moduleUnderRepo(repoDir, name) {
|
|
19468
|
+
const rel = name.replace(/\./g, "/");
|
|
19469
|
+
return existsSync21(join39(repoDir, `${rel}.py`)) || existsSync21(join39(repoDir, rel, "__init__.py"));
|
|
19470
|
+
}
|
|
19471
|
+
function pytestExit1IsRed(output) {
|
|
19472
|
+
if (/^!+ _pytest\.outcomes\.Exit\b/m.test(output))
|
|
19473
|
+
return false;
|
|
19474
|
+
if (/^=*\s*(\d+ \w+(, )?)*\d+ failed\b.* in [\d.]+s/.test(output.trimEnd().split(`
|
|
19475
|
+
`).pop() ?? ""))
|
|
19476
|
+
return true;
|
|
19477
|
+
const hdr = [...output.matchAll(/^=+ short test summary info =+$/gm)].pop();
|
|
19478
|
+
return !!hdr && /^FAILED \S+/m.test(output.slice(hdr.index));
|
|
19479
|
+
}
|
|
19480
|
+
function parsedFailCount(runner, output) {
|
|
19481
|
+
const m = runner === "bun" ? /^\s*(\d+)\s+fail\s*$/m.exec(output) : runner === "jest" ? /Tests:\s*(\d+)\s+failed/i.exec(output) : runner === "vitest" ? /Tests\s+(\d+)\s+failed/i.exec(output) : null;
|
|
19482
|
+
return m ? Number(m[1]) : 0;
|
|
19483
|
+
}
|
|
19484
|
+
function nodeIsRed(f, raw2) {
|
|
19485
|
+
const o = raw2.replace(/^# (?!(?:tests|suites|pass|fail|cancelled|skipped|todo|duration_ms|Subtest)\b)/gm, "");
|
|
19486
|
+
const mod = /^Error(?: \[\w+\])?: Cannot find (?:module|package) '([^']+)'(?:(?!\u2716)[\s\S])*?\nNode\.js v/m.exec(o), top = /^(\S+):\d+\n.*\n.*\n\n?(?:ReferenceError|SyntaxError)\b/m.exec(o);
|
|
19487
|
+
const named = [...o.matchAll(/^(?:\u2716|\s*not ok \d+ -) (?!failing tests:)(.+?)(?: \(|$)/gm)].some((m) => !f.path.endsWith(m[1]) && !m[1].endsWith(f.path)), blk = o.split("failing tests:")[1] ?? "", esm = /^SyntaxError: The requested module '([^']+)' does not provide an export named/m.exec(o), errs = [...blk.matchAll(/^ {2}(\w+Error): .*\n\s+at (?:.*\()?([^\s()]+?):\d+:\d+/gm), ...o.matchAll(/^ +name: '(\w+Error)'\n(?:.*\n)*? +stack: \|-\n\s+(?:.*\()?([^\s()]+?):\d+:\d+/gm)];
|
|
19488
|
+
return /^(?:#|\u2139) fail [1-9]/m.test(raw2) && (/^ {2}AssertionError\b/m.test(blk) || /^ +name: 'AssertionError'/m.test(o) || (mod && !named ? /^[./]/.test(mod[1]) : esm ? /^[./]/.test(esm[1]) : top ? !top[1].endsWith(f.path) : !errs.length || errs.some((e) => !/^(?:Reference|Syntax)Error$/.test(e[1]) || !(e[2].startsWith("node:") || e[2].endsWith(f.path)))));
|
|
19489
|
+
}
|
|
19490
|
+
function classify(f, status, output, repoDir, interpreter) {
|
|
19491
|
+
if (interpreter === "system")
|
|
19492
|
+
return "not_run";
|
|
19493
|
+
if (status === null || status === 126 || status === 127)
|
|
19494
|
+
return "not_run";
|
|
19495
|
+
if (status === 0)
|
|
19496
|
+
return classifyCheck({ kind: "test", ok: true, out: output, path: f.path, ...f.runner === "go" ? { runner: "go" } : {} }).result === "pass" ? "pass" : "not_run";
|
|
19497
|
+
if (f.runner === "pytest")
|
|
19498
|
+
return status === 1 ? pytestExit1IsRed(output) ? "fail" : "not_run" : status === 2 ? pytestCollectionIsRed(output, repoDir) ? "fail" : "not_run" : "not_run";
|
|
19499
|
+
if (f.runner === "jest" || f.runner === "vitest" || f.runner === "bun")
|
|
19500
|
+
return parsedFailCount(f.runner, output) > 0 ? "fail" : "not_run";
|
|
19501
|
+
return f.runner === "node" && nodeIsRed(f, output) ? "fail" : "not_run";
|
|
19502
|
+
}
|
|
19503
|
+
|
|
19504
|
+
class RealBaseTestRunner {
|
|
19505
|
+
api;
|
|
19506
|
+
timeoutMs;
|
|
19507
|
+
constructor(api, timeoutMs = BASE_RUN_TIMEOUT_MS) {
|
|
19508
|
+
this.api = api;
|
|
19509
|
+
this.timeoutMs = timeoutMs;
|
|
19510
|
+
}
|
|
19511
|
+
run(repoDir, files) {
|
|
19512
|
+
let pass = 0, fail = 0, not_run = 0;
|
|
19513
|
+
const api = this.api === undefined ? loadProjectApi(repoDir) : this.api;
|
|
19514
|
+
for (const f of files) {
|
|
19515
|
+
const { cmd, args, interpreter, cwd } = commandFor(f, repoDir, api);
|
|
19516
|
+
const r = spawnSync3(cmd, f.runner === "pytest" ? [...args, "-rfE"] : args, { cwd, encoding: "utf8", timeout: Math.max(1000, this.timeoutMs), env: process.env });
|
|
19517
|
+
const status = r.error ? null : r.status;
|
|
19518
|
+
const result = classify(f, status, `${r.stdout ?? ""}
|
|
19519
|
+
${r.stderr ?? ""}`, repoDir, interpreter);
|
|
19520
|
+
if (result === "pass")
|
|
19521
|
+
pass++;
|
|
19522
|
+
else if (result === "fail")
|
|
19523
|
+
fail++;
|
|
19524
|
+
else
|
|
19525
|
+
not_run++;
|
|
19526
|
+
}
|
|
19527
|
+
return { pass, fail, not_run };
|
|
19528
|
+
}
|
|
19529
|
+
}
|
|
19530
|
+
|
|
19531
|
+
// ../../loki-ts/src/e10ext/discard.ts
|
|
19532
|
+
import { chmodSync as chmodSync4, closeSync as closeSync6, lstatSync as lstatSync6, mkdirSync as mkdirSync10, openSync as openSync6, readFileSync as readFileSync26, realpathSync as realpathSync18, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
19533
|
+
import { dirname as dirname12, join as join40, relative as relative6, sep as sep11 } from "path";
|
|
19534
|
+
function safeRestore(repoDir, f, want, mode) {
|
|
19535
|
+
const root = realpathSync18(repoDir), fp = join40(root, f), rel = relative6(root, dirname12(fp));
|
|
19536
|
+
if (rel.startsWith(".."))
|
|
19537
|
+
throw new Error("outside repo");
|
|
19538
|
+
let cur = root;
|
|
19539
|
+
for (const part of rel === "" ? [] : rel.split(sep11)) {
|
|
19540
|
+
cur = join40(cur, part);
|
|
19541
|
+
let st2;
|
|
19542
|
+
try {
|
|
19543
|
+
st2 = lstatSync6(cur);
|
|
19544
|
+
} catch {
|
|
19545
|
+
mkdirSync10(cur);
|
|
19546
|
+
continue;
|
|
19547
|
+
}
|
|
19548
|
+
if (!st2.isDirectory())
|
|
19549
|
+
throw new Error("parent is not a real directory");
|
|
19550
|
+
}
|
|
19551
|
+
let st;
|
|
19552
|
+
try {
|
|
19553
|
+
st = lstatSync6(fp);
|
|
19554
|
+
} catch {
|
|
19555
|
+
st = null;
|
|
19556
|
+
}
|
|
19557
|
+
if (st?.isFile() && readFileSync26(fp).equals(want)) {
|
|
19558
|
+
chmodSync4(fp, mode);
|
|
19559
|
+
return;
|
|
19560
|
+
}
|
|
19561
|
+
if (st) {
|
|
19562
|
+
if (st.isDirectory())
|
|
19563
|
+
rmdirSync(fp);
|
|
19564
|
+
else
|
|
19565
|
+
unlinkSync3(fp);
|
|
19566
|
+
}
|
|
19567
|
+
const fd = openSync6(fp, "wx", mode);
|
|
19568
|
+
try {
|
|
19569
|
+
writeFileSync13(fd, want);
|
|
19570
|
+
} finally {
|
|
19571
|
+
closeSync6(fd);
|
|
19572
|
+
}
|
|
19573
|
+
chmodSync4(fp, mode);
|
|
19574
|
+
}
|
|
19575
|
+
function alreadySatisfied(o) {
|
|
19576
|
+
const b = o.wall?.base_run ?? {};
|
|
19577
|
+
return o.intake?.already_satisfied === true || o.implement?.exit === "already_done" || typeof b.pass === "number" && b.pass > 0 && b.fail === 0 && (b.not_run ?? 0) === 0;
|
|
19578
|
+
}
|
|
19579
|
+
function catBlob(repoDir, sha2) {
|
|
19580
|
+
const r = safeGitSpawn(repoDir, ["cat-file", "blob", sha2], { maxBuffer: 1 << 28 });
|
|
19581
|
+
if (r.error || r.status !== 0)
|
|
19582
|
+
throw r.error ?? new Error(`git cat-file blob ${sha2} failed`);
|
|
19583
|
+
return r.stdout;
|
|
19584
|
+
}
|
|
19585
|
+
async function discardIfSatisfied(git2, base, o, staged, keep, repoDir) {
|
|
19586
|
+
if (!alreadySatisfied(o))
|
|
19587
|
+
return null;
|
|
19588
|
+
const pre = Object.assign(Object.create(null), o.intake?.preexisting_dirty ?? {}), own = new Set([...Array.isArray(o.intake?.preexisting_untracked) ? o.intake.preexisting_untracked : [], ...Object.keys(o.intake?.preexisting_untracked_blobs ?? {})]);
|
|
19589
|
+
const gone = staged.filter(({ f }) => !keep.has(f) && !own.has(f) && !Object.hasOwn(pre, f) && !f.startsWith(".loki/")), lit = ["--literal-pathspecs"];
|
|
19590
|
+
const del = gone.filter(({ st }) => st === "A").map(({ f }) => f), back = gone.filter(({ st }) => st !== "A").map(({ f }) => f);
|
|
19591
|
+
if (del.length > 0 && (await git2([...lit, "rm", "-q", "-f", "--", ...del])).code !== 0 || back.length > 0 && (await git2([...lit, "restore", `--source=${base}`, "--staged", "--worktree", "--", ...back])).code !== 0 || (await git2(["reset", "-q", base])).code !== 0)
|
|
19592
|
+
return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
|
|
19593
|
+
const notProven = [];
|
|
19594
|
+
for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
|
|
19595
|
+
const fp = join40(repoDir, f);
|
|
19596
|
+
try {
|
|
19597
|
+
if (v.startsWith("!")) {
|
|
19598
|
+
const st = lstatSync6(fp);
|
|
19599
|
+
if (`!${st.size}:${st.mtimeMs}:${st.ctimeMs}` !== v)
|
|
19600
|
+
throw new Error("changed, not snapshotted");
|
|
19601
|
+
continue;
|
|
19602
|
+
}
|
|
19603
|
+
const [sha2, mode] = v.split(" "), want = catBlob(repoDir, sha2);
|
|
19604
|
+
safeRestore(repoDir, f, want, parseInt(mode ?? "644", 8));
|
|
19605
|
+
} catch {
|
|
19606
|
+
notProven.push(f);
|
|
19607
|
+
}
|
|
19608
|
+
}
|
|
19609
|
+
for (const f of Object.keys(pre))
|
|
19610
|
+
if (staged.some((s) => s.f === f) && !keep.has(f)) {
|
|
19611
|
+
try {
|
|
19612
|
+
let m = 420;
|
|
19613
|
+
try {
|
|
19614
|
+
const l = lstatSync6(join40(repoDir, f));
|
|
19615
|
+
if (l.isFile())
|
|
19616
|
+
m = l.mode & 511;
|
|
19617
|
+
} catch {}
|
|
19618
|
+
safeRestore(repoDir, f, catBlob(repoDir, pre[f]), m);
|
|
19619
|
+
} catch {
|
|
19620
|
+
notProven.push(f);
|
|
19621
|
+
}
|
|
19622
|
+
}
|
|
19623
|
+
return { status: "completed", data: { committed: false, discarded: gone.length, ...notProven.length > 0 ? { not_proven: notProven.map((f) => `pre-existing file not restored: ${f}`) } : {} } };
|
|
19624
|
+
}
|
|
19625
|
+
|
|
19626
|
+
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
19627
|
+
import { dirname as dirname13 } from "path";
|
|
19628
|
+
|
|
19629
|
+
// ../../loki-ts/src/e10ext/preexisting_dirty.ts
|
|
19630
|
+
var blob2 = (repoDir, path, env = process.env) => safeGit(repoDir, ["hash-object", "-w", "--no-filters", "--", path], { env }).trim();
|
|
19631
|
+
function untouchedSinceIntake(repoDir, recorded, env = process.env) {
|
|
19632
|
+
if (!recorded || typeof recorded !== "object")
|
|
19633
|
+
return [];
|
|
19634
|
+
return Object.entries(recorded).filter(([p, h]) => {
|
|
19635
|
+
try {
|
|
19636
|
+
return blob2(repoDir, p, env) === h;
|
|
19637
|
+
} catch {
|
|
19638
|
+
return false;
|
|
19639
|
+
}
|
|
19640
|
+
}).map(([p]) => p);
|
|
19641
|
+
}
|
|
19642
|
+
|
|
19643
|
+
// ../../loki-ts/src/e10ext/commit_filter.ts
|
|
19644
|
+
var parseStaged = (out2) => out2.split("\x00").reduce((a, t, i, all) => i % 2 === 0 && t ? [...a, { st: t, f: all[i + 1] }] : a, []);
|
|
19645
|
+
function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
|
|
19646
|
+
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) && dirname13(m) === dirname13(f)) && /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|poetry\.lock|Cargo\.lock|go\.sum)$/.test(f));
|
|
18232
19647
|
}
|
|
18233
19648
|
|
|
19649
|
+
// ../../loki-ts/src/e10ext/scope.ts
|
|
19650
|
+
import { basename as basename15, dirname as dirname14 } from "path";
|
|
19651
|
+
|
|
18234
19652
|
// ../../loki-ts/src/features/speed/unit_mode.ts
|
|
18235
|
-
import { closeSync as
|
|
19653
|
+
import { closeSync as closeSync7, constants, fstatSync as fstatSync4, lstatSync as lstatSync7, openSync as openSync7, readSync as readSync5, realpathSync as realpathSync19 } from "fs";
|
|
18236
19654
|
var MAX_SPEC_BYTES = 1024 * 1024;
|
|
18237
19655
|
var MAX_TOKEN_BUDGET = 1e9;
|
|
18238
19656
|
var norm2 = (p) => p.replace(/^\.\//, "");
|
|
18239
19657
|
var CTRL = /[\x00-\x1f\x7f]/;
|
|
18240
19658
|
var strs = (v) => Array.isArray(v) && v.every((x) => typeof x === "string" && !CTRL.test(x)) ? v.map(norm2) : null;
|
|
18241
19659
|
function readBounded(path) {
|
|
18242
|
-
const l =
|
|
19660
|
+
const l = lstatSync7(path);
|
|
18243
19661
|
if (l.isSymbolicLink() || !l.isFile() || l.size > MAX_SPEC_BYTES)
|
|
18244
19662
|
return null;
|
|
18245
|
-
const fd =
|
|
19663
|
+
const fd = openSync7(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
18246
19664
|
try {
|
|
18247
19665
|
const st = fstatSync4(fd);
|
|
18248
19666
|
if (!st.isFile() || st.size > MAX_SPEC_BYTES)
|
|
@@ -18250,7 +19668,7 @@ function readBounded(path) {
|
|
|
18250
19668
|
const buf = Buffer.alloc(st.size + 1);
|
|
18251
19669
|
let n2 = 0;
|
|
18252
19670
|
for (;; ) {
|
|
18253
|
-
const r =
|
|
19671
|
+
const r = readSync5(fd, buf, n2, buf.length - n2, null);
|
|
18254
19672
|
if (r <= 0)
|
|
18255
19673
|
break;
|
|
18256
19674
|
n2 += r;
|
|
@@ -18259,7 +19677,7 @@ function readBounded(path) {
|
|
|
18259
19677
|
}
|
|
18260
19678
|
return buf.subarray(0, n2).toString("utf8");
|
|
18261
19679
|
} finally {
|
|
18262
|
-
|
|
19680
|
+
closeSync7(fd);
|
|
18263
19681
|
}
|
|
18264
19682
|
}
|
|
18265
19683
|
function parseText(txt) {
|
|
@@ -18300,8 +19718,8 @@ function unitFlags(staged, env = process.env, pre = {}) {
|
|
|
18300
19718
|
// ../../loki-ts/src/e10ext/scope.ts
|
|
18301
19719
|
var SCOPE_UNDETERMINED = "scope not determined; all edits committed";
|
|
18302
19720
|
var outsideNote = (f) => `outside stated scope: ${f}`;
|
|
18303
|
-
var
|
|
18304
|
-
var mentions = (text2, name) => new RegExp(`(?<![\\w./-])${
|
|
19721
|
+
var esc2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19722
|
+
var mentions = (text2, name) => new RegExp(`(?<![\\w./-])${esc2(name)}(?![\\w-]|\\.\\w)`).test(text2);
|
|
18305
19723
|
var STOP = new Set(["the", "and", "for", "with", "that", "this", "all", "any", "src", "from", "into", "each", "across", "apply", "add", "use", "make"]);
|
|
18306
19724
|
var stem = (w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w;
|
|
18307
19725
|
var surfaceTokens = (text2) => new Set((text2.toLowerCase().match(/[a-z][a-z0-9_-]{2,}/g) ?? []).filter((w) => !STOP.has(w)).map(stem));
|
|
@@ -18319,8 +19737,8 @@ function outsideScope(o, staged) {
|
|
|
18319
19737
|
const text2 = `${plan}
|
|
18320
19738
|
${typeof o.intake?.task === "string" ? o.intake.task : ""}
|
|
18321
19739
|
${contractText(o)}`, pre = o.intake?.preexisting_dirty ?? {};
|
|
18322
|
-
const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) =>
|
|
18323
|
-
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2,
|
|
19740
|
+
const toks = surfaceTokens(text2), plannedDirs = new Set(rel.map((r) => dirname14(r)).filter((d) => d !== "."));
|
|
19741
|
+
return staged.filter(({ st, f }) => st !== "A" && !isTestFile(f) && !Object.hasOwn(pre, f) && !rel.includes(f) && !mentions(text2, f) && !mentions(text2, basename15(f)) && !plannedDirs.has(dirname14(f)) && !dirSegs(f).some((d) => toks.has(d))).map(({ f }) => f);
|
|
18324
19742
|
}
|
|
18325
19743
|
function flagOutsideScope(o, staged) {
|
|
18326
19744
|
const unit = unitFlags(staged, process.env, o.intake?.preexisting_dirty ?? {});
|
|
@@ -18331,80 +19749,96 @@ function flagOutsideScope(o, staged) {
|
|
|
18331
19749
|
// ../../loki-ts/src/util/receipt_signer.ts
|
|
18332
19750
|
var RECEIPT_SIGNER_BASENAME = "receipt-ed25519.pem";
|
|
18333
19751
|
|
|
18334
|
-
// ../../loki-ts/src/util/
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
19752
|
+
// ../../loki-ts/src/util/pr_lessons.ts
|
|
19753
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync27 } from "fs";
|
|
19754
|
+
import { dirname as dirname16, join as join41 } from "path";
|
|
19755
|
+
|
|
19756
|
+
// ../../loki-ts/src/util/atomic.ts
|
|
19757
|
+
import {
|
|
19758
|
+
closeSync as closeSync8,
|
|
19759
|
+
fstatSync as fstatSync5,
|
|
19760
|
+
fsyncSync as fsyncSync2,
|
|
19761
|
+
lstatSync as lstatSync8,
|
|
19762
|
+
mkdirSync as mkdirSync11,
|
|
19763
|
+
openSync as openSync8,
|
|
19764
|
+
readSync as readSync6,
|
|
19765
|
+
renameSync as renameSync5,
|
|
19766
|
+
rmSync as rmSync10,
|
|
19767
|
+
statSync as statSync12,
|
|
19768
|
+
unlinkSync as unlinkSync4,
|
|
19769
|
+
writeSync as writeSync3
|
|
19770
|
+
} from "fs";
|
|
19771
|
+
import { dirname as dirname15 } from "path";
|
|
19772
|
+
var _tmpCounter = 0;
|
|
19773
|
+
function durableWriteThenRename(target, body) {
|
|
19774
|
+
mkdirSync11(dirname15(target), { recursive: true });
|
|
19775
|
+
const tmp = `${target}.tmp.${process.pid}.${++_tmpCounter}`;
|
|
19776
|
+
const fd = openSync8(tmp, "w");
|
|
18341
19777
|
try {
|
|
18342
|
-
|
|
18343
|
-
|
|
18344
|
-
if (done)
|
|
18345
|
-
break;
|
|
18346
|
-
if (!value)
|
|
18347
|
-
continue;
|
|
18348
|
-
total += value.byteLength;
|
|
18349
|
-
if (total > maxBytes) {
|
|
18350
|
-
const keep = value.byteLength - (total - maxBytes);
|
|
18351
|
-
out2 += decoder.decode(value.subarray(0, keep), { stream: true });
|
|
18352
|
-
break;
|
|
18353
|
-
}
|
|
18354
|
-
out2 += decoder.decode(value, { stream: true });
|
|
18355
|
-
}
|
|
18356
|
-
out2 += decoder.decode();
|
|
19778
|
+
writeSync3(fd, body);
|
|
19779
|
+
fsyncSync2(fd);
|
|
18357
19780
|
} finally {
|
|
18358
|
-
|
|
18359
|
-
await reader.cancel();
|
|
18360
|
-
} catch {}
|
|
18361
|
-
reader.releaseLock();
|
|
19781
|
+
closeSync8(fd);
|
|
18362
19782
|
}
|
|
18363
|
-
|
|
19783
|
+
renameSync5(tmp, target);
|
|
19784
|
+
fsyncParentDir(target);
|
|
18364
19785
|
}
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
});
|
|
18373
|
-
let timer;
|
|
18374
|
-
let killTimer;
|
|
18375
|
-
if (opts.timeoutMs && opts.timeoutMs > 0) {
|
|
18376
|
-
timer = setTimeout(() => {
|
|
19786
|
+
function fsyncParentDir(target) {
|
|
19787
|
+
let dfd = null;
|
|
19788
|
+
try {
|
|
19789
|
+
dfd = openSync8(dirname15(target), "r");
|
|
19790
|
+
fsyncSync2(dfd);
|
|
19791
|
+
} catch {} finally {
|
|
19792
|
+
if (dfd !== null) {
|
|
18377
19793
|
try {
|
|
18378
|
-
|
|
19794
|
+
closeSync8(dfd);
|
|
18379
19795
|
} catch {}
|
|
18380
|
-
|
|
18381
|
-
try {
|
|
18382
|
-
proc.kill("SIGKILL");
|
|
18383
|
-
} catch {}
|
|
18384
|
-
}, 2000);
|
|
18385
|
-
}, opts.timeoutMs);
|
|
19796
|
+
}
|
|
18386
19797
|
}
|
|
19798
|
+
}
|
|
19799
|
+
function atomicWriteJson(target, data) {
|
|
19800
|
+
durableWriteThenRename(target, `${JSON.stringify(data, null, 2)}
|
|
19801
|
+
`);
|
|
19802
|
+
}
|
|
19803
|
+
var _appendChains = new Map;
|
|
19804
|
+
|
|
19805
|
+
// ../../loki-ts/src/util/pr_lessons.ts
|
|
19806
|
+
var lessonsPath = (repoDir) => join41(repoDir, ".loki", "memory", "semantic", "pr-lessons.json");
|
|
19807
|
+
function loadLessons(repoDir) {
|
|
19808
|
+
const p = lessonsPath(repoDir);
|
|
19809
|
+
if (!existsSync22(p))
|
|
19810
|
+
return [];
|
|
18387
19811
|
try {
|
|
18388
|
-
const
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
]);
|
|
18393
|
-
return { stdout, stderr, exitCode };
|
|
18394
|
-
} finally {
|
|
18395
|
-
if (timer)
|
|
18396
|
-
clearTimeout(timer);
|
|
18397
|
-
if (killTimer)
|
|
18398
|
-
clearTimeout(killTimer);
|
|
19812
|
+
const j = JSON.parse(readFileSync27(p, "utf-8"));
|
|
19813
|
+
return Array.isArray(j.lessons) ? j.lessons : [];
|
|
19814
|
+
} catch {
|
|
19815
|
+
return [];
|
|
18399
19816
|
}
|
|
18400
19817
|
}
|
|
19818
|
+
function save(repoDir, lessons) {
|
|
19819
|
+
const p = lessonsPath(repoDir);
|
|
19820
|
+
mkdirSync12(dirname16(p), { recursive: true });
|
|
19821
|
+
atomicWriteJson(p, { version: 1, lessons });
|
|
19822
|
+
}
|
|
19823
|
+
function recordRunVerdict(repoDir, runId, verdict) {
|
|
19824
|
+
const lessons = loadLessons(repoDir);
|
|
19825
|
+
let dirty = false;
|
|
19826
|
+
for (const l of lessons)
|
|
19827
|
+
for (const u of l.uses)
|
|
19828
|
+
if (u.run_id === runId && u.verdict === null) {
|
|
19829
|
+
u.verdict = verdict;
|
|
19830
|
+
dirty = true;
|
|
19831
|
+
}
|
|
19832
|
+
if (dirty)
|
|
19833
|
+
save(repoDir, lessons);
|
|
19834
|
+
}
|
|
18401
19835
|
|
|
18402
19836
|
// ../../loki-ts/src/features/visual_evidence.ts
|
|
18403
19837
|
import { spawn as spawn7 } from "child_process";
|
|
18404
|
-
import { createHash as
|
|
18405
|
-
import { existsSync as
|
|
19838
|
+
import { createHash as createHash8, randomBytes as randomBytes2 } from "crypto";
|
|
19839
|
+
import { existsSync as existsSync23, lstatSync as lstatSync9, mkdirSync as mkdirSync13, readdirSync as readdirSync12, readFileSync as readFileSync28, writeFileSync as writeFileSync14 } from "fs";
|
|
18406
19840
|
import { createServer } from "net";
|
|
18407
|
-
import { dirname as
|
|
19841
|
+
import { dirname as dirname17, isAbsolute as isAbsolute6, join as join42, normalize as normalize2, relative as relative7, sep as sep12 } from "path";
|
|
18408
19842
|
var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0";
|
|
18409
19843
|
var PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
18410
19844
|
var isPageFile = (p) => PAGE_RE.test(p);
|
|
@@ -18414,12 +19848,12 @@ function routeFor(file) {
|
|
|
18414
19848
|
return `/${r}`.replace(/\/+/g, "/");
|
|
18415
19849
|
}
|
|
18416
19850
|
var screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index";
|
|
18417
|
-
var sha256File = (p) =>
|
|
19851
|
+
var sha256File = (p) => createHash8("sha256").update(readFileSync28(p)).digest("hex");
|
|
18418
19852
|
function hashScreens(runDir, rels) {
|
|
18419
19853
|
const out2 = [];
|
|
18420
19854
|
for (const rel of rels) {
|
|
18421
19855
|
try {
|
|
18422
|
-
out2.push({ path: rel, sha256: sha256File(
|
|
19856
|
+
out2.push({ path: rel, sha256: sha256File(join42(runDir, rel)) });
|
|
18423
19857
|
} catch {}
|
|
18424
19858
|
}
|
|
18425
19859
|
return out2;
|
|
@@ -18430,16 +19864,16 @@ function checkScreens(runDir, screens) {
|
|
|
18430
19864
|
for (const s of screens) {
|
|
18431
19865
|
if (typeof s?.path !== "string" || typeof s.sha256 !== "string")
|
|
18432
19866
|
return "evidence_screens entry is malformed";
|
|
18433
|
-
if (
|
|
19867
|
+
if (isAbsolute6(s.path) || normalize2(s.path).startsWith(".."))
|
|
18434
19868
|
return `evidence screenshot path escapes the evidence dir: ${s.path}`;
|
|
18435
|
-
const file =
|
|
19869
|
+
const file = join42(runDir, s.path);
|
|
18436
19870
|
let cur = runDir;
|
|
18437
19871
|
for (const seg of normalize2(s.path).split(sep12)) {
|
|
18438
|
-
cur =
|
|
19872
|
+
cur = join42(cur, seg);
|
|
18439
19873
|
if (isSymlink(cur))
|
|
18440
19874
|
return `evidence screenshot path is a symlink: ${s.path}`;
|
|
18441
19875
|
}
|
|
18442
|
-
if (!
|
|
19876
|
+
if (!existsSync23(file))
|
|
18443
19877
|
return `evidence screenshot is missing: ${s.path}`;
|
|
18444
19878
|
if (!isRegularFile(file))
|
|
18445
19879
|
return `evidence screenshot is not a regular file: ${s.path}`;
|
|
@@ -18481,16 +19915,16 @@ async function waitUp(url, ms, signal) {
|
|
|
18481
19915
|
return false;
|
|
18482
19916
|
}
|
|
18483
19917
|
var playwrightBin = (repoDir) => {
|
|
18484
|
-
const bin =
|
|
18485
|
-
return
|
|
19918
|
+
const bin = join42(repoDir, "node_modules", ".bin", "playwright");
|
|
19919
|
+
return existsSync23(bin) ? bin : null;
|
|
18486
19920
|
};
|
|
18487
19921
|
function openapiPaths(repoDir) {
|
|
18488
19922
|
for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
|
|
18489
|
-
const p =
|
|
18490
|
-
if (!
|
|
19923
|
+
const p = join42(repoDir, f);
|
|
19924
|
+
if (!existsSync23(p))
|
|
18491
19925
|
continue;
|
|
18492
19926
|
try {
|
|
18493
|
-
const txt =
|
|
19927
|
+
const txt = readFileSync28(p, "utf8");
|
|
18494
19928
|
const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
|
|
18495
19929
|
return paths.filter((x) => !x.includes("{")).slice(0, 10);
|
|
18496
19930
|
} catch {
|
|
@@ -18500,8 +19934,8 @@ function openapiPaths(repoDir) {
|
|
|
18500
19934
|
return [];
|
|
18501
19935
|
}
|
|
18502
19936
|
var playwrightTestPkg = (repoDir) => {
|
|
18503
|
-
const d =
|
|
18504
|
-
return
|
|
19937
|
+
const d = join42(repoDir, "node_modules", "@playwright", "test");
|
|
19938
|
+
return existsSync23(join42(d, "package.json")) ? d : null;
|
|
18505
19939
|
};
|
|
18506
19940
|
var e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
|
|
18507
19941
|
test("loki e2e walkthrough", async ({ page }) => {
|
|
@@ -18513,24 +19947,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
|
|
|
18513
19947
|
if (!pw || !pkg)
|
|
18514
19948
|
return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
|
|
18515
19949
|
try {
|
|
18516
|
-
const dir =
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
|
|
19950
|
+
const dir = join42(runDir, relDir, "e2e");
|
|
19951
|
+
mkdirSync13(dir, { recursive: true });
|
|
19952
|
+
writeFileSync14(join42(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
|
|
19953
|
+
writeFileSync14(join42(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
|
|
18520
19954
|
`);
|
|
18521
|
-
if (!await shoot(pw, ["test", "--config",
|
|
19955
|
+
if (!await shoot(pw, ["test", "--config", join42(dir, "playwright.config.cjs")], repoDir, ms, signal))
|
|
18522
19956
|
return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
|
|
18523
19957
|
const rels = [];
|
|
18524
19958
|
const walk = (d) => {
|
|
18525
|
-
for (const e of
|
|
18526
|
-
const full =
|
|
19959
|
+
for (const e of readdirSync12(d, { withFileTypes: true })) {
|
|
19960
|
+
const full = join42(d, e.name);
|
|
18527
19961
|
if (e.isDirectory())
|
|
18528
19962
|
walk(full);
|
|
18529
19963
|
else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
|
|
18530
|
-
rels.push(
|
|
19964
|
+
rels.push(join42(relDir, "e2e", relative7(dir, full)));
|
|
18531
19965
|
}
|
|
18532
19966
|
};
|
|
18533
|
-
walk(
|
|
19967
|
+
walk(join42(dir, "out"));
|
|
18534
19968
|
return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
|
|
18535
19969
|
} catch (e) {
|
|
18536
19970
|
return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
|
|
@@ -18568,14 +20002,14 @@ function shoot(pw, args, cwd, ms, signal) {
|
|
|
18568
20002
|
}
|
|
18569
20003
|
var isSymlink = (p) => {
|
|
18570
20004
|
try {
|
|
18571
|
-
return
|
|
20005
|
+
return lstatSync9(p).isSymbolicLink();
|
|
18572
20006
|
} catch {
|
|
18573
20007
|
return false;
|
|
18574
20008
|
}
|
|
18575
20009
|
};
|
|
18576
20010
|
var isRegularFile = (p) => {
|
|
18577
20011
|
try {
|
|
18578
|
-
return
|
|
20012
|
+
return lstatSync9(p).isFile();
|
|
18579
20013
|
} catch {
|
|
18580
20014
|
return false;
|
|
18581
20015
|
}
|
|
@@ -18624,13 +20058,13 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
18624
20058
|
if (!visualEvidenceEnabled(opts.env))
|
|
18625
20059
|
return skip("LOKI_VISUAL_EVIDENCE is 0");
|
|
18626
20060
|
const api = loadProjectApi(repoDir), boot = isMultiRoot(api) ? api.uiBoot() : null;
|
|
18627
|
-
const appDir = boot ?
|
|
18628
|
-
const pkgPath =
|
|
20061
|
+
const appDir = boot ? join42(repoDir, boot.boot.cwd) : repoDir;
|
|
20062
|
+
const pkgPath = join42(appDir, "package.json");
|
|
18629
20063
|
let script = null;
|
|
18630
20064
|
if (!boot) {
|
|
18631
|
-
if (!
|
|
20065
|
+
if (!existsSync23(pkgPath))
|
|
18632
20066
|
return skip("no package.json");
|
|
18633
|
-
script = pickScript(JSON.parse(
|
|
20067
|
+
script = pickScript(JSON.parse(readFileSync28(pkgPath, "utf8")));
|
|
18634
20068
|
if (!script)
|
|
18635
20069
|
return skip("no dev, preview or start script");
|
|
18636
20070
|
}
|
|
@@ -18657,20 +20091,20 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
18657
20091
|
}
|
|
18658
20092
|
if (!await waitUp(base, left(), signal))
|
|
18659
20093
|
return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
|
|
18660
|
-
const evRoot =
|
|
20094
|
+
const evRoot = join42(runDir, "evidence");
|
|
18661
20095
|
if (isSymlink(evRoot))
|
|
18662
20096
|
return skip("evidence dir is a symlink");
|
|
18663
|
-
|
|
20097
|
+
mkdirSync13(evRoot, { recursive: true });
|
|
18664
20098
|
if (pages.length > 0) {
|
|
18665
|
-
const rel0 =
|
|
18666
|
-
|
|
20099
|
+
const rel0 = join42("evidence", randomBytes2(6).toString("hex"));
|
|
20100
|
+
mkdirSync13(join42(runDir, rel0));
|
|
18667
20101
|
const rels = [];
|
|
18668
20102
|
for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
|
|
18669
20103
|
if (aborted() || left() <= 0)
|
|
18670
20104
|
break;
|
|
18671
|
-
const rel =
|
|
18672
|
-
const ok = await shoot(pw, ["screenshot", `${base}${route}`,
|
|
18673
|
-
if (ok && !aborted() && !isSymlink(
|
|
20105
|
+
const rel = join42(rel0, `${screenName(route)}.png`);
|
|
20106
|
+
const ok = await shoot(pw, ["screenshot", `${base}${route}`, join42(runDir, rel)], appDir, left(), signal);
|
|
20107
|
+
if (ok && !aborted() && !isSymlink(join42(runDir, rel)) && existsSync23(join42(runDir, rel)))
|
|
18674
20108
|
rels.push(rel);
|
|
18675
20109
|
}
|
|
18676
20110
|
if (aborted())
|
|
@@ -18695,9 +20129,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
18695
20129
|
}
|
|
18696
20130
|
if (aborted())
|
|
18697
20131
|
return skip("aborted");
|
|
18698
|
-
const hdir =
|
|
18699
|
-
|
|
18700
|
-
|
|
20132
|
+
const hdir = join42(evRoot, randomBytes2(6).toString("hex"));
|
|
20133
|
+
mkdirSync13(hdir);
|
|
20134
|
+
writeFileSync14(join42(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
|
|
18701
20135
|
`);
|
|
18702
20136
|
return { screens: [], http: true, skipped: null };
|
|
18703
20137
|
} catch (e) {
|
|
@@ -18720,12 +20154,13 @@ async function sealEvidence(repoDir, runDir, o, notProven, signal, emit) {
|
|
|
18720
20154
|
return ev.screens.length > 0 ? { evidence_screens: ev.screens } : {};
|
|
18721
20155
|
}
|
|
18722
20156
|
function receiptScreensProblem(receiptPath, receipt) {
|
|
18723
|
-
return receipt["evidence_screens"] === undefined ? null : checkScreens(
|
|
20157
|
+
return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname17(receiptPath), receipt["evidence_screens"]);
|
|
18724
20158
|
}
|
|
18725
20159
|
|
|
18726
20160
|
// ../../loki-ts/src/engine10/stages/xreview.ts
|
|
18727
|
-
|
|
18728
|
-
import {
|
|
20161
|
+
init_shell();
|
|
20162
|
+
import { existsSync as existsSync24, readFileSync as readFileSync29 } from "fs";
|
|
20163
|
+
import { join as join43 } from "path";
|
|
18729
20164
|
var RANK2 = { FAILED: 0, SPEC_CONFLICT: 0, PARTIAL: 1, ALREADY_SATISFIED: 2, VERIFIED: 2 };
|
|
18730
20165
|
var TIMEOUT_MS = 300000;
|
|
18731
20166
|
var MAX_DIFF = 60000;
|
|
@@ -18743,11 +20178,11 @@ function reviewProvider(repoDir, env = process.env) {
|
|
|
18743
20178
|
if (e)
|
|
18744
20179
|
return e;
|
|
18745
20180
|
for (const f of ["loki.yaml", "loki.yml"]) {
|
|
18746
|
-
const p =
|
|
18747
|
-
if (!
|
|
20181
|
+
const p = join43(repoDir, f);
|
|
20182
|
+
if (!existsSync24(p))
|
|
18748
20183
|
continue;
|
|
18749
20184
|
try {
|
|
18750
|
-
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(
|
|
20185
|
+
const m = /^review:[ \t]*["']?([A-Za-z]+)["']?[ \t]*(?:#.*)?$/m.exec(readFileSync29(p, "utf8"));
|
|
18751
20186
|
const v = asProvider(m?.[1]);
|
|
18752
20187
|
if (v)
|
|
18753
20188
|
return v;
|
|
@@ -18775,7 +20210,7 @@ async function crossReview(ctx, verdict, head) {
|
|
|
18775
20210
|
return null;
|
|
18776
20211
|
if (verdict !== "VERIFIED" && verdict !== "ALREADY_SATISFIED")
|
|
18777
20212
|
return null;
|
|
18778
|
-
const d = await
|
|
20213
|
+
const d = await safeGitRun(ctx.repoDir, ["diff", "--no-color", ctx.baseSha, head, "--", ".", ":(exclude).loki"], { timeoutMs: 20000 }).catch(() => null);
|
|
18779
20214
|
if (!d || d.exitCode !== 0)
|
|
18780
20215
|
return { provider: p, level: "not_run", notes: [`cross-review not run: diff unavailable for ${p}`] };
|
|
18781
20216
|
const prompt = [
|
|
@@ -18797,17 +20232,6 @@ async function crossReview(ctx, verdict, head) {
|
|
|
18797
20232
|
}
|
|
18798
20233
|
}
|
|
18799
20234
|
|
|
18800
|
-
// ../../loki-ts/src/runner/router/flag.ts
|
|
18801
|
-
function routerMode(env = process.env) {
|
|
18802
|
-
const v = (env.LOKI_ROUTER ?? "").trim().toLowerCase();
|
|
18803
|
-
if (v === "0" || v === "false" || v === "off")
|
|
18804
|
-
return "opt-out";
|
|
18805
|
-
if (v === "1" || v === "true" || v === "on")
|
|
18806
|
-
return "on";
|
|
18807
|
-
return "off";
|
|
18808
|
-
}
|
|
18809
|
-
var routerEnabled = (env = process.env) => routerMode(env) === "on";
|
|
18810
|
-
|
|
18811
20235
|
// ../../loki-ts/src/runner/router/route_block.ts
|
|
18812
20236
|
var ALIAS = { haiku: "haiku-5.5", sonnet: "sonnet-5.5", opus: "opus-5.5" };
|
|
18813
20237
|
var modelLabel = (m) => ALIAS[m] ?? m;
|
|
@@ -18824,13 +20248,14 @@ var nN = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
|
18824
20248
|
var rec = (v) => v === null ? "not recorded" : String(v);
|
|
18825
20249
|
var s0 = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
18826
20250
|
function toEsc(raw2) {
|
|
18827
|
-
const
|
|
18828
|
-
return
|
|
20251
|
+
const esc3 = Array.isArray(raw2) ? raw2 : [];
|
|
20252
|
+
return esc3.map((e) => ({ trigger: s0(e["trigger"]) ?? "unknown", from: modelLabel(s0(e["from"]) ?? "?"), to: modelLabel(s0(e["to"]) ?? "?"), evidence: s0(e["evidence"]) ?? "none recorded" }));
|
|
18829
20253
|
}
|
|
18830
20254
|
var assignedBy = (src, explicit) => s0(explicit) ?? (src === null || src === "opus" ? "opus" : src);
|
|
18831
|
-
function buildRouteBlock(env, provider, route, telemetry) {
|
|
20255
|
+
function buildRouteBlock(env, provider, route, telemetry, runModel) {
|
|
18832
20256
|
if (!routerEnabled(env))
|
|
18833
20257
|
return null;
|
|
20258
|
+
const applied = route?.["applied"] !== false;
|
|
18834
20259
|
const executor = s0(route?.["executor"]);
|
|
18835
20260
|
const unavailable = s0(route?.["advisor_unavailable_reason"]) ?? advisorUnavailableReason(env, provider);
|
|
18836
20261
|
const total = nN(telemetry?.requests_total);
|
|
@@ -18840,11 +20265,11 @@ function buildRouteBlock(env, provider, route, telemetry) {
|
|
|
18840
20265
|
const source = s0(route?.["source"]);
|
|
18841
20266
|
const reason = s0(route?.["reason"]) ?? (executor ? "no reason recorded" : "no route recorded by implement");
|
|
18842
20267
|
const rawUnits = Array.isArray(route?.["units"]) ? route["units"] : [];
|
|
18843
|
-
const units = rawUnits.length > 0 ? rawUnits.map((u, i) => ({ id: s0(u["id"]) ?? String(i + 1), executor: modelLabel(s0(u["executor"]) ?? "unknown"), assigned_by: assignedBy(s0(u["source"]) ?? source, u["assigned_by"]), reason: s0(u["reason"]) ?? "no reason recorded", escalations: toEsc(u["escalations"]) })) : executor ? [{ id: "run", executor: modelLabel(executor), assigned_by: assignedBy(source, route?.["assigned_by"]), reason, escalations }] : [];
|
|
20268
|
+
const units = rawUnits.length > 0 ? rawUnits.map((u, i) => ({ id: s0(u["id"]) ?? String(i + 1), executor: modelLabel(s0(u["executor"]) ?? "unknown"), applied, assigned_by: assignedBy(s0(u["source"]) ?? source, u["assigned_by"]), reason: s0(u["reason"]) ?? "no reason recorded", escalations: toEsc(u["escalations"]) })) : executor && route?.["routed"] !== false ? [{ id: "run", executor: modelLabel(executor), applied, assigned_by: assignedBy(source, route?.["assigned_by"]), reason, escalations }] : [];
|
|
18844
20269
|
return {
|
|
18845
|
-
routed: executor !== null || units.length > 0,
|
|
20270
|
+
routed: route?.["routed"] === false ? false : executor !== null || units.length > 0,
|
|
18846
20271
|
units,
|
|
18847
|
-
executor: executor ? modelLabel(executor) : units[0]?.executor ?? null,
|
|
20272
|
+
executor: !applied ? `${runModel ? modelLabel(runModel) : "run model"} (per-unit assignment not applied)` : executor ? modelLabel(executor) : units[0]?.executor ?? null,
|
|
18848
20273
|
advisor: unavailable ? null : modelLabel(s0(route?.["advisor"]) ?? "opus"),
|
|
18849
20274
|
advisor_unavailable_reason: unavailable,
|
|
18850
20275
|
reason,
|
|
@@ -18857,7 +20282,11 @@ function buildRouteBlock(env, provider, route, telemetry) {
|
|
|
18857
20282
|
over_100k_share: total !== null && over !== null && total > 0 ? Math.round(over / total * 1e4) / 1e4 : null,
|
|
18858
20283
|
advisor_calls: nN(telemetry?.advisor_calls),
|
|
18859
20284
|
advisor_input_tokens: nN(telemetry?.advisor_input_tokens),
|
|
18860
|
-
advisor_output_tokens: nN(telemetry?.advisor_output_tokens)
|
|
20285
|
+
advisor_output_tokens: nN(telemetry?.advisor_output_tokens),
|
|
20286
|
+
plan_model: s0(route?.["plan_model"]),
|
|
20287
|
+
applied,
|
|
20288
|
+
run_model: runModel ?? null,
|
|
20289
|
+
not_proven: Array.isArray(route?.["route_not_proven"]) ? route["route_not_proven"].filter((x) => typeof x === "string") : []
|
|
18861
20290
|
};
|
|
18862
20291
|
}
|
|
18863
20292
|
var over = (r, unit) => r.requests_total === null || r.requests_over_100k === null ? "not recorded" : `${r.requests_over_100k} of ${r.requests_total}${unit} (${pct(r)})`;
|
|
@@ -18876,119 +20305,154 @@ function routeReceiptLines(r) {
|
|
|
18876
20305
|
`- Route shape_key: ${r.shape_key ?? "unknown"} shape_parity: ${r.shape_parity}`,
|
|
18877
20306
|
`- Route escalations: ${r.escalations.length === 0 ? "none" : r.escalations.map((e) => `${e.from} -> ${e.to} (${e.trigger}; evidence ${e.evidence})`).join("; ")}`,
|
|
18878
20307
|
`- Route advisor: ${rec(r.advisor_calls)} calls, ${rec(r.advisor_input_tokens)} in / ${rec(r.advisor_output_tokens)} out tokens`,
|
|
18879
|
-
...r.units.map((u) => `- Route unit ${u.id}: executor ${u.executor}, assigned by ${u.assigned_by}: ${u.reason}; escalation: ${u.escalations.length === 0 ? "none" : u.escalations.map((e) => `${e.from} -> ${e.to} (${e.trigger})`).join(", ")}`),
|
|
20308
|
+
...r.units.map((u) => `- Route unit ${u.id}: executor ${u.applied ? u.executor : `not applied (assigned ${u.executor})`}, assigned by ${u.assigned_by}: ${u.reason}; escalation: ${u.escalations.length === 0 ? "none" : u.escalations.map((e) => `${e.from} -> ${e.to} (${e.trigger})`).join(", ")}`),
|
|
18880
20309
|
`- Route requests over 100K: ${over(r, "")}`
|
|
18881
20310
|
];
|
|
18882
20311
|
}
|
|
18883
20312
|
function routeNotProven(r) {
|
|
18884
20313
|
return [
|
|
18885
20314
|
...r.advisor_unavailable_reason ? [`advisor: NOT PROVEN (owner provider): ${r.advisor_unavailable_reason}`] : [],
|
|
18886
|
-
`route.shape_parity: ${r.shape_parity}
|
|
20315
|
+
`route.shape_parity: ${r.shape_parity}`,
|
|
20316
|
+
...r.applied ? [] : [`route.executor: NOT PROVEN (owner engine): per-unit executor assignment is not applied (R1-11); every stage ran ${r.run_model ?? "the run model"}`],
|
|
20317
|
+
...r.not_proven
|
|
18887
20318
|
];
|
|
18888
20319
|
}
|
|
18889
20320
|
|
|
18890
|
-
// ../../loki-ts/src/
|
|
18891
|
-
import {
|
|
18892
|
-
import { join as
|
|
18893
|
-
var
|
|
18894
|
-
function
|
|
18895
|
-
|
|
20321
|
+
// ../../loki-ts/src/runner/router/route_record.ts
|
|
20322
|
+
import { existsSync as existsSync25, lstatSync as lstatSync10, mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync15 } from "fs";
|
|
20323
|
+
import { join as join44 } from "path";
|
|
20324
|
+
var ROUTE_RECORD_FILE = "route.json";
|
|
20325
|
+
function loadRouteRecord(runDir) {
|
|
20326
|
+
try {
|
|
20327
|
+
const p = join44(runDir, ROUTE_RECORD_FILE);
|
|
20328
|
+
if (!existsSync25(p) || !lstatSync10(p).isFile())
|
|
20329
|
+
return;
|
|
20330
|
+
const v = JSON.parse(readFileSync30(p, "utf8"));
|
|
20331
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined;
|
|
20332
|
+
} catch {
|
|
20333
|
+
return;
|
|
20334
|
+
}
|
|
18896
20335
|
}
|
|
18897
|
-
|
|
18898
|
-
|
|
20336
|
+
|
|
20337
|
+
// ../../loki-ts/src/runner/router/history.ts
|
|
20338
|
+
import { dirname as dirname18, join as join45, resolve as resolve11 } from "path";
|
|
20339
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
20340
|
+
|
|
20341
|
+
// ../../loki-ts/src/runner/router/history_store.ts
|
|
20342
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync15, readFileSync as readFileSync31, renameSync as renameSync6, rmSync as rmSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
20343
|
+
import { resolve as resolve10 } from "path";
|
|
20344
|
+
var HISTORY_FILE = "router_history.json";
|
|
20345
|
+
var HISTORY_CAP = 200;
|
|
20346
|
+
var EXECUTORS = ["haiku", "sonnet"];
|
|
20347
|
+
var VERDICTS = ["pass", "fail", "error", "not_proven"];
|
|
20348
|
+
var OWNERS = ["code", "harness", "env", "provider"];
|
|
20349
|
+
function validRun(v) {
|
|
20350
|
+
if (typeof v !== "object" || v === null)
|
|
20351
|
+
return false;
|
|
20352
|
+
const r = v;
|
|
20353
|
+
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"]);
|
|
18899
20354
|
}
|
|
18900
|
-
function
|
|
18901
|
-
return
|
|
20355
|
+
function historyPath(key, cacheRoot) {
|
|
20356
|
+
return resolve10(repoCacheDir(key, cacheRoot), HISTORY_FILE);
|
|
18902
20357
|
}
|
|
18903
|
-
function
|
|
18904
|
-
const
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
} } : {},
|
|
18922
|
-
model: null,
|
|
18923
|
-
source: "",
|
|
18924
|
-
missing: []
|
|
18925
|
-
};
|
|
18926
|
-
const sources2 = [];
|
|
18927
|
-
let usd = 0;
|
|
18928
|
-
for (const iter of iterations) {
|
|
18929
|
-
const path = resultCostPath(lokiRoot, iter);
|
|
18930
|
-
let rec2;
|
|
20358
|
+
function readRunHistory(key, cacheRoot) {
|
|
20359
|
+
const path = historyPath(key, cacheRoot);
|
|
20360
|
+
if (!existsSync26(path))
|
|
20361
|
+
return [];
|
|
20362
|
+
try {
|
|
20363
|
+
const parsed = JSON.parse(readFileSync31(path, "utf8"));
|
|
20364
|
+
const runs2 = parsed?.runs;
|
|
20365
|
+
return Array.isArray(runs2) ? runs2.filter(validRun) : [];
|
|
20366
|
+
} catch {
|
|
20367
|
+
return [];
|
|
20368
|
+
}
|
|
20369
|
+
}
|
|
20370
|
+
function appendRunOutcome(key, outcome, cacheRoot) {
|
|
20371
|
+
try {
|
|
20372
|
+
const runs2 = [...readRunHistory(key, cacheRoot), outcome].slice(-HISTORY_CAP);
|
|
20373
|
+
mkdirSync15(repoCacheDir(key, cacheRoot), { recursive: true });
|
|
20374
|
+
const target = historyPath(key, cacheRoot);
|
|
20375
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
18931
20376
|
try {
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
const inTok = num3(rec2["input_tokens"]);
|
|
18938
|
-
const outTok = num3(rec2["output_tokens"]);
|
|
18939
|
-
const cacheR = num3(rec2["cache_read_tokens"]);
|
|
18940
|
-
const cacheC = num3(rec2["cache_creation_tokens"]);
|
|
18941
|
-
out2.input_tokens += inTok;
|
|
18942
|
-
out2.output_tokens += outTok;
|
|
18943
|
-
out2.cache_read_tokens += cacheR;
|
|
18944
|
-
out2.cache_creation_tokens += cacheC;
|
|
18945
|
-
if (out2.router)
|
|
18946
|
-
out2.router["requests_total"] += num3(rec2["requests_total"]);
|
|
18947
|
-
if (out2.router)
|
|
18948
|
-
out2.router["requests_over_100k"] += num3(rec2["requests_over_100k"]);
|
|
18949
|
-
if (out2.router)
|
|
18950
|
-
out2.router["over_100k_input_tokens"] += num3(rec2["over_100k_input_tokens"]);
|
|
18951
|
-
if (out2.router)
|
|
18952
|
-
out2.router["over_100k_output_tokens"] += num3(rec2["over_100k_output_tokens"]);
|
|
18953
|
-
if (out2.router)
|
|
18954
|
-
out2.router["advisor_calls"] += num3(rec2["advisor_calls"]);
|
|
18955
|
-
if (out2.router)
|
|
18956
|
-
out2.router["advisor_input_tokens"] += num3(rec2["advisor_input_tokens"]);
|
|
18957
|
-
if (out2.router)
|
|
18958
|
-
out2.router["advisor_output_tokens"] += num3(rec2["advisor_output_tokens"]);
|
|
18959
|
-
if (typeof rec2["model"] === "string" && rec2["model"])
|
|
18960
|
-
out2.model = rec2["model"];
|
|
18961
|
-
sources2.push(path);
|
|
18962
|
-
const c = rec2["total_cost_usd"];
|
|
18963
|
-
const unmetered = rec2["source"] === UNMETERED;
|
|
18964
|
-
if (unmetered)
|
|
18965
|
-
out2.unmetered = true;
|
|
18966
|
-
const noUsage = !unmetered && inTok === 0 && outTok === 0 && cacheR === 0 && cacheC === 0;
|
|
18967
|
-
if (typeof c !== "number" || !Number.isFinite(c) || noUsage) {
|
|
18968
|
-
out2.missing.push(iter);
|
|
18969
|
-
continue;
|
|
20377
|
+
writeFileSync16(tmp, JSON.stringify({ runs: runs2 }));
|
|
20378
|
+
renameSync6(tmp, target);
|
|
20379
|
+
} catch (e) {
|
|
20380
|
+
rmSync11(tmp, { force: true, recursive: false });
|
|
20381
|
+
throw e;
|
|
18970
20382
|
}
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
20383
|
+
return true;
|
|
20384
|
+
} catch {
|
|
20385
|
+
return false;
|
|
20386
|
+
}
|
|
20387
|
+
}
|
|
20388
|
+
// ../../loki-ts/src/runner/router/history.ts
|
|
20389
|
+
var SHAPE_DEFAULTS_FILE = resolve11(dirname18(fileURLToPath3(import.meta.url)), "..", "..", "..", "data", "router-shape-defaults.json");
|
|
20390
|
+
function shapeKey(model) {
|
|
20391
|
+
if (!model || model.status !== "ok")
|
|
20392
|
+
return null;
|
|
20393
|
+
const runners = new Set(model.packages.map((p) => (p.runner ?? "").trim().toLowerCase() || "none"));
|
|
20394
|
+
return `${model.workspaceKind}:${[...runners].sort().join("+")}`;
|
|
20395
|
+
}
|
|
20396
|
+
function shapeKeyForRepo(repoDir) {
|
|
20397
|
+
try {
|
|
20398
|
+
const dirs = shallowDirs(repoDir);
|
|
20399
|
+
const committed = loadCommitted(repoDir);
|
|
20400
|
+
if (committed)
|
|
20401
|
+
return shapeKey({ ...committed.model, key: computeKey(repoDir, committed.model.fingerprintFiles, dirs, committed.hash) });
|
|
20402
|
+
const cached = loadCached(repoDir);
|
|
20403
|
+
if (!cached || computeKey(repoDir, cached.fingerprintFiles, dirs) !== cached.key)
|
|
20404
|
+
return null;
|
|
20405
|
+
return shapeKey(cached);
|
|
20406
|
+
} catch {
|
|
20407
|
+
return null;
|
|
18974
20408
|
}
|
|
18975
|
-
out2.source = sources2.join(",");
|
|
18976
|
-
if (iterations.length > 0 && out2.missing.length === 0)
|
|
18977
|
-
out2.usd = usd;
|
|
18978
|
-
return out2;
|
|
18979
20409
|
}
|
|
18980
20410
|
|
|
18981
|
-
// ../../loki-ts/src/util/
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
20411
|
+
// ../../loki-ts/src/util/engine_origin.ts
|
|
20412
|
+
function readOriginUrl(repoDir) {
|
|
20413
|
+
try {
|
|
20414
|
+
const url = safeGit(repoDir, ["config", "--get", "remote.origin.url"]).trim();
|
|
20415
|
+
return url || null;
|
|
20416
|
+
} catch {
|
|
20417
|
+
return null;
|
|
20418
|
+
}
|
|
20419
|
+
}
|
|
20420
|
+
|
|
20421
|
+
// ../../loki-ts/src/runner/router/cost_preview.ts
|
|
20422
|
+
var NOT_RECORDED = "NOT RECORDED";
|
|
20423
|
+
var previewOn = (env) => env["LOKI_COST_PREVIEW"] !== "0";
|
|
20424
|
+
function receiptBlock(env, usd, unmetered, wallS) {
|
|
20425
|
+
if (!previewOn(env))
|
|
20426
|
+
return {};
|
|
20427
|
+
let r = { ok: false, reason: "no estimate carried from the run start" };
|
|
20428
|
+
try {
|
|
20429
|
+
const raw2 = env["LOKI_E10_COST_ESTIMATE"];
|
|
20430
|
+
if (raw2)
|
|
20431
|
+
r = JSON.parse(raw2);
|
|
20432
|
+
} catch {}
|
|
20433
|
+
const source = r.ok === true ? "history" : r.prior ? "rough_prior" : null;
|
|
20434
|
+
return { cost_preview: {
|
|
20435
|
+
...source ? { estimate_source: source } : {},
|
|
20436
|
+
estimate: !r.ok && r.prior ? { usd_low: r.prior.usd[0], usd_high: r.prior.usd[1], tier: r.prior.tier, size_class: r.prior.size_class } : r.ok === true ? { usd_low: r.est.usd[0], usd_high: r.est.usd[1], wall_low_s: r.est.wall_s[0], wall_high_s: r.est.wall_s[1], prior_runs: r.est.runs, shape: r.est.shape } : `NOT AVAILABLE (${r.reason ?? "unreadable estimate"})`,
|
|
20437
|
+
actual: { usd: usd === null || unmetered ? NOT_RECORDED : usd, wall_s: wallS === null || wallS <= 0 ? NOT_RECORDED : wallS }
|
|
20438
|
+
} };
|
|
20439
|
+
}
|
|
20440
|
+
function recordRun(env, repoDir, model, verdict, usd, unmetered, wallS, cacheRoot) {
|
|
20441
|
+
if (!previewOn(env) || verdict !== "VERIFIED" || usd === null || unmetered || wallS <= 0)
|
|
20442
|
+
return false;
|
|
20443
|
+
const shape = shapeKeyForRepo(repoDir);
|
|
20444
|
+
if (shape === null)
|
|
20445
|
+
return false;
|
|
20446
|
+
return appendRunOutcome(repoKey(readOriginUrl(repoDir), repoDir), { shape, executor: /haiku/i.test(model) ? "haiku" : "sonnet", verdict: "pass", owner: null, escalated: false, usd, wallS }, cacheRoot);
|
|
20447
|
+
}
|
|
20448
|
+
|
|
20449
|
+
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
20450
|
+
init_cost();
|
|
18987
20451
|
|
|
18988
20452
|
// ../../loki-ts/src/features/contract.ts
|
|
18989
|
-
import { existsSync as
|
|
18990
|
-
import { dirname as
|
|
18991
|
-
import { createHash as
|
|
20453
|
+
import { existsSync as existsSync27, lstatSync as lstatSync11, mkdirSync as mkdirSync16, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
|
|
20454
|
+
import { dirname as dirname19, join as join46 } from "path";
|
|
20455
|
+
import { createHash as createHash9 } from "crypto";
|
|
18992
20456
|
var MAX_CRITERIA = 50;
|
|
18993
20457
|
var MAX_TEXT = 500;
|
|
18994
20458
|
function contractEnabled(env = process.env) {
|
|
@@ -19002,11 +20466,11 @@ function overlaps(criterionWords, target) {
|
|
|
19002
20466
|
const tw = new Set(words(target));
|
|
19003
20467
|
return criterionWords.some((w) => tw.has(w) || w.length > 4 && w.endsWith("s") && tw.has(w.slice(0, -1)));
|
|
19004
20468
|
}
|
|
19005
|
-
function traceContract(contract,
|
|
20469
|
+
function traceContract(contract, changedFiles2, checkNames) {
|
|
19006
20470
|
return {
|
|
19007
20471
|
criteria: contract.criteria.map((c) => {
|
|
19008
20472
|
const cw = words(c.text);
|
|
19009
|
-
const files =
|
|
20473
|
+
const files = changedFiles2.filter((f) => overlaps(cw, f));
|
|
19010
20474
|
const checks = checkNames.filter((n2) => overlaps(cw, n2));
|
|
19011
20475
|
const traced = files.length > 0;
|
|
19012
20476
|
return { id: c.id, text: c.text, files, checks: traced ? checks : [], status: traced ? "keyword_match" : "no_match" };
|
|
@@ -19022,14 +20486,14 @@ function untracedLines(trace) {
|
|
|
19022
20486
|
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)}`);
|
|
19023
20487
|
}
|
|
19024
20488
|
function contractPath(repoDir) {
|
|
19025
|
-
return
|
|
20489
|
+
return join46(repoDir, ".loki", "contract.json");
|
|
19026
20490
|
}
|
|
19027
20491
|
var MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
19028
20492
|
function readContract(repoDir) {
|
|
19029
20493
|
const p = contractPath(repoDir);
|
|
19030
20494
|
let st;
|
|
19031
20495
|
try {
|
|
19032
|
-
st =
|
|
20496
|
+
st = lstatSync11(p);
|
|
19033
20497
|
} catch {
|
|
19034
20498
|
return { contract: null, notes: [], sha256: null };
|
|
19035
20499
|
}
|
|
@@ -19039,19 +20503,19 @@ function readContract(repoDir) {
|
|
|
19039
20503
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
19040
20504
|
let raw2;
|
|
19041
20505
|
try {
|
|
19042
|
-
raw2 =
|
|
20506
|
+
raw2 = readFileSync32(p);
|
|
19043
20507
|
} catch {
|
|
19044
20508
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
19045
20509
|
}
|
|
19046
|
-
const
|
|
20510
|
+
const sha2563 = createHash9("sha256").update(raw2).digest("hex");
|
|
19047
20511
|
let j;
|
|
19048
20512
|
try {
|
|
19049
20513
|
j = JSON.parse(raw2.toString("utf8"));
|
|
19050
20514
|
} catch {
|
|
19051
|
-
return { contract: null, notes: ["contract unreadable: invalid JSON"], sha256:
|
|
20515
|
+
return { contract: null, notes: ["contract unreadable: invalid JSON"], sha256: sha2563 };
|
|
19052
20516
|
}
|
|
19053
20517
|
if (!Array.isArray(j?.criteria))
|
|
19054
|
-
return { contract: null, notes: [], sha256:
|
|
20518
|
+
return { contract: null, notes: [], sha256: sha2563 };
|
|
19055
20519
|
const criteria = [];
|
|
19056
20520
|
let malformed = 0;
|
|
19057
20521
|
for (const c of j.criteria) {
|
|
@@ -19065,7 +20529,7 @@ function readContract(repoDir) {
|
|
|
19065
20529
|
criteria.push({ id: o.id.slice(0, 40), text: o.text.slice(0, MAX_TEXT), source_line: typeof o.source_line === "number" ? o.source_line : 0 });
|
|
19066
20530
|
}
|
|
19067
20531
|
const notes = malformed > 0 ? [`contract: ${malformed} malformed criteria dropped`] : [];
|
|
19068
|
-
return { contract: { source: typeof j.source === "string" ? j.source : "", criteria }, notes, sha256:
|
|
20532
|
+
return { contract: { source: typeof j.source === "string" ? j.source : "", criteria }, notes, sha256: sha2563 };
|
|
19069
20533
|
}
|
|
19070
20534
|
function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
19071
20535
|
if (!contractEnabled(env))
|
|
@@ -19074,7 +20538,7 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
19074
20538
|
const live2 = readContract(repoDir);
|
|
19075
20539
|
let exists2 = true;
|
|
19076
20540
|
try {
|
|
19077
|
-
|
|
20541
|
+
lstatSync11(contractPath(repoDir));
|
|
19078
20542
|
} catch {
|
|
19079
20543
|
exists2 = false;
|
|
19080
20544
|
}
|
|
@@ -19102,33 +20566,33 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
19102
20566
|
}
|
|
19103
20567
|
|
|
19104
20568
|
// ../../loki-ts/src/features/speed/seal_group.ts
|
|
19105
|
-
import { createHash as
|
|
19106
|
-
import { existsSync as
|
|
19107
|
-
import { dirname as
|
|
20569
|
+
import { createHash as createHash10 } from "crypto";
|
|
20570
|
+
import { existsSync as existsSync28, readdirSync as readdirSync13, readFileSync as readFileSync33, statSync as statSync13 } from "fs";
|
|
20571
|
+
import { dirname as dirname20, join as join47 } from "path";
|
|
19108
20572
|
var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
19109
20573
|
var PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
|
|
19110
|
-
var sha2 = (b) =>
|
|
20574
|
+
var sha2 = (b) => createHash10("sha256").update(b).digest("hex");
|
|
19111
20575
|
var isObj10 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
19112
20576
|
var text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "";
|
|
19113
20577
|
var num4 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
19114
|
-
var unitsDir = (runDir) =>
|
|
20578
|
+
var unitsDir = (runDir) => join47(runDir, "group", "units");
|
|
19115
20579
|
var readJson3 = (p) => {
|
|
19116
20580
|
try {
|
|
19117
|
-
return JSON.parse(
|
|
20581
|
+
return JSON.parse(readFileSync33(p, "utf8"));
|
|
19118
20582
|
} catch {
|
|
19119
20583
|
return;
|
|
19120
20584
|
}
|
|
19121
20585
|
};
|
|
19122
20586
|
var dirNames = (d) => {
|
|
19123
20587
|
try {
|
|
19124
|
-
return
|
|
20588
|
+
return readdirSync13(d).filter((n2) => !n2.startsWith(".")).sort();
|
|
19125
20589
|
} catch {
|
|
19126
20590
|
return null;
|
|
19127
20591
|
}
|
|
19128
20592
|
};
|
|
19129
20593
|
function sealGroup(runDir, rh) {
|
|
19130
|
-
const manifestPath =
|
|
19131
|
-
if (!
|
|
20594
|
+
const manifestPath = join47(runDir, "group", "manifest.json");
|
|
20595
|
+
if (!existsSync28(manifestPath))
|
|
19132
20596
|
return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
|
|
19133
20597
|
const notProven = [];
|
|
19134
20598
|
let problems = 0;
|
|
@@ -19152,10 +20616,10 @@ function sealGroup(runDir, rh) {
|
|
|
19152
20616
|
return fail(`unit ${index2} has an invalid, duplicate or case-colliding id`);
|
|
19153
20617
|
seen.add(id);
|
|
19154
20618
|
seenLower.add(id.toLowerCase());
|
|
19155
|
-
const dir =
|
|
20619
|
+
const dir = join47(unitsDir(runDir), id), rp = join47(dir, "receipt.json"), ep = join47(dir, "events.jsonl");
|
|
19156
20620
|
let bytes;
|
|
19157
20621
|
try {
|
|
19158
|
-
bytes =
|
|
20622
|
+
bytes = readFileSync33(rp);
|
|
19159
20623
|
} catch {
|
|
19160
20624
|
return fail(`unit ${id} sub-receipt missing`);
|
|
19161
20625
|
}
|
|
@@ -19172,7 +20636,7 @@ function sealGroup(runDir, rh) {
|
|
|
19172
20636
|
unit_id: id,
|
|
19173
20637
|
sub_receipt_sha256: sha2(bytes),
|
|
19174
20638
|
receipt_sha256: sub["receipt_sha256"],
|
|
19175
|
-
events_file_sha256:
|
|
20639
|
+
events_file_sha256: existsSync28(ep) ? sha2(readFileSync33(ep)) : null,
|
|
19176
20640
|
verdict,
|
|
19177
20641
|
goal: text2(u["goal"], 200),
|
|
19178
20642
|
files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
|
|
@@ -19192,12 +20656,12 @@ function capGroupVerdict(v, g) {
|
|
|
19192
20656
|
return !g.allUnitsPass && (v === "VERIFIED" || v === "ALREADY_SATISFIED") ? "PARTIAL" : v;
|
|
19193
20657
|
}
|
|
19194
20658
|
async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
19195
|
-
const runDir =
|
|
20659
|
+
const runDir = dirname20(receiptPath);
|
|
19196
20660
|
const signed = isObj10(receipt["verification"]) && typeof receipt["verification"]["jwt"] === "string";
|
|
19197
20661
|
const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
|
|
19198
20662
|
const g = receipt["group"];
|
|
19199
20663
|
if (g === undefined)
|
|
19200
|
-
return
|
|
20664
|
+
return existsSync28(join47(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
|
|
19201
20665
|
if (!isObj10(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
|
|
19202
20666
|
return bad("group section malformed");
|
|
19203
20667
|
const entries = g["units"];
|
|
@@ -19209,20 +20673,20 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
|
19209
20673
|
return bad("unit directories do not match the receipt (missing or extra unit)");
|
|
19210
20674
|
let unchecked = null;
|
|
19211
20675
|
for (let i = 0;i < entries.length; i++) {
|
|
19212
|
-
const e = entries[i], id = ids[i], dir =
|
|
20676
|
+
const e = entries[i], id = ids[i], dir = join47(unitsDir(runDir), id), rp = join47(dir, "receipt.json"), ep = join47(dir, "events.jsonl");
|
|
19213
20677
|
if (e["index"] !== i)
|
|
19214
20678
|
return bad(`unit ${id} is out of order (index ${String(e["index"])} at position ${i})`);
|
|
19215
20679
|
let bytes;
|
|
19216
20680
|
try {
|
|
19217
|
-
if (!
|
|
20681
|
+
if (!statSync13(dir).isDirectory())
|
|
19218
20682
|
return bad(`unit ${id} is not a directory`);
|
|
19219
|
-
bytes =
|
|
20683
|
+
bytes = readFileSync33(rp);
|
|
19220
20684
|
} catch {
|
|
19221
20685
|
return bad(`unit ${id} sub-receipt missing`);
|
|
19222
20686
|
}
|
|
19223
20687
|
if (sha2(bytes) !== e["sub_receipt_sha256"])
|
|
19224
20688
|
return bad(`unit ${id} sub-receipt does not match its recorded hash`);
|
|
19225
|
-
const evSha =
|
|
20689
|
+
const evSha = existsSync28(ep) ? sha2(readFileSync33(ep)) : null;
|
|
19226
20690
|
if (evSha !== (e["events_file_sha256"] ?? null))
|
|
19227
20691
|
return bad(`unit ${id} events.jsonl does not match its recorded hash`);
|
|
19228
20692
|
const sub = readJson3(rp);
|
|
@@ -19241,11 +20705,263 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
|
19241
20705
|
return unchecked ? { verdict: "UNCHECKED", reason: `group: ${unchecked}` } : null;
|
|
19242
20706
|
}
|
|
19243
20707
|
|
|
20708
|
+
// ../../loki-ts/src/supply/supply_guard.ts
|
|
20709
|
+
init_shell();
|
|
20710
|
+
import { existsSync as existsSync29, readFileSync as readFileSync34, statSync as statSync14 } from "fs";
|
|
20711
|
+
import { basename as basename16, join as join48 } from "path";
|
|
20712
|
+
var MIN_AGE_DAYS = 7;
|
|
20713
|
+
var DECLARED_FILE = "supply-declared.json";
|
|
20714
|
+
var MAX_CHECKED = 50;
|
|
20715
|
+
var MAX_DECLARED = 200;
|
|
20716
|
+
var MAX_FILE_BYTES = 64 * 1024;
|
|
20717
|
+
var DAY_MS = 86400000;
|
|
20718
|
+
var MANIFESTS = /^(package\.json|package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|requirements.*\.txt|pyproject\.toml|poetry\.lock|uv\.lock|Pipfile|Pipfile\.lock|go\.mod|go\.sum|Cargo\.toml|Cargo\.lock|Gemfile|Gemfile\.lock|composer\.json|composer\.lock|pom\.xml|build\.gradle(\.kts)?)$/;
|
|
20719
|
+
function supplyEnabled(env) {
|
|
20720
|
+
return env["LOKI_SUPPLY_GUARD"] !== "0";
|
|
20721
|
+
}
|
|
20722
|
+
function failAgeDays(env) {
|
|
20723
|
+
const n2 = Number(env["LOKI_SUPPLY_MIN_AGE_DAYS"]);
|
|
20724
|
+
return Number.isFinite(n2) && n2 > 0 ? n2 : null;
|
|
20725
|
+
}
|
|
20726
|
+
function supplyVerdict(verdict, r) {
|
|
20727
|
+
return r.blocked && verdict === "VERIFIED" ? "FAILED" : verdict;
|
|
20728
|
+
}
|
|
20729
|
+
function isManifestPath(path) {
|
|
20730
|
+
return MANIFESTS.test(basename16(path));
|
|
20731
|
+
}
|
|
20732
|
+
var ECO = { npm: "npm", node: "npm", pypi: "pypi", pip: "pypi", python: "pypi", cargo: "cargo", crates: "cargo", go: "go", golang: "go" };
|
|
20733
|
+
var NAME2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~+-]{0,213}$/;
|
|
20734
|
+
function readDeclared(repoDir) {
|
|
20735
|
+
const p = join48(repoDir, ".loki", DECLARED_FILE);
|
|
20736
|
+
if (!existsSync29(p))
|
|
20737
|
+
return { deps: [], problem: null };
|
|
20738
|
+
try {
|
|
20739
|
+
if (statSync14(p).size > MAX_FILE_BYTES)
|
|
20740
|
+
return { deps: [], problem: "declaration file too large" };
|
|
20741
|
+
const j = JSON.parse(readFileSync34(p, "utf8"));
|
|
20742
|
+
if (!Array.isArray(j))
|
|
20743
|
+
return { deps: [], problem: "declaration is not a JSON array" };
|
|
20744
|
+
const deps = [];
|
|
20745
|
+
for (const o of j.slice(0, MAX_DECLARED)) {
|
|
20746
|
+
if (!o || typeof o !== "object")
|
|
20747
|
+
continue;
|
|
20748
|
+
const r = o;
|
|
20749
|
+
const eco = ECO[String(r["ecosystem"] ?? "").toLowerCase()] ?? String(r["ecosystem"] ?? "").toLowerCase().slice(0, 30);
|
|
20750
|
+
const name = String(r["name"] ?? "");
|
|
20751
|
+
if (!eco || !NAME2.test(name))
|
|
20752
|
+
continue;
|
|
20753
|
+
deps.push({ ecosystem: eco, name, version_spec: String(r["version_spec"] ?? "").slice(0, 100), registry: String(r["registry"] ?? "default").slice(0, 200) });
|
|
20754
|
+
}
|
|
20755
|
+
return { deps, problem: null };
|
|
20756
|
+
} catch {
|
|
20757
|
+
return { deps: [], problem: "declaration unreadable" };
|
|
20758
|
+
}
|
|
20759
|
+
}
|
|
20760
|
+
function readAllowlist(repoDir) {
|
|
20761
|
+
const p = join48(repoDir, ".loki", "supply-allowlist");
|
|
20762
|
+
if (!existsSync29(p))
|
|
20763
|
+
return new Set;
|
|
20764
|
+
try {
|
|
20765
|
+
return new Set(readFileSync34(p, "utf8").split(`
|
|
20766
|
+
`).map((l) => l.replace(/#.*$/, "").trim()).filter(Boolean));
|
|
20767
|
+
} catch {
|
|
20768
|
+
return new Set;
|
|
20769
|
+
}
|
|
20770
|
+
}
|
|
20771
|
+
function classify2(eco, name, exitCode, stdout, stderr) {
|
|
20772
|
+
const err = `${stderr}
|
|
20773
|
+
${stdout}`;
|
|
20774
|
+
if (eco === "npm") {
|
|
20775
|
+
if (exitCode === 0) {
|
|
20776
|
+
const m = /"?(\d{4}-\d\d-\d\dT[\d:.]+Z)"?/.exec(stdout);
|
|
20777
|
+
return m ? { status: "exists", firstPublish: Date.parse(m[1]) } : { status: "exists" };
|
|
20778
|
+
}
|
|
20779
|
+
return /E404|404 Not Found|is not in this registry/i.test(err) ? { status: "missing" } : { status: "unproven" };
|
|
20780
|
+
}
|
|
20781
|
+
if (eco === "pypi") {
|
|
20782
|
+
if (exitCode === 0)
|
|
20783
|
+
return /Available versions/i.test(stdout) ? { status: "exists" } : { status: "unproven" };
|
|
20784
|
+
return /No matching distribution|from versions: none/i.test(err) ? { status: "missing" } : { status: "unproven" };
|
|
20785
|
+
}
|
|
20786
|
+
if (eco === "cargo") {
|
|
20787
|
+
if (exitCode !== 0)
|
|
20788
|
+
return { status: "unproven" };
|
|
20789
|
+
const norm3 = (x) => x.toLowerCase().replace(/_/g, "-");
|
|
20790
|
+
return stdout.split(`
|
|
20791
|
+
`).some((l) => norm3(l).startsWith(`${norm3(name)} = `)) ? { status: "exists" } : { status: "missing" };
|
|
20792
|
+
}
|
|
20793
|
+
if (eco === "go") {
|
|
20794
|
+
if (exitCode === 0)
|
|
20795
|
+
return { status: "exists" };
|
|
20796
|
+
return /no matching versions|not found|404|410/i.test(err) && !/dial tcp|timeout|no such host|connection/i.test(err) ? { status: "missing" } : { status: "unproven" };
|
|
20797
|
+
}
|
|
20798
|
+
return { status: "unproven" };
|
|
20799
|
+
}
|
|
20800
|
+
var defaultResolver = async (dep, cwd) => {
|
|
20801
|
+
const argv = {
|
|
20802
|
+
npm: ["npm", "view", dep.name, "time.created", "--json"],
|
|
20803
|
+
pypi: ["pip", "index", "versions", "--pre", "--ignore-requires-python", dep.name],
|
|
20804
|
+
cargo: ["cargo", "search", dep.name, "--limit", "5"],
|
|
20805
|
+
go: ["go", "list", "-m", "-json", `${dep.name}@latest`]
|
|
20806
|
+
};
|
|
20807
|
+
const cmd = argv[dep.ecosystem];
|
|
20808
|
+
if (!cmd || dep.name.startsWith("-"))
|
|
20809
|
+
return { status: "unproven" };
|
|
20810
|
+
try {
|
|
20811
|
+
const r = await run(cmd, { cwd, timeoutMs: 1e4 });
|
|
20812
|
+
return classify2(dep.ecosystem, dep.name, r.exitCode, r.stdout, r.stderr);
|
|
20813
|
+
} catch {
|
|
20814
|
+
return { status: "unproven" };
|
|
20815
|
+
}
|
|
20816
|
+
};
|
|
20817
|
+
var PUBLIC_HOSTS = new Set(["registry.npmjs.org", "registry.npmjs.com", "registry.yarnpkg.com", "npmjs.com", "npmjs.org", "pypi.org", "pypi.python.org", "files.pythonhosted.org", "crates.io", "index.crates.io", "static.crates.io", "proxy.golang.org", "golang.org", "pkg.go.dev"]);
|
|
20818
|
+
var hostOf = (u) => u.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "").replace(/^www\./, "").split(/[/?#\s]/)[0].replace(/^[^@]*@/, "").replace(/:\d+$/, "");
|
|
20819
|
+
var privateUrl = (u) => /[a-z0-9]/i.test(u) && !PUBLIC_HOSTS.has(hostOf(u));
|
|
20820
|
+
var readSmall = (p) => {
|
|
20821
|
+
try {
|
|
20822
|
+
return statSync14(p).size <= MAX_FILE_BYTES ? readFileSync34(p, "utf8") : "";
|
|
20823
|
+
} catch {
|
|
20824
|
+
return "";
|
|
20825
|
+
}
|
|
20826
|
+
};
|
|
20827
|
+
function userConfigPrivate(eco, repoDir, env, goEnv = {}, name = "") {
|
|
20828
|
+
const home = env["HOME"] ?? "";
|
|
20829
|
+
const files = (...p) => p.filter((x) => x !== "").map((x) => readSmall(x));
|
|
20830
|
+
if (eco === "npm") {
|
|
20831
|
+
if (privateUrlList([env["npm_config_registry"] ?? "", env["NPM_CONFIG_REGISTRY"] ?? ""]))
|
|
20832
|
+
return true;
|
|
20833
|
+
return files(join48(repoDir, ".npmrc"), home ? join48(home, ".npmrc") : "").some((t) => t.split(`
|
|
20834
|
+
`).some((l) => {
|
|
20835
|
+
const m = /^\s*(?:(@[^:\s]+):)?registry\s*=\s*(\S+)/.exec(l);
|
|
20836
|
+
return !!m && privateUrl(m[2]) && (m[1] === undefined || name.startsWith(`${m[1]}/`));
|
|
20837
|
+
}));
|
|
20838
|
+
}
|
|
20839
|
+
if (eco === "pypi") {
|
|
20840
|
+
if (privateUrlList([env["PIP_INDEX_URL"] ?? "", env["PIP_EXTRA_INDEX_URL"] ?? ""]))
|
|
20841
|
+
return true;
|
|
20842
|
+
const cfg = [env["PIP_CONFIG_FILE"] ?? "", home ? join48(home, ".config", "pip", "pip.conf") : "", home ? join48(home, ".pip", "pip.conf") : "", join48(repoDir, "pip.conf")];
|
|
20843
|
+
return files(...cfg).some((t) => t.split(`
|
|
20844
|
+
`).some((l) => {
|
|
20845
|
+
const m = /^\s*(?:extra-)?index-url\s*[=:]\s*(.+)$/.exec(l);
|
|
20846
|
+
return !!m && m[1].split(/\s+/).some(privateUrl);
|
|
20847
|
+
}));
|
|
20848
|
+
}
|
|
20849
|
+
if (eco === "cargo") {
|
|
20850
|
+
if (Object.keys(env).some((k) => /^CARGO_REGISTRIES_.+_INDEX$/.test(k) || /^CARGO_SOURCE_/.test(k)))
|
|
20851
|
+
return true;
|
|
20852
|
+
return files(join48(repoDir, ".cargo", "config.toml"), join48(repoDir, ".cargo", "config"), home ? join48(home, ".cargo", "config.toml") : "", home ? join48(home, ".cargo", "config") : "").some((t) => /^\s*\[(registries\.|source\.)/m.test(t) || /replace-with\s*=/.test(t));
|
|
20853
|
+
}
|
|
20854
|
+
if (eco === "go") {
|
|
20855
|
+
const gp = goEnv["GOPROXY"] ?? env["GOPROXY"] ?? "";
|
|
20856
|
+
return gp.split(/[,|]/).map((x) => x.trim()).filter((x) => x && x !== "direct" && x !== "off").some(privateUrl);
|
|
20857
|
+
}
|
|
20858
|
+
return false;
|
|
20859
|
+
}
|
|
20860
|
+
var privateUrlList = (v) => v.some((x) => x.split(/\s+/).some((u) => u !== "" && privateUrl(u)));
|
|
20861
|
+
function goPrivate(d, env) {
|
|
20862
|
+
if (d.ecosystem !== "go")
|
|
20863
|
+
return false;
|
|
20864
|
+
const pats = `${env["GOPRIVATE"] ?? ""},${env["GONOPROXY"] ?? ""}`.split(",").map((x) => x.trim()).filter(Boolean);
|
|
20865
|
+
const parts = d.name.split("/");
|
|
20866
|
+
return pats.some((pat) => {
|
|
20867
|
+
const n2 = pat.split("/").length;
|
|
20868
|
+
const re = new RegExp(`^${pat.split("/").map((seg) => seg.replace(/[.+^${}()|\\[\]]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")).join("/")}$`);
|
|
20869
|
+
return re.test(parts.slice(0, n2).join("/"));
|
|
20870
|
+
});
|
|
20871
|
+
}
|
|
20872
|
+
async function readGoEnv(cwd) {
|
|
20873
|
+
try {
|
|
20874
|
+
const r = await run(["go", "env", "-json", "GOPRIVATE", "GONOPROXY", "GOPROXY"], { cwd, timeoutMs: 1e4 });
|
|
20875
|
+
const j = JSON.parse(r.stdout);
|
|
20876
|
+
return Object.fromEntries(Object.entries(j).map(([k, v]) => [k, String(v)]));
|
|
20877
|
+
} catch {
|
|
20878
|
+
return {};
|
|
20879
|
+
}
|
|
20880
|
+
}
|
|
20881
|
+
var injected = null;
|
|
20882
|
+
async function supplyGuard(repoDir, changedFiles2, declared, env, opts = {}) {
|
|
20883
|
+
const none = { block: null, notProven: [], blocked: false };
|
|
20884
|
+
if (!supplyEnabled(env))
|
|
20885
|
+
return none;
|
|
20886
|
+
const touched = changedFiles2.filter(isManifestPath);
|
|
20887
|
+
if (touched.length === 0)
|
|
20888
|
+
return none;
|
|
20889
|
+
const resolver = opts.resolver ?? injected ?? defaultResolver, now2 = opts.now ?? Date.now(), failAge = failAgeDays(env);
|
|
20890
|
+
const goEnv = opts.goEnv ?? (declared.deps.some((x) => x.ecosystem === "go") && !opts.resolver && !injected ? await readGoEnv(repoDir) : {});
|
|
20891
|
+
const notProven = [];
|
|
20892
|
+
let blocked = false;
|
|
20893
|
+
if (declared.problem)
|
|
20894
|
+
notProven.push(`supply guard NOT PROVEN: ${declared.problem}`);
|
|
20895
|
+
if (declared.deps.length === 0) {
|
|
20896
|
+
if (!declared.problem)
|
|
20897
|
+
notProven.push(`supply guard WARNING: dependencies changed (${[...new Set(touched)].slice(0, 5).join(", ")}) but none declared; not checked`);
|
|
20898
|
+
return { block: { guard: "v2", warn_age_days: MIN_AGE_DAYS, fail_age_days: failAge, declared: 0, entries: [] }, notProven, blocked: false };
|
|
20899
|
+
}
|
|
20900
|
+
const allow = readAllowlist(repoDir), cache2 = new Map;
|
|
20901
|
+
let checked = 0;
|
|
20902
|
+
const entries = [];
|
|
20903
|
+
for (const d of declared.deps) {
|
|
20904
|
+
const base = { ecosystem: d.ecosystem, name: d.name, version_spec: d.version_spec, registry: d.registry };
|
|
20905
|
+
if (!["npm", "pypi", "cargo", "go"].includes(d.ecosystem)) {
|
|
20906
|
+
entries.push({ ...base, status: "unsupported" });
|
|
20907
|
+
continue;
|
|
20908
|
+
}
|
|
20909
|
+
if (allow.has(d.name)) {
|
|
20910
|
+
entries.push({ ...base, status: "allowlisted" });
|
|
20911
|
+
continue;
|
|
20912
|
+
}
|
|
20913
|
+
if (++checked > MAX_CHECKED) {
|
|
20914
|
+
entries.push({ ...base, status: "capped" });
|
|
20915
|
+
continue;
|
|
20916
|
+
}
|
|
20917
|
+
const key = `${d.ecosystem}:${d.name}`;
|
|
20918
|
+
let h = cache2.get(key);
|
|
20919
|
+
if (!h) {
|
|
20920
|
+
h = resolver(d, repoDir, env).catch(() => ({ status: "unproven" }));
|
|
20921
|
+
cache2.set(key, h);
|
|
20922
|
+
}
|
|
20923
|
+
let res = await h;
|
|
20924
|
+
if (res.status === "missing" && (userConfigPrivate(d.ecosystem, repoDir, env, goEnv, d.name) || goPrivate(d, { ...env, ...goEnv })))
|
|
20925
|
+
res = { status: "unproven" };
|
|
20926
|
+
if (res.status === "missing")
|
|
20927
|
+
entries.push({ ...base, status: "nonexistent" });
|
|
20928
|
+
else if (res.status === "unproven")
|
|
20929
|
+
entries.push({ ...base, status: "unreachable" });
|
|
20930
|
+
else if (res.firstPublish === undefined || !Number.isFinite(res.firstPublish))
|
|
20931
|
+
entries.push({ ...base, status: "ok" });
|
|
20932
|
+
else {
|
|
20933
|
+
const age = Math.floor((now2 - res.firstPublish) / DAY_MS);
|
|
20934
|
+
entries.push({ ...base, status: age < MIN_AGE_DAYS || failAge !== null && age < failAge ? "too_new" : "ok", age_days: age });
|
|
20935
|
+
}
|
|
20936
|
+
}
|
|
20937
|
+
for (const e of entries) {
|
|
20938
|
+
const id = `${e.ecosystem}:${e.name}`;
|
|
20939
|
+
if (e.status === "nonexistent") {
|
|
20940
|
+
blocked = true;
|
|
20941
|
+
notProven.push(`supply guard FAILED: declared dependency ${id} does not resolve in the registry (possible hallucinated package)`);
|
|
20942
|
+
} else if (e.status === "too_new") {
|
|
20943
|
+
if (failAge !== null && (e.age_days ?? 0) < failAge) {
|
|
20944
|
+
blocked = true;
|
|
20945
|
+
notProven.push(`supply guard FAILED: ${id} first published ${e.age_days} days ago (LOKI_SUPPLY_MIN_AGE_DAYS=${failAge}); allowlist in .loki/supply-allowlist to accept`);
|
|
20946
|
+
} else
|
|
20947
|
+
notProven.push(`supply guard WARNING: ${id} first published ${e.age_days} days ago (under ${MIN_AGE_DAYS}); verdict unchanged`);
|
|
20948
|
+
} else if (e.status === "unreachable")
|
|
20949
|
+
notProven.push(`supply guard NOT PROVEN: resolver could not answer for ${id}`);
|
|
20950
|
+
else if (e.status === "capped")
|
|
20951
|
+
notProven.push(`supply guard: not checked: cap ${MAX_CHECKED} exceeded: ${id}`);
|
|
20952
|
+
else if (e.status === "unsupported")
|
|
20953
|
+
notProven.push(`supply guard: not checked (ecosystem unsupported in v1): ${id}`);
|
|
20954
|
+
else if (e.status === "allowlisted")
|
|
20955
|
+
notProven.push(`supply guard: allowlisted, not checked: ${id}`);
|
|
20956
|
+
}
|
|
20957
|
+
return { block: { guard: "v2", warn_age_days: MIN_AGE_DAYS, fail_age_days: failAge, declared: declared.deps.length, entries }, notProven, blocked };
|
|
20958
|
+
}
|
|
20959
|
+
|
|
19244
20960
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
19245
20961
|
var DEEP_NOT_PROVEN = ["full suite", "app boot", "council", "security scan"];
|
|
19246
20962
|
var SIGNING_UNAVAILABLE = "receipt signing unavailable (no usable signing key: invalid key or unwritable ~/.loki/keys)";
|
|
19247
20963
|
var EXCLUDE_LOKI = ":(exclude).loki";
|
|
19248
|
-
var sha2562 = (s) =>
|
|
20964
|
+
var sha2562 = (s) => createHash11("sha256").update(s).digest("hex");
|
|
19249
20965
|
var str3 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
19250
20966
|
var strs2 = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x !== "") : [];
|
|
19251
20967
|
function sanitizeReason(s) {
|
|
@@ -19270,37 +20986,37 @@ function receiptSha256(r) {
|
|
|
19270
20986
|
return sha2562(canonicalJson(body));
|
|
19271
20987
|
}
|
|
19272
20988
|
var b64u = (b) => Buffer.from(b).toString("base64url");
|
|
19273
|
-
var kidOf2 = (pub) => b64u(
|
|
20989
|
+
var kidOf2 = (pub) => b64u(createHash11("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest());
|
|
19274
20990
|
function loadSigningKey(generate = true) {
|
|
19275
20991
|
try {
|
|
19276
20992
|
const inline = process.env["LOKI_RECEIPT_SIGNING_KEY"]?.trim();
|
|
19277
20993
|
let pem = inline ?? "";
|
|
19278
20994
|
if (!inline) {
|
|
19279
20995
|
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
19280
|
-
const file = given ||
|
|
20996
|
+
const file = given || join49(process.env["HOME"] || homedir8(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
19281
20997
|
try {
|
|
19282
|
-
pem =
|
|
20998
|
+
pem = readFileSync35(file);
|
|
19283
20999
|
if (!given) {
|
|
19284
|
-
for (const [f, m] of [[file, 384], [
|
|
19285
|
-
if (
|
|
21000
|
+
for (const [f, m] of [[file, 384], [dirname21(file), 448]])
|
|
21001
|
+
if (statSync15(f).mode & 63)
|
|
19286
21002
|
chmodSync5(f, m);
|
|
19287
21003
|
}
|
|
19288
21004
|
} catch (e) {
|
|
19289
21005
|
if (e.code !== "ENOENT" || !generate)
|
|
19290
21006
|
return null;
|
|
19291
|
-
|
|
19292
|
-
|
|
21007
|
+
mkdirSync17(dirname21(dirname21(file)), { recursive: true, mode: 448 });
|
|
21008
|
+
mkdirSync17(dirname21(file), { recursive: true, mode: 448 });
|
|
19293
21009
|
const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
19294
|
-
|
|
21010
|
+
writeFileSync18(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
|
|
19295
21011
|
try {
|
|
19296
21012
|
linkSync(tmp, file);
|
|
19297
21013
|
} catch (l) {
|
|
19298
21014
|
if (l.code !== "EEXIST")
|
|
19299
21015
|
throw l;
|
|
19300
21016
|
} finally {
|
|
19301
|
-
|
|
21017
|
+
unlinkSync5(tmp);
|
|
19302
21018
|
}
|
|
19303
|
-
pem =
|
|
21019
|
+
pem = readFileSync35(file);
|
|
19304
21020
|
}
|
|
19305
21021
|
}
|
|
19306
21022
|
const k = createPrivateKey(pem);
|
|
@@ -19317,37 +21033,37 @@ function signReceipt(runId, hash) {
|
|
|
19317
21033
|
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) }))}`;
|
|
19318
21034
|
return { jwt: `${input}.${b64u(sign(null, Buffer.from(input), key))}`, kid };
|
|
19319
21035
|
}
|
|
19320
|
-
async function
|
|
19321
|
-
const r = await
|
|
21036
|
+
async function git2(ctx, args, keep = {}) {
|
|
21037
|
+
const r = await safeGitRun(ctx.repoDir, args, { timeoutMs: 20000, ...keep });
|
|
19322
21038
|
return { out: r.stdout, code: r.exitCode };
|
|
19323
21039
|
}
|
|
19324
21040
|
var commitStage = {
|
|
19325
21041
|
name: "commit",
|
|
19326
21042
|
...STAGE_BUDGETS.commit,
|
|
19327
21043
|
async run(ctx) {
|
|
19328
|
-
if (!ctx.baseSha || (await
|
|
21044
|
+
if (!ctx.baseSha || (await git2(ctx, ["rev-parse", "--verify", "-q", `${ctx.baseSha}^{commit}`])).code !== 0)
|
|
19329
21045
|
return { status: "failed", data: {}, reason: "base commit not resolvable" };
|
|
19330
|
-
if ((await
|
|
21046
|
+
if ((await git2(ctx, ["add", "-A", "--", "."], { repoDrivers: true })).code !== 0)
|
|
19331
21047
|
return { status: "failed", data: {}, reason: "git add failed" };
|
|
19332
|
-
const sd = await
|
|
21048
|
+
const sd = await git2(ctx, ["diff", "--cached", "--name-status", "--no-renames", "-z", ctx.baseSha]);
|
|
19333
21049
|
if (sd.code !== 0)
|
|
19334
21050
|
return { status: "failed", data: {}, reason: "git diff against base failed" };
|
|
19335
21051
|
const staged = parseStaged(sd.out);
|
|
19336
21052
|
const drop = dropSet(ctx.repoDir, staged, ctx.outputs().intake?.preexisting_dirty);
|
|
19337
|
-
const sat = await discardIfSatisfied((a) =>
|
|
21053
|
+
const sat = await discardIfSatisfied((a) => git2(ctx, a), ctx.baseSha, ctx.outputs(), staged, new Set(drop.filter(({ st }) => st === "L").map(({ f }) => f)), ctx.repoDir);
|
|
19338
21054
|
if (sat)
|
|
19339
21055
|
return sat;
|
|
19340
|
-
if (drop.length > 0 && (await
|
|
21056
|
+
if (drop.length > 0 && (await git2(ctx, ["--literal-pathspecs", "reset", "-q", ctx.baseSha, "--", ...drop.map(({ f }) => f)])).code !== 0)
|
|
19341
21057
|
return { status: "failed", data: {}, reason: "git reset failed" };
|
|
19342
21058
|
const dropped2 = new Set(drop.map(({ f }) => f)), notes = flagOutsideScope(ctx.outputs(), staged.filter(({ f }) => !dropped2.has(f)));
|
|
19343
|
-
if ((await
|
|
21059
|
+
if ((await git2(ctx, ["diff", "--cached", "--quiet"])).code === 0)
|
|
19344
21060
|
return { status: "completed", data: { committed: false, scope_notes: notes } };
|
|
19345
21061
|
const title = (str3(ctx.outputs().intake?.title) ?? `run ${ctx.runId}`).split(`
|
|
19346
21062
|
`)[0].slice(0, 72);
|
|
19347
|
-
const c = await
|
|
21063
|
+
const c = await git2(ctx, ["commit", "-q", "-m", `loki: ${title}`, "-m", `Loki-Run: ${ctx.runId}`], { repoDrivers: true, userHooks: true });
|
|
19348
21064
|
if (c.code !== 0)
|
|
19349
21065
|
return { status: "failed", data: {}, reason: "git commit failed" };
|
|
19350
|
-
return { status: "completed", data: { committed: true, head_sha: (await
|
|
21066
|
+
return { status: "completed", data: { committed: true, head_sha: (await git2(ctx, ["rev-parse", "HEAD"])).out.trim(), scope_notes: notes } };
|
|
19351
21067
|
}
|
|
19352
21068
|
};
|
|
19353
21069
|
function verdictOf2(o, checks, emptyDiff, verifyNotProven, wallGreenOnBase, proof, targetProof = false, uncovered = []) {
|
|
@@ -19396,6 +21112,7 @@ function renderReceiptMd(r) {
|
|
|
19396
21112
|
`- receipt_sha256: ${r.receipt_sha256}`,
|
|
19397
21113
|
`- Signature: ${sig}`,
|
|
19398
21114
|
`- Provider: ${r.provider} (${r.model}) Cost: ${usd} Wall: ${r.time.wall_s}s`,
|
|
21115
|
+
...r.mutation_proof ? [`- ${r.mutation_proof}`] : [],
|
|
19399
21116
|
...r.route ? routeReceiptLines(r.route) : [],
|
|
19400
21117
|
"",
|
|
19401
21118
|
"### Checks",
|
|
@@ -19413,9 +21130,9 @@ var sealStage = {
|
|
|
19413
21130
|
...STAGE_BUDGETS.seal,
|
|
19414
21131
|
async run(ctx, signal) {
|
|
19415
21132
|
const o = ctx.outputs();
|
|
19416
|
-
const head = (await
|
|
19417
|
-
const tree = (await
|
|
19418
|
-
const diff = await
|
|
21133
|
+
const head = (await git2(ctx, ["rev-parse", "HEAD"])).out.trim();
|
|
21134
|
+
const tree = (await git2(ctx, ["rev-parse", "HEAD^{tree}"])).out.trim();
|
|
21135
|
+
const diff = await safeGitRun(ctx.repoDir, ["diff-tree", "-r", "-z", "--raw", "--no-renames", "--no-abbrev", "-O/dev/null", ctx.baseSha, head, "--", ".", EXCLUDE_LOKI], { timeoutMs: 20000 });
|
|
19419
21136
|
const diffOk = diff.exitCode === 0 && /^[0-9a-f]{40,64}$/.test(head);
|
|
19420
21137
|
const checks = checksOf(o.verify?.checks);
|
|
19421
21138
|
const verifyNotProven = strs2(o.verify?.not_proven);
|
|
@@ -19432,8 +21149,12 @@ var sealStage = {
|
|
|
19432
21149
|
const proof = wallGreenOnBase || hasExecutedProof(Array.isArray(o.verify?.checks) ? o.verify.checks : []);
|
|
19433
21150
|
const uncoveredAfterLimit = o.implement?.exit === "killed" ? strs2(o.verify?.uncovered_changed) : [];
|
|
19434
21151
|
const verdict0 = capGroupVerdict(verdictOf2(o, checks.filter((c) => !(c.result === "fail" && preRedChecks.includes(c.name))), !diffOk || diff.stdout === "", verifyNotProven.length > 0 || weakTests.length > 0, wallGreenOnBase, proof, targetProofOf(o.verify), uncoveredAfterLimit), grp);
|
|
19435
|
-
const
|
|
19436
|
-
const
|
|
21152
|
+
const supply = await supplyGuard(ctx.repoDir, rawDiff.filter((_, i) => i % 2 === 1), readDeclared(ctx.repoDir), process.env);
|
|
21153
|
+
const verdict1 = supplyVerdict(verdict0, supply);
|
|
21154
|
+
const xr = await crossReview(ctx, verdict1, head), verdict2 = minVerdict(verdict1, xr);
|
|
21155
|
+
const mp = verdict2 === "VERIFIED" && mutationEnabled() ? mutationProof({ repoDir: ctx.repoDir, baseSha: ctx.baseSha, runDir: ctx.runDir, wallFiles: Array.isArray(o.wall?.files) ? o.wall.files : [], checks: Array.isArray(o.verify?.checks) ? o.verify.checks : [], runner: (ms) => new RealBaseTestRunner(undefined, ms) }) : null;
|
|
21156
|
+
const verdict = mp?.outcome === "no" && mutationStrict() && o.plan?.behavior_change === true ? "PARTIAL" : verdict2;
|
|
21157
|
+
const notProven = new Set([...DEEP_NOT_PROVEN, ...supply.notProven, ...grp.notProven, ...xr?.notes ?? []]);
|
|
19437
21158
|
if (!proof && (verdict === "PARTIAL" || verdict === "VERIFIED" || verdict === "ALREADY_SATISFIED")) {
|
|
19438
21159
|
const vc = Array.isArray(o.verify?.checks) ? o.verify.checks : [];
|
|
19439
21160
|
notProven.add(vc.length > 0 && vc.every((c) => c.n !== 0 && String(c.reason ?? "").startsWith(UNMEASURED_REASON)) ? UNMEASURED_REASON : vc.length > 0 && vc.every((c) => c.n !== 0 && String(c.reason ?? "").startsWith(UNCONFIRMED_REASON)) ? UNCONFIRMED_REASON : NO_TESTS_REASON);
|
|
@@ -19444,8 +21165,8 @@ var sealStage = {
|
|
|
19444
21165
|
notProven.add(`wall test discarded: ${String(d.file)} (${String(d.reason)})`);
|
|
19445
21166
|
if (wallNotRun > 0) {
|
|
19446
21167
|
try {
|
|
19447
|
-
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) =>
|
|
19448
|
-
for (const n2 of
|
|
21168
|
+
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename17(String(f.path))));
|
|
21169
|
+
for (const n2 of readdirSync14(join49(ctx.runDir, "wall")).sort())
|
|
19449
21170
|
if (!kept.has(n2))
|
|
19450
21171
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
19451
21172
|
} catch {}
|
|
@@ -19494,6 +21215,8 @@ var sealStage = {
|
|
|
19494
21215
|
if (source !== "text" && source !== "issue")
|
|
19495
21216
|
notProven.add("task source not recorded by intake");
|
|
19496
21217
|
const repo = str3(o.intake?.repo);
|
|
21218
|
+
if (mp?.outcome === "no")
|
|
21219
|
+
notProven.add(`Wall tests passed without the fix (mutation proof)${verdict !== verdict2 ? "; verdict downgraded to PARTIAL (LOKI_MUTATION_STRICT=1, declared behavior change)" : ""}`);
|
|
19497
21220
|
if (repo === null)
|
|
19498
21221
|
notProven.add("repo not recorded by intake");
|
|
19499
21222
|
if (typeof o.intake?.resumed !== "boolean")
|
|
@@ -19508,12 +21231,12 @@ var sealStage = {
|
|
|
19508
21231
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
19509
21232
|
if (cost.unmetered)
|
|
19510
21233
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
19511
|
-
const eventsPath =
|
|
21234
|
+
const eventsPath = join49(ctx.runDir, "events.jsonl");
|
|
19512
21235
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
19513
21236
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
19514
21237
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
19515
21238
|
notProven.add("wall result not recorded by verify");
|
|
19516
|
-
const routeBlock = buildRouteBlock(process.env, ctx.provider, o.implement?.route, cost.router ?? (routerEnabled(process.env) ? sumResultCosts(
|
|
21239
|
+
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(join49(ctx.repoDir, ".loki"), iterIds).router : undefined), ctx.model);
|
|
19517
21240
|
if (routeBlock)
|
|
19518
21241
|
for (const l of routeNotProven(routeBlock))
|
|
19519
21242
|
notProven.add(l);
|
|
@@ -19532,6 +21255,7 @@ var sealStage = {
|
|
|
19532
21255
|
verdict,
|
|
19533
21256
|
...typeof o.implement?.limit_s === "number" ? { implement_limit: { limit_s: o.implement.limit_s, elapsed_s: typeof o.implement.elapsed_s === "number" ? o.implement.elapsed_s : 0 } } : {},
|
|
19534
21257
|
...grp.section ? { group: grp.section } : {},
|
|
21258
|
+
...ctx.failovers && ctx.failovers().length > 0 ? { failover: ctx.failovers() } : {},
|
|
19535
21259
|
...str3(o.implement?.spec_conflict_reason) !== null ? { spec_conflict_reason: sanitizeReason(str3(o.implement?.spec_conflict_reason)) } : {},
|
|
19536
21260
|
evidence: strs2(o.intake?.evidence),
|
|
19537
21261
|
...o.intake?.preexisting_dirty ? { pre_existing_dirty: Object.keys(o.intake.preexisting_dirty) } : {},
|
|
@@ -19549,10 +21273,13 @@ var sealStage = {
|
|
|
19549
21273
|
provider: ctx.provider,
|
|
19550
21274
|
model: ctx.model,
|
|
19551
21275
|
resumed: o.intake?.resumed === true,
|
|
19552
|
-
events_sha256: sha2562(
|
|
21276
|
+
events_sha256: sha2562(existsSync30(eventsPath) ? readFileSync35(eventsPath) : ""),
|
|
19553
21277
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
19554
21278
|
log_seal: true,
|
|
19555
|
-
...
|
|
21279
|
+
...receiptBlock(process.env, cost.usd, cost.unmetered === true, Object.values(stages).reduce((a, b) => a + (b ?? 0), 0)),
|
|
21280
|
+
...mp ? { mutation_proof: mp.line, mutation_outcome: mp.outcome } : {},
|
|
21281
|
+
...routeBlock ? { route: routeBlock } : {},
|
|
21282
|
+
...supply.block ? { supply: supply.block } : {}
|
|
19556
21283
|
};
|
|
19557
21284
|
for (const l of sealContract(ctx.repoDir, body, rawDiff, checks, process.env, o.intake?.contract_snapshot))
|
|
19558
21285
|
notProven.add(l);
|
|
@@ -19564,13 +21291,17 @@ var sealStage = {
|
|
|
19564
21291
|
hash = receiptSha256(body);
|
|
19565
21292
|
}
|
|
19566
21293
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
19567
|
-
|
|
19568
|
-
const path =
|
|
19569
|
-
|
|
21294
|
+
mkdirSync17(ctx.runDir, { recursive: true });
|
|
21295
|
+
const path = join49(ctx.runDir, "receipt.json");
|
|
21296
|
+
writeFileSync18(path, JSON.stringify(receipt, null, 2) + `
|
|
19570
21297
|
`);
|
|
19571
|
-
|
|
21298
|
+
writeFileSync18(join49(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
21299
|
+
recordRun(process.env, ctx.repoDir, ctx.model, verdict, cost.usd, cost.unmetered === true, receipt.time.wall_s);
|
|
19572
21300
|
const signed = sig.jwt !== null;
|
|
19573
|
-
const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} };
|
|
21301
|
+
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) } : {} };
|
|
21302
|
+
try {
|
|
21303
|
+
recordRunVerdict(ctx.repoDir, ctx.runId, verdict);
|
|
21304
|
+
} catch {}
|
|
19574
21305
|
ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven, ...routeBlock ? { route_line: routePrLine(routeBlock) } : {} });
|
|
19575
21306
|
return { status: "completed", data: { ...data, summary: `${verdict} receipt ${hash.slice(0, 12)} ${signed ? `SIGNED kid ${sig.kid}` : "UNSIGNED"}` } };
|
|
19576
21307
|
}
|
|
@@ -19578,7 +21309,7 @@ var sealStage = {
|
|
|
19578
21309
|
|
|
19579
21310
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
19580
21311
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
19581
|
-
import { readFileSync as
|
|
21312
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
19582
21313
|
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
19583
21314
|
var STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
19584
21315
|
var PREDICATE_TYPE = "https://autonomi.dev/loki/receipt/v10";
|
|
@@ -19659,7 +21390,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
19659
21390
|
}
|
|
19660
21391
|
function runIdGuard(receiptPath, runId) {
|
|
19661
21392
|
try {
|
|
19662
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
21393
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync36(receiptPath, "utf8")), runId);
|
|
19663
21394
|
} catch {
|
|
19664
21395
|
return null;
|
|
19665
21396
|
}
|
|
@@ -19673,7 +21404,7 @@ function pubFor(kid, given) {
|
|
|
19673
21404
|
const active = loadSigningKey(false);
|
|
19674
21405
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
19675
21406
|
try {
|
|
19676
|
-
return [createPublicKey4(
|
|
21407
|
+
return [createPublicKey4(readFileSync37(f))];
|
|
19677
21408
|
} catch {
|
|
19678
21409
|
return [];
|
|
19679
21410
|
}
|
|
@@ -19703,18 +21434,18 @@ function checkAttestation(jwt, expectedHash, given) {
|
|
|
19703
21434
|
}
|
|
19704
21435
|
function checkEventLog(receiptPath, receipt) {
|
|
19705
21436
|
const bound = receipt["events_sha256"];
|
|
19706
|
-
if (typeof bound !== "string" || bound ===
|
|
21437
|
+
if (typeof bound !== "string" || bound === createHash12("sha256").digest("hex"))
|
|
19707
21438
|
return null;
|
|
19708
|
-
const path =
|
|
19709
|
-
if (!
|
|
21439
|
+
const path = join50(dirname22(receiptPath), "events.jsonl");
|
|
21440
|
+
if (!existsSync31(path))
|
|
19710
21441
|
return "events.jsonl is missing";
|
|
19711
|
-
const raw2 =
|
|
21442
|
+
const raw2 = readFileSync37(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
19712
21443
|
`).filter((l) => l.trim() !== "");
|
|
19713
21444
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
19714
21445
|
return "events.jsonl has a forged, edited or missing line";
|
|
19715
21446
|
if (events2.some((e) => e.type === "tamper.detected"))
|
|
19716
21447
|
return "the supervisor detected events.jsonl being modified during the run";
|
|
19717
|
-
const h =
|
|
21448
|
+
const h = createHash12("sha256");
|
|
19718
21449
|
for (const l of lines) {
|
|
19719
21450
|
if (h.copy().digest("hex") === bound)
|
|
19720
21451
|
return null;
|
|
@@ -19724,7 +21455,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
19724
21455
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
19725
21456
|
}
|
|
19726
21457
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
19727
|
-
const path =
|
|
21458
|
+
const path = join50(dirname22(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
19728
21459
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
19729
21460
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
19730
21461
|
if (done < 0)
|
|
@@ -19732,8 +21463,8 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
19732
21463
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
19733
21464
|
if (!d)
|
|
19734
21465
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
19735
|
-
const lines =
|
|
19736
|
-
`).filter((l) => l.trim() !== ""), sha3 =
|
|
21466
|
+
const lines = readFileSync37(path, "utf8").split(`
|
|
21467
|
+
`).filter((l) => l.trim() !== ""), sha3 = createHash12("sha256").update(lines.slice(0, done + 1).join(`
|
|
19737
21468
|
`) + `
|
|
19738
21469
|
`).digest("hex");
|
|
19739
21470
|
const pub = d["kid"] === kid ? pubFor(kid, given) : undefined;
|
|
@@ -19742,11 +21473,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
19742
21473
|
return null;
|
|
19743
21474
|
}
|
|
19744
21475
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
19745
|
-
if (!
|
|
21476
|
+
if (!existsSync31(receiptPath))
|
|
19746
21477
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
19747
21478
|
let receipt;
|
|
19748
21479
|
try {
|
|
19749
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
21480
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync37(p, "utf8")))(receiptPath));
|
|
19750
21481
|
} catch {
|
|
19751
21482
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
19752
21483
|
}
|
|
@@ -19814,11 +21545,11 @@ function mount21(ctx) {
|
|
|
19814
21545
|
return done(404, "receipt not available", "not_found");
|
|
19815
21546
|
let runDir, file;
|
|
19816
21547
|
try {
|
|
19817
|
-
const root =
|
|
19818
|
-
runDir =
|
|
19819
|
-
if (runDir !==
|
|
21548
|
+
const root = realpathSync20(join51(repo.p, ".loki", "runs"));
|
|
21549
|
+
runDir = realpathSync20(join51(root, run2));
|
|
21550
|
+
if (runDir !== join51(root, run2))
|
|
19820
21551
|
return done(404, "receipt not found", "not_found");
|
|
19821
|
-
file =
|
|
21552
|
+
file = realpathSync20(join51(runDir, "receipt.json"));
|
|
19822
21553
|
} catch {
|
|
19823
21554
|
return done(404, "receipt not found", "not_found");
|
|
19824
21555
|
}
|
|
@@ -19837,7 +21568,7 @@ function mount21(ctx) {
|
|
|
19837
21568
|
sha3 = r.receiptSha256 ?? null;
|
|
19838
21569
|
}
|
|
19839
21570
|
try {
|
|
19840
|
-
outcome = outcomeOf(JSON.parse(
|
|
21571
|
+
outcome = outcomeOf(JSON.parse(readFileSync38(file, "utf8")));
|
|
19841
21572
|
} catch {
|
|
19842
21573
|
outcome = "UNREADABLE";
|
|
19843
21574
|
}
|
|
@@ -19899,8 +21630,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now2 = Date.now) {
|
|
|
19899
21630
|
}
|
|
19900
21631
|
|
|
19901
21632
|
// src/server/legacy/shim.ts
|
|
19902
|
-
import { existsSync as
|
|
19903
|
-
import { join as
|
|
21633
|
+
import { existsSync as existsSync32, readFileSync as readFileSync39 } from "fs";
|
|
21634
|
+
import { join as join52 } from "path";
|
|
19904
21635
|
|
|
19905
21636
|
// src/server/legacy/routes.ts
|
|
19906
21637
|
var LEGACY_ROUTES = [
|
|
@@ -20272,10 +22003,10 @@ var logged = new Set;
|
|
|
20272
22003
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
20273
22004
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
20274
22005
|
var repoVersion = () => {
|
|
20275
|
-
for (const f of [
|
|
22006
|
+
for (const f of [join52(import.meta.dir, "../../../../../VERSION")]) {
|
|
20276
22007
|
try {
|
|
20277
|
-
if (
|
|
20278
|
-
return
|
|
22008
|
+
if (existsSync32(f))
|
|
22009
|
+
return readFileSync39(f, "utf8").trim();
|
|
20279
22010
|
} catch {}
|
|
20280
22011
|
}
|
|
20281
22012
|
return "unknown";
|
|
@@ -20457,7 +22188,7 @@ function legacyShim(opts) {
|
|
|
20457
22188
|
|
|
20458
22189
|
// src/server/app.ts
|
|
20459
22190
|
var MAX_BODY2 = 1e6;
|
|
20460
|
-
var defaultUiDir = () => [
|
|
22191
|
+
var defaultUiDir = () => [join53(import.meta.dir, "../../ui/dist"), join53(import.meta.dir, "../ui/dist")].find((d) => existsSync33(join53(d, "index.html")));
|
|
20461
22192
|
function createApp(opts) {
|
|
20462
22193
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
20463
22194
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
@@ -20486,10 +22217,10 @@ function createApp(opts) {
|
|
|
20486
22217
|
await next();
|
|
20487
22218
|
});
|
|
20488
22219
|
const readInstalled = () => {
|
|
20489
|
-
for (const f of [process.env.LOKI_VERSION_FILE,
|
|
22220
|
+
for (const f of [process.env.LOKI_VERSION_FILE, join53(import.meta.dir, "../../../../VERSION")]) {
|
|
20490
22221
|
try {
|
|
20491
22222
|
if (f)
|
|
20492
|
-
return
|
|
22223
|
+
return readFileSync40(f, "utf8").trim() || "unknown";
|
|
20493
22224
|
} catch {}
|
|
20494
22225
|
}
|
|
20495
22226
|
return "unknown";
|
|
@@ -20598,13 +22329,13 @@ function createApp(opts) {
|
|
|
20598
22329
|
}
|
|
20599
22330
|
if (!uiDir || p.startsWith("/v1/"))
|
|
20600
22331
|
return c.json({ error: "not found" }, 404);
|
|
20601
|
-
const root =
|
|
20602
|
-
const f =
|
|
20603
|
-
if (f.startsWith(root + sep14) &&
|
|
22332
|
+
const root = resolve12(uiDir);
|
|
22333
|
+
const f = resolve12(root, `.${p}`);
|
|
22334
|
+
if (f.startsWith(root + sep14) && existsSync33(f) && statSync17(f).isFile())
|
|
20604
22335
|
return new Response(Bun.file(f));
|
|
20605
22336
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
20606
22337
|
return c.json({ error: "not found" }, 404);
|
|
20607
|
-
return new Response(Bun.file(
|
|
22338
|
+
return new Response(Bun.file(join53(root, "index.html")));
|
|
20608
22339
|
});
|
|
20609
22340
|
return { app, db, close: () => {
|
|
20610
22341
|
clearInterval(reconciler);
|