@qwen-code/qwen-code 0.21.3 → 0.21.4-nightly.20260804.d6f55a1c9
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/model-providers.md +8 -7
- package/bundled/qc-helper/docs/configuration/settings.md +14 -1
- package/bundled/qc-helper/docs/features/channels/github.md +12 -8
- package/bundled/qc-helper/docs/features/code-review.md +16 -1
- package/bundled/qc-helper/docs/features/commands.md +46 -40
- package/bundled/qc-helper/docs/features/mcp.md +11 -3
- 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 +99 -23
- package/chunks/{MaxSizedBox-OJR636GP.js → MaxSizedBox-6J7WTW2Q.js} +16 -15
- package/chunks/{StandaloneSessionPicker-AMMBF5UY.js → StandaloneSessionPicker-J3HNBNWF.js} +30 -29
- package/chunks/{acpAgent-M57LVIQA.js → acpAgent-SGIEWC4W.js} +681 -291
- package/chunks/{agent-PE37ARLM.js → agent-W6IG55Q7.js} +16 -15
- package/chunks/{agent-headless-45ASME4R.js → agent-headless-EAPKUL6K.js} +16 -15
- package/chunks/{anthropicContentGenerator-WCBYB6JG.js → anthropicContentGenerator-3CAHEZBM.js} +4 -2
- package/chunks/{artifact-tool-TBOYEKTY.js → artifact-tool-RROCCJDA.js} +1 -1
- package/chunks/{askUserQuestion-23Y5WZFJ.js → askUserQuestion-2EPPBPUZ.js} +1 -1
- package/chunks/{bridge-K3ZRPO4D.js → bridge-SNQAVLI3.js} +17 -16
- 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-TR7UMQVQ.js} +22 -21
- package/chunks/{chunk-L7FTDKCD.js → chunk-23DFTW2G.js} +21 -3
- package/chunks/{chunk-PWCQRLM5.js → chunk-2AGOQ7QO.js} +1 -1
- package/chunks/{chunk-HUIQ4FYC.js → chunk-2UYGQRYZ.js} +3 -3
- package/chunks/{chunk-4K3RV3OA.js → chunk-36Q4BOUL.js} +243 -108
- package/chunks/{chunk-2MI7ZTRT.js → chunk-3KTHFE53.js} +5 -5
- package/chunks/{chunk-QDWLUXKP.js → chunk-3X3CRYEU.js} +1 -1
- package/chunks/{chunk-YXEVDA66.js → chunk-4LLUH3A7.js} +19 -18
- package/chunks/{chunk-QPSYIV2O.js → chunk-4RNMAX6B.js} +2492 -690
- package/chunks/{chunk-O2ZYHG33.js → chunk-5L6CQ4ZU.js} +131 -67
- package/chunks/{chunk-HEBW74ZW.js → chunk-5WFVHBQR.js} +3 -3
- package/chunks/{chunk-LZV2BZPL.js → chunk-6BBIRJ6Z.js} +2 -2
- package/chunks/{chunk-TZTBZ25J.js → chunk-6WBWIPBF.js} +2 -2
- package/chunks/{chunk-QWDVZH24.js → chunk-7BUWEQD3.js} +3 -3
- package/chunks/{chunk-EGYJRIDF.js → chunk-7DKCE4VO.js} +2 -2
- package/chunks/{chunk-UQDFEU47.js → chunk-7E5BCHGR.js} +1165 -385
- package/chunks/{chunk-X6QNZOTC.js → chunk-7MFQLOHZ.js} +1 -1
- package/chunks/{chunk-MCXMWLFT.js → chunk-7VZZVBW7.js} +5 -5
- package/chunks/{chunk-FF75UUB2.js → chunk-7WE7B63C.js} +1 -1
- package/chunks/{chunk-M5LIY7Y6.js → chunk-AUM6IYIZ.js} +1 -1
- package/chunks/{chunk-K2J5JZ6B.js → chunk-B452JFK7.js} +1 -1
- package/chunks/{chunk-6NE5NTHJ.js → chunk-BDUPVCHA.js} +1 -1
- package/chunks/{chunk-SKSF3LGQ.js → chunk-BH4HRGTW.js} +4 -4
- package/chunks/{chunk-3XF4WT3B.js → chunk-BKLXFYJJ.js} +1 -1
- package/chunks/{chunk-RL6J3BPE.js → chunk-BQUXJJ72.js} +3 -3
- package/chunks/{chunk-H6FIBBXK.js → chunk-C4EK5K64.js} +13 -13
- package/chunks/{chunk-VUNRNRN6.js → chunk-CE424L46.js} +5 -5
- package/chunks/{chunk-DSGJLHM3.js → chunk-CJGLCNDE.js} +3 -3
- package/chunks/{chunk-FXVHVCNA.js → chunk-CTRZ7IDH.js} +1 -1
- package/chunks/{chunk-CFDR3GNL.js → chunk-D7D7RXIJ.js} +11 -0
- package/chunks/{chunk-ZGSUFIL5.js → chunk-DMI5SX2V.js} +1 -1
- package/chunks/{chunk-X6WUYMOV.js → chunk-DTAPI453.js} +2 -2
- package/chunks/{chunk-NW57GHNL.js → chunk-EF2FDVAL.js} +5 -5
- package/chunks/{chunk-BSKUTHLO.js → chunk-EVSV25NF.js} +16 -3
- package/chunks/{chunk-3MWNROHB.js → chunk-FQL6PVUI.js} +3 -3
- package/chunks/{chunk-IF6K42YJ.js → chunk-FSHOUNV4.js} +1 -1
- package/chunks/chunk-HGCQ5SE7.js +136 -0
- package/chunks/{chunk-GMSBXEH4.js → chunk-HTCXG5RY.js} +6 -6
- package/chunks/{chunk-FG63DZQA.js → chunk-I4PRUCND.js} +10 -10
- package/chunks/{chunk-YWMDTMAU.js → chunk-J2QPGVAE.js} +2 -2
- package/chunks/{chunk-RCXCGZG6.js → chunk-KCHQQBHO.js} +4 -4
- package/chunks/{chunk-LGT3YMLN.js → chunk-LEZFUYPM.js} +1 -1
- package/chunks/{chunk-G7XXKQYS.js → chunk-LKRMNRSN.js} +27 -1
- package/chunks/{chunk-WY2ENRDN.js → chunk-LZPIUB47.js} +1 -1
- package/chunks/{chunk-HFVW54NL.js → chunk-MBEMTXQT.js} +9 -2
- package/chunks/{chunk-RPYYTL7E.js → chunk-MGSSJT27.js} +2 -2
- package/chunks/{chunk-H35Q5CEG.js → chunk-MMWH7ERH.js} +6 -6
- package/chunks/{chunk-MUEJ4R3E.js → chunk-MP2XAFAD.js} +3 -3
- package/chunks/{chunk-R7XNRHYD.js → chunk-N64L3CUV.js} +1 -1
- package/chunks/{chunk-EIMKVOSF.js → chunk-NJ4ANA44.js} +1 -1
- package/chunks/{chunk-ZJES4C3I.js → chunk-O6GB7LVR.js} +2 -2
- package/chunks/{chunk-QWW6I2UO.js → chunk-PFQWATDS.js} +410 -39
- package/chunks/{chunk-XUMAD5IT.js → chunk-PMMWMPWV.js} +1 -1
- package/chunks/{chunk-H6ZYP74A.js → chunk-PUQDGXJV.js} +1 -1
- package/chunks/{chunk-XMJO6C44.js → chunk-PYXZLKUJ.js} +3 -3
- package/chunks/{chunk-KXWERR6P.js → chunk-QIS6FQFI.js} +10 -10
- package/chunks/{chunk-22YFMJBW.js → chunk-QK6BFCAL.js} +59 -7
- package/chunks/{chunk-HIXVWF7I.js → chunk-R2JZCL4S.js} +3 -3
- package/chunks/{chunk-N42C2FU3.js → chunk-RJ2KM6NF.js} +1 -1
- package/chunks/{chunk-2LZCLQIS.js → chunk-S5VUT4K2.js} +5 -5
- package/chunks/{chunk-4AGGSHCH.js → chunk-SVR6JPEL.js} +197 -53
- package/chunks/{chunk-SQ3YD5MI.js → chunk-T4LUW2CP.js} +4 -3
- package/chunks/{chunk-BKGA75MU.js → chunk-T7AALAF5.js} +1 -1
- package/chunks/{chunk-YU6TBRTA.js → chunk-URJHXOSO.js} +2 -2
- package/chunks/{chunk-MTR4JWQJ.js → chunk-UZNJUCOI.js} +4 -4
- package/chunks/{chunk-D2GPKXCQ.js → chunk-VQHIIPVE.js} +4 -4
- package/chunks/{chunk-IWGTGH4G.js → chunk-W2W6PMZX.js} +2 -2
- package/chunks/{chunk-SRY26X4H.js → chunk-WVAESIM6.js} +12 -3
- package/chunks/{chunk-5VYFXHY2.js → chunk-XKX3PNZR.js} +3 -3
- package/chunks/{chunk-BKXTHMOZ.js → chunk-XLD2R34T.js} +1 -1
- package/chunks/{chunk-5JPVOYWA.js → chunk-YT2BQ4JH.js} +5 -5
- package/chunks/{chunk-YPUHMQXV.js → chunk-Z5ZLRJFS.js} +1 -1
- package/chunks/{chunk-VA5NLVPT.js → chunk-Z7JILXGX.js} +1 -1
- package/chunks/{chunk-RR224BUP.js → chunk-ZLJQIGCH.js} +45 -1
- package/chunks/{chunk-TXABHVZD.js → chunk-ZNCRSKRP.js} +1 -1
- package/chunks/{chunk-UEJESCS7.js → chunk-ZPPP6VCL.js} +1 -1
- package/chunks/{chunk-Q3G5L5KF.js → chunk-ZQPHKE4K.js} +1 -1
- package/chunks/{computer-use-UJMK6JQU.js → computer-use-NFBEZ73C.js} +16 -15
- package/chunks/{config-utils-7COTJMWK.js → config-utils-E3ZQTHGM.js} +2 -2
- package/chunks/{contextCommand-4YABGF4S.js → contextCommand-HJN4W5PC.js} +18 -17
- package/chunks/{core-runtime-7M4VM3DA.js → core-runtime-Z4IALQ37.js} +16 -15
- package/chunks/{create-sub-session-J32WC64U.js → create-sub-session-6EWLAZD5.js} +1 -1
- package/chunks/{create-sub-session-HOFDW4N7.js → create-sub-session-MJKTT6R4.js} +28 -21
- package/chunks/{cron-create-HMFSHIAR.js → cron-create-CCRIULJE.js} +1 -1
- package/chunks/{cron-delete-ZJFHOX57.js → cron-delete-FCFW6BGP.js} +1 -1
- package/chunks/{cron-list-ZEX2NBMN.js → cron-list-QDBVCHRF.js} +1 -1
- package/chunks/{daemon-status-provider-W2U5Q3J5.js → daemon-status-provider-GR32VNM6.js} +21 -20
- package/chunks/{daemon-trust-policy-IFICWPXP.js → daemon-trust-policy-O4DGWLR5.js} +21 -20
- package/chunks/{daemon-trust-policy-monitor-GX7EO6FW.js → daemon-trust-policy-monitor-5RHH3YSK.js} +21 -20
- package/chunks/{de-A6TI4LBB.js → de-VL76CPLV.js} +4 -0
- package/chunks/{deferred-core-runtime-U36OOOKE.js → deferred-core-runtime-A5CKPGQZ.js} +16 -15
- package/chunks/{display-image-VS3TXYQH.js → display-image-GUJA352B.js} +2 -2
- 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-ZB4EHBSD.js} +16 -15
- package/chunks/{edit-J7SW6UP6.js → edit-QSTJ353U.js} +17 -16
- package/chunks/{en-L4UQDLWW.js → en-IRJ2WN5T.js} +4 -0
- package/chunks/{enter-worktree-NRLOHRB6.js → enter-worktree-R73VWVFF.js} +16 -15
- package/chunks/{enterPlanMode-GDSTEFSM.js → enterPlanMode-OWFAGX5J.js} +16 -15
- package/chunks/{environment-KHYEKG5W.js → environment-57MAPDBT.js} +18 -17
- package/chunks/{errors-VNLAJV3O.js → errors-5BKZ3KMI.js} +18 -17
- package/chunks/{exit-worktree-ARVID7DF.js → exit-worktree-6O2324LA.js} +16 -15
- package/chunks/{exitPlanMode-4ZFY7KUH.js → exitPlanMode-GCRUFTNX.js} +16 -15
- package/chunks/{fast-path-KA65VREJ.js → fast-path-VN3BV4WN.js} +11 -2
- package/chunks/{fr-QF7RI5ZE.js → fr-GK3HQYVB.js} +4 -0
- package/chunks/{gemini-5PMCNPF7.js → gemini-K43YNDW7.js} +50 -48
- package/chunks/{geminiContentGenerator-NFSYJ4HA.js → geminiContentGenerator-BTPPSMCF.js} +1 -1
- package/chunks/{glob-F3EAUDAX.js → glob-4MLY2HOS.js} +16 -15
- package/chunks/{goal-tools-SPQ5M6D3.js → goal-tools-3AZKX5WQ.js} +2 -18
- package/chunks/{grep-UJMPVZER.js → grep-XFBSAUJW.js} +16 -15
- package/chunks/{handleAutoUpdate-MG4FRZPN.js → handleAutoUpdate-HWVO6M65.js} +20 -19
- package/chunks/{i18n-EVHV4ZF3.js → i18n-MASWUB3I.js} +17 -16
- package/chunks/{image-gen-UC7XVHI7.js → image-gen-XLFJRDFA.js} +2 -2
- package/chunks/{initializer-O5B6HUBI.js → initializer-M6JYLRYA.js} +21 -20
- package/chunks/{installationInfo-2LIKCNVX.js → installationInfo-TEJOXP5W.js} +17 -16
- package/chunks/{ja-URQESVGW.js → ja-3FA46IKD.js} +4 -0
- package/chunks/{list-BPKIE3Z4.js → list-UABKY6YY.js} +24 -23
- package/chunks/{list-agents-TWEGRJWO.js → list-agents-GC4BRET7.js} +1 -1
- package/chunks/{loadedSettingsAdapter-NGLFF4V4.js → loadedSettingsAdapter-L7PIVGJX.js} +21 -20
- package/chunks/{loggingContentGenerator-7TFESRG5.js → loggingContentGenerator-W7QO23AD.js} +8 -8
- package/chunks/{loop-wakeup-6LLEOQYV.js → loop-wakeup-SQQSBMEY.js} +2 -2
- package/chunks/{ls-H5WX22VK.js → ls-63CC3NNX.js} +1 -1
- package/chunks/{lsp-G6FXYEIS.js → lsp-P6NVJVNT.js} +1 -1
- package/chunks/{managed-npm-update-CLZGGV7F.js → managed-npm-update-WQ2J42H7.js} +17 -16
- package/chunks/{mcp-X5OZOOXX.js → mcp-A652OREB.js} +21 -20
- package/chunks/{monitor-TGSVSAZZ.js → monitor-Y7YAHCA6.js} +16 -15
- package/chunks/{nonInteractiveCli-GUMJ5JLG.js → nonInteractiveCli-EKLP5WCN.js} +48 -46
- package/chunks/{notebook-edit-EZISE7IC.js → notebook-edit-MALKVMST.js} +17 -16
- package/chunks/{openaiContentGenerator-ZAKS5A6P.js → openaiContentGenerator-WM2TUU7E.js} +8 -8
- package/chunks/{pidfile-PCMBX4LR.js → pidfile-QBNVWD7M.js} +16 -15
- package/chunks/{processUtils-2CSVE5BW.js → processUtils-4L46PM2I.js} +2 -2
- package/chunks/{pt-EEEQXRUI.js → pt-3AOBKPIJ.js} +4 -0
- package/chunks/{qwenContentGenerator-S3H6PIC4.js → qwenContentGenerator-GG6SY4YP.js} +17 -17
- package/chunks/{qwenOAuth2-MNU425VE.js → qwenOAuth2-PE27GJRG.js} +1 -1
- package/chunks/{read-file-HXJXHBI6.js → read-file-IHXKC6D3.js} +6 -6
- package/chunks/{read-mcp-resource-DRZSI3HE.js → read-mcp-resource-RTSSEER5.js} +1 -1
- package/chunks/{record-artifact-IPIRWMFY.js → record-artifact-L3BAHQFN.js} +2 -2
- package/chunks/{resumeHistoryUtils-4QAMC2S6.js → resumeHistoryUtils-BWVAHKRZ.js} +19 -18
- package/chunks/{ripGrep-MCETPF2E.js → ripGrep-7ASLNF5O.js} +16 -15
- package/chunks/{ru-WGZDYQAB.js → ru-VJRAMC4W.js} +4 -0
- package/chunks/{run-qwen-serve-3NMODTXV.js → run-qwen-serve-JLFJHJSU.js} +82 -35
- package/chunks/{runtime-AQOV7DVP.js → runtime-DVQFRHKX.js} +24 -23
- package/chunks/{scheduler-DN7SZ6ZE.js → scheduler-WMFCKI7V.js} +16 -15
- package/chunks/{sdk-exporters-http-IKNWQJNV.js → sdk-exporters-http-IL2346MJ.js} +6 -2
- package/chunks/{sdk-impl-7CUUMQDB.js → sdk-impl-SYKGVWOZ.js} +2 -2
- package/chunks/{send-message-HGCVVDTZ.js → send-message-TL6FIMMN.js} +3 -3
- package/chunks/{serve-WZB45HVT.js → serve-NYWUO6YF.js} +22 -20
- package/chunks/{server-GOTZFEEZ.js → server-WHLRWZCG.js} +44 -42
- package/chunks/{session-23LOFLWN.js → session-EN34VEJM.js} +49 -47
- package/chunks/{settings-LI2ORVXC.js → settings-FZH5YZLH.js} +20 -19
- package/chunks/{shell-GLC6OP3C.js → shell-FQLEHJKT.js} +16 -15
- package/chunks/{skill-C7W6M4GO.js → skill-QAXTGG6F.js} +7 -7
- package/chunks/{skill-settings-W25NCK5I.js → skill-settings-SRUMUJIE.js} +20 -19
- package/chunks/{spawnChannel-OHJLEBLD.js → spawnChannel-VYQCFI7T.js} +16 -15
- package/chunks/{standalone-update-E4V5V7WT.js → standalone-update-LEPP5BOB.js} +18 -17
- package/chunks/{startInteractiveUI-FKILLW7Q.js → startInteractiveUI-IQIG2MKN.js} +77 -62
- package/chunks/{syntheticOutput-EYL7DWO7.js → syntheticOutput-BN7E626U.js} +2 -2
- package/chunks/{task-create-CSCEG4SM.js → task-create-2X4UP4F3.js} +4 -4
- package/chunks/{task-list-6CVQPORX.js → task-list-WOLJPZ64.js} +1 -1
- package/chunks/{task-stop-Y5ZTFVRX.js → task-stop-TNXRNAL2.js} +1 -1
- package/chunks/{task-update-C52LNFU5.js → task-update-VDUSN36E.js} +4 -4
- package/chunks/{team-create-HBIJ7NHT.js → team-create-7B5NKWA2.js} +16 -15
- package/chunks/{team-delete-CE7DC53R.js → team-delete-D2VMSDOT.js} +1 -1
- package/chunks/{team-plan-approval-IBJOKS4J.js → team-plan-approval-J6TURTZC.js} +16 -15
- package/chunks/{terminal-image-renderer-BOQJPVLB.js → terminal-image-renderer-KZMAF5E5.js} +17 -16
- package/chunks/{theme-manager-NSS7VLUD.js → theme-manager-NPISCKAX.js} +16 -15
- package/chunks/{todoWrite-YKAA57W2.js → todoWrite-MHWHO5UO.js} +3 -2
- package/chunks/{tool-search-DEAS54VG.js → tool-search-MFTNRTFA.js} +8 -8
- package/chunks/{total-session-admission-4LWTX3WD.js → total-session-admission-42THE4X2.js} +17 -16
- package/chunks/{trustedFolders-NF3J2TA7.js → trustedFolders-OT7IORFI.js} +17 -16
- package/chunks/{update-relaunch-WUHRH5V3.js → update-relaunch-2NJAMGWQ.js} +5 -5
- package/chunks/{updateCheck-7EG4BLBH.js → updateCheck-XSIBJXYA.js} +19 -18
- package/chunks/{useAutoAcceptIndicator-ZRV56GVP.js → useAutoAcceptIndicator-VH4DU3JR.js} +22 -21
- package/chunks/{validateNonInterActiveAuth-WC2FPE3I.js → validateNonInterActiveAuth-JD3VEJ2O.js} +45 -43
- package/chunks/{version-6SS7KCUP.js → version-F75ALUI3.js} +1 -1
- package/chunks/{web-fetch-Y5QQMPKA.js → web-fetch-Y6VEJMFN.js} +6 -6
- package/chunks/{web-search-QKKEU723.js → web-search-BTKVYWOR.js} +4 -4
- package/chunks/{workflow-PWIU6Q2M.js → workflow-LWFOBFN3.js} +17 -16
- package/chunks/{workspace-providers-status-B2DM6FNK.js → workspace-providers-status-VFEB7C5S.js} +23 -22
- package/chunks/{workspace-registration-store-7Y33TTHJ.js → workspace-registration-store-MB24E7PA.js} +1 -1
- package/chunks/{workspace-registry-TSNJKLPC.js → workspace-registry-FHFAPD74.js} +17 -16
- package/chunks/{workspace-service-4I2QHDX4.js → workspace-service-A6EFCR5H.js} +24 -23
- package/chunks/{workspace-skills-status-HVXNWVGR.js → workspace-skills-status-SDX2JT7P.js} +21 -20
- package/chunks/{workspace-trust-reconciler-RDBBGMWP.js → workspace-trust-reconciler-5NOCATAW.js} +22 -21
- package/chunks/{write-file-RQ4DG5RY.js → write-file-OHTP7KKV.js} +16 -15
- 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-WLSVNK2C.js} +6 -6
- 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-Dpg66jw7.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-CpnOJqNE.js → architectureDiagram-3BPJPVTR-BJ4FIBxN.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-NxVTiWnk.js → blockDiagram-GPEHLZMM-VXmYdtYT.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-BWT75l82.js → c4Diagram-AAUBKEIU-BNLDb8FE.js} +1 -1
- package/web-shell/assets/channel-Bd9wOnaA.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-Dzz03hk6.js → chunk-2J33WTMH-DR5yHxr0.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-DRF-egfm.js → chunk-4BX2VUAB-Czkq8UZU.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-Co15vq5x.js → chunk-55IACEB6-DugUgM6s.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-DHSUk1J7.js → chunk-727SXJPM-Dw6quA1K.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-Br8u_E2R.js → chunk-AQP2D5EJ-BoVYOTTz.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-DXbPTnqF.js → chunk-FMBD7UC4--Amcse-j.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-BleQdi4d.js → chunk-ND2GUHAM-ajaEYAC8.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-CqgBf-95.js → chunk-QZHKN3VN-CUNDZ7im.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-XhCT8FE3.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-XhCT8FE3.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-WS7b13Yg.js → cose-bilkent-S5V4N54A-BnfkD-m1.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-v_55ki8o.js → dagre-BM42HDAG-BuUdFk7Z.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-DaYAjbdK.js → diagram-2AECGRRQ-B2ZXxoab.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-D_Hy-L64.js → diagram-5GNKFQAL-BsgOICzM.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-dB06cZyD.js → diagram-KO2AKTUF-DTTjG6U-.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-DzSluD4F.js → diagram-LMA3HP47-temLf1x7.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-Cvb6CeXQ.js → diagram-OG6HWLK6-CRFjScs2.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-7eB2Hmov.js → erDiagram-TEJ5UH35-D4oaY-rQ.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-B08iGCw_.js → flowDiagram-I6XJVG4X-BqG8B49b.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-BgsrCHq-.js → ganttDiagram-6RSMTGT7-DffQBdd5.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DQLurQ2i.js → gitGraphDiagram-PVQCEYII-CMDPfseF.js} +1 -1
- package/web-shell/assets/index-2F4RsNzd.js +1756 -0
- package/web-shell/assets/index-CaucmEO_.css +5 -0
- package/web-shell/assets/{index-B_1Z0Mgr.js → index-qTQR_smP.js} +1 -1
- package/web-shell/assets/{infoDiagram-5YYISTIA-DXZGO8qB.js → infoDiagram-5YYISTIA-Cn3T83MA.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-g7gbuH2w.js → ishikawaDiagram-YF4QCWOH-ijFs7e25.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-euefICGn.js → journeyDiagram-JHISSGLW-CINTv8VB.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-Dws8-mjG.js → kanban-definition-UN3LZRKU-CMGRZCxx.js} +1 -1
- package/web-shell/assets/{linear-C63F0tyh.js → linear-Co-2_37d.js} +1 -1
- package/web-shell/assets/{mermaid.core-fLrhp_4-.js → mermaid.core-DijbyaN5.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-qRkydfT-.js → mindmap-definition-RKZ34NQL-k_sDYDPZ.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-BZ0wQaKN.js → pieDiagram-4H26LBE5-CG-oz8V3.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-Fjq6GbSZ.js → quadrantDiagram-W4KKPZXB-Baq7HIG2.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-CqWvjLHU.js → requirementDiagram-4Y6WPE33-CDM3ZvBt.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-VcXX6PVZ.js → sankeyDiagram-5OEKKPKP-C2dJbWKN.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-BJSRtxFr.js → sequenceDiagram-3UESZ5HK-Cf1_h59p.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-Dt9zUwmZ.js → stateDiagram-AJRCARHV-CI5clUEc.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDc8asKD.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-DQwlNRws.js → timeline-definition-PNZ67QCA-B2O8cAZX.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-DwxjUEtQ.js → vennDiagram-CIIHVFJN-BLsJ_h9s.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-D3QdMSpJ.js → wardley-L42UT6IY-BlPU4FTA.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BddN8oK8.js → wardleyDiagram-YWT4CUSO-D_U-RdU4.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-BUNuBrRJ.js → xychartDiagram-2RQKCTM6-BynXGSTl.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/index-Bi1dP2mU.css +0 -5
- package/web-shell/assets/index-DJ-z1Oba.js +0 -1756
- 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-EVSV25NF.js";
|
|
6
6
|
import {
|
|
7
7
|
listCommand
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UZNJUCOI.js";
|
|
9
9
|
import {
|
|
10
10
|
getErrorMessage as getErrorMessage2
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7DKCE4VO.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-S5VUT4K2.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-VQHIIPVE.js";
|
|
57
57
|
import {
|
|
58
58
|
collectProviderModelsForProtocol,
|
|
59
59
|
getAuthTypeFromEnv,
|
|
60
60
|
resolveCliGenerationConfig
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-XKX3PNZR.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-3KTHFE53.js";
|
|
132
132
|
import {
|
|
133
133
|
loadSettings
|
|
134
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-QK6BFCAL.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-QIS6FQFI.js";
|
|
142
142
|
import {
|
|
143
143
|
loadEnvironment
|
|
144
|
-
} from "./chunk-
|
|
144
|
+
} from "./chunk-BDUPVCHA.js";
|
|
145
145
|
import {
|
|
146
146
|
getCliVersion
|
|
147
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-XLD2R34T.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-AUM6IYIZ.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-7E5BCHGR.js";
|
|
195
195
|
import {
|
|
196
196
|
nextFireTime,
|
|
197
197
|
parseCron
|
|
@@ -202,18 +202,18 @@ import {
|
|
|
202
202
|
import {
|
|
203
203
|
parseBooleanEnvFlag,
|
|
204
204
|
resolveTelemetrySettings
|
|
205
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-MGSSJT27.js";
|
|
206
206
|
import {
|
|
207
207
|
getAllGeminiMdFilenames,
|
|
208
208
|
setGeminiMdFilename
|
|
209
|
-
} from "./chunk-
|
|
209
|
+
} from "./chunk-36Q4BOUL.js";
|
|
210
210
|
import {
|
|
211
211
|
SchemaValidator,
|
|
212
212
|
ToolNames
|
|
213
|
-
} from "./chunk-
|
|
213
|
+
} from "./chunk-D7D7RXIJ.js";
|
|
214
214
|
import {
|
|
215
215
|
DEFAULT_QWEN_EMBEDDING_MODEL
|
|
216
|
-
} from "./chunk-
|
|
216
|
+
} from "./chunk-MMWH7ERH.js";
|
|
217
217
|
import {
|
|
218
218
|
isTlsVerificationDisabled
|
|
219
219
|
} from "./chunk-BFHXDAR7.js";
|
|
@@ -222,7 +222,7 @@ import {
|
|
|
222
222
|
} from "./chunk-UH7Z7Y45.js";
|
|
223
223
|
import {
|
|
224
224
|
recordChannelMemoryRecallMetrics
|
|
225
|
-
} from "./chunk-
|
|
225
|
+
} from "./chunk-5L6CQ4ZU.js";
|
|
226
226
|
import {
|
|
227
227
|
FatalConfigError,
|
|
228
228
|
FatalSandboxError,
|
|
@@ -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 dirname24 = '"' + path3.dirname(s) + '"';
|
|
362
|
+
var basename6 = '"' + path3.basename(s) + '"';
|
|
363
|
+
return dirname24 + ":" + 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(resolve24, reject) {
|
|
372
372
|
commandExists2(commandName, function(error, output) {
|
|
373
373
|
if (output) {
|
|
374
|
-
|
|
374
|
+
resolve24(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((resolve24) => {
|
|
2259
|
+
releasePending = resolve24;
|
|
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((resolve24) => setTimeout(resolve24, 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((resolve24, 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(resolve24, 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((resolve24, 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 resolve24();
|
|
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((resolve24) => {
|
|
3885
|
+
finish = resolve24;
|
|
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((resolve24) => {
|
|
3914
|
+
const timer = setTimeout(resolve24, 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;
|
|
@@ -6260,6 +6283,11 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6260
6283
|
const idleAgents = [];
|
|
6261
6284
|
const unopenedAgents = [];
|
|
6262
6285
|
const rewrittenPrompts = [];
|
|
6286
|
+
const driftedLaunches = [];
|
|
6287
|
+
const openedBriefOf = /* @__PURE__ */ __name((rec, key) => {
|
|
6288
|
+
const needle = JSON.stringify(briefPath(planPath, key));
|
|
6289
|
+
return rec.successfulCallArgs.some((a) => a.includes(needle));
|
|
6290
|
+
}, "openedBriefOf");
|
|
6263
6291
|
const disclosures = [];
|
|
6264
6292
|
const disclose = /* @__PURE__ */ __name((subject, reason, pub) => {
|
|
6265
6293
|
disclosures.push({
|
|
@@ -6301,26 +6329,26 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6301
6329
|
}
|
|
6302
6330
|
return false;
|
|
6303
6331
|
}, "keySatisfied");
|
|
6304
|
-
const superseded = /* @__PURE__ */ __name((rec,
|
|
6332
|
+
const superseded = /* @__PURE__ */ __name((rec, chunk2) => chunk2 !== null ? chunkSatisfied(chunk2, rec) : keySatisfied(rec), "superseded");
|
|
6305
6333
|
for (const rec of records) {
|
|
6306
|
-
const
|
|
6307
|
-
const name = label(rec,
|
|
6334
|
+
const chunk2 = assignedChunk(rec);
|
|
6335
|
+
const name = label(rec, chunk2);
|
|
6308
6336
|
const given = wasGivenTheDiff(rec, plan.diffPathAbsolute);
|
|
6309
|
-
if (
|
|
6310
|
-
if (!superseded(rec,
|
|
6337
|
+
if (chunk2 !== null && !given) {
|
|
6338
|
+
if (!superseded(rec, chunk2)) blindAgents.push(name);
|
|
6311
6339
|
continue;
|
|
6312
6340
|
}
|
|
6313
6341
|
if (rec.successfulToolCalls === 0) {
|
|
6314
|
-
if (!superseded(rec,
|
|
6342
|
+
if (!superseded(rec, chunk2)) idleAgents.push(name);
|
|
6315
6343
|
continue;
|
|
6316
6344
|
}
|
|
6317
6345
|
if (!given) continue;
|
|
6318
6346
|
let rewrittenThisRecord = false;
|
|
6319
|
-
if (
|
|
6320
|
-
const b = builtOf(`chunk-${
|
|
6347
|
+
if (chunk2 !== null) {
|
|
6348
|
+
const b = builtOf(`chunk-${chunk2}`);
|
|
6321
6349
|
if (b === void 0) {
|
|
6322
6350
|
rewrittenThisRecord = true;
|
|
6323
|
-
if (!nothingBuiltAtAll && !superseded(rec,
|
|
6351
|
+
if (!nothingBuiltAtAll && !superseded(rec, chunk2)) {
|
|
6324
6352
|
rewrittenPrompts.push(
|
|
6325
6353
|
disclose(
|
|
6326
6354
|
name,
|
|
@@ -6332,21 +6360,29 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6332
6360
|
);
|
|
6333
6361
|
}
|
|
6334
6362
|
} else if (!wasDeliveredVerbatim(rec.launchPrompt, b)) {
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
)
|
|
6363
|
+
if (openedBriefOf(rec, `chunk-${chunk2}`) && rec.diffToolCalls > 0) {
|
|
6364
|
+
if (!superseded(rec, chunk2)) {
|
|
6365
|
+
driftedLaunches.push(
|
|
6366
|
+
`${name} \u2014 launched with a near-verbatim prompt; its brief was opened and the diff was read, so the delivery stands`
|
|
6367
|
+
);
|
|
6368
|
+
}
|
|
6369
|
+
} else {
|
|
6370
|
+
rewrittenThisRecord = true;
|
|
6371
|
+
if (!superseded(rec, chunk2)) {
|
|
6372
|
+
rewrittenPrompts.push(
|
|
6373
|
+
disclose(
|
|
6374
|
+
name,
|
|
6375
|
+
"launched with a prompt that is not the one the CLI built",
|
|
6376
|
+
{ reasonZh: "\u542F\u52A8\u65F6\u4F7F\u7528\u7684 prompt \u4E0D\u662F CLI \u6784\u5EFA\u7684\u90A3\u4E00\u4EFD" }
|
|
6377
|
+
)
|
|
6378
|
+
);
|
|
6379
|
+
}
|
|
6344
6380
|
}
|
|
6345
6381
|
}
|
|
6346
6382
|
}
|
|
6347
6383
|
const told = pointedAt(rec.launchPrompt, plan);
|
|
6348
6384
|
if (told.length > 0 && rec.diffToolCalls === 0) {
|
|
6349
|
-
if (!rewrittenThisRecord && !superseded(rec,
|
|
6385
|
+
if (!rewrittenThisRecord && !superseded(rec, chunk2)) {
|
|
6350
6386
|
unopenedAgents.push(name);
|
|
6351
6387
|
}
|
|
6352
6388
|
continue;
|
|
@@ -6354,8 +6390,8 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6354
6390
|
const ranges = merge([...told, ...rec.diffReads]);
|
|
6355
6391
|
if (ranges.length === 0) continue;
|
|
6356
6392
|
const u = UNCOVERABLE_RE.exec(rec.finalText);
|
|
6357
|
-
if (u &&
|
|
6358
|
-
uncoverable.add(
|
|
6393
|
+
if (u && chunk2 !== null && Number(u[1]) === chunk2) {
|
|
6394
|
+
uncoverable.add(chunk2);
|
|
6359
6395
|
continue;
|
|
6360
6396
|
}
|
|
6361
6397
|
for (const c of plan.chunks) {
|
|
@@ -6384,16 +6420,12 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6384
6420
|
);
|
|
6385
6421
|
}
|
|
6386
6422
|
const buildable = roster.filter((r) => builtOf(r.key) !== void 0);
|
|
6387
|
-
const openedBrief = /* @__PURE__ */ __name((rec, key) => {
|
|
6388
|
-
const needle = JSON.stringify(briefPath(planPath, key));
|
|
6389
|
-
return rec.successfulCallArgs.some((a) => a.includes(needle));
|
|
6390
|
-
}, "openedBrief");
|
|
6391
6423
|
const candidatesOf = buildable.map((req) => {
|
|
6392
6424
|
const b = builtOf(req.key);
|
|
6393
6425
|
return records.filter((r) => wasDeliveredVerbatim(r.launchPrompt, b));
|
|
6394
6426
|
});
|
|
6395
6427
|
const openedOfReq = buildable.map(
|
|
6396
|
-
(req, i) => candidatesOf[i].filter((r) =>
|
|
6428
|
+
(req, i) => candidatesOf[i].filter((r) => openedBriefOf(r, req.key))
|
|
6397
6429
|
);
|
|
6398
6430
|
const matchedRec = /* @__PURE__ */ new Map();
|
|
6399
6431
|
const augment = /* @__PURE__ */ __name((i, edges, seen) => {
|
|
@@ -6418,6 +6450,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6418
6450
|
}
|
|
6419
6451
|
const assignment = /* @__PURE__ */ new Map();
|
|
6420
6452
|
for (const [rec, i] of matchedRec) assignment.set(i, rec);
|
|
6453
|
+
const rescued = /* @__PURE__ */ new Set();
|
|
6421
6454
|
let buildableIdx = -1;
|
|
6422
6455
|
for (const req of roster) {
|
|
6423
6456
|
const b = builtOf(req.key);
|
|
@@ -6442,6 +6475,21 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6442
6475
|
const pick2 = assignment.get(buildableIdx);
|
|
6443
6476
|
if (pick2 === void 0) {
|
|
6444
6477
|
const anyMatch = candidatesOf[buildableIdx].length > 0;
|
|
6478
|
+
if (!anyMatch) {
|
|
6479
|
+
const needsDiff = req.role === "chunk" || BRIEFS[req.role].readsDiff;
|
|
6480
|
+
const rescue = records.find(
|
|
6481
|
+
(r) => !matchedRec.has(r) && !rescued.has(r) && r.successfulToolCalls > 0 && (!needsDiff || r.diffToolCalls > 0) && openedBriefOf(r, req.key)
|
|
6482
|
+
);
|
|
6483
|
+
if (rescue !== void 0) {
|
|
6484
|
+
rescued.add(rescue);
|
|
6485
|
+
if (req.role !== "chunk") {
|
|
6486
|
+
driftedLaunches.push(
|
|
6487
|
+
`${roleLabel(req)} \u2014 no launch matched its block verbatim, but an agent opened this role's brief and did the work, so the delivery stands`
|
|
6488
|
+
);
|
|
6489
|
+
}
|
|
6490
|
+
continue;
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6445
6493
|
missingRoles.push(
|
|
6446
6494
|
disclose(
|
|
6447
6495
|
roleLabel(req),
|
|
@@ -6489,6 +6537,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
|
|
|
6489
6537
|
idleAgents,
|
|
6490
6538
|
unopenedAgents,
|
|
6491
6539
|
rewrittenPrompts,
|
|
6540
|
+
driftedLaunches,
|
|
6492
6541
|
missingRoles,
|
|
6493
6542
|
missingRoleSelectors,
|
|
6494
6543
|
disclosures,
|
|
@@ -6751,6 +6800,10 @@ function gh(...args) {
|
|
|
6751
6800
|
return execGhWithRetry(args, {});
|
|
6752
6801
|
}
|
|
6753
6802
|
__name(gh, "gh");
|
|
6803
|
+
function ghWithInputRetried(input, ...args) {
|
|
6804
|
+
return execGhWithRetry(args, { input });
|
|
6805
|
+
}
|
|
6806
|
+
__name(ghWithInputRetried, "ghWithInputRetried");
|
|
6754
6807
|
function ghWithInput(input, ...args) {
|
|
6755
6808
|
return execFileSync2("gh", args, {
|
|
6756
6809
|
encoding: "utf8",
|
|
@@ -7190,25 +7243,25 @@ function composeReviewBody(input, cliVersion) {
|
|
|
7190
7243
|
}
|
|
7191
7244
|
if (contextUnavailable) cappedBy.push("context-unavailable");
|
|
7192
7245
|
if (criticalsUnverified) cappedBy.push("criticals-unverified");
|
|
7193
|
-
let
|
|
7194
|
-
if (
|
|
7246
|
+
let event2 = baseEvent;
|
|
7247
|
+
if (event2 === "APPROVE" && cappedBy.length > 0) event2 = "COMMENT";
|
|
7195
7248
|
const deterministicBodyCriticals = bodyCriticals.length - nonDeterministicBodyCriticals;
|
|
7196
|
-
if (
|
|
7197
|
-
|
|
7249
|
+
if (event2 === "REQUEST_CHANGES" && criticalsUnverified && deterministicBodyCriticals === 0) {
|
|
7250
|
+
event2 = "COMMENT";
|
|
7198
7251
|
}
|
|
7199
7252
|
let downgraded = false;
|
|
7200
7253
|
let downgradedFrom = null;
|
|
7201
|
-
if (
|
|
7202
|
-
|
|
7254
|
+
if (event2 === "APPROVE" && downgradeApprove) {
|
|
7255
|
+
event2 = "COMMENT";
|
|
7203
7256
|
downgraded = true;
|
|
7204
7257
|
downgradedFrom = "Approve";
|
|
7205
|
-
} else if ((
|
|
7206
|
-
|
|
7258
|
+
} else if ((event2 === "REQUEST_CHANGES" || baseEvent === "REQUEST_CHANGES" && criticalsUnverified) && downgradeRequestChanges) {
|
|
7259
|
+
event2 = "COMMENT";
|
|
7207
7260
|
downgraded = true;
|
|
7208
7261
|
downgradedFrom = "Request changes";
|
|
7209
7262
|
}
|
|
7210
7263
|
let lowSignal = null;
|
|
7211
|
-
if (
|
|
7264
|
+
if (event2 === "APPROVE" && input.planPath) {
|
|
7212
7265
|
try {
|
|
7213
7266
|
const plan = JSON.parse(
|
|
7214
7267
|
readFileSync8(input.planPath, "utf8")
|
|
@@ -7222,10 +7275,10 @@ function composeReviewBody(input, cliVersion) {
|
|
|
7222
7275
|
}
|
|
7223
7276
|
const footer = `_\u2014 ${modelId} via Qwen Code /review (v${cliVersion})_`;
|
|
7224
7277
|
const bilingual = bilingualFromPlan(input.planPath, input.prBodyFetcher);
|
|
7225
|
-
const render = /* @__PURE__ */ __name((parts,
|
|
7226
|
-
const en = parts.map((p) => p.en).join(
|
|
7278
|
+
const render = /* @__PURE__ */ __name((parts, sep5) => {
|
|
7279
|
+
const en = parts.map((p) => p.en).join(sep5);
|
|
7227
7280
|
if (en === "") return "";
|
|
7228
|
-
const zh = parts.map((p) => p.zh).join(
|
|
7281
|
+
const zh = parts.map((p) => p.zh).join(sep5);
|
|
7229
7282
|
const text = bilingual && zh !== en ? `${en}
|
|
7230
7283
|
|
|
7231
7284
|
<details>
|
|
@@ -7359,7 +7412,7 @@ ${footer}`;
|
|
|
7359
7412
|
zh: `Test Plan\uFF08\u975E\u963B\u65AD\uFF09\uFF1A${testPlanNotes.join("; ")}\u3002`
|
|
7360
7413
|
}
|
|
7361
7414
|
] : [];
|
|
7362
|
-
if (
|
|
7415
|
+
if (event2 === "REQUEST_CHANGES") {
|
|
7363
7416
|
const parts = [
|
|
7364
7417
|
...contextUnavailable ? [contextUnavailableClause] : [],
|
|
7365
7418
|
...cannotTellBlock,
|
|
@@ -7369,7 +7422,7 @@ ${footer}`;
|
|
|
7369
7422
|
...bodyCriticalBlock
|
|
7370
7423
|
];
|
|
7371
7424
|
return {
|
|
7372
|
-
event,
|
|
7425
|
+
event: event2,
|
|
7373
7426
|
body: render(parts, "\n\n"),
|
|
7374
7427
|
baseEvent,
|
|
7375
7428
|
cappedBy,
|
|
@@ -7379,9 +7432,9 @@ ${footer}`;
|
|
|
7379
7432
|
lowSignal
|
|
7380
7433
|
};
|
|
7381
7434
|
}
|
|
7382
|
-
if (
|
|
7435
|
+
if (event2 === "APPROVE") {
|
|
7383
7436
|
return {
|
|
7384
|
-
event,
|
|
7437
|
+
event: event2,
|
|
7385
7438
|
body: render(
|
|
7386
7439
|
[
|
|
7387
7440
|
{ en: "No issues found. LGTM! \u2705", zh: "\u672A\u53D1\u73B0\u95EE\u9898\u3002LGTM\uFF01\u2705" },
|
|
@@ -7444,7 +7497,7 @@ ${footer}`;
|
|
|
7444
7497
|
clauses.push(...bodyCriticalBlock);
|
|
7445
7498
|
}
|
|
7446
7499
|
return {
|
|
7447
|
-
event,
|
|
7500
|
+
event: event2,
|
|
7448
7501
|
body: render(clauses, " "),
|
|
7449
7502
|
baseEvent,
|
|
7450
7503
|
cappedBy,
|
|
@@ -7821,7 +7874,7 @@ __name(verdictLine, "verdictLine");
|
|
|
7821
7874
|
|
|
7822
7875
|
// packages/cli/src/commands/review/findings.ts
|
|
7823
7876
|
init_esbuild_shims();
|
|
7824
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
7877
|
+
import { existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
|
|
7825
7878
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
7826
7879
|
var SEVERITIES = ["Critical", "Suggestion", "Nice to have"];
|
|
7827
7880
|
var CONFIDENCES = ["high", "low"];
|
|
@@ -7845,6 +7898,18 @@ function asString(o, key) {
|
|
|
7845
7898
|
return typeof v === "string" && v.trim() !== "" ? v : void 0;
|
|
7846
7899
|
}
|
|
7847
7900
|
__name(asString, "asString");
|
|
7901
|
+
function stringArray(o, key, index) {
|
|
7902
|
+
const v = o[key];
|
|
7903
|
+
if (v == null) return void 0;
|
|
7904
|
+
if (!Array.isArray(v) || v.some((x) => typeof x !== "string" || x.trim() === "")) {
|
|
7905
|
+
fail(
|
|
7906
|
+
index,
|
|
7907
|
+
`"${key}" must be an array of non-empty strings, got ${JSON.stringify(v)}.`
|
|
7908
|
+
);
|
|
7909
|
+
}
|
|
7910
|
+
return v.length > 0 ? v : void 0;
|
|
7911
|
+
}
|
|
7912
|
+
__name(stringArray, "stringArray");
|
|
7848
7913
|
function oneOf(value, allowed) {
|
|
7849
7914
|
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
7850
7915
|
}
|
|
@@ -7862,8 +7927,8 @@ function parseLocations(o, index) {
|
|
|
7862
7927
|
const lo = l;
|
|
7863
7928
|
const file2 = asString(lo, "file");
|
|
7864
7929
|
if (!file2) fail(index, `location ${j} is missing a non-empty "file".`);
|
|
7865
|
-
if (lo["line"] !== void 0 && typeof lo["line"] !== "number") {
|
|
7866
|
-
fail(index, `location ${j} has
|
|
7930
|
+
if (lo["line"] !== void 0 && (typeof lo["line"] !== "number" || !Number.isSafeInteger(lo["line"]) || lo["line"] <= 0)) {
|
|
7931
|
+
fail(index, `location ${j} has an invalid "line".`);
|
|
7867
7932
|
}
|
|
7868
7933
|
return {
|
|
7869
7934
|
file: file2,
|
|
@@ -7879,8 +7944,8 @@ function parseLocations(o, index) {
|
|
|
7879
7944
|
'needs a non-empty "file" (or a "locations" array for a pattern aggregate).'
|
|
7880
7945
|
);
|
|
7881
7946
|
}
|
|
7882
|
-
if (o["line"] !== void 0 && typeof o["line"] !== "number") {
|
|
7883
|
-
fail(index, 'has
|
|
7947
|
+
if (o["line"] !== void 0 && (typeof o["line"] !== "number" || !Number.isSafeInteger(o["line"]) || o["line"] <= 0)) {
|
|
7948
|
+
fail(index, 'has an invalid "line".');
|
|
7884
7949
|
}
|
|
7885
7950
|
return [
|
|
7886
7951
|
{
|
|
@@ -7939,7 +8004,11 @@ function validateFindings(raw) {
|
|
|
7939
8004
|
`has outcome ${JSON.stringify(o["outcome"])}; expected one of ${OUTCOMES.map((s) => JSON.stringify(s)).join(", ")}.`
|
|
7940
8005
|
);
|
|
7941
8006
|
}
|
|
8007
|
+
const assetFiles = stringArray(o, "assetFiles", i) ?? stringArray(o, "asset_files", i);
|
|
8008
|
+
const assets = stringArray(o, "assets", i);
|
|
7942
8009
|
const outcomeNote = asString(o, "outcomeNote") ?? asString(o, "outcome_note");
|
|
8010
|
+
const heldRaw = o.heldByMeasurement;
|
|
8011
|
+
const heldFile = heldRaw && typeof heldRaw === "object" ? asString(heldRaw, "file") : void 0;
|
|
7943
8012
|
const shortSummary = asString(o, "shortSummary") ?? asString(o, "short_summary");
|
|
7944
8013
|
return {
|
|
7945
8014
|
id,
|
|
@@ -7954,6 +8023,9 @@ function validateFindings(raw) {
|
|
|
7954
8023
|
} : {},
|
|
7955
8024
|
...asString(o, "category") ? { category: asString(o, "category") } : {},
|
|
7956
8025
|
locations: parseLocations(o, i),
|
|
8026
|
+
...assetFiles ? { assetFiles } : {},
|
|
8027
|
+
...assets ? { assets } : {},
|
|
8028
|
+
...heldFile ? { heldByMeasurement: { file: heldFile } } : {},
|
|
7957
8029
|
...outcome ? { outcome } : {},
|
|
7958
8030
|
...outcome && outcomeNote ? { outcomeNote } : {}
|
|
7959
8031
|
};
|
|
@@ -7970,6 +8042,89 @@ function validateFindings(raw) {
|
|
|
7970
8042
|
return findings;
|
|
7971
8043
|
}
|
|
7972
8044
|
__name(validateFindings, "validateFindings");
|
|
8045
|
+
function namesPath(text, probe) {
|
|
8046
|
+
const isNameChar = /* @__PURE__ */ __name((c) => c !== void 0 && /[A-Za-z0-9._-]/.test(c), "isNameChar");
|
|
8047
|
+
const isBoundary = /* @__PURE__ */ __name((c) => c === void 0 || !isNameChar(c) && c !== "/", "isBoundary");
|
|
8048
|
+
let from = 0;
|
|
8049
|
+
for (; ; ) {
|
|
8050
|
+
const at = text.indexOf(probe, from);
|
|
8051
|
+
if (at < 0) return false;
|
|
8052
|
+
const after = text[at + probe.length];
|
|
8053
|
+
const extendsName = after !== void 0 && (/[A-Za-z0-9_-]/.test(after) || after === "." && /[A-Za-z0-9]/.test(text[at + probe.length + 1] ?? ""));
|
|
8054
|
+
if (isBoundary(at === 0 ? void 0 : text[at - 1]) && !extendsName) {
|
|
8055
|
+
return true;
|
|
8056
|
+
}
|
|
8057
|
+
from = at + 1;
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
__name(namesPath, "namesPath");
|
|
8061
|
+
function holdCriticalsFailingOnBase(findings, sharedFailingFiles) {
|
|
8062
|
+
const held = [];
|
|
8063
|
+
const readjudicated = [];
|
|
8064
|
+
const out = findings.map((f) => {
|
|
8065
|
+
if (f.severity !== "Critical") return f;
|
|
8066
|
+
const haystack = [f.summary, f.failureScenario].join("\n");
|
|
8067
|
+
const hit = sharedFailingFiles.find((p) => p && namesPath(haystack, p));
|
|
8068
|
+
if (!hit) return f;
|
|
8069
|
+
if (f.heldByMeasurement?.file === hit) {
|
|
8070
|
+
readjudicated.push({ id: f.id, file: hit });
|
|
8071
|
+
return f;
|
|
8072
|
+
}
|
|
8073
|
+
held.push({ id: f.id, file: hit });
|
|
8074
|
+
return {
|
|
8075
|
+
...f,
|
|
8076
|
+
severity: "Suggestion",
|
|
8077
|
+
heldByMeasurement: { file: hit },
|
|
8078
|
+
failureScenario: `${f.failureScenario}
|
|
8079
|
+
|
|
8080
|
+
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.`
|
|
8081
|
+
};
|
|
8082
|
+
});
|
|
8083
|
+
return { findings: out, held, readjudicated };
|
|
8084
|
+
}
|
|
8085
|
+
__name(holdCriticalsFailingOnBase, "holdCriticalsFailingOnBase");
|
|
8086
|
+
var WORKSPACE_IN_COMMAND_RE = /--workspace="([^"]+)"/;
|
|
8087
|
+
function repoRelative(path3, workspace) {
|
|
8088
|
+
const at = path3.indexOf("::");
|
|
8089
|
+
const bare = at < 0 ? path3 : path3.slice(at + 2);
|
|
8090
|
+
if (at >= 0 && !workspace) return void 0;
|
|
8091
|
+
if (!workspace || bare.startsWith(`${workspace}/`)) return bare;
|
|
8092
|
+
return `${workspace}/${bare}`;
|
|
8093
|
+
}
|
|
8094
|
+
__name(repoRelative, "repoRelative");
|
|
8095
|
+
function sharedFailingFilesOf(raw) {
|
|
8096
|
+
if (!raw || typeof raw !== "object")
|
|
8097
|
+
return { shared: [], unidentifiable: [] };
|
|
8098
|
+
const shared = /* @__PURE__ */ new Set();
|
|
8099
|
+
const netNew = /* @__PURE__ */ new Set();
|
|
8100
|
+
const unidentifiable = /* @__PURE__ */ new Set();
|
|
8101
|
+
const take = /* @__PURE__ */ __name((into, v, workspace) => {
|
|
8102
|
+
if (!Array.isArray(v)) return;
|
|
8103
|
+
for (const e of v) {
|
|
8104
|
+
if (typeof e !== "string" || !e) continue;
|
|
8105
|
+
const qualified = repoRelative(e, workspace);
|
|
8106
|
+
if (qualified === void 0) {
|
|
8107
|
+
if (into === shared) unidentifiable.add(e);
|
|
8108
|
+
} else into.add(qualified);
|
|
8109
|
+
}
|
|
8110
|
+
}, "take");
|
|
8111
|
+
const entries = raw.entries;
|
|
8112
|
+
const rows = Array.isArray(entries) ? entries : [];
|
|
8113
|
+
if (rows.length > 0) {
|
|
8114
|
+
for (const e of rows) {
|
|
8115
|
+
if (!e || typeof e !== "object") continue;
|
|
8116
|
+
const command = e.command;
|
|
8117
|
+
const workspace = typeof command === "string" ? WORKSPACE_IN_COMMAND_RE.exec(command)?.[1] ?? void 0 : void 0;
|
|
8118
|
+
take(shared, e.shared, workspace);
|
|
8119
|
+
take(netNew, e.netNew, workspace);
|
|
8120
|
+
}
|
|
8121
|
+
}
|
|
8122
|
+
return {
|
|
8123
|
+
shared: [...shared].filter((f) => !netNew.has(f)),
|
|
8124
|
+
unidentifiable: [...unidentifiable]
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
8127
|
+
__name(sharedFailingFilesOf, "sharedFailingFilesOf");
|
|
7973
8128
|
function sortFindings(findings) {
|
|
7974
8129
|
return [...findings].sort((a, b) => {
|
|
7975
8130
|
const bySeverity = SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity);
|
|
@@ -8072,6 +8227,7 @@ function buildReport(findings) {
|
|
|
8072
8227
|
total: sorted.length,
|
|
8073
8228
|
bySeverity,
|
|
8074
8229
|
byConfidence,
|
|
8230
|
+
held: sorted.filter((f) => f.heldByMeasurement !== void 0).length,
|
|
8075
8231
|
...byOutcome ? { byOutcome } : {}
|
|
8076
8232
|
},
|
|
8077
8233
|
outcomesRecorded
|
|
@@ -8121,12 +8277,15 @@ var findingsCommand = {
|
|
|
8121
8277
|
}).option("outcomes", {
|
|
8122
8278
|
type: "string",
|
|
8123
8279
|
describe: "JSON array of {id, outcome, note?} recording what --fix did to each finding. Must cover every finding."
|
|
8280
|
+
}).option("test-delta", {
|
|
8281
|
+
type: "string",
|
|
8282
|
+
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
8283
|
}).option("print", {
|
|
8125
8284
|
type: "boolean",
|
|
8126
8285
|
describe: "Also print one line per finding to stdout"
|
|
8127
8286
|
}), "builder"),
|
|
8128
8287
|
handler: /* @__PURE__ */ __name((argv) => {
|
|
8129
|
-
const { input, out, outcomes, print } = argv;
|
|
8288
|
+
const { input, out, outcomes, print, testDelta } = argv;
|
|
8130
8289
|
let findings = validateFindings(readJson(input, "findings"));
|
|
8131
8290
|
if (outcomes !== void 0) {
|
|
8132
8291
|
findings = applyOutcomes(
|
|
@@ -8134,6 +8293,30 @@ var findingsCommand = {
|
|
|
8134
8293
|
validateOutcomes(readJson(outcomes, "outcomes"))
|
|
8135
8294
|
);
|
|
8136
8295
|
}
|
|
8296
|
+
let held = [];
|
|
8297
|
+
let readjudicated = [];
|
|
8298
|
+
if (testDelta !== void 0) {
|
|
8299
|
+
let measurement;
|
|
8300
|
+
if (existsSync3(resolve5(testDelta))) {
|
|
8301
|
+
try {
|
|
8302
|
+
measurement = readJson(testDelta, "test-delta");
|
|
8303
|
+
} catch (err) {
|
|
8304
|
+
writeStderrLine(
|
|
8305
|
+
`findings: no holds applied \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
8306
|
+
);
|
|
8307
|
+
}
|
|
8308
|
+
}
|
|
8309
|
+
const { shared, unidentifiable } = sharedFailingFilesOf(measurement);
|
|
8310
|
+
for (const f of unidentifiable) {
|
|
8311
|
+
writeStderrLine(
|
|
8312
|
+
`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`
|
|
8313
|
+
);
|
|
8314
|
+
}
|
|
8315
|
+
({ findings, held, readjudicated } = holdCriticalsFailingOnBase(
|
|
8316
|
+
findings,
|
|
8317
|
+
shared
|
|
8318
|
+
));
|
|
8319
|
+
}
|
|
8137
8320
|
const report = buildReport(findings);
|
|
8138
8321
|
const target = resolve5(out);
|
|
8139
8322
|
mkdirSync6(dirname5(target), { recursive: true });
|
|
@@ -8143,6 +8326,16 @@ var findingsCommand = {
|
|
|
8143
8326
|
writeStderrLine(
|
|
8144
8327
|
`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
8328
|
);
|
|
8329
|
+
for (const h of held) {
|
|
8330
|
+
writeStderrLine(
|
|
8331
|
+
`findings: ${h.id} held back from Critical \u2014 test-delta measured ${h.file} as failing on the merge base too`
|
|
8332
|
+
);
|
|
8333
|
+
}
|
|
8334
|
+
for (const r of readjudicated) {
|
|
8335
|
+
writeStderrLine(
|
|
8336
|
+
`findings: ${r.id} left at Critical \u2014 it already carries the ${r.file} measurement and was raised again anyway`
|
|
8337
|
+
);
|
|
8338
|
+
}
|
|
8146
8339
|
if (report.counts.byOutcome) {
|
|
8147
8340
|
const o = report.counts.byOutcome;
|
|
8148
8341
|
writeStderrLine(
|
|
@@ -8164,7 +8357,7 @@ import { dirname as dirname6, resolve as resolve6 } from "node:path";
|
|
|
8164
8357
|
// packages/cli/src/commands/review/lib/git.ts
|
|
8165
8358
|
init_esbuild_shims();
|
|
8166
8359
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
8167
|
-
import { existsSync as
|
|
8360
|
+
import { existsSync as existsSync4, rmSync as rmSync3 } from "node:fs";
|
|
8168
8361
|
var GIT_TIMEOUT_MS2 = 12e4;
|
|
8169
8362
|
function gitOpts() {
|
|
8170
8363
|
return {
|
|
@@ -8210,7 +8403,7 @@ function worktreeReleaseResult(existed, stillThere, removeError) {
|
|
|
8210
8403
|
}
|
|
8211
8404
|
__name(worktreeReleaseResult, "worktreeReleaseResult");
|
|
8212
8405
|
function releaseWorktree(worktreePath2) {
|
|
8213
|
-
const existed =
|
|
8406
|
+
const existed = existsSync4(worktreePath2);
|
|
8214
8407
|
let removeError;
|
|
8215
8408
|
if (existed) {
|
|
8216
8409
|
gitOpt("worktree", "remove", worktreePath2, "--force");
|
|
@@ -8221,7 +8414,7 @@ function releaseWorktree(worktreePath2) {
|
|
|
8221
8414
|
}
|
|
8222
8415
|
}
|
|
8223
8416
|
gitOpt("worktree", "prune");
|
|
8224
|
-
return worktreeReleaseResult(existed,
|
|
8417
|
+
return worktreeReleaseResult(existed, existsSync4(worktreePath2), removeError);
|
|
8225
8418
|
}
|
|
8226
8419
|
__name(releaseWorktree, "releaseWorktree");
|
|
8227
8420
|
function gitRaw(...args) {
|
|
@@ -10087,8 +10280,8 @@ var loadRulesCommand = {
|
|
|
10087
10280
|
// packages/cli/src/commands/review/presubmit.ts
|
|
10088
10281
|
init_esbuild_shims();
|
|
10089
10282
|
import { writeFileSync as writeFileSync13, readFileSync as readFileSync13 } from "node:fs";
|
|
10090
|
-
function verdictStamp(
|
|
10091
|
-
return
|
|
10283
|
+
function verdictStamp(run4) {
|
|
10284
|
+
return run4.completed_at ?? run4.started_at ?? "";
|
|
10092
10285
|
}
|
|
10093
10286
|
__name(verdictStamp, "verdictStamp");
|
|
10094
10287
|
var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
@@ -10110,11 +10303,11 @@ var PENDING_STATES = /* @__PURE__ */ new Set([
|
|
|
10110
10303
|
"requested"
|
|
10111
10304
|
]);
|
|
10112
10305
|
var NOT_RUN_CONCLUSIONS = /* @__PURE__ */ new Set(["skipped", "neutral", "stale"]);
|
|
10113
|
-
function isCurrentActionsRunCheck(
|
|
10306
|
+
function isCurrentActionsRunCheck(run4) {
|
|
10114
10307
|
const runId = process.env["GITHUB_RUN_ID"];
|
|
10115
10308
|
if (!runId) return false;
|
|
10116
10309
|
const runUrlMarker = `/actions/runs/${runId}/`;
|
|
10117
|
-
return [
|
|
10310
|
+
return [run4.details_url, run4.html_url].some(
|
|
10118
10311
|
(url) => typeof url === "string" && url.includes(runUrlMarker)
|
|
10119
10312
|
);
|
|
10120
10313
|
}
|
|
@@ -10179,33 +10372,33 @@ function classifyCi(checkRuns, statuses) {
|
|
|
10179
10372
|
const failedCheckNames = [];
|
|
10180
10373
|
let hasPending = false;
|
|
10181
10374
|
const relevantCheckRuns = checkRuns.filter(
|
|
10182
|
-
(
|
|
10375
|
+
(run4) => !isCurrentActionsRunCheck(run4)
|
|
10183
10376
|
);
|
|
10184
10377
|
const executedNames = /* @__PURE__ */ new Set();
|
|
10185
10378
|
const notRunNames = /* @__PURE__ */ new Set();
|
|
10186
|
-
for (const
|
|
10187
|
-
if (
|
|
10188
|
-
if (!
|
|
10189
|
-
notRunNames.add(
|
|
10379
|
+
for (const run4 of relevantCheckRuns) {
|
|
10380
|
+
if (run4.status !== "completed") continue;
|
|
10381
|
+
if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) {
|
|
10382
|
+
notRunNames.add(run4.name);
|
|
10190
10383
|
} else {
|
|
10191
|
-
executedNames.add(
|
|
10384
|
+
executedNames.add(run4.name);
|
|
10192
10385
|
}
|
|
10193
10386
|
}
|
|
10194
10387
|
const skippedCheckNames = [...notRunNames].filter((n) => !executedNames.has(n)).sort();
|
|
10195
10388
|
const latestVerdicts = /* @__PURE__ */ new Map();
|
|
10196
|
-
for (const
|
|
10197
|
-
if (
|
|
10198
|
-
if (PENDING_STATES.has(
|
|
10389
|
+
for (const run4 of relevantCheckRuns) {
|
|
10390
|
+
if (run4.status !== "completed") {
|
|
10391
|
+
if (PENDING_STATES.has(run4.status)) hasPending = true;
|
|
10199
10392
|
continue;
|
|
10200
10393
|
}
|
|
10201
|
-
if (!
|
|
10202
|
-
const prev = latestVerdicts.get(
|
|
10203
|
-
if (!prev || verdictStamp(
|
|
10204
|
-
latestVerdicts.set(
|
|
10394
|
+
if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) continue;
|
|
10395
|
+
const prev = latestVerdicts.get(run4.name);
|
|
10396
|
+
if (!prev || verdictStamp(run4) > verdictStamp(prev)) {
|
|
10397
|
+
latestVerdicts.set(run4.name, run4);
|
|
10205
10398
|
}
|
|
10206
10399
|
}
|
|
10207
|
-
for (const [name,
|
|
10208
|
-
if (FAIL_CONCLUSIONS.has(
|
|
10400
|
+
for (const [name, run4] of latestVerdicts) {
|
|
10401
|
+
if (FAIL_CONCLUSIONS.has(run4.conclusion)) {
|
|
10209
10402
|
failedCheckNames.push(name);
|
|
10210
10403
|
}
|
|
10211
10404
|
}
|
|
@@ -10461,136 +10654,706 @@ var presubmitCommand = {
|
|
|
10461
10654
|
}, "handler")
|
|
10462
10655
|
};
|
|
10463
10656
|
|
|
10464
|
-
// packages/cli/src/commands/review/
|
|
10657
|
+
// packages/cli/src/commands/review/publish-assets.ts
|
|
10465
10658
|
init_esbuild_shims();
|
|
10466
|
-
import {
|
|
10467
|
-
import {
|
|
10659
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10660
|
+
import { readFileSync as readFileSync15, statSync as statSync5, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14 } from "node:fs";
|
|
10661
|
+
import { basename as basename5, dirname as dirname11, resolve as resolve10 } from "node:path";
|
|
10468
10662
|
|
|
10469
|
-
// packages/cli/src/commands/review/lib/
|
|
10663
|
+
// packages/cli/src/commands/review/lib/authorization.ts
|
|
10470
10664
|
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;
|
|
10665
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
10666
|
+
|
|
10667
|
+
// packages/cli/src/services/skill-args-file.ts
|
|
10668
|
+
init_esbuild_shims();
|
|
10669
|
+
import {
|
|
10670
|
+
mkdirSync as mkdirSync13,
|
|
10671
|
+
openSync as openSync2,
|
|
10672
|
+
writeSync,
|
|
10673
|
+
closeSync as closeSync2,
|
|
10674
|
+
rmSync as rmSync4,
|
|
10675
|
+
lstatSync as lstatSync3,
|
|
10676
|
+
existsSync as existsSync5,
|
|
10677
|
+
constants
|
|
10678
|
+
} from "node:fs";
|
|
10679
|
+
import { join as join11 } from "node:path";
|
|
10680
|
+
var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
|
|
10681
|
+
var SKILL_ARGS_DIR = join11(".qwen", "tmp");
|
|
10682
|
+
function safe(s) {
|
|
10683
|
+
return s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10495
10684
|
}
|
|
10496
|
-
__name(
|
|
10497
|
-
function
|
|
10498
|
-
return
|
|
10685
|
+
__name(safe, "safe");
|
|
10686
|
+
function currentSessionId() {
|
|
10687
|
+
return sessionIdContext.getStore()?.trim() || process.env["QWEN_CODE_SESSION_ID"]?.trim() || "";
|
|
10499
10688
|
}
|
|
10500
|
-
__name(
|
|
10501
|
-
function
|
|
10502
|
-
return s
|
|
10689
|
+
__name(currentSessionId, "currentSessionId");
|
|
10690
|
+
function skillArgsDir(sessionId = currentSessionId()) {
|
|
10691
|
+
return sessionId ? join11(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
|
|
10503
10692
|
}
|
|
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;
|
|
10693
|
+
__name(skillArgsDir, "skillArgsDir");
|
|
10694
|
+
function skillArgsPath(skillName, sessionId = currentSessionId()) {
|
|
10695
|
+
return join11(
|
|
10696
|
+
skillArgsDir(sessionId),
|
|
10697
|
+
`qwen-skill-args-${safe(skillName)}.txt`
|
|
10698
|
+
);
|
|
10523
10699
|
}
|
|
10524
|
-
__name(
|
|
10525
|
-
function
|
|
10526
|
-
const
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10700
|
+
__name(skillArgsPath, "skillArgsPath");
|
|
10701
|
+
function writeSkillArgs(skillName, args) {
|
|
10702
|
+
const path3 = skillArgsPath(skillName);
|
|
10703
|
+
try {
|
|
10704
|
+
const dir = skillArgsDir();
|
|
10705
|
+
mkdirSync13(dir, { recursive: true });
|
|
10706
|
+
if (lstatSync3(dir).isSymbolicLink()) {
|
|
10707
|
+
debugLogger3.warn(
|
|
10708
|
+
`Skill args directory ${dir} is a symlink; refusing to write through it.`
|
|
10709
|
+
);
|
|
10710
|
+
return null;
|
|
10711
|
+
}
|
|
10712
|
+
if (lstatSync3(path3, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
10713
|
+
debugLogger3.warn(
|
|
10714
|
+
`Skill args file ${path3} is a symlink; refusing to write through it.`
|
|
10715
|
+
);
|
|
10716
|
+
return null;
|
|
10717
|
+
}
|
|
10718
|
+
const fd = openSync2(
|
|
10719
|
+
path3,
|
|
10720
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
|
|
10721
|
+
384
|
|
10722
|
+
);
|
|
10723
|
+
try {
|
|
10724
|
+
const buf = Buffer.from(args, "utf8");
|
|
10725
|
+
let off = 0;
|
|
10726
|
+
while (off < buf.length) {
|
|
10727
|
+
off += writeSync(fd, buf, off, buf.length - off);
|
|
10540
10728
|
}
|
|
10729
|
+
} finally {
|
|
10730
|
+
closeSync2(fd);
|
|
10541
10731
|
}
|
|
10542
|
-
|
|
10732
|
+
return path3;
|
|
10733
|
+
} catch (err) {
|
|
10734
|
+
debugLogger3.warn(
|
|
10735
|
+
`Could not write skill args to ${path3}: ${err.message}`
|
|
10736
|
+
);
|
|
10737
|
+
return null;
|
|
10543
10738
|
}
|
|
10544
|
-
return starts;
|
|
10545
|
-
}
|
|
10546
|
-
__name(matchRuns, "matchRuns");
|
|
10547
|
-
function candidatesFor(hay, needle, norm) {
|
|
10548
|
-
return matchRuns(hay, needle, norm).map((i) => {
|
|
10549
|
-
const run3 = hay.slice(i, i + needle.length);
|
|
10550
|
-
return {
|
|
10551
|
-
startLine: run3[0].newLine,
|
|
10552
|
-
line: run3[run3.length - 1].newLine,
|
|
10553
|
-
added: run3.some((l) => l.added)
|
|
10554
|
-
};
|
|
10555
|
-
});
|
|
10556
10739
|
}
|
|
10557
|
-
__name(
|
|
10558
|
-
function
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10740
|
+
__name(writeSkillArgs, "writeSkillArgs");
|
|
10741
|
+
function clearSkillArgs(skillName) {
|
|
10742
|
+
const path3 = skillArgsPath(skillName);
|
|
10743
|
+
try {
|
|
10744
|
+
rmSync4(path3, { force: true });
|
|
10745
|
+
} catch (err) {
|
|
10746
|
+
debugLogger3.warn(
|
|
10747
|
+
`Could not clear skill args for ${skillName}: ${err.message}`
|
|
10748
|
+
);
|
|
10749
|
+
return false;
|
|
10566
10750
|
}
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
__name(pick, "pick");
|
|
10571
|
-
function resolveAnchor(newSideLines, anchor, claimedLine) {
|
|
10572
|
-
const variants = anchorVariants(anchor);
|
|
10573
|
-
if (variants.length === 0) {
|
|
10574
|
-
return { status: "unmatched", reason: "anchor is empty" };
|
|
10751
|
+
if (existsSync5(path3)) {
|
|
10752
|
+
debugLogger3.warn(`Skill args for ${skillName} survived removal at ${path3}`);
|
|
10753
|
+
return false;
|
|
10575
10754
|
}
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10755
|
+
return true;
|
|
10756
|
+
}
|
|
10757
|
+
__name(clearSkillArgs, "clearSkillArgs");
|
|
10758
|
+
function staleArgsWarning() {
|
|
10759
|
+
return `
|
|
10760
|
+
|
|
10761
|
+
<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>
|
|
10762
|
+
`;
|
|
10763
|
+
}
|
|
10764
|
+
__name(staleArgsWarning, "staleArgsWarning");
|
|
10765
|
+
function skillArgsNote(path3, args) {
|
|
10766
|
+
return `
|
|
10767
|
+
|
|
10768
|
+
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.
|
|
10769
|
+
<skill-args-file>${path3}</skill-args-file>
|
|
10770
|
+
<skill-args>${args}</skill-args>
|
|
10771
|
+
`;
|
|
10772
|
+
}
|
|
10773
|
+
__name(skillArgsNote, "skillArgsNote");
|
|
10774
|
+
|
|
10775
|
+
// packages/cli/src/commands/review/lib/authorization.ts
|
|
10776
|
+
function defaultSkillArgsPath() {
|
|
10777
|
+
return skillArgsPath("review");
|
|
10778
|
+
}
|
|
10779
|
+
__name(defaultSkillArgsPath, "defaultSkillArgsPath");
|
|
10780
|
+
function reviewWriteAuthorization(req) {
|
|
10781
|
+
if (req.userAuthorized) {
|
|
10782
|
+
return { ok: true, why: "the user asked for this review to be published" };
|
|
10783
|
+
}
|
|
10784
|
+
const sessionScoped = defaultSkillArgsPath();
|
|
10785
|
+
const path3 = currentSessionId() === "" && req.skillArgs ? req.skillArgs : sessionScoped;
|
|
10786
|
+
let raw;
|
|
10787
|
+
try {
|
|
10788
|
+
raw = readFileSync14(path3, "utf8");
|
|
10789
|
+
} catch {
|
|
10790
|
+
return {
|
|
10791
|
+
ok: false,
|
|
10792
|
+
why: `no review arguments were recorded at ${path3}, so this run cannot show that \`--comment\` was requested`
|
|
10793
|
+
};
|
|
10794
|
+
}
|
|
10795
|
+
const verdict = parseReviewArgs(raw);
|
|
10796
|
+
if (!verdict.comment.effective) {
|
|
10797
|
+
return {
|
|
10798
|
+
ok: false,
|
|
10799
|
+
why: `\`--comment\` was not in the review arguments (${JSON.stringify(raw.trim())})`
|
|
10800
|
+
};
|
|
10801
|
+
}
|
|
10802
|
+
const t2 = verdict.target;
|
|
10803
|
+
const authorisedPr = t2.type === "pr-number" || t2.type === "pr-url" ? t2.number : void 0;
|
|
10804
|
+
if (authorisedPr === void 0) {
|
|
10805
|
+
return {
|
|
10806
|
+
ok: false,
|
|
10807
|
+
why: `the review arguments (${JSON.stringify(raw.trim())}) do not name a pull request, so they cannot authorise posting to one`
|
|
10808
|
+
};
|
|
10809
|
+
}
|
|
10810
|
+
if (authorisedPr !== req.pr) {
|
|
10811
|
+
return {
|
|
10812
|
+
ok: false,
|
|
10813
|
+
why: `the review arguments authorise pull request #${authorisedPr}, but this submission targets #${req.pr}`
|
|
10814
|
+
};
|
|
10815
|
+
}
|
|
10816
|
+
if (t2.type === "pr-url") {
|
|
10817
|
+
if (req.repo !== void 0) {
|
|
10818
|
+
const authorisedRepo = `${t2.owner}/${t2.repo}`;
|
|
10819
|
+
if (authorisedRepo.toLowerCase() !== req.repo.toLowerCase()) {
|
|
10820
|
+
return {
|
|
10821
|
+
ok: false,
|
|
10822
|
+
why: `the review arguments authorise ${authorisedRepo}, but this submission targets ${req.repo}`
|
|
10823
|
+
};
|
|
10824
|
+
}
|
|
10825
|
+
}
|
|
10826
|
+
const writeHost = (req.host ?? "github.com").toLowerCase();
|
|
10827
|
+
if (t2.host.toLowerCase() !== writeHost) {
|
|
10828
|
+
return {
|
|
10829
|
+
ok: false,
|
|
10830
|
+
why: `the review arguments authorise ${t2.host}, but this submission targets ${req.host ?? "github.com"}`
|
|
10831
|
+
};
|
|
10832
|
+
}
|
|
10833
|
+
}
|
|
10834
|
+
return {
|
|
10835
|
+
ok: true,
|
|
10836
|
+
why: `\`--comment\` was in the review arguments for #${authorisedPr}`
|
|
10837
|
+
};
|
|
10838
|
+
}
|
|
10839
|
+
__name(reviewWriteAuthorization, "reviewWriteAuthorization");
|
|
10840
|
+
|
|
10841
|
+
// packages/cli/src/commands/review/lib/assets.ts
|
|
10842
|
+
init_esbuild_shims();
|
|
10843
|
+
var ASSET_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp"]);
|
|
10844
|
+
var MAX_ASSET_BYTES = 10 * 1024 * 1024;
|
|
10845
|
+
var MAX_TOTAL_ASSET_BYTES = 40 * 1024 * 1024;
|
|
10846
|
+
function assetsBranch(pr) {
|
|
10847
|
+
return `pr-assets/${pr}-review`;
|
|
10848
|
+
}
|
|
10849
|
+
__name(assetsBranch, "assetsBranch");
|
|
10850
|
+
function remoteAssetPath(pr, basename6, sha256Hex) {
|
|
10851
|
+
const safe2 = basename6.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
10852
|
+
return `${pr}-review/${sha256Hex.slice(0, 12)}-${safe2}`;
|
|
10853
|
+
}
|
|
10854
|
+
__name(remoteAssetPath, "remoteAssetPath");
|
|
10855
|
+
function rawAssetUrl(opts) {
|
|
10856
|
+
const host = opts.host && opts.host.trim() !== "" ? opts.host : "github.com";
|
|
10857
|
+
return `https://${host}/${opts.repo}/raw/${opts.commitSha}/${opts.remotePath}`;
|
|
10858
|
+
}
|
|
10859
|
+
__name(rawAssetUrl, "rawAssetUrl");
|
|
10860
|
+
function parseAssetsRepo(value) {
|
|
10861
|
+
const v = (value ?? "").trim();
|
|
10862
|
+
if (v === "") {
|
|
10863
|
+
return {
|
|
10864
|
+
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."
|
|
10865
|
+
};
|
|
10866
|
+
}
|
|
10867
|
+
const parts = v.split("/");
|
|
10868
|
+
const segmentOk = /* @__PURE__ */ __name((p) => /^[A-Za-z0-9_.-]+$/.test(p) && p !== "." && p !== "..", "segmentOk");
|
|
10869
|
+
if (parts.length !== 2 || !parts.every(segmentOk)) {
|
|
10870
|
+
return {
|
|
10871
|
+
error: `QWEN_REVIEW_ASSETS_REPO must be owner/repo, got ${JSON.stringify(v)}.`
|
|
10872
|
+
};
|
|
10873
|
+
}
|
|
10874
|
+
return { repo: v };
|
|
10875
|
+
}
|
|
10876
|
+
__name(parseAssetsRepo, "parseAssetsRepo");
|
|
10877
|
+
function validateAssetFile(basename6, bytes) {
|
|
10878
|
+
const ext = basename6.includes(".") ? basename6.slice(basename6.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
10879
|
+
if (!ASSET_EXTENSIONS.has(ext)) {
|
|
10880
|
+
return {
|
|
10881
|
+
ok: false,
|
|
10882
|
+
reason: `${basename6}: extension ${JSON.stringify(ext)} is not an allowed evidence image type (${[...ASSET_EXTENSIONS].join(", ")})`
|
|
10883
|
+
};
|
|
10884
|
+
}
|
|
10885
|
+
if (bytes <= 0) {
|
|
10886
|
+
return { ok: false, reason: `${basename6}: empty file` };
|
|
10887
|
+
}
|
|
10888
|
+
if (bytes > MAX_ASSET_BYTES) {
|
|
10889
|
+
return {
|
|
10890
|
+
ok: false,
|
|
10891
|
+
reason: `${basename6}: ${bytes} bytes exceeds the per-file cap of ${MAX_ASSET_BYTES} bytes`
|
|
10892
|
+
};
|
|
10893
|
+
}
|
|
10894
|
+
return { ok: true };
|
|
10895
|
+
}
|
|
10896
|
+
__name(validateAssetFile, "validateAssetFile");
|
|
10897
|
+
function validateAssetBatch(files) {
|
|
10898
|
+
let total = 0;
|
|
10899
|
+
for (const f of files) {
|
|
10900
|
+
const one = validateAssetFile(f.basename, f.bytes);
|
|
10901
|
+
if (!one.ok) return one;
|
|
10902
|
+
total += f.bytes;
|
|
10903
|
+
if (total > MAX_TOTAL_ASSET_BYTES) {
|
|
10904
|
+
return {
|
|
10905
|
+
ok: false,
|
|
10906
|
+
reason: `total size exceeds ${MAX_TOTAL_ASSET_BYTES} bytes`
|
|
10907
|
+
};
|
|
10908
|
+
}
|
|
10909
|
+
}
|
|
10910
|
+
return { ok: true };
|
|
10911
|
+
}
|
|
10912
|
+
__name(validateAssetBatch, "validateAssetBatch");
|
|
10913
|
+
|
|
10914
|
+
// packages/cli/src/commands/review/publish-assets.ts
|
|
10915
|
+
function putContent(repo, branch, remotePath, contentBase64, pr) {
|
|
10916
|
+
const api = `repos/${repo}/contents/${remotePath}`;
|
|
10917
|
+
const message = `review evidence for PR #${pr}`;
|
|
10918
|
+
const payload = /* @__PURE__ */ __name((sha) => JSON.stringify({
|
|
10919
|
+
message,
|
|
10920
|
+
content: contentBase64,
|
|
10921
|
+
branch,
|
|
10922
|
+
...sha ? { sha } : {}
|
|
10923
|
+
}), "payload");
|
|
10924
|
+
try {
|
|
10925
|
+
ghWithInputRetried(payload(), "api", "-X", "PUT", api, "--input", "-");
|
|
10926
|
+
} catch (err) {
|
|
10927
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10928
|
+
if (!/HTTP 422|"sha"|sha wasn't supplied|already exists/i.test(msg)) {
|
|
10929
|
+
throw err;
|
|
10930
|
+
}
|
|
10931
|
+
let existing;
|
|
10932
|
+
try {
|
|
10933
|
+
existing = gh(
|
|
10934
|
+
"api",
|
|
10935
|
+
`${api}?ref=${encodeURIComponent(branch)}`,
|
|
10936
|
+
"--jq",
|
|
10937
|
+
".sha"
|
|
10938
|
+
).trim();
|
|
10939
|
+
} catch {
|
|
10940
|
+
throw err;
|
|
10941
|
+
}
|
|
10942
|
+
if (existing === "" || existing === "null") {
|
|
10943
|
+
throw err;
|
|
10944
|
+
}
|
|
10945
|
+
ghWithInputRetried(
|
|
10946
|
+
payload(existing),
|
|
10947
|
+
"api",
|
|
10948
|
+
"-X",
|
|
10949
|
+
"PUT",
|
|
10950
|
+
api,
|
|
10951
|
+
"--input",
|
|
10952
|
+
"-"
|
|
10953
|
+
);
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
10956
|
+
__name(putContent, "putContent");
|
|
10957
|
+
function ensureBranch(repo, branch) {
|
|
10958
|
+
try {
|
|
10959
|
+
gh("api", `repos/${repo}/git/ref/heads/${branch}`);
|
|
10960
|
+
return;
|
|
10961
|
+
} catch (err) {
|
|
10962
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10963
|
+
if (!/HTTP 404|Not Found/i.test(msg)) throw err;
|
|
10964
|
+
}
|
|
10965
|
+
const defaultBranch = gh("api", `repos/${repo}`, "--jq", ".default_branch");
|
|
10966
|
+
let baseSha;
|
|
10967
|
+
try {
|
|
10968
|
+
baseSha = gh(
|
|
10969
|
+
"api",
|
|
10970
|
+
`repos/${repo}/git/ref/heads/${defaultBranch.trim()}`,
|
|
10971
|
+
"--jq",
|
|
10972
|
+
".object.sha"
|
|
10973
|
+
);
|
|
10974
|
+
} catch (err) {
|
|
10975
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10976
|
+
if (/HTTP 404|Not Found/i.test(msg)) {
|
|
10977
|
+
throw new Error(
|
|
10978
|
+
`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.`
|
|
10979
|
+
);
|
|
10980
|
+
}
|
|
10981
|
+
throw err;
|
|
10982
|
+
}
|
|
10983
|
+
const base = baseSha.trim();
|
|
10984
|
+
if (base === "" || base === "null") {
|
|
10985
|
+
throw new Error(
|
|
10986
|
+
`could not resolve the head of ${repo}@${defaultBranch.trim()} \u2014 the ref lookup returned nothing to branch from.`
|
|
10987
|
+
);
|
|
10988
|
+
}
|
|
10989
|
+
try {
|
|
10990
|
+
ghWithInputRetried(
|
|
10991
|
+
JSON.stringify({ ref: `refs/heads/${branch}`, sha: base }),
|
|
10992
|
+
"api",
|
|
10993
|
+
"-X",
|
|
10994
|
+
"POST",
|
|
10995
|
+
`repos/${repo}/git/refs`,
|
|
10996
|
+
"--input",
|
|
10997
|
+
"-"
|
|
10998
|
+
);
|
|
10999
|
+
} catch (err) {
|
|
11000
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11001
|
+
if (!/already exists/i.test(msg)) throw err;
|
|
11002
|
+
}
|
|
11003
|
+
}
|
|
11004
|
+
__name(ensureBranch, "ensureBranch");
|
|
11005
|
+
function runPublishAssets(args) {
|
|
11006
|
+
const refuse = /* @__PURE__ */ __name((reason) => {
|
|
11007
|
+
writeStderrLine(`publish-assets: refused \u2014 ${reason}`);
|
|
11008
|
+
writeStdoutLine(JSON.stringify({ published: false }));
|
|
11009
|
+
process.exitCode = 3;
|
|
11010
|
+
}, "refuse");
|
|
11011
|
+
if (!Number.isInteger(args.pr) || args.pr <= 0) {
|
|
11012
|
+
refuse(`--pr must be a positive integer, got ${String(args.pr)}.`);
|
|
11013
|
+
return;
|
|
11014
|
+
}
|
|
11015
|
+
const repoResult = parseAssetsRepo(process.env["QWEN_REVIEW_ASSETS_REPO"]);
|
|
11016
|
+
if ("error" in repoResult) {
|
|
11017
|
+
refuse(repoResult.error);
|
|
11018
|
+
return;
|
|
11019
|
+
}
|
|
11020
|
+
const repo = repoResult.repo;
|
|
11021
|
+
const effectiveHost = args.host ?? (process.env["GH_HOST"]?.trim() || void 0);
|
|
11022
|
+
const auth = reviewWriteAuthorization({
|
|
11023
|
+
userAuthorized: args.userAuthorized,
|
|
11024
|
+
skillArgs: args.skillArgs,
|
|
11025
|
+
pr: args.pr,
|
|
11026
|
+
// Bind the REVIEWED repo when the caller names it, never the assets repo:
|
|
11027
|
+
// the designation itself is the consent for the destination, and binding a
|
|
11028
|
+
// URL-shaped authorisation against a fork-hosted assets repo refused
|
|
11029
|
+
// legitimately authorised runs. Without --reviewed-repo the gate binds the
|
|
11030
|
+
// PR number (and host) alone.
|
|
11031
|
+
repo: args.reviewedRepo,
|
|
11032
|
+
host: effectiveHost
|
|
11033
|
+
});
|
|
11034
|
+
if (!auth.ok) {
|
|
11035
|
+
refuse(
|
|
11036
|
+
`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.`
|
|
11037
|
+
);
|
|
11038
|
+
return;
|
|
11039
|
+
}
|
|
11040
|
+
if (effectiveHost) {
|
|
11041
|
+
try {
|
|
11042
|
+
setGhHost(effectiveHost);
|
|
11043
|
+
} catch (err) {
|
|
11044
|
+
refuse(
|
|
11045
|
+
`${err instanceof Error ? err.message : String(err)} (from ${args.host !== void 0 ? "--host" : "the GH_HOST environment variable"})`
|
|
11046
|
+
);
|
|
11047
|
+
return;
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
11050
|
+
let findings;
|
|
11051
|
+
const fileList = [...args.files ?? []];
|
|
11052
|
+
if (args.findings) {
|
|
11053
|
+
try {
|
|
11054
|
+
const artifact = JSON.parse(readFileSync15(resolve10(args.findings), "utf8"));
|
|
11055
|
+
findings = validateFindings(
|
|
11056
|
+
Array.isArray(artifact) ? artifact : artifact.findings
|
|
11057
|
+
);
|
|
11058
|
+
} catch (err) {
|
|
11059
|
+
refuse(
|
|
11060
|
+
`cannot use the findings artifact at ${JSON.stringify(args.findings)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11061
|
+
);
|
|
11062
|
+
return;
|
|
11063
|
+
}
|
|
11064
|
+
for (const f of findings) {
|
|
11065
|
+
for (const a of f.assetFiles ?? []) fileList.push(a);
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
const unique = [...new Set(fileList)];
|
|
11069
|
+
if (unique.length === 0) {
|
|
11070
|
+
if (args.findings) {
|
|
11071
|
+
writeStderrLine(
|
|
11072
|
+
"publish-assets: no finding carries assetFiles \u2014 nothing to publish."
|
|
11073
|
+
);
|
|
11074
|
+
writeStdoutLine(JSON.stringify({ published: false, count: 0 }));
|
|
11075
|
+
return;
|
|
11076
|
+
}
|
|
11077
|
+
refuse("no files to publish.");
|
|
11078
|
+
return;
|
|
11079
|
+
}
|
|
11080
|
+
const stats = [];
|
|
11081
|
+
for (const file of unique) {
|
|
11082
|
+
const abs = resolve10(file);
|
|
11083
|
+
try {
|
|
11084
|
+
const st = statSync5(abs);
|
|
11085
|
+
if (!st.isFile()) throw new Error("not a regular file");
|
|
11086
|
+
stats.push({ file, abs, basename: basename5(abs), bytes: st.size });
|
|
11087
|
+
} catch (err) {
|
|
11088
|
+
refuse(
|
|
11089
|
+
`cannot read ${JSON.stringify(file)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11090
|
+
);
|
|
11091
|
+
return;
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
const batch = validateAssetBatch(stats);
|
|
11095
|
+
if (!batch.ok) {
|
|
11096
|
+
refuse(batch.reason);
|
|
11097
|
+
return;
|
|
11098
|
+
}
|
|
11099
|
+
const prepared = [];
|
|
11100
|
+
for (const st of stats) {
|
|
11101
|
+
let content;
|
|
11102
|
+
try {
|
|
11103
|
+
content = readFileSync15(st.abs);
|
|
11104
|
+
} catch (err) {
|
|
11105
|
+
refuse(
|
|
11106
|
+
`cannot read ${JSON.stringify(st.file)}: ${err instanceof Error ? err.message : String(err)}`
|
|
11107
|
+
);
|
|
11108
|
+
return;
|
|
11109
|
+
}
|
|
11110
|
+
const sha256 = createHash2("sha256").update(content).digest("hex");
|
|
11111
|
+
prepared.push({
|
|
11112
|
+
file: st.file,
|
|
11113
|
+
bytes: st.bytes,
|
|
11114
|
+
sha256,
|
|
11115
|
+
remotePath: remoteAssetPath(args.pr, st.basename, sha256),
|
|
11116
|
+
contentBase64: content.toString("base64")
|
|
11117
|
+
});
|
|
11118
|
+
}
|
|
11119
|
+
const branch = assetsBranch(args.pr);
|
|
11120
|
+
ensureBranch(repo, branch);
|
|
11121
|
+
for (const p of prepared) {
|
|
11122
|
+
putContent(repo, branch, p.remotePath, p.contentBase64, args.pr);
|
|
11123
|
+
}
|
|
11124
|
+
const headSha = gh(
|
|
11125
|
+
"api",
|
|
11126
|
+
`repos/${repo}/git/ref/heads/${branch}`,
|
|
11127
|
+
"--jq",
|
|
11128
|
+
".object.sha"
|
|
11129
|
+
).trim();
|
|
11130
|
+
const published = prepared.map((p) => ({
|
|
11131
|
+
file: p.file,
|
|
11132
|
+
remotePath: p.remotePath,
|
|
11133
|
+
url: rawAssetUrl({
|
|
11134
|
+
host: effectiveHost,
|
|
11135
|
+
repo,
|
|
11136
|
+
commitSha: headSha,
|
|
11137
|
+
remotePath: p.remotePath
|
|
11138
|
+
}),
|
|
11139
|
+
bytes: p.bytes,
|
|
11140
|
+
sha256: p.sha256
|
|
11141
|
+
}));
|
|
11142
|
+
const manifest = {
|
|
11143
|
+
repo,
|
|
11144
|
+
branch,
|
|
11145
|
+
commitSha: headSha,
|
|
11146
|
+
pr: args.pr,
|
|
11147
|
+
published
|
|
11148
|
+
};
|
|
11149
|
+
const outPath = resolve10(args.out);
|
|
11150
|
+
mkdirSync14(dirname11(outPath), { recursive: true });
|
|
11151
|
+
writeFileSync14(outPath, `${JSON.stringify(manifest, null, 2)}
|
|
11152
|
+
`, "utf8");
|
|
11153
|
+
if (findings && !args.findingsOut) {
|
|
11154
|
+
writeStderrLine(
|
|
11155
|
+
"publish-assets: --findings given without --findings-out \u2014 the URLs are in the manifest only; the artifact keeps local paths."
|
|
11156
|
+
);
|
|
11157
|
+
}
|
|
11158
|
+
if (findings && args.findingsOut) {
|
|
11159
|
+
const urlByFile = new Map(published.map((p) => [p.file, p.url]));
|
|
11160
|
+
const updated = findings.map((f) => {
|
|
11161
|
+
const urls = (f.assetFiles ?? []).map((a) => urlByFile.get(a)).filter((u) => u !== void 0);
|
|
11162
|
+
return urls.length > 0 ? { ...f, assets: urls } : f;
|
|
11163
|
+
});
|
|
11164
|
+
const report = buildReport(updated);
|
|
11165
|
+
const fOut = resolve10(args.findingsOut);
|
|
11166
|
+
mkdirSync14(dirname11(fOut), { recursive: true });
|
|
11167
|
+
writeFileSync14(fOut, `${JSON.stringify(report, null, 2)}
|
|
11168
|
+
`, "utf8");
|
|
11169
|
+
}
|
|
11170
|
+
writeStderrLine(
|
|
11171
|
+
`publish-assets: ${published.length} file(s) \u2192 ${repo}@${branch} (commit ${headSha.slice(0, 9)})`
|
|
11172
|
+
);
|
|
11173
|
+
writeStdoutLine(JSON.stringify({ published: true, count: published.length }));
|
|
11174
|
+
}
|
|
11175
|
+
__name(runPublishAssets, "runPublishAssets");
|
|
11176
|
+
var publishAssetsCommand = {
|
|
11177
|
+
command: "publish-assets",
|
|
11178
|
+
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",
|
|
11179
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("pr", {
|
|
11180
|
+
type: "number",
|
|
11181
|
+
demandOption: true,
|
|
11182
|
+
describe: "The pull request this evidence belongs to"
|
|
11183
|
+
}).option("reviewed-repo", {
|
|
11184
|
+
type: "string",
|
|
11185
|
+
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"
|
|
11186
|
+
}).option("files", {
|
|
11187
|
+
type: "string",
|
|
11188
|
+
array: true,
|
|
11189
|
+
describe: "Evidence image files to publish"
|
|
11190
|
+
}).option("findings", {
|
|
11191
|
+
type: "string",
|
|
11192
|
+
describe: "Findings artifact whose per-finding assetFiles should be published"
|
|
11193
|
+
}).option("findings-out", {
|
|
11194
|
+
type: "string",
|
|
11195
|
+
implies: "findings",
|
|
11196
|
+
describe: "Where to write the findings artifact with published URLs woven into each finding (requires --findings)"
|
|
11197
|
+
}).option("out", {
|
|
11198
|
+
type: "string",
|
|
11199
|
+
demandOption: true,
|
|
11200
|
+
describe: "Where to write the publish manifest"
|
|
11201
|
+
}).option("host", {
|
|
11202
|
+
type: "string",
|
|
11203
|
+
describe: "GitHub Enterprise host (defaults to github.com)"
|
|
11204
|
+
}).option("user-authorized", {
|
|
11205
|
+
type: "boolean",
|
|
11206
|
+
default: false,
|
|
11207
|
+
describe: "Pass ONLY when the user asked, in a message they typed this session, for this review to be published"
|
|
11208
|
+
}).option("skill-args", {
|
|
11209
|
+
type: "string",
|
|
11210
|
+
describe: "Test seam: path to the recorded review arguments (ignored when a session id is present)"
|
|
11211
|
+
}), "builder"),
|
|
11212
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
11213
|
+
runPublishAssets({
|
|
11214
|
+
pr: argv["pr"],
|
|
11215
|
+
reviewedRepo: argv["reviewed-repo"],
|
|
11216
|
+
files: argv["files"],
|
|
11217
|
+
findings: argv["findings"],
|
|
11218
|
+
findingsOut: argv["findings-out"],
|
|
11219
|
+
out: argv["out"],
|
|
11220
|
+
host: argv["host"],
|
|
11221
|
+
userAuthorized: Boolean(argv["user-authorized"]),
|
|
11222
|
+
skillArgs: argv["skill-args"]
|
|
11223
|
+
});
|
|
11224
|
+
}, "handler")
|
|
11225
|
+
};
|
|
11226
|
+
|
|
11227
|
+
// packages/cli/src/commands/review/resolve-anchors.ts
|
|
11228
|
+
init_esbuild_shims();
|
|
11229
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "node:fs";
|
|
11230
|
+
import { dirname as dirname12, resolve as resolve11 } from "node:path";
|
|
11231
|
+
|
|
11232
|
+
// packages/cli/src/commands/review/lib/anchors.ts
|
|
11233
|
+
init_esbuild_shims();
|
|
11234
|
+
function collectNewSideLines(diffText, file) {
|
|
11235
|
+
const lines2 = diffText.split("\n");
|
|
11236
|
+
const out = [];
|
|
11237
|
+
for (const hunk of file.hunks) {
|
|
11238
|
+
if (hunk.newCount === 0) continue;
|
|
11239
|
+
let newCursor = hunk.newStart;
|
|
11240
|
+
for (let n = hunk.diffStart + 1; n <= hunk.diffEnd; n++) {
|
|
11241
|
+
const raw = lines2[n - 1];
|
|
11242
|
+
if (raw === void 0) break;
|
|
11243
|
+
if (raw.startsWith("+")) {
|
|
11244
|
+
out.push({ newLine: newCursor, text: raw.slice(1), added: true });
|
|
11245
|
+
newCursor++;
|
|
11246
|
+
} else if (raw.startsWith("-")) {
|
|
11247
|
+
} else if (raw.startsWith(" ") || raw === "") {
|
|
11248
|
+
out.push({
|
|
11249
|
+
newLine: newCursor,
|
|
11250
|
+
text: raw.startsWith(" ") ? raw.slice(1) : "",
|
|
11251
|
+
added: false
|
|
11252
|
+
});
|
|
11253
|
+
newCursor++;
|
|
11254
|
+
}
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
11257
|
+
return out;
|
|
11258
|
+
}
|
|
11259
|
+
__name(collectNewSideLines, "collectNewSideLines");
|
|
11260
|
+
function normalizeExact(s) {
|
|
11261
|
+
return s.replace(/\s+$/, "");
|
|
11262
|
+
}
|
|
11263
|
+
__name(normalizeExact, "normalizeExact");
|
|
11264
|
+
function normalizeLoose(s) {
|
|
11265
|
+
return s.trim();
|
|
11266
|
+
}
|
|
11267
|
+
__name(normalizeLoose, "normalizeLoose");
|
|
11268
|
+
function anchorVariants(anchor) {
|
|
11269
|
+
const lines2 = anchor.replace(/\r\n/g, "\n").split("\n");
|
|
11270
|
+
while (lines2.length > 0 && lines2[0].trim() === "") lines2.shift();
|
|
11271
|
+
while (lines2.length > 0 && lines2[lines2.length - 1].trim() === "") lines2.pop();
|
|
11272
|
+
if (lines2.length === 0) return [];
|
|
11273
|
+
const variants = [lines2];
|
|
11274
|
+
const meaningful = lines2.filter((l) => l.trim() !== "");
|
|
11275
|
+
if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
|
|
11276
|
+
variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
|
|
11277
|
+
}
|
|
11278
|
+
const NO_NEWLINE = /^\$/;
|
|
11279
|
+
const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
|
|
11280
|
+
if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
|
|
11281
|
+
variants.push(
|
|
11282
|
+
lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
|
|
11283
|
+
);
|
|
11284
|
+
}
|
|
11285
|
+
return variants;
|
|
11286
|
+
}
|
|
11287
|
+
__name(anchorVariants, "anchorVariants");
|
|
11288
|
+
function matchRuns(hay, needle, norm) {
|
|
11289
|
+
const starts = [];
|
|
11290
|
+
if (needle.length === 0 || hay.length < needle.length) return starts;
|
|
11291
|
+
const normNeedle = needle.map(norm);
|
|
11292
|
+
for (let i = 0; i + normNeedle.length <= hay.length; i++) {
|
|
11293
|
+
let ok = true;
|
|
11294
|
+
for (let j = 0; j < normNeedle.length; j++) {
|
|
11295
|
+
const cell = hay[i + j];
|
|
11296
|
+
if (norm(cell.text) !== normNeedle[j]) {
|
|
11297
|
+
ok = false;
|
|
11298
|
+
break;
|
|
11299
|
+
}
|
|
11300
|
+
if (j > 0 && cell.newLine !== hay[i + j - 1].newLine + 1) {
|
|
11301
|
+
ok = false;
|
|
11302
|
+
break;
|
|
11303
|
+
}
|
|
11304
|
+
}
|
|
11305
|
+
if (ok) starts.push(i);
|
|
11306
|
+
}
|
|
11307
|
+
return starts;
|
|
11308
|
+
}
|
|
11309
|
+
__name(matchRuns, "matchRuns");
|
|
11310
|
+
function candidatesFor(hay, needle, norm) {
|
|
11311
|
+
return matchRuns(hay, needle, norm).map((i) => {
|
|
11312
|
+
const run4 = hay.slice(i, i + needle.length);
|
|
11313
|
+
return {
|
|
11314
|
+
startLine: run4[0].newLine,
|
|
11315
|
+
line: run4[run4.length - 1].newLine,
|
|
11316
|
+
added: run4.some((l) => l.added)
|
|
11317
|
+
};
|
|
11318
|
+
});
|
|
11319
|
+
}
|
|
11320
|
+
__name(candidatesFor, "candidatesFor");
|
|
11321
|
+
function pick(cands, claimedLine) {
|
|
11322
|
+
if (cands.length === 1) return cands[0];
|
|
11323
|
+
if (claimedLine !== void 0) {
|
|
11324
|
+
const dist = /* @__PURE__ */ __name((c) => Math.abs(c.startLine - claimedLine), "dist");
|
|
11325
|
+
let best = Infinity;
|
|
11326
|
+
for (const c of cands) best = Math.min(best, dist(c));
|
|
11327
|
+
const nearest = cands.filter((c) => dist(c) === best);
|
|
11328
|
+
return nearest.length === 1 ? nearest[0] : null;
|
|
11329
|
+
}
|
|
11330
|
+
const added = cands.filter((c) => c.added);
|
|
11331
|
+
return added.length === 1 ? added[0] : null;
|
|
11332
|
+
}
|
|
11333
|
+
__name(pick, "pick");
|
|
11334
|
+
function resolveAnchor(newSideLines, anchor, claimedLine) {
|
|
11335
|
+
const variants = anchorVariants(anchor);
|
|
11336
|
+
if (variants.length === 0) {
|
|
11337
|
+
return { status: "unmatched", reason: "anchor is empty" };
|
|
11338
|
+
}
|
|
11339
|
+
for (const [vi, needle] of variants.entries()) {
|
|
11340
|
+
const norms = vi === 0 ? [
|
|
11341
|
+
[true, normalizeExact],
|
|
11342
|
+
[false, normalizeLoose]
|
|
11343
|
+
] : [[true, normalizeExact]];
|
|
11344
|
+
for (const [exact, norm] of norms) {
|
|
11345
|
+
const cands = candidatesFor(newSideLines, needle, norm);
|
|
11346
|
+
if (cands.length === 0) continue;
|
|
11347
|
+
if (!exact && cands.length > 1) {
|
|
11348
|
+
return {
|
|
11349
|
+
status: "unmatched",
|
|
11350
|
+
reason: "the snippet matched in more than one place only after its indentation was normalised \u2014 and in an indentation-significant language the nesting level IS the semantics, so choosing between them would be choosing which block the finding is about. Quote it verbatim."
|
|
11351
|
+
};
|
|
11352
|
+
}
|
|
11353
|
+
const best = pick(cands, claimedLine);
|
|
11354
|
+
if (!best) {
|
|
11355
|
+
return {
|
|
11356
|
+
status: "unmatched",
|
|
10594
11357
|
reason: "the snippet appears in more than one place and nothing distinguishes them \u2014 quote more lines so it is unique, or give the line number you mean so the nearest match can be chosen"
|
|
10595
11358
|
};
|
|
10596
11359
|
}
|
|
@@ -10661,8 +11424,8 @@ function validateRequests(raw) {
|
|
|
10661
11424
|
);
|
|
10662
11425
|
}
|
|
10663
11426
|
}
|
|
10664
|
-
if (o["line"] !== void 0 && typeof o["line"] !== "number") {
|
|
10665
|
-
throw new Error(`Finding "${o["id"]}" has
|
|
11427
|
+
if (o["line"] !== void 0 && (typeof o["line"] !== "number" || !Number.isSafeInteger(o["line"]) || o["line"] <= 0)) {
|
|
11428
|
+
throw new Error(`Finding "${o["id"]}" has an invalid "line".`);
|
|
10666
11429
|
}
|
|
10667
11430
|
return {
|
|
10668
11431
|
id: o["id"],
|
|
@@ -10688,7 +11451,7 @@ __name(validateRequests, "validateRequests");
|
|
|
10688
11451
|
function runResolveAnchors(args) {
|
|
10689
11452
|
let diffText;
|
|
10690
11453
|
try {
|
|
10691
|
-
diffText =
|
|
11454
|
+
diffText = readFileSync16(args.diff, "utf8");
|
|
10692
11455
|
} catch (err) {
|
|
10693
11456
|
throw new Error(
|
|
10694
11457
|
`Cannot read diff file ${args.diff}: ${err.message}`
|
|
@@ -10696,7 +11459,7 @@ function runResolveAnchors(args) {
|
|
|
10696
11459
|
}
|
|
10697
11460
|
let rawText;
|
|
10698
11461
|
try {
|
|
10699
|
-
rawText =
|
|
11462
|
+
rawText = readFileSync16(args.input, "utf8");
|
|
10700
11463
|
} catch (err) {
|
|
10701
11464
|
throw new Error(
|
|
10702
11465
|
`Cannot read findings file ${args.input}: ${err.message}`
|
|
@@ -10727,8 +11490,8 @@ function runResolveAnchors(args) {
|
|
|
10727
11490
|
loose: resolved.filter((r) => r.tier?.startsWith("loose")).length
|
|
10728
11491
|
}
|
|
10729
11492
|
};
|
|
10730
|
-
|
|
10731
|
-
|
|
11493
|
+
mkdirSync15(dirname12(resolve11(args.out)), { recursive: true });
|
|
11494
|
+
writeFileSync15(args.out, JSON.stringify(report, null, 2), "utf8");
|
|
10732
11495
|
writeStdoutLine(`Wrote resolved anchors to ${args.out}`);
|
|
10733
11496
|
const s = report.stats;
|
|
10734
11497
|
writeStderrLine(
|
|
@@ -10775,8 +11538,8 @@ var resolveAnchorsCommand = {
|
|
|
10775
11538
|
|
|
10776
11539
|
// packages/cli/src/commands/review/check-coverage.ts
|
|
10777
11540
|
init_esbuild_shims();
|
|
10778
|
-
import { writeFileSync as
|
|
10779
|
-
import { dirname as
|
|
11541
|
+
import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
|
|
11542
|
+
import { dirname as dirname13, resolve as resolve12 } from "node:path";
|
|
10780
11543
|
function runCheckCoverage(args) {
|
|
10781
11544
|
let report;
|
|
10782
11545
|
try {
|
|
@@ -10792,8 +11555,8 @@ This is an environment problem, not a finding about the agents. Coverage cannot
|
|
|
10792
11555
|
}
|
|
10793
11556
|
throw err;
|
|
10794
11557
|
}
|
|
10795
|
-
|
|
10796
|
-
|
|
11558
|
+
mkdirSync16(dirname13(resolve12(args.out)), { recursive: true });
|
|
11559
|
+
writeFileSync16(args.out, JSON.stringify(report, null, 2));
|
|
10797
11560
|
writeStdoutLine(`Wrote coverage report to ${args.out}`);
|
|
10798
11561
|
const totalChunks = report.coveredChunks.length + report.missingChunks.length + report.uncoverableChunks.length;
|
|
10799
11562
|
const worked = report.agents - report.blindAgents.length - report.idleAgents.length;
|
|
@@ -10810,6 +11573,11 @@ This is an environment problem, not a finding about the agents. Coverage cannot
|
|
|
10810
11573
|
`ERROR: ${report.rewrittenPrompts.length} agent(s) were not launched with the prompt this CLI built for them \u2014 ${report.rewrittenPrompts.join("; ")}. \`agent-prompt\` prints a prompt to be passed VERBATIM; a summary of it is not it. The last run to paraphrase one dropped the rule against reciting a stock sentence and replaced the project's review rules with three sentences of its own. Re-run \`"\${QWEN_CODE_CLI:-qwen}" review agent-prompt\` and pass its output unedited \u2014 copy it, do not retype it.`
|
|
10811
11574
|
);
|
|
10812
11575
|
}
|
|
11576
|
+
if (report.driftedLaunches.length > 0) {
|
|
11577
|
+
writeStderrLine(
|
|
11578
|
+
`NOTE: ${report.driftedLaunches.length} agent(s) launched with a near-verbatim prompt \u2014 ${report.driftedLaunches.join("; ")}. Their briefs were opened and the work is on record, so the delivery stands and NO relaunch is owed. Copy future blocks exactly \u2014 one edited word is what turns a clean pass into this note.`
|
|
11579
|
+
);
|
|
11580
|
+
}
|
|
10813
11581
|
if (report.missingRoles.length > 0) {
|
|
10814
11582
|
writeStderrLine(
|
|
10815
11583
|
// No count: when no role was briefed at all, `missingRoles` collapses to one
|
|
@@ -10886,9 +11654,9 @@ var checkCoverageCommand = {
|
|
|
10886
11654
|
|
|
10887
11655
|
// packages/cli/src/commands/review/agent-prompt.ts
|
|
10888
11656
|
init_esbuild_shims();
|
|
10889
|
-
import { createHash as
|
|
10890
|
-
import { readFileSync as
|
|
10891
|
-
import { dirname as
|
|
11657
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
11658
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
11659
|
+
import { dirname as dirname14, join as join12, resolve as resolve13 } from "node:path";
|
|
10892
11660
|
|
|
10893
11661
|
// packages/cli/src/commands/review/lib/path-rules.ts
|
|
10894
11662
|
init_esbuild_shims();
|
|
@@ -10918,14 +11686,85 @@ var GITHUB_ACTIONS = {
|
|
|
10918
11686
|
|
|
10919
11687
|
**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
11688
|
};
|
|
10921
|
-
var
|
|
11689
|
+
var JAVA = {
|
|
11690
|
+
title: "Java / JVM performance",
|
|
11691
|
+
matches: /* @__PURE__ */ __name((p) => /\.java$/i.test(p), "matches"),
|
|
11692
|
+
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.
|
|
11693
|
+
|
|
11694
|
+
**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.
|
|
11695
|
+
|
|
11696
|
+
**Correctness traps dressed as concurrency or performance code (Critical):**
|
|
11697
|
+
|
|
11698
|
+
- **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\`.
|
|
11699
|
+
- **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\`.
|
|
11700
|
+
- **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).
|
|
11701
|
+
|
|
11702
|
+
**JVM-cost defects provable from the source (Suggestion \u2014 a cost, not a wrongness):**
|
|
11703
|
+
|
|
11704
|
+
- **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\`.
|
|
11705
|
+
- **\`+=\` 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.
|
|
11706
|
+
- **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.
|
|
11707
|
+
- **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.
|
|
11708
|
+
- **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.
|
|
11709
|
+
- **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).
|
|
11710
|
+
- **Legacy synchronized types in new code.** \`Vector\`, \`Hashtable\`, \`StringBuffer\` put a monitor on every call; the unsynchronized equivalents are the default for a reason.
|
|
11711
|
+
- **An exception used as control flow.** Validating by catching \`NumberFormatException\`, looping until \`EOFException\`: \`fillInStackTrace\` costs more than the work being guarded. Validate instead.
|
|
11712
|
+
- **Reflection rediscovered per call.** \`getMethod\`/\`getField\` on every invocation of a hot path; cache the \`Method\`/\`Field\` \u2014 or a \`MethodHandle\` \u2014 once.
|
|
11713
|
+
|
|
11714
|
+
**JIT inlining \u2014 the regression no dimension can see, and you cannot prove from source:**
|
|
11715
|
+
|
|
11716
|
+
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):
|
|
11717
|
+
|
|
11718
|
+
| callee bytecode size | C2 normal-policy size cap |
|
|
11719
|
+
| --- | --- |
|
|
11720
|
+
| \u2264 6 (\`MaxTrivialSize\`) | under the trivial cap \u2014 size alone does not block it |
|
|
11721
|
+
| \u2264 35 (\`MaxInlineSize\`) | under the normal cap \u2014 size alone does not block it |
|
|
11722
|
+
| \u2264 325 (\`FreqInlineSize\`) | under the hot cap \u2014 size blocks it unless the call site is hot |
|
|
11723
|
+
| > 325 | over the hot cap \u2014 size blocks it at every call site |
|
|
11724
|
+
| > 8000 (\`HugeMethodLimit\`, a develop flag gated by the product \`DontCompileHugeMethods\`) | over the huge-method limit \u2014 size blocks even JIT compilation |
|
|
11725
|
+
|
|
11726
|
+
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.
|
|
11727
|
+
|
|
11728
|
+
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.
|
|
11729
|
+
|
|
11730
|
+
**Measure it; do not estimate bytecode from source.** Two tiers, in order of cost:
|
|
11731
|
+
|
|
11732
|
+
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.
|
|
11733
|
+
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.
|
|
11734
|
+
|
|
11735
|
+
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.
|
|
11736
|
+
|
|
11737
|
+
**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.
|
|
11738
|
+
|
|
11739
|
+
**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.`
|
|
11740
|
+
};
|
|
11741
|
+
var PATH_RULES = [GITHUB_ACTIONS, JAVA];
|
|
11742
|
+
function isOutOfScope(p) {
|
|
11743
|
+
return /src\/(test|integrationTest|integTest|androidTest|testFixtures)\//i.test(
|
|
11744
|
+
p
|
|
11745
|
+
) || /(?:^|\/)(?:package-info|module-info)\.java$/i.test(p) || /(?:^|\/)(?:target|build)\/(?:generated-sources|generated-test-sources|generated)\//i.test(
|
|
11746
|
+
p
|
|
11747
|
+
);
|
|
11748
|
+
}
|
|
11749
|
+
__name(isOutOfScope, "isOutOfScope");
|
|
11750
|
+
function describePaths(which) {
|
|
11751
|
+
const CAP = 10;
|
|
11752
|
+
const prod = which.filter((p) => !isOutOfScope(p));
|
|
11753
|
+
const rest = which.filter((p) => isOutOfScope(p));
|
|
11754
|
+
const ordered = [...prod, ...rest];
|
|
11755
|
+
if (ordered.length <= CAP) {
|
|
11756
|
+
return ordered.join(", ");
|
|
11757
|
+
}
|
|
11758
|
+
return `${ordered.slice(0, CAP).join(", ")}, \u2026and ${ordered.length - CAP} more`;
|
|
11759
|
+
}
|
|
11760
|
+
__name(describePaths, "describePaths");
|
|
10922
11761
|
function pathRulesFor(paths) {
|
|
10923
11762
|
const hit = PATH_RULES.filter((r) => paths.some((p) => r.matches(p)));
|
|
10924
11763
|
if (hit.length === 0) return "";
|
|
10925
11764
|
const parts = ["## Rules for the files in front of you", ""];
|
|
10926
11765
|
for (const r of hit) {
|
|
10927
11766
|
const which = paths.filter((p) => r.matches(p));
|
|
10928
|
-
parts.push(`### ${r.title} \u2014 ${which
|
|
11767
|
+
parts.push(`### ${r.title} \u2014 ${describePaths(which)}`, "", r.checklist, "");
|
|
10929
11768
|
}
|
|
10930
11769
|
return parts.join("\n").trimEnd();
|
|
10931
11770
|
}
|
|
@@ -10990,32 +11829,32 @@ function chunkFrom(report, id) {
|
|
|
10990
11829
|
throw new Error("agent-prompt: the plan has no `chunks[]`.");
|
|
10991
11830
|
}
|
|
10992
11831
|
const chunks = report.chunks;
|
|
10993
|
-
const
|
|
10994
|
-
if (!
|
|
11832
|
+
const chunk2 = chunks.find((c) => c?.id === id);
|
|
11833
|
+
if (!chunk2) {
|
|
10995
11834
|
throw new Error(
|
|
10996
11835
|
`agent-prompt: the plan has no chunk ${id} (it has ${chunks.length}: ${chunks.map((c) => c?.id).join(", ")}).`
|
|
10997
11836
|
);
|
|
10998
11837
|
}
|
|
10999
|
-
if (!Number.isSafeInteger(
|
|
11838
|
+
if (!Number.isSafeInteger(chunk2.startLine) || !Number.isSafeInteger(chunk2.endLine) || chunk2.startLine < 1 || chunk2.endLine < chunk2.startLine) {
|
|
11000
11839
|
throw new Error(
|
|
11001
|
-
`agent-prompt: chunk ${id} has no usable line range (startLine=${
|
|
11840
|
+
`agent-prompt: chunk ${id} has no usable line range (startLine=${chunk2.startLine}, endLine=${chunk2.endLine}).`
|
|
11002
11841
|
);
|
|
11003
11842
|
}
|
|
11004
|
-
return { diffPath, chunk, total: chunks.length };
|
|
11843
|
+
return { diffPath, chunk: chunk2, total: chunks.length };
|
|
11005
11844
|
}
|
|
11006
11845
|
__name(chunkFrom, "chunkFrom");
|
|
11007
11846
|
function buildChunkAgentPrompt(report, id, rules) {
|
|
11008
|
-
const { chunk, total } = chunkFrom(report, id);
|
|
11009
|
-
const files = (Array.isArray(
|
|
11847
|
+
const { chunk: chunk2, total } = chunkFrom(report, id);
|
|
11848
|
+
const files = (Array.isArray(chunk2.files) ? chunk2.files : []).filter(
|
|
11010
11849
|
(f) => !!f && typeof f.path === "string" && f.path.length > 0
|
|
11011
11850
|
).map(
|
|
11012
11851
|
(f) => `- ${inertPath(f.path)} (new-side lines ${f.newStart}-${f.newEnd})`
|
|
11013
11852
|
).join("\n");
|
|
11014
|
-
const unreachable =
|
|
11853
|
+
const unreachable = chunk2.maxLineChars > READ_FILE_CHAR_CAP;
|
|
11015
11854
|
const parts = [
|
|
11016
|
-
`You are reviewing chunk ${
|
|
11855
|
+
`You are reviewing chunk ${chunk2.id} of ${total} of a code diff.`,
|
|
11017
11856
|
"",
|
|
11018
|
-
`Your territory: lines ${
|
|
11857
|
+
`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
11858
|
"",
|
|
11020
11859
|
"It covers these source files:",
|
|
11021
11860
|
files || "- (none recorded)",
|
|
@@ -11025,11 +11864,11 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11025
11864
|
if (unreachable) {
|
|
11026
11865
|
parts.push(
|
|
11027
11866
|
"",
|
|
11028
|
-
`**This chunk contains a single line of ${
|
|
11867
|
+
`**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
11868
|
"",
|
|
11030
|
-
` Uncoverable: chunk ${
|
|
11869
|
+
` Uncoverable: chunk ${chunk2.id} \u2014 line exceeds the read limit`
|
|
11031
11870
|
);
|
|
11032
|
-
} else if (
|
|
11871
|
+
} else if (chunk2.oversized) {
|
|
11033
11872
|
parts.push(
|
|
11034
11873
|
"",
|
|
11035
11874
|
"**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 +11888,7 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11049
11888
|
"",
|
|
11050
11889
|
EXCLUSIONS
|
|
11051
11890
|
);
|
|
11052
|
-
const chunkPaths = (Array.isArray(
|
|
11891
|
+
const chunkPaths = (Array.isArray(chunk2.files) ? chunk2.files : []).map((f) => f?.path).filter((p) => typeof p === "string");
|
|
11053
11892
|
const pathRules = pathRulesFor(chunkPaths);
|
|
11054
11893
|
if (pathRules) parts.push("", pathRules);
|
|
11055
11894
|
if (rules && rules.trim()) {
|
|
@@ -11064,7 +11903,7 @@ function buildChunkAgentPrompt(report, id, rules) {
|
|
|
11064
11903
|
if (!unreachable) {
|
|
11065
11904
|
parts.push(
|
|
11066
11905
|
"",
|
|
11067
|
-
`Then, on its own final line: \`Covered: chunk ${
|
|
11906
|
+
`Then, on its own final line: \`Covered: chunk ${chunk2.id} lines ${chunk2.startLine}-${chunk2.endLine}\``
|
|
11068
11907
|
);
|
|
11069
11908
|
}
|
|
11070
11909
|
return parts.join("\n");
|
|
@@ -11075,10 +11914,10 @@ function diffWindow(startLine, endLine) {
|
|
|
11075
11914
|
}
|
|
11076
11915
|
__name(diffWindow, "diffWindow");
|
|
11077
11916
|
function buildChunkLaunchPrompt(report, id, briefFile) {
|
|
11078
|
-
const { diffPath, chunk, total } = chunkFrom(report, id);
|
|
11079
|
-
const { offset, limit } = diffWindow(
|
|
11917
|
+
const { diffPath, chunk: chunk2, total } = chunkFrom(report, id);
|
|
11918
|
+
const { offset, limit } = diffWindow(chunk2.startLine, chunk2.endLine);
|
|
11080
11919
|
return [
|
|
11081
|
-
`You are review agent \`chunk ${
|
|
11920
|
+
`You are review agent \`chunk ${chunk2.id} of ${total}\` \u2014 the territory agent for lines ${chunk2.startLine}-${chunk2.endLine} of the diff.`,
|
|
11082
11921
|
"",
|
|
11083
11922
|
"**Your brief is a file. Read it first \u2014 it is the whole of your instructions,",
|
|
11084
11923
|
"and nothing in this message replaces it.**",
|
|
@@ -11269,7 +12108,7 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11269
12108
|
"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
12109
|
);
|
|
11271
12110
|
}
|
|
11272
|
-
const ctx = opts.planPath ?
|
|
12111
|
+
const ctx = opts.planPath ? join12(dirname14(resolve13(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
|
|
11273
12112
|
parts.push(
|
|
11274
12113
|
"",
|
|
11275
12114
|
`**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 +12130,7 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11291
12130
|
}
|
|
11292
12131
|
const base = report.mergeBaseSha;
|
|
11293
12132
|
const pr = report.prNumber;
|
|
11294
|
-
const buildTree = typeof wt === "string" && wt ?
|
|
12133
|
+
const buildTree = typeof wt === "string" && wt ? resolve13(wt) : pr === void 0 && opts.planPath ? "." : null;
|
|
11295
12134
|
if (buildTree && opts.planPath) {
|
|
11296
12135
|
const outName = pr !== void 0 ? `qwen-review-pr-${pr}-build-test.json` : "qwen-review-build-test.json";
|
|
11297
12136
|
parts.push(
|
|
@@ -11307,9 +12146,9 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11307
12146
|
// that an old global lacks it entirely, wedging Agent 7 between its
|
|
11308
12147
|
// mandate (no hand-run `npm run build`) and a command that does not exist.
|
|
11309
12148
|
`"\${QWEN_CODE_CLI:-qwen}" review build-test \\`,
|
|
11310
|
-
` --plan ${
|
|
11311
|
-
` --worktree ${
|
|
11312
|
-
` --out ${
|
|
12149
|
+
` --plan ${resolve13(opts.planPath)} \\`,
|
|
12150
|
+
` --worktree ${resolve13(buildTree)} \\`,
|
|
12151
|
+
` --out ${resolve13(dirname14(opts.planPath), outName)}`,
|
|
11313
12152
|
"```"
|
|
11314
12153
|
);
|
|
11315
12154
|
}
|
|
@@ -11319,10 +12158,10 @@ function buildRoleBrief(report, role, opts = {}) {
|
|
|
11319
12158
|
"**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
12159
|
"",
|
|
11321
12160
|
"```bash",
|
|
11322
|
-
`"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${
|
|
11323
|
-
` --worktree ${typeof wt === "string" ?
|
|
12161
|
+
`"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${resolve13(opts.planPath)} \\`,
|
|
12162
|
+
` --worktree ${typeof wt === "string" ? resolve13(wt) : "<worktree>"} \\`,
|
|
11324
12163
|
` --base ${base} \\`,
|
|
11325
|
-
` --out ${
|
|
12164
|
+
` --out ${resolve13(dirname14(opts.planPath), `qwen-review-pr-${pr}-efficacy.json`)}`,
|
|
11326
12165
|
"```",
|
|
11327
12166
|
"",
|
|
11328
12167
|
'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 +12307,7 @@ function buildLaunch(report, planPath, spec, rules) {
|
|
|
11468
12307
|
}
|
|
11469
12308
|
__name(buildLaunch, "buildLaunch");
|
|
11470
12309
|
function findingsDigest(content, rules) {
|
|
11471
|
-
return
|
|
12310
|
+
return createHash3("sha256").update(JSON.stringify([content, rules ?? null])).digest("hex").slice(0, 12);
|
|
11472
12311
|
}
|
|
11473
12312
|
__name(findingsDigest, "findingsDigest");
|
|
11474
12313
|
function foldFindings(role, content, prompt) {
|
|
@@ -11674,7 +12513,7 @@ function runAgentPrompt(args) {
|
|
|
11674
12513
|
}
|
|
11675
12514
|
let report;
|
|
11676
12515
|
try {
|
|
11677
|
-
report = JSON.parse(
|
|
12516
|
+
report = JSON.parse(readFileSync17(args.plan, "utf8"));
|
|
11678
12517
|
} catch (err) {
|
|
11679
12518
|
throw new Error(
|
|
11680
12519
|
`agent-prompt: cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -11683,7 +12522,7 @@ function runAgentPrompt(args) {
|
|
|
11683
12522
|
let rules;
|
|
11684
12523
|
if (args.rules) {
|
|
11685
12524
|
try {
|
|
11686
|
-
rules =
|
|
12525
|
+
rules = readFileSync17(args.rules, "utf8");
|
|
11687
12526
|
} catch (err) {
|
|
11688
12527
|
throw new Error(
|
|
11689
12528
|
`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 +12537,7 @@ function runAgentPrompt(args) {
|
|
|
11698
12537
|
if (hasFindings && args.role) {
|
|
11699
12538
|
const role = args.role;
|
|
11700
12539
|
try {
|
|
11701
|
-
findingsContent =
|
|
12540
|
+
findingsContent = readFileSync17(args.findings, "utf8");
|
|
11702
12541
|
} catch (err) {
|
|
11703
12542
|
throw new Error(
|
|
11704
12543
|
`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 +12646,18 @@ var agentPromptCommand = {
|
|
|
11807
12646
|
init_esbuild_shims();
|
|
11808
12647
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
11809
12648
|
import {
|
|
11810
|
-
existsSync as
|
|
11811
|
-
readFileSync as
|
|
11812
|
-
rmSync as
|
|
12649
|
+
existsSync as existsSync7,
|
|
12650
|
+
readFileSync as readFileSync19,
|
|
12651
|
+
rmSync as rmSync5,
|
|
11813
12652
|
statfsSync,
|
|
11814
|
-
writeFileSync as
|
|
12653
|
+
writeFileSync as writeFileSync17
|
|
11815
12654
|
} from "node:fs";
|
|
11816
|
-
import { join as
|
|
12655
|
+
import { join as join14, resolve as resolve14 } from "node:path";
|
|
11817
12656
|
|
|
11818
12657
|
// packages/cli/src/commands/review/lib/workspaces.ts
|
|
11819
12658
|
init_esbuild_shims();
|
|
11820
|
-
import { existsSync as
|
|
11821
|
-
import { join as
|
|
12659
|
+
import { existsSync as existsSync6, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "node:fs";
|
|
12660
|
+
import { join as join13 } from "node:path";
|
|
11822
12661
|
function isWorkspaceMember(filePath, workspaceGlobs) {
|
|
11823
12662
|
return workspaceDirFor(filePath, workspaceGlobs) !== null;
|
|
11824
12663
|
}
|
|
@@ -11856,7 +12695,7 @@ __name(hasUnmodeledWorkspaceGlob, "hasUnmodeledWorkspaceGlob");
|
|
|
11856
12695
|
function readWorkspaceGlobs(root) {
|
|
11857
12696
|
try {
|
|
11858
12697
|
const pkg = JSON.parse(
|
|
11859
|
-
|
|
12698
|
+
readFileSync18(join13(root, "package.json"), "utf8")
|
|
11860
12699
|
);
|
|
11861
12700
|
const ws = pkg.workspaces;
|
|
11862
12701
|
const globs = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
@@ -11869,7 +12708,7 @@ __name(readWorkspaceGlobs, "readWorkspaceGlobs");
|
|
|
11869
12708
|
function readRootPackage(root) {
|
|
11870
12709
|
let pkg;
|
|
11871
12710
|
try {
|
|
11872
|
-
pkg = JSON.parse(
|
|
12711
|
+
pkg = JSON.parse(readFileSync18(join13(root, "package.json"), "utf8"));
|
|
11873
12712
|
} catch {
|
|
11874
12713
|
return null;
|
|
11875
12714
|
}
|
|
@@ -11893,7 +12732,7 @@ function readWorkspacePackages(root) {
|
|
|
11893
12732
|
const base = g.slice(0, -2);
|
|
11894
12733
|
let entries;
|
|
11895
12734
|
try {
|
|
11896
|
-
entries = readdirSync4(
|
|
12735
|
+
entries = readdirSync4(join13(root, base), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11897
12736
|
} catch {
|
|
11898
12737
|
continue;
|
|
11899
12738
|
}
|
|
@@ -11905,11 +12744,11 @@ function readWorkspacePackages(root) {
|
|
|
11905
12744
|
const pkgs = [];
|
|
11906
12745
|
for (const dir of dirs) {
|
|
11907
12746
|
if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) continue;
|
|
11908
|
-
const manifest =
|
|
11909
|
-
if (!
|
|
12747
|
+
const manifest = join13(root, dir, "package.json");
|
|
12748
|
+
if (!existsSync6(manifest)) continue;
|
|
11910
12749
|
let pkg;
|
|
11911
12750
|
try {
|
|
11912
|
-
pkg = JSON.parse(
|
|
12751
|
+
pkg = JSON.parse(readFileSync18(manifest, "utf8"));
|
|
11913
12752
|
} catch {
|
|
11914
12753
|
continue;
|
|
11915
12754
|
}
|
|
@@ -12082,7 +12921,7 @@ __name(unresolvedWorkspaceDeps, "unresolvedWorkspaceDeps");
|
|
|
12082
12921
|
function changedFilesFrom(planPath) {
|
|
12083
12922
|
let parsed;
|
|
12084
12923
|
try {
|
|
12085
|
-
parsed = JSON.parse(
|
|
12924
|
+
parsed = JSON.parse(readFileSync19(planPath, "utf8"));
|
|
12086
12925
|
} catch (err) {
|
|
12087
12926
|
throw new Error(
|
|
12088
12927
|
`build-test: cannot read the plan ${planPath}: ${err.message}`
|
|
@@ -12099,7 +12938,7 @@ function changedFilesFrom(planPath) {
|
|
|
12099
12938
|
}
|
|
12100
12939
|
__name(changedFilesFrom, "changedFilesFrom");
|
|
12101
12940
|
function runBuildTest(args) {
|
|
12102
|
-
const root =
|
|
12941
|
+
const root = resolve14(args.worktree);
|
|
12103
12942
|
const perCommandMs = args.timeout * 1e3;
|
|
12104
12943
|
const exec = args.exec ?? run;
|
|
12105
12944
|
const changed = changedFilesFrom(args.plan);
|
|
@@ -12165,15 +13004,15 @@ function runBuildTest(args) {
|
|
|
12165
13004
|
timedOut: [],
|
|
12166
13005
|
note: ""
|
|
12167
13006
|
};
|
|
12168
|
-
const npmLock =
|
|
12169
|
-
const installComplete = /* @__PURE__ */ __name(() =>
|
|
12170
|
-
if (args.install && !npmLock && !
|
|
13007
|
+
const npmLock = existsSync7(join14(root, "package-lock.json"));
|
|
13008
|
+
const installComplete = /* @__PURE__ */ __name(() => existsSync7(join14(root, "node_modules", ".package-lock.json")), "installComplete");
|
|
13009
|
+
if (args.install && !npmLock && !existsSync7(join14(root, "node_modules"))) {
|
|
12171
13010
|
const altLock = [
|
|
12172
13011
|
["yarn.lock", "yarn install --frozen-lockfile"],
|
|
12173
13012
|
["pnpm-lock.yaml", "pnpm install --frozen-lockfile"],
|
|
12174
13013
|
["bun.lockb", "bun install --frozen-lockfile"],
|
|
12175
13014
|
["bun.lock", "bun install --frozen-lockfile"]
|
|
12176
|
-
].find(([f]) =>
|
|
13015
|
+
].find(([f]) => existsSync7(join14(root, f)));
|
|
12177
13016
|
return unsupportedReport(
|
|
12178
13017
|
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
13018
|
);
|
|
@@ -12191,7 +13030,7 @@ function runBuildTest(args) {
|
|
|
12191
13030
|
if (install.timedOut) results.timedOut.push(install.command);
|
|
12192
13031
|
if (install.timedOut) {
|
|
12193
13032
|
try {
|
|
12194
|
-
|
|
13033
|
+
rmSync5(join14(root, "node_modules"), {
|
|
12195
13034
|
recursive: true,
|
|
12196
13035
|
force: true,
|
|
12197
13036
|
maxRetries: 3
|
|
@@ -12314,7 +13153,7 @@ var buildTestCommand = {
|
|
|
12314
13153
|
try {
|
|
12315
13154
|
const report = runBuildTest(args);
|
|
12316
13155
|
if (args.out) {
|
|
12317
|
-
|
|
13156
|
+
writeFileSync17(args.out, JSON.stringify(report, null, 2));
|
|
12318
13157
|
}
|
|
12319
13158
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12320
13159
|
} catch (err) {
|
|
@@ -12328,25 +13167,25 @@ var buildTestCommand = {
|
|
|
12328
13167
|
init_esbuild_shims();
|
|
12329
13168
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12330
13169
|
import {
|
|
12331
|
-
existsSync as
|
|
12332
|
-
mkdirSync as
|
|
12333
|
-
readFileSync as
|
|
12334
|
-
rmSync as
|
|
12335
|
-
statSync as
|
|
12336
|
-
writeFileSync as
|
|
13170
|
+
existsSync as existsSync8,
|
|
13171
|
+
mkdirSync as mkdirSync17,
|
|
13172
|
+
readFileSync as readFileSync20,
|
|
13173
|
+
rmSync as rmSync7,
|
|
13174
|
+
statSync as statSync6,
|
|
13175
|
+
writeFileSync as writeFileSync18
|
|
12337
13176
|
} from "node:fs";
|
|
12338
|
-
import { dirname as
|
|
13177
|
+
import { dirname as dirname15, join as join15, resolve as resolve15 } from "node:path";
|
|
12339
13178
|
|
|
12340
13179
|
// packages/cli/src/commands/review/lib/worktree.ts
|
|
12341
13180
|
init_esbuild_shims();
|
|
12342
13181
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12343
|
-
import { rmSync as
|
|
13182
|
+
import { rmSync as rmSync6 } from "node:fs";
|
|
12344
13183
|
function discardWorktree(cwd, tree) {
|
|
12345
13184
|
const sweep = spawnSync3("git", ["worktree", "remove", "--force", tree], {
|
|
12346
13185
|
cwd,
|
|
12347
13186
|
encoding: "utf8"
|
|
12348
13187
|
});
|
|
12349
|
-
|
|
13188
|
+
rmSync6(tree, { recursive: true, force: true });
|
|
12350
13189
|
return sweep;
|
|
12351
13190
|
}
|
|
12352
13191
|
__name(discardWorktree, "discardWorktree");
|
|
@@ -12382,7 +13221,7 @@ function runBaseTree(args) {
|
|
|
12382
13221
|
}), "unavailable");
|
|
12383
13222
|
let plan;
|
|
12384
13223
|
try {
|
|
12385
|
-
plan = JSON.parse(
|
|
13224
|
+
plan = JSON.parse(readFileSync20(args.plan, "utf8"));
|
|
12386
13225
|
} catch (err) {
|
|
12387
13226
|
return unavailable(
|
|
12388
13227
|
`cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -12399,15 +13238,15 @@ function runBaseTree(args) {
|
|
|
12399
13238
|
"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
13239
|
);
|
|
12401
13240
|
}
|
|
12402
|
-
const worktree =
|
|
12403
|
-
if (!
|
|
13241
|
+
const worktree = resolve15(args.worktree);
|
|
13242
|
+
if (!existsSync8(worktree)) {
|
|
12404
13243
|
return unavailable(`the review worktree ${worktree} does not exist`);
|
|
12405
13244
|
}
|
|
12406
13245
|
const tree = baseWorktreePath(worktree);
|
|
12407
|
-
const marker = /* @__PURE__ */ __name(() =>
|
|
12408
|
-
const failedMarker = /* @__PURE__ */ __name(() =>
|
|
13246
|
+
const marker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-ok"), "marker");
|
|
13247
|
+
const failedMarker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-failed"), "failedMarker");
|
|
12409
13248
|
try {
|
|
12410
|
-
if (
|
|
13249
|
+
if (existsSync8(tree) && readFileSync20(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
|
|
12411
13250
|
return {
|
|
12412
13251
|
available: true,
|
|
12413
13252
|
path: tree,
|
|
@@ -12419,7 +13258,7 @@ function runBaseTree(args) {
|
|
|
12419
13258
|
} catch {
|
|
12420
13259
|
}
|
|
12421
13260
|
try {
|
|
12422
|
-
if (
|
|
13261
|
+
if (existsSync8(tree) && readFileSync20(failedMarker(), "utf8").trim() === baseSha) {
|
|
12423
13262
|
return {
|
|
12424
13263
|
available: false,
|
|
12425
13264
|
path: tree,
|
|
@@ -12432,14 +13271,14 @@ function runBaseTree(args) {
|
|
|
12432
13271
|
}
|
|
12433
13272
|
const lock = `${tree}.lock`;
|
|
12434
13273
|
try {
|
|
12435
|
-
const age = Date.now() -
|
|
13274
|
+
const age = Date.now() - statSync6(lock).mtimeMs;
|
|
12436
13275
|
if (age > 30 * 60 * 1e3) {
|
|
12437
|
-
|
|
13276
|
+
rmSync7(lock, { recursive: true, force: true });
|
|
12438
13277
|
}
|
|
12439
13278
|
} catch {
|
|
12440
13279
|
}
|
|
12441
13280
|
try {
|
|
12442
|
-
|
|
13281
|
+
mkdirSync17(lock);
|
|
12443
13282
|
} catch (err) {
|
|
12444
13283
|
if (err.code !== "EEXIST") {
|
|
12445
13284
|
return unavailable(
|
|
@@ -12453,7 +13292,7 @@ function runBaseTree(args) {
|
|
|
12453
13292
|
try {
|
|
12454
13293
|
return buildBaseTree(baseSha);
|
|
12455
13294
|
} finally {
|
|
12456
|
-
|
|
13295
|
+
rmSync7(lock, { recursive: true, force: true });
|
|
12457
13296
|
}
|
|
12458
13297
|
function buildBaseTree(baseSha2) {
|
|
12459
13298
|
let sweep;
|
|
@@ -12477,7 +13316,7 @@ function runBaseTree(args) {
|
|
|
12477
13316
|
});
|
|
12478
13317
|
if (!build.ok || build.toolchain !== "npm" || build.build.length === 0) {
|
|
12479
13318
|
try {
|
|
12480
|
-
|
|
13319
|
+
writeFileSync18(failedMarker(), `${baseSha2}
|
|
12481
13320
|
`);
|
|
12482
13321
|
} catch {
|
|
12483
13322
|
}
|
|
@@ -12490,7 +13329,7 @@ function runBaseTree(args) {
|
|
|
12490
13329
|
};
|
|
12491
13330
|
}
|
|
12492
13331
|
try {
|
|
12493
|
-
|
|
13332
|
+
writeFileSync18(marker(), `${baseSha2}
|
|
12494
13333
|
`);
|
|
12495
13334
|
} catch {
|
|
12496
13335
|
}
|
|
@@ -12530,8 +13369,8 @@ var baseTreeCommand = {
|
|
|
12530
13369
|
try {
|
|
12531
13370
|
const report = runBaseTree(args);
|
|
12532
13371
|
if (args.out) {
|
|
12533
|
-
|
|
12534
|
-
|
|
13372
|
+
mkdirSync17(dirname15(resolve15(args.out)), { recursive: true });
|
|
13373
|
+
writeFileSync18(resolve15(args.out), JSON.stringify(report, null, 2));
|
|
12535
13374
|
}
|
|
12536
13375
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12537
13376
|
writeStderrLine(`base-tree: ${report.note}`);
|
|
@@ -12545,8 +13384,8 @@ var baseTreeCommand = {
|
|
|
12545
13384
|
// packages/cli/src/commands/review/test-delta.ts
|
|
12546
13385
|
init_esbuild_shims();
|
|
12547
13386
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12548
|
-
import { existsSync as
|
|
12549
|
-
import { dirname as
|
|
13387
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
|
|
13388
|
+
import { dirname as dirname16, resolve as resolve16 } from "node:path";
|
|
12550
13389
|
var ANSI_SGR_RE2 = /\x1b\[[0-9;]*m/g;
|
|
12551
13390
|
var RERUNNABLE_COMMAND_RE = /^npm test(?: --workspace="[\w@./-]+")?$/;
|
|
12552
13391
|
var TRIM_MARKER_RE = /\.\.\. \[\d+ characters omitted/;
|
|
@@ -12609,7 +13448,7 @@ var TOTAL_BUDGET_MS = 54e4;
|
|
|
12609
13448
|
var DEFAULT_TIMEOUT_S = 300;
|
|
12610
13449
|
function runTestDelta(args) {
|
|
12611
13450
|
const exec = args.exec ?? run2;
|
|
12612
|
-
const baseline =
|
|
13451
|
+
const baseline = resolve16(args.baseline);
|
|
12613
13452
|
const empty = /* @__PURE__ */ __name((note) => ({
|
|
12614
13453
|
entries: [],
|
|
12615
13454
|
netNew: [],
|
|
@@ -12619,13 +13458,13 @@ function runTestDelta(args) {
|
|
|
12619
13458
|
}), "empty");
|
|
12620
13459
|
let report;
|
|
12621
13460
|
try {
|
|
12622
|
-
report = JSON.parse(
|
|
13461
|
+
report = JSON.parse(readFileSync21(args.report, "utf8"));
|
|
12623
13462
|
} catch (err) {
|
|
12624
13463
|
return empty(
|
|
12625
13464
|
`cannot read the build-test report ${args.report}: ${err.message}`
|
|
12626
13465
|
);
|
|
12627
13466
|
}
|
|
12628
|
-
if (!
|
|
13467
|
+
if (!existsSync9(baseline)) {
|
|
12629
13468
|
return empty(
|
|
12630
13469
|
`the base tree ${baseline} does not exist \u2014 run \`qwen review base-tree\` first`
|
|
12631
13470
|
);
|
|
@@ -12724,56 +13563,743 @@ function runTestDelta(args) {
|
|
|
12724
13563
|
`${timedOut} base-side rerun(s) timed out \u2014 infrastructure, not evidence` + (budgetClamped.length ? ` (${budgetClamped.length} of them on a deadline the whole-command budget shortened, not their own: ${budgetClamped.join(", ")} \u2014 a rerun with budget to spare may still measure them)` : "")
|
|
12725
13564
|
);
|
|
12726
13565
|
}
|
|
12727
|
-
if (skippedForBudget.length) {
|
|
12728
|
-
parts.push(
|
|
12729
|
-
`${skippedForBudget.length} failed command(s) not rerun \u2014 the whole-command budget was exhausted (${skippedForBudget.join(", ")}); their failures stay unattributed, judge them by the diff`
|
|
12730
|
-
);
|
|
13566
|
+
if (skippedForBudget.length) {
|
|
13567
|
+
parts.push(
|
|
13568
|
+
`${skippedForBudget.length} failed command(s) not rerun \u2014 the whole-command budget was exhausted (${skippedForBudget.join(", ")}); their failures stay unattributed, judge them by the diff`
|
|
13569
|
+
);
|
|
13570
|
+
}
|
|
13571
|
+
return {
|
|
13572
|
+
entries,
|
|
13573
|
+
netNew,
|
|
13574
|
+
shared,
|
|
13575
|
+
skippedForBudget,
|
|
13576
|
+
note: parts.join(". ") || "nothing to report"
|
|
13577
|
+
};
|
|
13578
|
+
}
|
|
13579
|
+
__name(runTestDelta, "runTestDelta");
|
|
13580
|
+
var testDeltaCommand = {
|
|
13581
|
+
command: "test-delta",
|
|
13582
|
+
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)",
|
|
13583
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("report", {
|
|
13584
|
+
type: "string",
|
|
13585
|
+
demandOption: true,
|
|
13586
|
+
describe: "Agent 7's build-test report (its failed commands and outputs)"
|
|
13587
|
+
}).option("baseline", {
|
|
13588
|
+
type: "string",
|
|
13589
|
+
demandOption: true,
|
|
13590
|
+
describe: "The BUILT base tree from `qwen review base-tree`"
|
|
13591
|
+
}).option("pr-worktree", {
|
|
13592
|
+
type: "string",
|
|
13593
|
+
describe: "The PR worktree the report's failures were produced in \u2014 its root is stripped so both sides compare as repo-relative paths"
|
|
13594
|
+
}).option("out", { type: "string", describe: "Write the JSON report here" }).option("timeout", {
|
|
13595
|
+
type: "number",
|
|
13596
|
+
default: 300,
|
|
13597
|
+
describe: "Per-command deadline in seconds, as build-test"
|
|
13598
|
+
}), "builder"),
|
|
13599
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
13600
|
+
const args = argv;
|
|
13601
|
+
const report = runTestDelta(args);
|
|
13602
|
+
if (args.out) {
|
|
13603
|
+
mkdirSync18(dirname16(resolve16(args.out)), { recursive: true });
|
|
13604
|
+
writeFileSync19(resolve16(args.out), JSON.stringify(report, null, 2));
|
|
13605
|
+
}
|
|
13606
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
13607
|
+
writeStderrLine(`test-delta: ${report.note}`);
|
|
13608
|
+
}, "handler")
|
|
13609
|
+
};
|
|
13610
|
+
|
|
13611
|
+
// packages/cli/src/commands/review/drive.ts
|
|
13612
|
+
init_esbuild_shims();
|
|
13613
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
13614
|
+
import {
|
|
13615
|
+
mkdirSync as mkdirSync19,
|
|
13616
|
+
writeFileSync as writeFileSync20,
|
|
13617
|
+
readFileSync as readFileSync22,
|
|
13618
|
+
existsSync as existsSync10,
|
|
13619
|
+
rmSync as rmSync8,
|
|
13620
|
+
statSync as statSync7
|
|
13621
|
+
} from "node:fs";
|
|
13622
|
+
import { dirname as dirname17, join as join16, resolve as resolve17 } from "node:path";
|
|
13623
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
13624
|
+
var SERVER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
13625
|
+
function shellQuote(v) {
|
|
13626
|
+
return `'${v.split("'").join(`'\\''`)}'`;
|
|
13627
|
+
}
|
|
13628
|
+
__name(shellQuote, "shellQuote");
|
|
13629
|
+
var CAPTURE_MAX = 2e5;
|
|
13630
|
+
var LOG_MAX_BYTES = 8 * 1024 * 1024;
|
|
13631
|
+
var POLL_MS = 250;
|
|
13632
|
+
function logBytes(p) {
|
|
13633
|
+
try {
|
|
13634
|
+
return statSync7(p).size;
|
|
13635
|
+
} catch {
|
|
13636
|
+
return 0;
|
|
13637
|
+
}
|
|
13638
|
+
}
|
|
13639
|
+
__name(logBytes, "logBytes");
|
|
13640
|
+
function waitMs(ms) {
|
|
13641
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
13642
|
+
}
|
|
13643
|
+
__name(waitMs, "waitMs");
|
|
13644
|
+
function run3(cmd, args, input) {
|
|
13645
|
+
const r = spawnSync6(cmd, args, {
|
|
13646
|
+
encoding: "utf8",
|
|
13647
|
+
input,
|
|
13648
|
+
timeout: 3e4,
|
|
13649
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
13650
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
13651
|
+
});
|
|
13652
|
+
return {
|
|
13653
|
+
status: r.status ?? null,
|
|
13654
|
+
stdout: r.stdout ?? "",
|
|
13655
|
+
stderr: r.stderr ?? ""
|
|
13656
|
+
};
|
|
13657
|
+
}
|
|
13658
|
+
__name(run3, "run");
|
|
13659
|
+
var DRIVE_SENTINEL = "__QWEN_REVIEW_DRIVE_DONE__";
|
|
13660
|
+
function wrapScript(script, sentinelPath, sentinel = DRIVE_SENTINEL) {
|
|
13661
|
+
return `trap '__qwen_rc=$?; echo "${sentinel} rc=\${__qwen_rc}" > ${shellQuote(sentinelPath)}' EXIT
|
|
13662
|
+
set +e
|
|
13663
|
+
${script}
|
|
13664
|
+
`;
|
|
13665
|
+
}
|
|
13666
|
+
__name(wrapScript, "wrapScript");
|
|
13667
|
+
function sentinelExitCode(capture, sentinel = DRIVE_SENTINEL) {
|
|
13668
|
+
const re = new RegExp(`${sentinel} rc=(\\d+)`, "g");
|
|
13669
|
+
let last = null;
|
|
13670
|
+
for (let m = re.exec(capture); m; m = re.exec(capture)) last = m;
|
|
13671
|
+
return last ? Number(last[1]) : null;
|
|
13672
|
+
}
|
|
13673
|
+
__name(sentinelExitCode, "sentinelExitCode");
|
|
13674
|
+
function trimCapture(s) {
|
|
13675
|
+
if (s.length <= CAPTURE_MAX) return { text: s, truncated: false };
|
|
13676
|
+
return {
|
|
13677
|
+
text: `... [${s.length - CAPTURE_MAX} characters omitted from the head] ...
|
|
13678
|
+
${s.slice(-CAPTURE_MAX)}`,
|
|
13679
|
+
truncated: true
|
|
13680
|
+
};
|
|
13681
|
+
}
|
|
13682
|
+
__name(trimCapture, "trimCapture");
|
|
13683
|
+
function runDrive(args) {
|
|
13684
|
+
const exec = args.exec ?? run3;
|
|
13685
|
+
const server = args.server;
|
|
13686
|
+
if (!SERVER_NAME_RE.test(server)) {
|
|
13687
|
+
return {
|
|
13688
|
+
outcome: "unavailable",
|
|
13689
|
+
observed: false,
|
|
13690
|
+
exitCode: null,
|
|
13691
|
+
readyAfterMs: null,
|
|
13692
|
+
droveForMs: 0,
|
|
13693
|
+
output: "",
|
|
13694
|
+
truncated: false,
|
|
13695
|
+
killedStale: false,
|
|
13696
|
+
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.`
|
|
13697
|
+
};
|
|
13698
|
+
}
|
|
13699
|
+
const tmux = /* @__PURE__ */ __name((...a) => exec("tmux", ["-L", server, ...a]), "tmux");
|
|
13700
|
+
if (exec("tmux", ["-V"]).status !== 0) {
|
|
13701
|
+
return {
|
|
13702
|
+
outcome: "unavailable",
|
|
13703
|
+
observed: false,
|
|
13704
|
+
exitCode: null,
|
|
13705
|
+
readyAfterMs: null,
|
|
13706
|
+
droveForMs: 0,
|
|
13707
|
+
output: "",
|
|
13708
|
+
truncated: false,
|
|
13709
|
+
killedStale: false,
|
|
13710
|
+
note: "tmux is not available, so nothing could be driven \u2014 an environment gap, not a finding about the diff"
|
|
13711
|
+
};
|
|
13712
|
+
}
|
|
13713
|
+
const killedStale = tmux("kill-server").status === 0;
|
|
13714
|
+
const started = Date.now();
|
|
13715
|
+
let readyAfterMs = null;
|
|
13716
|
+
if (args.ready) {
|
|
13717
|
+
const deadline = started + args.readyTimeout * 1e3;
|
|
13718
|
+
for (; ; ) {
|
|
13719
|
+
if (exec("bash", ["-lc", args.ready]).status === 0) {
|
|
13720
|
+
readyAfterMs = Date.now() - started;
|
|
13721
|
+
break;
|
|
13722
|
+
}
|
|
13723
|
+
if (Date.now() >= deadline) {
|
|
13724
|
+
tmux("kill-server");
|
|
13725
|
+
return {
|
|
13726
|
+
outcome: "not-ready",
|
|
13727
|
+
observed: false,
|
|
13728
|
+
exitCode: null,
|
|
13729
|
+
readyAfterMs: null,
|
|
13730
|
+
droveForMs: 0,
|
|
13731
|
+
output: "",
|
|
13732
|
+
truncated: false,
|
|
13733
|
+
killedStale,
|
|
13734
|
+
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.`
|
|
13735
|
+
};
|
|
13736
|
+
}
|
|
13737
|
+
waitMs(POLL_MS);
|
|
13738
|
+
}
|
|
13739
|
+
}
|
|
13740
|
+
const dir = join16(tmpdir2(), `qwen-review-drive-${server}`);
|
|
13741
|
+
mkdirSync19(dir, { recursive: true });
|
|
13742
|
+
const scriptPath = join16(dir, "drive.sh");
|
|
13743
|
+
const logPath = args.logPath ?? join16(dir, "drive.log");
|
|
13744
|
+
const sentinelPath = join16(dir, "drive.rc");
|
|
13745
|
+
rmSync8(sentinelPath, { force: true });
|
|
13746
|
+
writeFileSync20(scriptPath, wrapScript(args.script, sentinelPath), "utf8");
|
|
13747
|
+
const droveFrom = Date.now();
|
|
13748
|
+
let output = "";
|
|
13749
|
+
let exitCode = null;
|
|
13750
|
+
let outcome = "timed-out";
|
|
13751
|
+
try {
|
|
13752
|
+
const create = tmux(
|
|
13753
|
+
"new-session",
|
|
13754
|
+
"-d",
|
|
13755
|
+
"-c",
|
|
13756
|
+
args.cwd,
|
|
13757
|
+
"bash",
|
|
13758
|
+
"-lc",
|
|
13759
|
+
`bash ${shellQuote(scriptPath)} > ${shellQuote(logPath)} 2>&1`
|
|
13760
|
+
);
|
|
13761
|
+
if (create.status !== 0) {
|
|
13762
|
+
return {
|
|
13763
|
+
outcome: "unavailable",
|
|
13764
|
+
observed: false,
|
|
13765
|
+
exitCode: null,
|
|
13766
|
+
readyAfterMs,
|
|
13767
|
+
droveForMs: 0,
|
|
13768
|
+
output: "",
|
|
13769
|
+
truncated: false,
|
|
13770
|
+
killedStale,
|
|
13771
|
+
note: `tmux could not start a session: ${create.stderr.trim() || "no error text"} \u2014 an environment gap, not a finding`
|
|
13772
|
+
};
|
|
13773
|
+
}
|
|
13774
|
+
const deadline = droveFrom + args.timeout * 1e3;
|
|
13775
|
+
for (; ; ) {
|
|
13776
|
+
output = existsSync10(logPath) ? readFileSync22(logPath, "utf8") : "";
|
|
13777
|
+
exitCode = existsSync10(sentinelPath) ? sentinelExitCode(readFileSync22(sentinelPath, "utf8")) : null;
|
|
13778
|
+
if (exitCode !== null) {
|
|
13779
|
+
outcome = "completed";
|
|
13780
|
+
break;
|
|
13781
|
+
}
|
|
13782
|
+
if (logBytes(logPath) > LOG_MAX_BYTES) {
|
|
13783
|
+
outcome = "overflowed";
|
|
13784
|
+
break;
|
|
13785
|
+
}
|
|
13786
|
+
if (Date.now() >= deadline) break;
|
|
13787
|
+
waitMs(POLL_MS);
|
|
13788
|
+
}
|
|
13789
|
+
} finally {
|
|
13790
|
+
tmux("kill-server");
|
|
13791
|
+
if (!args.logPath) rmSync8(dir, { recursive: true, force: true });
|
|
13792
|
+
}
|
|
13793
|
+
const { text, truncated } = trimCapture(output);
|
|
13794
|
+
const droveForMs = Date.now() - droveFrom;
|
|
13795
|
+
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.`;
|
|
13796
|
+
return {
|
|
13797
|
+
outcome,
|
|
13798
|
+
observed: outcome === "completed",
|
|
13799
|
+
exitCode,
|
|
13800
|
+
readyAfterMs,
|
|
13801
|
+
droveForMs,
|
|
13802
|
+
output: text,
|
|
13803
|
+
truncated,
|
|
13804
|
+
killedStale,
|
|
13805
|
+
note
|
|
13806
|
+
};
|
|
13807
|
+
}
|
|
13808
|
+
__name(runDrive, "runDrive");
|
|
13809
|
+
var driveCommand = {
|
|
13810
|
+
command: "drive",
|
|
13811
|
+
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",
|
|
13812
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("script", {
|
|
13813
|
+
type: "string",
|
|
13814
|
+
demandOption: true,
|
|
13815
|
+
describe: "Shell script to drive (its exit code is captured)"
|
|
13816
|
+
}).option("cwd", {
|
|
13817
|
+
type: "string",
|
|
13818
|
+
demandOption: true,
|
|
13819
|
+
describe: "Working directory \u2014 usually the PR or base worktree"
|
|
13820
|
+
}).option("ready", {
|
|
13821
|
+
type: "string",
|
|
13822
|
+
describe: "Command polled until it exits 0 before driving (omit only when nothing needs to come up first)"
|
|
13823
|
+
}).option("ready-timeout", {
|
|
13824
|
+
type: "number",
|
|
13825
|
+
default: 60,
|
|
13826
|
+
describe: "Seconds to wait for readiness"
|
|
13827
|
+
}).option("timeout", {
|
|
13828
|
+
type: "number",
|
|
13829
|
+
default: 300,
|
|
13830
|
+
describe: "Seconds to wait for the script to reach its sentinel"
|
|
13831
|
+
}).option("server", {
|
|
13832
|
+
type: "string",
|
|
13833
|
+
default: `qr-${process.pid}`,
|
|
13834
|
+
describe: "tmux server name \u2014 namespaced so runs cannot capture each other"
|
|
13835
|
+
}).option("out", {
|
|
13836
|
+
type: "string",
|
|
13837
|
+
describe: "Write the JSON report here"
|
|
13838
|
+
}), "builder"),
|
|
13839
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
13840
|
+
try {
|
|
13841
|
+
const args = argv;
|
|
13842
|
+
const report = runDrive(args);
|
|
13843
|
+
if (args.out) {
|
|
13844
|
+
mkdirSync19(dirname17(resolve17(args.out)), { recursive: true });
|
|
13845
|
+
writeFileSync20(resolve17(args.out), JSON.stringify(report, null, 2));
|
|
13846
|
+
}
|
|
13847
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
13848
|
+
writeStderrLine(`drive: ${report.note}`);
|
|
13849
|
+
if (!report.observed) process.exitCode = 1;
|
|
13850
|
+
} catch (err) {
|
|
13851
|
+
writeStderrLine(err.message);
|
|
13852
|
+
process.exitCode = 1;
|
|
13853
|
+
}
|
|
13854
|
+
}, "handler")
|
|
13855
|
+
};
|
|
13856
|
+
|
|
13857
|
+
// packages/cli/src/commands/review/mock-provider.ts
|
|
13858
|
+
init_esbuild_shims();
|
|
13859
|
+
import {
|
|
13860
|
+
createServer
|
|
13861
|
+
} from "node:http";
|
|
13862
|
+
import { appendFileSync, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
|
|
13863
|
+
import { dirname as dirname18, resolve as resolve18 } from "node:path";
|
|
13864
|
+
import { pathToFileURL } from "node:url";
|
|
13865
|
+
function messagesText(body) {
|
|
13866
|
+
const msgs = Array.isArray(body?.["messages"]) ? body["messages"] : [];
|
|
13867
|
+
const one = /* @__PURE__ */ __name((m) => {
|
|
13868
|
+
const c = m?.content;
|
|
13869
|
+
if (typeof c === "string") return c;
|
|
13870
|
+
if (Array.isArray(c))
|
|
13871
|
+
return c.map((p) => String(p?.text ?? "")).join("");
|
|
13872
|
+
return "";
|
|
13873
|
+
}, "one");
|
|
13874
|
+
return msgs.map(one).join("\n");
|
|
13875
|
+
}
|
|
13876
|
+
__name(messagesText, "messagesText");
|
|
13877
|
+
var enc = /* @__PURE__ */ __name((o) => `data: ${JSON.stringify(o)}
|
|
13878
|
+
|
|
13879
|
+
`, "enc");
|
|
13880
|
+
var BODY_MAX_BYTES = 4 * 1024 * 1024;
|
|
13881
|
+
var RECORD_TEXT_MAX = 8 * 1024;
|
|
13882
|
+
function replyProblem(r) {
|
|
13883
|
+
if (r === null || typeof r !== "object")
|
|
13884
|
+
return `responder returned ${r === void 0 ? "undefined" : JSON.stringify(r)}; it must return {text} | {tool, args?} | {status, body?}`;
|
|
13885
|
+
const o = r;
|
|
13886
|
+
if ("status" in o) {
|
|
13887
|
+
const st = o["status"];
|
|
13888
|
+
if (!(typeof st === "number" && Number.isInteger(st) && st >= 100 && st <= 599))
|
|
13889
|
+
return `responder returned a non-HTTP status ${JSON.stringify(st)}`;
|
|
13890
|
+
try {
|
|
13891
|
+
JSON.stringify(o["body"] ?? {});
|
|
13892
|
+
} catch {
|
|
13893
|
+
return "responder returned a status body that cannot be serialised (a circular reference?)";
|
|
13894
|
+
}
|
|
13895
|
+
return null;
|
|
13896
|
+
}
|
|
13897
|
+
if ("tool" in o) {
|
|
13898
|
+
if (typeof o["tool"] !== "string" || o["tool"] === "")
|
|
13899
|
+
return `responder returned an empty or non-string tool name ${JSON.stringify(o["tool"])}`;
|
|
13900
|
+
try {
|
|
13901
|
+
JSON.stringify(o["args"] ?? {});
|
|
13902
|
+
} catch {
|
|
13903
|
+
return "responder returned tool args that cannot be serialised (a circular reference?)";
|
|
13904
|
+
}
|
|
13905
|
+
return null;
|
|
13906
|
+
}
|
|
13907
|
+
if ("text" in o)
|
|
13908
|
+
return typeof o["text"] === "string" ? null : `responder returned a non-string text ${JSON.stringify(o["text"])}`;
|
|
13909
|
+
return `responder returned an object with none of text/tool/status: ${JSON.stringify(o).slice(0, 80)}`;
|
|
13910
|
+
}
|
|
13911
|
+
__name(replyProblem, "replyProblem");
|
|
13912
|
+
function forRecord(v) {
|
|
13913
|
+
return v.length <= RECORD_TEXT_MAX ? v : `${v.slice(0, RECORD_TEXT_MAX)}\u2026[${v.length - RECORD_TEXT_MAX} more characters]`;
|
|
13914
|
+
}
|
|
13915
|
+
__name(forRecord, "forRecord");
|
|
13916
|
+
function forRecordReq(r) {
|
|
13917
|
+
return {
|
|
13918
|
+
method: r.method,
|
|
13919
|
+
path: r.path,
|
|
13920
|
+
wire: r.wire,
|
|
13921
|
+
n: r.n,
|
|
13922
|
+
stream: r.stream,
|
|
13923
|
+
text: forRecord(r.text),
|
|
13924
|
+
bodyKeys: r.body ? Object.keys(r.body) : []
|
|
13925
|
+
};
|
|
13926
|
+
}
|
|
13927
|
+
__name(forRecordReq, "forRecordReq");
|
|
13928
|
+
function chunk(delta, finish = null) {
|
|
13929
|
+
return {
|
|
13930
|
+
id: "chatcmpl-qwen-review-mock",
|
|
13931
|
+
object: "chat.completion.chunk",
|
|
13932
|
+
created: Math.floor(Date.now() / 1e3),
|
|
13933
|
+
model: "qwen-review-mock",
|
|
13934
|
+
choices: [{ index: 0, delta, finish_reason: finish }]
|
|
13935
|
+
};
|
|
13936
|
+
}
|
|
13937
|
+
__name(chunk, "chunk");
|
|
13938
|
+
function chunksFor(reply) {
|
|
13939
|
+
const out = [
|
|
13940
|
+
chunk({ role: "assistant", content: "" })
|
|
13941
|
+
];
|
|
13942
|
+
if ("tool" in reply) {
|
|
13943
|
+
out.push(
|
|
13944
|
+
chunk({
|
|
13945
|
+
tool_calls: [
|
|
13946
|
+
{
|
|
13947
|
+
index: 0,
|
|
13948
|
+
id: `call_${reply.tool}_1`,
|
|
13949
|
+
type: "function",
|
|
13950
|
+
function: {
|
|
13951
|
+
name: reply.tool,
|
|
13952
|
+
arguments: JSON.stringify(reply.args ?? {})
|
|
13953
|
+
}
|
|
13954
|
+
}
|
|
13955
|
+
]
|
|
13956
|
+
}),
|
|
13957
|
+
chunk({}, "tool_calls")
|
|
13958
|
+
);
|
|
13959
|
+
return out;
|
|
13960
|
+
}
|
|
13961
|
+
if ("text" in reply) {
|
|
13962
|
+
for (const piece of reply.text.match(/[\s\S]{1,40}/g) ?? [])
|
|
13963
|
+
out.push(chunk({ content: piece }));
|
|
12731
13964
|
}
|
|
13965
|
+
out.push(chunk({}, "stop"));
|
|
13966
|
+
return out;
|
|
13967
|
+
}
|
|
13968
|
+
__name(chunksFor, "chunksFor");
|
|
13969
|
+
function completionFor(reply) {
|
|
13970
|
+
const message = "tool" in reply ? {
|
|
13971
|
+
role: "assistant",
|
|
13972
|
+
content: null,
|
|
13973
|
+
tool_calls: [
|
|
13974
|
+
{
|
|
13975
|
+
index: 0,
|
|
13976
|
+
id: `call_${reply.tool}_1`,
|
|
13977
|
+
type: "function",
|
|
13978
|
+
function: {
|
|
13979
|
+
name: reply.tool,
|
|
13980
|
+
arguments: JSON.stringify(reply.args ?? {})
|
|
13981
|
+
}
|
|
13982
|
+
}
|
|
13983
|
+
]
|
|
13984
|
+
} : { role: "assistant", content: "text" in reply ? reply.text : "" };
|
|
12732
13985
|
return {
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
13986
|
+
id: "chatcmpl-qwen-review-mock",
|
|
13987
|
+
object: "chat.completion",
|
|
13988
|
+
created: Math.floor(Date.now() / 1e3),
|
|
13989
|
+
model: "qwen-review-mock",
|
|
13990
|
+
choices: [
|
|
13991
|
+
{
|
|
13992
|
+
index: 0,
|
|
13993
|
+
message,
|
|
13994
|
+
finish_reason: "tool" in reply ? "tool_calls" : "stop"
|
|
13995
|
+
}
|
|
13996
|
+
],
|
|
13997
|
+
// 69% of the corpus carried one, and a client that reads token counts
|
|
13998
|
+
// behaves differently without it — an absent usage block is a behaviour
|
|
13999
|
+
// difference the mock introduced, not one the diff did.
|
|
14000
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
12738
14001
|
};
|
|
12739
14002
|
}
|
|
12740
|
-
__name(
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
14003
|
+
__name(completionFor, "completionFor");
|
|
14004
|
+
function sseEvent(type, data) {
|
|
14005
|
+
return `event: ${type}
|
|
14006
|
+
data: ${JSON.stringify({ type, ...data })}
|
|
14007
|
+
|
|
14008
|
+
`;
|
|
14009
|
+
}
|
|
14010
|
+
__name(sseEvent, "sseEvent");
|
|
14011
|
+
function anthropicStream(reply) {
|
|
14012
|
+
const usage = {
|
|
14013
|
+
input_tokens: 0,
|
|
14014
|
+
output_tokens: 0,
|
|
14015
|
+
cache_creation_input_tokens: 0,
|
|
14016
|
+
cache_read_input_tokens: 0
|
|
14017
|
+
};
|
|
14018
|
+
let out = sseEvent("message_start", {
|
|
14019
|
+
message: {
|
|
14020
|
+
id: "msg_qwen_review_mock",
|
|
14021
|
+
type: "message",
|
|
14022
|
+
role: "assistant",
|
|
14023
|
+
model: "qwen-review-mock",
|
|
14024
|
+
content: [],
|
|
14025
|
+
stop_reason: null,
|
|
14026
|
+
usage
|
|
14027
|
+
}
|
|
14028
|
+
});
|
|
14029
|
+
if ("tool" in reply) {
|
|
14030
|
+
out += sseEvent("content_block_start", {
|
|
14031
|
+
index: 0,
|
|
14032
|
+
content_block: {
|
|
14033
|
+
type: "tool_use",
|
|
14034
|
+
id: `toolu_${reply.tool}_1`,
|
|
14035
|
+
name: reply.tool,
|
|
14036
|
+
input: {}
|
|
14037
|
+
}
|
|
14038
|
+
});
|
|
14039
|
+
out += sseEvent("content_block_delta", {
|
|
14040
|
+
index: 0,
|
|
14041
|
+
delta: {
|
|
14042
|
+
type: "input_json_delta",
|
|
14043
|
+
partial_json: JSON.stringify(reply.args ?? {})
|
|
14044
|
+
}
|
|
14045
|
+
});
|
|
14046
|
+
out += sseEvent("content_block_stop", { index: 0 });
|
|
14047
|
+
out += sseEvent("message_delta", {
|
|
14048
|
+
delta: { stop_reason: "tool_use", stop_sequence: null },
|
|
14049
|
+
usage: { output_tokens: 0 }
|
|
14050
|
+
});
|
|
14051
|
+
} else {
|
|
14052
|
+
const text = "text" in reply ? reply.text : "";
|
|
14053
|
+
out += sseEvent("content_block_start", {
|
|
14054
|
+
index: 0,
|
|
14055
|
+
content_block: { type: "text", text: "" }
|
|
14056
|
+
});
|
|
14057
|
+
for (const piece of text.match(/[\s\S]{1,40}/g) ?? [])
|
|
14058
|
+
out += sseEvent("content_block_delta", {
|
|
14059
|
+
index: 0,
|
|
14060
|
+
delta: { type: "text_delta", text: piece }
|
|
14061
|
+
});
|
|
14062
|
+
out += sseEvent("content_block_stop", { index: 0 });
|
|
14063
|
+
out += sseEvent("message_delta", {
|
|
14064
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
14065
|
+
usage: { output_tokens: 0 }
|
|
14066
|
+
});
|
|
14067
|
+
}
|
|
14068
|
+
return out + sseEvent("message_stop", {});
|
|
14069
|
+
}
|
|
14070
|
+
__name(anthropicStream, "anthropicStream");
|
|
14071
|
+
function anthropicMessage(reply) {
|
|
14072
|
+
const content = "tool" in reply ? [
|
|
14073
|
+
{
|
|
14074
|
+
type: "tool_use",
|
|
14075
|
+
id: `toolu_${reply.tool}_1`,
|
|
14076
|
+
name: reply.tool,
|
|
14077
|
+
input: reply.args ?? {}
|
|
14078
|
+
}
|
|
14079
|
+
] : [{ type: "text", text: "text" in reply ? reply.text : "" }];
|
|
14080
|
+
return {
|
|
14081
|
+
id: "msg_qwen_review_mock",
|
|
14082
|
+
type: "message",
|
|
14083
|
+
role: "assistant",
|
|
14084
|
+
model: "qwen-review-mock",
|
|
14085
|
+
content,
|
|
14086
|
+
stop_reason: "tool" in reply ? "tool_use" : "end_turn",
|
|
14087
|
+
stop_sequence: null,
|
|
14088
|
+
usage: {
|
|
14089
|
+
input_tokens: 0,
|
|
14090
|
+
output_tokens: 0,
|
|
14091
|
+
cache_creation_input_tokens: 0,
|
|
14092
|
+
cache_read_input_tokens: 0
|
|
14093
|
+
}
|
|
14094
|
+
};
|
|
14095
|
+
}
|
|
14096
|
+
__name(anthropicMessage, "anthropicMessage");
|
|
14097
|
+
function anthropicText(body) {
|
|
14098
|
+
const sys = body?.["system"];
|
|
14099
|
+
const sysText = typeof sys === "string" ? sys : Array.isArray(sys) ? sys.map((b) => String(b?.text ?? "")).join("") : "";
|
|
14100
|
+
const rest = messagesText(body);
|
|
14101
|
+
return sysText && rest ? `${sysText}
|
|
14102
|
+
${rest}` : sysText || rest;
|
|
14103
|
+
}
|
|
14104
|
+
__name(anthropicText, "anthropicText");
|
|
14105
|
+
var DEFAULT_REPLY = { text: "ok" };
|
|
14106
|
+
async function startMockProvider(args, respondOverride) {
|
|
14107
|
+
let respond = respondOverride ?? (() => DEFAULT_REPLY);
|
|
14108
|
+
if (!respondOverride && args.responder) {
|
|
14109
|
+
const mod = await import(pathToFileURL(resolve18(args.responder)).href);
|
|
14110
|
+
const fn = mod.respond ?? mod.default;
|
|
14111
|
+
if (typeof fn !== "function") {
|
|
14112
|
+
throw new Error(
|
|
14113
|
+
`mock-provider: ${args.responder} exports no \`respond\` function \u2014 a responder module must export \`respond(req)\` (or a default export of the same shape)`
|
|
14114
|
+
);
|
|
14115
|
+
}
|
|
14116
|
+
respond = fn;
|
|
14117
|
+
}
|
|
14118
|
+
const logPath = resolve18(args.log);
|
|
14119
|
+
mkdirSync20(dirname18(logPath), { recursive: true });
|
|
14120
|
+
writeFileSync21(logPath, "");
|
|
14121
|
+
let n = 0;
|
|
14122
|
+
const record = /* @__PURE__ */ __name((entry) => appendFileSync(logPath, `${JSON.stringify(entry)}
|
|
14123
|
+
`), "record");
|
|
14124
|
+
const server = createServer((req, res) => {
|
|
14125
|
+
const parts = [];
|
|
14126
|
+
let size = 0;
|
|
14127
|
+
let tooBig = false;
|
|
14128
|
+
req.on("data", (d) => {
|
|
14129
|
+
size += d.length;
|
|
14130
|
+
if (size > BODY_MAX_BYTES) {
|
|
14131
|
+
tooBig = true;
|
|
14132
|
+
parts.length = 0;
|
|
14133
|
+
return;
|
|
14134
|
+
}
|
|
14135
|
+
parts.push(d);
|
|
14136
|
+
});
|
|
14137
|
+
req.on("end", () => {
|
|
14138
|
+
if (tooBig) {
|
|
14139
|
+
appendFileSync(
|
|
14140
|
+
logPath,
|
|
14141
|
+
`${JSON.stringify({ t: Date.now(), method: req.method, path: req.url, refused: "body-too-large", bytes: size })}
|
|
14142
|
+
`
|
|
14143
|
+
);
|
|
14144
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
14145
|
+
res.end(
|
|
14146
|
+
JSON.stringify({
|
|
14147
|
+
error: `request body exceeded ${BODY_MAX_BYTES} bytes; refused rather than truncated, because a truncated body is a different request`
|
|
14148
|
+
})
|
|
14149
|
+
);
|
|
14150
|
+
return;
|
|
14151
|
+
}
|
|
14152
|
+
void (async () => {
|
|
14153
|
+
const raw = Buffer.concat(parts).toString("utf8");
|
|
14154
|
+
let body = null;
|
|
14155
|
+
try {
|
|
14156
|
+
body = raw ? JSON.parse(raw) : null;
|
|
14157
|
+
} catch {
|
|
14158
|
+
body = null;
|
|
14159
|
+
}
|
|
14160
|
+
const path3 = req.url ?? "/";
|
|
14161
|
+
const isModels = path3.startsWith("/v1/models");
|
|
14162
|
+
const chat = path3.includes("/chat/completions") || path3.includes("/v1/messages");
|
|
14163
|
+
const willAsk = !isModels && chat && (req.method ?? "GET") === "POST" && body !== null;
|
|
14164
|
+
const wire = path3.includes("/v1/messages") ? "anthropic" : "openai";
|
|
14165
|
+
if (willAsk) n += 1;
|
|
14166
|
+
const mreq = {
|
|
14167
|
+
method: req.method ?? "GET",
|
|
14168
|
+
path: path3,
|
|
14169
|
+
body,
|
|
14170
|
+
wire,
|
|
14171
|
+
text: wire === "anthropic" ? anthropicText(body) : messagesText(body),
|
|
14172
|
+
stream: body?.["stream"] === true,
|
|
14173
|
+
// Null for anything the responder was never asked about, so the
|
|
14174
|
+
// record cannot be read as "the responder saw this".
|
|
14175
|
+
n: willAsk ? n : null
|
|
14176
|
+
};
|
|
14177
|
+
if (!isModels) {
|
|
14178
|
+
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;
|
|
14179
|
+
if (why) {
|
|
14180
|
+
record({ t: Date.now(), ...forRecordReq(mreq), refused: why });
|
|
14181
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
14182
|
+
res.end(JSON.stringify({ error: why }));
|
|
14183
|
+
return;
|
|
14184
|
+
}
|
|
14185
|
+
}
|
|
14186
|
+
if (isModels) {
|
|
14187
|
+
record({ t: Date.now(), ...forRecordReq(mreq), reply: "models" });
|
|
14188
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
14189
|
+
res.end(
|
|
14190
|
+
JSON.stringify({
|
|
14191
|
+
object: "list",
|
|
14192
|
+
data: [{ id: "qwen-review-mock", object: "model" }]
|
|
14193
|
+
})
|
|
14194
|
+
);
|
|
14195
|
+
return;
|
|
14196
|
+
}
|
|
14197
|
+
let reply;
|
|
14198
|
+
try {
|
|
14199
|
+
reply = await respond(mreq);
|
|
14200
|
+
const problem = replyProblem(reply);
|
|
14201
|
+
if (problem) throw new Error(problem);
|
|
14202
|
+
} catch (err) {
|
|
14203
|
+
reply = {
|
|
14204
|
+
status: 500,
|
|
14205
|
+
body: { error: String(err.message) }
|
|
14206
|
+
};
|
|
14207
|
+
}
|
|
14208
|
+
record({ t: Date.now(), ...forRecordReq(mreq), reply });
|
|
14209
|
+
if ("status" in reply) {
|
|
14210
|
+
res.writeHead(reply.status, { "Content-Type": "application/json" });
|
|
14211
|
+
res.end(JSON.stringify(reply.body ?? {}));
|
|
14212
|
+
return;
|
|
14213
|
+
}
|
|
14214
|
+
if (!mreq.stream) {
|
|
14215
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
14216
|
+
res.end(
|
|
14217
|
+
JSON.stringify(
|
|
14218
|
+
wire === "anthropic" ? anthropicMessage(reply) : completionFor(reply)
|
|
14219
|
+
)
|
|
14220
|
+
);
|
|
14221
|
+
return;
|
|
14222
|
+
}
|
|
14223
|
+
res.writeHead(200, {
|
|
14224
|
+
"Content-Type": "text/event-stream",
|
|
14225
|
+
"Cache-Control": "no-cache",
|
|
14226
|
+
Connection: "keep-alive"
|
|
14227
|
+
});
|
|
14228
|
+
if (wire === "anthropic") {
|
|
14229
|
+
res.write(anthropicStream(reply));
|
|
14230
|
+
} else {
|
|
14231
|
+
for (const c of chunksFor(reply)) res.write(enc(c));
|
|
14232
|
+
res.write("data: [DONE]\n\n");
|
|
14233
|
+
}
|
|
14234
|
+
res.end();
|
|
14235
|
+
})();
|
|
14236
|
+
});
|
|
14237
|
+
});
|
|
14238
|
+
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
|
|
14239
|
+
const addr = server.address();
|
|
14240
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
14241
|
+
return {
|
|
14242
|
+
report: {
|
|
14243
|
+
port,
|
|
14244
|
+
baseUrl: `http://127.0.0.1:${port}/v1`,
|
|
14245
|
+
logPath,
|
|
14246
|
+
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`
|
|
14247
|
+
},
|
|
14248
|
+
close: /* @__PURE__ */ __name(() => new Promise((ok) => {
|
|
14249
|
+
server.close(() => ok());
|
|
14250
|
+
}), "close")
|
|
14251
|
+
};
|
|
14252
|
+
}
|
|
14253
|
+
__name(startMockProvider, "startMockProvider");
|
|
14254
|
+
var mockProviderCommand = {
|
|
14255
|
+
command: "mock-provider",
|
|
14256
|
+
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`",
|
|
14257
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("responder", {
|
|
12745
14258
|
type: "string",
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
}).option("baseline", {
|
|
14259
|
+
describe: "Module exporting `respond(req)` returning {text} | {tool,args} | {status,body}"
|
|
14260
|
+
}).option("log", {
|
|
12749
14261
|
type: "string",
|
|
12750
14262
|
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", {
|
|
14263
|
+
describe: "Where to append the JSONL request record"
|
|
14264
|
+
}).option("ttl", {
|
|
12756
14265
|
type: "number",
|
|
12757
|
-
default:
|
|
12758
|
-
describe: "
|
|
14266
|
+
default: 600,
|
|
14267
|
+
describe: "Seconds to serve before shutting down on its own"
|
|
14268
|
+
}).option("out", {
|
|
14269
|
+
type: "string",
|
|
14270
|
+
describe: "Write the JSON report here"
|
|
12759
14271
|
}), "builder"),
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
14272
|
+
// `async`, and the returned promise is what keeps the process alive. A
|
|
14273
|
+
// fire-and-forget `void (async () => …)()` here produced a command that
|
|
14274
|
+
// printed nothing at all and exited: yargs returned, node found no pending
|
|
14275
|
+
// work it could see, and the server it had just bound went with it. Measured
|
|
14276
|
+
// — both streams empty, exit 0, a mock that was never reachable.
|
|
14277
|
+
handler: /* @__PURE__ */ __name(async (argv) => {
|
|
14278
|
+
try {
|
|
14279
|
+
const args = argv;
|
|
14280
|
+
const { report, close } = await startMockProvider(args);
|
|
14281
|
+
if (args.out) {
|
|
14282
|
+
mkdirSync20(dirname18(resolve18(args.out)), { recursive: true });
|
|
14283
|
+
writeFileSync21(resolve18(args.out), JSON.stringify(report, null, 2));
|
|
14284
|
+
}
|
|
14285
|
+
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
14286
|
+
writeStderrLine(`mock-provider: ${report.note}`);
|
|
14287
|
+
await new Promise((done) => {
|
|
14288
|
+
setTimeout(done, Math.max(1, args.ttl) * 1e3);
|
|
14289
|
+
});
|
|
14290
|
+
await close();
|
|
14291
|
+
} catch (err) {
|
|
14292
|
+
writeStderrLine(err.message);
|
|
14293
|
+
process.exitCode = 1;
|
|
12766
14294
|
}
|
|
12767
|
-
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
12768
|
-
writeStderrLine(`test-delta: ${report.note}`);
|
|
12769
14295
|
}, "handler")
|
|
12770
14296
|
};
|
|
12771
14297
|
|
|
12772
14298
|
// packages/cli/src/commands/review/extract-step.ts
|
|
12773
14299
|
init_esbuild_shims();
|
|
12774
14300
|
var import_yaml = __toESM(require_dist(), 1);
|
|
12775
|
-
import { mkdirSync as
|
|
12776
|
-
import { dirname as
|
|
14301
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync22, chmodSync } from "node:fs";
|
|
14302
|
+
import { dirname as dirname19, resolve as resolve19 } from "node:path";
|
|
12777
14303
|
function expressionsOf(...texts) {
|
|
12778
14304
|
const seen = /* @__PURE__ */ new Set();
|
|
12779
14305
|
for (const t2 of texts) {
|
|
@@ -12986,8 +14512,8 @@ function nearestScoped(...values) {
|
|
|
12986
14512
|
__name(nearestScoped, "nearestScoped");
|
|
12987
14513
|
function runDefaultsOf(container) {
|
|
12988
14514
|
const defaults = container?.defaults;
|
|
12989
|
-
const
|
|
12990
|
-
return
|
|
14515
|
+
const run4 = defaults && typeof defaults === "object" ? defaults.run : void 0;
|
|
14516
|
+
return run4 && typeof run4 === "object" ? run4 : {};
|
|
12991
14517
|
}
|
|
12992
14518
|
__name(runDefaultsOf, "runDefaultsOf");
|
|
12993
14519
|
function envValue(v) {
|
|
@@ -13017,10 +14543,10 @@ function nearestFirst(env, sources) {
|
|
|
13017
14543
|
}
|
|
13018
14544
|
__name(nearestFirst, "nearestFirst");
|
|
13019
14545
|
function runExtractStep(args) {
|
|
13020
|
-
const wfPath =
|
|
14546
|
+
const wfPath = resolve19(args.workflow);
|
|
13021
14547
|
let text;
|
|
13022
14548
|
try {
|
|
13023
|
-
text =
|
|
14549
|
+
text = readFileSync23(wfPath, "utf8");
|
|
13024
14550
|
} catch (err) {
|
|
13025
14551
|
throw new Error(
|
|
13026
14552
|
`extract-step: cannot read ${args.workflow}: ${err.message}`
|
|
@@ -13086,8 +14612,8 @@ function runExtractStep(args) {
|
|
|
13086
14612
|
const workingDirectory = workingDir?.value;
|
|
13087
14613
|
const script = step.run;
|
|
13088
14614
|
const stepLabel = String(step.name ?? step.id ?? index);
|
|
13089
|
-
const outPath =
|
|
13090
|
-
|
|
14615
|
+
const outPath = resolve19(args.out);
|
|
14616
|
+
mkdirSync21(dirname19(outPath), { recursive: true });
|
|
13091
14617
|
const header = [
|
|
13092
14618
|
`#!/usr/bin/env ${shellCommand}`,
|
|
13093
14619
|
...commentLines(
|
|
@@ -13111,7 +14637,7 @@ function runExtractStep(args) {
|
|
|
13111
14637
|
),
|
|
13112
14638
|
""
|
|
13113
14639
|
].join("\n");
|
|
13114
|
-
|
|
14640
|
+
writeFileSync22(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
|
|
13115
14641
|
chmodSync(outPath, 493);
|
|
13116
14642
|
return {
|
|
13117
14643
|
workflow: args.workflow,
|
|
@@ -13136,177 +14662,65 @@ function runExtractStep(args) {
|
|
|
13136
14662
|
scriptPath: outPath
|
|
13137
14663
|
};
|
|
13138
14664
|
}
|
|
13139
|
-
__name(runExtractStep, "runExtractStep");
|
|
13140
|
-
var extractStepCommand = {
|
|
13141
|
-
command: "extract-step",
|
|
13142
|
-
describe: "Extract one workflow step's run: script, verbatim, into an executable \u2014 with its env and ${{ }} stub list as metadata",
|
|
13143
|
-
builder: /* @__PURE__ */ __name((yargs) => yargs.option("workflow", {
|
|
13144
|
-
type: "string",
|
|
13145
|
-
demandOption: true,
|
|
13146
|
-
describe: "Path to the workflow YAML (in the tree being reviewed)"
|
|
13147
|
-
}).option("job", { type: "string", demandOption: true, describe: "Job id" }).option("step", {
|
|
13148
|
-
type: "string",
|
|
13149
|
-
demandOption: true,
|
|
13150
|
-
describe: "Step name, id, or 0-based index within the job (an all-digit value is always read as an index)"
|
|
13151
|
-
}).option("out", {
|
|
13152
|
-
type: "string",
|
|
13153
|
-
demandOption: true,
|
|
13154
|
-
describe: "Where to write the executable script"
|
|
13155
|
-
}), "builder"),
|
|
13156
|
-
handler: /* @__PURE__ */ __name((argv) => {
|
|
13157
|
-
try {
|
|
13158
|
-
const meta = runExtractStep(argv);
|
|
13159
|
-
writeStdoutLine(JSON.stringify(meta, null, 2));
|
|
13160
|
-
const inherited = Object.values(meta.envSources).filter(
|
|
13161
|
-
(scope) => scope !== "step"
|
|
13162
|
-
).length;
|
|
13163
|
-
writeStderrLine(
|
|
13164
|
-
`extract-step: wrote ${meta.scriptPath} (${meta.expressions.length} \${{ }} site(s) to stub, ${Object.keys(meta.env).length} env var(s), ${inherited} inherited from job/workflow, invokes: ${meta.invokes.join(", ") || "(none detected)"})`
|
|
13165
|
-
);
|
|
13166
|
-
} catch (err) {
|
|
13167
|
-
writeStderrLine(err.message);
|
|
13168
|
-
process.exitCode = 1;
|
|
13169
|
-
}
|
|
13170
|
-
}, "handler")
|
|
13171
|
-
};
|
|
13172
|
-
|
|
13173
|
-
// packages/cli/src/commands/review/submit.ts
|
|
13174
|
-
init_esbuild_shims();
|
|
13175
|
-
import { mkdirSync as mkdirSync19, readFileSync as readFileSync21 } from "node:fs";
|
|
13176
|
-
|
|
13177
|
-
// packages/cli/src/commands/review/lib/receipt.ts
|
|
13178
|
-
init_esbuild_shims();
|
|
13179
|
-
function parseReceiptIds(raw) {
|
|
13180
|
-
let value;
|
|
13181
|
-
try {
|
|
13182
|
-
value = JSON.parse(raw);
|
|
13183
|
-
} catch {
|
|
13184
|
-
return [];
|
|
13185
|
-
}
|
|
13186
|
-
if (value === null || typeof value !== "object") return [];
|
|
13187
|
-
const parsed = value;
|
|
13188
|
-
const ids = Array.isArray(parsed.reviewIds) ? parsed.reviewIds : typeof parsed.reviewId === "number" ? [parsed.reviewId] : [];
|
|
13189
|
-
return ids.filter((n) => typeof n === "number");
|
|
13190
|
-
}
|
|
13191
|
-
__name(parseReceiptIds, "parseReceiptIds");
|
|
13192
|
-
|
|
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 `
|
|
14665
|
+
__name(runExtractStep, "runExtractStep");
|
|
14666
|
+
var extractStepCommand = {
|
|
14667
|
+
command: "extract-step",
|
|
14668
|
+
describe: "Extract one workflow step's run: script, verbatim, into an executable \u2014 with its env and ${{ }} stub list as metadata",
|
|
14669
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("workflow", {
|
|
14670
|
+
type: "string",
|
|
14671
|
+
demandOption: true,
|
|
14672
|
+
describe: "Path to the workflow YAML (in the tree being reviewed)"
|
|
14673
|
+
}).option("job", { type: "string", demandOption: true, describe: "Job id" }).option("step", {
|
|
14674
|
+
type: "string",
|
|
14675
|
+
demandOption: true,
|
|
14676
|
+
describe: "Step name, id, or 0-based index within the job (an all-digit value is always read as an index)"
|
|
14677
|
+
}).option("out", {
|
|
14678
|
+
type: "string",
|
|
14679
|
+
demandOption: true,
|
|
14680
|
+
describe: "Where to write the executable script"
|
|
14681
|
+
}), "builder"),
|
|
14682
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
14683
|
+
try {
|
|
14684
|
+
const meta = runExtractStep(argv);
|
|
14685
|
+
writeStdoutLine(JSON.stringify(meta, null, 2));
|
|
14686
|
+
const inherited = Object.values(meta.envSources).filter(
|
|
14687
|
+
(scope) => scope !== "step"
|
|
14688
|
+
).length;
|
|
14689
|
+
writeStderrLine(
|
|
14690
|
+
`extract-step: wrote ${meta.scriptPath} (${meta.expressions.length} \${{ }} site(s) to stub, ${Object.keys(meta.env).length} env var(s), ${inherited} inherited from job/workflow, invokes: ${meta.invokes.join(", ") || "(none detected)"})`
|
|
14691
|
+
);
|
|
14692
|
+
} catch (err) {
|
|
14693
|
+
writeStderrLine(err.message);
|
|
14694
|
+
process.exitCode = 1;
|
|
14695
|
+
}
|
|
14696
|
+
}, "handler")
|
|
14697
|
+
};
|
|
13286
14698
|
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
}
|
|
13290
|
-
__name(staleArgsWarning, "staleArgsWarning");
|
|
13291
|
-
function skillArgsNote(path3, args) {
|
|
13292
|
-
return `
|
|
14699
|
+
// packages/cli/src/commands/review/submit.ts
|
|
14700
|
+
init_esbuild_shims();
|
|
14701
|
+
import { mkdirSync as mkdirSync22, readFileSync as readFileSync24 } from "node:fs";
|
|
13293
14702
|
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
14703
|
+
// packages/cli/src/commands/review/lib/receipt.ts
|
|
14704
|
+
init_esbuild_shims();
|
|
14705
|
+
function parseReceiptIds(raw) {
|
|
14706
|
+
let value;
|
|
14707
|
+
try {
|
|
14708
|
+
value = JSON.parse(raw);
|
|
14709
|
+
} catch {
|
|
14710
|
+
return [];
|
|
14711
|
+
}
|
|
14712
|
+
if (value === null || typeof value !== "object") return [];
|
|
14713
|
+
const parsed = value;
|
|
14714
|
+
const ids = Array.isArray(parsed.reviewIds) ? parsed.reviewIds : typeof parsed.reviewId === "number" ? [parsed.reviewId] : [];
|
|
14715
|
+
return ids.filter((n) => typeof n === "number");
|
|
13298
14716
|
}
|
|
13299
|
-
__name(
|
|
14717
|
+
__name(parseReceiptIds, "parseReceiptIds");
|
|
13300
14718
|
|
|
13301
14719
|
// packages/cli/src/commands/review/submit.ts
|
|
13302
|
-
function defaultSkillArgsPath() {
|
|
13303
|
-
return skillArgsPath("review");
|
|
13304
|
-
}
|
|
13305
|
-
__name(defaultSkillArgsPath, "defaultSkillArgsPath");
|
|
13306
14720
|
var EVENTS = /* @__PURE__ */ new Set(["APPROVE", "REQUEST_CHANGES", "COMMENT"]);
|
|
13307
14721
|
function readReceiptIds(receiptPath) {
|
|
13308
14722
|
try {
|
|
13309
|
-
return parseReceiptIds(
|
|
14723
|
+
return parseReceiptIds(readFileSync24(receiptPath, "utf8"));
|
|
13310
14724
|
} catch {
|
|
13311
14725
|
return [];
|
|
13312
14726
|
}
|
|
@@ -13317,60 +14731,20 @@ function isDiffLine(n) {
|
|
|
13317
14731
|
}
|
|
13318
14732
|
__name(isDiffLine, "isDiffLine");
|
|
13319
14733
|
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
|
-
};
|
|
14734
|
+
return reviewWriteAuthorization({
|
|
14735
|
+
userAuthorized: args.userAuthorized,
|
|
14736
|
+
skillArgs: args.skillArgs,
|
|
14737
|
+
pr: args.pr,
|
|
14738
|
+
repo: args.repo,
|
|
14739
|
+
// The EFFECTIVE host, not merely the flag: with --host absent the gh
|
|
14740
|
+
// child inherits an operator-exported GH_HOST, so that is where this
|
|
14741
|
+
// write would route — and what the gate must bind. Same resolution as
|
|
14742
|
+
// publish-assets.
|
|
14743
|
+
// `|| undefined`, not `??`: an exported-but-empty GH_HOST ("" survives
|
|
14744
|
+
// `??`, being non-nullish) must read as "no host", not as a host named
|
|
14745
|
+
// "" that fails every comparison.
|
|
14746
|
+
host: args.host ?? (process.env["GH_HOST"]?.trim() || void 0)
|
|
14747
|
+
});
|
|
13374
14748
|
}
|
|
13375
14749
|
__name(authorization, "authorization");
|
|
13376
14750
|
function compose(payload, cliVersion) {
|
|
@@ -13419,12 +14793,12 @@ function structuralProblems(payload) {
|
|
|
13419
14793
|
return problems;
|
|
13420
14794
|
}
|
|
13421
14795
|
__name(structuralProblems, "structuralProblems");
|
|
13422
|
-
function inconsistencies(payload,
|
|
14796
|
+
function inconsistencies(payload, event2) {
|
|
13423
14797
|
const problems = [];
|
|
13424
14798
|
const comments = payload.comments ?? [];
|
|
13425
|
-
if (!EVENTS.has(
|
|
14799
|
+
if (!EVENTS.has(event2)) {
|
|
13426
14800
|
problems.push(
|
|
13427
|
-
`computed \`event\` is ${JSON.stringify(
|
|
14801
|
+
`computed \`event\` is ${JSON.stringify(event2)}; GitHub accepts only ${[...EVENTS].join(", ")}`
|
|
13428
14802
|
);
|
|
13429
14803
|
}
|
|
13430
14804
|
comments.forEach((c, i) => {
|
|
@@ -13481,7 +14855,7 @@ function runSubmit(args, cliVersion = "unknown") {
|
|
|
13481
14855
|
}
|
|
13482
14856
|
let payload;
|
|
13483
14857
|
try {
|
|
13484
|
-
payload = JSON.parse(
|
|
14858
|
+
payload = JSON.parse(readFileSync24(args.review, "utf8"));
|
|
13485
14859
|
} catch (err) {
|
|
13486
14860
|
throw new Error(
|
|
13487
14861
|
`Cannot read review JSON ${args.review}: ${err.message}`
|
|
@@ -13506,18 +14880,18 @@ Posting is a public, irreversible write, and this run has no authorisation for o
|
|
|
13506
14880
|
` + structural.map((p) => ` - ${p}`).join("\n")
|
|
13507
14881
|
);
|
|
13508
14882
|
}
|
|
13509
|
-
let
|
|
14883
|
+
let event2;
|
|
13510
14884
|
let body;
|
|
13511
14885
|
let cappedBy;
|
|
13512
14886
|
try {
|
|
13513
|
-
({ event, body, cappedBy } = compose(payload, cliVersion));
|
|
14887
|
+
({ event: event2, body, cappedBy } = compose(payload, cliVersion));
|
|
13514
14888
|
} catch (err) {
|
|
13515
14889
|
throw new Error(
|
|
13516
14890
|
`The review state does not compose into a verdict; refusing to post:
|
|
13517
14891
|
- ${err.message}`
|
|
13518
14892
|
);
|
|
13519
14893
|
}
|
|
13520
|
-
const problems = inconsistencies(payload,
|
|
14894
|
+
const problems = inconsistencies(payload, event2);
|
|
13521
14895
|
if (problems.length > 0) {
|
|
13522
14896
|
throw new Error(
|
|
13523
14897
|
`The review payload contradicts itself; refusing to post it:
|
|
@@ -13526,7 +14900,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
|
|
|
13526
14900
|
}
|
|
13527
14901
|
const post = {
|
|
13528
14902
|
commit_id: payload.commit_id,
|
|
13529
|
-
event,
|
|
14903
|
+
event: event2,
|
|
13530
14904
|
body,
|
|
13531
14905
|
comments: payload.comments ?? []
|
|
13532
14906
|
};
|
|
@@ -13537,7 +14911,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
|
|
|
13537
14911
|
);
|
|
13538
14912
|
writeStdoutLine(
|
|
13539
14913
|
JSON.stringify(
|
|
13540
|
-
{ posted: false, wouldPost: true, target, event, cappedBy },
|
|
14914
|
+
{ posted: false, wouldPost: true, target, event: event2, cappedBy },
|
|
13541
14915
|
null,
|
|
13542
14916
|
2
|
|
13543
14917
|
)
|
|
@@ -13557,23 +14931,23 @@ Posting is a public, irreversible write, and this run has no authorisation for o
|
|
|
13557
14931
|
const receiptPath = tmpFile(`pr-${args.pr}`, "submit-receipt.json");
|
|
13558
14932
|
const priorIds = readReceiptIds(receiptPath);
|
|
13559
14933
|
const reviewIds = [.../* @__PURE__ */ new Set([...priorIds, reviewId])];
|
|
13560
|
-
|
|
14934
|
+
mkdirSync22(REVIEW_TMP_DIR, { recursive: true });
|
|
13561
14935
|
atomicWriteFileSync(
|
|
13562
14936
|
receiptPath,
|
|
13563
|
-
`${JSON.stringify({ reviewIds, event, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
14937
|
+
`${JSON.stringify({ reviewIds, event: event2, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
13564
14938
|
`
|
|
13565
14939
|
);
|
|
13566
14940
|
}
|
|
13567
14941
|
} catch {
|
|
13568
14942
|
}
|
|
13569
14943
|
writeStderrLine(
|
|
13570
|
-
`Posted ${
|
|
14944
|
+
`Posted ${event2} to ${args.repo}#${args.pr} \u2014 ${auth.why}` + (cappedBy.length ? ` (capped by ${cappedBy.join(", ")})` : "") + "."
|
|
13571
14945
|
);
|
|
13572
14946
|
writeStdoutLine(
|
|
13573
14947
|
JSON.stringify(
|
|
13574
14948
|
{
|
|
13575
14949
|
posted: true,
|
|
13576
|
-
event,
|
|
14950
|
+
event: event2,
|
|
13577
14951
|
cappedBy,
|
|
13578
14952
|
inlineComments: post.comments.length
|
|
13579
14953
|
},
|
|
@@ -13620,20 +14994,20 @@ var submitCommand = {
|
|
|
13620
14994
|
|
|
13621
14995
|
// packages/cli/src/commands/review/test-efficacy.ts
|
|
13622
14996
|
init_esbuild_shims();
|
|
13623
|
-
import { spawnSync as
|
|
14997
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
13624
14998
|
import {
|
|
13625
|
-
mkdirSync as
|
|
14999
|
+
mkdirSync as mkdirSync23,
|
|
13626
15000
|
readdirSync as readdirSync5,
|
|
13627
|
-
writeFileSync as
|
|
13628
|
-
readFileSync as
|
|
13629
|
-
rmSync as
|
|
15001
|
+
writeFileSync as writeFileSync23,
|
|
15002
|
+
readFileSync as readFileSync25,
|
|
15003
|
+
rmSync as rmSync9,
|
|
13630
15004
|
lstatSync as lstatSync4,
|
|
13631
|
-
existsSync as
|
|
13632
|
-
statSync as
|
|
15005
|
+
existsSync as existsSync11,
|
|
15006
|
+
statSync as statSync8,
|
|
13633
15007
|
symlinkSync
|
|
13634
15008
|
} from "node:fs";
|
|
13635
15009
|
import { createRequire } from "node:module";
|
|
13636
|
-
import { dirname as
|
|
15010
|
+
import { dirname as dirname20, join as join17, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
|
|
13637
15011
|
var FIXTURE_DIR_RE = /(^|\/)(__fixtures__|__mocks__|__snapshots__|fixtures)\//;
|
|
13638
15012
|
function planTestEfficacy(files, workspaceGlobs) {
|
|
13639
15013
|
const tests = files.filter((f) => f.kind === "test").map((f) => f.path);
|
|
@@ -13988,6 +15362,73 @@ function collocatedProbe(file, testPaths) {
|
|
|
13988
15362
|
});
|
|
13989
15363
|
}
|
|
13990
15364
|
__name(collocatedProbe, "collocatedProbe");
|
|
15365
|
+
function heldForRedCollocatedTest(kind, file, probes, greenProbes, baselinePerFile) {
|
|
15366
|
+
const own = collocatedProbe(file, probes);
|
|
15367
|
+
if (!own || greenProbes.includes(own)) return void 0;
|
|
15368
|
+
return collocatedNotGreenDetail(kind, own, baselinePerFile);
|
|
15369
|
+
}
|
|
15370
|
+
__name(heldForRedCollocatedTest, "heldForRedCollocatedTest");
|
|
15371
|
+
function runnerFailureReason(r) {
|
|
15372
|
+
return r.signal || r.error?.code === "ETIMEDOUT" ? "runner-died" : "not-run";
|
|
15373
|
+
}
|
|
15374
|
+
__name(runnerFailureReason, "runnerFailureReason");
|
|
15375
|
+
var ProbeRunFailure = class extends Error {
|
|
15376
|
+
constructor(message, reason) {
|
|
15377
|
+
super(message);
|
|
15378
|
+
this.reason = reason;
|
|
15379
|
+
this.name = "ProbeRunFailure";
|
|
15380
|
+
}
|
|
15381
|
+
static {
|
|
15382
|
+
__name(this, "ProbeRunFailure");
|
|
15383
|
+
}
|
|
15384
|
+
};
|
|
15385
|
+
var BASELINE_REASON = /* @__PURE__ */ new Set([
|
|
15386
|
+
"no-output",
|
|
15387
|
+
"not-in-results",
|
|
15388
|
+
"no-tests",
|
|
15389
|
+
"all-skipped"
|
|
15390
|
+
]);
|
|
15391
|
+
var REASON_PHRASE = {
|
|
15392
|
+
// The suite ran and read green, but the control proved the runner could not
|
|
15393
|
+
// have reported otherwise.
|
|
15394
|
+
"control-failed": "it read green there, but the positive control failed, so nothing could have turned that run red",
|
|
15395
|
+
// Nothing ran: no worktree, the checkout failed, or the runner could not be
|
|
15396
|
+
// started at all — a spawn that fails with ENOENT never gets to a test.
|
|
15397
|
+
"not-run": "no probe suite ran for it at all there",
|
|
15398
|
+
// Started and did not survive. How much of it ran before that is unknown.
|
|
15399
|
+
"runner-died": "the probe suite was started there and did not survive (killed at the deadline, or by a signal)",
|
|
15400
|
+
// Nothing is known about collection here — the runner never produced output
|
|
15401
|
+
// to read. Saying "collected no tests" would be the same invented cause this
|
|
15402
|
+
// function exists to stop, one layer down: a reader sent after a compile
|
|
15403
|
+
// error that is not there, while the runner itself is what fell over.
|
|
15404
|
+
"no-output": "the runner produced no parseable output there, so nothing at all is known about it",
|
|
15405
|
+
// The run answered, and this file was not in the answer. A compile or import
|
|
15406
|
+
// error looks like this — and so does a path that failed to match, which the
|
|
15407
|
+
// boundary and case rules in `classifyProbeRun` exist because of. Naming one
|
|
15408
|
+
// would be picking between two live possibilities.
|
|
15409
|
+
"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",
|
|
15410
|
+
"no-tests": "it collected no tests there \u2014 the shape a compile or import error in the probe tree takes",
|
|
15411
|
+
"all-skipped": "it collected tests there but executed none of them"
|
|
15412
|
+
};
|
|
15413
|
+
var NOT_REPORTED = "the baseline did not report it";
|
|
15414
|
+
function collocatedNotGreenDetail(kind, probe, baselinePerFile) {
|
|
15415
|
+
const entry = baselinePerFile.find((p) => p.file === probe);
|
|
15416
|
+
const reason = entry === void 0 ? NOT_REPORTED : entry.verdict === "inconclusive" ? (
|
|
15417
|
+
// Three reasons are set on the run-level `results` array and never by
|
|
15418
|
+
// `classifyProbeRun`, so a baseline entry cannot carry them. Reaching
|
|
15419
|
+
// one means the caller passed something other than the baseline —
|
|
15420
|
+
// say that, rather than emit "did not run green … it read green".
|
|
15421
|
+
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`
|
|
15422
|
+
) : entry.verdict === "gated" ? "it was RED there" : (
|
|
15423
|
+
// `inert` IS green, so the sentence this function builds does not
|
|
15424
|
+
// apply to it. Say that, rather than produce a fluent claim that
|
|
15425
|
+
// contradicts the measurement — the caller is what is wrong here.
|
|
15426
|
+
"the baseline reported it GREEN, so this explanation does not apply to it"
|
|
15427
|
+
);
|
|
15428
|
+
const what = kind === "mutant" ? "the statement" : "the hunk";
|
|
15429
|
+
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`;
|
|
15430
|
+
}
|
|
15431
|
+
__name(collocatedNotGreenDetail, "collocatedNotGreenDetail");
|
|
13991
15432
|
function hasCollocatedNewTest(file, testPaths) {
|
|
13992
15433
|
return collocatedProbe(file, testPaths) !== void 0;
|
|
13993
15434
|
}
|
|
@@ -14018,6 +15459,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14018
15459
|
return probes.map((file) => ({
|
|
14019
15460
|
file,
|
|
14020
15461
|
verdict: "inconclusive",
|
|
15462
|
+
reason: "no-output",
|
|
14021
15463
|
detail: `runner produced no parseable JSON (exit ${exitCode})${why ? `: ${why}` : ""}`
|
|
14022
15464
|
}));
|
|
14023
15465
|
}
|
|
@@ -14032,10 +15474,19 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14032
15474
|
const assertions = result?.assertionResults ?? [];
|
|
14033
15475
|
const failed = assertions.filter((a) => a.status === "failed").length;
|
|
14034
15476
|
const passed = assertions.filter((a) => a.status === "passed").length;
|
|
14035
|
-
if (!result
|
|
15477
|
+
if (!result) {
|
|
15478
|
+
return {
|
|
15479
|
+
file,
|
|
15480
|
+
verdict: "inconclusive",
|
|
15481
|
+
reason: "not-in-results",
|
|
15482
|
+
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`
|
|
15483
|
+
};
|
|
15484
|
+
}
|
|
15485
|
+
if (assertions.length === 0) {
|
|
14036
15486
|
return {
|
|
14037
15487
|
file,
|
|
14038
15488
|
verdict: "inconclusive",
|
|
15489
|
+
reason: "no-tests",
|
|
14039
15490
|
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
15491
|
};
|
|
14041
15492
|
}
|
|
@@ -14050,6 +15501,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14050
15501
|
return {
|
|
14051
15502
|
file,
|
|
14052
15503
|
verdict: "inconclusive",
|
|
15504
|
+
reason: "all-skipped",
|
|
14053
15505
|
detail: `${assertions.length} test(s) collected but none executed with the source reverted (all skipped) \u2014 not evidence either way`
|
|
14054
15506
|
};
|
|
14055
15507
|
}
|
|
@@ -14062,7 +15514,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
|
|
|
14062
15514
|
}
|
|
14063
15515
|
__name(classifyProbeRun, "classifyProbeRun");
|
|
14064
15516
|
function git3(cwd, ...args) {
|
|
14065
|
-
const r =
|
|
15517
|
+
const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
|
|
14066
15518
|
if (r.error) throw r.error;
|
|
14067
15519
|
if (r.status !== 0) {
|
|
14068
15520
|
throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
|
|
@@ -14070,7 +15522,7 @@ function git3(cwd, ...args) {
|
|
|
14070
15522
|
}
|
|
14071
15523
|
__name(git3, "git");
|
|
14072
15524
|
function gitOut2(cwd, ...args) {
|
|
14073
|
-
const r =
|
|
15525
|
+
const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
|
|
14074
15526
|
if (r.error) throw r.error;
|
|
14075
15527
|
if (r.status !== 0) {
|
|
14076
15528
|
throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
|
|
@@ -14079,7 +15531,7 @@ function gitOut2(cwd, ...args) {
|
|
|
14079
15531
|
}
|
|
14080
15532
|
__name(gitOut2, "gitOut");
|
|
14081
15533
|
function findVitestBin(worktree) {
|
|
14082
|
-
const req = createRequire(
|
|
15534
|
+
const req = createRequire(join17(worktree, "noop.js"));
|
|
14083
15535
|
let pkgPath;
|
|
14084
15536
|
try {
|
|
14085
15537
|
pkgPath = req.resolve("vitest/package.json");
|
|
@@ -14089,16 +15541,16 @@ function findVitestBin(worktree) {
|
|
|
14089
15541
|
}
|
|
14090
15542
|
throw error;
|
|
14091
15543
|
}
|
|
14092
|
-
const pkg = JSON.parse(
|
|
15544
|
+
const pkg = JSON.parse(readFileSync25(pkgPath, "utf8"));
|
|
14093
15545
|
const bin = pkg.bin?.vitest;
|
|
14094
15546
|
if (!bin) {
|
|
14095
15547
|
throw new Error(`vitest package at ${pkgPath} declares no "vitest" bin`);
|
|
14096
15548
|
}
|
|
14097
|
-
return
|
|
15549
|
+
return join17(dirname20(pkgPath), bin);
|
|
14098
15550
|
}
|
|
14099
15551
|
__name(findVitestBin, "findVitestBin");
|
|
14100
15552
|
function gitCapture(cwd, ...args) {
|
|
14101
|
-
const r =
|
|
15553
|
+
const r = spawnSync7("git", args, {
|
|
14102
15554
|
cwd,
|
|
14103
15555
|
encoding: "utf8",
|
|
14104
15556
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -14111,7 +15563,7 @@ function gitCapture(cwd, ...args) {
|
|
|
14111
15563
|
}
|
|
14112
15564
|
__name(gitCapture, "gitCapture");
|
|
14113
15565
|
function existsAtRev(cwd, rev, path3) {
|
|
14114
|
-
const r =
|
|
15566
|
+
const r = spawnSync7("git", ["cat-file", "-e", `${rev}:${path3}`], { cwd });
|
|
14115
15567
|
if (r.error) throw r.error;
|
|
14116
15568
|
return r.status === 0;
|
|
14117
15569
|
}
|
|
@@ -14120,7 +15572,7 @@ function safeRmWithin(worktree, relPath) {
|
|
|
14120
15572
|
const parts = relPath.split(/[/\\]+/).filter((s) => s && s !== ".");
|
|
14121
15573
|
let cur = worktree;
|
|
14122
15574
|
for (let i = 0; i < parts.length; i++) {
|
|
14123
|
-
cur =
|
|
15575
|
+
cur = join17(cur, parts[i]);
|
|
14124
15576
|
let st;
|
|
14125
15577
|
try {
|
|
14126
15578
|
st = lstatSync4(cur);
|
|
@@ -14133,7 +15585,7 @@ function safeRmWithin(worktree, relPath) {
|
|
|
14133
15585
|
);
|
|
14134
15586
|
}
|
|
14135
15587
|
}
|
|
14136
|
-
|
|
15588
|
+
rmSync9(cur, { force: true });
|
|
14137
15589
|
}
|
|
14138
15590
|
__name(safeRmWithin, "safeRmWithin");
|
|
14139
15591
|
var existsAtBase = /* @__PURE__ */ __name((cwd, base, path3) => existsAtRev(cwd, base, path3), "existsAtBase");
|
|
@@ -14145,15 +15597,15 @@ __name(probeCleanupFailureDetail, "probeCleanupFailureDetail");
|
|
|
14145
15597
|
function exposeDependencies(probeTree, dependencyRoot) {
|
|
14146
15598
|
const done = { linked: 0, failed: 0 };
|
|
14147
15599
|
if (probeTree === dependencyRoot) return done;
|
|
14148
|
-
const source =
|
|
14149
|
-
const target =
|
|
14150
|
-
if (!
|
|
14151
|
-
|
|
15600
|
+
const source = join17(dependencyRoot, "node_modules");
|
|
15601
|
+
const target = join17(probeTree, "node_modules");
|
|
15602
|
+
if (!existsSync11(source) || existsSync11(target)) return done;
|
|
15603
|
+
mkdirSync23(target);
|
|
14152
15604
|
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
14153
15605
|
for (const entry of readdirSync5(source, { withFileTypes: true })) {
|
|
14154
15606
|
if (entry.name === ".vite" || entry.name === ".vite-temp") continue;
|
|
14155
|
-
const sourceEntry =
|
|
14156
|
-
const targetEntry =
|
|
15607
|
+
const sourceEntry = join17(source, entry.name);
|
|
15608
|
+
const targetEntry = join17(target, entry.name);
|
|
14157
15609
|
let sourceStats;
|
|
14158
15610
|
try {
|
|
14159
15611
|
sourceStats = lstatSync4(sourceEntry);
|
|
@@ -14163,7 +15615,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14163
15615
|
}
|
|
14164
15616
|
if (sourceStats.isSymbolicLink()) {
|
|
14165
15617
|
try {
|
|
14166
|
-
if (!
|
|
15618
|
+
if (!statSync8(sourceEntry).isDirectory()) continue;
|
|
14167
15619
|
} catch {
|
|
14168
15620
|
continue;
|
|
14169
15621
|
}
|
|
@@ -14173,7 +15625,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14173
15625
|
if (entry.name.startsWith("@")) {
|
|
14174
15626
|
let pkgs;
|
|
14175
15627
|
try {
|
|
14176
|
-
|
|
15628
|
+
mkdirSync23(targetEntry);
|
|
14177
15629
|
pkgs = readdirSync5(sourceEntry);
|
|
14178
15630
|
} catch {
|
|
14179
15631
|
done.failed++;
|
|
@@ -14181,7 +15633,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
|
|
|
14181
15633
|
}
|
|
14182
15634
|
for (const pkg of pkgs) {
|
|
14183
15635
|
try {
|
|
14184
|
-
symlinkSync(
|
|
15636
|
+
symlinkSync(join17(sourceEntry, pkg), join17(targetEntry, pkg), linkType);
|
|
14185
15637
|
done.linked++;
|
|
14186
15638
|
} catch {
|
|
14187
15639
|
done.failed++;
|
|
@@ -14203,7 +15655,7 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
|
|
|
14203
15655
|
const started = now();
|
|
14204
15656
|
const timeout = deadlineAt !== void 0 ? Math.max(1, Math.min(PROBE_RUN_TIMEOUT_MS, deadlineAt - started)) : PROBE_RUN_TIMEOUT_MS;
|
|
14205
15657
|
const exposed = exposeDependencies(probeTree, dependencyRoot);
|
|
14206
|
-
const r =
|
|
15658
|
+
const r = spawnSync7(
|
|
14207
15659
|
process.execPath,
|
|
14208
15660
|
[findVitestBin(dependencyRoot), "run", "--reporter=json", ...probes],
|
|
14209
15661
|
{
|
|
@@ -14216,10 +15668,12 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
|
|
|
14216
15668
|
maxBuffer: 64 * 1024 * 1024
|
|
14217
15669
|
}
|
|
14218
15670
|
);
|
|
14219
|
-
if (r.error)
|
|
15671
|
+
if (r.error)
|
|
15672
|
+
throw new ProbeRunFailure(r.error.message, runnerFailureReason(r));
|
|
14220
15673
|
if (r.signal) {
|
|
14221
|
-
throw new
|
|
14222
|
-
`runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}
|
|
15674
|
+
throw new ProbeRunFailure(
|
|
15675
|
+
`runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}`,
|
|
15676
|
+
runnerFailureReason(r)
|
|
14223
15677
|
);
|
|
14224
15678
|
}
|
|
14225
15679
|
return {
|
|
@@ -14325,10 +15779,10 @@ function newSideLength(header) {
|
|
|
14325
15779
|
__name(newSideLength, "newSideLength");
|
|
14326
15780
|
function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
14327
15781
|
const { patch: _patch, ...meta } = hunk;
|
|
14328
|
-
const abs =
|
|
15782
|
+
const abs = join17(probeTree, hunk.file);
|
|
14329
15783
|
let original;
|
|
14330
15784
|
try {
|
|
14331
|
-
original =
|
|
15785
|
+
original = readFileSync25(abs, "utf8");
|
|
14332
15786
|
} catch (e) {
|
|
14333
15787
|
return {
|
|
14334
15788
|
...meta,
|
|
@@ -14336,7 +15790,7 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
|
14336
15790
|
detail: `the probe tree does not hold ${hunk.file}: ${e instanceof Error ? e.message : String(e)}`
|
|
14337
15791
|
};
|
|
14338
15792
|
}
|
|
14339
|
-
const applied =
|
|
15793
|
+
const applied = spawnSync7("git", ["apply", "--reverse", "-"], {
|
|
14340
15794
|
cwd: probeTree,
|
|
14341
15795
|
input: hunk.patch,
|
|
14342
15796
|
encoding: "utf8"
|
|
@@ -14354,21 +15808,21 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
|
|
|
14354
15808
|
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
15809
|
return { ...meta, verdict, detail };
|
|
14356
15810
|
} finally {
|
|
14357
|
-
|
|
14358
|
-
|
|
15811
|
+
mkdirSync23(dirname20(abs), { recursive: true });
|
|
15812
|
+
writeFileSync23(abs, original, "utf8");
|
|
14359
15813
|
}
|
|
14360
15814
|
}
|
|
14361
15815
|
__name(runOneHunkProbe, "runOneHunkProbe");
|
|
14362
15816
|
function runControlMutant(probeTree, probeFile, deadlineAt, now = Date.now) {
|
|
14363
|
-
const abs =
|
|
15817
|
+
const abs = join17(probeTree, probeFile);
|
|
14364
15818
|
let original;
|
|
14365
15819
|
try {
|
|
14366
|
-
original =
|
|
15820
|
+
original = readFileSync25(abs, "utf8");
|
|
14367
15821
|
} catch {
|
|
14368
15822
|
return null;
|
|
14369
15823
|
}
|
|
14370
15824
|
try {
|
|
14371
|
-
|
|
15825
|
+
writeFileSync23(
|
|
14372
15826
|
abs,
|
|
14373
15827
|
`${original}
|
|
14374
15828
|
;import { it as __qcIt, expect as __qcExpect } from 'vitest';
|
|
@@ -14381,13 +15835,13 @@ __qcIt('QWEN-REVIEW-POSITIVE-CONTROL', () => {
|
|
|
14381
15835
|
const { perFile } = runProbeSuite(probeTree, [probeFile], deadlineAt, now);
|
|
14382
15836
|
return perFile.some((r) => r.verdict === "gated");
|
|
14383
15837
|
} finally {
|
|
14384
|
-
|
|
15838
|
+
writeFileSync23(abs, original, "utf8");
|
|
14385
15839
|
}
|
|
14386
15840
|
}
|
|
14387
15841
|
__name(runControlMutant, "runControlMutant");
|
|
14388
15842
|
function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dependencyRoot = probeTree) {
|
|
14389
|
-
const abs =
|
|
14390
|
-
const original =
|
|
15843
|
+
const abs = join17(probeTree, mutant.file);
|
|
15844
|
+
const original = readFileSync25(abs, "utf8");
|
|
14391
15845
|
const lines2 = original.split("\n");
|
|
14392
15846
|
if ((lines2[mutant.line - 1] ?? "").trim() !== mutant.statement) {
|
|
14393
15847
|
return {
|
|
@@ -14403,7 +15857,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
|
|
|
14403
15857
|
lines2.splice(mutant.line - 1, 1);
|
|
14404
15858
|
}
|
|
14405
15859
|
try {
|
|
14406
|
-
|
|
15860
|
+
writeFileSync23(abs, lines2.join("\n"), "utf8");
|
|
14407
15861
|
const { perFile } = runProbeSuite(
|
|
14408
15862
|
probeTree,
|
|
14409
15863
|
probes,
|
|
@@ -14415,7 +15869,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
|
|
|
14415
15869
|
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
15870
|
return { ...mutant, verdict, detail };
|
|
14417
15871
|
} finally {
|
|
14418
|
-
|
|
15872
|
+
writeFileSync23(abs, original, "utf8");
|
|
14419
15873
|
}
|
|
14420
15874
|
}
|
|
14421
15875
|
__name(runOneMutant, "runOneMutant");
|
|
@@ -14454,9 +15908,9 @@ async function runTestEfficacy(args) {
|
|
|
14454
15908
|
const now = args.now ?? Date.now;
|
|
14455
15909
|
const startedAt = now();
|
|
14456
15910
|
const { report, worktree, base, out } = args;
|
|
14457
|
-
const plan = JSON.parse(
|
|
15911
|
+
const plan = JSON.parse(readFileSync25(report, "utf8"));
|
|
14458
15912
|
const rootPkg = JSON.parse(
|
|
14459
|
-
|
|
15913
|
+
readFileSync25(`${worktree}/package.json`, "utf8")
|
|
14460
15914
|
);
|
|
14461
15915
|
const globs = rootPkg.workspaces ?? [];
|
|
14462
15916
|
const { unreachable, probes, revert } = planTestEfficacy(
|
|
@@ -14464,8 +15918,8 @@ async function runTestEfficacy(args) {
|
|
|
14464
15918
|
globs
|
|
14465
15919
|
);
|
|
14466
15920
|
for (const p of revert) {
|
|
14467
|
-
const norm =
|
|
14468
|
-
const root =
|
|
15921
|
+
const norm = join17(worktree, p);
|
|
15922
|
+
const root = join17(worktree, ".");
|
|
14469
15923
|
if (isAbsolute3(p) || norm !== root && !norm.startsWith(root + sep3)) {
|
|
14470
15924
|
throw new Error(
|
|
14471
15925
|
`refusing to run: revert path escapes the worktree: ${JSON.stringify(p)}`
|
|
@@ -14568,7 +16022,12 @@ async function runTestEfficacy(args) {
|
|
|
14568
16022
|
String(sweep?.stderr ?? "")
|
|
14569
16023
|
);
|
|
14570
16024
|
for (const file of probes) {
|
|
14571
|
-
results.push({
|
|
16025
|
+
results.push({
|
|
16026
|
+
file,
|
|
16027
|
+
verdict: "inconclusive",
|
|
16028
|
+
reason: "not-run",
|
|
16029
|
+
detail
|
|
16030
|
+
});
|
|
14572
16031
|
}
|
|
14573
16032
|
for (const c of candidates) {
|
|
14574
16033
|
mutantResults.push({ ...c, verdict: "inconclusive", detail });
|
|
@@ -14618,6 +16077,21 @@ async function runTestEfficacy(args) {
|
|
|
14618
16077
|
);
|
|
14619
16078
|
}
|
|
14620
16079
|
for (const c of harnessValidated === false ? [] : candidates) {
|
|
16080
|
+
const heldDetail = heldForRedCollocatedTest(
|
|
16081
|
+
"mutant",
|
|
16082
|
+
c.file,
|
|
16083
|
+
probes,
|
|
16084
|
+
greenProbes,
|
|
16085
|
+
baseline.perFile
|
|
16086
|
+
);
|
|
16087
|
+
if (heldDetail) {
|
|
16088
|
+
mutantResults.push({
|
|
16089
|
+
...c,
|
|
16090
|
+
verdict: "inconclusive",
|
|
16091
|
+
detail: heldDetail
|
|
16092
|
+
});
|
|
16093
|
+
continue;
|
|
16094
|
+
}
|
|
14621
16095
|
const remaining = mutantDeadline - now();
|
|
14622
16096
|
if (!fitsAnotherMutantRun(remaining, estimatedRunMs)) {
|
|
14623
16097
|
mutantsSkippedForBudget = candidates.length - mutantResults.length;
|
|
@@ -14635,13 +16109,19 @@ async function runTestEfficacy(args) {
|
|
|
14635
16109
|
);
|
|
14636
16110
|
}
|
|
14637
16111
|
for (const h of harnessValidated === false ? [] : hunkCandidates) {
|
|
14638
|
-
const
|
|
14639
|
-
|
|
16112
|
+
const heldDetail = heldForRedCollocatedTest(
|
|
16113
|
+
"hunk",
|
|
16114
|
+
h.file,
|
|
16115
|
+
probes,
|
|
16116
|
+
greenProbes,
|
|
16117
|
+
baseline.perFile
|
|
16118
|
+
);
|
|
16119
|
+
if (heldDetail) {
|
|
14640
16120
|
const { patch: _patch, ...meta } = h;
|
|
14641
16121
|
hunkResults.push({
|
|
14642
16122
|
...meta,
|
|
14643
16123
|
verdict: "inconclusive",
|
|
14644
|
-
detail:
|
|
16124
|
+
detail: heldDetail
|
|
14645
16125
|
});
|
|
14646
16126
|
continue;
|
|
14647
16127
|
}
|
|
@@ -14692,11 +16172,14 @@ async function runTestEfficacy(args) {
|
|
|
14692
16172
|
noteDependencyFarm(revertRun.exposed);
|
|
14693
16173
|
results.push(...revertRun.perFile);
|
|
14694
16174
|
} catch (e) {
|
|
14695
|
-
const
|
|
16175
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
16176
|
+
const detail = `probe could not run: ${message}`;
|
|
16177
|
+
const reason = e instanceof ProbeRunFailure ? e.reason : "not-run";
|
|
14696
16178
|
results.push(
|
|
14697
16179
|
...probes.map((file) => ({
|
|
14698
16180
|
file,
|
|
14699
16181
|
verdict: "inconclusive",
|
|
16182
|
+
reason,
|
|
14700
16183
|
detail
|
|
14701
16184
|
}))
|
|
14702
16185
|
);
|
|
@@ -14708,7 +16191,7 @@ async function runTestEfficacy(args) {
|
|
|
14708
16191
|
} catch (e) {
|
|
14709
16192
|
removeError = e;
|
|
14710
16193
|
}
|
|
14711
|
-
if (
|
|
16194
|
+
if (existsSync11(probeTree)) {
|
|
14712
16195
|
cleanupFailure = probeCleanupFailureDetail(
|
|
14713
16196
|
probeTree,
|
|
14714
16197
|
removeError,
|
|
@@ -14732,10 +16215,15 @@ async function runTestEfficacy(args) {
|
|
|
14732
16215
|
h.detail = detail;
|
|
14733
16216
|
}
|
|
14734
16217
|
}
|
|
14735
|
-
for (
|
|
16218
|
+
for (let i = 0; i < results.length; i++) {
|
|
16219
|
+
const r = results[i];
|
|
14736
16220
|
if (r.verdict === "inert") {
|
|
14737
|
-
|
|
14738
|
-
|
|
16221
|
+
results[i] = {
|
|
16222
|
+
file: r.file,
|
|
16223
|
+
verdict: "inconclusive",
|
|
16224
|
+
reason: "control-failed",
|
|
16225
|
+
detail
|
|
16226
|
+
};
|
|
14739
16227
|
}
|
|
14740
16228
|
}
|
|
14741
16229
|
}
|
|
@@ -14802,8 +16290,8 @@ async function runTestEfficacy(args) {
|
|
|
14802
16290
|
findings,
|
|
14803
16291
|
cleanupFailure
|
|
14804
16292
|
};
|
|
14805
|
-
|
|
14806
|
-
|
|
16293
|
+
mkdirSync23(dirname20(out), { recursive: true });
|
|
16294
|
+
writeFileSync23(out, JSON.stringify(result, null, 2), "utf8");
|
|
14807
16295
|
writeStdoutLine(
|
|
14808
16296
|
`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
16297
|
);
|
|
@@ -14880,8 +16368,8 @@ var testEfficacyCommand = {
|
|
|
14880
16368
|
|
|
14881
16369
|
// packages/cli/src/commands/review/test-plan.ts
|
|
14882
16370
|
init_esbuild_shims();
|
|
14883
|
-
import { existsSync as
|
|
14884
|
-
import { dirname as
|
|
16371
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync24, readFileSync as readFileSync26, writeFileSync as writeFileSync24 } from "node:fs";
|
|
16372
|
+
import { dirname as dirname21, join as join18, normalize, resolve as resolve20 } from "node:path";
|
|
14885
16373
|
var PLAN_NAME_RE = /(test\s*plan|\btesting\b|how\s+(?:has\s+this|to)\s+(?:been\s+)?test(?:ed)?|测试计划|测试方案|测试步骤)/i;
|
|
14886
16374
|
var HEADING_LINE_RE = /^(#{1,6})(?:[ \t](.*))?$/;
|
|
14887
16375
|
var BOLD_LINE_RE = /^\*\*([^*\n]+)\*\*:?\s*$/;
|
|
@@ -15084,7 +16572,7 @@ function rulePath(text, worktree, changed) {
|
|
|
15084
16572
|
};
|
|
15085
16573
|
}
|
|
15086
16574
|
const ignored = isGitIgnored(worktree, path3);
|
|
15087
|
-
if (
|
|
16575
|
+
if (existsSync12(join18(worktree, path3))) {
|
|
15088
16576
|
return {
|
|
15089
16577
|
kind: "path",
|
|
15090
16578
|
text,
|
|
@@ -15155,7 +16643,7 @@ function ruleCommand(text, worktree, buildTest) {
|
|
|
15155
16643
|
let rootScripts = [];
|
|
15156
16644
|
try {
|
|
15157
16645
|
const rootPkg = JSON.parse(
|
|
15158
|
-
|
|
16646
|
+
readFileSync26(join18(worktree, "package.json"), "utf8")
|
|
15159
16647
|
);
|
|
15160
16648
|
rootScripts = Object.keys(rootPkg.scripts ?? {});
|
|
15161
16649
|
} catch {
|
|
@@ -15233,7 +16721,7 @@ __name(fetchPrBody, "fetchPrBody");
|
|
|
15233
16721
|
function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
15234
16722
|
let plan;
|
|
15235
16723
|
try {
|
|
15236
|
-
plan = JSON.parse(
|
|
16724
|
+
plan = JSON.parse(readFileSync26(args.plan, "utf8"));
|
|
15237
16725
|
} catch (err) {
|
|
15238
16726
|
throw new Error(
|
|
15239
16727
|
`test-plan: cannot read the plan ${args.plan}: ${err.message}`
|
|
@@ -15260,7 +16748,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
|
15260
16748
|
note: "the PR description has no Test Plan section"
|
|
15261
16749
|
};
|
|
15262
16750
|
}
|
|
15263
|
-
const worktree =
|
|
16751
|
+
const worktree = resolve20(args.worktree);
|
|
15264
16752
|
const changed = new Set(
|
|
15265
16753
|
(plan.files ?? []).map((f) => normalize(String(f.path)))
|
|
15266
16754
|
);
|
|
@@ -15268,7 +16756,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
|
|
|
15268
16756
|
if (args.buildTest) {
|
|
15269
16757
|
try {
|
|
15270
16758
|
buildTest = JSON.parse(
|
|
15271
|
-
|
|
16759
|
+
readFileSync26(args.buildTest, "utf8")
|
|
15272
16760
|
);
|
|
15273
16761
|
} catch {
|
|
15274
16762
|
buildTest = null;
|
|
@@ -15325,8 +16813,8 @@ var testPlanCommand = {
|
|
|
15325
16813
|
try {
|
|
15326
16814
|
const report = runTestPlan(args);
|
|
15327
16815
|
if (args.out) {
|
|
15328
|
-
|
|
15329
|
-
|
|
16816
|
+
mkdirSync24(dirname21(resolve20(args.out)), { recursive: true });
|
|
16817
|
+
writeFileSync24(resolve20(args.out), JSON.stringify(report, null, 2));
|
|
15330
16818
|
}
|
|
15331
16819
|
writeStdoutLine(JSON.stringify(report, null, 2));
|
|
15332
16820
|
writeStderrLine(`test-plan: ${report.note}`);
|
|
@@ -15340,8 +16828,8 @@ var testPlanCommand = {
|
|
|
15340
16828
|
// packages/cli/src/commands/review/cleanup.ts
|
|
15341
16829
|
init_esbuild_shims();
|
|
15342
16830
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
15343
|
-
import { existsSync as
|
|
15344
|
-
import { join as
|
|
16831
|
+
import { existsSync as existsSync13, readFileSync as readFileSync27, readdirSync as readdirSync6, rmSync as rmSync10 } from "node:fs";
|
|
16832
|
+
import { join as join19 } from "node:path";
|
|
15345
16833
|
var AUTOMATION_MARKER = "<!-- qwen-";
|
|
15346
16834
|
function isAutomationComment(body) {
|
|
15347
16835
|
return (body ?? "").trimStart().startsWith(AUTOMATION_MARKER);
|
|
@@ -15372,7 +16860,7 @@ var OWNER_REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
|
15372
16860
|
function readAuditWindow(target, expectedPrNumber) {
|
|
15373
16861
|
let raw;
|
|
15374
16862
|
try {
|
|
15375
|
-
raw =
|
|
16863
|
+
raw = readFileSync27(tmpFile(target, "fetch.json"), "utf8");
|
|
15376
16864
|
} catch (err) {
|
|
15377
16865
|
const code = err.code;
|
|
15378
16866
|
return code === "ENOENT" ? { skip: "no fetch report" } : {
|
|
@@ -15419,7 +16907,7 @@ function readSubmitReceipt(target) {
|
|
|
15419
16907
|
try {
|
|
15420
16908
|
return new Set(
|
|
15421
16909
|
parseReceiptIds(
|
|
15422
|
-
|
|
16910
|
+
readFileSync27(tmpFile(target, "submit-receipt.json"), "utf8")
|
|
15423
16911
|
)
|
|
15424
16912
|
);
|
|
15425
16913
|
} catch {
|
|
@@ -15523,7 +17011,7 @@ function runCleanup(target) {
|
|
|
15523
17011
|
report("probe worktree", probeWorktreePath(wt));
|
|
15524
17012
|
report("base worktree", baseWorktreePath(wt));
|
|
15525
17013
|
try {
|
|
15526
|
-
|
|
17014
|
+
rmSync10(`${baseWorktreePath(wt)}.lock`, { recursive: true, force: true });
|
|
15527
17015
|
} catch (err) {
|
|
15528
17016
|
writeStderrLine(
|
|
15529
17017
|
`note: could not remove base lock ${baseWorktreePath(wt)}.lock: ${err.message}`
|
|
@@ -15546,7 +17034,7 @@ function runCleanup(target) {
|
|
|
15546
17034
|
const prefix = tmpPrefix(target);
|
|
15547
17035
|
let tmpEntries = [];
|
|
15548
17036
|
try {
|
|
15549
|
-
tmpEntries =
|
|
17037
|
+
tmpEntries = existsSync13(REVIEW_TMP_DIR) ? readdirSync6(REVIEW_TMP_DIR) : [];
|
|
15550
17038
|
} catch (err) {
|
|
15551
17039
|
writeStderrLine(
|
|
15552
17040
|
`Failed to read ${REVIEW_TMP_DIR}: ${err.message}`
|
|
@@ -15554,9 +17042,9 @@ function runCleanup(target) {
|
|
|
15554
17042
|
}
|
|
15555
17043
|
for (const file of tmpEntries) {
|
|
15556
17044
|
if (!file.startsWith(prefix)) continue;
|
|
15557
|
-
const full =
|
|
17045
|
+
const full = join19(REVIEW_TMP_DIR, file);
|
|
15558
17046
|
try {
|
|
15559
|
-
|
|
17047
|
+
rmSync10(full, { recursive: true, force: true });
|
|
15560
17048
|
writeStdoutLine(`Removed temp file: ${full}`);
|
|
15561
17049
|
removedAny = true;
|
|
15562
17050
|
} catch (err) {
|
|
@@ -15588,8 +17076,8 @@ var cleanupCommand = {
|
|
|
15588
17076
|
// packages/cli/src/commands/review/run.ts
|
|
15589
17077
|
init_esbuild_shims();
|
|
15590
17078
|
import { spawn, execFileSync as execFileSync6 } from "node:child_process";
|
|
15591
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
15592
|
-
import { join as
|
|
17079
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync28, realpathSync as realpathSync3, statSync as statSync9 } from "node:fs";
|
|
17080
|
+
import { dirname as dirname22, join as join20, resolve as resolve21 } from "node:path";
|
|
15593
17081
|
var COMPOSED_PATTERN = /^qwen-review-.*composed\.json$/;
|
|
15594
17082
|
var COMPOSED_POLL_MS = 250;
|
|
15595
17083
|
var SIGNAL_EXIT_CODES = {
|
|
@@ -15623,10 +17111,10 @@ function newestArtifactSince(dir, pattern, startMs) {
|
|
|
15623
17111
|
}
|
|
15624
17112
|
for (const name of names) {
|
|
15625
17113
|
if (!pattern.test(name)) continue;
|
|
15626
|
-
const path3 =
|
|
17114
|
+
const path3 = join20(dir, name);
|
|
15627
17115
|
let mtime;
|
|
15628
17116
|
try {
|
|
15629
|
-
mtime =
|
|
17117
|
+
mtime = statSync9(path3).mtimeMs;
|
|
15630
17118
|
} catch {
|
|
15631
17119
|
continue;
|
|
15632
17120
|
}
|
|
@@ -15636,15 +17124,15 @@ function newestArtifactSince(dir, pattern, startMs) {
|
|
|
15636
17124
|
return best ? best.path : null;
|
|
15637
17125
|
}
|
|
15638
17126
|
__name(newestArtifactSince, "newestArtifactSince");
|
|
15639
|
-
function exitCodeFor(completed,
|
|
17127
|
+
function exitCodeFor(completed, event2, failOn) {
|
|
15640
17128
|
if (!completed) return 1;
|
|
15641
|
-
if (failOn === "request-changes" &&
|
|
17129
|
+
if (failOn === "request-changes" && event2 === "REQUEST_CHANGES") return 3;
|
|
15642
17130
|
return 0;
|
|
15643
17131
|
}
|
|
15644
17132
|
__name(exitCodeFor, "exitCodeFor");
|
|
15645
17133
|
function readComposed(path3) {
|
|
15646
17134
|
try {
|
|
15647
|
-
const parsed = JSON.parse(
|
|
17135
|
+
const parsed = JSON.parse(readFileSync28(path3, "utf8"));
|
|
15648
17136
|
return typeof parsed.event === "string" ? parsed : null;
|
|
15649
17137
|
} catch {
|
|
15650
17138
|
return null;
|
|
@@ -15667,6 +17155,23 @@ function killProcessGroup(pid, signal) {
|
|
|
15667
17155
|
}
|
|
15668
17156
|
}
|
|
15669
17157
|
__name(killProcessGroup, "killProcessGroup");
|
|
17158
|
+
function childEnv() {
|
|
17159
|
+
const env = { ...process.env };
|
|
17160
|
+
const inherited = env["QWEN_CODE_CLI"];
|
|
17161
|
+
const ownEntry = process.argv[1];
|
|
17162
|
+
if (!inherited || !ownEntry) {
|
|
17163
|
+
return env;
|
|
17164
|
+
}
|
|
17165
|
+
try {
|
|
17166
|
+
if (dirname22(realpathSync3(inherited)) === dirname22(realpathSync3(ownEntry))) {
|
|
17167
|
+
return env;
|
|
17168
|
+
}
|
|
17169
|
+
} catch {
|
|
17170
|
+
}
|
|
17171
|
+
env["QWEN_CODE_CLI"] = "";
|
|
17172
|
+
return env;
|
|
17173
|
+
}
|
|
17174
|
+
__name(childEnv, "childEnv");
|
|
15670
17175
|
async function runReview(args) {
|
|
15671
17176
|
const startMs = Date.now();
|
|
15672
17177
|
const prompt = buildReviewPrompt(args);
|
|
@@ -15682,6 +17187,7 @@ async function runReview(args) {
|
|
|
15682
17187
|
args.approvalMode
|
|
15683
17188
|
],
|
|
15684
17189
|
{
|
|
17190
|
+
env: childEnv(),
|
|
15685
17191
|
// stdin CLOSED, not inherited: piped input would be prepended to the
|
|
15686
17192
|
// prompt and the leading `/` would no longer be the first character —
|
|
15687
17193
|
// the slash command would reach the model as plain text.
|
|
@@ -15693,9 +17199,9 @@ async function runReview(args) {
|
|
|
15693
17199
|
}
|
|
15694
17200
|
);
|
|
15695
17201
|
if (!args.quiet) {
|
|
15696
|
-
const writeProgress = /* @__PURE__ */ __name((
|
|
17202
|
+
const writeProgress = /* @__PURE__ */ __name((chunk2) => {
|
|
15697
17203
|
try {
|
|
15698
|
-
process.stderr.write(
|
|
17204
|
+
process.stderr.write(chunk2);
|
|
15699
17205
|
} catch {
|
|
15700
17206
|
}
|
|
15701
17207
|
}, "writeProgress");
|
|
@@ -15779,8 +17285,8 @@ async function runReview(args) {
|
|
|
15779
17285
|
downgraded: composed?.downgraded ?? false,
|
|
15780
17286
|
downgradedFrom: composed?.downgradedFrom ?? null,
|
|
15781
17287
|
remediation: composed?.remediation ?? [],
|
|
15782
|
-
composedPath: composedPath ?
|
|
15783
|
-
reportPath: reportPath ?
|
|
17288
|
+
composedPath: composedPath ? resolve21(composedPath) : null,
|
|
17289
|
+
reportPath: reportPath ? resolve21(reportPath) : null,
|
|
15784
17290
|
childExitCode,
|
|
15785
17291
|
childSignal,
|
|
15786
17292
|
timedOut,
|
|
@@ -15794,7 +17300,7 @@ async function runReview(args) {
|
|
|
15794
17300
|
writeStdoutLine(result.verdictLine ?? `Event: ${result.event}`);
|
|
15795
17301
|
if (result.reportPath) writeStdoutLine(`Report: ${result.reportPath}`);
|
|
15796
17302
|
} else {
|
|
15797
|
-
const detail = composedPath !== null ? `a composed verdict was found at ${
|
|
17303
|
+
const detail = composedPath !== null ? `a composed verdict was found at ${resolve21(composedPath)} but could not be parsed` : "no composed verdict was produced";
|
|
15798
17304
|
writeStdoutLine(
|
|
15799
17305
|
timedOut ? "Review did not complete: timed out." : `Review did not complete: ${detail}${childExitCode !== null ? ` (CLI exit ${childExitCode})` : ""}${childSignal !== null ? ` (killed by ${childSignal})` : ""}.`
|
|
15800
17306
|
);
|
|
@@ -15857,13 +17363,308 @@ var runCommand = {
|
|
|
15857
17363
|
}, "handler")
|
|
15858
17364
|
};
|
|
15859
17365
|
|
|
17366
|
+
// packages/cli/src/commands/review/save-artifact.ts
|
|
17367
|
+
init_esbuild_shims();
|
|
17368
|
+
import {
|
|
17369
|
+
existsSync as existsSync14,
|
|
17370
|
+
lstatSync as lstatSync5,
|
|
17371
|
+
mkdirSync as mkdirSync25,
|
|
17372
|
+
readFileSync as readFileSync29,
|
|
17373
|
+
statSync as statSync10
|
|
17374
|
+
} from "node:fs";
|
|
17375
|
+
import {
|
|
17376
|
+
dirname as dirname23,
|
|
17377
|
+
isAbsolute as isAbsolute4,
|
|
17378
|
+
join as join21,
|
|
17379
|
+
relative as relative3,
|
|
17380
|
+
resolve as resolve22,
|
|
17381
|
+
sep as sep4,
|
|
17382
|
+
toNamespacedPath
|
|
17383
|
+
} from "node:path";
|
|
17384
|
+
import { isDeepStrictEqual } from "node:util";
|
|
17385
|
+
function workspaceRoot() {
|
|
17386
|
+
return resolve22(process.env["QWEN_CODE_PROJECT_DIR"] ?? process.cwd());
|
|
17387
|
+
}
|
|
17388
|
+
__name(workspaceRoot, "workspaceRoot");
|
|
17389
|
+
function isWithin(parent, child) {
|
|
17390
|
+
const rel = relative3(parent, child);
|
|
17391
|
+
return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep4}`);
|
|
17392
|
+
}
|
|
17393
|
+
__name(isWithin, "isWithin");
|
|
17394
|
+
function workspacePath(root, value, label2) {
|
|
17395
|
+
const absolute = resolve22(root, value);
|
|
17396
|
+
if (!isWithin(root, absolute)) {
|
|
17397
|
+
throw new Error(
|
|
17398
|
+
`${label2} must be inside the workspace: ${JSON.stringify(value)}.`
|
|
17399
|
+
);
|
|
17400
|
+
}
|
|
17401
|
+
return absolute;
|
|
17402
|
+
}
|
|
17403
|
+
__name(workspacePath, "workspacePath");
|
|
17404
|
+
function rejectSymlinkPath(root, path3, label2) {
|
|
17405
|
+
const rel = relative3(root, path3);
|
|
17406
|
+
let current = root;
|
|
17407
|
+
for (const segment of rel.split(sep4)) {
|
|
17408
|
+
current = join21(current, segment);
|
|
17409
|
+
if (existsSync14(current) && lstatSync5(current).isSymbolicLink()) {
|
|
17410
|
+
throw new Error(`${label2} must not traverse a symbolic link.`);
|
|
17411
|
+
}
|
|
17412
|
+
}
|
|
17413
|
+
}
|
|
17414
|
+
__name(rejectSymlinkPath, "rejectSymlinkPath");
|
|
17415
|
+
function sameFile(left, right) {
|
|
17416
|
+
if (left === right) return true;
|
|
17417
|
+
if (!existsSync14(left) || !existsSync14(right)) return false;
|
|
17418
|
+
const leftStat = statSync10(left);
|
|
17419
|
+
const rightStat = statSync10(right);
|
|
17420
|
+
return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
|
|
17421
|
+
}
|
|
17422
|
+
__name(sameFile, "sameFile");
|
|
17423
|
+
function readText(path3, label2) {
|
|
17424
|
+
try {
|
|
17425
|
+
return readFileSync29(path3, "utf8");
|
|
17426
|
+
} catch (error) {
|
|
17427
|
+
throw new Error(
|
|
17428
|
+
`Could not read the ${label2} file at ${JSON.stringify(path3)}: ${error instanceof Error ? error.message : String(error)}`
|
|
17429
|
+
);
|
|
17430
|
+
}
|
|
17431
|
+
}
|
|
17432
|
+
__name(readText, "readText");
|
|
17433
|
+
function readJson2(path3, label2) {
|
|
17434
|
+
const text = readText(path3, label2);
|
|
17435
|
+
try {
|
|
17436
|
+
return JSON.parse(text);
|
|
17437
|
+
} catch (error) {
|
|
17438
|
+
throw new Error(
|
|
17439
|
+
`The ${label2} file at ${JSON.stringify(path3)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
17440
|
+
);
|
|
17441
|
+
}
|
|
17442
|
+
}
|
|
17443
|
+
__name(readJson2, "readJson");
|
|
17444
|
+
function object(value, label2) {
|
|
17445
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
17446
|
+
throw new Error(`${label2} must be an object.`);
|
|
17447
|
+
}
|
|
17448
|
+
return value;
|
|
17449
|
+
}
|
|
17450
|
+
__name(object, "object");
|
|
17451
|
+
function string(value, label2) {
|
|
17452
|
+
if (typeof value !== "string") {
|
|
17453
|
+
throw new Error(`${label2} must be a string.`);
|
|
17454
|
+
}
|
|
17455
|
+
return value;
|
|
17456
|
+
}
|
|
17457
|
+
__name(string, "string");
|
|
17458
|
+
function nonEmptyString(value, label2) {
|
|
17459
|
+
const text = string(value, label2);
|
|
17460
|
+
if (text.trim() === "") {
|
|
17461
|
+
throw new Error(`${label2} must be a non-empty string.`);
|
|
17462
|
+
}
|
|
17463
|
+
return text;
|
|
17464
|
+
}
|
|
17465
|
+
__name(nonEmptyString, "nonEmptyString");
|
|
17466
|
+
function stringArray2(value, label2) {
|
|
17467
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
17468
|
+
throw new Error(`${label2} must be an array of strings.`);
|
|
17469
|
+
}
|
|
17470
|
+
return value;
|
|
17471
|
+
}
|
|
17472
|
+
__name(stringArray2, "stringArray");
|
|
17473
|
+
function event(value, label2) {
|
|
17474
|
+
if (value !== "APPROVE" && value !== "COMMENT" && value !== "REQUEST_CHANGES") {
|
|
17475
|
+
throw new Error(`${label2} has an unsupported value.`);
|
|
17476
|
+
}
|
|
17477
|
+
return value;
|
|
17478
|
+
}
|
|
17479
|
+
__name(event, "event");
|
|
17480
|
+
function validateVerdict(value) {
|
|
17481
|
+
const verdict = object(value, "Composed verdict");
|
|
17482
|
+
const downgradedFrom = verdict["downgradedFrom"];
|
|
17483
|
+
if (downgradedFrom !== null && downgradedFrom !== "Approve" && downgradedFrom !== "Request changes") {
|
|
17484
|
+
throw new Error(
|
|
17485
|
+
"Composed verdict.downgradedFrom has an unsupported value."
|
|
17486
|
+
);
|
|
17487
|
+
}
|
|
17488
|
+
if (typeof verdict["downgraded"] !== "boolean") {
|
|
17489
|
+
throw new Error("Composed verdict.downgraded must be a boolean.");
|
|
17490
|
+
}
|
|
17491
|
+
const lowSignal = verdict["lowSignal"];
|
|
17492
|
+
if (lowSignal !== null) {
|
|
17493
|
+
const signal = object(lowSignal, "Composed verdict.lowSignal");
|
|
17494
|
+
for (const key of ["agents", "srcDiffLines"]) {
|
|
17495
|
+
if (typeof signal[key] !== "number" || !Number.isInteger(signal[key]) || signal[key] < 0) {
|
|
17496
|
+
throw new Error(
|
|
17497
|
+
`Composed verdict.lowSignal.${key} must be a non-negative integer.`
|
|
17498
|
+
);
|
|
17499
|
+
}
|
|
17500
|
+
}
|
|
17501
|
+
}
|
|
17502
|
+
return {
|
|
17503
|
+
event: event(verdict["event"], "Composed verdict.event"),
|
|
17504
|
+
body: string(verdict["body"], "Composed verdict.body"),
|
|
17505
|
+
baseEvent: event(verdict["baseEvent"], "Composed verdict.baseEvent"),
|
|
17506
|
+
cappedBy: stringArray2(verdict["cappedBy"], "Composed verdict.cappedBy"),
|
|
17507
|
+
downgraded: verdict["downgraded"],
|
|
17508
|
+
downgradedFrom,
|
|
17509
|
+
remediation: stringArray2(
|
|
17510
|
+
verdict["remediation"],
|
|
17511
|
+
"Composed verdict.remediation"
|
|
17512
|
+
),
|
|
17513
|
+
lowSignal: lowSignal === null ? null : {
|
|
17514
|
+
agents: lowSignal["agents"],
|
|
17515
|
+
srcDiffLines: lowSignal["srcDiffLines"]
|
|
17516
|
+
},
|
|
17517
|
+
verdictLine: nonEmptyString(
|
|
17518
|
+
verdict["verdictLine"],
|
|
17519
|
+
"Composed verdict.verdictLine"
|
|
17520
|
+
)
|
|
17521
|
+
};
|
|
17522
|
+
}
|
|
17523
|
+
__name(validateVerdict, "validateVerdict");
|
|
17524
|
+
function validateFindingsReport(value) {
|
|
17525
|
+
const report = object(value, "Canonical findings report");
|
|
17526
|
+
if (typeof report["outcomesRecorded"] !== "boolean") {
|
|
17527
|
+
throw new Error(
|
|
17528
|
+
"Canonical findings report.outcomesRecorded must be a boolean."
|
|
17529
|
+
);
|
|
17530
|
+
}
|
|
17531
|
+
const canonical = buildReport(validateFindings(report["findings"]));
|
|
17532
|
+
const supplied = {
|
|
17533
|
+
findings: report["findings"],
|
|
17534
|
+
counts: report["counts"],
|
|
17535
|
+
outcomesRecorded: report["outcomesRecorded"]
|
|
17536
|
+
};
|
|
17537
|
+
if (!isDeepStrictEqual(supplied, canonical)) {
|
|
17538
|
+
throw new Error(
|
|
17539
|
+
"Canonical findings report is malformed or inconsistent with its findings."
|
|
17540
|
+
);
|
|
17541
|
+
}
|
|
17542
|
+
return supplied;
|
|
17543
|
+
}
|
|
17544
|
+
__name(validateFindingsReport, "validateFindingsReport");
|
|
17545
|
+
function saveReviewArtifact(args) {
|
|
17546
|
+
const root = workspaceRoot();
|
|
17547
|
+
const findingsPath = workspacePath(root, args.findings, "Findings input");
|
|
17548
|
+
const composedPath = workspacePath(root, args.composed, "Composed input");
|
|
17549
|
+
const reportPath = workspacePath(root, args.report, "Markdown report");
|
|
17550
|
+
const outputPath = workspacePath(root, args.out, "Output");
|
|
17551
|
+
const reviewsRoot = resolve22(root, REVIEWS_DIR);
|
|
17552
|
+
if (!isWithin(reviewsRoot, reportPath) || reportPath === reviewsRoot) {
|
|
17553
|
+
throw new Error(
|
|
17554
|
+
`Markdown report must be a file under ${JSON.stringify(REVIEWS_DIR)}: ${JSON.stringify(args.report)}.`
|
|
17555
|
+
);
|
|
17556
|
+
}
|
|
17557
|
+
if (!isWithin(reviewsRoot, outputPath) || outputPath === reviewsRoot) {
|
|
17558
|
+
throw new Error(
|
|
17559
|
+
`Output must be a file under ${JSON.stringify(REVIEWS_DIR)}: ${JSON.stringify(args.out)}.`
|
|
17560
|
+
);
|
|
17561
|
+
}
|
|
17562
|
+
for (const [label2, inputPath] of [
|
|
17563
|
+
["findings input", findingsPath],
|
|
17564
|
+
["composed input", composedPath],
|
|
17565
|
+
["Markdown report", reportPath]
|
|
17566
|
+
]) {
|
|
17567
|
+
if (sameFile(outputPath, inputPath)) {
|
|
17568
|
+
throw new Error(`Output must not overwrite the ${label2}.`);
|
|
17569
|
+
}
|
|
17570
|
+
}
|
|
17571
|
+
rejectSymlinkPath(root, findingsPath, "Findings input");
|
|
17572
|
+
rejectSymlinkPath(root, composedPath, "Composed input");
|
|
17573
|
+
rejectSymlinkPath(root, reportPath, "Markdown report");
|
|
17574
|
+
rejectSymlinkPath(root, outputPath, "Output");
|
|
17575
|
+
if (!EFFORT_LEVELS.has(args.effort)) {
|
|
17576
|
+
throw new Error(
|
|
17577
|
+
`Unsupported review effort: ${JSON.stringify(args.effort)}.`
|
|
17578
|
+
);
|
|
17579
|
+
}
|
|
17580
|
+
if (args.effort === "low") {
|
|
17581
|
+
throw new Error(
|
|
17582
|
+
"save-artifact does not support low-effort reviews: low has no canonical composed verdict to persist."
|
|
17583
|
+
);
|
|
17584
|
+
}
|
|
17585
|
+
nonEmptyString(args.target, "Target");
|
|
17586
|
+
const findings = validateFindingsReport(
|
|
17587
|
+
readJson2(findingsPath, "canonical findings")
|
|
17588
|
+
);
|
|
17589
|
+
const verdict = validateVerdict(readJson2(composedPath, "composed verdict"));
|
|
17590
|
+
let reportIsFile;
|
|
17591
|
+
try {
|
|
17592
|
+
reportIsFile = statSync10(reportPath).isFile();
|
|
17593
|
+
} catch {
|
|
17594
|
+
}
|
|
17595
|
+
if (reportIsFile === false) {
|
|
17596
|
+
throw new Error(
|
|
17597
|
+
`Markdown report is not a file: ${JSON.stringify(args.report)}.`
|
|
17598
|
+
);
|
|
17599
|
+
}
|
|
17600
|
+
readText(reportPath, "Markdown report");
|
|
17601
|
+
const markdownReportPath = relative3(root, reportPath).split(sep4).join("/");
|
|
17602
|
+
const document = {
|
|
17603
|
+
schemaVersion: 1,
|
|
17604
|
+
target: args.target,
|
|
17605
|
+
effort: args.effort,
|
|
17606
|
+
verdict,
|
|
17607
|
+
findings: findings.findings,
|
|
17608
|
+
counts: findings.counts,
|
|
17609
|
+
outcomesRecorded: findings.outcomesRecorded,
|
|
17610
|
+
markdownReportPath
|
|
17611
|
+
};
|
|
17612
|
+
mkdirSync25(dirname23(outputPath), { recursive: true });
|
|
17613
|
+
atomicWriteFileSync(
|
|
17614
|
+
toNamespacedPath(outputPath),
|
|
17615
|
+
`${JSON.stringify(document, null, 2)}
|
|
17616
|
+
`,
|
|
17617
|
+
{ encoding: "utf8", noFollow: true }
|
|
17618
|
+
);
|
|
17619
|
+
return {
|
|
17620
|
+
path: outputPath,
|
|
17621
|
+
workspacePath: relative3(root, outputPath).split(sep4).join("/")
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
__name(saveReviewArtifact, "saveReviewArtifact");
|
|
17625
|
+
var saveArtifactCommand = {
|
|
17626
|
+
command: "save-artifact",
|
|
17627
|
+
describe: "Create a durable versioned code-review JSON document from canonical review artifacts",
|
|
17628
|
+
builder: /* @__PURE__ */ __name((yargs) => yargs.option("findings", {
|
|
17629
|
+
type: "string",
|
|
17630
|
+
demandOption: true,
|
|
17631
|
+
describe: "Canonical findings JSON"
|
|
17632
|
+
}).option("composed", {
|
|
17633
|
+
type: "string",
|
|
17634
|
+
demandOption: true,
|
|
17635
|
+
describe: "Composed verdict JSON"
|
|
17636
|
+
}).option("report", {
|
|
17637
|
+
type: "string",
|
|
17638
|
+
demandOption: true,
|
|
17639
|
+
describe: "Existing durable Markdown report"
|
|
17640
|
+
}).option("target", {
|
|
17641
|
+
type: "string",
|
|
17642
|
+
demandOption: true,
|
|
17643
|
+
describe: "Review target label"
|
|
17644
|
+
}).option("effort", {
|
|
17645
|
+
type: "string",
|
|
17646
|
+
choices: [...EFFORT_LEVELS].filter((effort) => effort !== "low"),
|
|
17647
|
+
demandOption: true,
|
|
17648
|
+
describe: "Resolved review effort"
|
|
17649
|
+
}).option("out", {
|
|
17650
|
+
type: "string",
|
|
17651
|
+
demandOption: true,
|
|
17652
|
+
describe: "Output path under .qwen/reviews/"
|
|
17653
|
+
}), "builder"),
|
|
17654
|
+
handler: /* @__PURE__ */ __name((argv) => {
|
|
17655
|
+
const saved = saveReviewArtifact(argv);
|
|
17656
|
+
writeStdoutLine(JSON.stringify(saved));
|
|
17657
|
+
writeStderrLine(`save-artifact: wrote ${saved.path}`);
|
|
17658
|
+
}, "handler")
|
|
17659
|
+
};
|
|
17660
|
+
|
|
15860
17661
|
// packages/cli/src/commands/review.ts
|
|
15861
17662
|
var reviewCommand = {
|
|
15862
17663
|
command: "review",
|
|
15863
17664
|
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(
|
|
17665
|
+
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(saveArtifactCommand).command(submitCommand).command(cleanupCommand).demandCommand(
|
|
15865
17666
|
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."
|
|
17667
|
+
"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, save-artifact, submit, or cleanup."
|
|
15867
17668
|
).version(false), "builder"),
|
|
15868
17669
|
handler: /* @__PURE__ */ __name(() => {
|
|
15869
17670
|
}, "handler")
|
|
@@ -16063,10 +17864,10 @@ var updateCommand3 = {
|
|
|
16063
17864
|
stdioHelpers,
|
|
16064
17865
|
{ updateEventEmitter }
|
|
16065
17866
|
] = await Promise.all([
|
|
16066
|
-
import("./settings-
|
|
16067
|
-
import("./updateCheck-
|
|
16068
|
-
import("./installationInfo-
|
|
16069
|
-
import("./standalone-update-
|
|
17867
|
+
import("./settings-FZH5YZLH.js"),
|
|
17868
|
+
import("./updateCheck-XSIBJXYA.js"),
|
|
17869
|
+
import("./installationInfo-TEJOXP5W.js"),
|
|
17870
|
+
import("./standalone-update-LEPP5BOB.js"),
|
|
16070
17871
|
import("./stdioHelpers-HGIYTWAY.js"),
|
|
16071
17872
|
import("./updateEventEmitter-5MKC7BLQ.js")
|
|
16072
17873
|
]);
|
|
@@ -17233,7 +19034,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
|
|
|
17233
19034
|
disabledSlashCommands: disabledSlashCommands.length > 0 ? disabledSlashCommands : void 0,
|
|
17234
19035
|
disabledSkillNamesProvider: bareMode || safeMode ? void 0 : disabledSkillNamesProvider,
|
|
17235
19036
|
terminalImageRenderSupportProvider: interactive ? async () => {
|
|
17236
|
-
const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-
|
|
19037
|
+
const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-KZMAF5E5.js");
|
|
17237
19038
|
return getTerminalImageRenderSupport();
|
|
17238
19039
|
} : void 0,
|
|
17239
19040
|
disabledSkillLevels: bareMode || safeMode || !Array.isArray(settings.skills?.disabledLevels) ? void 0 : settings.skills.disabledLevels.filter(isSkillLevel),
|
|
@@ -17387,6 +19188,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
|
|
|
17387
19188
|
enableAutoSkill: bareMode || safeMode ? false : settings.memory?.enableAutoSkill ?? false,
|
|
17388
19189
|
autoSkillConfirm: bareMode || safeMode ? false : settings.memory?.autoSkillConfirm ?? true,
|
|
17389
19190
|
memoryAgentTimeoutMinutes: settings.memory?.agentTimeoutMinutes,
|
|
19191
|
+
memoryAgentMaxTurns: settings.memory?.agentMaxTurns,
|
|
17390
19192
|
fastModel: settings.fastModel || void 0,
|
|
17391
19193
|
webSearch: bareMode || safeMode ? void 0 : resolveWebSearchSettings(settings),
|
|
17392
19194
|
visionModel: settings.visionModel || void 0,
|
|
@@ -17508,11 +19310,11 @@ var ExtensionRefreshState = class {
|
|
|
17508
19310
|
contentChangedDuringReload = false;
|
|
17509
19311
|
suppressionDepth = 0;
|
|
17510
19312
|
suppressUntil = 0;
|
|
17511
|
-
on(
|
|
17512
|
-
this.events.on(
|
|
19313
|
+
on(event2, listener) {
|
|
19314
|
+
this.events.on(event2, listener);
|
|
17513
19315
|
}
|
|
17514
|
-
off(
|
|
17515
|
-
this.events.off(
|
|
19316
|
+
off(event2, listener) {
|
|
19317
|
+
this.events.off(event2, listener);
|
|
17516
19318
|
}
|
|
17517
19319
|
markExtensionsChanged(reason) {
|
|
17518
19320
|
if (this.isSuppressed()) {
|