@qwen-code/qwen-code 0.21.3 → 0.21.4
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/bundled/qc-helper/docs/configuration/settings.md +13 -0
- package/bundled/qc-helper/docs/features/channels/github.md +12 -8
- package/bundled/qc-helper/docs/features/code-review.md +14 -1
- package/bundled/qc-helper/docs/features/commands.md +46 -40
- package/bundled/qc-helper/docs/qwen-serve.md +27 -26
- package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +50 -19
- package/bundled/review/SKILL.md +67 -22
- package/chunks/{MaxSizedBox-OJR636GP.js → MaxSizedBox-5XDFQHTQ.js} +8 -7
- package/chunks/{StandaloneSessionPicker-AMMBF5UY.js → StandaloneSessionPicker-DLADA54K.js} +21 -20
- package/chunks/{acpAgent-M57LVIQA.js → acpAgent-MVEFZSBS.js} +48 -42
- package/chunks/{agent-PE37ARLM.js → agent-RB6LCRZX.js} +8 -7
- package/chunks/{agent-headless-45ASME4R.js → agent-headless-272ZACJV.js} +8 -7
- package/chunks/{anthropicContentGenerator-WCBYB6JG.js → anthropicContentGenerator-3CAHEZBM.js} +4 -2
- package/chunks/{bridge-K3ZRPO4D.js → bridge-GXUXFSHK.js} +9 -8
- package/chunks/{ca-4OJN75WT.js → ca-NYKNH222.js} +4 -0
- package/chunks/{channel-management-service-IXJ2MZTS.js → channel-management-service-UHX56NBO.js} +1 -1
- package/chunks/{channel-settings-store-PEAR3IME.js → channel-settings-store-5UA2SABE.js} +14 -13
- package/chunks/{chunk-IWGTGH4G.js → chunk-2CCUHZP5.js} +2 -2
- package/chunks/{chunk-HUIQ4FYC.js → chunk-2JN5V3I7.js} +3 -3
- package/chunks/{chunk-BSKUTHLO.js → chunk-2ON2O4MD.js} +16 -3
- package/chunks/{chunk-QPSYIV2O.js → chunk-2T3RYG4L.js} +2039 -563
- package/chunks/{chunk-ZGSUFIL5.js → chunk-3PHUGFPH.js} +1 -1
- package/chunks/{chunk-QWW6I2UO.js → chunk-4CTV3FZH.js} +400 -33
- package/chunks/{chunk-6NE5NTHJ.js → chunk-4PZ7SPD5.js} +1 -1
- package/chunks/{chunk-2LZCLQIS.js → chunk-5XGI4IPK.js} +4 -4
- package/chunks/{chunk-GMSBXEH4.js → chunk-6KQUOXT5.js} +6 -6
- package/chunks/{chunk-2MI7ZTRT.js → chunk-6ME3PVMM.js} +4 -4
- package/chunks/{chunk-SRY26X4H.js → chunk-7UXI6P72.js} +11 -2
- package/chunks/{chunk-YU6TBRTA.js → chunk-A7PONO5G.js} +1 -1
- package/chunks/{chunk-N42C2FU3.js → chunk-AFJSRJTO.js} +1 -1
- package/chunks/{chunk-H6FIBBXK.js → chunk-AWTLFUVN.js} +11 -11
- package/chunks/{chunk-HIXVWF7I.js → chunk-B3EBXISV.js} +2 -2
- package/chunks/{chunk-3XF4WT3B.js → chunk-BC4LTKEG.js} +1 -1
- package/chunks/{chunk-ZJES4C3I.js → chunk-BOWKLLWA.js} +2 -2
- package/chunks/{chunk-SKSF3LGQ.js → chunk-C5YFS6VR.js} +3 -3
- package/chunks/{chunk-IF6K42YJ.js → chunk-C6FU76AA.js} +1 -1
- package/chunks/{chunk-VUNRNRN6.js → chunk-DHNFB7NC.js} +4 -4
- package/chunks/{chunk-LZV2BZPL.js → chunk-DJA7YBLJ.js} +2 -2
- package/chunks/{chunk-K2J5JZ6B.js → chunk-DOCBJIBL.js} +1 -1
- package/chunks/{chunk-X6WUYMOV.js → chunk-DTAPI453.js} +2 -2
- package/chunks/{chunk-XUMAD5IT.js → chunk-DZPOYHBT.js} +1 -1
- package/chunks/{chunk-UQDFEU47.js → chunk-E2SYUEIA.js} +463 -114
- package/chunks/{chunk-DSGJLHM3.js → chunk-EIAPOG76.js} +3 -3
- package/chunks/{chunk-EGYJRIDF.js → chunk-EJXATV5R.js} +2 -2
- package/chunks/{chunk-BKGA75MU.js → chunk-FPPVCCAH.js} +1 -1
- package/chunks/{chunk-KXWERR6P.js → chunk-FV4TUUDE.js} +10 -10
- package/chunks/{chunk-WY2ENRDN.js → chunk-G6KI6B5L.js} +1 -1
- package/chunks/{chunk-H6ZYP74A.js → chunk-GDREW3U5.js} +1 -1
- package/chunks/{chunk-BKXTHMOZ.js → chunk-HBCKZF2A.js} +1 -1
- package/chunks/chunk-HGCQ5SE7.js +136 -0
- package/chunks/{chunk-HEBW74ZW.js → chunk-HI4YGRJD.js} +3 -3
- package/chunks/{chunk-5JPVOYWA.js → chunk-I32URQI7.js} +4 -4
- package/chunks/{chunk-5VYFXHY2.js → chunk-IMGNGOGC.js} +3 -3
- package/chunks/{chunk-4K3RV3OA.js → chunk-JDJ7SC4E.js} +7 -3
- package/chunks/{chunk-22YFMJBW.js → chunk-KB6VSG2U.js} +58 -6
- package/chunks/{chunk-R7XNRHYD.js → chunk-KHSJSMFS.js} +1 -1
- package/chunks/{chunk-LGT3YMLN.js → chunk-LEZFUYPM.js} +1 -1
- package/chunks/{chunk-TZTBZ25J.js → chunk-LLMGP5IC.js} +1 -1
- package/chunks/{chunk-NW57GHNL.js → chunk-LPLMUGIM.js} +3 -3
- package/chunks/{chunk-L7FTDKCD.js → chunk-M2L4TMH3.js} +21 -3
- package/chunks/{chunk-RCXCGZG6.js → chunk-M6RNMBUO.js} +3 -3
- package/chunks/{chunk-D2GPKXCQ.js → chunk-O4IWFYUW.js} +4 -4
- package/chunks/{chunk-Q3G5L5KF.js → chunk-OOPFAIWP.js} +1 -1
- package/chunks/{chunk-MTR4JWQJ.js → chunk-P5UJMCVS.js} +4 -4
- package/chunks/{chunk-FF75UUB2.js → chunk-PCVMRBLN.js} +1 -1
- package/chunks/{chunk-XMJO6C44.js → chunk-PQ5JFCNL.js} +3 -3
- package/chunks/{chunk-SQ3YD5MI.js → chunk-PQEIKQXN.js} +1 -1
- package/chunks/{chunk-3MWNROHB.js → chunk-QXTGHZCX.js} +3 -3
- package/chunks/{chunk-TXABHVZD.js → chunk-QZQ72YF6.js} +1 -1
- package/chunks/{chunk-YPUHMQXV.js → chunk-RL2NPV5E.js} +1 -1
- package/chunks/{chunk-FXVHVCNA.js → chunk-RNKGMZEW.js} +1 -1
- package/chunks/{chunk-EIMKVOSF.js → chunk-SBBHZ3YT.js} +1 -1
- package/chunks/{chunk-QDWLUXKP.js → chunk-SV5X2EUV.js} +1 -1
- package/chunks/{chunk-YWMDTMAU.js → chunk-THLV7YGY.js} +2 -2
- package/chunks/{chunk-H35Q5CEG.js → chunk-TXZDRD36.js} +6 -6
- package/chunks/{chunk-MUEJ4R3E.js → chunk-UATJZOAJ.js} +3 -3
- package/chunks/{chunk-M5LIY7Y6.js → chunk-URVJ4AZC.js} +1 -1
- package/chunks/{chunk-4AGGSHCH.js → chunk-WDLQGFWP.js} +196 -52
- package/chunks/{chunk-RL6J3BPE.js → chunk-XGA6XHH3.js} +3 -3
- package/chunks/{chunk-QWDVZH24.js → chunk-XPAY4EMH.js} +3 -3
- package/chunks/{chunk-MCXMWLFT.js → chunk-XSOAW7FC.js} +5 -5
- package/chunks/{chunk-RR224BUP.js → chunk-ZLJQIGCH.js} +45 -1
- package/chunks/{chunk-FG63DZQA.js → chunk-ZRQY5BBS.js} +9 -9
- package/chunks/{computer-use-UJMK6JQU.js → computer-use-MDOMIVTL.js} +8 -7
- package/chunks/{config-utils-7COTJMWK.js → config-utils-E3ZQTHGM.js} +2 -2
- package/chunks/{contextCommand-4YABGF4S.js → contextCommand-FCK3CB37.js} +10 -9
- package/chunks/{core-runtime-7M4VM3DA.js → core-runtime-UHRHHU2O.js} +8 -7
- package/chunks/{create-sub-session-HOFDW4N7.js → create-sub-session-MIXUVLWW.js} +20 -13
- package/chunks/{daemon-status-provider-W2U5Q3J5.js → daemon-status-provider-6LC7LUNR.js} +13 -12
- package/chunks/{daemon-trust-policy-IFICWPXP.js → daemon-trust-policy-Y36HGRAM.js} +13 -12
- package/chunks/{daemon-trust-policy-monitor-GX7EO6FW.js → daemon-trust-policy-monitor-ROTBBDOH.js} +13 -12
- package/chunks/{de-A6TI4LBB.js → de-VL76CPLV.js} +4 -0
- package/chunks/{deferred-core-runtime-U36OOOKE.js → deferred-core-runtime-ZQ4JIAJD.js} +8 -7
- package/chunks/{display-image-VS3TXYQH.js → display-image-2WOG27DY.js} +1 -1
- package/chunks/{dist-NEBRI7WO.js → dist-C5U4LJKW.js} +95 -0
- package/chunks/{dist-4KAX7CCL.js → dist-IKGVITP3.js} +401 -24
- package/chunks/{earlyInputCapture-5ZWDH3SB.js → earlyInputCapture-GPRUNXTP.js} +8 -7
- package/chunks/{edit-J7SW6UP6.js → edit-YK3UP6D2.js} +8 -7
- package/chunks/{en-L4UQDLWW.js → en-IRJ2WN5T.js} +4 -0
- package/chunks/{enter-worktree-NRLOHRB6.js → enter-worktree-QMVQW7UG.js} +8 -7
- package/chunks/{enterPlanMode-GDSTEFSM.js → enterPlanMode-TZAYQ5MP.js} +8 -7
- package/chunks/{environment-KHYEKG5W.js → environment-3UQURCZG.js} +10 -9
- package/chunks/{errors-VNLAJV3O.js → errors-VBIMTNNI.js} +10 -9
- package/chunks/{exit-worktree-ARVID7DF.js → exit-worktree-4EC7LBBC.js} +8 -7
- package/chunks/{exitPlanMode-4ZFY7KUH.js → exitPlanMode-K5Y3REU2.js} +8 -7
- package/chunks/{fast-path-KA65VREJ.js → fast-path-KS6IE7YR.js} +11 -2
- package/chunks/{fr-QF7RI5ZE.js → fr-GK3HQYVB.js} +4 -0
- package/chunks/{gemini-5PMCNPF7.js → gemini-LTUCUOJH.js} +42 -40
- package/chunks/{geminiContentGenerator-NFSYJ4HA.js → geminiContentGenerator-M3HJTHKR.js} +1 -1
- package/chunks/{glob-F3EAUDAX.js → glob-SKUYGTF2.js} +8 -7
- package/chunks/{grep-UJMPVZER.js → grep-SQ7RD7SV.js} +8 -7
- package/chunks/{handleAutoUpdate-MG4FRZPN.js → handleAutoUpdate-OK7OX23A.js} +12 -11
- package/chunks/{i18n-EVHV4ZF3.js → i18n-P77YBKY4.js} +9 -8
- package/chunks/{image-gen-UC7XVHI7.js → image-gen-2ZWPS42A.js} +1 -1
- package/chunks/{initializer-O5B6HUBI.js → initializer-PIOYXVW7.js} +13 -12
- package/chunks/{installationInfo-2LIKCNVX.js → installationInfo-IWMM6DPV.js} +9 -8
- package/chunks/{ja-URQESVGW.js → ja-3FA46IKD.js} +4 -0
- package/chunks/{list-BPKIE3Z4.js → list-3DCR4TRI.js} +16 -15
- package/chunks/{loadedSettingsAdapter-NGLFF4V4.js → loadedSettingsAdapter-EJC6CU5N.js} +13 -12
- package/chunks/{loggingContentGenerator-7TFESRG5.js → loggingContentGenerator-2JEFU25T.js} +3 -3
- package/chunks/{managed-npm-update-CLZGGV7F.js → managed-npm-update-EGVQPSBR.js} +9 -8
- package/chunks/{mcp-X5OZOOXX.js → mcp-KFFH2MBV.js} +13 -12
- package/chunks/{monitor-TGSVSAZZ.js → monitor-6Y3NDMTI.js} +8 -7
- package/chunks/{nonInteractiveCli-GUMJ5JLG.js → nonInteractiveCli-426IQDPE.js} +40 -38
- package/chunks/{notebook-edit-EZISE7IC.js → notebook-edit-373IQQLE.js} +8 -7
- package/chunks/{openaiContentGenerator-ZAKS5A6P.js → openaiContentGenerator-PGFSOMIQ.js} +3 -3
- package/chunks/{pidfile-PCMBX4LR.js → pidfile-Y4INDMHG.js} +8 -7
- package/chunks/{processUtils-2CSVE5BW.js → processUtils-6BCPCJZW.js} +2 -2
- package/chunks/{pt-EEEQXRUI.js → pt-3AOBKPIJ.js} +4 -0
- package/chunks/{qwenContentGenerator-S3H6PIC4.js → qwenContentGenerator-QC3KSHSX.js} +8 -8
- package/chunks/{qwenOAuth2-MNU425VE.js → qwenOAuth2-IOAXKJ3L.js} +1 -1
- package/chunks/{read-file-HXJXHBI6.js → read-file-2T54FYZV.js} +2 -2
- package/chunks/{resumeHistoryUtils-4QAMC2S6.js → resumeHistoryUtils-O62RDCZ3.js} +11 -10
- package/chunks/{ripGrep-MCETPF2E.js → ripGrep-3H72KY7S.js} +8 -7
- package/chunks/{ru-WGZDYQAB.js → ru-VJRAMC4W.js} +4 -0
- package/chunks/{run-qwen-serve-3NMODTXV.js → run-qwen-serve-3AIDNNHO.js} +82 -35
- package/chunks/{runtime-AQOV7DVP.js → runtime-AZ4EHHFV.js} +16 -15
- package/chunks/{scheduler-DN7SZ6ZE.js → scheduler-L4UUCU4Z.js} +8 -7
- package/chunks/{send-message-HGCVVDTZ.js → send-message-LHJLVWFD.js} +2 -2
- package/chunks/{serve-WZB45HVT.js → serve-CLXGSYOE.js} +14 -12
- package/chunks/{server-GOTZFEEZ.js → server-MEAE3WMM.js} +36 -34
- package/chunks/{session-23LOFLWN.js → session-OSLD2LT5.js} +41 -39
- package/chunks/{settings-LI2ORVXC.js → settings-ILWAOKA4.js} +12 -11
- package/chunks/{shell-GLC6OP3C.js → shell-RT5DBH6B.js} +8 -7
- package/chunks/{skill-C7W6M4GO.js → skill-LVGA3ATZ.js} +2 -2
- package/chunks/{skill-settings-W25NCK5I.js → skill-settings-WTZE7EPE.js} +12 -11
- package/chunks/{spawnChannel-OHJLEBLD.js → spawnChannel-BOBS7DVM.js} +8 -7
- package/chunks/{standalone-update-E4V5V7WT.js → standalone-update-Y5JB4S2K.js} +10 -9
- package/chunks/{startInteractiveUI-FKILLW7Q.js → startInteractiveUI-ZV25CD2S.js} +62 -50
- package/chunks/{task-create-CSCEG4SM.js → task-create-4ALZMGP5.js} +3 -3
- package/chunks/{task-update-C52LNFU5.js → task-update-IKCEEWJ6.js} +3 -3
- package/chunks/{team-create-HBIJ7NHT.js → team-create-ZVPQIXCP.js} +8 -7
- package/chunks/{team-plan-approval-IBJOKS4J.js → team-plan-approval-5UHUBY3W.js} +8 -7
- package/chunks/{terminal-image-renderer-BOQJPVLB.js → terminal-image-renderer-NWC2RALG.js} +8 -7
- package/chunks/{theme-manager-NSS7VLUD.js → theme-manager-5KICSBOB.js} +8 -7
- package/chunks/{todoWrite-YKAA57W2.js → todoWrite-FTZWQRZH.js} +2 -1
- package/chunks/{tool-search-DEAS54VG.js → tool-search-2AOU77H4.js} +4 -4
- package/chunks/{total-session-admission-4LWTX3WD.js → total-session-admission-FR7RMHVL.js} +9 -8
- package/chunks/{trustedFolders-NF3J2TA7.js → trustedFolders-V7UUPQXO.js} +9 -8
- package/chunks/{update-relaunch-WUHRH5V3.js → update-relaunch-TBWRMQD6.js} +5 -5
- package/chunks/{updateCheck-7EG4BLBH.js → updateCheck-42SPCLH7.js} +11 -10
- package/chunks/{useAutoAcceptIndicator-ZRV56GVP.js → useAutoAcceptIndicator-4C2KM345.js} +14 -13
- package/chunks/{validateNonInterActiveAuth-WC2FPE3I.js → validateNonInterActiveAuth-UVFRXV3Z.js} +37 -35
- package/chunks/{version-6SS7KCUP.js → version-HKTV6TPN.js} +1 -1
- package/chunks/{web-fetch-Y5QQMPKA.js → web-fetch-ZRXSV5AN.js} +2 -2
- package/chunks/{web-search-QKKEU723.js → web-search-WOLV3L6T.js} +2 -2
- package/chunks/{workflow-PWIU6Q2M.js → workflow-F63ES3OB.js} +9 -8
- package/chunks/{workspace-providers-status-B2DM6FNK.js → workspace-providers-status-PJMGMYMC.js} +15 -14
- package/chunks/{workspace-registration-store-7Y33TTHJ.js → workspace-registration-store-LNBOX5TJ.js} +1 -1
- package/chunks/{workspace-registry-TSNJKLPC.js → workspace-registry-4PR7VDQ3.js} +9 -8
- package/chunks/{workspace-service-4I2QHDX4.js → workspace-service-PVI3VUNQ.js} +16 -15
- package/chunks/{workspace-skills-status-HVXNWVGR.js → workspace-skills-status-2Q5ZISO6.js} +13 -12
- package/chunks/{workspace-trust-reconciler-RDBBGMWP.js → workspace-trust-reconciler-NOILUH3C.js} +14 -13
- package/chunks/{write-file-RQ4DG5RY.js → write-file-MOM4JDHG.js} +8 -7
- package/chunks/{zh-72XPUG5P.js → zh-ARWDLDLR.js} +4 -0
- package/chunks/{zh-TW-LTB4SRH3.js → zh-TW-Y4RGI3P7.js} +4 -0
- package/chunks/{zoom-image-O5YCB7WN.js → zoom-image-E3VHRCFU.js} +2 -2
- package/cli.js +12 -12
- package/locales/ca.js +8 -0
- package/locales/de.js +8 -0
- package/locales/en.js +8 -0
- package/locales/fr.js +8 -0
- package/locales/ja.js +8 -0
- package/locales/pt.js +8 -0
- package/locales/ru.js +8 -0
- package/locales/zh-TW.js +7 -0
- package/locales/zh.js +7 -0
- package/package.json +3 -3
- package/web-shell/assets/{arc-Bd7d781i.js → arc-GpdmhIxr.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-CpnOJqNE.js → architectureDiagram-3BPJPVTR-C-LhCgH_.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-NxVTiWnk.js → blockDiagram-GPEHLZMM-CzNdTCm-.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-BWT75l82.js → c4Diagram-AAUBKEIU-Ce7hbrRM.js} +1 -1
- package/web-shell/assets/channel-95y6Rcp3.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-Dzz03hk6.js → chunk-2J33WTMH-BBSEn1PK.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-DRF-egfm.js → chunk-4BX2VUAB-_llgjfe5.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-Co15vq5x.js → chunk-55IACEB6-9fJZ_oZE.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-DHSUk1J7.js → chunk-727SXJPM-DgEzQWOu.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-Br8u_E2R.js → chunk-AQP2D5EJ-B4RthUTc.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-DXbPTnqF.js → chunk-FMBD7UC4-CDI-emMW.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-BleQdi4d.js → chunk-ND2GUHAM-jM2-yOd8.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-CqgBf-95.js → chunk-QZHKN3VN-XGZHlKPF.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-pfzVkmjQ.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-pfzVkmjQ.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-WS7b13Yg.js → cose-bilkent-S5V4N54A-90nbW1xQ.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-v_55ki8o.js → dagre-BM42HDAG-BtzlyqZ9.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-DaYAjbdK.js → diagram-2AECGRRQ-DAaqGWaH.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-D_Hy-L64.js → diagram-5GNKFQAL-CxlzSIFa.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-dB06cZyD.js → diagram-KO2AKTUF-8kj-1H6u.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-DzSluD4F.js → diagram-LMA3HP47-6NhjFwRF.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-Cvb6CeXQ.js → diagram-OG6HWLK6-Bn9kvInz.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-7eB2Hmov.js → erDiagram-TEJ5UH35-CaRoGsAt.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-B08iGCw_.js → flowDiagram-I6XJVG4X-TkLweHdf.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BgsrCHq-.js → ganttDiagram-6RSMTGT7-ruwhCKBl.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DQLurQ2i.js → gitGraphDiagram-PVQCEYII-DNI_PxoU.js} +1 -1
- package/web-shell/assets/{index-DJ-z1Oba.js → index-T4KluHvu.js} +336 -336
- package/web-shell/assets/{index-Bi1dP2mU.css → index-WxSvj2tI.css} +2 -2
- package/web-shell/assets/{index-B_1Z0Mgr.js → index-p1gz-sxE.js} +1 -1
- package/web-shell/assets/{infoDiagram-5YYISTIA-DXZGO8qB.js → infoDiagram-5YYISTIA-DarXq1-A.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-g7gbuH2w.js → ishikawaDiagram-YF4QCWOH-GxDaDlT0.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-euefICGn.js → journeyDiagram-JHISSGLW-mDEoGBzY.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-Dws8-mjG.js → kanban-definition-UN3LZRKU-Ds0t-Mup.js} +1 -1
- package/web-shell/assets/{linear-C63F0tyh.js → linear-05nJ0xZ7.js} +1 -1
- package/web-shell/assets/{mermaid.core-fLrhp_4-.js → mermaid.core-Bua24ThM.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-qRkydfT-.js → mindmap-definition-RKZ34NQL-D_zRdvUS.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-BZ0wQaKN.js → pieDiagram-4H26LBE5-DZHr1x16.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-Fjq6GbSZ.js → quadrantDiagram-W4KKPZXB-HA06Wf0h.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-CqWvjLHU.js → requirementDiagram-4Y6WPE33-DH3urmJf.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-VcXX6PVZ.js → sankeyDiagram-5OEKKPKP-C4xgz0TT.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-BJSRtxFr.js → sequenceDiagram-3UESZ5HK-DAUoZWgv.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-Dt9zUwmZ.js → stateDiagram-AJRCARHV-v7NzXy4l.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CtdFexEf.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-DQwlNRws.js → timeline-definition-PNZ67QCA-B3GTYR2W.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-DwxjUEtQ.js → vennDiagram-CIIHVFJN-BWHjSPAq.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-D3QdMSpJ.js → wardley-L42UT6IY-m9r6IUBC.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BddN8oK8.js → wardleyDiagram-YWT4CUSO-kT0FkVfK.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-BUNuBrRJ.js → xychartDiagram-2RQKCTM6-CYRm10z5.js} +1 -1
- package/web-shell/index.html +2 -2
- package/web-shell/assets/channel-A434sWK5.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-CwPuhldB.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwPuhldB.js +0 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CCxQt56w.js +0 -1
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
import {
|
|
4
4
|
serveCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2ON2O4MD.js";
|
|
6
6
|
import {
|
|
7
7
|
listCommand
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-P5UJMCVS.js";
|
|
9
9
|
import {
|
|
10
10
|
getErrorMessage as getErrorMessage2
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EJXATV5R.js";
|
|
12
12
|
import {
|
|
13
13
|
ObservedChannelContactStore
|
|
14
14
|
} from "./chunk-DYA25Y7U.js";
|
|
@@ -46,22 +46,22 @@ import {
|
|
|
46
46
|
registerToolCallDispatch,
|
|
47
47
|
selectFirstModel,
|
|
48
48
|
sessionsPath
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-5XGI4IPK.js";
|
|
50
50
|
import {
|
|
51
51
|
getExtensionManager,
|
|
52
52
|
requestChoicePluginNonInteractive,
|
|
53
53
|
requestConsentNonInteractive,
|
|
54
54
|
requestConsentOrFail,
|
|
55
55
|
resolveExtensionCommandScope
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-O4IWFYUW.js";
|
|
57
57
|
import {
|
|
58
58
|
collectProviderModelsForProtocol,
|
|
59
59
|
getAuthTypeFromEnv,
|
|
60
60
|
resolveCliGenerationConfig
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-IMGNGOGC.js";
|
|
62
62
|
import {
|
|
63
63
|
parseChannelConfig
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-LEZFUYPM.js";
|
|
65
65
|
import {
|
|
66
66
|
findCliEntryPath
|
|
67
67
|
} from "./chunk-V6WHFGST.js";
|
|
@@ -128,29 +128,29 @@ import {
|
|
|
128
128
|
assembleMcpServers,
|
|
129
129
|
getPendingGatedMcpServers,
|
|
130
130
|
mcpCommand
|
|
131
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-6ME3PVMM.js";
|
|
132
132
|
import {
|
|
133
133
|
loadSettings
|
|
134
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-KB6VSG2U.js";
|
|
135
135
|
import {
|
|
136
136
|
detectSystemLanguage,
|
|
137
137
|
getCurrentLanguage,
|
|
138
138
|
initializeI18n,
|
|
139
139
|
resolveLanguageSetting,
|
|
140
140
|
t
|
|
141
|
-
} from "./chunk-
|
|
141
|
+
} from "./chunk-FV4TUUDE.js";
|
|
142
142
|
import {
|
|
143
143
|
loadEnvironment
|
|
144
|
-
} from "./chunk-
|
|
144
|
+
} from "./chunk-4PZ7SPD5.js";
|
|
145
145
|
import {
|
|
146
146
|
getCliVersion
|
|
147
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-HBCKZF2A.js";
|
|
148
148
|
import {
|
|
149
149
|
getPackageJson
|
|
150
150
|
} from "./chunk-VJNBRRZG.js";
|
|
151
151
|
import {
|
|
152
152
|
isWorkspaceTrusted
|
|
153
|
-
} from "./chunk-
|
|
153
|
+
} from "./chunk-URVJ4AZC.js";
|
|
154
154
|
import {
|
|
155
155
|
require_strip_json_comments
|
|
156
156
|
} from "./chunk-ALGGS7UH.js";
|
|
@@ -191,7 +191,7 @@ import {
|
|
|
191
191
|
unquoteCStylePath,
|
|
192
192
|
updateChannelMemoryEntry,
|
|
193
193
|
updateSetting
|
|
194
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-E2SYUEIA.js";
|
|
195
195
|
import {
|
|
196
196
|
nextFireTime,
|
|
197
197
|
parseCron
|
|
@@ -206,14 +206,14 @@ import {
|
|
|
206
206
|
import {
|
|
207
207
|
getAllGeminiMdFilenames,
|
|
208
208
|
setGeminiMdFilename
|
|
209
|
-
} from "./chunk-
|
|
209
|
+
} from "./chunk-JDJ7SC4E.js";
|
|
210
210
|
import {
|
|
211
211
|
SchemaValidator,
|
|
212
212
|
ToolNames
|
|
213
213
|
} from "./chunk-CFDR3GNL.js";
|
|
214
214
|
import {
|
|
215
215
|
DEFAULT_QWEN_EMBEDDING_MODEL
|
|
216
|
-
} from "./chunk-
|
|
216
|
+
} from "./chunk-TXZDRD36.js";
|
|
217
217
|
import {
|
|
218
218
|
isTlsVerificationDisabled
|
|
219
219
|
} from "./chunk-BFHXDAR7.js";
|
|
@@ -358,9 +358,9 @@ var require_command_exists = __commonJS({
|
|
|
358
358
|
cleanInput = /* @__PURE__ */ __name(function(s) {
|
|
359
359
|
var isPathName = /[\\]/.test(s);
|
|
360
360
|
if (isPathName) {
|
|
361
|
-
var
|
|
362
|
-
var
|
|
363
|
-
return
|
|
361
|
+
var dirname23 = '"' + path3.dirname(s) + '"';
|
|
362
|
+
var basename6 = '"' + path3.basename(s) + '"';
|
|
363
|
+
return dirname23 + ":" + basename6;
|
|
364
364
|
}
|
|
365
365
|
return '"' + s + '"';
|
|
366
366
|
}, "cleanInput");
|
|
@@ -368,10 +368,10 @@ var require_command_exists = __commonJS({
|
|
|
368
368
|
module.exports = /* @__PURE__ */ __name(function commandExists2(commandName, callback) {
|
|
369
369
|
var cleanedCommandName = cleanInput(commandName);
|
|
370
370
|
if (!callback && typeof Promise !== "undefined") {
|
|
371
|
-
return new Promise(function(
|
|
371
|
+
return new Promise(function(resolve23, reject) {
|
|
372
372
|
commandExists2(commandName, function(error, output) {
|
|
373
373
|
if (output) {
|
|
374
|
-
|
|
374
|
+
resolve23(commandName);
|
|
375
375
|
} else {
|
|
376
376
|
reject(error);
|
|
377
377
|
}
|
|
@@ -2255,8 +2255,8 @@ function createBridgeReadinessGate() {
|
|
|
2255
2255
|
current: /* @__PURE__ */ __name(() => pending, "current"),
|
|
2256
2256
|
block: /* @__PURE__ */ __name(() => {
|
|
2257
2257
|
if (pending) return;
|
|
2258
|
-
pending = new Promise((
|
|
2259
|
-
releasePending =
|
|
2258
|
+
pending = new Promise((resolve23) => {
|
|
2259
|
+
releasePending = resolve23;
|
|
2260
2260
|
});
|
|
2261
2261
|
}, "block"),
|
|
2262
2262
|
release: /* @__PURE__ */ __name(() => {
|
|
@@ -2338,7 +2338,7 @@ function createBridgeRecovery(options) {
|
|
|
2338
2338
|
writeStderrLine(
|
|
2339
2339
|
`[Channel] Bridge crashed (${recentCrashCount}/${MAX_CRASH_RESTARTS} in window). Restarting in ${RESTART_DELAY_MS / 1e3}s...`
|
|
2340
2340
|
);
|
|
2341
|
-
await new Promise((
|
|
2341
|
+
await new Promise((resolve23) => setTimeout(resolve23, RESTART_DELAY_MS));
|
|
2342
2342
|
const bridge = new AcpBridge(bridgeOpts);
|
|
2343
2343
|
setBridge(bridge);
|
|
2344
2344
|
attachDisconnectHandler(bridge);
|
|
@@ -3206,12 +3206,12 @@ async function abortableStartup(value, signal) {
|
|
|
3206
3206
|
const promise = Promise.resolve(value);
|
|
3207
3207
|
if (!signal) return await promise;
|
|
3208
3208
|
if (signal.aborted) throw startupAbortError();
|
|
3209
|
-
return await new Promise((
|
|
3209
|
+
return await new Promise((resolve23, reject) => {
|
|
3210
3210
|
const onAbort = /* @__PURE__ */ __name(() => {
|
|
3211
3211
|
reject(startupAbortError());
|
|
3212
3212
|
}, "onAbort");
|
|
3213
3213
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3214
|
-
promise.then(
|
|
3214
|
+
promise.then(resolve23, reject).finally(() => {
|
|
3215
3215
|
signal.removeEventListener("abort", onAbort);
|
|
3216
3216
|
});
|
|
3217
3217
|
});
|
|
@@ -3619,7 +3619,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
|
|
|
3619
3619
|
if (!send) {
|
|
3620
3620
|
return Promise.reject(new Error("Channel worker IPC is unavailable."));
|
|
3621
3621
|
}
|
|
3622
|
-
return new Promise((
|
|
3622
|
+
return new Promise((resolve23, reject) => {
|
|
3623
3623
|
let settled = false;
|
|
3624
3624
|
let unsubscribeMessage = /* @__PURE__ */ __name(() => {
|
|
3625
3625
|
}, "unsubscribeMessage");
|
|
@@ -3633,7 +3633,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
|
|
|
3633
3633
|
settled = true;
|
|
3634
3634
|
cleanup();
|
|
3635
3635
|
if (error) reject(error);
|
|
3636
|
-
else
|
|
3636
|
+
else resolve23();
|
|
3637
3637
|
}, "finish");
|
|
3638
3638
|
const onMessage = /* @__PURE__ */ __name((value) => {
|
|
3639
3639
|
if (isChannelStartupReportAckMessage(value)) {
|
|
@@ -3881,8 +3881,8 @@ var daemonWorkerCommand = {
|
|
|
3881
3881
|
let shuttingDown = false;
|
|
3882
3882
|
let exitCode = 0;
|
|
3883
3883
|
let finish;
|
|
3884
|
-
const finished = new Promise((
|
|
3885
|
-
finish =
|
|
3884
|
+
const finished = new Promise((resolve23) => {
|
|
3885
|
+
finish = resolve23;
|
|
3886
3886
|
});
|
|
3887
3887
|
const shutdown = /* @__PURE__ */ __name(async (reason) => {
|
|
3888
3888
|
if (shuttingDown) {
|
|
@@ -3910,8 +3910,8 @@ var daemonWorkerCommand = {
|
|
|
3910
3910
|
...activeChannelDeliveries.values(),
|
|
3911
3911
|
...activeWebhookTasks.values()
|
|
3912
3912
|
]),
|
|
3913
|
-
new Promise((
|
|
3914
|
-
const timer = setTimeout(
|
|
3913
|
+
new Promise((resolve23) => {
|
|
3914
|
+
const timer = setTimeout(resolve23, WORKER_SHUTDOWN_DRAIN_MS);
|
|
3915
3915
|
timer.unref();
|
|
3916
3916
|
})
|
|
3917
3917
|
]);
|
|
@@ -6127,6 +6127,22 @@ A finding an A/B settled carries \`Source: [probe]\` like any other run-produced
|
|
|
6127
6127
|
|
|
6128
6128
|
It writes the \`run:\` script **verbatim** as an executable and reports what the runner would have supplied: the effective \`env:\` with all three levels merged and each key's level named, every \`\${{ \u2026 }}\` site listed **unevaluated** \u2014 that list is precisely what you have to stub, because the command refuses to invent values for it \u2014 the resolved \`shell\` and \`working-directory\`, and the commands the script invokes. Stubbing and input are yours: shim \`gh\`/\`curl\` onto \`PATH\`, export the env, run it, observe. **Combined with \`base-tree\`, a workflow A/B is two invocations** \u2014 extract the same step from both trees, feed both the same input, diff what each would have done. That is how the strongest workflow finding in this pipeline's history was produced: the real composer step from both arms, a stubbed \`gh\`, and a byte-for-byte comparison against a comment the workflow had actually posted. Three limits worth knowing before you spend the step: a \`uses:\` step has no \`run:\` and is refused rather than simulated; a step NAME that two steps in the job share is refused as ambiguous rather than resolved to the first, so pass the index (which is what an A/B wants anyway \u2014 the two trees must select the same step, and a name that moved between them is exactly how they stop doing that); and the \`invokes\` list is a labelled heuristic \u2014 the verbatim script beside it is the authority.
|
|
6129
6129
|
|
|
6130
|
+
**When the claim is about what the product DOES at runtime, drive it \u2014 two commands make that mechanical.** A finding about behaviour ("this hangs when the provider 429s", "the retry never fires", "the daemon answers before it is ready") is settled by running the built product and watching, and the two halves that used to be hand-written every time are now commands.
|
|
6131
|
+
|
|
6132
|
+
\`\`\`bash
|
|
6133
|
+
"\${QWEN_CODE_CLI:-qwen}" review mock-provider --responder <a module you write> \\
|
|
6134
|
+
--log <plan dir>/mock.jsonl --ttl 600 --out <plan dir>/mock.json &
|
|
6135
|
+
until [ -s <plan dir>/mock.json ]; do sleep 0.1; done # its port is in that report
|
|
6136
|
+
"\${QWEN_CODE_CLI:-qwen}" review drive --cwd <the worktree> --script <what to run> \\
|
|
6137
|
+
--ready <a command polled until it exits 0> --timeout 300 --out <plan dir>/drive.json
|
|
6138
|
+
\`\`\`
|
|
6139
|
+
|
|
6140
|
+
\`mock-provider\` serves \`/v1/chat/completions\` (OpenAI) and \`/v1/messages\` (Anthropic) on an OS-assigned port it reports back, and appends every request to a JSONL log; your responder module exports \`respond(req)\` returning \`{text}\`, \`{tool, args}\` or \`{status, body}\`, and never has to get SSE framing right. **It serves for the whole \`--ttl\` and returns only when that expires** \u2014 so background it and wait, as above; run sequentially it is already shut down by the time the next line starts. Its report is written once the port is bound, which is what makes the file's appearance a readiness signal rather than a guess, and the TTL is the only thing that ends it \u2014 set it to bound the drive, not to match it. **The log is the A/B evidence** \u2014 drive the same script against the PR worktree and the \`base-tree\` path, then diff the two request sequences; a difference is evidence, a reading is not.
|
|
6141
|
+
|
|
6142
|
+
\`drive\` owns the three things that used to be guesswork, and its \`outcome\` is what you rule on, never the captured text alone: \`completed\` carries the script's own \`exitCode\` and is the only value that licenses a behavioural claim; \`not-ready\` means the readiness probe never passed, so **nothing was driven and nothing observed is evidence either way**; \`timed-out\` and \`overflowed\` mean the capture is PARTIAL \u2014 a partial capture is not evidence that the run produced nothing; \`unavailable\` (no tmux) is an environment gap and explicitly not a finding. Pass \`--ready\` for anything that binds a port: without it the drive starts immediately, and an empty capture reads as "the feature does not work" when it means "the daemon had not finished starting".
|
|
6143
|
+
|
|
6144
|
+
For anything that is not one of those two wires \u2014 the project's own HTTP service, an MCP server, an OAuth endpoint \u2014 stand it up yourself and let \`drive\` own the lifecycle.
|
|
6145
|
+
|
|
6130
6146
|
**When the claim is about GITHUB's behaviour, neither tree can settle it \u2014 only GitHub can.** A claim like "this encoding renders identically and can never ping", "GitHub strips this tag", "this markdown shape closes the fold" is about the comment pipeline's parser, sanitizer allowlist and notification path, none of which exist in this environment \u2014 a local markdown library is a model of GitHub, and judging a sanitizer claim against a model of the authority is exactly the parser-divergence failure under review. Measured live: an \`@\` \u2192 \`@\` defusal read as sound in every local trace, and GitHub's real renderer registered the mention and fired the notification. So:
|
|
6131
6147
|
|
|
6132
6148
|
- **If the environment variable \`QWEN_REVIEW_SCRATCH_REPO\` is set** (an \`owner/repo\` the user designated for disposable test posts), you may adjudicate on the real renderer: post the payload as an issue comment there \u2014 \`gh api repos/$QWEN_REVIEW_SCRATCH_REPO/issues/<n>/comments -f body=@<file>\` against an issue you created there for this purpose \u2014 read it back with \`-H "Accept: application/vnd.github.html+json"\`, and rule on the returned HTML (and, for mention claims, the timeline events). The observation is the verdict; quote it. This is the ONLY write destination other than \`submit\`'s that any part of this review may touch, it is user-designated, and nothing about the PR under review, its code, or its authors may appear in what you post there \u2014 post the minimal payload shape, not the report.
|
|
@@ -6142,6 +6158,13 @@ Return, for each finding, one verdict:
|
|
|
6142
6158
|
|
|
6143
6159
|
**For anything non-Critical, when uncertain, downgrade to low confidence rather than rejecting.** Reserve outright rejection for a finding that clearly does not match the code (it describes behaviour the code does not have) or matches an Exclusion Criterion. Low confidence is for "likely real, needs human judgement", not for "I have no idea" \u2014 a vague suspicion with no concrete evidence in the code can still be rejected.
|
|
6144
6160
|
|
|
6161
|
+
**Your job is to falsify, not to fail-to-verify \u2014 and the two feel identical from inside a trace that went nowhere.** Rejection is a claim that you hold **direct counter-evidence**: the quoted contradicting code, the observed probe output, the matched Exclusion Criterion (the one exception stays as stated above: a finding that names nothing checkable at all is rejectable for that reason). Two states reliably masquerade as grounds to reject, and neither is (the split is measured practice from a reflection filter that ran over production reviews at millions-of-comments scale, whose single operating rule was this asymmetry):
|
|
6162
|
+
|
|
6163
|
+
- **"I could not verify it."** A trace that fails to confirm a claim is information about your trace, not about the claim \u2014 the trigger may need state you did not construct, a platform you are not on, a timing window your read-through cannot open. If the trace neither confirms nor contradicts, the verdict is \`confirmed (low confidence)\` with what-would-settle-it named. "Could not reproduce the reasoning" is already listed above as insufficient to reject a Critical; it is equally insufficient for a Suggestion whose evidence is merely out of your reach.
|
|
6164
|
+
- **"Its evidence is somewhere I did not look."** A finding may rest on evidence the finder gathered and you have not \u2014 a caller grepped in a file the diff never touches, issue evidence fetched from GitHub, a behaviour observed in a run. The evidence being absent from *your* view is not evidence of absence: you have the same tools the finder had, so **go read the claimed source first** \u2014 the named file, the quoted issue text in the launch message, the cited output. Reject only if what you find there contradicts the claim. If the source is genuinely unreachable from this environment (a lightweight diff-only review, an external service), the floor is the low-confidence downgrade, never rejection.
|
|
6165
|
+
|
|
6166
|
+
The asymmetry cuts both ways: confirming also requires the trace, and a finding that merely *sounds* right confirms nothing. What it forbids is only the shortcut in the rejecting direction, because that direction is the irreversible one.
|
|
6167
|
+
|
|
6145
6168
|
**Do not reject an issue-fidelity / root-cause-ownership finding merely because the code compiles, runs, or has a passing test.** A working sanitizer with a green "malformed-shape" test does not disprove an issue-grounded claim that the root cause belongs upstream. Verify such a finding against the issue evidence quoted in the message that launched you; if that evidence is absent or genuinely inconclusive, downgrade rather than reject.`
|
|
6146
6169
|
},
|
|
6147
6170
|
"reverse-audit": {
|
|
@@ -6245,8 +6268,8 @@ function publicRoleLabelZh(req) {
|
|
|
6245
6268
|
return req.file ? `${base}\uFF08${req.file}\uFF09` : base;
|
|
6246
6269
|
}
|
|
6247
6270
|
__name(publicRoleLabelZh, "publicRoleLabelZh");
|
|
6248
|
-
function label(rec,
|
|
6249
|
-
if (
|
|
6271
|
+
function label(rec, chunk2) {
|
|
6272
|
+
if (chunk2 !== null) return `chunk ${chunk2}`;
|
|
6250
6273
|
const first = rec.launchPrompt.split("\n")[0]?.trim() ?? "";
|
|
6251
6274
|
if (first) return first.length > 60 ? `${first.slice(0, 57)}...` : first;
|
|
6252
6275
|
return rec.agentName || rec.agentId;
|
|
@@ -6301,26 +6324,26 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6301
6324
|
}
|
|
6302
6325
|
return false;
|
|
6303
6326
|
}, "keySatisfied");
|
|
6304
|
-
const superseded = /* @__PURE__ */ __name((rec,
|
|
6327
|
+
const superseded = /* @__PURE__ */ __name((rec, chunk2) => chunk2 !== null ? chunkSatisfied(chunk2, rec) : keySatisfied(rec), "superseded");
|
|
6305
6328
|
for (const rec of records) {
|
|
6306
|
-
const
|
|
6307
|
-
const name = label(rec,
|
|
6329
|
+
const chunk2 = assignedChunk(rec);
|
|
6330
|
+
const name = label(rec, chunk2);
|
|
6308
6331
|
const given = wasGivenTheDiff(rec, plan.diffPathAbsolute);
|
|
6309
|
-
if (
|
|
6310
|
-
if (!superseded(rec,
|
|
6332
|
+
if (chunk2 !== null && !given) {
|
|
6333
|
+
if (!superseded(rec, chunk2)) blindAgents.push(name);
|
|
6311
6334
|
continue;
|
|
6312
6335
|
}
|
|
6313
6336
|
if (rec.successfulToolCalls === 0) {
|
|
6314
|
-
if (!superseded(rec,
|
|
6337
|
+
if (!superseded(rec, chunk2)) idleAgents.push(name);
|
|
6315
6338
|
continue;
|
|
6316
6339
|
}
|
|
6317
6340
|
if (!given) continue;
|
|
6318
6341
|
let rewrittenThisRecord = false;
|
|
6319
|
-
if (
|
|
6320
|
-
const b = builtOf(`chunk-${
|
|
6342
|
+
if (chunk2 !== null) {
|
|
6343
|
+
const b = builtOf(`chunk-${chunk2}`);
|
|
6321
6344
|
if (b === void 0) {
|
|
6322
6345
|
rewrittenThisRecord = true;
|
|
6323
|
-
if (!nothingBuiltAtAll && !superseded(rec,
|
|
6346
|
+
if (!nothingBuiltAtAll && !superseded(rec, chunk2)) {
|
|
6324
6347
|
rewrittenPrompts.push(
|
|
6325
6348
|
disclose(
|
|
6326
6349
|
name,
|
|
@@ -6333,7 +6356,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6333
6356
|
}
|
|
6334
6357
|
} else if (!wasDeliveredVerbatim(rec.launchPrompt, b)) {
|
|
6335
6358
|
rewrittenThisRecord = true;
|
|
6336
|
-
if (!superseded(rec,
|
|
6359
|
+
if (!superseded(rec, chunk2)) {
|
|
6337
6360
|
rewrittenPrompts.push(
|
|
6338
6361
|
disclose(
|
|
6339
6362
|
name,
|
|
@@ -6346,7 +6369,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6346
6369
|
}
|
|
6347
6370
|
const told = pointedAt(rec.launchPrompt, plan);
|
|
6348
6371
|
if (told.length > 0 && rec.diffToolCalls === 0) {
|
|
6349
|
-
if (!rewrittenThisRecord && !superseded(rec,
|
|
6372
|
+
if (!rewrittenThisRecord && !superseded(rec, chunk2)) {
|
|
6350
6373
|
unopenedAgents.push(name);
|
|
6351
6374
|
}
|
|
6352
6375
|
continue;
|
|
@@ -6354,8 +6377,8 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6354
6377
|
const ranges = merge([...told, ...rec.diffReads]);
|
|
6355
6378
|
if (ranges.length === 0) continue;
|
|
6356
6379
|
const u = UNCOVERABLE_RE.exec(rec.finalText);
|
|
6357
|
-
if (u &&
|
|
6358
|
-
uncoverable.add(
|
|
6380
|
+
if (u && chunk2 !== null && Number(u[1]) === chunk2) {
|
|
6381
|
+
uncoverable.add(chunk2);
|
|
6359
6382
|
continue;
|
|
6360
6383
|
}
|
|
6361
6384
|
for (const c of plan.chunks) {
|
|
@@ -6751,6 +6774,10 @@ function gh(...args) {
|
|
|
6751
6774
|
return execGhWithRetry(args, {});
|
|
6752
6775
|
}
|
|
6753
6776
|
__name(gh, "gh");
|
|
6777
|
+
function ghWithInputRetried(input, ...args) {
|
|
6778
|
+
return execGhWithRetry(args, { input });
|
|
6779
|
+
}
|
|
6780
|
+
__name(ghWithInputRetried, "ghWithInputRetried");
|
|
6754
6781
|
function ghWithInput(input, ...args) {
|
|
6755
6782
|
return execFileSync2("gh", args, {
|
|
6756
6783
|
encoding: "utf8",
|
|
@@ -7821,7 +7848,7 @@ __name(verdictLine, "verdictLine");
|
|
|
7821
7848
|
|
|
7822
7849
|
// packages/cli/src/commands/review/findings.ts
|
|
7823
7850
|
init_esbuild_shims();
|
|
7824
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
7851
|
+
import { existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
7825
7852
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
7826
7853
|
var SEVERITIES = ["Critical", "Suggestion", "Nice to have"];
|
|
7827
7854
|
var CONFIDENCES = ["high", "low"];
|
|
@@ -7845,6 +7872,18 @@ function asString(o, key) {
|
|
|
7845
7872
|
return typeof v === "string" && v.trim() !== "" ? v : void 0;
|
|
7846
7873
|
}
|
|
7847
7874
|
__name(asString, "asString");
|
|
7875
|
+
function stringArray(o, key, index) {
|
|
7876
|
+
const v = o[key];
|
|
7877
|
+
if (v == null) return void 0;
|
|
7878
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== "string" || x.trim() === "")) {
|
|
7879
|
+
fail(
|
|
7880
|
+
index,
|
|
7881
|
+
`"${key}" must be an array of non-empty strings, got ${JSON.stringify(v)}.`
|
|
7882
|
+
);
|
|
7883
|
+
}
|
|
7884
|
+
return v.length > 0 ? v : void 0;
|
|
7885
|
+
}
|
|
7886
|
+
__name(stringArray, "stringArray");
|
|
7848
7887
|
function oneOf(value, allowed) {
|
|
7849
7888
|
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
7850
7889
|
}
|
|
@@ -7939,7 +7978,11 @@ function validateFindings(raw) {
|
|
|
7939
7978
|
`has outcome ${JSON.stringify(o["outcome"])}; expected one of ${OUTCOMES.map((s) => JSON.stringify(s)).join(", ")}.`
|
|
7940
7979
|
);
|
|
7941
7980
|
}
|
|
7981
|
+
const assetFiles = stringArray(o, "assetFiles", i) ?? stringArray(o, "asset_files", i);
|
|
7982
|
+
const assets = stringArray(o, "assets", i);
|
|
7942
7983
|
const outcomeNote = asString(o, "outcomeNote") ?? asString(o, "outcome_note");
|
|
7984
|
+
const heldRaw = o.heldByMeasurement;
|
|
7985
|
+
const heldFile = heldRaw && typeof heldRaw === "object" ? asString(heldRaw, "file") : void 0;
|
|
7943
7986
|
const shortSummary = asString(o, "shortSummary") ?? asString(o, "short_summary");
|
|
7944
7987
|
return {
|
|
7945
7988
|
id,
|
|
@@ -7954,6 +7997,9 @@ function validateFindings(raw) {
|
|
|
7954
7997
|
} : {},
|
|
7955
7998
|
...asString(o, "category") ? { category: asString(o, "category") } : {},
|
|
7956
7999
|
locations: parseLocations(o, i),
|
|
8000
|
+
...assetFiles ? { assetFiles } : {},
|
|
8001
|
+
...assets ? { assets } : {},
|
|
8002
|
+
...heldFile ? { heldByMeasurement: { file: heldFile } } : {},
|
|
7957
8003
|
...outcome ? { outcome } : {},
|
|
7958
8004
|
...outcome && outcomeNote ? { outcomeNote } : {}
|
|
7959
8005
|
};
|
|
@@ -7970,6 +8016,89 @@ function validateFindings(raw) {
|
|
|
7970
8016
|
return findings;
|
|
7971
8017
|
}
|
|
7972
8018
|
__name(validateFindings, "validateFindings");
|
|
8019
|
+
function namesPath(text, probe) {
|
|
8020
|
+
const isNameChar = /* @__PURE__ */ __name((c) => c !== void 0 && /[A-Za-z0-9._-]/.test(c), "isNameChar");
|
|
8021
|
+
const isBoundary = /* @__PURE__ */ __name((c) => c === void 0 || !isNameChar(c) && c !== "/", "isBoundary");
|
|
8022
|
+
let from = 0;
|
|
8023
|
+
for (; ; ) {
|
|
8024
|
+
const at = text.indexOf(probe, from);
|
|
8025
|
+
if (at < 0) return false;
|
|
8026
|
+
const after = text[at + probe.length];
|
|
8027
|
+
const extendsName = after !== void 0 && (/[A-Za-z0-9_-]/.test(after) || after === "." && /[A-Za-z0-9]/.test(text[at + probe.length + 1] ?? ""));
|
|
8028
|
+
if (isBoundary(at === 0 ? void 0 : text[at - 1]) && !extendsName) {
|
|
8029
|
+
return true;
|
|
8030
|
+
}
|
|
8031
|
+
from = at + 1;
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
__name(namesPath, "namesPath");
|
|
8035
|
+
function holdCriticalsFailingOnBase(findings, sharedFailingFiles) {
|
|
8036
|
+
const held = [];
|
|
8037
|
+
const readjudicated = [];
|
|
8038
|
+
const out = findings.map((f) => {
|
|
8039
|
+
if (f.severity !== "Critical") return f;
|
|
8040
|
+
const haystack = [f.summary, f.failureScenario].join("\n");
|
|
8041
|
+
const hit = sharedFailingFiles.find((p) => p && namesPath(haystack, p));
|
|
8042
|
+
if (!hit) return f;
|
|
8043
|
+
if (f.heldByMeasurement?.file === hit) {
|
|
8044
|
+
readjudicated.push({ id: f.id, file: hit });
|
|
8045
|
+
return f;
|
|
8046
|
+
}
|
|
8047
|
+
held.push({ id: f.id, file: hit });
|
|
8048
|
+
return {
|
|
8049
|
+
...f,
|
|
8050
|
+
severity: "Suggestion",
|
|
8051
|
+
heldByMeasurement: { file: hit },
|
|
8052
|
+
failureScenario: `${f.failureScenario}
|
|
8053
|
+
|
|
8054
|
+
Held back from Critical by measurement: \`test-delta\` reran the failing test command on the merge base and ${hit} failed there too, so this is not a passing test the PR turns red. If the PR makes an already-red test fail for a NEW reason, say which test, quote both sides, and file it at Critical again: a finding that already carries this measurement and is raised anyway is left where you put it.`
|
|
8055
|
+
};
|
|
8056
|
+
});
|
|
8057
|
+
return { findings: out, held, readjudicated };
|
|
8058
|
+
}
|
|
8059
|
+
__name(holdCriticalsFailingOnBase, "holdCriticalsFailingOnBase");
|
|
8060
|
+
var WORKSPACE_IN_COMMAND_RE = /--workspace="([^"]+)"/;
|
|
8061
|
+
function repoRelative(path3, workspace) {
|
|
8062
|
+
const at = path3.indexOf("::");
|
|
8063
|
+
const bare = at < 0 ? path3 : path3.slice(at + 2);
|
|
8064
|
+
if (at >= 0 && !workspace) return void 0;
|
|
8065
|
+
if (!workspace || bare.startsWith(`${workspace}/`)) return bare;
|
|
8066
|
+
return `${workspace}/${bare}`;
|
|
8067
|
+
}
|
|
8068
|
+
__name(repoRelative, "repoRelative");
|
|
8069
|
+
function sharedFailingFilesOf(raw) {
|
|
8070
|
+
if (!raw || typeof raw !== "object")
|
|
8071
|
+
return { shared: [], unidentifiable: [] };
|
|
8072
|
+
const shared = /* @__PURE__ */ new Set();
|
|
8073
|
+
const netNew = /* @__PURE__ */ new Set();
|
|
8074
|
+
const unidentifiable = /* @__PURE__ */ new Set();
|
|
8075
|
+
const take = /* @__PURE__ */ __name((into, v, workspace) => {
|
|
8076
|
+
if (!Array.isArray(v)) return;
|
|
8077
|
+
for (const e of v) {
|
|
8078
|
+
if (typeof e !== "string" || !e) continue;
|
|
8079
|
+
const qualified = repoRelative(e, workspace);
|
|
8080
|
+
if (qualified === void 0) {
|
|
8081
|
+
if (into === shared) unidentifiable.add(e);
|
|
8082
|
+
} else into.add(qualified);
|
|
8083
|
+
}
|
|
8084
|
+
}, "take");
|
|
8085
|
+
const entries = raw.entries;
|
|
8086
|
+
const rows = Array.isArray(entries) ? entries : [];
|
|
8087
|
+
if (rows.length > 0) {
|
|
8088
|
+
for (const e of rows) {
|
|
8089
|
+
if (!e || typeof e !== "object") continue;
|
|
8090
|
+
const command = e.command;
|
|
8091
|
+
const workspace = typeof command === "string" ? WORKSPACE_IN_COMMAND_RE.exec(command)?.[1] ?? void 0 : void 0;
|
|
8092
|
+
take(shared, e.shared, workspace);
|
|
8093
|
+
take(netNew, e.netNew, workspace);
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8096
|
+
return {
|
|
8097
|
+
shared: [...shared].filter((f) => !netNew.has(f)),
|
|
8098
|
+
unidentifiable: [...unidentifiable]
|
|
8099
|
+
};
|
|
8100
|
+
}
|
|
8101
|
+
__name(sharedFailingFilesOf, "sharedFailingFilesOf");
|
|
7973
8102
|
function sortFindings(findings) {
|
|
7974
8103
|
return [...findings].sort((a, b) => {
|
|
7975
8104
|
const bySeverity = SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity);
|
|
@@ -8072,6 +8201,7 @@ function buildReport(findings) {
|
|
|
8072
8201
|
total: sorted.length,
|
|
8073
8202
|
bySeverity,
|
|
8074
8203
|
byConfidence,
|
|
8204
|
+
held: sorted.filter((f) => f.heldByMeasurement !== void 0).length,
|
|
8075
8205
|
...byOutcome ? { byOutcome } : {}
|
|
8076
8206
|
},
|
|
8077
8207
|
outcomesRecorded
|
|
@@ -8121,12 +8251,15 @@ var findingsCommand = {
|
|
|
8121
8251
|
}).option("outcomes", {
|
|
8122
8252
|
type: "string",
|
|
8123
8253
|
describe: "JSON array of {id, outcome, note?} recording what --fix did to each finding. Must cover every finding."
|
|
8254
|
+
}).option("test-delta", {
|
|
8255
|
+
type: "string",
|
|
8256
|
+
describe: "The test-delta artifact. A Critical naming a test file that also failed on the merge base is held back to Suggestion, carrying the measurement that demoted it."
|
|
8124
8257
|
}).option("print", {
|
|
8125
8258
|
type: "boolean",
|
|
8126
8259
|
describe: "Also print one line per finding to stdout"
|
|
8127
8260
|
}), "builder"),
|
|
8128
8261
|
handler: /* @__PURE__ */ __name((argv) => {
|
|
8129
|
-
const { input, out, outcomes, print } = argv;
|
|
8262
|
+
const { input, out, outcomes, print, testDelta } = argv;
|
|
8130
8263
|
let findings = validateFindings(readJson(input, "findings"));
|
|
8131
8264
|
if (outcomes !== void 0) {
|
|
8132
8265
|
findings = applyOutcomes(
|
|
@@ -8134,6 +8267,30 @@ var findingsCommand = {
|
|
|
8134
8267
|
validateOutcomes(readJson(outcomes, "outcomes"))
|
|
8135
8268
|
);
|
|
8136
8269
|
}
|
|
8270
|
+
let held = [];
|
|
8271
|
+
let readjudicated = [];
|
|
8272
|
+
if (testDelta !== void 0) {
|
|
8273
|
+
let measurement;
|
|
8274
|
+
if (existsSync3(resolve5(testDelta))) {
|
|
8275
|
+
try {
|
|
8276
|
+
measurement = readJson(testDelta, "test-delta");
|
|
8277
|
+
} catch (err) {
|
|
8278
|
+
writeStderrLine(
|
|
8279
|
+
`findings: no holds applied \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
8280
|
+
);
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
const { shared, unidentifiable } = sharedFailingFilesOf(measurement);
|
|
8284
|
+
for (const f of unidentifiable) {
|
|
8285
|
+
writeStderrLine(
|
|
8286
|
+
`findings: ignored the measured file ${f} \u2014 it carries a vitest project key and no workspace to resolve it against, so which project it names cannot be established`
|
|
8287
|
+
);
|
|
8288
|
+
}
|
|
8289
|
+
({ findings, held, readjudicated } = holdCriticalsFailingOnBase(
|
|
8290
|
+
findings,
|
|
8291
|
+
shared
|
|
8292
|
+
));
|
|
8293
|
+
}
|
|
8137
8294
|
const report = buildReport(findings);
|
|
8138
8295
|
const target = resolve5(out);
|
|
8139
8296
|
mkdirSync6(dirname5(target), { recursive: true });
|
|
@@ -8143,6 +8300,16 @@ var findingsCommand = {
|
|
|
8143
8300
|
writeStderrLine(
|
|
8144
8301
|
`findings: ${report.counts.total} total \u2014 ${bySeverity["Critical"]} Critical, ${bySeverity["Suggestion"]} Suggestion, ${bySeverity["Nice to have"]} Nice to have; ${byConfidence["low"]} low-confidence. Wrote ${target}`
|
|
8145
8302
|
);
|
|
8303
|
+
for (const h of held) {
|
|
8304
|
+
writeStderrLine(
|
|
8305
|
+
`findings: ${h.id} held back from Critical \u2014 test-delta measured ${h.file} as failing on the merge base too`
|
|
8306
|
+
);
|
|
8307
|
+
}
|
|
8308
|
+
for (const r of readjudicated) {
|
|
8309
|
+
writeStderrLine(
|
|
8310
|
+
`findings: ${r.id} left at Critical \u2014 it already carries the ${r.file} measurement and was raised again anyway`
|
|
8311
|
+
);
|
|
8312
|
+
}
|
|
8146
8313
|
if (report.counts.byOutcome) {
|
|
8147
8314
|
const o = report.counts.byOutcome;
|
|
8148
8315
|
writeStderrLine(
|
|
@@ -8164,7 +8331,7 @@ import { dirname as dirname6, resolve as resolve6 } from "node:path";
|
|
|
8164
8331
|
// packages/cli/src/commands/review/lib/git.ts
|
|
8165
8332
|
init_esbuild_shims();
|
|
8166
8333
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
8167
|
-
import { existsSync as
|
|
8334
|
+
import { existsSync as existsSync4, rmSync as rmSync3 } from "node:fs";
|
|
8168
8335
|
var GIT_TIMEOUT_MS2 = 12e4;
|
|
8169
8336
|
function gitOpts() {
|
|
8170
8337
|
return {
|
|
@@ -8210,7 +8377,7 @@ function worktreeReleaseResult(existed, stillThere, removeError) {
|
|
|
8210
8377
|
}
|
|
8211
8378
|
__name(worktreeReleaseResult, "worktreeReleaseResult");
|
|
8212
8379
|
function releaseWorktree(worktreePath2) {
|
|
8213
|
-
const existed =
|
|
8380
|
+
const existed = existsSync4(worktreePath2);
|
|
8214
8381
|
let removeError;
|
|
8215
8382
|
if (existed) {
|
|
8216
8383
|
gitOpt("worktree", "remove", worktreePath2, "--force");
|
|
@@ -8221,7 +8388,7 @@ function releaseWorktree(worktreePath2) {
|
|
|
8221
8388
|
}
|
|
8222
8389
|
}
|
|
8223
8390
|
gitOpt("worktree", "prune");
|
|
8224
|
-
return worktreeReleaseResult(existed,
|
|
8391
|
+
return worktreeReleaseResult(existed, existsSync4(worktreePath2), removeError);
|
|
8225
8392
|
}
|
|
8226
8393
|
__name(releaseWorktree, "releaseWorktree");
|
|
8227
8394
|
function gitRaw(...args) {
|
|
@@ -10087,8 +10254,8 @@ var loadRulesCommand = {
|
|
|
10087
10254
|
// packages/cli/src/commands/review/presubmit.ts
|
|
10088
10255
|
init_esbuild_shims();
|
|
10089
10256
|
import { writeFileSync as writeFileSync13, readFileSync as readFileSync13 } from "node:fs";
|
|
10090
|
-
function verdictStamp(
|
|
10091
|
-
return
|
|
10257
|
+
function verdictStamp(run4) {
|
|
10258
|
+
return run4.completed_at ?? run4.started_at ?? "";
|
|
10092
10259
|
}
|
|
10093
10260
|
__name(verdictStamp, "verdictStamp");
|
|
10094
10261
|
var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
@@ -10110,11 +10277,11 @@ var PENDING_STATES = /* @__PURE__ */ new Set([
|
|
|
10110
10277
|
"requested"
|
|
10111
10278
|
]);
|
|
10112
10279
|
var NOT_RUN_CONCLUSIONS = /* @__PURE__ */ new Set(["skipped", "neutral", "stale"]);
|
|
10113
|
-
function isCurrentActionsRunCheck(
|
|
10280
|
+
function isCurrentActionsRunCheck(run4) {
|
|
10114
10281
|
const runId = process.env["GITHUB_RUN_ID"];
|
|
10115
10282
|
if (!runId) return false;
|
|
10116
10283
|
const runUrlMarker = `/actions/runs/${runId}/`;
|
|
10117
|
-
return [
|
|
10284
|
+
return [run4.details_url, run4.html_url].some(
|
|
10118
10285
|
(url) => typeof url === "string" && url.includes(runUrlMarker)
|
|
10119
10286
|
);
|
|
10120
10287
|
}
|
|
@@ -10179,33 +10346,33 @@ function classifyCi(checkRuns, statuses) {
|
|
|
10179
10346
|
const failedCheckNames = [];
|
|
10180
10347
|
let hasPending = false;
|
|
10181
10348
|
const relevantCheckRuns = checkRuns.filter(
|
|
10182
|
-
(
|
|
10349
|
+
(run4) => !isCurrentActionsRunCheck(run4)
|
|
10183
10350
|
);
|
|
10184
10351
|
const executedNames = /* @__PURE__ */ new Set();
|
|
10185
10352
|
const notRunNames = /* @__PURE__ */ new Set();
|
|
10186
|
-
for (const
|
|
10187
|
-
if (
|
|
10188
|
-
if (!
|
|
10189
|
-
notRunNames.add(
|
|
10353
|
+
for (const run4 of relevantCheckRuns) {
|
|
10354
|
+
if (run4.status !== "completed") continue;
|
|
10355
|
+
if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) {
|
|
10356
|
+
notRunNames.add(run4.name);
|
|
10190
10357
|
} else {
|
|
10191
|
-
executedNames.add(
|
|
10358
|
+
executedNames.add(run4.name);
|
|
10192
10359
|
}
|
|
10193
10360
|
}
|
|
10194
10361
|
const skippedCheckNames = [...notRunNames].filter((n) => !executedNames.has(n)).sort();
|
|
10195
10362
|
const latestVerdicts = /* @__PURE__ */ new Map();
|
|
10196
|
-
for (const
|
|
10197
|
-
if (
|
|
10198
|
-
if (PENDING_STATES.has(
|
|
10363
|
+
for (const run4 of relevantCheckRuns) {
|
|
10364
|
+
if (run4.status !== "completed") {
|
|
10365
|
+
if (PENDING_STATES.has(run4.status)) hasPending = true;
|
|
10199
10366
|
continue;
|
|
10200
10367
|
}
|
|
10201
|
-
if (!
|
|
10202
|
-
const prev = latestVerdicts.get(
|
|
10203
|
-
if (!prev || verdictStamp(
|
|
10204
|
-
latestVerdicts.set(
|
|
10368
|
+
if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) continue;
|
|
10369
|
+
const prev = latestVerdicts.get(run4.name);
|
|
10370
|
+
if (!prev || verdictStamp(run4) > verdictStamp(prev)) {
|
|
10371
|
+
latestVerdicts.set(run4.name, run4);
|
|
10205
10372
|
}
|
|
10206
10373
|
}
|
|
10207
|
-
for (const [name,
|
|
10208
|
-
if (FAIL_CONCLUSIONS.has(
|
|
10374
|
+
for (const [name, run4] of latestVerdicts) {
|
|
10375
|
+
if (FAIL_CONCLUSIONS.has(run4.conclusion)) {
|
|
10209
10376
|
failedCheckNames.push(name);
|
|
10210
10377
|
}
|
|
10211
10378
|
}
|
|
@@ -10461,120 +10628,690 @@ var presubmitCommand = {
|
|
|
10461
10628
|
}, "handler")
|
|
10462
10629
|
};
|
|
10463
10630
|
|
|
10464
|
-
// packages/cli/src/commands/review/
|
|
10631
|
+
// packages/cli/src/commands/review/publish-assets.ts
|
|
10465
10632
|
init_esbuild_shims();
|
|
10466
|
-
import {
|
|
10467
|
-
import {
|
|
10633
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10634
|
+
import { readFileSync as readFileSync15, statSync as statSync5, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14 } from "node:fs";
|
|
10635
|
+
import { basename as basename5, dirname as dirname11, resolve as resolve10 } from "node:path";
|
|
10468
10636
|
|
|
10469
|
-
// packages/cli/src/commands/review/lib/
|
|
10637
|
+
// packages/cli/src/commands/review/lib/authorization.ts
|
|
10470
10638
|
init_esbuild_shims();
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
newCursor++;
|
|
10491
|
-
}
|
|
10492
|
-
}
|
|
10493
|
-
}
|
|
10494
|
-
return out;
|
|
10639
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
10640
|
+
|
|
10641
|
+
// packages/cli/src/services/skill-args-file.ts
|
|
10642
|
+
init_esbuild_shims();
|
|
10643
|
+
import {
|
|
10644
|
+
mkdirSync as mkdirSync13,
|
|
10645
|
+
openSync as openSync2,
|
|
10646
|
+
writeSync,
|
|
10647
|
+
closeSync as closeSync2,
|
|
10648
|
+
rmSync as rmSync4,
|
|
10649
|
+
lstatSync as lstatSync3,
|
|
10650
|
+
existsSync as existsSync5,
|
|
10651
|
+
constants
|
|
10652
|
+
} from "node:fs";
|
|
10653
|
+
import { join as join11 } from "node:path";
|
|
10654
|
+
var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
|
|
10655
|
+
var SKILL_ARGS_DIR = join11(".qwen", "tmp");
|
|
10656
|
+
function safe(s) {
|
|
10657
|
+
return s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10495
10658
|
}
|
|
10496
|
-
__name(
|
|
10497
|
-
function
|
|
10498
|
-
return
|
|
10659
|
+
__name(safe, "safe");
|
|
10660
|
+
function currentSessionId() {
|
|
10661
|
+
return sessionIdContext.getStore()?.trim() || process.env["QWEN_CODE_SESSION_ID"]?.trim() || "";
|
|
10499
10662
|
}
|
|
10500
|
-
__name(
|
|
10501
|
-
function
|
|
10502
|
-
return s
|
|
10663
|
+
__name(currentSessionId, "currentSessionId");
|
|
10664
|
+
function skillArgsDir(sessionId = currentSessionId()) {
|
|
10665
|
+
return sessionId ? join11(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
|
|
10503
10666
|
}
|
|
10504
|
-
__name(
|
|
10505
|
-
function
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
const variants = [lines2];
|
|
10511
|
-
const meaningful = lines2.filter((l) => l.trim() !== "");
|
|
10512
|
-
if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
|
|
10513
|
-
variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
|
|
10514
|
-
}
|
|
10515
|
-
const NO_NEWLINE = /^\$/;
|
|
10516
|
-
const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
|
|
10517
|
-
if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
|
|
10518
|
-
variants.push(
|
|
10519
|
-
lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
|
|
10520
|
-
);
|
|
10521
|
-
}
|
|
10522
|
-
return variants;
|
|
10667
|
+
__name(skillArgsDir, "skillArgsDir");
|
|
10668
|
+
function skillArgsPath(skillName, sessionId = currentSessionId()) {
|
|
10669
|
+
return join11(
|
|
10670
|
+
skillArgsDir(sessionId),
|
|
10671
|
+
`qwen-skill-args-${safe(skillName)}.txt`
|
|
10672
|
+
);
|
|
10523
10673
|
}
|
|
10524
|
-
__name(
|
|
10525
|
-
function
|
|
10526
|
-
const
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10674
|
+
__name(skillArgsPath, "skillArgsPath");
|
|
10675
|
+
function writeSkillArgs(skillName, args) {
|
|
10676
|
+
const path3 = skillArgsPath(skillName);
|
|
10677
|
+
try {
|
|
10678
|
+
const dir = skillArgsDir();
|
|
10679
|
+
mkdirSync13(dir, { recursive: true });
|
|
10680
|
+
if (lstatSync3(dir).isSymbolicLink()) {
|
|
10681
|
+
debugLogger3.warn(
|
|
10682
|
+
`Skill args directory ${dir} is a symlink; refusing to write through it.`
|
|
10683
|
+
);
|
|
10684
|
+
return null;
|
|
10685
|
+
}
|
|
10686
|
+
if (lstatSync3(path3, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
10687
|
+
debugLogger3.warn(
|
|
10688
|
+
`Skill args file ${path3} is a symlink; refusing to write through it.`
|
|
10689
|
+
);
|
|
10690
|
+
return null;
|
|
10691
|
+
}
|
|
10692
|
+
const fd = openSync2(
|
|
10693
|
+
path3,
|
|
10694
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
|
|
10695
|
+
384
|
|
10696
|
+
);
|
|
10697
|
+
try {
|
|
10698
|
+
const buf = Buffer.from(args, "utf8");
|
|
10699
|
+
let off = 0;
|
|
10700
|
+
while (off < buf.length) {
|
|
10701
|
+
off += writeSync(fd, buf, off, buf.length - off);
|
|
10540
10702
|
}
|
|
10703
|
+
} finally {
|
|
10704
|
+
closeSync2(fd);
|
|
10541
10705
|
}
|
|
10542
|
-
|
|
10706
|
+
return path3;
|
|
10707
|
+
} catch (err) {
|
|
10708
|
+
debugLogger3.warn(
|
|
10709
|
+
`Could not write skill args to ${path3}: ${err.message}`
|
|
10710
|
+
);
|
|
10711
|
+
return null;
|
|
10543
10712
|
}
|
|
10544
|
-
return starts;
|
|
10545
10713
|
}
|
|
10546
|
-
__name(
|
|
10547
|
-
function
|
|
10548
|
-
|
|
10549
|
-
|
|
10714
|
+
__name(writeSkillArgs, "writeSkillArgs");
|
|
10715
|
+
function clearSkillArgs(skillName) {
|
|
10716
|
+
const path3 = skillArgsPath(skillName);
|
|
10717
|
+
try {
|
|
10718
|
+
rmSync4(path3, { force: true });
|
|
10719
|
+
} catch (err) {
|
|
10720
|
+
debugLogger3.warn(
|
|
10721
|
+
`Could not clear skill args for ${skillName}: ${err.message}`
|
|
10722
|
+
);
|
|
10723
|
+
return false;
|
|
10724
|
+
}
|
|
10725
|
+
if (existsSync5(path3)) {
|
|
10726
|
+
debugLogger3.warn(`Skill args for ${skillName} survived removal at ${path3}`);
|
|
10727
|
+
return false;
|
|
10728
|
+
}
|
|
10729
|
+
return true;
|
|
10730
|
+
}
|
|
10731
|
+
__name(clearSkillArgs, "clearSkillArgs");
|
|
10732
|
+
function staleArgsWarning() {
|
|
10733
|
+
return `
|
|
10734
|
+
|
|
10735
|
+
<skill-args-stale>A previous invocation's argument record could not be removed, so it is still on disk and still names whatever it named. This invocation supplied NO arguments. Do not treat that stale record as this run's authorisation: this run has none, and must not post.</skill-args-stale>
|
|
10736
|
+
`;
|
|
10737
|
+
}
|
|
10738
|
+
__name(staleArgsWarning, "staleArgsWarning");
|
|
10739
|
+
function skillArgsNote(path3, args) {
|
|
10740
|
+
return `
|
|
10741
|
+
|
|
10742
|
+
Your invocation arguments have been written verbatim to a session-private file. Its exact path is below \u2014 use it wherever these instructions say to read the args file (e.g. \`< '${path3}'\`), and do not retype the arguments, which is how they get mistyped.
|
|
10743
|
+
<skill-args-file>${path3}</skill-args-file>
|
|
10744
|
+
<skill-args>${args}</skill-args>
|
|
10745
|
+
`;
|
|
10746
|
+
}
|
|
10747
|
+
__name(skillArgsNote, "skillArgsNote");
|
|
10748
|
+
|
|
10749
|
+
// packages/cli/src/commands/review/lib/authorization.ts
|
|
10750
|
+
function defaultSkillArgsPath() {
|
|
10751
|
+
return skillArgsPath("review");
|
|
10752
|
+
}
|
|
10753
|
+
__name(defaultSkillArgsPath, "defaultSkillArgsPath");
|
|
10754
|
+
function reviewWriteAuthorization(req) {
|
|
10755
|
+
if (req.userAuthorized) {
|
|
10756
|
+
return { ok: true, why: "the user asked for this review to be published" };
|
|
10757
|
+
}
|
|
10758
|
+
const sessionScoped = defaultSkillArgsPath();
|
|
10759
|
+
const path3 = currentSessionId() === "" && req.skillArgs ? req.skillArgs : sessionScoped;
|
|
10760
|
+
let raw;
|
|
10761
|
+
try {
|
|
10762
|
+
raw = readFileSync14(path3, "utf8");
|
|
10763
|
+
} catch {
|
|
10550
10764
|
return {
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
added: run3.some((l) => l.added)
|
|
10765
|
+
ok: false,
|
|
10766
|
+
why: `no review arguments were recorded at ${path3}, so this run cannot show that \`--comment\` was requested`
|
|
10554
10767
|
};
|
|
10555
|
-
});
|
|
10556
|
-
}
|
|
10557
|
-
__name(candidatesFor, "candidatesFor");
|
|
10558
|
-
function pick(cands, claimedLine) {
|
|
10559
|
-
if (cands.length === 1) return cands[0];
|
|
10560
|
-
if (claimedLine !== void 0) {
|
|
10561
|
-
const dist = /* @__PURE__ */ __name((c) => Math.abs(c.startLine - claimedLine), "dist");
|
|
10562
|
-
let best = Infinity;
|
|
10563
|
-
for (const c of cands) best = Math.min(best, dist(c));
|
|
10564
|
-
const nearest = cands.filter((c) => dist(c) === best);
|
|
10565
|
-
return nearest.length === 1 ? nearest[0] : null;
|
|
10566
10768
|
}
|
|
10567
|
-
const
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
if (variants.length === 0) {
|
|
10574
|
-
return { status: "unmatched", reason: "anchor is empty" };
|
|
10769
|
+
const verdict = parseReviewArgs(raw);
|
|
10770
|
+
if (!verdict.comment.effective) {
|
|
10771
|
+
return {
|
|
10772
|
+
ok: false,
|
|
10773
|
+
why: `\`--comment\` was not in the review arguments (${JSON.stringify(raw.trim())})`
|
|
10774
|
+
};
|
|
10575
10775
|
}
|
|
10576
|
-
|
|
10577
|
-
|
|
10776
|
+
const t2 = verdict.target;
|
|
10777
|
+
const authorisedPr = t2.type === "pr-number" || t2.type === "pr-url" ? t2.number : void 0;
|
|
10778
|
+
if (authorisedPr === void 0) {
|
|
10779
|
+
return {
|
|
10780
|
+
ok: false,
|
|
10781
|
+
why: `the review arguments (${JSON.stringify(raw.trim())}) do not name a pull request, so they cannot authorise posting to one`
|
|
10782
|
+
};
|
|
10783
|
+
}
|
|
10784
|
+
if (authorisedPr !== req.pr) {
|
|
10785
|
+
return {
|
|
10786
|
+
ok: false,
|
|
10787
|
+
why: `the review arguments authorise pull request #${authorisedPr}, but this submission targets #${req.pr}`
|
|
10788
|
+
};
|
|
10789
|
+
}
|
|
10790
|
+
if (t2.type === "pr-url") {
|
|
10791
|
+
if (req.repo !== void 0) {
|
|
10792
|
+
const authorisedRepo = `${t2.owner}/${t2.repo}`;
|
|
10793
|
+
if (authorisedRepo.toLowerCase() !== req.repo.toLowerCase()) {
|
|
10794
|
+
return {
|
|
10795
|
+
ok: false,
|
|
10796
|
+
why: `the review arguments authorise ${authorisedRepo}, but this submission targets ${req.repo}`
|
|
10797
|
+
};
|
|
10798
|
+
}
|
|
10799
|
+
}
|
|
10800
|
+
const writeHost = (req.host ?? "github.com").toLowerCase();
|
|
10801
|
+
if (t2.host.toLowerCase() !== writeHost) {
|
|
10802
|
+
return {
|
|
10803
|
+
ok: false,
|
|
10804
|
+
why: `the review arguments authorise ${t2.host}, but this submission targets ${req.host ?? "github.com"}`
|
|
10805
|
+
};
|
|
10806
|
+
}
|
|
10807
|
+
}
|
|
10808
|
+
return {
|
|
10809
|
+
ok: true,
|
|
10810
|
+
why: `\`--comment\` was in the review arguments for #${authorisedPr}`
|
|
10811
|
+
};
|
|
10812
|
+
}
|
|
10813
|
+
__name(reviewWriteAuthorization, "reviewWriteAuthorization");
|
|
10814
|
+
|
|
10815
|
+
// packages/cli/src/commands/review/lib/assets.ts
|
|
10816
|
+
init_esbuild_shims();
|
|
10817
|
+
var ASSET_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp"]);
|
|
10818
|
+
var MAX_ASSET_BYTES = 10 * 1024 * 1024;
|
|
10819
|
+
var MAX_TOTAL_ASSET_BYTES = 40 * 1024 * 1024;
|
|
10820
|
+
function assetsBranch(pr) {
|
|
10821
|
+
return `pr-assets/${pr}-review`;
|
|
10822
|
+
}
|
|
10823
|
+
__name(assetsBranch, "assetsBranch");
|
|
10824
|
+
function remoteAssetPath(pr, basename6, sha256Hex) {
|
|
10825
|
+
const safe2 = basename6.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10826
|
+
return `${pr}-review/${sha256Hex.slice(0, 12)}-${safe2}`;
|
|
10827
|
+
}
|
|
10828
|
+
__name(remoteAssetPath, "remoteAssetPath");
|
|
10829
|
+
function rawAssetUrl(opts) {
|
|
10830
|
+
const host = opts.host && opts.host.trim() !== "" ? opts.host : "github.com";
|
|
10831
|
+
return `https://${host}/${opts.repo}/raw/${opts.commitSha}/${opts.remotePath}`;
|
|
10832
|
+
}
|
|
10833
|
+
__name(rawAssetUrl, "rawAssetUrl");
|
|
10834
|
+
function parseAssetsRepo(value) {
|
|
10835
|
+
const v = (value ?? "").trim();
|
|
10836
|
+
if (v === "") {
|
|
10837
|
+
return {
|
|
10838
|
+
error: "QWEN_REVIEW_ASSETS_REPO is not set. Designate an assets repository (owner/repo you can push to \u2014 the repo under review for maintainers, a fork or scratch repo otherwise) to publish review evidence images."
|
|
10839
|
+
};
|
|
10840
|
+
}
|
|
10841
|
+
const parts = v.split("/");
|
|
10842
|
+
const segmentOk = /* @__PURE__ */ __name((p) => /^[A-Za-z0-9_.-]+$/.test(p) && p !== "." && p !== "..", "segmentOk");
|
|
10843
|
+
if (parts.length !== 2 || !parts.every(segmentOk)) {
|
|
10844
|
+
return {
|
|
10845
|
+
error: `QWEN_REVIEW_ASSETS_REPO must be owner/repo, got ${JSON.stringify(v)}.`
|
|
10846
|
+
};
|
|
10847
|
+
}
|
|
10848
|
+
return { repo: v };
|
|
10849
|
+
}
|
|
10850
|
+
__name(parseAssetsRepo, "parseAssetsRepo");
|
|
10851
|
+
function validateAssetFile(basename6, bytes) {
|
|
10852
|
+
const ext = basename6.includes(".") ? basename6.slice(basename6.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
10853
|
+
if (!ASSET_EXTENSIONS.has(ext)) {
|
|
10854
|
+
return {
|
|
10855
|
+
ok: false,
|
|
10856
|
+
reason: `${basename6}: extension ${JSON.stringify(ext)} is not an allowed evidence image type (${[...ASSET_EXTENSIONS].join(", ")})`
|
|
10857
|
+
};
|
|
10858
|
+
}
|
|
10859
|
+
if (bytes <= 0) {
|
|
10860
|
+
return { ok: false, reason: `${basename6}: empty file` };
|
|
10861
|
+
}
|
|
10862
|
+
if (bytes > MAX_ASSET_BYTES) {
|
|
10863
|
+
return {
|
|
10864
|
+
ok: false,
|
|
10865
|
+
reason: `${basename6}: ${bytes} bytes exceeds the per-file cap of ${MAX_ASSET_BYTES} bytes`
|
|
10866
|
+
};
|
|
10867
|
+
}
|
|
10868
|
+
return { ok: true };
|
|
10869
|
+
}
|
|
10870
|
+
__name(validateAssetFile, "validateAssetFile");
|
|
10871
|
+
function validateAssetBatch(files) {
|
|
10872
|
+
let total = 0;
|
|
10873
|
+
for (const f of files) {
|
|
10874
|
+
const one = validateAssetFile(f.basename, f.bytes);
|
|
10875
|
+
if (!one.ok) return one;
|
|
10876
|
+
total += f.bytes;
|
|
10877
|
+
if (total > MAX_TOTAL_ASSET_BYTES) {
|
|
10878
|
+
return {
|
|
10879
|
+
ok: false,
|
|
10880
|
+
reason: `total size exceeds ${MAX_TOTAL_ASSET_BYTES} bytes`
|
|
10881
|
+
};
|
|
10882
|
+
}
|
|
10883
|
+
}
|
|
10884
|
+
return { ok: true };
|
|
10885
|
+
}
|
|
10886
|
+
__name(validateAssetBatch, "validateAssetBatch");
|
|
10887
|
+
|
|
10888
|
+
// packages/cli/src/commands/review/publish-assets.ts
|
|
10889
|
+
function putContent(repo, branch, remotePath, contentBase64, pr) {
|
|
10890
|
+
const api = `repos/${repo}/contents/${remotePath}`;
|
|
10891
|
+
const message = `review evidence for PR #${pr}`;
|
|
10892
|
+
const payload = /* @__PURE__ */ __name((sha) => JSON.stringify({
|
|
10893
|
+
message,
|
|
10894
|
+
content: contentBase64,
|
|
10895
|
+
branch,
|
|
10896
|
+
...sha ? { sha } : {}
|
|
10897
|
+
}), "payload");
|
|
10898
|
+
try {
|
|
10899
|
+
ghWithInputRetried(payload(), "api", "-X", "PUT", api, "--input", "-");
|
|
10900
|
+
} catch (err) {
|
|
10901
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10902
|
+
if (!/HTTP 422|"sha"|sha wasn't supplied|already exists/i.test(msg)) {
|
|
10903
|
+
throw err;
|
|
10904
|
+
}
|
|
10905
|
+
let existing;
|
|
10906
|
+
try {
|
|
10907
|
+
existing = gh(
|
|
10908
|
+
"api",
|
|
10909
|
+
`${api}?ref=${encodeURIComponent(branch)}`,
|
|
10910
|
+
"--jq",
|
|
10911
|
+
".sha"
|
|
10912
|
+
).trim();
|
|
10913
|
+
} catch {
|
|
10914
|
+
throw err;
|
|
10915
|
+
}
|
|
10916
|
+
if (existing === "" || existing === "null") {
|
|
10917
|
+
throw err;
|
|
10918
|
+
}
|
|
10919
|
+
ghWithInputRetried(
|
|
10920
|
+
payload(existing),
|
|
10921
|
+
"api",
|
|
10922
|
+
"-X",
|
|
10923
|
+
"PUT",
|
|
10924
|
+
api,
|
|
10925
|
+
"--input",
|
|
10926
|
+
"-"
|
|
10927
|
+
);
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
__name(putContent, "putContent");
|
|
10931
|
+
function ensureBranch(repo, branch) {
|
|
10932
|
+
try {
|
|
10933
|
+
gh("api", `repos/${repo}/git/ref/heads/${branch}`);
|
|
10934
|
+
return;
|
|
10935
|
+
} catch (err) {
|
|
10936
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10937
|
+
if (!/HTTP 404|Not Found/i.test(msg)) throw err;
|
|
10938
|
+
}
|
|
10939
|
+
const defaultBranch = gh("api", `repos/${repo}`, "--jq", ".default_branch");
|
|
10940
|
+
let baseSha;
|
|
10941
|
+
try {
|
|
10942
|
+
baseSha = gh(
|
|
10943
|
+
"api",
|
|
10944
|
+
`repos/${repo}/git/ref/heads/${defaultBranch.trim()}`,
|
|
10945
|
+
"--jq",
|
|
10946
|
+
".object.sha"
|
|
10947
|
+
);
|
|
10948
|
+
} catch (err) {
|
|
10949
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10950
|
+
if (/HTTP 404|Not Found/i.test(msg)) {
|
|
10951
|
+
throw new Error(
|
|
10952
|
+
`assets repository ${repo} appears to be empty (its default branch ${defaultBranch.trim()} has no commits). Push an initial commit to it once; publish-assets creates and reuses its own branch from there.`
|
|
10953
|
+
);
|
|
10954
|
+
}
|
|
10955
|
+
throw err;
|
|
10956
|
+
}
|
|
10957
|
+
const base = baseSha.trim();
|
|
10958
|
+
if (base === "" || base === "null") {
|
|
10959
|
+
throw new Error(
|
|
10960
|
+
`could not resolve the head of ${repo}@${defaultBranch.trim()} \u2014 the ref lookup returned nothing to branch from.`
|
|
10961
|
+
);
|
|
10962
|
+
}
|
|
10963
|
+
try {
|
|
10964
|
+
ghWithInputRetried(
|
|
10965
|
+
JSON.stringify({ ref: `refs/heads/${branch}`, sha: base }),
|
|
10966
|
+
"api",
|
|
10967
|
+
"-X",
|
|
10968
|
+
"POST",
|
|
10969
|
+
`repos/${repo}/git/refs`,
|
|
10970
|
+
"--input",
|
|
10971
|
+
"-"
|
|
10972
|
+
);
|
|
10973
|
+
} catch (err) {
|
|
10974
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10975
|
+
if (!/already exists/i.test(msg)) throw err;
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
__name(ensureBranch, "ensureBranch");
|
|
10979
|
+
function runPublishAssets(args) {
|
|
10980
|
+
const refuse = /* @__PURE__ */ __name((reason) => {
|
|
10981
|
+
writeStderrLine(`publish-assets: refused \u2014 ${reason}`);
|
|
10982
|
+
writeStdoutLine(JSON.stringify({ published: false }));
|
|
10983
|
+
process.exitCode = 3;
|
|
10984
|
+
}, "refuse");
|
|
10985
|
+
if (!Number.isInteger(args.pr) || args.pr <= 0) {
|
|
10986
|
+
refuse(`--pr must be a positive integer, got ${String(args.pr)}.`);
|
|
10987
|
+
return;
|
|
10988
|
+
}
|
|
10989
|
+
const repoResult = parseAssetsRepo(process.env["QWEN_REVIEW_ASSETS_REPO"]);
|
|
10990
|
+
if ("error" in repoResult) {
|
|
10991
|
+
refuse(repoResult.error);
|
|
10992
|
+
return;
|
|
10993
|
+
}
|
|
10994
|
+
const repo = repoResult.repo;
|
|
10995
|
+
const effectiveHost = args.host ?? (process.env["GH_HOST"]?.trim() || void 0);
|
|
10996
|
+
const auth = reviewWriteAuthorization({
|
|
10997
|
+
userAuthorized: args.userAuthorized,
|
|
10998
|
+
skillArgs: args.skillArgs,
|
|
10999
|
+
pr: args.pr,
|
|
11000
|
+
// Bind the REVIEWED repo when the caller names it, never the assets repo:
|
|
11001
|
+
// the designation itself is the consent for the destination, and binding a
|
|
11002
|
+
// URL-shaped authorisation against a fork-hosted assets repo refused
|
|
11003
|
+
// legitimately authorised runs. Without --reviewed-repo the gate binds the
|
|
11004
|
+
// PR number (and host) alone.
|
|
11005
|
+
repo: args.reviewedRepo,
|
|
11006
|
+
host: effectiveHost
|
|
11007
|
+
});
|
|
11008
|
+
if (!auth.ok) {
|
|
11009
|
+
refuse(
|
|
11010
|
+
`not authorised \u2014 ${auth.why}. Evidence images are published only for a run that is authorised to post the review itself; the findings and their local file paths remain in the terminal output and the saved report.`
|
|
11011
|
+
);
|
|
11012
|
+
return;
|
|
11013
|
+
}
|
|
11014
|
+
if (effectiveHost) {
|
|
11015
|
+
try {
|
|
11016
|
+
setGhHost(effectiveHost);
|
|
11017
|
+
} catch (err) {
|
|
11018
|
+
refuse(
|
|
11019
|
+
`${err instanceof Error ? err.message : String(err)} (from ${args.host !== void 0 ? "--host" : "the GH_HOST environment variable"})`
|
|
11020
|
+
);
|
|
11021
|
+
return;
|
|
11022
|
+
}
|
|
11023
|
+
}
|
|
11024
|
+
let findings;
|
|
11025
|
+
const fileList = [...args.files ?? []];
|
|
11026
|
+
if (args.findings) {
|
|
11027
|
+
try {
|
|
11028
|
+
const artifact = JSON.parse(readFileSync15(resolve10(args.findings), "utf8"));
|
|
11029
|
+
findings = validateFindings(
|
|
11030
|
+
Array.isArray(artifact) ? artifact : artifact.findings
|
|
11031
|
+
);
|
|
11032
|
+
} catch (err) {
|
|
11033
|
+
refuse(
|
|
11034
|
+
`cannot use the findings artifact at ${JSON.stringify(args.findings)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11035
|
+
);
|
|
11036
|
+
return;
|
|
11037
|
+
}
|
|
11038
|
+
for (const f of findings) {
|
|
11039
|
+
for (const a of f.assetFiles ?? []) fileList.push(a);
|
|
11040
|
+
}
|
|
11041
|
+
}
|
|
11042
|
+
const unique = [...new Set(fileList)];
|
|
11043
|
+
if (unique.length === 0) {
|
|
11044
|
+
if (args.findings) {
|
|
11045
|
+
writeStderrLine(
|
|
11046
|
+
"publish-assets: no finding carries assetFiles \u2014 nothing to publish."
|
|
11047
|
+
);
|
|
11048
|
+
writeStdoutLine(JSON.stringify({ published: false, count: 0 }));
|
|
11049
|
+
return;
|
|
11050
|
+
}
|
|
11051
|
+
refuse("no files to publish.");
|
|
11052
|
+
return;
|
|
11053
|
+
}
|
|
11054
|
+
const stats = [];
|
|
11055
|
+
for (const file of unique) {
|
|
11056
|
+
const abs = resolve10(file);
|
|
11057
|
+
try {
|
|
11058
|
+
const st = statSync5(abs);
|
|
11059
|
+
if (!st.isFile()) throw new Error("not a regular file");
|
|
11060
|
+
stats.push({ file, abs, basename: basename5(abs), bytes: st.size });
|
|
11061
|
+
} catch (err) {
|
|
11062
|
+
refuse(
|
|
11063
|
+
`cannot read ${JSON.stringify(file)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11064
|
+
);
|
|
11065
|
+
return;
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
const batch = validateAssetBatch(stats);
|
|
11069
|
+
if (!batch.ok) {
|
|
11070
|
+
refuse(batch.reason);
|
|
11071
|
+
return;
|
|
11072
|
+
}
|
|
11073
|
+
const prepared = [];
|
|
11074
|
+
for (const st of stats) {
|
|
11075
|
+
let content;
|
|
11076
|
+
try {
|
|
11077
|
+
content = readFileSync15(st.abs);
|
|
11078
|
+
} catch (err) {
|
|
11079
|
+
refuse(
|
|
11080
|
+
`cannot read ${JSON.stringify(st.file)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11081
|
+
);
|
|
11082
|
+
return;
|
|
11083
|
+
}
|
|
11084
|
+
const sha256 = createHash2("sha256").update(content).digest("hex");
|
|
11085
|
+
prepared.push({
|
|
11086
|
+
file: st.file,
|
|
11087
|
+
bytes: st.bytes,
|
|
11088
|
+
sha256,
|
|
11089
|
+
remotePath: remoteAssetPath(args.pr, st.basename, sha256),
|
|
11090
|
+
contentBase64: content.toString("base64")
|
|
11091
|
+
});
|
|
11092
|
+
}
|
|
11093
|
+
const branch = assetsBranch(args.pr);
|
|
11094
|
+
ensureBranch(repo, branch);
|
|
11095
|
+
for (const p of prepared) {
|
|
11096
|
+
putContent(repo, branch, p.remotePath, p.contentBase64, args.pr);
|
|
11097
|
+
}
|
|
11098
|
+
const headSha = gh(
|
|
11099
|
+
"api",
|
|
11100
|
+
`repos/${repo}/git/ref/heads/${branch}`,
|
|
11101
|
+
"--jq",
|
|
11102
|
+
".object.sha"
|
|
11103
|
+
).trim();
|
|
11104
|
+
const published = prepared.map((p) => ({
|
|
11105
|
+
file: p.file,
|
|
11106
|
+
remotePath: p.remotePath,
|
|
11107
|
+
url: rawAssetUrl({
|
|
11108
|
+
host: effectiveHost,
|
|
11109
|
+
repo,
|
|
11110
|
+
commitSha: headSha,
|
|
11111
|
+
remotePath: p.remotePath
|
|
11112
|
+
}),
|
|
11113
|
+
bytes: p.bytes,
|
|
11114
|
+
sha256: p.sha256
|
|
11115
|
+
}));
|
|
11116
|
+
const manifest = {
|
|
11117
|
+
repo,
|
|
11118
|
+
branch,
|
|
11119
|
+
commitSha: headSha,
|
|
11120
|
+
pr: args.pr,
|
|
11121
|
+
published
|
|
11122
|
+
};
|
|
11123
|
+
const outPath = resolve10(args.out);
|
|
11124
|
+
mkdirSync14(dirname11(outPath), { recursive: true });
|
|
11125
|
+
writeFileSync14(outPath, `${JSON.stringify(manifest, null, 2)}
|
|
11126
|
+
`, "utf8");
|
|
11127
|
+
if (findings && !args.findingsOut) {
|
|
11128
|
+
writeStderrLine(
|
|
11129
|
+
"publish-assets: --findings given without --findings-out \u2014 the URLs are in the manifest only; the artifact keeps local paths."
|
|
11130
|
+
);
|
|
11131
|
+
}
|
|
11132
|
+
if (findings && args.findingsOut) {
|
|
11133
|
+
const urlByFile = new Map(published.map((p) => [p.file, p.url]));
|
|
11134
|
+
const updated = findings.map((f) => {
|
|
11135
|
+
const urls = (f.assetFiles ?? []).map((a) => urlByFile.get(a)).filter((u) => u !== void 0);
|
|
11136
|
+
return urls.length > 0 ? { ...f, assets: urls } : f;
|
|
11137
|
+
});
|
|
11138
|
+
const report = buildReport(updated);
|
|
11139
|
+
const fOut = resolve10(args.findingsOut);
|
|
11140
|
+
mkdirSync14(dirname11(fOut), { recursive: true });
|
|
11141
|
+
writeFileSync14(fOut, `${JSON.stringify(report, null, 2)}
|
|
11142
|
+
`, "utf8");
|
|
11143
|
+
}
|
|
11144
|
+
writeStderrLine(
|
|
11145
|
+
`publish-assets: ${published.length} file(s) \u2192 ${repo}@${branch} (commit ${headSha.slice(0, 9)})`
|
|
11146
|
+
);
|
|
11147
|
+
writeStdoutLine(JSON.stringify({ published: true, count: published.length }));
|
|
11148
|
+
}
|
|
11149
|
+
__name(runPublishAssets, "runPublishAssets");
|
|
11150
|
+
var publishAssetsCommand = {
|
|
11151
|
+
command: "publish-assets",
|
|
11152
|
+
describe: "Publish review evidence images to the user-designated assets repository (QWEN_REVIEW_ASSETS_REPO) and emit commit-pinned URLs \u2014 gated on the same authorisation as submit",
|
|
11153
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("pr", {
|
|
11154
|
+
type: "number",
|
|
11155
|
+
demandOption: true,
|
|
11156
|
+
describe: "The pull request this evidence belongs to"
|
|
11157
|
+
}).option("reviewed-repo", {
|
|
11158
|
+
type: "string",
|
|
11159
|
+
describe: "The owner/repo the reviewed PR lives in \u2014 strengthens the authorisation binding for URL-shaped review arguments; omit to bind by PR number alone"
|
|
11160
|
+
}).option("files", {
|
|
11161
|
+
type: "string",
|
|
11162
|
+
array: true,
|
|
11163
|
+
describe: "Evidence image files to publish"
|
|
11164
|
+
}).option("findings", {
|
|
11165
|
+
type: "string",
|
|
11166
|
+
describe: "Findings artifact whose per-finding assetFiles should be published"
|
|
11167
|
+
}).option("findings-out", {
|
|
11168
|
+
type: "string",
|
|
11169
|
+
implies: "findings",
|
|
11170
|
+
describe: "Where to write the findings artifact with published URLs woven into each finding (requires --findings)"
|
|
11171
|
+
}).option("out", {
|
|
11172
|
+
type: "string",
|
|
11173
|
+
demandOption: true,
|
|
11174
|
+
describe: "Where to write the publish manifest"
|
|
11175
|
+
}).option("host", {
|
|
11176
|
+
type: "string",
|
|
11177
|
+
describe: "GitHub Enterprise host (defaults to github.com)"
|
|
11178
|
+
}).option("user-authorized", {
|
|
11179
|
+
type: "boolean",
|
|
11180
|
+
default: false,
|
|
11181
|
+
describe: "Pass ONLY when the user asked, in a message they typed this session, for this review to be published"
|
|
11182
|
+
}).option("skill-args", {
|
|
11183
|
+
type: "string",
|
|
11184
|
+
describe: "Test seam: path to the recorded review arguments (ignored when a session id is present)"
|
|
11185
|
+
}), "builder"),
|
|
11186
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
11187
|
+
runPublishAssets({
|
|
11188
|
+
pr: argv["pr"],
|
|
11189
|
+
reviewedRepo: argv["reviewed-repo"],
|
|
11190
|
+
files: argv["files"],
|
|
11191
|
+
findings: argv["findings"],
|
|
11192
|
+
findingsOut: argv["findings-out"],
|
|
11193
|
+
out: argv["out"],
|
|
11194
|
+
host: argv["host"],
|
|
11195
|
+
userAuthorized: Boolean(argv["user-authorized"]),
|
|
11196
|
+
skillArgs: argv["skill-args"]
|
|
11197
|
+
});
|
|
11198
|
+
}, "handler")
|
|
11199
|
+
};
|
|
11200
|
+
|
|
11201
|
+
// packages/cli/src/commands/review/resolve-anchors.ts
|
|
11202
|
+
init_esbuild_shims();
|
|
11203
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "node:fs";
|
|
11204
|
+
import { dirname as dirname12, resolve as resolve11 } from "node:path";
|
|
11205
|
+
|
|
11206
|
+
// packages/cli/src/commands/review/lib/anchors.ts
|
|
11207
|
+
init_esbuild_shims();
|
|
11208
|
+
function collectNewSideLines(diffText, file) {
|
|
11209
|
+
const lines2 = diffText.split("\n");
|
|
11210
|
+
const out = [];
|
|
11211
|
+
for (const hunk of file.hunks) {
|
|
11212
|
+
if (hunk.newCount === 0) continue;
|
|
11213
|
+
let newCursor = hunk.newStart;
|
|
11214
|
+
for (let n = hunk.diffStart + 1; n <= hunk.diffEnd; n++) {
|
|
11215
|
+
const raw = lines2[n - 1];
|
|
11216
|
+
if (raw === void 0) break;
|
|
11217
|
+
if (raw.startsWith("+")) {
|
|
11218
|
+
out.push({ newLine: newCursor, text: raw.slice(1), added: true });
|
|
11219
|
+
newCursor++;
|
|
11220
|
+
} else if (raw.startsWith("-")) {
|
|
11221
|
+
} else if (raw.startsWith(" ") || raw === "") {
|
|
11222
|
+
out.push({
|
|
11223
|
+
newLine: newCursor,
|
|
11224
|
+
text: raw.startsWith(" ") ? raw.slice(1) : "",
|
|
11225
|
+
added: false
|
|
11226
|
+
});
|
|
11227
|
+
newCursor++;
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
}
|
|
11231
|
+
return out;
|
|
11232
|
+
}
|
|
11233
|
+
__name(collectNewSideLines, "collectNewSideLines");
|
|
11234
|
+
function normalizeExact(s) {
|
|
11235
|
+
return s.replace(/\s+$/, "");
|
|
11236
|
+
}
|
|
11237
|
+
__name(normalizeExact, "normalizeExact");
|
|
11238
|
+
function normalizeLoose(s) {
|
|
11239
|
+
return s.trim();
|
|
11240
|
+
}
|
|
11241
|
+
__name(normalizeLoose, "normalizeLoose");
|
|
11242
|
+
function anchorVariants(anchor) {
|
|
11243
|
+
const lines2 = anchor.replace(/\r\n/g, "\n").split("\n");
|
|
11244
|
+
while (lines2.length > 0 && lines2[0].trim() === "") lines2.shift();
|
|
11245
|
+
while (lines2.length > 0 && lines2[lines2.length - 1].trim() === "") lines2.pop();
|
|
11246
|
+
if (lines2.length === 0) return [];
|
|
11247
|
+
const variants = [lines2];
|
|
11248
|
+
const meaningful = lines2.filter((l) => l.trim() !== "");
|
|
11249
|
+
if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
|
|
11250
|
+
variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
|
|
11251
|
+
}
|
|
11252
|
+
const NO_NEWLINE = /^\$/;
|
|
11253
|
+
const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
|
|
11254
|
+
if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
|
|
11255
|
+
variants.push(
|
|
11256
|
+
lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
|
|
11257
|
+
);
|
|
11258
|
+
}
|
|
11259
|
+
return variants;
|
|
11260
|
+
}
|
|
11261
|
+
__name(anchorVariants, "anchorVariants");
|
|
11262
|
+
function matchRuns(hay, needle, norm) {
|
|
11263
|
+
const starts = [];
|
|
11264
|
+
if (needle.length === 0 || hay.length < needle.length) return starts;
|
|
11265
|
+
const normNeedle = needle.map(norm);
|
|
11266
|
+
for (let i = 0; i + normNeedle.length <= hay.length; i++) {
|
|
11267
|
+
let ok = true;
|
|
11268
|
+
for (let j = 0; j < normNeedle.length; j++) {
|
|
11269
|
+
const cell = hay[i + j];
|
|
11270
|
+
if (norm(cell.text) !== normNeedle[j]) {
|
|
11271
|
+
ok = false;
|
|
11272
|
+
break;
|
|
11273
|
+
}
|
|
11274
|
+
if (j > 0 && cell.newLine !== hay[i + j - 1].newLine + 1) {
|
|
11275
|
+
ok = false;
|
|
11276
|
+
break;
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
11279
|
+
if (ok) starts.push(i);
|
|
11280
|
+
}
|
|
11281
|
+
return starts;
|
|
11282
|
+
}
|
|
11283
|
+
__name(matchRuns, "matchRuns");
|
|
11284
|
+
function candidatesFor(hay, needle, norm) {
|
|
11285
|
+
return matchRuns(hay, needle, norm).map((i) => {
|
|
11286
|
+
const run4 = hay.slice(i, i + needle.length);
|
|
11287
|
+
return {
|
|
11288
|
+
startLine: run4[0].newLine,
|
|
11289
|
+
line: run4[run4.length - 1].newLine,
|
|
11290
|
+
added: run4.some((l) => l.added)
|
|
11291
|
+
};
|
|
11292
|
+
});
|
|
11293
|
+
}
|
|
11294
|
+
__name(candidatesFor, "candidatesFor");
|
|
11295
|
+
function pick(cands, claimedLine) {
|
|
11296
|
+
if (cands.length === 1) return cands[0];
|
|
11297
|
+
if (claimedLine !== void 0) {
|
|
11298
|
+
const dist = /* @__PURE__ */ __name((c) => Math.abs(c.startLine - claimedLine), "dist");
|
|
11299
|
+
let best = Infinity;
|
|
11300
|
+
for (const c of cands) best = Math.min(best, dist(c));
|
|
11301
|
+
const nearest = cands.filter((c) => dist(c) === best);
|
|
11302
|
+
return nearest.length === 1 ? nearest[0] : null;
|
|
11303
|
+
}
|
|
11304
|
+
const added = cands.filter((c) => c.added);
|
|
11305
|
+
return added.length === 1 ? added[0] : null;
|
|
11306
|
+
}
|
|
11307
|
+
__name(pick, "pick");
|
|
11308
|
+
function resolveAnchor(newSideLines, anchor, claimedLine) {
|
|
11309
|
+
const variants = anchorVariants(anchor);
|
|
11310
|
+
if (variants.length === 0) {
|
|
11311
|
+
return { status: "unmatched", reason: "anchor is empty" };
|
|
11312
|
+
}
|
|
11313
|
+
for (const [vi, needle] of variants.entries()) {
|
|
11314
|
+
const norms = vi === 0 ? [
|
|
10578
11315
|
[true, normalizeExact],
|
|
10579
11316
|
[false, normalizeLoose]
|
|
10580
11317
|
] : [[true, normalizeExact]];
|
|
@@ -10688,7 +11425,7 @@ __name(validateRequests, "validateRequests");
|
|
|
10688
11425
|
function runResolveAnchors(args) {
|
|
10689
11426
|
let diffText;
|
|
10690
11427
|
try {
|
|
10691
|
-
diffText =
|
|
11428
|
+
diffText = readFileSync16(args.diff, "utf8");
|
|
10692
11429
|
} catch (err) {
|
|
10693
11430
|
throw new Error(
|
|
10694
11431
|
`Cannot read diff file ${args.diff}: ${err.message}`
|
|
@@ -10696,7 +11433,7 @@ function runResolveAnchors(args) {
|
|
|
10696
11433
|
}
|
|
10697
11434
|
let rawText;
|
|
10698
11435
|
try {
|
|
10699
|
-
rawText =
|
|
11436
|
+
rawText = readFileSync16(args.input, "utf8");
|
|
10700
11437
|
} catch (err) {
|
|
10701
11438
|
throw new Error(
|
|
10702
11439
|
`Cannot read findings file ${args.input}: ${err.message}`
|
|
@@ -10727,8 +11464,8 @@ function runResolveAnchors(args) {
|
|
|
10727
11464
|
loose: resolved.filter((r) => r.tier?.startsWith("loose")).length
|
|
10728
11465
|
}
|
|
10729
11466
|
};
|
|
10730
|
-
|
|
10731
|
-
|
|
11467
|
+
mkdirSync15(dirname12(resolve11(args.out)), { recursive: true });
|
|
11468
|
+
writeFileSync15(args.out, JSON.stringify(report, null, 2), "utf8");
|
|
10732
11469
|
writeStdoutLine(`Wrote resolved anchors to ${args.out}`);
|
|
10733
11470
|
const s = report.stats;
|
|
10734
11471
|
writeStderrLine(
|
|
@@ -10775,8 +11512,8 @@ var resolveAnchorsCommand = {
|
|
|
10775
11512
|
|
|
10776
11513
|
// packages/cli/src/commands/review/check-coverage.ts
|
|
10777
11514
|
init_esbuild_shims();
|
|
10778
|
-
import { writeFileSync as
|
|
10779
|
-
import { dirname as
|
|
11515
|
+
import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
|
|
11516
|
+
import { dirname as dirname13, resolve as resolve12 } from "node:path";
|
|
10780
11517
|
function runCheckCoverage(args) {
|
|
10781
11518
|
let report;
|
|
10782
11519
|
try {
|
|
@@ -10792,8 +11529,8 @@ This is an environment problem, not a finding about the agents. Coverage cannot
|
|
|
10792
11529
|
}
|
|
10793
11530
|
throw err;
|
|
10794
11531
|
}
|
|
10795
|
-
|
|
10796
|
-
|
|
11532
|
+
mkdirSync16(dirname13(resolve12(args.out)), { recursive: true });
|
|
11533
|
+
writeFileSync16(args.out, JSON.stringify(report, null, 2));
|
|
10797
11534
|
writeStdoutLine(`Wrote coverage report to ${args.out}`);
|
|
10798
11535
|
const totalChunks = report.coveredChunks.length + report.missingChunks.length + report.uncoverableChunks.length;
|
|
10799
11536
|
const worked = report.agents - report.blindAgents.length - report.idleAgents.length;
|
|
@@ -10886,9 +11623,9 @@ var checkCoverageCommand = {
|
|
|
10886
11623
|
|
|
10887
11624
|
// packages/cli/src/commands/review/agent-prompt.ts
|
|
10888
11625
|
init_esbuild_shims();
|
|
10889
|
-
import { createHash as
|
|
10890
|
-
import { readFileSync as
|
|
10891
|
-
import { dirname as
|
|
11626
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
11627
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
11628
|
+
import { dirname as dirname14, join as join12, resolve as resolve13 } from "node:path";
|
|
10892
11629
|
|
|
10893
11630
|
// packages/cli/src/commands/review/lib/path-rules.ts
|
|
10894
11631
|
init_esbuild_shims();
|
|
@@ -10918,14 +11655,85 @@ var GITHUB_ACTIONS = {
|
|
|
10918
11655
|
|
|
10919
11656
|
**Favour precision over recall here.** A false alarm on a workflow costs more reviewer trust than a missed minor nit, because a YAML finding is the easiest kind for an author to dismiss. Every finding needs the concrete trigger and the concrete outcome, like any other.`
|
|
10920
11657
|
};
|
|
10921
|
-
var
|
|
11658
|
+
var JAVA = {
|
|
11659
|
+
title: "Java / JVM performance",
|
|
11660
|
+
matches: /* @__PURE__ */ __name((p) => /\.java$/i.test(p), "matches"),
|
|
11661
|
+
checklist: `A Java change's most expensive regressions are decided by the JVM, not by anything a reader can see in the source: HotSpot chooses what to inline, what to scalar-replace, and what to compile at all \u2014 and a one-line change can flip each of those on a hot path. The general performance lens (N+1, repeated work, data structures) sees none of it.
|
|
11662
|
+
|
|
11663
|
+
**You are reviewing this diff, not auditing this file.** A JVM-cost weakness the code already had, on a line this change does not touch, is out of scope \u2014 the same rule as everywhere else. What is in scope: a line this diff **adds or changes**, and a cheap path this diff **makes hot** (a new caller in a request loop, a call moved inside a loop). Test sources (\`src/test/**\`), \`package-info\`/\`module-info\`, and generated sources are out of scope for the hot-path items \u2014 nothing there is hot.
|
|
11664
|
+
|
|
11665
|
+
**Correctness traps dressed as concurrency or performance code (Critical):**
|
|
11666
|
+
|
|
11667
|
+
- **A \`SimpleDateFormat\` shared across threads.** It is not thread-safe: concurrent \`parse\`/\`format\` corrupts its calendar state and returns silently wrong values or throws. If the diff adds a shared (static or instance field) \`SimpleDateFormat\`, or makes an existing one reachable from more than one thread, that is wrong, not slow. The fix is \`java.time.format.DateTimeFormatter\` (immutable) or a \`ThreadLocal\`.
|
|
11668
|
+
- **A compound action on \`ConcurrentHashMap\` built from two calls.** \`get\`-then-\`put\`, \`containsKey\`-then-\`put\`, check-then-act of any shape: the map's thread safety covers each call, not the pair, and the race loses an update or runs the guarded work twice. Say what the race corrupts \u2014 that is the finding. Fix with \`putIfAbsent\`, \`computeIfAbsent\`, or \`merge\`.
|
|
11669
|
+
- **Double-checked locking without \`volatile\` on the field.** The reading thread can observe a partially constructed object. If the diff adds or touches the idiom, the field must be \`volatile\` (or replaced with the holder idiom).
|
|
11670
|
+
|
|
11671
|
+
**JVM-cost defects provable from the source (Suggestion \u2014 a cost, not a wrongness):**
|
|
11672
|
+
|
|
11673
|
+
- **A regex compiled per call.** \`Pattern.compile(...)\` in a method body or loop, and the \`String\` conveniences that hide it \u2014 \`matches\`, \`replaceAll\`, \`replaceFirst\`, and \`split\` with a real regex (a one- or two-character \`split\` argument takes a fast path only when it is not a regex construct \u2014 \`split(".")\` does not, since \`.\` is a metacharacter; the escaped two-character form \`split("\\\\.")\` does) \u2014 recompile the pattern on every invocation. On a per-request or per-record path that is real CPU. Fix: \`private static final Pattern\`.
|
|
11674
|
+
- **\`+=\` on a \`String\` inside a loop.** Each iteration rebuilds and copies the whole accumulated prefix; the loop is O(n\xB2) in the final length. Fix: one \`StringBuilder\` outside the loop.
|
|
11675
|
+
- **Boxing on a hot path.** A \`Long\`/\`Integer\` loop accumulator, a \`Map<Long, \u2026>\` over dense \`int\` keys, \`.boxed()\` in a counted stream: every box past the small-integer cache is an allocation plus a pointer chase per iteration. Fix: primitives, or the primitive streams/collections.
|
|
11676
|
+
- **A capturing lambda or method reference inside a hot loop.** A non-capturing lambda is a singleton; one that closes over a local allocates per iteration. If the diff moves a capturing lambda into a loop, hoist it or restructure it to capture nothing.
|
|
11677
|
+
- **A log message built whether or not it is logged.** \`log.debug("result: " + value)\` pays the concatenation with the level off, and even \`log.debug("{}", expensive())\` still pays \`expensive()\`. Fix: parameterized messages, and guard expensive arguments with \`isDebugEnabled()\` or a supplier-based logging API.
|
|
11678
|
+
- **A collection sized after the fact.** A \`HashMap\`/\`ArrayList\` the code then fills with a known N pays repeated rehash/grow-copy chains; presize it (a \`HashMap\` needs \`N/0.75 + 1\` to avoid rehashing at all \u2014 \`HashMap.newHashMap(n)\`, JDK 19+, does that arithmetic for you).
|
|
11679
|
+
- **Legacy synchronized types in new code.** \`Vector\`, \`Hashtable\`, \`StringBuffer\` put a monitor on every call; the unsynchronized equivalents are the default for a reason.
|
|
11680
|
+
- **An exception used as control flow.** Validating by catching \`NumberFormatException\`, looping until \`EOFException\`: \`fillInStackTrace\` costs more than the work being guarded. Validate instead.
|
|
11681
|
+
- **Reflection rediscovered per call.** \`getMethod\`/\`getField\` on every invocation of a hot path; cache the \`Method\`/\`Field\` \u2014 or a \`MethodHandle\` \u2014 once.
|
|
11682
|
+
|
|
11683
|
+
**JIT inlining \u2014 the regression no dimension can see, and you cannot prove from source:**
|
|
11684
|
+
|
|
11685
|
+
HotSpot's C2 compiler gates inlining on the **bytecode size of the callee** among other criteria. These are the size caps in its normal policy (a project's own \`-XX:\` flags, or a different JVM, change them \u2014 check before citing):
|
|
11686
|
+
|
|
11687
|
+
| callee bytecode size | C2 normal-policy size cap |
|
|
11688
|
+
| --- | --- |
|
|
11689
|
+
| \u2264 6 (\`MaxTrivialSize\`) | under the trivial cap \u2014 size alone does not block it |
|
|
11690
|
+
| \u2264 35 (\`MaxInlineSize\`) | under the normal cap \u2014 size alone does not block it |
|
|
11691
|
+
| \u2264 325 (\`FreqInlineSize\`) | under the hot cap \u2014 size blocks it unless the call site is hot |
|
|
11692
|
+
| > 325 | over the hot cap \u2014 size blocks it at every call site |
|
|
11693
|
+
| > 8000 (\`HugeMethodLimit\`, a develop flag gated by the product \`DontCompileHugeMethods\`) | over the huge-method limit \u2014 size blocks even JIT compilation |
|
|
11694
|
+
|
|
11695
|
+
A size cap is a gate, not an outcome: passing one is necessary but not sufficient. Independent of size, C2 also declines a call site that was **never executed**, has **low call site frequency**, exceeds the inlining **depth or recursion limits**, or hits the **node-count** cutoff \u2014 so a method under a cap can still be left un-inlined, and a static size measurement proves only that a threshold was crossed, not that the method *was* inlined before and *is not* now.
|
|
11696
|
+
|
|
11697
|
+
Two more ways a diff can flip it: an already-compiled callee whose **native** size passes \`InlineSmallCode\` (x86_64 default: 2500 on JDK 17+, 2000 on JDK 8\u201316; 1000 only with \`-XX:-TieredCompilation\`) stops being inlined to protect the code cache, and a call site that gains a **third** receiver implementation goes megamorphic \u2014 vtable dispatch, and no inlining **unless one receiver still dominates the profile** (\`TypeProfileMajorReceiverPercent\`, 90% by default), in which case C2 inlines that receiver behind a guard with an uncommon trap for the rest. So the diff-shapes that matter: a small method on a hot path the change **grows** (added validation, logging, a branch), a new layer in a hot call chain, a new implementation registered for an interface invoked in a hot loop. A cold method over 325 bytes is **not** a finding \u2014 inlining only matters where the call is hot.
|
|
11698
|
+
|
|
11699
|
+
**Measure it; do not estimate bytecode from source.** Two tiers, in order of cost:
|
|
11700
|
+
|
|
11701
|
+
1. **Static \u2014 available when the class compiles, which for a leaf class is always and for a connected one needs its dependencies.** Allocate a private scratch dir first \u2014 \`SCRATCH=$(mktemp -d)\`, never a fixed path like \`/tmp/scratch\`: other agents are compiling concurrently, and two of them writing different revisions of the same class into one directory read each other's \`.class\` files and measure the wrong bytecode (on Windows, \`mkdir %TEMP%\\review-%RANDOM%\` gives the same uniqueness). Then \`javac -proc:none -nowarn -d "$SCRATCH" X.java\` \u2014 \`-proc:none\` is not optional: annotation processors on the classpath (Lombok, Dagger, one the PR itself added) run at compile time with your privileges, so a \`javac\` without it is itself an untrusted-code execution. It is also a fidelity flag: on a project whose processor contributes code to the measured class (Lombok's generated members, Dagger's injected fields), the compiled class is missing those members and \`javap\` reports a size that is simply wrong \u2014 if the project runs a processor that touches this class, the static tier is **void**; go to \`Confidence: low\` with the mechanism. Give the compiler what it needs to resolve imports: \`-sourcepath\` at the module source root, plus \`-cp\` against an already-built \`target/classes\` / \`build/classes\` if one exists. Pass \`--release <N>\` at the project's target level (read it from \`maven.compiler.release\`, \`<release>\`, or Gradle's \`release\`/\`targetCompatibility\`): the same source compiles to different bytecode at different levels \u2014 a five-\`+\` concatenation is 37 bytes at release 8 and 14 at 9+ \u2014 and measuring the wrong level produces a threshold verdict on bytecode the shipped artifact does not contain; if the target level cannot be determined, say so in the finding. Then \`javap -c -p\` the class and read the method's size: the offset of its last instruction plus that instruction's width. Compare against 35 and 325. For the **crossing** claim \u2014 "this diff pushed it over the threshold" \u2014 get the base side **without touching the tree**: \`git show <merge-base>:<path> > "$SCRATCH/X.java"\`, compile that too, and measure both; a method already over on the base side is pre-existing, not a finding. A file the PR **adds** has no base side \u2014 \`git show\` fails, and there is no crossing to claim; report the size without a before/after. The base side compiles against the same \`target/classes\` the head side used; on a PR that changes more than the measured file the conditions are not perfectly equivalent \u2014 note that caveat when it applies. If the class will not compile (no pre-built classpath, no source path that resolves its imports), go straight to the mechanism-at-\`Confidence: low\` form below \u2014 **never \`git checkout\`, \`git stash\`, build in place, or run \`mvn\`/\`gradle\` to force a compile.** In a PR review the worktree is shared with agents running concurrently and the branch's build logic is contributor-controlled \u2014 the attack surface, not a cost; in a local review it is the user's own checkout. Mutating either corrupts work you cannot see.
|
|
11702
|
+
2. **Dynamic \u2014 when the code is runnable.** Run the workload with \`-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining\` and grep for the method: \`callee is too large\` / \`hot method too big\` is the JVM itself declining to inline. JMH gives the before/after throughput; JITWatch visualizes the same decision logs.
|
|
11703
|
+
|
|
11704
|
+
A finding that a method "can no longer be inlined" needs the **dynamic** tier \u2014 only \`PrintInlining\` or equivalent runtime evidence shows the JVM actually declined to inline. The static tier proves a **size-gate crossing**, which is the mechanism: report it at \`Confidence: low\` \u2014 the threshold crossed, what the diff added to the method, and the runtime measurement still to run. A "can no longer be inlined" claim with neither tier is a guess stated as fact. And if the diff *claims* this path got faster while the mechanism above says it cannot have, the finding is the unsubstantiated claim.
|
|
11705
|
+
|
|
11706
|
+
**When the finding IS a grown hot method, the fix to suggest is hot/cold splitting \u2014 not reverting the change, and not a JVM tuning flag** (\`-XX:FreqInlineSize\`, \`-XX:CompileCommand=inline\`): those are runtime knobs the PR's author cannot ship in a code change, and raising an inline threshold to fit one method pays for it at every other call site. (The JDK-internal \`@ForceInline\` is not available to application code at all.) Move the cold paths \u2014 error handling, rare branches, defensive validation, the \`switch\` arms that almost never fire \u2014 into a small private helper, leaving the common path under the threshold; the helper, now called from one site, is itself inlinable. Name the bytecode range to extract and the size it leaves behind, the way the measurement names them: "extract bytecodes 212\u2013311 (~100 bytes) into \`applyRounding\`, leaving \`parseAmount\` at ~238 bytes" is a finding an author can act on; "consider splitting the method" is not.
|
|
11707
|
+
|
|
11708
|
+
**Favour precision over recall here.** A guessed JVM finding is the easiest kind for an author to dismiss, and one dismissal teaches them to skip the rest of the review. Every finding needs the concrete hot path and the concrete cost, like any other. Performance findings are **Suggestions** \u2014 slow is a cost, not incorrect behaviour \u2014 **except where the cost is itself the wrongness**: unbounded allocation, quadratic work, or unbounded cache growth on attacker-reachable input is a denial-of-service hole, which the severity ladder grades Critical, not a Suggestion. Name the reachable input that triggers it; "this loop is slow" with no attacker-reachable trigger stays a Suggestion.`
|
|
11709
|
+
};
|
|
11710
|
+
var PATH_RULES = [GITHUB_ACTIONS, JAVA];
|
|
11711
|
+
function isOutOfScope(p) {
|
|
11712
|
+
return /src\/(test|integrationTest|integTest|androidTest|testFixtures)\//i.test(
|
|
11713
|
+
p
|
|
11714
|
+
) || /(?:^|\/)(?:package-info|module-info)\.java$/i.test(p) || /(?:^|\/)(?:target|build)\/(?:generated-sources|generated-test-sources|generated)\//i.test(
|
|
11715
|
+
p
|
|
11716
|
+
);
|
|
11717
|
+
}
|
|
11718
|
+
__name(isOutOfScope, "isOutOfScope");
|
|
11719
|
+
function describePaths(which) {
|
|
11720
|
+
const CAP = 10;
|
|
11721
|
+
const prod = which.filter((p) => !isOutOfScope(p));
|
|
11722
|
+
const rest = which.filter((p) => isOutOfScope(p));
|
|
11723
|
+
const ordered = [...prod, ...rest];
|
|
11724
|
+
if (ordered.length <= CAP) {
|
|
11725
|
+
return ordered.join(", ");
|
|
11726
|
+
}
|
|
11727
|
+
return `${ordered.slice(0, CAP).join(", ")}, \u2026and ${ordered.length - CAP} more`;
|
|
11728
|
+
}
|
|
11729
|
+
__name(describePaths, "describePaths");
|
|
10922
11730
|
function pathRulesFor(paths) {
|
|
10923
11731
|
const hit = PATH_RULES.filter((r) => paths.some((p) => r.matches(p)));
|
|
10924
11732
|
if (hit.length === 0) return "";
|
|
10925
11733
|
const parts = ["## Rules for the files in front of you", ""];
|
|
10926
11734
|
for (const r of hit) {
|
|
10927
11735
|
const which = paths.filter((p) => r.matches(p));
|
|
10928
|
-
parts.push(`### ${r.title} \u2014 ${which
|
|
11736
|
+
parts.push(`### ${r.title} \u2014 ${describePaths(which)}`, "", r.checklist, "");
|
|
10929
11737
|
}
|
|
10930
11738
|
return parts.join("\n").trimEnd();
|
|
10931
11739
|
}
|
|
@@ -10990,32 +11798,32 @@ function chunkFrom(report, id) {
|
|
|
10990
11798
|
throw new Error("agent-prompt: the plan has no `chunks[]`.");
|
|
10991
11799
|
}
|
|
10992
11800
|
const chunks = report.chunks;
|
|
10993
|
-
const
|
|
10994
|
-
if (!
|
|
11801
|
+
const chunk2 = chunks.find((c) => c?.id === id);
|
|
11802
|
+
if (!chunk2) {
|
|
10995
11803
|
throw new Error(
|
|
10996
11804
|
`agent-prompt: the plan has no chunk ${id} (it has ${chunks.length}: ${chunks.map((c) => c?.id).join(", ")}).`
|
|
10997
11805
|
);
|
|
10998
11806
|
}
|
|
10999
|
-
if (!Number.isSafeInteger(
|
|
11807
|
+
if (!Number.isSafeInteger(chunk2.startLine) || !Number.isSafeInteger(chunk2.endLine) || chunk2.startLine < 1 || chunk2.endLine < chunk2.startLine) {
|
|
11000
11808
|
throw new Error(
|
|
11001
|
-
`agent-prompt: chunk ${id} has no usable line range (startLine=${
|
|
11809
|
+
`agent-prompt: chunk ${id} has no usable line range (startLine=${chunk2.startLine}, endLine=${chunk2.endLine}).`
|
|
11002
11810
|
);
|
|
11003
11811
|
}
|
|
11004
|
-
return { diffPath, chunk, total: chunks.length };
|
|
11812
|
+
return { diffPath, chunk: chunk2, total: chunks.length };
|
|
11005
11813
|
}
|
|
11006
11814
|
__name(chunkFrom, "chunkFrom");
|
|
11007
11815
|
function buildChunkAgentPrompt(report, id, rules) {
|
|
11008
|
-
const { chunk, total } = chunkFrom(report, id);
|
|
11009
|
-
const files = (Array.isArray(
|
|
11816
|
+
const { chunk: chunk2, total } = chunkFrom(report, id);
|
|
11817
|
+
const files = (Array.isArray(chunk2.files) ? chunk2.files : []).filter(
|
|
11010
11818
|
(f) => !!f && typeof f.path === "string" && f.path.length > 0
|
|
11011
11819
|
).map(
|
|
11012
11820
|
(f) => `- ${inertPath(f.path)} (new-side lines ${f.newStart}-${f.newEnd})`
|
|
11013
11821
|
).join("\n");
|
|
11014
|
-
const unreachable =
|
|
11822
|
+
const unreachable = chunk2.maxLineChars > READ_FILE_CHAR_CAP;
|
|
11015
11823
|
const parts = [
|
|
11016
|
-
`You are reviewing chunk ${
|
|
11824
|
+
`You are reviewing chunk ${chunk2.id} of ${total} of a code diff.`,
|
|
11017
11825
|
"",
|
|
11018
|
-
`Your territory: lines ${
|
|
11826
|
+
`Your territory: lines ${chunk2.startLine}-${chunk2.endLine} of the diff (${chunk2.lines} lines, ${chunk2.chars} characters). The surrounding chunks belong to other agents \u2014 do not review them.`,
|
|
11019
11827
|
"",
|
|
11020
11828
|
"It covers these source files:",
|
|
11021
11829
|
files || "- (none recorded)",
|
|
@@ -11025,11 +11833,11 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11025
11833
|
if (unreachable) {
|
|
11026
11834
|
parts.push(
|
|
11027
11835
|
"",
|
|
11028
|
-
`**This chunk contains a single line of ${
|
|
11836
|
+
`**This chunk contains a single line of ${chunk2.maxLineChars} characters** \u2014 longer than one read returns, and paging cannot reach its tail (every page starts at a line boundary). Do not claim to have reviewed it. Return exactly:`,
|
|
11029
11837
|
"",
|
|
11030
|
-
` Uncoverable: chunk ${
|
|
11838
|
+
` Uncoverable: chunk ${chunk2.id} \u2014 line exceeds the read limit`
|
|
11031
11839
|
);
|
|
11032
|
-
} else if (
|
|
11840
|
+
} else if (chunk2.oversized) {
|
|
11033
11841
|
parts.push(
|
|
11034
11842
|
"",
|
|
11035
11843
|
"**This chunk is oversized** \u2014 it is a single hunk with no safe place to cut, and it may exceed one read. Expect to page."
|
|
@@ -11049,7 +11857,7 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11049
11857
|
"",
|
|
11050
11858
|
EXCLUSIONS
|
|
11051
11859
|
);
|
|
11052
|
-
const chunkPaths = (Array.isArray(
|
|
11860
|
+
const chunkPaths = (Array.isArray(chunk2.files) ? chunk2.files : []).map((f) => f?.path).filter((p) => typeof p === "string");
|
|
11053
11861
|
const pathRules = pathRulesFor(chunkPaths);
|
|
11054
11862
|
if (pathRules) parts.push("", pathRules);
|
|
11055
11863
|
if (rules && rules.trim()) {
|
|
@@ -11064,7 +11872,7 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11064
11872
|
if (!unreachable) {
|
|
11065
11873
|
parts.push(
|
|
11066
11874
|
"",
|
|
11067
|
-
`Then, on its own final line: \`Covered: chunk ${
|
|
11875
|
+
`Then, on its own final line: \`Covered: chunk ${chunk2.id} lines ${chunk2.startLine}-${chunk2.endLine}\``
|
|
11068
11876
|
);
|
|
11069
11877
|
}
|
|
11070
11878
|
return parts.join("\n");
|
|
@@ -11075,10 +11883,10 @@ function diffWindow(startLine, endLine) {
|
|
|
11075
11883
|
}
|
|
11076
11884
|
__name(diffWindow, "diffWindow");
|
|
11077
11885
|
function buildChunkLaunchPrompt(report, id, briefFile) {
|
|
11078
|
-
const { diffPath, chunk, total } = chunkFrom(report, id);
|
|
11079
|
-
const { offset, limit } = diffWindow(
|
|
11886
|
+
const { diffPath, chunk: chunk2, total } = chunkFrom(report, id);
|
|
11887
|
+
const { offset, limit } = diffWindow(chunk2.startLine, chunk2.endLine);
|
|
11080
11888
|
return [
|
|
11081
|
-
`You are review agent \`chunk ${
|
|
11889
|
+
`You are review agent \`chunk ${chunk2.id} of ${total}\` \u2014 the territory agent for lines ${chunk2.startLine}-${chunk2.endLine} of the diff.`,
|
|
11082
11890
|
"",
|
|
11083
11891
|
"**Your brief is a file. Read it first \u2014 it is the whole of your instructions,",
|
|
11084
11892
|
"and nothing in this message replaces it.**",
|
|
@@ -11269,7 +12077,7 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11269
12077
|
"agent-prompt: --role 0 needs a plan with `prNumber` and `ownerRepo` (the report `fetch-pr` writes). Issue fidelity has nothing to check against without a pull request."
|
|
11270
12078
|
);
|
|
11271
12079
|
}
|
|
11272
|
-
const ctx = opts.planPath ?
|
|
12080
|
+
const ctx = opts.planPath ? join12(dirname14(resolve13(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
|
|
11273
12081
|
parts.push(
|
|
11274
12082
|
"",
|
|
11275
12083
|
`**This PR:** #${pr} of \`${repo}\`. Use exactly that number and repo \u2014 a bare \`gh pr view\` falls back to the current branch's PR and would judge this diff against an unrelated issue.`
|
|
@@ -11291,7 +12099,7 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11291
12099
|
}
|
|
11292
12100
|
const base = report.mergeBaseSha;
|
|
11293
12101
|
const pr = report.prNumber;
|
|
11294
|
-
const buildTree = typeof wt === "string" && wt ?
|
|
12102
|
+
const buildTree = typeof wt === "string" && wt ? resolve13(wt) : pr === void 0 && opts.planPath ? "." : null;
|
|
11295
12103
|
if (buildTree && opts.planPath) {
|
|
11296
12104
|
const outName = pr !== void 0 ? `qwen-review-pr-${pr}-build-test.json` : "qwen-review-build-test.json";
|
|
11297
12105
|
parts.push(
|
|
@@ -11307,9 +12115,9 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11307
12115
|
// that an old global lacks it entirely, wedging Agent 7 between its
|
|
11308
12116
|
// mandate (no hand-run `npm run build`) and a command that does not exist.
|
|
11309
12117
|
`"\${QWEN_CODE_CLI:-qwen}" review build-test \\`,
|
|
11310
|
-
` --plan ${
|
|
11311
|
-
` --worktree ${
|
|
11312
|
-
` --out ${
|
|
12118
|
+
` --plan ${resolve13(opts.planPath)} \\`,
|
|
12119
|
+
` --worktree ${resolve13(buildTree)} \\`,
|
|
12120
|
+
` --out ${resolve13(dirname14(opts.planPath), outName)}`,
|
|
11313
12121
|
"```"
|
|
11314
12122
|
);
|
|
11315
12123
|
}
|
|
@@ -11319,10 +12127,10 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11319
12127
|
"**Then run the test-efficacy probe.** A green suite says the tests pass. It does not say they would have failed had the change been wrong, and those are different claims. Give this call `timeout: 600000` too \u2014 besides the revert probe it runs up to 8 single-statement deletion mutants and up to 6 per-hunk reverse-apply probes, each a suite run, and it budgets itself to finish inside that ceiling:",
|
|
11320
12128
|
"",
|
|
11321
12129
|
"```bash",
|
|
11322
|
-
`"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${
|
|
11323
|
-
` --worktree ${typeof wt === "string" ?
|
|
12130
|
+
`"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${resolve13(opts.planPath)} \\`,
|
|
12131
|
+
` --worktree ${typeof wt === "string" ? resolve13(wt) : "<worktree>"} \\`,
|
|
11324
12132
|
` --base ${base} \\`,
|
|
11325
|
-
` --out ${
|
|
12133
|
+
` --out ${resolve13(dirname14(opts.planPath), `qwen-review-pr-${pr}-efficacy.json`)}`,
|
|
11326
12134
|
"```",
|
|
11327
12135
|
"",
|
|
11328
12136
|
'Read its `findings[]`. `kind: "unreachable"` is a test the project\'s test command never collects \u2014 it did not run here and it does not run in CI. `kind: "inert"` is a test that **still passed with the change reverted**: it is green whether or not the feature exists, so it cannot catch a regression in it. `kind: "mutant-survived"` is a single safety statement the diff added (a `.clear()`, an `.abort(\u2026)`, a reset-to-empty) that was **deleted and every affected test stayed green** \u2014 no test in the diff fails when it is removed, which the whole-file revert cannot see when the file\'s other, tested behaviours mask it. `kind: "hunk-survived"` is one HUNK reverted on its own with every affected test still green \u2014 that specific change ships with nothing gating it. Report each of the four as a **Suggestion** with `Source: [test]`, saying plainly which behaviour has no test in this diff that would catch its removal. `harnessValidated` has THREE values and they are not two: `false` means the positive control failed and every would-be survivor was re-classed \u2014 say the harness could not be validated instead of implying clean coverage; `null` means the control never ran, so the run is neither validated nor refuted and any survivor below is unconfirmed by a control; only `true` licenses reading a survivor as a coverage gap. **`inconclusive` is not a finding** \u2014 for probes, mutants and hunks alike, reverting or mutating the source often breaks the test\'s own compile, and that is not the test catching anything. Entries counted in `mutants.skipped*` or `hunks.skipped*` never ran \u2014 not findings either, and `skippedForControl` in particular means the control stopped the run, NOT that the window ran out. `mutants.note`, when present, explains why no mutants ran at all. Note them and move on.'
|
|
@@ -11468,7 +12276,7 @@ function buildLaunch(report, planPath, spec, rules) {
|
|
|
11468
12276
|
}
|
|
11469
12277
|
__name(buildLaunch, "buildLaunch");
|
|
11470
12278
|
function findingsDigest(content, rules) {
|
|
11471
|
-
return
|
|
12279
|
+
return createHash3("sha256").update(JSON.stringify([content, rules ?? null])).digest("hex").slice(0, 12);
|
|
11472
12280
|
}
|
|
11473
12281
|
__name(findingsDigest, "findingsDigest");
|
|
11474
12282
|
function foldFindings(role, content, prompt) {
|
|
@@ -11674,7 +12482,7 @@ function runAgentPrompt(args) {
|
|
|
11674
12482
|
}
|
|
11675
12483
|
let report;
|
|
11676
12484
|
try {
|
|
11677
|
-
report = JSON.parse(
|
|
12485
|
+
report = JSON.parse(readFileSync17(args.plan, "utf8"));
|
|
11678
12486
|
} catch (err) {
|
|
11679
12487
|
throw new Error(
|
|
11680
12488
|
`agent-prompt: cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -11683,7 +12491,7 @@ function runAgentPrompt(args) {
|
|
|
11683
12491
|
let rules;
|
|
11684
12492
|
if (args.rules) {
|
|
11685
12493
|
try {
|
|
11686
|
-
rules =
|
|
12494
|
+
rules = readFileSync17(args.rules, "utf8");
|
|
11687
12495
|
} catch (err) {
|
|
11688
12496
|
throw new Error(
|
|
11689
12497
|
`agent-prompt: cannot read the rules ${args.rules}: ${err.message}. Omit --rules if this review has none; passing a path that does not resolve would silently review without the project rules it was told to enforce.`
|
|
@@ -11698,7 +12506,7 @@ function runAgentPrompt(args) {
|
|
|
11698
12506
|
if (hasFindings && args.role) {
|
|
11699
12507
|
const role = args.role;
|
|
11700
12508
|
try {
|
|
11701
|
-
findingsContent =
|
|
12509
|
+
findingsContent = readFileSync17(args.findings, "utf8");
|
|
11702
12510
|
} catch (err) {
|
|
11703
12511
|
throw new Error(
|
|
11704
12512
|
`agent-prompt: cannot read the findings ${args.findings}: ${err.message}. Pass a path that resolves \u2014 --findings is required for this role, so omitting it only fails one guard earlier. An early reverse-audit round with nothing confirmed passes an empty file (create it first).`
|
|
@@ -11807,18 +12615,18 @@ var agentPromptCommand = {
|
|
|
11807
12615
|
init_esbuild_shims();
|
|
11808
12616
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
11809
12617
|
import {
|
|
11810
|
-
existsSync as
|
|
11811
|
-
readFileSync as
|
|
11812
|
-
rmSync as
|
|
12618
|
+
existsSync as existsSync7,
|
|
12619
|
+
readFileSync as readFileSync19,
|
|
12620
|
+
rmSync as rmSync5,
|
|
11813
12621
|
statfsSync,
|
|
11814
|
-
writeFileSync as
|
|
12622
|
+
writeFileSync as writeFileSync17
|
|
11815
12623
|
} from "node:fs";
|
|
11816
|
-
import { join as
|
|
12624
|
+
import { join as join14, resolve as resolve14 } from "node:path";
|
|
11817
12625
|
|
|
11818
12626
|
// packages/cli/src/commands/review/lib/workspaces.ts
|
|
11819
12627
|
init_esbuild_shims();
|
|
11820
|
-
import { existsSync as
|
|
11821
|
-
import { join as
|
|
12628
|
+
import { existsSync as existsSync6, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "node:fs";
|
|
12629
|
+
import { join as join13 } from "node:path";
|
|
11822
12630
|
function isWorkspaceMember(filePath, workspaceGlobs) {
|
|
11823
12631
|
return workspaceDirFor(filePath, workspaceGlobs) !== null;
|
|
11824
12632
|
}
|
|
@@ -11856,7 +12664,7 @@ __name(hasUnmodeledWorkspaceGlob, "hasUnmodeledWorkspaceGlob");
|
|
|
11856
12664
|
function readWorkspaceGlobs(root) {
|
|
11857
12665
|
try {
|
|
11858
12666
|
const pkg = JSON.parse(
|
|
11859
|
-
|
|
12667
|
+
readFileSync18(join13(root, "package.json"), "utf8")
|
|
11860
12668
|
);
|
|
11861
12669
|
const ws = pkg.workspaces;
|
|
11862
12670
|
const globs = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
@@ -11869,7 +12677,7 @@ __name(readWorkspaceGlobs, "readWorkspaceGlobs");
|
|
|
11869
12677
|
function readRootPackage(root) {
|
|
11870
12678
|
let pkg;
|
|
11871
12679
|
try {
|
|
11872
|
-
pkg = JSON.parse(
|
|
12680
|
+
pkg = JSON.parse(readFileSync18(join13(root, "package.json"), "utf8"));
|
|
11873
12681
|
} catch {
|
|
11874
12682
|
return null;
|
|
11875
12683
|
}
|
|
@@ -11893,7 +12701,7 @@ function readWorkspacePackages(root) {
|
|
|
11893
12701
|
const base = g.slice(0, -2);
|
|
11894
12702
|
let entries;
|
|
11895
12703
|
try {
|
|
11896
|
-
entries = readdirSync4(
|
|
12704
|
+
entries = readdirSync4(join13(root, base), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11897
12705
|
} catch {
|
|
11898
12706
|
continue;
|
|
11899
12707
|
}
|
|
@@ -11905,11 +12713,11 @@ function readWorkspacePackages(root) {
|
|
|
11905
12713
|
const pkgs = [];
|
|
11906
12714
|
for (const dir of dirs) {
|
|
11907
12715
|
if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) continue;
|
|
11908
|
-
const manifest =
|
|
11909
|
-
if (!
|
|
12716
|
+
const manifest = join13(root, dir, "package.json");
|
|
12717
|
+
if (!existsSync6(manifest)) continue;
|
|
11910
12718
|
let pkg;
|
|
11911
12719
|
try {
|
|
11912
|
-
pkg = JSON.parse(
|
|
12720
|
+
pkg = JSON.parse(readFileSync18(manifest, "utf8"));
|
|
11913
12721
|
} catch {
|
|
11914
12722
|
continue;
|
|
11915
12723
|
}
|
|
@@ -12082,7 +12890,7 @@ __name(unresolvedWorkspaceDeps, "unresolvedWorkspaceDeps");
|
|
|
12082
12890
|
function changedFilesFrom(planPath) {
|
|
12083
12891
|
let parsed;
|
|
12084
12892
|
try {
|
|
12085
|
-
parsed = JSON.parse(
|
|
12893
|
+
parsed = JSON.parse(readFileSync19(planPath, "utf8"));
|
|
12086
12894
|
} catch (err) {
|
|
12087
12895
|
throw new Error(
|
|
12088
12896
|
`build-test: cannot read the plan ${planPath}: ${err.message}`
|
|
@@ -12099,7 +12907,7 @@ function changedFilesFrom(planPath) {
|
|
|
12099
12907
|
}
|
|
12100
12908
|
__name(changedFilesFrom, "changedFilesFrom");
|
|
12101
12909
|
function runBuildTest(args) {
|
|
12102
|
-
const root =
|
|
12910
|
+
const root = resolve14(args.worktree);
|
|
12103
12911
|
const perCommandMs = args.timeout * 1e3;
|
|
12104
12912
|
const exec = args.exec ?? run;
|
|
12105
12913
|
const changed = changedFilesFrom(args.plan);
|
|
@@ -12165,15 +12973,15 @@ function runBuildTest(args) {
|
|
|
12165
12973
|
timedOut: [],
|
|
12166
12974
|
note: ""
|
|
12167
12975
|
};
|
|
12168
|
-
const npmLock =
|
|
12169
|
-
const installComplete = /* @__PURE__ */ __name(() =>
|
|
12170
|
-
if (args.install && !npmLock && !
|
|
12976
|
+
const npmLock = existsSync7(join14(root, "package-lock.json"));
|
|
12977
|
+
const installComplete = /* @__PURE__ */ __name(() => existsSync7(join14(root, "node_modules", ".package-lock.json")), "installComplete");
|
|
12978
|
+
if (args.install && !npmLock && !existsSync7(join14(root, "node_modules"))) {
|
|
12171
12979
|
const altLock = [
|
|
12172
12980
|
["yarn.lock", "yarn install --frozen-lockfile"],
|
|
12173
12981
|
["pnpm-lock.yaml", "pnpm install --frozen-lockfile"],
|
|
12174
12982
|
["bun.lockb", "bun install --frozen-lockfile"],
|
|
12175
12983
|
["bun.lock", "bun install --frozen-lockfile"]
|
|
12176
|
-
].find(([f]) =>
|
|
12984
|
+
].find(([f]) => existsSync7(join14(root, f)));
|
|
12177
12985
|
return unsupportedReport(
|
|
12178
12986
|
altLock ? `This is a ${altLock[0]} repo with no installed \`node_modules\`, so \`npm ci\` cannot install it. Run \`${altLock[1]}\` first, then fall back to the build/test precedence in your brief, each command with a deadline it can meet.` : "There is no lockfile and no `node_modules` here, so nothing can be installed deterministically. Install dependencies first, then fall back to the build/test precedence in your brief."
|
|
12179
12987
|
);
|
|
@@ -12191,7 +12999,7 @@ function runBuildTest(args) {
|
|
|
12191
12999
|
if (install.timedOut) results.timedOut.push(install.command);
|
|
12192
13000
|
if (install.timedOut) {
|
|
12193
13001
|
try {
|
|
12194
|
-
|
|
13002
|
+
rmSync5(join14(root, "node_modules"), {
|
|
12195
13003
|
recursive: true,
|
|
12196
13004
|
force: true,
|
|
12197
13005
|
maxRetries: 3
|
|
@@ -12314,7 +13122,7 @@ var buildTestCommand = {
|
|
|
12314
13122
|
try {
|
|
12315
13123
|
const report = runBuildTest(args);
|
|
12316
13124
|
if (args.out) {
|
|
12317
|
-
|
|
13125
|
+
writeFileSync17(args.out, JSON.stringify(report, null, 2));
|
|
12318
13126
|
}
|
|
12319
13127
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12320
13128
|
} catch (err) {
|
|
@@ -12328,25 +13136,25 @@ var buildTestCommand = {
|
|
|
12328
13136
|
init_esbuild_shims();
|
|
12329
13137
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12330
13138
|
import {
|
|
12331
|
-
existsSync as
|
|
12332
|
-
mkdirSync as
|
|
12333
|
-
readFileSync as
|
|
12334
|
-
rmSync as
|
|
12335
|
-
statSync as
|
|
12336
|
-
writeFileSync as
|
|
13139
|
+
existsSync as existsSync8,
|
|
13140
|
+
mkdirSync as mkdirSync17,
|
|
13141
|
+
readFileSync as readFileSync20,
|
|
13142
|
+
rmSync as rmSync7,
|
|
13143
|
+
statSync as statSync6,
|
|
13144
|
+
writeFileSync as writeFileSync18
|
|
12337
13145
|
} from "node:fs";
|
|
12338
|
-
import { dirname as
|
|
13146
|
+
import { dirname as dirname15, join as join15, resolve as resolve15 } from "node:path";
|
|
12339
13147
|
|
|
12340
13148
|
// packages/cli/src/commands/review/lib/worktree.ts
|
|
12341
13149
|
init_esbuild_shims();
|
|
12342
13150
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12343
|
-
import { rmSync as
|
|
13151
|
+
import { rmSync as rmSync6 } from "node:fs";
|
|
12344
13152
|
function discardWorktree(cwd, tree) {
|
|
12345
13153
|
const sweep = spawnSync3("git", ["worktree", "remove", "--force", tree], {
|
|
12346
13154
|
cwd,
|
|
12347
13155
|
encoding: "utf8"
|
|
12348
13156
|
});
|
|
12349
|
-
|
|
13157
|
+
rmSync6(tree, { recursive: true, force: true });
|
|
12350
13158
|
return sweep;
|
|
12351
13159
|
}
|
|
12352
13160
|
__name(discardWorktree, "discardWorktree");
|
|
@@ -12382,7 +13190,7 @@ function runBaseTree(args) {
|
|
|
12382
13190
|
}), "unavailable");
|
|
12383
13191
|
let plan;
|
|
12384
13192
|
try {
|
|
12385
|
-
plan = JSON.parse(
|
|
13193
|
+
plan = JSON.parse(readFileSync20(args.plan, "utf8"));
|
|
12386
13194
|
} catch (err) {
|
|
12387
13195
|
return unavailable(
|
|
12388
13196
|
`cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -12399,15 +13207,15 @@ function runBaseTree(args) {
|
|
|
12399
13207
|
"the base branch could not be fetched, so mergeBaseSha may be stale; an A/B against it would attribute the base branch's own commits to this PR"
|
|
12400
13208
|
);
|
|
12401
13209
|
}
|
|
12402
|
-
const worktree =
|
|
12403
|
-
if (!
|
|
13210
|
+
const worktree = resolve15(args.worktree);
|
|
13211
|
+
if (!existsSync8(worktree)) {
|
|
12404
13212
|
return unavailable(`the review worktree ${worktree} does not exist`);
|
|
12405
13213
|
}
|
|
12406
13214
|
const tree = baseWorktreePath(worktree);
|
|
12407
|
-
const marker = /* @__PURE__ */ __name(() =>
|
|
12408
|
-
const failedMarker = /* @__PURE__ */ __name(() =>
|
|
13215
|
+
const marker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-ok"), "marker");
|
|
13216
|
+
const failedMarker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-failed"), "failedMarker");
|
|
12409
13217
|
try {
|
|
12410
|
-
if (
|
|
13218
|
+
if (existsSync8(tree) && readFileSync20(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
|
|
12411
13219
|
return {
|
|
12412
13220
|
available: true,
|
|
12413
13221
|
path: tree,
|
|
@@ -12419,7 +13227,7 @@ function runBaseTree(args) {
|
|
|
12419
13227
|
} catch {
|
|
12420
13228
|
}
|
|
12421
13229
|
try {
|
|
12422
|
-
if (
|
|
13230
|
+
if (existsSync8(tree) && readFileSync20(failedMarker(), "utf8").trim() === baseSha) {
|
|
12423
13231
|
return {
|
|
12424
13232
|
available: false,
|
|
12425
13233
|
path: tree,
|
|
@@ -12432,14 +13240,14 @@ function runBaseTree(args) {
|
|
|
12432
13240
|
}
|
|
12433
13241
|
const lock = `${tree}.lock`;
|
|
12434
13242
|
try {
|
|
12435
|
-
const age = Date.now() -
|
|
13243
|
+
const age = Date.now() - statSync6(lock).mtimeMs;
|
|
12436
13244
|
if (age > 30 * 60 * 1e3) {
|
|
12437
|
-
|
|
13245
|
+
rmSync7(lock, { recursive: true, force: true });
|
|
12438
13246
|
}
|
|
12439
13247
|
} catch {
|
|
12440
13248
|
}
|
|
12441
13249
|
try {
|
|
12442
|
-
|
|
13250
|
+
mkdirSync17(lock);
|
|
12443
13251
|
} catch (err) {
|
|
12444
13252
|
if (err.code !== "EEXIST") {
|
|
12445
13253
|
return unavailable(
|
|
@@ -12453,7 +13261,7 @@ function runBaseTree(args) {
|
|
|
12453
13261
|
try {
|
|
12454
13262
|
return buildBaseTree(baseSha);
|
|
12455
13263
|
} finally {
|
|
12456
|
-
|
|
13264
|
+
rmSync7(lock, { recursive: true, force: true });
|
|
12457
13265
|
}
|
|
12458
13266
|
function buildBaseTree(baseSha2) {
|
|
12459
13267
|
let sweep;
|
|
@@ -12477,7 +13285,7 @@ function runBaseTree(args) {
|
|
|
12477
13285
|
});
|
|
12478
13286
|
if (!build.ok || build.toolchain !== "npm" || build.build.length === 0) {
|
|
12479
13287
|
try {
|
|
12480
|
-
|
|
13288
|
+
writeFileSync18(failedMarker(), `${baseSha2}
|
|
12481
13289
|
`);
|
|
12482
13290
|
} catch {
|
|
12483
13291
|
}
|
|
@@ -12490,7 +13298,7 @@ function runBaseTree(args) {
|
|
|
12490
13298
|
};
|
|
12491
13299
|
}
|
|
12492
13300
|
try {
|
|
12493
|
-
|
|
13301
|
+
writeFileSync18(marker(), `${baseSha2}
|
|
12494
13302
|
`);
|
|
12495
13303
|
} catch {
|
|
12496
13304
|
}
|
|
@@ -12530,8 +13338,8 @@ var baseTreeCommand = {
|
|
|
12530
13338
|
try {
|
|
12531
13339
|
const report = runBaseTree(args);
|
|
12532
13340
|
if (args.out) {
|
|
12533
|
-
|
|
12534
|
-
|
|
13341
|
+
mkdirSync17(dirname15(resolve15(args.out)), { recursive: true });
|
|
13342
|
+
writeFileSync18(resolve15(args.out), JSON.stringify(report, null, 2));
|
|
12535
13343
|
}
|
|
12536
13344
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12537
13345
|
writeStderrLine(`base-tree: ${report.note}`);
|
|
@@ -12545,8 +13353,8 @@ var baseTreeCommand = {
|
|
|
12545
13353
|
// packages/cli/src/commands/review/test-delta.ts
|
|
12546
13354
|
init_esbuild_shims();
|
|
12547
13355
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12548
|
-
import { existsSync as
|
|
12549
|
-
import { dirname as
|
|
13356
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
|
|
13357
|
+
import { dirname as dirname16, resolve as resolve16 } from "node:path";
|
|
12550
13358
|
var ANSI_SGR_RE2 = /\x1b\[[0-9;]*m/g;
|
|
12551
13359
|
var RERUNNABLE_COMMAND_RE = /^npm test(?: --workspace="[\w@./-]+")?$/;
|
|
12552
13360
|
var TRIM_MARKER_RE = /\.\.\. \[\d+ characters omitted/;
|
|
@@ -12609,7 +13417,7 @@ var TOTAL_BUDGET_MS = 54e4;
|
|
|
12609
13417
|
var DEFAULT_TIMEOUT_S = 300;
|
|
12610
13418
|
function runTestDelta(args) {
|
|
12611
13419
|
const exec = args.exec ?? run2;
|
|
12612
|
-
const baseline =
|
|
13420
|
+
const baseline = resolve16(args.baseline);
|
|
12613
13421
|
const empty = /* @__PURE__ */ __name((note) => ({
|
|
12614
13422
|
entries: [],
|
|
12615
13423
|
netNew: [],
|
|
@@ -12619,13 +13427,13 @@ function runTestDelta(args) {
|
|
|
12619
13427
|
}), "empty");
|
|
12620
13428
|
let report;
|
|
12621
13429
|
try {
|
|
12622
|
-
report = JSON.parse(
|
|
13430
|
+
report = JSON.parse(readFileSync21(args.report, "utf8"));
|
|
12623
13431
|
} catch (err) {
|
|
12624
13432
|
return empty(
|
|
12625
13433
|
`cannot read the build-test report ${args.report}: ${err.message}`
|
|
12626
13434
|
);
|
|
12627
13435
|
}
|
|
12628
|
-
if (!
|
|
13436
|
+
if (!existsSync9(baseline)) {
|
|
12629
13437
|
return empty(
|
|
12630
13438
|
`the base tree ${baseline} does not exist \u2014 run \`qwen review base-tree\` first`
|
|
12631
13439
|
);
|
|
@@ -12730,50 +13538,737 @@ function runTestDelta(args) {
|
|
|
12730
13538
|
);
|
|
12731
13539
|
}
|
|
12732
13540
|
return {
|
|
12733
|
-
entries,
|
|
12734
|
-
netNew,
|
|
12735
|
-
shared,
|
|
12736
|
-
skippedForBudget,
|
|
12737
|
-
note: parts.join(". ") || "nothing to report"
|
|
13541
|
+
entries,
|
|
13542
|
+
netNew,
|
|
13543
|
+
shared,
|
|
13544
|
+
skippedForBudget,
|
|
13545
|
+
note: parts.join(". ") || "nothing to report"
|
|
13546
|
+
};
|
|
13547
|
+
}
|
|
13548
|
+
__name(runTestDelta, "runTestDelta");
|
|
13549
|
+
var testDeltaCommand = {
|
|
13550
|
+
command: "test-delta",
|
|
13551
|
+
describe: "Rerun the PR side's failed test commands on the base tree and report which failing files are the PR's own (net-new) vs pre-existing (shared)",
|
|
13552
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("report", {
|
|
13553
|
+
type: "string",
|
|
13554
|
+
demandOption: true,
|
|
13555
|
+
describe: "Agent 7's build-test report (its failed commands and outputs)"
|
|
13556
|
+
}).option("baseline", {
|
|
13557
|
+
type: "string",
|
|
13558
|
+
demandOption: true,
|
|
13559
|
+
describe: "The BUILT base tree from `qwen review base-tree`"
|
|
13560
|
+
}).option("pr-worktree", {
|
|
13561
|
+
type: "string",
|
|
13562
|
+
describe: "The PR worktree the report's failures were produced in \u2014 its root is stripped so both sides compare as repo-relative paths"
|
|
13563
|
+
}).option("out", { type: "string", describe: "Write the JSON report here" }).option("timeout", {
|
|
13564
|
+
type: "number",
|
|
13565
|
+
default: 300,
|
|
13566
|
+
describe: "Per-command deadline in seconds, as build-test"
|
|
13567
|
+
}), "builder"),
|
|
13568
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
13569
|
+
const args = argv;
|
|
13570
|
+
const report = runTestDelta(args);
|
|
13571
|
+
if (args.out) {
|
|
13572
|
+
mkdirSync18(dirname16(resolve16(args.out)), { recursive: true });
|
|
13573
|
+
writeFileSync19(resolve16(args.out), JSON.stringify(report, null, 2));
|
|
13574
|
+
}
|
|
13575
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
13576
|
+
writeStderrLine(`test-delta: ${report.note}`);
|
|
13577
|
+
}, "handler")
|
|
13578
|
+
};
|
|
13579
|
+
|
|
13580
|
+
// packages/cli/src/commands/review/drive.ts
|
|
13581
|
+
init_esbuild_shims();
|
|
13582
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
13583
|
+
import {
|
|
13584
|
+
mkdirSync as mkdirSync19,
|
|
13585
|
+
writeFileSync as writeFileSync20,
|
|
13586
|
+
readFileSync as readFileSync22,
|
|
13587
|
+
existsSync as existsSync10,
|
|
13588
|
+
rmSync as rmSync8,
|
|
13589
|
+
statSync as statSync7
|
|
13590
|
+
} from "node:fs";
|
|
13591
|
+
import { dirname as dirname17, join as join16, resolve as resolve17 } from "node:path";
|
|
13592
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
13593
|
+
var SERVER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
13594
|
+
function shellQuote(v) {
|
|
13595
|
+
return `'${v.split("'").join(`'\\''`)}'`;
|
|
13596
|
+
}
|
|
13597
|
+
__name(shellQuote, "shellQuote");
|
|
13598
|
+
var CAPTURE_MAX = 2e5;
|
|
13599
|
+
var LOG_MAX_BYTES = 8 * 1024 * 1024;
|
|
13600
|
+
var POLL_MS = 250;
|
|
13601
|
+
function logBytes(p) {
|
|
13602
|
+
try {
|
|
13603
|
+
return statSync7(p).size;
|
|
13604
|
+
} catch {
|
|
13605
|
+
return 0;
|
|
13606
|
+
}
|
|
13607
|
+
}
|
|
13608
|
+
__name(logBytes, "logBytes");
|
|
13609
|
+
function waitMs(ms) {
|
|
13610
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
13611
|
+
}
|
|
13612
|
+
__name(waitMs, "waitMs");
|
|
13613
|
+
function run3(cmd, args, input) {
|
|
13614
|
+
const r = spawnSync6(cmd, args, {
|
|
13615
|
+
encoding: "utf8",
|
|
13616
|
+
input,
|
|
13617
|
+
timeout: 3e4,
|
|
13618
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
13619
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
13620
|
+
});
|
|
13621
|
+
return {
|
|
13622
|
+
status: r.status ?? null,
|
|
13623
|
+
stdout: r.stdout ?? "",
|
|
13624
|
+
stderr: r.stderr ?? ""
|
|
13625
|
+
};
|
|
13626
|
+
}
|
|
13627
|
+
__name(run3, "run");
|
|
13628
|
+
var DRIVE_SENTINEL = "__QWEN_REVIEW_DRIVE_DONE__";
|
|
13629
|
+
function wrapScript(script, sentinelPath, sentinel = DRIVE_SENTINEL) {
|
|
13630
|
+
return `trap '__qwen_rc=$?; echo "${sentinel} rc=\${__qwen_rc}" > ${shellQuote(sentinelPath)}' EXIT
|
|
13631
|
+
set +e
|
|
13632
|
+
${script}
|
|
13633
|
+
`;
|
|
13634
|
+
}
|
|
13635
|
+
__name(wrapScript, "wrapScript");
|
|
13636
|
+
function sentinelExitCode(capture, sentinel = DRIVE_SENTINEL) {
|
|
13637
|
+
const re = new RegExp(`${sentinel} rc=(\\d+)`, "g");
|
|
13638
|
+
let last = null;
|
|
13639
|
+
for (let m = re.exec(capture); m; m = re.exec(capture)) last = m;
|
|
13640
|
+
return last ? Number(last[1]) : null;
|
|
13641
|
+
}
|
|
13642
|
+
__name(sentinelExitCode, "sentinelExitCode");
|
|
13643
|
+
function trimCapture(s) {
|
|
13644
|
+
if (s.length <= CAPTURE_MAX) return { text: s, truncated: false };
|
|
13645
|
+
return {
|
|
13646
|
+
text: `... [${s.length - CAPTURE_MAX} characters omitted from the head] ...
|
|
13647
|
+
${s.slice(-CAPTURE_MAX)}`,
|
|
13648
|
+
truncated: true
|
|
13649
|
+
};
|
|
13650
|
+
}
|
|
13651
|
+
__name(trimCapture, "trimCapture");
|
|
13652
|
+
function runDrive(args) {
|
|
13653
|
+
const exec = args.exec ?? run3;
|
|
13654
|
+
const server = args.server;
|
|
13655
|
+
if (!SERVER_NAME_RE.test(server)) {
|
|
13656
|
+
return {
|
|
13657
|
+
outcome: "unavailable",
|
|
13658
|
+
observed: false,
|
|
13659
|
+
exitCode: null,
|
|
13660
|
+
readyAfterMs: null,
|
|
13661
|
+
droveForMs: 0,
|
|
13662
|
+
output: "",
|
|
13663
|
+
truncated: false,
|
|
13664
|
+
killedStale: false,
|
|
13665
|
+
note: `--server ${JSON.stringify(server)} is not a name this command will own: it becomes both a path under the temp dir and a word in the shell line tmux runs, so it is restricted to letters, digits, dot, dash and underscore (max 64). Nothing was started.`
|
|
13666
|
+
};
|
|
13667
|
+
}
|
|
13668
|
+
const tmux = /* @__PURE__ */ __name((...a) => exec("tmux", ["-L", server, ...a]), "tmux");
|
|
13669
|
+
if (exec("tmux", ["-V"]).status !== 0) {
|
|
13670
|
+
return {
|
|
13671
|
+
outcome: "unavailable",
|
|
13672
|
+
observed: false,
|
|
13673
|
+
exitCode: null,
|
|
13674
|
+
readyAfterMs: null,
|
|
13675
|
+
droveForMs: 0,
|
|
13676
|
+
output: "",
|
|
13677
|
+
truncated: false,
|
|
13678
|
+
killedStale: false,
|
|
13679
|
+
note: "tmux is not available, so nothing could be driven \u2014 an environment gap, not a finding about the diff"
|
|
13680
|
+
};
|
|
13681
|
+
}
|
|
13682
|
+
const killedStale = tmux("kill-server").status === 0;
|
|
13683
|
+
const started = Date.now();
|
|
13684
|
+
let readyAfterMs = null;
|
|
13685
|
+
if (args.ready) {
|
|
13686
|
+
const deadline = started + args.readyTimeout * 1e3;
|
|
13687
|
+
for (; ; ) {
|
|
13688
|
+
if (exec("bash", ["-lc", args.ready]).status === 0) {
|
|
13689
|
+
readyAfterMs = Date.now() - started;
|
|
13690
|
+
break;
|
|
13691
|
+
}
|
|
13692
|
+
if (Date.now() >= deadline) {
|
|
13693
|
+
tmux("kill-server");
|
|
13694
|
+
return {
|
|
13695
|
+
outcome: "not-ready",
|
|
13696
|
+
observed: false,
|
|
13697
|
+
exitCode: null,
|
|
13698
|
+
readyAfterMs: null,
|
|
13699
|
+
droveForMs: 0,
|
|
13700
|
+
output: "",
|
|
13701
|
+
truncated: false,
|
|
13702
|
+
killedStale,
|
|
13703
|
+
note: `readiness probe never succeeded within ${args.readyTimeout}s (\`${args.ready}\`) \u2014 nothing was driven, so nothing here is evidence about the diff. A slower machine needs a larger --ready-timeout; a probe that can never pass needs a different probe.`
|
|
13704
|
+
};
|
|
13705
|
+
}
|
|
13706
|
+
waitMs(POLL_MS);
|
|
13707
|
+
}
|
|
13708
|
+
}
|
|
13709
|
+
const dir = join16(tmpdir2(), `qwen-review-drive-${server}`);
|
|
13710
|
+
mkdirSync19(dir, { recursive: true });
|
|
13711
|
+
const scriptPath = join16(dir, "drive.sh");
|
|
13712
|
+
const logPath = args.logPath ?? join16(dir, "drive.log");
|
|
13713
|
+
const sentinelPath = join16(dir, "drive.rc");
|
|
13714
|
+
rmSync8(sentinelPath, { force: true });
|
|
13715
|
+
writeFileSync20(scriptPath, wrapScript(args.script, sentinelPath), "utf8");
|
|
13716
|
+
const droveFrom = Date.now();
|
|
13717
|
+
let output = "";
|
|
13718
|
+
let exitCode = null;
|
|
13719
|
+
let outcome = "timed-out";
|
|
13720
|
+
try {
|
|
13721
|
+
const create = tmux(
|
|
13722
|
+
"new-session",
|
|
13723
|
+
"-d",
|
|
13724
|
+
"-c",
|
|
13725
|
+
args.cwd,
|
|
13726
|
+
"bash",
|
|
13727
|
+
"-lc",
|
|
13728
|
+
`bash ${shellQuote(scriptPath)} > ${shellQuote(logPath)} 2>&1`
|
|
13729
|
+
);
|
|
13730
|
+
if (create.status !== 0) {
|
|
13731
|
+
return {
|
|
13732
|
+
outcome: "unavailable",
|
|
13733
|
+
observed: false,
|
|
13734
|
+
exitCode: null,
|
|
13735
|
+
readyAfterMs,
|
|
13736
|
+
droveForMs: 0,
|
|
13737
|
+
output: "",
|
|
13738
|
+
truncated: false,
|
|
13739
|
+
killedStale,
|
|
13740
|
+
note: `tmux could not start a session: ${create.stderr.trim() || "no error text"} \u2014 an environment gap, not a finding`
|
|
13741
|
+
};
|
|
13742
|
+
}
|
|
13743
|
+
const deadline = droveFrom + args.timeout * 1e3;
|
|
13744
|
+
for (; ; ) {
|
|
13745
|
+
output = existsSync10(logPath) ? readFileSync22(logPath, "utf8") : "";
|
|
13746
|
+
exitCode = existsSync10(sentinelPath) ? sentinelExitCode(readFileSync22(sentinelPath, "utf8")) : null;
|
|
13747
|
+
if (exitCode !== null) {
|
|
13748
|
+
outcome = "completed";
|
|
13749
|
+
break;
|
|
13750
|
+
}
|
|
13751
|
+
if (logBytes(logPath) > LOG_MAX_BYTES) {
|
|
13752
|
+
outcome = "overflowed";
|
|
13753
|
+
break;
|
|
13754
|
+
}
|
|
13755
|
+
if (Date.now() >= deadline) break;
|
|
13756
|
+
waitMs(POLL_MS);
|
|
13757
|
+
}
|
|
13758
|
+
} finally {
|
|
13759
|
+
tmux("kill-server");
|
|
13760
|
+
if (!args.logPath) rmSync8(dir, { recursive: true, force: true });
|
|
13761
|
+
}
|
|
13762
|
+
const { text, truncated } = trimCapture(output);
|
|
13763
|
+
const droveForMs = Date.now() - droveFrom;
|
|
13764
|
+
const note = outcome === "overflowed" ? `the drive wrote more than ${Math.round(LOG_MAX_BYTES / 1024 / 1024)} MiB and was stopped \u2014 no exit code is reported because it never gave one, and a run this command had to stop is not evidence about the diff either way. Quieten the script, or have it manage its own output file.` : outcome === "completed" ? `drove for ${Math.round(droveForMs / 1e3)}s and reached its sentinel with exit ${exitCode}${readyAfterMs === null ? "" : ` (ready after ${Math.round(readyAfterMs / 1e3)}s)`}${truncated ? "; the capture was trimmed at the head, so early output is missing" : ""}` : `the drive did not finish within ${args.timeout}s \u2014 the capture below is PARTIAL, and a partial capture is not evidence that the run produced nothing. Raise --timeout, or give the script a smaller job.`;
|
|
13765
|
+
return {
|
|
13766
|
+
outcome,
|
|
13767
|
+
observed: outcome === "completed",
|
|
13768
|
+
exitCode,
|
|
13769
|
+
readyAfterMs,
|
|
13770
|
+
droveForMs,
|
|
13771
|
+
output: text,
|
|
13772
|
+
truncated,
|
|
13773
|
+
killedStale,
|
|
13774
|
+
note
|
|
13775
|
+
};
|
|
13776
|
+
}
|
|
13777
|
+
__name(runDrive, "runDrive");
|
|
13778
|
+
var driveCommand = {
|
|
13779
|
+
command: "drive",
|
|
13780
|
+
describe: "Start something, wait until it is really up, drive it, and capture what it did \u2014 readiness polled rather than slept on, completion proven by a sentinel, cleanup guaranteed",
|
|
13781
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("script", {
|
|
13782
|
+
type: "string",
|
|
13783
|
+
demandOption: true,
|
|
13784
|
+
describe: "Shell script to drive (its exit code is captured)"
|
|
13785
|
+
}).option("cwd", {
|
|
13786
|
+
type: "string",
|
|
13787
|
+
demandOption: true,
|
|
13788
|
+
describe: "Working directory \u2014 usually the PR or base worktree"
|
|
13789
|
+
}).option("ready", {
|
|
13790
|
+
type: "string",
|
|
13791
|
+
describe: "Command polled until it exits 0 before driving (omit only when nothing needs to come up first)"
|
|
13792
|
+
}).option("ready-timeout", {
|
|
13793
|
+
type: "number",
|
|
13794
|
+
default: 60,
|
|
13795
|
+
describe: "Seconds to wait for readiness"
|
|
13796
|
+
}).option("timeout", {
|
|
13797
|
+
type: "number",
|
|
13798
|
+
default: 300,
|
|
13799
|
+
describe: "Seconds to wait for the script to reach its sentinel"
|
|
13800
|
+
}).option("server", {
|
|
13801
|
+
type: "string",
|
|
13802
|
+
default: `qr-${process.pid}`,
|
|
13803
|
+
describe: "tmux server name \u2014 namespaced so runs cannot capture each other"
|
|
13804
|
+
}).option("out", {
|
|
13805
|
+
type: "string",
|
|
13806
|
+
describe: "Write the JSON report here"
|
|
13807
|
+
}), "builder"),
|
|
13808
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
13809
|
+
try {
|
|
13810
|
+
const args = argv;
|
|
13811
|
+
const report = runDrive(args);
|
|
13812
|
+
if (args.out) {
|
|
13813
|
+
mkdirSync19(dirname17(resolve17(args.out)), { recursive: true });
|
|
13814
|
+
writeFileSync20(resolve17(args.out), JSON.stringify(report, null, 2));
|
|
13815
|
+
}
|
|
13816
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
13817
|
+
writeStderrLine(`drive: ${report.note}`);
|
|
13818
|
+
if (!report.observed) process.exitCode = 1;
|
|
13819
|
+
} catch (err) {
|
|
13820
|
+
writeStderrLine(err.message);
|
|
13821
|
+
process.exitCode = 1;
|
|
13822
|
+
}
|
|
13823
|
+
}, "handler")
|
|
13824
|
+
};
|
|
13825
|
+
|
|
13826
|
+
// packages/cli/src/commands/review/mock-provider.ts
|
|
13827
|
+
init_esbuild_shims();
|
|
13828
|
+
import {
|
|
13829
|
+
createServer
|
|
13830
|
+
} from "node:http";
|
|
13831
|
+
import { appendFileSync, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
|
|
13832
|
+
import { dirname as dirname18, resolve as resolve18 } from "node:path";
|
|
13833
|
+
import { pathToFileURL } from "node:url";
|
|
13834
|
+
function messagesText(body) {
|
|
13835
|
+
const msgs = Array.isArray(body?.["messages"]) ? body["messages"] : [];
|
|
13836
|
+
const one = /* @__PURE__ */ __name((m) => {
|
|
13837
|
+
const c = m?.content;
|
|
13838
|
+
if (typeof c === "string") return c;
|
|
13839
|
+
if (Array.isArray(c))
|
|
13840
|
+
return c.map((p) => String(p?.text ?? "")).join("");
|
|
13841
|
+
return "";
|
|
13842
|
+
}, "one");
|
|
13843
|
+
return msgs.map(one).join("\n");
|
|
13844
|
+
}
|
|
13845
|
+
__name(messagesText, "messagesText");
|
|
13846
|
+
var enc = /* @__PURE__ */ __name((o) => `data: ${JSON.stringify(o)}
|
|
13847
|
+
|
|
13848
|
+
`, "enc");
|
|
13849
|
+
var BODY_MAX_BYTES = 4 * 1024 * 1024;
|
|
13850
|
+
var RECORD_TEXT_MAX = 8 * 1024;
|
|
13851
|
+
function replyProblem(r) {
|
|
13852
|
+
if (r === null || typeof r !== "object")
|
|
13853
|
+
return `responder returned ${r === void 0 ? "undefined" : JSON.stringify(r)}; it must return {text} | {tool, args?} | {status, body?}`;
|
|
13854
|
+
const o = r;
|
|
13855
|
+
if ("status" in o) {
|
|
13856
|
+
const st = o["status"];
|
|
13857
|
+
if (!(typeof st === "number" && Number.isInteger(st) && st >= 100 && st <= 599))
|
|
13858
|
+
return `responder returned a non-HTTP status ${JSON.stringify(st)}`;
|
|
13859
|
+
try {
|
|
13860
|
+
JSON.stringify(o["body"] ?? {});
|
|
13861
|
+
} catch {
|
|
13862
|
+
return "responder returned a status body that cannot be serialised (a circular reference?)";
|
|
13863
|
+
}
|
|
13864
|
+
return null;
|
|
13865
|
+
}
|
|
13866
|
+
if ("tool" in o) {
|
|
13867
|
+
if (typeof o["tool"] !== "string" || o["tool"] === "")
|
|
13868
|
+
return `responder returned an empty or non-string tool name ${JSON.stringify(o["tool"])}`;
|
|
13869
|
+
try {
|
|
13870
|
+
JSON.stringify(o["args"] ?? {});
|
|
13871
|
+
} catch {
|
|
13872
|
+
return "responder returned tool args that cannot be serialised (a circular reference?)";
|
|
13873
|
+
}
|
|
13874
|
+
return null;
|
|
13875
|
+
}
|
|
13876
|
+
if ("text" in o)
|
|
13877
|
+
return typeof o["text"] === "string" ? null : `responder returned a non-string text ${JSON.stringify(o["text"])}`;
|
|
13878
|
+
return `responder returned an object with none of text/tool/status: ${JSON.stringify(o).slice(0, 80)}`;
|
|
13879
|
+
}
|
|
13880
|
+
__name(replyProblem, "replyProblem");
|
|
13881
|
+
function forRecord(v) {
|
|
13882
|
+
return v.length <= RECORD_TEXT_MAX ? v : `${v.slice(0, RECORD_TEXT_MAX)}\u2026[${v.length - RECORD_TEXT_MAX} more characters]`;
|
|
13883
|
+
}
|
|
13884
|
+
__name(forRecord, "forRecord");
|
|
13885
|
+
function forRecordReq(r) {
|
|
13886
|
+
return {
|
|
13887
|
+
method: r.method,
|
|
13888
|
+
path: r.path,
|
|
13889
|
+
wire: r.wire,
|
|
13890
|
+
n: r.n,
|
|
13891
|
+
stream: r.stream,
|
|
13892
|
+
text: forRecord(r.text),
|
|
13893
|
+
bodyKeys: r.body ? Object.keys(r.body) : []
|
|
13894
|
+
};
|
|
13895
|
+
}
|
|
13896
|
+
__name(forRecordReq, "forRecordReq");
|
|
13897
|
+
function chunk(delta, finish = null) {
|
|
13898
|
+
return {
|
|
13899
|
+
id: "chatcmpl-qwen-review-mock",
|
|
13900
|
+
object: "chat.completion.chunk",
|
|
13901
|
+
created: Math.floor(Date.now() / 1e3),
|
|
13902
|
+
model: "qwen-review-mock",
|
|
13903
|
+
choices: [{ index: 0, delta, finish_reason: finish }]
|
|
13904
|
+
};
|
|
13905
|
+
}
|
|
13906
|
+
__name(chunk, "chunk");
|
|
13907
|
+
function chunksFor(reply) {
|
|
13908
|
+
const out = [
|
|
13909
|
+
chunk({ role: "assistant", content: "" })
|
|
13910
|
+
];
|
|
13911
|
+
if ("tool" in reply) {
|
|
13912
|
+
out.push(
|
|
13913
|
+
chunk({
|
|
13914
|
+
tool_calls: [
|
|
13915
|
+
{
|
|
13916
|
+
index: 0,
|
|
13917
|
+
id: `call_${reply.tool}_1`,
|
|
13918
|
+
type: "function",
|
|
13919
|
+
function: {
|
|
13920
|
+
name: reply.tool,
|
|
13921
|
+
arguments: JSON.stringify(reply.args ?? {})
|
|
13922
|
+
}
|
|
13923
|
+
}
|
|
13924
|
+
]
|
|
13925
|
+
}),
|
|
13926
|
+
chunk({}, "tool_calls")
|
|
13927
|
+
);
|
|
13928
|
+
return out;
|
|
13929
|
+
}
|
|
13930
|
+
if ("text" in reply) {
|
|
13931
|
+
for (const piece of reply.text.match(/[\s\S]{1,40}/g) ?? [])
|
|
13932
|
+
out.push(chunk({ content: piece }));
|
|
13933
|
+
}
|
|
13934
|
+
out.push(chunk({}, "stop"));
|
|
13935
|
+
return out;
|
|
13936
|
+
}
|
|
13937
|
+
__name(chunksFor, "chunksFor");
|
|
13938
|
+
function completionFor(reply) {
|
|
13939
|
+
const message = "tool" in reply ? {
|
|
13940
|
+
role: "assistant",
|
|
13941
|
+
content: null,
|
|
13942
|
+
tool_calls: [
|
|
13943
|
+
{
|
|
13944
|
+
index: 0,
|
|
13945
|
+
id: `call_${reply.tool}_1`,
|
|
13946
|
+
type: "function",
|
|
13947
|
+
function: {
|
|
13948
|
+
name: reply.tool,
|
|
13949
|
+
arguments: JSON.stringify(reply.args ?? {})
|
|
13950
|
+
}
|
|
13951
|
+
}
|
|
13952
|
+
]
|
|
13953
|
+
} : { role: "assistant", content: "text" in reply ? reply.text : "" };
|
|
13954
|
+
return {
|
|
13955
|
+
id: "chatcmpl-qwen-review-mock",
|
|
13956
|
+
object: "chat.completion",
|
|
13957
|
+
created: Math.floor(Date.now() / 1e3),
|
|
13958
|
+
model: "qwen-review-mock",
|
|
13959
|
+
choices: [
|
|
13960
|
+
{
|
|
13961
|
+
index: 0,
|
|
13962
|
+
message,
|
|
13963
|
+
finish_reason: "tool" in reply ? "tool_calls" : "stop"
|
|
13964
|
+
}
|
|
13965
|
+
],
|
|
13966
|
+
// 69% of the corpus carried one, and a client that reads token counts
|
|
13967
|
+
// behaves differently without it — an absent usage block is a behaviour
|
|
13968
|
+
// difference the mock introduced, not one the diff did.
|
|
13969
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
13970
|
+
};
|
|
13971
|
+
}
|
|
13972
|
+
__name(completionFor, "completionFor");
|
|
13973
|
+
function sseEvent(type, data) {
|
|
13974
|
+
return `event: ${type}
|
|
13975
|
+
data: ${JSON.stringify({ type, ...data })}
|
|
13976
|
+
|
|
13977
|
+
`;
|
|
13978
|
+
}
|
|
13979
|
+
__name(sseEvent, "sseEvent");
|
|
13980
|
+
function anthropicStream(reply) {
|
|
13981
|
+
const usage = {
|
|
13982
|
+
input_tokens: 0,
|
|
13983
|
+
output_tokens: 0,
|
|
13984
|
+
cache_creation_input_tokens: 0,
|
|
13985
|
+
cache_read_input_tokens: 0
|
|
13986
|
+
};
|
|
13987
|
+
let out = sseEvent("message_start", {
|
|
13988
|
+
message: {
|
|
13989
|
+
id: "msg_qwen_review_mock",
|
|
13990
|
+
type: "message",
|
|
13991
|
+
role: "assistant",
|
|
13992
|
+
model: "qwen-review-mock",
|
|
13993
|
+
content: [],
|
|
13994
|
+
stop_reason: null,
|
|
13995
|
+
usage
|
|
13996
|
+
}
|
|
13997
|
+
});
|
|
13998
|
+
if ("tool" in reply) {
|
|
13999
|
+
out += sseEvent("content_block_start", {
|
|
14000
|
+
index: 0,
|
|
14001
|
+
content_block: {
|
|
14002
|
+
type: "tool_use",
|
|
14003
|
+
id: `toolu_${reply.tool}_1`,
|
|
14004
|
+
name: reply.tool,
|
|
14005
|
+
input: {}
|
|
14006
|
+
}
|
|
14007
|
+
});
|
|
14008
|
+
out += sseEvent("content_block_delta", {
|
|
14009
|
+
index: 0,
|
|
14010
|
+
delta: {
|
|
14011
|
+
type: "input_json_delta",
|
|
14012
|
+
partial_json: JSON.stringify(reply.args ?? {})
|
|
14013
|
+
}
|
|
14014
|
+
});
|
|
14015
|
+
out += sseEvent("content_block_stop", { index: 0 });
|
|
14016
|
+
out += sseEvent("message_delta", {
|
|
14017
|
+
delta: { stop_reason: "tool_use", stop_sequence: null },
|
|
14018
|
+
usage: { output_tokens: 0 }
|
|
14019
|
+
});
|
|
14020
|
+
} else {
|
|
14021
|
+
const text = "text" in reply ? reply.text : "";
|
|
14022
|
+
out += sseEvent("content_block_start", {
|
|
14023
|
+
index: 0,
|
|
14024
|
+
content_block: { type: "text", text: "" }
|
|
14025
|
+
});
|
|
14026
|
+
for (const piece of text.match(/[\s\S]{1,40}/g) ?? [])
|
|
14027
|
+
out += sseEvent("content_block_delta", {
|
|
14028
|
+
index: 0,
|
|
14029
|
+
delta: { type: "text_delta", text: piece }
|
|
14030
|
+
});
|
|
14031
|
+
out += sseEvent("content_block_stop", { index: 0 });
|
|
14032
|
+
out += sseEvent("message_delta", {
|
|
14033
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
14034
|
+
usage: { output_tokens: 0 }
|
|
14035
|
+
});
|
|
14036
|
+
}
|
|
14037
|
+
return out + sseEvent("message_stop", {});
|
|
14038
|
+
}
|
|
14039
|
+
__name(anthropicStream, "anthropicStream");
|
|
14040
|
+
function anthropicMessage(reply) {
|
|
14041
|
+
const content = "tool" in reply ? [
|
|
14042
|
+
{
|
|
14043
|
+
type: "tool_use",
|
|
14044
|
+
id: `toolu_${reply.tool}_1`,
|
|
14045
|
+
name: reply.tool,
|
|
14046
|
+
input: reply.args ?? {}
|
|
14047
|
+
}
|
|
14048
|
+
] : [{ type: "text", text: "text" in reply ? reply.text : "" }];
|
|
14049
|
+
return {
|
|
14050
|
+
id: "msg_qwen_review_mock",
|
|
14051
|
+
type: "message",
|
|
14052
|
+
role: "assistant",
|
|
14053
|
+
model: "qwen-review-mock",
|
|
14054
|
+
content,
|
|
14055
|
+
stop_reason: "tool" in reply ? "tool_use" : "end_turn",
|
|
14056
|
+
stop_sequence: null,
|
|
14057
|
+
usage: {
|
|
14058
|
+
input_tokens: 0,
|
|
14059
|
+
output_tokens: 0,
|
|
14060
|
+
cache_creation_input_tokens: 0,
|
|
14061
|
+
cache_read_input_tokens: 0
|
|
14062
|
+
}
|
|
14063
|
+
};
|
|
14064
|
+
}
|
|
14065
|
+
__name(anthropicMessage, "anthropicMessage");
|
|
14066
|
+
function anthropicText(body) {
|
|
14067
|
+
const sys = body?.["system"];
|
|
14068
|
+
const sysText = typeof sys === "string" ? sys : Array.isArray(sys) ? sys.map((b) => String(b?.text ?? "")).join("") : "";
|
|
14069
|
+
const rest = messagesText(body);
|
|
14070
|
+
return sysText && rest ? `${sysText}
|
|
14071
|
+
${rest}` : sysText || rest;
|
|
14072
|
+
}
|
|
14073
|
+
__name(anthropicText, "anthropicText");
|
|
14074
|
+
var DEFAULT_REPLY = { text: "ok" };
|
|
14075
|
+
async function startMockProvider(args, respondOverride) {
|
|
14076
|
+
let respond = respondOverride ?? (() => DEFAULT_REPLY);
|
|
14077
|
+
if (!respondOverride && args.responder) {
|
|
14078
|
+
const mod = await import(pathToFileURL(resolve18(args.responder)).href);
|
|
14079
|
+
const fn = mod.respond ?? mod.default;
|
|
14080
|
+
if (typeof fn !== "function") {
|
|
14081
|
+
throw new Error(
|
|
14082
|
+
`mock-provider: ${args.responder} exports no \`respond\` function \u2014 a responder module must export \`respond(req)\` (or a default export of the same shape)`
|
|
14083
|
+
);
|
|
14084
|
+
}
|
|
14085
|
+
respond = fn;
|
|
14086
|
+
}
|
|
14087
|
+
const logPath = resolve18(args.log);
|
|
14088
|
+
mkdirSync20(dirname18(logPath), { recursive: true });
|
|
14089
|
+
writeFileSync21(logPath, "");
|
|
14090
|
+
let n = 0;
|
|
14091
|
+
const record = /* @__PURE__ */ __name((entry) => appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
14092
|
+
`), "record");
|
|
14093
|
+
const server = createServer((req, res) => {
|
|
14094
|
+
const parts = [];
|
|
14095
|
+
let size = 0;
|
|
14096
|
+
let tooBig = false;
|
|
14097
|
+
req.on("data", (d) => {
|
|
14098
|
+
size += d.length;
|
|
14099
|
+
if (size > BODY_MAX_BYTES) {
|
|
14100
|
+
tooBig = true;
|
|
14101
|
+
parts.length = 0;
|
|
14102
|
+
return;
|
|
14103
|
+
}
|
|
14104
|
+
parts.push(d);
|
|
14105
|
+
});
|
|
14106
|
+
req.on("end", () => {
|
|
14107
|
+
if (tooBig) {
|
|
14108
|
+
appendFileSync(
|
|
14109
|
+
logPath,
|
|
14110
|
+
`${JSON.stringify({ t: Date.now(), method: req.method, path: req.url, refused: "body-too-large", bytes: size })}
|
|
14111
|
+
`
|
|
14112
|
+
);
|
|
14113
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
14114
|
+
res.end(
|
|
14115
|
+
JSON.stringify({
|
|
14116
|
+
error: `request body exceeded ${BODY_MAX_BYTES} bytes; refused rather than truncated, because a truncated body is a different request`
|
|
14117
|
+
})
|
|
14118
|
+
);
|
|
14119
|
+
return;
|
|
14120
|
+
}
|
|
14121
|
+
void (async () => {
|
|
14122
|
+
const raw = Buffer.concat(parts).toString("utf8");
|
|
14123
|
+
let body = null;
|
|
14124
|
+
try {
|
|
14125
|
+
body = raw ? JSON.parse(raw) : null;
|
|
14126
|
+
} catch {
|
|
14127
|
+
body = null;
|
|
14128
|
+
}
|
|
14129
|
+
const path3 = req.url ?? "/";
|
|
14130
|
+
const isModels = path3.startsWith("/v1/models");
|
|
14131
|
+
const chat = path3.includes("/chat/completions") || path3.includes("/v1/messages");
|
|
14132
|
+
const willAsk = !isModels && chat && (req.method ?? "GET") === "POST" && body !== null;
|
|
14133
|
+
const wire = path3.includes("/v1/messages") ? "anthropic" : "openai";
|
|
14134
|
+
if (willAsk) n += 1;
|
|
14135
|
+
const mreq = {
|
|
14136
|
+
method: req.method ?? "GET",
|
|
14137
|
+
path: path3,
|
|
14138
|
+
body,
|
|
14139
|
+
wire,
|
|
14140
|
+
text: wire === "anthropic" ? anthropicText(body) : messagesText(body),
|
|
14141
|
+
stream: body?.["stream"] === true,
|
|
14142
|
+
// Null for anything the responder was never asked about, so the
|
|
14143
|
+
// record cannot be read as "the responder saw this".
|
|
14144
|
+
n: willAsk ? n : null
|
|
14145
|
+
};
|
|
14146
|
+
if (!isModels) {
|
|
14147
|
+
const why = !chat ? `this mock serves /v1/chat/completions and /v1/messages only; ${path3} is not one of them` : mreq.method !== "POST" ? `${mreq.method} is not how either chat endpoint is called; use POST` : body === null ? "the request body was empty or not JSON, so there is nothing to answer" : null;
|
|
14148
|
+
if (why) {
|
|
14149
|
+
record({ t: Date.now(), ...forRecordReq(mreq), refused: why });
|
|
14150
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
14151
|
+
res.end(JSON.stringify({ error: why }));
|
|
14152
|
+
return;
|
|
14153
|
+
}
|
|
14154
|
+
}
|
|
14155
|
+
if (isModels) {
|
|
14156
|
+
record({ t: Date.now(), ...forRecordReq(mreq), reply: "models" });
|
|
14157
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
14158
|
+
res.end(
|
|
14159
|
+
JSON.stringify({
|
|
14160
|
+
object: "list",
|
|
14161
|
+
data: [{ id: "qwen-review-mock", object: "model" }]
|
|
14162
|
+
})
|
|
14163
|
+
);
|
|
14164
|
+
return;
|
|
14165
|
+
}
|
|
14166
|
+
let reply;
|
|
14167
|
+
try {
|
|
14168
|
+
reply = await respond(mreq);
|
|
14169
|
+
const problem = replyProblem(reply);
|
|
14170
|
+
if (problem) throw new Error(problem);
|
|
14171
|
+
} catch (err) {
|
|
14172
|
+
reply = {
|
|
14173
|
+
status: 500,
|
|
14174
|
+
body: { error: String(err.message) }
|
|
14175
|
+
};
|
|
14176
|
+
}
|
|
14177
|
+
record({ t: Date.now(), ...forRecordReq(mreq), reply });
|
|
14178
|
+
if ("status" in reply) {
|
|
14179
|
+
res.writeHead(reply.status, { "Content-Type": "application/json" });
|
|
14180
|
+
res.end(JSON.stringify(reply.body ?? {}));
|
|
14181
|
+
return;
|
|
14182
|
+
}
|
|
14183
|
+
if (!mreq.stream) {
|
|
14184
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
14185
|
+
res.end(
|
|
14186
|
+
JSON.stringify(
|
|
14187
|
+
wire === "anthropic" ? anthropicMessage(reply) : completionFor(reply)
|
|
14188
|
+
)
|
|
14189
|
+
);
|
|
14190
|
+
return;
|
|
14191
|
+
}
|
|
14192
|
+
res.writeHead(200, {
|
|
14193
|
+
"Content-Type": "text/event-stream",
|
|
14194
|
+
"Cache-Control": "no-cache",
|
|
14195
|
+
Connection: "keep-alive"
|
|
14196
|
+
});
|
|
14197
|
+
if (wire === "anthropic") {
|
|
14198
|
+
res.write(anthropicStream(reply));
|
|
14199
|
+
} else {
|
|
14200
|
+
for (const c of chunksFor(reply)) res.write(enc(c));
|
|
14201
|
+
res.write("data: [DONE]\n\n");
|
|
14202
|
+
}
|
|
14203
|
+
res.end();
|
|
14204
|
+
})();
|
|
14205
|
+
});
|
|
14206
|
+
});
|
|
14207
|
+
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
|
|
14208
|
+
const addr = server.address();
|
|
14209
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
14210
|
+
return {
|
|
14211
|
+
report: {
|
|
14212
|
+
port,
|
|
14213
|
+
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
14214
|
+
logPath,
|
|
14215
|
+
note: `mock provider on port ${port}; every request is appended to ${logPath} as JSONL \u2014 diff those between two trees and the difference is evidence`
|
|
14216
|
+
},
|
|
14217
|
+
close: /* @__PURE__ */ __name(() => new Promise((ok) => {
|
|
14218
|
+
server.close(() => ok());
|
|
14219
|
+
}), "close")
|
|
12738
14220
|
};
|
|
12739
14221
|
}
|
|
12740
|
-
__name(
|
|
12741
|
-
var
|
|
12742
|
-
command: "
|
|
12743
|
-
describe: "
|
|
12744
|
-
builder: /* @__PURE__ */ __name((yargs) => yargs.option("
|
|
14222
|
+
__name(startMockProvider, "startMockProvider");
|
|
14223
|
+
var mockProviderCommand = {
|
|
14224
|
+
command: "mock-provider",
|
|
14225
|
+
describe: "Serve OpenAI (/v1/chat/completions) and Anthropic (/v1/messages) endpoints for the review to drive the real product against, recording every request as JSONL \u2014 the protocol is provided, the answers are yours; for any other kind of upstream, start it yourself and use `drive`",
|
|
14226
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("responder", {
|
|
12745
14227
|
type: "string",
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
}).option("baseline", {
|
|
14228
|
+
describe: "Module exporting `respond(req)` returning {text} | {tool,args} | {status,body}"
|
|
14229
|
+
}).option("log", {
|
|
12749
14230
|
type: "string",
|
|
12750
14231
|
demandOption: true,
|
|
12751
|
-
describe: "
|
|
12752
|
-
}).option("
|
|
12753
|
-
type: "string",
|
|
12754
|
-
describe: "The PR worktree the report's failures were produced in \u2014 its root is stripped so both sides compare as repo-relative paths"
|
|
12755
|
-
}).option("out", { type: "string", describe: "Write the JSON report here" }).option("timeout", {
|
|
14232
|
+
describe: "Where to append the JSONL request record"
|
|
14233
|
+
}).option("ttl", {
|
|
12756
14234
|
type: "number",
|
|
12757
|
-
default:
|
|
12758
|
-
describe: "
|
|
14235
|
+
default: 600,
|
|
14236
|
+
describe: "Seconds to serve before shutting down on its own"
|
|
14237
|
+
}).option("out", {
|
|
14238
|
+
type: "string",
|
|
14239
|
+
describe: "Write the JSON report here"
|
|
12759
14240
|
}), "builder"),
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
14241
|
+
// `async`, and the returned promise is what keeps the process alive. A
|
|
14242
|
+
// fire-and-forget `void (async () => …)()` here produced a command that
|
|
14243
|
+
// printed nothing at all and exited: yargs returned, node found no pending
|
|
14244
|
+
// work it could see, and the server it had just bound went with it. Measured
|
|
14245
|
+
// — both streams empty, exit 0, a mock that was never reachable.
|
|
14246
|
+
handler: /* @__PURE__ */ __name(async (argv) => {
|
|
14247
|
+
try {
|
|
14248
|
+
const args = argv;
|
|
14249
|
+
const { report, close } = await startMockProvider(args);
|
|
14250
|
+
if (args.out) {
|
|
14251
|
+
mkdirSync20(dirname18(resolve18(args.out)), { recursive: true });
|
|
14252
|
+
writeFileSync21(resolve18(args.out), JSON.stringify(report, null, 2));
|
|
14253
|
+
}
|
|
14254
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
14255
|
+
writeStderrLine(`mock-provider: ${report.note}`);
|
|
14256
|
+
await new Promise((done) => {
|
|
14257
|
+
setTimeout(done, Math.max(1, args.ttl) * 1e3);
|
|
14258
|
+
});
|
|
14259
|
+
await close();
|
|
14260
|
+
} catch (err) {
|
|
14261
|
+
writeStderrLine(err.message);
|
|
14262
|
+
process.exitCode = 1;
|
|
12766
14263
|
}
|
|
12767
|
-
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12768
|
-
writeStderrLine(`test-delta: ${report.note}`);
|
|
12769
14264
|
}, "handler")
|
|
12770
14265
|
};
|
|
12771
14266
|
|
|
12772
14267
|
// packages/cli/src/commands/review/extract-step.ts
|
|
12773
14268
|
init_esbuild_shims();
|
|
12774
14269
|
var import_yaml = __toESM(require_dist(), 1);
|
|
12775
|
-
import { mkdirSync as
|
|
12776
|
-
import { dirname as
|
|
14270
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync22, chmodSync } from "node:fs";
|
|
14271
|
+
import { dirname as dirname19, resolve as resolve19 } from "node:path";
|
|
12777
14272
|
function expressionsOf(...texts) {
|
|
12778
14273
|
const seen = /* @__PURE__ */ new Set();
|
|
12779
14274
|
for (const t2 of texts) {
|
|
@@ -12986,8 +14481,8 @@ function nearestScoped(...values) {
|
|
|
12986
14481
|
__name(nearestScoped, "nearestScoped");
|
|
12987
14482
|
function runDefaultsOf(container) {
|
|
12988
14483
|
const defaults = container?.defaults;
|
|
12989
|
-
const
|
|
12990
|
-
return
|
|
14484
|
+
const run4 = defaults && typeof defaults === "object" ? defaults.run : void 0;
|
|
14485
|
+
return run4 && typeof run4 === "object" ? run4 : {};
|
|
12991
14486
|
}
|
|
12992
14487
|
__name(runDefaultsOf, "runDefaultsOf");
|
|
12993
14488
|
function envValue(v) {
|
|
@@ -13017,10 +14512,10 @@ function nearestFirst(env, sources) {
|
|
|
13017
14512
|
}
|
|
13018
14513
|
__name(nearestFirst, "nearestFirst");
|
|
13019
14514
|
function runExtractStep(args) {
|
|
13020
|
-
const wfPath =
|
|
14515
|
+
const wfPath = resolve19(args.workflow);
|
|
13021
14516
|
let text;
|
|
13022
14517
|
try {
|
|
13023
|
-
text =
|
|
14518
|
+
text = readFileSync23(wfPath, "utf8");
|
|
13024
14519
|
} catch (err) {
|
|
13025
14520
|
throw new Error(
|
|
13026
14521
|
`extract-step: cannot read ${args.workflow}: ${err.message}`
|
|
@@ -13086,8 +14581,8 @@ function runExtractStep(args) {
|
|
|
13086
14581
|
const workingDirectory = workingDir?.value;
|
|
13087
14582
|
const script = step.run;
|
|
13088
14583
|
const stepLabel = String(step.name ?? step.id ?? index);
|
|
13089
|
-
const outPath =
|
|
13090
|
-
|
|
14584
|
+
const outPath = resolve19(args.out);
|
|
14585
|
+
mkdirSync21(dirname19(outPath), { recursive: true });
|
|
13091
14586
|
const header = [
|
|
13092
14587
|
`#!/usr/bin/env ${shellCommand}`,
|
|
13093
14588
|
...commentLines(
|
|
@@ -13111,7 +14606,7 @@ function runExtractStep(args) {
|
|
|
13111
14606
|
),
|
|
13112
14607
|
""
|
|
13113
14608
|
].join("\n");
|
|
13114
|
-
|
|
14609
|
+
writeFileSync22(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
|
|
13115
14610
|
chmodSync(outPath, 493);
|
|
13116
14611
|
return {
|
|
13117
14612
|
workflow: args.workflow,
|
|
@@ -13172,7 +14667,7 @@ var extractStepCommand = {
|
|
|
13172
14667
|
|
|
13173
14668
|
// packages/cli/src/commands/review/submit.ts
|
|
13174
14669
|
init_esbuild_shims();
|
|
13175
|
-
import { mkdirSync as
|
|
14670
|
+
import { mkdirSync as mkdirSync22, readFileSync as readFileSync24 } from "node:fs";
|
|
13176
14671
|
|
|
13177
14672
|
// packages/cli/src/commands/review/lib/receipt.ts
|
|
13178
14673
|
init_esbuild_shims();
|
|
@@ -13190,123 +14685,11 @@ function parseReceiptIds(raw) {
|
|
|
13190
14685
|
}
|
|
13191
14686
|
__name(parseReceiptIds, "parseReceiptIds");
|
|
13192
14687
|
|
|
13193
|
-
// packages/cli/src/services/skill-args-file.ts
|
|
13194
|
-
init_esbuild_shims();
|
|
13195
|
-
import {
|
|
13196
|
-
mkdirSync as mkdirSync18,
|
|
13197
|
-
openSync as openSync2,
|
|
13198
|
-
writeSync,
|
|
13199
|
-
closeSync as closeSync2,
|
|
13200
|
-
rmSync as rmSync7,
|
|
13201
|
-
lstatSync as lstatSync3,
|
|
13202
|
-
existsSync as existsSync8,
|
|
13203
|
-
constants
|
|
13204
|
-
} from "node:fs";
|
|
13205
|
-
import { join as join15 } from "node:path";
|
|
13206
|
-
var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
|
|
13207
|
-
var SKILL_ARGS_DIR = join15(".qwen", "tmp");
|
|
13208
|
-
function safe(s) {
|
|
13209
|
-
return s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
13210
|
-
}
|
|
13211
|
-
__name(safe, "safe");
|
|
13212
|
-
function currentSessionId() {
|
|
13213
|
-
return sessionIdContext.getStore()?.trim() || process.env["QWEN_CODE_SESSION_ID"]?.trim() || "";
|
|
13214
|
-
}
|
|
13215
|
-
__name(currentSessionId, "currentSessionId");
|
|
13216
|
-
function skillArgsDir(sessionId = currentSessionId()) {
|
|
13217
|
-
return sessionId ? join15(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
|
|
13218
|
-
}
|
|
13219
|
-
__name(skillArgsDir, "skillArgsDir");
|
|
13220
|
-
function skillArgsPath(skillName, sessionId = currentSessionId()) {
|
|
13221
|
-
return join15(
|
|
13222
|
-
skillArgsDir(sessionId),
|
|
13223
|
-
`qwen-skill-args-${safe(skillName)}.txt`
|
|
13224
|
-
);
|
|
13225
|
-
}
|
|
13226
|
-
__name(skillArgsPath, "skillArgsPath");
|
|
13227
|
-
function writeSkillArgs(skillName, args) {
|
|
13228
|
-
const path3 = skillArgsPath(skillName);
|
|
13229
|
-
try {
|
|
13230
|
-
const dir = skillArgsDir();
|
|
13231
|
-
mkdirSync18(dir, { recursive: true });
|
|
13232
|
-
if (lstatSync3(dir).isSymbolicLink()) {
|
|
13233
|
-
debugLogger3.warn(
|
|
13234
|
-
`Skill args directory ${dir} is a symlink; refusing to write through it.`
|
|
13235
|
-
);
|
|
13236
|
-
return null;
|
|
13237
|
-
}
|
|
13238
|
-
if (lstatSync3(path3, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
13239
|
-
debugLogger3.warn(
|
|
13240
|
-
`Skill args file ${path3} is a symlink; refusing to write through it.`
|
|
13241
|
-
);
|
|
13242
|
-
return null;
|
|
13243
|
-
}
|
|
13244
|
-
const fd = openSync2(
|
|
13245
|
-
path3,
|
|
13246
|
-
constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
|
|
13247
|
-
384
|
|
13248
|
-
);
|
|
13249
|
-
try {
|
|
13250
|
-
const buf = Buffer.from(args, "utf8");
|
|
13251
|
-
let off = 0;
|
|
13252
|
-
while (off < buf.length) {
|
|
13253
|
-
off += writeSync(fd, buf, off, buf.length - off);
|
|
13254
|
-
}
|
|
13255
|
-
} finally {
|
|
13256
|
-
closeSync2(fd);
|
|
13257
|
-
}
|
|
13258
|
-
return path3;
|
|
13259
|
-
} catch (err) {
|
|
13260
|
-
debugLogger3.warn(
|
|
13261
|
-
`Could not write skill args to ${path3}: ${err.message}`
|
|
13262
|
-
);
|
|
13263
|
-
return null;
|
|
13264
|
-
}
|
|
13265
|
-
}
|
|
13266
|
-
__name(writeSkillArgs, "writeSkillArgs");
|
|
13267
|
-
function clearSkillArgs(skillName) {
|
|
13268
|
-
const path3 = skillArgsPath(skillName);
|
|
13269
|
-
try {
|
|
13270
|
-
rmSync7(path3, { force: true });
|
|
13271
|
-
} catch (err) {
|
|
13272
|
-
debugLogger3.warn(
|
|
13273
|
-
`Could not clear skill args for ${skillName}: ${err.message}`
|
|
13274
|
-
);
|
|
13275
|
-
return false;
|
|
13276
|
-
}
|
|
13277
|
-
if (existsSync8(path3)) {
|
|
13278
|
-
debugLogger3.warn(`Skill args for ${skillName} survived removal at ${path3}`);
|
|
13279
|
-
return false;
|
|
13280
|
-
}
|
|
13281
|
-
return true;
|
|
13282
|
-
}
|
|
13283
|
-
__name(clearSkillArgs, "clearSkillArgs");
|
|
13284
|
-
function staleArgsWarning() {
|
|
13285
|
-
return `
|
|
13286
|
-
|
|
13287
|
-
<skill-args-stale>A previous invocation's argument record could not be removed, so it is still on disk and still names whatever it named. This invocation supplied NO arguments. Do not treat that stale record as this run's authorisation: this run has none, and must not post.</skill-args-stale>
|
|
13288
|
-
`;
|
|
13289
|
-
}
|
|
13290
|
-
__name(staleArgsWarning, "staleArgsWarning");
|
|
13291
|
-
function skillArgsNote(path3, args) {
|
|
13292
|
-
return `
|
|
13293
|
-
|
|
13294
|
-
Your invocation arguments have been written verbatim to a session-private file. Its exact path is below \u2014 use it wherever these instructions say to read the args file (e.g. \`< '${path3}'\`), and do not retype the arguments, which is how they get mistyped.
|
|
13295
|
-
<skill-args-file>${path3}</skill-args-file>
|
|
13296
|
-
<skill-args>${args}</skill-args>
|
|
13297
|
-
`;
|
|
13298
|
-
}
|
|
13299
|
-
__name(skillArgsNote, "skillArgsNote");
|
|
13300
|
-
|
|
13301
14688
|
// packages/cli/src/commands/review/submit.ts
|
|
13302
|
-
function defaultSkillArgsPath() {
|
|
13303
|
-
return skillArgsPath("review");
|
|
13304
|
-
}
|
|
13305
|
-
__name(defaultSkillArgsPath, "defaultSkillArgsPath");
|
|
13306
14689
|
var EVENTS = /* @__PURE__ */ new Set(["APPROVE", "REQUEST_CHANGES", "COMMENT"]);
|
|
13307
14690
|
function readReceiptIds(receiptPath) {
|
|
13308
14691
|
try {
|
|
13309
|
-
return parseReceiptIds(
|
|
14692
|
+
return parseReceiptIds(readFileSync24(receiptPath, "utf8"));
|
|
13310
14693
|
} catch {
|
|
13311
14694
|
return [];
|
|
13312
14695
|
}
|
|
@@ -13317,60 +14700,20 @@ function isDiffLine(n) {
|
|
|
13317
14700
|
}
|
|
13318
14701
|
__name(isDiffLine, "isDiffLine");
|
|
13319
14702
|
function authorization(args) {
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
}
|
|
13334
|
-
const verdict = parseReviewArgs(raw);
|
|
13335
|
-
if (!verdict.comment.effective) {
|
|
13336
|
-
return {
|
|
13337
|
-
ok: false,
|
|
13338
|
-
why: `\`--comment\` was not in the review arguments (${JSON.stringify(raw.trim())})`
|
|
13339
|
-
};
|
|
13340
|
-
}
|
|
13341
|
-
const t2 = verdict.target;
|
|
13342
|
-
const authorisedPr = t2.type === "pr-number" || t2.type === "pr-url" ? t2.number : void 0;
|
|
13343
|
-
if (authorisedPr === void 0) {
|
|
13344
|
-
return {
|
|
13345
|
-
ok: false,
|
|
13346
|
-
why: `the review arguments (${JSON.stringify(raw.trim())}) do not name a pull request, so they cannot authorise posting to one`
|
|
13347
|
-
};
|
|
13348
|
-
}
|
|
13349
|
-
if (authorisedPr !== args.pr) {
|
|
13350
|
-
return {
|
|
13351
|
-
ok: false,
|
|
13352
|
-
why: `the review arguments authorise pull request #${authorisedPr}, but this submission targets #${args.pr}`
|
|
13353
|
-
};
|
|
13354
|
-
}
|
|
13355
|
-
if (t2.type === "pr-url") {
|
|
13356
|
-
const authorisedRepo = `${t2.owner}/${t2.repo}`;
|
|
13357
|
-
if (authorisedRepo.toLowerCase() !== args.repo.toLowerCase()) {
|
|
13358
|
-
return {
|
|
13359
|
-
ok: false,
|
|
13360
|
-
why: `the review arguments authorise ${authorisedRepo}, but this submission targets ${args.repo}`
|
|
13361
|
-
};
|
|
13362
|
-
}
|
|
13363
|
-
if (args.host && t2.host.toLowerCase() !== args.host.toLowerCase()) {
|
|
13364
|
-
return {
|
|
13365
|
-
ok: false,
|
|
13366
|
-
why: `the review arguments authorise ${t2.host}, but this submission targets ${args.host}`
|
|
13367
|
-
};
|
|
13368
|
-
}
|
|
13369
|
-
}
|
|
13370
|
-
return {
|
|
13371
|
-
ok: true,
|
|
13372
|
-
why: `\`--comment\` was in the review arguments for #${authorisedPr}`
|
|
13373
|
-
};
|
|
14703
|
+
return reviewWriteAuthorization({
|
|
14704
|
+
userAuthorized: args.userAuthorized,
|
|
14705
|
+
skillArgs: args.skillArgs,
|
|
14706
|
+
pr: args.pr,
|
|
14707
|
+
repo: args.repo,
|
|
14708
|
+
// The EFFECTIVE host, not merely the flag: with --host absent the gh
|
|
14709
|
+
// child inherits an operator-exported GH_HOST, so that is where this
|
|
14710
|
+
// write would route — and what the gate must bind. Same resolution as
|
|
14711
|
+
// publish-assets.
|
|
14712
|
+
// `|| undefined`, not `??`: an exported-but-empty GH_HOST ("" survives
|
|
14713
|
+
// `??`, being non-nullish) must read as "no host", not as a host named
|
|
14714
|
+
// "" that fails every comparison.
|
|
14715
|
+
host: args.host ?? (process.env["GH_HOST"]?.trim() || void 0)
|
|
14716
|
+
});
|
|
13374
14717
|
}
|
|
13375
14718
|
__name(authorization, "authorization");
|
|
13376
14719
|
function compose(payload, cliVersion) {
|
|
@@ -13481,7 +14824,7 @@ function runSubmit(args, cliVersion = "unknown") {
|
|
|
13481
14824
|
}
|
|
13482
14825
|
let payload;
|
|
13483
14826
|
try {
|
|
13484
|
-
payload = JSON.parse(
|
|
14827
|
+
payload = JSON.parse(readFileSync24(args.review, "utf8"));
|
|
13485
14828
|
} catch (err) {
|
|
13486
14829
|
throw new Error(
|
|
13487
14830
|
`Cannot read review JSON ${args.review}: ${err.message}`
|
|
@@ -13557,7 +14900,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
|
|
|
13557
14900
|
const receiptPath = tmpFile(`pr-${args.pr}`, "submit-receipt.json");
|
|
13558
14901
|
const priorIds = readReceiptIds(receiptPath);
|
|
13559
14902
|
const reviewIds = [.../* @__PURE__ */ new Set([...priorIds, reviewId])];
|
|
13560
|
-
|
|
14903
|
+
mkdirSync22(REVIEW_TMP_DIR, { recursive: true });
|
|
13561
14904
|
atomicWriteFileSync(
|
|
13562
14905
|
receiptPath,
|
|
13563
14906
|
`${JSON.stringify({ reviewIds, event, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
@@ -13620,20 +14963,20 @@ var submitCommand = {
|
|
|
13620
14963
|
|
|
13621
14964
|
// packages/cli/src/commands/review/test-efficacy.ts
|
|
13622
14965
|
init_esbuild_shims();
|
|
13623
|
-
import { spawnSync as
|
|
14966
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13624
14967
|
import {
|
|
13625
|
-
mkdirSync as
|
|
14968
|
+
mkdirSync as mkdirSync23,
|
|
13626
14969
|
readdirSync as readdirSync5,
|
|
13627
|
-
writeFileSync as
|
|
13628
|
-
readFileSync as
|
|
13629
|
-
rmSync as
|
|
14970
|
+
writeFileSync as writeFileSync23,
|
|
14971
|
+
readFileSync as readFileSync25,
|
|
14972
|
+
rmSync as rmSync9,
|
|
13630
14973
|
lstatSync as lstatSync4,
|
|
13631
|
-
existsSync as
|
|
13632
|
-
statSync as
|
|
14974
|
+
existsSync as existsSync11,
|
|
14975
|
+
statSync as statSync8,
|
|
13633
14976
|
symlinkSync
|
|
13634
14977
|
} from "node:fs";
|
|
13635
14978
|
import { createRequire } from "node:module";
|
|
13636
|
-
import { dirname as
|
|
14979
|
+
import { dirname as dirname20, join as join17, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
|
|
13637
14980
|
var FIXTURE_DIR_RE = /(^|\/)(__fixtures__|__mocks__|__snapshots__|fixtures)\//;
|
|
13638
14981
|
function planTestEfficacy(files, workspaceGlobs) {
|
|
13639
14982
|
const tests = files.filter((f) => f.kind === "test").map((f) => f.path);
|
|
@@ -13988,6 +15331,73 @@ function collocatedProbe(file, testPaths) {
|
|
|
13988
15331
|
});
|
|
13989
15332
|
}
|
|
13990
15333
|
__name(collocatedProbe, "collocatedProbe");
|
|
15334
|
+
function heldForRedCollocatedTest(kind, file, probes, greenProbes, baselinePerFile) {
|
|
15335
|
+
const own = collocatedProbe(file, probes);
|
|
15336
|
+
if (!own || greenProbes.includes(own)) return void 0;
|
|
15337
|
+
return collocatedNotGreenDetail(kind, own, baselinePerFile);
|
|
15338
|
+
}
|
|
15339
|
+
__name(heldForRedCollocatedTest, "heldForRedCollocatedTest");
|
|
15340
|
+
function runnerFailureReason(r) {
|
|
15341
|
+
return r.signal || r.error?.code === "ETIMEDOUT" ? "runner-died" : "not-run";
|
|
15342
|
+
}
|
|
15343
|
+
__name(runnerFailureReason, "runnerFailureReason");
|
|
15344
|
+
var ProbeRunFailure = class extends Error {
|
|
15345
|
+
constructor(message, reason) {
|
|
15346
|
+
super(message);
|
|
15347
|
+
this.reason = reason;
|
|
15348
|
+
this.name = "ProbeRunFailure";
|
|
15349
|
+
}
|
|
15350
|
+
static {
|
|
15351
|
+
__name(this, "ProbeRunFailure");
|
|
15352
|
+
}
|
|
15353
|
+
};
|
|
15354
|
+
var BASELINE_REASON = /* @__PURE__ */ new Set([
|
|
15355
|
+
"no-output",
|
|
15356
|
+
"not-in-results",
|
|
15357
|
+
"no-tests",
|
|
15358
|
+
"all-skipped"
|
|
15359
|
+
]);
|
|
15360
|
+
var REASON_PHRASE = {
|
|
15361
|
+
// The suite ran and read green, but the control proved the runner could not
|
|
15362
|
+
// have reported otherwise.
|
|
15363
|
+
"control-failed": "it read green there, but the positive control failed, so nothing could have turned that run red",
|
|
15364
|
+
// Nothing ran: no worktree, the checkout failed, or the runner could not be
|
|
15365
|
+
// started at all — a spawn that fails with ENOENT never gets to a test.
|
|
15366
|
+
"not-run": "no probe suite ran for it at all there",
|
|
15367
|
+
// Started and did not survive. How much of it ran before that is unknown.
|
|
15368
|
+
"runner-died": "the probe suite was started there and did not survive (killed at the deadline, or by a signal)",
|
|
15369
|
+
// Nothing is known about collection here — the runner never produced output
|
|
15370
|
+
// to read. Saying "collected no tests" would be the same invented cause this
|
|
15371
|
+
// function exists to stop, one layer down: a reader sent after a compile
|
|
15372
|
+
// error that is not there, while the runner itself is what fell over.
|
|
15373
|
+
"no-output": "the runner produced no parseable output there, so nothing at all is known about it",
|
|
15374
|
+
// The run answered, and this file was not in the answer. A compile or import
|
|
15375
|
+
// error looks like this — and so does a path that failed to match, which the
|
|
15376
|
+
// boundary and case rules in `classifyProbeRun` exist because of. Naming one
|
|
15377
|
+
// would be picking between two live possibilities.
|
|
15378
|
+
"not-in-results": "the run produced results there but none for it \u2014 which a compile or import error looks like, and so does a path that did not match",
|
|
15379
|
+
"no-tests": "it collected no tests there \u2014 the shape a compile or import error in the probe tree takes",
|
|
15380
|
+
"all-skipped": "it collected tests there but executed none of them"
|
|
15381
|
+
};
|
|
15382
|
+
var NOT_REPORTED = "the baseline did not report it";
|
|
15383
|
+
function collocatedNotGreenDetail(kind, probe, baselinePerFile) {
|
|
15384
|
+
const entry = baselinePerFile.find((p) => p.file === probe);
|
|
15385
|
+
const reason = entry === void 0 ? NOT_REPORTED : entry.verdict === "inconclusive" ? (
|
|
15386
|
+
// Three reasons are set on the run-level `results` array and never by
|
|
15387
|
+
// `classifyProbeRun`, so a baseline entry cannot carry them. Reaching
|
|
15388
|
+
// one means the caller passed something other than the baseline —
|
|
15389
|
+
// say that, rather than emit "did not run green … it read green".
|
|
15390
|
+
BASELINE_REASON.has(entry.reason) ? REASON_PHRASE[entry.reason] : `the baseline did not classify it (${REASON_PHRASE[entry.reason]}), so this explanation does not apply to it`
|
|
15391
|
+
) : entry.verdict === "gated" ? "it was RED there" : (
|
|
15392
|
+
// `inert` IS green, so the sentence this function builds does not
|
|
15393
|
+
// apply to it. Say that, rather than produce a fluent claim that
|
|
15394
|
+
// contradicts the measurement — the caller is what is wrong here.
|
|
15395
|
+
"the baseline reported it GREEN, so this explanation does not apply to it"
|
|
15396
|
+
);
|
|
15397
|
+
const what = kind === "mutant" ? "the statement" : "the hunk";
|
|
15398
|
+
return `this ${kind}'s collocated test ${probe} did not run green in the unmutated baseline \u2014 ${reason}, so the remaining probes passing cannot show ${what} is uncovered`;
|
|
15399
|
+
}
|
|
15400
|
+
__name(collocatedNotGreenDetail, "collocatedNotGreenDetail");
|
|
13991
15401
|
function hasCollocatedNewTest(file, testPaths) {
|
|
13992
15402
|
return collocatedProbe(file, testPaths) !== void 0;
|
|
13993
15403
|
}
|
|
@@ -14018,6 +15428,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14018
15428
|
return probes.map((file) => ({
|
|
14019
15429
|
file,
|
|
14020
15430
|
verdict: "inconclusive",
|
|
15431
|
+
reason: "no-output",
|
|
14021
15432
|
detail: `runner produced no parseable JSON (exit ${exitCode})${why ? `: ${why}` : ""}`
|
|
14022
15433
|
}));
|
|
14023
15434
|
}
|
|
@@ -14032,10 +15443,19 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14032
15443
|
const assertions = result?.assertionResults ?? [];
|
|
14033
15444
|
const failed = assertions.filter((a) => a.status === "failed").length;
|
|
14034
15445
|
const passed = assertions.filter((a) => a.status === "passed").length;
|
|
14035
|
-
if (!result
|
|
15446
|
+
if (!result) {
|
|
15447
|
+
return {
|
|
15448
|
+
file,
|
|
15449
|
+
verdict: "inconclusive",
|
|
15450
|
+
reason: "not-in-results",
|
|
15451
|
+
detail: `the run produced results but none for this file (run exit ${exitCode}) \u2014 a compile or import error looks like this, and so does a path that did not match; not evidence either way`
|
|
15452
|
+
};
|
|
15453
|
+
}
|
|
15454
|
+
if (assertions.length === 0) {
|
|
14036
15455
|
return {
|
|
14037
15456
|
file,
|
|
14038
15457
|
verdict: "inconclusive",
|
|
15458
|
+
reason: "no-tests",
|
|
14039
15459
|
detail: `collected no tests with the source reverted (run exit ${exitCode}) \u2014 likely a compile or import error, which is not evidence either way`
|
|
14040
15460
|
};
|
|
14041
15461
|
}
|
|
@@ -14050,6 +15470,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14050
15470
|
return {
|
|
14051
15471
|
file,
|
|
14052
15472
|
verdict: "inconclusive",
|
|
15473
|
+
reason: "all-skipped",
|
|
14053
15474
|
detail: `${assertions.length} test(s) collected but none executed with the source reverted (all skipped) \u2014 not evidence either way`
|
|
14054
15475
|
};
|
|
14055
15476
|
}
|
|
@@ -14062,7 +15483,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14062
15483
|
}
|
|
14063
15484
|
__name(classifyProbeRun, "classifyProbeRun");
|
|
14064
15485
|
function git3(cwd, ...args) {
|
|
14065
|
-
const r =
|
|
15486
|
+
const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
|
|
14066
15487
|
if (r.error) throw r.error;
|
|
14067
15488
|
if (r.status !== 0) {
|
|
14068
15489
|
throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
|
|
@@ -14070,7 +15491,7 @@ function git3(cwd, ...args) {
|
|
|
14070
15491
|
}
|
|
14071
15492
|
__name(git3, "git");
|
|
14072
15493
|
function gitOut2(cwd, ...args) {
|
|
14073
|
-
const r =
|
|
15494
|
+
const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
|
|
14074
15495
|
if (r.error) throw r.error;
|
|
14075
15496
|
if (r.status !== 0) {
|
|
14076
15497
|
throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
|
|
@@ -14079,7 +15500,7 @@ function gitOut2(cwd, ...args) {
|
|
|
14079
15500
|
}
|
|
14080
15501
|
__name(gitOut2, "gitOut");
|
|
14081
15502
|
function findVitestBin(worktree) {
|
|
14082
|
-
const req = createRequire(
|
|
15503
|
+
const req = createRequire(join17(worktree, "noop.js"));
|
|
14083
15504
|
let pkgPath;
|
|
14084
15505
|
try {
|
|
14085
15506
|
pkgPath = req.resolve("vitest/package.json");
|
|
@@ -14089,16 +15510,16 @@ function findVitestBin(worktree) {
|
|
|
14089
15510
|
}
|
|
14090
15511
|
throw error;
|
|
14091
15512
|
}
|
|
14092
|
-
const pkg = JSON.parse(
|
|
15513
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf8"));
|
|
14093
15514
|
const bin = pkg.bin?.vitest;
|
|
14094
15515
|
if (!bin) {
|
|
14095
15516
|
throw new Error(`vitest package at ${pkgPath} declares no "vitest" bin`);
|
|
14096
15517
|
}
|
|
14097
|
-
return
|
|
15518
|
+
return join17(dirname20(pkgPath), bin);
|
|
14098
15519
|
}
|
|
14099
15520
|
__name(findVitestBin, "findVitestBin");
|
|
14100
15521
|
function gitCapture(cwd, ...args) {
|
|
14101
|
-
const r =
|
|
15522
|
+
const r = spawnSync7("git", args, {
|
|
14102
15523
|
cwd,
|
|
14103
15524
|
encoding: "utf8",
|
|
14104
15525
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -14111,7 +15532,7 @@ function gitCapture(cwd, ...args) {
|
|
|
14111
15532
|
}
|
|
14112
15533
|
__name(gitCapture, "gitCapture");
|
|
14113
15534
|
function existsAtRev(cwd, rev, path3) {
|
|
14114
|
-
const r =
|
|
15535
|
+
const r = spawnSync7("git", ["cat-file", "-e", `${rev}:${path3}`], { cwd });
|
|
14115
15536
|
if (r.error) throw r.error;
|
|
14116
15537
|
return r.status === 0;
|
|
14117
15538
|
}
|
|
@@ -14120,7 +15541,7 @@ function safeRmWithin(worktree, relPath) {
|
|
|
14120
15541
|
const parts = relPath.split(/[/\\]+/).filter((s) => s && s !== ".");
|
|
14121
15542
|
let cur = worktree;
|
|
14122
15543
|
for (let i = 0; i < parts.length; i++) {
|
|
14123
|
-
cur =
|
|
15544
|
+
cur = join17(cur, parts[i]);
|
|
14124
15545
|
let st;
|
|
14125
15546
|
try {
|
|
14126
15547
|
st = lstatSync4(cur);
|
|
@@ -14133,7 +15554,7 @@ function safeRmWithin(worktree, relPath) {
|
|
|
14133
15554
|
);
|
|
14134
15555
|
}
|
|
14135
15556
|
}
|
|
14136
|
-
|
|
15557
|
+
rmSync9(cur, { force: true });
|
|
14137
15558
|
}
|
|
14138
15559
|
__name(safeRmWithin, "safeRmWithin");
|
|
14139
15560
|
var existsAtBase = /* @__PURE__ */ __name((cwd, base, path3) => existsAtRev(cwd, base, path3), "existsAtBase");
|
|
@@ -14145,15 +15566,15 @@ __name(probeCleanupFailureDetail, "probeCleanupFailureDetail");
|
|
|
14145
15566
|
function exposeDependencies(probeTree, dependencyRoot) {
|
|
14146
15567
|
const done = { linked: 0, failed: 0 };
|
|
14147
15568
|
if (probeTree === dependencyRoot) return done;
|
|
14148
|
-
const source =
|
|
14149
|
-
const target =
|
|
14150
|
-
if (!
|
|
14151
|
-
|
|
15569
|
+
const source = join17(dependencyRoot, "node_modules");
|
|
15570
|
+
const target = join17(probeTree, "node_modules");
|
|
15571
|
+
if (!existsSync11(source) || existsSync11(target)) return done;
|
|
15572
|
+
mkdirSync23(target);
|
|
14152
15573
|
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
14153
15574
|
for (const entry of readdirSync5(source, { withFileTypes: true })) {
|
|
14154
15575
|
if (entry.name === ".vite" || entry.name === ".vite-temp") continue;
|
|
14155
|
-
const sourceEntry =
|
|
14156
|
-
const targetEntry =
|
|
15576
|
+
const sourceEntry = join17(source, entry.name);
|
|
15577
|
+
const targetEntry = join17(target, entry.name);
|
|
14157
15578
|
let sourceStats;
|
|
14158
15579
|
try {
|
|
14159
15580
|
sourceStats = lstatSync4(sourceEntry);
|
|
@@ -14163,7 +15584,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14163
15584
|
}
|
|
14164
15585
|
if (sourceStats.isSymbolicLink()) {
|
|
14165
15586
|
try {
|
|
14166
|
-
if (!
|
|
15587
|
+
if (!statSync8(sourceEntry).isDirectory()) continue;
|
|
14167
15588
|
} catch {
|
|
14168
15589
|
continue;
|
|
14169
15590
|
}
|
|
@@ -14173,7 +15594,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14173
15594
|
if (entry.name.startsWith("@")) {
|
|
14174
15595
|
let pkgs;
|
|
14175
15596
|
try {
|
|
14176
|
-
|
|
15597
|
+
mkdirSync23(targetEntry);
|
|
14177
15598
|
pkgs = readdirSync5(sourceEntry);
|
|
14178
15599
|
} catch {
|
|
14179
15600
|
done.failed++;
|
|
@@ -14181,7 +15602,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14181
15602
|
}
|
|
14182
15603
|
for (const pkg of pkgs) {
|
|
14183
15604
|
try {
|
|
14184
|
-
symlinkSync(
|
|
15605
|
+
symlinkSync(join17(sourceEntry, pkg), join17(targetEntry, pkg), linkType);
|
|
14185
15606
|
done.linked++;
|
|
14186
15607
|
} catch {
|
|
14187
15608
|
done.failed++;
|
|
@@ -14203,7 +15624,7 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
|
|
|
14203
15624
|
const started = now();
|
|
14204
15625
|
const timeout = deadlineAt !== void 0 ? Math.max(1, Math.min(PROBE_RUN_TIMEOUT_MS, deadlineAt - started)) : PROBE_RUN_TIMEOUT_MS;
|
|
14205
15626
|
const exposed = exposeDependencies(probeTree, dependencyRoot);
|
|
14206
|
-
const r =
|
|
15627
|
+
const r = spawnSync7(
|
|
14207
15628
|
process.execPath,
|
|
14208
15629
|
[findVitestBin(dependencyRoot), "run", "--reporter=json", ...probes],
|
|
14209
15630
|
{
|
|
@@ -14216,10 +15637,12 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
|
|
|
14216
15637
|
maxBuffer: 64 * 1024 * 1024
|
|
14217
15638
|
}
|
|
14218
15639
|
);
|
|
14219
|
-
if (r.error)
|
|
15640
|
+
if (r.error)
|
|
15641
|
+
throw new ProbeRunFailure(r.error.message, runnerFailureReason(r));
|
|
14220
15642
|
if (r.signal) {
|
|
14221
|
-
throw new
|
|
14222
|
-
`runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}
|
|
15643
|
+
throw new ProbeRunFailure(
|
|
15644
|
+
`runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}`,
|
|
15645
|
+
runnerFailureReason(r)
|
|
14223
15646
|
);
|
|
14224
15647
|
}
|
|
14225
15648
|
return {
|
|
@@ -14325,10 +15748,10 @@ function newSideLength(header) {
|
|
|
14325
15748
|
__name(newSideLength, "newSideLength");
|
|
14326
15749
|
function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
14327
15750
|
const { patch: _patch, ...meta } = hunk;
|
|
14328
|
-
const abs =
|
|
15751
|
+
const abs = join17(probeTree, hunk.file);
|
|
14329
15752
|
let original;
|
|
14330
15753
|
try {
|
|
14331
|
-
original =
|
|
15754
|
+
original = readFileSync25(abs, "utf8");
|
|
14332
15755
|
} catch (e) {
|
|
14333
15756
|
return {
|
|
14334
15757
|
...meta,
|
|
@@ -14336,7 +15759,7 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
|
14336
15759
|
detail: `the probe tree does not hold ${hunk.file}: ${e instanceof Error ? e.message : String(e)}`
|
|
14337
15760
|
};
|
|
14338
15761
|
}
|
|
14339
|
-
const applied =
|
|
15762
|
+
const applied = spawnSync7("git", ["apply", "--reverse", "-"], {
|
|
14340
15763
|
cwd: probeTree,
|
|
14341
15764
|
input: hunk.patch,
|
|
14342
15765
|
encoding: "utf8"
|
|
@@ -14354,21 +15777,21 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
|
14354
15777
|
const detail = verdict === "killed" ? "the suite went red with this hunk reverted \u2014 a test covers this change" : verdict === "survived" ? "every affected test still PASSED with this hunk reverted \u2014 no test in this diff fails when the change is undone" : "the tree with this hunk reverted produced no clean verdict (likely a compile or import error) \u2014 not evidence either way";
|
|
14355
15778
|
return { ...meta, verdict, detail };
|
|
14356
15779
|
} finally {
|
|
14357
|
-
|
|
14358
|
-
|
|
15780
|
+
mkdirSync23(dirname20(abs), { recursive: true });
|
|
15781
|
+
writeFileSync23(abs, original, "utf8");
|
|
14359
15782
|
}
|
|
14360
15783
|
}
|
|
14361
15784
|
__name(runOneHunkProbe, "runOneHunkProbe");
|
|
14362
15785
|
function runControlMutant(probeTree, probeFile, deadlineAt, now = Date.now) {
|
|
14363
|
-
const abs =
|
|
15786
|
+
const abs = join17(probeTree, probeFile);
|
|
14364
15787
|
let original;
|
|
14365
15788
|
try {
|
|
14366
|
-
original =
|
|
15789
|
+
original = readFileSync25(abs, "utf8");
|
|
14367
15790
|
} catch {
|
|
14368
15791
|
return null;
|
|
14369
15792
|
}
|
|
14370
15793
|
try {
|
|
14371
|
-
|
|
15794
|
+
writeFileSync23(
|
|
14372
15795
|
abs,
|
|
14373
15796
|
`${original}
|
|
14374
15797
|
;import { it as __qcIt, expect as __qcExpect } from 'vitest';
|
|
@@ -14381,13 +15804,13 @@ __qcIt('QWEN-REVIEW-POSITIVE-CONTROL', () => {
|
|
|
14381
15804
|
const { perFile } = runProbeSuite(probeTree, [probeFile], deadlineAt, now);
|
|
14382
15805
|
return perFile.some((r) => r.verdict === "gated");
|
|
14383
15806
|
} finally {
|
|
14384
|
-
|
|
15807
|
+
writeFileSync23(abs, original, "utf8");
|
|
14385
15808
|
}
|
|
14386
15809
|
}
|
|
14387
15810
|
__name(runControlMutant, "runControlMutant");
|
|
14388
15811
|
function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dependencyRoot = probeTree) {
|
|
14389
|
-
const abs =
|
|
14390
|
-
const original =
|
|
15812
|
+
const abs = join17(probeTree, mutant.file);
|
|
15813
|
+
const original = readFileSync25(abs, "utf8");
|
|
14391
15814
|
const lines2 = original.split("\n");
|
|
14392
15815
|
if ((lines2[mutant.line - 1] ?? "").trim() !== mutant.statement) {
|
|
14393
15816
|
return {
|
|
@@ -14403,7 +15826,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
|
|
|
14403
15826
|
lines2.splice(mutant.line - 1, 1);
|
|
14404
15827
|
}
|
|
14405
15828
|
try {
|
|
14406
|
-
|
|
15829
|
+
writeFileSync23(abs, lines2.join("\n"), "utf8");
|
|
14407
15830
|
const { perFile } = runProbeSuite(
|
|
14408
15831
|
probeTree,
|
|
14409
15832
|
probes,
|
|
@@ -14415,7 +15838,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
|
|
|
14415
15838
|
const detail = verdict === "killed" ? `the suite went red with this statement ${what} \u2014 a test catches it` : verdict === "survived" ? `every affected test still PASSED with this statement ${what} \u2014 no test fails ${mutant.mutated === void 0 ? "when it is removed" : "when it changes"}` : "the mutated tree produced no clean verdict (likely a compile or import error) \u2014 not evidence either way";
|
|
14416
15839
|
return { ...mutant, verdict, detail };
|
|
14417
15840
|
} finally {
|
|
14418
|
-
|
|
15841
|
+
writeFileSync23(abs, original, "utf8");
|
|
14419
15842
|
}
|
|
14420
15843
|
}
|
|
14421
15844
|
__name(runOneMutant, "runOneMutant");
|
|
@@ -14454,9 +15877,9 @@ async function runTestEfficacy(args) {
|
|
|
14454
15877
|
const now = args.now ?? Date.now;
|
|
14455
15878
|
const startedAt = now();
|
|
14456
15879
|
const { report, worktree, base, out } = args;
|
|
14457
|
-
const plan = JSON.parse(
|
|
15880
|
+
const plan = JSON.parse(readFileSync25(report, "utf8"));
|
|
14458
15881
|
const rootPkg = JSON.parse(
|
|
14459
|
-
|
|
15882
|
+
readFileSync25(`${worktree}/package.json`, "utf8")
|
|
14460
15883
|
);
|
|
14461
15884
|
const globs = rootPkg.workspaces ?? [];
|
|
14462
15885
|
const { unreachable, probes, revert } = planTestEfficacy(
|
|
@@ -14464,8 +15887,8 @@ async function runTestEfficacy(args) {
|
|
|
14464
15887
|
globs
|
|
14465
15888
|
);
|
|
14466
15889
|
for (const p of revert) {
|
|
14467
|
-
const norm =
|
|
14468
|
-
const root =
|
|
15890
|
+
const norm = join17(worktree, p);
|
|
15891
|
+
const root = join17(worktree, ".");
|
|
14469
15892
|
if (isAbsolute3(p) || norm !== root && !norm.startsWith(root + sep3)) {
|
|
14470
15893
|
throw new Error(
|
|
14471
15894
|
`refusing to run: revert path escapes the worktree: ${JSON.stringify(p)}`
|
|
@@ -14568,7 +15991,12 @@ async function runTestEfficacy(args) {
|
|
|
14568
15991
|
String(sweep?.stderr ?? "")
|
|
14569
15992
|
);
|
|
14570
15993
|
for (const file of probes) {
|
|
14571
|
-
results.push({
|
|
15994
|
+
results.push({
|
|
15995
|
+
file,
|
|
15996
|
+
verdict: "inconclusive",
|
|
15997
|
+
reason: "not-run",
|
|
15998
|
+
detail
|
|
15999
|
+
});
|
|
14572
16000
|
}
|
|
14573
16001
|
for (const c of candidates) {
|
|
14574
16002
|
mutantResults.push({ ...c, verdict: "inconclusive", detail });
|
|
@@ -14618,6 +16046,21 @@ async function runTestEfficacy(args) {
|
|
|
14618
16046
|
);
|
|
14619
16047
|
}
|
|
14620
16048
|
for (const c of harnessValidated === false ? [] : candidates) {
|
|
16049
|
+
const heldDetail = heldForRedCollocatedTest(
|
|
16050
|
+
"mutant",
|
|
16051
|
+
c.file,
|
|
16052
|
+
probes,
|
|
16053
|
+
greenProbes,
|
|
16054
|
+
baseline.perFile
|
|
16055
|
+
);
|
|
16056
|
+
if (heldDetail) {
|
|
16057
|
+
mutantResults.push({
|
|
16058
|
+
...c,
|
|
16059
|
+
verdict: "inconclusive",
|
|
16060
|
+
detail: heldDetail
|
|
16061
|
+
});
|
|
16062
|
+
continue;
|
|
16063
|
+
}
|
|
14621
16064
|
const remaining = mutantDeadline - now();
|
|
14622
16065
|
if (!fitsAnotherMutantRun(remaining, estimatedRunMs)) {
|
|
14623
16066
|
mutantsSkippedForBudget = candidates.length - mutantResults.length;
|
|
@@ -14635,13 +16078,19 @@ async function runTestEfficacy(args) {
|
|
|
14635
16078
|
);
|
|
14636
16079
|
}
|
|
14637
16080
|
for (const h of harnessValidated === false ? [] : hunkCandidates) {
|
|
14638
|
-
const
|
|
14639
|
-
|
|
16081
|
+
const heldDetail = heldForRedCollocatedTest(
|
|
16082
|
+
"hunk",
|
|
16083
|
+
h.file,
|
|
16084
|
+
probes,
|
|
16085
|
+
greenProbes,
|
|
16086
|
+
baseline.perFile
|
|
16087
|
+
);
|
|
16088
|
+
if (heldDetail) {
|
|
14640
16089
|
const { patch: _patch, ...meta } = h;
|
|
14641
16090
|
hunkResults.push({
|
|
14642
16091
|
...meta,
|
|
14643
16092
|
verdict: "inconclusive",
|
|
14644
|
-
detail:
|
|
16093
|
+
detail: heldDetail
|
|
14645
16094
|
});
|
|
14646
16095
|
continue;
|
|
14647
16096
|
}
|
|
@@ -14692,11 +16141,14 @@ async function runTestEfficacy(args) {
|
|
|
14692
16141
|
noteDependencyFarm(revertRun.exposed);
|
|
14693
16142
|
results.push(...revertRun.perFile);
|
|
14694
16143
|
} catch (e) {
|
|
14695
|
-
const
|
|
16144
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
16145
|
+
const detail = `probe could not run: ${message}`;
|
|
16146
|
+
const reason = e instanceof ProbeRunFailure ? e.reason : "not-run";
|
|
14696
16147
|
results.push(
|
|
14697
16148
|
...probes.map((file) => ({
|
|
14698
16149
|
file,
|
|
14699
16150
|
verdict: "inconclusive",
|
|
16151
|
+
reason,
|
|
14700
16152
|
detail
|
|
14701
16153
|
}))
|
|
14702
16154
|
);
|
|
@@ -14708,7 +16160,7 @@ async function runTestEfficacy(args) {
|
|
|
14708
16160
|
} catch (e) {
|
|
14709
16161
|
removeError = e;
|
|
14710
16162
|
}
|
|
14711
|
-
if (
|
|
16163
|
+
if (existsSync11(probeTree)) {
|
|
14712
16164
|
cleanupFailure = probeCleanupFailureDetail(
|
|
14713
16165
|
probeTree,
|
|
14714
16166
|
removeError,
|
|
@@ -14732,10 +16184,15 @@ async function runTestEfficacy(args) {
|
|
|
14732
16184
|
h.detail = detail;
|
|
14733
16185
|
}
|
|
14734
16186
|
}
|
|
14735
|
-
for (
|
|
16187
|
+
for (let i = 0; i < results.length; i++) {
|
|
16188
|
+
const r = results[i];
|
|
14736
16189
|
if (r.verdict === "inert") {
|
|
14737
|
-
|
|
14738
|
-
|
|
16190
|
+
results[i] = {
|
|
16191
|
+
file: r.file,
|
|
16192
|
+
verdict: "inconclusive",
|
|
16193
|
+
reason: "control-failed",
|
|
16194
|
+
detail
|
|
16195
|
+
};
|
|
14739
16196
|
}
|
|
14740
16197
|
}
|
|
14741
16198
|
}
|
|
@@ -14802,8 +16259,8 @@ async function runTestEfficacy(args) {
|
|
|
14802
16259
|
findings,
|
|
14803
16260
|
cleanupFailure
|
|
14804
16261
|
};
|
|
14805
|
-
|
|
14806
|
-
|
|
16262
|
+
mkdirSync23(dirname20(out), { recursive: true });
|
|
16263
|
+
writeFileSync23(out, JSON.stringify(result, null, 2), "utf8");
|
|
14807
16264
|
writeStdoutLine(
|
|
14808
16265
|
`Wrote test-efficacy report to ${out} (${unreachable.length} unreachable, ${results.length} probed, ${mutantResults.length} mutant(s), ${hunkResults.length} hunk probe(s), ${findings.length} finding(s))`
|
|
14809
16266
|
);
|
|
@@ -14880,8 +16337,8 @@ var testEfficacyCommand = {
|
|
|
14880
16337
|
|
|
14881
16338
|
// packages/cli/src/commands/review/test-plan.ts
|
|
14882
16339
|
init_esbuild_shims();
|
|
14883
|
-
import { existsSync as
|
|
14884
|
-
import { dirname as
|
|
16340
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync24, readFileSync as readFileSync26, writeFileSync as writeFileSync24 } from "node:fs";
|
|
16341
|
+
import { dirname as dirname21, join as join18, normalize, resolve as resolve20 } from "node:path";
|
|
14885
16342
|
var PLAN_NAME_RE = /(test\s*plan|\btesting\b|how\s+(?:has\s+this|to)\s+(?:been\s+)?test(?:ed)?|测试计划|测试方案|测试步骤)/i;
|
|
14886
16343
|
var HEADING_LINE_RE = /^(#{1,6})(?:[ \t](.*))?$/;
|
|
14887
16344
|
var BOLD_LINE_RE = /^\*\*([^*\n]+)\*\*:?\s*$/;
|
|
@@ -15084,7 +16541,7 @@ function rulePath(text, worktree, changed) {
|
|
|
15084
16541
|
};
|
|
15085
16542
|
}
|
|
15086
16543
|
const ignored = isGitIgnored(worktree, path3);
|
|
15087
|
-
if (
|
|
16544
|
+
if (existsSync12(join18(worktree, path3))) {
|
|
15088
16545
|
return {
|
|
15089
16546
|
kind: "path",
|
|
15090
16547
|
text,
|
|
@@ -15155,7 +16612,7 @@ function ruleCommand(text, worktree, buildTest) {
|
|
|
15155
16612
|
let rootScripts = [];
|
|
15156
16613
|
try {
|
|
15157
16614
|
const rootPkg = JSON.parse(
|
|
15158
|
-
|
|
16615
|
+
readFileSync26(join18(worktree, "package.json"), "utf8")
|
|
15159
16616
|
);
|
|
15160
16617
|
rootScripts = Object.keys(rootPkg.scripts ?? {});
|
|
15161
16618
|
} catch {
|
|
@@ -15233,7 +16690,7 @@ __name(fetchPrBody, "fetchPrBody");
|
|
|
15233
16690
|
function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
15234
16691
|
let plan;
|
|
15235
16692
|
try {
|
|
15236
|
-
plan = JSON.parse(
|
|
16693
|
+
plan = JSON.parse(readFileSync26(args.plan, "utf8"));
|
|
15237
16694
|
} catch (err) {
|
|
15238
16695
|
throw new Error(
|
|
15239
16696
|
`test-plan: cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -15260,7 +16717,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
|
15260
16717
|
note: "the PR description has no Test Plan section"
|
|
15261
16718
|
};
|
|
15262
16719
|
}
|
|
15263
|
-
const worktree =
|
|
16720
|
+
const worktree = resolve20(args.worktree);
|
|
15264
16721
|
const changed = new Set(
|
|
15265
16722
|
(plan.files ?? []).map((f) => normalize(String(f.path)))
|
|
15266
16723
|
);
|
|
@@ -15268,7 +16725,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
|
15268
16725
|
if (args.buildTest) {
|
|
15269
16726
|
try {
|
|
15270
16727
|
buildTest = JSON.parse(
|
|
15271
|
-
|
|
16728
|
+
readFileSync26(args.buildTest, "utf8")
|
|
15272
16729
|
);
|
|
15273
16730
|
} catch {
|
|
15274
16731
|
buildTest = null;
|
|
@@ -15325,8 +16782,8 @@ var testPlanCommand = {
|
|
|
15325
16782
|
try {
|
|
15326
16783
|
const report = runTestPlan(args);
|
|
15327
16784
|
if (args.out) {
|
|
15328
|
-
|
|
15329
|
-
|
|
16785
|
+
mkdirSync24(dirname21(resolve20(args.out)), { recursive: true });
|
|
16786
|
+
writeFileSync24(resolve20(args.out), JSON.stringify(report, null, 2));
|
|
15330
16787
|
}
|
|
15331
16788
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
15332
16789
|
writeStderrLine(`test-plan: ${report.note}`);
|
|
@@ -15340,8 +16797,8 @@ var testPlanCommand = {
|
|
|
15340
16797
|
// packages/cli/src/commands/review/cleanup.ts
|
|
15341
16798
|
init_esbuild_shims();
|
|
15342
16799
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
15343
|
-
import { existsSync as
|
|
15344
|
-
import { join as
|
|
16800
|
+
import { existsSync as existsSync13, readFileSync as readFileSync27, readdirSync as readdirSync6, rmSync as rmSync10 } from "node:fs";
|
|
16801
|
+
import { join as join19 } from "node:path";
|
|
15345
16802
|
var AUTOMATION_MARKER = "<!-- qwen-";
|
|
15346
16803
|
function isAutomationComment(body) {
|
|
15347
16804
|
return (body ?? "").trimStart().startsWith(AUTOMATION_MARKER);
|
|
@@ -15372,7 +16829,7 @@ var OWNER_REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
|
15372
16829
|
function readAuditWindow(target, expectedPrNumber) {
|
|
15373
16830
|
let raw;
|
|
15374
16831
|
try {
|
|
15375
|
-
raw =
|
|
16832
|
+
raw = readFileSync27(tmpFile(target, "fetch.json"), "utf8");
|
|
15376
16833
|
} catch (err) {
|
|
15377
16834
|
const code = err.code;
|
|
15378
16835
|
return code === "ENOENT" ? { skip: "no fetch report" } : {
|
|
@@ -15419,7 +16876,7 @@ function readSubmitReceipt(target) {
|
|
|
15419
16876
|
try {
|
|
15420
16877
|
return new Set(
|
|
15421
16878
|
parseReceiptIds(
|
|
15422
|
-
|
|
16879
|
+
readFileSync27(tmpFile(target, "submit-receipt.json"), "utf8")
|
|
15423
16880
|
)
|
|
15424
16881
|
);
|
|
15425
16882
|
} catch {
|
|
@@ -15523,7 +16980,7 @@ function runCleanup(target) {
|
|
|
15523
16980
|
report("probe worktree", probeWorktreePath(wt));
|
|
15524
16981
|
report("base worktree", baseWorktreePath(wt));
|
|
15525
16982
|
try {
|
|
15526
|
-
|
|
16983
|
+
rmSync10(`${baseWorktreePath(wt)}.lock`, { recursive: true, force: true });
|
|
15527
16984
|
} catch (err) {
|
|
15528
16985
|
writeStderrLine(
|
|
15529
16986
|
`note: could not remove base lock ${baseWorktreePath(wt)}.lock: ${err.message}`
|
|
@@ -15546,7 +17003,7 @@ function runCleanup(target) {
|
|
|
15546
17003
|
const prefix = tmpPrefix(target);
|
|
15547
17004
|
let tmpEntries = [];
|
|
15548
17005
|
try {
|
|
15549
|
-
tmpEntries =
|
|
17006
|
+
tmpEntries = existsSync13(REVIEW_TMP_DIR) ? readdirSync6(REVIEW_TMP_DIR) : [];
|
|
15550
17007
|
} catch (err) {
|
|
15551
17008
|
writeStderrLine(
|
|
15552
17009
|
`Failed to read ${REVIEW_TMP_DIR}: ${err.message}`
|
|
@@ -15554,9 +17011,9 @@ function runCleanup(target) {
|
|
|
15554
17011
|
}
|
|
15555
17012
|
for (const file of tmpEntries) {
|
|
15556
17013
|
if (!file.startsWith(prefix)) continue;
|
|
15557
|
-
const full =
|
|
17014
|
+
const full = join19(REVIEW_TMP_DIR, file);
|
|
15558
17015
|
try {
|
|
15559
|
-
|
|
17016
|
+
rmSync10(full, { recursive: true, force: true });
|
|
15560
17017
|
writeStdoutLine(`Removed temp file: ${full}`);
|
|
15561
17018
|
removedAny = true;
|
|
15562
17019
|
} catch (err) {
|
|
@@ -15588,8 +17045,8 @@ var cleanupCommand = {
|
|
|
15588
17045
|
// packages/cli/src/commands/review/run.ts
|
|
15589
17046
|
init_esbuild_shims();
|
|
15590
17047
|
import { spawn, execFileSync as execFileSync6 } from "node:child_process";
|
|
15591
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
15592
|
-
import { join as
|
|
17048
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync28, realpathSync as realpathSync3, statSync as statSync9 } from "node:fs";
|
|
17049
|
+
import { dirname as dirname22, join as join20, resolve as resolve21 } from "node:path";
|
|
15593
17050
|
var COMPOSED_PATTERN = /^qwen-review-.*composed\.json$/;
|
|
15594
17051
|
var COMPOSED_POLL_MS = 250;
|
|
15595
17052
|
var SIGNAL_EXIT_CODES = {
|
|
@@ -15623,10 +17080,10 @@ function newestArtifactSince(dir, pattern, startMs) {
|
|
|
15623
17080
|
}
|
|
15624
17081
|
for (const name of names) {
|
|
15625
17082
|
if (!pattern.test(name)) continue;
|
|
15626
|
-
const path3 =
|
|
17083
|
+
const path3 = join20(dir, name);
|
|
15627
17084
|
let mtime;
|
|
15628
17085
|
try {
|
|
15629
|
-
mtime =
|
|
17086
|
+
mtime = statSync9(path3).mtimeMs;
|
|
15630
17087
|
} catch {
|
|
15631
17088
|
continue;
|
|
15632
17089
|
}
|
|
@@ -15644,7 +17101,7 @@ function exitCodeFor(completed, event, failOn) {
|
|
|
15644
17101
|
__name(exitCodeFor, "exitCodeFor");
|
|
15645
17102
|
function readComposed(path3) {
|
|
15646
17103
|
try {
|
|
15647
|
-
const parsed = JSON.parse(
|
|
17104
|
+
const parsed = JSON.parse(readFileSync28(path3, "utf8"));
|
|
15648
17105
|
return typeof parsed.event === "string" ? parsed : null;
|
|
15649
17106
|
} catch {
|
|
15650
17107
|
return null;
|
|
@@ -15667,6 +17124,23 @@ function killProcessGroup(pid, signal) {
|
|
|
15667
17124
|
}
|
|
15668
17125
|
}
|
|
15669
17126
|
__name(killProcessGroup, "killProcessGroup");
|
|
17127
|
+
function childEnv() {
|
|
17128
|
+
const env = { ...process.env };
|
|
17129
|
+
const inherited = env["QWEN_CODE_CLI"];
|
|
17130
|
+
const ownEntry = process.argv[1];
|
|
17131
|
+
if (!inherited || !ownEntry) {
|
|
17132
|
+
return env;
|
|
17133
|
+
}
|
|
17134
|
+
try {
|
|
17135
|
+
if (dirname22(realpathSync3(inherited)) === dirname22(realpathSync3(ownEntry))) {
|
|
17136
|
+
return env;
|
|
17137
|
+
}
|
|
17138
|
+
} catch {
|
|
17139
|
+
}
|
|
17140
|
+
env["QWEN_CODE_CLI"] = "";
|
|
17141
|
+
return env;
|
|
17142
|
+
}
|
|
17143
|
+
__name(childEnv, "childEnv");
|
|
15670
17144
|
async function runReview(args) {
|
|
15671
17145
|
const startMs = Date.now();
|
|
15672
17146
|
const prompt = buildReviewPrompt(args);
|
|
@@ -15682,6 +17156,7 @@ async function runReview(args) {
|
|
|
15682
17156
|
args.approvalMode
|
|
15683
17157
|
],
|
|
15684
17158
|
{
|
|
17159
|
+
env: childEnv(),
|
|
15685
17160
|
// stdin CLOSED, not inherited: piped input would be prepended to the
|
|
15686
17161
|
// prompt and the leading `/` would no longer be the first character —
|
|
15687
17162
|
// the slash command would reach the model as plain text.
|
|
@@ -15693,9 +17168,9 @@ async function runReview(args) {
|
|
|
15693
17168
|
}
|
|
15694
17169
|
);
|
|
15695
17170
|
if (!args.quiet) {
|
|
15696
|
-
const writeProgress = /* @__PURE__ */ __name((
|
|
17171
|
+
const writeProgress = /* @__PURE__ */ __name((chunk2) => {
|
|
15697
17172
|
try {
|
|
15698
|
-
process.stderr.write(
|
|
17173
|
+
process.stderr.write(chunk2);
|
|
15699
17174
|
} catch {
|
|
15700
17175
|
}
|
|
15701
17176
|
}, "writeProgress");
|
|
@@ -15779,8 +17254,8 @@ async function runReview(args) {
|
|
|
15779
17254
|
downgraded: composed?.downgraded ?? false,
|
|
15780
17255
|
downgradedFrom: composed?.downgradedFrom ?? null,
|
|
15781
17256
|
remediation: composed?.remediation ?? [],
|
|
15782
|
-
composedPath: composedPath ?
|
|
15783
|
-
reportPath: reportPath ?
|
|
17257
|
+
composedPath: composedPath ? resolve21(composedPath) : null,
|
|
17258
|
+
reportPath: reportPath ? resolve21(reportPath) : null,
|
|
15784
17259
|
childExitCode,
|
|
15785
17260
|
childSignal,
|
|
15786
17261
|
timedOut,
|
|
@@ -15794,7 +17269,7 @@ async function runReview(args) {
|
|
|
15794
17269
|
writeStdoutLine(result.verdictLine ?? `Event: ${result.event}`);
|
|
15795
17270
|
if (result.reportPath) writeStdoutLine(`Report: ${result.reportPath}`);
|
|
15796
17271
|
} else {
|
|
15797
|
-
const detail = composedPath !== null ? `a composed verdict was found at ${
|
|
17272
|
+
const detail = composedPath !== null ? `a composed verdict was found at ${resolve21(composedPath)} but could not be parsed` : "no composed verdict was produced";
|
|
15798
17273
|
writeStdoutLine(
|
|
15799
17274
|
timedOut ? "Review did not complete: timed out." : `Review did not complete: ${detail}${childExitCode !== null ? ` (CLI exit ${childExitCode})` : ""}${childSignal !== null ? ` (killed by ${childSignal})` : ""}.`
|
|
15800
17275
|
);
|
|
@@ -15861,9 +17336,9 @@ var runCommand = {
|
|
|
15861
17336
|
var reviewCommand = {
|
|
15862
17337
|
command: "review",
|
|
15863
17338
|
describe: "Run a review non-interactively (`run`), plus the internal helpers used by the /review skill (PR worktree setup, context fetch, rules loading, presubmit checks, cleanup)",
|
|
15864
|
-
builder: /* @__PURE__ */ __name((yargs) => yargs.command(runCommand).command(parseArgsCommand).command(fetchPrCommand).command(captureLocalCommand).command(planDiffCommand).command(prContextCommand).command(commentStatusCommand).command(loadRulesCommand).command(agentPromptCommand).command(buildTestCommand).command(baseTreeCommand).command(testDeltaCommand).command(extractStepCommand).command(scriptLintCommand).command(resolveAnchorsCommand).command(checkCoverageCommand).command(presubmitCommand).command(testEfficacyCommand).command(testPlanCommand).command(findingsCommand).command(composeReviewCommand).command(submitCommand).command(cleanupCommand).demandCommand(
|
|
17339
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.command(runCommand).command(parseArgsCommand).command(fetchPrCommand).command(captureLocalCommand).command(planDiffCommand).command(prContextCommand).command(commentStatusCommand).command(loadRulesCommand).command(agentPromptCommand).command(buildTestCommand).command(baseTreeCommand).command(testDeltaCommand).command(driveCommand).command(mockProviderCommand).command(extractStepCommand).command(scriptLintCommand).command(resolveAnchorsCommand).command(checkCoverageCommand).command(presubmitCommand).command(testEfficacyCommand).command(testPlanCommand).command(findingsCommand).command(publishAssetsCommand).command(composeReviewCommand).command(submitCommand).command(cleanupCommand).demandCommand(
|
|
15865
17340
|
1,
|
|
15866
|
-
"Specify a subcommand: run, parse-args, fetch-pr, capture-local, plan-diff, pr-context, comment-status, load-rules, agent-prompt, build-test, base-tree, test-delta, extract-step, script-lint, resolve-anchors, check-coverage, presubmit, test-efficacy, test-plan, findings, compose-review, submit, or cleanup."
|
|
17341
|
+
"Specify a subcommand: run, parse-args, fetch-pr, capture-local, plan-diff, pr-context, comment-status, load-rules, agent-prompt, build-test, base-tree, test-delta, drive, mock-provider, extract-step, script-lint, resolve-anchors, check-coverage, presubmit, test-efficacy, test-plan, findings, publish-assets, compose-review, submit, or cleanup."
|
|
15867
17342
|
).version(false), "builder"),
|
|
15868
17343
|
handler: /* @__PURE__ */ __name(() => {
|
|
15869
17344
|
}, "handler")
|
|
@@ -16063,10 +17538,10 @@ var updateCommand3 = {
|
|
|
16063
17538
|
stdioHelpers,
|
|
16064
17539
|
{ updateEventEmitter }
|
|
16065
17540
|
] = await Promise.all([
|
|
16066
|
-
import("./settings-
|
|
16067
|
-
import("./updateCheck-
|
|
16068
|
-
import("./installationInfo-
|
|
16069
|
-
import("./standalone-update-
|
|
17541
|
+
import("./settings-ILWAOKA4.js"),
|
|
17542
|
+
import("./updateCheck-42SPCLH7.js"),
|
|
17543
|
+
import("./installationInfo-IWMM6DPV.js"),
|
|
17544
|
+
import("./standalone-update-Y5JB4S2K.js"),
|
|
16070
17545
|
import("./stdioHelpers-HGIYTWAY.js"),
|
|
16071
17546
|
import("./updateEventEmitter-5MKC7BLQ.js")
|
|
16072
17547
|
]);
|
|
@@ -17233,7 +18708,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
|
|
|
17233
18708
|
disabledSlashCommands: disabledSlashCommands.length > 0 ? disabledSlashCommands : void 0,
|
|
17234
18709
|
disabledSkillNamesProvider: bareMode || safeMode ? void 0 : disabledSkillNamesProvider,
|
|
17235
18710
|
terminalImageRenderSupportProvider: interactive ? async () => {
|
|
17236
|
-
const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-
|
|
18711
|
+
const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-NWC2RALG.js");
|
|
17237
18712
|
return getTerminalImageRenderSupport();
|
|
17238
18713
|
} : void 0,
|
|
17239
18714
|
disabledSkillLevels: bareMode || safeMode || !Array.isArray(settings.skills?.disabledLevels) ? void 0 : settings.skills.disabledLevels.filter(isSkillLevel),
|
|
@@ -17387,6 +18862,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
|
|
|
17387
18862
|
enableAutoSkill: bareMode || safeMode ? false : settings.memory?.enableAutoSkill ?? false,
|
|
17388
18863
|
autoSkillConfirm: bareMode || safeMode ? false : settings.memory?.autoSkillConfirm ?? true,
|
|
17389
18864
|
memoryAgentTimeoutMinutes: settings.memory?.agentTimeoutMinutes,
|
|
18865
|
+
memoryAgentMaxTurns: settings.memory?.agentMaxTurns,
|
|
17390
18866
|
fastModel: settings.fastModel || void 0,
|
|
17391
18867
|
webSearch: bareMode || safeMode ? void 0 : resolveWebSearchSettings(settings),
|
|
17392
18868
|
visionModel: settings.visionModel || void 0,
|