chainlesschain 0.162.161 → 0.162.163
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DMn528q-.js → AIOps-BQ0kgfA1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-D9ZcDCJL.js → ActionButton-Dr1MDANe.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-A_K-8PX0.js → Analytics-CZ5vsaXW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D4pFW3X6.js → AppLayout-BIBga881.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dz46COAd.js → Artifacts-vpXHRU3u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-cbMjbyg0.js → Audit-B8exuoub.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dtf88GAj.js → BackgroundAgents-923evRRN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C5KRxn52.js → Backup-sr_sswlQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dd2UK3BN.js → BaseInput-CnW-4XKT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-lptKTFdG.js → Chat-FZCsTZIL.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_yspG6rG.js → Checkbox-DFrxB59H.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BGq_GAha.js → Codegen-_b6WEel8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CePor71g.js → Col-CkbnnZ-K.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOWhIT7.js → Community-BxM_82zW.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CivJt2vT.js → Compact-DYinP45R.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Btmo1GGE.js → Compliance-DyXp3sSP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Ct35OlG4.js → Cowork-BEr2DhW7.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CTc2GmhB.js → Cron-B-Vwo1hN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-ZtiBETk1.js → Crosschain-DbLxKddd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BNqMQQXw.js → DID-BsQ_LFw9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmzZd-9V.js → Dashboard-lVlXQHu5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CMVgvP2M.js → Dropdown-Beo_foO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcqQFAfT.js → EmailListRenderer-DszHIXED.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CTqB1_4V.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CiKOOzlb.js → Federation-CuJLa2eb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Wk-OT0gr.js → FormItemContext-Cxd1lxhc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DbEQT6ue.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-0ZvCPSD9.js → Git-WqPKetx6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CmVuKkPb.js → Governance-CWYtY3ke.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CTWE3Jwg.js → Inference-BnX1iiNV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBEIF00L.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLhxDwVG.js → Logs-DqLMNntm.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BgsCBrnc.js → Marketplace-_vGfmhNl.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-ClBUGDS2.js → McpTools-BeJDOkDj.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CnG_ZnYw.js → Memory-Da0EEZDO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BUOAbR1Z.js → MobileBridge-DSuzcCCy.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dnt_9-WB.js → MobileProjects-D0RAk4e8.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CLjt4kCD.js → Mtc-BE9YgdA0.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-f24U9Aes.js → MtcAudit-fC2BpO5S.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-GrmGQbSq.js → Multisig-D24Xxqw3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DADaWn-n.js → NLProgramming-Bs3oGvS8.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-xtf5RX6C.js → Notes-BFjmAA-3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cd27Rgtx.js → NotificationSettings-BX_9Bhv0.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-fXRxlDjl.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-joCFlNB0.js → Organization-DBcdsVEP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B1HoURq0.js → Overflow-DDyaF9vC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DiwsKbT7.js → P2P-BB78L4he.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Xe-BsW6S.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C23qi8kl.js → Permissions-INbnWv4j.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BS1syX2f.js → PersonalDataHub-BST-VmTq.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DRYwwDyh.js → Pipeline-CE7_tS2k.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BQKaDHPH.js → Privacy-hK5L8dOB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BC6F9xiG.js → ProjectInit-BvkkUoDn.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ClFPc1Nd.js → ProjectSettings-u7_EUtkm.js} +2 -2
- package/src/assets/web-panel/assets/Projects-5Fmt_8Nw.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CLWuOUNt.js → Providers-B9eanarR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DiqULTxb.js → QrScannerModal-C0dX5N2f.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-9A03P-E_.js → QuickAsk-CKTpG-PZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJZAnbq_.js → Recommend-Px_w_XQM.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C9A1R77h.js → RemoteSession-Di0fnYkM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BIZc6Rky.js → Reputation-vesnWjXt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DfIPNtuG.js → Row-exbY6LkT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C5znFcQ1.js → RssFeed-B3O3x70t.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DSs2z3OX.js → Search-uRA0DhNz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CV6LcrLY.js → Security-CViqdH6K.js} +4 -4
- package/src/assets/web-panel/assets/{Services-cmhoX0Xj.js → Services-CkXS97WN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-tCsg2po7.js → Skeleton-DxW5WYnE.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BK1KUDiy.js → Skills-CLxmGFse.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yRgyeA82.js → Sla-Cr5fziXq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-c1mo1mlp.js → SpeechSettings-H0JSb3cp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-koIAfuT3.js → SyncSettings-DMYWMae7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DkkreAaB.js → Tasks-CbYyDdF4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-fy5Ctf9M.js → Templates-Cpc8JjiK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Bw70Jf6S.js → Tenant-CRh77Jda.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BKh6Rx-4.js → Terminal-4D8tNBO5.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Dg54WP0c.js → TimelineRenderer-D41jzzbG.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvvD5SxJ.js → Tokens-C_jWEy6b.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B7Ug9x4e.js → Trigger-Dk1ZUhsy.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BjvuMORY.js → Trust-CLVy4l7m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZqhkx8p.js → UkeySign-C-FeqFDj.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClR50lhX.js → VideoEditing-CqVZzyMm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DlfppYjx.js → Wallet-DX9BW9pu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-BirgVr8B.js → WebAuthn-7hQDpWw_.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-B8DP_bax.js → WorkflowEditor-6XqUAO0j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DA8AUZzL.js → chat-5R_vkaJG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C38pLGp1.js → colors-huUPZdC-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-81rtr98o.js → compact-item-CfJlgisE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D34ixq8t.js → createContext-jO8heofr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D4bzqTrZ.js → hasIn-DY8Vxqx7.js} +1 -1
- package/src/assets/web-panel/assets/{index-E1OJRXs4.js → index-A48PtXw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3zCCZRS.js → index-B300RPnT.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cd3KuNK5.js → index-B74OXFxC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmo3dFHb.js → index-BAON7TzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddk0e_Pi.js → index-BCqxyvqp.js} +1 -1
- package/src/assets/web-panel/assets/{index-9qoVpPt6.js → index-BHE8OIls.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBFSo1NF.js → index-BQS-uPOK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUQLXLQM.js → index-BRXOP09X.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6GfvEQG.js → index-BYLBPpt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFyWKOBD.js → index-BfCx4pFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP-zkXK8.js → index-BliaT02i.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMgnRwrK.js → index-BmqGerpC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIP62a6r.js → index-BsBtRWbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkHqMnn3.js → index-BtEXSoPL.js} +1 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
- package/src/assets/web-panel/assets/{index-B8mkLM4x.js → index-Bxax9vQ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZRJRItm0.js → index-C2iVSFpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP5alL4t.js → index-CA6sO5VD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS3M5H9V.js → index-CA90TZxG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvfCEJG-.js → index-CLhBh-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-tKiC8T-4.js → index-CRElRhyS.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0QArPQt.js → index-Cbe9G0V4.js} +1 -1
- package/src/assets/web-panel/assets/{index-COe_cOLJ.js → index-ChvHubX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvL_3J_n.js → index-Cis_Ip7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-F76w1NiL.js → index-Cjxbu68P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlxEphl7.js → index-CzKmteNS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuS-UPoj.js → index-D0pet5Uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbhE3j5j.js → index-D1VLuww0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_2VYPr1.js → index-DXBcvUnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DakfFZ-k.js → index-Db1cLWir.js} +1 -1
- package/src/assets/web-panel/assets/{index-fI5sXMLg.js → index-DoI2sZm7.js} +1 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
- package/src/assets/web-panel/assets/{index-CoVgZw_b.js → index-KRHroryI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvD7b2IK.js → index-d1xkbtNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtGxuQrO.js → index-dKx57io7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqIhLAbA.js → index-j3O2EEJe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXd2_LmH.js → index-p85YaaKw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFHa1HIg.js → index-v3pTaEQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Ih7Hwn.js → index-vXs3B8GJ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ByyvflIR.js → initDefaultProps-MCB7-raZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B3FQm2By.js → motion-UWDF9swV.js} +1 -1
- package/src/assets/web-panel/assets/{move-BSfns6Ef.js → move-CsTmgXZU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CTG1Ieri.js → mtc-parser-fQy6KGLg.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DObM_2j6.js → omit-woPnEjd1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DOfelQ1t.js → pickAttrs-Bs2dZ1gf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CHZILds_.js → placementArrow-C7EVgPkg.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-REbucKSx.js → responsiveObserve-BH2kYCTN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C6EZzD7o.js → slide-BFBP2V_e.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BxkWS-g3.js → statusUtils-xdDxDYse.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Chklyoq6.js → styleChecker-aAWgPPQf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CRVwKrrI.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DV24ik_J.js → useFs-BrOih40N.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C7vIo4Pg.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-By-v5GlF.js → vnode-B8lutNSE.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B1w9Am3r.js → zoom-CffNre7V.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +254 -23
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +239 -1
- package/src/commands/review.js +352 -2
- package/src/data/changelog.json +14 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +5 -2
- package/src/lib/agent-schedule-store.js +289 -10
- package/src/lib/agents.js +19 -0
- package/src/lib/background-agent-supervisor.js +34 -7
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/docs-drift.js +166 -0
- package/src/lib/doctor-checkup.js +128 -0
- package/src/lib/hook-event-bus.cjs +0 -0
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +75 -12
- package/src/lib/json-schema-validate.js +831 -0
- package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
- package/src/lib/lsp/diagnostics-scheduler.js +127 -0
- package/src/lib/operation-fingerprint.js +95 -0
- package/src/lib/plugin-runtime/capabilities.js +371 -0
- package/src/lib/plugin-runtime/capability-consent.js +175 -0
- package/src/lib/plugin-runtime/manifest.js +30 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +16 -0
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +52 -0
- package/src/lib/review-pipeline.js +227 -0
- package/src/lib/runtime-checkup.js +318 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/session-lifecycle.js +305 -0
- package/src/lib/settings-hook-events.cjs +52 -13
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +296 -0
- package/src/lib/sub-agent-context.js +74 -5
- package/src/lib/subagent-contract.js +383 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry-ids.js +190 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/turn-binding.js +397 -0
- package/src/repl/agent-repl.js +136 -0
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +387 -14
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/headless-runner.js +267 -0
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C2KLtwHz.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +0 -1
- package/src/assets/web-panel/assets/index-CY1NURWZ.js +0 -1
- package/src/assets/web-panel/assets/index-DokUYqpq.js +0 -1
package/src/lib/repl-rewind.js
CHANGED
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
* turn be matched to its pre-mutation snapshot purely from the count.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import {
|
|
29
|
+
buildTurnBindingFromMarks,
|
|
30
|
+
resolveRestorePlan,
|
|
31
|
+
RESTORE_SCOPE,
|
|
32
|
+
TURN_COVERAGE,
|
|
33
|
+
} from "./turn-binding.js";
|
|
34
|
+
|
|
28
35
|
export const DEFAULT_LIST_LIMIT = 10;
|
|
29
36
|
export const PREVIEW_CHARS = 60;
|
|
30
37
|
|
|
@@ -219,6 +226,51 @@ export function restoreClearedConversation(messages, marks, cleared) {
|
|
|
219
226
|
};
|
|
220
227
|
}
|
|
221
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Coverage-aware restore plan for a rewound turn, derived from the REPL's
|
|
231
|
+
* EXISTING implicit state (`listUserTurns()` output + `{atMessageCount,id,tool}`
|
|
232
|
+
* marks) — no new agent-loop events required. Bridges those into the explicit
|
|
233
|
+
* turn-binding table and asks `resolveRestorePlan` for the honest plan, so
|
|
234
|
+
* `/rewind` can warn when a restore can't be fully promised (side-effects, a
|
|
235
|
+
* missing checkpoint, or a conversation/files drift) instead of silently doing a
|
|
236
|
+
* lossy rewind.
|
|
237
|
+
*
|
|
238
|
+
* @param {Array} messages live conversation (for listUserTurns)
|
|
239
|
+
* @param {Array<{atMessageCount:number,id:string,tool?:string}>} marks
|
|
240
|
+
* @param {number} turnIndex message index returned by rewindToTurn()
|
|
241
|
+
* @param {string} [scope] one of RESTORE_SCOPE (default BOTH)
|
|
242
|
+
* @returns {{scope, coverage, conversation, files, warnings:string[]}}
|
|
243
|
+
*/
|
|
244
|
+
export function buildRewindPlan(
|
|
245
|
+
messages,
|
|
246
|
+
marks,
|
|
247
|
+
turnIndex,
|
|
248
|
+
scope = RESTORE_SCOPE.BOTH,
|
|
249
|
+
) {
|
|
250
|
+
const turns = listUserTurns(messages, { limit: 1000 });
|
|
251
|
+
const log = buildTurnBindingFromMarks(turns, marks);
|
|
252
|
+
const turn = log.get(`turn-${Number(turnIndex)}`);
|
|
253
|
+
return resolveRestorePlan(turn, scope);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Human-readable lines for a rewind plan's coverage + warnings, for `/rewind` to
|
|
258
|
+
* print before it truncates. Returns [] for a fully-restorable turn with no
|
|
259
|
+
* warnings (nothing worth interrupting the user for).
|
|
260
|
+
*
|
|
261
|
+
* @param {{coverage:string, warnings:string[]}} plan from buildRewindPlan()
|
|
262
|
+
* @returns {string[]}
|
|
263
|
+
*/
|
|
264
|
+
export function renderRewindWarnings(plan) {
|
|
265
|
+
if (!plan) return [];
|
|
266
|
+
const lines = [];
|
|
267
|
+
if (plan.coverage && plan.coverage !== TURN_COVERAGE.FULL) {
|
|
268
|
+
lines.push(` coverage: ${plan.coverage} (restore is not guaranteed)`);
|
|
269
|
+
}
|
|
270
|
+
for (const w of plan.warnings || []) lines.push(` ⚠ ${w}`);
|
|
271
|
+
return lines;
|
|
272
|
+
}
|
|
273
|
+
|
|
222
274
|
/** Render the picker list (shared by /rewind and double-Esc). */
|
|
223
275
|
export function renderTurnList(turns) {
|
|
224
276
|
if (!turns.length) return " (no user turns yet)";
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-agent code-review pipeline core (P2 "多 Agent Code Review").
|
|
3
|
+
*
|
|
4
|
+
* `cc review` today is a single read-only agent pass. The gap upgrades it to:
|
|
5
|
+
* 1. multiple finders (correctness / security / performance / tests),
|
|
6
|
+
* 2. a verifier that independently reproduces each finding,
|
|
7
|
+
* 3. a deduper that merges duplicates and confidence-filters,
|
|
8
|
+
* 4. structured output (path / line / category / severity / failure_scenario /
|
|
9
|
+
* evidence).
|
|
10
|
+
*
|
|
11
|
+
* This module is the PURE aggregation core — normalize, dedup+merge, apply
|
|
12
|
+
* verifier verdicts, confidence-filter, rank, and render the structured report.
|
|
13
|
+
* The fan-out of finder/verifier AGENTS is orchestration the command layer adds;
|
|
14
|
+
* everything here is deterministic and testable without an LLM.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The finder dimensions a full review fans out across. */
|
|
18
|
+
export const REVIEW_DIMENSIONS = Object.freeze([
|
|
19
|
+
"correctness",
|
|
20
|
+
"security",
|
|
21
|
+
"performance",
|
|
22
|
+
"tests",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const SEVERITY_RANK = Object.freeze({
|
|
26
|
+
critical: 5,
|
|
27
|
+
high: 4,
|
|
28
|
+
medium: 3,
|
|
29
|
+
low: 2,
|
|
30
|
+
note: 1,
|
|
31
|
+
});
|
|
32
|
+
const SEVERITY_LABEL = Object.freeze({
|
|
33
|
+
5: "Critical",
|
|
34
|
+
4: "High",
|
|
35
|
+
3: "Medium",
|
|
36
|
+
2: "Low",
|
|
37
|
+
1: "Note",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** Canonical severity label (unknown → "Note"). */
|
|
41
|
+
export function normalizeSeverity(sev) {
|
|
42
|
+
const r = SEVERITY_RANK[String(sev || "").toLowerCase()] || 1;
|
|
43
|
+
return SEVERITY_LABEL[r];
|
|
44
|
+
}
|
|
45
|
+
export function severityRank(sev) {
|
|
46
|
+
return SEVERITY_RANK[String(sev || "").toLowerCase()] || 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Clamp a confidence to [0,1]; non-numeric → `dflt`. */
|
|
50
|
+
export function clampConfidence(v, dflt = 0.5) {
|
|
51
|
+
const n = Number(v);
|
|
52
|
+
if (!Number.isFinite(n)) return dflt;
|
|
53
|
+
return Math.max(0, Math.min(1, n));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function confOf(f) {
|
|
57
|
+
const n = Number(f?.confidence);
|
|
58
|
+
return Number.isFinite(n) ? n : 0.5;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normTitle(title) {
|
|
62
|
+
return String(title || "")
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/\s+/g, " ")
|
|
65
|
+
.trim()
|
|
66
|
+
.slice(0, 80);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Dedup identity: same file location + same issue (title). Category-agnostic so
|
|
70
|
+
* two finders reporting one bug under different categories merge. */
|
|
71
|
+
export function findingKey(f) {
|
|
72
|
+
return `${f?.path || ""}:${f?.line ?? ""}:${normTitle(f?.title)}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function unionField(group, keys) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const f of group) {
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
const v = f?.[key];
|
|
81
|
+
const list = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
82
|
+
for (const item of list) {
|
|
83
|
+
const s = String(item);
|
|
84
|
+
if (s && !seen.has(s)) {
|
|
85
|
+
seen.add(s);
|
|
86
|
+
out.push(s);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function maxSeverityLabel(group) {
|
|
95
|
+
let best = 0;
|
|
96
|
+
for (const f of group) best = Math.max(best, severityRank(f.severity));
|
|
97
|
+
return SEVERITY_LABEL[best] || "Note";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Merge findings that describe the SAME issue at the SAME location. The
|
|
102
|
+
* representative keeps the highest severity + highest confidence; categories /
|
|
103
|
+
* dimensions / evidence are unioned WHEN PRESENT (so a plain
|
|
104
|
+
* `{path,line,severity,title}` finding round-trips unchanged — the wiring into
|
|
105
|
+
* `parseFindings` relies on this). Shape-preserving for inputs without those
|
|
106
|
+
* fields.
|
|
107
|
+
*/
|
|
108
|
+
export function dedupeFindings(findings) {
|
|
109
|
+
const groups = new Map();
|
|
110
|
+
const order = [];
|
|
111
|
+
for (const f of findings || []) {
|
|
112
|
+
if (!f) continue;
|
|
113
|
+
const key = findingKey(f);
|
|
114
|
+
if (!groups.has(key)) {
|
|
115
|
+
groups.set(key, []);
|
|
116
|
+
order.push(key);
|
|
117
|
+
}
|
|
118
|
+
groups.get(key).push(f);
|
|
119
|
+
}
|
|
120
|
+
return order.map((key) => {
|
|
121
|
+
const group = groups.get(key);
|
|
122
|
+
const winner = [...group].sort(
|
|
123
|
+
(a, b) =>
|
|
124
|
+
severityRank(b.severity) - severityRank(a.severity) ||
|
|
125
|
+
confOf(b) - confOf(a),
|
|
126
|
+
)[0];
|
|
127
|
+
const merged = { ...winner, severity: maxSeverityLabel(group) };
|
|
128
|
+
if (Number.isFinite(Number(winner.confidence))) {
|
|
129
|
+
merged.confidence = Math.max(...group.map(confOf));
|
|
130
|
+
}
|
|
131
|
+
const cats = unionField(group, ["category", "categories"]);
|
|
132
|
+
if (cats.length) merged.categories = cats;
|
|
133
|
+
const dims = unionField(group, ["dimension", "dimensions"]);
|
|
134
|
+
if (dims.length) merged.dimensions = dims;
|
|
135
|
+
if (group.length > 1) merged.mergedCount = group.length;
|
|
136
|
+
return merged;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Apply verifier verdicts. `verdicts` maps a findingKey → { verified, confidence?,
|
|
142
|
+
* note? }. A finding the verifier could NOT reproduce (`verified === false`) is
|
|
143
|
+
* DROPPED; a reproduced one is kept, its confidence updated and a `verified`
|
|
144
|
+
* flag set. Findings with no verdict pass through as unverified.
|
|
145
|
+
*/
|
|
146
|
+
export function applyVerdicts(findings, verdicts = {}) {
|
|
147
|
+
const map =
|
|
148
|
+
verdicts instanceof Map
|
|
149
|
+
? verdicts
|
|
150
|
+
: new Map(Object.entries(verdicts || {}));
|
|
151
|
+
const out = [];
|
|
152
|
+
for (const f of findings || []) {
|
|
153
|
+
if (!f) continue;
|
|
154
|
+
const v = map.get(findingKey(f));
|
|
155
|
+
if (!v) {
|
|
156
|
+
out.push(f);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (v.verified === false) continue; // refuted → drop
|
|
160
|
+
out.push({
|
|
161
|
+
...f,
|
|
162
|
+
verified: true,
|
|
163
|
+
confidence:
|
|
164
|
+
v.confidence != null ? clampConfidence(v.confidence) : confOf(f),
|
|
165
|
+
...(v.note ? { verifierNote: String(v.note) } : {}),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Keep findings whose confidence is at least `minConfidence`. */
|
|
172
|
+
export function filterByConfidence(findings, { minConfidence = 0 } = {}) {
|
|
173
|
+
return (findings || []).filter((f) => f && confOf(f) >= minConfidence);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Sort by severity (desc) then confidence (desc) then path/line for stability. */
|
|
177
|
+
export function rankFindings(findings) {
|
|
178
|
+
return [...(findings || [])].sort(
|
|
179
|
+
(a, b) =>
|
|
180
|
+
severityRank(b.severity) - severityRank(a.severity) ||
|
|
181
|
+
confOf(b) - confOf(a) ||
|
|
182
|
+
String(a.path || "").localeCompare(String(b.path || "")) ||
|
|
183
|
+
(Number(a.line) || 0) - (Number(b.line) || 0),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function toOutputFinding(f) {
|
|
188
|
+
return {
|
|
189
|
+
path: String(f.path || ""),
|
|
190
|
+
line: Number.isFinite(Number(f.line)) ? Math.floor(Number(f.line)) : null,
|
|
191
|
+
category: f.category || (f.categories && f.categories[0]) || "correctness",
|
|
192
|
+
severity: normalizeSeverity(f.severity),
|
|
193
|
+
failure_scenario: String(f.failureScenario || f.failure_scenario || ""),
|
|
194
|
+
evidence: String(f.evidence || ""),
|
|
195
|
+
confidence: clampConfidence(f.confidence),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The full pipeline: dedup → apply verdicts → confidence-filter → rank → render
|
|
201
|
+
* the structured report (the gap's path/line/category/severity/failure_scenario/
|
|
202
|
+
* evidence output) plus severity/category rollups.
|
|
203
|
+
*
|
|
204
|
+
* @param {Array} rawFindings findings from all finders
|
|
205
|
+
* @param {object} [opts] { verdicts, minConfidence }
|
|
206
|
+
*/
|
|
207
|
+
export function buildReviewReport(
|
|
208
|
+
rawFindings,
|
|
209
|
+
{ verdicts = {}, minConfidence = 0 } = {},
|
|
210
|
+
) {
|
|
211
|
+
const deduped = dedupeFindings(rawFindings);
|
|
212
|
+
const verified = applyVerdicts(deduped, verdicts);
|
|
213
|
+
const filtered = filterByConfidence(verified, { minConfidence });
|
|
214
|
+
const ranked = rankFindings(filtered);
|
|
215
|
+
const findings = ranked.map(toOutputFinding);
|
|
216
|
+
|
|
217
|
+
const bySeverity = { Critical: 0, High: 0, Medium: 0, Low: 0, Note: 0 };
|
|
218
|
+
const byCategory = {};
|
|
219
|
+
for (const f of findings) {
|
|
220
|
+
bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
|
|
221
|
+
byCategory[f.category] = (byCategory[f.category] || 0) + 1;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
findings,
|
|
225
|
+
summary: { total: findings.length, bySeverity, byCategory },
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Checkup evaluators for `cc doctor` (P2 "Doctor").
|
|
3
|
+
*
|
|
4
|
+
* `cc doctor` today checks install/config/service health. The gap upgrades it to
|
|
5
|
+
* a RUNTIME checkup over live state: stale sessions/worktrees/agenda, orphaned
|
|
6
|
+
* background processes, slow or circuit-broken hooks, dead plugins/LSP servers,
|
|
7
|
+
* and verbose / code-derivable instruction files.
|
|
8
|
+
*
|
|
9
|
+
* This is the PURE evaluation core. The command layer collects a runtime
|
|
10
|
+
* SNAPSHOT (from session store, worktree list, agenda store, process table,
|
|
11
|
+
* hook/plugin stats) and a `now`, and this returns categorized findings. No I/O,
|
|
12
|
+
* no clock, no process access here → deterministic + unit-testable.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
export const CHECKUP_SEVERITY = Object.freeze({
|
|
18
|
+
info: 1,
|
|
19
|
+
warn: 2,
|
|
20
|
+
error: 3,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function sev(level) {
|
|
24
|
+
return CHECKUP_SEVERITY[level] || CHECKUP_SEVERITY.info;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Default thresholds; every one is overridable per-call. */
|
|
28
|
+
export const DEFAULT_CHECKUP_THRESHOLDS = Object.freeze({
|
|
29
|
+
staleSessionMs: 30 * DAY,
|
|
30
|
+
staleWorktreeMs: 7 * DAY,
|
|
31
|
+
agendaMissedGraceMs: 60 * 60 * 1000, // due but never fired for >1h → missed
|
|
32
|
+
agendaFiredRetireMs: 30 * DAY, // long-fired entry left lying around
|
|
33
|
+
slowHookMs: 5000,
|
|
34
|
+
hookFailureThreshold: 3,
|
|
35
|
+
instructionsMaxBytes: 12 * 1024,
|
|
36
|
+
instructionsDerivableRatio: 0.6, // ≥60% derivable from code → prune candidate
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function finding(id, category, level, message, remediation, extra = {}) {
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
category,
|
|
43
|
+
severity: level,
|
|
44
|
+
severityRank: sev(level),
|
|
45
|
+
message,
|
|
46
|
+
remediation,
|
|
47
|
+
...extra,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Sessions untouched past the stale threshold. */
|
|
52
|
+
export function checkStaleSessions(sessions = [], now, t) {
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const s of sessions) {
|
|
55
|
+
const last = Number(s?.lastActiveAt);
|
|
56
|
+
if (!Number.isFinite(last)) continue;
|
|
57
|
+
const age = now - last;
|
|
58
|
+
if (age > t.staleSessionMs) {
|
|
59
|
+
out.push(
|
|
60
|
+
finding(
|
|
61
|
+
"stale-session",
|
|
62
|
+
"sessions",
|
|
63
|
+
"warn",
|
|
64
|
+
`Session ${s.id} idle ${Math.floor(age / DAY)}d`,
|
|
65
|
+
"cc session prune",
|
|
66
|
+
{ ref: s.id, ageMs: age },
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Worktrees that are merged, or simply old and untouched. */
|
|
75
|
+
export function checkStaleWorktrees(worktrees = [], now, t) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const w of worktrees) {
|
|
78
|
+
const created = Number(w?.createdAt);
|
|
79
|
+
if (!Number.isFinite(created)) continue;
|
|
80
|
+
const age = now - created;
|
|
81
|
+
if (w?.merged) {
|
|
82
|
+
out.push(
|
|
83
|
+
finding(
|
|
84
|
+
"merged-worktree",
|
|
85
|
+
"worktrees",
|
|
86
|
+
"warn",
|
|
87
|
+
`Worktree ${w.path} is merged and can be removed`,
|
|
88
|
+
"cc team worktree rm / git worktree remove",
|
|
89
|
+
{ ref: w.path, merged: true },
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
} else if (age > t.staleWorktreeMs) {
|
|
93
|
+
out.push(
|
|
94
|
+
finding(
|
|
95
|
+
"stale-worktree",
|
|
96
|
+
"worktrees",
|
|
97
|
+
"info",
|
|
98
|
+
`Worktree ${w.path} unused ${Math.floor(age / DAY)}d`,
|
|
99
|
+
"review and remove if abandoned",
|
|
100
|
+
{ ref: w.path, ageMs: age },
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Agenda entries that missed their fire window, or long-fired leftovers. */
|
|
109
|
+
export function checkAgenda(agenda = [], now, t) {
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const a of agenda) {
|
|
112
|
+
const dueAt = Number(a?.dueAt);
|
|
113
|
+
const firedAt = Number(a?.firedAt);
|
|
114
|
+
if (Number.isFinite(dueAt) && !Number.isFinite(firedAt)) {
|
|
115
|
+
if (now - dueAt > t.agendaMissedGraceMs) {
|
|
116
|
+
out.push(
|
|
117
|
+
finding(
|
|
118
|
+
"agenda-missed",
|
|
119
|
+
"agenda",
|
|
120
|
+
"error",
|
|
121
|
+
`Agenda ${a.id} was due but never fired`,
|
|
122
|
+
"cc agenda list / re-arm or remove",
|
|
123
|
+
{ ref: a.id, dueAt },
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} else if (
|
|
128
|
+
Number.isFinite(firedAt) &&
|
|
129
|
+
now - firedAt > t.agendaFiredRetireMs &&
|
|
130
|
+
!a?.recurring
|
|
131
|
+
) {
|
|
132
|
+
out.push(
|
|
133
|
+
finding(
|
|
134
|
+
"agenda-retire",
|
|
135
|
+
"agenda",
|
|
136
|
+
"info",
|
|
137
|
+
`Agenda ${a.id} fired ${Math.floor((now - firedAt) / DAY)}d ago and can be retired`,
|
|
138
|
+
"cc agenda prune",
|
|
139
|
+
{ ref: a.id, firedAt },
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Background processes whose recorded parent is gone (orphans), or that outlived
|
|
149
|
+
* the session that spawned them.
|
|
150
|
+
*/
|
|
151
|
+
export function checkOrphanProcesses(processes = [], activeSessionIds = []) {
|
|
152
|
+
const active = new Set(activeSessionIds.map(String));
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const p of processes) {
|
|
155
|
+
if (!p?.alive) continue;
|
|
156
|
+
const parentDead = p.parentAlive === false;
|
|
157
|
+
const sessionGone = p.sessionId != null && !active.has(String(p.sessionId));
|
|
158
|
+
if (parentDead || sessionGone) {
|
|
159
|
+
out.push(
|
|
160
|
+
finding(
|
|
161
|
+
"orphan-process",
|
|
162
|
+
"processes",
|
|
163
|
+
"error",
|
|
164
|
+
`Background process pid=${p.pid}${p.kind ? ` (${p.kind})` : ""} orphaned`,
|
|
165
|
+
"terminate stray process; check background-shell cleanup",
|
|
166
|
+
{ ref: p.pid, reason: parentDead ? "parent-dead" : "session-gone" },
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Hooks that run slow or have tripped their circuit breaker. */
|
|
175
|
+
export function checkHooks(hooks = [], t) {
|
|
176
|
+
const out = [];
|
|
177
|
+
for (const h of hooks) {
|
|
178
|
+
if (h?.circuitOpen) {
|
|
179
|
+
out.push(
|
|
180
|
+
finding(
|
|
181
|
+
"hook-circuit-open",
|
|
182
|
+
"hooks",
|
|
183
|
+
"error",
|
|
184
|
+
`Hook ${h.id} circuit is open after ${h.failures ?? "?"} failures`,
|
|
185
|
+
"fix the hook or disable it in settings",
|
|
186
|
+
{ ref: h.id, failures: h.failures },
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (Number(h?.failures) >= t.hookFailureThreshold) {
|
|
192
|
+
out.push(
|
|
193
|
+
finding(
|
|
194
|
+
"hook-failing",
|
|
195
|
+
"hooks",
|
|
196
|
+
"warn",
|
|
197
|
+
`Hook ${h.id} failed ${h.failures} times`,
|
|
198
|
+
"inspect hook logs",
|
|
199
|
+
{ ref: h.id, failures: h.failures },
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (Number(h?.avgMs) > t.slowHookMs) {
|
|
204
|
+
out.push(
|
|
205
|
+
finding(
|
|
206
|
+
"hook-slow",
|
|
207
|
+
"hooks",
|
|
208
|
+
"warn",
|
|
209
|
+
`Hook ${h.id} averages ${Math.round(h.avgMs)}ms`,
|
|
210
|
+
"make the hook async or faster",
|
|
211
|
+
{ ref: h.id, avgMs: h.avgMs },
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Plugins / LSP servers reported unhealthy or dead. */
|
|
220
|
+
export function checkPluginsAndLsp(plugins = []) {
|
|
221
|
+
const out = [];
|
|
222
|
+
for (const p of plugins) {
|
|
223
|
+
if (p?.healthy === false) {
|
|
224
|
+
const isLsp = p.kind === "lsp" || p.lsp === true;
|
|
225
|
+
out.push(
|
|
226
|
+
finding(
|
|
227
|
+
isLsp ? "lsp-dead" : "plugin-dead",
|
|
228
|
+
isLsp ? "lsp" : "plugins",
|
|
229
|
+
"warn",
|
|
230
|
+
`${isLsp ? "LSP server" : "Plugin"} ${p.id} is unavailable`,
|
|
231
|
+
isLsp ? "cc code-intel / restart server" : "cc plugin validate",
|
|
232
|
+
{ ref: p.id },
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Instruction files (cc.md / AGENTS.md) that are oversized or code-derivable. */
|
|
241
|
+
export function checkInstructionFiles(files = [], t) {
|
|
242
|
+
const out = [];
|
|
243
|
+
for (const f of files) {
|
|
244
|
+
const bytes = Number(f?.bytes);
|
|
245
|
+
if (Number.isFinite(bytes) && bytes > t.instructionsMaxBytes) {
|
|
246
|
+
out.push(
|
|
247
|
+
finding(
|
|
248
|
+
"instructions-verbose",
|
|
249
|
+
"instructions",
|
|
250
|
+
"info",
|
|
251
|
+
`${f.path} is ${(bytes / 1024).toFixed(1)}KB — consider trimming`,
|
|
252
|
+
"move detail into referenced docs",
|
|
253
|
+
{ ref: f.path, bytes },
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const ratio = Number(f?.derivableRatio);
|
|
258
|
+
if (Number.isFinite(ratio) && ratio >= t.instructionsDerivableRatio) {
|
|
259
|
+
out.push(
|
|
260
|
+
finding(
|
|
261
|
+
"instructions-derivable",
|
|
262
|
+
"instructions",
|
|
263
|
+
"info",
|
|
264
|
+
`${f.path} is ~${Math.round(ratio * 100)}% derivable from code`,
|
|
265
|
+
"delete code-derivable prose; keep only non-obvious guidance",
|
|
266
|
+
{ ref: f.path, derivableRatio: ratio },
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Run every checkup against a runtime snapshot. Returns findings sorted most
|
|
276
|
+
* severe first, plus a rollup. Any missing snapshot section is simply skipped —
|
|
277
|
+
* partial snapshots produce partial (never crashing) results.
|
|
278
|
+
*
|
|
279
|
+
* @param {object} snapshot { sessions, worktrees, agenda, processes,
|
|
280
|
+
* activeSessionIds, hooks, plugins, instructionFiles, now }
|
|
281
|
+
* @param {object} [opts] { thresholds }
|
|
282
|
+
*/
|
|
283
|
+
export function runRuntimeCheckup(snapshot = {}, { thresholds = {} } = {}) {
|
|
284
|
+
const t = { ...DEFAULT_CHECKUP_THRESHOLDS, ...thresholds };
|
|
285
|
+
const now = Number.isFinite(Number(snapshot.now))
|
|
286
|
+
? Number(snapshot.now)
|
|
287
|
+
: null;
|
|
288
|
+
const findings = [];
|
|
289
|
+
if (now != null) {
|
|
290
|
+
findings.push(...checkStaleSessions(snapshot.sessions, now, t));
|
|
291
|
+
findings.push(...checkStaleWorktrees(snapshot.worktrees, now, t));
|
|
292
|
+
findings.push(...checkAgenda(snapshot.agenda, now, t));
|
|
293
|
+
}
|
|
294
|
+
findings.push(
|
|
295
|
+
...checkOrphanProcesses(snapshot.processes, snapshot.activeSessionIds),
|
|
296
|
+
);
|
|
297
|
+
findings.push(...checkHooks(snapshot.hooks, t));
|
|
298
|
+
findings.push(...checkPluginsAndLsp(snapshot.plugins));
|
|
299
|
+
findings.push(...checkInstructionFiles(snapshot.instructionFiles, t));
|
|
300
|
+
|
|
301
|
+
findings.sort(
|
|
302
|
+
(a, b) =>
|
|
303
|
+
b.severityRank - a.severityRank ||
|
|
304
|
+
String(a.category).localeCompare(String(b.category)),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const byCategory = {};
|
|
308
|
+
const bySeverity = { error: 0, warn: 0, info: 0 };
|
|
309
|
+
for (const f of findings) {
|
|
310
|
+
byCategory[f.category] = (byCategory[f.category] || 0) + 1;
|
|
311
|
+
bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
findings,
|
|
315
|
+
summary: { total: findings.length, byCategory, bySeverity },
|
|
316
|
+
ok: bySeverity.error === 0,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
@@ -52,11 +52,27 @@ export function baseFireAt(entry) {
|
|
|
52
52
|
return Number.isFinite(n) ? n : null;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The jitter window that governs an entry: a PER-ENTRY `entry.jitterMs` (a
|
|
57
|
+
* finite, non-negative number stored at create time) is authoritative and wins
|
|
58
|
+
* over the caller's `jitterMs` fallback; an entry without its own field falls
|
|
59
|
+
* back to the passed global. This lets each scheduled task carry its own spread
|
|
60
|
+
* — one noisy cron can jitter without perturbing the rest — while still honoring
|
|
61
|
+
* a daemon-wide default for entries that predate per-entry jitter.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveEntryJitterMs(entry, jitterMs = 0) {
|
|
64
|
+
const per = Number(entry?.jitterMs);
|
|
65
|
+
if (Number.isFinite(per) && per >= 0) return per;
|
|
66
|
+
return Number(jitterMs) || 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
/** Base fire time with the entry's deterministic jitter applied. */
|
|
56
70
|
export function effectiveFireAt(entry, jitterMs = 0) {
|
|
57
71
|
const base = baseFireAt(entry);
|
|
58
72
|
if (base == null) return null;
|
|
59
|
-
return
|
|
73
|
+
return (
|
|
74
|
+
base + jitterOffsetMs(entry?.id, resolveEntryJitterMs(entry, jitterMs))
|
|
75
|
+
);
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
/** Is an entry still pending/active (not in a terminal state)? */
|