0sec-cli 0.22.1 → 0.23.0
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/0.js +64 -47
- package/README.md +73 -53
- package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-V3JFWOKC.js} +4 -4
- package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-NYKUF2M5.js} +2 -2
- package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-ZX3YVWEH.js} +19 -15
- package/chunks/backend-connections-H52NIVH2.js +23 -0
- package/chunks/{chunk-NLH4LMWH.js → chunk-3E54JYS4.js} +1 -1
- package/chunks/chunk-3P3XXSIJ.js +463 -0
- package/chunks/{chunk-CPWA6RWE.js → chunk-3ZNI7F42.js} +3 -3
- package/chunks/{chunk-LGCASF2C.js → chunk-4HGGNBKK.js} +7405 -2225
- package/chunks/{chunk-TM74SSHC.js → chunk-62ENS6XO.js} +1 -1
- package/chunks/{chunk-4VGLO2YS.js → chunk-65H3MRBF.js} +5 -2
- package/chunks/chunk-A2ADXAC5.js +5329 -0
- package/chunks/chunk-A5QO7N3S.js +74 -0
- package/chunks/chunk-AAA4PRWK.js +990 -0
- package/chunks/{chunk-AV2LG2L7.js → chunk-BF5FIK7H.js} +257 -145
- package/chunks/chunk-CP6HANBE.js +355 -0
- package/chunks/{chunk-HHPWRY2M.js → chunk-EH2WTNY7.js} +23474 -22746
- package/chunks/{chunk-OSBPGWTE.js → chunk-EJH2TNVH.js} +3 -3
- package/chunks/chunk-EWXOG6D3.js +1695 -0
- package/chunks/{chunk-WZRVS7SB.js → chunk-F344NYUN.js} +1 -1
- package/chunks/chunk-FXR3BT2G.js +226 -0
- package/chunks/{chunk-GOEA3NW6.js → chunk-HFDSIHTE.js} +1603 -1295
- package/chunks/chunk-I3OCJ2OA.js +309 -0
- package/chunks/{chunk-X7LAEEUW.js → chunk-ITES7BCY.js} +1 -1
- package/chunks/{chunk-YL6YAGLL.js → chunk-JEYIWMGL.js} +1 -1
- package/chunks/{chunk-WSDDGFA6.js → chunk-K55B4OH4.js} +1 -1
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/chunk-KHPOSLCF.js +1659 -0
- package/chunks/chunk-LT7BFGXA.js +733 -0
- package/chunks/{chunk-KAU6NLGP.js → chunk-LTEE2JWK.js} +786 -20
- package/chunks/{chunk-ZKQ2I3S2.js → chunk-LVGZGG63.js} +1 -1
- package/chunks/chunk-MQJVU2GZ.js +277 -0
- package/chunks/{chunk-OHHCGDJ2.js → chunk-NI6QYQSK.js} +126 -1089
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-3E2KALZ5.js → chunk-NX4H6L4M.js} +2 -2
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-IVQV574Y.js → chunk-O6Z4XPOM.js} +1 -1
- package/chunks/{chunk-V6GHX34G.js → chunk-OLSKXTQZ.js} +8 -274
- package/chunks/{chunk-5BTJMINS.js → chunk-P7KDN5PK.js} +208 -46
- package/chunks/chunk-PIR6BNE7.js +83 -0
- package/chunks/{chunk-XCISCI4T.js → chunk-QMICC5O2.js} +30 -12
- package/chunks/{chunk-6GHSR47F.js → chunk-R35KKJA4.js} +1 -58
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-VWITLWXB.js → chunk-T7ZEVY5G.js} +307 -34
- package/chunks/chunk-TPUJZ74W.js +36 -0
- package/chunks/{chunk-IFME676H.js → chunk-UZUELPFW.js} +79 -18
- package/chunks/{chunk-TV27OWIW.js → chunk-V2BWXPZP.js} +9 -9
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-XDKB2P5X.js → chunk-W5VNCB57.js} +1 -1
- package/chunks/{chunk-4G7OVYJX.js → chunk-WIYNYNL2.js} +1 -1
- package/chunks/chunk-WYNZFNMF.js +457 -0
- package/chunks/{chunk-7XCKYNLO.js → chunk-XORZUR2H.js} +1 -1
- package/chunks/{chunk-2OQOQ2FZ.js → chunk-Y4HJRUFE.js} +20 -4
- package/chunks/{chunk-V36HVTUL.js → chunk-YAEJAZYW.js} +21 -18
- package/chunks/{chunk-XA2O4WXA.js → chunk-YYS2H33A.js} +169 -15
- package/chunks/{codex-models-NHQW45SO.js → codex-models-Y457ND6Q.js} +9 -3
- package/chunks/{commands-N6GN5SBF.js → commands-PBDAOZKA.js} +7019 -5937
- package/chunks/{db-5IWEJUYV.js → db-TPSI552V.js} +3 -3
- package/chunks/deep-review-DKED6EN5.js +81 -0
- package/chunks/{dist-L3XJWWZ6.js → dist-4LGRIBOC.js} +209 -114
- package/chunks/{dist-FSS7MBDF.js → dist-7Q5A6TQM.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-BS4CY4RG.js → dist-RLPNLRZD.js} +79 -1
- package/chunks/eval-runner-RZKDOQ2G.js +30 -0
- package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-RMFYNG7H.js} +1 -1
- package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-2A2PQI4E.js} +1 -1
- package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-DEJ232QN.js} +2 -2
- package/chunks/{fix-UUKPXCFB.js → fix-JGBW7XUN.js} +12 -6
- package/chunks/{harness-C5HOKIEV.js → harness-CBOLMUHB.js} +3 -3
- package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-MIRMABY4.js} +17 -13
- package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-NAPDMJC7.js} +2 -2
- package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-PR5OSDQ2.js} +2 -2
- package/chunks/managed-operation-YNQQEA5V.js +59 -0
- package/chunks/{memsafety-scan-FCPAXIZE.js → memsafety-scan-RPS446CD.js} +2 -2
- package/chunks/{native-loop-P6P5GYVJ.js → native-loop-CGOJMNHV.js} +19 -9
- package/chunks/orchestrate-5NK34H3W.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-YVNENPNL.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/remote-workflow-runtime-RGQ5ECMN.js +96 -0
- package/chunks/{run-GAFKBCTN.js → run-VH4OIDKF.js} +1919 -4817
- package/chunks/{runtime-XFWJTBUO.js → runtime-4TAJFVF7.js} +7 -1
- package/chunks/{runtime-H7YBR5IZ.js → runtime-HWL6HG7A.js} +10 -4
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-HJAZJONE.js} +7 -1
- package/chunks/{scope-QWY4VQZ7.js → scope-Q5PIX2IS.js} +2 -2
- package/chunks/service-plugins-QYFITODK.js +63 -0
- package/chunks/{session-store-XIDFGG7K.js → session-store-2FEIZGSV.js} +6 -4
- package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-TX5V6XOT.js} +9 -3
- package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-6HOUGF7Q.js} +11 -6
- package/chunks/workflow-runtime-ZEEBJFQG.js +64 -0
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/abnfDiagram-VCTEODGH-BuvumFYR.js +1 -0
- package/dashboard/assets/arc-DZzWDuCG.js +1 -0
- package/dashboard/assets/architecture-7GRP2DOG-DK9bv1Md.js +1 -0
- package/dashboard/assets/architectureDiagram-5GKGNRK7-Bw5el_EL.js +36 -0
- package/dashboard/assets/array-BifhSqXX.js +1 -0
- package/dashboard/assets/blockDiagram-I7D4REHJ-BjVLEApp.js +129 -0
- package/dashboard/assets/c4Diagram-7LVT6UL2-B9nNzx2J.js +38 -0
- package/dashboard/assets/channel-Ck17eKdX.js +1 -0
- package/dashboard/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dashboard/assets/chunk-4HAMMTFA-y72Q95nW.js +62 -0
- package/dashboard/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dashboard/assets/chunk-75Z2AOVW-BOhrOGMO.js +2 -0
- package/dashboard/assets/chunk-DU6HZSFF-B0EhAaNb.js +127 -0
- package/dashboard/assets/chunk-F27PBJKO-BR-hiDvM.js +1 -0
- package/dashboard/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
- package/dashboard/assets/chunk-GMAD6QVW-H15vJYXg.js +72 -0
- package/dashboard/assets/chunk-GVQU2GXP-BRndps0o.js +1 -0
- package/dashboard/assets/chunk-IMKFNOWR-CIzC_OWx.js +231 -0
- package/dashboard/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dashboard/assets/chunk-L3NEJ4N5-NR4P1Vko.js +1 -0
- package/dashboard/assets/chunk-OSK3NFVY-DtjTDf8S.js +10 -0
- package/dashboard/assets/chunk-P2QGCYS3-CYT11Qjx.js +1 -0
- package/dashboard/assets/chunk-POPQ4Y6H-Duj2Ygwu.js +1 -0
- package/dashboard/assets/chunk-PWAF6VOD-z4p5Tf_y.js +1 -0
- package/dashboard/assets/chunk-SHT3W25Y-DYeLYLS3.js +168 -0
- package/dashboard/assets/chunk-SVP7TREG-BKowxCVI.js +88 -0
- package/dashboard/assets/chunk-TICWLB2K-GDAwEFlH.js +206 -0
- package/dashboard/assets/chunk-XXDRQBXY-DRD79Mjx.js +1 -0
- package/dashboard/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dashboard/assets/classDiagram-ZZMXUADV-BaeZA6LE.js +1 -0
- package/dashboard/assets/classDiagram-v2-VYDZK3BY-BaeZA6LE.js +1 -0
- package/dashboard/assets/common-vnzd7UOF.js +5 -0
- package/dashboard/assets/cose-bilkent-JH36ORCC-GqCLsRyf.js +1 -0
- package/dashboard/assets/cynefin-OW5HDTMX-CJN9YwWN.js +1 -0
- package/dashboard/assets/cynefinDiagram-5FMLGOSQ-DVJHCYQY.js +62 -0
- package/dashboard/assets/cytoscape.esm-CGd-uY3x.js +321 -0
- package/dashboard/assets/dagre-GXQ25YYZ-BMCrrJJR.js +4 -0
- package/dashboard/assets/dagre-PrKaheQc.js +1 -0
- package/dashboard/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dashboard/assets/diagram-S7CK7UJ4-CLYDdOEm.js +30 -0
- package/dashboard/assets/diagram-UQ7AKVKN-CnPuAZK7.js +41 -0
- package/dashboard/assets/diagram-VSXAHHWV-Cr2I2DnG.js +3 -0
- package/dashboard/assets/diagram-VX7I27RA-_mGW1wpp.js +24 -0
- package/dashboard/assets/diagram-Z3DM3KII-igybB0l9.js +24 -0
- package/dashboard/assets/dist-Bb7_-x5y.js +1 -0
- package/dashboard/assets/ebnfDiagram-PWID7BFC-CVCICf2R.js +1 -0
- package/dashboard/assets/erDiagram-RLTQ6QDP-CRjnfgxj.js +99 -0
- package/dashboard/assets/eventmodeling-NTZA5JFV-po5oDydv.js +1 -0
- package/dashboard/assets/flowDiagram-HODETNUW-TSV9s7q4.js +1 -0
- package/dashboard/assets/ganttDiagram-EL5Y4UJY-DppD7sD7.js +292 -0
- package/dashboard/assets/gitGraph-4MIJSDKK-Tw0vNnQc.js +1 -0
- package/dashboard/assets/gitGraphDiagram-WWUBYQGX-Cpf96-z3.js +106 -0
- package/dashboard/assets/graphlib-DS17s2tU.js +1 -0
- package/dashboard/assets/info-A6RAGUB7-DLyt9arx.js +1 -0
- package/dashboard/assets/infoDiagram-27XIBGKW-B6_piSNS.js +2 -0
- package/dashboard/assets/init-C-OQMol4.js +1 -0
- package/dashboard/assets/ishikawaDiagram-5VMMS53U-DvMO0B0X.js +70 -0
- package/dashboard/assets/journeyDiagram-3NMN7TZE-f7ZK98b6.js +139 -0
- package/dashboard/assets/kanban-definition-UXKFOSKX-Dso0nAxL.js +89 -0
- package/dashboard/assets/katex-CXMH3UgJ.js +257 -0
- package/dashboard/assets/line-VcKaFG0G.js +1 -0
- package/dashboard/assets/linear-D7YnpiWh.js +1 -0
- package/dashboard/assets/mermaid-parser.core-DriL2hrV.js +7 -0
- package/dashboard/assets/mermaid.core-DJWYDh_P.js +44 -0
- package/dashboard/assets/mindmap-definition-YA3MSWOX-DZsoIeac.js +96 -0
- package/dashboard/assets/operations-BIaUSnYv.js +121 -0
- package/dashboard/assets/operations-BeCdkT4S.css +1 -0
- package/dashboard/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dashboard/assets/packet-AYTQ26CC-BQBIXKxk.js +1 -0
- package/dashboard/assets/path-fybaL0A-.js +1 -0
- package/dashboard/assets/pegDiagram-XKGWAZYB-B0-lslgW.js +1 -0
- package/dashboard/assets/pie-WAS4IAKB-DoY6dwQs.js +1 -0
- package/dashboard/assets/pieDiagram-E7YTZNPT-ElmdRjp5.js +39 -0
- package/dashboard/assets/quadrantDiagram-AXDQQJYC-Ci1h98Q2.js +7 -0
- package/dashboard/assets/radar-RG4KPBEZ-DHod2WgB.js +1 -0
- package/dashboard/assets/railroad-74A4TZTK-90JxB4xV.js +1 -0
- package/dashboard/assets/railroad-abnf-HS5TGJTU-BkXGl114.js +1 -0
- package/dashboard/assets/railroad-ebnf-LZEXJU2U-QVAERn2C.js +1 -0
- package/dashboard/assets/railroad-peg-WCYAUIDC-B1-PM01z.js +1 -0
- package/dashboard/assets/railroadDiagram-O6MQD6OU-k3190A6L.js +1 -0
- package/dashboard/assets/requirementDiagram-BXWQKSXE-C3-eR7I5.js +84 -0
- package/dashboard/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dashboard/assets/sankeyDiagram-P5KCCOFB-Bc86pi8w.js +40 -0
- package/dashboard/assets/sequenceDiagram-WJ2MYXX4-Bv_MvEmz.js +162 -0
- package/dashboard/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dashboard/assets/src-D7Dqq5yP.js +1 -0
- package/dashboard/assets/stateDiagram-D77RDMKH-Bq_GVmeW.js +1 -0
- package/dashboard/assets/stateDiagram-v2-MP3YSRHH-z1XRaftz.js +1 -0
- package/dashboard/assets/swimlanes-42K2YHIH-q0iK-e0P.js +1 -0
- package/dashboard/assets/swimlanesDiagram-VR7AAH4N-C9L6XBQj.js +8 -0
- package/dashboard/assets/timeline-definition-24CTP7MA-CNfF07hM.js +120 -0
- package/dashboard/assets/treeView-Q6P3EWNA-m629ePnS.js +1 -0
- package/dashboard/assets/treemap-WGGIJYW6-D9Cb2ETv.js +1 -0
- package/dashboard/assets/vennDiagram-4TSXK5OY-v4dKF-FE.js +34 -0
- package/dashboard/assets/wardley-WFR3VGLG-DkwH_nRe.js +1 -0
- package/dashboard/assets/wardleyDiagram-VM6X3IG4-3ImohVu4.js +78 -0
- package/dashboard/assets/xychartDiagram-S5SC5T6Z-QJu3dm7K.js +7 -0
- package/dashboard/assets/zero-connect-BGDY0PMZ.webp +0 -0
- package/dashboard/assets/zero-curious-DovTLOs8.webp +0 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/assets/zero-planning-CBeHDrG_.webp +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-BCSRRQG5.js +0 -3514
- package/chunks/chunk-BOUGKEKA.js +0 -193
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/eval-runner-EMEGF24P.js +0 -26
- package/chunks/orchestrate-DWWUT5RC.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
isAdmittedSmolvmWorkbench
|
|
6
|
+
} from "./chunk-NI6QYQSK.js";
|
|
7
|
+
import {
|
|
8
|
+
isScopeEnforcementEnabled
|
|
9
|
+
} from "./chunk-ITES7BCY.js";
|
|
10
|
+
import {
|
|
11
|
+
external_exports
|
|
12
|
+
} from "./chunk-VIT5ALXF.js";
|
|
13
|
+
|
|
14
|
+
// packages/core/dist/bench/improvement-promotion.js
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
var DEFAULT_IMPROVEMENT_PROMOTION_POLICY = Object.freeze({
|
|
17
|
+
minimumCases: 10,
|
|
18
|
+
minimumDevelopmentLift: 0.05,
|
|
19
|
+
minimumHeldOutLift: 0.03,
|
|
20
|
+
maximumNegativeControlFpDelta: 0.02,
|
|
21
|
+
maximumCostMultiplier: 1.5
|
|
22
|
+
});
|
|
23
|
+
function canonicalJson(value) {
|
|
24
|
+
if (value === null || typeof value !== "object")
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
28
|
+
const record = value;
|
|
29
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
30
|
+
}
|
|
31
|
+
function digest(value) {
|
|
32
|
+
return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
|
|
33
|
+
}
|
|
34
|
+
function isSha256(value) {
|
|
35
|
+
return /^sha256:[a-f0-9]{64}$/i.test(value);
|
|
36
|
+
}
|
|
37
|
+
function metricSampleIsSufficient(result, minimumCases) {
|
|
38
|
+
return result.development.champion.cases >= minimumCases && result.development.challenger.cases >= minimumCases && result.heldOut.champion.cases >= minimumCases && result.heldOut.challenger.cases >= minimumCases && result.negativeControls.champion.cases >= minimumCases && result.negativeControls.challenger.cases >= minimumCases;
|
|
39
|
+
}
|
|
40
|
+
function resultRatesAreFinite(result) {
|
|
41
|
+
return [
|
|
42
|
+
result.development.champion.successRate,
|
|
43
|
+
result.development.challenger.successRate,
|
|
44
|
+
result.heldOut.champion.successRate,
|
|
45
|
+
result.heldOut.challenger.successRate,
|
|
46
|
+
result.negativeControls.champion.falsePositiveRate,
|
|
47
|
+
result.negativeControls.challenger.falsePositiveRate
|
|
48
|
+
].every((value) => Number.isFinite(value) && value >= 0 && value <= 1);
|
|
49
|
+
}
|
|
50
|
+
function makeDecision(candidateId, status, checks) {
|
|
51
|
+
const unsigned = {
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
candidateId,
|
|
54
|
+
status,
|
|
55
|
+
checks
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
...unsigned,
|
|
59
|
+
decisionDigest: digest(unsigned)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function evaluateImprovementPromotion(candidate, policy = DEFAULT_IMPROVEMENT_PROMOTION_POLICY) {
|
|
63
|
+
const result = candidate.result;
|
|
64
|
+
const checks = [];
|
|
65
|
+
checks.push({
|
|
66
|
+
id: "candidate_identity",
|
|
67
|
+
passed: candidate.candidateId.length > 0 && candidate.candidateId === result.candidateId,
|
|
68
|
+
detail: "candidate id must be non-empty and match the sealed research result"
|
|
69
|
+
});
|
|
70
|
+
checks.push({
|
|
71
|
+
id: "artifact_digests",
|
|
72
|
+
passed: isSha256(candidate.baseArtifactDigest) && isSha256(candidate.candidateArtifactDigest) && candidate.baseArtifactDigest !== candidate.candidateArtifactDigest,
|
|
73
|
+
detail: "base and candidate artifacts must be distinct sha256 digests"
|
|
74
|
+
});
|
|
75
|
+
checks.push({
|
|
76
|
+
id: "ci",
|
|
77
|
+
passed: result.ciPassed,
|
|
78
|
+
detail: "candidate evaluation must carry a passing CI attestation"
|
|
79
|
+
});
|
|
80
|
+
checks.push({
|
|
81
|
+
id: "evaluator_stability",
|
|
82
|
+
passed: isSha256(result.evaluatorDigestBefore) && result.evaluatorDigestBefore === result.evaluatorDigestAfter,
|
|
83
|
+
detail: "the evaluator digest must remain stable across the candidate evaluation"
|
|
84
|
+
});
|
|
85
|
+
const provenance = result.provenance;
|
|
86
|
+
checks.push({
|
|
87
|
+
id: "comparison_compatibility",
|
|
88
|
+
passed: provenance === void 0 || provenance.compatibilityStatus === "compatible" && Boolean(provenance.resolvedModel) && Boolean(provenance.provider) && provenance.evaluatorDigest === result.evaluatorDigestBefore && provenance.development.digest === result.developmentCorpusDigest && provenance.heldOut.digest === result.heldOutCorpusDigest && provenance.negativeControl.digest === result.negativeControlCorpusDigest,
|
|
89
|
+
detail: provenance === void 0 ? "legacy result has no comparison provenance; explicit provenance enables longitudinal gating" : provenance.compatibilityStatus === "compatible" ? "comparison provenance is compatible" : "comparison provenance is incompatible or unresolved"
|
|
90
|
+
});
|
|
91
|
+
checks.push({
|
|
92
|
+
id: "holdout_exposure",
|
|
93
|
+
passed: result.campaignGate === void 0 || result.campaignGate.allowed,
|
|
94
|
+
detail: result.campaignGate?.reason ?? "holdout exposure and durable campaign accounting permit promotion"
|
|
95
|
+
});
|
|
96
|
+
checks.push({
|
|
97
|
+
id: "evidence",
|
|
98
|
+
passed: result.evidenceRefs.length > 0,
|
|
99
|
+
detail: "candidate evaluation must retain at least one evidence reference"
|
|
100
|
+
});
|
|
101
|
+
checks.push({
|
|
102
|
+
id: "sample_size",
|
|
103
|
+
passed: resultRatesAreFinite(result) && metricSampleIsSufficient(result, policy.minimumCases),
|
|
104
|
+
detail: `every sealed lane must contain at least ${policy.minimumCases} finite observations`
|
|
105
|
+
});
|
|
106
|
+
const developmentLift = result.development.challenger.successRate - result.development.champion.successRate;
|
|
107
|
+
checks.push({
|
|
108
|
+
id: "development_lift",
|
|
109
|
+
passed: developmentLift >= policy.minimumDevelopmentLift,
|
|
110
|
+
detail: `development success lift ${developmentLift.toFixed(4)} must be at least ${policy.minimumDevelopmentLift.toFixed(4)}`
|
|
111
|
+
});
|
|
112
|
+
const heldOutLift = result.heldOut.challenger.successRate - result.heldOut.champion.successRate;
|
|
113
|
+
checks.push({
|
|
114
|
+
id: "held_out_lift",
|
|
115
|
+
passed: heldOutLift >= policy.minimumHeldOutLift,
|
|
116
|
+
detail: `held-out success lift ${heldOutLift.toFixed(4)} must be at least ${policy.minimumHeldOutLift.toFixed(4)}`
|
|
117
|
+
});
|
|
118
|
+
const fpDelta = result.negativeControls.challenger.falsePositiveRate - result.negativeControls.champion.falsePositiveRate;
|
|
119
|
+
checks.push({
|
|
120
|
+
id: "negative_control_precision",
|
|
121
|
+
passed: fpDelta <= policy.maximumNegativeControlFpDelta,
|
|
122
|
+
detail: `negative-control FP delta ${fpDelta.toFixed(4)} must not exceed ${policy.maximumNegativeControlFpDelta.toFixed(4)}`
|
|
123
|
+
});
|
|
124
|
+
const championCost = result.heldOut.champion.costPerSuccessUsd;
|
|
125
|
+
const challengerCost = result.heldOut.challenger.costPerSuccessUsd;
|
|
126
|
+
const zeroSuccessBaseline = championCost === null && result.heldOut.champion.successRate === 0 && result.heldOut.champion.inconclusiveRate === 0;
|
|
127
|
+
const costPasses = challengerCost !== null && Number.isFinite(challengerCost) && challengerCost >= 0 && (zeroSuccessBaseline || championCost !== null && Number.isFinite(championCost) && championCost >= 0 && challengerCost <= championCost * policy.maximumCostMultiplier);
|
|
128
|
+
checks.push({
|
|
129
|
+
id: "cost_discipline",
|
|
130
|
+
passed: costPasses,
|
|
131
|
+
detail: `held-out challenger cost must be finite and nonnegative; at most ${policy.maximumCostMultiplier.toFixed(2)}x champion cost unless the fully observed champion has zero successes`
|
|
132
|
+
});
|
|
133
|
+
if (checks.some((check) => !check.passed)) {
|
|
134
|
+
return makeDecision(candidate.candidateId, "rejected", checks);
|
|
135
|
+
}
|
|
136
|
+
return makeDecision(candidate.candidateId, candidate.kind === "source" ? "requires_human_approval" : "eligible_for_canary", checks);
|
|
137
|
+
}
|
|
138
|
+
function appendImprovementLedgerEntry(entries, event) {
|
|
139
|
+
const verification = verifyImprovementLedger(entries);
|
|
140
|
+
if (!verification.valid)
|
|
141
|
+
throw new Error(`cannot append to invalid improvement ledger: ${verification.reason}`);
|
|
142
|
+
if (!isSha256(event.payloadDigest))
|
|
143
|
+
throw new Error("ledger payloadDigest must be a sha256 digest");
|
|
144
|
+
const previousDigest = entries.length === 0 ? null : entries[entries.length - 1].entryDigest;
|
|
145
|
+
const unsigned = {
|
|
146
|
+
schemaVersion: 1,
|
|
147
|
+
sequence: entries.length,
|
|
148
|
+
occurredAt: event.occurredAt,
|
|
149
|
+
type: event.type,
|
|
150
|
+
candidateId: event.candidateId,
|
|
151
|
+
payloadDigest: event.payloadDigest,
|
|
152
|
+
previousDigest
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
...unsigned,
|
|
156
|
+
entryDigest: digest(unsigned)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function verifyImprovementLedger(entries) {
|
|
160
|
+
let previousDigest = null;
|
|
161
|
+
for (const [sequence, entry] of entries.entries()) {
|
|
162
|
+
if (entry.sequence !== sequence)
|
|
163
|
+
return { valid: false, reason: `unexpected sequence at ${sequence}` };
|
|
164
|
+
if (entry.previousDigest !== previousDigest)
|
|
165
|
+
return { valid: false, reason: `broken predecessor at ${sequence}` };
|
|
166
|
+
if (!isSha256(entry.payloadDigest))
|
|
167
|
+
return { valid: false, reason: `invalid payload digest at ${sequence}` };
|
|
168
|
+
const unsigned = {
|
|
169
|
+
schemaVersion: entry.schemaVersion,
|
|
170
|
+
sequence: entry.sequence,
|
|
171
|
+
occurredAt: entry.occurredAt,
|
|
172
|
+
type: entry.type,
|
|
173
|
+
candidateId: entry.candidateId,
|
|
174
|
+
payloadDigest: entry.payloadDigest,
|
|
175
|
+
previousDigest: entry.previousDigest
|
|
176
|
+
};
|
|
177
|
+
if (entry.entryDigest !== digest(unsigned))
|
|
178
|
+
return { valid: false, reason: `invalid entry digest at ${sequence}` };
|
|
179
|
+
previousDigest = entry.entryDigest;
|
|
180
|
+
}
|
|
181
|
+
return { valid: true };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// packages/core/dist/improvement/config.js
|
|
185
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
186
|
+
import { homedir } from "node:os";
|
|
187
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
188
|
+
var relativePath = external_exports.string().min(1).max(512).refine((value) => !isAbsolute(value) && !value.includes("\\") && !value.includes("\0") && value !== "." && value.split("/").every((part) => part !== ".." && part !== "" && part !== "."), "must be a normalized relative file or directory path (not the entire workspace)");
|
|
189
|
+
var argv = external_exports.array(external_exports.string().max(8192).refine((value) => !value.includes("\0"))).min(1).max(128).refine((value) => value[0].length > 0, "executable must not be empty");
|
|
190
|
+
var finitePositive = external_exports.number().finite().positive();
|
|
191
|
+
var schema = external_exports.object({
|
|
192
|
+
schemaVersion: external_exports.literal(1),
|
|
193
|
+
sourceRoot: external_exports.string().min(1),
|
|
194
|
+
storePath: external_exports.string().min(1),
|
|
195
|
+
image: external_exports.string().min(1).max(512).regex(/^[a-zA-Z0-9][a-zA-Z0-9_./:@-]*$/),
|
|
196
|
+
backend: external_exports.enum(["docker", "smolvm"]).optional(),
|
|
197
|
+
imageArchive: external_exports.string().min(1).max(4096).refine((value) => !value.includes("\0")).optional(),
|
|
198
|
+
sourcePaths: external_exports.array(relativePath).min(1).max(256),
|
|
199
|
+
editablePaths: external_exports.array(relativePath).min(1).max(256),
|
|
200
|
+
kind: external_exports.enum(["source", "skill", "router", "lens"]).default("source"),
|
|
201
|
+
command: argv,
|
|
202
|
+
buildCommand: argv.optional(),
|
|
203
|
+
cases: external_exports.array(external_exports.object({
|
|
204
|
+
id: external_exports.string().min(1).max(128),
|
|
205
|
+
lane: external_exports.enum(["development", "held-out", "negative-control"]),
|
|
206
|
+
input: external_exports.unknown(),
|
|
207
|
+
expected: external_exports.unknown()
|
|
208
|
+
}).strict()).min(3).max(1e3),
|
|
209
|
+
repeats: external_exports.number().int().min(2).max(20).default(3),
|
|
210
|
+
maxIterations: external_exports.number().int().min(1).max(100).default(3),
|
|
211
|
+
maxModelTurns: external_exports.number().int().min(1).max(64).default(12),
|
|
212
|
+
maxModelCostUsd: finitePositive.max(1e3).default(5),
|
|
213
|
+
maxEvaluationCostUsd: finitePositive.max(1e3).default(5),
|
|
214
|
+
computeUsdPerSecond: finitePositive.max(10),
|
|
215
|
+
timeoutMs: external_exports.number().int().min(100).max(6e5).default(6e4),
|
|
216
|
+
memoryMb: external_exports.number().int().min(32).max(16384).default(1024),
|
|
217
|
+
cpus: finitePositive.max(16).default(1),
|
|
218
|
+
maxOutputBytes: external_exports.number().int().min(256).max(16 * 1024 * 1024).default(64 * 1024),
|
|
219
|
+
maxSourceBytes: external_exports.number().int().min(1024).max(512 * 1024 * 1024).default(64 * 1024 * 1024),
|
|
220
|
+
maxChangedBytes: external_exports.number().int().min(128).max(4 * 1024 * 1024).default(256 * 1024),
|
|
221
|
+
model: external_exports.string().min(1).optional(),
|
|
222
|
+
objective: external_exports.string().min(1).max(16e3),
|
|
223
|
+
allowModelSourceAccess: external_exports.boolean().default(false),
|
|
224
|
+
autoPromote: external_exports.boolean().default(false),
|
|
225
|
+
canaryTrials: external_exports.number().int().min(1).max(20).default(2),
|
|
226
|
+
promotionPolicy: external_exports.object({
|
|
227
|
+
minimumCases: external_exports.number().int().min(3).max(1e3).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumCases),
|
|
228
|
+
minimumDevelopmentLift: finitePositive.max(1).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumDevelopmentLift),
|
|
229
|
+
minimumHeldOutLift: finitePositive.max(1).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.minimumHeldOutLift),
|
|
230
|
+
maximumNegativeControlFpDelta: external_exports.literal(0).default(0),
|
|
231
|
+
maximumCostMultiplier: finitePositive.max(10).default(DEFAULT_IMPROVEMENT_PROMOTION_POLICY.maximumCostMultiplier)
|
|
232
|
+
}).strict().default({}),
|
|
233
|
+
safety: external_exports.object({
|
|
234
|
+
enabled: external_exports.boolean().default(false),
|
|
235
|
+
holdoutExposureLimit: external_exports.number().int().min(1).max(1e6)
|
|
236
|
+
}).strict().optional(),
|
|
237
|
+
maxAlternativeParents: external_exports.number().int().min(0).max(10).optional()
|
|
238
|
+
}).strict();
|
|
239
|
+
function canonicalEvolutionJson(value) {
|
|
240
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
241
|
+
return JSON.stringify(value);
|
|
242
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
243
|
+
return JSON.stringify(value);
|
|
244
|
+
if (Array.isArray(value))
|
|
245
|
+
return `[${Array.from(value, canonicalEvolutionJson).join(",")}]`;
|
|
246
|
+
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
247
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalEvolutionJson(value[key])}`).join(",")}}`;
|
|
248
|
+
}
|
|
249
|
+
throw new Error("evolution cases must contain finite JSON values only");
|
|
250
|
+
}
|
|
251
|
+
function parseEvolutionConfig(raw, baseDir = process.cwd()) {
|
|
252
|
+
const parsed = schema.parse(raw);
|
|
253
|
+
const admitted = isAdmittedSmolvmWorkbench();
|
|
254
|
+
if (admitted) {
|
|
255
|
+
if (parsed.imageArchive)
|
|
256
|
+
throw new Error("Admitted workbench images must use the host-approved catalog, not guest archive paths");
|
|
257
|
+
parsed.backend = "smolvm";
|
|
258
|
+
}
|
|
259
|
+
if (parsed.backend === "smolvm") {
|
|
260
|
+
if (!parsed.imageArchive && !admitted)
|
|
261
|
+
throw new Error("smolvm requires a local imageArchive");
|
|
262
|
+
if (!Number.isInteger(parsed.cpus))
|
|
263
|
+
throw new Error("smolvm cpus must be an integer");
|
|
264
|
+
if (parsed.imageArchive)
|
|
265
|
+
parsed.imageArchive = resolve(baseDir, parsed.imageArchive);
|
|
266
|
+
} else if (parsed.imageArchive !== void 0) {
|
|
267
|
+
throw new Error("imageArchive is only valid with backend smolvm");
|
|
268
|
+
}
|
|
269
|
+
if (parsed.cases.length * parsed.repeats * 4 * parsed.maxOutputBytes > 64 * 1024 * 1024) {
|
|
270
|
+
throw new Error("evaluation stream retention exceeds 64 MiB; reduce cases, repeats or maxOutputBytes");
|
|
271
|
+
}
|
|
272
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
273
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
274
|
+
for (const entry of parsed.cases) {
|
|
275
|
+
if (seenIds.has(entry.id))
|
|
276
|
+
throw new Error(`duplicate evolution case id: ${entry.id}`);
|
|
277
|
+
seenIds.add(entry.id);
|
|
278
|
+
const input = canonicalEvolutionJson(entry.input);
|
|
279
|
+
canonicalEvolutionJson(entry.expected);
|
|
280
|
+
if (inputs.has(input))
|
|
281
|
+
throw new Error(`duplicate evaluation input: ${entry.id}; cases and lanes must be independent`);
|
|
282
|
+
inputs.set(input, entry.lane);
|
|
283
|
+
}
|
|
284
|
+
for (const lane of ["development", "held-out", "negative-control"]) {
|
|
285
|
+
if (parsed.cases.filter((entry) => entry.lane === lane).length < parsed.promotionPolicy.minimumCases) {
|
|
286
|
+
throw new Error(`${lane} requires at least ${parsed.promotionPolicy.minimumCases} distinct cases`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const edit of parsed.editablePaths) {
|
|
290
|
+
if (!parsed.sourcePaths.some((source) => edit === source || edit.startsWith(`${source}/`))) {
|
|
291
|
+
throw new Error(`editable path ${edit} is not inside a selected source path`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const sourceRoot = resolve(baseDir, parsed.sourceRoot === "~" ? homedir() : parsed.sourceRoot.startsWith("~/") ? resolve(homedir(), parsed.sourceRoot.slice(2)) : parsed.sourceRoot);
|
|
295
|
+
const storePath = resolve(baseDir, parsed.storePath === "~" ? homedir() : parsed.storePath.startsWith("~/") ? resolve(homedir(), parsed.storePath.slice(2)) : parsed.storePath);
|
|
296
|
+
if (sourceRoot === storePath)
|
|
297
|
+
throw new Error("evolution store cannot be the source workspace");
|
|
298
|
+
for (const selected of parsed.sourcePaths) {
|
|
299
|
+
const source = resolve(sourceRoot, selected);
|
|
300
|
+
if (storePath === source || storePath.startsWith(`${source}/`) || source.startsWith(`${storePath}/`)) {
|
|
301
|
+
throw new Error("evolution store and selected source paths must not overlap");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return JSON.parse(JSON.stringify({ ...parsed, sourceRoot, storePath }));
|
|
305
|
+
}
|
|
306
|
+
function loadEvolutionConfigFile(path) {
|
|
307
|
+
const absPath = resolve(path);
|
|
308
|
+
if (!existsSync(absPath))
|
|
309
|
+
throw new Error(`config file not found: ${absPath}`);
|
|
310
|
+
const configFile = realpathSync(absPath);
|
|
311
|
+
const raw = JSON.parse(readFileSync(absPath, "utf8"));
|
|
312
|
+
const config = parseEvolutionConfig(raw, resolve(path, ".."));
|
|
313
|
+
for (const source of config.sourcePaths) {
|
|
314
|
+
const sourcePath = resolve(config.sourceRoot, source);
|
|
315
|
+
const selected = existsSync(sourcePath) ? realpathSync(sourcePath) : sourcePath;
|
|
316
|
+
if (configFile === selected || configFile.startsWith(`${selected}${sep}`)) {
|
|
317
|
+
throw new Error("evolution config contains private answers and must be outside selected source paths");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return config;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// packages/core/dist/verification-spec/spec.js
|
|
324
|
+
import { execFile } from "node:child_process";
|
|
325
|
+
import { promises as fs } from "node:fs";
|
|
326
|
+
import { tmpdir } from "node:os";
|
|
327
|
+
import { join, resolve as resolve2, isAbsolute as isAbsolute2, normalize, sep as sep2 } from "node:path";
|
|
328
|
+
import { promisify } from "node:util";
|
|
329
|
+
var execFileAsync = promisify(execFile);
|
|
330
|
+
var MAX_GIT_DIFF_BYTES = 1e6;
|
|
331
|
+
var GIT_CHECK_TIMEOUT_MS = 5e3;
|
|
332
|
+
var FULL_GIT_OBJECT_ID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
|
|
333
|
+
var MAX_PATTERN_LENGTH = 512;
|
|
334
|
+
var MAX_FILE_BYTES = 1e6;
|
|
335
|
+
function resolveRepoPath(repoRoot, file) {
|
|
336
|
+
if (typeof file !== "string" || file.length === 0)
|
|
337
|
+
return null;
|
|
338
|
+
if (isAbsolute2(file))
|
|
339
|
+
return null;
|
|
340
|
+
const root = resolve2(repoRoot);
|
|
341
|
+
const candidate = resolve2(root, file);
|
|
342
|
+
const normalized = normalize(candidate);
|
|
343
|
+
if (normalized !== root && !normalized.startsWith(root + sep2)) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
return normalized;
|
|
347
|
+
}
|
|
348
|
+
async function checkRepoBoundary(repoRoot, absPath) {
|
|
349
|
+
try {
|
|
350
|
+
await fs.lstat(absPath);
|
|
351
|
+
} catch {
|
|
352
|
+
return "missing";
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const [realRoot, realTarget] = await Promise.all([
|
|
356
|
+
fs.realpath(repoRoot),
|
|
357
|
+
fs.realpath(absPath)
|
|
358
|
+
]);
|
|
359
|
+
if (realTarget === realRoot || realTarget.startsWith(realRoot + sep2)) {
|
|
360
|
+
return "inside";
|
|
361
|
+
}
|
|
362
|
+
return "outside";
|
|
363
|
+
} catch {
|
|
364
|
+
return "outside";
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function safeRegex(pattern, flags) {
|
|
368
|
+
if (typeof pattern !== "string")
|
|
369
|
+
return null;
|
|
370
|
+
if (pattern.length > MAX_PATTERN_LENGTH)
|
|
371
|
+
return null;
|
|
372
|
+
try {
|
|
373
|
+
return new RegExp(pattern, flags);
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function readFileSafe(absPath) {
|
|
379
|
+
try {
|
|
380
|
+
const stat = await fs.stat(absPath);
|
|
381
|
+
if (!stat.isFile())
|
|
382
|
+
return null;
|
|
383
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
384
|
+
return null;
|
|
385
|
+
return await fs.readFile(absPath, "utf8");
|
|
386
|
+
} catch {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async function evaluateGitDiffApplies(predicate, repoRoot) {
|
|
391
|
+
if (predicate.diff.trim().length === 0) {
|
|
392
|
+
return { predicate, passed: false, reason: "git diff is empty" };
|
|
393
|
+
}
|
|
394
|
+
if (Buffer.byteLength(predicate.diff, "utf8") > MAX_GIT_DIFF_BYTES) {
|
|
395
|
+
return {
|
|
396
|
+
predicate,
|
|
397
|
+
passed: false,
|
|
398
|
+
reason: `git diff exceeds ${MAX_GIT_DIFF_BYTES} bytes`
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (!FULL_GIT_OBJECT_ID_RE.test(predicate.baseCommit)) {
|
|
402
|
+
return { predicate, passed: false, reason: "invalid git base commit" };
|
|
403
|
+
}
|
|
404
|
+
let head;
|
|
405
|
+
try {
|
|
406
|
+
const result = await execFileAsync("git", ["rev-parse", "--verify", "HEAD"], {
|
|
407
|
+
cwd: repoRoot,
|
|
408
|
+
timeout: GIT_CHECK_TIMEOUT_MS,
|
|
409
|
+
maxBuffer: 4096
|
|
410
|
+
});
|
|
411
|
+
head = result.stdout.trim();
|
|
412
|
+
} catch {
|
|
413
|
+
return {
|
|
414
|
+
predicate,
|
|
415
|
+
passed: false,
|
|
416
|
+
reason: "git repository HEAD unavailable"
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (head.toLowerCase() !== predicate.baseCommit.toLowerCase()) {
|
|
420
|
+
return {
|
|
421
|
+
predicate,
|
|
422
|
+
passed: false,
|
|
423
|
+
reason: "git HEAD does not match diff base commit"
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
let tempDir;
|
|
427
|
+
try {
|
|
428
|
+
tempDir = await fs.mkdtemp(join(tmpdir(), "0-verify-diff-"));
|
|
429
|
+
const patchPath = join(tempDir, "evidence.patch");
|
|
430
|
+
await fs.writeFile(patchPath, predicate.diff, { encoding: "utf8", mode: 384 });
|
|
431
|
+
await execFileAsync("git", ["apply", "--check", "--whitespace=nowarn", "--", patchPath], {
|
|
432
|
+
cwd: repoRoot,
|
|
433
|
+
timeout: GIT_CHECK_TIMEOUT_MS,
|
|
434
|
+
maxBuffer: 65536
|
|
435
|
+
});
|
|
436
|
+
return {
|
|
437
|
+
predicate,
|
|
438
|
+
passed: true,
|
|
439
|
+
reason: "git diff applies cleanly"
|
|
440
|
+
};
|
|
441
|
+
} catch {
|
|
442
|
+
return {
|
|
443
|
+
predicate,
|
|
444
|
+
passed: false,
|
|
445
|
+
reason: "git diff does not apply cleanly"
|
|
446
|
+
};
|
|
447
|
+
} finally {
|
|
448
|
+
if (tempDir) {
|
|
449
|
+
try {
|
|
450
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function evaluateOne(predicate, repoRoot) {
|
|
457
|
+
switch (predicate.kind) {
|
|
458
|
+
case "file-exists": {
|
|
459
|
+
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
460
|
+
if (!abs) {
|
|
461
|
+
return {
|
|
462
|
+
predicate,
|
|
463
|
+
passed: false,
|
|
464
|
+
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
468
|
+
if (outcome === "outside") {
|
|
469
|
+
return {
|
|
470
|
+
predicate,
|
|
471
|
+
passed: false,
|
|
472
|
+
reason: `path resolves outside repo root: ${predicate.file}`
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const ok = outcome === "inside";
|
|
476
|
+
return {
|
|
477
|
+
predicate,
|
|
478
|
+
passed: ok,
|
|
479
|
+
reason: ok ? "file exists" : `file not found: ${predicate.file}`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
case "file-contains": {
|
|
483
|
+
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
484
|
+
if (!abs) {
|
|
485
|
+
return {
|
|
486
|
+
predicate,
|
|
487
|
+
passed: false,
|
|
488
|
+
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
492
|
+
if (outcome === "outside") {
|
|
493
|
+
return {
|
|
494
|
+
predicate,
|
|
495
|
+
passed: false,
|
|
496
|
+
reason: `path resolves outside repo root: ${predicate.file}`
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (outcome === "missing") {
|
|
500
|
+
return {
|
|
501
|
+
predicate,
|
|
502
|
+
passed: false,
|
|
503
|
+
reason: `file not found or unreadable: ${predicate.file}`
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const content = await readFileSafe(abs);
|
|
507
|
+
if (content === null) {
|
|
508
|
+
return {
|
|
509
|
+
predicate,
|
|
510
|
+
passed: false,
|
|
511
|
+
reason: `file not found or unreadable: ${predicate.file}`
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
const re = safeRegex(predicate.pattern, predicate.flags);
|
|
515
|
+
if (!re) {
|
|
516
|
+
return {
|
|
517
|
+
predicate,
|
|
518
|
+
passed: false,
|
|
519
|
+
reason: `invalid regex: /${predicate.pattern}/${predicate.flags ?? ""}`
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const matched = re.test(content);
|
|
523
|
+
return {
|
|
524
|
+
predicate,
|
|
525
|
+
passed: matched,
|
|
526
|
+
reason: matched ? `pattern matched in ${predicate.file}` : `pattern not found in ${predicate.file}`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
case "file-missing-pattern": {
|
|
530
|
+
const abs = resolveRepoPath(repoRoot, predicate.file);
|
|
531
|
+
if (!abs) {
|
|
532
|
+
return {
|
|
533
|
+
predicate,
|
|
534
|
+
passed: false,
|
|
535
|
+
reason: `path escapes repo root or is invalid: ${predicate.file}`
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const outcome = await checkRepoBoundary(repoRoot, abs);
|
|
539
|
+
if (outcome === "outside") {
|
|
540
|
+
return {
|
|
541
|
+
predicate,
|
|
542
|
+
passed: false,
|
|
543
|
+
reason: `path resolves outside repo root: ${predicate.file}`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
if (outcome === "missing") {
|
|
547
|
+
return {
|
|
548
|
+
predicate,
|
|
549
|
+
passed: false,
|
|
550
|
+
reason: `file not found or unreadable: ${predicate.file}`
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const content = await readFileSafe(abs);
|
|
554
|
+
if (content === null) {
|
|
555
|
+
return {
|
|
556
|
+
predicate,
|
|
557
|
+
passed: false,
|
|
558
|
+
reason: `file not found or unreadable: ${predicate.file}`
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const re = safeRegex(predicate.pattern, predicate.flags);
|
|
562
|
+
if (!re) {
|
|
563
|
+
return {
|
|
564
|
+
predicate,
|
|
565
|
+
passed: false,
|
|
566
|
+
reason: `invalid regex: /${predicate.pattern}/${predicate.flags ?? ""}`
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const matched = re.test(content);
|
|
570
|
+
return {
|
|
571
|
+
predicate,
|
|
572
|
+
passed: !matched,
|
|
573
|
+
reason: matched ? `pattern unexpectedly present in ${predicate.file}` : `pattern absent in ${predicate.file}`
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
case "git-diff-applies":
|
|
577
|
+
return evaluateGitDiffApplies(predicate, repoRoot);
|
|
578
|
+
case "ast-shape": {
|
|
579
|
+
return {
|
|
580
|
+
predicate,
|
|
581
|
+
passed: false,
|
|
582
|
+
reason: "ast-shape predicates are not yet implemented in the OSS verifier"
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
default: {
|
|
586
|
+
const _exhaustive = predicate;
|
|
587
|
+
void _exhaustive;
|
|
588
|
+
return {
|
|
589
|
+
predicate,
|
|
590
|
+
passed: false,
|
|
591
|
+
reason: "unknown predicate kind"
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function evaluateVerificationSpec(spec, repoRoot) {
|
|
597
|
+
const results = [];
|
|
598
|
+
for (const predicate of spec.code) {
|
|
599
|
+
results.push(await evaluateOne(predicate, repoRoot));
|
|
600
|
+
}
|
|
601
|
+
const failedPredicates = results.filter((r) => !r.passed);
|
|
602
|
+
const hasVulnerabilityPredicate = spec.code.some((predicate) => predicate.kind !== "git-diff-applies");
|
|
603
|
+
if (!hasVulnerabilityPredicate && !spec.behavior) {
|
|
604
|
+
return {
|
|
605
|
+
passed: false,
|
|
606
|
+
failedPredicates,
|
|
607
|
+
reason: spec.code.length === 0 ? "no predicates" : "no vulnerability predicates"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
const codePassed = failedPredicates.length === 0 && hasVulnerabilityPredicate;
|
|
611
|
+
if (spec.behavior) {
|
|
612
|
+
return {
|
|
613
|
+
passed: codePassed,
|
|
614
|
+
failedPredicates,
|
|
615
|
+
reason: "behavior eval not yet supported"
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
passed: codePassed,
|
|
620
|
+
failedPredicates
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// packages/core/dist/improvement/artifacts.js
|
|
625
|
+
import { randomUUID } from "node:crypto";
|
|
626
|
+
import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, unlinkSync, writeFileSync } from "node:fs";
|
|
627
|
+
import { dirname, isAbsolute as isAbsolute3, join as join2, parse, resolve as resolve3, sep as sep3 } from "node:path";
|
|
628
|
+
var MAX_ARTIFACT_BYTES = 128 * 1024 * 1024;
|
|
629
|
+
function hasCode(error, code) {
|
|
630
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
631
|
+
}
|
|
632
|
+
function resolveEvolutionDirectoryPath(path) {
|
|
633
|
+
const requested = resolve3(path);
|
|
634
|
+
const root = parse(requested).root;
|
|
635
|
+
let cursor = root;
|
|
636
|
+
const components = requested.slice(root.length).split(sep3).filter(Boolean);
|
|
637
|
+
for (let index = 0; index < components.length; index++) {
|
|
638
|
+
cursor = join2(cursor, components[index]);
|
|
639
|
+
let stat;
|
|
640
|
+
try {
|
|
641
|
+
stat = lstatSync(cursor);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
if (!hasCode(error, "ENOENT"))
|
|
644
|
+
throw error;
|
|
645
|
+
return join2(cursor, ...components.slice(index + 1));
|
|
646
|
+
}
|
|
647
|
+
if (stat.isSymbolicLink()) {
|
|
648
|
+
const trustedTarget = process.platform === "darwin" ? { "/tmp": "/private/tmp", "/var": "/private/var", "/etc": "/private/etc" }[cursor] : void 0;
|
|
649
|
+
if (!trustedTarget || stat.uid !== 0 || realpathSync2(cursor) !== trustedTarget || index === components.length - 1) {
|
|
650
|
+
throw new Error(`unsafe evolution directory: ${cursor}`);
|
|
651
|
+
}
|
|
652
|
+
cursor = trustedTarget;
|
|
653
|
+
} else if (!stat.isDirectory()) {
|
|
654
|
+
throw new Error(`unsafe evolution directory: ${cursor}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return cursor;
|
|
658
|
+
}
|
|
659
|
+
function inspectDirectories(path, create) {
|
|
660
|
+
if (!isAbsolute3(path))
|
|
661
|
+
throw new Error("evolution storage paths must be absolute");
|
|
662
|
+
const absolute = resolveEvolutionDirectoryPath(path);
|
|
663
|
+
const root = parse(absolute).root;
|
|
664
|
+
let cursor = root;
|
|
665
|
+
for (const component of absolute.slice(root.length).split(sep3).filter(Boolean)) {
|
|
666
|
+
cursor = join2(cursor, component);
|
|
667
|
+
if (create) {
|
|
668
|
+
try {
|
|
669
|
+
mkdirSync(cursor, { mode: 448 });
|
|
670
|
+
} catch (error) {
|
|
671
|
+
if (!hasCode(error, "EEXIST"))
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const stat = lstatSync(cursor);
|
|
676
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
677
|
+
throw new Error(`unsafe evolution directory: ${cursor}`);
|
|
678
|
+
}
|
|
679
|
+
const leaf = lstatSync(absolute);
|
|
680
|
+
if (typeof process.getuid === "function" && leaf.uid !== process.getuid() || (leaf.mode & 63) !== 0) {
|
|
681
|
+
throw new Error(`evolution storage directory must be owner-only: ${absolute}`);
|
|
682
|
+
}
|
|
683
|
+
return absolute;
|
|
684
|
+
}
|
|
685
|
+
function ensureEvolutionDirectory(path) {
|
|
686
|
+
return inspectDirectories(path, true);
|
|
687
|
+
}
|
|
688
|
+
function readEvolutionArtifact(path) {
|
|
689
|
+
inspectDirectories(dirname(path), false);
|
|
690
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
691
|
+
try {
|
|
692
|
+
const stat = fstatSync(fd);
|
|
693
|
+
if (!stat.isFile() || stat.size > MAX_ARTIFACT_BYTES || (stat.mode & 63) !== 0 || typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
694
|
+
throw new Error(`unsafe evolution artifact: ${path}`);
|
|
695
|
+
}
|
|
696
|
+
const value = JSON.parse(readFileSync2(fd, "utf8"));
|
|
697
|
+
canonicalEvolutionJson(value);
|
|
698
|
+
return value;
|
|
699
|
+
} finally {
|
|
700
|
+
closeSync(fd);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function publishEvolutionArtifact(path, value) {
|
|
704
|
+
const content = canonicalEvolutionJson(value);
|
|
705
|
+
if (Buffer.byteLength(content) > MAX_ARTIFACT_BYTES)
|
|
706
|
+
throw new Error("evolution artifact exceeds retention limit");
|
|
707
|
+
const directory = ensureEvolutionDirectory(dirname(path));
|
|
708
|
+
const temporary = join2(directory, `.artifact-${randomUUID()}.tmp`);
|
|
709
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
710
|
+
try {
|
|
711
|
+
try {
|
|
712
|
+
writeFileSync(fd, content, "utf8");
|
|
713
|
+
fchmodSync(fd, 256);
|
|
714
|
+
fsyncSync(fd);
|
|
715
|
+
} finally {
|
|
716
|
+
closeSync(fd);
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
linkSync(temporary, path);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
if (!hasCode(error, "EEXIST"))
|
|
722
|
+
throw error;
|
|
723
|
+
if (canonicalEvolutionJson(readEvolutionArtifact(path)) !== content) {
|
|
724
|
+
throw new Error(`immutable evolution artifact collision: ${path}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const directoryFd = openSync(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
728
|
+
try {
|
|
729
|
+
fsyncSync(directoryFd);
|
|
730
|
+
} finally {
|
|
731
|
+
closeSync(directoryFd);
|
|
732
|
+
}
|
|
733
|
+
} finally {
|
|
734
|
+
unlinkSync(temporary);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// packages/core/dist/agent/apply-patch.js
|
|
739
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
740
|
+
import { dirname as dirname2 } from "node:path";
|
|
741
|
+
var BEGIN = "*** Begin Patch";
|
|
742
|
+
var END = "*** End Patch";
|
|
743
|
+
function parsePatch(input) {
|
|
744
|
+
const text = input.replace(/\r\n/g, "\n");
|
|
745
|
+
const lines = text.split("\n");
|
|
746
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
747
|
+
lines.pop();
|
|
748
|
+
}
|
|
749
|
+
if (lines.length === 0 || lines[0].trim() !== BEGIN) {
|
|
750
|
+
throw new Error(`apply_patch: envelope must start with "${BEGIN}" (got: ${lines[0] ?? "<empty>"})`);
|
|
751
|
+
}
|
|
752
|
+
if (lines[lines.length - 1].trim() !== END) {
|
|
753
|
+
throw new Error(`apply_patch: envelope must end with "${END}"`);
|
|
754
|
+
}
|
|
755
|
+
const body = lines.slice(1, -1);
|
|
756
|
+
const ops = [];
|
|
757
|
+
let i = 0;
|
|
758
|
+
while (i < body.length) {
|
|
759
|
+
const line = body[i];
|
|
760
|
+
const trimmed = line.trim();
|
|
761
|
+
if (trimmed === "") {
|
|
762
|
+
i += 1;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const addMatch = /^\*\*\* Add File: (.+)$/.exec(trimmed);
|
|
766
|
+
const replaceMatch = /^\*\*\* Replace File: (.+)$/.exec(trimmed);
|
|
767
|
+
const updateMatch = /^\*\*\* Update File: (.+)$/.exec(trimmed);
|
|
768
|
+
const deleteMatch = /^\*\*\* Delete File: (.+)$/.exec(trimmed);
|
|
769
|
+
if (addMatch || replaceMatch) {
|
|
770
|
+
const path = (addMatch?.[1] ?? replaceMatch[1]).trim();
|
|
771
|
+
const overwrite = replaceMatch !== null;
|
|
772
|
+
i += 1;
|
|
773
|
+
const contentLines = [];
|
|
774
|
+
while (i < body.length && !isOpHeader(body[i])) {
|
|
775
|
+
const ln = body[i];
|
|
776
|
+
if (ln.length === 0) {
|
|
777
|
+
contentLines.push("");
|
|
778
|
+
} else if (ln[0] === "+") {
|
|
779
|
+
contentLines.push(ln.slice(1));
|
|
780
|
+
} else {
|
|
781
|
+
throw new Error(`apply_patch: Add/Replace body lines must start with "+" (got: ${JSON.stringify(ln)})`);
|
|
782
|
+
}
|
|
783
|
+
i += 1;
|
|
784
|
+
}
|
|
785
|
+
ops.push({
|
|
786
|
+
kind: "add",
|
|
787
|
+
path,
|
|
788
|
+
contents: contentLines.join("\n"),
|
|
789
|
+
overwrite
|
|
790
|
+
});
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (deleteMatch) {
|
|
794
|
+
ops.push({ kind: "delete", path: deleteMatch[1].trim() });
|
|
795
|
+
i += 1;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (updateMatch) {
|
|
799
|
+
const path = updateMatch[1].trim();
|
|
800
|
+
i += 1;
|
|
801
|
+
const hunks = [];
|
|
802
|
+
while (i < body.length && !isOpHeader(body[i])) {
|
|
803
|
+
const hunkLine = body[i];
|
|
804
|
+
if (!hunkLine.startsWith("@@")) {
|
|
805
|
+
throw new Error(`apply_patch: Update File body must begin with "@@ <anchor>" hunk header (got: ${JSON.stringify(hunkLine)})`);
|
|
806
|
+
}
|
|
807
|
+
const anchor = hunkLine.slice(2).trim();
|
|
808
|
+
i += 1;
|
|
809
|
+
const hunkBody = [];
|
|
810
|
+
while (i < body.length && !isOpHeader(body[i]) && !body[i].startsWith("@@")) {
|
|
811
|
+
const ln = body[i];
|
|
812
|
+
if (ln === "") {
|
|
813
|
+
hunkBody.push({ kind: "context", text: "" });
|
|
814
|
+
} else if (ln[0] === " ") {
|
|
815
|
+
hunkBody.push({ kind: "context", text: ln.slice(1) });
|
|
816
|
+
} else if (ln[0] === "+") {
|
|
817
|
+
hunkBody.push({ kind: "add", text: ln.slice(1) });
|
|
818
|
+
} else if (ln[0] === "-") {
|
|
819
|
+
hunkBody.push({ kind: "del", text: ln.slice(1) });
|
|
820
|
+
} else {
|
|
821
|
+
throw new Error(`apply_patch: Update hunk body lines must start with " ", "+", or "-" (got: ${JSON.stringify(ln)})`);
|
|
822
|
+
}
|
|
823
|
+
i += 1;
|
|
824
|
+
}
|
|
825
|
+
hunks.push({ anchor, body: hunkBody });
|
|
826
|
+
}
|
|
827
|
+
if (hunks.length === 0) {
|
|
828
|
+
throw new Error(`apply_patch: "Update File: ${path}" requires at least one "@@" hunk`);
|
|
829
|
+
}
|
|
830
|
+
ops.push({ kind: "update", path, hunks });
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
throw new Error(`apply_patch: unknown directive (got: ${JSON.stringify(trimmed)})`);
|
|
834
|
+
}
|
|
835
|
+
if (ops.length === 0) {
|
|
836
|
+
throw new Error("apply_patch: envelope contains no operations");
|
|
837
|
+
}
|
|
838
|
+
return ops;
|
|
839
|
+
}
|
|
840
|
+
function isOpHeader(line) {
|
|
841
|
+
return line.startsWith("*** Add File:") || line.startsWith("*** Replace File:") || line.startsWith("*** Update File:") || line.startsWith("*** Delete File:");
|
|
842
|
+
}
|
|
843
|
+
function applyPatchOps(ops, pathResolver) {
|
|
844
|
+
const applied = [];
|
|
845
|
+
for (const op of ops) {
|
|
846
|
+
const abs = pathResolver(op.path);
|
|
847
|
+
if (op.kind === "add") {
|
|
848
|
+
if (!op.overwrite && existsSync2(abs)) {
|
|
849
|
+
throw new Error(`apply_patch: Add File "${op.path}" failed \u2014 file already exists. Use "*** Replace File: ${op.path}" to overwrite.`);
|
|
850
|
+
}
|
|
851
|
+
mkdirSync2(dirname2(abs), { recursive: true });
|
|
852
|
+
writeFileSync2(abs, op.contents, "utf-8");
|
|
853
|
+
applied.push({ kind: "add", path: op.path });
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (op.kind === "delete") {
|
|
857
|
+
if (!existsSync2(abs)) {
|
|
858
|
+
throw new Error(`apply_patch: Delete File "${op.path}" failed \u2014 file does not exist`);
|
|
859
|
+
}
|
|
860
|
+
unlinkSync2(abs);
|
|
861
|
+
applied.push({ kind: "delete", path: op.path });
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (!existsSync2(abs)) {
|
|
865
|
+
throw new Error(`apply_patch: Update File "${op.path}" failed \u2014 file does not exist`);
|
|
866
|
+
}
|
|
867
|
+
const original = readFileSync3(abs, "utf-8");
|
|
868
|
+
const updated = applyUpdateHunks(original, op.path, op.hunks);
|
|
869
|
+
writeFileSync2(abs, updated, "utf-8");
|
|
870
|
+
applied.push({ kind: "update", path: op.path });
|
|
871
|
+
}
|
|
872
|
+
return { applied };
|
|
873
|
+
}
|
|
874
|
+
function applyUpdateHunks(source, logicalPath, hunks) {
|
|
875
|
+
const hadTrailingNewline = source.endsWith("\n");
|
|
876
|
+
const sourceBody = hadTrailingNewline ? source.slice(0, -1) : source;
|
|
877
|
+
let lines = sourceBody.split("\n");
|
|
878
|
+
for (const hunk of hunks) {
|
|
879
|
+
const anchorIdx = locateAnchor(lines, hunk.anchor, logicalPath);
|
|
880
|
+
const { newLines, consumed } = applyOneHunk(lines, anchorIdx, hunk, logicalPath);
|
|
881
|
+
lines = [
|
|
882
|
+
...lines.slice(0, anchorIdx),
|
|
883
|
+
...newLines,
|
|
884
|
+
...lines.slice(anchorIdx + consumed)
|
|
885
|
+
];
|
|
886
|
+
}
|
|
887
|
+
return lines.join("\n") + (hadTrailingNewline ? "\n" : "");
|
|
888
|
+
}
|
|
889
|
+
function locateAnchor(lines, anchor, logicalPath) {
|
|
890
|
+
if (anchor === "") {
|
|
891
|
+
return 0;
|
|
892
|
+
}
|
|
893
|
+
const matches = [];
|
|
894
|
+
for (let i = 0; i < lines.length; i++) {
|
|
895
|
+
if (lines[i].includes(anchor)) {
|
|
896
|
+
matches.push(i);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (matches.length === 0) {
|
|
900
|
+
throw new Error(`apply_patch: anchor "${anchor}" not found in ${logicalPath}; refine the @@ anchor to a line that exists in the file`);
|
|
901
|
+
}
|
|
902
|
+
if (matches.length > 1) {
|
|
903
|
+
throw new Error(`apply_patch: context "${anchor}" matches ${matches.length} locations in ${logicalPath}; refine the @@ anchor to be unique`);
|
|
904
|
+
}
|
|
905
|
+
return matches[0];
|
|
906
|
+
}
|
|
907
|
+
function applyOneHunk(lines, anchorIdx, hunk, logicalPath) {
|
|
908
|
+
const newLines = [];
|
|
909
|
+
let cursor = anchorIdx;
|
|
910
|
+
for (const entry of hunk.body) {
|
|
911
|
+
if (entry.kind === "context") {
|
|
912
|
+
const observed = lines[cursor];
|
|
913
|
+
if (observed === void 0 || observed !== entry.text) {
|
|
914
|
+
throw new Error(`apply_patch: context mismatch in ${logicalPath} at line ${cursor + 1}: expected ${JSON.stringify(entry.text)}, got ${JSON.stringify(observed ?? "<eof>")}`);
|
|
915
|
+
}
|
|
916
|
+
newLines.push(entry.text);
|
|
917
|
+
cursor += 1;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (entry.kind === "del") {
|
|
921
|
+
const observed = lines[cursor];
|
|
922
|
+
if (observed === void 0 || observed !== entry.text) {
|
|
923
|
+
throw new Error(`apply_patch: removed line mismatch in ${logicalPath} at line ${cursor + 1}: expected ${JSON.stringify(entry.text)}, got ${JSON.stringify(observed ?? "<eof>")}`);
|
|
924
|
+
}
|
|
925
|
+
cursor += 1;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
newLines.push(entry.text);
|
|
929
|
+
}
|
|
930
|
+
return { newLines, consumed: cursor - anchorIdx };
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// packages/core/dist/agent/tools/scope-path.js
|
|
934
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync3 } from "node:fs";
|
|
935
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute4, resolve as resolve4, sep as sep4 } from "node:path";
|
|
936
|
+
function pathEntryExists(path) {
|
|
937
|
+
try {
|
|
938
|
+
lstatSync2(path);
|
|
939
|
+
return true;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
if (error.code === "ENOENT")
|
|
942
|
+
return false;
|
|
943
|
+
throw error;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
function canonicalizePath(path) {
|
|
947
|
+
const missingSegments = [];
|
|
948
|
+
let existingPath = path;
|
|
949
|
+
while (!pathEntryExists(existingPath)) {
|
|
950
|
+
const parent = dirname3(existingPath);
|
|
951
|
+
if (parent === existingPath) {
|
|
952
|
+
throw new Error(`Path does not have an existing parent: ${path}`);
|
|
953
|
+
}
|
|
954
|
+
missingSegments.unshift(basename(existingPath));
|
|
955
|
+
existingPath = parent;
|
|
956
|
+
}
|
|
957
|
+
return resolve4(realpathSync3(existingPath), ...missingSegments);
|
|
958
|
+
}
|
|
959
|
+
function isScopedPath(scopePath, inputPath) {
|
|
960
|
+
const root = realpathSync3(scopePath);
|
|
961
|
+
const logicalCandidate = isAbsolute4(inputPath) ? resolve4(inputPath) : resolve4(root, inputPath);
|
|
962
|
+
const candidate = canonicalizePath(logicalCandidate);
|
|
963
|
+
return candidate === root || candidate.startsWith(root + sep4);
|
|
964
|
+
}
|
|
965
|
+
function resolveScopedPath(scopePath, inputPath) {
|
|
966
|
+
if (isScopeEnforcementEnabled() && (!scopePath || !isScopedPath(scopePath, inputPath))) {
|
|
967
|
+
throw new Error(`Path escapes the allowed scope: ${inputPath}`);
|
|
968
|
+
}
|
|
969
|
+
const root = scopePath ? realpathSync3(scopePath) : process.cwd();
|
|
970
|
+
const logicalCandidate = isAbsolute4(inputPath) ? resolve4(inputPath) : resolve4(root, inputPath);
|
|
971
|
+
return canonicalizePath(logicalCandidate);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export {
|
|
975
|
+
DEFAULT_IMPROVEMENT_PROMOTION_POLICY,
|
|
976
|
+
evaluateImprovementPromotion,
|
|
977
|
+
appendImprovementLedgerEntry,
|
|
978
|
+
verifyImprovementLedger,
|
|
979
|
+
canonicalEvolutionJson,
|
|
980
|
+
parseEvolutionConfig,
|
|
981
|
+
loadEvolutionConfigFile,
|
|
982
|
+
resolveEvolutionDirectoryPath,
|
|
983
|
+
ensureEvolutionDirectory,
|
|
984
|
+
readEvolutionArtifact,
|
|
985
|
+
publishEvolutionArtifact,
|
|
986
|
+
evaluateVerificationSpec,
|
|
987
|
+
parsePatch,
|
|
988
|
+
applyPatchOps,
|
|
989
|
+
resolveScopedPath
|
|
990
|
+
};
|