gitnexus 1.6.11-rc.8 → 1.6.11

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 (531) hide show
  1. package/README.md +95 -26
  2. package/dist/_shared/graph/types.d.ts +81 -2
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
  5. package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
  6. package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
  7. package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
  8. package/dist/_shared/language-detection.d.ts.map +1 -1
  9. package/dist/_shared/language-detection.js +2 -0
  10. package/dist/_shared/language-detection.js.map +1 -1
  11. package/dist/_shared/languages.d.ts +2 -1
  12. package/dist/_shared/languages.d.ts.map +1 -1
  13. package/dist/_shared/languages.js +1 -0
  14. package/dist/_shared/languages.js.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  16. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  17. package/dist/_shared/lbug/schema-constants.js +4 -0
  18. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  19. package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
  20. package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
  21. package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
  22. package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
  23. package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/language-classification.js +4 -0
  25. package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
  26. package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
  27. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
  28. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
  29. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
  30. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  31. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  32. package/dist/_shared/scope-resolution/types.d.ts +15 -0
  33. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  34. package/dist/cli/ai-context.d.ts +4 -0
  35. package/dist/cli/ai-context.js +90 -14
  36. package/dist/cli/analyze-config.js +14 -0
  37. package/dist/cli/analyze-options.d.ts +14 -0
  38. package/dist/cli/analyze-watch.d.ts +20 -0
  39. package/dist/cli/analyze-watch.js +434 -0
  40. package/dist/cli/analyze.js +27 -9
  41. package/dist/cli/auto-sync.d.ts +1 -0
  42. package/dist/cli/auto-sync.js +107 -0
  43. package/dist/cli/command-banner.d.ts +15 -0
  44. package/dist/cli/command-banner.js +42 -0
  45. package/dist/cli/detect-changes-format.js +21 -0
  46. package/dist/cli/doctor.d.ts +7 -0
  47. package/dist/cli/doctor.js +14 -0
  48. package/dist/cli/group.js +7 -1
  49. package/dist/cli/help-i18n.js +4 -0
  50. package/dist/cli/i18n/en.d.ts +18 -3
  51. package/dist/cli/i18n/en.js +18 -3
  52. package/dist/cli/i18n/resources.d.ts +33 -3
  53. package/dist/cli/i18n/zh-CN.d.ts +15 -0
  54. package/dist/cli/i18n/zh-CN.js +18 -3
  55. package/dist/cli/index.js +34 -7
  56. package/dist/cli/mcp.d.ts +15 -0
  57. package/dist/cli/mcp.js +44 -0
  58. package/dist/cli/optional-grammars.d.ts +3 -2
  59. package/dist/cli/optional-grammars.js +19 -3
  60. package/dist/cli/setup.js +41 -15
  61. package/dist/cli/update-notice.d.ts +34 -0
  62. package/dist/cli/update-notice.js +91 -0
  63. package/dist/cli/update.d.ts +21 -0
  64. package/dist/cli/update.js +93 -0
  65. package/dist/cli/watch.d.ts +1 -20
  66. package/dist/cli/watch.js +5 -371
  67. package/dist/core/analysis-feature-registry.d.ts +2 -0
  68. package/dist/core/analysis-feature-registry.js +15 -0
  69. package/dist/core/analyzer-identity.js +21 -3
  70. package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
  71. package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
  72. package/dist/core/auto-sync/config.d.ts +33 -0
  73. package/dist/core/auto-sync/config.js +320 -0
  74. package/dist/core/auto-sync/index.d.ts +6 -0
  75. package/dist/core/auto-sync/index.js +6 -0
  76. package/dist/core/auto-sync/path-security.d.ts +9 -0
  77. package/dist/core/auto-sync/path-security.js +251 -0
  78. package/dist/core/auto-sync/repo.d.ts +1 -0
  79. package/dist/core/auto-sync/repo.js +6 -0
  80. package/dist/core/auto-sync/runner.d.ts +44 -0
  81. package/dist/core/auto-sync/runner.js +428 -0
  82. package/dist/core/auto-sync/starter.d.ts +50 -0
  83. package/dist/core/auto-sync/starter.js +497 -0
  84. package/dist/core/auto-sync/state.d.ts +37 -0
  85. package/dist/core/auto-sync/state.js +119 -0
  86. package/dist/core/group/config-parser.js +8 -1
  87. package/dist/core/group/cross-impact.d.ts +3 -0
  88. package/dist/core/group/cross-impact.js +36 -4
  89. package/dist/core/group/extractors/http-patterns/java.js +22 -8
  90. package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
  91. package/dist/core/group/extractors/http-patterns/node.js +145 -8
  92. package/dist/core/group/extractors/http-patterns/python.js +11 -7
  93. package/dist/core/group/extractors/http-route-extractor.js +63 -17
  94. package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
  95. package/dist/core/group/group-lock.js +5 -6
  96. package/dist/core/group/normalization.d.ts +38 -1
  97. package/dist/core/group/normalization.js +59 -3
  98. package/dist/core/group/service.d.ts +11 -0
  99. package/dist/core/group/service.js +13 -1
  100. package/dist/core/group/storage.js +2 -2
  101. package/dist/core/group/sync.d.ts +24 -1
  102. package/dist/core/group/sync.js +46 -8
  103. package/dist/core/group/types.d.ts +13 -0
  104. package/dist/core/incremental/derived-writeback.d.ts +36 -0
  105. package/dist/core/incremental/derived-writeback.js +68 -0
  106. package/dist/core/incremental/spring-config-drift.d.ts +12 -0
  107. package/dist/core/incremental/spring-config-drift.js +46 -0
  108. package/dist/core/incremental/subgraph-extract.d.ts +7 -4
  109. package/dist/core/incremental/subgraph-extract.js +30 -5
  110. package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
  111. package/dist/core/ingestion/asyncapi/document.js +758 -0
  112. package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
  113. package/dist/core/ingestion/asyncapi/protocol.js +203 -0
  114. package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
  115. package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
  116. package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
  117. package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
  118. package/dist/core/ingestion/call-processor.js +3 -1
  119. package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
  120. package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
  121. package/dist/core/ingestion/class-extractors/generic.js +4 -1
  122. package/dist/core/ingestion/class-types.d.ts +6 -2
  123. package/dist/core/ingestion/destination-key.d.ts +59 -0
  124. package/dist/core/ingestion/destination-key.js +61 -0
  125. package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
  126. package/dist/core/ingestion/entry-point-scoring.js +8 -44
  127. package/dist/core/ingestion/export-detection.d.ts +37 -0
  128. package/dist/core/ingestion/export-detection.js +60 -0
  129. package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
  130. package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
  131. package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
  132. package/dist/core/ingestion/field-extractors/generic.js +12 -1
  133. package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
  134. package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
  135. package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
  136. package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
  137. package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
  138. package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
  139. package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
  140. package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
  141. package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
  142. package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
  143. package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
  144. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
  145. package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
  146. package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
  147. package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
  148. package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
  149. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
  150. package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
  151. package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
  152. package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
  153. package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
  154. package/dist/core/ingestion/import-resolvers/zig.js +193 -0
  155. package/dist/core/ingestion/language-config.d.ts +177 -0
  156. package/dist/core/ingestion/language-config.js +719 -0
  157. package/dist/core/ingestion/language-provider.d.ts +178 -1
  158. package/dist/core/ingestion/language-provider.js +22 -0
  159. package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
  160. package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
  161. package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
  162. package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
  163. package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
  164. package/dist/core/ingestion/languages/index.d.ts +1 -0
  165. package/dist/core/ingestion/languages/index.js +2 -0
  166. package/dist/core/ingestion/languages/java/analysis-features.d.ts +1 -1
  167. package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
  168. package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
  169. package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
  170. package/dist/core/ingestion/languages/java/captures.js +22 -1
  171. package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
  172. package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
  173. package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
  174. package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
  175. package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
  176. package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
  177. package/dist/core/ingestion/languages/java/spring-di.js +44 -2
  178. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
  179. package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
  180. package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
  181. package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
  182. package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
  183. package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
  184. package/dist/core/ingestion/languages/java.js +18 -8
  185. package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
  186. package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
  187. package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
  188. package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
  189. package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
  190. package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
  191. package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
  192. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
  193. package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
  194. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
  195. package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
  196. package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
  197. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
  198. package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
  199. package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
  200. package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
  201. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
  202. package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
  203. package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
  204. package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
  205. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
  206. package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
  207. package/dist/core/ingestion/languages/kotlin.js +24 -0
  208. package/dist/core/ingestion/languages/python.js +2 -0
  209. package/dist/core/ingestion/languages/rust/query.js +8 -8
  210. package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
  211. package/dist/core/ingestion/languages/zig/captures.js +2070 -0
  212. package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
  213. package/dist/core/ingestion/languages/zig/index.js +4 -0
  214. package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
  215. package/dist/core/ingestion/languages/zig/interpret.js +176 -0
  216. package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
  217. package/dist/core/ingestion/languages/zig/query.js +426 -0
  218. package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
  219. package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
  220. package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
  221. package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
  222. package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
  223. package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
  224. package/dist/core/ingestion/languages/zig.d.ts +32 -0
  225. package/dist/core/ingestion/languages/zig.js +130 -0
  226. package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
  227. package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
  228. package/dist/core/ingestion/method-extractors/generic.js +16 -4
  229. package/dist/core/ingestion/method-types.d.ts +10 -4
  230. package/dist/core/ingestion/model/field-registry.d.ts +3 -3
  231. package/dist/core/ingestion/model/field-registry.js +11 -7
  232. package/dist/core/ingestion/model/method-registry.d.ts +3 -3
  233. package/dist/core/ingestion/model/method-registry.js +13 -8
  234. package/dist/core/ingestion/model/registration-table.js +3 -0
  235. package/dist/core/ingestion/model/type-registry.js +8 -4
  236. package/dist/core/ingestion/pipeline-phases/di.js +47 -14
  237. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  238. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  239. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
  240. package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
  241. package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
  242. package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
  243. package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
  244. package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
  245. package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
  246. package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
  247. package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
  248. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
  249. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
  250. package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
  251. package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
  252. package/dist/core/ingestion/pipeline.d.ts +30 -1
  253. package/dist/core/ingestion/pipeline.js +52 -14
  254. package/dist/core/ingestion/resolve-references.js +5 -0
  255. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
  256. package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
  257. package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
  258. package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
  259. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
  260. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
  261. package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
  262. package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
  263. package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
  264. package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
  265. package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
  266. package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
  267. package/dist/core/ingestion/route-extractors/spring.js +9 -5
  268. package/dist/core/ingestion/scope-extractor.js +8 -0
  269. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
  270. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
  271. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
  272. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
  273. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
  274. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
  275. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
  276. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
  277. package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
  278. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
  279. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
  280. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
  281. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
  282. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
  283. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
  284. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  285. package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
  286. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
  287. package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
  288. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
  289. package/dist/core/ingestion/tree-sitter-queries.js +161 -0
  290. package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
  291. package/dist/core/ingestion/type-extractors/zig.js +17 -0
  292. package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
  293. package/dist/core/ingestion/utils/ast-helpers.js +124 -6
  294. package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
  295. package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
  296. package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
  297. package/dist/core/ingestion/utils/test-file-path.js +87 -0
  298. package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
  299. package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
  300. package/dist/core/ingestion/workers/parse-worker.js +85 -14
  301. package/dist/core/install-context.d.ts +4 -0
  302. package/dist/core/install-context.js +152 -0
  303. package/dist/core/lbug/csv-generator.d.ts +10 -1
  304. package/dist/core/lbug/csv-generator.js +92 -22
  305. package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
  306. package/dist/core/lbug/graph-emit-sink.js +4 -2
  307. package/dist/core/lbug/lbug-adapter.d.ts +100 -5
  308. package/dist/core/lbug/lbug-adapter.js +236 -24
  309. package/dist/core/lbug/schema.d.ts +6 -5
  310. package/dist/core/lbug/schema.js +84 -22
  311. package/dist/core/net/url-guard.d.ts +6 -0
  312. package/dist/core/net/url-guard.js +147 -0
  313. package/dist/core/package-version.d.ts +5 -0
  314. package/dist/core/package-version.js +10 -0
  315. package/dist/core/run-analyze.d.ts +21 -4
  316. package/dist/core/run-analyze.js +426 -117
  317. package/dist/core/search/fts-indexes.d.ts +19 -1
  318. package/dist/core/search/fts-indexes.js +28 -1
  319. package/dist/core/tree-sitter/parser-loader.js +16 -0
  320. package/dist/core/update-cache.d.ts +39 -0
  321. package/dist/core/update-cache.js +141 -0
  322. package/dist/core/update-check.d.ts +31 -0
  323. package/dist/core/update-check.js +274 -0
  324. package/dist/mcp/local/local-backend.d.ts +27 -13
  325. package/dist/mcp/local/local-backend.js +148 -60
  326. package/dist/mcp/repository-policy.d.ts +5 -1
  327. package/dist/mcp/repository-policy.js +48 -4
  328. package/dist/mcp/resources.js +10 -1
  329. package/dist/mcp/server.js +8 -9
  330. package/dist/mcp/tools.js +32 -22
  331. package/dist/server/analyze-launch.d.ts +2 -0
  332. package/dist/server/analyze-launch.js +2 -0
  333. package/dist/server/analyze-worker-protocol.d.ts +6 -1
  334. package/dist/server/analyze-worker.d.ts +1 -0
  335. package/dist/server/analyze-worker.js +38 -20
  336. package/dist/server/api.d.ts +5 -1
  337. package/dist/server/api.js +105 -88
  338. package/dist/server/git-clone.d.ts +40 -16
  339. package/dist/server/git-clone.js +253 -199
  340. package/dist/server/grep-params.d.ts +18 -0
  341. package/dist/server/grep-params.js +83 -0
  342. package/dist/server/grep-scan.d.ts +23 -0
  343. package/dist/server/grep-scan.js +107 -0
  344. package/dist/server/grep-worker.d.ts +1 -0
  345. package/dist/server/grep-worker.js +12 -0
  346. package/dist/server/mcp-http.d.ts +8 -0
  347. package/dist/server/mcp-http.js +16 -1
  348. package/dist/server/update-controller.d.ts +29 -0
  349. package/dist/server/update-controller.js +82 -0
  350. package/dist/storage/file-lock.d.ts +15 -0
  351. package/dist/storage/file-lock.js +155 -0
  352. package/dist/storage/fs-atomic.d.ts +24 -0
  353. package/dist/storage/fs-atomic.js +86 -2
  354. package/dist/storage/global-dir.d.ts +2 -0
  355. package/dist/storage/global-dir.js +6 -0
  356. package/dist/storage/parse-cache.d.ts +45 -7
  357. package/dist/storage/parse-cache.js +189 -42
  358. package/dist/storage/parsedfile-store.d.ts +41 -60
  359. package/dist/storage/parsedfile-store.js +247 -171
  360. package/dist/storage/repo-manager.d.ts +10 -4
  361. package/dist/storage/repo-manager.js +35 -25
  362. package/dist/storage/repo-meta.d.ts +34 -0
  363. package/dist/storage/v8-sidecar.d.ts +48 -0
  364. package/dist/storage/v8-sidecar.js +347 -0
  365. package/dist/types/pipeline.d.ts +14 -0
  366. package/dist/utils/process-identity.d.ts +2 -0
  367. package/dist/utils/process-identity.js +35 -0
  368. package/package.json +10 -4
  369. package/scripts/assert-web-assets.mjs +35 -0
  370. package/scripts/build-web.js +72 -0
  371. package/scripts/build.js +31 -28
  372. package/scripts/cross-platform-tests.ts +5 -0
  373. package/skills/gitnexus-cli.md +5 -1
  374. package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
  375. package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
  376. package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
  377. package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
  378. package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
  379. package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
  380. package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
  381. package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
  382. package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
  383. package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
  384. package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
  385. package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
  386. package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
  387. package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
  388. package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
  389. package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
  390. package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
  391. package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
  392. package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
  393. package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
  394. package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
  395. package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
  396. package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
  397. package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
  398. package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
  399. package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
  400. package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
  401. package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
  402. package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
  403. package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
  404. package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
  405. package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
  406. package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
  407. package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
  408. package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
  409. package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
  410. package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
  411. package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
  412. package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
  413. package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
  414. package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
  415. package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
  416. package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
  417. package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
  418. package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
  419. package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
  420. package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
  421. package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
  422. package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
  423. package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
  424. package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
  425. package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
  426. package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
  427. package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
  428. package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
  429. package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
  430. package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
  431. package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
  432. package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
  433. package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
  434. package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
  435. package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
  436. package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
  437. package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
  438. package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
  439. package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
  440. package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
  441. package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
  442. package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
  443. package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
  444. package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
  445. package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
  446. package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
  447. package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
  448. package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
  449. package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
  450. package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
  451. package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
  452. package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
  453. package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
  454. package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
  455. package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
  456. package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
  457. package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
  458. package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
  459. package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
  460. package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
  461. package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
  462. package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
  463. package/web/assets/src-Df5C1Nz4.js +1 -0
  464. package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
  465. package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
  466. package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
  467. package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
  468. package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
  469. package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
  470. package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
  471. package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
  472. package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
  473. package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
  474. package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
  475. package/web/index.html +19 -17
  476. package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
  477. package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
  478. package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
  479. package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
  480. package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
  481. package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
  482. package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
  483. package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
  484. package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
  485. package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
  486. package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
  487. package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
  488. package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
  489. package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
  490. package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
  491. package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
  492. package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
  493. package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
  494. package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
  495. package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
  496. package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
  497. package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
  498. package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
  499. package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
  500. package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
  501. package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
  502. package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
  503. package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
  504. package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
  505. package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
  506. package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
  507. package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
  508. package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
  509. package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
  510. package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
  511. package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
  512. package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
  513. package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
  514. package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
  515. package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
  516. package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
  517. package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
  518. package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
  519. package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
  520. package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
  521. package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
  522. package/web/assets/src-C5sx_gMp.js +0 -1
  523. package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
  524. package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
  525. package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
  526. package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
  527. package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
  528. package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
  529. package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
  530. package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
  531. /package/web/assets/{sizeCapture-5GORAGP4-BEqQzsxs.js → sizeCapture-FGA5AGYP-BEqQzsxs.js} +0 -0
