chainlesschain 0.162.60 → 0.162.65

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 (166) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-a2cSbSEu.js → AIOps-DjJf_QIn.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DwvSB5Pp.js → ActionButton-BT45g-KL.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BqaRaBDD.js → Analytics-CRaTHble.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Beck7v8t.js → AppLayout-72r5TM1u.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-UtJhPdXJ.js → Audit-BNlvJ3Yc.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-HVZhcdll.js → Backup-Kuj0-vBg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-DRY_ZGmj.js → BaseInput-_pKOPRf4.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D7Vuwfe2.js → Chat-CMNhGWK5.js} +5 -5
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-DxJmwLv8.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-B406i7N1.js → Checkbox-B5R2TdAI.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-BvTCqHi3.js → Codegen-69RAQ0Gi.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-DRiyxTQP.js → Col-DlbssQEY.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DWmhxHQa.js → Community-DU3SAZIS.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DO1HBZEz.js → Compact-BqdNnAZv.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-D4j-VHwS.js → Compliance-D9a9-ihS.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-BGBkWtat.js → Cowork-DWBtOBbU.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-Xa9PtMUQ.js → Cron-ClSuf90k.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-wVWs4lqN.js → Crosschain-BFjRKvpa.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-DTkqiRuT.js → DID-BwBGRlMm.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-d9STUbrr.js → Dashboard-CHrXGmQ3.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-CrdxS-C8.js → Dropdown-C24B5sk2.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-D78XHUEp.js → EmailListRenderer-DaXTSK5p.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-iAeSETIP.js → FamilyGuardDashboard-65d89G5t.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-CTV1Sxqs.js → Federation-CkWdqmVs.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BtwNuQKK.js → FormItemContext-BV4W2nrT.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-CdEgHjkl.js → GenericCardRenderer-D60KJ0_b.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BTo-PJr_.js → Git-BIKuoGvW.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-DquOG94r.js → Governance-CKnJpq5X.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DDtcBxRB.js → Inference-C7G3YGeg.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-KUOmNj5C.js → KnowledgeGraph-D7fCUd4B.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-HKm7kRs7.js → Logs-C0unjcbC.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-IxrOcbFB.js → Marketplace-BzLlnyI8.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-D6a1LM3S.js → McpTools-DSKFRB1-.js} +4 -4
  35. package/src/assets/web-panel/assets/{Memory-lFkD2ZuM.js → Memory-C_QrLAnt.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-xwuQTps5.js → MobileBridge-DBeaFERD.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-C2L_RttC.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-BXpJGrjm.js → Mtc-B3Tdh6-l.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-CWttaim1.js → MtcAudit-B3O_EUvt.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-jKgTuVLS.js → Multisig--60rVmDj.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-xl4RDzQj.js → NLProgramming-D60vxATf.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DPBOvscE.js → Notes-D2gj2uFI.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-8TkIkRo3.js → NotificationSettings-D0DWHNlF.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-Dwa-XtoE.js → OrderTableRenderer-CNi1B7fH.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-CJ0xVwZM.js → Organization-BRcdFgAd.js} +3 -3
  46. package/src/assets/web-panel/assets/{Overflow-V7VuUslt.js → Overflow-C3_Oap7v.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-BxuccEGq.js → P2P-DEbZ93QW.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-DaP2Q5kU.js → PdhVaultBrowser-DN_pmo2N.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-CPJFF0zU.js → Permissions-CPj3C9o2.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-Cmn2uiuw.js → PersonalDataHub-BZGupZzh.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-0zX89_iz.js → Pipeline-SMLW1BG7.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-DROUg3XE.js → Privacy-o24SJ2no.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-c5KESOK4.js → ProjectInit-DxjAXD8f.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-BfiCcnb_.js → ProjectSettings-DipynlqL.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-BtZH5-Eh.js → Projects-CZ9egQ8r.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-C9Rr_dOk.js → Providers-x3p-wcab.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-Du4p90W6.js → QuickAsk-CZ7beKFC.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-B-DQenTl.js → Recommend-VJCd2i9_.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-DvwlAVRZ.js → Reputation-pl12NmBF.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-rTnbvkP-.js → Row-NkSeo4Tb.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-DwvsqWbB.js → RssFeed-B17vp67R.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-U_Xj5SvF.js → Search-Dij0_m6W.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-CdjsVDQ8.js → Security-n9CSBX-9.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-DUd3mFXk.js → Services-bZOzqHdK.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CA2gCJmY.js → Skeleton-B23D5vJ-.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-BIw7Rb-m.js → Skills-IXh-0mk0.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-vySPesC0.js → Sla-QEofxmdK.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-EniuTjBJ.js → SpeechSettings-u68R59ft.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DkrqbzYS.js → SyncSettings-B3tc986U.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-Cp2QxGrr.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-C2Kvn60U.js → Templates-CMWiWxiH.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-x6jVMx4D.js → Tenant-M8aPJ3C7.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-CK3zKjIE.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BF6HAETd.js → TimelineRenderer-DF6aIS-d.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-B-KcVAin.js → Tokens-BcfMMw_e.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-B-Caiptm.js → Trigger-jIbNmxvm.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-_8sq7pJp.js → Trust-ChLil6CZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-CLveUEgo.js → UkeySign-B1WzYAon.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-iVc9jxt9.js → VideoEditing-Dwm0LyCc.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-D1n5pidD.js → Wallet-DVyxsX-O.js} +3 -3
  81. package/src/assets/web-panel/assets/{WebAuthn-CA8kubXb.js → WebAuthn-WYPNy2Q7.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-BXWwd_fB.js → WorkflowEditor-Br3dCsmv.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-DUNkQr1A.js → chat-fAKHY2HK.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-Dz5ozTcp.js → colors-BXqS-Bwi.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-CZ5-JSLh.js → compact-item-BgCQhtW3.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CFxlcPug.js → createContext-weZBwqHy.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BpXdFCJ4.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CqWIkHJm.js → hasIn-Dx68UNFL.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-C5zhjact.js → index-5e-OAZOb.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-xaZX6ZDL.js → index-B0rgvjX8.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-DUyhvh0L.js → index-B8-2rQdr.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-3elHm6lI.js → index-B8zNZ_oH.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CD7UjlnE.js → index-B9NeNwHP.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CE-gpaY9.js → index-BC-4la9j.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CLu3Oyef.js → index-BSQEQCft.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CBeASfAD.js → index-BawcE_zG.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Dc-5Ulgt.js → index-Bazltj8w.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DO6mf95c.js → index-BehvfmYd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-rR060KAF.js → index-BjsidvP5.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BiAcVeea.js → index-Bo4UTTla.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDq8IVvv.js → index-BqcH_mKR.js} +1 -1
  102. package/src/assets/web-panel/assets/index-BrPKR2RZ.js +1 -0
  103. package/src/assets/web-panel/assets/{index-8l5LLWxH.js → index-Bwv_UrNF.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BLNgGXeg.js → index-C0ZjD3Ac.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-GVbsyUQm.js → index-CEjLe8FJ.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BLLSLAC3.js → index-CJXZDwkf.js} +1 -1
  107. package/src/assets/web-panel/assets/index-CMEfvACO.js +1 -0
  108. package/src/assets/web-panel/assets/{index-CL6wt2JN.js → index-CX9cxRnU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BBq1ySIt.js → index-CfqzwaAV.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DW18L-o6.js → index-ChdeuOni.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-FsYDYVUk.js → index-ClfP1Yax.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CbcHBDYj.js → index-Co5cQnlv.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BI2EU3hC.js → index-CxfVwfub.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-noQc_RpT.js → index-Cxsfc5Ou.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DF0hXW5L.js → index-DCYJDUab.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BO-yo7Jv.js → index-DG2KCc8h.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DEzYXMgc.js → index-DNF3aCJF.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CvMZxZOn.js → index-DXuz90bX.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Ci1-8q-g.js → index-Dbv5btEU.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BJt6sNTF.js → index-DgUC575c.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-D6Nkerss.js → index-G_wPnPoA.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CguUaiiY.js → index-HIN85jl7.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C5XUilwu.js → index-LxcdLeFj.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C_WWTpLE.js → index-loaP_41H.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C2SoMbLc.js → index-ohVNy7ua.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DvUlrMy-.js → index-qUBHSW_3.js} +3 -3
  127. package/src/assets/web-panel/assets/{index-DGAjS_D9.js → index-u2U9t07r.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-PIetywTX.js → initDefaultProps-M7xH4eUK.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-gQJEK3wO.js → motion-BrJP4mFE.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-ML1nRxts.js → move-BufxEuU9.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-wUWsw3YL.js → omit-s6dtQtFP.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-A0Wlomih.js → pickAttrs-BcYdIZqz.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-C5RKYdxT.js → placementArrow-Ds-3Hw3n.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-DIxNVSJl.js → responsiveObserve-BRtrRTxl.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-B-tNesVu.js → slide-RJzqMQM4.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-CftzO200.js → statusUtils-BuBhJXvr.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-CMgvWu90.js → styleChecker-so8acGHq.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-sxqoDNhZ.js → useFlexGapSupport-BpkV467K.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-CowEXz4v.js → useFs-lES1RctZ.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-6ISRG7V-.js → usePersonalDataHub-DdCNi4bA.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-C2mnXfmw.js → vnode-nAeEg_3h.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-DMsur0jx.js → zoom-BWjRAfRy.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +40 -1
  145. package/src/commands/review.js +463 -0
  146. package/src/commands/terminal-setup.js +127 -0
  147. package/src/index.js +4 -0
  148. package/src/lib/cost-budget.js +109 -0
  149. package/src/lib/ide-context.js +50 -0
  150. package/src/lib/image-input.js +8 -2
  151. package/src/lib/llm-pricing.js +6 -0
  152. package/src/lib/personal-data-hub-wiring.js +16 -0
  153. package/src/lib/terminal-setup.js +209 -0
  154. package/src/repl/agent-repl.js +58 -1
  155. package/src/repl/tasks-status.js +82 -0
  156. package/src/runtime/agent-core.js +38 -3
  157. package/src/runtime/headless-runner.js +51 -3
  158. package/src/runtime/headless-stream.js +62 -4
  159. package/src/runtime/mcp-config.js +6 -0
  160. package/src/assets/web-panel/assets/ChatBubbleRenderer-BS2q_hPX.js +0 -1
  161. package/src/assets/web-panel/assets/MobileProjects-BYr1D3WO.js +0 -1
  162. package/src/assets/web-panel/assets/Tasks-oyPnWRbw.js +0 -1
  163. package/src/assets/web-panel/assets/Terminal-C2nZbPBs.js +0 -3
  164. package/src/assets/web-panel/assets/devWarning-BMRVR8Xp.js +0 -1
  165. package/src/assets/web-panel/assets/index-BT2s_zxU.js +0 -1
  166. package/src/assets/web-panel/assets/index-DOTL6NDc.js +0 -1
@@ -0,0 +1,463 @@
1
+ /**
2
+ * cc review — diff-first code review (Claude-Code `/code-review` parity).
3
+ *
4
+ * cc review review the working tree vs HEAD
5
+ * cc review --staged review staged changes only (git diff --cached)
6
+ * cc review --base main review this branch vs main (main...HEAD)
7
+ * cc review --range A..B review an explicit revision range
8
+ * cc review high broader, more thorough pass (effort tier)
9
+ * cc review --fix apply fixes to the working tree (reversible)
10
+ * cc review --security security-focused review (/security-review)
11
+ * cc review --simplify cleanup-only review (/simplify), no bug hunt
12
+ *
13
+ * "Diff-first" means the changed lines are collected with git and handed to the
14
+ * agent up front, so the review is anchored on what actually changed instead of
15
+ * the whole repo. The agent still has read/search tools to open surrounding code
16
+ * for context.
17
+ *
18
+ * Two modes:
19
+ * - review-only (default): runs in PLAN permission mode → the agent is clamped
20
+ * to read-only tools and CANNOT modify files. It emits a Markdown findings
21
+ * report on stdout.
22
+ * - --fix: runs in acceptEdits mode with auto-checkpointing ON, so the agent
23
+ * applies the fixes directly; every file edit is captured as a git-plumbing
24
+ * shadow commit first, so the whole pass is reversible with `cc checkpoint
25
+ * restore <id>`.
26
+ *
27
+ * Requires a git work tree (the diff is the input). LLM defaults follow
28
+ * .chainlesschain/config.json `llm` exactly like `cc agent` / `cc ask`.
29
+ */
30
+
31
+ import { spawnSync } from "node:child_process";
32
+ import fs from "node:fs";
33
+ import path from "node:path";
34
+ import chalk from "chalk";
35
+ import { logger } from "../lib/logger.js";
36
+
37
+ /** Diffs larger than this are truncated before going to the model. */
38
+ const MAX_DIFF_CHARS = 200_000;
39
+ /** Per-untracked-file and total caps when inlining brand-new files. */
40
+ const MAX_UNTRACKED_FILE_CHARS = 32_000;
41
+ const MAX_UNTRACKED_TOTAL_CHARS = 128_000;
42
+
43
+ const VALID_EFFORTS = Object.freeze(["low", "medium", "high"]);
44
+
45
+ /**
46
+ * Run git with an argv array (no shell → no quoting hazards). UTF-8 in/out.
47
+ * Returns trimmed stdout; throws with git's stderr on failure.
48
+ */
49
+ function gitCli(args, { cwd } = {}) {
50
+ const res = spawnSync("git", args, {
51
+ cwd,
52
+ encoding: "utf-8",
53
+ windowsHide: true,
54
+ maxBuffer: 256 * 1024 * 1024,
55
+ });
56
+ if (res.error) throw res.error;
57
+ if (res.status !== 0) {
58
+ const msg = (res.stderr || res.stdout || "").toString().trim();
59
+ throw new Error(msg || `git ${args.join(" ")} failed (exit ${res.status})`);
60
+ }
61
+ return (res.stdout || "").toString();
62
+ }
63
+
64
+ function isGitRepo(cwd, git = gitCli) {
65
+ try {
66
+ return git(["rev-parse", "--is-inside-work-tree"], { cwd }).trim() === "true";
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Build the `git diff` argv for the requested scope. Pure.
74
+ *
75
+ * @param {object} opts { staged, base, range, paths }
76
+ * @param {boolean} [stat] append --stat for the summary variant
77
+ * @returns {{ args:string[], scope:string, label:string }}
78
+ */
79
+ export function resolveDiffArgs(opts = {}, stat = false) {
80
+ const { staged, base, range, paths } = opts;
81
+ let args;
82
+ let scope;
83
+ let label;
84
+ if (range) {
85
+ args = ["diff", range];
86
+ scope = "range";
87
+ label = `range ${range}`;
88
+ } else if (base) {
89
+ // three-dot: changes on HEAD since it diverged from <base> (PR-style).
90
+ args = ["diff", `${base}...HEAD`];
91
+ scope = "base";
92
+ label = `${base}...HEAD`;
93
+ } else if (staged) {
94
+ args = ["diff", "--cached"];
95
+ scope = "staged";
96
+ label = "staged changes";
97
+ } else {
98
+ args = ["diff", "HEAD"];
99
+ scope = "working";
100
+ label = "working tree vs HEAD";
101
+ }
102
+ if (stat) args.push("--stat");
103
+ const cleanPaths = Array.isArray(paths) ? paths.filter(Boolean) : [];
104
+ if (cleanPaths.length) args.push("--", ...cleanPaths);
105
+ return { args, scope, label };
106
+ }
107
+
108
+ /** Normalize/validate an effort tier; defaults to "medium". Pure. */
109
+ export function normalizeEffort(value) {
110
+ if (!value) return "medium";
111
+ const v = String(value).toLowerCase().trim();
112
+ if (!VALID_EFFORTS.includes(v)) {
113
+ throw new Error(
114
+ `Invalid effort "${value}". Expected one of: ${VALID_EFFORTS.join(", ")}.`,
115
+ );
116
+ }
117
+ return v;
118
+ }
119
+
120
+ /** Resolve the review lens from flags. Pure. */
121
+ export function resolveReviewMode({ security, simplify } = {}) {
122
+ if (security && simplify) {
123
+ throw new Error("--security and --simplify are mutually exclusive.");
124
+ }
125
+ if (security) return "security";
126
+ if (simplify) return "simplify";
127
+ return "default";
128
+ }
129
+
130
+ const LENS = Object.freeze({
131
+ default:
132
+ "Review the changes for, in priority order: (1) CORRECTNESS bugs — logic " +
133
+ "errors, unhandled edge cases, null/undefined hazards, off-by-one, race " +
134
+ "conditions, missing/incorrect error handling, resource leaks, wrong API " +
135
+ "usage, broken invariants; and (2) CLEANUP — duplicated logic that could " +
136
+ "reuse an existing helper, code that could be simplified, and obvious " +
137
+ "inefficiencies. Correctness findings come first.",
138
+ security:
139
+ "This is a SECURITY review. Look only for security-relevant defects: " +
140
+ "injection (SQL / shell / path), broken authentication or authorization, " +
141
+ "secrets committed in code, weak or misused cryptography, SSRF, unsafe " +
142
+ "deserialization, path traversal, XSS, insecure randomness, missing input " +
143
+ "validation, TOCTOU races, and insecure defaults. Ignore style and " +
144
+ "non-security cleanups.",
145
+ simplify:
146
+ "This is a CLEANUP-ONLY review — do NOT hunt for bugs. Look for: duplicated " +
147
+ "logic that could reuse existing code, over-complicated code that can be " +
148
+ "simplified, inefficient patterns, and wrong-altitude abstractions. Only " +
149
+ "propose changes that PRESERVE behavior.",
150
+ });
151
+
152
+ const EFFORT_GUIDE = Object.freeze({
153
+ low:
154
+ "Report ONLY the few highest-confidence, highest-impact findings. Skip " +
155
+ "anything speculative or minor.",
156
+ medium:
157
+ "Report high-confidence findings across the whole diff. Skip speculative " +
158
+ "nitpicks.",
159
+ high:
160
+ "Be thorough across the whole diff. You may include lower-confidence " +
161
+ "findings, but clearly mark each as such.",
162
+ });
163
+
164
+ /**
165
+ * Build the review prompt embedding the diff. Pure.
166
+ *
167
+ * @param {object} o { diff, summary, effort, mode, fix, label, untrackedBlocks, truncated }
168
+ * @returns {string}
169
+ */
170
+ export function buildReviewPrompt(o = {}) {
171
+ const {
172
+ diff = "",
173
+ summary = "",
174
+ effort = "medium",
175
+ mode = "default",
176
+ fix = false,
177
+ label = "working tree vs HEAD",
178
+ untrackedBlocks = "",
179
+ truncated = false,
180
+ } = o;
181
+
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.";
197
+
198
+ return [
199
+ `You are an expert code reviewer. ${LENS[mode]}`,
200
+ EFFORT_GUIDE[effort],
201
+ "",
202
+ `The change under review is the ${label}, shown below as a unified diff. ` +
203
+ "Before judging a hunk, use your read/search tools to open the " +
204
+ "surrounding code so your findings reflect real context, not just the " +
205
+ "diff window.",
206
+ "",
207
+ summary ? "Diffstat:\n```\n" + summary.trim() + "\n```\n" : "",
208
+ "Unified diff:",
209
+ "```diff",
210
+ truncated
211
+ ? diff + "\n\n[... diff truncated — review what is shown; note the cutoff]"
212
+ : diff,
213
+ "```",
214
+ untrackedBlocks ? "\n" + untrackedBlocks : "",
215
+ "",
216
+ tail,
217
+ ]
218
+ .filter((s) => s !== "")
219
+ .join("\n");
220
+ }
221
+
222
+ /**
223
+ * Collect untracked files (working scope only) and render them as labeled
224
+ * "new file" blocks so brand-new code is reviewed too (git diff HEAD omits it).
225
+ */
226
+ function collectUntracked(cwd, git) {
227
+ let list = [];
228
+ try {
229
+ list = git(["ls-files", "--others", "--exclude-standard"], { cwd })
230
+ .split("\n")
231
+ .map((s) => s.trim())
232
+ .filter(Boolean);
233
+ } catch {
234
+ return { blocks: "", files: [], skipped: [] };
235
+ }
236
+ if (!list.length) return { blocks: "", files: [], skipped: [] };
237
+
238
+ const parts = [];
239
+ const included = [];
240
+ const skipped = [];
241
+ let total = 0;
242
+ for (const rel of list) {
243
+ if (total >= MAX_UNTRACKED_TOTAL_CHARS) {
244
+ skipped.push(rel);
245
+ continue;
246
+ }
247
+ let content;
248
+ try {
249
+ content = fs.readFileSync(path.resolve(cwd, rel), "utf-8");
250
+ } catch {
251
+ skipped.push(rel);
252
+ continue;
253
+ }
254
+ // Skip files that look binary (NUL byte in the first chunk).
255
+ if (content.includes("\u0000")) {
256
+ skipped.push(rel);
257
+ continue;
258
+ }
259
+ let body = content;
260
+ if (body.length > MAX_UNTRACKED_FILE_CHARS) {
261
+ body = body.slice(0, MAX_UNTRACKED_FILE_CHARS) + "\n[... file truncated]";
262
+ }
263
+ total += body.length;
264
+ included.push(rel);
265
+ parts.push(`New file \`${rel}\`:\n\`\`\`\n${body}\n\`\`\``);
266
+ }
267
+ return {
268
+ blocks: parts.length ? "Untracked new files:\n\n" + parts.join("\n\n") : "",
269
+ files: included,
270
+ skipped,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Core review run — collects the diff and dispatches one headless agent turn.
276
+ * Deps are injected for tests (git / runAgentHeadless / config helpers).
277
+ *
278
+ * @returns {Promise<{exitCode:number, isError:boolean, scope:string, empty?:boolean}>}
279
+ */
280
+ export async function runReview(options = {}, deps = {}) {
281
+ const cwd = options.cwd || process.cwd();
282
+ const git = deps.git || gitCli;
283
+ const repoCheck = deps.isGitRepo || isGitRepo;
284
+
285
+ if (!repoCheck(cwd, git)) {
286
+ throw new Error(
287
+ "cc review needs a git work tree (the diff is the review input).",
288
+ );
289
+ }
290
+
291
+ const effort = normalizeEffort(options.effort);
292
+ const mode = resolveReviewMode(options);
293
+ const fix = options.fix === true;
294
+
295
+ const scopeOpts = {
296
+ staged: options.staged === true,
297
+ base: options.base || null,
298
+ range: options.range || null,
299
+ paths: options.paths || [],
300
+ };
301
+
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);
323
+ if (!hasDiff && !hasUntracked) {
324
+ return { exitCode: 0, isError: false, scope, empty: true };
325
+ }
326
+
327
+ const truncated = diff.length > MAX_DIFF_CHARS;
328
+ if (truncated) diff = diff.slice(0, MAX_DIFF_CHARS);
329
+
330
+ const prompt = buildReviewPrompt({
331
+ diff: hasDiff ? diff : "(no tracked changes)",
332
+ summary,
333
+ effort,
334
+ mode,
335
+ fix,
336
+ label,
337
+ untrackedBlocks: untracked.blocks,
338
+ truncated,
339
+ });
340
+
341
+ // LLM defaults: honor .chainlesschain/config.json `llm` like cc agent/ask.
342
+ // Explicit flags win. Best-effort — never block the review.
343
+ 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");
349
+ applyConfigLlmDefaults(options, loadConfig().llm || {}, {
350
+ explicitModel: options.model,
351
+ });
352
+ } catch {
353
+ // fall back to runner defaults
354
+ }
355
+
356
+ // review-only → plan mode (clamped to read-only tools, cannot mutate).
357
+ // --fix → acceptEdits + auto-checkpoint (reversible edits).
358
+ const permissionMode = fix ? "acceptEdits" : "plan";
359
+ const autoCheckpoint = fix && options.checkpoint !== false;
360
+ const defaultMaxTurns = fix ? 40 : 20;
361
+ const maxTurns = Number.isFinite(options.maxTurns)
362
+ ? options.maxTurns
363
+ : defaultMaxTurns;
364
+
365
+ const run = deps.runAgentHeadless ||
366
+ (await import("../runtime/headless-runner.js")).runAgentHeadless;
367
+
368
+ const outcome = await run({
369
+ prompt,
370
+ model: options.model,
371
+ provider: options.provider,
372
+ baseUrl: options.baseUrl,
373
+ apiKey: options.apiKey,
374
+ outputFormat: options.outputFormat || "text",
375
+ permissionMode,
376
+ autoCheckpoint,
377
+ checkpointSession: options.checkpointSession || `review-${scope}`,
378
+ maxTurns,
379
+ cwd,
380
+ // The diff carries `@@` hunk markers and may contain `@tokens`; never run
381
+ // the @file-reference expander over it.
382
+ expandFileRefs: false,
383
+ });
384
+
385
+ return { ...outcome, scope, empty: false };
386
+ }
387
+
388
+ export function registerReviewCommand(program) {
389
+ program
390
+ .command("review [effort]")
391
+ .description(
392
+ "Diff-first code review of your changes (Claude-Code /code-review parity)",
393
+ )
394
+ .option("--staged", "Review staged changes only (git diff --cached)")
395
+ .option("--base <ref>", "Review this branch vs a base ref (<ref>...HEAD)")
396
+ .option("--range <range>", "Review an explicit revision range (e.g. A..B)")
397
+ .option(
398
+ "--paths <paths...>",
399
+ "Limit the review to these paths (repeatable)",
400
+ )
401
+ .option("-e, --effort <level>", "low | medium | high (default: medium)")
402
+ .option("--fix", "Apply the fixes to the working tree (auto-checkpointed)")
403
+ .option("--security", "Security-focused review (/security-review parity)")
404
+ .option("--simplify", "Cleanup-only review, no bug hunt (/simplify parity)")
405
+ .option("--no-untracked", "Skip untracked new files (working scope)")
406
+ .option("--no-checkpoint", "With --fix: do not auto-checkpoint before edits")
407
+ .option("--model <model>", "Override the review model")
408
+ .option("--provider <provider>", "Override the LLM provider")
409
+ .option("--base-url <url>", "Override the API base URL")
410
+ .option("--api-key <key>", "Override the API key")
411
+ .option("--max-turns <n>", "Cap agent loop iterations")
412
+ .option("--json", "Emit the agent result envelope as JSON")
413
+ .action(async (effortArg, options) => {
414
+ try {
415
+ const merged = {
416
+ ...options,
417
+ effort: options.effort || effortArg,
418
+ // commander stores --no-untracked as untracked:false, --no-checkpoint
419
+ // as checkpoint:false; pass them through unchanged.
420
+ maxTurns: options.maxTurns
421
+ ? parseInt(options.maxTurns, 10)
422
+ : undefined,
423
+ outputFormat: options.json ? "json" : "text",
424
+ cwd: process.cwd(),
425
+ };
426
+
427
+ // Pre-flight messaging on stderr so stdout stays a clean payload.
428
+ const mode = resolveReviewMode(merged);
429
+ const effort = normalizeEffort(merged.effort);
430
+ const { label } = resolveDiffArgs({
431
+ staged: merged.staged,
432
+ base: merged.base,
433
+ range: merged.range,
434
+ paths: merged.paths,
435
+ });
436
+ const modeLabel =
437
+ mode === "security"
438
+ ? "security"
439
+ : mode === "simplify"
440
+ ? "cleanup-only"
441
+ : "bugs + cleanup";
442
+ logger.info(
443
+ chalk.gray(
444
+ `Reviewing ${label} · ${effort} effort · ${modeLabel}` +
445
+ (merged.fix ? " · applying fixes" : " · read-only"),
446
+ ),
447
+ );
448
+
449
+ const result = await runReview(merged, {});
450
+
451
+ if (result.empty) {
452
+ logger.log(chalk.gray("No changes to review."));
453
+ return;
454
+ }
455
+ if (result.isError) {
456
+ process.exitCode = result.exitCode || 1;
457
+ }
458
+ } catch (err) {
459
+ logger.error(chalk.red(`review failed: ${err.message}`));
460
+ process.exitCode = 1;
461
+ }
462
+ });
463
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * cc terminal-setup — bind Shift+Enter to insert a newline in the agent REPL
3
+ * (Claude-Code `/terminal-setup` parity).
4
+ *
5
+ * cc terminal-setup preview what to configure for this terminal
6
+ * cc terminal-setup --apply write the VS Code keybinding (VS Code only)
7
+ *
8
+ * cc multiline works by ending a line with a backslash; this makes Shift+Enter
9
+ * do that for you. Only VS Code can be auto-configured (its user
10
+ * keybindings.json); other terminals get manual instructions.
11
+ */
12
+
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import chalk from "chalk";
16
+ import { logger } from "../lib/logger.js";
17
+ import {
18
+ detectTerminal,
19
+ vscodeKeybinding,
20
+ vscodeKeybindingsPath,
21
+ parseKeybindings,
22
+ hasKeybinding,
23
+ appendKeybindingText,
24
+ instructionsFor,
25
+ } from "../lib/terminal-setup.js";
26
+
27
+ /** Shared core so the REPL `/terminal-setup` can reuse it. Returns lines. */
28
+ export function runTerminalSetup({
29
+ apply = false,
30
+ env = process.env,
31
+ platform = process.platform,
32
+ _fs = fs,
33
+ } = {}) {
34
+ const term = detectTerminal(env);
35
+ const lines = [chalk.bold(`Terminal: ${term.name}`)];
36
+ lines.push(
37
+ chalk.gray(
38
+ "Goal: Shift+Enter → newline in the REPL (cc treats a trailing \\ as a line continuation).",
39
+ ),
40
+ );
41
+
42
+ if (term.id !== "vscode") {
43
+ lines.push("");
44
+ for (const l of instructionsFor(term.id)) lines.push(" " + l);
45
+ if (apply) {
46
+ lines.push("");
47
+ lines.push(
48
+ chalk.yellow(
49
+ "--apply only auto-configures VS Code; follow the steps above for this terminal.",
50
+ ),
51
+ );
52
+ }
53
+ return { changed: false, lines, terminal: term.id };
54
+ }
55
+
56
+ // VS Code: read → check → (optionally) write keybindings.json.
57
+ const kbPath = vscodeKeybindingsPath(platform, env);
58
+ const binding = vscodeKeybinding();
59
+ let text = "";
60
+ try {
61
+ text = _fs.readFileSync(kbPath, "utf-8");
62
+ } catch {
63
+ text = ""; // missing file → we'll create it
64
+ }
65
+ const parsed = parseKeybindings(text);
66
+
67
+ if (Array.isArray(parsed) && hasKeybinding(parsed, binding)) {
68
+ lines.push(chalk.green(`Already configured: ${kbPath}`));
69
+ return { changed: false, lines, terminal: term.id, path: kbPath };
70
+ }
71
+
72
+ if (!apply) {
73
+ lines.push("");
74
+ lines.push("Would add this to " + chalk.cyan(kbPath) + ":");
75
+ lines.push(chalk.gray(JSON.stringify(binding, null, 2)));
76
+ lines.push("");
77
+ lines.push(
78
+ "Run " +
79
+ chalk.cyan("cc terminal-setup --apply") +
80
+ " to write it, then reload VS Code.",
81
+ );
82
+ return { changed: false, lines, terminal: term.id, path: kbPath };
83
+ }
84
+
85
+ // Apply. Bail rather than clobber a file we cannot parse.
86
+ if (parsed === null) {
87
+ lines.push(
88
+ chalk.red(`Could not parse ${kbPath} — add this binding manually:`),
89
+ );
90
+ lines.push(chalk.gray(JSON.stringify(binding, null, 2)));
91
+ return { changed: false, lines, terminal: term.id, path: kbPath };
92
+ }
93
+ const next = appendKeybindingText(text, binding);
94
+ if (next === null) {
95
+ lines.push(chalk.red(`Unexpected keybindings.json shape — add manually:`));
96
+ lines.push(chalk.gray(JSON.stringify(binding, null, 2)));
97
+ return { changed: false, lines, terminal: term.id, path: kbPath };
98
+ }
99
+ try {
100
+ _fs.mkdirSync(path.dirname(kbPath), { recursive: true });
101
+ if (text) _fs.writeFileSync(kbPath + ".bak", text, "utf-8");
102
+ _fs.writeFileSync(kbPath, next, "utf-8");
103
+ } catch (err) {
104
+ lines.push(chalk.red(`Write failed: ${err.message}`));
105
+ return { changed: false, lines, terminal: term.id, path: kbPath };
106
+ }
107
+ lines.push(chalk.green(`Added Shift+Enter binding → ${kbPath}`));
108
+ if (text) lines.push(chalk.gray(`(backup: ${kbPath}.bak)`));
109
+ lines.push(
110
+ chalk.gray("Reload VS Code (Developer: Reload Window) to activate."),
111
+ );
112
+ return { changed: true, lines, terminal: term.id, path: kbPath };
113
+ }
114
+
115
+ export function registerTerminalSetupCommand(program) {
116
+ program
117
+ .command("terminal-setup")
118
+ .description("Bind Shift+Enter to a REPL newline (Claude-Code parity)")
119
+ .option(
120
+ "--apply",
121
+ "Write the keybinding (VS Code only; otherwise prints steps)",
122
+ )
123
+ .action((options) => {
124
+ const res = runTerminalSetup({ apply: options.apply === true });
125
+ for (const l of res.lines) logger.log(l);
126
+ });
127
+ }
package/src/index.js CHANGED
@@ -64,9 +64,11 @@ import { registerGoalCommand } from "./commands/goal.js";
64
64
  import { registerCommandCommand } from "./commands/command.js";
65
65
  import { registerCompactCommand } from "./commands/compact.js";
66
66
  import { registerLoopCommand } from "./commands/loop.js";
67
+ import { registerReviewCommand } from "./commands/review.js";
67
68
  import { registerPermissionsCommand } from "./commands/permissions.js";
68
69
  import { registerOutputStyleCommand } from "./commands/output-style.js";
69
70
  import { registerStatuslineCommand } from "./commands/statusline.js";
71
+ import { registerTerminalSetupCommand } from "./commands/terminal-setup.js";
70
72
  import { registerIdeCommand } from "./commands/ide.js";
71
73
  import { registerConsolCommand } from "./commands/consol.js";
72
74
  import { registerImportCommand } from "./commands/import.js";
@@ -476,9 +478,11 @@ export function createProgram(opts = {}) {
476
478
  registerCommandCommand(program);
477
479
  registerCompactCommand(program);
478
480
  registerLoopCommand(program);
481
+ registerReviewCommand(program);
479
482
  registerPermissionsCommand(program);
480
483
  registerOutputStyleCommand(program);
481
484
  registerStatuslineCommand(program);
485
+ registerTerminalSetupCommand(program);
482
486
  registerIdeCommand(program);
483
487
  registerConsolCommand(program);
484
488