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
@@ -0,0 +1,497 @@
1
+ import fs from 'node:fs/promises';
2
+ import crypto from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { acquireFileLock, FileLockBusyError } from '../../storage/file-lock.js';
6
+ import { getGlobalDir } from '../../storage/repo-manager.js';
7
+ import { isProcessAlive, readProcessStartTime } from '../../utils/process-identity.js';
8
+ import { loadAutoSyncConfig } from './config.js';
9
+ import { runAutoSyncOnce } from './runner.js';
10
+ import { getAutoSyncMutexPath, getAutoSyncWatchDir } from './state.js';
11
+ const WATCH_STOP_POLL_MS = 250;
12
+ export function getAutoSyncWatchPaths(gitnexusDir = getGlobalDir()) {
13
+ const watchDir = getAutoSyncWatchDir(gitnexusDir);
14
+ return {
15
+ pidPath: path.join(watchDir, 'watch.pid'),
16
+ mutexPath: getAutoSyncMutexPath(gitnexusDir),
17
+ ownerPath: path.join(watchDir, 'watch.owner.json'),
18
+ statusPath: path.join(watchDir, 'watch.status.json'),
19
+ };
20
+ }
21
+ export async function startAutoSyncWatch(options = {}) {
22
+ const stderr = options.stderr ?? process.stderr;
23
+ const paths = options.paths ?? getAutoSyncWatchPaths();
24
+ const deps = resolveWatchDeps(options.deps);
25
+ const ownerId = crypto.randomUUID();
26
+ const processStartTime = deps.readProcessStartTime(process.pid);
27
+ if (!processStartTime) {
28
+ stderr.write('[auto-sync] Unable to verify the watch process start time.\n');
29
+ return null;
30
+ }
31
+ await fs.mkdir(path.dirname(paths.pidPath), { recursive: true });
32
+ const releaseLock = await acquireWatchLock(paths, deps, stderr, processStartTime);
33
+ if (!releaseLock)
34
+ return null;
35
+ try {
36
+ await writeWatchOwner(paths, {
37
+ pid: process.pid,
38
+ ownerId,
39
+ processStartTime,
40
+ createdAt: new Date().toISOString(),
41
+ });
42
+ await writeAtomicText(paths.pidPath, `${process.pid}\n`);
43
+ const loaded = await loadAutoSyncConfig();
44
+ if (loaded.ok === false) {
45
+ stderr.write(`${loaded.message}\n`);
46
+ await writeWatchStatus(paths, {
47
+ state: 'error',
48
+ pid: process.pid,
49
+ ownerId,
50
+ message: loaded.message,
51
+ updatedAt: new Date().toISOString(),
52
+ });
53
+ await cleanupWatchFiles(paths, ownerId, releaseLock);
54
+ return null;
55
+ }
56
+ await writeWatchStatus(paths, {
57
+ state: 'running',
58
+ pid: process.pid,
59
+ ownerId,
60
+ configPath: loaded.config.configPath,
61
+ updatedAt: new Date().toISOString(),
62
+ });
63
+ const runOnce = options.runOnce ?? runAutoSyncOnce;
64
+ const setIntervalFn = options.setIntervalFn ?? setInterval;
65
+ const clearIntervalFn = options.clearIntervalFn ?? clearInterval;
66
+ let activeRun;
67
+ let activeAbortController;
68
+ let stopping = false;
69
+ let statusWrite = Promise.resolve();
70
+ const updateStatus = (state, message) => {
71
+ const write = statusWrite.then(() => writeWatchStatus(paths, {
72
+ state,
73
+ pid: process.pid,
74
+ ownerId,
75
+ configPath: loaded.config.configPath,
76
+ message,
77
+ updatedAt: new Date().toISOString(),
78
+ }));
79
+ statusWrite = write.catch(() => { });
80
+ return write;
81
+ };
82
+ const reportStatusWriteFailure = (error) => {
83
+ stderr.write(`[auto-sync] Failed to publish watch status: ${error.message}\n`);
84
+ };
85
+ const runSafely = () => {
86
+ if (stopping)
87
+ return;
88
+ if (activeRun) {
89
+ stderr.write('[auto-sync] Previous run is still active; skipping overlapping run.\n');
90
+ return;
91
+ }
92
+ const startedAt = new Date();
93
+ stderr.write(`[auto-sync] Watch loop started at ${startedAt.toISOString()}.\n`);
94
+ const abortController = new AbortController();
95
+ const run = runOnce(loaded.config, {
96
+ signal: abortController.signal,
97
+ onAnalysisCancellationRequested: () => {
98
+ if (!stopping) {
99
+ void updateStatus('cancelling', 'Analysis cancellation requested; waiting for the worker to reach a safe shutdown point.').catch(reportStatusWriteFailure);
100
+ }
101
+ },
102
+ })
103
+ .then((result) => {
104
+ stderr.write(`[auto-sync] Watch loop finished: synced=${result.synced} analyzed=${result.analyzed} skipped=${result.skippedAnalysis} failed=${result.failed}.\n`);
105
+ })
106
+ .catch((err) => {
107
+ stderr.write(`[auto-sync] Scheduled run failed: ${err.message}\n`);
108
+ stderr.write('[auto-sync] Watch loop finished: failed.\n');
109
+ })
110
+ .finally(async () => {
111
+ if (activeRun === run) {
112
+ activeRun = undefined;
113
+ activeAbortController = undefined;
114
+ }
115
+ if (!stopping) {
116
+ await updateStatus('running').catch(reportStatusWriteFailure);
117
+ }
118
+ });
119
+ activeRun = run;
120
+ activeAbortController = abortController;
121
+ };
122
+ let stopPromise;
123
+ const stop = () => (stopPromise ??= (async () => {
124
+ stopping = true;
125
+ clearIntervalFn(timer);
126
+ clearIntervalFn(controlTimer);
127
+ activeAbortController?.abort();
128
+ try {
129
+ await updateStatus('stopping');
130
+ await activeRun?.catch(() => { });
131
+ await updateStatus('stopped');
132
+ }
133
+ finally {
134
+ await cleanupWatchFiles(paths, ownerId, releaseLock);
135
+ }
136
+ })());
137
+ const checkStopRequest = async () => {
138
+ const request = await readStopRequest(stopRequestPath(paths, ownerId));
139
+ if (request?.pid === process.pid &&
140
+ request.ownerId === ownerId &&
141
+ request.processStartTime === processStartTime) {
142
+ void stop().catch((error) => {
143
+ stderr.write(`[auto-sync] Failed to stop watch: ${error.message}\n`);
144
+ });
145
+ }
146
+ };
147
+ runSafely();
148
+ const controlTimer = setIntervalFn(() => void checkStopRequest(), WATCH_STOP_POLL_MS);
149
+ const timer = setIntervalFn(runSafely, loaded.config.syncIntervalMinutes * 60_000);
150
+ if (options.keepAlive === false) {
151
+ controlTimer.unref?.();
152
+ timer.unref?.();
153
+ }
154
+ return { stop };
155
+ }
156
+ catch (error) {
157
+ await cleanupWatchFiles(paths, ownerId, releaseLock).catch(() => { });
158
+ throw error;
159
+ }
160
+ }
161
+ async function acquireWatchLock(paths, deps, stderr, processStartTime) {
162
+ try {
163
+ return await acquireFileLock(paths.mutexPath, {
164
+ pid: process.pid,
165
+ processStartTime,
166
+ isProcessAlive: deps.isProcessAlive,
167
+ readProcessStartTime: deps.readProcessStartTime,
168
+ });
169
+ }
170
+ catch (err) {
171
+ if (!(err instanceof FileLockBusyError))
172
+ throw err;
173
+ }
174
+ const owner = await readOwnerFile(paths.ownerPath);
175
+ if (!owner) {
176
+ stderr.write(`[auto-sync] Watch mutex is held but owner metadata is not ready or invalid. Confirm no watch process is running, then remove ${paths.mutexPath}.\n`);
177
+ return null;
178
+ }
179
+ if (!deps.isProcessAlive(owner.pid)) {
180
+ stderr.write(`[auto-sync] Watch mutex remains after owner pid ${owner.pid} exited. Confirm no watch process is running, then remove ${paths.mutexPath}.\n`);
181
+ return null;
182
+ }
183
+ const reason = getWatchProcessIdentityError(owner, deps);
184
+ if (reason) {
185
+ stderr.write(`[auto-sync] Refusing to trust existing watch pid ${owner.pid}; ${reason}.\n`);
186
+ return null;
187
+ }
188
+ stderr.write(`[auto-sync] Watch is already running with pid ${owner.pid}.\n`);
189
+ return null;
190
+ }
191
+ export async function stopAutoSyncWatch(options = {}) {
192
+ const stderr = options.stderr ?? process.stderr;
193
+ const paths = options.paths ?? getAutoSyncWatchPaths();
194
+ const deps = resolveWatchDeps(options.deps);
195
+ const timeoutMs = options.timeoutMs ?? 10_000;
196
+ const pollMs = options.pollMs ?? 100;
197
+ const pid = await readPid(paths.pidPath);
198
+ if (!pid) {
199
+ const owner = await readOwnerFile(paths.ownerPath);
200
+ if (owner && deps.isProcessAlive(owner.pid)) {
201
+ stderr.write(`[auto-sync] Watch appears to be starting with pid ${owner.pid}; pid file is not ready.\n`);
202
+ return 'refused';
203
+ }
204
+ if (owner || (await fileExists(paths.mutexPath))) {
205
+ stderr.write(`[auto-sync] Watch ownership is stale or incomplete. Confirm no watch process is running, then remove ${paths.mutexPath}.\n`);
206
+ return 'refused';
207
+ }
208
+ stderr.write('[auto-sync] Watch is not running.\n');
209
+ return 'not_running';
210
+ }
211
+ if (!deps.isProcessAlive(pid)) {
212
+ stderr.write(`[auto-sync] Watch pid ${pid} is stale. Confirm no watch process is running, then remove ${paths.mutexPath}.\n`);
213
+ return 'refused';
214
+ }
215
+ const owner = await readVerifiedWatchOwner(paths, pid, deps);
216
+ if (owner.ok === false) {
217
+ stderr.write(`[auto-sync] Refusing to stop pid ${pid}; ${owner.reason}.\n`);
218
+ return 'refused';
219
+ }
220
+ const currentPid = await readPid(paths.pidPath);
221
+ const currentOwner = await readVerifiedWatchOwner(paths, pid, deps);
222
+ if (currentPid !== pid ||
223
+ currentOwner.ok === false ||
224
+ currentOwner.owner.ownerId !== owner.owner.ownerId) {
225
+ stderr.write(`[auto-sync] Refusing to stop pid ${pid}; watch ownership changed.\n`);
226
+ return 'refused';
227
+ }
228
+ await writeAtomicText(stopRequestPath(paths, owner.owner.ownerId), `${JSON.stringify({
229
+ pid,
230
+ ownerId: owner.owner.ownerId,
231
+ processStartTime: owner.owner.processStartTime,
232
+ requestedAt: new Date().toISOString(),
233
+ })}\n`);
234
+ stderr.write(`[auto-sync] Stop requested for watch pid ${pid}.\n`);
235
+ const stopped = await waitForProcessExit(pid, {
236
+ deps,
237
+ timeoutMs,
238
+ pollMs,
239
+ processStartTime: owner.owner.processStartTime,
240
+ });
241
+ if (!stopped) {
242
+ stderr.write(`[auto-sync] Watch pid ${pid} did not exit within ${timeoutMs}ms.\n`);
243
+ return 'timeout';
244
+ }
245
+ return 'stopped';
246
+ }
247
+ export async function readAutoSyncWatchStatus(paths = getAutoSyncWatchPaths(), deps = {}) {
248
+ const resolvedDeps = resolveWatchDeps(deps);
249
+ const pid = await readPid(paths.pidPath);
250
+ const stored = await readStatusFile(paths.statusPath);
251
+ const updatedAt = stored?.updatedAt ?? new Date().toISOString();
252
+ if (pid && !resolvedDeps.isProcessAlive(pid)) {
253
+ return {
254
+ ...stored,
255
+ state: 'stale',
256
+ pid,
257
+ message: 'pid file exists but process is not running',
258
+ updatedAt,
259
+ };
260
+ }
261
+ if (pid) {
262
+ const owner = await readVerifiedWatchOwner(paths, pid, resolvedDeps);
263
+ if (owner.ok === false) {
264
+ return {
265
+ ...stored,
266
+ state: 'error',
267
+ pid,
268
+ message: owner.reason,
269
+ updatedAt,
270
+ };
271
+ }
272
+ if (stored?.state === 'error') {
273
+ return {
274
+ ...stored,
275
+ pid,
276
+ ownerId: owner.owner.ownerId,
277
+ updatedAt,
278
+ };
279
+ }
280
+ return {
281
+ ...stored,
282
+ state: stored?.state === 'cancelling' || stored?.state === 'stopping' ? stored.state : 'running',
283
+ pid,
284
+ ownerId: owner.owner.ownerId,
285
+ updatedAt,
286
+ };
287
+ }
288
+ return stored ?? { state: 'stopped', updatedAt };
289
+ }
290
+ function isSafeWatchOwnerId(ownerId) {
291
+ return (ownerId === path.basename(ownerId) &&
292
+ !ownerId.includes('..') &&
293
+ !ownerId.includes('/') &&
294
+ !ownerId.includes('\\'));
295
+ }
296
+ async function readOwnerFile(ownerPath) {
297
+ try {
298
+ const raw = await fs.readFile(ownerPath, 'utf-8');
299
+ const parsed = JSON.parse(raw);
300
+ if (parsed &&
301
+ typeof parsed === 'object' &&
302
+ Number.isInteger(parsed.pid) &&
303
+ parsed.pid > 0 &&
304
+ typeof parsed.ownerId === 'string' &&
305
+ parsed.ownerId &&
306
+ isSafeWatchOwnerId(parsed.ownerId) &&
307
+ typeof parsed.processStartTime === 'string' &&
308
+ parsed.processStartTime) {
309
+ return parsed;
310
+ }
311
+ return undefined;
312
+ }
313
+ catch (err) {
314
+ if (err.code === 'ENOENT')
315
+ return undefined;
316
+ return undefined;
317
+ }
318
+ }
319
+ async function readVerifiedWatchOwner(paths, pid, deps) {
320
+ const [status, owner] = await Promise.all([
321
+ readStatusFile(paths.statusPath),
322
+ readOwnerFile(paths.ownerPath),
323
+ ]);
324
+ if (!owner)
325
+ return { ok: false, reason: 'watch owner is missing or invalid' };
326
+ if (!status)
327
+ return { ok: false, reason: 'watch status is missing or invalid' };
328
+ if (owner.pid !== pid)
329
+ return { ok: false, reason: 'watch owner pid does not match pid file' };
330
+ if (status.pid !== pid)
331
+ return { ok: false, reason: 'watch status pid does not match pid file' };
332
+ if (!status.ownerId || status.ownerId !== owner.ownerId) {
333
+ return { ok: false, reason: 'watch status owner does not match watch owner' };
334
+ }
335
+ const identityError = getWatchProcessIdentityError(owner, deps);
336
+ if (identityError)
337
+ return { ok: false, reason: identityError };
338
+ return { ok: true, owner };
339
+ }
340
+ function getWatchProcessIdentityError(owner, deps) {
341
+ const processStartTime = deps.readProcessStartTime(owner.pid);
342
+ if (!processStartTime)
343
+ return 'unable to verify process start time';
344
+ if (processStartTime !== owner.processStartTime)
345
+ return 'pid belongs to a different process';
346
+ const command = deps.readProcessCommand(owner.pid);
347
+ if (!command)
348
+ return 'unable to verify process command';
349
+ if (!/(?:^|\s)(?:watch|auto-sync)(?:\s|$)/.test(command) ||
350
+ !/(?:gitnexus|[\\/]cli[\\/]index\.(?:ts|[cm]?js))/.test(command)) {
351
+ return 'pid command is not a GitNexus auto-sync process';
352
+ }
353
+ return undefined;
354
+ }
355
+ async function waitForProcessExit(pid, options) {
356
+ // A bare liveness poll cannot tell "still running" from "exited, and the OS
357
+ // handed the pid to something else" — so a reused pid would keep us waiting
358
+ // on an unrelated process and then report the watch stopped once THAT exits.
359
+ // The start time identifies the process behind the number.
360
+ const isOriginalProcessAlive = () => {
361
+ if (!options.deps.isProcessAlive(pid))
362
+ return false;
363
+ if (!options.processStartTime)
364
+ return true;
365
+ const startTime = options.deps.readProcessStartTime(pid);
366
+ return startTime === undefined || startTime === options.processStartTime;
367
+ };
368
+ const deadline = Date.now() + options.timeoutMs;
369
+ while (Date.now() < deadline) {
370
+ if (!isOriginalProcessAlive())
371
+ return true;
372
+ await options.deps.sleep(options.pollMs);
373
+ }
374
+ return !isOriginalProcessAlive();
375
+ }
376
+ async function readPid(pidPath) {
377
+ try {
378
+ const raw = await fs.readFile(pidPath, 'utf-8');
379
+ const pid = Number(raw.trim());
380
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
381
+ }
382
+ catch (err) {
383
+ if (err.code === 'ENOENT')
384
+ return undefined;
385
+ throw err;
386
+ }
387
+ }
388
+ async function readStatusFile(statusPath) {
389
+ try {
390
+ const parsed = JSON.parse(await fs.readFile(statusPath, 'utf-8'));
391
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
392
+ }
393
+ catch (err) {
394
+ if (err.code === 'ENOENT')
395
+ return undefined;
396
+ return {
397
+ state: 'error',
398
+ message: `unable to read status file: ${err.message}`,
399
+ updatedAt: new Date().toISOString(),
400
+ };
401
+ }
402
+ }
403
+ function stopRequestPath(paths, ownerId) {
404
+ if (!isSafeWatchOwnerId(ownerId)) {
405
+ throw new Error('watch ownerId is not a safe filename component');
406
+ }
407
+ return path.join(path.dirname(paths.pidPath), `watch.stop.${ownerId}.json`);
408
+ }
409
+ async function readStopRequest(filePath) {
410
+ try {
411
+ const parsed = JSON.parse(await fs.readFile(filePath, 'utf-8'));
412
+ if (parsed &&
413
+ typeof parsed === 'object' &&
414
+ Number.isInteger(parsed.pid) &&
415
+ parsed.pid > 0 &&
416
+ typeof parsed.ownerId === 'string' &&
417
+ parsed.ownerId &&
418
+ typeof parsed.processStartTime === 'string' &&
419
+ parsed.processStartTime &&
420
+ typeof parsed.requestedAt === 'string' &&
421
+ parsed.requestedAt) {
422
+ return parsed;
423
+ }
424
+ }
425
+ catch (error) {
426
+ if (error.code !== 'ENOENT')
427
+ return undefined;
428
+ }
429
+ return undefined;
430
+ }
431
+ async function writeWatchStatus(paths, record) {
432
+ await fs.mkdir(path.dirname(paths.statusPath), { recursive: true });
433
+ const tmpPath = `${paths.statusPath}.tmp.${process.pid}.${Date.now()}`;
434
+ await fs.writeFile(tmpPath, `${JSON.stringify(record, null, 2)}\n`, 'utf-8');
435
+ await fs.rename(tmpPath, paths.statusPath);
436
+ }
437
+ async function writeWatchOwner(paths, record) {
438
+ await writeAtomicText(paths.ownerPath, `${JSON.stringify(record, null, 2)}\n`);
439
+ }
440
+ async function cleanupWatchFiles(paths, ownerId, releaseLock) {
441
+ try {
442
+ const owner = await readOwnerFile(paths.ownerPath);
443
+ if (owner?.ownerId === ownerId) {
444
+ if ((await readPid(paths.pidPath)) === owner.pid)
445
+ await removeIfExists(paths.pidPath);
446
+ if ((await readOwnerFile(paths.ownerPath))?.ownerId === ownerId) {
447
+ await removeIfExists(paths.ownerPath);
448
+ }
449
+ await removeIfExists(stopRequestPath(paths, ownerId));
450
+ }
451
+ }
452
+ finally {
453
+ await releaseLock();
454
+ }
455
+ }
456
+ async function writeAtomicText(filePath, content) {
457
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
458
+ const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
459
+ await fs.writeFile(tmpPath, content, 'utf-8');
460
+ await fs.rename(tmpPath, filePath);
461
+ }
462
+ async function removeIfExists(filePath) {
463
+ await fs.rm(filePath, { force: true });
464
+ }
465
+ async function fileExists(filePath) {
466
+ return fs.access(filePath).then(() => true, () => false);
467
+ }
468
+ function resolveWatchDeps(deps = {}) {
469
+ return {
470
+ isProcessAlive: deps.isProcessAlive ?? isProcessAlive,
471
+ readProcessCommand: deps.readProcessCommand ??
472
+ ((pid) => {
473
+ try {
474
+ const command = process.platform === 'win32'
475
+ ? execFileSync('powershell.exe', [
476
+ '-NoProfile',
477
+ '-NonInteractive',
478
+ '-Command',
479
+ `(Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\").CommandLine`,
480
+ ], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
481
+ : execFileSync('ps', ['-p', String(pid), '-o', 'command='], {
482
+ encoding: 'utf-8',
483
+ stdio: ['ignore', 'pipe', 'ignore'],
484
+ }).trim();
485
+ return command || undefined;
486
+ }
487
+ catch {
488
+ return undefined;
489
+ }
490
+ }),
491
+ readProcessStartTime: deps.readProcessStartTime ?? readProcessStartTime,
492
+ sleep: deps.sleep ??
493
+ ((ms) => new Promise((resolve) => {
494
+ setTimeout(resolve, ms);
495
+ })),
496
+ };
497
+ }
@@ -0,0 +1,37 @@
1
+ export type AutoSyncAnalyzeStatus = 'success' | 'failed' | 'skipped' | 'threshold_skipped';
2
+ export interface AutoSyncCommitStateEntry {
3
+ codeCommitId: string;
4
+ analyzedCommitId?: string;
5
+ lastAnalyzeStatus?: AutoSyncAnalyzeStatus;
6
+ analyzeConsecutiveFailures?: number;
7
+ lastAnalyzeError?: string;
8
+ groupSyncPending?: boolean;
9
+ lastSyncTime: string;
10
+ }
11
+ export type AutoSyncCommitState = Record<string, AutoSyncCommitStateEntry>;
12
+ export declare function getAutoSyncWatchDir(gitnexusDir?: string): string;
13
+ export declare function getAutoSyncMutexPath(gitnexusDir?: string): string;
14
+ export declare function getAutoSyncStatePath(gitnexusDir?: string): string;
15
+ export declare function getProjectCommitInfoPath(gitnexusDir?: string): string;
16
+ export declare function resetAutoSyncState(gitnexusDir?: string): Promise<boolean>;
17
+ export declare function buildStateKey(repoPath: string, branch: string): string;
18
+ export declare function shouldAnalyzeCommit(input: {
19
+ currentCommit: string;
20
+ previousAnalyzedCommit?: string;
21
+ previousStatus?: AutoSyncAnalyzeStatus;
22
+ }): boolean;
23
+ export declare function loadAutoSyncState(statePath?: string): Promise<AutoSyncCommitState>;
24
+ export declare function saveAutoSyncState(state: AutoSyncCommitState, statePath?: string): Promise<void>;
25
+ export declare function writeProjectCommitInfo(entries: ProjectCommitInfoEntry[], infoPath?: string): Promise<void>;
26
+ export interface ProjectCommitInfoEntry {
27
+ remoteUrl: string;
28
+ localPath: string;
29
+ branch?: string;
30
+ codeCommitId?: string;
31
+ analyzedCommitId?: string;
32
+ status: AutoSyncAnalyzeStatus | 'sync_failed' | 'branch_skipped' | 'branch_unavailable' | 'sync_timeout';
33
+ analyzeConsecutiveFailures?: number;
34
+ analyzeFailureThreshold?: number;
35
+ lastAnalyzeError?: string;
36
+ lastSyncTime: string;
37
+ }
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { acquireFileLock, FileLockBusyError } from '../../storage/file-lock.js';
4
+ import { getGlobalDir } from '../../storage/repo-manager.js';
5
+ export function getAutoSyncWatchDir(gitnexusDir = getGlobalDir()) {
6
+ return path.join(gitnexusDir, 'watch');
7
+ }
8
+ export function getAutoSyncMutexPath(gitnexusDir = getGlobalDir()) {
9
+ return path.join(getAutoSyncWatchDir(gitnexusDir), 'watch.mutex');
10
+ }
11
+ export function getAutoSyncStatePath(gitnexusDir = getGlobalDir()) {
12
+ return path.join(getAutoSyncWatchDir(gitnexusDir), 'auto-sync-state.json');
13
+ }
14
+ export function getProjectCommitInfoPath(gitnexusDir = getGlobalDir()) {
15
+ return path.join(getAutoSyncWatchDir(gitnexusDir), 'project_commit_info.txt');
16
+ }
17
+ export async function resetAutoSyncState(gitnexusDir = getGlobalDir()) {
18
+ let releaseLock;
19
+ try {
20
+ releaseLock = await acquireFileLock(getAutoSyncMutexPath(gitnexusDir));
21
+ }
22
+ catch (error) {
23
+ if (error instanceof FileLockBusyError)
24
+ return false;
25
+ throw error;
26
+ }
27
+ try {
28
+ await Promise.all([
29
+ fs.rm(getAutoSyncStatePath(gitnexusDir), { force: true }),
30
+ fs.rm(getProjectCommitInfoPath(gitnexusDir), { force: true }),
31
+ ]);
32
+ return true;
33
+ }
34
+ finally {
35
+ await releaseLock();
36
+ }
37
+ }
38
+ export function buildStateKey(repoPath, branch) {
39
+ return `${path.resolve(repoPath)}|${branch}`;
40
+ }
41
+ export function shouldAnalyzeCommit(input) {
42
+ if (!input.currentCommit)
43
+ return false;
44
+ if (input.previousStatus === 'failed')
45
+ return true;
46
+ return input.currentCommit !== input.previousAnalyzedCommit;
47
+ }
48
+ export async function loadAutoSyncState(statePath = getAutoSyncStatePath()) {
49
+ try {
50
+ const raw = await fs.readFile(statePath, 'utf-8');
51
+ const parsed = JSON.parse(raw);
52
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
53
+ return {};
54
+ return Object.fromEntries(Object.entries(parsed).filter((entry) => isAutoSyncCommitStateEntry(entry[1])));
55
+ }
56
+ catch (err) {
57
+ if (err.code === 'ENOENT')
58
+ return {};
59
+ // Corrupt JSON is genuinely unrecoverable, so rebuilding is the only move.
60
+ // An unreadable file (EACCES, EIO, EISDIR) is different: the state is
61
+ // probably intact, and returning {} here would make the tick overwrite it,
62
+ // losing every repo's analyzed commit and failure count.
63
+ if (!(err instanceof SyntaxError))
64
+ throw err;
65
+ process.stderr.write(`[auto-sync] Ignoring corrupt state file: ${statePath}. State will be rebuilt.\n`);
66
+ return {};
67
+ }
68
+ }
69
+ function isAutoSyncCommitStateEntry(value) {
70
+ if (!value || typeof value !== 'object' || Array.isArray(value))
71
+ return false;
72
+ const entry = value;
73
+ return (typeof entry.codeCommitId === 'string' &&
74
+ typeof entry.lastSyncTime === 'string' &&
75
+ (entry.analyzedCommitId === undefined || typeof entry.analyzedCommitId === 'string') &&
76
+ (entry.lastAnalyzeStatus === undefined ||
77
+ entry.lastAnalyzeStatus === 'success' ||
78
+ entry.lastAnalyzeStatus === 'failed' ||
79
+ entry.lastAnalyzeStatus === 'skipped' ||
80
+ entry.lastAnalyzeStatus === 'threshold_skipped') &&
81
+ (entry.analyzeConsecutiveFailures === undefined ||
82
+ (typeof entry.analyzeConsecutiveFailures === 'number' &&
83
+ Number.isInteger(entry.analyzeConsecutiveFailures) &&
84
+ entry.analyzeConsecutiveFailures >= 0)) &&
85
+ (entry.lastAnalyzeError === undefined || typeof entry.lastAnalyzeError === 'string') &&
86
+ (entry.groupSyncPending === undefined || typeof entry.groupSyncPending === 'boolean'));
87
+ }
88
+ export async function saveAutoSyncState(state, statePath = getAutoSyncStatePath()) {
89
+ await fs.mkdir(path.dirname(statePath), { recursive: true });
90
+ const tmpPath = `${statePath}.tmp.${process.pid}.${Date.now()}`;
91
+ await fs.writeFile(tmpPath, `${JSON.stringify(state, null, 2)}\n`, 'utf-8');
92
+ await fs.rename(tmpPath, statePath);
93
+ }
94
+ export async function writeProjectCommitInfo(entries, infoPath = getProjectCommitInfoPath()) {
95
+ await fs.mkdir(path.dirname(infoPath), { recursive: true });
96
+ const lines = [
97
+ '# GitNexus auto-sync project commit info',
98
+ `updated_at: ${new Date().toISOString()}`,
99
+ '',
100
+ ...entries.flatMap((entry) => [
101
+ `remote: ${entry.remoteUrl}`,
102
+ `local_path: ${entry.localPath}`,
103
+ `branch: ${entry.branch ?? ''}`,
104
+ `code_commit: ${entry.codeCommitId ?? ''}`,
105
+ `analyzed_commit: ${entry.analyzedCommitId ?? ''}`,
106
+ `status: ${entry.status}`,
107
+ `analyze_consecutive_failures: ${entry.analyzeConsecutiveFailures ?? 0}`,
108
+ ...(entry.analyzeFailureThreshold === undefined
109
+ ? []
110
+ : [`analyze_failure_threshold: ${entry.analyzeFailureThreshold}`]),
111
+ ...(entry.lastAnalyzeError ? [`last_analyze_error: ${entry.lastAnalyzeError}`] : []),
112
+ `last_sync_time: ${entry.lastSyncTime}`,
113
+ '',
114
+ ]),
115
+ ];
116
+ const tmpPath = `${infoPath}.tmp.${process.pid}.${Date.now()}`;
117
+ await fs.writeFile(tmpPath, `${lines.join('\n')}\n`, 'utf-8');
118
+ await fs.rename(tmpPath, infoPath);
119
+ }
@@ -48,7 +48,14 @@ export function parseGroupConfig(yamlContent) {
48
48
  if (!raw.repos || typeof raw.repos !== 'object' || Array.isArray(raw.repos)) {
49
49
  throw new Error('repos is required in group.yaml (must be a mapping)');
50
50
  }
51
- const repos = raw.repos;
51
+ const reposRaw = raw.repos;
52
+ const repos = {};
53
+ for (const [memberPath, registryName] of Object.entries(reposRaw)) {
54
+ if (typeof registryName !== 'string' || registryName.trim() === '') {
55
+ throw new Error(`repos["${memberPath}"] must be a non-empty registry name string, not ${typeof registryName}`);
56
+ }
57
+ repos[memberPath] = registryName.trim();
58
+ }
52
59
  const repoPaths = new Set(Object.keys(repos));
53
60
  const rawLinks = raw.links || [];
54
61
  const links = rawLinks.map((l, i) => {
@@ -62,6 +62,9 @@ export declare function validateGroupImpactParams(params: Record<string, unknown
62
62
  name: string;
63
63
  repoPath: string;
64
64
  target: string;
65
+ target_uid?: string;
66
+ file_path?: string;
67
+ kind?: string;
65
68
  direction: 'upstream' | 'downstream';
66
69
  maxDepth: number;
67
70
  crossDepth: number;