@@ -7,10 +7,14 @@
7
7
  import { spawn } from 'child_process';
8
8
  import path from 'path';
9
9
  import fs from 'fs/promises';
10
- import { isIP } from 'net';
10
+ import os from 'node:os';
11
11
  import { logger } from '../core/logger.js';
12
- import { parseRepoNameFromUrl, stripUrlCredentials } from '../storage/git.js';
13
12
  import { getGlobalDir } from '../storage/repo-manager.js';
13
+ import { sanitizeRepoName, stripUrlCredentials } from '../storage/git.js';
14
+ import { validateGitUrl } from '../core/net/url-guard.js';
15
+ import { assertDirectoryOwnerAndPermissions, quarantineAutoSyncPartial, } from '../core/auto-sync/path-security.js';
16
+ import { validateAutoSyncRemoteUrl } from '../core/auto-sync/config.js';
17
+ export { validateGitUrl };
14
18
  /**
15
19
  * Root directory for all cloned repositories. Targets must resolve inside this.
16
20
  *
@@ -35,16 +39,41 @@ export const REPO_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
35
39
  * clone root via path traversal.
36
40
  */
37
41
  export function extractRepoName(url) {
38
- const name = parseRepoNameFromUrl(url);
42
+ let trimmed = url.trim();
43
+ while (trimmed.endsWith('/'))
44
+ trimmed = trimmed.slice(0, -1);
45
+ const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;
46
+ const name = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';
39
47
  if (!name ||
40
48
  name === '.' ||
41
49
  name === '..' ||
42
50
  name === 'unknown' ||
51
+ name.startsWith('-') ||
43
52
  !REPO_NAME_PATTERN.test(name)) {
44
53
  throw new Error('Could not extract a valid repository name from URL');
45
54
  }
46
55
  return name;
47
56
  }
