agentic-qe 3.13.0 → 3.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/aqe-plan-quality/SKILL.md +25 -0
- package/.agents/skills/aqe-plan-quality/agents/openai.yaml +4 -0
- package/.agents/skills/aqe-plan-quality/references/qe-routing.md +22 -0
- package/.agents/skills/aqe-review-quality/SKILL.md +24 -0
- package/.agents/skills/aqe-review-quality/agents/openai.yaml +4 -0
- package/.agents/skills/aqe-review-quality/references/quality-gates.md +20 -0
- package/.agents/skills/aqe-test-change/SKILL.md +26 -0
- package/.agents/skills/aqe-test-change/agents/openai.yaml +4 -0
- package/.agents/skills/aqe-test-change/references/test-specialties.md +23 -0
- package/.claude/helpers/ruflo-hook.cjs +5 -1
- package/.claude/skills/skills-manifest.json +1 -1
- package/.codex/hooks/aqe-codex-hook.cjs +69 -0
- package/.codex/hooks/ruflo-codex-hook.cjs +94 -0
- package/.codex/hooks.json +169 -0
- package/CHANGELOG.md +94 -0
- package/assets/skills/skills-manifest.json +1 -1
- package/dist/cli/bundle.js +5 -5
- package/dist/cli/chunks/adapter-VRZNL53W.js +2 -0
- package/dist/cli/chunks/{agent-booster-wasm-ACX2DK7Y.js → agent-booster-wasm-4VM3DE42.js} +2 -2
- package/dist/cli/chunks/{agent-handler-B45WP4ZJ.js → agent-handler-ZDLMZQQX.js} +2 -2
- package/dist/cli/chunks/{agent-memory-branch-QQLZDE56.js → agent-memory-branch-P72MDHHV.js} +2 -2
- package/dist/cli/chunks/aqe-learning-engine-XCNMNERX.js +2 -0
- package/dist/cli/chunks/{arena-G3IKZAG5.js → arena-55FZGKEV.js} +4 -4
- package/dist/cli/chunks/{arena-SDB7723R.js → arena-GCRBQ26R.js} +2 -2
- package/dist/cli/chunks/{audit-GY46XUZA.js → audit-VYAYVLIV.js} +3 -3
- package/dist/cli/chunks/base-VZZNR4UR.js +2 -0
- package/dist/cli/chunks/{hnswlib-node-RJPBJU2Q.js → better-sqlite3-JDDFYVCX.js} +2 -2
- package/dist/cli/chunks/{brain-handler-347CYMPN.js → brain-handler-IUKMRGWO.js} +3 -3
- package/dist/cli/chunks/{branch-enumerator-Z3ULBFSM.js → branch-enumerator-TUI3RWLX.js} +2 -2
- package/dist/cli/chunks/{browser-OYEIHD7F.js → browser-NBQTZYX2.js} +2 -2
- package/dist/cli/chunks/browser-workflow-5DZNIFUY.js +2 -0
- package/dist/cli/chunks/{chunk-2NRZYQSF.js → chunk-23MT7OBY.js} +2 -2
- package/dist/cli/chunks/{chunk-7UNY32PU.js → chunk-2CQAWYYE.js} +1 -1
- package/dist/cli/chunks/{chunk-ZLXBNNNT.js → chunk-2D2XFUKI.js} +2 -2
- package/dist/cli/chunks/{chunk-SHCTEXNH.js → chunk-2G2QWV7B.js} +2 -2
- package/dist/cli/chunks/{chunk-TZ73SXQ5.js → chunk-2ISLQU3G.js} +1 -1
- package/dist/cli/chunks/{chunk-VLNPJAVH.js → chunk-2OM3TDC4.js} +1 -1
- package/dist/cli/chunks/{chunk-JGM6XCCP.js → chunk-2XZ4BHWS.js} +1 -1
- package/dist/cli/chunks/{chunk-32ZGV4Q4.js → chunk-3F4XH4ZA.js} +1 -1
- package/dist/cli/chunks/{chunk-RDU4WTV2.js → chunk-3K4YC3GC.js} +2 -2
- package/dist/cli/chunks/chunk-3KGCBAUH.js +2 -0
- package/dist/cli/chunks/{chunk-DYUC7WFA.js → chunk-3Y6CHW67.js} +1 -1
- package/dist/cli/chunks/{chunk-3JVUWKCO.js → chunk-4QMWWYNU.js} +2 -2
- package/dist/cli/chunks/{chunk-L4LLA5U7.js → chunk-4WGSMODQ.js} +1 -1
- package/dist/cli/chunks/{chunk-KUA4HHU6.js → chunk-55WDVSCM.js} +2 -2
- package/dist/cli/chunks/{chunk-RVJPWRV6.js → chunk-5BB5DUL4.js} +2 -2
- package/dist/cli/chunks/{chunk-ZOXWASCC.js → chunk-6C3MZLYW.js} +2 -2
- package/dist/cli/chunks/{chunk-CFVEDUTW.js → chunk-6QKQFHS7.js} +2 -2
- package/dist/cli/chunks/{chunk-YFGAHNCG.js → chunk-6XZXMEK4.js} +3 -3
- package/dist/cli/chunks/{chunk-Z6QCOZ2H.js → chunk-7D7XFZZI.js} +2 -2
- package/dist/cli/chunks/{chunk-AO7AHX6K.js → chunk-7WEHZCSK.js} +2 -2
- package/dist/cli/chunks/{chunk-K5N6SLHO.js → chunk-A2MG3YXO.js} +1 -1
- package/dist/cli/chunks/{chunk-LXPB7H2P.js → chunk-AHTOWWSE.js} +1 -1
- package/dist/cli/chunks/{chunk-U2SW6KK5.js → chunk-AOMXMFDH.js} +2 -2
- package/dist/cli/chunks/{chunk-VSNRPMAZ.js → chunk-ARTLSAXO.js} +2 -2
- package/dist/cli/chunks/{chunk-FBBZHATF.js → chunk-AY7S7AWN.js} +1 -1
- package/dist/cli/chunks/{chunk-CYL2FV23.js → chunk-AYYDO52G.js} +2 -2
- package/dist/cli/chunks/chunk-BBLWZT5I.js +2 -0
- package/dist/cli/chunks/{chunk-QTU3CVOB.js → chunk-BF5BKSDU.js} +2 -2
- package/dist/cli/chunks/{chunk-CWUBOLKY.js → chunk-BOCEOLLJ.js} +2 -2
- package/dist/cli/chunks/{chunk-FL3BM54K.js → chunk-CE6LYEHT.js} +1 -1
- package/dist/cli/chunks/{chunk-THBTN7XB.js → chunk-CWP4RMOM.js} +2 -2
- package/dist/cli/chunks/{chunk-Q7PBGNUD.js → chunk-CWXDL7AH.js} +1 -1
- package/dist/cli/chunks/{chunk-RVRBTOWX.js → chunk-DTRFZD7N.js} +2 -2
- package/dist/cli/chunks/{chunk-IZRLIWFN.js → chunk-DVPGQG4W.js} +2 -2
- package/dist/cli/chunks/{chunk-3NOCK6ED.js → chunk-E4D47QTF.js} +2 -2
- package/dist/cli/chunks/{chunk-FQLWQWRU.js → chunk-EAKHU5YX.js} +3 -3
- package/dist/cli/chunks/{chunk-56WZFXTQ.js → chunk-EGMGOWCE.js} +2 -2
- package/dist/cli/chunks/{chunk-OXXMVQDF.js → chunk-ELMUHNXN.js} +2 -2
- package/dist/cli/chunks/{chunk-SNTUKW4Q.js → chunk-EVDYFGVH.js} +2 -2
- package/dist/cli/chunks/{chunk-RLCWR7TS.js → chunk-FDUSWM6O.js} +98 -88
- package/dist/cli/chunks/{chunk-D3MPV5G7.js → chunk-FM272B4O.js} +2 -2
- package/dist/cli/chunks/{chunk-DRIJYTSK.js → chunk-FQUUMC57.js} +2 -2
- package/dist/cli/chunks/{chunk-5KAT6WBQ.js → chunk-FQXQJMSF.js} +2 -2
- package/dist/cli/chunks/{chunk-BCKTBGDM.js → chunk-FRAGSKXQ.js} +3 -3
- package/dist/cli/chunks/{chunk-SRQ3CPJE.js → chunk-GASOBSNB.js} +4 -4
- package/dist/cli/chunks/{chunk-GTUFX7AR.js → chunk-GJEEPKAT.js} +2 -2
- package/dist/cli/chunks/{chunk-5EIOQCGN.js → chunk-GOV2MHLV.js} +2 -2
- package/dist/cli/chunks/{chunk-QI4CGJNK.js → chunk-GTPZVLL6.js} +1 -1
- package/dist/cli/chunks/chunk-H7YZ2YF2.js +3 -0
- package/dist/cli/chunks/{chunk-ANIDFJCT.js → chunk-HFI5PHG5.js} +3 -3
- package/dist/cli/chunks/{chunk-2VQVEQS4.js → chunk-HUMQU4ES.js} +2 -2
- package/dist/cli/chunks/{chunk-VHYMSZNF.js → chunk-HYIRFJDB.js} +2 -2
- package/dist/cli/chunks/{chunk-A4QLGPYO.js → chunk-IBSBOHUX.js} +2 -2
- package/dist/cli/chunks/{chunk-6SXKCIN5.js → chunk-IJA4WS7B.js} +1 -1
- package/dist/cli/chunks/{chunk-3GSYD5SL.js → chunk-IJP42OGW.js} +2 -2
- package/dist/cli/chunks/{chunk-PERJO2DY.js → chunk-IMQKEFW6.js} +1 -1
- package/dist/cli/chunks/{chunk-ZEIG5IQ4.js → chunk-INYS35DV.js} +2 -2
- package/dist/cli/chunks/{chunk-J3L52EA4.js → chunk-IV2VCR75.js} +5 -5
- package/dist/cli/chunks/{chunk-TVMFH5N2.js → chunk-IZO6TPGX.js} +2 -2
- package/dist/cli/chunks/{chunk-ZJG6N2OK.js → chunk-J45DX3IK.js} +1 -1
- package/dist/cli/chunks/{chunk-HL2QMM4M.js → chunk-J4T6D2OQ.js} +2 -2
- package/dist/cli/chunks/{chunk-5ICQL42E.js → chunk-JAPOMPFD.js} +2 -2
- package/dist/cli/chunks/{chunk-L2SFGFE6.js → chunk-JELNVHBF.js} +1 -1
- package/dist/cli/chunks/{chunk-RSFIOR2I.js → chunk-JOTNKZ7A.js} +87 -87
- package/dist/cli/chunks/{chunk-34PEINFS.js → chunk-KNWDXI3I.js} +2 -2
- package/dist/cli/chunks/{chunk-FUU32QY5.js → chunk-KW5NBORV.js} +2 -2
- package/dist/cli/chunks/{chunk-N5MMBA35.js → chunk-L7IOTWWY.js} +1 -1
- package/dist/cli/chunks/{chunk-RJZRWVL4.js → chunk-LJQ27PUE.js} +1 -1
- package/dist/cli/chunks/{chunk-QQB55ZJJ.js → chunk-LSKQUUUN.js} +2 -2
- package/dist/cli/chunks/{chunk-NETN3OUG.js → chunk-LSZZQ7H6.js} +1 -1
- package/dist/cli/chunks/{chunk-IYDYJ33J.js → chunk-LTKNCYMO.js} +2 -2
- package/dist/cli/chunks/{chunk-6DTOGSDV.js → chunk-LTWT4GZD.js} +4 -4
- package/dist/cli/chunks/{chunk-NQNKKVNO.js → chunk-LVHMXGDI.js} +3 -3
- package/dist/cli/chunks/{chunk-3UHEZ5HZ.js → chunk-M3T6FB54.js} +2 -2
- package/dist/cli/chunks/{chunk-ZJIX5FGQ.js → chunk-MAYZTGJ2.js} +2 -2
- package/dist/cli/chunks/{chunk-3K4SF57Q.js → chunk-MCIO7BDK.js} +1 -1
- package/dist/cli/chunks/{chunk-A7GLWWSE.js → chunk-MPD7RR4P.js} +1 -1
- package/dist/cli/chunks/{chunk-FAQLI3P5.js → chunk-MUFYDKVC.js} +2 -2
- package/dist/cli/chunks/{chunk-XFCWYQHW.js → chunk-MX5ZDKRR.js} +2 -2
- package/dist/cli/chunks/{chunk-MBCGI3M2.js → chunk-MXKHIZVM.js} +5 -5
- package/dist/cli/chunks/chunk-MY47CR27.js +2 -0
- package/dist/cli/chunks/chunk-N7WAZ2Y6.js +2 -0
- package/dist/cli/chunks/{chunk-VC6EXDGY.js → chunk-NHV4PTG6.js} +2 -2
- package/dist/cli/chunks/{chunk-LBE2P6UC.js → chunk-NP4CP3LR.js} +2 -2
- package/dist/cli/chunks/{chunk-6AOBLBR4.js → chunk-OCKTPTQ7.js} +2 -2
- package/dist/cli/chunks/{chunk-ES7WCYOU.js → chunk-OE4B6LDZ.js} +1 -1
- package/dist/cli/chunks/{chunk-IPHLXAMX.js → chunk-OPQY2333.js} +2 -2
- package/dist/cli/chunks/{chunk-7DMXCCSD.js → chunk-ORDH4VPL.js} +1 -1
- package/dist/cli/chunks/{chunk-HTPM3URI.js → chunk-P4Y3G6OB.js} +2 -2
- package/dist/cli/chunks/{chunk-TNRIGQ6U.js → chunk-PIOO2M4W.js} +2 -2
- package/dist/cli/chunks/{chunk-BGYLNCL6.js → chunk-PKMCZFET.js} +2 -2
- package/dist/cli/chunks/{chunk-G7FMYFTN.js → chunk-QES4QHQW.js} +1 -1
- package/dist/cli/chunks/{chunk-UIPPJKON.js → chunk-QG6BXSNJ.js} +2 -2
- package/dist/cli/chunks/{chunk-7YXT6BXN.js → chunk-QLOOW23R.js} +3 -3
- package/dist/cli/chunks/{chunk-CQFT7H3F.js → chunk-RSCGBHLM.js} +2 -2
- package/dist/cli/chunks/{chunk-QCEW4ZDW.js → chunk-RZ6CO3RZ.js} +2 -2
- package/dist/cli/chunks/{chunk-OETDMOOE.js → chunk-S6F3IJMK.js} +1 -1
- package/dist/cli/chunks/{chunk-KVL63BSU.js → chunk-S6VNPKHO.js} +1 -1
- package/dist/cli/chunks/{chunk-BUGWPTMF.js → chunk-SHWXADCI.js} +2 -2
- package/dist/cli/chunks/{chunk-FQY3UTGA.js → chunk-SPJQJWGK.js} +2 -2
- package/dist/cli/chunks/{chunk-BOVH6WSM.js → chunk-SS6K4FBH.js} +2 -2
- package/dist/cli/chunks/{chunk-4BESADKE.js → chunk-SXADKQJN.js} +2 -2
- package/dist/cli/chunks/{chunk-USMDIIHY.js → chunk-TBQTGQ75.js} +2 -2
- package/dist/cli/chunks/{chunk-4BDLD7V2.js → chunk-TDGE52HJ.js} +1 -1
- package/dist/cli/chunks/{chunk-JPIBPKBP.js → chunk-THFLRJT6.js} +3 -3
- package/dist/cli/chunks/{chunk-ZCFZ4USU.js → chunk-TQ2MA3SR.js} +1 -1
- package/dist/cli/chunks/{chunk-G5DFYX3J.js → chunk-UCQ66N6M.js} +2 -2
- package/dist/cli/chunks/{chunk-CPH5JP4L.js → chunk-UHGMS2PJ.js} +1 -1
- package/dist/cli/chunks/{chunk-ANYYTWOQ.js → chunk-UHKXKWMD.js} +2 -2
- package/dist/cli/chunks/{chunk-YRQLVGCI.js → chunk-UJ6TMZC6.js} +2 -2
- package/dist/cli/chunks/{chunk-EECM5XKY.js → chunk-UKBDH27A.js} +1 -1
- package/dist/cli/chunks/{chunk-VLBV2VL3.js → chunk-UMCEEXQN.js} +1 -1
- package/dist/cli/chunks/{chunk-LOXVVMS4.js → chunk-USUFTHHT.js} +3 -3
- package/dist/cli/chunks/{chunk-HOKON5EL.js → chunk-V32MDID3.js} +2 -2
- package/dist/cli/chunks/{chunk-KZTBNDNE.js → chunk-VAJT6QWX.js} +1 -1
- package/dist/cli/chunks/{chunk-UNIBND6A.js → chunk-VLUJWI56.js} +1 -1
- package/dist/cli/chunks/{chunk-QWYISZTN.js → chunk-WINA3XUT.js} +2 -2
- package/dist/cli/chunks/{chunk-YPTGE5NJ.js → chunk-WWCH5SSF.js} +2 -2
- package/dist/cli/chunks/{chunk-W5YEIHIO.js → chunk-WWP2VXYJ.js} +2 -2
- package/dist/cli/chunks/{chunk-GFBLWFRV.js → chunk-WZROSTQZ.js} +1 -1
- package/dist/cli/chunks/{chunk-UMG5URNV.js → chunk-XFUF654L.js} +1 -1
- package/dist/cli/chunks/{chunk-54MFKCLT.js → chunk-XN2CD5S6.js} +2 -2
- package/dist/cli/chunks/{chunk-IHQTXJAQ.js → chunk-XOQQGZFY.js} +1 -1
- package/dist/cli/chunks/{chunk-WRYT4HTZ.js → chunk-XSCOEY4C.js} +2 -2
- package/dist/cli/chunks/{chunk-R3GEXYO4.js → chunk-Y734CWHJ.js} +2 -2
- package/dist/cli/chunks/{chunk-NUQBJGGF.js → chunk-YBPCBFEE.js} +2 -2
- package/dist/cli/chunks/{chunk-GUEZQOHL.js → chunk-YLAEIS6F.js} +1 -1
- package/dist/cli/chunks/{chunk-EFRESRLB.js → chunk-YMMVCOPF.js} +3 -3
- package/dist/cli/chunks/{chunk-N7VOOI3U.js → chunk-YQCAXYBF.js} +2 -2
- package/dist/cli/chunks/{chunk-5FLTZ3KT.js → chunk-YRVUYM62.js} +2 -2
- package/dist/cli/chunks/{chunk-GJMZR7WU.js → chunk-YVKMHXQK.js} +3 -3
- package/dist/cli/chunks/{chunk-TZPKTRJB.js → chunk-ZCLFAOVT.js} +2 -2
- package/dist/cli/chunks/{chunk-L4S722JD.js → chunk-ZH6ZBDVV.js} +1 -1
- package/dist/cli/chunks/{chunk-KSYLRM4Q.js → chunk-ZHEKGB4A.js} +2 -2
- package/dist/cli/chunks/{chunk-E3B5XTXJ.js → chunk-ZIBW7CB4.js} +1 -1
- package/dist/cli/chunks/{chunk-2DIT5AUU.js → chunk-ZLCATRXI.js} +2 -2
- package/dist/cli/chunks/{chunk-LWYLW7RX.js → chunk-ZXX2VFMA.js} +2 -2
- package/dist/cli/chunks/{chunk-XKFE3FLI.js → chunk-ZYPESQ4M.js} +2 -2
- package/dist/cli/chunks/{ci-74ZUCO5V.js → ci-HHL5WQXQ.js} +2 -2
- package/dist/cli/chunks/{ci-output-HD6IZNJA.js → ci-output-HVOJC2KN.js} +2 -2
- package/dist/cli/chunks/{circuit-breaker-USZVMA42.js → circuit-breaker-HRXMIY2G.js} +2 -2
- package/dist/cli/chunks/{claude-flow-setup-S4XKNCD5.js → claude-flow-setup-3I7WVMZN.js} +2 -2
- package/dist/cli/chunks/client-EY2IYQDL.js +2 -0
- package/dist/cli/chunks/{cline-installer-6EW4CHAR.js → cline-installer-NBREL4QT.js} +2 -2
- package/dist/cli/chunks/{code-TOLWD2XL.js → code-TX4E2RWU.js} +2 -2
- package/dist/cli/chunks/{code-index-extractor-UA4QQ7B3.js → code-index-extractor-YELOFLSL.js} +2 -2
- package/dist/cli/chunks/codex-installer-HEX3KFRB.js +10 -0
- package/dist/cli/chunks/{completions-KVUMQQ4K.js → completions-72GGFRGG.js} +2 -2
- package/dist/cli/chunks/{complexity-analyzer-DFH6LMK2.js → complexity-analyzer-WRAKDYUK.js} +2 -2
- package/dist/cli/chunks/{continuedev-installer-LYDQSAU7.js → continuedev-installer-UGYKSKTI.js} +2 -2
- package/dist/cli/chunks/{copilot-installer-7USXLZHZ.js → copilot-installer-RPCRUKIJ.js} +2 -2
- package/dist/cli/chunks/core-handlers-G4GLA4DT.js +2 -0
- package/dist/cli/chunks/{cost-tracker-JBDIUMKZ.js → cost-tracker-CNHJ7G4M.js} +2 -2
- package/dist/cli/chunks/{coverage-RGO3K357.js → coverage-RHIQ4XEX.js} +3 -3
- package/dist/cli/chunks/cross-domain-router-P3K4IH5H.js +2 -0
- package/dist/cli/chunks/{cursor-installer-YEZGD37F.js → cursor-installer-KSRVA4V2.js} +2 -2
- package/dist/cli/chunks/{daemon-FVETQ3LL.js → daemon-IT4AAFBW.js} +3 -3
- package/dist/cli/chunks/{daemon-LPEXHNYG.js → daemon-QBXSJABV.js} +4 -4
- package/dist/cli/chunks/{dag-attention-scheduler-UKYE2FII.js → dag-attention-scheduler-F7WCQTLY.js} +2 -2
- package/dist/cli/chunks/{detect-WBG5P7ER.js → detect-2IYU332U.js} +2 -2
- package/dist/cli/chunks/{dist-node-YW6YRBCU.js → dist-node-CIYNYCYN.js} +2 -2
- package/dist/cli/chunks/{domain-handler-HH7J4A32.js → domain-handler-QRYQO2QK.js} +2 -2
- package/dist/cli/chunks/{domain-transfer-V34GXD5I.js → domain-transfer-2OQDOD3G.js} +2 -2
- package/dist/cli/chunks/dream-IT4ZQGEO.js +2 -0
- package/dist/cli/chunks/{embed-and-insert-pattern-WYLRS3CL.js → embed-and-insert-pattern-P2IFC7JL.js} +2 -2
- package/dist/cli/chunks/{eval-EIMVXY5J.js → eval-SBPK37IB.js} +2 -2
- package/dist/cli/chunks/{experience-capture-middleware-QMZHRUKM.js → experience-capture-middleware-3GMFND3F.js} +3 -3
- package/dist/cli/chunks/{fast-paths-NHLW6XS6.js → fast-paths-4OYYII2F.js} +2 -2
- package/dist/cli/chunks/{feature-flags-NE3MBL3P.js → feature-flags-BM6QTMMC.js} +2 -2
- package/dist/cli/chunks/{feature-flags-N3UHMSZ3.js → feature-flags-NFCAYUWO.js} +2 -2
- package/dist/cli/chunks/{file-discovery-SI44X4YS.js → file-discovery-2M3ZUAGC.js} +2 -2
- package/dist/cli/chunks/{fleet-MMYUXRRM.js → fleet-QMGGVIDV.js} +3 -3
- package/dist/cli/chunks/{gnn-wrapper-RLYXYU2Q.js → gnn-wrapper-WGMAGZMO.js} +2 -2
- package/dist/cli/chunks/{heartbeat-handler-6EDRUYLV.js → heartbeat-handler-5RSMEZGG.js} +4 -4
- package/dist/cli/chunks/heartbeat-scheduler-UUWSPHF2.js +2 -0
- package/dist/cli/chunks/hnsw-adapter-YUJVSFPD.js +2 -0
- package/dist/cli/chunks/hnsw-index-FQZ47RSJ.js +2 -0
- package/dist/cli/chunks/{hnsw-legacy-bridge-RVM7EAIH.js → hnsw-legacy-bridge-TYPQ2T6P.js} +2 -2
- package/dist/cli/chunks/{better-sqlite3-DUZ6FFAH.js → hnswlib-node-GMRPPMDM.js} +2 -2
- package/dist/cli/chunks/{hooks-ZF4VKHEO.js → hooks-NUC5P3OJ.js} +10 -10
- package/dist/cli/chunks/hybrid-router-T5MF73NF.js +2 -0
- package/dist/cli/chunks/{hypergraph-engine-YWS66PGO.js → hypergraph-engine-OIB7WK54.js} +2 -2
- package/dist/cli/chunks/{hypergraph-handler-OQHVRPWB.js → hypergraph-handler-XX2N75QC.js} +3 -3
- package/dist/cli/chunks/impact-analyzer-AEGELYMD.js +2 -0
- package/dist/cli/chunks/{init-handler-YW74YF2V.js → init-handler-AKSTJMZY.js} +6 -6
- package/dist/cli/chunks/init-wizard-WKX3OGWM.js +2 -0
- package/dist/cli/chunks/kernel-5CVWFIGR.js +2 -0
- package/dist/cli/chunks/{kilocode-installer-APWB2GMA.js → kilocode-installer-375V5ZZB.js} +2 -2
- package/dist/cli/chunks/{kiro-installer-DICKFVNC.js → kiro-installer-GKY5GB5Q.js} +2 -2
- package/dist/cli/chunks/knowledge-graph-EGPMNDMT.js +2 -0
- package/dist/cli/chunks/{learning-MXHNYW5D.js → learning-2CQE3QP6.js} +3 -3
- package/dist/cli/chunks/{llm-router-LTFIX7NY.js → llm-router-FTQNRF45.js} +4 -4
- package/dist/cli/chunks/llm-router-service-H4LXOARB.js +2 -0
- package/dist/cli/chunks/{load-JUMIQ4LZ.js → load-3DBAO5ZG.js} +2 -2
- package/dist/cli/chunks/load-test-OOOGITSN.js +2 -0
- package/dist/cli/chunks/{mcp-WHIARJGO.js → mcp-AWR6TDXM.js} +2 -2
- package/dist/cli/chunks/{memory-KAHS6Z5A.js → memory-GI4LCNXY.js} +5 -5
- package/dist/cli/chunks/memory-backend-O2YOT4LW.js +2 -0
- package/dist/cli/chunks/{memory-handlers-DG3RRXB6.js → memory-handlers-MALKG7GP.js} +2 -2
- package/dist/cli/chunks/{multi-model-executor-E3Y2RHMX.js → multi-model-executor-SWFT5U72.js} +2 -2
- package/dist/cli/chunks/{opencode-installer-3RKGNGGE.js → opencode-installer-EYXGYJX5.js} +2 -2
- package/dist/cli/chunks/{orchestrator-TXITUKWD.js → orchestrator-MP3EAZVC.js} +5 -5
- package/dist/cli/chunks/pattern-null-store-WBOHE2XL.js +2 -0
- package/dist/cli/chunks/{pattern-usage-recorder-EE2BUFZT.js → pattern-usage-recorder-25P3R3W7.js} +2 -2
- package/dist/cli/chunks/{pipeline-AXTT6KZB.js → pipeline-LSAFL25D.js} +2 -2
- package/dist/cli/chunks/{platform-TICPVKE2.js → platform-IJ6DJ2PG.js} +2 -2
- package/dist/cli/chunks/{plugin-VQKNV3EM.js → plugin-OXOZRZW2.js} +2 -2
- package/dist/cli/chunks/{prime-radiant-advanced-wasm-6GY5YY7D.js → prime-radiant-advanced-wasm-HJNJQQ3R.js} +2 -2
- package/dist/cli/chunks/protocol-executor-JCNTPW5J.js +2 -0
- package/dist/cli/chunks/{protocol-handler-YFWDR7UZ.js → protocol-handler-TCMUH2HL.js} +2 -2
- package/dist/cli/chunks/{prove-FUMRCXAA.js → prove-ADXQ2GOX.js} +2 -2
- package/dist/cli/chunks/provider-manager-LPGQ5GOO.js +2 -0
- package/dist/cli/chunks/qe-reasoning-bank-FWVYVSPP.js +2 -0
- package/dist/cli/chunks/{quality-2APISBW5.js → quality-EWP36ORX.js} +2 -2
- package/dist/cli/chunks/{quality-gate-MCC3E5TM.js → quality-gate-3BIG7PVU.js} +3 -3
- package/dist/cli/chunks/queen-coordinator-UXNALRVD.js +2 -0
- package/dist/cli/chunks/{real-embeddings-CC5TNOTI.js → real-embeddings-D4NBT7W5.js} +2 -2
- package/dist/cli/chunks/{roocode-installer-YAHTEVPI.js → roocode-installer-DOPWH6YV.js} +2 -2
- package/dist/cli/chunks/router-C6LUIQYY.js +2 -0
- package/dist/cli/chunks/routing-feedback-KMSGGDVD.js +2 -0
- package/dist/cli/chunks/{routing-handler-6NQBW4TQ.js → routing-handler-5ADEFOHS.js} +2 -2
- package/dist/cli/chunks/{ruvector-commands-ZHXLHUPG.js → ruvector-commands-PLMEIHSN.js} +2 -2
- package/dist/cli/chunks/{rvf-dual-writer-5KF2DGCW.js → rvf-dual-writer-EQWKVNQB.js} +2 -2
- package/dist/cli/chunks/{rvf-migration-adapter-BBU57BGF.js → rvf-migration-adapter-2OWFUUCG.js} +2 -2
- package/dist/cli/chunks/{rvf-migration-coordinator-VN4VRZA4.js → rvf-migration-coordinator-URAETAPU.js} +2 -2
- package/dist/cli/chunks/rvf-native-adapter-K54BELUE.js +2 -0
- package/dist/cli/chunks/{rvf-store-integrity-XCY2XQZA.js → rvf-store-integrity-4AWPW4TF.js} +2 -2
- package/dist/cli/chunks/safe-db-NGOZWF2D.js +2 -0
- package/dist/cli/chunks/schedule-B3Y5P64E.js +2 -0
- package/dist/cli/chunks/scheduler-Z6QVRMTB.js +2 -0
- package/dist/cli/chunks/{security-PYP4H3KN.js → security-HDLZVM62.js} +3 -3
- package/dist/cli/chunks/{shared-rvf-adapter-3CJHRNX6.js → shared-rvf-adapter-YQT7PF7X.js} +2 -2
- package/dist/cli/chunks/{shared-rvf-dual-writer-MKSLKWDI.js → shared-rvf-dual-writer-N5D3NSJ7.js} +2 -2
- package/dist/cli/chunks/sqlite-persistence-PLOUGATS.js +2 -0
- package/dist/cli/chunks/{status-handler-VYIZ3VJF.js → status-handler-KLYRJ2MR.js} +2 -2
- package/dist/cli/chunks/{structural-health-MVV4OWXP.js → structural-health-6LKVRP6Y.js} +2 -2
- package/dist/cli/chunks/{sync-VNX6L3TI.js → sync-7VMFPBVB.js} +2 -2
- package/dist/cli/chunks/{sync-G47OYIQ3.js → sync-YYOW6L6W.js} +2 -2
- package/dist/cli/chunks/{task-handler-GOWF2GEH.js → task-handler-RKQDGFN2.js} +2 -2
- package/dist/cli/chunks/{task-handlers-J2WC6J37.js → task-handlers-6YJ4QTEP.js} +3 -3
- package/dist/cli/chunks/{test-DMCJ3BXS.js → test-ASPUIUJH.js} +4 -4
- package/dist/cli/chunks/{test-scheduling-TRFHHKFM.js → test-scheduling-I7ABQBBU.js} +3 -3
- package/dist/cli/chunks/{token-bootstrap-KEQ7AHFF.js → token-bootstrap-2XZBTODN.js} +2 -2
- package/dist/cli/chunks/{token-usage-B54FOFOF.js → token-usage-TMUE6PTF.js} +2 -2
- package/dist/cli/chunks/{transformers-IACZG7IV.js → transformers-FDHHH4SL.js} +2 -2
- package/dist/cli/chunks/{tree-sitter-wasm-parser-N7H4X37E.js → tree-sitter-wasm-parser-G7PGZA5C.js} +2 -2
- package/dist/cli/chunks/{types-B3SHT4OD.js → types-JGT57UFP.js} +2 -2
- package/dist/cli/chunks/unified-memory-P5YVLMFI.js +2 -0
- package/dist/cli/chunks/unified-memory-hnsw-7HI43A2O.js +2 -0
- package/dist/cli/chunks/unified-persistence-U26YP3N3.js +2 -0
- package/dist/cli/chunks/{upgrade-A2PQ5EWK.js → upgrade-V6UQODJF.js} +2 -2
- package/dist/cli/chunks/{validate-UXV7UQUT.js → validate-UBZAE5RW.js} +2 -2
- package/dist/cli/chunks/{validate-swarm-MMBU4FPP.js → validate-swarm-TSO2MI5X.js} +2 -2
- package/dist/cli/chunks/{vibium-R4GUVADA.js → vibium-7G3VYSQA.js} +2 -2
- package/dist/cli/chunks/visual-security-6FCDY5FF.js +2 -0
- package/dist/cli/chunks/{web-tree-sitter-MP6E6CJB.js → web-tree-sitter-MIYJSF2L.js} +2 -2
- package/dist/cli/chunks/{windsurf-installer-Z6BWMUUO.js → windsurf-installer-5TLZSQIJ.js} +2 -2
- package/dist/cli/chunks/witness-chain-I3K3WQID.js +2 -0
- package/dist/cli/chunks/{witness-chain-EFLTABBA.js → witness-chain-LTV7H2HM.js} +2 -2
- package/dist/cli/chunks/{witness-key-manager-PN36TV43.js → witness-key-manager-PAJ2J4AL.js} +2 -2
- package/dist/cli/chunks/{workflow-A2AX3RJU.js → workflow-DL3ESR2H.js} +4 -4
- package/dist/cli/chunks/workflow-orchestrator-MAALP4WA.js +2 -0
- package/dist/cli/chunks/{wrappers-NA6FHA63.js → wrappers-5HSU665E.js} +2 -2
- package/dist/cli/handlers/init-handler.js +1 -1
- package/dist/coordination/handlers/coverage-collection.d.ts +161 -0
- package/dist/coordination/handlers/coverage-collection.js +639 -0
- package/dist/coordination/handlers/coverage-handlers.js +199 -146
- package/dist/coordination/handlers/handler-types.d.ts +6 -1
- package/dist/coordination/handlers/handler-utils.js +10 -3
- package/dist/coordination/handlers/security-handlers.js +17 -2
- package/dist/coordination/handlers/test-execution-handlers.js +16 -1
- package/dist/coordination/result-saver.js +119 -97
- package/dist/coordination/task-executor.d.ts +26 -1
- package/dist/coordination/task-executor.js +52 -4
- package/dist/domains/code-intelligence/services/knowledge-graph.js +8 -0
- package/dist/domains/coverage-analysis/services/coverage-analyzer.js +4 -0
- package/dist/domains/coverage-analysis/services/gap-detector.js +4 -0
- package/dist/domains/defect-intelligence/services/defect-predictor.js +4 -0
- package/dist/domains/defect-intelligence/services/root-cause-analyzer.js +4 -0
- package/dist/domains/quality-assessment/services/deployment-advisor.js +4 -0
- package/dist/domains/quality-assessment/services/quality-analyzer.js +4 -0
- package/dist/domains/security-compliance/services/scanners/sast-scanner.js +4 -0
- package/dist/domains/test-generation/index.js +17 -2
- package/dist/domains/test-generation/services/test-generator.js +31 -9
- package/dist/init/codex-installer.d.ts +13 -0
- package/dist/init/codex-installer.js +122 -1
- package/dist/init/phases/09-assets.js +4 -0
- package/dist/init/phases/phase-interface.d.ts +1 -1
- package/dist/learning/real-embeddings.js +15 -1
- package/dist/mcp/bundle.js +429 -419
- package/dist/mcp/handlers/domain-handler-configs.js +79 -14
- package/dist/mcp/types.d.ts +42 -4
- package/dist/optimization/session-cache.d.ts +13 -1
- package/dist/optimization/session-cache.js +27 -2
- package/dist/shared/llm/llm-router-service.js +59 -4
- package/dist/shared/llm/router/agent-router-config.d.ts +46 -2
- package/dist/shared/llm/router/agent-router-config.js +124 -2
- package/dist/shared/llm/router/config-store.d.ts +17 -1
- package/dist/shared/llm/router/config-store.js +103 -0
- package/dist/shared/llm/router/index.d.ts +2 -2
- package/dist/shared/llm/router/index.js +2 -0
- package/dist/shared/llm/router/types.d.ts +44 -0
- package/dist/shared/llm/router/types.js +1 -0
- package/package.json +9 -6
- package/dist/cli/chunks/adapter-CWIMFWAI.js +0 -2
- package/dist/cli/chunks/aqe-learning-engine-OTXT2DLN.js +0 -2
- package/dist/cli/chunks/base-O3SPSWBA.js +0 -2
- package/dist/cli/chunks/browser-workflow-MXS3ZKY2.js +0 -2
- package/dist/cli/chunks/chunk-G4GTE3RJ.js +0 -2
- package/dist/cli/chunks/chunk-HVB2L52I.js +0 -2
- package/dist/cli/chunks/chunk-MVI2QMRW.js +0 -2
- package/dist/cli/chunks/chunk-OZR25R7W.js +0 -2
- package/dist/cli/chunks/chunk-PP2RYSHT.js +0 -3
- package/dist/cli/chunks/client-33PGO5FA.js +0 -2
- package/dist/cli/chunks/codex-installer-C2CUP4FW.js +0 -8
- package/dist/cli/chunks/core-handlers-CSNZIM3A.js +0 -2
- package/dist/cli/chunks/cross-domain-router-DRIBR5L3.js +0 -2
- package/dist/cli/chunks/dream-WUNO723O.js +0 -2
- package/dist/cli/chunks/heartbeat-scheduler-VAFQDJLL.js +0 -2
- package/dist/cli/chunks/hnsw-adapter-TIRPA7XE.js +0 -2
- package/dist/cli/chunks/hnsw-index-SSQBRQCQ.js +0 -2
- package/dist/cli/chunks/hybrid-router-AKBWZOQ3.js +0 -2
- package/dist/cli/chunks/impact-analyzer-46FQIZ6Y.js +0 -2
- package/dist/cli/chunks/init-wizard-U3NHGYSS.js +0 -2
- package/dist/cli/chunks/kernel-ZDXHT6QF.js +0 -2
- package/dist/cli/chunks/knowledge-graph-6XTW6WO6.js +0 -2
- package/dist/cli/chunks/llm-router-service-3F4E64SY.js +0 -2
- package/dist/cli/chunks/load-test-SWZNGEIY.js +0 -2
- package/dist/cli/chunks/memory-backend-PR74ZPBA.js +0 -2
- package/dist/cli/chunks/pattern-null-store-EPE36BVT.js +0 -2
- package/dist/cli/chunks/protocol-executor-IELENNST.js +0 -2
- package/dist/cli/chunks/provider-manager-3R3KOBHG.js +0 -2
- package/dist/cli/chunks/qe-reasoning-bank-S2R2PCBS.js +0 -2
- package/dist/cli/chunks/queen-coordinator-2E23NEV4.js +0 -2
- package/dist/cli/chunks/router-3FVLWO6S.js +0 -2
- package/dist/cli/chunks/routing-feedback-ZQLRNNR7.js +0 -2
- package/dist/cli/chunks/rvf-native-adapter-YFGLQNEB.js +0 -2
- package/dist/cli/chunks/safe-db-4SBLBTW7.js +0 -2
- package/dist/cli/chunks/schedule-5Y3BGVAA.js +0 -2
- package/dist/cli/chunks/scheduler-DOID3QF3.js +0 -2
- package/dist/cli/chunks/sqlite-persistence-M4JEQO57.js +0 -2
- package/dist/cli/chunks/unified-memory-WNYWTGVX.js +0 -2
- package/dist/cli/chunks/unified-memory-hnsw-YPAV7U4E.js +0 -2
- package/dist/cli/chunks/unified-persistence-TJ252S7L.js +0 -2
- package/dist/cli/chunks/visual-security-VKYXEGJB.js +0 -2
- package/dist/cli/chunks/witness-chain-WVXRXKQP.js +0 -2
- package/dist/cli/chunks/workflow-orchestrator-GOWSPR3G.js +0 -2
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage collection and provenance (issue #569).
|
|
3
|
+
*
|
|
4
|
+
* Two things live here, and the distinction between them is the whole point:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Measurement** — run real instrumentation and parse what it reports.
|
|
7
|
+
* 2. **Estimation** — a static guess from source shape, used only when no
|
|
8
|
+
* instrumentation could run.
|
|
9
|
+
*
|
|
10
|
+
* Before #569 these were indistinguishable in the output. `coverage_analyze_sublinear`
|
|
11
|
+
* on a Rust crate reported `lineCoverage: 78.3 / branchCoverage: 100 /
|
|
12
|
+
* functionCoverage: 0` with `confidence: 0.7` attached to gaps that pointed at
|
|
13
|
+
* `#[cfg(test)]` blocks and at line numbers past end-of-file. Every one of those
|
|
14
|
+
* numbers came from a JS-shaped regex heuristic; none was a measurement. A caller
|
|
15
|
+
* had no way to tell.
|
|
16
|
+
*
|
|
17
|
+
* So: every collector returns a `CoverageProvenance` alongside the data, and
|
|
18
|
+
* estimation is labelled as estimation, at the source, all the way out to the
|
|
19
|
+
* caller.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as os from 'os';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { parseLcovInfo } from './handler-utils';
|
|
25
|
+
/**
|
|
26
|
+
* Is the "never execute build tooling" kill-switch set?
|
|
27
|
+
*
|
|
28
|
+
* Measuring coverage means *running the project's own tests*, which means
|
|
29
|
+
* executing code from the analyzed repository: `npx vitest/jest/nyc` runs its
|
|
30
|
+
* test suite and npm lifecycle scripts, and `cargo llvm-cov` additionally
|
|
31
|
+
* compiles and runs `build.rs` and honors any `runner`/linker directive in that
|
|
32
|
+
* repo's `.cargo/config.toml`. For a trusted project — your own repo, CI on your
|
|
33
|
+
* own code — that is exactly what the caller wants and is how coverage has always
|
|
34
|
+
* been collected here.
|
|
35
|
+
*
|
|
36
|
+
* It is surprising, though, for an operation that reads like a static query, and
|
|
37
|
+
* an agent can be pointed at an untrusted checkout. `AQE_COVERAGE_NO_EXEC=1`
|
|
38
|
+
* disables execution entirely: already-present coverage reports are still parsed,
|
|
39
|
+
* and everything else degrades to clearly-labelled static estimation.
|
|
40
|
+
*
|
|
41
|
+
* Follows the same parsing convention as `AQE_LLM_ROUTER_DISABLED`.
|
|
42
|
+
*/
|
|
43
|
+
export function isCoverageExecDisabled(env = process.env) {
|
|
44
|
+
const v = (env.AQE_COVERAGE_NO_EXEC ?? '').trim().toLowerCase();
|
|
45
|
+
if (!v)
|
|
46
|
+
return false;
|
|
47
|
+
return v !== 'false' && v !== '0' && v !== 'no' && v !== 'off';
|
|
48
|
+
}
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Shared file classification
|
|
51
|
+
// ============================================================================
|
|
52
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
53
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
54
|
+
'.py', '.go', '.rs', '.java', '.rb',
|
|
55
|
+
]);
|
|
56
|
+
const SKIP_DIRS = new Set([
|
|
57
|
+
'node_modules', 'dist', 'build', 'coverage', '.git', '.claude', '.agentic-qe',
|
|
58
|
+
'__pycache__', '.venv', 'target', 'vendor',
|
|
59
|
+
]);
|
|
60
|
+
/** Directory names whose entire contents are test/bench/example code, not production code. */
|
|
61
|
+
const TEST_DIRS = new Set(['tests', 'test', '__tests__', 'benches', 'examples', 'spec']);
|
|
62
|
+
/**
|
|
63
|
+
* Test-file naming conventions, as ANCHORED patterns.
|
|
64
|
+
*
|
|
65
|
+
* Anchoring matters: a bare `includes('test_')` also matches `latest_parser.rs`,
|
|
66
|
+
* `contest_runner.ts`, and `fastest_path.rs`, silently dropping production files
|
|
67
|
+
* from the analysis entirely. `test_` is a Python/Rust *prefix* convention, so it
|
|
68
|
+
* only counts at the start of the basename.
|
|
69
|
+
*/
|
|
70
|
+
const TEST_FILENAME_PATTERNS = [
|
|
71
|
+
/\.test\./i, // foo.test.ts
|
|
72
|
+
/\.spec\./i, // foo.spec.ts
|
|
73
|
+
/_test\./i, // foo_test.rs, foo_test.go
|
|
74
|
+
/_spec\./i, // foo_spec.rb
|
|
75
|
+
/^test_/i, // test_foo.py (prefix only — NOT `latest_parser`)
|
|
76
|
+
/^test\./i, // test.ts
|
|
77
|
+
/^conftest\./i, // pytest fixture module — support code, not production
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Is this path test code rather than production code?
|
|
81
|
+
*
|
|
82
|
+
* #569 work item: "Exclude `tests/` files from 'uncovered production code'".
|
|
83
|
+
* Coverage *of* a test file is not a meaningful metric — reporting
|
|
84
|
+
* `tests/verifier_matrix.rs` at 79.2% with its own gap entry sends a developer
|
|
85
|
+
* to write tests for their tests.
|
|
86
|
+
*
|
|
87
|
+
* Erring in either direction costs something: a false negative reports test code
|
|
88
|
+
* as a production gap (the original bug), a false positive silently drops real
|
|
89
|
+
* production code from the denominator. Hence anchored patterns, not substrings.
|
|
90
|
+
*/
|
|
91
|
+
export function isTestPath(filePath, rootDir) {
|
|
92
|
+
const normalized = filePath.split(path.sep).join('/');
|
|
93
|
+
const basename = path.basename(normalized);
|
|
94
|
+
if (TEST_FILENAME_PATTERNS.some(p => p.test(basename)))
|
|
95
|
+
return true;
|
|
96
|
+
// Directory check must run on the path RELATIVE to the analysis root.
|
|
97
|
+
// Against the absolute path, a project that merely *lives* under a directory
|
|
98
|
+
// called `tests` — `~/examples/myapp`, a CI workspace at `/build/spec/proj`,
|
|
99
|
+
// a monorepo `packages/test-utils/` — has every one of its source files
|
|
100
|
+
// classified as test code, and coverage analysis returns null for the whole
|
|
101
|
+
// project. Only directories *inside* the analyzed tree are meaningful.
|
|
102
|
+
let relative = normalized;
|
|
103
|
+
if (rootDir) {
|
|
104
|
+
const normalizedRoot = path.resolve(rootDir).split(path.sep).join('/');
|
|
105
|
+
const absolute = path.resolve(filePath).split(path.sep).join('/');
|
|
106
|
+
if (absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}/`)) {
|
|
107
|
+
relative = absolute.slice(normalizedRoot.length).replace(/^\//, '');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Rust convention: `foo_test.rs` handled above; `tests/` dir handled here.
|
|
111
|
+
return relative.split('/').some(segment => TEST_DIRS.has(segment));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Blank out Rust string literals, char literals, and comments so brace counting
|
|
115
|
+
* sees only structural braces.
|
|
116
|
+
*
|
|
117
|
+
* Without this, a `#[cfg(test)]` module containing `assert_eq!("}", "}")` closes
|
|
118
|
+
* its range early, and every test line after that point is reported as uncovered
|
|
119
|
+
* *production* code — exactly the #569 failure this function exists to prevent.
|
|
120
|
+
* Characters are replaced with spaces rather than removed so line and column
|
|
121
|
+
* positions are preserved.
|
|
122
|
+
*/
|
|
123
|
+
function maskRustLiteralsAndComments(source) {
|
|
124
|
+
const out = source.split('');
|
|
125
|
+
const n = source.length;
|
|
126
|
+
let i = 0;
|
|
127
|
+
const blank = (from, to) => {
|
|
128
|
+
for (let j = from; j < to && j < n; j++) {
|
|
129
|
+
if (source[j] !== '\n')
|
|
130
|
+
out[j] = ' ';
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
while (i < n) {
|
|
134
|
+
const ch = source[i];
|
|
135
|
+
// Line comment
|
|
136
|
+
if (ch === '/' && source[i + 1] === '/') {
|
|
137
|
+
const end = source.indexOf('\n', i);
|
|
138
|
+
const stop = end === -1 ? n : end;
|
|
139
|
+
blank(i, stop);
|
|
140
|
+
i = stop;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Block comment (Rust allows nesting)
|
|
144
|
+
if (ch === '/' && source[i + 1] === '*') {
|
|
145
|
+
let depth = 0;
|
|
146
|
+
const start = i;
|
|
147
|
+
while (i < n) {
|
|
148
|
+
if (source[i] === '/' && source[i + 1] === '*') {
|
|
149
|
+
depth++;
|
|
150
|
+
i += 2;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (source[i] === '*' && source[i + 1] === '/') {
|
|
154
|
+
depth--;
|
|
155
|
+
i += 2;
|
|
156
|
+
if (depth === 0)
|
|
157
|
+
break;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
i++;
|
|
161
|
+
}
|
|
162
|
+
blank(start, i);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Raw string: r"...", r#"..."#, br##"..."##.
|
|
166
|
+
// The slice must be wide enough for the longest realistic hash run — a
|
|
167
|
+
// 15-hash opener is 17 chars and would otherwise fall through to the normal
|
|
168
|
+
// string branch, which stops at the first internal quote and re-exposes any
|
|
169
|
+
// `}` after it.
|
|
170
|
+
const raw = /^b?r(#*)"/.exec(source.slice(i, i + 300));
|
|
171
|
+
if (raw) {
|
|
172
|
+
const terminator = `"${raw[1]}`;
|
|
173
|
+
const bodyStart = i + raw[0].length;
|
|
174
|
+
const end = source.indexOf(terminator, bodyStart);
|
|
175
|
+
const stop = end === -1 ? n : end + terminator.length;
|
|
176
|
+
blank(i, stop);
|
|
177
|
+
i = stop;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Normal string literal
|
|
181
|
+
if (ch === '"') {
|
|
182
|
+
const start = i;
|
|
183
|
+
i++;
|
|
184
|
+
while (i < n) {
|
|
185
|
+
if (source[i] === '\\') {
|
|
186
|
+
i += 2;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (source[i] === '"') {
|
|
190
|
+
i++;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
i++;
|
|
194
|
+
}
|
|
195
|
+
blank(start, i);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// Char literal. Must not swallow a lifetime (`&'a str`), so only treat it as
|
|
199
|
+
// a literal when a closing quote lands where a char literal would put one.
|
|
200
|
+
if (ch === "'") {
|
|
201
|
+
let stop = -1;
|
|
202
|
+
if (source[i + 1] === '\\') {
|
|
203
|
+
const close = source.indexOf("'", i + 2);
|
|
204
|
+
if (close !== -1 && close <= i + 5)
|
|
205
|
+
stop = close + 1;
|
|
206
|
+
}
|
|
207
|
+
else if (source[i + 2] === "'") {
|
|
208
|
+
stop = i + 3;
|
|
209
|
+
}
|
|
210
|
+
if (stop !== -1) {
|
|
211
|
+
blank(i, stop);
|
|
212
|
+
i = stop;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
return out.join('');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Line ranges occupied by inline test code, 1-indexed and inclusive.
|
|
222
|
+
*
|
|
223
|
+
* Rust puts unit tests in the same file as the code under test, inside a
|
|
224
|
+
* `#[cfg(test)] mod tests { ... }` block. #569's first and worst symptom was
|
|
225
|
+
* exactly this: gap `gap-…-0` flagged `src/jwks.rs` 335–449 as "Missing test
|
|
226
|
+
* case", where line 335 is an `assert!` inside a *passing* test.
|
|
227
|
+
*
|
|
228
|
+
* Brace-matched rather than "assume the block runs to EOF", because a
|
|
229
|
+
* `#[cfg(test)]` module is not always last in the file.
|
|
230
|
+
*/
|
|
231
|
+
export function findInlineTestRanges(content, ext) {
|
|
232
|
+
if (ext !== '.rs')
|
|
233
|
+
return [];
|
|
234
|
+
// Brace-match against a copy with strings/chars/comments masked out. Counting
|
|
235
|
+
// a `}` inside `assert_eq!("}", "}")` closes the range early and re-exposes
|
|
236
|
+
// the rest of the test module as "uncovered production code". Attribute
|
|
237
|
+
// detection also runs on the MASKED text, so a `/* #[cfg(test)] */` in a
|
|
238
|
+
// comment doesn't cause real production code to be excluded.
|
|
239
|
+
const maskedLines = maskRustLiteralsAndComments(content).split('\n');
|
|
240
|
+
const ranges = [];
|
|
241
|
+
for (let i = 0; i < maskedLines.length; i++) {
|
|
242
|
+
if (!/#\s*\[\s*cfg\s*\(\s*test\s*\)\s*\]/.test(maskedLines[i]))
|
|
243
|
+
continue;
|
|
244
|
+
// The attribute must actually be attached to a `mod` — `#[cfg(test)]` also
|
|
245
|
+
// decorates consts, `use` statements, and single functions, and blindly
|
|
246
|
+
// taking "the next `{` within 10 lines" would then swallow the *following*
|
|
247
|
+
// production item's body and drop real code from the analysis.
|
|
248
|
+
let openLine = -1;
|
|
249
|
+
for (let j = i; j < Math.min(maskedLines.length, i + 10); j++) {
|
|
250
|
+
const line = maskedLines[j];
|
|
251
|
+
// Stop at a terminator before any `{`: that means this attribute applied
|
|
252
|
+
// to a non-block item (`#[cfg(test)] const X: &str = "...";`).
|
|
253
|
+
const braceAt = line.indexOf('{');
|
|
254
|
+
const semiAt = line.indexOf(';');
|
|
255
|
+
if (semiAt !== -1 && (braceAt === -1 || semiAt < braceAt))
|
|
256
|
+
break;
|
|
257
|
+
if (braceAt !== -1) {
|
|
258
|
+
// Only a module body counts. Look for `mod` between the attribute and
|
|
259
|
+
// the brace.
|
|
260
|
+
const headers = maskedLines.slice(i, j + 1).join(' ');
|
|
261
|
+
if (/\bmod\s+\w+/.test(headers))
|
|
262
|
+
openLine = j;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (openLine === -1)
|
|
267
|
+
continue;
|
|
268
|
+
let depth = 0;
|
|
269
|
+
let closeLine = -1;
|
|
270
|
+
for (let j = openLine; j < maskedLines.length; j++) {
|
|
271
|
+
for (const ch of maskedLines[j]) {
|
|
272
|
+
if (ch === '{')
|
|
273
|
+
depth++;
|
|
274
|
+
else if (ch === '}')
|
|
275
|
+
depth--;
|
|
276
|
+
}
|
|
277
|
+
if (depth <= 0) {
|
|
278
|
+
closeLine = j;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
ranges.push([i + 1, (closeLine === -1 ? maskedLines.length - 1 : closeLine) + 1]);
|
|
283
|
+
i = closeLine === -1 ? maskedLines.length : closeLine;
|
|
284
|
+
}
|
|
285
|
+
return ranges;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Effective line count for a file.
|
|
289
|
+
*
|
|
290
|
+
* #569 work item: "Never emit a line number beyond the file's length."
|
|
291
|
+
* `content.split('\n')` on a file with a trailing newline yields one extra
|
|
292
|
+
* empty element — which is how a 448-line `jwks.rs` produced a gap ending at
|
|
293
|
+
* line 449, and a 292-line `verify.rs` one ending at 293.
|
|
294
|
+
*/
|
|
295
|
+
export function countLines(content) {
|
|
296
|
+
if (content.length === 0)
|
|
297
|
+
return 0;
|
|
298
|
+
const parts = content.split('\n');
|
|
299
|
+
if (parts[parts.length - 1] === '')
|
|
300
|
+
parts.pop();
|
|
301
|
+
return parts.length;
|
|
302
|
+
}
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// Rust: real instrumentation
|
|
305
|
+
// ============================================================================
|
|
306
|
+
/**
|
|
307
|
+
* Find the Rust crate/workspace root for a target, or null if there isn't one.
|
|
308
|
+
*
|
|
309
|
+
* Walks UP from the target rather than only checking the target itself: the
|
|
310
|
+
* common invocation is `analyze(target: "src/")` or a package subdirectory, and
|
|
311
|
+
* `Cargo.toml` lives at the crate root. Checking only the target meant every
|
|
312
|
+
* such call silently skipped real instrumentation and fell through to labelled
|
|
313
|
+
* estimation — a measurement quietly downgraded to a guess.
|
|
314
|
+
*
|
|
315
|
+
* Bounded to `maxDepth` ancestors so a stray target can't walk to `/`.
|
|
316
|
+
*/
|
|
317
|
+
export function findCargoRoot(targetPath, maxDepth = 6) {
|
|
318
|
+
let dir = path.resolve(targetPath);
|
|
319
|
+
try {
|
|
320
|
+
// A target that doesn't exist must NOT resolve to its nearest crate — that
|
|
321
|
+
// would run a full-crate `cargo llvm-cov` for a typo'd path and report the
|
|
322
|
+
// whole crate's coverage as if it were the requested target's.
|
|
323
|
+
if (!fs.existsSync(dir))
|
|
324
|
+
return null;
|
|
325
|
+
if (!fs.statSync(dir).isDirectory())
|
|
326
|
+
dir = path.dirname(dir);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
for (let i = 0; i <= maxDepth; i++) {
|
|
332
|
+
if (fs.existsSync(path.join(dir, 'Cargo.toml')))
|
|
333
|
+
return dir;
|
|
334
|
+
const parent = path.dirname(dir);
|
|
335
|
+
if (parent === dir)
|
|
336
|
+
break; // reached filesystem root
|
|
337
|
+
dir = parent;
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/** Is there at least one `.rs` file in this subtree? Bounded, early-exit. */
|
|
342
|
+
function containsRustSources(dir, depth = 0) {
|
|
343
|
+
if (depth > 4)
|
|
344
|
+
return false;
|
|
345
|
+
let entries;
|
|
346
|
+
try {
|
|
347
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
if (entry.isFile() && path.extname(entry.name) === '.rs')
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
for (const entry of entries) {
|
|
357
|
+
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
358
|
+
if (containsRustSources(path.join(dir, entry.name), depth + 1))
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Does this target belong to a Rust crate/workspace *and* actually contain Rust
|
|
366
|
+
* code?
|
|
367
|
+
*
|
|
368
|
+
* Both halves are load-bearing. Without the upward walk, `analyze(target:"src/")`
|
|
369
|
+
* on a crate skips instrumentation. Without the `.rs` check, a polyglot monorepo
|
|
370
|
+
* with a `Cargo.toml` at its root would route `analyze(target:"frontend/")`
|
|
371
|
+
* through `cargo llvm-cov` and return whole-crate Rust coverage for a
|
|
372
|
+
* TypeScript directory.
|
|
373
|
+
*/
|
|
374
|
+
export function isRustProject(targetPath) {
|
|
375
|
+
const root = findCargoRoot(targetPath);
|
|
376
|
+
if (root === null)
|
|
377
|
+
return false;
|
|
378
|
+
const resolved = path.resolve(targetPath);
|
|
379
|
+
// The crate root itself always qualifies; a subdirectory must hold Rust code.
|
|
380
|
+
return resolved === root || containsRustSources(resolved);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Run `cargo llvm-cov` and parse its LCOV output.
|
|
384
|
+
*
|
|
385
|
+
* #569 work item: "Detect language and delegate to real instrumentation where
|
|
386
|
+
* available." LCOV rather than `--json`: it carries per-line `DA:` records (so
|
|
387
|
+
* we get genuine uncovered line numbers for gap detection) plus `BRF:`/`BRH:`
|
|
388
|
+
* and `FNF:`/`FNH:`, and it reuses `parseLcovInfo`, which is already exercised
|
|
389
|
+
* by the JS path.
|
|
390
|
+
*
|
|
391
|
+
* Returns `null` — never throws, never guesses — when cargo or the
|
|
392
|
+
* `cargo-llvm-cov` subcommand is unavailable or the run fails. The caller then
|
|
393
|
+
* decides whether to estimate (and label it) or report nothing.
|
|
394
|
+
*/
|
|
395
|
+
export async function collectRustCoverage(targetPath, options = {}) {
|
|
396
|
+
// Running cargo compiles and executes code from the analyzed repository
|
|
397
|
+
// (test binaries, `build.rs`, `.cargo/config.toml` runner directives).
|
|
398
|
+
// Honor the opt-out before doing any of that.
|
|
399
|
+
if (isCoverageExecDisabled())
|
|
400
|
+
return null;
|
|
401
|
+
const { execSync } = await import('child_process');
|
|
402
|
+
const timeout = options.timeoutMs ?? 300000;
|
|
403
|
+
// cargo must run from the crate root, not from whatever subdirectory the
|
|
404
|
+
// caller happened to point at.
|
|
405
|
+
const crateRoot = findCargoRoot(targetPath);
|
|
406
|
+
if (!crateRoot)
|
|
407
|
+
return null;
|
|
408
|
+
try {
|
|
409
|
+
execSync('cargo llvm-cov --version', {
|
|
410
|
+
cwd: crateRoot, timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return null; // cargo-llvm-cov not installed — not an error, just unavailable.
|
|
415
|
+
}
|
|
416
|
+
const outPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'aqe-rust-cov-')), 'lcov.info');
|
|
417
|
+
try {
|
|
418
|
+
execSync(`cargo llvm-cov --lcov --output-path ${JSON.stringify(outPath)}`, {
|
|
419
|
+
cwd: crateRoot, timeout, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
420
|
+
});
|
|
421
|
+
if (!fs.existsSync(outPath))
|
|
422
|
+
return null;
|
|
423
|
+
const parsed = parseLcovInfo(fs.readFileSync(outPath, 'utf-8'));
|
|
424
|
+
// Drop test targets from the production-coverage view (#569 contradiction 3).
|
|
425
|
+
const productionFiles = parsed.files.filter(f => !isTestPath(f.path, crateRoot));
|
|
426
|
+
if (productionFiles.length === 0)
|
|
427
|
+
return null;
|
|
428
|
+
const data = withRecomputedSummary(productionFiles);
|
|
429
|
+
const branchDataCollected = productionFiles.some(f => f.branches.total > 0);
|
|
430
|
+
return {
|
|
431
|
+
data,
|
|
432
|
+
provenance: {
|
|
433
|
+
method: 'cargo-llvm-cov',
|
|
434
|
+
estimated: false,
|
|
435
|
+
branchDataCollected,
|
|
436
|
+
functionDataCollected: productionFiles.some(f => f.functions.total > 0),
|
|
437
|
+
notes: branchDataCollected
|
|
438
|
+
? []
|
|
439
|
+
: [
|
|
440
|
+
'cargo-llvm-cov reported no branch data (Rust branch coverage requires a ' +
|
|
441
|
+
'nightly toolchain with -Z coverage-options=branch). Branch coverage is ' +
|
|
442
|
+
'reported as null rather than assumed.',
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
try {
|
|
452
|
+
fs.rmSync(path.dirname(outPath), { recursive: true, force: true });
|
|
453
|
+
}
|
|
454
|
+
catch { /* best-effort */ }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// Static estimation (clearly labelled as such)
|
|
459
|
+
// ============================================================================
|
|
460
|
+
/**
|
|
461
|
+
* Per-language patterns for counting declarations.
|
|
462
|
+
*
|
|
463
|
+
* #569 contradictions 4 and 5 both trace to a single JS-shaped regex,
|
|
464
|
+
* `/\b(function|=>)\b/`, applied to every language. On Rust it matched nothing,
|
|
465
|
+
* so `functionCount` fell to its `Math.max(..., 1)` floor and every file
|
|
466
|
+
* reported 0% function coverage — right next to a 100% branch figure derived
|
|
467
|
+
* from a formula that saturates. Language-specific patterns at least count the
|
|
468
|
+
* right things; the result is still an estimate and still labelled one.
|
|
469
|
+
*/
|
|
470
|
+
const LANGUAGE_PATTERNS = {
|
|
471
|
+
'.rs': { fn: /\bfn\s+\w+/g, branch: /\b(if|match|while|for)\b|\?\?|\|\||&&/g },
|
|
472
|
+
'.go': { fn: /\bfunc\s+\w*/g, branch: /\b(if|switch|case|for|select)\b|\|\||&&/g },
|
|
473
|
+
'.py': { fn: /\b(def|lambda)\b/g, branch: /\b(if|elif|for|while|except)\b|\band\b|\bor\b/g },
|
|
474
|
+
'.java': { fn: /\b(public|private|protected|static)[\w<>,\s[\]]+\w+\s*\(/g, branch: /\b(if|switch|case|for|while|catch)\b|\|\||&&/g },
|
|
475
|
+
'.rb': { fn: /\bdef\s+\w+/g, branch: /\b(if|elsif|unless|case|when|while)\b|\|\||&&/g },
|
|
476
|
+
default: { fn: /\bfunction\b|=>|\b\w+\s*\([^)]*\)\s*\{/g, branch: /\b(if|switch|case|for|while|catch)\b|\?\?|\|\||&&/g },
|
|
477
|
+
};
|
|
478
|
+
function patternsFor(ext) {
|
|
479
|
+
return LANGUAGE_PATTERNS[ext] ?? LANGUAGE_PATTERNS.default;
|
|
480
|
+
}
|
|
481
|
+
function countMatches(content, pattern) {
|
|
482
|
+
return (content.match(new RegExp(pattern.source, pattern.flags)) || []).length;
|
|
483
|
+
}
|
|
484
|
+
/** Recompute the aggregate summary from a file list. */
|
|
485
|
+
function withRecomputedSummary(files) {
|
|
486
|
+
const sum = (pick) => files.reduce((s, f) => s + pick(f), 0);
|
|
487
|
+
const safeDiv = (a, b) => (b > 0 ? Math.round((a / b) * 100) : 0);
|
|
488
|
+
const totalLines = sum(f => f.lines.total);
|
|
489
|
+
const coveredLines = sum(f => f.lines.covered);
|
|
490
|
+
return {
|
|
491
|
+
files,
|
|
492
|
+
summary: {
|
|
493
|
+
line: safeDiv(coveredLines, totalLines),
|
|
494
|
+
branch: safeDiv(sum(f => f.branches.covered), sum(f => f.branches.total)),
|
|
495
|
+
function: safeDiv(sum(f => f.functions.covered), sum(f => f.functions.total)),
|
|
496
|
+
statement: safeDiv(coveredLines, totalLines),
|
|
497
|
+
files: files.length,
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function walkSourceFiles(targetPath) {
|
|
502
|
+
const found = [];
|
|
503
|
+
// Classification is relative to the analysis root (see isTestPath).
|
|
504
|
+
const rootDir = targetPath;
|
|
505
|
+
function walk(dir, depth) {
|
|
506
|
+
if (depth > 6)
|
|
507
|
+
return;
|
|
508
|
+
let entries;
|
|
509
|
+
try {
|
|
510
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
for (const entry of entries) {
|
|
516
|
+
const full = path.join(dir, entry.name);
|
|
517
|
+
if (entry.isDirectory()) {
|
|
518
|
+
if (SKIP_DIRS.has(entry.name) || TEST_DIRS.has(entry.name))
|
|
519
|
+
continue;
|
|
520
|
+
walk(full, depth + 1);
|
|
521
|
+
}
|
|
522
|
+
else if (entry.isFile()) {
|
|
523
|
+
if (!SOURCE_EXTENSIONS.has(path.extname(entry.name)))
|
|
524
|
+
continue;
|
|
525
|
+
if (isTestPath(full, rootDir))
|
|
526
|
+
continue;
|
|
527
|
+
found.push(full);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
walk(targetPath, 0);
|
|
532
|
+
return found;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Static coverage estimate from source shape, for when no instrumentation could
|
|
536
|
+
* be run.
|
|
537
|
+
*
|
|
538
|
+
* This is a *guess*. It is returned with `estimated: true` and
|
|
539
|
+
* `branchDataCollected: false` so no caller can mistake it for a measurement —
|
|
540
|
+
* which is the core of #569, where a caller had "no signal that no
|
|
541
|
+
* instrumentation ran".
|
|
542
|
+
*
|
|
543
|
+
* Fixed here relative to the pre-#569 version:
|
|
544
|
+
* - trailing-newline off-by-one no longer emits line numbers past EOF
|
|
545
|
+
* - `#[cfg(test)]` blocks and `tests/` files are excluded from both the
|
|
546
|
+
* denominator and the gap list
|
|
547
|
+
* - declaration counting is language-aware instead of JS-shaped
|
|
548
|
+
* - branch counts are reported as uncollected rather than invented
|
|
549
|
+
*/
|
|
550
|
+
export function buildEstimatedCoverage(targetPath) {
|
|
551
|
+
if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isDirectory())
|
|
552
|
+
return null;
|
|
553
|
+
const sourceFiles = walkSourceFiles(targetPath);
|
|
554
|
+
if (sourceFiles.length === 0)
|
|
555
|
+
return null;
|
|
556
|
+
const files = sourceFiles.map(filePath => {
|
|
557
|
+
let content = '';
|
|
558
|
+
try {
|
|
559
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
560
|
+
}
|
|
561
|
+
catch { /* unreadable — treat as empty */ }
|
|
562
|
+
const ext = path.extname(filePath);
|
|
563
|
+
const totalLines = countLines(content);
|
|
564
|
+
// Lines belonging to inline test modules are not production code and must
|
|
565
|
+
// never be reported as an uncovered production gap.
|
|
566
|
+
const testRanges = findInlineTestRanges(content, ext);
|
|
567
|
+
const isTestLine = (line) => testRanges.some(([lo, hi]) => line >= lo && line <= hi);
|
|
568
|
+
const testLineCount = testRanges.reduce((s, [lo, hi]) => s + (hi - lo + 1), 0);
|
|
569
|
+
const productionLines = Math.max(totalLines - testLineCount, 0);
|
|
570
|
+
const patterns = patternsFor(ext);
|
|
571
|
+
const functionCount = countMatches(content, patterns.fn);
|
|
572
|
+
// Presence of *any* test signal for this file: a co-located test module, or
|
|
573
|
+
// a sibling/mirrored test file.
|
|
574
|
+
const hasInlineTests = testRanges.length > 0;
|
|
575
|
+
const hasTestFile = hasSiblingTestFile(filePath);
|
|
576
|
+
const hasTest = hasInlineTests || hasTestFile;
|
|
577
|
+
const branchCount = countMatches(content, patterns.branch);
|
|
578
|
+
const complexityPenalty = Math.min(branchCount * 0.005, 0.15);
|
|
579
|
+
const sizePenalty = Math.min(productionLines * 0.0001, 0.1);
|
|
580
|
+
const coverageRate = Math.max(0.05, Math.min(0.95, hasTest ? 0.85 - complexityPenalty - sizePenalty : 0.20));
|
|
581
|
+
const coveredLines = Math.floor(productionLines * coverageRate);
|
|
582
|
+
// Uncovered lines are drawn from real production line numbers only:
|
|
583
|
+
// within [1, totalLines] and never inside a test block.
|
|
584
|
+
const candidates = [];
|
|
585
|
+
for (let line = 1; line <= totalLines; line++) {
|
|
586
|
+
if (!isTestLine(line))
|
|
587
|
+
candidates.push(line);
|
|
588
|
+
}
|
|
589
|
+
const uncoveredLines = candidates.slice(coveredLines);
|
|
590
|
+
return {
|
|
591
|
+
path: filePath,
|
|
592
|
+
lines: { covered: coveredLines, total: productionLines },
|
|
593
|
+
// Branch and function data were NOT collected. Reporting 0/0 keeps the
|
|
594
|
+
// aggregate honest; the provenance flags tell the caller to render null.
|
|
595
|
+
branches: { covered: 0, total: 0 },
|
|
596
|
+
functions: { covered: 0, total: functionCount },
|
|
597
|
+
statements: { covered: coveredLines, total: productionLines },
|
|
598
|
+
uncoveredLines,
|
|
599
|
+
uncoveredBranches: [],
|
|
600
|
+
};
|
|
601
|
+
});
|
|
602
|
+
return {
|
|
603
|
+
data: withRecomputedSummary(files),
|
|
604
|
+
provenance: {
|
|
605
|
+
method: 'static-estimation',
|
|
606
|
+
estimated: true,
|
|
607
|
+
branchDataCollected: false,
|
|
608
|
+
functionDataCollected: false,
|
|
609
|
+
notes: [
|
|
610
|
+
'No instrumentation ran. These numbers are a STATIC ESTIMATE derived from ' +
|
|
611
|
+
'source shape (file size, declaration counts, presence of a test file) — ' +
|
|
612
|
+
'they are not measured coverage and must not be reported as such.',
|
|
613
|
+
'Branch and function coverage were not collected and are reported as null.',
|
|
614
|
+
],
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/** Does a sibling or mirrored test file exist for this source file? */
|
|
619
|
+
function hasSiblingTestFile(filePath) {
|
|
620
|
+
const ext = path.extname(filePath);
|
|
621
|
+
const stem = filePath.slice(0, -ext.length);
|
|
622
|
+
const candidates = [
|
|
623
|
+
`${stem}.test${ext}`,
|
|
624
|
+
`${stem}.spec${ext}`,
|
|
625
|
+
`${stem}_test${ext}`,
|
|
626
|
+
`${stem}.test${ext}`.replace('/src/', '/tests/'),
|
|
627
|
+
`${stem}.spec${ext}`.replace('/src/', '/tests/'),
|
|
628
|
+
`${stem}${ext}`.replace('/src/', '/tests/'),
|
|
629
|
+
];
|
|
630
|
+
return candidates.some(c => {
|
|
631
|
+
try {
|
|
632
|
+
return fs.existsSync(c);
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
//# sourceMappingURL=coverage-collection.js.map
|