gitnexus 1.6.8-rc.9 → 1.6.8

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 (430) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +11 -0
  135. package/dist/core/ingestion/languages/c-cpp.js +6 -1
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +3 -0
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +23 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
  238. package/dist/core/ingestion/workers/parse-worker.js +76 -17
  239. package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
  240. package/dist/core/ingestion/workers/worker-pool.js +15 -2
  241. package/dist/core/lbug/csv-generator.d.ts +51 -3
  242. package/dist/core/lbug/csv-generator.js +369 -255
  243. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  244. package/dist/core/lbug/lbug-adapter.js +346 -84
  245. package/dist/core/lbug/lbug-config.d.ts +19 -0
  246. package/dist/core/lbug/lbug-config.js +29 -0
  247. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  248. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  249. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  250. package/dist/core/lbug/pool-adapter.js +90 -1
  251. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  252. package/dist/core/lbug/rel-pair-routing.js +140 -0
  253. package/dist/core/lbug/schema.d.ts +2 -2
  254. package/dist/core/lbug/schema.js +3 -0
  255. package/dist/core/logger.d.ts +0 -5
  256. package/dist/core/run-analyze.d.ts +96 -0
  257. package/dist/core/run-analyze.js +207 -3
  258. package/dist/core/tree-sitter/parser-loader.js +5 -4
  259. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  260. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  261. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  262. package/dist/core/wiki/generator.js +3 -1
  263. package/dist/core/wiki/graph-queries.d.ts +6 -0
  264. package/dist/core/wiki/graph-queries.js +9 -1
  265. package/dist/mcp/http-transport.d.ts +134 -0
  266. package/dist/mcp/http-transport.js +503 -0
  267. package/dist/mcp/local/local-backend.d.ts +110 -24
  268. package/dist/mcp/local/local-backend.js +1225 -19
  269. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  270. package/dist/mcp/local/pdg-impact.js +1972 -0
  271. package/dist/mcp/resources.js +13 -1
  272. package/dist/mcp/tools.d.ts +12 -0
  273. package/dist/mcp/tools.js +214 -7
  274. package/dist/server/analyze-launch.d.ts +29 -0
  275. package/dist/server/analyze-launch.js +137 -0
  276. package/dist/server/analyze-upload.d.ts +33 -0
  277. package/dist/server/analyze-upload.js +123 -0
  278. package/dist/server/analyze-worker.d.ts +17 -1
  279. package/dist/server/api.d.ts +17 -0
  280. package/dist/server/api.js +132 -168
  281. package/dist/server/git-clone.d.ts +43 -2
  282. package/dist/server/git-clone.js +211 -16
  283. package/dist/server/mcp-http.d.ts +5 -6
  284. package/dist/server/mcp-http.js +9 -82
  285. package/dist/server/middleware.d.ts +40 -0
  286. package/dist/server/middleware.js +92 -0
  287. package/dist/server/private-ip.d.ts +1 -0
  288. package/dist/server/private-ip.js +15 -0
  289. package/dist/server/upload-ingest.d.ts +56 -0
  290. package/dist/server/upload-ingest.js +276 -0
  291. package/dist/server/upload-paths.d.ts +37 -0
  292. package/dist/server/upload-paths.js +57 -0
  293. package/dist/server/upload-sweep.d.ts +20 -0
  294. package/dist/server/upload-sweep.js +57 -0
  295. package/dist/storage/git.js +5 -8
  296. package/dist/storage/parse-cache.d.ts +22 -1
  297. package/dist/storage/parse-cache.js +41 -10
  298. package/dist/storage/repo-manager.d.ts +121 -10
  299. package/dist/storage/repo-manager.js +43 -5
  300. package/dist/types/pipeline.d.ts +9 -0
  301. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  302. package/hooks/claude/gitnexus-hook.cjs +110 -13
  303. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  304. package/package.json +8 -2
  305. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  306. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  307. package/scripts/cross-platform-tests.ts +1 -0
  308. package/skills/gitnexus-debugging.md +16 -4
  309. package/skills/gitnexus-exploring.md +3 -3
  310. package/skills/gitnexus-guide.md +33 -0
  311. package/skills/gitnexus-pdg-query.md +89 -0
  312. package/skills/gitnexus-refactoring.md +1 -1
  313. package/skills/gitnexus-taint-analysis.md +178 -0
  314. package/vendor/tree-sitter-c/package.json +1 -1
  315. package/vendor/tree-sitter-dart/package.json +1 -1
  316. package/vendor/tree-sitter-kotlin/package.json +1 -1
  317. package/vendor/tree-sitter-proto/package.json +1 -1
  318. package/vendor/tree-sitter-swift/package.json +1 -1
  319. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  320. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  321. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  322. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  323. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  324. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  325. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  326. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  327. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  328. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  329. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  330. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  331. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  332. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  333. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  334. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  335. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  336. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  337. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  338. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  339. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  340. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  341. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  342. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  343. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  344. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  345. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  346. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  347. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  348. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  349. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  350. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  351. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  352. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  353. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  354. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  355. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  356. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  357. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  358. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  359. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  360. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  361. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  362. package/web/assets/index-BvYev1UJ.js +635 -0
  363. package/web/assets/index-_lgn7hs5.css +2 -0
  364. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  365. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  366. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  367. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  368. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  369. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  370. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  371. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  372. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  373. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  374. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  375. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  376. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  377. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  378. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  379. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  380. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  381. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  382. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  383. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  384. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  385. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  386. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  387. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  388. package/web/index.html +13 -15
  389. package/scripts/materialize-vendor-grammars.cjs +0 -97
  390. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  391. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  392. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  393. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  394. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  395. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  396. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  397. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  398. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  399. package/web/assets/chunk-CilyBKbf.js +0 -1
  400. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  401. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  402. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  403. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  404. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  405. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  406. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  407. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  408. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  409. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  410. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  411. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  412. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  413. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  414. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  415. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  416. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  417. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  418. package/web/assets/index-BKWA-m7o.css +0 -2
  419. package/web/assets/index-CG6q8eTs.js +0 -626
  420. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  421. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  422. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  423. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  424. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  425. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  426. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  427. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  428. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  429. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  430. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Secure ingestion of a browser folder upload (multipart/form-data).