57
+ /**
58
+ * Derive a clone directory name for the web `/api/analyze` boundary.
59
+ *
60
+ * The API historically accepted Azure DevOps and similar URLs whose repo
61
+ * segment contains spaces or other directory-unsafe characters by sanitizing
62
+ * the final segment. Keep that compatibility at the web boundary while leaving
63
+ * `extractRepoName()` strict for internal/security-sensitive callers.
64
+ */
65
+ export function extractWebRepoName(url) {
66
+ let trimmed = url.trim();
67
+ while (trimmed.endsWith('/'))
68
+ trimmed = trimmed.slice(0, -1);
69
+ const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;
70
+ const rawName = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';
71
+ const safeName = sanitizeRepoName(rawName);
72
+ if (!rawName || safeName === 'unknown') {
73
+ throw new Error('Could not extract a valid repository name from URL');
74
+ }
75
+ return safeName;
76
+ }
48
77
  /** Get the clone target directory for a repo name. */
49
78
  export function getCloneDir(repoName) {
50
79
  // Re-validate at the boundary even though extractRepoName already checked —
@@ -54,148 +83,6 @@ export function getCloneDir(repoName) {
54
83
  }
55
84
  return path.join(CLONE_ROOT, repoName);
56
85
  }
57
- // Cloud metadata hostnames that must never be reachable via user-supplied URLs
58
- const BLOCKED_HOSTNAMES = new Set([
59
- 'localhost',
60
- 'metadata.google.internal',
61
- 'metadata.azure.com',
62
- 'metadata.internal',
63
- ]);
64
- /**
65
- * Validate a git URL to prevent SSRF attacks.
66
- * Only allows https:// and http:// schemes. Blocks private/internal addresses,
67
- * IPv6 private ranges, cloud metadata hostnames, and numeric IP encodings.
68
- */
69
- export function validateGitUrl(url) {
70
- let parsed;
71
- try {
72
- parsed = new URL(url);
73
- }
74
- catch {
75
- throw new Error('Invalid URL');
76
- }
77
- if (!['https:', 'http:'].includes(parsed.protocol)) {
78
- throw new Error('Only https:// and http:// git URLs are allowed');
79
- }
80
- const host = parsed.hostname.toLowerCase();
81
- // Block known dangerous hostnames (cloud metadata services)
82
- if (BLOCKED_HOSTNAMES.has(host)) {
83
- throw new Error('Cloning from private/internal addresses is not allowed');
84
- }
85
- // Strip IPv6 brackets if present (URL parser behavior varies across Node versions)
86
- let normalizedHost = host;
87
- if (host.startsWith('[') && host.endsWith(']')) {
88
- normalizedHost = host.slice(1, -1);
89
- }
90
- // Check if this is an IPv6 address
91
- // Use manual colon detection as fallback since isIP may return 0 for some
92
- // normalized IPv6 forms (e.g. ::ffff:7f00:1)
93
- const isIPv6 = isIP(normalizedHost) === 6 || normalizedHost.includes(':');
94
- if (isIPv6) {
95
- assertNotPrivateIPv6(normalizedHost);
96
- return;
97
- }
98
- // Check if this is an IPv4 address (including numeric encodings)
99
- if (isIP(normalizedHost) === 4) {
100
- assertNotPrivateIPv4(normalizedHost);
101
- return;
102
- }
103
- // For non-IP hostnames, check for numeric IP tricks
104
- // Decimal encoding: 2130706433 = 127.0.0.1
105
- // Hex encoding: 0x7f000001 = 127.0.0.1
106
- if (/^\d+$/.test(host) || /^0x[0-9a-f]+$/i.test(host)) {
107
- throw new Error('Cloning from private/internal addresses is not allowed');
108
- }
109
- // Standard IPv4 regex checks for dotted notation
110
- if (/^127\./.test(host) ||
111
- /^10\./.test(host) ||
112
- /^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
113
- /^192\.168\./.test(host) ||
114
- /^169\.254\./.test(host) ||
115
- /^0\./.test(host) ||
116
- host === '0.0.0.0' ||
117
- /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(host) ||
118
- /^198\.1[89]\./.test(host)) {
119
- throw new Error('Cloning from private/internal addresses is not allowed');
120
- }
121
- }
122
- function assertNotPrivateIPv6(ip) {
123
- // Expand common compressed forms for comparison
124
- const lower = ip.toLowerCase();
125
- // IPv6 loopback
126
- if (lower === '::1' || lower === '0:0:0:0:0:0:0:1') {
127
- throw new Error('Cloning from private/internal addresses is not allowed');
128
- }
129
- // Unspecified address
130
- if (lower === '::' || lower === '0:0:0:0:0:0:0:0') {
131
- throw new Error('Cloning from private/internal addresses is not allowed');
132
- }
133
- // IPv6 Unique Local Address (fc00::/7 = fc and fd prefixes)
134
- if (lower.startsWith('fc') || lower.startsWith('fd')) {
135
- throw new Error('Cloning from private/internal addresses is not allowed');
136
- }
137
- // IPv6 link-local (fe80::/10)
138
- if (lower.startsWith('fe80') ||
139
- lower.startsWith('fe8') ||
140
- lower.startsWith('fe9') ||
141
- lower.startsWith('fea') ||
142
- lower.startsWith('feb')) {
143
- throw new Error('Cloning from private/internal addresses is not allowed');
144
- }
145
- // IPv4-mapped IPv6 (::ffff:x.x.x.x or ::ffff:hex:hex)
146
- // Node may normalize ::ffff:127.0.0.1 to ::ffff:7f00:1
147
- if (lower.startsWith('::ffff:')) {
148
- throw new Error('Cloning from private/internal addresses is not allowed');
149
- }
150
- // Also catch the expanded form: 0:0:0:0:0:ffff:
151
- if (lower.includes(':ffff:')) {
152
- throw new Error('Cloning from private/internal addresses is not allowed');
153
- }
154
- // IPv4-compatible IPv6 (RFC 4291 § 2.5.5.1, deprecated form: ::w.x.y.z).
155
- // Node's URL parser collapses http://[::127.0.0.1]/ to "::7f00:1" — the IPv4
156
- // is hidden in the last 32 bits without the ::ffff: marker, so the check
157
- // above misses it. The form is still routable to the embedded IPv4 on most
158
- // network stacks, so any address compressed to ::xxxx[:yyyy] must be blocked.
159
- if (/^::[0-9a-f]{1,4}(:[0-9a-f]{1,4})?$/.test(lower)) {
160
- throw new Error('Cloning from private/internal addresses is not allowed');
161
- }
162
- // NAT64 well-known prefix (RFC 6052 § 2.1: 64:ff9b::/96, plus the local
163
- // 64:ff9b:1::/48 from RFC 8215). Maps any IPv4 address — including private
164
- // ranges — into IPv6, so a host with NAT64 can reach the embedded IPv4 via
165
- // e.g. 64:ff9b::7f00:1 → 127.0.0.1.
166
- // The check intentionally covers the full 64:ff9b::/32 block (broader than
167
- // the two cited ranges): IANA reserves it for IPv4-IPv6 translation, so
168
- // blocking the whole prefix is defensively sound and prevents a narrower
169
- // CIDR check from quietly re-opening the bypass for 64:ff9b:1::/48 or any
170
- // future translation assignment.
171
- if (lower.startsWith('64:ff9b:')) {
172
- throw new Error('Cloning from private/internal addresses is not allowed');
173
- }
174
- // 6to4 (RFC 3056, 2002::/16). Encodes an IPv4 address in bits 17-48, so
175
- // 2002:7f00:0001::1 routes to 127.0.0.1 on 6to4-capable stacks. The
176
- // protocol was deprecated by RFC 7526 and the public relay anycast
177
- // (192.88.99.1) has been retired, so broad-blocking the prefix has near-
178
- // zero false-positive cost while closing the IPv4-embedded bypass.
179
- // Teredo (2001::/32) embeds IPv4 obfuscated by XOR; precise blocking is
180
- // impractical and is out of scope here.
181
- if (lower.startsWith('2002:')) {
182
- throw new Error('Cloning from private/internal addresses is not allowed');
183
- }
184
- }
185
- function assertNotPrivateIPv4(ip) {
186
- const parts = ip.split('.').map(Number);
187
- const [a, b] = parts;
188
- if (a === 127 ||
189
- a === 10 ||
190
- (a === 172 && b >= 16 && b <= 31) ||
191
- (a === 192 && b === 168) ||
192
- (a === 169 && b === 254) ||
193
- a === 0 ||
194
- (a === 100 && b >= 64 && b <= 127) ||
195
- (a === 198 && (b === 18 || b === 19))) {
196
- throw new Error('Cloning from private/internal addresses is not allowed');
197
- }
198
- }
199
86
  /**
200
87
  * Build the `git clone` argument list for a given URL and target directory.
201
88
  *
@@ -263,6 +150,9 @@ export function warnIfInsecureAzureConfig() {
263
150
  export function buildCloneArgs(url, targetDir) {
264
151
  return ['clone', '--depth', '1', '--', url, targetDir];
265
152
  }
153
+ export function buildBranchCloneArgs(url, targetDir, branch) {
154
+ return ['clone', '--depth', '1', '--branch', branch, '--', url, targetDir];
155
+ }
266
156
  /**
267
157
  * Normalize a git URL into a comparable form.
268
158
  *
@@ -320,28 +210,16 @@ export function normalizeGitUrlForCompare(url) {
320
210
  * remote means for its threat model — for cloneOrPull, a missing remote
321
211
  * on an existing clone is treated as a refuse-to-pull condition.
322
212
  */
