chainlesschain 0.162.150 → 0.162.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CBLvrjeG.js → AIOps-CVLVJS-Q.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DYv2GtE0.js → ActionButton-CJ2_GJsI.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CL6PZ-Ww.js → Analytics-CZsmlTF8.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-xuyHt4uA.js → AppLayout-DREvMZ62.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-DOvSVycq.js → Audit-B4NR42w_.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DMhfTswr.js → Backup-CJFmf3In.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CQxDdm63.js → BaseInput-Bh8_iUZY.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BEeNhK2a.js → Chat-DEwKjIxV.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BOqHkxK_.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CL04O-ER.js → Checkbox-DYWCqYI9.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C50o-n7x.js → Codegen-BlXrfZAJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CH-bDkzs.js → Col-DLPM22eX.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DqAIMvSe.js → Community-PFyzqijs.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C43fo_my.js → Compact-7DIfY5Wp.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BERnMrdP.js → Compliance-DF4CinPU.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Baw3w1gp.js → Cowork-B1J80Vcj.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-BEqAHF1-.js → Cron-B7sx2OXX.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CvnmKwK4.js → Crosschain-omLkxmu-.js} +1 -1
- package/src/assets/web-panel/assets/{DID-iMPxgVdt.js → DID-B_7r7b5F.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard--4SJX3YR.js → Dashboard-Cp7k3GZg.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BgLV6xgz.js → Dropdown-BCKO89Y8.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bvb8oUD8.js → EmailListRenderer-CNgTSsFZ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DpXPjJB5.js → FamilyGuardDashboard-DobM83JP.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbFfJ1Xi.js → Federation-XVAYQIeu.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DeGq1B-q.js → FormItemContext-DV5ka1KQ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CNW7s9zM.js → GenericCardRenderer-Dg5q-KpC.js} +1 -1
- package/src/assets/web-panel/assets/{Git-L1PjW-lT.js → Git-Dsn3Xhxf.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-PK-X58to.js → Governance-D06S4Qow.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BePWEH-d.js → Inference-nAvwxUXy.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-LF_sBvdL.js → KnowledgeGraph-CtqRP_rF.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BKkfk9hy.js → Logs-J5aGAyfY.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-BNMqUDla.js → Marketplace-CaeY23Kj.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-w4WPiyz2.js → McpTools-C2HHnwnE.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DUKMVGNi.js → Memory-CWBrdKlR.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BWhiEyTJ.js → MobileBridge-CBLQ8uCY.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Ocf5JEkX.js → MobileProjects-Q_Cy_W4_.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D2B6MMhU.js → Mtc-BXur-euo.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-DdYjCq1O.js → MtcAudit-CpQSlOQC.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-Cb1hfuUZ.js → Multisig-DlFwdEOA.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CTeImGp7.js → NLProgramming-DSh7fevc.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BjYNc7eQ.js → Notes-Bv4NOVxg.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Dn_NtRXQ.js → NotificationSettings-CzHNnIfa.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DVYYIizh.js → OrderTableRenderer-D7pHfKK4.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Be2Kmr5j.js → Organization-COzV01ed.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cu_-qRlR.js → Overflow-CYRZJb2_.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DD2HoGzE.js → P2P-DFm49FE0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BRI7DVth.js → PdhVaultBrowser-Ctr-6k5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C9Srcs2M.js → Permissions-DXVrhFyv.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CUqcMgo1.js → PersonalDataHub-BO5utpLK.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-HBrQGLXt.js → Pipeline-C9irYCg7.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D-B0vjwh.js → Privacy-r13hrvoF.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-eTeLIu9e.js → ProjectInit-PRKNPZZC.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DrRqCsC6.js → ProjectSettings-QvxM1ZDO.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-1G9DNOhI.js → Projects-DfgmsN3j.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-RkTZzpxP.js → Providers-DxbrccZ7.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BSCUgsgC.js → QrScannerModal-BMHVRxBX.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CHgyM3Bz.js → QuickAsk-Co3_ndAH.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-0lry4OZq.js → Recommend-Cv3UWR6s.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Cn45UroZ.js → RemoteSession-CSDsO2zA.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Q-Zjb707.js → Reputation-Bdj59vzM.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BdM70oda.js → Row-CejfUjmo.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DmOAKKap.js → RssFeed-C83yKkvU.js} +2 -2
- package/src/assets/web-panel/assets/{Search-Dwavbm07.js → Search-Ck4l_VI3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DukXFCnk.js → Security-DGIIEx6K.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BIlRnITu.js → Services-Cj2aNAy3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-YYVQdhhQ.js → Skeleton-Dlyy8akb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B8_iYHz2.js → Skills-C4aDa9Zh.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BdVrhT31.js → Sla-tHd_Bauo.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CD3ggRx7.js → SpeechSettings-DI7R0Qgd.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Bp02VZFH.js → SyncSettings-OOxiPdsE.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DEVmCEsr.js → Tasks-Nro_Ltg5.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-B6czWc4N.js → Templates-Bm7eSMWH.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BCJLv17r.js → Tenant-B8F8eFvD.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CD132d2Y.js → Terminal-2oWhR7QF.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-ZB-CvkZl.js → TimelineRenderer-GLnecmpd.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BnSY0S-s.js → Tokens-CtpFyati.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VZw9Oy2w.js → Trigger-DGkXZzbN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-pZq7Hjd2.js → Trust-ClYZMkXN.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DnQaqk4q.js → UkeySign-C1AuEW5-.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BEUfmvJV.js → VideoEditing-DHa9ekSK.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CoZKMXJ2.js → Wallet-CyQv8ZAN.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CBOGVx1I.js → WebAuthn-DPxEEb5Y.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-huSKn7TT.js → WorkflowEditor-C-fCM8Si.js} +1 -1
- package/src/assets/web-panel/assets/{chat-A_3w6FBO.js → chat-BMu1Pk8B.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D1Bem9Oy.js → colors-DYPOXyWQ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CJYJj0oO.js → compact-item-C5r_plPk.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BkbFiKT4.js → createContext-9fWrWmuJ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DmRz5Pet.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CKvUafVY.js → hasIn-CjZM2-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHdailyo.js → index-0DVMoDzF.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-WIY-FQ.js → index-B1iUe3Ba.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOoAQjJz.js → index-B56CI20h.js} +1 -1
- package/src/assets/web-panel/assets/{index-AjrSPnvv.js → index-BBVCXmJV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CD62EEGo.js → index-BPucc_ES.js} +1 -1
- package/src/assets/web-panel/assets/{index-tgO8iza6.js → index-BVFtY8Y4.js} +1 -1
- package/src/assets/web-panel/assets/{index-F--HuPG1.js → index-BW-x3mUK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTs_D1OL.js → index-B_bTdeou.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiDWxzbn.js → index-Bglw_kmu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBaosWRO.js → index-BsHmE__1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8i0lViZ.js → index-Bti4lpw5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJwlEnYo.js → index-ByVDaARk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVS8Ymuq.js → index-C3E7xVl1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBlBVJ_m.js → index-CDaA4A0b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfgCdFrN.js → index-CGqrVLWe.js} +27 -27
- package/src/assets/web-panel/assets/{index-Crp8CbRg.js → index-CJJx0kbR.js} +1 -1
- package/src/assets/web-panel/assets/{index-JEAGMii9.js → index-CTnaRErh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co3OFL0t.js → index-CYoS2cwK.js} +1 -1
- package/src/assets/web-panel/assets/{index-b-sbVdjQ.js → index-CcDGi9U1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjYrecNb.js → index-Cd9_KCPs.js} +1 -1
- package/src/assets/web-panel/assets/{index-PF-mpv8K.js → index-Co9kBFxY.js} +1 -1
- package/src/assets/web-panel/assets/index-CrsL7YBW.js +1 -0
- package/src/assets/web-panel/assets/{index-Fuk6t_3K.js → index-CvUR6l7C.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHdHSX06.js → index-CyUtBxnC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUj-l7GM.js → index-D0rFX-xN.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9bWmTcO.js → index-D6zyUpD6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChkXUj3f.js → index-DJk9cPrs.js} +1 -1
- package/src/assets/web-panel/assets/{index-pyvMVX8r.js → index-DLCoKst9.js} +1 -1
- package/src/assets/web-panel/assets/{index-ttDQVhZy.js → index-DNxR1CHs.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9Svn0zc.js → index-DWwI0Jjd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdutMsne.js → index-DjTB4wPn.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5a0T_W.js → index-Du-w2i6G.js} +1 -1
- package/src/assets/web-panel/assets/{index-D22zNXiS.js → index-HJdfPSMz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSgWxAOG.js → index-I3Qdrfcf.js} +1 -1
- package/src/assets/web-panel/assets/{index-qNTtOVi_.js → index-Mipa7XgU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpW9cf3e.js → index-jO4lIpFE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C37f6iey.js → index-oerotzoq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj0OZ-37.js → index-phHPSXlV.js} +1 -1
- package/src/assets/web-panel/assets/index-zW69WSDA.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-DtrnfsZH.js → initDefaultProps-B7tpABc_.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D4zqSaX3.js → motion-DmvaRoVv.js} +1 -1
- package/src/assets/web-panel/assets/{move-CGFGOUQj.js → move-DF4X29Wf.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DH2HvkJl.js → mtc-parser-C0cbg4PO.js} +1 -1
- package/src/assets/web-panel/assets/{omit-jJ2at5HX.js → omit-DZzcYy0l.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CRyMT1Fv.js → pickAttrs-DaZWfzuH.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DZ_CX0Pt.js → placementArrow-CBxOEyvn.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-wruHk-h5.js → responsiveObserve-CGl8NIlO.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BGgx8q67.js → slide-Bv330MSZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Do5M7gKm.js → statusUtils-DJVy5oEM.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CRa-GZpX.js → styleChecker-BiY1H5ST.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BxPcq6pj.js → useFlexGapSupport-D1sN2OlQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-v5O2ZcJm.js → useFs-DxxHawzW.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub--z2FU0Px.js → usePersonalDataHub-C1HblLrD.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1emidVKd.js → vnode-Dex0jy4T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CcJaRyHs.js → zoom-Bk1MScAD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +8 -1
- package/src/commands/agent.js +8 -1
- package/src/commands/changelog.js +148 -0
- package/src/commands/eval.js +88 -23
- package/src/commands/plugin.js +10 -0
- package/src/commands/team.js +26 -3
- package/src/data/changelog.json +199 -0
- package/src/gateways/ws/remote-session-protocol.js +9 -2
- package/src/harness/remote-command-ledger.js +60 -10
- package/src/index.js +2 -0
- package/src/lib/agent-sandbox.js +11 -5
- package/src/lib/agent-team/task-lease.js +28 -0
- package/src/lib/agent-team/team-runner.js +25 -0
- package/src/lib/changelog.js +252 -0
- package/src/lib/eval/tasks.js +62 -41
- package/src/lib/eval/trend.js +7 -1
- package/src/lib/lsp/__tests__/lsp-client.test.js +12 -6
- package/src/lib/lsp/code-intelligence.js +55 -17
- package/src/lib/lsp/lsp-client.js +7 -1
- package/src/lib/lsp/lsp-server-registry.js +15 -4
- package/src/lib/plugin-runtime/hooks.js +20 -4
- package/src/lib/plugin-runtime/install.js +20 -1
- package/src/lib/plugin-runtime/monitors.js +20 -4
- package/src/lib/plugin-runtime/policy.js +13 -0
- package/src/lib/plugin-runtime/remote-source.js +34 -0
- package/src/lib/plugin-runtime/signature.js +95 -22
- package/src/lib/plugin-security.js +13 -14
- package/src/lib/sandbox-egress-proxy.js +38 -2
- package/src/lib/sandbox-network-policy.js +26 -4
- package/src/lib/telemetry/span-recorder.js +26 -4
- package/src/repl/agent-repl.js +12 -8
- package/src/runtime/__tests__/auto-pin.test.js +27 -4
- package/src/runtime/auto-pin.js +19 -0
- package/src/runtime/headless-runner.js +23 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +0 -1
- package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +0 -1
- package/src/assets/web-panel/assets/index-Dwix3p4g.js +0 -1
- package/src/assets/web-panel/assets/index-SHaoZ0CA.js +0 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The `cc changelog` data source.
|
|
9
|
+
*
|
|
10
|
+
* The canonical update record is the repository-root `CHANGELOG.md`, which is
|
|
11
|
+
* organized by PRODUCT version (`## [v5.0.3.x]`). Each block references the CLI
|
|
12
|
+
* npm version (`chainlesschain 0.162.x`) it shipped. This parser distills that
|
|
13
|
+
* into a CLI-focused view: it keeps only blocks that actually reference a CLI
|
|
14
|
+
* npm version, and within each block keeps only the CLI-relevant sub-sections.
|
|
15
|
+
*
|
|
16
|
+
* At publish time `scripts/build-changelog.mjs` runs `parseChangelog` over the
|
|
17
|
+
* root CHANGELOG and writes the result to `src/data/changelog.json`, which is
|
|
18
|
+
* bundled with the npm package. `loadChangelog()` reads that file (with a
|
|
19
|
+
* fallback to parsing a raw CHANGELOG.md for in-repo dev use).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Signals that a `### ` sub-section HEADING is about the CLI npm package rather
|
|
23
|
+
// than the desktop app / Android / iOS / PDH portions of the same product
|
|
24
|
+
// release. CASE-SENSITIVE on purpose: real CLI notes write the acronym "CLI"
|
|
25
|
+
// (e.g. "cc CLI 0.162.150", "CLI 安全"), whereas PDH/Android bundle-roll notes
|
|
26
|
+
// write the version tag lowercase ("(pdh 0.4.6 / cli 0.162.48)"). Matching only
|
|
27
|
+
// uppercase "CLI" keeps collector releases that merely bundle a cli bump out of
|
|
28
|
+
// the CLI view. Heading-only (not body) for the same reason.
|
|
29
|
+
const CLI_HEADING_SIGNAL = /\bCLI\b|命令行/;
|
|
30
|
+
|
|
31
|
+
// A block with no `### ` sub-sections is a CLI release only if its header or
|
|
32
|
+
// preamble uses CLI-publish language. Same case-sensitivity: a lowercase
|
|
33
|
+
// bundle-roll "cli 0.162.x" mention is not enough.
|
|
34
|
+
const CLI_PUBLISH_SIGNAL = /\bCLI\b|命令行|catch-up/;
|
|
35
|
+
|
|
36
|
+
// CLI npm versions are 3-segment (0.162.x). The `(?<![\d.])` / `(?!\.\d)` guards
|
|
37
|
+
// stop a 4-segment PRODUCT version (v5.0.3.134) from matching as "5.0.3".
|
|
38
|
+
const V = "(?<![\\d.])(\\d+\\.\\d+\\.\\d+)(?!\\.\\d)";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract every CLI npm version token referenced in a block of text and return
|
|
42
|
+
* the "primary" one (the highest semver — the released target in a
|
|
43
|
+
* `0.162.149 → 0.162.150` style note).
|
|
44
|
+
*
|
|
45
|
+
* @param {string} text
|
|
46
|
+
* @returns {{ primary: string|null, all: string[] }}
|
|
47
|
+
*/
|
|
48
|
+
export function extractCliVersions(text) {
|
|
49
|
+
const found = new Set();
|
|
50
|
+
const patterns = [
|
|
51
|
+
// "cc CLI 0.162.150", "CLI 0.162.150"
|
|
52
|
+
new RegExp(`(?:cc\\s+)?cli\\s+${V}`, "gi"),
|
|
53
|
+
// "chainlesschain` 0.162.149 → **0.162.150**" (capture the arrow target)
|
|
54
|
+
new RegExp(
|
|
55
|
+
`chainlesschain[\`\\s]+(?:\\d+\\.\\d+\\.\\d+\\s*(?:→|->)\\s*)?\\*{0,2}${V}`,
|
|
56
|
+
"gi",
|
|
57
|
+
),
|
|
58
|
+
// bare "cli 0.162.71" inside "(pdh 0.4.25 / cli 0.162.71)"
|
|
59
|
+
new RegExp(`\\bcli\\s+${V}`, "gi"),
|
|
60
|
+
// "→ 0.162.150" / "→ **0.162.150**"
|
|
61
|
+
new RegExp(`(?:→|->)\\s*\\*{0,2}${V}`, "gi"),
|
|
62
|
+
];
|
|
63
|
+
for (const re of patterns) {
|
|
64
|
+
let m;
|
|
65
|
+
while ((m = re.exec(text)) !== null) {
|
|
66
|
+
found.add(m[1]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// The CLI npm package (`chainlesschain`) is on the 0.x alpha track; the
|
|
70
|
+
// product line is 5.x. Keeping only major-0 tokens discards any product
|
|
71
|
+
// version (v5.0.3.x) or desktop-app-vue (5.0.3-alpha.x) tokens that slip past
|
|
72
|
+
// the context regexes, so the "primary" is always a real CLI npm version.
|
|
73
|
+
const all = [...found].filter((v) => v.startsWith("0."));
|
|
74
|
+
const primary =
|
|
75
|
+
all.length === 0
|
|
76
|
+
? null
|
|
77
|
+
: all.reduce(
|
|
78
|
+
(best, v) => (compareSemver(v, best) > 0 ? v : best),
|
|
79
|
+
all[0],
|
|
80
|
+
);
|
|
81
|
+
return { primary, all };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Numeric semver compare for plain X.Y.Z (no prerelease). */
|
|
85
|
+
export function compareSemver(a, b) {
|
|
86
|
+
const pa = a.split(".").map(Number);
|
|
87
|
+
const pb = b.split(".").map(Number);
|
|
88
|
+
for (let i = 0; i < 3; i++) {
|
|
89
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
90
|
+
}
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse a raw CHANGELOG.md string into CLI-focused release entries.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} md
|
|
98
|
+
* @returns {Array<{
|
|
99
|
+
* productVersion: string,
|
|
100
|
+
* cliVersion: string|null,
|
|
101
|
+
* cliVersions: string[],
|
|
102
|
+
* date: string|null,
|
|
103
|
+
* title: string,
|
|
104
|
+
* sections: Array<{ heading: string, body: string }>,
|
|
105
|
+
* }>}
|
|
106
|
+
*/
|
|
107
|
+
export function parseChangelog(md) {
|
|
108
|
+
const lines = md.split(/\r?\n/);
|
|
109
|
+
const blocks = [];
|
|
110
|
+
let current = null;
|
|
111
|
+
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
const h2 = line.match(/^##\s+(.+)$/);
|
|
114
|
+
if (h2 && !line.startsWith("###")) {
|
|
115
|
+
if (current) blocks.push(current);
|
|
116
|
+
current = { header: h2[1].trim(), lines: [] };
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (current) current.lines.push(line);
|
|
120
|
+
}
|
|
121
|
+
if (current) blocks.push(current);
|
|
122
|
+
|
|
123
|
+
const releases = [];
|
|
124
|
+
for (const block of blocks) {
|
|
125
|
+
const parsed = parseBlockHeader(block.header);
|
|
126
|
+
const bodyText = block.lines.join("\n");
|
|
127
|
+
const fullText = block.header + "\n" + bodyText;
|
|
128
|
+
|
|
129
|
+
const { primary, all } = extractCliVersions(fullText);
|
|
130
|
+
// A release must reference a CLI npm version at all.
|
|
131
|
+
if (!primary) continue;
|
|
132
|
+
|
|
133
|
+
// CLI-relevance gate is decided by section HEADINGS (or, for a section-less
|
|
134
|
+
// block, by CLI-publish language) — not by a bare bundled-cli mention.
|
|
135
|
+
const sections = extractCliSections(block.lines, block.header);
|
|
136
|
+
if (!sections) continue;
|
|
137
|
+
|
|
138
|
+
releases.push({
|
|
139
|
+
productVersion: parsed.version,
|
|
140
|
+
cliVersion: primary,
|
|
141
|
+
cliVersions: all.sort(compareSemver),
|
|
142
|
+
date: parsed.date,
|
|
143
|
+
title: parsed.title,
|
|
144
|
+
sections,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return releases;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Parse "## [v5.0.3.134] - 2026-07-03 — title" into parts. */
|
|
151
|
+
function parseBlockHeader(header) {
|
|
152
|
+
const tag = header.match(/\[([^\]]+)\]/);
|
|
153
|
+
const version = tag ? tag[1] : header;
|
|
154
|
+
const rest = header.replace(/\[[^\]]+\]\s*/, "");
|
|
155
|
+
const dateMatch = rest.match(/(\d{4}-\d{2}-\d{2})/);
|
|
156
|
+
const date = dateMatch ? dateMatch[1] : null;
|
|
157
|
+
// Title = text after the em dash separator, else whatever remains.
|
|
158
|
+
let title = "";
|
|
159
|
+
const emDash = rest.split(/\s+—\s+/);
|
|
160
|
+
if (emDash.length > 1) {
|
|
161
|
+
title = emDash.slice(1).join(" — ").trim();
|
|
162
|
+
} else {
|
|
163
|
+
title = rest
|
|
164
|
+
.replace(/^-\s*/, "")
|
|
165
|
+
.replace(/\d{4}-\d{2}-\d{2}/, "")
|
|
166
|
+
.trim();
|
|
167
|
+
}
|
|
168
|
+
return { version, date, title };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Split a block body into `### ` sub-sections and keep only CLI-relevant ones.
|
|
173
|
+
*
|
|
174
|
+
* Returns:
|
|
175
|
+
* - an array of CLI sections, when the block is a CLI release; or
|
|
176
|
+
* - `null`, when the block is NOT a CLI release (all-non-CLI sub-sections, e.g.
|
|
177
|
+
* a PDH/Android collector release that merely bundles a new cli version).
|
|
178
|
+
*
|
|
179
|
+
* @param {string[]} bodyLines
|
|
180
|
+
* @param {string} header
|
|
181
|
+
* @returns {Array<{heading:string, body:string}>|null}
|
|
182
|
+
*/
|
|
183
|
+
function extractCliSections(bodyLines, header) {
|
|
184
|
+
const sections = [];
|
|
185
|
+
const preamble = [];
|
|
186
|
+
let cur = null;
|
|
187
|
+
for (const line of bodyLines) {
|
|
188
|
+
const h3 = line.match(/^###\s+(.+)$/);
|
|
189
|
+
if (h3) {
|
|
190
|
+
if (cur) sections.push(cur);
|
|
191
|
+
cur = { heading: h3[1].trim(), lines: [] };
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (cur) cur.lines.push(line);
|
|
195
|
+
else preamble.push(line); // before the first ### (blockquote summary etc.)
|
|
196
|
+
}
|
|
197
|
+
if (cur) sections.push(cur);
|
|
198
|
+
|
|
199
|
+
if (sections.length === 0) {
|
|
200
|
+
// Section-less block: CLI release only if the `##` TITLE names the CLI.
|
|
201
|
+
// The preamble is NOT consulted — every block carries a boilerplate
|
|
202
|
+
// "Version surfaces: CLI 0.162.x → ..." line, so a body match would let
|
|
203
|
+
// every PDH/Android release through.
|
|
204
|
+
if (!CLI_PUBLISH_SIGNAL.test(header)) return null;
|
|
205
|
+
const body = preamble.join("\n").trim();
|
|
206
|
+
return body ? [{ heading: "", body }] : [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const cliSections = sections
|
|
210
|
+
.filter((s) => CLI_HEADING_SIGNAL.test(s.heading))
|
|
211
|
+
.map((s) => ({ heading: s.heading, body: s.lines.join("\n").trim() }));
|
|
212
|
+
|
|
213
|
+
// Has `### ` sections but none are CLI-headed → not a CLI release.
|
|
214
|
+
return cliSections.length > 0 ? cliSections : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Resolve and load the CLI changelog data.
|
|
219
|
+
*
|
|
220
|
+
* Order is CHANGELOG-FIRST so the data can never go stale: when the repo-root
|
|
221
|
+
* CHANGELOG.md is reachable (source checkout / dev / this monorepo) it is parsed
|
|
222
|
+
* live, always reflecting the latest edits. The bundled `src/data/changelog.json`
|
|
223
|
+
* (a build artifact, refreshed at `prepublishOnly`) is only the fallback for an
|
|
224
|
+
* installed npm package, where CHANGELOG.md is not shipped.
|
|
225
|
+
*
|
|
226
|
+
* @returns {{ source: string, releases: Array }}
|
|
227
|
+
*/
|
|
228
|
+
export function loadChangelog() {
|
|
229
|
+
const changelogCandidates = [
|
|
230
|
+
path.join(__dirname, "..", "..", "CHANGELOG.md"), // packages/cli/CHANGELOG.md
|
|
231
|
+
path.join(__dirname, "..", "..", "..", "..", "CHANGELOG.md"), // repo root
|
|
232
|
+
];
|
|
233
|
+
for (const p of changelogCandidates) {
|
|
234
|
+
if (fs.existsSync(p)) {
|
|
235
|
+
return {
|
|
236
|
+
source: "CHANGELOG.md",
|
|
237
|
+
releases: parseChangelog(fs.readFileSync(p, "utf-8")),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Installed npm package: CHANGELOG.md isn't shipped, use the bundled artifact.
|
|
242
|
+
const jsonPath = path.join(__dirname, "..", "data", "changelog.json");
|
|
243
|
+
if (fs.existsSync(jsonPath)) {
|
|
244
|
+
try {
|
|
245
|
+
const data = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
|
|
246
|
+
if (Array.isArray(data.releases)) return data;
|
|
247
|
+
} catch {
|
|
248
|
+
/* fall through to empty */
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { source: null, releases: [] };
|
|
252
|
+
}
|
package/src/lib/eval/tasks.js
CHANGED
|
@@ -30,6 +30,39 @@ const STRINGUTIL_V2 =
|
|
|
30
30
|
" return str.charAt(0).toUpperCase() + str.slice(1);\n" +
|
|
31
31
|
"}\n";
|
|
32
32
|
|
|
33
|
+
// Harness files the agent is told NOT to edit, shared by setup and check so the
|
|
34
|
+
// check can byte-compare them (same anti-cheat as STRINGUTIL_V2 above). A mere
|
|
35
|
+
// presence-regex is neuterable: prepending `console.log('ALL OK');
|
|
36
|
+
// process.exit(0)` keeps the assertion text present while never running it.
|
|
37
|
+
const RUN_CHECKS_MJS = [
|
|
38
|
+
"import { sum } from './calc.js';",
|
|
39
|
+
"const cases = [[1,2,3],[10,5,15],[0,0,0]];",
|
|
40
|
+
"for (const [a,b,want] of cases) {",
|
|
41
|
+
" if (sum(a,b) !== want) {",
|
|
42
|
+
" console.error(`FAIL sum(${a},${b})=${sum(a,b)} want ${want}`);",
|
|
43
|
+
" process.exit(1);",
|
|
44
|
+
" }",
|
|
45
|
+
"}",
|
|
46
|
+
"console.log('ALL OK');",
|
|
47
|
+
"",
|
|
48
|
+
].join("\n");
|
|
49
|
+
|
|
50
|
+
const BUILD_MJS =
|
|
51
|
+
'import { banner } from "./app.mjs";\n' +
|
|
52
|
+
'if (banner() !== "v1.0.0") {\n' +
|
|
53
|
+
' console.error("BUILD FAIL: " + banner());\n' +
|
|
54
|
+
" process.exit(1);\n" +
|
|
55
|
+
"}\n" +
|
|
56
|
+
'console.log("BUILD OK");\n';
|
|
57
|
+
|
|
58
|
+
const RUN_MJS =
|
|
59
|
+
'import { label } from "./app.mjs";\n' +
|
|
60
|
+
'if (label("hello") !== "Hello") {\n' +
|
|
61
|
+
' console.error("FAIL: " + label("hello"));\n' +
|
|
62
|
+
" process.exit(1);\n" +
|
|
63
|
+
"}\n" +
|
|
64
|
+
'console.log("OK");\n';
|
|
65
|
+
|
|
33
66
|
export const BUILTIN_TASKS = [
|
|
34
67
|
{
|
|
35
68
|
id: "create-file",
|
|
@@ -134,32 +167,22 @@ export const BUILTIN_TASKS = [
|
|
|
134
167
|
);
|
|
135
168
|
fs.writeFileSync(
|
|
136
169
|
path.join(dir, "run-checks.mjs"),
|
|
137
|
-
|
|
138
|
-
"import { sum } from './calc.js';",
|
|
139
|
-
"const cases = [[1,2,3],[10,5,15],[0,0,0]];",
|
|
140
|
-
"for (const [a,b,want] of cases) {",
|
|
141
|
-
" if (sum(a,b) !== want) {",
|
|
142
|
-
" console.error(`FAIL sum(${a},${b})=${sum(a,b)} want ${want}`);",
|
|
143
|
-
" process.exit(1);",
|
|
144
|
-
" }",
|
|
145
|
-
"}",
|
|
146
|
-
"console.log('ALL OK');",
|
|
147
|
-
"",
|
|
148
|
-
].join("\n"),
|
|
170
|
+
RUN_CHECKS_MJS,
|
|
149
171
|
"utf8",
|
|
150
172
|
);
|
|
151
173
|
},
|
|
152
174
|
check: (dir) => {
|
|
153
|
-
// Objective: the test harness must actually pass.
|
|
154
|
-
//
|
|
155
|
-
//
|
|
175
|
+
// Objective: the test harness must actually pass. Byte-compare the
|
|
176
|
+
// harness — a presence-regex alone is neuterable (prepend an early
|
|
177
|
+
// `console.log('ALL OK'); process.exit(0)` and the assertion text is
|
|
178
|
+
// still there but never runs).
|
|
156
179
|
const checks = read(dir, "run-checks.mjs");
|
|
157
180
|
if (checks == null)
|
|
158
181
|
return { pass: false, detail: "run-checks.mjs missing" };
|
|
159
|
-
if (
|
|
182
|
+
if (checks !== RUN_CHECKS_MJS) {
|
|
160
183
|
return {
|
|
161
184
|
pass: false,
|
|
162
|
-
detail: "run-checks.mjs
|
|
185
|
+
detail: "run-checks.mjs was edited (fix calc.js instead)",
|
|
163
186
|
};
|
|
164
187
|
}
|
|
165
188
|
try {
|
|
@@ -487,23 +510,19 @@ export const BUILTIN_TASKS = [
|
|
|
487
510
|
'export function banner() {\n return "v" + VERSION;\n}\n',
|
|
488
511
|
"utf8",
|
|
489
512
|
);
|
|
490
|
-
fs.writeFileSync(
|
|
491
|
-
path.join(dir, "build.mjs"),
|
|
492
|
-
'import { banner } from "./app.mjs";\n' +
|
|
493
|
-
'if (banner() !== "v1.0.0") {\n' +
|
|
494
|
-
' console.error("BUILD FAIL: " + banner());\n' +
|
|
495
|
-
" process.exit(1);\n" +
|
|
496
|
-
"}\n" +
|
|
497
|
-
'console.log("BUILD OK");\n',
|
|
498
|
-
"utf8",
|
|
499
|
-
);
|
|
513
|
+
fs.writeFileSync(path.join(dir, "build.mjs"), BUILD_MJS, "utf8");
|
|
500
514
|
},
|
|
501
515
|
check: (dir) => {
|
|
502
516
|
const build = read(dir, "build.mjs");
|
|
503
517
|
if (build == null) return { pass: false, detail: "build.mjs missing" };
|
|
504
|
-
// Guard: fix the modules, don't neuter the build harness.
|
|
505
|
-
|
|
506
|
-
|
|
518
|
+
// Guard: fix the modules, don't neuter the build harness. Byte-compare —
|
|
519
|
+
// a presence-regex is satisfiable by a rewritten build.mjs that defines
|
|
520
|
+
// its own banner() and never imports app.mjs.
|
|
521
|
+
if (build !== BUILD_MJS) {
|
|
522
|
+
return {
|
|
523
|
+
pass: false,
|
|
524
|
+
detail: "build.mjs was edited (reconcile config.mjs/app.mjs instead)",
|
|
525
|
+
};
|
|
507
526
|
}
|
|
508
527
|
try {
|
|
509
528
|
const out = execFileSync(process.execPath, ["build.mjs"], {
|
|
@@ -534,7 +553,7 @@ export const BUILTIN_TASKS = [
|
|
|
534
553
|
"`capitalize` export was renamed to `toTitle` (same behavior). Do all of: " +
|
|
535
554
|
"(1) bump stringutil to ^2.0.0 in package.json; (2) update app.mjs to " +
|
|
536
555
|
"import and use `toTitle` instead of `capitalize`; (3) do NOT edit " +
|
|
537
|
-
"stringutil.mjs. Afterwards `node run.mjs` must print OK.",
|
|
556
|
+
"stringutil.mjs or run.mjs. Afterwards `node run.mjs` must print OK.",
|
|
538
557
|
setup: (dir) => {
|
|
539
558
|
fs.writeFileSync(
|
|
540
559
|
path.join(dir, "package.json"),
|
|
@@ -558,16 +577,7 @@ export const BUILTIN_TASKS = [
|
|
|
558
577
|
"export function label(s) {\n return capitalize(s);\n}\n",
|
|
559
578
|
"utf8",
|
|
560
579
|
);
|
|
561
|
-
fs.writeFileSync(
|
|
562
|
-
path.join(dir, "run.mjs"),
|
|
563
|
-
'import { label } from "./app.mjs";\n' +
|
|
564
|
-
'if (label("hello") !== "Hello") {\n' +
|
|
565
|
-
' console.error("FAIL: " + label("hello"));\n' +
|
|
566
|
-
" process.exit(1);\n" +
|
|
567
|
-
"}\n" +
|
|
568
|
-
'console.log("OK");\n',
|
|
569
|
-
"utf8",
|
|
570
|
-
);
|
|
580
|
+
fs.writeFileSync(path.join(dir, "run.mjs"), RUN_MJS, "utf8");
|
|
571
581
|
},
|
|
572
582
|
check: (dir) => {
|
|
573
583
|
// The dependency is vendored — the agent must adapt the caller, NOT edit
|
|
@@ -580,6 +590,17 @@ export const BUILTIN_TASKS = [
|
|
|
580
590
|
detail: "the stringutil dependency was edited (adapt the caller)",
|
|
581
591
|
};
|
|
582
592
|
}
|
|
593
|
+
// The run harness is equally off-limits: without this guard an agent
|
|
594
|
+
// could overwrite run.mjs with `console.log("OK")` and pass the textual
|
|
595
|
+
// checks below without app.mjs ever being executed.
|
|
596
|
+
const runner = read(dir, "run.mjs");
|
|
597
|
+
if (runner == null) return { pass: false, detail: "run.mjs missing" };
|
|
598
|
+
if (runner !== RUN_MJS) {
|
|
599
|
+
return {
|
|
600
|
+
pass: false,
|
|
601
|
+
detail: "run.mjs was edited (adapt app.mjs/package.json instead)",
|
|
602
|
+
};
|
|
603
|
+
}
|
|
583
604
|
const pkg = read(dir, "package.json");
|
|
584
605
|
if (pkg == null || !/"stringutil"\s*:\s*"[\^~]?2\./.test(pkg)) {
|
|
585
606
|
return {
|
package/src/lib/eval/trend.js
CHANGED
|
@@ -84,7 +84,13 @@ function ordered(records) {
|
|
|
84
84
|
* }}
|
|
85
85
|
*/
|
|
86
86
|
export function computeTrend(runs, { regressionThreshold = 0 } = {}) {
|
|
87
|
-
|
|
87
|
+
// A dry-run record is always 0% (the no-op agent creates nothing) — folding
|
|
88
|
+
// it in would flag every previously-passing task as a regression (or, in the
|
|
89
|
+
// other direction, fake an "all fixed" jump). Smoke-testing with
|
|
90
|
+
// `--dry-run --history` must not poison the release gate.
|
|
91
|
+
const norm = ordered(
|
|
92
|
+
(runs || []).filter((r) => r?.dryRun !== true).map((r) => summarizeRun(r)),
|
|
93
|
+
);
|
|
88
94
|
const history = norm.map((r) => r.passRate);
|
|
89
95
|
if (norm.length === 0) {
|
|
90
96
|
return {
|
|
@@ -225,14 +225,20 @@ describe("uri <-> path", () => {
|
|
|
225
225
|
describe("normalizeUri", () => {
|
|
226
226
|
it("lowercases the windows drive letter so client/server keys match", () => {
|
|
227
227
|
// path.resolve yields C:, servers echo c: — must collapse to one key
|
|
228
|
-
expect(normalizeUri("file:///C%3A/proj/a.ts")).toBe(
|
|
229
|
-
|
|
230
|
-
);
|
|
231
|
-
expect(normalizeUri("file:///c%3A/proj/a.ts")).toBe(
|
|
232
|
-
"file:///c%3A/proj/a.ts",
|
|
233
|
-
);
|
|
228
|
+
expect(normalizeUri("file:///C%3A/proj/a.ts")).toBe("file:///c:/proj/a.ts");
|
|
229
|
+
expect(normalizeUri("file:///c%3A/proj/a.ts")).toBe("file:///c:/proj/a.ts");
|
|
234
230
|
expect(normalizeUri("file:///C:/proj/a.ts")).toBe("file:///c:/proj/a.ts");
|
|
235
231
|
});
|
|
232
|
+
it("collapses the %3A and bare-colon drive forms to ONE key (gopls)", () => {
|
|
233
|
+
// The client sends percent-encoded didOpen URIs (file:///C%3A/…);
|
|
234
|
+
// tsserver/pyright echo that form back, but gopls REWRITES to a bare
|
|
235
|
+
// colon (file:///C:/…). Before this canonicalization the two forms were
|
|
236
|
+
// distinct diagnostics-Map keys, so every gopls publish missed the
|
|
237
|
+
// didOpen key and `cc code-intel diag` on Go was permanently empty.
|
|
238
|
+
const ours = normalizeUri("file:///C%3A/Users/u/goproj/broken.go");
|
|
239
|
+
const goplsPublished = normalizeUri("file:///C:/Users/u/goproj/broken.go");
|
|
240
|
+
expect(ours).toBe(goplsPublished);
|
|
241
|
+
});
|
|
236
242
|
it("leaves POSIX file URIs unchanged", () => {
|
|
237
243
|
expect(normalizeUri("file:///home/u/a.ts")).toBe("file:///home/u/a.ts");
|
|
238
244
|
});
|
|
@@ -85,11 +85,15 @@ export class CodeIntelligence {
|
|
|
85
85
|
async references(filePath, line, col, { includeDeclaration = true } = {}) {
|
|
86
86
|
const ready = await this._ensure(filePath);
|
|
87
87
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
88
|
-
const result = await
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const result = await this._readRequest(
|
|
89
|
+
ready.client,
|
|
90
|
+
"textDocument/references",
|
|
91
|
+
{
|
|
92
|
+
textDocument: { uri: ready.uri },
|
|
93
|
+
position: toLspPosition({ line, col }),
|
|
94
|
+
context: { includeDeclaration },
|
|
95
|
+
},
|
|
96
|
+
);
|
|
93
97
|
return { available: true, locations: this._normalizeLocations(result) };
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -97,7 +101,7 @@ export class CodeIntelligence {
|
|
|
97
101
|
async hover(filePath, line, col) {
|
|
98
102
|
const ready = await this._ensure(filePath);
|
|
99
103
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
100
|
-
const result = await ready.client
|
|
104
|
+
const result = await this._readRequest(ready.client, "textDocument/hover", {
|
|
101
105
|
textDocument: { uri: ready.uri },
|
|
102
106
|
position: toLspPosition({ line, col }),
|
|
103
107
|
});
|
|
@@ -108,9 +112,13 @@ export class CodeIntelligence {
|
|
|
108
112
|
async documentSymbols(filePath) {
|
|
109
113
|
const ready = await this._ensure(filePath);
|
|
110
114
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
111
|
-
const result = await
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
const result = await this._readRequest(
|
|
116
|
+
ready.client,
|
|
117
|
+
"textDocument/documentSymbol",
|
|
118
|
+
{
|
|
119
|
+
textDocument: { uri: ready.uri },
|
|
120
|
+
},
|
|
121
|
+
);
|
|
114
122
|
return {
|
|
115
123
|
available: true,
|
|
116
124
|
symbols: this._normalizeSymbols(result, ready.uri),
|
|
@@ -124,7 +132,9 @@ export class CodeIntelligence {
|
|
|
124
132
|
if (!seed) return unavailable("no indexable file found to start a server");
|
|
125
133
|
const ready = await this.manager.ensureFor(seed, this._ensureOpts);
|
|
126
134
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
127
|
-
const result = await ready.client
|
|
135
|
+
const result = await this._readRequest(ready.client, "workspace/symbol", {
|
|
136
|
+
query,
|
|
137
|
+
});
|
|
128
138
|
return { available: true, symbols: this._normalizeSymbols(result) };
|
|
129
139
|
}
|
|
130
140
|
|
|
@@ -160,7 +170,8 @@ export class CodeIntelligence {
|
|
|
160
170
|
const caps = ready.client.serverCapabilities || {};
|
|
161
171
|
if (caps.diagnosticProvider) {
|
|
162
172
|
try {
|
|
163
|
-
const report = await
|
|
173
|
+
const report = await this._readRequest(
|
|
174
|
+
ready.client,
|
|
164
175
|
"textDocument/diagnostic",
|
|
165
176
|
{ textDocument: { uri: ready.uri } },
|
|
166
177
|
{ timeoutMs },
|
|
@@ -182,11 +193,15 @@ export class CodeIntelligence {
|
|
|
182
193
|
async renamePreview(filePath, line, col, newName) {
|
|
183
194
|
const ready = await this._ensure(filePath);
|
|
184
195
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
185
|
-
const result = await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
const result = await this._readRequest(
|
|
197
|
+
ready.client,
|
|
198
|
+
"textDocument/rename",
|
|
199
|
+
{
|
|
200
|
+
textDocument: { uri: ready.uri },
|
|
201
|
+
position: toLspPosition({ line, col }),
|
|
202
|
+
newName,
|
|
203
|
+
},
|
|
204
|
+
);
|
|
190
205
|
return { available: true, edits: this._normalizeWorkspaceEdit(result) };
|
|
191
206
|
}
|
|
192
207
|
|
|
@@ -196,10 +211,33 @@ export class CodeIntelligence {
|
|
|
196
211
|
|
|
197
212
|
// ---- internals ----
|
|
198
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Issue an idempotent READ request with a bounded ContentModified retry.
|
|
216
|
+
* LSP -32801 (ContentModified) means "server state changed mid-request —
|
|
217
|
+
* the result would be stale, ask again"; rust-analyzer emits it freely
|
|
218
|
+
* while its index is still settling after didOpen. Read-only queries can
|
|
219
|
+
* always be retried; mutating requests must NOT go through here.
|
|
220
|
+
*/
|
|
221
|
+
async _readRequest(client, method, params, opts) {
|
|
222
|
+
let lastErr;
|
|
223
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
224
|
+
try {
|
|
225
|
+
return await client.request(method, params, opts);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const contentModified =
|
|
228
|
+
err?.code === -32801 || /content modified/i.test(err?.message || "");
|
|
229
|
+
if (!contentModified) throw err;
|
|
230
|
+
lastErr = err;
|
|
231
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
throw lastErr;
|
|
235
|
+
}
|
|
236
|
+
|
|
199
237
|
async _locationQuery(method, filePath, line, col) {
|
|
200
238
|
const ready = await this._ensure(filePath);
|
|
201
239
|
if (ready.unavailable) return unavailable(ready.reason);
|
|
202
|
-
const result = await ready.client
|
|
240
|
+
const result = await this._readRequest(ready.client, method, {
|
|
203
241
|
textDocument: { uri: ready.uri },
|
|
204
242
|
position: toLspPosition({ line, col }),
|
|
205
243
|
});
|
|
@@ -297,9 +297,15 @@ export function pathToFileUri(p) {
|
|
|
297
297
|
*/
|
|
298
298
|
export function normalizeUri(uri) {
|
|
299
299
|
if (!uri) return uri;
|
|
300
|
+
// Canonicalize BOTH the drive-letter case AND the colon encoding. Servers
|
|
301
|
+
// differ in the form they publish back: tsserver/pyright echo the client's
|
|
302
|
+
// percent-encoded `c%3A/…`, but gopls rewrites to a bare `C:/…` — keeping
|
|
303
|
+
// the incoming colon form made the two forms distinct Map keys, so every
|
|
304
|
+
// gopls publishDiagnostics missed the didOpen key and Go diagnostics were
|
|
305
|
+
// permanently empty regardless of timeout.
|
|
300
306
|
return String(uri).replace(
|
|
301
307
|
/^(file:\/\/\/)([A-Za-z])(%3[Aa]|:)/,
|
|
302
|
-
(_m, p, drive
|
|
308
|
+
(_m, p, drive) => p + drive.toLowerCase() + ":",
|
|
303
309
|
);
|
|
304
310
|
}
|
|
305
311
|
|
|
@@ -70,6 +70,13 @@ function tsServer() {
|
|
|
70
70
|
// Registry of plugin-contributed servers, keyed by languageId. Wins over
|
|
71
71
|
// builtins if a plugin explicitly overrides.
|
|
72
72
|
const pluginServers = new Map();
|
|
73
|
+
// Plugin-contributed extension→languageId overrides, kept SEPARATE from the
|
|
74
|
+
// builtin EXTENSION_LANGUAGE map so `_resetPluginServers` (used by
|
|
75
|
+
// `/reload-plugins`) can fully roll them back. Mutating the builtin map
|
|
76
|
+
// directly meant an uninstalled/untrusted plugin's `.py`→"mylang" remap
|
|
77
|
+
// survived a reload — languageIdForFile kept returning the dead languageId and
|
|
78
|
+
// the builtin server for that extension became unreachable until process exit.
|
|
79
|
+
const pluginExtensions = new Map();
|
|
73
80
|
|
|
74
81
|
/**
|
|
75
82
|
* Register an extra language server (from a plugin `.lsp.json`).
|
|
@@ -87,21 +94,25 @@ export function registerLanguageServer(def) {
|
|
|
87
94
|
});
|
|
88
95
|
if (Array.isArray(def.extensions)) {
|
|
89
96
|
for (const ext of def.extensions) {
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
pluginExtensions.set(
|
|
98
|
+
ext.startsWith(".") ? ext : "." + ext,
|
|
99
|
+
def.languageId,
|
|
100
|
+
);
|
|
92
101
|
}
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
/** Test/reset hook — clears plugin registrations. */
|
|
105
|
+
/** Test/reset hook — clears plugin registrations (servers AND extension maps). */
|
|
97
106
|
export function _resetPluginServers() {
|
|
98
107
|
pluginServers.clear();
|
|
108
|
+
pluginExtensions.clear();
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
/** Map a file path to its LSP languageId, or null if unsupported. */
|
|
102
112
|
export function languageIdForFile(filePath) {
|
|
103
113
|
const ext = path.extname(String(filePath || "")).toLowerCase();
|
|
104
|
-
|
|
114
|
+
// Plugin overrides win over builtins (same precedence as pluginServers).
|
|
115
|
+
return pluginExtensions.get(ext) || EXTENSION_LANGUAGE[ext] || null;
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
/**
|
|
@@ -60,11 +60,27 @@ export function collectPluginHooks(opts = {}) {
|
|
|
60
60
|
for (const p of trusted) {
|
|
61
61
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
62
62
|
const h = p.manifest.components?.hooks;
|
|
63
|
-
if (!h
|
|
63
|
+
if (!h) continue;
|
|
64
64
|
let parsed;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if (h.absPath) {
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(_deps.readFileSync(h.absPath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
} else if (h.inline) {
|
|
72
|
+
// Hooks declared inline in plugin.json — the normalized component keeps
|
|
73
|
+
// only counts, so re-read the raw manifest for the actual entries (same
|
|
74
|
+
// approach as the MCP collector). Without this, inline hooks never fire.
|
|
75
|
+
try {
|
|
76
|
+
const raw = JSON.parse(
|
|
77
|
+
_deps.readFileSync(p.manifest.manifestPath, "utf8"),
|
|
78
|
+
);
|
|
79
|
+
parsed = raw && typeof raw === "object" ? raw.hooks : null;
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
68
84
|
continue;
|
|
69
85
|
}
|
|
70
86
|
const map = normalizeHookMap(parsed);
|