claude-code-workflow 7.2.5 → 7.2.7

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 (235) hide show
  1. package/.claude/commands/workflow/analyze-with-file.md +11 -10
  2. package/.claude/skills/ccw-help/command.json +3 -3
  3. package/.claude/skills/ccw-help/index/all-commands.json +3 -3
  4. package/.claude/skills/ccw-help/index/by-category.json +3 -3
  5. package/.claude/skills/ccw-help/index/by-use-case.json +3 -3
  6. package/.claude/skills/skill-iter-tune/SKILL.md +382 -0
  7. package/.claude/skills/skill-iter-tune/phases/01-setup.md +144 -0
  8. package/.claude/skills/skill-iter-tune/phases/02-execute.md +292 -0
  9. package/.claude/skills/skill-iter-tune/phases/03-evaluate.md +312 -0
  10. package/.claude/skills/skill-iter-tune/phases/04-improve.md +186 -0
  11. package/.claude/skills/skill-iter-tune/phases/05-report.md +166 -0
  12. package/.claude/skills/skill-iter-tune/specs/evaluation-criteria.md +63 -0
  13. package/.claude/skills/skill-iter-tune/templates/eval-prompt.md +134 -0
  14. package/.claude/skills/skill-iter-tune/templates/execute-prompt.md +97 -0
  15. package/.claude/skills/team-roadmap-dev/SKILL.md +1 -1
  16. package/.claude/skills/team-roadmap-dev/roles/coordinator/role.md +1 -1
  17. package/.claude/skills/team-roadmap-dev/specs/team-config.json +1 -1
  18. package/ccw/dist/commands/install.d.ts.map +1 -1
  19. package/ccw/dist/commands/install.js +11 -9
  20. package/ccw/dist/commands/install.js.map +1 -1
  21. package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
  22. package/ccw/dist/core/routes/codexlens/config-handlers.js +4 -0
  23. package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
  24. package/ccw/dist/mcp-server/index.js +85 -8
  25. package/ccw/dist/mcp-server/index.js.map +1 -1
  26. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  27. package/ccw/dist/tools/cli-executor-core.js +0 -25
  28. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  29. package/ccw/dist/tools/cli-executor-state.d.ts.map +1 -1
  30. package/ccw/dist/tools/cli-executor-state.js +0 -31
  31. package/ccw/dist/tools/cli-executor-state.js.map +1 -1
  32. package/ccw/dist/tools/cli-history-store.d.ts.map +1 -1
  33. package/ccw/dist/tools/cli-history-store.js +1 -30
  34. package/ccw/dist/tools/cli-history-store.js.map +1 -1
  35. package/ccw/dist/tools/smart-search.d.ts +72 -1
  36. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  37. package/ccw/dist/tools/smart-search.js +726 -117
  38. package/ccw/dist/tools/smart-search.js.map +1 -1
  39. package/ccw/frontend/dist/assets/{AlertDialog-CFGtc76Y.js → AlertDialog-CSZq4WHZ.js} +2 -2
  40. package/ccw/frontend/dist/assets/{AlertDialog-CFGtc76Y.js.map → AlertDialog-CSZq4WHZ.js.map} +1 -1
  41. package/ccw/frontend/dist/assets/{AnalysisPage-Duxia_dG.js → AnalysisPage-DVDvLHqI.js} +2 -2
  42. package/ccw/frontend/dist/assets/{AnalysisPage-Duxia_dG.js.map → AnalysisPage-DVDvLHqI.js.map} +1 -1
  43. package/ccw/frontend/dist/assets/{ApiSettingsPage-CU9hesAE.js → ApiSettingsPage-D69j8mI6.js} +2 -2
  44. package/ccw/frontend/dist/assets/{ApiSettingsPage-CU9hesAE.js.map → ApiSettingsPage-D69j8mI6.js.map} +1 -1
  45. package/ccw/frontend/dist/assets/{CliModeToggle-DcdOFSvM.js → CliModeToggle-fF9xHeGR.js} +2 -2
  46. package/ccw/frontend/dist/assets/{CliModeToggle-DcdOFSvM.js.map → CliModeToggle-fF9xHeGR.js.map} +1 -1
  47. package/ccw/frontend/dist/assets/{CliSessionSharePage-oKN-GRIT.js → CliSessionSharePage-Ce8tOQNx.js} +2 -2
  48. package/ccw/frontend/dist/assets/{CliSessionSharePage-oKN-GRIT.js.map → CliSessionSharePage-Ce8tOQNx.js.map} +1 -1
  49. package/ccw/frontend/dist/assets/{CliViewerPage-_wUoHaY-.js → CliViewerPage-Bsp1zmfD.js} +2 -2
  50. package/ccw/frontend/dist/assets/{CliViewerPage-_wUoHaY-.js.map → CliViewerPage-Bsp1zmfD.js.map} +1 -1
  51. package/ccw/frontend/dist/assets/{CodexLensManagerPage-ClPNashJ.js → CodexLensManagerPage-DUMzbrr9.js} +9 -9
  52. package/ccw/frontend/dist/assets/CodexLensManagerPage-DUMzbrr9.js.map +1 -0
  53. package/ccw/frontend/dist/assets/{Collapsible-Dladh9Eu.js → Collapsible-I5pixbHb.js} +2 -2
  54. package/ccw/frontend/dist/assets/{Collapsible-Dladh9Eu.js.map → Collapsible-I5pixbHb.js.map} +1 -1
  55. package/ccw/frontend/dist/assets/{CommandsManagerPage-B6DBb_Ew.js → CommandsManagerPage-BH2xukdx.js} +2 -2
  56. package/ccw/frontend/dist/assets/{CommandsManagerPage-B6DBb_Ew.js.map → CommandsManagerPage-BH2xukdx.js.map} +1 -1
  57. package/ccw/frontend/dist/assets/{DeepWikiPage-CDA3xm3R.js → DeepWikiPage-D41ccWzY.js} +2 -2
  58. package/ccw/frontend/dist/assets/{DeepWikiPage-CDA3xm3R.js.map → DeepWikiPage-D41ccWzY.js.map} +1 -1
  59. package/ccw/frontend/dist/assets/{EndpointsPage-Bu7iU0Wy.js → EndpointsPage-5a8jfEWN.js} +2 -2
  60. package/ccw/frontend/dist/assets/{EndpointsPage-Bu7iU0Wy.js.map → EndpointsPage-5a8jfEWN.js.map} +1 -1
  61. package/ccw/frontend/dist/assets/{ExplorerPage-xim2cboZ.js → ExplorerPage-YCqIOu1v.js} +2 -2
  62. package/ccw/frontend/dist/assets/{ExplorerPage-xim2cboZ.js.map → ExplorerPage-YCqIOu1v.js.map} +1 -1
  63. package/ccw/frontend/dist/assets/{FixSessionPage-BTFYoFQF.js → FixSessionPage-wK1s4kbo.js} +2 -2
  64. package/ccw/frontend/dist/assets/{FixSessionPage-BTFYoFQF.js.map → FixSessionPage-wK1s4kbo.js.map} +1 -1
  65. package/ccw/frontend/dist/assets/{FloatingFileBrowser-B30ANzTJ.js → FloatingFileBrowser-B1U6uk_5.js} +2 -2
  66. package/ccw/frontend/dist/assets/{FloatingFileBrowser-B30ANzTJ.js.map → FloatingFileBrowser-B1U6uk_5.js.map} +1 -1
  67. package/ccw/frontend/dist/assets/{FloatingPanel-Ds84B7Ob.js → FloatingPanel-D51p5jNy.js} +2 -2
  68. package/ccw/frontend/dist/assets/{FloatingPanel-Ds84B7Ob.js.map → FloatingPanel-D51p5jNy.js.map} +1 -1
  69. package/ccw/frontend/dist/assets/{GraphExplorerPage-DJ4YnFOQ.js → GraphExplorerPage-DpIWrpWH.js} +2 -2
  70. package/ccw/frontend/dist/assets/{GraphExplorerPage-DJ4YnFOQ.js.map → GraphExplorerPage-DpIWrpWH.js.map} +1 -1
  71. package/ccw/frontend/dist/assets/{HistoryPage-rzLVsw11.js → HistoryPage-C09lyJGN.js} +2 -2
  72. package/ccw/frontend/dist/assets/{HistoryPage-rzLVsw11.js.map → HistoryPage-C09lyJGN.js.map} +1 -1
  73. package/ccw/frontend/dist/assets/{HookManagerPage-DizO_dCP.js → HookManagerPage-CFBe8F_G.js} +2 -2
  74. package/ccw/frontend/dist/assets/{HookManagerPage-DizO_dCP.js.map → HookManagerPage-CFBe8F_G.js.map} +1 -1
  75. package/ccw/frontend/dist/assets/{InstallationsPage-BFamuE4y.js → InstallationsPage-qt7NxnsB.js} +2 -2
  76. package/ccw/frontend/dist/assets/{InstallationsPage-BFamuE4y.js.map → InstallationsPage-qt7NxnsB.js.map} +1 -1
  77. package/ccw/frontend/dist/assets/{IssueHubPage-DHwQWuUc.js → IssueHubPage-CHol1-Ap.js} +2 -2
  78. package/ccw/frontend/dist/assets/{IssueHubPage-DHwQWuUc.js.map → IssueHubPage-CHol1-Ap.js.map} +1 -1
  79. package/ccw/frontend/dist/assets/{LiteTasksPage-DMDkG7pi.js → LiteTasksPage-jH00SLnZ.js} +2 -2
  80. package/ccw/frontend/dist/assets/{LiteTasksPage-DMDkG7pi.js.map → LiteTasksPage-jH00SLnZ.js.map} +1 -1
  81. package/ccw/frontend/dist/assets/{McpManagerPage-CS2v_1OG.js → McpManagerPage-BS-HgeQJ.js} +11 -11
  82. package/ccw/frontend/dist/assets/McpManagerPage-BS-HgeQJ.js.map +1 -0
  83. package/ccw/frontend/dist/assets/{MemoryPage-DhYPE-VC.js → MemoryPage-1UahrmRX.js} +2 -2
  84. package/ccw/frontend/dist/assets/{MemoryPage-DhYPE-VC.js.map → MemoryPage-1UahrmRX.js.map} +1 -1
  85. package/ccw/frontend/dist/assets/{NotFoundPage-BLmC7-ot.js → NotFoundPage-5FVm_IkS.js} +2 -2
  86. package/ccw/frontend/dist/assets/{NotFoundPage-BLmC7-ot.js.map → NotFoundPage-5FVm_IkS.js.map} +1 -1
  87. package/ccw/frontend/dist/assets/{OrchestratorPage-1jzSUFZW.js → OrchestratorPage-ThBG24Gg.js} +2 -2
  88. package/ccw/frontend/dist/assets/{OrchestratorPage-1jzSUFZW.js.map → OrchestratorPage-ThBG24Gg.js.map} +1 -1
  89. package/ccw/frontend/dist/assets/{ProjectOverviewPage-gWuAj-Cn.js → ProjectOverviewPage-CrmW0IxI.js} +2 -2
  90. package/ccw/frontend/dist/assets/{ProjectOverviewPage-gWuAj-Cn.js.map → ProjectOverviewPage-CrmW0IxI.js.map} +1 -1
  91. package/ccw/frontend/dist/assets/{PromptHistoryPage-q7ySVNHM.js → PromptHistoryPage-1cCGwS3s.js} +2 -2
  92. package/ccw/frontend/dist/assets/{PromptHistoryPage-q7ySVNHM.js.map → PromptHistoryPage-1cCGwS3s.js.map} +1 -1
  93. package/ccw/frontend/dist/assets/{ReviewSessionPage-DX1uT_nH.js → ReviewSessionPage-DxiEc9HK.js} +2 -2
  94. package/ccw/frontend/dist/assets/{ReviewSessionPage-DX1uT_nH.js.map → ReviewSessionPage-DxiEc9HK.js.map} +1 -1
  95. package/ccw/frontend/dist/assets/{RulesManagerPage-Bg6T5W-W.js → RulesManagerPage-BpplliZF.js} +2 -2
  96. package/ccw/frontend/dist/assets/{RulesManagerPage-Bg6T5W-W.js.map → RulesManagerPage-BpplliZF.js.map} +1 -1
  97. package/ccw/frontend/dist/assets/{SessionDetailPage-C_ig6JSh.js → SessionDetailPage-crDIiysh.js} +2 -2
  98. package/ccw/frontend/dist/assets/{SessionDetailPage-C_ig6JSh.js.map → SessionDetailPage-crDIiysh.js.map} +1 -1
  99. package/ccw/frontend/dist/assets/{SessionsPage-BENFqFDn.js → SessionsPage-DThweYY8.js} +2 -2
  100. package/ccw/frontend/dist/assets/{SessionsPage-BENFqFDn.js.map → SessionsPage-DThweYY8.js.map} +1 -1
  101. package/ccw/frontend/dist/assets/{SettingsPage-2c-oTxfz.js → SettingsPage-OCdOz5-z.js} +4 -4
  102. package/ccw/frontend/dist/assets/{SettingsPage-2c-oTxfz.js.map → SettingsPage-OCdOz5-z.js.map} +1 -1
  103. package/ccw/frontend/dist/assets/{SkillsManagerPage-GLo6DXnt.js → SkillsManagerPage-BJd3bK1G.js} +2 -2
  104. package/ccw/frontend/dist/assets/{SkillsManagerPage-GLo6DXnt.js.map → SkillsManagerPage-BJd3bK1G.js.map} +1 -1
  105. package/ccw/frontend/dist/assets/{SpecsSettingsPage-CRqMV90p.js → SpecsSettingsPage-BAWL2cpO.js} +2 -2
  106. package/ccw/frontend/dist/assets/{SpecsSettingsPage-CRqMV90p.js.map → SpecsSettingsPage-BAWL2cpO.js.map} +1 -1
  107. package/ccw/frontend/dist/assets/{Switch-vlMavMRD.js → Switch-w0I3JCcC.js} +2 -2
  108. package/ccw/frontend/dist/assets/{Switch-vlMavMRD.js.map → Switch-w0I3JCcC.js.map} +1 -1
  109. package/ccw/frontend/dist/assets/{TabsNavigation-CLKz7pWP.js → TabsNavigation-jhUge9YV.js} +2 -2
  110. package/ccw/frontend/dist/assets/{TabsNavigation-CLKz7pWP.js.map → TabsNavigation-jhUge9YV.js.map} +1 -1
  111. package/ccw/frontend/dist/assets/{TaskDrawer-BAFvNqF8.js → TaskDrawer-APWpZulm.js} +2 -2
  112. package/ccw/frontend/dist/assets/{TaskDrawer-BAFvNqF8.js.map → TaskDrawer-APWpZulm.js.map} +1 -1
  113. package/ccw/frontend/dist/assets/{TeamPage-C2IsDkAv.js → TeamPage-8OnLsAfJ.js} +2 -2
  114. package/ccw/frontend/dist/assets/{TeamPage-C2IsDkAv.js.map → TeamPage-8OnLsAfJ.js.map} +1 -1
  115. package/ccw/frontend/dist/assets/{TerminalDashboardPage-mgS-iKDm.js → TerminalDashboardPage-Be1hcN4p.js} +2 -2
  116. package/ccw/frontend/dist/assets/{TerminalDashboardPage-mgS-iKDm.js.map → TerminalDashboardPage-Be1hcN4p.js.map} +1 -1
  117. package/ccw/frontend/dist/assets/{archive-CxxK_YIp.js → archive-Bo1tC9yo.js} +2 -2
  118. package/ccw/frontend/dist/assets/{archive-CxxK_YIp.js.map → archive-Bo1tC9yo.js.map} +1 -1
  119. package/ccw/frontend/dist/assets/{archive-restore-DZsrBaeG.js → archive-restore-DYx41yLJ.js} +2 -2
  120. package/ccw/frontend/dist/assets/{archive-restore-DZsrBaeG.js.map → archive-restore-DYx41yLJ.js.map} +1 -1
  121. package/ccw/frontend/dist/assets/{arrow-right-BotRHOhr.js → arrow-right-C6pOqWF1.js} +2 -2
  122. package/ccw/frontend/dist/assets/{arrow-right-BotRHOhr.js.map → arrow-right-C6pOqWF1.js.map} +1 -1
  123. package/ccw/frontend/dist/assets/{bookmark-plus-C0MWZ3S9.js → bookmark-plus-4Qpa6hMJ.js} +2 -2
  124. package/ccw/frontend/dist/assets/{bookmark-plus-C0MWZ3S9.js.map → bookmark-plus-4Qpa6hMJ.js.map} +1 -1
  125. package/ccw/frontend/dist/assets/{bot-BcHFJXqD.js → bot-HXJTmx8r.js} +2 -2
  126. package/ccw/frontend/dist/assets/{bot-BcHFJXqD.js.map → bot-HXJTmx8r.js.map} +1 -1
  127. package/ccw/frontend/dist/assets/{braces-D_p0IZhP.js → braces-Dj2IdY7I.js} +2 -2
  128. package/ccw/frontend/dist/assets/{braces-D_p0IZhP.js.map → braces-Dj2IdY7I.js.map} +1 -1
  129. package/ccw/frontend/dist/assets/{circle-stop-Bt634NUt.js → circle-stop-M29TJKHc.js} +2 -2
  130. package/ccw/frontend/dist/assets/{circle-stop-Bt634NUt.js.map → circle-stop-M29TJKHc.js.map} +1 -1
  131. package/ccw/frontend/dist/assets/{cpu-DjeGKtvT.js → cpu-DX15mzMT.js} +2 -2
  132. package/ccw/frontend/dist/assets/{cpu-DjeGKtvT.js.map → cpu-DX15mzMT.js.map} +1 -1
  133. package/ccw/frontend/dist/assets/{ellipsis-vertical-BmNPuatq.js → ellipsis-vertical-Fs6bysWx.js} +2 -2
  134. package/ccw/frontend/dist/assets/{ellipsis-vertical-BmNPuatq.js.map → ellipsis-vertical-Fs6bysWx.js.map} +1 -1
  135. package/ccw/frontend/dist/assets/{eye-B0-xkP67.js → eye-D6BRlJ1r.js} +2 -2
  136. package/ccw/frontend/dist/assets/{eye-B0-xkP67.js.map → eye-D6BRlJ1r.js.map} +1 -1
  137. package/ccw/frontend/dist/assets/{eye-off-Bz0Fkli3.js → eye-off-DjbIj5iN.js} +2 -2
  138. package/ccw/frontend/dist/assets/{eye-off-Bz0Fkli3.js.map → eye-off-DjbIj5iN.js.map} +1 -1
  139. package/ccw/frontend/dist/assets/{file-json-PUAS4vBm.js → file-json-kg4Lid-H.js} +2 -2
  140. package/ccw/frontend/dist/assets/{file-json-PUAS4vBm.js.map → file-json-kg4Lid-H.js.map} +1 -1
  141. package/ccw/frontend/dist/assets/{file-text-BMeW5-Sb.js → file-text-BMy5pXNV.js} +2 -2
  142. package/ccw/frontend/dist/assets/{file-text-BMeW5-Sb.js.map → file-text-BMy5pXNV.js.map} +1 -1
  143. package/ccw/frontend/dist/assets/{filter-B4wgMvwi.js → filter-BTN-lUUb.js} +2 -2
  144. package/ccw/frontend/dist/assets/{filter-B4wgMvwi.js.map → filter-BTN-lUUb.js.map} +1 -1
  145. package/ccw/frontend/dist/assets/{folder-BSHjjjND.js → folder-ChYtty7V.js} +2 -2
  146. package/ccw/frontend/dist/assets/{folder-BSHjjjND.js.map → folder-ChYtty7V.js.map} +1 -1
  147. package/ccw/frontend/dist/assets/{gauge-7VRNEl22.js → gauge-C0VJbjRb.js} +2 -2
  148. package/ccw/frontend/dist/assets/{gauge-7VRNEl22.js.map → gauge-C0VJbjRb.js.map} +1 -1
  149. package/ccw/frontend/dist/assets/{globe-PvVHBjbW.js → globe-DbNfDXnv.js} +2 -2
  150. package/ccw/frontend/dist/assets/{globe-PvVHBjbW.js.map → globe-DbNfDXnv.js.map} +1 -1
  151. package/ccw/frontend/dist/assets/{grid-3x3-CsmmDlhN.js → grid-3x3-xWcpBNxo.js} +2 -2
  152. package/ccw/frontend/dist/assets/{grid-3x3-CsmmDlhN.js.map → grid-3x3-xWcpBNxo.js.map} +1 -1
  153. package/ccw/frontend/dist/assets/{hard-drive-D88EZgTY.js → hard-drive-BTACzgUO.js} +2 -2
  154. package/ccw/frontend/dist/assets/{hard-drive-D88EZgTY.js.map → hard-drive-BTACzgUO.js.map} +1 -1
  155. package/ccw/frontend/dist/assets/{hash-CZpNRWqt.js → hash-B0dYlmyB.js} +2 -2
  156. package/ccw/frontend/dist/assets/{hash-CZpNRWqt.js.map → hash-B0dYlmyB.js.map} +1 -1
  157. package/ccw/frontend/dist/assets/{history-DxN1mopa.js → history-CRRLu6ck.js} +2 -2
  158. package/ccw/frontend/dist/assets/{history-DxN1mopa.js.map → history-CRRLu6ck.js.map} +1 -1
  159. package/ccw/frontend/dist/assets/{index-BzGEkwIf.js → index-BJIIo0B3.js} +2 -2
  160. package/ccw/frontend/dist/assets/{index-BzGEkwIf.js.map → index-BJIIo0B3.js.map} +1 -1
  161. package/ccw/frontend/dist/assets/{index-DlcRFzFq.js → index-BP-D3Wv6.js} +2 -2
  162. package/ccw/frontend/dist/assets/{index-DlcRFzFq.js.map → index-BP-D3Wv6.js.map} +1 -1
  163. package/ccw/frontend/dist/assets/{index-BUiq2PUK.js → index-B_QzEioU.js} +2 -2
  164. package/ccw/frontend/dist/assets/{index-BUiq2PUK.js.map → index-B_QzEioU.js.map} +1 -1
  165. package/ccw/frontend/dist/assets/{index-C04xeBGC.js → index-BpoRYgiq.js} +3 -3
  166. package/ccw/frontend/dist/assets/{index-C04xeBGC.js.map → index-BpoRYgiq.js.map} +1 -1
  167. package/ccw/frontend/dist/assets/{index-CIoWWkDn.js → index-CeESlrZb.js} +8 -8
  168. package/ccw/frontend/dist/assets/{index-CIoWWkDn.js.map → index-CeESlrZb.js.map} +1 -1
  169. package/ccw/frontend/dist/assets/{index-bSgtHHOF.js → index-DzTbMyaS.js} +4 -4
  170. package/ccw/frontend/dist/assets/{index-bSgtHHOF.js.map → index-DzTbMyaS.js.map} +1 -1
  171. package/ccw/frontend/dist/assets/{layout-grid-BZqfuNG0.js → layout-grid-ZN26YcTM.js} +2 -2
  172. package/ccw/frontend/dist/assets/{layout-grid-BZqfuNG0.js.map → layout-grid-ZN26YcTM.js.map} +1 -1
  173. package/ccw/frontend/dist/assets/{lightbulb-DqJ4OHRL.js → lightbulb-BHrwx9j6.js} +2 -2
  174. package/ccw/frontend/dist/assets/{lightbulb-DqJ4OHRL.js.map → lightbulb-BHrwx9j6.js.map} +1 -1
  175. package/ccw/frontend/dist/assets/{link-2-Bq7fLkC9.js → link-2-zZRu2j4G.js} +2 -2
  176. package/ccw/frontend/dist/assets/{link-2-Bq7fLkC9.js.map → link-2-zZRu2j4G.js.map} +1 -1
  177. package/ccw/frontend/dist/assets/{link-dSLYjCIG.js → link-8lz-HgnZ.js} +2 -2
  178. package/ccw/frontend/dist/assets/{link-dSLYjCIG.js.map → link-8lz-HgnZ.js.map} +1 -1
  179. package/ccw/frontend/dist/assets/{list-Bm_CnR56.js → list-DlablIjt.js} +2 -2
  180. package/ccw/frontend/dist/assets/{list-Bm_CnR56.js.map → list-DlablIjt.js.map} +1 -1
  181. package/ccw/frontend/dist/assets/{map-pin-B5TELa0z.js → map-pin-JAsCUplr.js} +2 -2
  182. package/ccw/frontend/dist/assets/{map-pin-B5TELa0z.js.map → map-pin-JAsCUplr.js.map} +1 -1
  183. package/ccw/frontend/dist/assets/{messages-square-B9sWItdK.js → messages-square-D0iSFb2Y.js} +2 -2
  184. package/ccw/frontend/dist/assets/{messages-square-B9sWItdK.js.map → messages-square-D0iSFb2Y.js.map} +1 -1
  185. package/ccw/frontend/dist/assets/{minimize-2-Z1MYs_Tp.js → minimize-2-BJmkU9CE.js} +2 -2
  186. package/ccw/frontend/dist/assets/{minimize-2-Z1MYs_Tp.js.map → minimize-2-BJmkU9CE.js.map} +1 -1
  187. package/ccw/frontend/dist/assets/{package-EEv4N_Qk.js → package-DYrbBCxk.js} +2 -2
  188. package/ccw/frontend/dist/assets/{package-EEv4N_Qk.js.map → package-DYrbBCxk.js.map} +1 -1
  189. package/ccw/frontend/dist/assets/{plug-CEkvvYXh.js → plug-VG7-23M-.js} +2 -2
  190. package/ccw/frontend/dist/assets/{plug-CEkvvYXh.js.map → plug-VG7-23M-.js.map} +1 -1
  191. package/ccw/frontend/dist/assets/{power-B6Rod0u_.js → power-zPFYNx7u.js} +2 -2
  192. package/ccw/frontend/dist/assets/{power-B6Rod0u_.js.map → power-zPFYNx7u.js.map} +1 -1
  193. package/ccw/frontend/dist/assets/{save-DULWrB2g.js → save-D4XQY9i9.js} +2 -2
  194. package/ccw/frontend/dist/assets/{save-DULWrB2g.js.map → save-D4XQY9i9.js.map} +1 -1
  195. package/ccw/frontend/dist/assets/{send-C4z974WG.js → send-ClSnDjj-.js} +2 -2
  196. package/ccw/frontend/dist/assets/{send-C4z974WG.js.map → send-ClSnDjj-.js.map} +1 -1
  197. package/ccw/frontend/dist/assets/{square-check-big-CqEEw-V0.js → square-check-big-Bffh_MFc.js} +2 -2
  198. package/ccw/frontend/dist/assets/{square-check-big-CqEEw-V0.js.map → square-check-big-Bffh_MFc.js.map} +1 -1
  199. package/ccw/frontend/dist/assets/{square-pen-D-4BrMtQ.js → square-pen-DsOvAvME.js} +2 -2
  200. package/ccw/frontend/dist/assets/{square-pen-D-4BrMtQ.js.map → square-pen-DsOvAvME.js.map} +1 -1
  201. package/ccw/frontend/dist/assets/{star-CDVVM69o.js → star-TLJjvoy2.js} +2 -2
  202. package/ccw/frontend/dist/assets/{star-CDVVM69o.js.map → star-TLJjvoy2.js.map} +1 -1
  203. package/ccw/frontend/dist/assets/{style-CumJ7XC8.js → style-BXKXgpOY.js} +2 -2
  204. package/ccw/frontend/dist/assets/{style-CumJ7XC8.js.map → style-BXKXgpOY.js.map} +1 -1
  205. package/ccw/frontend/dist/assets/{target-Dr9SuV1Y.js → target-FDuAB6SF.js} +2 -2
  206. package/ccw/frontend/dist/assets/{target-Dr9SuV1Y.js.map → target-FDuAB6SF.js.map} +1 -1
  207. package/ccw/frontend/dist/assets/{test-tube-DzDRKk8F.js → test-tube-B9vj-Im8.js} +2 -2
  208. package/ccw/frontend/dist/assets/{test-tube-DzDRKk8F.js.map → test-tube-B9vj-Im8.js.map} +1 -1
  209. package/ccw/frontend/dist/assets/{upload-CihSmIM7.js → upload-CHgTrVN5.js} +2 -2
  210. package/ccw/frontend/dist/assets/{upload-CihSmIM7.js.map → upload-CHgTrVN5.js.map} +1 -1
  211. package/ccw/frontend/dist/assets/{useApiSettings-CevxWX2z.js → useApiSettings-XsHrqf-L.js} +2 -2
  212. package/ccw/frontend/dist/assets/{useApiSettings-CevxWX2z.js.map → useApiSettings-XsHrqf-L.js.map} +1 -1
  213. package/ccw/frontend/dist/assets/{useCli-jVaF89uU.js → useCli-DOlWtux5.js} +2 -2
  214. package/ccw/frontend/dist/assets/{useCli-jVaF89uU.js.map → useCli-DOlWtux5.js.map} +1 -1
  215. package/ccw/frontend/dist/assets/{useCommands-D4uGmNpF.js → useCommands-DazCajiX.js} +2 -2
  216. package/ccw/frontend/dist/assets/{useCommands-D4uGmNpF.js.map → useCommands-DazCajiX.js.map} +1 -1
  217. package/ccw/frontend/dist/assets/{useDebounce-BEYmuZYP.js → useDebounce-Cah-bua6.js} +2 -2
  218. package/ccw/frontend/dist/assets/{useDebounce-BEYmuZYP.js.map → useDebounce-Cah-bua6.js.map} +1 -1
  219. package/ccw/frontend/dist/assets/{useFileExplorer-DEv0DVy7.js → useFileExplorer-B9SCPhhl.js} +2 -2
  220. package/ccw/frontend/dist/assets/{useFileExplorer-DEv0DVy7.js.map → useFileExplorer-B9SCPhhl.js.map} +1 -1
  221. package/ccw/frontend/dist/assets/{useLocale-BRJreqvl.js → useLocale-CmG6pxZe.js} +2 -2
  222. package/ccw/frontend/dist/assets/{useLocale-BRJreqvl.js.map → useLocale-CmG6pxZe.js.map} +1 -1
  223. package/ccw/frontend/dist/assets/{useSkills-lnfeXe9l.js → useSkills-DvEJ0JOv.js} +3 -3
  224. package/ccw/frontend/dist/assets/{useSkills-lnfeXe9l.js.map → useSkills-DvEJ0JOv.js.map} +1 -1
  225. package/ccw/frontend/dist/assets/{useSystemSettings-PlxVdbXI.js → useSystemSettings-BDpwSa04.js} +2 -2
  226. package/ccw/frontend/dist/assets/{useSystemSettings-PlxVdbXI.js.map → useSystemSettings-BDpwSa04.js.map} +1 -1
  227. package/ccw/frontend/dist/assets/{wand-sparkles-BbqUxeEg.js → wand-sparkles-T2KGCRiy.js} +2 -2
  228. package/ccw/frontend/dist/assets/{wand-sparkles-BbqUxeEg.js.map → wand-sparkles-T2KGCRiy.js.map} +1 -1
  229. package/ccw/frontend/dist/index.html +1 -1
  230. package/codex-lens/src/codexlens/cli/commands.py +12 -1
  231. package/codex-lens/src/codexlens/config.py +12 -0
  232. package/codex-lens/src/codexlens/env_config.py +1 -0
  233. package/package.json +1 -1
  234. package/ccw/frontend/dist/assets/CodexLensManagerPage-ClPNashJ.js.map +0 -1
  235. package/ccw/frontend/dist/assets/McpManagerPage-CS2v_1OG.js.map +0 -1