323
- export function getRemoteOriginUrl(cwd) {
324
- return new Promise((resolve) => {
325
- const proc = spawn('git', ['config', '--get', 'remote.origin.url'], {
326
- cwd,
327
- stdio: ['ignore', 'pipe', 'pipe'],
328
- windowsHide: true,
329
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
330
- });
331
- let stdout = '';
332
- proc.stdout.on('data', (chunk) => {
333
- stdout += chunk;
334
- });
335
- proc.on('close', (code) => {
336
- if (code === 0 && stdout.trim()) {
337
- resolve(stdout.trim());
338
- }
339
- else {
340
- resolve(null);
341
- }
342
- });
343
- proc.on('error', () => resolve(null));
344
- });
213
+ export async function getRemoteOriginUrl(cwd, timeoutMs) {
214
+ try {
215
+ const stdout = await runGit(['config', '--get', 'remote.origin.url'], cwd, { timeoutMs });
216
+ return stdout.trim() || null;
217
+ }
218
+ catch (error) {
219
+ if (error.message.includes('timed out'))
220
+ throw error;
221
+ return null;
222
+ }
345
223
  }
346
224
  /**
347
225
  * Verify that an existing clone's `remote.origin.url` matches the requested
@@ -357,8 +235,8 @@ export function getRemoteOriginUrl(cwd) {
357
235
  * Exported so the comparison logic is testable in isolation against any
358
236
  * tmpdir-based fixture, without needing to populate CLONE_ROOT.
359
237
  */
360
- export async function assertRemoteMatchesRequestedUrl(targetDir, requestedUrl) {
361
- const remoteUrl = await getRemoteOriginUrl(targetDir);
238
+ export async function assertRemoteMatchesRequestedUrl(targetDir, requestedUrl, timeoutMs) {
239
+ const remoteUrl = await getRemoteOriginUrl(targetDir, timeoutMs);
362
240
  if (remoteUrl === null) {
363
241
  throw new Error(`Existing clone at ${targetDir} has no remote.origin — refusing to pull`);
364
242
  }
@@ -400,37 +278,179 @@ export async function cloneOrPull(url, targetDir, onProgress, options) {
400
278
  // subprocess sink. The same `safeTarget` is used for every downstream
401
279
  // path operation — no reassignment that the analyzer could lose track of.
402
280
  //
403
- // Limitation: this is a lexical containment check, not a realpath check.
404
- // If an attacker can place a symlink under CLONE_ROOT pointing outside it,
405
- // the lexical check passes but the clone lands at the symlink target. That
406
- // requires pre-existing local write access to CLONE_ROOT, so the threat
407
- // model considers it out of scope; CodeQL js/path-injection accepts the
408
- // lexical form. Tracked as a follow-up if defense-in-depth is needed.
281
+ // The lexical check runs before filesystem creation; realpath and symlink
282
+ // checks below run before pull/clone and again after clone completes.
283
+ const cloneRoot = path.resolve(options?.allowedCloneRoot ?? CLONE_ROOT);
284
+ const expectedRepoName = options?.expectedRepoName;
285
+ if (expectedRepoName !== undefined && expectedRepoName !== extractRepoName(url)) {
286
+ throw new Error(`Clone target repo name ${expectedRepoName} does not match requested URL`);
287
+ }
409
288
  const safeTarget = path.resolve(targetDir);
410
- const rel = path.relative(CLONE_ROOT, safeTarget);
289
+ if (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {
290
+ throw new Error(`Clone target basename must match repository name ${expectedRepoName}`);
291
+ }
292
+ const rel = path.relative(cloneRoot, safeTarget);
411
293
  if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
412
- throw new Error(`Clone target must be a subdirectory of ${CLONE_ROOT}`);
294
+ throw new Error(`Clone target must be a subdirectory of ${cloneRoot}`);
413
295
  }
414
296
  // Always validate the requested URL — the prior shape only ran this in
415
297
  // the code path where the repo was cloned. Now it runs unconditionally,
416
298
  // preventing SSRF / blocked-host bypasses even when targetDir already exists.
417
- validateGitUrl(url);
299
+ if (options?.allowAutoSyncSsh)
300
+ validateAutoSyncRemoteUrl(url);
301
+ else
302
+ validateGitUrl(url);
303
+ await fs.mkdir(cloneRoot, { recursive: true });
304
+ if (options?.allowedCloneRoot) {
305
+ await assertDirectoryOwnerAndPermissions(cloneRoot);
306
+ }
307
+ await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));
308
+ await fs.mkdir(path.dirname(safeTarget), { recursive: true });
309
+ await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));
310
+ await assertPreRealpathContainment(cloneRoot, safeTarget);
418
311
  const exists = await fs.access(path.join(safeTarget, '.git')).then(() => true, () => false);
