@crownpeak/dqm-react-component-dev-mcp 1.2.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 (444) hide show
  1. package/README.md +138 -0
  2. package/data/.env.example +22 -0
  3. package/data/.gitattributes +47 -0
  4. package/data/.glfrc.json +7 -0
  5. package/data/.husky/pre-commit +5 -0
  6. package/data/.nvmrc +1 -0
  7. package/data/CHANGELOG.md +75 -0
  8. package/data/CODE_OF_CONDUCT.md +129 -0
  9. package/data/CONTRIBUTING.md +203 -0
  10. package/data/DOCS-STRUCTURE.md +307 -0
  11. package/data/I18N.md +292 -0
  12. package/data/LICENSE +22 -0
  13. package/data/README.md +315 -0
  14. package/data/SECURITY.md +125 -0
  15. package/data/WIKI-DEPLOYMENT.md +348 -0
  16. package/data/docs/AI-FEATURES.md +610 -0
  17. package/data/docs/API-REFERENCE.md +1022 -0
  18. package/data/docs/AUTHENTICATION.md +301 -0
  19. package/data/docs/BACKEND-API.md +468 -0
  20. package/data/docs/DEVELOPMENT.md +375 -0
  21. package/data/docs/EXAMPLES.md +622 -0
  22. package/data/docs/MCP-SERVER.md +307 -0
  23. package/data/docs/MIGRATION-GUIDE.md +367 -0
  24. package/data/docs/NPM-PUBLISH.md +193 -0
  25. package/data/docs/QUICKSTART.md +206 -0
  26. package/data/docs/REDIS-SETUP.md +162 -0
  27. package/data/docs/SERVER.md +228 -0
  28. package/data/docs/TROUBLESHOOTING.md +657 -0
  29. package/data/docs/WIDGET-GUIDE.md +638 -0
  30. package/data/docs/WIKI-HOME.md +58 -0
  31. package/data/docs/WIKI-SIDEBAR.md +39 -0
  32. package/data/package.json +171 -0
  33. package/data/playwright.config.ts +64 -0
  34. package/data/probe/.cargo/config.toml +10 -0
  35. package/data/probe/.claude/commands/performance-review.md +15 -0
  36. package/data/probe/.clinerules +288 -0
  37. package/data/probe/.dockerignore +57 -0
  38. package/data/probe/.githooks/post-commit +11 -0
  39. package/data/probe/.githooks/pre-commit +99 -0
  40. package/data/probe/.githooks/pre-commit-vow +9 -0
  41. package/data/probe/.prompts/engineer.md +41 -0
  42. package/data/probe/.roomodes +28 -0
  43. package/data/probe/.windsurfrules +0 -0
  44. package/data/probe/BASH_TOOL_SUMMARY.md +148 -0
  45. package/data/probe/BENCHMARKING.md +256 -0
  46. package/data/probe/CLAUDE.md +226 -0
  47. package/data/probe/CODE_OF_CONDUCT.md +128 -0
  48. package/data/probe/CONTRIBUTING.md +193 -0
  49. package/data/probe/Cargo.toml +120 -0
  50. package/data/probe/Cross.toml +10 -0
  51. package/data/probe/DOCKER-README.md +224 -0
  52. package/data/probe/Dockerfile +32 -0
  53. package/data/probe/ENHANCED_DEBUG_TELEMETRY.md +188 -0
  54. package/data/probe/LICENSE +201 -0
  55. package/data/probe/Makefile +210 -0
  56. package/data/probe/README.md +824 -0
  57. package/data/probe/SECURITY.md +67 -0
  58. package/data/probe/WINDOWS-GUIDE.md +294 -0
  59. package/data/probe/benches/parsing_benchmarks.rs +370 -0
  60. package/data/probe/benches/search_benchmarks.rs +599 -0
  61. package/data/probe/benches/simd_benchmarks.rs +372 -0
  62. package/data/probe/benches/timing_benchmarks.rs +287 -0
  63. package/data/probe/build-windows.bat +229 -0
  64. package/data/probe/codex-config/config.toml +6 -0
  65. package/data/probe/docs/PERFORMANCE_OPTIMIZATION.md +161 -0
  66. package/data/probe/examples/cache_demo.rs +46 -0
  67. package/data/probe/examples/chat/.dockerignore +37 -0
  68. package/data/probe/examples/chat/ChatSessionManager.js +295 -0
  69. package/data/probe/examples/chat/Dockerfile +98 -0
  70. package/data/probe/examples/chat/LICENSE +201 -0
  71. package/data/probe/examples/chat/LOCAL_IMAGE_SUPPORT.md +195 -0
  72. package/data/probe/examples/chat/MCP_INTEGRATION.md +400 -0
  73. package/data/probe/examples/chat/README.md +338 -0
  74. package/data/probe/examples/chat/TRACING.md +226 -0
  75. package/data/probe/examples/chat/appTracer.js +968 -0
  76. package/data/probe/examples/chat/auth.js +76 -0
  77. package/data/probe/examples/chat/bin/probe-chat.js +13 -0
  78. package/data/probe/examples/chat/build.js +104 -0
  79. package/data/probe/examples/chat/cancelRequest.js +84 -0
  80. package/data/probe/examples/chat/demo-agentic-image-flow.js +88 -0
  81. package/data/probe/examples/chat/demo-local-images.js +128 -0
  82. package/data/probe/examples/chat/fileSpanExporter.js +181 -0
  83. package/data/probe/examples/chat/implement/README.md +228 -0
  84. package/data/probe/examples/chat/implement/backends/AiderBackend.js +750 -0
  85. package/data/probe/examples/chat/implement/backends/BaseBackend.js +276 -0
  86. package/data/probe/examples/chat/implement/backends/ClaudeCodeBackend.js +767 -0
  87. package/data/probe/examples/chat/implement/backends/MockBackend.js +237 -0
  88. package/data/probe/examples/chat/implement/backends/registry.js +85 -0
  89. package/data/probe/examples/chat/implement/core/BackendManager.js +567 -0
  90. package/data/probe/examples/chat/implement/core/ImplementTool.js +354 -0
  91. package/data/probe/examples/chat/implement/core/config.js +428 -0
  92. package/data/probe/examples/chat/implement/core/timeouts.js +58 -0
  93. package/data/probe/examples/chat/implement/core/utils.js +496 -0
  94. package/data/probe/examples/chat/implement/types/BackendTypes.js +126 -0
  95. package/data/probe/examples/chat/index.js +669 -0
  96. package/data/probe/examples/chat/mcpServer.js +341 -0
  97. package/data/probe/examples/chat/npm/LICENSE +15 -0
  98. package/data/probe/examples/chat/npm/README.md +168 -0
  99. package/data/probe/examples/chat/npm/bin/probe-chat.js +156 -0
  100. package/data/probe/examples/chat/npm/index.js +259 -0
  101. package/data/probe/examples/chat/npm/package.json +54 -0
  102. package/data/probe/examples/chat/package.json +102 -0
  103. package/data/probe/examples/chat/probeChat.js +456 -0
  104. package/data/probe/examples/chat/probeTool.js +491 -0
  105. package/data/probe/examples/chat/storage/JsonChatStorage.js +476 -0
  106. package/data/probe/examples/chat/telemetry.js +281 -0
  107. package/data/probe/examples/chat/test/integration/chatFlows.test.js +320 -0
  108. package/data/probe/examples/chat/test/integration/toolCalling.test.js +471 -0
  109. package/data/probe/examples/chat/test/mocks/mockLLMProvider.js +269 -0
  110. package/data/probe/examples/chat/test/test-backends.js +90 -0
  111. package/data/probe/examples/chat/test/testUtils.js +530 -0
  112. package/data/probe/examples/chat/test/unit/backendTimeout.test.js +161 -0
  113. package/data/probe/examples/chat/test/unit/packageFiles.test.js +120 -0
  114. package/data/probe/examples/chat/test/verify-tests.js +118 -0
  115. package/data/probe/examples/chat/test-agentic-image-loading.js +294 -0
  116. package/data/probe/examples/chat/test-ai-sdk-telemetry.js +204 -0
  117. package/data/probe/examples/chat/test-chat-tracing.js +38 -0
  118. package/data/probe/examples/chat/test-direct-function.js +49 -0
  119. package/data/probe/examples/chat/test-file-size-validation.js +103 -0
  120. package/data/probe/examples/chat/test-full-mcp-integration.js +258 -0
  121. package/data/probe/examples/chat/test-github-context.txt +12 -0
  122. package/data/probe/examples/chat/test-hierarchy.js +203 -0
  123. package/data/probe/examples/chat/test-image-spans.js +37 -0
  124. package/data/probe/examples/chat/test-local-image-reading.js +176 -0
  125. package/data/probe/examples/chat/test-mcp-integration.js +136 -0
  126. package/data/probe/examples/chat/test-mcp-probe-server.js +161 -0
  127. package/data/probe/examples/chat/test-mcp-with-ai.js +279 -0
  128. package/data/probe/examples/chat/test-multiple-allowed-dirs.js +111 -0
  129. package/data/probe/examples/chat/test-probe-mcp-server.js +110 -0
  130. package/data/probe/examples/chat/test-security-validation.js +145 -0
  131. package/data/probe/examples/chat/test-simple-tracing.js +32 -0
  132. package/data/probe/examples/chat/test-trace-verification.js +235 -0
  133. package/data/probe/examples/chat/test-tracing.js +114 -0
  134. package/data/probe/examples/chat/tokenCounter.js +419 -0
  135. package/data/probe/examples/chat/tokenUsageDisplay.js +134 -0
  136. package/data/probe/examples/chat/webServer.js +1103 -0
  137. package/data/probe/examples/reranker/Cargo.toml +33 -0
  138. package/data/probe/examples/reranker/DEBUG_OUTPUT_ANALYSIS.md +71 -0
  139. package/data/probe/examples/reranker/MODELS.md +66 -0
  140. package/data/probe/examples/reranker/MODEL_COMPARISON.md +60 -0
  141. package/data/probe/examples/reranker/MULTI_MODEL_ANALYSIS.md +176 -0
  142. package/data/probe/examples/reranker/PERFORMANCE_SUMMARY.md +156 -0
  143. package/data/probe/examples/reranker/README.md +347 -0
  144. package/data/probe/examples/reranker/RUST_BERT_COMPARISON.md +82 -0
  145. package/data/probe/examples/reranker/TOKENIZATION_GUIDE.md +120 -0
  146. package/data/probe/examples/reranker/check_rust_tokenizer.py +108 -0
  147. package/data/probe/examples/reranker/convert_to_torchscript.py +109 -0
  148. package/data/probe/examples/reranker/debug_scoring.py +189 -0
  149. package/data/probe/examples/reranker/debug_tokenization.py +154 -0
  150. package/data/probe/examples/reranker/download_models.sh +73 -0
  151. package/data/probe/examples/reranker/requirements.txt +13 -0
  152. package/data/probe/examples/reranker/run_comprehensive_benchmark.sh +83 -0
  153. package/data/probe/examples/reranker/rust_bert_test/Cargo.toml +12 -0
  154. package/data/probe/examples/reranker/rust_bert_test/README.md +54 -0
  155. package/data/probe/examples/reranker/simple_test.py +50 -0
  156. package/data/probe/examples/reranker/test_all_models.sh +63 -0
  157. package/data/probe/examples/reranker/test_bert_results.sh +44 -0
  158. package/data/probe/examples/reranker/test_cross_encoder.py +334 -0
  159. package/data/probe/examples/reranker/test_cross_encoder.sh +80 -0
  160. package/data/probe/examples/reranker/test_exact_comparison.py +151 -0
  161. package/data/probe/examples/reranker/test_parallel_performance.sh +56 -0
  162. package/data/probe/examples/reranker/test_scores.py +132 -0
  163. package/data/probe/install.ps1 +508 -0
  164. package/data/probe/install.sh +460 -0
  165. package/data/probe/npm/CLONE_METHOD_EXAMPLES.md +596 -0
  166. package/data/probe/npm/CONTEXT_COMPACTION.md +303 -0
  167. package/data/probe/npm/DELEGATE_TOOL_README.md +166 -0
  168. package/data/probe/npm/MAID_INTEGRATION.md +313 -0
  169. package/data/probe/npm/MCP_INTEGRATION_SUMMARY.md +241 -0
  170. package/data/probe/npm/README.md +824 -0
  171. package/data/probe/npm/bin/.gitignore +7 -0
  172. package/data/probe/npm/bin/.gitkeep +0 -0
  173. package/data/probe/npm/bin/README.md +12 -0
  174. package/data/probe/npm/bin/probe +167 -0
  175. package/data/probe/npm/docs/CLAUDE_CODE_INTEGRATION.md +414 -0
  176. package/data/probe/npm/docs/CODEX_INTEGRATION.md +502 -0
  177. package/data/probe/npm/docs/EDIT_CREATE_TOOLS.md +233 -0
  178. package/data/probe/npm/docs/RETRY_AND_FALLBACK.md +674 -0
  179. package/data/probe/npm/example-usage.js +335 -0
  180. package/data/probe/npm/examples/multi-engine-demo.js +117 -0
  181. package/data/probe/npm/examples/probe-agent-cli.js +113 -0
  182. package/data/probe/npm/examples/test-agent-edit.js +114 -0
  183. package/data/probe/npm/examples/test-edit-create.js +120 -0
  184. package/data/probe/npm/examples/test-edit-direct.js +114 -0
  185. package/data/probe/npm/index.d.ts +744 -0
  186. package/data/probe/npm/jest.config.js +52 -0
  187. package/data/probe/npm/package.json +117 -0
  188. package/data/probe/npm/scripts/build-agent.cjs +75 -0
  189. package/data/probe/npm/scripts/build-cjs.js +124 -0
  190. package/data/probe/npm/scripts/build-mcp.cjs +36 -0
  191. package/data/probe/npm/scripts/postinstall.js +216 -0
  192. package/data/probe/npm/test-codex-e2e.js +78 -0
  193. package/data/probe/npm/test-download-lock.js +109 -0
  194. package/data/probe/npm/test-grep-security.js +94 -0
  195. package/data/probe/npm/test-grep-simplified.js +63 -0
  196. package/data/probe/npm/test-grep.js +51 -0
  197. package/data/probe/npm/tests/README.md +96 -0
  198. package/data/probe/npm/tests/agent-compact-history.test.js +174 -0
  199. package/data/probe/npm/tests/allow-tests-default.test.js +151 -0
  200. package/data/probe/npm/tests/contextCompactor.test.js +498 -0
  201. package/data/probe/npm/tests/delegate-config.test.js +353 -0
  202. package/data/probe/npm/tests/delegate-integration.test.js +348 -0
  203. package/data/probe/npm/tests/extractor-integration.test.js +162 -0
  204. package/data/probe/npm/tests/extractor.test.js +317 -0
  205. package/data/probe/npm/tests/fixtures/sampleDiagrams.js +267 -0
  206. package/data/probe/npm/tests/integration/claude-code-auto-fallback.spec.js +148 -0
  207. package/data/probe/npm/tests/integration/claude-code-multi-step.spec.js +127 -0
  208. package/data/probe/npm/tests/integration/claude-code-tool-events.spec.js +163 -0
  209. package/data/probe/npm/tests/integration/codex-auto-fallback.spec.js +191 -0
  210. package/data/probe/npm/tests/integration/codex-tool-events.spec.js +147 -0
  211. package/data/probe/npm/tests/integration/examplesChatMcp.test.js +402 -0
  212. package/data/probe/npm/tests/integration/mcpDotenvSupport.test.js +174 -0
  213. package/data/probe/npm/tests/integration/mcpErrorHandling.test.js +566 -0
  214. package/data/probe/npm/tests/integration/mcpRobustness.test.js +564 -0
  215. package/data/probe/npm/tests/integration/mcpStdoutPurity.test.js +355 -0
  216. package/data/probe/npm/tests/integration/probeAgentMcp.test.js +398 -0
  217. package/data/probe/npm/tests/integration/retryFallback.test.js +368 -0
  218. package/data/probe/npm/tests/integration/schema-in-initial-message.test.js +318 -0
  219. package/data/probe/npm/tests/integration/schema-validation-loop-prevention.test.js +244 -0
  220. package/data/probe/npm/tests/integration/schemaRetryLogic.test.js +94 -0
  221. package/data/probe/npm/tests/integration/validationFlow.test.js +329 -0
  222. package/data/probe/npm/tests/manual/test-codex-basic.js +110 -0
  223. package/data/probe/npm/tests/mcp/mcpClientManager.test.js +614 -0
  224. package/data/probe/npm/tests/mcp/mcpConfig.test.js +359 -0
  225. package/data/probe/npm/tests/mcp/mcpXmlBridge.test.js +436 -0
  226. package/data/probe/npm/tests/mcp/mockMcpServer.js +510 -0
  227. package/data/probe/npm/tests/mcp-strict-syntax.test.js +319 -0
  228. package/data/probe/npm/tests/mermaidQuoteEscaping.test.js +214 -0
  229. package/data/probe/npm/tests/nestedQuoteFix.test.js +40 -0
  230. package/data/probe/npm/tests/setup.js +46 -0
  231. package/data/probe/npm/tests/unit/allowed-tools.test.js +513 -0
  232. package/data/probe/npm/tests/unit/attempt-completion-closing-tag-in-content.test.js +188 -0
  233. package/data/probe/npm/tests/unit/attemptCompletionJsonFix.test.js +238 -0
  234. package/data/probe/npm/tests/unit/attemptCompletionJsonIssue.test.js +128 -0
  235. package/data/probe/npm/tests/unit/backtickAutoFix.test.js +35 -0
  236. package/data/probe/npm/tests/unit/bash-probe-agent-integration.test.js +389 -0
  237. package/data/probe/npm/tests/unit/bash-simple-commands.test.js +324 -0
  238. package/data/probe/npm/tests/unit/bash-tool-comprehensive.test.js +371 -0
  239. package/data/probe/npm/tests/unit/bash-tool-integration.test.js +310 -0
  240. package/data/probe/npm/tests/unit/bash-tool.test.js +341 -0
  241. package/data/probe/npm/tests/unit/completion-prompt.test.js +379 -0
  242. package/data/probe/npm/tests/unit/cwd-path-options.test.js +287 -0
  243. package/data/probe/npm/tests/unit/delegate-limits.test.js +422 -0
  244. package/data/probe/npm/tests/unit/direct-content-attempt-completion.test.js +235 -0
  245. package/data/probe/npm/tests/unit/edit-create-tools.test.js +609 -0
  246. package/data/probe/npm/tests/unit/enhancedMermaidValidation.test.js +577 -0
  247. package/data/probe/npm/tests/unit/extract-content.test.js +83 -0
  248. package/data/probe/npm/tests/unit/extract-multiple-targets.test.js +89 -0
  249. package/data/probe/npm/tests/unit/fallbackManager.test.js +442 -0
  250. package/data/probe/npm/tests/unit/githubCompatibilityValidation.test.js +258 -0
  251. package/data/probe/npm/tests/unit/imageConfig.test.js +149 -0
  252. package/data/probe/npm/tests/unit/imagePathResolution.test.js +345 -0
  253. package/data/probe/npm/tests/unit/json-fixing-agent.test.js +238 -0
  254. package/data/probe/npm/tests/unit/json-validation-enhanced-errors.test.js +199 -0
  255. package/data/probe/npm/tests/unit/jsonValidationInfiniteLoopFix.test.js +228 -0
  256. package/data/probe/npm/tests/unit/maidIntegration.test.js +139 -0
  257. package/data/probe/npm/tests/unit/maxIterationsWarning.test.js +195 -0
  258. package/data/probe/npm/tests/unit/mermaidEdgeLabelFix.test.js +161 -0
  259. package/data/probe/npm/tests/unit/mermaidHtmlEntities.test.js +76 -0
  260. package/data/probe/npm/tests/unit/mermaidInfiniteLoopFix.test.js +64 -0
  261. package/data/probe/npm/tests/unit/mermaidValidation.test.js +723 -0
  262. package/data/probe/npm/tests/unit/mermaidValidationVisorExample.test.js +309 -0
  263. package/data/probe/npm/tests/unit/probe-agent-clone-realistic.test.js +643 -0
  264. package/data/probe/npm/tests/unit/probe-agent-clone.test.js +476 -0
  265. package/data/probe/npm/tests/unit/probe-agent-delegate.test.js +400 -0
  266. package/data/probe/npm/tests/unit/probe-agent-model-option.test.js +118 -0
  267. package/data/probe/npm/tests/unit/probeTool-security.test.js +283 -0
  268. package/data/probe/npm/tests/unit/readImageTool.test.js +418 -0
  269. package/data/probe/npm/tests/unit/retryManager.test.js +317 -0
  270. package/data/probe/npm/tests/unit/schema-aware-reminders.test.js +288 -0
  271. package/data/probe/npm/tests/unit/schemaDefinitionDetection.test.js +115 -0
  272. package/data/probe/npm/tests/unit/schemaUtils.test.js +1268 -0
  273. package/data/probe/npm/tests/unit/simpleTelemetry.test.js +282 -0
  274. package/data/probe/npm/tests/unit/simplified-attempt-completion.test.js +274 -0
  275. package/data/probe/npm/tests/unit/single-quote-json-bug.test.js +231 -0
  276. package/data/probe/npm/tests/unit/subgraphAutoFix.test.js +110 -0
  277. package/data/probe/npm/tests/unit/system-prompt.test.js +32 -0
  278. package/data/probe/npm/tests/unit/types-probe-agent-options.test.js +42 -0
  279. package/data/probe/npm/tests/unit/xmlParsing.test.js +720 -0
  280. package/data/probe/npm/tsconfig.json +21 -0
  281. package/data/probe/result1.txt +19 -0
  282. package/data/probe/result2.txt +26 -0
  283. package/data/probe/scripts/benchmark.sh +270 -0
  284. package/data/probe/scripts/cache_memory_analysis.rs +844 -0
  285. package/data/probe/scripts/claude-hook-wrapper.sh +56 -0
  286. package/data/probe/site/.env.example +10 -0
  287. package/data/probe/site/DEPLOYMENT.md +86 -0
  288. package/data/probe/site/README.md +183 -0
  289. package/data/probe/site/adding-languages.md +135 -0
  290. package/data/probe/site/ai-chat.md +427 -0
  291. package/data/probe/site/ai-integration.md +1488 -0
  292. package/data/probe/site/blog/agentic-flow-custom-xml-protocol.md +407 -0
  293. package/data/probe/site/blog/index.md +118 -0
  294. package/data/probe/site/blog/v0.6.0-release.md +426 -0
  295. package/data/probe/site/blog.md +8 -0
  296. package/data/probe/site/changelog.md +200 -0
  297. package/data/probe/site/cli-mode.md +437 -0
  298. package/data/probe/site/code-extraction.md +436 -0
  299. package/data/probe/site/contributing/README.md +9 -0
  300. package/data/probe/site/contributing/documentation-cross-references.md +215 -0
  301. package/data/probe/site/contributing/documentation-maintenance.md +275 -0
  302. package/data/probe/site/contributing/documentation-structure.md +75 -0
  303. package/data/probe/site/documentation-cross-references.md +215 -0
  304. package/data/probe/site/documentation-guide.md +132 -0
  305. package/data/probe/site/documentation-maintenance.md +275 -0
  306. package/data/probe/site/features.md +147 -0
  307. package/data/probe/site/how-it-works.md +118 -0
  308. package/data/probe/site/index.md +175 -0
  309. package/data/probe/site/index.md.bak +133 -0
  310. package/data/probe/site/installation.md +235 -0
  311. package/data/probe/site/integrations/docker.md +248 -0
  312. package/data/probe/site/integrations/github-actions.md +413 -0
  313. package/data/probe/site/language-support-overview.md +168 -0
  314. package/data/probe/site/mcp-integration.md +587 -0
  315. package/data/probe/site/mcp-server.md +304 -0
  316. package/data/probe/site/navigation-structure.md +76 -0
  317. package/data/probe/site/nodejs-sdk.md +798 -0
  318. package/data/probe/site/output-formats.md +625 -0
  319. package/data/probe/site/package.json +21 -0
  320. package/data/probe/site/public/_headers +28 -0
  321. package/data/probe/site/public/_redirects +11 -0
  322. package/data/probe/site/quick-start.md +289 -0
  323. package/data/probe/site/search-functionality.md +291 -0
  324. package/data/probe/site/search-reference.md +291 -0
  325. package/data/probe/site/supported-languages.md +215 -0
  326. package/data/probe/site/use-cases/README.md +8 -0
  327. package/data/probe/site/use-cases/advanced-cli.md +253 -0
  328. package/data/probe/site/use-cases/ai-code-editors.md +239 -0
  329. package/data/probe/site/use-cases/building-ai-tools.md +529 -0
  330. package/data/probe/site/use-cases/cli-ai-workflows.md +285 -0
  331. package/data/probe/site/use-cases/deploying-probe-web-interface.md +255 -0
  332. package/data/probe/site/use-cases/integrating-probe-into-ai-code-editors.md +161 -0
  333. package/data/probe/site/use-cases/nodejs-sdk.md +596 -0
  334. package/data/probe/site/use-cases/team-chat.md +350 -0
  335. package/data/probe/site/web-interface.md +434 -0
  336. package/data/probe/site/wrangler.toml +9 -0
  337. package/data/probe/test-api-key.sh +1 -0
  338. package/data/probe/test-probe-implementation/hello.js +7 -0
  339. package/data/probe/test_cases/demonstrate_early_termination_issues.sh +176 -0
  340. package/data/probe/test_cases/early_termination_issues.rs +533 -0
  341. package/data/probe/test_data/test_nested_struct.go +26 -0
  342. package/data/probe/tests/README.md +286 -0
  343. package/data/probe/tests/README_search_determinism_tests.md +116 -0
  344. package/data/probe/tests/adjacent_comment_test.rs +152 -0
  345. package/data/probe/tests/apostrophe_handling_tests.rs +132 -0
  346. package/data/probe/tests/block_filtering_with_ast_tests.rs +669 -0
  347. package/data/probe/tests/block_merging_tests.rs +396 -0
  348. package/data/probe/tests/c_outline_format_tests.rs +2179 -0
  349. package/data/probe/tests/cache_invalidation_issues.rs.disabled +682 -0
  350. package/data/probe/tests/cache_order_tests.rs +147 -0
  351. package/data/probe/tests/cache_query_scoping_tests.rs +221 -0
  352. package/data/probe/tests/cli_tests.rs +680 -0
  353. package/data/probe/tests/comment_context_integration_test.rs +240 -0
  354. package/data/probe/tests/common.rs +33 -0
  355. package/data/probe/tests/complex_block_merging_tests.rs +599 -0
  356. package/data/probe/tests/complex_query_block_filtering_tests.rs +422 -0
  357. package/data/probe/tests/control_flow_closing_braces_test.rs +91 -0
  358. package/data/probe/tests/cpp_outline_format_tests.rs +1507 -0
  359. package/data/probe/tests/csharp_outline_format_tests.rs +941 -0
  360. package/data/probe/tests/elastic_query_integration_tests.rs +922 -0
  361. package/data/probe/tests/extract_command_tests.rs +1848 -0
  362. package/data/probe/tests/extract_deduplication_tests.rs +146 -0
  363. package/data/probe/tests/extract_input_file_tests.rs +84 -0
  364. package/data/probe/tests/extract_prompt_tests.rs +102 -0
  365. package/data/probe/tests/filename_search_tests.rs +96 -0
  366. package/data/probe/tests/fixtures/user/AssemblyInfo.cs +3 -0
  367. package/data/probe/tests/github_extract_tests.rs +234 -0
  368. package/data/probe/tests/go_comment_test.rs +253 -0
  369. package/data/probe/tests/go_outline_format_tests.rs +2587 -0
  370. package/data/probe/tests/go_path_resolver_tests.rs +96 -0
  371. package/data/probe/tests/html_outline_format_tests.rs +637 -0
  372. package/data/probe/tests/integration_tests.rs +837 -0
  373. package/data/probe/tests/ip_whitelist_test.rs +148 -0
  374. package/data/probe/tests/java_outline_format_tests.rs +1611 -0
  375. package/data/probe/tests/javascript_extract_tests.rs +315 -0
  376. package/data/probe/tests/javascript_outline_format_tests.rs +1464 -0
  377. package/data/probe/tests/json_format_tests.rs +436 -0
  378. package/data/probe/tests/json_schema_validation_tests.rs +450 -0
  379. package/data/probe/tests/lib_usage.rs +60 -0
  380. package/data/probe/tests/line_comment_context_extension_test.rs +459 -0
  381. package/data/probe/tests/line_map_cache_tests.rs +114 -0
  382. package/data/probe/tests/markdown_integration_tests.rs +190 -0
  383. package/data/probe/tests/mocks/test_ip_whitelist.go +11 -0
  384. package/data/probe/tests/mocks/test_object.js +27 -0
  385. package/data/probe/tests/mocks/test_struct.go +50 -0
  386. package/data/probe/tests/multi_keyword_pattern_tests.rs +464 -0
  387. package/data/probe/tests/multi_language_syntax_integration_tests.rs +218 -0
  388. package/data/probe/tests/multiple_capture_groups_tests.rs +169 -0
  389. package/data/probe/tests/negative_compound_word_tests.rs +246 -0
  390. package/data/probe/tests/nested_symbol_extraction_tests.rs +99 -0
  391. package/data/probe/tests/outline_cross_file_interference_test.rs +335 -0
  392. package/data/probe/tests/outline_keyword_preservation_test.rs +67 -0
  393. package/data/probe/tests/output_format_edge_cases_tests.rs +693 -0
  394. package/data/probe/tests/parallel_extraction_tests.rs +178 -0
  395. package/data/probe/tests/parallel_search_tests.rs +355 -0
  396. package/data/probe/tests/path_resolver_tests.rs +698 -0
  397. package/data/probe/tests/php_outline_format_extended_tests.rs +928 -0
  398. package/data/probe/tests/php_outline_format_tests.rs +768 -0
  399. package/data/probe/tests/property_tests.proptest-regressions +9 -0
  400. package/data/probe/tests/property_tests.rs +118 -0
  401. package/data/probe/tests/python_outline_format_tests.rs +1538 -0
  402. package/data/probe/tests/query_command_json_tests.rs +438 -0
  403. package/data/probe/tests/query_command_tests.rs +232 -0
  404. package/data/probe/tests/query_command_xml_tests.rs +569 -0
  405. package/data/probe/tests/quoted_term_with_negative_keyword_tests.rs +216 -0
  406. package/data/probe/tests/required_terms_filename_tests.rs +116 -0
  407. package/data/probe/tests/ruby_outline_format_tests.rs +1011 -0
  408. package/data/probe/tests/rust_line_comment_context_test.rs +151 -0
  409. package/data/probe/tests/rust_outline_format_enhanced_tests.rs +725 -0
  410. package/data/probe/tests/rust_outline_format_tests.rs +843 -0
  411. package/data/probe/tests/schemas/xml_output_schema.xsd +38 -0
  412. package/data/probe/tests/search_determinism_tests.rs +451 -0
  413. package/data/probe/tests/search_hints_tests.rs +253 -0
  414. package/data/probe/tests/special_character_escaping_tests.rs +417 -0
  415. package/data/probe/tests/stemming_compound_word_filtering_tests.rs +535 -0
  416. package/data/probe/tests/strict_elastic_syntax_tests.rs +404 -0
  417. package/data/probe/tests/swift_outline_format_tests.rs +3319 -0
  418. package/data/probe/tests/symbols_tests.rs +166 -0
  419. package/data/probe/tests/test_file.rs +45 -0
  420. package/data/probe/tests/test_tokenize.rs +28 -0
  421. package/data/probe/tests/timeout_tests.rs +82 -0
  422. package/data/probe/tests/tokenization_tests.rs +195 -0
  423. package/data/probe/tests/tokenized_block_filtering_tests.rs +174 -0
  424. package/data/probe/tests/typescript_extract_tests.rs +214 -0
  425. package/data/probe/tests/typescript_outline_format_tests.rs +2188 -0
  426. package/data/probe/tests/xml_format_tests.rs +568 -0
  427. package/data/probe/tests/xml_schema_validation_tests.rs +497 -0
  428. package/data/scripts/postinstall.mjs +9 -0
  429. package/data/scripts/set-version.js +0 -0
  430. package/data/scripts/wiki-build.sh +111 -0
  431. package/data/scripts/wiki-deploy.sh +73 -0
  432. package/data/serve.json +12 -0
  433. package/data/test/demo-dynamic.html +134 -0
  434. package/data/test/demo-esm.html +105 -0
  435. package/data/test/demo-iife.html +78 -0
  436. package/data/tsconfig.json +7 -0
  437. package/data/vite.server.ts +483 -0
  438. package/data/vitest.config.ts +40 -0
  439. package/data/wiki/Home.md +58 -0
  440. package/data/wiki/_Sidebar.md +39 -0
  441. package/docs-mcp.config.json +20 -0
  442. package/package.json +56 -0
  443. package/src/config.js +111 -0
  444. package/src/index.js +395 -0
