chainlesschain 0.162.65 → 0.162.67

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.
Files changed (159) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-DjJf_QIn.js → AIOps-DUdh61OE.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BT45g-KL.js → ActionButton-DkIwLnBD.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CRaTHble.js → Analytics-D-iPmzfz.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-72r5TM1u.js → AppLayout-kHttiFUZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Audit-BNlvJ3Yc.js → Audit-DstzaQFj.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-Kuj0-vBg.js → Backup-COQb9n1n.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-_pKOPRf4.js → BaseInput-BzOSEUx9.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-CMNhGWK5.js → Chat-CCnsErT6.js} +6 -6
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-DVQnqmLx.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-B5R2TdAI.js → Checkbox-CGEGTeL3.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-69RAQ0Gi.js → Codegen-DhdKOsrH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-DlbssQEY.js → Col-DTaHrzPc.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DU3SAZIS.js → Community-hmabC5TC.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-BqdNnAZv.js → Compact-DF1kuYEV.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-D9a9-ihS.js → Compliance-gGsJ0l1f.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-DWBtOBbU.js → Cowork-xzH66HkJ.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-ClSuf90k.js → Cron-D-E1fiyr.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-BFjRKvpa.js → Crosschain-CAxzKuw_.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-BwBGRlMm.js → DID-CG3AoeWL.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CHrXGmQ3.js → Dashboard-Dd5371Pn.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-C24B5sk2.js → Dropdown-CniUpxUk.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DaXTSK5p.js → EmailListRenderer-CqkhyNVa.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-65d89G5t.js → FamilyGuardDashboard-D8PGYrZS.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-CkWdqmVs.js → Federation-C6PQ0D0v.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BV4W2nrT.js → FormItemContext-Ce0Kjapm.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-D60KJ0_b.js → GenericCardRenderer-DTSx1uxu.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BIKuoGvW.js → Git-CjIKQPwh.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-CKnJpq5X.js → Governance-CQ6ensk6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-C7G3YGeg.js → Inference-ZhEZeJBX.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-D7fCUd4B.js → KnowledgeGraph-ClRD7Ny9.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-C0unjcbC.js → Logs-BRxYBrqs.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-BzLlnyI8.js → Marketplace-wC7vePL2.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-DSKFRB1-.js → McpTools-CbZ7EZCS.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-C_QrLAnt.js → Memory-DX48TSrr.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-DBeaFERD.js → MobileBridge-C8YXsY-I.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-C2L_RttC.js → MobileProjects-D42PKdIA.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-B3Tdh6-l.js → Mtc-Bq73thQS.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-B3O_EUvt.js → MtcAudit-CgxcxR8Q.js} +6 -6
  40. package/src/assets/web-panel/assets/{Multisig--60rVmDj.js → Multisig-C7KNb6_o.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-D60vxATf.js → NLProgramming-CVzm6Kax.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-D2gj2uFI.js → Notes-E962LwaY.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-D0DWHNlF.js → NotificationSettings-r5gwao_d.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-CNi1B7fH.js → OrderTableRenderer-5RHLJQ7W.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-BRcdFgAd.js → Organization-518E-wDo.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-C3_Oap7v.js → Overflow-C6IG4Y8V.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DEbZ93QW.js → P2P-BmrkH4JM.js} +2 -2
  48. package/src/assets/web-panel/assets/PdhVaultBrowser-BmTXbyXR.js +7 -0
  49. package/src/assets/web-panel/assets/{Permissions-CPj3C9o2.js → Permissions-DqtYyQ_n.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BZGupZzh.js → PersonalDataHub-CrBAGe6w.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-SMLW1BG7.js → Pipeline-Cq9nvUtI.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-o24SJ2no.js → Privacy-BHS8ULGU.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-DxjAXD8f.js → ProjectInit-B5GZXX6Z.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DipynlqL.js → ProjectSettings-Bgq9qhzU.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-CZ9egQ8r.js → Projects-DQtd5Z3I.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-x3p-wcab.js → Providers-BC1YU1b4.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-CZ7beKFC.js → QuickAsk-9v1Sc4sD.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-VJCd2i9_.js → Recommend-BJ1Mk9tH.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-pl12NmBF.js → Reputation-D_6Q_ApA.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-NkSeo4Tb.js → Row-B9q6bn8K.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-B17vp67R.js → RssFeed-OBUMDtGN.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Dij0_m6W.js → Search-CduYF1wK.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-n9CSBX-9.js → Security-Cr-I8Oz7.js} +3 -3
  64. package/src/assets/web-panel/assets/{Services-bZOzqHdK.js → Services-C1qVm4kB.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-B23D5vJ-.js → Skeleton-Ww1MhByn.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-IXh-0mk0.js → Skills-MdBisytD.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-QEofxmdK.js → Sla-B_y_VqaO.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-u68R59ft.js → SpeechSettings-BFTJeQjg.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-B3tc986U.js → SyncSettings-D6OUnG6a.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-Cp2QxGrr.js → Tasks-CIFAHI0I.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-CMWiWxiH.js → Templates-Chm6LnIA.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-M8aPJ3C7.js → Tenant-C8uT7TxF.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-CgMzHdbj.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-DF6aIS-d.js → TimelineRenderer-DCv-s6ME.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-BcfMMw_e.js → Tokens-Dbp_wE8i.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-jIbNmxvm.js → Trigger-Bcm94QVz.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-ChLil6CZ.js → Trust-DWUByLU4.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-B1WzYAon.js → UkeySign-gadPtraM.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-Dwm0LyCc.js → VideoEditing-BWAd8rH5.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-DVyxsX-O.js → Wallet-O10l9J3d.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-WYPNy2Q7.js → WebAuthn-BL59D-AQ.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-Br3dCsmv.js → WorkflowEditor-B0GK1YYA.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-fAKHY2HK.js → chat-rxMjzdi6.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BXqS-Bwi.js → colors-MGsVs63X.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BgCQhtW3.js → compact-item-B99MX2Sj.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-weZBwqHy.js → createContext-D-UE372V.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-yudSIew_.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Dx68UNFL.js → hasIn-BTwO4423.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DCYJDUab.js → index--o4IljwP.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DNF3aCJF.js → index-BC5oUiWa.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-LxcdLeFj.js → index-BCo1fFG4.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-DgUC575c.js → index-BDDFilGb.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BqcH_mKR.js → index-BITfpivm.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-HIN85jl7.js → index-BhXE09CC.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DXuz90bX.js → index-Bq8J_BJr.js} +1 -1
  96. package/src/assets/web-panel/assets/index-BsuHZ7he.js +1 -0
  97. package/src/assets/web-panel/assets/{index-G_wPnPoA.js → index-Bw7OfNf0.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Dbv5btEU.js → index-C-104k0S.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BawcE_zG.js → index-C-wDGsps.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bwv_UrNF.js → index-C1M9EJTY.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C0ZjD3Ac.js → index-C1V4w_cy.js} +1 -1
  102. package/src/assets/web-panel/assets/index-C5yXj1wd.js +1 -0
  103. package/src/assets/web-panel/assets/{index-BjsidvP5.js → index-C7AbKc5A.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BC-4la9j.js → index-CJh-sOln.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B8-2rQdr.js → index-CNVDW7m4.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-ChdeuOni.js → index-CO-buu4K.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CEjLe8FJ.js → index-COL6Py19.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bo4UTTla.js → index-CgLTxhj8.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DG2KCc8h.js → index-CnPf5EHZ.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CJXZDwkf.js → index-Cqf3vqmJ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CxfVwfub.js → index-CtBHvTvK.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BSQEQCft.js → index-DGBEzFa6.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B9NeNwHP.js → index-DGd9fYgP.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B0rgvjX8.js → index-DOA-2IBx.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-ohVNy7ua.js → index-DSWGJvOb.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Cxsfc5Ou.js → index-DdweEw7u.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bazltj8w.js → index-DfQrgIGE.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CfqzwaAV.js → index-DkQN4c_n.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-5e-OAZOb.js → index-DlHo7h2D.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BehvfmYd.js → index-Ds58gvvD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Co5cQnlv.js → index-DvMa-5Zw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-u2U9t07r.js → index-DyaKy5hU.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-ClfP1Yax.js → index-YDbokOUC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CX9cxRnU.js → index-eMKPU-8h.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B8zNZ_oH.js → index-fwIj9Ew1.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-qUBHSW_3.js → index-mJP_zO6_.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-loaP_41H.js → index-qHtF3rlV.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-M7xH4eUK.js → initDefaultProps-DT6c9bjH.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BrJP4mFE.js → motion-Bd91G2kX.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BufxEuU9.js → move-B8frVtgu.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-s6dtQtFP.js → omit-Df9R5myu.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-BcYdIZqz.js → pickAttrs-C-XUO3g1.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-Ds-3Hw3n.js → placementArrow-BbgfnB12.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BRtrRTxl.js → responsiveObserve-CjuJL75b.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-RJzqMQM4.js → slide-_AVhS93T.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-BuBhJXvr.js → statusUtils-CexyzTkp.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-so8acGHq.js → styleChecker-BQGZefM3.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-BpkV467K.js → useFlexGapSupport-DheJEFqe.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-lES1RctZ.js → useFs-2PJHnfsw.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-DdCNi4bA.js → usePersonalDataHub-eHN-CciG.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-nAeEg_3h.js → vnode-BFGaUmOy.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BWjRAfRy.js → zoom-BtGezWHW.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/insights.js +137 -0
  145. package/src/commands/review.js +392 -48
  146. package/src/index.js +2 -0
  147. package/src/lib/ide-context.js +62 -5
  148. package/src/lib/personal-data-hub-wiring.js +36 -0
  149. package/src/lib/session-insights.js +145 -0
  150. package/src/lib/skill-loader.js +18 -3
  151. package/src/repl/agent-repl.js +1 -3
  152. package/src/skills-bundled/run/SKILL.md +49 -0
  153. package/src/skills-bundled/verify/SKILL.md +49 -0
  154. package/src/assets/web-panel/assets/ChatBubbleRenderer-DxJmwLv8.js +0 -1
  155. package/src/assets/web-panel/assets/PdhVaultBrowser-DN_pmo2N.js +0 -7
  156. package/src/assets/web-panel/assets/Terminal-CK3zKjIE.js +0 -3
  157. package/src/assets/web-panel/assets/devWarning-BpXdFCJ4.js +0 -1
  158. package/src/assets/web-panel/assets/index-BrPKR2RZ.js +0 -1
  159. package/src/assets/web-panel/assets/index-CMEfvACO.js +0 -1
@@ -63,7 +63,9 @@ function gitCli(args, { cwd } = {}) {
63
63
 
64
64
  function isGitRepo(cwd, git = gitCli) {
65
65
  try {
66
- return git(["rev-parse", "--is-inside-work-tree"], { cwd }).trim() === "true";
66
+ return (
67
+ git(["rev-parse", "--is-inside-work-tree"], { cwd }).trim() === "true"
68
+ );
67
69
  } catch {
68
70
  return false;
69
71
  }
@@ -174,26 +176,43 @@ export function buildReviewPrompt(o = {}) {
174
176
  effort = "medium",
175
177
  mode = "default",
176
178
  fix = false,
179
+ comment = false,
177
180
  label = "working tree vs HEAD",
178
181
  untrackedBlocks = "",
179
182
  truncated = false,
180
183
  } = o;
181
184
 
182
- const tail = fix
183
- ? "First identify the issues as above. Then APPLY the fixes directly with " +
184
- "your edit tools make the smallest change that resolves each issue and " +
185
- "match the surrounding code's style. Every file edit is automatically " +
186
- "checkpointed and reversible, so edit confidently. Stay within the " +
187
- "reviewed change and its immediate dependencies do not refactor " +
188
- "unrelated code. Do NOT run destructive shell commands. When done, output " +
189
- "a Markdown summary: what you changed (with `path:line`) and any issue you " +
190
- "deliberately did NOT fix, each with a one-line reason."
191
- : "Output a Markdown report. For each finding give: a severity " +
192
- "(Critical / High / Medium / Low), the `path:line`, a one-line title, " +
193
- "why it matters, and a concrete suggested fix (a short code snippet when " +
194
- "it helps). Group findings by severity, most severe first. If nothing is " +
195
- "worth raising, say so plainly. Do NOT modify any files — this is a " +
196
- "review only.";
185
+ // comment mode → machine-readable findings so each maps to a PR inline comment.
186
+ const commentTail =
187
+ "Output ONLY a JSON array of findings and nothing else no prose, no " +
188
+ "markdown fence. Each element: " +
189
+ '{"path": "<repo-relative file path exactly as in the diff>", ' +
190
+ '"line": <integer line number in the NEW version of the file, which MUST ' +
191
+ "appear in the diff>, " +
192
+ '"severity": "Critical"|"High"|"Medium"|"Low", ' +
193
+ '"title": "<one-line summary>", ' +
194
+ '"body": "<why it matters + a concrete suggested fix>"}. ' +
195
+ "Only report lines that are present in the diff (added/context lines on the " +
196
+ "new side). Do NOT modify any files. If there is nothing worth raising, " +
197
+ "output exactly [].";
198
+
199
+ const tail = comment
200
+ ? commentTail
201
+ : fix
202
+ ? "First identify the issues as above. Then APPLY the fixes directly with " +
203
+ "your edit tools — make the smallest change that resolves each issue and " +
204
+ "match the surrounding code's style. Every file edit is automatically " +
205
+ "checkpointed and reversible, so edit confidently. Stay within the " +
206
+ "reviewed change and its immediate dependencies — do not refactor " +
207
+ "unrelated code. Do NOT run destructive shell commands. When done, output " +
208
+ "a Markdown summary: what you changed (with `path:line`) and any issue you " +
209
+ "deliberately did NOT fix, each with a one-line reason."
210
+ : "Output a Markdown report. For each finding give: a severity " +
211
+ "(Critical / High / Medium / Low), the `path:line`, a one-line title, " +
212
+ "why it matters, and a concrete suggested fix (a short code snippet when " +
213
+ "it helps). Group findings by severity, most severe first. If nothing is " +
214
+ "worth raising, say so plainly. Do NOT modify any files — this is a " +
215
+ "review only.";
197
216
 
198
217
  return [
199
218
  `You are an expert code reviewer. ${LENS[mode]}`,
@@ -208,7 +227,8 @@ export function buildReviewPrompt(o = {}) {
208
227
  "Unified diff:",
209
228
  "```diff",
210
229
  truncated
211
- ? diff + "\n\n[... diff truncated — review what is shown; note the cutoff]"
230
+ ? diff +
231
+ "\n\n[... diff truncated — review what is shown; note the cutoff]"
212
232
  : diff,
213
233
  "```",
214
234
  untrackedBlocks ? "\n" + untrackedBlocks : "",
@@ -271,6 +291,273 @@ function collectUntracked(cwd, git) {
271
291
  };
272
292
  }
273
293
 
294
+ /**
295
+ * Collect the diff (+ untracked new files) for a review scope. Shared by
296
+ * runReview and runReviewComment. Returns the (possibly truncated) diff.
297
+ */
298
+ function collectReviewDiff(scopeOpts, { cwd, git, includeUntracked }) {
299
+ const { args: diffArgs, scope, label } = resolveDiffArgs(scopeOpts, false);
300
+ const { args: statArgs } = resolveDiffArgs(scopeOpts, true);
301
+ let diff = "";
302
+ let summary = "";
303
+ try {
304
+ diff = git(diffArgs, { cwd });
305
+ summary = git(statArgs, { cwd });
306
+ } catch (err) {
307
+ throw new Error(`git diff failed: ${err.message}`);
308
+ }
309
+ // Untracked new files only matter for the default working scope; staged /
310
+ // base / range are already fully described by git.
311
+ let untracked = { blocks: "", files: [], skipped: [] };
312
+ if (scope === "working" && includeUntracked) {
313
+ untracked = collectUntracked(cwd, git);
314
+ }
315
+ const hasDiff = Boolean(diff.trim());
316
+ const hasUntracked = Boolean(untracked.blocks);
317
+ const truncated = diff.length > MAX_DIFF_CHARS;
318
+ if (truncated) diff = diff.slice(0, MAX_DIFF_CHARS);
319
+ return {
320
+ diff,
321
+ summary,
322
+ scope,
323
+ label,
324
+ untracked,
325
+ hasDiff,
326
+ hasUntracked,
327
+ truncated,
328
+ };
329
+ }
330
+
331
+ /** Run `gh` with an argv array (no shell). UTF-8 in/out; throws on failure. */
332
+ function ghCli(args, { cwd, input } = {}) {
333
+ const res = spawnSync("gh", args, {
334
+ cwd,
335
+ input,
336
+ encoding: "utf-8",
337
+ windowsHide: true,
338
+ maxBuffer: 64 * 1024 * 1024,
339
+ });
340
+ if (res.error) throw res.error;
341
+ if (res.status !== 0) {
342
+ const msg = (res.stderr || res.stdout || "").toString().trim();
343
+ throw new Error(msg || `gh ${args.join(" ")} failed (exit ${res.status})`);
344
+ }
345
+ return (res.stdout || "").toString();
346
+ }
347
+
348
+ /** Resolve the PR for the current branch via gh (read-only). Throws if none. */
349
+ function resolvePr(cwd, gh) {
350
+ let out;
351
+ try {
352
+ out = gh(
353
+ ["pr", "view", "--json", "number,baseRefName,headRefName,headRefOid,url"],
354
+ { cwd },
355
+ );
356
+ } catch (err) {
357
+ throw new Error(
358
+ `no open PR for the current branch (gh: ${err.message}). ` +
359
+ "Push the branch and open a PR first, or use `cc review` without --comment.",
360
+ );
361
+ }
362
+ let pr;
363
+ try {
364
+ pr = JSON.parse(out);
365
+ } catch {
366
+ throw new Error("could not parse `gh pr view` output.");
367
+ }
368
+ let repo = null;
369
+ try {
370
+ repo = JSON.parse(gh(["repo", "view", "--json", "nameWithOwner"], { cwd }))
371
+ .nameWithOwner;
372
+ } catch {
373
+ repo = null;
374
+ }
375
+ return { ...pr, repo };
376
+ }
377
+
378
+ /**
379
+ * Parse the agent's findings JSON (lenient). Strips a code fence, extracts the
380
+ * first JSON array, and keeps only well-formed findings. Pure.
381
+ *
382
+ * @returns {{path:string,line:number,severity:string,title:string,body:string}[]}
383
+ */
384
+ export function parseFindings(text) {
385
+ if (!text) return [];
386
+ let s = String(text).trim();
387
+ const fence = s.match(/```(?:json)?\s*([\s\S]*?)```/i);
388
+ if (fence) s = fence[1].trim();
389
+ const start = s.indexOf("[");
390
+ const end = s.lastIndexOf("]");
391
+ if (start === -1 || end === -1 || end < start) return [];
392
+ let arr;
393
+ try {
394
+ arr = JSON.parse(s.slice(start, end + 1));
395
+ } catch {
396
+ return [];
397
+ }
398
+ if (!Array.isArray(arr)) return [];
399
+ return arr
400
+ .filter(
401
+ (f) =>
402
+ f &&
403
+ typeof f === "object" &&
404
+ f.path &&
405
+ Number.isFinite(Number(f.line)) &&
406
+ Number(f.line) > 0,
407
+ )
408
+ .map((f) => ({
409
+ path: String(f.path),
410
+ line: Math.floor(Number(f.line)),
411
+ severity: f.severity ? String(f.severity) : "Note",
412
+ title: f.title ? String(f.title) : "",
413
+ body: f.body ? String(f.body) : f.title ? String(f.title) : "finding",
414
+ }));
415
+ }
416
+
417
+ /** Format one finding into a PR comment body. Pure. */
418
+ export function buildCommentBody(f) {
419
+ const sev = f.severity ? `**[${f.severity}]** ` : "";
420
+ const title = f.title ? `${f.title}\n\n` : "";
421
+ return `${sev}${title}${f.body}`.trim();
422
+ }
423
+
424
+ /** Build the GitHub "create review" API payload from findings. Pure. */
425
+ export function buildReviewPayload(findings, { commitId } = {}) {
426
+ const comments = (findings || []).map((f) => ({
427
+ path: f.path,
428
+ line: f.line,
429
+ side: "RIGHT",
430
+ body: buildCommentBody(f),
431
+ }));
432
+ const payload = {
433
+ event: "COMMENT",
434
+ body: `cc review — ${comments.length} finding(s).`,
435
+ comments,
436
+ };
437
+ if (commitId) payload.commit_id = commitId;
438
+ return payload;
439
+ }
440
+
441
+ /**
442
+ * Post the findings to the PR as a single review with inline comments
443
+ * (outward-facing — callers gate this behind --dry-run / confirmation).
444
+ */
445
+ export function postReviewComments(pr, findings, { gh = ghCli, cwd, commitId } = {}) {
446
+ if (!pr || !pr.repo || !pr.number) {
447
+ throw new Error("cannot post: PR repo/number not resolved.");
448
+ }
449
+ const payload = buildReviewPayload(findings, { commitId });
450
+ const out = gh(
451
+ [
452
+ "api",
453
+ "--method",
454
+ "POST",
455
+ `repos/${pr.repo}/pulls/${pr.number}/reviews`,
456
+ "--input",
457
+ "-",
458
+ ],
459
+ { cwd, input: JSON.stringify(payload) },
460
+ );
461
+ try {
462
+ return JSON.parse(out);
463
+ } catch {
464
+ return { raw: out };
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Comment-mode review: resolve the PR, collect its diff, get machine-readable
470
+ * findings from one read-only agent turn. Side-effect-free (PR resolution is a
471
+ * read); the caller posts after confirmation. Deps injected for tests.
472
+ *
473
+ * @returns {Promise<{empty:boolean, pr:object, findings:object[], scope?:string,
474
+ * label?:string, isError?:boolean}>}
475
+ */
476
+ export async function runReviewComment(options = {}, deps = {}) {
477
+ const cwd = options.cwd || process.cwd();
478
+ const git = deps.git || gitCli;
479
+ const gh = deps.gh || ghCli;
480
+ const repoCheck = deps.isGitRepo || isGitRepo;
481
+ if (!repoCheck(cwd, git)) {
482
+ throw new Error("cc review needs a git work tree.");
483
+ }
484
+
485
+ const pr = (deps.resolvePr || resolvePr)(cwd, gh);
486
+
487
+ // Default the scope to the PR's base branch (review the PR's changes) unless
488
+ // the user gave an explicit scope.
489
+ const explicitScope =
490
+ options.staged || options.base || options.range || (options.paths || []).length;
491
+ const scopeOpts = {
492
+ staged: options.staged === true,
493
+ base: options.base || (explicitScope ? null : pr.baseRefName),
494
+ range: options.range || null,
495
+ paths: options.paths || [],
496
+ };
497
+
498
+ const collected = collectReviewDiff(scopeOpts, {
499
+ cwd,
500
+ git,
501
+ includeUntracked: false,
502
+ });
503
+ if (!collected.hasDiff && !collected.hasUntracked) {
504
+ return { empty: true, pr, findings: [] };
505
+ }
506
+
507
+ // LLM config defaults (parity with runReview).
508
+ try {
509
+ const loadConfig =
510
+ deps.loadConfig || (await import("../lib/config-manager.js")).loadConfig;
511
+ const { applyConfigLlmDefaults } = deps.applyConfigLlmDefaults
512
+ ? { applyConfigLlmDefaults: deps.applyConfigLlmDefaults }
513
+ : await import("../lib/llm-config-defaults.js");
514
+ applyConfigLlmDefaults(options, loadConfig().llm || {}, {
515
+ explicitModel: options.model,
516
+ });
517
+ } catch {
518
+ /* fall back to runner defaults */
519
+ }
520
+
521
+ const prompt = buildReviewPrompt({
522
+ diff: collected.hasDiff ? collected.diff : "(no tracked changes)",
523
+ summary: collected.summary,
524
+ effort: normalizeEffort(options.effort),
525
+ mode: resolveReviewMode(options),
526
+ comment: true,
527
+ label: collected.label,
528
+ truncated: collected.truncated,
529
+ });
530
+
531
+ const run =
532
+ deps.runAgentHeadless ||
533
+ (await import("../runtime/headless-runner.js")).runAgentHeadless;
534
+ // Suppress the runner's own stdout/stderr — we only want the structured result.
535
+ const outcome = await run(
536
+ {
537
+ prompt,
538
+ model: options.model,
539
+ provider: options.provider,
540
+ baseUrl: options.baseUrl,
541
+ apiKey: options.apiKey,
542
+ outputFormat: "text",
543
+ permissionMode: "plan",
544
+ maxTurns: Number.isFinite(options.maxTurns) ? options.maxTurns : 20,
545
+ cwd,
546
+ expandFileRefs: false,
547
+ },
548
+ { writeOut: () => {}, writeErr: () => {} },
549
+ );
550
+
551
+ return {
552
+ empty: false,
553
+ pr,
554
+ scope: collected.scope,
555
+ label: collected.label,
556
+ findings: parseFindings(outcome.result || ""),
557
+ isError: outcome.isError === true,
558
+ };
559
+ }
560
+
274
561
  /**
275
562
  * Core review run — collects the diff and dispatches one headless agent turn.
276
563
  * Deps are injected for tests (git / runAgentHeadless / config helpers).
@@ -299,34 +586,16 @@ export async function runReview(options = {}, deps = {}) {
299
586
  paths: options.paths || [],
300
587
  };
301
588
 
302
- const { args: diffArgs, scope, label } = resolveDiffArgs(scopeOpts, false);
303
- const { args: statArgs } = resolveDiffArgs(scopeOpts, true);
304
-
305
- let diff = "";
306
- let summary = "";
307
- try {
308
- diff = git(diffArgs, { cwd });
309
- summary = git(statArgs, { cwd });
310
- } catch (err) {
311
- throw new Error(`git diff failed: ${err.message}`);
312
- }
313
-
314
- // Untracked new files only matter for the default working scope; staged /
315
- // base / range are already fully described by git.
316
- let untracked = { blocks: "", files: [], skipped: [] };
317
- if (scope === "working" && options.untracked !== false) {
318
- untracked = collectUntracked(cwd, git);
319
- }
320
-
321
- const hasDiff = Boolean(diff.trim());
322
- const hasUntracked = Boolean(untracked.blocks);
589
+ const { diff, summary, scope, label, untracked, hasDiff, hasUntracked, truncated } =
590
+ collectReviewDiff(scopeOpts, {
591
+ cwd,
592
+ git,
593
+ includeUntracked: options.untracked !== false,
594
+ });
323
595
  if (!hasDiff && !hasUntracked) {
324
596
  return { exitCode: 0, isError: false, scope, empty: true };
325
597
  }
326
598
 
327
- const truncated = diff.length > MAX_DIFF_CHARS;
328
- if (truncated) diff = diff.slice(0, MAX_DIFF_CHARS);
329
-
330
599
  const prompt = buildReviewPrompt({
331
600
  diff: hasDiff ? diff : "(no tracked changes)",
332
601
  summary,
@@ -341,11 +610,11 @@ export async function runReview(options = {}, deps = {}) {
341
610
  // LLM defaults: honor .chainlesschain/config.json `llm` like cc agent/ask.
342
611
  // Explicit flags win. Best-effort — never block the review.
343
612
  try {
344
- const loadConfig = deps.loadConfig || (await import("../lib/config-manager.js")).loadConfig;
345
- const { applyConfigLlmDefaults } =
346
- deps.applyConfigLlmDefaults
347
- ? { applyConfigLlmDefaults: deps.applyConfigLlmDefaults }
348
- : await import("../lib/llm-config-defaults.js");
613
+ const loadConfig =
614
+ deps.loadConfig || (await import("../lib/config-manager.js")).loadConfig;
615
+ const { applyConfigLlmDefaults } = deps.applyConfigLlmDefaults
616
+ ? { applyConfigLlmDefaults: deps.applyConfigLlmDefaults }
617
+ : await import("../lib/llm-config-defaults.js");
349
618
  applyConfigLlmDefaults(options, loadConfig().llm || {}, {
350
619
  explicitModel: options.model,
351
620
  });
@@ -362,7 +631,8 @@ export async function runReview(options = {}, deps = {}) {
362
631
  ? options.maxTurns
363
632
  : defaultMaxTurns;
364
633
 
365
- const run = deps.runAgentHeadless ||
634
+ const run =
635
+ deps.runAgentHeadless ||
366
636
  (await import("../runtime/headless-runner.js")).runAgentHeadless;
367
637
 
368
638
  const outcome = await run({
@@ -403,12 +673,23 @@ export function registerReviewCommand(program) {
403
673
  .option("--security", "Security-focused review (/security-review parity)")
404
674
  .option("--simplify", "Cleanup-only review, no bug hunt (/simplify parity)")
405
675
  .option("--no-untracked", "Skip untracked new files (working scope)")
406
- .option("--no-checkpoint", "With --fix: do not auto-checkpoint before edits")
676
+ .option(
677
+ "--no-checkpoint",
678
+ "With --fix: do not auto-checkpoint before edits",
679
+ )
407
680
  .option("--model <model>", "Override the review model")
408
681
  .option("--provider <provider>", "Override the LLM provider")
409
682
  .option("--base-url <url>", "Override the API base URL")
410
683
  .option("--api-key <key>", "Override the API key")
411
684
  .option("--max-turns <n>", "Cap agent loop iterations")
685
+ .option(
686
+ "--comment",
687
+ "Post findings as inline comments on the current branch's PR (via gh); defaults the scope to the PR base",
688
+ )
689
+ .option(
690
+ "--dry-run",
691
+ "With --comment: show what would be posted without posting",
692
+ )
412
693
  .option("--json", "Emit the agent result envelope as JSON")
413
694
  .action(async (effortArg, options) => {
414
695
  try {
@@ -439,6 +720,69 @@ export function registerReviewCommand(program) {
439
720
  : mode === "simplify"
440
721
  ? "cleanup-only"
441
722
  : "bugs + cleanup";
723
+
724
+ // ── --comment: review the PR's diff and post inline comments ────────
725
+ if (merged.comment) {
726
+ logger.info(
727
+ chalk.gray(
728
+ `Reviewing PR diff · ${effort} effort · ${modeLabel} · ` +
729
+ (merged.dryRun ? "dry-run" : "will post comments"),
730
+ ),
731
+ );
732
+ const res = await runReviewComment(merged, {});
733
+ if (res.empty) {
734
+ logger.log(chalk.gray("No changes to review on this PR."));
735
+ return;
736
+ }
737
+ logger.log(
738
+ chalk.bold(
739
+ `${res.findings.length} finding(s) for PR #${res.pr.number}`,
740
+ ) + chalk.gray(` ${res.pr.url || ""}`),
741
+ );
742
+ for (const f of res.findings) {
743
+ logger.log(
744
+ ` ${chalk.yellow(`[${f.severity}]`)} ${chalk.cyan(`${f.path}:${f.line}`)} ${f.title}`,
745
+ );
746
+ }
747
+ if (res.findings.length === 0) {
748
+ logger.log(chalk.gray("Nothing to post."));
749
+ return;
750
+ }
751
+ if (merged.dryRun) {
752
+ logger.log(
753
+ chalk.gray("(--dry-run: not posting; re-run without --dry-run)"),
754
+ );
755
+ return;
756
+ }
757
+ // Outward-facing: confirm before posting when interactive.
758
+ if (process.stdin.isTTY) {
759
+ const { confirm } = await import("@inquirer/prompts");
760
+ const ok = await confirm({
761
+ message: `Post ${res.findings.length} comment(s) to PR #${res.pr.number}?`,
762
+ default: false,
763
+ }).catch(() => false);
764
+ if (!ok) {
765
+ logger.log(chalk.gray("Aborted — nothing posted."));
766
+ return;
767
+ }
768
+ }
769
+ try {
770
+ postReviewComments(res.pr, res.findings, {
771
+ cwd: merged.cwd,
772
+ commitId: res.pr.headRefOid,
773
+ });
774
+ logger.log(
775
+ chalk.green(
776
+ `✓ posted ${res.findings.length} comment(s) to PR #${res.pr.number}`,
777
+ ),
778
+ );
779
+ } catch (err) {
780
+ logger.error(chalk.red(`failed to post review: ${err.message}`));
781
+ process.exitCode = 1;
782
+ }
783
+ return;
784
+ }
785
+
442
786
  logger.info(
443
787
  chalk.gray(
444
788
  `Reviewing ${label} · ${effort} effort · ${modeLabel}` +
package/src/index.js CHANGED
@@ -65,6 +65,7 @@ import { registerCommandCommand } from "./commands/command.js";
65
65
  import { registerCompactCommand } from "./commands/compact.js";
66
66
  import { registerLoopCommand } from "./commands/loop.js";
67
67
  import { registerReviewCommand } from "./commands/review.js";
68
+ import { registerInsightsCommand } from "./commands/insights.js";
68
69
  import { registerPermissionsCommand } from "./commands/permissions.js";
69
70
  import { registerOutputStyleCommand } from "./commands/output-style.js";
70
71
  import { registerStatuslineCommand } from "./commands/statusline.js";
@@ -479,6 +480,7 @@ export function createProgram(opts = {}) {
479
480
  registerCompactCommand(program);
480
481
  registerLoopCommand(program);
481
482
  registerReviewCommand(program);
483
+ registerInsightsCommand(program);
482
484
  registerPermissionsCommand(program);
483
485
  registerOutputStyleCommand(program);
484
486
  registerStatuslineCommand(program);
@@ -21,6 +21,10 @@
21
21
  const SELECTION_TEXT_CAP = 2000;
22
22
  /** At most this many open-editor entries are listed. */
23
23
  const OPEN_EDITORS_CAP = 10;
24
+ /** Recent terminal commands pulled into the ambient block. */
25
+ const TERMINAL_LIMIT = 2;
26
+ /** Per-command output cap inside the ambient block (tighter than the tool's). */
27
+ const TERMINAL_OUTPUT_CAP = 800;
24
28
  /** Per-tool-call budget; the IDE answers from memory, so this is generous. */
25
29
  const DEFAULT_TIMEOUT_MS = 1500;
26
30
 
@@ -30,6 +34,17 @@ export function ideContextEnabled(env = process.env) {
30
34
  return !(v === "0" || v === "false" || v === "off");
31
35
  }
32
36
 
37
+ /**
38
+ * Sub-switch for terminal output in the ambient block: CC_IDE_TERMINAL=0|false|off
39
+ * drops it while keeping selection/editors (terminal output can be noisy or
40
+ * sensitive). Implies off whenever CC_IDE_CONTEXT is off.
41
+ */
42
+ export function ideTerminalEnabled(env = process.env) {
43
+ if (!ideContextEnabled(env)) return false;
44
+ const v = String(env?.CC_IDE_TERMINAL ?? "").toLowerCase();
45
+ return !(v === "0" || v === "false" || v === "off");
46
+ }
47
+
33
48
  /**
34
49
  * Does this resolved MCP bundle expose the IDE bridge's selection tool?
35
50
  * (`resolveAgentMcp` connects the bridge as server `ide`, so its tools land in
@@ -42,6 +57,14 @@ export function hasIdeContextTools(mcp) {
42
57
  );
43
58
  }
44
59
 
60
+ /** Does this MCP surface expose the IDE bridge's terminal-output tool? */
61
+ export function hasIdeTerminalTool(mcp) {
62
+ return !!(
63
+ mcp?.mcpClient?.callTool &&
64
+ mcp.externalToolExecutors?.mcp__ide__getTerminalOutput?.kind === "mcp"
65
+ );
66
+ }
67
+
45
68
  /**
46
69
  * Read an MCP tools/call result's first text block as JSON. The IDE bridge
47
70
  * servers always wrap handler data as
@@ -92,24 +115,38 @@ export async function collectIdeContext(mcp, opts = {}) {
92
115
  if (!hasIdeContextTools(mcp)) return null;
93
116
  const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
94
117
  const executors = mcp.externalToolExecutors;
95
- const call = (name) => {
118
+ const call = (name, args = {}) => {
96
119
  const exec = executors[name];
97
120
  if (!exec || exec.kind !== "mcp") return Promise.resolve(null);
98
121
  let p;
99
122
  try {
100
- p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, {});
123
+ p = mcp.mcpClient.callTool(exec.serverName, exec.toolName, args);
101
124
  } catch {
102
125
  return Promise.resolve(null);
103
126
  }
104
127
  return withTimeout(p.then(parseToolResultJson), timeoutMs);
105
128
  };
106
- const [selection, editors] = await Promise.all([
129
+ const wantTerminal =
130
+ ideTerminalEnabled(opts.env || process.env) && hasIdeTerminalTool(mcp);
131
+ const [selection, editors, terminalData] = await Promise.all([
107
132
  call("mcp__ide__getSelection"),
108
133
  call("mcp__ide__getOpenEditors"),
134
+ wantTerminal
135
+ ? call("mcp__ide__getTerminalOutput", { limit: TERMINAL_LIMIT })
136
+ : Promise.resolve(null),
109
137
  ]);
110
138
  const openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
111
- if (!selection && !(openEditors && openEditors.length > 0)) return null;
112
- return { selection: selection || null, openEditors };
139
+ const terminals = Array.isArray(terminalData?.terminals)
140
+ ? terminalData.terminals
141
+ : null;
142
+ if (
143
+ !selection &&
144
+ !(openEditors && openEditors.length > 0) &&
145
+ !(terminals && terminals.length > 0)
146
+ ) {
147
+ return null;
148
+ }
149
+ return { selection: selection || null, openEditors, terminals };
113
150
  }
114
151
 
115
152
  /**
@@ -149,6 +186,26 @@ export function formatIdeContext(ctx) {
149
186
  } else if (sel?.file && !active) {
150
187
  lines.push(`Active file: ${sel.file}`);
151
188
  }
189
+ const terms = Array.isArray(ctx.terminals) ? ctx.terminals : [];
190
+ if (terms.length > 0) {
191
+ const shown = terms
192
+ .slice(-TERMINAL_LIMIT)
193
+ .filter((t) => t && typeof t.command === "string");
194
+ if (shown.length > 0) {
195
+ lines.push("Recent terminal commands:");
196
+ for (const t of shown) {
197
+ const code = t.exitCode == null ? "" : ` (exit ${t.exitCode})`;
198
+ let out = typeof t.output === "string" ? t.output : "";
199
+ const truncated = out.length > TERMINAL_OUTPUT_CAP || t.outputTruncated;
200
+ if (out.length > TERMINAL_OUTPUT_CAP)
201
+ out = out.slice(-TERMINAL_OUTPUT_CAP);
202
+ lines.push(`$ ${t.command}${code}`);
203
+ if (out.trim().length > 0) {
204
+ lines.push(out + (truncated ? "\n...(output truncated)" : ""));
205
+ }
206
+ }
207
+ }
208
+ }
152
209
  if (lines.length === 0) return null;
153
210
  return (
154
211
  "<ide-context>\n" +