312
+ const targetExists = await fs.access(safeTarget).then(() => true, () => false);
419
313
  if (exists) {
314
+ if (options?.allowedCloneRoot) {
315
+ await assertNoSymlinkPath(cloneRoot, path.join(safeTarget, '.git'), true);
316
+ }
317
+ await assertPostRealpathContainment(cloneRoot, safeTarget);
420
318
  // Confirm the existing clone is actually the same repository the caller
421
319
  // requested. Without this check, a pull would silently succeed against
422
320
  // whatever remote the dir was originally cloned from.
423
- await assertRemoteMatchesRequestedUrl(safeTarget, url);
321
+ await assertRemoteMatchesRequestedUrl(safeTarget, url, options?.timeoutMs);
424
322
  onProgress?.({ phase: 'pulling', message: 'Pulling latest changes...' });
425
- await runGit(['pull', '--ff-only'], safeTarget, { token: options?.token, url });
323
+ const runGitImpl = options?.runGitForTest ?? runGit;
324
+ if (options?.branch) {
325
+ if (!options.overwriteLocalChanges) {
326
+ const status = await runGitImpl(['status', '--porcelain'], safeTarget, {
327
+ token: options?.token,
328
+ url,
329
+ timeoutMs: options?.timeoutMs,
330
+ });
331
+ if (status.trim()) {
332
+ throw new Error(`Refusing to update ${safeTarget}: local changes detected. Set overwrite_local_changes: true to overwrite them.`);
333
+ }
334
+ }
335
+ await runGitImpl([
336
+ 'fetch',
337
+ '--depth',
338
+ '1',
339
+ 'origin',
340
+ `refs/heads/${options.branch}:refs/remotes/origin/${options.branch}`,
341
+ ], safeTarget, {
342
+ token: options?.token,
343
+ url,
344
+ timeoutMs: options?.timeoutMs,
345
+ });
346
+ await runGitImpl([
347
+ 'checkout',
348
+ ...(options.overwriteLocalChanges ? ['--force'] : []),
349
+ '-B',
350
+ options.branch,
351
+ `origin/${options.branch}`,
352
+ ], safeTarget, {
353
+ token: options?.token,
354
+ url,
355
+ timeoutMs: options?.timeoutMs,
356
+ });
357
+ if (options.overwriteLocalChanges) {
358
+ // `checkout --force` rewrites tracked files only, so untracked sources
359
+ // left by an operator or an earlier branch survive and then get indexed
360
+ // as if they were part of the remote commit. Deliberately no `-x`/`-X`:
361
+ // ignored paths must survive, and `-e /.gitnexus` is belt-and-braces
362
+ // because `.git/info/exclude` is skipped on a read-only storage mount
363
+ // and a freshly cloned repo may not have been analyzed yet at all.
364
+ await runGitImpl(['clean', '--force', '-d', '-e', '/.gitnexus'], safeTarget, {
365
+ token: options?.token,
366
+ url,
367
+ timeoutMs: options?.timeoutMs,
368
+ });
369
+ }
370
+ }
371
+ else {
372
+ await runGitImpl(['pull', '--ff-only'], safeTarget, {
373
+ token: options?.token,
374
+ url,
375
+ timeoutMs: options?.timeoutMs,
376
+ });
377
+ }
426
378
  }