@@ -21,13 +21,14 @@ import { z } from 'zod';
21
21
  import { spawn, execSync } from 'child_process';
22
22
  import { existsSync, readFileSync, statSync } from 'fs';
23
23
  import { dirname, join, resolve } from 'path';
24
- import { ensureReady as ensureCodexLensReady, ensureLiteLLMEmbedderReady, executeCodexLens, getVenvPythonPath, } from './codex-lens.js';
24
+ import { ensureReady as ensureCodexLensReady, checkSemanticStatus, ensureLiteLLMEmbedderReady, executeCodexLens, getVenvPythonPath, } from './codex-lens.js';
25
25
  import { getProjectRoot } from '../utils/path-validator.js';
26
26
  import { getCodexLensDataDir } from '../utils/codexlens-path.js';
27
27
  import { EXEC_TIMEOUTS } from '../utils/exec-constants.js';
28
28
  import { generateRotationEndpoints } from '../config/litellm-api-config-manager.js';
29
29
  // Timing utilities for performance analysis
30
30
  const TIMING_ENABLED = process.env.SMART_SEARCH_TIMING === '1' || process.env.DEBUG?.includes('timing');
31
+ const SEARCH_OUTPUT_MODES = ['full', 'files_only', 'count', 'ace'];
31
32
  function createTimer() {
32
33
  const startTime = performance.now();
33
34
  const marks = [];
@@ -61,7 +62,7 @@ const ParamsSchema = z.object({
61
62
  query: z.string().optional().describe('Content search query (for action="search")'),
62
63
  pattern: z.string().optional().describe('Glob pattern for path matching (for action="find_files")'),
63
64
  mode: z.enum(['fuzzy', 'semantic']).default('fuzzy'),
64
- output_mode: z.enum(['full', 'files_only', 'count']).default('full'),
65
+ output_mode: z.enum(SEARCH_OUTPUT_MODES).default('ace'),
65
66
  path: z.string().optional(),
66
67
  paths: z.array(z.string()).default([]),
67
68
  contextLines: z.number().default(0),
@@ -221,6 +222,7 @@ function readCodexLensSettingsSnapshot() {
221
222
  return {
222
223
  embedding_backend: normalizeEmbeddingBackend(typeof embedding.backend === 'string' ? embedding.backend : undefined),
223
224
  embedding_model: typeof embedding.model === 'string' ? embedding.model : undefined,
225
+ embedding_auto_embed_missing: typeof embedding.auto_embed_missing === 'boolean' ? embedding.auto_embed_missing : undefined,
224
226
  reranker_enabled: typeof reranker.enabled === 'boolean' ? reranker.enabled : undefined,
225
227
  reranker_backend: typeof reranker.backend === 'string' ? reranker.backend : undefined,
226
228
  reranker_model: typeof reranker.model === 'string' ? reranker.model : undefined,
@@ -244,6 +246,13 @@ function stripAnsi(str) {
244
246
  }
245
247
  /** Default maximum content length to return (avoid excessive output) */
246
248
  const DEFAULT_MAX_CONTENT_LENGTH = 200;
249
+ const CODEX_LENS_FTS_COMPATIBILITY_PATTERNS = [
250
+ /UsageError:\s*Got unexpected extra arguments?/i,
251
+ /Option ['"]--method['"] does not take a value/i,
252
+ /TyperArgument\.make_metavar\(\) takes 1 positional argument but 2 were given/i,
253
+ ];
254
+ let codexLensFtsBackendBroken = false;
255
+ const autoEmbedJobs = new Map();
247
256
  /**
248
257
  * Truncate content to specified length with ellipsis
249
258
  * @param content - The content to truncate
@@ -348,12 +357,89 @@ function parseCodexLensJsonOutput(output) {
348
357
  }
349
358
  return null;
350
359
  }
360
+ function isValidRegexPattern(pattern) {
361
+ try {
362
+ new RegExp(pattern);
363
+ return true;
364
+ }
365
+ catch {
366
+ return false;
367
+ }
368
+ }
369
+ function resolveRipgrepQueryMode(query, regex = true, tokenize = true) {
370
+ const tokens = tokenize ? tokenizeQuery(query) : [query];
371
+ if (!regex) {
372
+ return {
373
+ regex: false,
374
+ tokenize,
375
+ tokens,
376
+ literalFallback: false,
377
+ };
378
+ }
379
+ const invalidTokens = tokens.filter((token) => token.length > 0 && !isValidRegexPattern(token));
380
+ if (invalidTokens.length === 0) {
381
+ return {
382
+ regex: true,
383
+ tokenize,
384
+ tokens,
385
+ literalFallback: false,
386
+ };
387
+ }
388
+ const preview = truncateContent(invalidTokens[0], 40);
389
+ return {
390
+ regex: false,
391
+ tokenize,
392
+ tokens,
393
+ literalFallback: true,
394
+ warning: invalidTokens.length === 1
395
+ ? `Query token "${preview}" is not a valid regular expression. Falling back to literal ripgrep matching.`
396
+ : 'Query contains invalid regular expression tokens. Falling back to literal ripgrep matching.',
397
+ };
398
+ }
399
+ function isCodexLensCliCompatibilityError(error) {
400
+ if (!error) {
401
+ return false;
402
+ }
403
+ const cleanError = stripAnsi(error);
404
+ return CODEX_LENS_FTS_COMPATIBILITY_PATTERNS.some((pattern) => pattern.test(cleanError));
405
+ }
406
+ function noteCodexLensFtsCompatibility(error) {
407
+ if (!isCodexLensCliCompatibilityError(error)) {
408
+ return false;
409
+ }
410
+ codexLensFtsBackendBroken = true;
411
+ return true;
412
+ }
413
+ function summarizeBackendError(error) {
414
+ const cleanError = stripAnsi(error || '').trim();
415
+ if (!cleanError) {
416
+ return 'unknown error';
417
+ }
418
+ if (isCodexLensCliCompatibilityError(cleanError)) {
419
+ return 'CodexLens exact search CLI is incompatible with the current Typer/Click runtime';
420
+ }
421
+ const regexSummary = cleanError.match(/error:\s*([^\r\n]+)/i);
422
+ if (/regex parse error/i.test(cleanError) && regexSummary?.[1]) {
423
+ return `invalid regular expression (${regexSummary[1].trim()})`;
424
+ }
425
+ const usageSummary = cleanError.match(/UsageError:\s*([^\r\n]+)/i);
426
+ if (usageSummary?.[1]) {
427
+ return usageSummary[1].trim();
428
+ }
429
+ const firstMeaningfulLine = cleanError
430
+ .split(/\r?\n/)
431
+ .map((line) => line.trim())
432
+ .find((line) => line && !line.startsWith('│') && !line.startsWith('┌') && !line.startsWith('└'));
433
+ return truncateContent(firstMeaningfulLine || cleanError, 180);
434
+ }
351
435
  function mapCodexLensSemanticMatches(data, scope, maxContentLength) {
352
436
  return filterResultsToTargetFile(data.map((item) => {
353
437
  const rawScore = item.score || 0;
354
438
  const similarityScore = rawScore > 0 ? 1 / (1 + rawScore) : 1;
355
439
  return {
356
440
  file: item.path || item.file,
441
+ line: typeof item.line === 'number' ? item.line : undefined,
442
+ column: typeof item.column === 'number' ? item.column : undefined,
357
443
  score: similarityScore,
358
444
  content: truncateContent(item.content || item.excerpt, maxContentLength),
359
445
  symbol: item.symbol || null,
@@ -399,11 +485,11 @@ function hasCentralizedVectorArtifacts(indexRoot) {
399
485
  }
400
486
  function collectBackendError(errors, backendName, backendResult) {
401
487
  if (backendResult.status === 'rejected') {
402
- errors.push(`${backendName}: ${String(backendResult.reason)}`);
488
+ errors.push(`${backendName}: ${summarizeBackendError(String(backendResult.reason))}`);
403
489
  return;
404
490
  }
405
491
  if (!backendResult.value.success) {
406
- errors.push(`${backendName}: ${backendResult.value.error || 'unknown error'}`);
492
+ errors.push(`${backendName}: ${summarizeBackendError(backendResult.value.error)}`);
407
493
  }
408
494
  }
409
495
  function mergeWarnings(...warnings) {
@@ -412,6 +498,75 @@ function mergeWarnings(...warnings) {
412
498
  .map((warning) => warning.trim()))];
413
499
  return merged.length > 0 ? merged.join(' | ') : undefined;
414
500
  }
501
+ function mergeNotes(...notes) {
502
+ const merged = [...new Set(notes
503
+ .filter((note) => typeof note === 'string' && note.trim().length > 0)
504
+ .map((note) => note.trim()))];
505
+ return merged.length > 0 ? merged.join(' | ') : undefined;
506
+ }
507
+ function mergeSuggestions(...groups) {
508
+ const merged = new Map();
509
+ for (const group of groups) {
510
+ for (const suggestion of group ?? []) {
511
+ if (!merged.has(suggestion.command)) {
512
+ merged.set(suggestion.command, suggestion);
513
+ }
514
+ }
515
+ }
516
+ return merged.size > 0 ? [...merged.values()] : undefined;
517
+ }
518
+ function formatSmartSearchCommand(action, pathValue, extraParams = {}) {
519
+ const normalizedPath = pathValue.replace(/\\/g, '/');
520
+ const args = [`action=${JSON.stringify(action)}`, `path=${JSON.stringify(normalizedPath)}`];
521
+ for (const [key, value] of Object.entries(extraParams)) {
522
+ if (value === undefined) {
523
+ continue;
524
+ }
525
+ args.push(`${key}=${JSON.stringify(value)}`);
526
+ }
527
+ return `smart_search(${args.join(', ')})`;
528
+ }
529
+ function isAutoEmbedMissingEnabled(config) {
530
+ return config?.embedding_auto_embed_missing !== false;
531
+ }
532
+ function buildIndexSuggestions(indexStatus, scope) {
533
+ const suggestions = [];
534
+ if (!indexStatus.indexed) {
535
+ suggestions.push({
536
+ title: 'Initialize index',
537
+ command: formatSmartSearchCommand('init', scope.workingDirectory),
538
+ reason: 'No CodexLens index exists for this path yet.',
539
+ });
540
+ suggestions.push({
541
+ title: 'Check index status',
542
+ command: formatSmartSearchCommand('status', scope.workingDirectory),
543
+ reason: 'Verify whether the target path is mapped to the expected CodexLens project root.',
544
+ });
545
+ return suggestions;
546
+ }
547
+ if (!indexStatus.has_embeddings) {
548
+ suggestions.push({
549
+ title: 'Generate embeddings',
550
+ command: formatSmartSearchCommand('embed', scope.workingDirectory),
551
+ reason: 'The index exists, but semantic/vector retrieval is unavailable until embeddings are generated.',
552
+ });
553
+ }
554
+ else if ((indexStatus.embeddings_coverage_percent ?? 0) < 50) {
555
+ suggestions.push({
556
+ title: 'Rebuild embeddings',
557
+ command: formatSmartSearchCommand('embed', scope.workingDirectory, { force: true }),
558
+ reason: `Embedding coverage is only ${(indexStatus.embeddings_coverage_percent ?? 0).toFixed(1)}%, so semantic search quality is degraded.`,
559
+ });
560
+ }
561
+ if (indexStatus.warning?.includes('Failed to parse index status')) {
562
+ suggestions.push({
563
+ title: 'Re-check status',
564
+ command: formatSmartSearchCommand('status', scope.workingDirectory),
565
+ reason: 'The index health payload could not be parsed cleanly.',
566
+ });
567
+ }
568
+ return suggestions.length > 0 ? suggestions : undefined;
569
+ }
415
570
  /**
416
571
  * Check if CodexLens index exists for current directory
417
572
  * @param path - Directory path to check
@@ -437,12 +592,15 @@ async function checkIndexStatus(path = '.') {
437
592
  ...settingsConfig,
438
593
  config_file: configData.config_file,
439
594
  index_dir: configData.index_dir,
440
- embedding_backend: configData.embedding_backend,
441
- embedding_model: configData.embedding_model,
442
- reranker_enabled: configData.reranker_enabled,
443
- reranker_backend: configData.reranker_backend,
444
- reranker_model: configData.reranker_model,
445
- reranker_top_k: configData.reranker_top_k,
595
+ embedding_backend: normalizeEmbeddingBackend(configData.embedding_backend) ?? settingsConfig.embedding_backend,
596
+ embedding_model: typeof configData.embedding_model === 'string' ? configData.embedding_model : settingsConfig.embedding_model,
597
+ embedding_auto_embed_missing: typeof configData.embedding_auto_embed_missing === 'boolean'
598
+ ? configData.embedding_auto_embed_missing
599
+ : settingsConfig.embedding_auto_embed_missing,
600
+ reranker_enabled: typeof configData.reranker_enabled === 'boolean' ? configData.reranker_enabled : settingsConfig.reranker_enabled,
601
+ reranker_backend: typeof configData.reranker_backend === 'string' ? configData.reranker_backend : settingsConfig.reranker_backend,
602
+ reranker_model: typeof configData.reranker_model === 'string' ? configData.reranker_model : settingsConfig.reranker_model,
603
+ reranker_top_k: typeof configData.reranker_top_k === 'number' ? configData.reranker_top_k : settingsConfig.reranker_top_k,
446
604
  };
447
605
  }
448
606
  catch {
@@ -641,6 +799,7 @@ function checkToolAvailability(toolName) {
641
799
  */
642
800
  function buildRipgrepCommand(params) {
643
801
  const { query, paths = ['.'], contextLines = 0, maxResults = 10, includeHidden = false, regex = false, caseSensitive = true, tokenize = true } = params;
802
+ const queryMode = resolveRipgrepQueryMode(query, regex, tokenize);
644
803
  const args = [
645
804
  '-n',
646
805
  '--color=never',
@@ -663,13 +822,12 @@ function buildRipgrepCommand(params) {
663
822
  if (includeHidden) {
664
823
  args.push('--hidden');
665
824
  }
666
- // Tokenize query for multi-word OR matching
667
- const tokens = tokenize ? tokenizeQuery(query) : [query];
825
+ const { tokens } = queryMode;
668
826
  if (tokens.length > 1) {
669
827
  // Multi-token: use multiple -e patterns (OR matching)
670
828
  // Each token is escaped for regex safety unless regex mode is enabled
671
829
  for (const token of tokens) {
672
- if (regex) {
830
+ if (queryMode.regex) {
673
831
  args.push('-e', token);
674
832
  }
675
833
  else {
@@ -681,7 +839,7 @@ function buildRipgrepCommand(params) {
681
839
  }
682
840
  else {
683
841
  // Single token or no tokenization: use original behavior
684
- if (regex) {
842
+ if (queryMode.regex) {
685
843
  args.push('-e', query);
686
844
  }
687
845
  else {
@@ -689,7 +847,123 @@ function buildRipgrepCommand(params) {
689
847
  }
690
848
  }
691
849
  args.push(...paths);
692
- return { command: 'rg', args, tokens };
850
+ return {
851
+ command: 'rg',
852
+ args,
853
+ tokens,
854
+ warning: queryMode.warning,
855
+ literalFallback: queryMode.literalFallback,
856
+ regex: queryMode.regex,
857
+ };
858
+ }
859
+ function finalizeRipgrepChunk(accumulator) {
860
+ if (!accumulator || accumulator.matchCount === 0 || accumulator.chunkLines.length === 0) {
861
+ return null;
862
+ }
863
+ const firstLine = accumulator.chunkLines[0]?.line ?? accumulator.firstMatchLine ?? 1;
864
+ const lastLine = accumulator.chunkLines[accumulator.chunkLines.length - 1]?.line ?? accumulator.firstMatchLine ?? firstLine;
865
+ return {
866
+ file: accumulator.file,
867
+ line: accumulator.firstMatchLine ?? firstLine,
868
+ endLine: lastLine,
869
+ column: accumulator.firstMatchColumn ?? 1,
870
+ content: accumulator.chunkLines.map((line) => line.text).join('\n').trim(),
871
+ chunkLines: [...accumulator.chunkLines],
872
+ };
873
+ }
874
+ function parseRipgrepJsonResults(stdout, effectiveLimit) {
875
+ const allResults = [];
876
+ const activeChunks = new Map();
877
+ const lines = stdout.split('\n').filter((line) => line.trim());
878
+ let resultLimitReached = false;
879
+ const flushChunk = (file) => {
880
+ const finalized = finalizeRipgrepChunk(activeChunks.get(file));
881
+ activeChunks.delete(file);
882
+ if (!finalized) {
883
+ return;
884
+ }
885
+ allResults.push(finalized);
886
+ if (allResults.length >= effectiveLimit) {
887
+ resultLimitReached = true;
888
+ }
889
+ };
890
+ for (const line of lines) {
891
+ if (resultLimitReached) {
892
+ break;
893
+ }
894
+ try {
895
+ const item = JSON.parse(line);
896
+ if (item.type !== 'match' && item.type !== 'context' && item.type !== 'end') {
897
+ continue;
898
+ }
899
+ const file = item.data?.path?.text;
900
+ if (!file) {
901
+ continue;
902
+ }
903
+ if (item.type === 'end') {
904
+ flushChunk(file);
905
+ continue;
906
+ }
907
+ const lineNumber = typeof item.data?.line_number === 'number' ? item.data.line_number : undefined;
908
+ const rawText = typeof item.data?.lines?.text === 'string'
909
+ ? item.data.lines.text.replace(/\r?\n$/, '')
910
+ : '';
911
+ if (lineNumber === undefined) {
912
+ continue;
913
+ }
914
+ let current = activeChunks.get(file);
915
+ const isContiguous = current && current.lastLine !== undefined && lineNumber <= current.lastLine + 1;
916
+ if (!current || !isContiguous) {
917
+ if (current) {
918
+ flushChunk(file);
919
+ if (resultLimitReached) {
920
+ break;
921
+ }
922
+ }
923
+ current = {
924
+ file,
925
+ chunkLines: [],
926
+ matchCount: 0,
927
+ };
928
+ activeChunks.set(file, current);
929
+ }
930
+ const previousLine = current.chunkLines[current.chunkLines.length - 1];
931
+ const duplicateLine = previousLine && previousLine.line === lineNumber && previousLine.text === rawText;
932
+ if (!duplicateLine) {
933
+ current.chunkLines.push({
934
+ line: lineNumber,
935
+ text: rawText,
936
+ isMatch: item.type === 'match',
937
+ });
938
+ }
939
+ else if (item.type === 'match') {
940
+ previousLine.isMatch = true;
941
+ }
942
+ if (item.type === 'match') {
943
+ current.matchCount += 1;
944
+ if (current.firstMatchLine === undefined) {
945
+ current.firstMatchLine = lineNumber;
946
+ current.firstMatchColumn =
947
+ item.data.submatches && item.data.submatches[0]
948
+ ? item.data.submatches[0].start + 1
949
+ : 1;
950
+ }
951
+ }
952
+ current.lastLine = lineNumber;
953
+ }
954
+ catch {
955
+ continue;
956
+ }
957
+ }
958
+ if (!resultLimitReached) {
959
+ for (const file of [...activeChunks.keys()]) {
960
+ flushChunk(file);
961
+ if (resultLimitReached) {
962
+ break;
963
+ }
964
+ }
965
+ }
966
+ return { results: allResults.slice(0, effectiveLimit), resultLimitReached };
693
967
  }
694
968
  function normalizeEmbeddingBackend(backend) {
695
969
  if (!backend) {
@@ -774,50 +1048,63 @@ function extractEmbedJsonLine(stdout) {
774
1048
  .filter((line) => !line.startsWith(EMBED_PROGRESS_PREFIX));
775
1049
  return [...lines].reverse().find((line) => line.startsWith('{') && line.endsWith('}'));
776
1050
  }
777
- async function executeEmbeddingsViaPython(params) {
1051
+ function buildEmbeddingPythonCode(params) {
778
1052
  const { projectPath, backend, model, force, maxWorkers, endpoints = [] } = params;
779
- const pythonCode = `
780
- import json
781
- import sys
782
- from pathlib import Path
783
- from codexlens.storage.registry import RegistryStore
784
- from codexlens.cli.embedding_manager import generate_dense_embeddings_centralized
785
-
786
- target_path = Path(r"__PROJECT_PATH__").expanduser().resolve()
787
- backend = __BACKEND__
788
- model = __MODEL__
789
- force = __FORCE__
790
- max_workers = __MAX_WORKERS__
791
- endpoints = json.loads(r'''__ENDPOINTS_JSON__''')
792
-
793
- def progress_update(message: str):
794
- print("__CCW_EMBED_PROGRESS__" + str(message), flush=True)
795
-
796
- registry = RegistryStore()
797
- registry.initialize()
798
- try:
799
- project = registry.get_project(target_path)
800
- if project is None:
801
- print(json.dumps({"success": False, "error": f"No index found for: {target_path}"}), flush=True)
802
- sys.exit(1)
803
-
804
- index_root = Path(project.index_root)
805
- result = generate_dense_embeddings_centralized(
806
- index_root,
807
- embedding_backend=backend,
808
- model_profile=model,
809
- force=force,
810
- use_gpu=True,
811
- max_workers=max_workers,
812
- endpoints=endpoints if endpoints else None,
813
- progress_callback=progress_update,
814
- )
815
-
816
- print(json.dumps(result), flush=True)
817
- if not result.get("success"):
818
- sys.exit(1)
819
- finally:
820
- registry.close()
1053
+ return `
1054
+ import json
1055
+ import sys
1056
+ from pathlib import Path
1057
+ from codexlens.storage.path_mapper import PathMapper
1058
+ from codexlens.storage.registry import RegistryStore
1059
+ from codexlens.cli.embedding_manager import generate_dense_embeddings_centralized
1060
+
1061
+ target_path = Path(r"__PROJECT_PATH__").expanduser().resolve()
1062
+ backend = __BACKEND__
1063
+ model = __MODEL__
1064
+ force = __FORCE__
1065
+ max_workers = __MAX_WORKERS__
1066
+ endpoints = json.loads(r'''__ENDPOINTS_JSON__''')
1067
+
1068
+ def progress_update(message: str):
1069
+ print("__CCW_EMBED_PROGRESS__" + str(message), flush=True)
1070
+
1071
+ registry = RegistryStore()
1072
+ registry.initialize()
1073
+ try:
1074
+ project = registry.get_project(target_path)
1075
+ index_root = None
1076
+ if project is not None:
1077
+ index_root = Path(project.index_root)
1078
+ else:
1079
+ mapper = PathMapper()
1080
+ index_db = mapper.source_to_index_db(target_path)
1081
+ if index_db.exists():
1082
+ index_root = index_db.parent
1083
+ else:
1084
+ nearest = registry.find_nearest_index(target_path)
1085
+ if nearest is not None:
1086
+ index_root = Path(nearest.index_path).parent
1087
+
1088
+ if index_root is None:
1089
+ print(json.dumps({"success": False, "error": f"No index found for: {target_path}"}), flush=True)
1090
+ sys.exit(1)
1091
+
1092
+ result = generate_dense_embeddings_centralized(
1093
+ index_root,
1094
+ embedding_backend=backend,
1095
+ model_profile=model,
1096
+ force=force,
1097
+ use_gpu=True,
1098
+ max_workers=max_workers,
1099
+ endpoints=endpoints if endpoints else None,
1100
+ progress_callback=progress_update,
1101
+ )
1102
+
1103
+ print(json.dumps(result), flush=True)
1104
+ if not result.get("success"):
1105
+ sys.exit(1)
1106
+ finally:
1107
+ registry.close()
821
1108
  `
822
1109
  .replace('__PROJECT_PATH__', projectPath.replace(/\\/g, '\\\\'))
823
1110
  .replace('__BACKEND__', backend ? JSON.stringify(backend) : 'None')
@@ -825,6 +1112,90 @@ finally:
825
1112
  .replace('__FORCE__', force ? 'True' : 'False')
826
1113
  .replace('__MAX_WORKERS__', typeof maxWorkers === 'number' ? String(Math.max(1, Math.floor(maxWorkers))) : 'None')
827
1114
  .replace('__ENDPOINTS_JSON__', JSON.stringify(endpoints).replace(/\\/g, '\\\\').replace(/'''/g, "\\'\\'\\'"));
1115
+ }
1116
+ function spawnBackgroundEmbeddingsViaPython(params) {
1117
+ const { projectPath, backend, model } = params;
1118
+ try {
1119
+ const child = spawn(getVenvPythonPath(), ['-c', buildEmbeddingPythonCode(params)], {
1120
+ cwd: projectPath,
1121
+ shell: false,
1122
+ detached: true,
1123
+ stdio: 'ignore',
1124
+ windowsHide: true,
1125
+ env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
1126
+ });
1127
+ autoEmbedJobs.set(projectPath, {
1128
+ startedAt: Date.now(),
1129
+ backend,
1130
+ model,
1131
+ });
1132
+ const cleanup = () => {
1133
+ autoEmbedJobs.delete(projectPath);
1134
+ };
1135
+ child.on('error', cleanup);
1136
+ child.on('close', cleanup);
1137
+ child.unref();
1138
+ return { success: true };
1139
+ }
1140
+ catch (error) {
1141
+ return {
1142
+ success: false,
1143
+ error: error instanceof Error ? error.message : String(error),
1144
+ };
1145
+ }
1146
+ }
1147
+ async function maybeStartBackgroundAutoEmbed(scope, indexStatus) {
1148
+ if (!indexStatus.indexed || indexStatus.has_embeddings) {
1149
+ return {};
1150
+ }
1151
+ if (!isAutoEmbedMissingEnabled(indexStatus.config)) {
1152
+ return {
1153
+ note: 'Automatic embedding warmup is disabled by CODEXLENS_AUTO_EMBED_MISSING=false.',
1154
+ };
1155
+ }
1156
+ if (autoEmbedJobs.has(scope.workingDirectory)) {
1157
+ return {
1158
+ note: 'Background embedding build is already running for this path.',
1159
+ };
1160
+ }
1161
+ const backend = normalizeEmbeddingBackend(indexStatus.config?.embedding_backend) ?? 'fastembed';
1162
+ const model = indexStatus.config?.embedding_model?.trim() || undefined;
1163
+ const semanticStatus = await checkSemanticStatus();
1164
+ if (!semanticStatus.available) {
1165
+ return {
1166
+ warning: 'Automatic embedding warmup skipped because semantic dependencies are not ready.',
1167
+ };
1168
+ }
1169
+ if (backend === 'litellm' && !semanticStatus.litellmAvailable) {
1170
+ return {
1171
+ warning: 'Automatic embedding warmup skipped because the LiteLLM embedder is not ready.',
1172
+ };
1173
+ }
1174
+ const endpoints = resolveEmbeddingEndpoints(backend);
1175
+ const configuredApiMaxWorkers = indexStatus.config?.api_max_workers;
1176
+ const effectiveApiMaxWorkers = typeof configuredApiMaxWorkers === 'number'
1177
+ ? Math.max(1, Math.floor(configuredApiMaxWorkers))
1178
+ : resolveApiWorkerCount(undefined, backend, endpoints);
1179
+ const spawned = spawnBackgroundEmbeddingsViaPython({
1180
+ projectPath: scope.workingDirectory,
1181
+ backend,
1182
+ model,
1183
+ force: false,
1184
+ maxWorkers: effectiveApiMaxWorkers,
1185
+ endpoints,
1186
+ });
1187
+ if (!spawned.success) {
1188
+ return {
1189
+ warning: `Automatic embedding warmup could not start: ${spawned.error}`,
1190
+ };
1191
+ }
1192
+ return {
1193
+ note: 'Background embedding build started for this path. Re-run semantic search shortly for vector results.',
1194
+ };
1195
+ }
1196
+ async function executeEmbeddingsViaPython(params) {
1197
+ const { projectPath } = params;
1198
+ const pythonCode = buildEmbeddingPythonCode(params);
828
1199
  return await new Promise((resolve) => {
829
1200
  const child = spawn(getVenvPythonPath(), ['-c', pythonCode], {
830
1201
  cwd: projectPath,
@@ -1040,6 +1411,7 @@ async function executeStatusAction(params) {
1040
1411
  // Embedding backend info
1041
1412
  const embeddingType = cfg.embedding_backend === 'litellm' ? 'API' : 'Local';
1042
1413
  statusParts.push(`Embedding: ${embeddingType} (${cfg.embedding_model || 'default'})`);
1414
+ statusParts.push(`Auto Embed Missing: ${isAutoEmbedMissingEnabled(cfg) ? 'on' : 'off'}`);
1043
1415
  if (typeof cfg.api_max_workers === 'number') {
1044
1416
  statusParts.push(`API Workers: ${cfg.api_max_workers}`);
1045
1417
  }
@@ -1062,6 +1434,12 @@ async function executeStatusAction(params) {
1062
1434
  success: true,
1063
1435
  status: indexStatus,
1064
1436
  message: indexStatus.warning || statusParts.join(' | '),
1437
+ metadata: {
1438
+ action: 'status',
1439
+ path: scope.workingDirectory,
1440
+ warning: indexStatus.warning,
1441
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1442
+ },
1065
1443
  };
1066
1444
  }
1067
1445
  /**
@@ -1180,7 +1558,7 @@ async function executeWatchAction(params) {
1180
1558
  * Runs both exact (FTS) and ripgrep searches in parallel, merges and ranks results
1181
1559
  */
1182
1560
  async function executeFuzzyMode(params) {
1183
- const { query, path = '.', maxResults = 5, extraFilesCount = 10, codeOnly = true, withDoc = false, excludeExtensions } = params;
1561
+ const { query, path = '.', maxResults = 5, extraFilesCount = 10, codeOnly = true, withDoc = false, excludeExtensions, regex = true, tokenize = true } = params;
1184
1562
  // withDoc overrides codeOnly
1185
1563
  const effectiveCodeOnly = withDoc ? false : codeOnly;
1186
1564
  if (!query) {
@@ -1190,12 +1568,34 @@ async function executeFuzzyMode(params) {
1190
1568
  };
1191
1569
  }
1192
1570
  const timer = createTimer();
1571
+ const ftsWasBroken = codexLensFtsBackendBroken;
1572
+ const ripgrepQueryMode = resolveRipgrepQueryMode(query, regex, tokenize);
1573
+ const fuzzyWarnings = [];
1574
+ let skipExactReason;
1575
+ if (ripgrepQueryMode.literalFallback) {
1576
+ skipExactReason = 'Skipped CodexLens FTS backend for a literal code-pattern query; using ripgrep literal matching.';
1577
+ }
1578
+ else if (codexLensFtsBackendBroken) {
1579
+ skipExactReason = 'CodexLens FTS backend disabled for this process due to CLI compatibility errors.';
1580
+ }
1193
1581
  // Run both searches in parallel
1194
1582
  const [ftsResult, ripgrepResult] = await Promise.allSettled([
1195
- executeCodexLensExactMode(params),
1583
+ skipExactReason
1584
+ ? Promise.resolve({ success: false, error: skipExactReason })
1585
+ : executeCodexLensExactMode(params),
1196
1586
  executeRipgrepMode(params),
1197
1587
  ]);
1198
1588
  timer.mark('parallel_search');
1589
+ if (!skipExactReason && !ftsWasBroken && codexLensFtsBackendBroken) {
1590
+ fuzzyWarnings.push('CodexLens FTS backend is incompatible with the current CLI runtime. Falling back to ripgrep results.');
1591
+ }
1592
+ if (skipExactReason) {
1593
+ fuzzyWarnings.push(skipExactReason);
1594
+ }
1595
+ if (ripgrepResult.status === 'fulfilled' && ripgrepResult.value.metadata?.warning) {
1596
+ fuzzyWarnings.push(String(ripgrepResult.value.metadata.warning));
1597
+ }
1598
+ const mergedSuggestions = mergeSuggestions(ftsResult.status === 'fulfilled' ? ftsResult.value.metadata?.suggestions : undefined, ripgrepResult.status === 'fulfilled' ? ripgrepResult.value.metadata?.suggestions : undefined);
1199
1599
  // Collect results from both sources
1200
1600
  const resultsMap = new Map();
1201
1601
  // Add FTS results if successful
@@ -1228,8 +1628,10 @@ async function executeFuzzyMode(params) {
1228
1628
  const normalizedResults = filteredFusedResults.map((item) => ({
1229
1629
  file: item.file || item.path,
1230
1630
  line: item.line || 0,
1631
+ endLine: item.endLine || item.line || 0,
1231
1632
  column: item.column || 0,
1232
1633
  content: item.content || '',
1634
+ chunkLines: Array.isArray(item.chunkLines) ? item.chunkLines : undefined,
1233
1635
  score: item.fusion_score || 0,
1234
1636
  matchCount: item.matchCount,
1235
1637
  matchScore: item.matchScore,
@@ -1239,16 +1641,21 @@ async function executeFuzzyMode(params) {
1239
1641
  // Log timing
1240
1642
  timer.log();
1241
1643
  const timings = timer.getTimings();
1644
+ const usingExactResults = resultsMap.has('exact');
1242
1645
  return {
1243
1646
  success: true,
1244
1647
  results,
1245
1648
  extra_files: extra_files.length > 0 ? extra_files : undefined,
1246
1649
  metadata: {
1247
1650
  mode: 'fuzzy',
1248
- backend: 'fts+ripgrep',
1651
+ backend: usingExactResults ? 'fts+ripgrep' : 'ripgrep',
1249
1652
  count: results.length,
1250
1653
  query,
1251
- note: `Fuzzy search using RRF fusion of FTS and ripgrep (weights: exact=${fusionWeights.exact}, ripgrep=${fusionWeights.ripgrep})`,
1654
+ note: usingExactResults
1655
+ ? `Fuzzy search using RRF fusion of FTS and ripgrep (weights: exact=${fusionWeights.exact}, ripgrep=${fusionWeights.ripgrep})`
1656
+ : 'Fuzzy search resolved using ripgrep only.',
1657
+ warning: mergeWarnings(...fuzzyWarnings),
1658
+ suggestions: mergedSuggestions,
1252
1659
  timing: TIMING_ENABLED ? timings : undefined,
1253
1660
  },
1254
1661
  };
@@ -1289,19 +1696,10 @@ async function executeAutoMode(params) {
1289
1696
  break;
1290
1697
  }
1291
1698
  // Add classification metadata
1292
- if (result.metadata) {
1293
- result.metadata.classified_as = classification.mode;
1294
- result.metadata.confidence = classification.confidence;
1295
- result.metadata.reasoning = classification.reasoning;
1296
- result.metadata.embeddings_coverage_percent = indexStatus.embeddings_coverage_percent;
1297
- result.metadata.index_status = indexStatus.indexed
1298
- ? (indexStatus.has_embeddings ? 'indexed' : 'partial')
1299
- : 'not_indexed';
1300
- // Add warning if needed
1301
- if (indexStatus.warning) {
1302
- result.metadata.warning = indexStatus.warning;
1303
- }
1304
- }
1699
+ result.metadata = enrichMetadataWithIndexStatus(result.metadata, indexStatus, scope);
1700
+ result.metadata.classified_as = classification.mode;
1701
+ result.metadata.confidence = classification.confidence;
1702
+ result.metadata.reasoning = classification.reasoning;
1305
1703
  return result;
1306
1704
  }
1307
1705
  /**
@@ -1337,9 +1735,10 @@ async function executeRipgrepMode(params) {
1337
1735
  const args = ['search', query, '--limit', totalToFetch.toString(), '--method', 'fts', '--json'];
1338
1736
  const result = await executeCodexLens(args, { cwd: scope.workingDirectory });
1339
1737
  if (!result.success) {
1738
+ noteCodexLensFtsCompatibility(result.error);
1340
1739
  return {
1341
1740
  success: false,
1342
- error: result.error,
1741
+ error: summarizeBackendError(result.error),
1343
1742
  metadata: {
1344
1743
  mode: 'ripgrep',
1345
1744
  backend: 'codexlens-fallback',
@@ -1380,7 +1779,7 @@ async function executeRipgrepMode(params) {
1380
1779
  };
1381
1780
  }
1382
1781
  // Use ripgrep - request more results to support split
1383
- const { command, args, tokens } = buildRipgrepCommand({
1782
+ const { command, args, tokens, warning: queryModeWarning } = buildRipgrepCommand({
1384
1783
  query,
1385
1784
  paths: scope.searchPaths,
1386
1785
  contextLines,
@@ -1405,34 +1804,11 @@ async function executeRipgrepMode(params) {
1405
1804
  stderr += data.toString();
1406
1805
  });
1407
1806
  child.on('close', (code) => {
1408
- const allResults = [];
1409
- const lines = stdout.split('\n').filter((line) => line.trim());
1410
1807
  // Limit total results to prevent memory overflow (--max-count only limits per-file)
1411
1808
  const effectiveLimit = totalToFetch > 0 ? totalToFetch : 500;
1412
- for (const line of lines) {
1413
- // Stop collecting if we've reached the limit
1414
- if (allResults.length >= effectiveLimit) {
1415
- resultLimitReached = true;
1416
- break;
1417
- }
1418
- try {
1419
- const item = JSON.parse(line);
1420
- if (item.type === 'match') {
1421
- const match = {
1422
- file: item.data.path.text,
1423
- line: item.data.line_number,
1424
- column: item.data.submatches && item.data.submatches[0]
1425
- ? item.data.submatches[0].start + 1
1426
- : 1,
1427
- content: item.data.lines.text.trim(),
1428
- };
1429
- allResults.push(match);
1430
- }
1431
- }
1432
- catch {
1433
- continue;
1434
- }
1435
- }
1809
+ const parsedResults = parseRipgrepJsonResults(stdout, effectiveLimit);
1810
+ const allResults = parsedResults.results;
1811
+ resultLimitReached = parsedResults.resultLimitReached;
1436
1812
  // Handle Windows device file errors gracefully (os error 1)
1437
1813
  // If we have results despite the error, return them as partial success
1438
1814
  const isWindowsDeviceError = stderr.includes('os error 1') || stderr.includes('函数不正确');
@@ -1446,6 +1822,9 @@ async function executeRipgrepMode(params) {
1446
1822
  const { results, extra_files } = splitResultsWithExtraFiles(filteredResults, maxResults, extraFilesCount);
1447
1823
  // Build warning message for various conditions
1448
1824
  const warnings = [];
1825
+ if (queryModeWarning) {
1826
+ warnings.push(queryModeWarning);
1827
+ }
1449
1828
  if (resultLimitReached) {
1450
1829
  warnings.push(`Result limit reached (${effectiveLimit}). Use a more specific query or increase limit.`);
1451
1830
  }
@@ -1539,15 +1918,17 @@ async function executeCodexLensExactMode(params) {
1539
1918
  }
1540
1919
  const result = await executeCodexLens(args, { cwd: scope.workingDirectory });
1541
1920
  if (!result.success) {
1921
+ noteCodexLensFtsCompatibility(result.error);
1542
1922
  return {
1543
1923
  success: false,
1544
- error: result.error,
1924
+ error: summarizeBackendError(result.error),
1545
1925
  metadata: {
1546
1926
  mode: 'exact',
1547
1927
  backend: 'codexlens',
1548
1928
  count: 0,
1549
1929
  query,
1550
1930
  warning: mergeWarnings(indexStatus.warning, result.warning),
1931
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1551
1932
  },
1552
1933
  };
1553
1934
  }
@@ -1611,6 +1992,7 @@ async function executeCodexLensExactMode(params) {
1611
1992
  warning: mergeWarnings(indexStatus.warning, fuzzyResult.warning),
1612
1993
  note: 'No exact matches found, showing fuzzy results',
1613
1994
  fallback: 'fuzzy',
1995
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1614
1996
  },
1615
1997
  };
1616
1998
  }
@@ -1628,6 +2010,7 @@ async function executeCodexLensExactMode(params) {
1628
2010
  count: results.length,
1629
2011
  query,
1630
2012
  warning: mergeWarnings(indexStatus.warning, result.warning),
2013
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1631
2014
  },
1632
2015
  };
1633
2016
  }
@@ -1690,6 +2073,7 @@ async function executeHybridMode(params) {
1690
2073
  count: 0,
1691
2074
  query,
1692
2075
  warning: mergeWarnings(indexStatus.warning, result.warning),
2076
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1693
2077
  },
1694
2078
  };
1695
2079
  }
@@ -1732,6 +2116,7 @@ async function executeHybridMode(params) {
1732
2116
  count: 0,
1733
2117
  query,
1734
2118
  warning: mergeWarnings(indexStatus.warning, result.warning, 'Failed to parse JSON output'),
2119
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1735
2120
  },
1736
2121
  };
1737
2122
  }
@@ -1760,6 +2145,7 @@ async function executeHybridMode(params) {
1760
2145
  query,
1761
2146
  note,
1762
2147
  warning: mergeWarnings(indexStatus.warning, result.warning),
2148
+ suggestions: buildIndexSuggestions(indexStatus, scope),
1763
2149
  suggested_weights: getRRFWeights(query),
1764
2150
  timing: TIMING_ENABLED ? timings : undefined,
1765
2151
  },
@@ -1986,30 +2372,100 @@ function filterDominantBaselineScores(results) {
1986
2372
  * Reference: codex-lens/src/codexlens/search/ranking.py
1987
2373
  * Formula: score(d) = Σ weight_source / (k + rank_source(d))
1988
2374
  */
2375
+ function normalizeFusionSnippet(value) {
2376
+ if (typeof value !== 'string') {
2377
+ return undefined;
2378
+ }
2379
+ const normalized = value.replace(/\s+/g, ' ').trim();
2380
+ return normalized ? normalized.slice(0, 240) : undefined;
2381
+ }
2382
+ function buildFusionIdentity(result) {
2383
+ const path = typeof result?.file === 'string'
2384
+ ? result.file
2385
+ : typeof result?.path === 'string'
2386
+ ? result.path
2387
+ : undefined;
2388
+ if (!path) {
2389
+ return null;
2390
+ }
2391
+ const line = typeof result?.line === 'number' && Number.isFinite(result.line)
2392
+ ? result.line
2393
+ : undefined;
2394
+ const endLine = typeof result?.endLine === 'number' && Number.isFinite(result.endLine)
2395
+ ? result.endLine
2396
+ : line;
2397
+ const column = typeof result?.column === 'number' && Number.isFinite(result.column)
2398
+ ? result.column
2399
+ : undefined;
2400
+ if (line !== undefined) {
2401
+ return `${path}#L${line}-${endLine ?? line}:C${column ?? 0}`;
2402
+ }
2403
+ const symbol = typeof result?.symbol === 'string' && result.symbol.trim()
2404
+ ? result.symbol.trim()
2405
+ : undefined;
2406
+ const snippet = normalizeFusionSnippet(result?.content);
2407
+ if (symbol && snippet) {
2408
+ return `${path}::${symbol}::${snippet}`;
2409
+ }
2410
+ if (snippet) {
2411
+ return `${path}::${snippet}`;
2412
+ }
2413
+ if (symbol) {
2414
+ return `${path}::${symbol}`;
2415
+ }
2416
+ return path;
2417
+ }
2418
+ function scoreFusionRepresentative(result) {
2419
+ let score = 0;
2420
+ if (typeof result?.line === 'number' && Number.isFinite(result.line)) {
2421
+ score += 1000;
2422
+ }
2423
+ if (typeof result?.endLine === 'number' && Number.isFinite(result.endLine)) {
2424
+ score += 250;
2425
+ }
2426
+ if (typeof result?.column === 'number' && Number.isFinite(result.column)) {
2427
+ score += 50;
2428
+ }
2429
+ if (Array.isArray(result?.chunkLines) && result.chunkLines.length > 0) {
2430
+ score += 500 + result.chunkLines.length;
2431
+ }
2432
+ if (typeof result?.symbol === 'string' && result.symbol.trim()) {
2433
+ score += 50;
2434
+ }
2435
+ if (typeof result?.content === 'string') {
2436
+ score += Math.min(result.content.length, 200);
2437
+ }
2438
+ return score;
2439
+ }
1989
2440
  function applyRRFFusion(resultsMap, weightsOrQuery, limit, k = 60) {
1990
2441
  const weights = typeof weightsOrQuery === 'string' ? getRRFWeights(weightsOrQuery) : weightsOrQuery;
1991
- const pathScores = new Map();
2442
+ const fusedScores = new Map();
1992
2443
  resultsMap.forEach((results, source) => {
1993
2444
  const weight = weights[source] || 0;
1994
2445
  if (weight === 0 || !results)
1995
2446
  return;
1996
2447
  results.forEach((result, rank) => {
1997
- const path = result.file || result.path;
1998
- if (!path)
2448
+ const identity = buildFusionIdentity(result);
2449
+ if (!identity)
1999
2450
  return;
2000
2451
  const rrfContribution = weight / (k + rank + 1);
2001
- if (!pathScores.has(path)) {
2002
- pathScores.set(path, { score: 0, result, sources: [] });
2452
+ const representativeScore = scoreFusionRepresentative(result);
2453
+ if (!fusedScores.has(identity)) {
2454
+ fusedScores.set(identity, { score: 0, result, sources: [], representativeScore });
2003
2455
  }
2004
- const entry = pathScores.get(path);
2456
+ const entry = fusedScores.get(identity);
2005
2457
  entry.score += rrfContribution;
2458
+ if (representativeScore > entry.representativeScore) {
2459
+ entry.result = result;
2460
+ entry.representativeScore = representativeScore;
2461
+ }
2006
2462
  if (!entry.sources.includes(source)) {
2007
2463
  entry.sources.push(source);
2008
2464
  }
2009
2465
  });
2010
2466
  });
2011
2467
  // Sort by fusion score descending
2012
- return Array.from(pathScores.values())
2468
+ return Array.from(fusedScores.values())
2013
2469
  .sort((a, b) => b.score - a.score)
2014
2470
  .slice(0, limit)
2015
2471
  .map(item => ({
@@ -2206,9 +2662,9 @@ Recommended MCP flow: use **action=\"search\"** for lookups, **action=\"init\"**
2206
2662
  },
2207
2663
  output_mode: {
2208
2664
  type: 'string',
2209
- enum: ['full', 'files_only', 'count'],
2210
- description: 'Output format: full (default), files_only (paths only), count (per-file counts)',
2211
- default: 'full',
2665
+ enum: [...SEARCH_OUTPUT_MODES],
2666
+ description: 'Output format: ace (default, ACE-style grouped code sections + rendered text), full (raw matches), files_only (paths only), count (per-file counts)',
2667
+ default: 'ace',
2212
2668
  },
2213
2669
  path: {
2214
2670
  type: 'string',
@@ -2540,6 +2996,108 @@ function applyPagination(results, offset, limit) {
2540
2996
  },
2541
2997
  };
2542
2998
  }
2999
+ function formatChunkRange(section) {
3000
+ if (section.lines && section.lines.length > 0) {
3001
+ const start = section.lines[0]?.line;
3002
+ const end = section.lines[section.lines.length - 1]?.line;
3003
+ if (typeof start === 'number' && typeof end === 'number' && end > start) {
3004
+ return `${start}-${end}`;
3005
+ }
3006
+ if (typeof start === 'number') {
3007
+ return String(start);
3008
+ }
3009
+ }
3010
+ if (section.line && section.endLine && section.endLine > section.line) {
3011
+ return `${section.line}-${section.endLine}`;
3012
+ }
3013
+ if (section.line) {
3014
+ return String(section.line);
3015
+ }
3016
+ return '?';
3017
+ }
3018
+ function renderAceSnippet(section) {
3019
+ if (section.lines && section.lines.length > 0) {
3020
+ return section.lines.map((line) => {
3021
+ const marker = line.isMatch ? '>' : ' ';
3022
+ return `${marker} ${String(line.line).padStart(4, ' ')} | ${line.text}`;
3023
+ });
3024
+ }
3025
+ return section.snippet.split(/\r?\n/).map((line) => ` ${line}`);
3026
+ }
3027
+ function formatAceLikeOutput(results) {
3028
+ const sections = [];
3029
+ for (const result of results) {
3030
+ const candidate = result;
3031
+ const path = typeof candidate.file === 'string'
3032
+ ? candidate.file
3033
+ : typeof candidate.path === 'string'
3034
+ ? candidate.path
3035
+ : undefined;
3036
+ if (!path) {
3037
+ continue;
3038
+ }
3039
+ const line = typeof candidate.line === 'number' && candidate.line > 0 ? candidate.line : undefined;
3040
+ const column = typeof candidate.column === 'number' && candidate.column > 0 ? candidate.column : undefined;
3041
+ const score = typeof candidate.score === 'number' ? candidate.score : undefined;
3042
+ const symbol = typeof candidate.symbol === 'string' ? candidate.symbol : null;
3043
+ const rawSnippet = typeof candidate.content === 'string'
3044
+ ? candidate.content
3045
+ : typeof candidate.name === 'string'
3046
+ ? candidate.name
3047
+ : typeof candidate.type === 'string'
3048
+ ? `[${candidate.type}]`
3049
+ : '';
3050
+ sections.push({
3051
+ path,
3052
+ line,
3053
+ endLine: typeof candidate.endLine === 'number' && candidate.endLine >= (line ?? 0) ? candidate.endLine : line,
3054
+ column,
3055
+ score,
3056
+ symbol,
3057
+ snippet: rawSnippet || '[no snippet available]',
3058
+ lines: Array.isArray(candidate.chunkLines) ? candidate.chunkLines : undefined,
3059
+ });
3060
+ }
3061
+ const groupsMap = new Map();
3062
+ for (const section of sections) {
3063
+ if (!groupsMap.has(section.path)) {
3064
+ groupsMap.set(section.path, {
3065
+ path: section.path,
3066
+ sections: [],
3067
+ total_matches: 0,
3068
+ });
3069
+ }
3070
+ const group = groupsMap.get(section.path);
3071
+ group.sections.push(section);
3072
+ group.total_matches += 1;
3073
+ }
3074
+ const groups = [...groupsMap.values()];
3075
+ const textParts = ['The following code sections were retrieved:'];
3076
+ for (const group of groups) {
3077
+ textParts.push('');
3078
+ textParts.push(`Path: ${group.path}`);
3079
+ group.sections.forEach((section, index) => {
3080
+ const chunkLabel = group.sections.length > 1 ? `Chunk ${index + 1}` : 'Chunk';
3081
+ textParts.push(`${chunkLabel}: lines ${formatChunkRange(section)}${section.score !== undefined ? ` | score=${section.score.toFixed(4)}` : ''}`);
3082
+ if (section.symbol) {
3083
+ textParts.push(`Symbol: ${section.symbol}`);
3084
+ }
3085
+ for (const snippetLine of renderAceSnippet(section)) {
3086
+ textParts.push(snippetLine);
3087
+ }
3088
+ if (index < group.sections.length - 1) {
3089
+ textParts.push('');
3090
+ }
3091
+ });
3092
+ }
3093
+ return {
3094
+ format: 'ace',
3095
+ text: textParts.join('\n'),
3096
+ groups,
3097
+ sections,
3098
+ total: sections.length,
3099
+ };
3100
+ }
2543
3101
  /**
2544
3102
  * Transform results based on output_mode
2545
3103
  */
@@ -2567,11 +3125,23 @@ function transformOutput(results, outputMode) {
2567
3125
  total: results.length,
2568
3126
  };
2569
3127
  }
3128
+ case 'ace':
3129
+ return formatAceLikeOutput(results);
2570
3130
  case 'full':
2571
3131
  default:
2572
3132
  return results;
2573
3133
  }
2574
3134
  }
3135
+ function enrichMetadataWithIndexStatus(metadata, indexStatus, scope) {
3136
+ const nextMetadata = { ...(metadata ?? {}) };
3137
+ nextMetadata.embeddings_coverage_percent = indexStatus.embeddings_coverage_percent;
3138
+ nextMetadata.index_status = indexStatus.indexed
3139
+ ? (indexStatus.has_embeddings ? 'indexed' : 'partial')
3140
+ : 'not_indexed';
3141
+ nextMetadata.warning = mergeWarnings(nextMetadata.warning, indexStatus.warning);
3142
+ nextMetadata.suggestions = mergeSuggestions(nextMetadata.suggestions, buildIndexSuggestions(indexStatus, scope));
3143
+ return nextMetadata;
3144
+ }
2575
3145
  // Handler function
2576
3146
  export async function handler(params) {
2577
3147
  const parsed = ParamsSchema.safeParse(params);
@@ -2642,11 +3212,19 @@ export async function handler(params) {
2642
3212
  }
2643
3213
  break;
2644
3214
  }
3215
+ let autoEmbedNote;
2645
3216
  // Transform output based on output_mode (for search actions only)
2646
3217
  if (action === 'search' || action === 'search_files') {
2647
- if (result.success && result.results && output_mode !== 'full') {
2648
- result.results = transformOutput(result.results, output_mode);
2649
- }
3218
+ const scope = resolveSearchScope(parsed.data.path ?? '.');
3219
+ const indexStatus = await checkIndexStatus(scope.workingDirectory);
3220
+ result.metadata = enrichMetadataWithIndexStatus(result.metadata, indexStatus, scope);
3221
+ const autoEmbedStatus = await maybeStartBackgroundAutoEmbed(scope, indexStatus);
3222
+ autoEmbedNote = autoEmbedStatus.note;
3223
+ result.metadata = {
3224
+ ...(result.metadata ?? {}),
3225
+ note: mergeNotes(result.metadata?.note, autoEmbedStatus.note),
3226
+ warning: mergeWarnings(result.metadata?.warning, autoEmbedStatus.warning),
3227
+ };
2650
3228
  // Add pagination metadata for search results if not already present
2651
3229
  if (result.success && result.results && Array.isArray(result.results)) {
2652
3230
  const totalResults = result.results.length;
@@ -2662,6 +3240,33 @@ export async function handler(params) {
2662
3240
  };
2663
3241
  }
2664
3242
  }
3243
+ if (result.success && result.results && output_mode !== 'full') {
3244
+ result.results = transformOutput(result.results, output_mode);
3245
+ if (output_mode === 'ace'
3246
+ && result.results
3247
+ && typeof result.results === 'object'
3248
+ && 'format' in result.results
3249
+ && result.results.format === 'ace') {
3250
+ const advisoryLines = [];
3251
+ if (result.metadata?.warning) {
3252
+ advisoryLines.push('', 'Warnings:', `- ${result.metadata.warning}`);
3253
+ }
3254
+ if (autoEmbedNote) {
3255
+ advisoryLines.push('', 'Notes:', `- ${autoEmbedNote}`);
3256
+ }
3257
+ if (result.metadata?.suggestions && result.metadata.suggestions.length > 0) {
3258
+ advisoryLines.push('', 'Suggestions:');
3259
+ for (const suggestion of result.metadata.suggestions) {
3260
+ advisoryLines.push(`- ${suggestion.title}: ${suggestion.command}`);
3261
+ advisoryLines.push(` ${suggestion.reason}`);
3262
+ }
3263
+ }
3264
+ const aceResults = result.results;
3265
+ if (advisoryLines.length > 0) {
3266
+ aceResults.text += `\n${advisoryLines.join('\n')}`;
3267
+ }
3268
+ }
3269
+ }
2665
3270
  }
2666
3271
  // Add deprecation warning if applicable
2667
3272
  if (deprecationWarning && result.metadata) {
@@ -2680,10 +3285,14 @@ export async function handler(params) {
2680
3285
  * @param onProgress - Optional callback for progress updates
2681
3286
  */
2682
3287
  export const __testables = {
3288
+ isCodexLensCliCompatibilityError,
2683
3289
  parseCodexLensJsonOutput,
2684
3290
  parsePlainTextFileMatches,
2685
3291
  hasCentralizedVectorArtifacts,
3292
+ resolveRipgrepQueryMode,
2686
3293
  resolveEmbeddingSelection,
3294
+ isAutoEmbedMissingEnabled,
3295
+ buildIndexSuggestions,
2687
3296
  };
2688
3297
  export async function executeInitWithProgress(params, onProgress) {
2689
3298
  const path = params.path || '.';