claude-code-workflow 6.2.7 → 6.3.0

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 (208) hide show
  1. package/.claude/CLAUDE.md +16 -1
  2. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
  3. package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
  4. package/.claude/workflows/cli-tools-usage.md +14 -24
  5. package/.codex/AGENTS.md +51 -1
  6. package/.codex/prompts/compact.md +378 -0
  7. package/.gemini/GEMINI.md +57 -20
  8. package/ccw/dist/cli.d.ts.map +1 -1
  9. package/ccw/dist/cli.js +21 -8
  10. package/ccw/dist/cli.js.map +1 -1
  11. package/ccw/dist/commands/cli.d.ts +2 -0
  12. package/ccw/dist/commands/cli.d.ts.map +1 -1
  13. package/ccw/dist/commands/cli.js +129 -8
  14. package/ccw/dist/commands/cli.js.map +1 -1
  15. package/ccw/dist/commands/hook.d.ts.map +1 -1
  16. package/ccw/dist/commands/hook.js +3 -2
  17. package/ccw/dist/commands/hook.js.map +1 -1
  18. package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
  19. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
  20. package/ccw/dist/config/litellm-api-config-manager.js +770 -0
  21. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
  22. package/ccw/dist/config/provider-models.d.ts +73 -0
  23. package/ccw/dist/config/provider-models.d.ts.map +1 -0
  24. package/ccw/dist/config/provider-models.js +172 -0
  25. package/ccw/dist/config/provider-models.js.map +1 -0
  26. package/ccw/dist/core/cache-manager.d.ts.map +1 -1
  27. package/ccw/dist/core/cache-manager.js +3 -5
  28. package/ccw/dist/core/cache-manager.js.map +1 -1
  29. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  30. package/ccw/dist/core/dashboard-generator.js +3 -1
  31. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  32. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  33. package/ccw/dist/core/routes/cli-routes.js +169 -0
  34. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  35. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  36. package/ccw/dist/core/routes/codexlens-routes.js +234 -18
  37. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  38. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  39. package/ccw/dist/core/routes/hooks-routes.js +30 -32
  40. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  41. package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
  42. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
  43. package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
  44. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
  45. package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
  46. package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
  47. package/ccw/dist/core/routes/litellm-routes.js +85 -0
  48. package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
  49. package/ccw/dist/core/routes/mcp-routes.js +2 -2
  50. package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
  51. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  52. package/ccw/dist/core/routes/status-routes.js +39 -0
  53. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  54. package/ccw/dist/core/routes/system-routes.js +1 -1
  55. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  56. package/ccw/dist/core/server.d.ts.map +1 -1
  57. package/ccw/dist/core/server.js +15 -1
  58. package/ccw/dist/core/server.js.map +1 -1
  59. package/ccw/dist/mcp-server/index.js +1 -1
  60. package/ccw/dist/mcp-server/index.js.map +1 -1
  61. package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
  62. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
  63. package/ccw/dist/tools/claude-cli-tools.js +216 -0
  64. package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
  65. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  66. package/ccw/dist/tools/cli-executor.js +76 -14
  67. package/ccw/dist/tools/cli-executor.js.map +1 -1
  68. package/ccw/dist/tools/codex-lens.d.ts +9 -2
  69. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  70. package/ccw/dist/tools/codex-lens.js +114 -9
  71. package/ccw/dist/tools/codex-lens.js.map +1 -1
  72. package/ccw/dist/tools/context-cache-store.d.ts +136 -0
  73. package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
  74. package/ccw/dist/tools/context-cache-store.js +256 -0
  75. package/ccw/dist/tools/context-cache-store.js.map +1 -0
  76. package/ccw/dist/tools/context-cache.d.ts +56 -0
  77. package/ccw/dist/tools/context-cache.d.ts.map +1 -0
  78. package/ccw/dist/tools/context-cache.js +294 -0
  79. package/ccw/dist/tools/context-cache.js.map +1 -0
  80. package/ccw/dist/tools/core-memory.d.ts.map +1 -1
  81. package/ccw/dist/tools/core-memory.js +33 -19
  82. package/ccw/dist/tools/core-memory.js.map +1 -1
  83. package/ccw/dist/tools/index.d.ts.map +1 -1
  84. package/ccw/dist/tools/index.js +2 -0
  85. package/ccw/dist/tools/index.js.map +1 -1
  86. package/ccw/dist/tools/litellm-client.d.ts +85 -0
  87. package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
  88. package/ccw/dist/tools/litellm-client.js +188 -0
  89. package/ccw/dist/tools/litellm-client.js.map +1 -0
  90. package/ccw/dist/tools/litellm-executor.d.ts +34 -0
  91. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
  92. package/ccw/dist/tools/litellm-executor.js +192 -0
  93. package/ccw/dist/tools/litellm-executor.js.map +1 -0
  94. package/ccw/dist/tools/pattern-parser.d.ts +55 -0
  95. package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
  96. package/ccw/dist/tools/pattern-parser.js +237 -0
  97. package/ccw/dist/tools/pattern-parser.js.map +1 -0
  98. package/ccw/dist/tools/smart-search.d.ts +1 -0
  99. package/ccw/dist/tools/smart-search.d.ts.map +1 -1
  100. package/ccw/dist/tools/smart-search.js +117 -41
  101. package/ccw/dist/tools/smart-search.js.map +1 -1
  102. package/ccw/dist/types/litellm-api-config.d.ts +294 -0
  103. package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
  104. package/ccw/dist/types/litellm-api-config.js +8 -0
  105. package/ccw/dist/types/litellm-api-config.js.map +1 -0
  106. package/ccw/src/cli.ts +258 -244
  107. package/ccw/src/commands/cli.ts +153 -9
  108. package/ccw/src/commands/hook.ts +3 -2
  109. package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
  110. package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
  111. package/ccw/src/config/provider-models.ts +222 -0
  112. package/ccw/src/core/cache-manager.ts +292 -294
  113. package/ccw/src/core/dashboard-generator.ts +3 -1
  114. package/ccw/src/core/routes/cli-routes.ts +192 -0
  115. package/ccw/src/core/routes/codexlens-routes.ts +241 -19
  116. package/ccw/src/core/routes/hooks-routes.ts +399 -405
  117. package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
  118. package/ccw/src/core/routes/litellm-routes.ts +107 -0
  119. package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
  120. package/ccw/src/core/routes/status-routes.ts +51 -0
  121. package/ccw/src/core/routes/system-routes.ts +1 -1
  122. package/ccw/src/core/server.ts +15 -1
  123. package/ccw/src/mcp-server/index.ts +1 -1
  124. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
  125. package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
  126. package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
  127. package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
  128. package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
  129. package/ccw/src/templates/dashboard-js/i18n.js +583 -1
  130. package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
  131. package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
  132. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
  133. package/ccw/src/templates/dashboard.html +840 -831
  134. package/ccw/src/tools/claude-cli-tools.ts +300 -0
  135. package/ccw/src/tools/cli-executor.ts +83 -14
  136. package/ccw/src/tools/codex-lens.ts +146 -9
  137. package/ccw/src/tools/context-cache-store.ts +368 -0
  138. package/ccw/src/tools/context-cache.ts +393 -0
  139. package/ccw/src/tools/core-memory.ts +33 -19
  140. package/ccw/src/tools/index.ts +2 -0
  141. package/ccw/src/tools/litellm-client.ts +246 -0
  142. package/ccw/src/tools/litellm-executor.ts +241 -0
  143. package/ccw/src/tools/pattern-parser.ts +329 -0
  144. package/ccw/src/tools/smart-search.ts +142 -41
  145. package/ccw/src/types/litellm-api-config.ts +402 -0
  146. package/ccw-litellm/README.md +180 -0
  147. package/ccw-litellm/pyproject.toml +35 -0
  148. package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
  149. package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
  151. package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
  152. package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
  153. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
  154. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  155. package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
  156. package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
  157. package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
  158. package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
  159. package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
  160. package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
  161. package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
  162. package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
  163. package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
  164. package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
  165. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
  167. package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
  168. package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
  169. package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
  170. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  171. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  172. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  173. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  174. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  175. package/codex-lens/src/codexlens/cli/commands.py +378 -23
  176. package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
  177. package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
  178. package/codex-lens/src/codexlens/cli/output.py +12 -1
  179. package/codex-lens/src/codexlens/config.py +93 -0
  180. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  181. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  182. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  183. package/codex-lens/src/codexlens/search/chain_search.py +6 -2
  184. package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
  185. package/codex-lens/src/codexlens/search/ranking.py +1 -1
  186. package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
  187. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  188. package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
  189. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  190. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  191. package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
  192. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  193. package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
  194. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  195. package/codex-lens/src/codexlens/semantic/base.py +61 -0
  196. package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
  197. package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
  198. package/codex-lens/src/codexlens/semantic/factory.py +98 -0
  199. package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
  200. package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
  201. package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
  202. package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
  203. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  204. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  205. package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
  206. package/package.json +15 -5
  207. package/.codex/prompts.zip +0 -0
  208. package/ccw/package.json +0 -65