427
379
  else {
428
- await fs.mkdir(path.dirname(safeTarget), { recursive: true });
380
+ if (targetExists && (await fs.readdir(safeTarget)).length > 0) {
381
+ throw new Error(`Clone target already exists but is not a git repository: ${safeTarget}`);
382
+ }
429
383
  onProgress?.({ phase: 'cloning', message: `Cloning ${url}...` });
430
- await runGit(buildCloneArgs(url, safeTarget), undefined, { token: options?.token, url });
384
+ try {
385
+ const runGitImpl = options?.runGitForTest ?? runGit;
386
+ const cloneArgs = options?.branch
387
+ ? buildBranchCloneArgs(url, safeTarget, options.branch)
388
+ : buildCloneArgs(url, safeTarget);
389
+ await runGitImpl(cloneArgs, undefined, {
390
+ token: options?.token,
391
+ url,
392
+ timeoutMs: options?.timeoutMs,
393
+ });
394
+ await assertPostRealpathContainment(cloneRoot, safeTarget);
395
+ }
396
+ catch (err) {
397
+ if (options?.quarantineRoot) {
398
+ const partialExists = await fs.access(safeTarget).then(() => true, () => false);
399
+ if (partialExists) {
400
+ try {
401
+ await quarantineAutoSyncPartial(safeTarget, options.quarantineRoot);
402
+ }
403
+ catch (quarantineError) {
404
+ throw new AggregateError([err, quarantineError], `Clone failed and partial checkout could not be quarantined: ${safeTarget}`);
405
+ }
406
+ }
407
+ }
408
+ throw err;
409
+ }
431
410
  }
