@askexenow/exe-os 0.9.374 → 0.9.376
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/deploy/compose/.env.customer.example +16 -1
- package/deploy/compose/.env.example +16 -1
- package/deploy/compose/docker-compose.yml +80 -18
- package/deploy/compose/gateway-watchdog.sh +17 -0
- package/deploy/compose/generate-env.ts +41 -1
- package/deploy/compose/sso-edge/default.conf.template +59 -63
- package/deploy/compose/sso-edge/error-pages/404.html +129 -0
- package/deploy/compose/sso-edge/error-pages/500.html +129 -0
- package/deploy/compose/sso-edge/error-pages/503.html +118 -0
- package/deploy/compose/sso-edge/sso-redirect.conf +115 -92
- package/dist/.dist-verified.json +3 -3
- package/dist/{active-agent-ONPPEWQV.js → active-agent-BOBBUJ74.js} +6 -6
- package/dist/{active-agent-UJKMG7LJ.js → active-agent-GFGEGK2U.js} +6 -6
- package/dist/{agent-defs-generator-ZWI6DXOE.js → agent-defs-generator-OX6UFPRX.js} +5 -5
- package/dist/{agent-loop-OYHJKNYH.js → agent-loop-7ZRH56EX.js} +1 -1
- package/dist/{agentic-ontology-WHFJ3QFS.js → agentic-ontology-DT3VOXBT.js} +1 -1
- package/dist/{backfill-metadata-3OUSQD5V.js → backfill-metadata-VRP6PTPP.js} +9 -9
- package/dist/{backup-verify-ZKTUYLTA.js → backup-verify-LWHKGOOV.js} +1 -1
- package/dist/behavior-injection-GMZKZ3BT.js +83 -0
- package/dist/behavior-promotion-QPC4AQ24.js +62 -0
- package/dist/{behaviors-ZJ45TFBJ.js → behaviors-GPI4BLTC.js} +16 -14
- package/dist/bin/agentic-ontology-backfill.js +10 -10
- package/dist/bin/agentic-reflection-backfill.js +11 -11
- package/dist/bin/agentic-semantic-label.js +10 -10
- package/dist/bin/backfill-conversations.js +14 -12
- package/dist/bin/backfill-responses.js +14 -12
- package/dist/bin/backfill-vectors.js +16 -14
- package/dist/bin/bulk-sync-postgres.js +21 -19
- package/dist/bin/cc-doctor.js +19 -11
- package/dist/bin/cleanup-stale-review-tasks.js +27 -26
- package/dist/bin/cli.js +83 -109
- package/dist/bin/daemon-restart-handoff.js +2 -1
- package/dist/bin/deferred-daemon-restart.js +118 -110
- package/dist/bin/exe-actions.js +176 -0
- package/dist/bin/exe-agent-config.js +5 -5
- package/dist/bin/exe-agent.js +23 -23
- package/dist/bin/exe-assign.js +16 -14
- package/dist/bin/exe-boot.js +91 -71
- package/dist/bin/exe-call.js +8 -8
- package/dist/bin/exe-cicd.js +1210 -0
- package/dist/bin/exe-cloud.js +70 -37
- package/dist/bin/exe-config-dump.js +5 -5
- package/dist/bin/exe-dispatch.js +28 -27
- package/dist/bin/exe-doctor.js +12 -11
- package/dist/bin/exe-export-behaviors.js +20 -18
- package/dist/bin/exe-forget.js +11 -11
- package/dist/bin/exe-gateway.js +10 -10
- package/dist/bin/exe-healthcheck.js +22 -10
- package/dist/bin/exe-heartbeat.js +33 -32
- package/dist/bin/exe-kill.js +32 -31
- package/dist/bin/exe-launch-agent.js +76 -54
- package/dist/bin/exe-mcp.js +10 -6
- package/dist/bin/exe-memory-reads.js +9 -9
- package/dist/bin/exe-model-supervisor.js +138 -0
- package/dist/bin/exe-new-employee.js +14 -14
- package/dist/bin/exe-os-mcp-proxy.js +10 -6
- package/dist/bin/exe-pending-messages.js +28 -27
- package/dist/bin/exe-pending-notifications.js +27 -26
- package/dist/bin/exe-pending-reviews.js +28 -27
- package/dist/bin/exe-rename.js +9 -9
- package/dist/bin/exe-repo-truth.js +1 -1
- package/dist/bin/exe-review.js +31 -30
- package/dist/bin/exe-search.js +10 -10
- package/dist/bin/exe-session-cleanup.js +32 -31
- package/dist/bin/exe-settings.js +21 -19
- package/dist/bin/exe-start-codex.js +31 -25
- package/dist/bin/exe-start-opencode.js +24 -22
- package/dist/bin/exe-start.sh +11 -2
- package/dist/bin/exe-status.js +29 -28
- package/dist/bin/exe-support.js +3 -3
- package/dist/bin/exe-team.js +7 -7
- package/dist/bin/exe-timers.js +2 -1
- package/dist/bin/exe-watchdog.js +170 -16
- package/dist/bin/generate-hook-manifest.js +8 -41
- package/dist/bin/git-sweep.js +28 -27
- package/dist/bin/graph-backfill.js +11 -11
- package/dist/bin/graph-export.js +10 -10
- package/dist/bin/hooks-cli.js +289 -0
- package/dist/bin/import-history.js +14 -14
- package/dist/bin/install.js +130 -116
- package/dist/bin/intercom-check.js +5 -5
- package/dist/bin/mcp-sessions.js +2 -2
- package/dist/bin/orchestration-metrics.js +7 -7
- package/dist/bin/postgres-agentic-reflection-backfill.js +4 -4
- package/dist/bin/postgres-agentic-semantic-backfill.js +4 -4
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/reconcile-backlog.js +2 -2
- package/dist/bin/registry-proxy.js +2 -2
- package/dist/bin/scan-tasks.js +52 -38
- package/dist/bin/setup.js +1 -1
- package/dist/bin/shard-migrate.js +9 -9
- package/dist/bin/stack-update.js +10 -5
- package/dist/bin/verify-stack.js +3 -1
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{boot-generation-JKVZ4ZAT.js → boot-generation-BN6GKASY.js} +6 -6
- package/dist/{boot-review-sections-2M6BTG73.js → boot-review-sections-OQ5JGSOD.js} +26 -25
- package/dist/{branch-hygiene-U5GFRSTP.js → branch-hygiene-QLD77IMJ.js} +1 -1
- package/dist/build-manifest.json +1186 -1106
- package/dist/{capability-cards-5IMJT6J7.js → capability-cards-EDZ62HKG.js} +5 -5
- package/dist/{capacity-monitor-SAXCQK7O.js → capacity-monitor-6GMTVILS.js} +28 -27
- package/dist/{catchup-brief-U2TXBLHD.js → catchup-brief-SGGBMDUP.js} +32 -31
- package/dist/{cc-binary-detect-6TNIY5JX.js → cc-binary-detect-ZR6WNOUU.js} +1 -3
- package/dist/{chunk-NKETJCB5.js → chunk-2ODXCCMR.js} +18 -3
- package/dist/{hook-integrity-KGAV6ZC2.js → chunk-3L72UBUH.js} +28 -15
- package/dist/chunk-3OX5S7KZ.js +273 -0
- package/dist/{chunk-W75GTHR3.js → chunk-3SRZNJKD.js} +1 -1
- package/dist/{chunk-I4DNJFDR.js → chunk-42IFYZUN.js} +4 -4
- package/dist/{chunk-JHI5WMK5.js → chunk-473JUDCT.js} +5 -5
- package/dist/{chunk-ANLFH23R.js → chunk-4F5CNI65.js} +1 -1
- package/dist/{chunk-AWWKRZMB.js → chunk-4KVSCQND.js} +87 -14
- package/dist/{chunk-ZCERWHY2.js → chunk-4M6RXJEY.js} +33 -23
- package/dist/{chunk-YTSXPJUQ.js → chunk-4YJEEOYT.js} +3 -3
- package/dist/{chunk-6RKKVPYF.js → chunk-4YUGB637.js} +1 -1
- package/dist/{chunk-XXOPG4BW.js → chunk-4ZSOKEDY.js} +3 -3
- package/dist/{chunk-55BEFF4V.js → chunk-5DF4Y6EC.js} +1 -1
- package/dist/{chunk-4QCFFW2H.js → chunk-5QCIWWFA.js} +46 -0
- package/dist/{chunk-3UUX5OY4.js → chunk-5QKLWJ4Z.js} +1 -1
- package/dist/{chunk-YSLGFPWX.js → chunk-5UBWBXLF.js} +3 -3
- package/dist/chunk-5WA4DG2A.js +117 -0
- package/dist/{chunk-4EN3IZ3Q.js → chunk-64ODONK2.js} +67 -1
- package/dist/{chunk-TAMDAOCU.js → chunk-66CGXQUA.js} +239 -39
- package/dist/{chunk-QHJR7IMX.js → chunk-6F3FQCAQ.js} +3 -3
- package/dist/{chunk-WMEYJT4R.js → chunk-6FG34V6D.js} +15 -14
- package/dist/{chunk-NMH5ZQPU.js → chunk-6KQ6WMTI.js} +1 -1
- package/dist/{chunk-IRVOXUSK.js → chunk-6WKBTZVL.js} +10 -4
- package/dist/{chunk-NOKJCF27.js → chunk-7INEFMVU.js} +9 -3
- package/dist/{chunk-GSJLB6JI.js → chunk-7THUQU44.js} +34 -8
- package/dist/{chunk-HDLAB7W3.js → chunk-7UH2JWBF.js} +1 -1
- package/dist/{chunk-KTCXJWJG.js → chunk-7UNMSF5O.js} +1 -1
- package/dist/{chunk-JTVIANUB.js → chunk-AEUGIJDO.js} +3 -1
- package/dist/{chunk-DSJI76XL.js → chunk-AHNVFBVB.js} +113 -16
- package/dist/{chunk-IBS2FAI4.js → chunk-AUKRBHHZ.js} +4 -1
- package/dist/{chunk-AKLJ4JH3.js → chunk-BBK2TSK3.js} +1 -1
- package/dist/{chunk-ZL4AGWAH.js → chunk-BNWWVYRN.js} +6 -5
- package/dist/{chunk-PY3FOMRN.js → chunk-BQEZPRUC.js} +4 -4
- package/dist/{chunk-DIXXEE3S.js → chunk-BQXYXJYC.js} +721 -309
- package/dist/{chunk-ORM7ZFKC.js → chunk-BZ546I4Q.js} +32 -0
- package/dist/{chunk-3KDCORDO.js → chunk-C2UN464N.js} +1 -1
- package/dist/{chunk-75H4R62K.js → chunk-CBPNZFSU.js} +0 -2
- package/dist/{chunk-YLXTKI6M.js → chunk-CHO5SNWR.js} +1 -1
- package/dist/{chunk-MXPNVSXI.js → chunk-CIEVY5TD.js} +11 -11
- package/dist/{chunk-UBK7HK3J.js → chunk-CKLJT2DH.js} +2 -2
- package/dist/{chunk-MOGA4Q7F.js → chunk-CKOJP7JI.js} +1 -1
- package/dist/{chunk-V3W3SATA.js → chunk-CL4J4757.js} +1 -1
- package/dist/{chunk-XNNECNDL.js → chunk-CQFWTWC6.js} +13 -13
- package/dist/{chunk-PDVXWXTL.js → chunk-CROVXJ4S.js} +3 -3
- package/dist/{chunk-WJG344NC.js → chunk-CWSLTSL6.js} +8 -8
- package/dist/{chunk-VMTZ4RUW.js → chunk-DDMGQ5Q3.js} +60 -26
- package/dist/{chunk-YKC7QDZH.js → chunk-DN24VTS5.js} +31 -6
- package/dist/{chunk-IN6CAM4F.js → chunk-EHAN752X.js} +7 -7
- package/dist/{chunk-EXTZSNWL.js → chunk-EPHSISMT.js} +2 -2
- package/dist/{chunk-6EZQUNE6.js → chunk-FCMQVEVL.js} +36 -3
- package/dist/{chunk-3L2QU5ZF.js → chunk-FS7XF7SE.js} +1 -1
- package/dist/{chunk-LGS2V2XD.js → chunk-FUKVP54K.js} +66 -19
- package/dist/{chunk-44TLJZJ5.js → chunk-FX2QRSRT.js} +1 -1
- package/dist/{chunk-PWRC5XZO.js → chunk-FXZ6VGFW.js} +16 -8
- package/dist/{chunk-NT7W4RXD.js → chunk-GKDAQL45.js} +1 -1
- package/dist/{chunk-EKE37MO4.js → chunk-GOT5ZHPX.js} +3 -3
- package/dist/{chunk-SLLLOOEI.js → chunk-GPJIBLP4.js} +1 -0
- package/dist/{chunk-EKYYMIVT.js → chunk-GPUOUUHA.js} +10 -1
- package/dist/{chunk-PB5S7YOF.js → chunk-GXNBPTPJ.js} +1 -1
- package/dist/{chunk-4SJ4LK4V.js → chunk-HE34XVD5.js} +5 -5
- package/dist/{chunk-6VHOQ3IM.js → chunk-HHAKXIKX.js} +12 -9
- package/dist/{chunk-TFKCERAK.js → chunk-HHT527PV.js} +38 -41
- package/dist/{chunk-ZSODLMFV.js → chunk-HIVOLPLD.js} +1 -40
- package/dist/{chunk-UCFC2AEI.js → chunk-I3LDTSWU.js} +1 -1
- package/dist/{chunk-Q3AXXKDF.js → chunk-IKXH4BFC.js} +5 -5
- package/dist/chunk-IMYGYIAI.js +23 -0
- package/dist/{chunk-GS7LIT34.js → chunk-IPHLUZ37.js} +1 -1
- package/dist/{chunk-M4IRPXSZ.js → chunk-IZA7XW2Q.js} +3 -3
- package/dist/{chunk-SEQIPGLU.js → chunk-IZLML47J.js} +1 -1
- package/dist/{chunk-4HKTO2SV.js → chunk-JRMHGO5B.js} +1 -1
- package/dist/{chunk-U73AC5AN.js → chunk-JSTTPYL3.js} +16 -16
- package/dist/{chunk-6SCGLHJ7.js → chunk-JVWLHBAV.js} +4 -4
- package/dist/{chunk-OVEQE6YS.js → chunk-K7UNIEB4.js} +254 -48
- package/dist/{chunk-HNWS7YHN.js → chunk-KBR5LIWD.js} +1 -1
- package/dist/{chunk-AKLQSC6W.js → chunk-KI7O64XV.js} +328 -98
- package/dist/{chunk-XXFKSJYA.js → chunk-KK3MIY5C.js} +5 -5
- package/dist/{chunk-U6SUKSZR.js → chunk-KO3OHI6V.js} +3 -3
- package/dist/{chunk-NJFJWQNN.js → chunk-L5CGXEZC.js} +27 -22
- package/dist/{chunk-NBXDXKLC.js → chunk-LK3XQDBP.js} +1 -1
- package/dist/{chunk-T3QUKNLI.js → chunk-LM443ORV.js} +1 -1
- package/dist/{chunk-BBSIEYF5.js → chunk-LNAFLXL7.js} +12 -4
- package/dist/{chunk-32YV32VA.js → chunk-LTUA5EYI.js} +122 -11
- package/dist/{chunk-23MVDXLY.js → chunk-MAXD5JQL.js} +11 -10
- package/dist/{chunk-GNUAL43X.js → chunk-MFASHRJ6.js} +5 -5
- package/dist/chunk-MFYMDGE7.js +37 -0
- package/dist/{chunk-VKJGHAZT.js → chunk-MGVILAYU.js} +57 -13
- package/dist/{chunk-DB55JZHU.js → chunk-MSLWRWTI.js} +1 -1
- package/dist/{chunk-C2L622QJ.js → chunk-NB5IRTC4.js} +52 -44
- package/dist/{chunk-2ZOEC4KC.js → chunk-ND75TGPR.js} +2 -2
- package/dist/{chunk-VRTCVZND.js → chunk-NE34MWVL.js} +142 -16
- package/dist/{chunk-S2WBS6BG.js → chunk-NMOFLWDY.js} +1 -1
- package/dist/{chunk-CRCB5S7I.js → chunk-NPXKGBVZ.js} +140 -23
- package/dist/{chunk-JSMDRKPJ.js → chunk-NRBGUIPE.js} +1 -1
- package/dist/{chunk-4LWBC7CM.js → chunk-NY6SBR4O.js} +1 -1
- package/dist/{chunk-UCUOGVDX.js → chunk-O3K2C22L.js} +13 -1
- package/dist/{chunk-R522MWAR.js → chunk-OATET4BV.js} +2 -2
- package/dist/chunk-OOZU5H3T.js +86 -0
- package/dist/{chunk-XFTEX77S.js → chunk-OQ4FNDRG.js} +28 -6
- package/dist/{chunk-VYQ7B7DI.js → chunk-OWVX52XF.js} +4 -4
- package/dist/{chunk-AETLDRZB.js → chunk-PEAAX6UA.js} +1 -1
- package/dist/{chunk-UM3N6VND.js → chunk-PFAVHC5N.js} +5 -5
- package/dist/chunk-PMVVUIHH.js +85 -0
- package/dist/chunk-PPMGQFLJ.js +2317 -0
- package/dist/{chunk-7TSBV3SD.js → chunk-PTY2N23C.js} +310 -2
- package/dist/{chunk-UKLN57Q5.js → chunk-PZ5SKYK6.js} +4 -4
- package/dist/{chunk-636LKCKE.js → chunk-Q6ZLQMP2.js} +1 -1
- package/dist/{chunk-N7VEP5B3.js → chunk-QFR5EKZG.js} +29 -7
- package/dist/{chunk-YMP2QSOP.js → chunk-QN56VQ6Z.js} +1 -1
- package/dist/{chunk-6YVUR3OU.js → chunk-QR3BUMOS.js} +2 -2
- package/dist/{chunk-XZ65ST2I.js → chunk-QV5CQLQH.js} +2 -2
- package/dist/{chunk-DJOIMFKV.js → chunk-REZIGNA6.js} +2 -2
- package/dist/{chunk-UQGSBM3U.js → chunk-RFPMXR6B.js} +11 -0
- package/dist/{chunk-DN4YVV4Z.js → chunk-RSABKW6B.js} +1 -1
- package/dist/{chunk-LP4GB656.js → chunk-RZNP35SI.js} +2 -2
- package/dist/{chunk-NUTZJZKW.js → chunk-SKTAZGFI.js} +10 -42
- package/dist/{chunk-TQAZTRQ3.js → chunk-SVXUT2DN.js} +4 -4
- package/dist/{chunk-DJ7YXOTU.js → chunk-T7EF5CQO.js} +3 -3
- package/dist/{chunk-QAEWWYHL.js → chunk-TDF2XKLK.js} +5 -1
- package/dist/{chunk-ZGYNKA3O.js → chunk-TT5BBVLX.js} +29 -31
- package/dist/{chunk-5H47VHB7.js → chunk-UWX6MMCX.js} +1 -1
- package/dist/{chunk-XL5MFGMR.js → chunk-V4YNN7PA.js} +10 -3
- package/dist/{chunk-SL6KQYDU.js → chunk-V5DDXXMP.js} +7 -7
- package/dist/{chunk-DZKPOSFD.js → chunk-VH6SPPNA.js} +1 -1
- package/dist/{chunk-LDNYSMPZ.js → chunk-VJUUI2VT.js} +1 -1
- package/dist/{chunk-CBCGDEJA.js → chunk-VN5WOOAQ.js} +42 -6
- package/dist/{chunk-EXDW3E2R.js → chunk-VPHJ2LUI.js} +165 -15
- package/dist/{chunk-GOMQ7PVQ.js → chunk-WJC6AAJN.js} +1 -1
- package/dist/{chunk-GJI77VDO.js → chunk-WVBVUVZB.js} +6 -5
- package/dist/{chunk-A6PDF4BO.js → chunk-XTQ7R2PA.js} +2 -2
- package/dist/{chunk-L6RXG36J.js → chunk-Y2HD2YB7.js} +1 -1
- package/dist/{chunk-KTU3EAWK.js → chunk-YAACLBZ7.js} +5 -5
- package/dist/{chunk-7GE2EQ23.js → chunk-YGVAKKGJ.js} +11 -6
- package/dist/chunk-YLHACCCB.js +166 -0
- package/dist/{chunk-PLZSZOZN.js → chunk-YP6UGVEY.js} +3 -3
- package/dist/{chunk-ULIBLJ6W.js → chunk-YYC553FO.js} +5 -5
- package/dist/{chunk-T35RCQFE.js → chunk-YZPEQW7H.js} +9 -8
- package/dist/{chunk-KI5PFRG6.js → chunk-ZKGWHT56.js} +60 -2
- package/dist/{chunk-NUK7EEN5.js → chunk-ZKJ5S23O.js} +33 -13
- package/dist/chunk-ZLPS3GR7.js +146 -0
- package/dist/{chunk-XH7YL7JM.js → chunk-ZMX22HSS.js} +50 -6
- package/dist/{chunk-JY5GAZYC.js → chunk-ZQEJ4IX2.js} +4 -0
- package/dist/{chunk-R4CBHKZ2.js → chunk-ZS5JO6QV.js} +7 -6
- package/dist/{cleanup-graph-garbage-DPJGO52H.js → cleanup-graph-garbage-LAPIZGFX.js} +12 -12
- package/dist/{cloud-sync-command-JPNTT4OQ.js → cloud-sync-command-QAZ2NWRA.js} +11 -2
- package/dist/{cloud-sync-db-init-V2LWIUQU.js → cloud-sync-db-init-HAGVF32Y.js} +1 -1
- package/dist/{cloud-sync-shutdown-BNALPWGX.js → cloud-sync-shutdown-DAEPCFVK.js} +5 -5
- package/dist/{co-activation-CMNSWS6B.js → co-activation-523TKQPZ.js} +5 -5
- package/dist/{co-occurrence-WRI2NAN6.js → co-occurrence-PQUCTENN.js} +36 -17
- package/dist/{code-context-index-NOJSHLPN.js → code-context-index-LIDOFKOR.js} +6 -4
- package/dist/{code-context-watcher-VZNZU3BO.js → code-context-watcher-4F7K2N3U.js} +6 -4
- package/dist/{content-extractor-NXYNEF5K.js → content-extractor-DJM5EUDA.js} +2 -2
- package/dist/{conversation-entity-extractor-74252CAO.js → conversation-entity-extractor-THGKQ5RD.js} +5 -5
- package/dist/{crdt-sync-B3JN6AIV.js → crdt-sync-32B55NOJ.js} +1 -1
- package/dist/{crm-webhook-GYWQRFET.js → crm-webhook-FHRISFYQ.js} +2 -2
- package/dist/{cto-delegation-gate-LQHVLEMW.js → cto-delegation-gate-O6XPGECP.js} +27 -26
- package/dist/{daemon-auth-QZHEO7IQ.js → daemon-auth-PNKHERLV.js} +6 -1
- package/dist/daemon-drift-N2GKUBAO.js +15 -0
- package/dist/{daemon-orchestration-5HIQALZH.js → daemon-orchestration-BWLSJG2X.js} +33 -30
- package/dist/{daemon-ready-P2BR2XP5.js → daemon-ready-BN6EZLTQ.js} +1 -1
- package/dist/{daemon-ready-signal-CKXPUMJ2.js → daemon-ready-signal-PNDMVKPJ.js} +3 -1
- package/dist/{daemon-restart-circuit-breaker-SMTWFX27.js → daemon-restart-circuit-breaker-CD4VMCU7.js} +1 -1
- package/dist/daemon-restart-report-FIO5HZTJ.js +15 -0
- package/dist/{daemon-timer-report-TNJH5SFY.js → daemon-timer-report-6UK5PSDP.js} +2 -1
- package/dist/{db-integrity-events-2DTFO37N.js → db-integrity-events-GYGANKZV.js} +1 -1
- package/dist/{destructive-data-guard-BVFWPPIS.js → destructive-data-guard-HHWUBIGS.js} +1 -1
- package/dist/{doc-graph-extractor-PQHDHQPQ.js → doc-graph-extractor-HOKOV7BE.js} +10 -10
- package/dist/{documents-C4SSPJMC.js → documents-HWEJMONR.js} +16 -14
- package/dist/{dreaming-XAO3FCBV.js → dreaming-FMNDODG2.js} +31 -30
- package/dist/{employee-boot-brief-W4OHUMRQ.js → employee-boot-brief-DBFROOQJ.js} +35 -34
- package/dist/{entity-boost-3UWGXG5O.js → entity-boost-5V4W6NFA.js} +1 -1
- package/dist/{exe-drift-AG2KMKD5.js → exe-drift-V55UAYRT.js} +6 -6
- package/dist/{exe-export-IC5BT335.js → exe-export-LHN42ZUS.js} +18 -16
- package/dist/{exe-import-A35ZLDPG.js → exe-import-M536BEZC.js} +18 -16
- package/dist/{exe-key-TCMJNYLA.js → exe-key-37XIR22X.js} +111 -107
- package/dist/{exe-org-JXCR3JYZ.js → exe-org-7TDIRNTE.js} +14 -14
- package/dist/{exe-snapshot-SV6SRIG6.js → exe-snapshot-EOUBCIQK.js} +34 -33
- package/dist/{fast-db-init-L2RGKJWH.js → fast-db-init-GWUZ746B.js} +1 -1
- package/dist/{founder-context-D4HFIESJ.js → founder-context-6LQKET4A.js} +2 -2
- package/dist/{fts-integrity-4YB34KWX.js → fts-integrity-PLAL6NMG.js} +1 -1
- package/dist/gateway/index.js +17 -17
- package/dist/{git-main-guard-VTW4N3CM.js → git-main-guard-BGICS25C.js} +1 -1
- package/dist/{git-staleness-TN7CYGFG.js → git-staleness-QNNXJK3W.js} +5 -5
- package/dist/{global-procedures-YFDEIOG7.js → global-procedures-NMQGF54V.js} +7 -7
- package/dist/{gotrue-session-ODZ2ERJX.js → gotrue-session-U4H3UGHL.js} +1 -3
- package/dist/{graph-auto-extract-3YR3TYRW.js → graph-auto-extract-7POGAKHM.js} +10 -10
- package/dist/{graph-query-AP5R6ZHO.js → graph-query-ZEDO4BDF.js} +1 -1
- package/dist/{graph-rag-Z2KCJTPK.js → graph-rag-JA7XN7NO.js} +5 -5
- package/dist/hook-integrity-6NLSNTXD.js +15 -0
- package/dist/{hook-prefetch-K5DN3TOQ.js → hook-prefetch-4KH3D4YU.js} +28 -26
- package/dist/hook-tamper-log-Z4PWC7N4.js +90 -0
- package/dist/hooks/bug-report-worker.js +31 -30
- package/dist/hooks/codex-stop-task-finalizer.js +33 -32
- package/dist/hooks/commit-complete.js +30 -29
- package/dist/hooks/error-recall.js +12 -12
- package/dist/hooks/exe-heartbeat-hook.js +7 -7
- package/dist/hooks/ingest-worker.js +17 -15
- package/dist/hooks/ingest.js +20 -20
- package/dist/hooks/instructions-loaded.js +9 -9
- package/dist/hooks/manifest.json +20 -20
- package/dist/hooks/notification.js +8 -8
- package/dist/hooks/post-compact.js +7 -7
- package/dist/hooks/post-tool-combined.js +14 -14
- package/dist/hooks/pre-compact.js +30 -29
- package/dist/hooks/pre-tool-use.js +303 -489
- package/dist/hooks/prompt-submit.js +61 -54
- package/dist/hooks/session-end.js +35 -34
- package/dist/hooks/session-start.js +18 -18
- package/dist/hooks/stop.js +36 -35
- package/dist/hooks/subagent-stop.js +32 -31
- package/dist/hooks/summary-worker.js +36 -35
- package/dist/index.js +39 -38
- package/dist/{installer-LK35OX6R.js → installer-M34Q7LAS.js} +25 -10
- package/dist/{installer-54HTN5RE.js → installer-POV6TDQ5.js} +12 -8
- package/dist/{installer-LLWKZI2S.js → installer-S624ZOMB.js} +12 -8
- package/dist/{intercom-queue-5NOT2RKI.js → intercom-queue-MKDWAEUV.js} +26 -25
- package/dist/lib/cloud-sync.js +27 -19
- package/dist/lib/consolidation.js +12 -12
- package/dist/lib/database.js +5 -5
- package/dist/lib/db-backup.js +7 -1
- package/dist/lib/db-daemon-client.js +8 -6
- package/dist/lib/db-write-worker.js +4 -4
- package/dist/lib/db.js +5 -5
- package/dist/lib/device-registry.js +1 -1
- package/dist/lib/embedder.js +6 -4
- package/dist/lib/employee-templates.js +8 -8
- package/dist/lib/employees.js +5 -5
- package/dist/lib/exe-daemon-client.js +5 -3
- package/dist/lib/exe-daemon.js +333 -262
- package/dist/lib/hybrid-search.js +10 -10
- package/dist/lib/identity-templates.js +1 -1
- package/dist/lib/identity.js +17 -7
- package/dist/lib/license.js +2 -2
- package/dist/lib/messaging.js +27 -26
- package/dist/lib/post-tool-memory.js +9 -9
- package/dist/lib/registry-proxy.js +3 -1
- package/dist/lib/reminders.js +6 -6
- package/dist/lib/schedules.js +11 -11
- package/dist/lib/session-registry.js +11 -7
- package/dist/lib/session-wrappers.js +1 -1
- package/dist/lib/skill-learning.js +17 -15
- package/dist/lib/status-brief.js +2 -1
- package/dist/lib/store.js +9 -9
- package/dist/lib/task-router.js +6 -6
- package/dist/lib/tasks.js +28 -27
- package/dist/lib/tmux-routing.js +26 -25
- package/dist/lib/tmux-status.js +5 -1
- package/dist/lib/token-spend.js +6 -6
- package/dist/{license-gate-JVMB6OFD.js → license-gate-2QNMBAZU.js} +3 -3
- package/dist/{license-sync-5VYS2F2W.js → license-sync-FEDKH5MD.js} +2 -2
- package/dist/{mailbox-inbox-4MEUOL3J.js → mailbox-inbox-XE72D3AX.js} +2 -2
- package/dist/{mailbox-outbox-J6WIF3UY.js → mailbox-outbox-DNDDP2HW.js} +1 -1
- package/dist/mcp/register-tools.js +110 -106
- package/dist/mcp/server.js +113 -109
- package/dist/mcp/tools/complete-reminder.js +7 -7
- package/dist/mcp/tools/create-reminder.js +7 -7
- package/dist/mcp/tools/create-task.js +30 -29
- package/dist/mcp/tools/deactivate-behavior.js +18 -16
- package/dist/mcp/tools/list-reminders.js +7 -7
- package/dist/mcp/tools/list-tasks.js +30 -29
- package/dist/mcp/tools/send-message.js +33 -30
- package/dist/mcp/tools/update-task.js +29 -28
- package/dist/{mcp-child-registry-OTG4MWO7.js → mcp-child-registry-DVHYAHPW.js} +2 -2
- package/dist/{mcp-health-HNVSWKFA.js → mcp-health-B5EKRCZG.js} +2 -2
- package/dist/{mcp-http-config-DQWL7U2Z.js → mcp-http-config-ZJ7XHWT7.js} +10 -6
- package/dist/{mcp-session-identity-277GASTQ.js → mcp-session-identity-PEVM7VIP.js} +26 -25
- package/dist/{memory-cards-3S2ZQPR7.js → memory-cards-VN5DEWPD.js} +9 -9
- package/dist/{memory-graph-extractor-A6DFEFIN.js → memory-graph-extractor-TMEB6WLA.js} +11 -11
- package/dist/{memory-poisoning-defense-ZVP4H6DY.js → memory-poisoning-defense-DYHARQ2G.js} +5 -5
- package/dist/{memory-pressure-watchdog-6W5F6R7H.js → memory-pressure-watchdog-J6Q3SIBL.js} +27 -26
- package/dist/{memory-queue-client-ADSDP6OX.js → memory-queue-client-QC2OTMJB.js} +6 -4
- package/dist/{memory-reflection-BKB2YOE6.js → memory-reflection-ZYJUOSFX.js} +9 -9
- package/dist/{message-queue-client-XQN7QABL.js → message-queue-client-OEQCGSWC.js} +1 -25
- package/dist/{notification-ledger-CLGM7ODF.js → notification-ledger-TG6EFYBP.js} +29 -28
- package/dist/{notifications-MACUZPYP.js → notifications-LGYY22W5.js} +26 -25
- package/dist/{orchestration-events-N7LYMUGA.js → orchestration-events-JPHB6NPY.js} +6 -6
- package/dist/{orchestrator-4X24EWHV.js → orchestrator-IC4WGP6L.js} +28 -27
- package/dist/{org-brief-session-FZOFMO5U.js → org-brief-session-DEPHWCQR.js} +9 -9
- package/dist/{org-consent-policy-O5E6HXBL.js → org-consent-policy-T27AO5OJ.js} +2 -2
- package/dist/{org-join-POWUG25F.js → org-join-A3YXC5RB.js} +2 -2
- package/dist/{org-key-admin-E7EMDOZZ.js → org-key-admin-SQFHCVP2.js} +3 -3
- package/dist/{org-partner-runtime-FODONVTM.js → org-partner-runtime-L7L2WWGN.js} +10 -10
- package/dist/{org-row-visibility-VX4AVV5Q.js → org-row-visibility-M54Z6ZH3.js} +1 -1
- package/dist/{pipeline-router-6GGT2ZW3.js → pipeline-router-NKYAUR3Y.js} +6 -6
- package/dist/{plan-limits-FUN76LYY.js → plan-limits-ISRRGF23.js} +8 -8
- package/dist/{platform-procedures-7YSCLF4Z.js → platform-procedures-Z7ZGBJWN.js} +1 -1
- package/dist/{project-boot-3Q65LCIP.js → project-boot-BLYGVUJH.js} +4 -1
- package/dist/{project-name-LJVFMYTQ.js → project-name-WKSNNKXK.js} +6 -6
- package/dist/{projection-worker-4OANGP5K.js → projection-worker-RNYPE46G.js} +2 -2
- package/dist/{prospective-memory-KR3P4T6N.js → prospective-memory-GEO6E3UL.js} +5 -5
- package/dist/{push-notifications-NCZLH6DI.js → push-notifications-EY6BXZUY.js} +27 -26
- package/dist/{reproject-worktree-memories-PWYKPB2U.js → reproject-worktree-memories-DAA5GWMH.js} +28 -27
- package/dist/{reranker-6V3C2AO4.js → reranker-7OADQPMG.js} +1 -1
- package/dist/{retrieval-health-5D7O6VH6.js → retrieval-health-FCEDWN2P.js} +2 -2
- package/dist/{review-polling-QW3LJ4KN.js → review-polling-G52BR4MC.js} +27 -26
- package/dist/runtime/index.js +39 -38
- package/dist/{session-events-T45SSTMF.js → session-events-P6E2373E.js} +27 -26
- package/dist/{session-kill-telemetry-QSPVJGBF.js → session-kill-telemetry-OHNJMIVT.js} +27 -26
- package/dist/{session-mcp-config-SC5BLWZG.js → session-mcp-config-WFZAH6R5.js} +2 -1
- package/dist/{session-scope-KRWE54C4.js → session-scope-DKOHPQPJ.js} +26 -25
- package/dist/{setup-wizard-GP3TBTPF.js → setup-wizard-RNU3RFBE.js} +1 -1
- package/dist/{skill-refinement-C7JTMLJG.js → skill-refinement-AKJTHFXC.js} +6 -6
- package/dist/{stack-generate-manifest-MFMDWRUP.js → stack-generate-manifest-7WJZWSPK.js} +163 -15
- package/dist/{stack-promote-EKSCYY63.js → stack-promote-OP2URGFF.js} +4 -4
- package/dist/{stack-release-2GZAAFO4.js → stack-release-MY6U2QIO.js} +31 -8
- package/dist/{stack-update-J6JXSTAZ.js → stack-update-QSHSLUTZ.js} +3 -3
- package/dist/{steward-gate-BFCCMCSW.js → steward-gate-KMLS5ECC.js} +6 -6
- package/dist/{subagent-brief-injection-ED36PLYY.js → subagent-brief-injection-HCKZQZBF.js} +37 -36
- package/dist/{support-inbox-YW47SYJF.js → support-inbox-UBXU5RKV.js} +11 -11
- package/dist/{support-outbox-HFJWG52M.js → support-outbox-G2NG2U5K.js} +2 -2
- package/dist/{sweep-budget-KMPJ7O5Z.js → sweep-budget-YGLCNXZZ.js} +9 -3
- package/dist/{task-enforcement-CECTQX4U.js → task-enforcement-2GR7HU6V.js} +27 -26
- package/dist/{task-request-inbox-Q5T4N5K7.js → task-request-inbox-ANCVK2VG.js} +2 -2
- package/dist/{task-scope-ERVEUEYU.js → task-scope-QANJJXXB.js} +30 -29
- package/dist/{tasks-crud-ZRQNJXQ2.js → tasks-crud-45WOIMIM.js} +44 -25
- package/dist/{tasks-notify-MIZH4C2O.js → tasks-notify-ZJS4MA3S.js} +29 -26
- package/dist/{tasks-review-Z7J2P6DG.js → tasks-review-ZVPKY47Z.js} +26 -25
- package/dist/{telemetry-upload-LVQUQYFP.js → telemetry-upload-VY3UWAEL.js} +10 -10
- package/dist/{token-budget-BU4NOVZF.js → token-budget-XHX6XFEC.js} +6 -6
- package/dist/{tool-capability-index-7F42MOJT.js → tool-capability-index-6HLZU437.js} +1 -1
- package/dist/{tool-telemetry-O27EVMBR.js → tool-telemetry-GBDI4AXR.js} +1 -1
- package/dist/tui/App.js +36 -35
- package/dist/{tui-data-TPWWBIUQ.js → tui-data-IQFE3UW3.js} +30 -29
- package/dist/{typed-messages-RCMPEKJN.js → typed-messages-P2HLLA62.js} +1 -1
- package/dist/{update-support-F2F5537B.js → update-support-BDN5AK4R.js} +25 -22
- package/dist/{work-claims-B73X5NE7.js → work-claims-PDQNLE5K.js} +7 -7
- package/dist/{work-state-heartbeat-P4YX6E2R.js → work-state-heartbeat-CH6BLQKV.js} +1 -1
- package/dist/{worker-gate-BEEIIVWA.js → worker-gate-NUUSQMBT.js} +1 -3
- package/dist/{workflow-engine-5X46PFCW.js → workflow-engine-RZ7QEI3Z.js} +2 -2
- package/dist/{worktree-W47CIMJ6.js → worktree-Q25PZST6.js} +11 -7
- package/dist/worktree-ownership-guard-4LKMW6PW.js +421 -0
- package/dist/{worktree-sweep-BNBD4RFX.js → worktree-sweep-EV5YQ2OB.js} +8 -8
- package/package.json +15 -3
- package/release-notes.json +5 -5
- package/src/commands/exe.md +16 -16
- package/stack.release.json +9 -8
- package/dist/behavior-injection-M6UYZCMW.js +0 -63
- package/dist/chunk-BJ7AVSY6.js +0 -50
- package/dist/daemon-restart-report-EIVD6WDJ.js +0 -10
- package/dist/worktree-ownership-guard-OUWJROW2.js +0 -88
- /package/dist/{chunk-GQOEWNVM.js → chunk-4EG4DT4F.js} +0 -0
- /package/dist/{chunk-AISULNGL.js → chunk-BRAIWGMF.js} +0 -0
- /package/dist/{chunk-NRG7KZIP.js → chunk-HQEAZDAC.js} +0 -0
- /package/dist/{chunk-VZAFWPAY.js → chunk-IK4344V5.js} +0 -0
- /package/dist/{chunk-GM7TLYOK.js → chunk-K3VDF6II.js} +0 -0
- /package/dist/{chunk-2AJGV5UL.js → chunk-KM7DAG5J.js} +0 -0
- /package/dist/{chunk-YO4BO2CL.js → chunk-QIEYJ4KZ.js} +0 -0
- /package/dist/{chunk-WSQASFLP.js → chunk-VCTK5HRA.js} +0 -0
- /package/dist/{chunk-KQ6RJWRW.js → chunk-VPT6VMBG.js} +0 -0
- /package/dist/{chunk-6WIFPD2P.js → chunk-YZ4AEMR7.js} +0 -0
- /package/dist/{core-memory-7I7RE2SJ.js → core-memory-UIZKCY4L.js} +0 -0
- /package/dist/{memory-isolation-2TUZ7Z4K.js → memory-isolation-3GJ6WLYX.js} +0 -0
- /package/dist/{oauth-server-AYM37SQ4.js → oauth-server-ZRHXIRXQ.js} +0 -0
- /package/dist/{wiki-acl-IBQLLBTS.js → wiki-acl-MFOYK5F4.js} +0 -0
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "../chunk-MLKGABMK.js";
|
|
3
|
+
|
|
4
|
+
// src/bin/exe-cicd.ts
|
|
5
|
+
import { execFile } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
import {
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
mkdirSync,
|
|
11
|
+
appendFileSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
cpSync,
|
|
14
|
+
readdirSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
chmodSync,
|
|
17
|
+
lstatSync,
|
|
18
|
+
mkdtempSync,
|
|
19
|
+
unlinkSync,
|
|
20
|
+
realpathSync
|
|
21
|
+
} from "fs";
|
|
22
|
+
import { homedir, loadavg } from "os";
|
|
23
|
+
import path from "path";
|
|
24
|
+
import { pathToFileURL } from "url";
|
|
25
|
+
|
|
26
|
+
// src/lib/cicd-merge-policy.ts
|
|
27
|
+
var FAILING = /* @__PURE__ */ new Set([
|
|
28
|
+
"FAILURE",
|
|
29
|
+
"ERROR",
|
|
30
|
+
"TIMED_OUT",
|
|
31
|
+
"CANCELLED",
|
|
32
|
+
"ACTION_REQUIRED",
|
|
33
|
+
"STALE"
|
|
34
|
+
]);
|
|
35
|
+
var IN_FLIGHT = /* @__PURE__ */ new Set([
|
|
36
|
+
"PENDING",
|
|
37
|
+
"IN_PROGRESS",
|
|
38
|
+
"QUEUED",
|
|
39
|
+
"WAITING",
|
|
40
|
+
// A null conclusion means the check has not concluded. Treating it as
|
|
41
|
+
// anything other than in-flight is how v1 masked real failures.
|
|
42
|
+
"NONE"
|
|
43
|
+
]);
|
|
44
|
+
var NON_SIGNAL_STATES = /* @__PURE__ */ new Set(["SKIPPED", "NEUTRAL"]);
|
|
45
|
+
function classifyChecks(states, mergeable) {
|
|
46
|
+
if (mergeable === "CONFLICTING") return "CONFLICT";
|
|
47
|
+
let failed = 0;
|
|
48
|
+
let inFlight = 0;
|
|
49
|
+
let succeeded = 0;
|
|
50
|
+
for (const s of states) {
|
|
51
|
+
if (FAILING.has(s)) failed++;
|
|
52
|
+
else if (IN_FLIGHT.has(s)) inFlight++;
|
|
53
|
+
else if (s === "SUCCESS") succeeded++;
|
|
54
|
+
else if (NON_SIGNAL_STATES.has(s)) {
|
|
55
|
+
} else {
|
|
56
|
+
failed++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (failed > 0) return "RED";
|
|
60
|
+
if (inFlight > 0) return "PENDING";
|
|
61
|
+
if (states.length === 0) return "NOCHECKS";
|
|
62
|
+
if (succeeded === 0) return "NOCHECKS";
|
|
63
|
+
return "GREEN";
|
|
64
|
+
}
|
|
65
|
+
function missingRequiredChecks(reported, required) {
|
|
66
|
+
return required.filter((name) => reported.get(name) !== "SUCCESS");
|
|
67
|
+
}
|
|
68
|
+
var PROTECTED_PATH_PATTERN = /(^|\/)(\.github\/workflows\/|auth|crypto|keychain|migrations?\/|schema|publish\.ya?ml|release)/i;
|
|
69
|
+
function touchesProtectedPath(files) {
|
|
70
|
+
return files.some((f) => PROTECTED_PATH_PATTERN.test(f));
|
|
71
|
+
}
|
|
72
|
+
var HOST_NOISE_LOAD_THRESHOLD = 18;
|
|
73
|
+
function isLikelyHostNoise(loadAverage, threshold = HOST_NOISE_LOAD_THRESHOLD) {
|
|
74
|
+
return loadAverage > threshold;
|
|
75
|
+
}
|
|
76
|
+
var MAX_FIX_ATTEMPTS = 3;
|
|
77
|
+
function decideAction(input) {
|
|
78
|
+
const {
|
|
79
|
+
verdict,
|
|
80
|
+
changedFiles: changedFiles2 = [],
|
|
81
|
+
missingRequired = [],
|
|
82
|
+
requiredChecksConfigured,
|
|
83
|
+
reviewDecision,
|
|
84
|
+
attempts = 0,
|
|
85
|
+
loadAverage = 0,
|
|
86
|
+
maxAttempts = MAX_FIX_ATTEMPTS
|
|
87
|
+
} = input;
|
|
88
|
+
switch (verdict) {
|
|
89
|
+
case "PENDING":
|
|
90
|
+
return { kind: "wait" };
|
|
91
|
+
case "CONFLICT":
|
|
92
|
+
return { kind: "rebase" };
|
|
93
|
+
case "NOCHECKS":
|
|
94
|
+
return {
|
|
95
|
+
kind: "hold",
|
|
96
|
+
reason: "no passing checks (absent, or all SKIPPED/NEUTRAL) \u2014 absence of failure is not proof of passing"
|
|
97
|
+
};
|
|
98
|
+
case "GREEN":
|
|
99
|
+
if (requiredChecksConfigured === false) {
|
|
100
|
+
return {
|
|
101
|
+
kind: "hold",
|
|
102
|
+
reason: "repo defines no required status checks \u2014 green here only means advisory checks passed"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (reviewDecision != null && reviewDecision !== "APPROVED") {
|
|
106
|
+
return {
|
|
107
|
+
kind: "hold",
|
|
108
|
+
reason: `review decision is ${reviewDecision}, not APPROVED`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (missingRequired.length > 0) {
|
|
112
|
+
return {
|
|
113
|
+
kind: "hold",
|
|
114
|
+
reason: `required checks with no reported success: ${missingRequired.join(", ")}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (touchesProtectedPath(changedFiles2)) {
|
|
118
|
+
return {
|
|
119
|
+
kind: "hold",
|
|
120
|
+
reason: "green but touches a protected path; human review required"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { kind: "merge" };
|
|
124
|
+
case "RED": {
|
|
125
|
+
if (attempts >= maxAttempts) {
|
|
126
|
+
return {
|
|
127
|
+
kind: "stop",
|
|
128
|
+
reason: `${attempts} failed fix attempts \u2014 stopping for a human`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (isLikelyHostNoise(loadAverage)) {
|
|
132
|
+
return {
|
|
133
|
+
kind: "hold",
|
|
134
|
+
reason: `host load ${loadAverage} \u2014 treating RED as host contention, not a code defect`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { kind: "escalate", attempt: attempts + 1 };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/lib/cicd-failure-triage.ts
|
|
143
|
+
var SIGNATURES = [
|
|
144
|
+
{
|
|
145
|
+
cls: "resource",
|
|
146
|
+
pattern: /ENOSPC|no space left on device|disk quota exceeded/i,
|
|
147
|
+
why: "runner ran out of disk"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
cls: "resource",
|
|
151
|
+
pattern: /JavaScript heap out of memory|Killed process|signal SIGKILL|oom-kill/i,
|
|
152
|
+
why: "process was killed for memory"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
cls: "resource",
|
|
156
|
+
pattern: /EMFILE|too many open files/i,
|
|
157
|
+
why: "runner hit the file-descriptor limit"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
cls: "network",
|
|
161
|
+
pattern: /ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up/i,
|
|
162
|
+
why: "network call failed"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
cls: "network",
|
|
166
|
+
pattern: /429 Too Many Requests|rate limit exceeded|registry\.npmjs\.org.*(?:500|503)/i,
|
|
167
|
+
why: "upstream rate-limited or unavailable"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
cls: "infrastructure",
|
|
171
|
+
// Deps resolve upward from the parent repo, so a worktree without its own
|
|
172
|
+
// node_modules fails this way with nothing wrong in the diff. Cost us a
|
|
173
|
+
// build investigation on this very branch.
|
|
174
|
+
//
|
|
175
|
+
// REGRESSION — codex-review P1 on PR #965: this used to also match
|
|
176
|
+
// `error TS2307: Cannot find module './foo'.` — a TypeScript COMPILER
|
|
177
|
+
// diagnostic pointing at a bad import the diff itself introduced, which is
|
|
178
|
+
// exactly a code defect, not a runner problem. Because this signature was
|
|
179
|
+
// checked before CODE_SIGNALS, that diagnostic classified as
|
|
180
|
+
// infrastructure with escalate:false and a real bug never reached the
|
|
181
|
+
// fixer. The negative lookbehind excludes "Cannot find module" when it is
|
|
182
|
+
// immediately preceded by a `TSxxxx:` diagnostic code, which only tsc
|
|
183
|
+
// produces — Node's runtime `MODULE_NOT_FOUND` error never has one.
|
|
184
|
+
pattern: /(?<!TS\d+:\s)Cannot find module|MODULE_NOT_FOUND|command not found: (?:tsx|vitest|tsc)/i,
|
|
185
|
+
why: "toolchain or dependency missing on the runner, not in the diff"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
cls: "infrastructure",
|
|
189
|
+
pattern: /No runner matching|waiting for a runner|The self-hosted runner.*lost communication/i,
|
|
190
|
+
why: "no runner available"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
cls: "timeout",
|
|
194
|
+
pattern: /The job running on runner .* has exceeded the maximum execution time|timed out after|Timeout - Async callback was not invoked/i,
|
|
195
|
+
why: "job exceeded its time budget \u2014 usually contention"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
cls: "timeout",
|
|
199
|
+
pattern: /The operation was canceled/i,
|
|
200
|
+
why: "job was cancelled, commonly by a timeout on a loaded box"
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
var REVIEW_BLOCK = /review found \[?(?:CRITICAL|P0|P1)\]?|Review gate FAILED CLOSED|review gate.*blocking/i;
|
|
204
|
+
var CODE_SIGNALS = [
|
|
205
|
+
/\bAssertionError\b|expected .* to (?:be|equal|contain)/i,
|
|
206
|
+
/\d+ (?:test|tests) failed|Tests:\s+\d+ failed/i,
|
|
207
|
+
/error TS\d+:/,
|
|
208
|
+
/✖ \d+ problems?|\d+ errors? and \d+ warnings?/i,
|
|
209
|
+
/FAIL\s+\S+\.(?:test|spec)\.[jt]sx?/
|
|
210
|
+
];
|
|
211
|
+
function triageFailureLog(log2) {
|
|
212
|
+
if (!log2.trim()) {
|
|
213
|
+
return {
|
|
214
|
+
cls: "unknown",
|
|
215
|
+
reason: "no log content available",
|
|
216
|
+
escalate: false
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
for (const sig of SIGNATURES) {
|
|
220
|
+
if (sig.pattern.test(log2)) {
|
|
221
|
+
return {
|
|
222
|
+
cls: sig.cls,
|
|
223
|
+
reason: `${sig.why} \u2014 environmental, not a code defect`,
|
|
224
|
+
escalate: false
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (REVIEW_BLOCK.test(log2)) {
|
|
229
|
+
return {
|
|
230
|
+
cls: "review",
|
|
231
|
+
reason: "an automated review gate found blocking issues in the diff",
|
|
232
|
+
escalate: true
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
for (const signal of CODE_SIGNALS) {
|
|
236
|
+
if (signal.test(log2)) {
|
|
237
|
+
return {
|
|
238
|
+
cls: "code",
|
|
239
|
+
reason: "log shows a genuine test/compile failure",
|
|
240
|
+
escalate: true
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
cls: "unknown",
|
|
246
|
+
reason: "no recognised failure signature \u2014 escalating, but with low confidence",
|
|
247
|
+
escalate: true
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function extractRelevantLines(log2, opts = {}) {
|
|
251
|
+
const { context = 3, maxLines = 200 } = opts;
|
|
252
|
+
const lines = log2.split("\n");
|
|
253
|
+
const interesting = /error|fail|✕|✖|✗|assert|expected|Cannot find|TS\d{4}|throw/i;
|
|
254
|
+
const keep = /* @__PURE__ */ new Set();
|
|
255
|
+
lines.forEach((line, i) => {
|
|
256
|
+
if (!interesting.test(line)) return;
|
|
257
|
+
for (let j = Math.max(0, i - context); j <= Math.min(lines.length - 1, i + context); j++) {
|
|
258
|
+
keep.add(j);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
if (keep.size === 0) {
|
|
262
|
+
return lines.slice(-Math.min(maxLines, lines.length)).join("\n");
|
|
263
|
+
}
|
|
264
|
+
const ordered = [...keep].sort((a, b) => a - b).slice(0, maxLines);
|
|
265
|
+
const out = [];
|
|
266
|
+
let prev = -1;
|
|
267
|
+
for (const i of ordered) {
|
|
268
|
+
if (prev !== -1 && i > prev + 1) out.push(" ...");
|
|
269
|
+
out.push(lines[i] ?? "");
|
|
270
|
+
prev = i;
|
|
271
|
+
}
|
|
272
|
+
return out.join("\n");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/lib/cicd-fixer.ts
|
|
276
|
+
var FIXER_BACKENDS = [
|
|
277
|
+
{
|
|
278
|
+
id: "glm",
|
|
279
|
+
command: "claude",
|
|
280
|
+
// Routed at Z.ai's Anthropic-compatible endpoint via the provider table.
|
|
281
|
+
args: ["-p", "{prompt}", "--permission-mode", "acceptEdits"],
|
|
282
|
+
envKeys: ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_MODEL"],
|
|
283
|
+
costRank: 1
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "codex",
|
|
287
|
+
command: "codex",
|
|
288
|
+
args: ["exec", "{prompt}"],
|
|
289
|
+
envKeys: [],
|
|
290
|
+
costRank: 2
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "opencode",
|
|
294
|
+
command: "opencode",
|
|
295
|
+
args: ["run", "{prompt}"],
|
|
296
|
+
envKeys: ["OPENCODE_API_KEY"],
|
|
297
|
+
costRank: 3
|
|
298
|
+
}
|
|
299
|
+
];
|
|
300
|
+
function selectFixer(available, attempt) {
|
|
301
|
+
if (available.length === 0) return null;
|
|
302
|
+
const ordered = FIXER_BACKENDS.filter((b) => available.includes(b.id)).sort((a, b) => a.costRank - b.costRank).map((b) => b.id);
|
|
303
|
+
if (ordered.length === 0) return null;
|
|
304
|
+
const idx = (Math.max(1, attempt) - 1) % ordered.length;
|
|
305
|
+
return ordered[idx] ?? null;
|
|
306
|
+
}
|
|
307
|
+
function buildFixPrompt(req) {
|
|
308
|
+
const files = req.changedFiles.length ? req.changedFiles.join("\n") : "(diff file list unavailable)";
|
|
309
|
+
return [
|
|
310
|
+
`CI is failing on ${req.repo} PR #${req.prNumber} (branch ${req.branch}).`,
|
|
311
|
+
`Triage classified this as: ${req.failureClass}.`,
|
|
312
|
+
"",
|
|
313
|
+
"Failing job output (trimmed to the relevant lines):",
|
|
314
|
+
"```",
|
|
315
|
+
req.evidence,
|
|
316
|
+
"```",
|
|
317
|
+
"",
|
|
318
|
+
"Files this PR already changes:",
|
|
319
|
+
"```",
|
|
320
|
+
files,
|
|
321
|
+
"```",
|
|
322
|
+
"",
|
|
323
|
+
"Your task:",
|
|
324
|
+
"1. Make the smallest change that makes this check pass.",
|
|
325
|
+
"2. Change ONLY what the failure requires. No refactors, no renames, no",
|
|
326
|
+
" reformatting of code you did not otherwise touch, no drive-by improvements.",
|
|
327
|
+
"3. Do NOT edit CI workflow files, auth, crypto, keychain, migrations or",
|
|
328
|
+
" release configuration. If the fix genuinely requires one of those, stop",
|
|
329
|
+
" and explain why instead of editing it.",
|
|
330
|
+
"4. Do NOT weaken, skip, or delete a test to make it pass. If the test is",
|
|
331
|
+
" correct and the code is wrong, fix the code.",
|
|
332
|
+
"5. If you cannot determine the cause with confidence, say so plainly rather",
|
|
333
|
+
" than guessing. A wrong fix costs more than no fix.",
|
|
334
|
+
"",
|
|
335
|
+
"Do not commit or push. Leave the working tree modified for review."
|
|
336
|
+
].join("\n");
|
|
337
|
+
}
|
|
338
|
+
function renderArgs(backend, prompt) {
|
|
339
|
+
return backend.args.map((a) => a.replace("{prompt}", prompt));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/lib/cicd-skylos.ts
|
|
343
|
+
var SKYLOS_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
344
|
+
var SKYLOS_REPORT_KEYS = {
|
|
345
|
+
secrets: "secret",
|
|
346
|
+
danger: "security",
|
|
347
|
+
ai_defects: "ai_defect",
|
|
348
|
+
quality: "quality"
|
|
349
|
+
};
|
|
350
|
+
function parseChangedLines(diff) {
|
|
351
|
+
const out = /* @__PURE__ */ new Map();
|
|
352
|
+
let file = null;
|
|
353
|
+
for (const line of diff.split("\n")) {
|
|
354
|
+
if (line.startsWith("+++ ")) {
|
|
355
|
+
const raw = line.slice(4).trim();
|
|
356
|
+
file = raw === "/dev/null" ? null : raw.replace(/^b\//, "");
|
|
357
|
+
if (file && !out.has(file)) out.set(file, /* @__PURE__ */ new Set());
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (!file || !line.startsWith("@@")) continue;
|
|
361
|
+
const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
362
|
+
if (!m) continue;
|
|
363
|
+
const start = Number(m[1]);
|
|
364
|
+
const count = m[2] === void 0 ? 1 : Number(m[2]);
|
|
365
|
+
const set = out.get(file);
|
|
366
|
+
if (!set) continue;
|
|
367
|
+
for (let i = 0; i < count; i += 1) set.add(start + i);
|
|
368
|
+
}
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
function buildSkylosArgs(opts) {
|
|
372
|
+
return [
|
|
373
|
+
opts.dir,
|
|
374
|
+
"--diff",
|
|
375
|
+
opts.baseRef,
|
|
376
|
+
"--secrets",
|
|
377
|
+
"--danger",
|
|
378
|
+
"--quality",
|
|
379
|
+
"--ai-defects",
|
|
380
|
+
"--format",
|
|
381
|
+
"json",
|
|
382
|
+
"--output",
|
|
383
|
+
opts.outputPath,
|
|
384
|
+
"--no-upload",
|
|
385
|
+
// Never let the scan decide an exit code for us. Phase 1 blocks nothing,
|
|
386
|
+
// and a non-zero exit would be indistinguishable from a crash.
|
|
387
|
+
"--force"
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
var SEVERITY_RANK = {
|
|
391
|
+
CRITICAL: 0,
|
|
392
|
+
HIGH: 1,
|
|
393
|
+
MEDIUM: 2,
|
|
394
|
+
LOW: 3
|
|
395
|
+
};
|
|
396
|
+
function normaliseSeverity(raw) {
|
|
397
|
+
const s = String(raw ?? "").toUpperCase();
|
|
398
|
+
return SKYLOS_SEVERITIES.includes(s) ? s : "LOW";
|
|
399
|
+
}
|
|
400
|
+
function toRelative(file, repoDir) {
|
|
401
|
+
const root = repoDir.endsWith("/") ? repoDir : `${repoDir}/`;
|
|
402
|
+
return file.startsWith(root) ? file.slice(root.length) : file;
|
|
403
|
+
}
|
|
404
|
+
function parseSkylosReport(raw, opts) {
|
|
405
|
+
let doc;
|
|
406
|
+
try {
|
|
407
|
+
doc = JSON.parse(raw);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
return {
|
|
410
|
+
status: "failed",
|
|
411
|
+
reason: `could not parse skylos JSON report: ${err instanceof Error ? err.message : String(err)}`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
415
|
+
return { status: "failed", reason: "skylos JSON report was not an object" };
|
|
416
|
+
}
|
|
417
|
+
const allow = opts.changedFiles ? new Set(opts.changedFiles) : null;
|
|
418
|
+
const record = doc;
|
|
419
|
+
const findings = [];
|
|
420
|
+
for (const [key, category] of Object.entries(SKYLOS_REPORT_KEYS)) {
|
|
421
|
+
const bucket = record[key];
|
|
422
|
+
if (!Array.isArray(bucket)) continue;
|
|
423
|
+
for (const entry of bucket) {
|
|
424
|
+
if (!entry || typeof entry !== "object") continue;
|
|
425
|
+
const e = entry;
|
|
426
|
+
const file = toRelative(String(e.file ?? ""), opts.repoDir);
|
|
427
|
+
if (allow && !allow.has(file)) continue;
|
|
428
|
+
const line = Number(e.line ?? 0);
|
|
429
|
+
const touched = opts.changedLines?.get(file);
|
|
430
|
+
findings.push({
|
|
431
|
+
ruleId: String(e.rule_id ?? "SKY-?"),
|
|
432
|
+
severity: normaliseSeverity(e.severity),
|
|
433
|
+
category: String(e.category ?? category),
|
|
434
|
+
file,
|
|
435
|
+
line,
|
|
436
|
+
message: String(e.message ?? ""),
|
|
437
|
+
// No changedLines at all, or no entry for this file (new/untracked):
|
|
438
|
+
// treat the whole file as the patch.
|
|
439
|
+
onChangedLine: touched ? touched.has(line) : true
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
findings.sort(
|
|
444
|
+
(a, b) => Number(b.onChangedLine) - Number(a.onChangedLine) || (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9) || a.file.localeCompare(b.file) || a.line - b.line
|
|
445
|
+
);
|
|
446
|
+
return { status: "ok", findings };
|
|
447
|
+
}
|
|
448
|
+
function countBySeverity(findings) {
|
|
449
|
+
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
450
|
+
for (const f of findings) counts[f.severity] += 1;
|
|
451
|
+
return counts;
|
|
452
|
+
}
|
|
453
|
+
var SKYLOS_LOG_LIMIT = 20;
|
|
454
|
+
function summarizeSkylosScan(scan, ctx) {
|
|
455
|
+
const key = `${ctx.repo}#${ctx.prNumber}`;
|
|
456
|
+
const head = `SKYLOS ${key} \u2014 scan of ${ctx.backend}'s patch`;
|
|
457
|
+
if (scan.status === "unavailable") {
|
|
458
|
+
return [
|
|
459
|
+
`${head}: DID NOT RUN \u2014 ${scan.reason}.`,
|
|
460
|
+
`SKYLOS ${key} \u2014 this patch was pushed WITHOUT a static-analysis scan. Install skylos (\`pipx install skylos\`) or set EXE_CICD_SKYLOS_BIN to restore it.`
|
|
461
|
+
];
|
|
462
|
+
}
|
|
463
|
+
if (scan.status === "failed") {
|
|
464
|
+
return [
|
|
465
|
+
`${head}: FAILED \u2014 ${scan.reason}.`,
|
|
466
|
+
`SKYLOS ${key} \u2014 this patch was pushed WITHOUT a usable static-analysis scan (non-blocking pilot; the push proceeded).`
|
|
467
|
+
];
|
|
468
|
+
}
|
|
469
|
+
if (scan.findings.length === 0) {
|
|
470
|
+
return [`${head}: clean \u2014 0 findings in the changed lines (non-blocking pilot).`];
|
|
471
|
+
}
|
|
472
|
+
const c = countBySeverity(scan.findings);
|
|
473
|
+
const onPatch = scan.findings.filter((f) => f.onChangedLine).length;
|
|
474
|
+
const lines = [
|
|
475
|
+
`${head}: ${scan.findings.length} finding(s) \u2014 ${c.CRITICAL} critical, ${c.HIGH} high, ${c.MEDIUM} medium, ${c.LOW} low. ${onPatch} on lines this patch changed, ${scan.findings.length - onPatch} pre-existing in the touched files. NON-BLOCKING (pilot) \u2014 the push proceeds regardless.`
|
|
476
|
+
];
|
|
477
|
+
for (const f of scan.findings.slice(0, SKYLOS_LOG_LIMIT)) {
|
|
478
|
+
const scope = f.onChangedLine ? "patch" : "file";
|
|
479
|
+
lines.push(` SKYLOS ${scope} ${f.severity} ${f.ruleId} ${f.file}:${f.line} \u2014 ${f.message}`);
|
|
480
|
+
}
|
|
481
|
+
if (scan.findings.length > SKYLOS_LOG_LIMIT) {
|
|
482
|
+
lines.push(
|
|
483
|
+
` SKYLOS ... and ${scan.findings.length - SKYLOS_LOG_LIMIT} more finding(s) not shown.`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return lines;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/bin/exe-cicd.ts
|
|
490
|
+
var run = promisify(execFile);
|
|
491
|
+
var STATE_DIR = path.join(homedir(), ".exe-os", "cicd");
|
|
492
|
+
var STATE_FILE = path.join(STATE_DIR, "state.json");
|
|
493
|
+
var LOG_FILE = path.join(STATE_DIR, "log.txt");
|
|
494
|
+
var DEFAULT_REPOS = [
|
|
495
|
+
"exe-os",
|
|
496
|
+
"exe-build",
|
|
497
|
+
"exe-chat",
|
|
498
|
+
"exe-gateway",
|
|
499
|
+
"exe-crm",
|
|
500
|
+
"exe-wiki",
|
|
501
|
+
"exe-erp",
|
|
502
|
+
"exe-db"
|
|
503
|
+
];
|
|
504
|
+
var ORG = process.env.EXE_CICD_ORG ?? "AskExe";
|
|
505
|
+
var FAILED_STATES = /* @__PURE__ */ new Set(["FAILURE", "ERROR", "TIMED_OUT", "CANCELLED"]);
|
|
506
|
+
function jobIdFromUrl(url) {
|
|
507
|
+
if (!url) return null;
|
|
508
|
+
const m = /\/job\/(\d+)/.exec(url);
|
|
509
|
+
return m?.[1] ?? null;
|
|
510
|
+
}
|
|
511
|
+
function log(msg) {
|
|
512
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}`;
|
|
513
|
+
console.log(line);
|
|
514
|
+
try {
|
|
515
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
516
|
+
appendFileSync(LOG_FILE, `${line}
|
|
517
|
+
`);
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function readState() {
|
|
522
|
+
try {
|
|
523
|
+
const raw = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
|
524
|
+
const out = {};
|
|
525
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
526
|
+
out[key] = typeof value === "number" ? { attempts: value } : value;
|
|
527
|
+
}
|
|
528
|
+
return out;
|
|
529
|
+
} catch {
|
|
530
|
+
return {};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function writeState(state) {
|
|
534
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
535
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
536
|
+
}
|
|
537
|
+
function attemptsForHead(record, headOid) {
|
|
538
|
+
if (!record) return 0;
|
|
539
|
+
if (record.headOid !== headOid) return 0;
|
|
540
|
+
return record.attempts;
|
|
541
|
+
}
|
|
542
|
+
function toCheckState(entry) {
|
|
543
|
+
const raw = entry.conclusion ?? entry.state ?? "NONE";
|
|
544
|
+
return String(raw).toUpperCase();
|
|
545
|
+
}
|
|
546
|
+
async function gh(args) {
|
|
547
|
+
const { stdout } = await run("gh", args, { maxBuffer: 32 * 1024 * 1024 });
|
|
548
|
+
return stdout;
|
|
549
|
+
}
|
|
550
|
+
async function listPullRequests(repo) {
|
|
551
|
+
try {
|
|
552
|
+
const out = await gh([
|
|
553
|
+
"pr",
|
|
554
|
+
"list",
|
|
555
|
+
"--repo",
|
|
556
|
+
`${ORG}/${repo}`,
|
|
557
|
+
"--state",
|
|
558
|
+
"open",
|
|
559
|
+
"--json",
|
|
560
|
+
"number,title,mergeable,isDraft,autoMergeRequest,statusCheckRollup,headRefName,headRefOid,reviewDecision"
|
|
561
|
+
]);
|
|
562
|
+
return JSON.parse(out);
|
|
563
|
+
} catch {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
var requiredChecksCache = /* @__PURE__ */ new Map();
|
|
568
|
+
async function requiredChecks(repo) {
|
|
569
|
+
const cached = requiredChecksCache.get(repo);
|
|
570
|
+
if (cached !== void 0) return cached;
|
|
571
|
+
const contexts = /* @__PURE__ */ new Set();
|
|
572
|
+
let read = false;
|
|
573
|
+
try {
|
|
574
|
+
const out = await gh([
|
|
575
|
+
"api",
|
|
576
|
+
`repos/${ORG}/${repo}/branches/main/protection/required_status_checks`
|
|
577
|
+
]);
|
|
578
|
+
const parsed = JSON.parse(out);
|
|
579
|
+
for (const c of parsed.contexts ?? []) contexts.add(c);
|
|
580
|
+
for (const c of parsed.checks ?? []) contexts.add(c.context);
|
|
581
|
+
read = true;
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const out = await gh(["api", `repos/${ORG}/${repo}/rules/branches/main`]);
|
|
586
|
+
const rules = JSON.parse(out);
|
|
587
|
+
for (const r of rules) {
|
|
588
|
+
if (r.type !== "required_status_checks") continue;
|
|
589
|
+
for (const c of r.parameters?.required_status_checks ?? []) contexts.add(c.context);
|
|
590
|
+
}
|
|
591
|
+
read = true;
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
const result = read ? [...contexts] : null;
|
|
595
|
+
requiredChecksCache.set(repo, result);
|
|
596
|
+
return result;
|
|
597
|
+
}
|
|
598
|
+
async function changedFiles(repo, num) {
|
|
599
|
+
try {
|
|
600
|
+
const out = await gh(["pr", "diff", String(num), "--repo", `${ORG}/${repo}`, "--name-only"]);
|
|
601
|
+
return out.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
602
|
+
} catch {
|
|
603
|
+
return [".github/workflows/UNREADABLE"];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function triageFailedJob(repo, pr) {
|
|
607
|
+
const failed = (pr.statusCheckRollup ?? []).find(
|
|
608
|
+
(c) => FAILED_STATES.has(String(c.conclusion ?? c.state ?? "").toUpperCase())
|
|
609
|
+
);
|
|
610
|
+
const jobId = jobIdFromUrl(failed?.detailsUrl ?? failed?.targetUrl);
|
|
611
|
+
if (!jobId) {
|
|
612
|
+
return {
|
|
613
|
+
cls: "unknown",
|
|
614
|
+
reason: "could not locate the failing job's log",
|
|
615
|
+
escalate: false
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
let raw;
|
|
619
|
+
try {
|
|
620
|
+
raw = await gh(["api", `repos/${ORG}/${repo}/actions/jobs/${jobId}/logs`]);
|
|
621
|
+
} catch {
|
|
622
|
+
return {
|
|
623
|
+
cls: "unknown",
|
|
624
|
+
reason: "failing job's log could not be read",
|
|
625
|
+
escalate: false
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const result = triageFailureLog(raw);
|
|
629
|
+
if (result.escalate) {
|
|
630
|
+
result.evidence = extractRelevantLines(raw);
|
|
631
|
+
log(` evidence: ${result.evidence.split("\n").length} relevant lines from job ${jobId}`);
|
|
632
|
+
}
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
var CHECKOUTS_DIR = path.join(STATE_DIR, "checkouts");
|
|
636
|
+
var FIXER_TIMEOUT_MS = Number(process.env.EXE_CICD_FIXER_TIMEOUT_MS ?? 15 * 60 * 1e3);
|
|
637
|
+
async function ensureCheckout(repo, branch, headOid, execFn = run) {
|
|
638
|
+
const dir = path.join(CHECKOUTS_DIR, repo);
|
|
639
|
+
mkdirSync(CHECKOUTS_DIR, { recursive: true });
|
|
640
|
+
if (!existsSync(path.join(dir, ".git"))) {
|
|
641
|
+
await execFn("git", ["clone", `https://github.com/${ORG}/${repo}.git`, dir], {});
|
|
642
|
+
}
|
|
643
|
+
await execFn("git", ["-C", dir, "fetch", "origin", branch], {});
|
|
644
|
+
await execFn("git", ["-C", dir, "checkout", "-B", branch, "FETCH_HEAD"], {});
|
|
645
|
+
await execFn("git", ["-C", dir, "reset", "--hard", headOid], {});
|
|
646
|
+
await execFn("git", ["-C", dir, "clean", "-fdx"], {});
|
|
647
|
+
return dir;
|
|
648
|
+
}
|
|
649
|
+
var SAFE_ENV_PASSTHROUGH = [
|
|
650
|
+
"PATH",
|
|
651
|
+
"USER",
|
|
652
|
+
"SHELL",
|
|
653
|
+
"LANG",
|
|
654
|
+
"LC_ALL",
|
|
655
|
+
"TMPDIR",
|
|
656
|
+
"TERM"
|
|
657
|
+
];
|
|
658
|
+
var FIXER_HOME_ROOT = path.join(STATE_DIR, "fixer-homes");
|
|
659
|
+
var CODEX_CREDENTIAL_FILES = ["auth.json", "config.json"];
|
|
660
|
+
function copyCodexCredentials(realCodexDir, isolatedCodexDir) {
|
|
661
|
+
mkdirSync(isolatedCodexDir, { recursive: true });
|
|
662
|
+
for (const name of CODEX_CREDENTIAL_FILES) {
|
|
663
|
+
const src = path.join(realCodexDir, name);
|
|
664
|
+
if (!existsSync(src)) continue;
|
|
665
|
+
try {
|
|
666
|
+
cpSync(src, path.join(isolatedCodexDir, name));
|
|
667
|
+
} catch {
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function defaultIsolatedFixerHome(def) {
|
|
672
|
+
const dir = path.join(
|
|
673
|
+
FIXER_HOME_ROOT,
|
|
674
|
+
`${def.id}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
675
|
+
);
|
|
676
|
+
mkdirSync(dir, { recursive: true });
|
|
677
|
+
if (def.id === "codex") {
|
|
678
|
+
const realCodexDir = path.join(homedir(), ".codex");
|
|
679
|
+
if (existsSync(realCodexDir)) {
|
|
680
|
+
copyCodexCredentials(realCodexDir, path.join(dir, ".codex"));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return dir;
|
|
684
|
+
}
|
|
685
|
+
var GLM_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
686
|
+
var GLM_DEFAULT_MODEL = "glm-5.3";
|
|
687
|
+
function resolveGlmAuthToken() {
|
|
688
|
+
if (process.env.ZAI_API_KEY) return process.env.ZAI_API_KEY;
|
|
689
|
+
try {
|
|
690
|
+
const raw = readFileSync(path.join(homedir(), ".glm-mcp", "config.json"), "utf8");
|
|
691
|
+
const parsed = JSON.parse(raw);
|
|
692
|
+
return parsed.apiKey ?? null;
|
|
693
|
+
} catch {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function fixerEnv(def, isolatedHome) {
|
|
698
|
+
const env = { HOME: isolatedHome };
|
|
699
|
+
for (const key of [...SAFE_ENV_PASSTHROUGH, ...def.envKeys]) {
|
|
700
|
+
const value = process.env[key];
|
|
701
|
+
if (value !== void 0) env[key] = value;
|
|
702
|
+
}
|
|
703
|
+
if (def.id === "glm") {
|
|
704
|
+
if (!env.ANTHROPIC_AUTH_TOKEN) {
|
|
705
|
+
const token = resolveGlmAuthToken();
|
|
706
|
+
if (token) env.ANTHROPIC_AUTH_TOKEN = token;
|
|
707
|
+
}
|
|
708
|
+
if (!env.ANTHROPIC_BASE_URL) env.ANTHROPIC_BASE_URL = GLM_BASE_URL;
|
|
709
|
+
if (!env.ANTHROPIC_MODEL) env.ANTHROPIC_MODEL = GLM_DEFAULT_MODEL;
|
|
710
|
+
}
|
|
711
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
712
|
+
env.GIT_ASKPASS = "";
|
|
713
|
+
env.SSH_ASKPASS = "";
|
|
714
|
+
env.GIT_CONFIG_COUNT = "1";
|
|
715
|
+
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
716
|
+
env.GIT_CONFIG_VALUE_0 = "";
|
|
717
|
+
return env;
|
|
718
|
+
}
|
|
719
|
+
var SECRET_ENV_KEYS = ["ANTHROPIC_AUTH_TOKEN", "OPENCODE_API_KEY"];
|
|
720
|
+
var TOKEN_LIKE_PATTERN = /[A-Za-z0-9_\-]{20,}/g;
|
|
721
|
+
function collectFixerSecrets(env, isolatedHome) {
|
|
722
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
723
|
+
for (const key of SECRET_ENV_KEYS) {
|
|
724
|
+
const value = env[key];
|
|
725
|
+
if (value && value.length >= 8) secrets.add(value);
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
const codexDir = path.join(isolatedHome, ".codex");
|
|
729
|
+
if (existsSync(codexDir)) {
|
|
730
|
+
for (const entry of readdirSync(codexDir, { withFileTypes: true })) {
|
|
731
|
+
if (!entry.isFile()) continue;
|
|
732
|
+
try {
|
|
733
|
+
const content = readFileSync(path.join(codexDir, entry.name), "utf8");
|
|
734
|
+
for (const match of content.matchAll(TOKEN_LIKE_PATTERN)) secrets.add(match[0]);
|
|
735
|
+
} catch {
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} catch {
|
|
740
|
+
}
|
|
741
|
+
return [...secrets];
|
|
742
|
+
}
|
|
743
|
+
async function runFixerBackend(backend, prompt, cwd, execFn = run, homeDirFn = defaultIsolatedFixerHome, cleanupHomeFn = defaultCleanupFixerHome) {
|
|
744
|
+
const def = FIXER_BACKENDS.find((b) => b.id === backend);
|
|
745
|
+
if (!def) return { backend, ok: false, error: `unknown fixer backend: ${backend}` };
|
|
746
|
+
const args = renderArgs(def, prompt);
|
|
747
|
+
const isolatedHome = homeDirFn(def);
|
|
748
|
+
try {
|
|
749
|
+
const env = fixerEnv(def, isolatedHome);
|
|
750
|
+
const secrets = collectFixerSecrets(env, isolatedHome);
|
|
751
|
+
try {
|
|
752
|
+
await execFn(def.command, args, {
|
|
753
|
+
cwd,
|
|
754
|
+
env,
|
|
755
|
+
timeout: FIXER_TIMEOUT_MS,
|
|
756
|
+
maxBuffer: 32 * 1024 * 1024
|
|
757
|
+
});
|
|
758
|
+
return { backend, ok: true, secrets };
|
|
759
|
+
} catch (err) {
|
|
760
|
+
return { backend, ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
761
|
+
}
|
|
762
|
+
} finally {
|
|
763
|
+
cleanupHomeFn(isolatedHome);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function forceRemovable(target) {
|
|
767
|
+
let st;
|
|
768
|
+
try {
|
|
769
|
+
st = lstatSync(target);
|
|
770
|
+
} catch {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (st.isSymbolicLink()) return;
|
|
774
|
+
if (!st.isDirectory()) {
|
|
775
|
+
try {
|
|
776
|
+
chmodSync(target, 384);
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
chmodSync(target, 448);
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
let children = [];
|
|
786
|
+
try {
|
|
787
|
+
children = readdirSync(target);
|
|
788
|
+
} catch {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
for (const child of children) forceRemovable(path.join(target, child));
|
|
792
|
+
}
|
|
793
|
+
function defaultCleanupFixerHome(home) {
|
|
794
|
+
try {
|
|
795
|
+
forceRemovable(home);
|
|
796
|
+
rmSync(home, { recursive: true, force: true });
|
|
797
|
+
} catch {
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
var SKYLOS_TIMEOUT_MS = Number(process.env.EXE_CICD_SKYLOS_TIMEOUT_MS ?? 10 * 60 * 1e3);
|
|
801
|
+
var SKYLOS_BIN = process.env.EXE_CICD_SKYLOS_BIN ?? "skylos";
|
|
802
|
+
var SKYLOS_REPORT_DIR = path.join(STATE_DIR, "skylos");
|
|
803
|
+
function isMissingCommand(err) {
|
|
804
|
+
const code = err?.code;
|
|
805
|
+
if (code === "ENOENT") return true;
|
|
806
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
807
|
+
return /\bENOENT\b|not found|No such file or directory/i.test(msg);
|
|
808
|
+
}
|
|
809
|
+
async function changedFilesInCheckout(dir, execFn) {
|
|
810
|
+
const { stdout } = await execFn("git", ["-C", dir, "status", "--porcelain"], {});
|
|
811
|
+
const files = [];
|
|
812
|
+
for (const line of stdout.split("\n")) {
|
|
813
|
+
if (!line.trim()) continue;
|
|
814
|
+
const rest = line.slice(3).trim();
|
|
815
|
+
const arrow = rest.indexOf(" -> ");
|
|
816
|
+
if (arrow === -1) files.push(rest);
|
|
817
|
+
else files.push(rest.slice(0, arrow), rest.slice(arrow + 4));
|
|
818
|
+
}
|
|
819
|
+
return files;
|
|
820
|
+
}
|
|
821
|
+
async function runSkylosScan(dir, baseRef, execFn = run) {
|
|
822
|
+
let workDir = null;
|
|
823
|
+
let outputPath = null;
|
|
824
|
+
const skylosCacheDir = path.join(dir, ".skylos");
|
|
825
|
+
const skylosCachePreexisted = existsSync(skylosCacheDir);
|
|
826
|
+
try {
|
|
827
|
+
const changedFiles2 = await changedFilesInCheckout(dir, execFn);
|
|
828
|
+
if (changedFiles2.length === 0) {
|
|
829
|
+
return { status: "ok", findings: [] };
|
|
830
|
+
}
|
|
831
|
+
mkdirSync(SKYLOS_REPORT_DIR, { recursive: true });
|
|
832
|
+
workDir = realpathSync(mkdtempSync(path.join(SKYLOS_REPORT_DIR, "scan-")));
|
|
833
|
+
outputPath = path.join(workDir, "report.json");
|
|
834
|
+
try {
|
|
835
|
+
await execFn(SKYLOS_BIN, buildSkylosArgs({ dir, baseRef, outputPath }), {
|
|
836
|
+
cwd: dir,
|
|
837
|
+
timeout: SKYLOS_TIMEOUT_MS,
|
|
838
|
+
maxBuffer: 32 * 1024 * 1024
|
|
839
|
+
});
|
|
840
|
+
} catch (err) {
|
|
841
|
+
if (isMissingCommand(err)) {
|
|
842
|
+
return {
|
|
843
|
+
status: "unavailable",
|
|
844
|
+
reason: `skylos executable '${SKYLOS_BIN}' not found on PATH`
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
if (!existsSync(outputPath)) {
|
|
848
|
+
return {
|
|
849
|
+
status: "failed",
|
|
850
|
+
reason: `skylos run failed: ${err instanceof Error ? err.message : String(err)}`
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (!existsSync(outputPath)) {
|
|
855
|
+
return { status: "failed", reason: "skylos produced no report file" };
|
|
856
|
+
}
|
|
857
|
+
const { stdout: diffOut } = await execFn("git", ["-C", dir, "diff", "-U0", "HEAD"], {});
|
|
858
|
+
return parseSkylosReport(readFileSync(outputPath, "utf8"), {
|
|
859
|
+
repoDir: dir,
|
|
860
|
+
changedFiles: changedFiles2,
|
|
861
|
+
changedLines: parseChangedLines(diffOut)
|
|
862
|
+
});
|
|
863
|
+
} catch (err) {
|
|
864
|
+
return {
|
|
865
|
+
status: "failed",
|
|
866
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
867
|
+
};
|
|
868
|
+
} finally {
|
|
869
|
+
try {
|
|
870
|
+
if (outputPath && existsSync(outputPath)) unlinkSync(outputPath);
|
|
871
|
+
if (workDir && existsSync(workDir)) rmSync(workDir, { recursive: true, force: true });
|
|
872
|
+
if (!skylosCachePreexisted && existsSync(skylosCacheDir)) {
|
|
873
|
+
rmSync(skylosCacheDir, { recursive: true, force: true });
|
|
874
|
+
}
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
var TEST_WEAKENING_PATTERNS = [
|
|
880
|
+
/^\+.*\b(?:it|test|describe)\.(?:skip|todo)\(/m,
|
|
881
|
+
/^\+.*\bx(?:it|describe|test)\(/m,
|
|
882
|
+
/^\+.*@ts-nocheck\b/m,
|
|
883
|
+
// REGRESSION — codex-review P1, round 10: this used to only match
|
|
884
|
+
// it.only/describe.only, missing `test.only(...)` — an unattended fixer
|
|
885
|
+
// could add that and potentially make CI run only its own focused test,
|
|
886
|
+
// the exact narrowing the "no .only markers" rule exists to forbid.
|
|
887
|
+
/^\+.*\b(?:it|test|describe)\.only\(/m
|
|
888
|
+
];
|
|
889
|
+
function diffWeakensTests(diff) {
|
|
890
|
+
return TEST_WEAKENING_PATTERNS.some((p) => p.test(diff));
|
|
891
|
+
}
|
|
892
|
+
var TEST_FILE_PATTERN = /(^|\/)(tests?|__tests__)\/|\.(?:test|spec)\.[jt]sx?$/i;
|
|
893
|
+
function deletesATestFile(entries) {
|
|
894
|
+
for (const e of entries) {
|
|
895
|
+
if (e.status.includes("D") && TEST_FILE_PATTERN.test(e.file)) return e.file;
|
|
896
|
+
if (e.renamedFrom && TEST_FILE_PATTERN.test(e.renamedFrom) && !TEST_FILE_PATTERN.test(e.file)) {
|
|
897
|
+
return e.renamedFrom;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
async function commitAndPushFix(dir, branch, backend, headOid, secrets = [], execFn = run) {
|
|
903
|
+
const { stdout: headNow } = await execFn("git", ["-C", dir, "rev-parse", "HEAD"], {});
|
|
904
|
+
if (headNow.trim() !== headOid) {
|
|
905
|
+
return {
|
|
906
|
+
kind: "blocked",
|
|
907
|
+
reason: `checkout no longer at the triaged head (expected ${headOid.slice(0, 10)}, found ${headNow.trim().slice(0, 10)}) \u2014 refusing to commit on top of an unverified base`,
|
|
908
|
+
files: []
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
const { stdout } = await execFn("git", ["-C", dir, "status", "--porcelain"], {});
|
|
912
|
+
if (!stdout.trim()) return { kind: "clean" };
|
|
913
|
+
const entries = stdout.split("\n").filter(Boolean).map((line) => {
|
|
914
|
+
const status = line.slice(0, 2);
|
|
915
|
+
const rest = line.slice(3).trim();
|
|
916
|
+
const arrow = rest.indexOf(" -> ");
|
|
917
|
+
if (arrow === -1) return { status, file: rest };
|
|
918
|
+
return { status, file: rest.slice(arrow + 4), renamedFrom: rest.slice(0, arrow) };
|
|
919
|
+
});
|
|
920
|
+
const files = entries.flatMap((e) => e.renamedFrom ? [e.renamedFrom, e.file] : [e.file]);
|
|
921
|
+
if (touchesProtectedPath(files)) {
|
|
922
|
+
return {
|
|
923
|
+
kind: "blocked",
|
|
924
|
+
reason: "fixer touched a protected path (workflows/auth/crypto/keychain/migrations/schema/release)",
|
|
925
|
+
files
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
const deletedTest = deletesATestFile(entries);
|
|
929
|
+
if (deletedTest) {
|
|
930
|
+
return {
|
|
931
|
+
kind: "blocked",
|
|
932
|
+
reason: `fixer deleted a test file (${deletedTest}) \u2014 the prompt forbids weakening or deleting tests to go green`,
|
|
933
|
+
files
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
await execFn("git", ["-C", dir, "add", "-A"], {});
|
|
937
|
+
const { stdout: diffOut } = await execFn("git", ["-C", dir, "diff", "--cached"], {});
|
|
938
|
+
if (diffWeakensTests(diffOut)) {
|
|
939
|
+
return {
|
|
940
|
+
kind: "blocked",
|
|
941
|
+
reason: "fixer's diff adds a skip/todo/only/@ts-nocheck marker \u2014 the prompt forbids weakening tests to go green",
|
|
942
|
+
files
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
const leakedSecret = secrets.find((s) => s.length >= 8 && diffOut.includes(s));
|
|
946
|
+
if (leakedSecret) {
|
|
947
|
+
return {
|
|
948
|
+
kind: "blocked",
|
|
949
|
+
reason: "fixer's diff contains a value matching a credential this run had access to \u2014 refusing to push a possible secret leak",
|
|
950
|
+
files
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
await execFn(
|
|
954
|
+
"git",
|
|
955
|
+
["-C", dir, "commit", "-m", `fix(cicd): automated fix via ${backend} fixer backend`],
|
|
956
|
+
{}
|
|
957
|
+
);
|
|
958
|
+
await execFn("git", ["-C", dir, "push", "origin", `HEAD:${branch}`], {});
|
|
959
|
+
return { kind: "pushed" };
|
|
960
|
+
}
|
|
961
|
+
function defaultEscalationDeps(execFn = run) {
|
|
962
|
+
return {
|
|
963
|
+
checkout: (repo, branch, headOid) => ensureCheckout(repo, branch, headOid, execFn),
|
|
964
|
+
runFixer: (backend, prompt, cwd) => runFixerBackend(backend, prompt, cwd, execFn),
|
|
965
|
+
scanPatch: (dir, baseRef) => runSkylosScan(dir, baseRef, execFn),
|
|
966
|
+
commitAndPush: (dir, branch, backend, headOid, secrets) => commitAndPushFix(dir, branch, backend, headOid, secrets, execFn)
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
async function runLiveEscalation(repo, branch, headOid, backend, prompt, deps = defaultEscalationDeps()) {
|
|
970
|
+
try {
|
|
971
|
+
const dir = await deps.checkout(repo, branch, headOid);
|
|
972
|
+
const outcome = await deps.runFixer(backend, prompt, dir);
|
|
973
|
+
if (!outcome.ok) return { ok: false, pushed: false, error: outcome.error };
|
|
974
|
+
let skylos;
|
|
975
|
+
try {
|
|
976
|
+
skylos = await deps.scanPatch(dir, headOid);
|
|
977
|
+
} catch (err) {
|
|
978
|
+
skylos = {
|
|
979
|
+
status: "failed",
|
|
980
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
const commit = await deps.commitAndPush(dir, branch, backend, headOid, outcome.secrets ?? []);
|
|
984
|
+
if (commit.kind === "blocked") {
|
|
985
|
+
return { ok: true, pushed: false, blocked: commit.reason, skylos };
|
|
986
|
+
}
|
|
987
|
+
return { ok: true, pushed: commit.kind === "pushed", skylos };
|
|
988
|
+
} catch (err) {
|
|
989
|
+
return { ok: false, pushed: false, error: err instanceof Error ? err.message : String(err) };
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async function processRepo(repo, live, fixers = []) {
|
|
993
|
+
const prs = await listPullRequests(repo);
|
|
994
|
+
const state = readState();
|
|
995
|
+
const load = loadavg()[0] ?? 0;
|
|
996
|
+
for (const pr of prs) {
|
|
997
|
+
if (pr.isDraft) continue;
|
|
998
|
+
const key = `${repo}#${pr.number}`;
|
|
999
|
+
const states = (pr.statusCheckRollup ?? []).map(toCheckState);
|
|
1000
|
+
const verdict = classifyChecks(states, pr.mergeable);
|
|
1001
|
+
if (pr.autoMergeRequest) {
|
|
1002
|
+
log(`DISARM ${key} \u2014 was armed to self-merge on green`);
|
|
1003
|
+
if (live) {
|
|
1004
|
+
await gh(["pr", "merge", String(pr.number), "--repo", `${ORG}/${repo}`, "--disable-auto"]).catch(
|
|
1005
|
+
() => void 0
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
let files = [];
|
|
1010
|
+
let missingRequired = [];
|
|
1011
|
+
let requiredConfigured;
|
|
1012
|
+
if (verdict === "GREEN") {
|
|
1013
|
+
files = await changedFiles(repo, pr.number);
|
|
1014
|
+
const required = await requiredChecks(repo);
|
|
1015
|
+
requiredConfigured = required === null ? void 0 : required.length > 0;
|
|
1016
|
+
if (required === null) {
|
|
1017
|
+
missingRequired = ["<branch protection unreadable>"];
|
|
1018
|
+
} else {
|
|
1019
|
+
const reported = /* @__PURE__ */ new Map();
|
|
1020
|
+
for (const c of pr.statusCheckRollup ?? []) {
|
|
1021
|
+
const name = c.name ?? c.context;
|
|
1022
|
+
if (name) reported.set(name, toCheckState(c));
|
|
1023
|
+
}
|
|
1024
|
+
missingRequired = missingRequiredChecks(reported, required);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
const action = decideAction({
|
|
1028
|
+
verdict,
|
|
1029
|
+
changedFiles: files,
|
|
1030
|
+
missingRequired,
|
|
1031
|
+
requiredChecksConfigured: requiredConfigured,
|
|
1032
|
+
reviewDecision: pr.reviewDecision,
|
|
1033
|
+
// Their head-pinned record, not a mangled key: it also migrates the old
|
|
1034
|
+
// number-only state entries, which key-mangling would have orphaned.
|
|
1035
|
+
attempts: attemptsForHead(state[key], pr.headRefOid),
|
|
1036
|
+
loadAverage: load
|
|
1037
|
+
});
|
|
1038
|
+
switch (action.kind) {
|
|
1039
|
+
case "wait":
|
|
1040
|
+
log(`WAIT ${key} \u2014 checks running`);
|
|
1041
|
+
break;
|
|
1042
|
+
case "rebase":
|
|
1043
|
+
log(`REBASE ${key} \u2014 conflicting; needs a rebase, not a model-authored fix`);
|
|
1044
|
+
break;
|
|
1045
|
+
case "hold":
|
|
1046
|
+
log(`HOLD ${key} \u2014 ${action.reason}`);
|
|
1047
|
+
break;
|
|
1048
|
+
case "stop":
|
|
1049
|
+
log(`STOP ${key} \u2014 ${action.reason}`);
|
|
1050
|
+
break;
|
|
1051
|
+
case "merge": {
|
|
1052
|
+
if (!pr.headRefOid) {
|
|
1053
|
+
log(`HOLD ${key} \u2014 no head commit to pin the merge to; refusing to merge blind`);
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
log(`MERGE ${key} \u2014 green, merging at ${pr.headRefOid.slice(0, 10)}`);
|
|
1057
|
+
if (live) {
|
|
1058
|
+
await gh([
|
|
1059
|
+
"pr",
|
|
1060
|
+
"merge",
|
|
1061
|
+
String(pr.number),
|
|
1062
|
+
"--repo",
|
|
1063
|
+
`${ORG}/${repo}`,
|
|
1064
|
+
"--squash",
|
|
1065
|
+
"--delete-branch",
|
|
1066
|
+
"--match-head-commit",
|
|
1067
|
+
pr.headRefOid
|
|
1068
|
+
]).catch((e) => log(` merge refused (head moved or gate failed): ${e.message}`));
|
|
1069
|
+
}
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
case "escalate": {
|
|
1073
|
+
const triage = await triageFailedJob(repo, pr);
|
|
1074
|
+
if (!triage.escalate) {
|
|
1075
|
+
log(`HOLD ${key} \u2014 ${triage.reason} (${triage.cls}); not escalating`);
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
const backend = selectFixer(fixers, action.attempt);
|
|
1079
|
+
if (!backend) {
|
|
1080
|
+
log(`HOLD ${key} \u2014 real failure but no fixer backend available`);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
const prompt = buildFixPrompt({
|
|
1084
|
+
repo,
|
|
1085
|
+
prNumber: pr.number,
|
|
1086
|
+
branch: pr.headRefName ?? "unknown",
|
|
1087
|
+
failureClass: triage.cls,
|
|
1088
|
+
evidence: triage.evidence ?? "",
|
|
1089
|
+
changedFiles: await changedFiles(repo, pr.number)
|
|
1090
|
+
});
|
|
1091
|
+
log(
|
|
1092
|
+
`FIX ${key} \u2014 attempt ${action.attempt} via ${backend} \u2014 ${triage.reason} (prompt ${prompt.length} chars)`
|
|
1093
|
+
);
|
|
1094
|
+
if (live) {
|
|
1095
|
+
if (!pr.headRefOid) {
|
|
1096
|
+
log(`HOLD ${key} \u2014 no head commit to pin the fixer checkout to; refusing to fix blind`);
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
state[key] = { attempts: action.attempt, headOid: pr.headRefOid };
|
|
1100
|
+
writeState(state);
|
|
1101
|
+
const result = await runLiveEscalation(
|
|
1102
|
+
repo,
|
|
1103
|
+
pr.headRefName ?? "unknown",
|
|
1104
|
+
pr.headRefOid,
|
|
1105
|
+
backend,
|
|
1106
|
+
prompt
|
|
1107
|
+
);
|
|
1108
|
+
if (result.skylos) {
|
|
1109
|
+
for (const line of summarizeSkylosScan(result.skylos, {
|
|
1110
|
+
repo,
|
|
1111
|
+
prNumber: pr.number,
|
|
1112
|
+
backend
|
|
1113
|
+
})) {
|
|
1114
|
+
log(` ${line}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (!result.ok) {
|
|
1118
|
+
log(` FAIL ${key} \u2014 ${backend} did not produce a usable fix: ${result.error}`);
|
|
1119
|
+
} else if (result.blocked) {
|
|
1120
|
+
log(` HOLD ${key} \u2014 ${backend}'s fix was blocked, not pushed: ${result.blocked}`);
|
|
1121
|
+
} else if (!result.pushed) {
|
|
1122
|
+
log(` HOLD ${key} \u2014 ${backend} made no changes (declined to fix with confidence)`);
|
|
1123
|
+
} else {
|
|
1124
|
+
log(` PUSHED ${key} \u2014 ${backend} pushed a fix to ${pr.headRefName}; awaiting next CI run`);
|
|
1125
|
+
}
|
|
1126
|
+
} else {
|
|
1127
|
+
log(` (dry-run \u2014 would invoke ${backend} in a local checkout)`);
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function codexHasCredentials(home = homedir()) {
|
|
1135
|
+
try {
|
|
1136
|
+
const raw = readFileSync(path.join(home, ".codex", "auth.json"), "utf8");
|
|
1137
|
+
return raw.trim().length > 0;
|
|
1138
|
+
} catch {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async function availableFixers() {
|
|
1143
|
+
const out = [];
|
|
1144
|
+
for (const backend of FIXER_BACKENDS) {
|
|
1145
|
+
try {
|
|
1146
|
+
await run("command", ["-v", backend.command]).catch(async () => {
|
|
1147
|
+
await run("which", [backend.command]);
|
|
1148
|
+
});
|
|
1149
|
+
} catch {
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (backend.id === "glm") {
|
|
1153
|
+
if (!resolveGlmAuthToken()) continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (backend.id === "opencode" && !process.env.OPENCODE_API_KEY) continue;
|
|
1156
|
+
if (backend.id === "codex" && !codexHasCredentials()) continue;
|
|
1157
|
+
out.push(backend.id);
|
|
1158
|
+
}
|
|
1159
|
+
return out;
|
|
1160
|
+
}
|
|
1161
|
+
async function runOnce(repos, live) {
|
|
1162
|
+
log(`=== exe-cicd pass (${live ? "LIVE" : "dry-run"}) load=${(loadavg()[0] ?? 0).toFixed(2)} ===`);
|
|
1163
|
+
const fixers = await availableFixers();
|
|
1164
|
+
log(`fixer backends available: ${fixers.length ? fixers.join(", ") : "none"}`);
|
|
1165
|
+
for (const repo of repos) {
|
|
1166
|
+
await processRepo(repo.trim(), live, fixers);
|
|
1167
|
+
}
|
|
1168
|
+
log("=== exe-cicd pass end ===");
|
|
1169
|
+
}
|
|
1170
|
+
async function main() {
|
|
1171
|
+
const args = process.argv.slice(2);
|
|
1172
|
+
const live = args.includes("--live");
|
|
1173
|
+
const watch = args.includes("--watch");
|
|
1174
|
+
const repoArg = args.find((a) => a.startsWith("--repos="));
|
|
1175
|
+
const repos = repoArg ? repoArg.slice("--repos=".length).split(",") : DEFAULT_REPOS;
|
|
1176
|
+
const intervalSec = Number(
|
|
1177
|
+
args.find((a) => a.startsWith("--interval="))?.slice("--interval=".length) ?? "300"
|
|
1178
|
+
);
|
|
1179
|
+
if (!watch) {
|
|
1180
|
+
await runOnce(repos, live);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
log(`watch mode: every ${intervalSec}s over ${repos.length} repo(s)`);
|
|
1184
|
+
for (; ; ) {
|
|
1185
|
+
try {
|
|
1186
|
+
await runOnce(repos, live);
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
log(`pass failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
1189
|
+
}
|
|
1190
|
+
await new Promise((r) => setTimeout(r, Math.max(30, intervalSec) * 1e3));
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
var isEntrypoint = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1194
|
+
if (isEntrypoint) {
|
|
1195
|
+
main().catch((err) => {
|
|
1196
|
+
log(`FATAL ${err instanceof Error ? err.message : String(err)}`);
|
|
1197
|
+
process.exitCode = 1;
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
export {
|
|
1201
|
+
attemptsForHead,
|
|
1202
|
+
codexHasCredentials,
|
|
1203
|
+
commitAndPushFix,
|
|
1204
|
+
copyCodexCredentials,
|
|
1205
|
+
defaultCleanupFixerHome,
|
|
1206
|
+
ensureCheckout,
|
|
1207
|
+
runFixerBackend,
|
|
1208
|
+
runLiveEscalation,
|
|
1209
|
+
runSkylosScan
|
|
1210
|
+
};
|