@@ -29,10 +29,12 @@ const ParamsSchema = z.object({
29
29
  path: z.string().optional(),
30
30
  paths: z.array(z.string()).default([]),
31
31
  contextLines: z.number().default(0),
32
- maxResults: z.number().default(20), // Increased default
32
+ maxResults: z.number().default(5), // Default 5 with full content
33
33
  includeHidden: z.boolean().default(false),
34
34
  languages: z.array(z.string()).optional(),
35
- limit: z.number().default(20), // Increased default
35
+ limit: z.number().default(5), // Default 5 with full content
36
+ extraFilesCount: z.number().default(10), // Additional file-only results
37
+ maxContentLength: z.number().default(200), // Max content length for truncation (50-2000)
36
38
  offset: z.number().default(0), // NEW: Pagination offset (start_index)
37
39
  enrich: z.boolean().default(false),
38
40
  // Search modifiers for ripgrep mode
@@ -163,6 +165,35 @@ function scoreByTokenMatch(results, tokens) {
163
165
  function stripAnsi(str) {
164
166
  return str.replace(/\x1b\[[0-9;]*m/g, '');
165
167
  }
168
+ /** Default maximum content length to return (avoid excessive output) */
169
+ const DEFAULT_MAX_CONTENT_LENGTH = 200;
170
+ /**
171
+ * Truncate content to specified length with ellipsis
172
+ * @param content - The content to truncate
173
+ * @param maxLength - Maximum length (default: 200)
174
+ */
175
+ function truncateContent(content, maxLength = DEFAULT_MAX_CONTENT_LENGTH) {
176
+ if (!content)
177
+ return '';
178
+ if (content.length <= maxLength)
179
+ return content;
180
+ return content.slice(0, maxLength) + '...';
181
+ }
182
+ /**
183
+ * Split results into full content results and extra file-only results
184
+ * Generic function supporting both SemanticMatch and ExactMatch types
185
+ * @param allResults - All search results (must have 'file' property)
186
+ * @param fullContentLimit - Number of results with full content (default: 5)
187
+ * @param extraFilesCount - Number of additional file-only results (default: 10)
188
+ */
189
+ function splitResultsWithExtraFiles(allResults, fullContentLimit = 5, extraFilesCount = 10) {
190
+ // First N results with full content
191
+ const results = allResults.slice(0, fullContentLimit);
192
+ // Next M results as file paths only (deduplicated)
193
+ const extraResults = allResults.slice(fullContentLimit, fullContentLimit + extraFilesCount);
194
+ const extra_files = [...new Set(extraResults.map(r => r.file))];
195
+ return { results, extra_files };
196
+ }
166
197
  /**
167
198
  * Check if CodexLens index exists for current directory
168
199
  * @param path - Directory path to check
@@ -190,6 +221,17 @@ async function checkIndexStatus(path = '.') {
190
221
  const embeddingsData = status.embeddings || {};
191
222
  const embeddingsCoverage = embeddingsData.coverage_percent || 0;
192
223
  const has_embeddings = embeddingsCoverage >= 50; // Threshold: 50%
224
+ const totalChunks = embeddingsData.total_chunks || 0;
225
+ // Extract model info if available
226
+ const modelInfoData = embeddingsData.model_info;
227
+ const modelInfo = modelInfoData ? {
228
+ model_profile: modelInfoData.model_profile,
229
+ model_name: modelInfoData.model_name,
230
+ embedding_dim: modelInfoData.embedding_dim,
231
+ backend: modelInfoData.backend,
232
+ created_at: modelInfoData.created_at,
233
+ updated_at: modelInfoData.updated_at,
234
+ } : undefined;
193
235
  let warning;
194
236
  if (!indexed) {
195
237
  warning = 'No CodexLens index found. Run smart_search(action="init") to create index for better search results.';
@@ -205,6 +247,9 @@ async function checkIndexStatus(path = '.') {
205
247
  has_embeddings,
206
248
  file_count: status.total_files,
207
249
  embeddings_coverage_percent: embeddingsCoverage,
250
+ total_chunks: totalChunks,
251
+ // Ensure model_info is null instead of undefined so it's included in JSON
252
+ model_info: modelInfo ?? null,
208
253
  warning,
209
254
  };
210
255
  }
@@ -514,7 +559,7 @@ async function executeAutoMode(params) {
514
559
  * Supports tokenized multi-word queries with OR matching and result ranking
515
560
  */
516
561
  async function executeRipgrepMode(params) {
517
- const { query, paths = [], contextLines = 0, maxResults = 10, includeHidden = false, path = '.', regex = true, caseSensitive = true, tokenize = true } = params;
562
+ const { query, paths = [], contextLines = 0, maxResults = 5, extraFilesCount = 10, maxContentLength = 200, includeHidden = false, path = '.', regex = true, caseSensitive = true, tokenize = true } = params;
518
563
  if (!query) {
519
564
  return {
520
565
  success: false,
@@ -523,6 +568,8 @@ async function executeRipgrepMode(params) {
523
568
  }
524
569
  // Check if ripgrep is available
525
570
  const hasRipgrep = checkToolAvailability('rg');
571
+ // Calculate total to fetch for split (full content + extra files)
572
+ const totalToFetch = maxResults + extraFilesCount;
526
573
  // If ripgrep not available, fall back to CodexLens exact mode
527
574
  if (!hasRipgrep) {
528
575
  const readyStatus = await ensureCodexLensReady();
@@ -533,7 +580,7 @@ async function executeRipgrepMode(params) {
533
580
  };
534
581
  }
535
582
  // Use CodexLens exact mode as fallback
536
- const args = ['search', query, '--limit', maxResults.toString(), '--mode', 'exact', '--json'];
583
+ const args = ['search', query, '--limit', totalToFetch.toString(), '--mode', 'exact', '--json'];
537
584
  const result = await executeCodexLens(args, { cwd: path });
538
585
  if (!result.success) {
539
586
  return {
@@ -548,23 +595,26 @@ async function executeRipgrepMode(params) {
548
595
  };
549
596
  }
550
597
  // Parse results
551
- let results = [];
598
+ let allResults = [];
552
599
  try {
553
600
  const parsed = JSON.parse(stripAnsi(result.output || '{}'));
554
601
  const data = parsed.result?.results || parsed.results || parsed;
555
- results = (Array.isArray(data) ? data : []).map((item) => ({
602
+ allResults = (Array.isArray(data) ? data : []).map((item) => ({
556
603
  file: item.path || item.file,
557
604
  score: item.score || 0,
558
- content: item.excerpt || item.content || '',
605
+ content: truncateContent(item.content || item.excerpt, maxContentLength),
559
606
  symbol: item.symbol || null,
560
607
  }));
561
608
  }
562
609
  catch {
563
610
  // Keep empty results
564
611
  }
612
+ // Split results: first N with full content, rest as file paths only
613
+ const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount);
565
614
  return {
566
615
  success: true,
567
616
  results,
617
+ extra_files: extra_files.length > 0 ? extra_files : undefined,
568
618
  metadata: {
569
619
  mode: 'ripgrep',
570
620
  backend: 'codexlens-fallback',
@@ -574,12 +624,12 @@ async function executeRipgrepMode(params) {
574
624
  },
575
625
  };
576
626
  }
577
- // Use ripgrep
627
+ // Use ripgrep - request more results to support split
578
628
  const { command, args, tokens } = buildRipgrepCommand({
579
629
  query,
580
630
  paths: paths.length > 0 ? paths : [path],
581
631
  contextLines,
582
- maxResults,
632
+ maxResults: totalToFetch, // Fetch more to support split
583
633
  includeHidden,
584
634
  regex,
585
635
  caseSensitive,
@@ -600,13 +650,13 @@ async function executeRipgrepMode(params) {
600
650
  stderr += data.toString();
601
651
  });
602
652
  child.on('close', (code) => {
603
- const results = [];
653
+ const allResults = [];
604
654
  const lines = stdout.split('\n').filter((line) => line.trim());
605
655
  // Limit total results to prevent memory overflow (--max-count only limits per-file)
606
- const effectiveLimit = maxResults > 0 ? maxResults : 500;
656
+ const effectiveLimit = totalToFetch > 0 ? totalToFetch : 500;
607
657
  for (const line of lines) {
608
658
  // Stop collecting if we've reached the limit
609
- if (results.length >= effectiveLimit) {
659
+ if (allResults.length >= effectiveLimit) {
610
660
  resultLimitReached = true;
611
661
  break;
612
662
  }
@@ -621,7 +671,7 @@ async function executeRipgrepMode(params) {
621
671
  : 1,
622
672
  content: item.data.lines.text.trim(),
623
673
  };
624
- results.push(match);
674
+ allResults.push(match);
625
675
  }
626
676
  }
627
677
  catch {
@@ -633,8 +683,10 @@ async function executeRipgrepMode(params) {
633
683
  const isWindowsDeviceError = stderr.includes('os error 1') || stderr.includes('函数不正确');
634
684
  // Apply token-based scoring and sorting for multi-word queries
635
685
  // Results matching more tokens are ranked higher (exact matches first)
636
- const scoredResults = tokens.length > 1 ? scoreByTokenMatch(results, tokens) : results;
686
+ const scoredResults = tokens.length > 1 ? scoreByTokenMatch(allResults, tokens) : allResults;
637
687
  if (code === 0 || code === 1 || (isWindowsDeviceError && scoredResults.length > 0)) {
688
+ // Split results: first N with full content, rest as file paths only
689
+ const { results, extra_files } = splitResultsWithExtraFiles(scoredResults, maxResults, extraFilesCount);
638
690
  // Build warning message for various conditions
639
691
  const warnings = [];
640
692
  if (resultLimitReached) {
@@ -645,11 +697,12 @@ async function executeRipgrepMode(params) {
645
697
  }
646
698
  resolve({
647
699
  success: true,
648
- results: scoredResults,
700
+ results,
701
+ extra_files: extra_files.length > 0 ? extra_files : undefined,
649
702
  metadata: {
650
703
  mode: 'ripgrep',
651
704
  backend: 'ripgrep',
652
- count: scoredResults.length,
705
+ count: results.length,
653
706
  query,
654
707
  tokens: tokens.length > 1 ? tokens : undefined, // Include tokens in metadata for debugging
655
708
  tokenized: tokens.length > 1,
@@ -657,7 +710,7 @@ async function executeRipgrepMode(params) {
657
710
  },
658
711
  });
659
712
  }
660
- else if (isWindowsDeviceError && results.length === 0) {
713
+ else if (isWindowsDeviceError && allResults.length === 0) {
661
714
  // Windows device error but no results - might be the only issue
662
715
  resolve({
663
716
  success: true,
@@ -693,7 +746,7 @@ async function executeRipgrepMode(params) {
693
746
  * Requires index
694
747
  */
695
748
  async function executeCodexLensExactMode(params) {
696
- const { query, path = '.', maxResults = 10, enrich = false } = params;
749
+ const { query, path = '.', maxResults = 5, extraFilesCount = 10, maxContentLength = 200, enrich = false } = params;
697
750
  if (!query) {
698
751
  return {
699
752
  success: false,
@@ -710,7 +763,9 @@ async function executeCodexLensExactMode(params) {
710
763
  }
711
764
  // Check index status
712
765
  const indexStatus = await checkIndexStatus(path);
713
- const args = ['search', query, '--limit', maxResults.toString(), '--mode', 'exact', '--json'];
766
+ // Request more results to support split (full content + extra files)
767
+ const totalToFetch = maxResults + extraFilesCount;
768
+ const args = ['search', query, '--limit', totalToFetch.toString(), '--mode', 'exact', '--json'];
714
769
  if (enrich) {
715
770
  args.push('--enrich');
716
771
  }
@@ -729,14 +784,14 @@ async function executeCodexLensExactMode(params) {
729
784
  };
730
785
  }
731
786
  // Parse results
732
- let results = [];
787
+ let allResults = [];
733
788
  try {
734
789
  const parsed = JSON.parse(stripAnsi(result.output || '{}'));
735
790
  const data = parsed.result?.results || parsed.results || parsed;
736
- results = (Array.isArray(data) ? data : []).map((item) => ({
791
+ allResults = (Array.isArray(data) ? data : []).map((item) => ({
737
792
  file: item.path || item.file,
738
793
  score: item.score || 0,
739
- content: item.excerpt || item.content || '',
794
+ content: truncateContent(item.content || item.excerpt, maxContentLength),
740
795
  symbol: item.symbol || null,
741
796
  }));
742
797
  }
@@ -744,8 +799,8 @@ async function executeCodexLensExactMode(params) {
744
799
  // Keep empty results
745
800
  }
746
801
  // Fallback to fuzzy mode if exact returns no results
747
- if (results.length === 0) {
748
- const fuzzyArgs = ['search', query, '--limit', maxResults.toString(), '--mode', 'fuzzy', '--json'];
802
+ if (allResults.length === 0) {
803
+ const fuzzyArgs = ['search', query, '--limit', totalToFetch.toString(), '--mode', 'fuzzy', '--json'];
749
804
  if (enrich) {
750
805
  fuzzyArgs.push('--enrich');
751
806
  }
@@ -754,20 +809,23 @@ async function executeCodexLensExactMode(params) {
754
809
  try {
755
810
  const parsed = JSON.parse(stripAnsi(fuzzyResult.output || '{}'));
756
811
  const data = parsed.result?.results || parsed.results || parsed;
757
- results = (Array.isArray(data) ? data : []).map((item) => ({
812
+ allResults = (Array.isArray(data) ? data : []).map((item) => ({
758
813
  file: item.path || item.file,
759
814
  score: item.score || 0,
760
- content: item.excerpt || item.content || '',
815
+ content: truncateContent(item.content || item.excerpt, maxContentLength),
761
816
  symbol: item.symbol || null,
762
817
  }));
763
818
  }
764
819
  catch {
765
820
  // Keep empty results
766
821
  }
767
- if (results.length > 0) {
822
+ if (allResults.length > 0) {
823
+ // Split results: first N with full content, rest as file paths only
824
+ const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount);
768
825
  return {
769
826
  success: true,
770
827
  results,
828
+ extra_files: extra_files.length > 0 ? extra_files : undefined,
771
829
  metadata: {
772
830
  mode: 'exact',
773
831
  backend: 'codexlens',
@@ -781,9 +839,12 @@ async function executeCodexLensExactMode(params) {
781
839
  }
782
840
  }
783
841
  }
842
+ // Split results: first N with full content, rest as file paths only
843
+ const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount);
784
844
  return {
785
845
  success: true,
786
846
  results,
847
+ extra_files: extra_files.length > 0 ? extra_files : undefined,
787
848
  metadata: {
788
849
  mode: 'exact',
789
850
  backend: 'codexlens',
@@ -799,7 +860,7 @@ async function executeCodexLensExactMode(params) {
799
860
  * Requires index with embeddings
800
861
  */
801
862
  async function executeHybridMode(params) {
802
- const { query, path = '.', maxResults = 10, enrich = false } = params;
863
+ const { query, path = '.', maxResults = 5, extraFilesCount = 10, maxContentLength = 200, enrich = false } = params;
803
864
  if (!query) {
804
865
  return {
805
866
  success: false,
@@ -816,7 +877,9 @@ async function executeHybridMode(params) {
816
877
  }
817
878
  // Check index status
818
879
  const indexStatus = await checkIndexStatus(path);
819
- const args = ['search', query, '--limit', maxResults.toString(), '--mode', 'hybrid', '--json'];
880
+ // Request more results to support split (full content + extra files)
881
+ const totalToFetch = maxResults + extraFilesCount;
882
+ const args = ['search', query, '--limit', totalToFetch.toString(), '--mode', 'hybrid', '--json'];
820
883
  if (enrich) {
821
884
  args.push('--enrich');
822
885
  }
@@ -835,13 +898,13 @@ async function executeHybridMode(params) {
835
898
  };
836
899
  }
837
900
  // Parse results
838
- let results = [];
901
+ let allResults = [];
839
902
  let baselineInfo = null;
840
903
  let initialCount = 0;
841
904
  try {
842
905
  const parsed = JSON.parse(stripAnsi(result.output || '{}'));
843
906
  const data = parsed.result?.results || parsed.results || parsed;
844
- results = (Array.isArray(data) ? data : []).map((item) => {
907
+ allResults = (Array.isArray(data) ? data : []).map((item) => {
845
908
  const rawScore = item.score || 0;
846
909
  // Hybrid mode returns distance scores (lower is better).
847
910
  // Convert to similarity scores (higher is better) for consistency.
@@ -850,24 +913,24 @@ async function executeHybridMode(params) {
850
913
  return {
851
914
  file: item.path || item.file,
852
915
  score: similarityScore,
853
- content: item.excerpt || item.content || '',
916
+ content: truncateContent(item.content || item.excerpt, maxContentLength),
854
917
  symbol: item.symbol || null,
855
918
  };
856
919
  });
857
- initialCount = results.length;
920
+ initialCount = allResults.length;
858
921
  // Post-processing pipeline to improve semantic search quality
859
922
  // 0. Filter dominant baseline scores (hot spot detection)
860
- const baselineResult = filterDominantBaselineScores(results);
861
- results = baselineResult.filteredResults;
923
+ const baselineResult = filterDominantBaselineScores(allResults);
924
+ allResults = baselineResult.filteredResults;
862
925
  baselineInfo = baselineResult.baselineInfo;
863
926
  // 1. Filter noisy files (coverage, node_modules, etc.)
864
- results = filterNoisyFiles(results);
927
+ allResults = filterNoisyFiles(allResults);
865
928
  // 2. Boost results containing query keywords
866
- results = applyKeywordBoosting(results, query);
929
+ allResults = applyKeywordBoosting(allResults, query);
867
930
  // 3. Enforce score diversity (penalize identical scores)
868
- results = enforceScoreDiversity(results);
931
+ allResults = enforceScoreDiversity(allResults);
869
932
  // 4. Re-sort by adjusted scores
870
- results.sort((a, b) => b.score - a.score);
933
+ allResults.sort((a, b) => b.score - a.score);
871
934
  }
872
935
  catch {
873
936
  return {
@@ -883,14 +946,17 @@ async function executeHybridMode(params) {
883
946
  },
884
947
  };
885
948
  }
949
+ // Split results: first N with full content, rest as file paths only
950
+ const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount);
886
951
  // Build metadata with baseline info if detected
887
952
  let note = 'Hybrid mode uses RRF fusion (exact + fuzzy + vector) for best results';
888
953
  if (baselineInfo) {
889
- note += ` | Filtered ${initialCount - results.length} hot-spot results with baseline score ~${baselineInfo.score.toFixed(4)}`;
954
+ note += ` | Filtered ${initialCount - allResults.length} hot-spot results with baseline score ~${baselineInfo.score.toFixed(4)}`;
890
955
  }
891
956
  return {
892
957
  success: true,
893
958
  results,
959
+ extra_files: extra_files.length > 0 ? extra_files : undefined,
894
960
  metadata: {
895
961
  mode: 'hybrid',
896
962
  backend: 'codexlens',
@@ -1258,7 +1324,7 @@ export const schema = {
1258
1324
  mode: {
1259
1325
  type: 'string',
1260
1326
  enum: SEARCH_MODES,
1261
- description: 'Search mode: auto (default), hybrid (best quality), exact (CodexLens FTS), ripgrep (fast, no index), priority (fallback: hybrid->exact->ripgrep)',
1327
+ description: 'Search mode: auto, hybrid (best quality), exact (CodexLens FTS), ripgrep (fast, no index), priority (fallback chain)',
1262
1328
  default: 'auto',
1263
1329
  },
1264
1330
  output_mode: {
@@ -1294,6 +1360,16 @@ export const schema = {
1294
1360
  description: 'Alias for maxResults (default: 20)',
1295
1361
  default: 20,
1296
1362
  },
1363
+ extraFilesCount: {
1364
+ type: 'number',
1365
+ description: 'Number of additional file-only results (paths without content)',
1366
+ default: 10,
1367
+ },
1368
+ maxContentLength: {
1369
+ type: 'number',
1370
+ description: 'Maximum content length for truncation (50-2000)',
1371
+ default: 200,
1372
+ },
1297
1373
  offset: {
1298
1374
  type: 'number',
1299
1375
  description: 'Pagination offset - skip first N results (default: 0)',