432
411
  return safeTarget;
433
412
  }
413
+ async function assertPreRealpathContainment(root, target) {
414
+ const realRoot = await fs.realpath(root);
415
+ const realParent = await fs.realpath(path.dirname(target));
416
+ const parentRel = path.relative(realRoot, realParent);
417
+ if (parentRel.startsWith('..') || path.isAbsolute(parentRel)) {
418
+ throw new Error(`Clone target parent must resolve inside ${root}`);
419
+ }
420
+ }
421
+ async function assertPostRealpathContainment(root, target) {
422
+ const realRoot = await fs.realpath(root);
423
+ const realTarget = await fs.realpath(target);
424
+ const rel = path.relative(realRoot, realTarget);
425
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
426
+ throw new Error(`Clone target must resolve inside ${root}`);
427
+ }
428
+ }
429
+ async function assertNoSymlinkPath(root, target, verifyOwnership = false) {
430
+ const resolvedRoot = path.resolve(root);
431
+ const resolvedTarget = path.resolve(target);
432
+ const relativeTarget = path.relative(resolvedRoot, resolvedTarget);
433
+ if (relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget))
434
+ return;
435
+ let current = resolvedRoot;
436
+ for (const segment of relativeTarget.split(path.sep).filter(Boolean)) {
437
+ current = path.join(current, segment);
438
+ let stat;
439
+ try {
440
+ stat = await fs.lstat(current);
441
+ }
442
+ catch (err) {
443
+ if (err.code === 'ENOENT')
444
+ break;
445
+ throw err;
446
+ }
447
+ if (stat.isSymbolicLink()) {
448
+ throw new Error(`Refusing symlink in clone target path: ${current}`);
449
+ }
450
+ if (verifyOwnership)
451
+ await assertDirectoryOwnerAndPermissions(current);
452
+ }
453
+ }
434
454
  /**
435
455
  * Hosts the per-request GitHub PAT may be sent to. Exported so the
436
456
  * /api/analyze boundary check and this injection-site check share one
@@ -527,11 +547,10 @@ function warnIfCleartextCredential(url) {
527
547
  }
528
548
  }
529
549
  /**
530
- * Build the spawn env for `git`. Suppresses credential prompts and, when a
531
- * credential resolves (see resolveGitCredential), injects a single
532
- * host-scoped Authorization header via the `GIT_CONFIG_*` env protocol
533
- * (git ≥2.31) so credentials never appear in argv or the URL. Appends after
534
- * any existing `GIT_CONFIG_COUNT` rather than overwriting it. Exported for
550
+ * Build the spawn env for managed `git` commands. Suppresses credential
551
+ * prompts, disables repository hooks, and injects at most one host-scoped
552
+ * Authorization header via the `GIT_CONFIG_*` env protocol (git ≥2.31).
553
+ * Managed settings append after any existing GIT_CONFIG_COUNT. Exported for
535
554
  * unit tests.
536
555
  */