@@ -0,0 +1,1848 @@
1
+ use std::fs;
2
+ use std::path::PathBuf;
3
+ use std::process::Command;
4
+
5
+ // Import the necessary functions from the extract module
6
+ use probe_code::extract::{
7
+ extract_file_paths_from_git_diff, format_and_print_extraction_results, is_git_diff_format,
8
+ process_file_for_extraction,
9
+ };
10
+
11
+ #[test]
12
+ fn test_process_file_for_extraction_full_file() {
13
+ // Create a temporary file for testing
14
+ let temp_dir = tempfile::tempdir().unwrap();
15
+ let file_path = temp_dir.path().join("test_file.txt");
16
+ let content = "Line 1\nLine 2\nLine 3\n";
17
+ fs::write(&file_path, content).unwrap();
18
+
19
+ // Test processing the full file
20
+ let result =
21
+ process_file_for_extraction(&file_path, None, None, None, false, 0, None, false).unwrap();
22
+
23
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
24
+ assert_eq!(result.lines, (1, 3)); // 3 lines in the content
25
+ assert_eq!(result.node_type, "file");
26
+ assert_eq!(result.code, content);
27
+
28
+ // Test with non-existent file
29
+ let non_existent = temp_dir.path().join("non_existent.txt");
30
+ let err = process_file_for_extraction(&non_existent, None, None, None, false, 0, None, false)
31
+ .unwrap_err();
32
+ assert!(err.to_string().contains("does not exist"));
33
+ }
34
+
35
+ #[test]
36
+ fn test_process_file_for_extraction_with_line() {
37
+ // Create a temporary file with Rust code for testing
38
+ let temp_dir = tempfile::tempdir().unwrap();
39
+ let file_path = temp_dir.path().join("test_file.rs");
40
+ let content = r#"
41
+ fn main() {
42
+ println!("Hello, world!");
43
+
44
+ let x = 42;
45
+ if x > 0 {
46
+ println!("Positive");
47
+ } else {
48
+ println!("Non-positive");
49
+ }
50
+ }
51
+
52
+ struct Point {
53
+ x: i32,
54
+ y: i32,
55
+ }
56
+
57
+ impl Point {
58
+ fn new(x: i32, y: i32) -> Self {
59
+ Self { x, y }
60
+ }
61
+ }
62
+ "#;
63
+ fs::write(&file_path, content).unwrap();
64
+
65
+ // Test extracting a function
66
+ let result =
67
+ process_file_for_extraction(&file_path, Some(3), None, None, false, 0, None, false)
68
+ .unwrap();
69
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
70
+ assert!(result.lines.0 <= 3 && result.lines.1 >= 3);
71
+ assert!(result.code.contains("fn main()"));
72
+ assert!(result.code.contains("Hello, world!"));
73
+
74
+ // Test extracting a struct
75
+ let result =
76
+ process_file_for_extraction(&file_path, Some(13), None, None, false, 0, None, false)
77
+ .unwrap();
78
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
79
+ assert!(result.lines.0 <= 13 && result.lines.1 >= 13);
80
+ assert!(result.code.contains("struct Point"));
81
+ assert!(result.code.contains("x: i32"));
82
+ assert!(result.code.contains("y: i32"));
83
+
84
+ // Test with out-of-bounds line number (should be clamped to valid range)
85
+ let result =
86
+ process_file_for_extraction(&file_path, Some(1000), None, None, false, 0, None, false)
87
+ .unwrap();
88
+ // The line number should be clamped to the maximum valid line
89
+ // Don't check for exact equality, just make sure it's within valid range
90
+ assert!(result.lines.0 <= result.lines.1);
91
+ assert!(result.lines.1 <= content.lines().count());
92
+ }
93
+
94
+ #[test]
95
+ fn test_process_file_for_extraction_fallback() {
96
+ // Create a temporary file with a non-supported extension
97
+ let temp_dir = tempfile::tempdir().unwrap();
98
+ let file_path = temp_dir.path().join("test_file.xyz");
99
+ let mut content = String::from(
100
+ "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10\n",
101
+ );
102
+ content.push_str("Line 11\nLine 12\nLine 13\nLine 14\nLine 15\nLine 16\nLine 17\nLine 18\nLine 19\nLine 20\n");
103
+ content.push_str("Line 21\nLine 22\nLine 23\nLine 24\nLine 25\n");
104
+ fs::write(&file_path, content).unwrap();
105
+
106
+ // Test fallback to line-based context with default context lines (10)
107
+ let result =
108
+ process_file_for_extraction(&file_path, Some(15), None, None, false, 10, None, false)
109
+ .unwrap();
110
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
111
+ assert_eq!(result.node_type, "context");
112
+
113
+ // Should include 10 lines before and after line 15
114
+ let start_line = result.lines.0;
115
+ let end_line = result.lines.1;
116
+
117
+ // Check that the range includes line 15 and has appropriate context
118
+ assert!(start_line <= 15 && end_line >= 15);
119
+ assert!(end_line - start_line >= 10); // At least 10 lines of context
120
+
121
+ // Test with a line at the beginning of the file
122
+ let result =
123
+ process_file_for_extraction(&file_path, Some(2), None, None, false, 10, None, false)
124
+ .unwrap();
125
+ assert!(result.lines.0 <= 2); // Should start at or before line 2
126
+ assert!(result.lines.1 >= 2); // Should include line 2
127
+
128
+ // Test with a line at the end of the file
129
+ let result =
130
+ process_file_for_extraction(&file_path, Some(25), None, None, false, 10, None, false)
131
+ .unwrap();
132
+ assert!(result.lines.0 <= 25); // Should include some lines before line 25
133
+ assert_eq!(result.lines.1, 25); // Can't go beyond the last line
134
+
135
+ // Test with custom context lines
136
+ let result =
137
+ process_file_for_extraction(&file_path, Some(15), None, None, false, 5, None, false)
138
+ .unwrap();
139
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
140
+ assert_eq!(result.node_type, "context");
141
+
142
+ // Should include 5 lines before and after line 15
143
+ let start_line = result.lines.0;
144
+ let end_line = result.lines.1;
145
+
146
+ // Check that the range includes line 15 and has appropriate context
147
+ assert!(start_line <= 15 && end_line >= 15);
148
+ assert!(end_line - start_line >= 5); // At least 5 lines of context
149
+ assert!(end_line - start_line <= 11); // At most 11 lines (5 before, 5 after, plus the line itself)
150
+ }
151
+
152
+ #[test]
153
+ fn test_format_and_print_extraction_results() {
154
+ // Create a simple search result
155
+ let result = probe_code::models::SearchResult {
156
+ file: "test_file.rs".to_string(),
157
+ lines: (1, 5),
158
+ node_type: "function".to_string(),
159
+ code: "fn test() {\n println!(\"Hello\");\n}".to_string(),
160
+ symbol_signature: None,
161
+ matched_by_filename: None,
162
+ rank: None,
163
+ score: None,
164
+ tfidf_score: None,
165
+ bm25_score: None,
166
+ tfidf_rank: None,
167
+ bm25_rank: None,
168
+ new_score: None,
169
+ hybrid2_rank: None,
170
+ combined_score_rank: None,
171
+ file_unique_terms: None,
172
+ file_total_matches: None,
173
+ file_match_rank: None,
174
+ block_unique_terms: None,
175
+ block_total_matches: None,
176
+ parent_file_id: None,
177
+ block_id: None,
178
+ matched_keywords: None,
179
+ tokenized_content: None,
180
+ parent_context: None,
181
+ matched_lines: None,
182
+ };
183
+
184
+ // Test different formats
185
+ let results = vec![result];
186
+
187
+ // We can't easily test the output directly, but we can at least ensure the function doesn't panic
188
+ format_and_print_extraction_results(&results, "terminal", None, None, None, false).unwrap();
189
+ format_and_print_extraction_results(&results, "markdown", None, None, None, false).unwrap();
190
+ format_and_print_extraction_results(&results, "plain", None, None, None, false).unwrap();
191
+ format_and_print_extraction_results(&results, "json", None, None, None, false).unwrap();
192
+ format_and_print_extraction_results(&results, "xml", None, None, None, false).unwrap();
193
+
194
+ // Test with system prompt and user instructions
195
+ format_and_print_extraction_results(
196
+ &results,
197
+ "terminal",
198
+ None,
199
+ Some("Test system prompt"),
200
+ Some("Test user instructions"),
201
+ false,
202
+ )
203
+ .unwrap();
204
+ }
205
+
206
+ #[test]
207
+ fn test_json_format_extraction_results() {
208
+ use serde_json::Value;
209
+ use std::process::Command;
210
+ use tempfile::TempDir;
211
+
212
+ // Create a temporary file for testing
213
+ let temp_dir = TempDir::new().expect("Failed to create temp dir");
214
+ let file_path = temp_dir.path().join("test_file.rs");
215
+ let content = r#"
216
+ fn test() {
217
+ println!("Hello");
218
+ }
219
+ "#;
220
+ fs::write(&file_path, content).unwrap();
221
+
222
+ // Print the file path for debugging
223
+ println!("File path: {}", file_path.display());
224
+
225
+ // Get the project root directory (where Cargo.toml is)
226
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
227
+
228
+ // Run the extract command with JSON format
229
+ let output = Command::new("cargo")
230
+ .args([
231
+ "run",
232
+ "--manifest-path",
233
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
234
+ "--",
235
+ "extract",
236
+ file_path.to_string_lossy().as_ref(),
237
+ "--format",
238
+ "json",
239
+ "--allow-tests", // Add this flag to ensure test files are included
240
+ "--prompt",
241
+ "engineer",
242
+ "--instructions",
243
+ "Extract the main function",
244
+ ])
245
+ .output()
246
+ .expect("Failed to execute command");
247
+ // Print the command output for debugging
248
+ println!(
249
+ "Command stdout: {}",
250
+ String::from_utf8_lossy(&output.stdout)
251
+ );
252
+ println!(
253
+ "Command stderr: {}",
254
+ String::from_utf8_lossy(&output.stderr)
255
+ );
256
+
257
+ // Check that the command executed successfully
258
+ assert!(
259
+ output.status.success(),
260
+ "Command failed with status: {}",
261
+ output.status
262
+ );
263
+ assert!(output.status.success());
264
+
265
+ // Get the output as a string
266
+ let stdout = String::from_utf8_lossy(&output.stdout);
267
+
268
+ // Helper function to extract JSON from command output
269
+ fn extract_json_from_output(output: &str) -> &str {
270
+ // Find the first occurrence of '{'
271
+ if let Some(start_index) = output.find('{') {
272
+ // Return the substring from the first '{' to the end
273
+ &output[start_index..]
274
+ } else {
275
+ // If no '{' is found, return the original string
276
+ output
277
+ }
278
+ }
279
+
280
+ // Print the output for debugging
281
+ println!("Command stdout: {stdout}");
282
+
283
+ // Extract and parse the JSON
284
+ let json_str = extract_json_from_output(&stdout);
285
+ println!("Extracted JSON: {json_str}");
286
+ let json_value: Value = serde_json::from_str(json_str).expect("Failed to parse JSON output");
287
+
288
+ // Validate the structure of the JSON output
289
+ assert!(json_value.is_object(), "JSON output should be an object");
290
+ assert!(
291
+ json_value.get("results").is_some(),
292
+ "JSON output should have a 'results' field"
293
+ );
294
+ assert!(
295
+ json_value.get("summary").is_some(),
296
+ "JSON output should have a 'summary' field"
297
+ );
298
+
299
+ // Validate the results array
300
+ let results_array = json_value.get("results").unwrap().as_array().unwrap();
301
+ assert_eq!(
302
+ results_array.len(),
303
+ 1,
304
+ "Results array should have 1 element"
305
+ );
306
+
307
+ // Validate the structure of the result
308
+ let result_obj = &results_array[0];
309
+ assert!(
310
+ result_obj.get("file").is_some(),
311
+ "Result should have a 'file' field"
312
+ );
313
+ assert!(
314
+ result_obj.get("lines").is_some(),
315
+ "Result should have a 'lines' field"
316
+ );
317
+ assert!(
318
+ result_obj.get("node_type").is_some(),
319
+ "Result should have a 'node_type' field"
320
+ );
321
+ assert!(
322
+ result_obj.get("code").is_some(),
323
+ "Result should have a 'code' field"
324
+ );
325
+
326
+ // Validate the values
327
+ assert!(result_obj
328
+ .get("file")
329
+ .unwrap()
330
+ .as_str()
331
+ .unwrap()
332
+ .contains("test_file.rs"));
333
+ assert!(
334
+ result_obj.get("node_type").is_some(),
335
+ "Result should have a node_type field"
336
+ );
337
+ assert!(
338
+ result_obj
339
+ .get("code")
340
+ .unwrap()
341
+ .as_str()
342
+ .unwrap()
343
+ .contains("fn test()")
344
+ && result_obj
345
+ .get("code")
346
+ .unwrap()
347
+ .as_str()
348
+ .unwrap()
349
+ .contains("println!(\"Hello\")")
350
+ );
351
+
352
+ // Validate the lines field is an array with two elements
353
+ let lines = result_obj.get("lines").unwrap().as_array().unwrap();
354
+ assert_eq!(lines.len(), 2, "Lines should be an array with 2 elements");
355
+ assert!(
356
+ lines[0].as_u64().unwrap() >= 1,
357
+ "Start line should be at least 1"
358
+ );
359
+ assert!(
360
+ lines[1].as_u64().unwrap() >= lines[0].as_u64().unwrap(),
361
+ "End line should be at least start line"
362
+ );
363
+
364
+ // Validate the summary object
365
+ let summary = json_value.get("summary").unwrap();
366
+ assert!(summary.is_object(), "Summary should be an object");
367
+ assert!(
368
+ summary.get("count").is_some(),
369
+ "Summary should have a 'count' field"
370
+ );
371
+ assert!(
372
+ summary.get("total_bytes").is_some(),
373
+ "Summary should have a 'total_bytes' field"
374
+ );
375
+ assert!(
376
+ summary.get("total_tokens").is_some(),
377
+ "Summary should have a 'total_tokens' field"
378
+ );
379
+
380
+ // Validate the count matches the number of results
381
+ assert_eq!(summary.get("count").unwrap().as_u64().unwrap(), 1);
382
+
383
+ // Validate system_prompt and user_instructions are present
384
+ assert!(
385
+ json_value.get("system_prompt").is_some(),
386
+ "JSON output should have a 'system_prompt' field"
387
+ );
388
+ assert!(
389
+ json_value.get("user_instructions").is_some(),
390
+ "JSON output should have a 'user_instructions' field"
391
+ );
392
+
393
+ // Validate the content of system_prompt and user_instructions
394
+ assert!(
395
+ json_value
396
+ .get("user_instructions")
397
+ .unwrap()
398
+ .as_str()
399
+ .unwrap()
400
+ == "Extract the main function",
401
+ "user_instructions should match the provided value"
402
+ );
403
+ }
404
+
405
+ #[test]
406
+ fn test_xml_format_extraction_results() {
407
+ use roxmltree::{Document, Node};
408
+ use std::process::Command;
409
+ use tempfile::TempDir;
410
+
411
+ // Create a temporary file for testing
412
+ let temp_dir = TempDir::new().expect("Failed to create temp dir");
413
+ let file_path = temp_dir.path().join("test_file.rs");
414
+ let content = r#"
415
+ fn test() {
416
+ println!("Hello");
417
+ }
418
+ "#;
419
+ fs::write(&file_path, content).unwrap();
420
+
421
+ // Print the file path for debugging
422
+ println!("File path: {}", file_path.display());
423
+
424
+ // Get the project root directory (where Cargo.toml is)
425
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
426
+
427
+ // Run the extract command with XML format
428
+ let output = Command::new("cargo")
429
+ .args([
430
+ "run",
431
+ "--manifest-path",
432
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
433
+ "--",
434
+ "extract",
435
+ file_path.to_string_lossy().as_ref(),
436
+ "--format",
437
+ "xml",
438
+ "--allow-tests", // Add this flag to ensure test files are included
439
+ "--prompt",
440
+ "engineer",
441
+ "--instructions",
442
+ "Extract the main function",
443
+ ])
444
+ .output()
445
+ .expect("Failed to execute command");
446
+
447
+ // Print the command output for debugging
448
+ println!(
449
+ "Command stdout: {}",
450
+ String::from_utf8_lossy(&output.stdout)
451
+ );
452
+ println!(
453
+ "Command stderr: {}",
454
+ String::from_utf8_lossy(&output.stderr)
455
+ );
456
+
457
+ // Check that the command executed successfully
458
+ assert!(
459
+ output.status.success(),
460
+ "Command failed with status: {}",
461
+ output.status
462
+ );
463
+
464
+ // Get the output as a string
465
+ let stdout = String::from_utf8_lossy(&output.stdout);
466
+
467
+ // Helper function to extract XML from command output
468
+ fn extract_xml_from_output(output: &str) -> &str {
469
+ // Find the first occurrence of '<?xml'
470
+ if let Some(start_index) = output.find("<?xml") {
471
+ // Return the substring from the first '<?xml' to the end
472
+ &output[start_index..]
473
+ } else {
474
+ // If no '<?xml' is found, return the original string
475
+ output
476
+ }
477
+ }
478
+
479
+ // Print the output for debugging
480
+ println!("Command stdout: {stdout}");
481
+
482
+ // Extract and parse the XML
483
+ let xml_str = extract_xml_from_output(&stdout);
484
+ println!("Extracted XML: {xml_str}");
485
+ let doc = Document::parse(xml_str).expect("Failed to parse XML output");
486
+ let root = doc.root_element();
487
+
488
+ // Validate the structure of the XML output
489
+ assert_eq!(
490
+ root.tag_name().name(),
491
+ "probe_results",
492
+ "Root element should be 'probe_results'"
493
+ );
494
+
495
+ // Validate that there are result elements
496
+ let results_nodes: Vec<Node> = root
497
+ .children()
498
+ .filter(|n| n.is_element() && n.tag_name().name() == "result")
499
+ .collect();
500
+ assert_eq!(
501
+ results_nodes.len(),
502
+ 1,
503
+ "Should have exactly one result element"
504
+ );
505
+
506
+ // Validate the structure of the result
507
+ let result_node = &results_nodes[0];
508
+
509
+ // Check for required elements
510
+ let file = result_node
511
+ .children()
512
+ .find(|n| n.is_element() && n.tag_name().name() == "file");
513
+ assert!(file.is_some(), "Result should have a file element");
514
+ assert!(
515
+ file.unwrap().text().unwrap().contains("test_file.rs"),
516
+ "File element should contain the correct file path"
517
+ );
518
+
519
+ // For a full file extraction, the lines element might not be present
520
+ let node_type = result_node
521
+ .children()
522
+ .find(|n| n.is_element() && n.tag_name().name() == "node_type");
523
+ if let Some(node_type) = node_type {
524
+ if node_type.text().unwrap() != "file" {
525
+ let lines = result_node
526
+ .children()
527
+ .find(|n| n.is_element() && n.tag_name().name() == "lines");
528
+ assert!(lines.is_some(), "Result should have a lines element");
529
+ }
530
+
531
+ // Validate system_prompt and user_instructions are present
532
+ let system_prompt = root
533
+ .children()
534
+ .find(|n| n.is_element() && n.tag_name().name() == "system_prompt");
535
+ assert!(
536
+ system_prompt.is_some(),
537
+ "Should have a system_prompt element"
538
+ );
539
+
540
+ let user_instructions = root
541
+ .children()
542
+ .find(|n| n.is_element() && n.tag_name().name() == "user_instructions");
543
+ assert!(
544
+ user_instructions.is_some(),
545
+ "Should have a user_instructions element"
546
+ );
547
+
548
+ // Validate the content of user_instructions
549
+ assert_eq!(
550
+ user_instructions.unwrap().text().unwrap(),
551
+ "Extract the main function",
552
+ "user_instructions should match the provided value"
553
+ );
554
+ }
555
+
556
+ // The node_type element might not be present for all extraction types
557
+ let _node_type = result_node
558
+ .children()
559
+ .find(|n| n.is_element() && n.tag_name().name() == "node_type");
560
+
561
+ let code = result_node
562
+ .children()
563
+ .find(|n| n.is_element() && n.tag_name().name() == "code");
564
+ assert!(code.is_some(), "Result should have a code element");
565
+ assert!(
566
+ code.unwrap().text().unwrap().contains("fn test()")
567
+ && code
568
+ .unwrap()
569
+ .text()
570
+ .unwrap()
571
+ .contains("println!(\"Hello\")"),
572
+ "Code element should contain the correct code"
573
+ );
574
+
575
+ // Validate the summary element
576
+ let summary = root
577
+ .children()
578
+ .find(|n| n.is_element() && n.tag_name().name() == "summary");
579
+ assert!(summary.is_some(), "Should have a summary element");
580
+
581
+ if let Some(summary) = summary {
582
+ // Validate the summary contains count, total_bytes, and total_tokens
583
+ let count = summary
584
+ .children()
585
+ .find(|n| n.is_element() && n.tag_name().name() == "count");
586
+ assert!(count.is_some(), "Summary should have a count element");
587
+ assert_eq!(count.unwrap().text().unwrap(), "1", "Count should be 1");
588
+
589
+ let total_bytes = summary
590
+ .children()
591
+ .find(|n| n.is_element() && n.tag_name().name() == "total_bytes");
592
+ assert!(
593
+ total_bytes.is_some(),
594
+ "Summary should have a total_bytes element"
595
+ );
596
+
597
+ let total_tokens = summary
598
+ .children()
599
+ .find(|n| n.is_element() && n.tag_name().name() == "total_tokens");
600
+ assert!(
601
+ total_tokens.is_some(),
602
+ "Summary should have a total_tokens element"
603
+ );
604
+ }
605
+ }
606
+
607
+ #[test]
608
+ fn test_process_file_for_extraction_with_range() {
609
+ // Create a temporary file for testing
610
+ let temp_dir = tempfile::tempdir().unwrap();
611
+ let file_path = temp_dir.path().join("test_file.txt");
612
+ let mut content = String::new();
613
+ for i in 1..=20 {
614
+ content.push_str(&format!("Line {i}\n"));
615
+ }
616
+ fs::write(&file_path, &content).unwrap();
617
+
618
+ // Test extracting a range of lines
619
+ let result =
620
+ process_file_for_extraction(&file_path, Some(1), Some(10), None, false, 0, None, false)
621
+ .unwrap();
622
+ assert_eq!(result.file, file_path.to_string_lossy().to_string());
623
+ assert_eq!(result.lines, (1, 10));
624
+ assert_eq!(result.node_type, "range");
625
+
626
+ // Check that the extracted content contains exactly lines 1-10
627
+ let expected_content = content.lines().take(10).collect::<Vec<_>>().join("\n");
628
+ assert_eq!(result.code, expected_content);
629
+
630
+ // Test with a different range
631
+ let result =
632
+ process_file_for_extraction(&file_path, Some(5), Some(15), None, false, 0, None, false)
633
+ .unwrap();
634
+ assert_eq!(result.lines, (5, 15));
635
+
636
+ // Check that the extracted content contains exactly lines 5-15
637
+ let expected_content = content
638
+ .lines()
639
+ .skip(4)
640
+ .take(11)
641
+ .collect::<Vec<_>>()
642
+ .join("\n");
643
+ assert_eq!(result.code, expected_content);
644
+
645
+ // Test with invalid range (start > end) - should be clamped to valid range
646
+ let result =
647
+ process_file_for_extraction(&file_path, Some(10), Some(5), None, false, 0, None, false)
648
+ .unwrap();
649
+ // The start and end lines should be clamped to valid values
650
+ assert!(result.lines.0 <= result.lines.1);
651
+ assert!(result.lines.1 <= content.lines().count());
652
+
653
+ // Test with out-of-bounds range (should be clamped to valid range)
654
+ let result =
655
+ process_file_for_extraction(&file_path, Some(15), Some(25), None, false, 0, None, false)
656
+ .unwrap();
657
+ // The end line should be clamped to the maximum valid line
658
+ assert!(result.lines.0 <= 15);
659
+ assert!(result.lines.1 <= content.lines().count());
660
+ // The node_type should be "range"
661
+ assert_eq!(result.node_type, "range");
662
+ }
663
+
664
+ #[test]
665
+ fn test_integration_extract_command() {
666
+ // Create a temporary file for testing
667
+ let temp_dir = tempfile::tempdir().unwrap();
668
+ let file_path = temp_dir.path().join("test_file.rs");
669
+ let content = r#"
670
+ fn main() {
671
+ println!("Hello, world!");
672
+ }
673
+
674
+ struct Point {
675
+ x: i32,
676
+ y: i32,
677
+ }
678
+ "#;
679
+ fs::write(&file_path, content).unwrap();
680
+
681
+ // Print the current directory and file path for debugging
682
+ println!("Current directory: {:?}", std::env::current_dir().unwrap());
683
+ println!("File path: {file_path:?}");
684
+
685
+ // Get the project root directory (where Cargo.toml is)
686
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
687
+ println!("Project directory: {project_dir:?}");
688
+
689
+ // Run the extract command using cargo run from the project directory
690
+ let output = Command::new("cargo")
691
+ .args([
692
+ "run",
693
+ "--manifest-path",
694
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
695
+ "--",
696
+ "extract",
697
+ file_path.to_string_lossy().as_ref(),
698
+ "--allow-tests", // Add this flag to ensure test files are included
699
+ ])
700
+ .current_dir(&project_dir) // Ensure we're in the project directory
701
+ .output()
702
+ .expect("Failed to execute command");
703
+
704
+ // Print the command output for debugging
705
+ println!(
706
+ "Command stdout: {}",
707
+ String::from_utf8_lossy(&output.stdout)
708
+ );
709
+ println!(
710
+ "Command stderr: {}",
711
+ String::from_utf8_lossy(&output.stderr)
712
+ );
713
+
714
+ // Check that the command executed successfully
715
+ assert!(output.status.success());
716
+
717
+ // Check that the output contains the file content
718
+ let stdout = String::from_utf8_lossy(&output.stdout);
719
+ assert!(stdout.contains("File:"));
720
+ assert!(stdout.contains("fn main()"));
721
+ // Note: We don't check for "struct Point" here anymore since the extract command
722
+ // might only extract the function and not the struct depending on the AST parsing
723
+
724
+ // Get the project root directory (where Cargo.toml is)
725
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
726
+
727
+ // Run with a line number
728
+ let output = Command::new("cargo")
729
+ .args([
730
+ "run",
731
+ "--manifest-path",
732
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
733
+ "--",
734
+ "extract",
735
+ &format!("{}:3", file_path.to_string_lossy()),
736
+ "--allow-tests", // Add this flag to ensure test files are included
737
+ ])
738
+ .current_dir(&project_dir) // Ensure we're in the project directory
739
+ .output()
740
+ .expect("Failed to execute command");
741
+
742
+ // Check that the command executed successfully
743
+ assert!(output.status.success());
744
+
745
+ // Check that the output contains the function
746
+ let stdout = String::from_utf8_lossy(&output.stdout);
747
+ assert!(stdout.contains("File:"));
748
+ assert!(stdout.contains("fn main()"));
749
+ assert!(stdout.contains("Hello, world!"));
750
+
751
+ // Get the project root directory (where Cargo.toml is)
752
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
753
+
754
+ // Run with a different format
755
+ let output = Command::new("cargo")
756
+ .args([
757
+ "run",
758
+ "--manifest-path",
759
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
760
+ "--",
761
+ "extract",
762
+ file_path.to_string_lossy().as_ref(),
763
+ "--format",
764
+ "markdown",
765
+ "--allow-tests", // Add this flag to ensure test files are included
766
+ ])
767
+ .current_dir(&project_dir) // Ensure we're in the project directory
768
+ .output()
769
+ .expect("Failed to execute command");
770
+
771
+ // Check that the command executed successfully
772
+ assert!(output.status.success());
773
+
774
+ // Check that the output is in markdown format
775
+ let stdout = String::from_utf8_lossy(&output.stdout);
776
+ assert!(stdout.contains("## File:"));
777
+
778
+ // Get the project root directory (where Cargo.toml is)
779
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
780
+
781
+ // Run with a line range
782
+ let output = Command::new("cargo")
783
+ .args([
784
+ "run",
785
+ "--manifest-path",
786
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
787
+ "--",
788
+ "extract",
789
+ &format!("{}:2-7", file_path.to_string_lossy()),
790
+ "--allow-tests", // Add this flag to ensure test files are included
791
+ ])
792
+ .current_dir(&project_dir) // Ensure we're in the project directory
793
+ .output()
794
+ .expect("Failed to execute command");
795
+
796
+ // Check that the command executed successfully
797
+ assert!(output.status.success());
798
+
799
+ // Check that the output contains the specified range
800
+ let stdout = String::from_utf8_lossy(&output.stdout);
801
+ assert!(stdout.contains("File:"));
802
+ // The exact format of the line range might vary, so just check for the content
803
+ // We only check for the content that should definitely be in the range
804
+ assert!(
805
+ stdout.contains("fn main()")
806
+ || stdout.contains("println!(\"Hello, world!\")")
807
+ || stdout.contains("struct Point")
808
+ );
809
+ }
810
+
811
+ #[test]
812
+ fn test_integration_extract_command_json_format() {
813
+ use serde_json::Value;
814
+
815
+ // Create a temporary file for testing with special characters
816
+ let temp_dir = tempfile::tempdir().unwrap();
817
+ let file_path = temp_dir.path().join("test_file.rs");
818
+ let content = r#"
819
+ fn main() {
820
+ // This contains special characters: "quotes", 'apostrophes', <tags>, &ampersands
821
+ println!("Hello, \"world\"!");
822
+ let message = 'A';
823
+ let html = "<div>Content & More</div>";
824
+ }
825
+ "#;
826
+ fs::write(&file_path, content).unwrap();
827
+
828
+ // Get the project root directory (where Cargo.toml is)
829
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
830
+
831
+ // Run the extract command with JSON format
832
+ let output = Command::new("cargo")
833
+ .args([
834
+ "run",
835
+ "--manifest-path",
836
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
837
+ "--",
838
+ "extract",
839
+ file_path.to_string_lossy().as_ref(),
840
+ "--format",
841
+ "json",
842
+ "--allow-tests", // Add this flag to ensure test files are included
843
+ ])
844
+ .output()
845
+ .expect("Failed to execute command");
846
+
847
+ // Check that the command executed successfully
848
+ assert!(output.status.success());
849
+
850
+ // Get the output as a string
851
+ let stdout = String::from_utf8_lossy(&output.stdout);
852
+
853
+ // Helper function to extract JSON from command output
854
+ fn extract_json_from_output(output: &str) -> &str {
855
+ // Find the first occurrence of '{'
856
+ if let Some(start_index) = output.find('{') {
857
+ // Return the substring from the first '{' to the end
858
+ &output[start_index..]
859
+ } else {
860
+ // If no '{' is found, return the original string
861
+ output
862
+ }
863
+ }
864
+
865
+ // Extract and parse the JSON
866
+ let json_str = extract_json_from_output(&stdout);
867
+ let json_value: Value = serde_json::from_str(json_str).expect("Failed to parse JSON output");
868
+
869
+ // Validate the structure of the JSON output
870
+ assert!(json_value.is_object(), "JSON output should be an object");
871
+ assert!(
872
+ json_value.get("results").is_some(),
873
+ "JSON output should have a 'results' field"
874
+ );
875
+ assert!(
876
+ json_value.get("summary").is_some(),
877
+ "JSON output should have a 'summary' field"
878
+ );
879
+
880
+ // Validate the results array
881
+ let results = json_value.get("results").unwrap().as_array().unwrap();
882
+ assert!(!results.is_empty(), "Results array should not be empty");
883
+
884
+ // Validate the first result
885
+ let first_result = &results[0];
886
+ assert!(
887
+ first_result.get("file").is_some(),
888
+ "Result should have a 'file' field"
889
+ );
890
+ assert!(
891
+ first_result.get("code").is_some(),
892
+ "Result should have a 'code' field"
893
+ );
894
+
895
+ // Verify that special characters are properly escaped in the JSON
896
+ let code = first_result.get("code").unwrap().as_str().unwrap();
897
+ assert!(
898
+ code.contains("\"quotes\""),
899
+ "Double quotes should be properly escaped in JSON"
900
+ );
901
+ assert!(
902
+ code.contains("'apostrophes'"),
903
+ "Apostrophes should be properly escaped in JSON"
904
+ );
905
+ assert!(
906
+ code.contains("<tags>"),
907
+ "Tags should be properly escaped in JSON"
908
+ );
909
+ assert!(
910
+ code.contains("&ampersands"),
911
+ "Ampersands should be properly escaped in JSON"
912
+ );
913
+
914
+ // Get the project root directory (where Cargo.toml is)
915
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
916
+
917
+ // Run with a line number and JSON format
918
+ let output = Command::new("cargo")
919
+ .args([
920
+ "run",
921
+ "--manifest-path",
922
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
923
+ "--",
924
+ "extract",
925
+ &format!("{}:3", file_path.to_string_lossy()),
926
+ "--format",
927
+ "json",
928
+ "--allow-tests", // Add this flag to ensure test files are included
929
+ ])
930
+ .current_dir(&project_dir) // Ensure we're in the project directory
931
+ .output()
932
+ .expect("Failed to execute command");
933
+
934
+ // Print the command output for debugging
935
+ println!(
936
+ "Command stdout: {}",
937
+ String::from_utf8_lossy(&output.stdout)
938
+ );
939
+ println!(
940
+ "Command stderr: {}",
941
+ String::from_utf8_lossy(&output.stderr)
942
+ );
943
+
944
+ // Check that the command executed successfully
945
+ assert!(
946
+ output.status.success(),
947
+ "Command failed with status: {}",
948
+ output.status
949
+ );
950
+
951
+ // Parse the JSON output
952
+ let stdout = String::from_utf8_lossy(&output.stdout);
953
+ let json_str = extract_json_from_output(&stdout);
954
+ let json_value: Value = serde_json::from_str(json_str).expect("Failed to parse JSON output");
955
+
956
+ // Validate the structure
957
+ let results = json_value.get("results").unwrap().as_array().unwrap();
958
+ assert!(!results.is_empty(), "Results array should not be empty");
959
+
960
+ // The result should contain the function with line 3
961
+ let first_result = &results[0];
962
+ let code = first_result.get("code").unwrap().as_str().unwrap();
963
+ assert!(
964
+ code.contains("fn main()"),
965
+ "Code should contain the main function"
966
+ );
967
+ assert!(
968
+ code.contains("Hello, \\\"world\\\"!"),
969
+ "Code should contain the println statement with escaped quotes"
970
+ );
971
+ }
972
+
973
+ #[test]
974
+ fn test_is_git_diff_format() {
975
+ // Test with git diff format
976
+ let diff_content = r#"diff --git a/tests/property_tests.rs b/tests/property_tests.rs
977
+ index cb2cb64..3717769 100644
978
+ --- a/tests/property_tests.rs
979
+ +++ b/tests/property_tests.rs
980
+ @@ -45,7 +45,7 @@ proptest! {
981
+ Err(_) => return proptest::test_runner::TestCaseResult::Ok(()), // Skip invalid queries
982
+ };
983
+
984
+ - let patterns = create_structured_patterns(&plan);
985
+ + let patterns = create_structured_patterns(&plan, false);
986
+ "#;
987
+ assert!(
988
+ is_git_diff_format(diff_content),
989
+ "Should detect git diff format"
990
+ );
991
+
992
+ // Test with whitespace before git diff format
993
+ let diff_content_with_whitespace = r#"
994
+
995
+ diff --git a/tests/property_tests.rs b/tests/property_tests.rs
996
+ index cb2cb64..3717769 100644
997
+ "#;
998
+ assert!(
999
+ is_git_diff_format(diff_content_with_whitespace),
1000
+ "Should detect git diff format with leading whitespace"
1001
+ );
1002
+
1003
+ // Test with non-git diff format
1004
+ let non_diff_content = r#"This is not a git diff format
1005
+ It's just some text
1006
+ "#;
1007
+ assert!(
1008
+ !is_git_diff_format(non_diff_content),
1009
+ "Should not detect git diff format"
1010
+ );
1011
+ }
1012
+
1013
+ #[test]
1014
+ fn test_extract_file_paths_from_git_diff() {
1015
+ // Sample git diff output
1016
+ let diff_content = r#"diff --git a/tests/property_tests.rs b/tests/property_tests.rs
1017
+ index cb2cb64..3717769 100644
1018
+ --- a/tests/property_tests.rs
1019
+ +++ b/tests/property_tests.rs
1020
+ @@ -45,7 +45,7 @@ proptest! {
1021
+ Err(_) => return proptest::test_runner::TestCaseResult::Ok(()), // Skip invalid queries
1022
+ };
1023
+
1024
+ - let patterns = create_structured_patterns(&plan);
1025
+ + let patterns = create_structured_patterns(&plan, false);
1026
+
1027
+ // Check that we have at least one pattern for each term
1028
+ for (term, &idx) in &plan.term_indices {
1029
+ "#;
1030
+
1031
+ // Parse the git diff
1032
+ let file_paths = extract_file_paths_from_git_diff(diff_content, true);
1033
+
1034
+ // Verify that we extracted the correct file path and line number
1035
+ assert_eq!(file_paths.len(), 1, "Should extract exactly one file path");
1036
+
1037
+ let (path, start_line, end_line, symbol, _specific_lines) = &file_paths[0];
1038
+ assert_eq!(
1039
+ path,
1040
+ &PathBuf::from("tests/property_tests.rs"),
1041
+ "Should extract the correct file path"
1042
+ );
1043
+ assert_eq!(
1044
+ *start_line,
1045
+ Some(48),
1046
+ "Should extract the correct line number"
1047
+ );
1048
+ assert_eq!(*end_line, Some(48), "End line should be 48");
1049
+ assert_eq!(*symbol, None, "Symbol should be None");
1050
+ }
1051
+
1052
+ #[test]
1053
+ fn test_extract_file_paths_from_git_diff_multiple_files() {
1054
+ // Sample git diff output with multiple files
1055
+ let diff_content = r#"diff --git a/tests/property_tests.rs b/tests/property_tests.rs
1056
+ index cb2cb64..3717769 100644
1057
+ --- a/tests/property_tests.rs
1058
+ +++ b/tests/property_tests.rs
1059
+ @@ -45,7 +45,7 @@ proptest! {
1060
+ Err(_) => return proptest::test_runner::TestCaseResult::Ok(()), // Skip invalid queries
1061
+ };
1062
+
1063
+ - let patterns = create_structured_patterns(&plan);
1064
+ + let patterns = create_structured_patterns(&plan, false);
1065
+
1066
+ // Check that we have at least one pattern for each term
1067
+ for (term, &idx) in &plan.term_indices {
1068
+ diff --git a/tests/tokenization_tests.rs b/tests/tokenization_tests.rs
1069
+ index abcdef1..1234567 100644
1070
+ --- a/tests/tokenization_tests.rs
1071
+ +++ b/tests/tokenization_tests.rs
1072
+ @@ -20,7 +20,7 @@ fn test_tokenize_with_stemming() {
1073
+ let tokens = tokenize_with_stemming("running runs runner");
1074
+
1075
+ - assert_eq!(tokens, vec!["run", "run", "runner"]);
1076
+ + assert_eq!(tokens, vec!["run", "run", "run"]);
1077
+ }
1078
+ "#;
1079
+
1080
+ // Parse the git diff
1081
+ let file_paths = extract_file_paths_from_git_diff(diff_content, true);
1082
+
1083
+ // Verify that we extracted the correct file paths and line numbers
1084
+ assert_eq!(file_paths.len(), 2, "Should extract exactly two file paths");
1085
+
1086
+ // Sort the results by file path to ensure consistent order for testing
1087
+ let mut sorted_paths = file_paths.clone();
1088
+ sorted_paths.sort_by(|a, b| a.0.cmp(&b.0));
1089
+
1090
+ // Check first file
1091
+ let (path1, start_line1, end_line1, symbol1, _specific_lines1) = &sorted_paths[0];
1092
+ assert_eq!(
1093
+ path1,
1094
+ &PathBuf::from("tests/property_tests.rs"),
1095
+ "Should extract the correct file path"
1096
+ );
1097
+ assert_eq!(
1098
+ *start_line1,
1099
+ Some(48),
1100
+ "Should extract the correct line number"
1101
+ );
1102
+ assert_eq!(*end_line1, Some(48), "End line should be 48");
1103
+ assert_eq!(*symbol1, None, "Symbol should be None");
1104
+
1105
+ // Check second file
1106
+ let (path2, start_line2, end_line2, symbol2, _specific_lines2) = &sorted_paths[1];
1107
+ assert_eq!(
1108
+ path2,
1109
+ &PathBuf::from("tests/tokenization_tests.rs"),
1110
+ "Should extract the correct file path"
1111
+ );
1112
+ assert_eq!(
1113
+ *start_line2,
1114
+ Some(22),
1115
+ "Should extract the correct line number"
1116
+ );
1117
+ assert_eq!(*end_line2, Some(22), "End line should be 22");
1118
+ assert_eq!(*symbol2, None, "Symbol should be None");
1119
+ }
1120
+
1121
+ #[test]
1122
+ fn test_integration_extract_command_with_diff_flag() {
1123
+ // Create a temporary file for testing
1124
+ let temp_dir = tempfile::tempdir().unwrap();
1125
+ let file_path = temp_dir.path().join("test_file.rs");
1126
+ let content = r#"
1127
+ fn main() {
1128
+ println!("Hello, world!");
1129
+ }
1130
+
1131
+ struct Point {
1132
+ x: i32,
1133
+ y: i32,
1134
+ }
1135
+ "#;
1136
+ fs::write(&file_path, content).unwrap();
1137
+
1138
+ // Create a git diff file
1139
+ let diff_path = temp_dir.path().join("test.diff");
1140
+ let diff_content = format!(
1141
+ r#"diff --git a/{0} b/{0}
1142
+ index cb2cb64..3717769 100644
1143
+ --- a/{0}
1144
+ +++ b/{0}
1145
+ @@ -3,1 +3,1 @@ fn main() {{
1146
+ - println!("Hello, world!");
1147
+ + println!("Hello, universe!");
1148
+ "#,
1149
+ file_path.file_name().unwrap().to_string_lossy()
1150
+ );
1151
+ fs::write(&diff_path, &diff_content).unwrap();
1152
+
1153
+ // Get the project root directory (where Cargo.toml is)
1154
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1155
+
1156
+ // Run the extract command with diff option
1157
+ let output = Command::new("cargo")
1158
+ .args([
1159
+ "run",
1160
+ "--manifest-path",
1161
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1162
+ "--",
1163
+ "extract",
1164
+ "--diff",
1165
+ diff_path.to_string_lossy().as_ref(),
1166
+ "--allow-tests", // Add this flag to ensure test files are included
1167
+ ])
1168
+ .output()
1169
+ .expect("Failed to execute command");
1170
+
1171
+ // Check that the command executed successfully
1172
+ assert!(output.status.success(), "Command failed to execute");
1173
+
1174
+ let stdout = String::from_utf8_lossy(&output.stdout);
1175
+ println!("Command output: {stdout}");
1176
+
1177
+ // The output should contain information about the extracted file
1178
+ assert!(
1179
+ stdout.contains("Files to extract:"),
1180
+ "Output should contain file list"
1181
+ );
1182
+ assert!(
1183
+ stdout.contains("test_file.rs"),
1184
+ "Output should contain the extracted file name"
1185
+ );
1186
+ }
1187
+
1188
+ #[test]
1189
+ fn test_integration_extract_command_with_auto_diff_detection() {
1190
+ // Create a temporary file for testing
1191
+ let temp_dir = tempfile::tempdir().unwrap();
1192
+ let file_path = temp_dir.path().join("test_file.rs");
1193
+ let content = r#"
1194
+ fn main() {
1195
+ println!("Hello, world!");
1196
+ }
1197
+
1198
+ struct Point {
1199
+ x: i32,
1200
+ y: i32,
1201
+ }
1202
+ "#;
1203
+ fs::write(&file_path, content).unwrap();
1204
+
1205
+ // Create a git diff file
1206
+ let diff_path = temp_dir.path().join("test.diff");
1207
+ let diff_content = format!(
1208
+ r#"diff --git a/{0} b/{0}
1209
+ index cb2cb64..3717769 100644
1210
+ --- a/{0}
1211
+ +++ b/{0}
1212
+ @@ -3,1 +3,1 @@ fn main() {{
1213
+ - println!("Hello, world!");
1214
+ + println!("Hello, universe!");
1215
+ "#,
1216
+ file_path.file_name().unwrap().to_string_lossy()
1217
+ );
1218
+ fs::write(&diff_path, &diff_content).unwrap();
1219
+
1220
+ // Get the project root directory (where Cargo.toml is)
1221
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1222
+
1223
+ // Run the extract command WITHOUT the diff flag - it should auto-detect
1224
+ let output = Command::new("cargo")
1225
+ .args([
1226
+ "run",
1227
+ "--manifest-path",
1228
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1229
+ "--",
1230
+ "extract",
1231
+ diff_path.to_string_lossy().as_ref(),
1232
+ "--allow-tests", // Add this flag to ensure test files are included
1233
+ ])
1234
+ .output()
1235
+ .expect("Failed to execute command");
1236
+
1237
+ // Check that the command executed successfully
1238
+ assert!(output.status.success(), "Command failed to execute");
1239
+
1240
+ let stdout = String::from_utf8_lossy(&output.stdout);
1241
+ println!("Command output: {stdout}");
1242
+
1243
+ // The output should contain information about the extracted file
1244
+ assert!(
1245
+ stdout.contains("Files to extract:"),
1246
+ "Output should contain file list"
1247
+ );
1248
+ assert!(
1249
+ stdout.contains("test_file.rs"),
1250
+ "Output should contain the extracted file name"
1251
+ );
1252
+ }
1253
+
1254
+ #[test]
1255
+ fn test_integration_extract_command_xml_format() {
1256
+ use roxmltree::Document;
1257
+
1258
+ // Create a temporary file for testing with special characters
1259
+ let temp_dir = tempfile::tempdir().unwrap();
1260
+ let file_path = temp_dir.path().join("test_file.rs");
1261
+ let content = r#"
1262
+ fn main() {
1263
+ // This contains special characters: "quotes", 'apostrophes', <tags>, &ampersands
1264
+ println!("Hello, \"world\"!");
1265
+ let message = 'A';
1266
+ let html = "<div>Content & More</div>";
1267
+ }
1268
+ "#;
1269
+ fs::write(&file_path, content).unwrap();
1270
+
1271
+ // Get the project root directory (where Cargo.toml is)
1272
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1273
+
1274
+ // Run the extract command with XML format
1275
+ let output = Command::new("cargo")
1276
+ .args([
1277
+ "run",
1278
+ "--manifest-path",
1279
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1280
+ "--",
1281
+ "extract",
1282
+ file_path.to_string_lossy().as_ref(),
1283
+ "--format",
1284
+ "xml",
1285
+ "--allow-tests", // Add this flag to ensure test files are included
1286
+ ])
1287
+ .current_dir(&project_dir) // Ensure we're in the project directory
1288
+ .output()
1289
+ .expect("Failed to execute command");
1290
+
1291
+ // Check that the command executed successfully
1292
+ assert!(output.status.success());
1293
+
1294
+ // Get the output as a string
1295
+ let stdout = String::from_utf8_lossy(&output.stdout);
1296
+
1297
+ // Helper function to extract XML from command output
1298
+ fn extract_xml_from_output(output: &str) -> &str {
1299
+ // Find the first occurrence of '<?xml'
1300
+ if let Some(start_index) = output.find("<?xml") {
1301
+ // Return the substring from the first '<?xml' to the end
1302
+ &output[start_index..]
1303
+ } else {
1304
+ // If no '<?xml' is found, return the original string
1305
+ output
1306
+ }
1307
+ }
1308
+
1309
+ // Extract and parse the XML
1310
+ let xml_str = extract_xml_from_output(&stdout);
1311
+ let doc = Document::parse(xml_str).expect("Failed to parse XML output");
1312
+ let root = doc.root_element();
1313
+
1314
+ // Validate the structure of the XML output
1315
+ assert_eq!(
1316
+ root.tag_name().name(),
1317
+ "probe_results",
1318
+ "Root element should be 'probe_results'"
1319
+ );
1320
+
1321
+ // Validate that there are result elements
1322
+ let results: Vec<_> = root
1323
+ .children()
1324
+ .filter(|n| n.is_element() && n.tag_name().name() == "result")
1325
+ .collect();
1326
+ assert!(
1327
+ !results.is_empty(),
1328
+ "Should have at least one result element"
1329
+ );
1330
+
1331
+ // Validate the first result
1332
+ let first_result = &results[0];
1333
+
1334
+ // Check for required elements
1335
+ let file = first_result
1336
+ .children()
1337
+ .find(|n| n.is_element() && n.tag_name().name() == "file");
1338
+ assert!(file.is_some(), "Result should have a file element");
1339
+
1340
+ let code = first_result
1341
+ .children()
1342
+ .find(|n| n.is_element() && n.tag_name().name() == "code");
1343
+ assert!(code.is_some(), "Result should have a code element");
1344
+
1345
+ // Verify that special characters are properly handled in the XML (should be in CDATA)
1346
+ if let Some(code_elem) = code {
1347
+ let code_text = code_elem.text().unwrap();
1348
+ assert!(
1349
+ code_text.contains("\"quotes\""),
1350
+ "Double quotes should be preserved in CDATA"
1351
+ );
1352
+ assert!(
1353
+ code_text.contains("'apostrophes'"),
1354
+ "Apostrophes should be preserved in CDATA"
1355
+ );
1356
+ assert!(
1357
+ code_text.contains("<tags>"),
1358
+ "Tags should be preserved in CDATA"
1359
+ );
1360
+ assert!(
1361
+ code_text.contains("&ampersands"),
1362
+ "Ampersands should be preserved in CDATA"
1363
+ );
1364
+ }
1365
+
1366
+ // Get the project root directory (where Cargo.toml is)
1367
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1368
+
1369
+ // Run with a line number and XML format
1370
+ let output = Command::new("cargo")
1371
+ .args([
1372
+ "run",
1373
+ "--manifest-path",
1374
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1375
+ "--",
1376
+ "extract",
1377
+ &format!("{}:3", file_path.to_string_lossy()),
1378
+ "--format",
1379
+ "xml",
1380
+ "--allow-tests", // Add this flag to ensure test files are included
1381
+ ])
1382
+ .current_dir(&project_dir) // Ensure we're in the project directory
1383
+ .output()
1384
+ .expect("Failed to execute command");
1385
+
1386
+ // Print the command output for debugging
1387
+ println!(
1388
+ "Command stdout: {}",
1389
+ String::from_utf8_lossy(&output.stdout)
1390
+ );
1391
+ println!(
1392
+ "Command stderr: {}",
1393
+ String::from_utf8_lossy(&output.stderr)
1394
+ );
1395
+
1396
+ // Check that the command executed successfully
1397
+ assert!(
1398
+ output.status.success(),
1399
+ "Command failed with status: {}",
1400
+ output.status
1401
+ );
1402
+
1403
+ // Parse the XML output
1404
+ let stdout = String::from_utf8_lossy(&output.stdout);
1405
+ let xml_str = extract_xml_from_output(&stdout);
1406
+ let doc = Document::parse(xml_str).expect("Failed to parse XML output");
1407
+ let root = doc.root_element();
1408
+
1409
+ // Validate the structure
1410
+ let results: Vec<_> = root
1411
+ .children()
1412
+ .filter(|n| n.is_element() && n.tag_name().name() == "result")
1413
+ .collect();
1414
+ assert!(
1415
+ !results.is_empty(),
1416
+ "Should have at least one result element"
1417
+ );
1418
+
1419
+ // The result should contain the function with line 3
1420
+ let first_result = &results[0];
1421
+ if let Some(code_elem) = first_result
1422
+ .children()
1423
+ .find(|n| n.is_element() && n.tag_name().name() == "code")
1424
+ {
1425
+ let code_text = code_elem.text().unwrap();
1426
+ assert!(
1427
+ code_text.contains("fn main()"),
1428
+ "Code should contain the main function"
1429
+ );
1430
+ assert!(
1431
+ code_text.contains("Hello") && code_text.contains("world"),
1432
+ "Code should contain the println statement"
1433
+ );
1434
+ }
1435
+ }
1436
+
1437
+ #[test]
1438
+ fn test_integration_extract_command_with_multiple_files_diff() {
1439
+ // Create temporary files for testing
1440
+ let temp_dir = tempfile::tempdir().unwrap();
1441
+
1442
+ // Change to the temp directory for this test
1443
+ let original_dir = std::env::current_dir().unwrap();
1444
+ std::env::set_current_dir(&temp_dir).unwrap();
1445
+
1446
+ // Create the test files in the temp directory
1447
+ let file_path1 = PathBuf::from("property_tests.rs");
1448
+ let content1 = r#"
1449
+ fn test_create_structured_patterns() {
1450
+ let plan = create_query_plan("test query", false).unwrap();
1451
+ let patterns = create_structured_patterns(&plan);
1452
+
1453
+ // Check that we have at least one pattern for each term
1454
+ for (term, &idx) in &plan.term_indices {
1455
+ assert!(!patterns[idx].is_empty());
1456
+ }
1457
+ }
1458
+ "#;
1459
+ fs::write(&file_path1, content1).unwrap();
1460
+
1461
+ let file_path2 = PathBuf::from("tokenization_tests.rs");
1462
+ let content2 = r#"
1463
+ fn test_tokenize_with_stemming() {
1464
+ let tokens = tokenize_with_stemming("running runs runner");
1465
+
1466
+ assert_eq!(tokens, vec!["run", "run", "runner"]);
1467
+ }
1468
+ "#;
1469
+ fs::write(&file_path2, content2).unwrap();
1470
+
1471
+ // Run the git diff command to create a real diff
1472
+ let status = Command::new("git")
1473
+ .args(["init"])
1474
+ .status()
1475
+ .expect("Failed to initialize git repo");
1476
+ assert!(status.success(), "Failed to initialize git repo");
1477
+
1478
+ let status = Command::new("git")
1479
+ .args(["config", "user.name", "Test User"])
1480
+ .status()
1481
+ .expect("Failed to configure git");
1482
+ assert!(status.success(), "Failed to configure git");
1483
+
1484
+ let status = Command::new("git")
1485
+ .args(["config", "user.email", "test@example.com"])
1486
+ .status()
1487
+ .expect("Failed to configure git");
1488
+ assert!(status.success(), "Failed to configure git");
1489
+
1490
+ let status = Command::new("git")
1491
+ .args(["add", "."])
1492
+ .status()
1493
+ .expect("Failed to add files to git");
1494
+ assert!(status.success(), "Failed to add files to git");
1495
+
1496
+ let status = Command::new("git")
1497
+ .args(["commit", "-m", "Initial commit"])
1498
+ .status()
1499
+ .expect("Failed to commit files");
1500
+ assert!(status.success(), "Failed to commit files");
1501
+
1502
+ // Modify the files
1503
+ let content1_modified = r#"
1504
+ fn test_create_structured_patterns() {
1505
+ let plan = create_query_plan("test query", false).unwrap();
1506
+ let patterns = create_structured_patterns(&plan, false);
1507
+
1508
+ // Check that we have at least one pattern for each term
1509
+ for (term, &idx) in &plan.term_indices {
1510
+ assert!(!patterns[idx].is_empty());
1511
+ }
1512
+ }
1513
+ "#;
1514
+ fs::write(&file_path1, content1_modified).unwrap();
1515
+
1516
+ let content2_modified = r#"
1517
+ fn test_tokenize_with_stemming() {
1518
+ let tokens = tokenize_with_stemming("running runs runner");
1519
+
1520
+ assert_eq!(tokens, vec!["run", "run", "run"]);
1521
+ }
1522
+ "#;
1523
+ fs::write(&file_path2, content2_modified).unwrap();
1524
+
1525
+ // Create the diff file
1526
+ let diff_output = Command::new("git")
1527
+ .args(["diff", "property_tests.rs", "tokenization_tests.rs"])
1528
+ .output()
1529
+ .expect("Failed to create git diff");
1530
+
1531
+ assert!(diff_output.status.success(), "Failed to create git diff");
1532
+ let diff_content = String::from_utf8_lossy(&diff_output.stdout).to_string();
1533
+
1534
+ // Write the diff to a file
1535
+ let diff_path = PathBuf::from("changes.diff");
1536
+ fs::write(&diff_path, &diff_content).unwrap();
1537
+
1538
+ // Get the project root directory (where Cargo.toml is)
1539
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1540
+
1541
+ // Run the extract command with the diff containing multiple files
1542
+ let output = Command::new("cargo")
1543
+ .args([
1544
+ "run",
1545
+ "--manifest-path",
1546
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1547
+ "--",
1548
+ "extract",
1549
+ diff_path.to_string_lossy().as_ref(),
1550
+ "--allow-tests", // Add this flag to ensure test files are included
1551
+ ])
1552
+ .output()
1553
+ .expect("Failed to execute command");
1554
+
1555
+ // Restore the original directory
1556
+ std::env::set_current_dir(original_dir).unwrap();
1557
+
1558
+ // Check that the command executed successfully
1559
+ assert!(output.status.success(), "Command failed to execute");
1560
+
1561
+ let stdout = String::from_utf8_lossy(&output.stdout);
1562
+ println!("Command output: {stdout}");
1563
+
1564
+ // The output should contain information about the diff file
1565
+ assert!(
1566
+ stdout.contains("Files to extract:"),
1567
+ "Output should contain file list"
1568
+ );
1569
+ assert!(
1570
+ stdout.contains("changes.diff"),
1571
+ "Output should contain the diff file name"
1572
+ );
1573
+
1574
+ // Verify that the diff content contains both files
1575
+ assert!(
1576
+ stdout.contains("property_tests.rs"),
1577
+ "Output should contain the first file name in diff content"
1578
+ );
1579
+ assert!(
1580
+ stdout.contains("tokenization_tests.rs"),
1581
+ "Output should contain the second file name in diff content"
1582
+ );
1583
+
1584
+ // When processing a diff file directly, we only get one "File:" entry for the diff itself
1585
+ let file_count = stdout.matches("File:").count();
1586
+ assert_eq!(file_count, 1, "Should process the diff file");
1587
+ }
1588
+
1589
+ #[test]
1590
+ fn test_keep_input_option_with_stdin() {
1591
+ use std::io::Write;
1592
+ use std::process::{Command, Stdio};
1593
+
1594
+ // Get the project root directory (where Cargo.toml is)
1595
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1596
+
1597
+ // Create a temporary file with some content
1598
+ let temp_dir = tempfile::tempdir().unwrap();
1599
+ let file_path = temp_dir.path().join("test_file.rs");
1600
+ let content = r#"
1601
+ fn main() {
1602
+ println!("Hello, world!");
1603
+ }
1604
+ "#;
1605
+ fs::write(&file_path, content).unwrap();
1606
+
1607
+ // Create input content that references the file
1608
+ let input_content = format!("{}", file_path.to_string_lossy());
1609
+
1610
+ // Run the extract command with stdin input and keep_input flag
1611
+ let mut child = Command::new("cargo")
1612
+ .args([
1613
+ "run",
1614
+ "--manifest-path",
1615
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1616
+ "--",
1617
+ "extract",
1618
+ "--keep-input",
1619
+ "--allow-tests", // Add this flag to ensure test files are included
1620
+ ])
1621
+ .stdin(Stdio::piped())
1622
+ .stdout(Stdio::piped())
1623
+ .spawn()
1624
+ .expect("Failed to spawn command");
1625
+
1626
+ // Write to stdin
1627
+ {
1628
+ let stdin = child.stdin.as_mut().expect("Failed to open stdin");
1629
+ stdin
1630
+ .write_all(input_content.as_bytes())
1631
+ .expect("Failed to write to stdin");
1632
+ }
1633
+
1634
+ // Get the output
1635
+ let output = child.wait_with_output().expect("Failed to read stdout");
1636
+
1637
+ // Check that the command executed successfully
1638
+ assert!(output.status.success(), "Command failed to execute");
1639
+
1640
+ // Get the output as a string
1641
+ let stdout = String::from_utf8_lossy(&output.stdout);
1642
+
1643
+ // The output should contain the original input
1644
+ assert!(
1645
+ stdout.contains("Original Input:"),
1646
+ "Output should contain 'Original Input:' section"
1647
+ );
1648
+ assert!(
1649
+ stdout.contains(&input_content),
1650
+ "Output should contain the original input content"
1651
+ );
1652
+ }
1653
+
1654
+ #[test]
1655
+ fn test_keep_input_option_with_clipboard() {
1656
+ use arboard::Clipboard;
1657
+ use std::process::Command;
1658
+
1659
+ // Skip this test if clipboard access is not available
1660
+ let clipboard_result = Clipboard::new();
1661
+ if clipboard_result.is_err() {
1662
+ println!("Skipping clipboard test as clipboard access is not available");
1663
+ return;
1664
+ }
1665
+
1666
+ // Get the project root directory (where Cargo.toml is)
1667
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1668
+
1669
+ // Create a temporary file with some content
1670
+ let temp_dir = tempfile::tempdir().unwrap();
1671
+ let file_path = temp_dir.path().join("test_file.rs");
1672
+ let content = r#"
1673
+ fn main() {
1674
+ println!("Hello, world!");
1675
+ }
1676
+ "#;
1677
+ fs::write(&file_path, content).unwrap();
1678
+
1679
+ // Create input content that references the file
1680
+ let input_content = format!("{}", file_path.to_string_lossy());
1681
+
1682
+ // Set clipboard content
1683
+ let mut clipboard = Clipboard::new().unwrap();
1684
+ clipboard.set_text(&input_content).unwrap();
1685
+
1686
+ // Run the extract command with clipboard input and keep_input flag
1687
+ let output = Command::new("cargo")
1688
+ .args([
1689
+ "run",
1690
+ "--manifest-path",
1691
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1692
+ "--",
1693
+ "extract",
1694
+ "--from-clipboard",
1695
+ "--keep-input",
1696
+ "--allow-tests", // Add this flag to ensure test files are included
1697
+ ])
1698
+ .output()
1699
+ .expect("Failed to execute command");
1700
+
1701
+ // Check that the command executed successfully
1702
+ assert!(output.status.success(), "Command failed to execute");
1703
+
1704
+ // Get the output as a string
1705
+ let stdout = String::from_utf8_lossy(&output.stdout);
1706
+
1707
+ // The output should contain the original input
1708
+ assert!(
1709
+ stdout.contains("Original Input:"),
1710
+ "Output should contain 'Original Input:' section"
1711
+ );
1712
+ assert!(
1713
+ stdout.contains(&input_content),
1714
+ "Output should contain the original input content"
1715
+ );
1716
+ }
1717
+
1718
+ #[test]
1719
+ fn test_extract_unsupported_file_type_symbol() {
1720
+ use tempfile::TempDir;
1721
+
1722
+ // Create a temporary Terraform file (unsupported by tree-sitter)
1723
+ let temp_dir = TempDir::new().expect("Failed to create temp dir");
1724
+ let file_path = temp_dir.path().join("main.tf");
1725
+ let content = r#"# Terraform configuration
1726
+ resource "aws_instance" "example" {
1727
+ ami = "ami-0c55b159cbfafe1f0"
1728
+ instance_type = "t2.micro"
1729
+
1730
+ tags = {
1731
+ Name = "ExampleInstance"
1732
+ }
1733
+ }
1734
+
1735
+ output "instance_id" {
1736
+ value = aws_instance.example.id
1737
+ }
1738
+ "#;
1739
+ fs::write(&file_path, content).unwrap();
1740
+
1741
+ // Test extracting a symbol from an unsupported file type
1742
+ // Should return the full file content as fallback
1743
+ let result = process_file_for_extraction(
1744
+ &file_path,
1745
+ None, // start_line
1746
+ None, // end_line
1747
+ Some("aws_instance"), // symbol
1748
+ false, // allow_tests
1749
+ 0, // context_lines
1750
+ None, // specific_line_numbers
1751
+ false, // symbols
1752
+ )
1753
+ .unwrap();
1754
+
1755
+ // Should return full file content as fallback
1756
+ assert_eq!(
1757
+ result.node_type, "file",
1758
+ "Should return file type for unsupported language"
1759
+ );
1760
+ assert_eq!(result.code, content, "Should return full file content");
1761
+ assert_eq!(result.lines, (1, 13), "Should return all lines");
1762
+ }
1763
+
1764
+ #[test]
1765
+ fn test_extract_unsupported_file_type_lines() {
1766
+ use tempfile::TempDir;
1767
+
1768
+ // Create a temporary YAML file (another unsupported type)
1769
+ let temp_dir = TempDir::new().expect("Failed to create temp dir");
1770
+ let file_path = temp_dir.path().join("config.yml");
1771
+ let content = r#"version: '3'
1772
+ services:
1773
+ web:
1774
+ image: nginx:latest
1775
+ ports:
1776
+ - "80:80"
1777
+ database:
1778
+ image: postgres:13
1779
+ environment:
1780
+ POSTGRES_PASSWORD: secret
1781
+ "#;
1782
+ fs::write(&file_path, content).unwrap();
1783
+
1784
+ // Test extracting specific lines from an unsupported file type
1785
+ let result = process_file_for_extraction(
1786
+ &file_path,
1787
+ Some(3), // start_line
1788
+ Some(6), // end_line
1789
+ None, // symbol
1790
+ false, // allow_tests
1791
+ 0, // context_lines
1792
+ None, // specific_line_numbers
1793
+ false, // symbols
1794
+ )
1795
+ .unwrap();
1796
+
1797
+ // Should return the requested lines
1798
+ assert_eq!(result.lines, (3, 6), "Should return requested line range");
1799
+ assert!(result.code.contains("web:"), "Should contain web service");
1800
+ assert!(
1801
+ result.code.contains("image: nginx"),
1802
+ "Should contain nginx image"
1803
+ );
1804
+ assert!(result.code.contains("80:80"), "Should contain port mapping");
1805
+ }
1806
+
1807
+ #[test]
1808
+ fn test_extract_cli_unsupported_file_type() {
1809
+ use tempfile::TempDir;
1810
+
1811
+ let temp_dir = TempDir::new().expect("Failed to create temp dir");
1812
+ let file_path = temp_dir.path().join("data.jsonl");
1813
+ let content = r#"{"id": 1, "name": "Alice", "age": 30}
1814
+ {"id": 2, "name": "Bob", "age": 25}
1815
+ {"id": 3, "name": "Charlie", "age": 35}
1816
+ "#;
1817
+ fs::write(&file_path, content).unwrap();
1818
+
1819
+ let project_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1820
+
1821
+ // Run the extract command on an unsupported file type
1822
+ let output = Command::new("cargo")
1823
+ .args([
1824
+ "run",
1825
+ "--manifest-path",
1826
+ project_dir.join("Cargo.toml").to_string_lossy().as_ref(),
1827
+ "--",
1828
+ "extract",
1829
+ &format!("{}:2", file_path.to_string_lossy()),
1830
+ ])
1831
+ .output()
1832
+ .expect("Failed to execute command");
1833
+
1834
+ // Should succeed even with unsupported file type
1835
+ assert!(
1836
+ output.status.success(),
1837
+ "Command should succeed for unsupported file type. Stderr: {}",
1838
+ String::from_utf8_lossy(&output.stderr)
1839
+ );
1840
+
1841
+ let stdout = String::from_utf8_lossy(&output.stdout);
1842
+
1843
+ // Should contain the requested line
1844
+ assert!(
1845
+ stdout.contains("Bob"),
1846
+ "Output should contain the second line with Bob"
1847
+ );
1848
+ }