3
+ *
4
+ * Replaces the path-injection-prone GET /api/fs/list directory listing. The
5
+ * client streams the selected files plus a JSON `manifest` of their
6
+ * webkitRelativePaths; we write each into an mkdtemp staging dir under
7
+ * UPLOAD_ROOT with PROVABLE containment (resolve-then-contain), hard resource
8
+ * caps, and guaranteed cleanup on every failure/abort path. No client value
9
+ * ever reaches a filesystem READ — the server only writes into a sandbox it
10
+ * created, then hands that sandbox to the analysis pipeline.
11
+ *
12
+ * Security references: CodeQL js/path-injection (resolve + startsWith(root+sep)),
13
+ * OWASP File Upload / Path Traversal.
14
+ */
15
+ import path from 'path';
16
+ import fs from 'fs';
17
+ import fsp from 'fs/promises';
18
+ import busboy from 'busboy';
19
+ import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
20
+ import { BadRequestError } from './validation.js';
21
+ export const DEFAULT_INGEST_LIMITS = {
22
+ maxTotalBytes: 250 * 1024 * 1024,
23
+ maxFileBytes: 25 * 1024 * 1024,
24
+ maxFiles: 20000,
25
+ maxParts: 20100,
26
+ maxDirs: 50000,
27
+ maxFieldBytes: 2 * 1024 * 1024,
28
+ };
29
+ const MAX_PATH_DEPTH = 64;
30
+ const MAX_PATH_LENGTH = 4096;
31
+ /**
32
+ * Resolve a client-provided relative path to an absolute destination PROVABLY
33
+ * contained within `stageRoot`. Throws BadRequestError on any unsafe input.
34
+ * This is the load-bearing path-traversal-on-write control; keep it pure and
35
+ * unit-tested.
36
+ */
37
+ export function resolveContainedDest(stageRoot, rel) {
38
+ if (typeof rel !== 'string' || rel.length === 0) {
39
+ throw new BadRequestError('Invalid upload path');
40
+ }
41
+ if (rel.length > MAX_PATH_LENGTH) {
42
+ throw new BadRequestError('Upload path too long');
43
+ }
44
+ // webkitRelativePath is always relative; a leading slash is absolute/hostile.
45
+ if (rel.startsWith('/')) {
46
+ throw new BadRequestError('Invalid upload path');
47
+ }
48
+ // Browsers emit forward slashes only; a NUL byte or backslash is hostile.
49
+ if (rel.includes('\u0000') || rel.includes('\\')) {
50
+ throw new BadRequestError('Invalid upload path');
51
+ }
52
+ const rawSegments = rel.split('/').filter((s) => s.length > 0);
53
+ if (rawSegments.length === 0 || rawSegments.length > MAX_PATH_DEPTH) {
54
+ throw new BadRequestError('Invalid upload path');
55
+ }
56
+ const segments = [];
57
+ for (const seg of rawSegments) {
58
+ // Normalize so NFC/NFD variants don't collide silently on case/unicode
59
+ // -folding filesystems (macOS/Windows).
60
+ const s = seg.normalize('NFC');
61
+ if (s === '.' || s === '..') {
62
+ throw new BadRequestError('Upload path must not contain traversal segments');
63
+ }
64
+ segments.push(s);
65
+ }
66
+ const dest = path.resolve(stageRoot, segments.join(path.sep));
67
+ // Suffix path.sep so a sibling prefix (/sandbox-evil vs /sandbox) can't pass.
68
+ if (dest !== stageRoot && !dest.startsWith(stageRoot + path.sep)) {
69
+ throw new BadRequestError('Upload path escapes the sandbox');
70
+ }
71
+ return dest;
72
+ }
73
+ /**
74
+ * Create the parent directories of `destFile` one segment at a time, asserting
75
+ * after each `mkdir` that the segment is a real directory (not a symlink
76
+ * swapped in mid-stream) still inside `stageRoot`. Counts created dirs against
77
+ * the inode-exhaustion cap.
78
+ */
79
+ function mkdirContained(stageRoot, destFile, state) {
80
+ const parent = path.dirname(destFile);
81
+ const relParent = path.relative(stageRoot, parent);
82
+ if (relParent === '' || relParent === '.')
83
+ return;
84
+ const segs = relParent.split(path.sep).filter(Boolean);
85
+ let cur = stageRoot;
86
+ for (const seg of segs) {
87
+ cur = path.join(cur, seg);
88
+ let made = false;
89
+ try {
90
+ fs.mkdirSync(cur);
91
+ made = true;
92
+ }
93
+ catch (err) {
94
+ if (err.code !== 'EEXIST')
95
+ throw err;
96
+ }
97
+ const st = fs.lstatSync(cur);
98
+ if (st.isSymbolicLink() || !st.isDirectory()) {
99
+ throw new BadRequestError('Upload path escapes the sandbox');
100
+ }
101
+ if (made) {
102
+ state.dirCount++;
103
+ if (state.dirCount > state.limits.maxDirs) {
104
+ throw new BadRequestError('Too many directories in upload', 413);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ /**
110
+ * Parse and securely write a multipart folder upload into a fresh staging
111
+ * directory under UPLOAD_ROOT. Resolves with the populated staging dir, or
112
+ * rejects with a BadRequestError (status 400/413) after removing the staging
113
+ * dir. The caller owns promotion + cleanup of the returned `stageRoot`.
114
+ */
115
+ export async function ingestUpload(req, limitsOverride, opts = {}) {
116
+ const limits = { ...DEFAULT_INGEST_LIMITS, ...limitsOverride };
117
+ const uploadRoot = opts.root ?? UPLOAD_ROOT;
118
+ await fsp.mkdir(uploadRoot, { recursive: true });
119
+ // mkdtemp creates the dir mode 0o700 (owner-only); realpath canonicalizes
120
+ // the root so the containment prefix check is exact.
121
+ const stageRoot = await fsp.realpath(await fsp.mkdtemp(path.join(uploadRoot, STAGING_PREFIX)));
122
+ let cleaned = false;
123
+ const cleanup = async () => {
124
+ if (cleaned)
125
+ return;
126
+ cleaned = true;
127
+ await fsp.rm(stageRoot, { recursive: true, force: true }).catch(() => { });
128
+ };
129
+ return new Promise((resolve, reject) => {
130
+ let settled = false;
131
+ let manifest = null;
132
+ let fileIndex = 0;
133
+ let fileCount = 0;
134
+ let totalBytes = 0;
135
+ let topLevelName = '';
136
+ const dirState = { dirCount: 0, limits };
137
+ const writePromises = [];
138
+ const bb = busboy({
139
+ headers: req.headers,
140
+ limits: {
141
+ fileSize: limits.maxFileBytes,
142
+ files: limits.maxFiles,
143
+ parts: limits.maxParts,
144
+ fields: 10,
145
+ fieldNameSize: 200,
146
+ fieldSize: limits.maxFieldBytes,
147
+ headerPairs: 2000,
148
+ },
149
+ });
150
+ const fail = (err) => {
151
+ if (settled)
152
+ return;
153
+ settled = true;
154
+ try {
155
+ req.unpipe(bb);
156
+ }
157
+ catch {
158
+ /* ignore */
159
+ }
160
+ try {
161
+ req.resume(); // drain remaining body so the socket isn't left hanging
162
+ }
163
+ catch {
164
+ /* ignore */
165
+ }
166
+ void cleanup().finally(() => reject(err));
167
+ };
168
+ bb.on('field', (name, val) => {
169
+ if (name !== 'manifest')
170
+ return;
171
+ try {
172
+ const parsed = JSON.parse(val);
173
+ if (!Array.isArray(parsed) || !parsed.every((p) => typeof p === 'string')) {
174
+ return fail(new BadRequestError('Invalid manifest'));
175
+ }
176
+ manifest = parsed;
177
+ }
178
+ catch {
179
+ fail(new BadRequestError('Invalid manifest'));
180
+ }
181
+ });
182
+ bb.on('file', (_name, stream, _info) => {
183
+ if (settled) {
184
+ stream.resume();
185
+ return;
186
+ }
187
+ if (manifest === null) {
188
+ // The manifest field MUST arrive before any file part.
189
+ stream.resume();
190
+ return fail(new BadRequestError('Manifest must precede file parts'));
191
+ }
192
+ const idx = fileIndex++;
193
+ const rel = manifest[idx];
194
+ let dest;
195
+ try {
196
+ dest = resolveContainedDest(stageRoot, rel);
197
+ // A folder upload is exactly one top-level directory: every entry must
198
+ // have ≥2 segments and share the same first segment. This rejects a
199
+ // bare file at the root (which would make the promote target a file)
200
+ // and a multi-top manifest (which would silently drop all but the
201
+ // first folder). Validated here, before any job is created.
202
+ const segs = String(rel)
203
+ .split('/')
204
+ .filter((s) => s.length > 0);
205
+ const firstSeg = (segs[0] ?? '').normalize('NFC');
206
+ if (!topLevelName) {
207
+ topLevelName = firstSeg;
208
+ }
209
+ if (segs.length < 2 || firstSeg !== topLevelName) {
210
+ throw new BadRequestError('Upload must be a single folder of files');
211
+ }
212
+ mkdirContained(stageRoot, dest, dirState);
213
+ }
214
+ catch (err) {
215
+ stream.resume();
216
+ return fail(err);
217
+ }
218
+ fileCount++;
219
+ const ws = fs.createWriteStream(dest, { flags: 'wx' });
220
+ const p = new Promise((res, rej) => {
221
+ stream.on('data', (chunk) => {
222
+ totalBytes += chunk.length;
223
+ if (totalBytes > limits.maxTotalBytes) {
224
+ stream.unpipe(ws);
225
+ ws.destroy();
226
+ rej(new BadRequestError('Upload exceeds total size limit', 413));
227
+ }
228
+ });
229
+ stream.on('limit', () => {
230
+ ws.destroy();
231
+ rej(new BadRequestError('File exceeds size limit', 413));
232
+ });
233
+ stream.on('error', rej);
234
+ ws.on('error', rej);
235
+ ws.on('finish', () => res());
236
+ stream.pipe(ws);
237
+ });
238
+ writePromises.push(p);
239
+ p.catch(fail);
240
+ });
241
+ bb.on('filesLimit', () => fail(new BadRequestError('Too many files in upload', 413)));
242
+ bb.on('partsLimit', () => fail(new BadRequestError('Too many parts in upload', 413)));
243
+ bb.on('fieldsLimit', () => fail(new BadRequestError('Too many fields in upload')));
244
+ bb.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Upload parse error')));
245
+ bb.on('close', () => {
246
+ if (settled)
247
+ return;
248
+ Promise.all(writePromises)
249
+ .then(() => {
250
+ if (settled)
251
+ return;
252
+ if (manifest === null)
253
+ return fail(new BadRequestError('Missing manifest'));
254
+ if (fileCount === 0)
255
+ return fail(new BadRequestError('Empty upload'));
256
+ if (fileCount !== manifest.length) {
257
+ return fail(new BadRequestError('Manifest/file count mismatch'));
258
+ }
259
+ if (!topLevelName) {
260
+ return fail(new BadRequestError('Could not determine upload folder name'));
261
+ }
262
+ settled = true;
263
+ resolve({ stageRoot, fileCount, totalBytes, topLevelName });
264
+ })
265
+ .catch(() => {
266
+ /* a write rejected → fail() already invoked via p.catch */
267
+ });
268
+ });
269
+ req.on('aborted', () => {
270
+ if (!settled)
271
+ fail(new BadRequestError('Upload aborted'));
272
+ });
273
+ req.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Request error')));
274
+ req.pipe(bb);
275
+ });
276
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Upload working-directory paths.
3
+ *
4
+ * Browser folder uploads are written into getGlobalDir()/uploads/{name}/ — a
5
+ * sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
6
+ * persists and behaves like a cloned one (the graph UI's /api/file reads its
7
+ * files after analysis, and DELETE /api/repo removes it). Staging happens in
8
+ * an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
9
+ * filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
10
+ * the exact Docker case this feature targets; see bridge-db.ts for the same
11
+ * anchored-staging pattern).
12
+ */
13
+ /**
14
+ * Root directory for all uploaded repositories. Targets must resolve inside this.
15
+ *
16
+ * Sourced from getGlobalDir() so it honors GITNEXUS_HOME and stays a sibling of
17
+ * the clone root on the same (in Docker, persistent) volume. Falls back to
18
+ * ~/.gitnexus when the env var is unset.
19
+ */
20
+ export declare const UPLOAD_ROOT: string;
21
+ /** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
22
+ export declare const STAGING_PREFIX = ".staging-";
23
+ /**
24
+ * Get the upload target directory for a repo name.
25
+ *
26
+ * Re-validates at the boundary (callers may derive the name from an untrusted
27
+ * manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
28
+ * emits for un-nameable inputs, names beginning with `.` (which would collide
29
+ * with the `.staging-` prefix), and anything outside the safe charset.
30
+ */
31
+ export declare function getUploadDir(repoName: string): string;
32
+ /**
33
+ * Derive a filesystem-safe upload directory name from the manifest's
34
+ * top-level folder. Returns null when the name is un-nameable (so the caller
35
+ * rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
36
+ */
37
+ export declare function deriveUploadName(topLevelName: string): string | null;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Upload working-directory paths.
3
+ *
4
+ * Browser folder uploads are written into getGlobalDir()/uploads/{name}/ — a
5
+ * sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
6
+ * persists and behaves like a cloned one (the graph UI's /api/file reads its
7
+ * files after analysis, and DELETE /api/repo removes it). Staging happens in
8
+ * an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
9
+ * filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
10
+ * the exact Docker case this feature targets; see bridge-db.ts for the same
11
+ * anchored-staging pattern).
12
+ */
13
+ import path from 'path';
14
+ import { sanitizeRepoName } from '../storage/git.js';
15
+ import { REPO_NAME_PATTERN } from './git-clone.js';
16
+ import { getGlobalDir } from '../storage/repo-manager.js';
17
+ /**
18
+ * Root directory for all uploaded repositories. Targets must resolve inside this.
19
+ *
20
+ * Sourced from getGlobalDir() so it honors GITNEXUS_HOME and stays a sibling of
21
+ * the clone root on the same (in Docker, persistent) volume. Falls back to
22
+ * ~/.gitnexus when the env var is unset.
23
+ */
24
+ export const UPLOAD_ROOT = path.resolve(path.join(getGlobalDir(), 'uploads'));
25
+ /** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
26
+ export const STAGING_PREFIX = '.staging-';
27
+ /**
28
+ * Get the upload target directory for a repo name.
29
+ *
30
+ * Re-validates at the boundary (callers may derive the name from an untrusted
31
+ * manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
32
+ * emits for un-nameable inputs, names beginning with `.` (which would collide
33
+ * with the `.staging-` prefix), and anything outside the safe charset.
34
+ */
35
+ export function getUploadDir(repoName) {
36
+ if (!repoName ||
37
+ repoName === '.' ||
38
+ repoName === '..' ||
39
+ repoName === 'unknown' ||
40
+ repoName.startsWith('.') ||
41
+ !REPO_NAME_PATTERN.test(repoName)) {
42
+ throw new Error('Invalid repository name');
43
+ }
44
+ return path.join(UPLOAD_ROOT, repoName);
45
+ }
46
+ /**
47
+ * Derive a filesystem-safe upload directory name from the manifest's
48
+ * top-level folder. Returns null when the name is un-nameable (so the caller
49
+ * rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
50
+ */
51
+ export function deriveUploadName(topLevelName) {
52
+ const safe = sanitizeRepoName(topLevelName);
53
+ if (safe === 'unknown' || safe === '.' || safe === '..' || safe.startsWith('.')) {
54
+ return null;
55
+ }
56
+ return safe;
57
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Backstop cleanup for abandoned upload staging directories.
3
+ *
4
+ * A crashed/killed process can leave a `.staging-*` directory under
5
+ * UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
6
+ * sweep, run once at server startup, removes staging dirs older than a
7
+ * threshold. Promoted upload dirs are persistent registered repos (like
8
+ * clones) and are NOT touched here — they are removed via DELETE /api/repo.
9
+ */
10
+ export interface SweepOptions {
11
+ /** Remove staging dirs older than this (default 6h). */
12
+ maxAgeMs?: number;
13
+ /** Override the root to sweep (defaults to UPLOAD_ROOT; for tests). */
14
+ root?: string;
15
+ /** Clock injection for tests. */
16
+ now?: number;
17
+ }
18
+ export declare function sweepStaleUploads(opts?: SweepOptions): Promise<{
19
+ removed: string[];
20
+ }>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Backstop cleanup for abandoned upload staging directories.
3
+ *
4
+ * A crashed/killed process can leave a `.staging-*` directory under
5
+ * UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
6
+ * sweep, run once at server startup, removes staging dirs older than a
7
+ * threshold. Promoted upload dirs are persistent registered repos (like
8
+ * clones) and are NOT touched here — they are removed via DELETE /api/repo.
9
+ */
10
+ import path from 'path';
11
+ import fsp from 'fs/promises';
12
+ import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
13
+ export async function sweepStaleUploads(opts = {}) {
14
+ const maxAgeMs = opts.maxAgeMs ?? 6 * 60 * 60 * 1000;
15
+ const root = opts.root ?? UPLOAD_ROOT;
16
+ const now = opts.now ?? Date.now();
17
+ const removed = [];
18
+ let entries;
19
+ try {
20
+ entries = await fsp.readdir(root, { withFileTypes: true });
21
+ }
22
+ catch {
23
+ return { removed }; // root does not exist yet — nothing to sweep
24
+ }
25
+ for (const entry of entries) {
26
+ if (!entry.isDirectory())
27
+ continue;
28
+ const full = path.join(root, entry.name);
29
+ try {
30
+ const st = await fsp.stat(full);
31
+ if (now - st.mtimeMs <= maxAgeMs)
32
+ continue; // recent — keep
33
+ if (entry.name.startsWith(STAGING_PREFIX)) {
34
+ // Transient staging dir orphaned by a crash — always removable.
35
+ await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
36
+ removed.push(full);
37
+ }
38
+ else {
39
+ // Promoted upload dir. A successfully-analyzed (registered) repo always
40
+ // has a `.gitnexus` index inside it; a stale promoted dir WITHOUT one is
41
+ // an orphan from an analysis that failed before registering — remove it.
42
+ const hasIndex = await fsp
43
+ .access(path.join(full, '.gitnexus'))
44
+ .then(() => true)
45
+ .catch(() => false);
46
+ if (!hasIndex) {
47
+ await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
48
+ removed.push(full);
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ /* stat race — skip */
54
+ }
55
+ }
56
+ return { removed };
57
+ }
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
2
2
  import { statSync } from 'fs';
3
3
  import path from 'path';
4
4
  // Git utilities for repository detection, commit tracking, and diff analysis
5
+ const chompGitOutput = (value) => value.toString().replace(/\r?\n$/, '');
5
6
  export const isGitRepo = (repoPath) => {
6
7
  try {
7
8
  execSync('git rev-parse --is-inside-work-tree', {
@@ -99,14 +100,12 @@ export const getRemoteUrl = (repoPath) => {
99
100
  */
100
101
  export const getGitRoot = (fromPath) => {
101
102
  try {
102
- const raw = execSync('git rev-parse --show-toplevel', {
103
+ const raw = chompGitOutput(execSync('git rev-parse --show-toplevel', {
103
104
  cwd: fromPath,
104
105
  // Suppress stderr -- see getCurrentCommit comment and #1172.
105
106
  stdio: ['ignore', 'pipe', 'ignore'],
106
107
  windowsHide: true,
107
- })
108
- .toString()
109
- .trim();
108
+ }));
110
109
  // On Windows, git returns /d/Projects/Foo — path.resolve normalizes to D:\Projects\Foo
111
110
  return path.resolve(raw);
112
111
  }
@@ -143,13 +142,11 @@ export const getGitRoot = (fromPath) => {
143
142
  */
144
143
  export const getCanonicalRepoRoot = (fromPath) => {
145
144
  try {
146
- const commonDir = execSync('git rev-parse --path-format=absolute --git-common-dir', {
145
+ const commonDir = chompGitOutput(execSync('git rev-parse --path-format=absolute --git-common-dir', {
147
146
  cwd: fromPath,
148
147
  stdio: ['ignore', 'pipe', 'ignore'],
149
148
  windowsHide: true,
150
- })
151
- .toString()
152
- .trim();
149
+ }));
153
150
  if (!commonDir)
154
151
  return null;
155
152
  // Common dir is `<repo>/.git` for both the main checkout and all
@@ -53,10 +53,31 @@ export declare const fileContentHash: (content: Buffer | string) => string;
53
53
  * in the chunk. We sort by filePath before hashing so chunks composed of
54
54
  * the same files in different order produce the same key.
55
55
  */
56
+ /** PDG/CFG cache namespace (#2081 M1) — every input that changes the
57
+ * WORKER-EMITTED `cfgSideChannel` must be folded into the chunk key, and
58
+ * ONLY those. The classification test for a future option: does the worker
59
+ * see it (workerData) and does it change the bytes the worker writes to the
60
+ * shard? `pdgMaxEdgesPerFunction` famously fails that test — it is applied
61
+ * at EMIT time on the main thread (scope-resolution run.ts), the worker
62
+ * never receives it, and the cached output is byte-identical across cap
63
+ * values; folding it in (as a prior review round did) only forced a
64
+ * spurious full re-parse on every cap change (#2099 F3). Options that
65
+ * change the PERSISTED GRAPH but not the shard belong in the RepoMeta pdg
66
+ * stamp (incremental-eligibility), not here. */
67
+ export interface PdgCacheKey {
68
+ readonly pdg?: boolean;
69
+ /** Per-function source-line cap (changes WHICH functions get a CFG —
70
+ * applied in the worker, so it shapes the cached shard). Callers must
71
+ * pass the RESOLVED value (the production call site in parse-impl.ts
72
+ * applies the worker's default before folding) so an explicit-default
73
+ * run shares the default run's keys — this function folds whatever it
74
+ * is given verbatim. */
75
+ readonly maxFunctionLines?: number;
76
+ }
56
77
  export declare const computeChunkHash: (entries: Array<{
57
78
  filePath: string;
58
79
  contentHash: string;
59
- }>) => string;
80
+ }>, pdg?: boolean | PdgCacheKey) => string;
60
81
  export declare const mapReplacer: (_key: string, value: unknown) => unknown;
61
82
  export declare const mapReviver: (_key: string, value: unknown) => unknown;
62
83
  /**
@@ -52,7 +52,7 @@ import { fileURLToPath } from 'url';
52
52
  // the main thread (the #1983 OOM). Because the two stores share this version,
53
53
  // any future change to the `ParsedFile` serialization shape MUST bump
54
54
  // SCHEMA_BUMP so both invalidate in lockstep.
55
- const SCHEMA_BUMP = 4;
55
+ const SCHEMA_BUMP = 6; // #2082 M2: cfgSideChannel gained bindings + per-block statement facts
56
56
  const GITNEXUS_PKG_VERSION = (() => {
57
57
  try {
58
58
  // package.json sits at gitnexus/package.json — two levels up from
@@ -92,17 +92,48 @@ const sha256Hex = (input) => createHash('sha256')
92
92
  .digest('hex');
93
93
  /** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
94
94
  export const fileContentHash = (content) => sha256Hex(content);
95
- /**
96
- * Compute the canonical cache key for a chunk's contents.
97
- *
98
- * `entries` is the list of (filePath, file content hash) for every file
99
- * in the chunk. We sort by filePath before hashing so chunks composed of
100
- * the same files in different order produce the same key.
101
- */
102
- export const computeChunkHash = (entries) => {
95
+ export const computeChunkHash = (entries, pdg = false) => {
103
96
  const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
104
97
  const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
105
- return sha256Hex(joined);
98
+ const opts = typeof pdg === 'boolean' ? { pdg } : pdg;
99
+ // pdg-off path keeps its pre-#2081 chunk-KEY format verbatim. Note this does
100
+ // NOT mean caches survive the M1 upgrade: SCHEMA_BUMP 4→5 changed
101
+ // PARSE_CACHE_VERSION, and both loadParseCache (below) and the durable
102
+ // parsedfile-store index hard-invalidate on it — every user pays one full
103
+ // cold re-parse on upgrade regardless of --pdg. Keeping the key format
104
+ // stable only means no SECOND invalidation class is introduced here.
105
+ if (!opts.pdg)
106
+ return sha256Hex(joined);
107
+ // Fold the worker-visible --pdg configuration into the key: the boolean
108
+ // plus `maxFunctionLines` (decides which functions get a CFG at all, in the
109
+ // worker). Without it a warm chunk built under one cap is served to a run
110
+ // with a different cap → a stale/under-built CFG: the #2038-class
111
+ // option-blind-key trap. `def` marks an unset (default) value so two
112
+ // default-cap runs share a key. The emit-time edge cap is deliberately
113
+ // absent — see the PdgCacheKey doc comment.
114
+ //
115
+ // NAMESPACE VERSION (`pdg:5`): bumped when the worker-emitted
116
+ // `cfgSideChannel` SHAPE changes for pdg-mode runs only — pdg:1→2 in #2083
117
+ // M3 U1 (TsHarvester emits taint `sites` on StatementFacts); pdg:2→3 in the
118
+ // #2227 follow-up U1 (every C-family / TS harvester now stamps the call-site
119
+ // anchor `SiteRecord.at`, which the resolved-callee-id join reads); pdg:3→4 in
120
+ // the #2227 tri-review-2 U4 (the Rust harvester now emits a `kind:'new'` site
121
+ // for `struct_expression`, a new worker-output site the join consumes); pdg:4→5
122
+ // in the FU-C call-summary soundness fix (the TS harvester now stamps
123
+ // `BindingEntry.formalIndex` on param bindings so the PDG call-summary keys
124
+ // return-flow on the enclosing FORMAL position, not the flattened binding
125
+ // ordinal — a warm chunk lacking it would route the harvest to its conservative
126
+ // empty-summary fallback). A warm chunk built by a worker predating the relevant
127
+ // change carries a stale site shape, so the join skips it and
128
+ // `BasicBlock.calleeIds` is silently empty (or missing the struct constructor)
129
+ // even though `callees` is populated — exactly the #2225-class shape skew this
130
+ // version token exists to prevent. Invalidates pdg-mode chunks and their durable
131
+ // parsedfile-cache entries; flag-off chunk keys never reach this line and stay
132
+ // byte-identical, so non-pdg users pay nothing. Deliberately NOT a SCHEMA_BUMP —
133
+ // that gates the whole cache version and would force a full cold re-parse on
134
+ // EVERY user (the M1 bump comment above records that cost).
135
+ const ns = `pdg:5;maxFn=${opts.maxFunctionLines ?? 'def'}`;
136
+ return sha256Hex(`${ns}\n${joined}`);
106
137
  };
107
138
  /**
108
139
  * JSON replacer that round-trips Map/Set instances through plain JSON.