537
556
  export function buildGitEnv(baseEnv, options) {
@@ -550,18 +569,20 @@ export function buildGitEnv(baseEnv, options) {
550
569
  GIT_TRACE_PACKET: undefined,
551
570
  GIT_CURL_VERBOSE: undefined,
552
571
  };
572
+ const existing = Number.parseInt(env.GIT_CONFIG_COUNT ?? '', 10);
573
+ let next = Number.isInteger(existing) && existing > 0 ? existing : 0;
574
+ env[`GIT_CONFIG_KEY_${next}`] = 'core.hooksPath';
575
+ env[`GIT_CONFIG_VALUE_${next}`] = os.devNull;
576
+ next += 1;
553
577
  const credential = resolveGitCredential(options);
554
578
  const key = options?.url ? buildExtraHeaderKey(options.url) : undefined;
555
579
  if (credential && key) {
556
- // Append after any GIT_CONFIG_* the operator already set, so we never
557
- // clobber their git config (e.g. an enforced http.sslVerify).
558
- const existing = Number.parseInt(env.GIT_CONFIG_COUNT ?? '', 10);
559
- const base = Number.isInteger(existing) && existing > 0 ? existing : 0;
560
- env.GIT_CONFIG_COUNT = String(base + 1);
561
- env[`GIT_CONFIG_KEY_${base}`] = key;
562
- env[`GIT_CONFIG_VALUE_${base}`] = `Authorization: Basic ${credential}`;
580
+ env[`GIT_CONFIG_KEY_${next}`] = key;
581
+ env[`GIT_CONFIG_VALUE_${next}`] = `Authorization: Basic ${credential}`;
582
+ next += 1;
563
583
  warnIfCleartextCredential(options?.url);
564
584
  }
585
+ env.GIT_CONFIG_COUNT = String(next);
565
586
  return env;
566
587
  }
567
588
  // `options` carries the inputs the credential resolver needs: a per-request
@@ -569,30 +590,63 @@ export function buildGitEnv(baseEnv, options) {
569
590
  // host-scoped Authorization header (GitHub PAT for github.com, else the
570
591
  // server's AZURE_DEVOPS_PAT for Azure hosts) via the GIT_CONFIG_* protocol —
571
592
  // never in argv. See resolveGitCredential / buildExtraHeaderKey.
572
- function runGit(args, cwd, options) {
593
+ export function runGit(args, cwd, options) {
573
594
  return new Promise((resolve, reject) => {
574
- const proc = spawn('git', args, {
595
+ const spawnGit = options?.spawnForTest ?? spawn;
596
+ const proc = spawnGit('git', args, {
575
597
  cwd,
576
598
  stdio: ['ignore', 'pipe', 'pipe'],
577
599
  windowsHide: true,
578
600
  env: buildGitEnv(process.env, options),
579
601
  });
602
+ let stdout = '';
580
603
  let stderr = '';
604
+ let settled = false;
605
+ let timedOut = false;
606
+ let killTimer;
607
+ const finish = (fn) => {
608
+ if (settled)
609
+ return;
610
+ settled = true;
611
+ if (timer)
612
+ clearTimeout(timer);
613
+ if (killTimer)
614
+ clearTimeout(killTimer);
615
+ fn();
616
+ };
617
+ const timer = options?.timeoutMs && options.timeoutMs > 0
618
+ ? setTimeout(() => {
619
+ timedOut = true;
620
+ proc.kill('SIGTERM');
621
+ killTimer = setTimeout(() => {
622
+ proc.kill('SIGKILL');
623
+ finish(() => reject(new Error(`git ${args[0]} timed out after ${options.timeoutMs}ms`)));
624
+ }, options.timeoutKillGraceMs ?? 1_000);
625
+ }, options.timeoutMs)
626
+ : undefined;
627
+ proc.stdout?.on('data', (chunk) => {
628
+ stdout += chunk;
629
+ });
581
630
  proc.stderr.on('data', (chunk) => {
582
631
  stderr += chunk;
583
632
  });
584
633
  proc.on('close', (code) => {
634
+ if (timedOut) {
635
+ finish(() => reject(new Error(`git ${args[0]} timed out after ${options?.timeoutMs}ms`)));
636
+ return;
637
+ }
585
638
  if (code === 0)
586
- resolve();
639
+ finish(() => resolve(stdout));
587
640
  else {
588
641
  // Log full stderr internally but don't expose it to API callers (SSRF mitigation)
589
642
  if (stderr.trim())
590
643
  logger.error(`git ${args[0]} stderr: ${stderr.trim()}`);
591
- reject(new Error(`git ${args[0]} failed (exit code ${code})`));
644
+ finish(() => reject(new Error(`git ${args[0]} failed (exit code ${code})`)));
592
645
  }
593
646
  });
594
647
  proc.on('error', (err) => {
595
- reject(new Error(`Failed to spawn git: ${err.message}`));
648
+ finish(() => reject(new Error(`Failed to spawn git: ${err.message}`)));
596
649
  });
597
650
  });
598
651
  }
652
+ export const runGitForTest = runGit;
@@ -0,0 +1,18 @@
1
+ /** Hard cap on pattern length — unchanged from the literal-only era. */
2
+ export declare const GREP_PATTERN_MAX_LENGTH = 200;
3
+ /** Wall-clock budget for one /api/grep call; parent terminate()s the scan worker. */
4
+ export declare const GREP_TIME_BUDGET_MS = 5000;
5
+ export declare const GREP_DEFAULT_LIMIT = 50;
6
+ export declare const GREP_MAX_LIMIT = 200;
7
+ export interface ParsedGrepQuery {
8
+ regex: RegExp;
9
+ /** Lowercased path substring; '' disables path filtering. */
10
+ fileFilter: string;
11
+ limit: number;
12
+ }
13
+ /**
14
+ * Parse /api/grep query parameters into a ready-to-use regex + filters.
15
+ * Throws BadRequestError (mapped to HTTP 400 by statusFromError) on
16
+ * missing/over-long patterns or invalid regex syntax.
17
+ */
18
+ export declare function parseGrepQuery(query: Record<string, unknown>): ParsedGrepQuery;