chainlesschain 0.162.148 → 0.162.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-BJif14yR.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-CXek6gJY.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-BgQhdAJi.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-DTYl5NtR.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-BzATQAeR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-BzdPEQhL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-BM0KVh8E.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-D7fHXHkz.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Wm34RR-b.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CAiSQ9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-Df40CrEe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-DIT2fNFU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-CSTIbW2k.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-DtqXZZUi.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-CUtzw6o7.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-CezmlnmU.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-P4BITarg.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-BRRiYwvq.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-L5ijB9i4.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard-CFxro4ob.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BmAFCQ71.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-DJBCEuon.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-DsVCpHMF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DP3bP5th.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-I3g_bAw9.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-jHS5-ioS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-CPL7sfx9.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-Jf236h4C.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-Cm2tcSKg.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-CZK82rhi.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-CZCeji7a.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-3l2KVS9k.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-VYdpoLh6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-CiB9cmm1.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-wCFZbBuL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-1a3THIyG.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-DMzLB2hG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-D9wZbf6l.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-hrFe2ZM-.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-CHGX5Jwm.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
- package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-By7FkhtY.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow--khGSzJn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-CAG9dH35.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-D35ec6JA.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CxBF7hW_.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-s6EtOO1s.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-BbJYmWmO.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-VGQq1jMT.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-B1ew3Teh.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-mrO47FIK.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-IJF2mHyw.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CU0wN_Z0.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-DhN37R6G.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-D_xOX_hu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-D_Ssv7uH.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-Kx5dKxX7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-BlGwqZkY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-hLFQzxpb.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DMlvsVt4.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-Bd0Qsj2m.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-DFclq9X3.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-BNtln2qY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-Dch5H19G.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-2UfQcU1g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-CD9YQr4i.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-BNu-7MzI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-D1n9CaIR.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-DenL3iBW.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-D87IfukO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BkiZc8E3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-BNRSytGN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-Bwbd4X2m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-B8O2bs0o.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-C2fL8zmH.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-BORYDdE1.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-C0V5S2FM.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-2dR0fcHL.js} +1 -1
- package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-BmuAFAn8.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-CN3smMcK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-DCnxb4kW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-vlR43pHn.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-D04tHYFd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-0DnaZGkC.js} +1 -1
- package/src/assets/web-panel/assets/{index-308gCGh7.js → index-430S68MN.js} +1 -1
- package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-B78xL3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BTPEZTk1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-BXRg8GFQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-BasvsWDM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-BeGDEXFs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-BqhASEL4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-BudvKQfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-C-6NO5il.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-C5Sxb1sE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-CP9m9Ggr.js} +3 -3
- package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-CTOCnIQ7.js} +1 -1
- package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
- package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-ClhAHDK3.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-CnXebZLL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-CngCC8hC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-Cr-A0FYJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-CsAsCPJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-D28DeAAB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-D5LZVZ0L.js} +1 -1
- package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-D65_XseV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-D8vwp4qp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-DDwLgQY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-DIpY0qM5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-DTWg-18U.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-DTtRscUa.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-DYAtmqiv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-D_n8_vfI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahryTX0.js → index-DbADHwhr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-DbVsQBOH.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
- package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-HhNjnCfe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-ToUh2b1-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DISWAYce.js → index-TslMTwNy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-_FIkN3jd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-hpvvtAZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-jzR7Ujuw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-ljuBiQW9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-C3wUb4je.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D-jYFUNA.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-tqb8nwJ2.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DSOjMJMz.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-CJ7VimIW.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-qXiG1iT3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-CkGBcy1Z.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-D3WvTlLO.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BEE2ftge.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-CsfBpxiG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-xPrIiJPI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-C5Yd-SCR.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-D_MDS8HI.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub-BqJh-usA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-chm_p-gz.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-3-YDNz0Z.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/assets/web-panel/remote-session-sw.js +43 -0
- package/src/command-manifest.json +22 -1
- package/src/commands/agent.js +44 -5
- package/src/commands/codeintel.js +252 -0
- package/src/commands/eval.js +248 -0
- package/src/commands/plugin.js +354 -0
- package/src/commands/team.js +456 -0
- package/src/gateways/ws/message-dispatcher.js +45 -2
- package/src/gateways/ws/remote-session-protocol.js +37 -0
- package/src/gateways/ws/ws-server.js +1 -1
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +158 -0
- package/src/harness/remote-session-push-apns.js +246 -0
- package/src/harness/remote-session-push-errors.js +15 -0
- package/src/harness/remote-session-push-fcm.js +9 -25
- package/src/harness/remote-session-push-huawei.js +174 -0
- package/src/harness/remote-session-push-oppo.js +171 -0
- package/src/harness/remote-session-push-senders.js +42 -0
- package/src/harness/remote-session-push-vivo.js +179 -0
- package/src/harness/remote-session-push-web.js +299 -0
- package/src/harness/remote-session-push-xiaomi.js +99 -0
- package/src/index.js +6 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +161 -5
- package/src/lib/agent-team/task-lease.js +434 -0
- package/src/lib/agent-team/team-budget.js +165 -0
- package/src/lib/agent-team/team-mailbox.js +106 -0
- package/src/lib/agent-team/team-runner.js +266 -0
- package/src/lib/agent-team/team-worktree.js +204 -0
- package/src/lib/agents.js +18 -0
- package/src/lib/async-hook-supervisor.cjs +305 -0
- package/src/lib/eval/runner.js +163 -0
- package/src/lib/eval/tasks.js +456 -0
- package/src/lib/eval/trend.js +185 -0
- package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
- package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
- package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +258 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
- package/src/lib/lsp/code-intelligence.js +356 -0
- package/src/lib/lsp/jsonrpc-stream.js +139 -0
- package/src/lib/lsp/lsp-client.js +318 -0
- package/src/lib/lsp/lsp-manager.js +323 -0
- package/src/lib/lsp/lsp-server-registry.js +196 -0
- package/src/lib/plugin-monitor-supervisor.js +238 -0
- package/src/lib/plugin-runtime/agents.js +51 -0
- package/src/lib/plugin-runtime/bin.js +100 -0
- package/src/lib/plugin-runtime/hooks.js +100 -0
- package/src/lib/plugin-runtime/install.js +388 -0
- package/src/lib/plugin-runtime/lsp.js +75 -0
- package/src/lib/plugin-runtime/manifest.js +458 -0
- package/src/lib/plugin-runtime/mcp.js +89 -0
- package/src/lib/plugin-runtime/monitors.js +100 -0
- package/src/lib/plugin-runtime/policy.js +144 -0
- package/src/lib/plugin-runtime/reload.js +79 -0
- package/src/lib/plugin-runtime/scopes.js +197 -0
- package/src/lib/plugin-runtime/settings.js +119 -0
- package/src/lib/plugin-runtime/signature.js +107 -0
- package/src/lib/plugin-runtime/skills.js +43 -0
- package/src/lib/plugin-runtime/trust.js +140 -0
- package/src/lib/sandbox-egress-proxy.js +162 -0
- package/src/lib/sandbox-network-policy.js +134 -0
- package/src/lib/sandbox-v2.js +10 -4
- package/src/lib/settings-hook-events.cjs +78 -6
- package/src/lib/settings-hooks.cjs +29 -3
- package/src/lib/settings-loader.cjs +35 -1
- package/src/lib/skill-loader.js +18 -0
- package/src/lib/telemetry/span-recorder.js +237 -0
- package/src/repl/agent-repl.js +400 -4
- package/src/runtime/agent-core.js +546 -52
- package/src/runtime/coding-agent-contract-shared.cjs +82 -8
- package/src/runtime/coding-agent-policy.cjs +32 -7
- package/src/runtime/fallback-model.js +134 -7
- package/src/runtime/headless-runner.js +353 -108
- package/src/runtime/mcp-config.js +46 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DIp8Xcbd.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dpid9CDg.js +0 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in eval task suite (Phase 7). Each task is a small, deterministic,
|
|
3
|
+
* self-checking coding problem: `setup` writes the starting files, `prompt` is
|
|
4
|
+
* handed to the agent, and `check` verifies the outcome with a real filesystem
|
|
5
|
+
* assertion (no LLM-graded fuzzy matching — pass/fail is objective).
|
|
6
|
+
*
|
|
7
|
+
* Kept intentionally small + provider-agnostic so `cc eval` can run against any
|
|
8
|
+
* configured model (including a local Ollama). Extend by adding entries here or
|
|
9
|
+
* loading an external suite file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
function read(dir, rel) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.readFileSync(path.join(dir, rel), "utf8");
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const BUILTIN_TASKS = [
|
|
25
|
+
{
|
|
26
|
+
id: "create-file",
|
|
27
|
+
description: "Create a new file with exact content",
|
|
28
|
+
prompt:
|
|
29
|
+
'Create a file named greeting.txt containing exactly the text "Hello, ChainlessChain!" (no trailing newline).',
|
|
30
|
+
setup: () => {},
|
|
31
|
+
check: (dir) => {
|
|
32
|
+
const got = read(dir, "greeting.txt");
|
|
33
|
+
if (got == null)
|
|
34
|
+
return { pass: false, detail: "greeting.txt not created" };
|
|
35
|
+
const trimmed = got.replace(/\n$/, "");
|
|
36
|
+
return trimmed === "Hello, ChainlessChain!"
|
|
37
|
+
? { pass: true, detail: "content matches" }
|
|
38
|
+
: { pass: false, detail: `content was ${JSON.stringify(got)}` };
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "fix-syntax-error",
|
|
43
|
+
description: "Fix a syntax error so the script runs",
|
|
44
|
+
prompt:
|
|
45
|
+
"The file bug.js has a syntax error. Fix it so that running `node bug.js` prints exactly OK.",
|
|
46
|
+
setup: (dir) => {
|
|
47
|
+
// Missing closing paren — a real, small bug to repair.
|
|
48
|
+
fs.writeFileSync(path.join(dir, "bug.js"), 'console.log("OK";\n', "utf8");
|
|
49
|
+
},
|
|
50
|
+
check: (dir) => {
|
|
51
|
+
const src = read(dir, "bug.js");
|
|
52
|
+
if (src == null) return { pass: false, detail: "bug.js missing" };
|
|
53
|
+
// Objective check: it must parse AND print OK. Re-run in a child so we
|
|
54
|
+
// score the actual behavior, not a heuristic on the source.
|
|
55
|
+
try {
|
|
56
|
+
const out = execFileSync(process.execPath, ["bug.js"], {
|
|
57
|
+
cwd: dir,
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
timeout: 10000,
|
|
60
|
+
stdio: ["ignore", "pipe", "pipe"], // capture stderr, don't inherit
|
|
61
|
+
});
|
|
62
|
+
return out.trim() === "OK"
|
|
63
|
+
? { pass: true, detail: "node bug.js prints OK" }
|
|
64
|
+
: { pass: false, detail: `printed ${JSON.stringify(out.trim())}` };
|
|
65
|
+
} catch (err) {
|
|
66
|
+
// Concise detail — the first line of the failure, not the whole stack.
|
|
67
|
+
const first = String(err.message || "").split("\n")[0];
|
|
68
|
+
return { pass: false, detail: `still broken: ${first}` };
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "add-function",
|
|
74
|
+
description: "Add a function to an existing module",
|
|
75
|
+
prompt:
|
|
76
|
+
"In math.js, add a named export `add(a, b)` that returns a + b. Keep the existing `mul` export.",
|
|
77
|
+
setup: (dir) => {
|
|
78
|
+
fs.writeFileSync(
|
|
79
|
+
path.join(dir, "math.js"),
|
|
80
|
+
"export function mul(a, b) {\n return a * b;\n}\n",
|
|
81
|
+
"utf8",
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
check: async (dir) => {
|
|
85
|
+
const src = read(dir, "math.js");
|
|
86
|
+
if (src == null) return { pass: false, detail: "math.js missing" };
|
|
87
|
+
if (!/export\s+function\s+mul/.test(src)) {
|
|
88
|
+
return { pass: false, detail: "mul export was removed" };
|
|
89
|
+
}
|
|
90
|
+
// Import the module and exercise both exports — behavioral, not textual.
|
|
91
|
+
try {
|
|
92
|
+
const url = "file://" + path.join(dir, "math.js").replace(/\\/g, "/");
|
|
93
|
+
const mod = await import(url);
|
|
94
|
+
if (typeof mod.add !== "function") {
|
|
95
|
+
return { pass: false, detail: "add is not exported as a function" };
|
|
96
|
+
}
|
|
97
|
+
if (mod.add(2, 3) !== 5 || mod.mul(2, 3) !== 6) {
|
|
98
|
+
return { pass: false, detail: "add/mul returned wrong values" };
|
|
99
|
+
}
|
|
100
|
+
return { pass: true, detail: "add + mul both work" };
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return { pass: false, detail: `import failed: ${err.message}` };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "fix-failing-test",
|
|
108
|
+
description:
|
|
109
|
+
"Fix a bug so an existing test suite passes (bug-fix category)",
|
|
110
|
+
prompt:
|
|
111
|
+
"calc.js has a bug — `sum` subtracts instead of adds. Fix calc.js so that " +
|
|
112
|
+
"running `node run-checks.mjs` passes (it exits 0 and prints ALL OK). Do " +
|
|
113
|
+
"not edit run-checks.mjs.",
|
|
114
|
+
setup: (dir) => {
|
|
115
|
+
// A buggy module + a real check harness that exercises it. The agent must
|
|
116
|
+
// repair the MODULE, not the checks — verified by actually running them.
|
|
117
|
+
fs.writeFileSync(
|
|
118
|
+
path.join(dir, "calc.js"),
|
|
119
|
+
"export function sum(a, b) {\n return a - b;\n}\n",
|
|
120
|
+
"utf8",
|
|
121
|
+
);
|
|
122
|
+
fs.writeFileSync(
|
|
123
|
+
path.join(dir, "run-checks.mjs"),
|
|
124
|
+
[
|
|
125
|
+
"import { sum } from './calc.js';",
|
|
126
|
+
"const cases = [[1,2,3],[10,5,15],[0,0,0]];",
|
|
127
|
+
"for (const [a,b,want] of cases) {",
|
|
128
|
+
" if (sum(a,b) !== want) {",
|
|
129
|
+
" console.error(`FAIL sum(${a},${b})=${sum(a,b)} want ${want}`);",
|
|
130
|
+
" process.exit(1);",
|
|
131
|
+
" }",
|
|
132
|
+
"}",
|
|
133
|
+
"console.log('ALL OK');",
|
|
134
|
+
"",
|
|
135
|
+
].join("\n"),
|
|
136
|
+
"utf8",
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
check: (dir) => {
|
|
140
|
+
// Objective: the test harness must actually pass. Guard against the agent
|
|
141
|
+
// "fixing" it by neutering run-checks.mjs — require the assertions to
|
|
142
|
+
// still be present.
|
|
143
|
+
const checks = read(dir, "run-checks.mjs");
|
|
144
|
+
if (checks == null)
|
|
145
|
+
return { pass: false, detail: "run-checks.mjs missing" };
|
|
146
|
+
if (!/sum\(a,\s*b\)\s*!==\s*want/.test(checks)) {
|
|
147
|
+
return {
|
|
148
|
+
pass: false,
|
|
149
|
+
detail: "run-checks.mjs assertions were altered",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const out = execFileSync(process.execPath, ["run-checks.mjs"], {
|
|
154
|
+
cwd: dir,
|
|
155
|
+
encoding: "utf8",
|
|
156
|
+
timeout: 10000,
|
|
157
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
158
|
+
});
|
|
159
|
+
return out.includes("ALL OK")
|
|
160
|
+
? { pass: true, detail: "test suite passes" }
|
|
161
|
+
: {
|
|
162
|
+
pass: false,
|
|
163
|
+
detail: `unexpected output ${JSON.stringify(out.trim())}`,
|
|
164
|
+
};
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const first = String(err.message || "").split("\n")[0];
|
|
167
|
+
return { pass: false, detail: `checks still failing: ${first}` };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "refactor-rename",
|
|
173
|
+
description:
|
|
174
|
+
"Rename a symbol across two files, keeping behavior (refactor)",
|
|
175
|
+
prompt:
|
|
176
|
+
"Rename the exported function `oldTotal` to `computeTotal` EVERYWHERE it " +
|
|
177
|
+
"appears (both util.js which defines it and main.js which imports and " +
|
|
178
|
+
"calls it). Behavior must be unchanged: `node main.js` must still print 6.",
|
|
179
|
+
setup: (dir) => {
|
|
180
|
+
fs.writeFileSync(
|
|
181
|
+
path.join(dir, "util.js"),
|
|
182
|
+
"export function oldTotal(nums) {\n return nums.reduce((a, b) => a + b, 0);\n}\n",
|
|
183
|
+
"utf8",
|
|
184
|
+
);
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
path.join(dir, "main.js"),
|
|
187
|
+
"import { oldTotal } from './util.js';\nconsole.log(oldTotal([1, 2, 3]));\n",
|
|
188
|
+
"utf8",
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
check: (dir) => {
|
|
192
|
+
const util = read(dir, "util.js");
|
|
193
|
+
const main = read(dir, "main.js");
|
|
194
|
+
if (util == null || main == null) {
|
|
195
|
+
return { pass: false, detail: "util.js or main.js missing" };
|
|
196
|
+
}
|
|
197
|
+
// The old name must be gone from BOTH files (a real rename, not an alias).
|
|
198
|
+
if (/\boldTotal\b/.test(util) || /\boldTotal\b/.test(main)) {
|
|
199
|
+
return {
|
|
200
|
+
pass: false,
|
|
201
|
+
detail: "oldTotal still present (incomplete rename)",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (!/\bcomputeTotal\b/.test(util) || !/\bcomputeTotal\b/.test(main)) {
|
|
205
|
+
return { pass: false, detail: "computeTotal not used in both files" };
|
|
206
|
+
}
|
|
207
|
+
// Behavior must be preserved: run main.js and check the output.
|
|
208
|
+
try {
|
|
209
|
+
const out = execFileSync(process.execPath, ["main.js"], {
|
|
210
|
+
cwd: dir,
|
|
211
|
+
encoding: "utf8",
|
|
212
|
+
timeout: 10000,
|
|
213
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
214
|
+
});
|
|
215
|
+
return out.trim() === "6"
|
|
216
|
+
? { pass: true, detail: "renamed + behavior preserved" }
|
|
217
|
+
: {
|
|
218
|
+
pass: false,
|
|
219
|
+
detail: `main.js printed ${JSON.stringify(out.trim())}`,
|
|
220
|
+
};
|
|
221
|
+
} catch (err) {
|
|
222
|
+
const first = String(err.message || "").split("\n")[0];
|
|
223
|
+
return { pass: false, detail: `main.js broken after rename: ${first}` };
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "write-test",
|
|
229
|
+
description:
|
|
230
|
+
"Write a meaningful verifier for a module (test-completion category)",
|
|
231
|
+
prompt:
|
|
232
|
+
"Write a Node script `verify.mjs` that imports `slugify` from `./slug.mjs` " +
|
|
233
|
+
"and checks it: exit 0 if slugify('Hello World') === 'hello-world' AND " +
|
|
234
|
+
"slugify(' A_B ') === 'a-b' (lowercase; runs of spaces/underscores become a " +
|
|
235
|
+
"single hyphen; trimmed), otherwise print what failed and exit 1. Do not " +
|
|
236
|
+
"modify slug.mjs.",
|
|
237
|
+
setup: (dir) => {
|
|
238
|
+
fs.writeFileSync(
|
|
239
|
+
path.join(dir, "slug.mjs"),
|
|
240
|
+
"export function slugify(s) {\n" +
|
|
241
|
+
" return String(s).trim().toLowerCase().replace(/[\\s_]+/g, '-');\n" +
|
|
242
|
+
"}\n",
|
|
243
|
+
"utf8",
|
|
244
|
+
);
|
|
245
|
+
},
|
|
246
|
+
check: (dir) => {
|
|
247
|
+
const slug = read(dir, "slug.mjs");
|
|
248
|
+
if (slug == null) return { pass: false, detail: "slug.mjs missing" };
|
|
249
|
+
if (!/export\s+function\s+slugify/.test(slug)) {
|
|
250
|
+
return { pass: false, detail: "slug.mjs export was changed" };
|
|
251
|
+
}
|
|
252
|
+
if (read(dir, "verify.mjs") == null) {
|
|
253
|
+
return { pass: false, detail: "verify.mjs not created" };
|
|
254
|
+
}
|
|
255
|
+
const run = () =>
|
|
256
|
+
execFileSync(process.execPath, ["verify.mjs"], {
|
|
257
|
+
cwd: dir,
|
|
258
|
+
timeout: 10000,
|
|
259
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
260
|
+
});
|
|
261
|
+
// 1) The verifier must PASS against the correct implementation.
|
|
262
|
+
try {
|
|
263
|
+
run();
|
|
264
|
+
} catch {
|
|
265
|
+
return {
|
|
266
|
+
pass: false,
|
|
267
|
+
detail: "verify.mjs failed against the correct implementation",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// 2) It must FAIL against a broken slugify — otherwise it's a no-op test
|
|
271
|
+
// that would rubber-stamp any implementation.
|
|
272
|
+
const good = slug;
|
|
273
|
+
try {
|
|
274
|
+
fs.writeFileSync(
|
|
275
|
+
path.join(dir, "slug.mjs"),
|
|
276
|
+
"export function slugify(s) {\n return String(s);\n}\n", // identity = wrong
|
|
277
|
+
"utf8",
|
|
278
|
+
);
|
|
279
|
+
let caughtFailure = false;
|
|
280
|
+
try {
|
|
281
|
+
run();
|
|
282
|
+
} catch {
|
|
283
|
+
caughtFailure = true;
|
|
284
|
+
}
|
|
285
|
+
if (!caughtFailure) {
|
|
286
|
+
return {
|
|
287
|
+
pass: false,
|
|
288
|
+
detail:
|
|
289
|
+
"verify.mjs passed even a broken slugify — the test is not meaningful",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
} finally {
|
|
293
|
+
fs.writeFileSync(path.join(dir, "slug.mjs"), good, "utf8"); // restore
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
pass: true,
|
|
297
|
+
detail: "verifier passes on the correct impl and catches a broken one",
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "migrate-signature",
|
|
303
|
+
description:
|
|
304
|
+
"Migrate a function's call convention and update all callers (api-upgrade category)",
|
|
305
|
+
prompt:
|
|
306
|
+
"In greet.mjs, change the exported `greet` to take a single options object " +
|
|
307
|
+
"`greet({ name, excited })`, returning `Hello, <name>!` normally or " +
|
|
308
|
+
"`Hello, <name>!!!` when `excited` is true. Update ALL callers in app.mjs to " +
|
|
309
|
+
"the new signature. Running `node app.mjs` must print exactly two lines:\n" +
|
|
310
|
+
"Hello, Ada!\nHello, Bob!!!",
|
|
311
|
+
setup: (dir) => {
|
|
312
|
+
fs.writeFileSync(
|
|
313
|
+
path.join(dir, "greet.mjs"),
|
|
314
|
+
"export function greet(name, excited) {\n" +
|
|
315
|
+
" return `Hello, ${name}!` + (excited ? '!!' : '');\n" +
|
|
316
|
+
"}\n",
|
|
317
|
+
"utf8",
|
|
318
|
+
);
|
|
319
|
+
fs.writeFileSync(
|
|
320
|
+
path.join(dir, "app.mjs"),
|
|
321
|
+
"import { greet } from './greet.mjs';\n" +
|
|
322
|
+
"console.log(greet('Ada', false));\n" +
|
|
323
|
+
"console.log(greet('Bob', true));\n",
|
|
324
|
+
"utf8",
|
|
325
|
+
);
|
|
326
|
+
},
|
|
327
|
+
check: (dir) => {
|
|
328
|
+
const g = read(dir, "greet.mjs");
|
|
329
|
+
const a = read(dir, "app.mjs");
|
|
330
|
+
if (g == null || a == null)
|
|
331
|
+
return { pass: false, detail: "greet.mjs / app.mjs missing" };
|
|
332
|
+
// New signature: greet must destructure an object parameter.
|
|
333
|
+
if (!/greet\s*\(\s*\{/.test(g)) {
|
|
334
|
+
return {
|
|
335
|
+
pass: false,
|
|
336
|
+
detail: "greet was not migrated to an options object",
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
// No caller may still pass the old positional (string-first) arguments.
|
|
340
|
+
if (/greet\(\s*['"`]/.test(a)) {
|
|
341
|
+
return {
|
|
342
|
+
pass: false,
|
|
343
|
+
detail: "a caller still uses the old positional signature",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const out = execFileSync(process.execPath, ["app.mjs"], {
|
|
348
|
+
cwd: dir,
|
|
349
|
+
encoding: "utf8",
|
|
350
|
+
timeout: 10000,
|
|
351
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
352
|
+
}).trim();
|
|
353
|
+
return out === "Hello, Ada!\nHello, Bob!!!"
|
|
354
|
+
? { pass: true, detail: "signature migrated + callers updated" }
|
|
355
|
+
: { pass: false, detail: `app.mjs printed ${JSON.stringify(out)}` };
|
|
356
|
+
} catch (err) {
|
|
357
|
+
const first = String(err.message || "").split("\n")[0];
|
|
358
|
+
return { pass: false, detail: `app.mjs failed: ${first}` };
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: "secure-path",
|
|
364
|
+
description:
|
|
365
|
+
"Fix a path-traversal vulnerability, verified by an exploit probe (security-fix category)",
|
|
366
|
+
prompt:
|
|
367
|
+
"notes.mjs's `readNote` is vulnerable to path traversal — a name like " +
|
|
368
|
+
"'../secret.txt' escapes the notes directory. Fix `readNote` so it can ONLY " +
|
|
369
|
+
"read files inside the notes directory (reject or neutralize both '../' and " +
|
|
370
|
+
"absolute paths), while `readNote('hello.txt')` still returns 'hi'. Keep the " +
|
|
371
|
+
"export name and signature.",
|
|
372
|
+
setup: (dir) => {
|
|
373
|
+
fs.mkdirSync(path.join(dir, "notes"), { recursive: true });
|
|
374
|
+
fs.writeFileSync(path.join(dir, "notes", "hello.txt"), "hi", "utf8");
|
|
375
|
+
// The secret lives OUTSIDE notes/ — a fixed readNote must never reach it.
|
|
376
|
+
fs.writeFileSync(path.join(dir, "secret.txt"), "TOP_SECRET", "utf8");
|
|
377
|
+
// BASE is anchored to the module's own dir (not process.cwd()) so the
|
|
378
|
+
// check can import it from anywhere.
|
|
379
|
+
fs.writeFileSync(
|
|
380
|
+
path.join(dir, "notes.mjs"),
|
|
381
|
+
"import fs from 'node:fs';\n" +
|
|
382
|
+
"import path from 'node:path';\n" +
|
|
383
|
+
"import { fileURLToPath } from 'node:url';\n" +
|
|
384
|
+
"const BASE = path.join(path.dirname(fileURLToPath(import.meta.url)), 'notes');\n" +
|
|
385
|
+
"// VULNERABLE: `name` can contain ../ (or be absolute) and escape BASE.\n" +
|
|
386
|
+
"export function readNote(name) {\n" +
|
|
387
|
+
" return fs.readFileSync(path.join(BASE, name), 'utf8');\n" +
|
|
388
|
+
"}\n",
|
|
389
|
+
"utf8",
|
|
390
|
+
);
|
|
391
|
+
},
|
|
392
|
+
check: async (dir) => {
|
|
393
|
+
const src = read(dir, "notes.mjs");
|
|
394
|
+
if (src == null) return { pass: false, detail: "notes.mjs missing" };
|
|
395
|
+
if (!/export\s+function\s+readNote/.test(src)) {
|
|
396
|
+
return { pass: false, detail: "readNote export was removed/renamed" };
|
|
397
|
+
}
|
|
398
|
+
let mod;
|
|
399
|
+
try {
|
|
400
|
+
const url = "file://" + path.join(dir, "notes.mjs").replace(/\\/g, "/");
|
|
401
|
+
mod = await import(url);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
return { pass: false, detail: `import failed: ${err.message}` };
|
|
404
|
+
}
|
|
405
|
+
// 1) Behavior: a legitimate read still works.
|
|
406
|
+
let hi;
|
|
407
|
+
try {
|
|
408
|
+
hi = mod.readNote("hello.txt");
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return {
|
|
411
|
+
pass: false,
|
|
412
|
+
detail: `readNote('hello.txt') threw: ${err.message}`,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (String(hi).trim() !== "hi") {
|
|
416
|
+
return {
|
|
417
|
+
pass: false,
|
|
418
|
+
detail: `readNote('hello.txt') = ${JSON.stringify(hi)}`,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// 2) Exploit probe — relative traversal must NOT leak the secret.
|
|
422
|
+
let leaked = null;
|
|
423
|
+
try {
|
|
424
|
+
leaked = mod.readNote("../secret.txt");
|
|
425
|
+
} catch {
|
|
426
|
+
leaked = null; // throwing is a valid way to reject
|
|
427
|
+
}
|
|
428
|
+
if (leaked != null && String(leaked).includes("TOP_SECRET")) {
|
|
429
|
+
return {
|
|
430
|
+
pass: false,
|
|
431
|
+
detail: "relative traversal still reads files outside notes/",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
// 3) Exploit probe — an absolute path must NOT escape either.
|
|
435
|
+
let leaked2 = null;
|
|
436
|
+
try {
|
|
437
|
+
leaked2 = mod.readNote(path.join(dir, "secret.txt"));
|
|
438
|
+
} catch {
|
|
439
|
+
leaked2 = null;
|
|
440
|
+
}
|
|
441
|
+
if (leaked2 != null && String(leaked2).includes("TOP_SECRET")) {
|
|
442
|
+
return { pass: false, detail: "absolute path escapes notes/" };
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
pass: true,
|
|
446
|
+
detail: "traversal neutralized, legitimate read preserved",
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
/** Look up a suite by name (only "builtin" for now). */
|
|
453
|
+
export function getSuite(name = "builtin") {
|
|
454
|
+
if (name === "builtin" || !name) return BUILTIN_TASKS;
|
|
455
|
+
throw new Error(`unknown eval suite: "${name}" (available: builtin)`);
|
|
456
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eval trend analysis (Phase 7) — turn a history of `cc eval` runs into a
|
|
3
|
+
* release-gate signal: is the task-success rate trending down, and did any task
|
|
4
|
+
* that used to pass start failing (a regression)?
|
|
5
|
+
*
|
|
6
|
+
* The eval RUNS themselves need a real model (a dry-run is always 0%), but the
|
|
7
|
+
* REPORTING is pure: given the recorded per-run summaries (pass-rate + which
|
|
8
|
+
* tasks passed), computing the delta, per-task regressions, and the gate verdict
|
|
9
|
+
* is deterministic and unit-testable with fixtures — which is what a release
|
|
10
|
+
* pipeline actually wires up. This module is that consumer.
|
|
11
|
+
*
|
|
12
|
+
* A run record is the JSON `cc eval --json` emits, optionally tagged with
|
|
13
|
+
* `ranAt` / `label` (version or commit). `perTask` is derived from `results`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Normalize one `cc eval` summary into a compact trend record. */
|
|
17
|
+
export function summarizeRun(record, { ranAt = null, label = null } = {}) {
|
|
18
|
+
const results = Array.isArray(record?.results) ? record.results : [];
|
|
19
|
+
const perTask = {};
|
|
20
|
+
for (const r of results) {
|
|
21
|
+
if (r && r.id != null) perTask[r.id] = r.pass === true;
|
|
22
|
+
}
|
|
23
|
+
const total = Number.isFinite(record?.total) ? record.total : results.length;
|
|
24
|
+
const passed = Number.isFinite(record?.passed)
|
|
25
|
+
? record.passed
|
|
26
|
+
: results.filter((r) => r && r.pass === true).length;
|
|
27
|
+
const passRate = Number.isFinite(record?.passRate)
|
|
28
|
+
? record.passRate
|
|
29
|
+
: total > 0
|
|
30
|
+
? passed / total
|
|
31
|
+
: 0;
|
|
32
|
+
return {
|
|
33
|
+
ranAt: record?.ranAt ?? ranAt,
|
|
34
|
+
label: record?.label ?? label,
|
|
35
|
+
passed,
|
|
36
|
+
total,
|
|
37
|
+
passRate,
|
|
38
|
+
perTask,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Stable chronological order: by `ranAt` when present, else preserve input order. */
|
|
43
|
+
function ordered(records) {
|
|
44
|
+
const withIdx = records.map((r, i) => ({ r, i }));
|
|
45
|
+
withIdx.sort((a, b) => {
|
|
46
|
+
const ax = a.r.ranAt,
|
|
47
|
+
bx = b.r.ranAt;
|
|
48
|
+
if (ax != null && bx != null && ax !== bx) return ax < bx ? -1 : 1;
|
|
49
|
+
if (ax != null && bx == null) return 1; // timestamped runs sort after untimestamped
|
|
50
|
+
if (ax == null && bx != null) return -1;
|
|
51
|
+
return a.i - b.i; // stable fallback
|
|
52
|
+
});
|
|
53
|
+
return withIdx.map((x) => x.r);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compare the latest run against the previous one and report the trend.
|
|
58
|
+
*
|
|
59
|
+
* @param {object[]} runs array of run records (raw `cc eval` summaries or
|
|
60
|
+
* already-summarized); ordered by `ranAt` when available, else input order.
|
|
61
|
+
* @param {object} [opts]
|
|
62
|
+
* @param {number} [opts.regressionThreshold] pass-rate drop (0..1) that trips the
|
|
63
|
+
* gate on its own, independent of per-task regressions. Default 0 (any drop or
|
|
64
|
+
* any newly-failing task trips it).
|
|
65
|
+
* @returns {{
|
|
66
|
+
* runs:number, latest:object|null, previous:object|null, delta:number,
|
|
67
|
+
* direction:"up"|"down"|"flat"|"n/a", regressions:string[], fixed:string[],
|
|
68
|
+
* newlyMissing:string[], regressed:boolean, history:number[]
|
|
69
|
+
* }}
|
|
70
|
+
*/
|
|
71
|
+
export function computeTrend(runs, { regressionThreshold = 0 } = {}) {
|
|
72
|
+
const norm = ordered((runs || []).map((r) => summarizeRun(r)));
|
|
73
|
+
const history = norm.map((r) => r.passRate);
|
|
74
|
+
if (norm.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
runs: 0,
|
|
77
|
+
latest: null,
|
|
78
|
+
previous: null,
|
|
79
|
+
delta: 0,
|
|
80
|
+
direction: "n/a",
|
|
81
|
+
regressions: [],
|
|
82
|
+
fixed: [],
|
|
83
|
+
newlyMissing: [],
|
|
84
|
+
regressed: false,
|
|
85
|
+
history,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const latest = norm[norm.length - 1];
|
|
89
|
+
const previous = norm.length >= 2 ? norm[norm.length - 2] : null;
|
|
90
|
+
if (!previous) {
|
|
91
|
+
return {
|
|
92
|
+
runs: norm.length,
|
|
93
|
+
latest,
|
|
94
|
+
previous: null,
|
|
95
|
+
delta: 0,
|
|
96
|
+
direction: "n/a",
|
|
97
|
+
regressions: [],
|
|
98
|
+
fixed: [],
|
|
99
|
+
newlyMissing: [],
|
|
100
|
+
regressed: false,
|
|
101
|
+
history,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const delta = latest.passRate - previous.passRate;
|
|
105
|
+
const regressions = []; // passed before, fails now
|
|
106
|
+
const fixed = []; // failed before, passes now
|
|
107
|
+
const newlyMissing = []; // in previous suite, absent from latest (dropped task)
|
|
108
|
+
for (const [id, prevPass] of Object.entries(previous.perTask)) {
|
|
109
|
+
if (!(id in latest.perTask)) {
|
|
110
|
+
newlyMissing.push(id);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const nowPass = latest.perTask[id];
|
|
114
|
+
if (prevPass && !nowPass) regressions.push(id);
|
|
115
|
+
else if (!prevPass && nowPass) fixed.push(id);
|
|
116
|
+
}
|
|
117
|
+
const direction = delta > 1e-9 ? "up" : delta < -1e-9 ? "down" : "flat";
|
|
118
|
+
// The gate trips on ANY task regression, or a pass-rate drop beyond the
|
|
119
|
+
// threshold. A per-task regression matters even when the aggregate holds
|
|
120
|
+
// (one task fixed while another broke = flat rate but a real regression).
|
|
121
|
+
const regressed =
|
|
122
|
+
regressions.length > 0 || delta < -Math.abs(regressionThreshold);
|
|
123
|
+
return {
|
|
124
|
+
runs: norm.length,
|
|
125
|
+
latest,
|
|
126
|
+
previous,
|
|
127
|
+
delta,
|
|
128
|
+
direction,
|
|
129
|
+
regressions: regressions.sort(),
|
|
130
|
+
fixed: fixed.sort(),
|
|
131
|
+
newlyMissing: newlyMissing.sort(),
|
|
132
|
+
regressed,
|
|
133
|
+
history,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const BARS = "▁▂▃▄▅▆▇█";
|
|
138
|
+
/** Tiny unicode sparkline of pass-rates (0..1) across the run history. */
|
|
139
|
+
export function sparkline(rates) {
|
|
140
|
+
if (!rates || rates.length === 0) return "";
|
|
141
|
+
return rates
|
|
142
|
+
.map((r) => {
|
|
143
|
+
const clamped = Math.max(0, Math.min(1, Number(r) || 0));
|
|
144
|
+
return BARS[
|
|
145
|
+
Math.min(BARS.length - 1, Math.round(clamped * (BARS.length - 1)))
|
|
146
|
+
];
|
|
147
|
+
})
|
|
148
|
+
.join("");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Human-readable trend report. */
|
|
152
|
+
export function formatTrend(trend) {
|
|
153
|
+
if (!trend || trend.runs === 0) return "No eval history to report.";
|
|
154
|
+
const pct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
155
|
+
const lines = [];
|
|
156
|
+
const l = trend.latest;
|
|
157
|
+
lines.push(
|
|
158
|
+
`Eval trend over ${trend.runs} run(s) ${sparkline(trend.history)}`,
|
|
159
|
+
);
|
|
160
|
+
lines.push(
|
|
161
|
+
` latest: ${l.passed}/${l.total} (${pct(l.passRate)})` +
|
|
162
|
+
(l.label ? ` [${l.label}]` : ""),
|
|
163
|
+
);
|
|
164
|
+
if (!trend.previous) {
|
|
165
|
+
lines.push(" (only one run — no delta yet)");
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
const arrow =
|
|
169
|
+
trend.direction === "up" ? "▲" : trend.direction === "down" ? "▼" : "▬";
|
|
170
|
+
const sign = trend.delta >= 0 ? "+" : "";
|
|
171
|
+
lines.push(
|
|
172
|
+
` vs previous: ${arrow} ${sign}${(trend.delta * 100).toFixed(1)} pts`,
|
|
173
|
+
);
|
|
174
|
+
if (trend.regressions.length)
|
|
175
|
+
lines.push(` ✗ regressions: ${trend.regressions.join(", ")}`);
|
|
176
|
+
if (trend.fixed.length) lines.push(` ✔ fixed: ${trend.fixed.join(", ")}`);
|
|
177
|
+
if (trend.newlyMissing.length)
|
|
178
|
+
lines.push(` · dropped tasks: ${trend.newlyMissing.join(", ")}`);
|
|
179
|
+
lines.push(
|
|
180
|
+
trend.regressed
|
|
181
|
+
? " RESULT: REGRESSED (gate fails)"
|
|
182
|
+
: " RESULT: ok (no regression)",
|
|
183
|
+
);
|
|
184
|
+
return lines.join("\n");
|
|
185
|
+
}
|