gitnexus 1.6.3-rc.8 → 1.6.3

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 (285) hide show
  1. package/README.md +21 -5
  2. package/dist/_shared/graph/types.d.ts +16 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +20 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +11 -0
  7. package/dist/_shared/index.js.map +1 -1
  8. package/dist/_shared/scope-resolution/def-index.js +2 -2
  9. package/dist/_shared/scope-resolution/def-index.js.map +1 -1
  10. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
  11. package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
  12. package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
  13. package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
  14. package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
  15. package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
  17. package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
  18. package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
  19. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
  20. package/dist/_shared/scope-resolution/parsed-file.js +54 -0
  21. package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
  22. package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
  23. package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/position-index.js +2 -2
  25. package/dist/_shared/scope-resolution/position-index.js.map +1 -1
  26. package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
  27. package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
  28. package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
  29. package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
  30. package/dist/_shared/scope-resolution/reference-site.js +24 -0
  31. package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
  32. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
  33. package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
  34. package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
  35. package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
  36. package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
  37. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
  38. package/dist/_shared/scope-resolution/registries/context.js +44 -0
  39. package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
  40. package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
  41. package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
  42. package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
  43. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
  44. package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
  45. package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
  46. package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
  47. package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
  48. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
  49. package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
  50. package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
  51. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
  52. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
  53. package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
  54. package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
  55. package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
  56. package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
  57. package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
  58. package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
  59. package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
  60. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
  61. package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
  62. package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
  63. package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
  64. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  65. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  66. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  67. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  68. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  69. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  70. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  71. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  72. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  73. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  74. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  75. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  76. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  77. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  78. package/dist/cli/ai-context.js +35 -4
  79. package/dist/cli/analyze.d.ts +27 -0
  80. package/dist/cli/analyze.js +31 -1
  81. package/dist/cli/clean.js +19 -1
  82. package/dist/cli/group.js +73 -0
  83. package/dist/cli/index-repo.js +8 -1
  84. package/dist/cli/index.js +26 -1
  85. package/dist/cli/list.js +11 -1
  86. package/dist/cli/remove.d.ts +30 -0
  87. package/dist/cli/remove.js +99 -0
  88. package/dist/cli/setup.js +185 -57
  89. package/dist/cli/tool.d.ts +5 -0
  90. package/dist/cli/tool.js +42 -0
  91. package/dist/config/ignore-service.d.ts +9 -0
  92. package/dist/config/ignore-service.js +80 -13
  93. package/dist/core/embedding-mode.d.ts +30 -0
  94. package/dist/core/embedding-mode.js +30 -0
  95. package/dist/core/embeddings/ast-utils.js +22 -22
  96. package/dist/core/embeddings/chunker.js +30 -25
  97. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  98. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  99. package/dist/core/embeddings/text-generator.d.ts +1 -1
  100. package/dist/core/embeddings/text-generator.js +33 -24
  101. package/dist/core/embeddings/types.d.ts +43 -1
  102. package/dist/core/embeddings/types.js +101 -29
  103. package/dist/core/git-staleness.d.ts +18 -0
  104. package/dist/core/git-staleness.js +108 -0
  105. package/dist/core/graph/graph.js +115 -20
  106. package/dist/core/graph/types.d.ts +12 -1
  107. package/dist/core/group/config-parser.d.ts +4 -0
  108. package/dist/core/group/config-parser.js +18 -1
  109. package/dist/core/group/cross-impact.d.ts +41 -0
  110. package/dist/core/group/cross-impact.js +441 -0
  111. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  112. package/dist/core/group/group-path-utils.d.ts +17 -0
  113. package/dist/core/group/group-path-utils.js +40 -0
  114. package/dist/core/group/resolve-at-member.d.ts +10 -0
  115. package/dist/core/group/resolve-at-member.js +31 -0
  116. package/dist/core/group/service.d.ts +9 -0
  117. package/dist/core/group/service.js +259 -25
  118. package/dist/core/group/types.d.ts +30 -0
  119. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  120. package/dist/core/ingestion/ast-cache.js +14 -2
  121. package/dist/core/ingestion/call-processor.js +9 -0
  122. package/dist/core/ingestion/emit-references.d.ts +88 -0
  123. package/dist/core/ingestion/emit-references.js +229 -0
  124. package/dist/core/ingestion/filesystem-walker.js +6 -4
  125. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  126. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  127. package/dist/core/ingestion/framework-detection.js +6 -2
  128. package/dist/core/ingestion/import-processor.js +4 -0
  129. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  130. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  131. package/dist/core/ingestion/import-target-adapter.js +95 -0
  132. package/dist/core/ingestion/language-provider.d.ts +36 -33
  133. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  134. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  135. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  136. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  137. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  138. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  139. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  140. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  141. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  142. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  143. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  144. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  145. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  146. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  147. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  148. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  149. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  150. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  151. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  152. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  153. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  154. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  155. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  156. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  157. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  158. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  159. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  160. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  161. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  162. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  163. package/dist/core/ingestion/languages/csharp.js +14 -0
  164. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  165. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  166. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  167. package/dist/core/ingestion/languages/python/arity.js +38 -0
  168. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  169. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  170. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  171. package/dist/core/ingestion/languages/python/captures.js +106 -0
  172. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  173. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  174. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  175. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  176. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  177. package/dist/core/ingestion/languages/python/index.js +80 -0
  178. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  179. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  180. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  181. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  182. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  183. package/dist/core/ingestion/languages/python/query.js +267 -0
  184. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  185. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  186. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  187. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  188. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  189. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  190. package/dist/core/ingestion/languages/python.js +14 -0
  191. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  192. package/dist/core/ingestion/model/method-registry.js +4 -0
  193. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  194. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  195. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  196. package/dist/core/ingestion/model/semantic-model.js +55 -0
  197. package/dist/core/ingestion/mro-processor.js +38 -22
  198. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  199. package/dist/core/ingestion/parsing-processor.js +45 -11
  200. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  201. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  202. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  203. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  204. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  205. package/dist/core/ingestion/pipeline.js +2 -1
  206. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  207. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  208. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  209. package/dist/core/ingestion/resolve-references.js +175 -0
  210. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  211. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  212. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  213. package/dist/core/ingestion/scope-extractor.js +758 -0
  214. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  215. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  216. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  217. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  218. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  219. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  220. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  221. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  222. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  223. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  224. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  225. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  226. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  227. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  228. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  229. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  230. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  231. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  232. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  233. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  234. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  235. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  236. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  237. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  238. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  239. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  240. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  241. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  242. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  243. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  244. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  245. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  246. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  247. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  248. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  249. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  250. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  251. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  252. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  253. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  254. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  255. package/dist/core/ingestion/shadow-harness.js +148 -0
  256. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  257. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  258. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  259. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  260. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  261. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  262. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  263. package/dist/core/lbug/lbug-adapter.js +58 -14
  264. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  265. package/dist/core/lbug/pool-adapter.js +24 -14
  266. package/dist/core/run-analyze.d.ts +32 -0
  267. package/dist/core/run-analyze.js +74 -19
  268. package/dist/core/search/bm25-index.d.ts +18 -0
  269. package/dist/core/search/bm25-index.js +125 -12
  270. package/dist/core/tree-sitter/parser-loader.js +6 -1
  271. package/dist/mcp/local/local-backend.d.ts +67 -3
  272. package/dist/mcp/local/local-backend.js +296 -34
  273. package/dist/mcp/resources.d.ts +31 -0
  274. package/dist/mcp/resources.js +100 -17
  275. package/dist/mcp/tools.d.ts +4 -1
  276. package/dist/mcp/tools.js +75 -54
  277. package/dist/server/api.js +6 -2
  278. package/dist/storage/git.d.ts +49 -0
  279. package/dist/storage/git.js +111 -0
  280. package/dist/storage/repo-manager.d.ts +246 -1
  281. package/dist/storage/repo-manager.js +391 -9
  282. package/package.json +7 -6
  283. package/scripts/bench-scope-resolution.ts +134 -0
  284. package/scripts/ci-list-migrated-languages.ts +24 -0
  285. package/skills/gitnexus-cli.md +1 -0
@@ -56,6 +56,7 @@ interface RepoHandle {
56
56
  lbugPath: string;
57
57
  indexedAt: string;
58
58
  lastCommit: string;
59
+ remoteUrl?: string;
59
60
  stats?: RegistryEntry['stats'];
60
61
  }
61
62
  export declare class LocalBackend {
@@ -65,6 +66,13 @@ export declare class LocalBackend {
65
66
  private reinitPromises;
66
67
  private lastStalenessCheck;
67
68
  private groupToolSvc;
69
+ /**
70
+ * One-shot stderr warnings for sibling-clone drift, keyed by
71
+ * `${repoId}|${cwdGitRoot}`. Without this guard every tool call
72
+ * from inside a sibling clone would print the same warning,
73
+ * making MCP stderr unreadable.
74
+ */
75
+ private warnedSiblingDrift;
68
76
  /**
69
77
  * Cross-repo group tools (CLI). Shares logic with MCP `group_*` handlers.
70
78
  */
@@ -110,14 +118,55 @@ export declare class LocalBackend {
110
118
  * List all registered repos with their metadata.
111
119
  * Re-reads the global registry so newly indexed repos are discovered
112
120
  * without restarting the MCP server.
121
+ *
122
+ * Each entry includes:
123
+ * - `staleness`: if the indexed clone's own HEAD has moved past
124
+ * the recorded `lastCommit` (option D in the issue's fix list).
125
+ * - `siblings`: other registered entries sharing the same
126
+ * `remoteUrl` (option B's payoff: callers can see at a glance
127
+ * that another clone of the same logical repo is registered).
128
+ * - `remoteUrl`: the canonical origin URL recorded at index time.
113
129
  */
114
130
  listRepos(): Promise<Array<{
115
131
  name: string;
116
132
  path: string;
117
133
  indexedAt: string;
118
134
  lastCommit: string;
135
+ remoteUrl?: string;
119
136
  stats?: any;
137
+ staleness?: {
138
+ commitsBehind: number;
139
+ hint?: string;
140
+ };
141
+ siblings?: Array<{
142
+ name: string;
143
+ path: string;
144
+ lastCommit: string;
145
+ }>;
120
146
  }>>;
147
+ /**
148
+ * Best-effort sibling-clone drift warning.
149
+ *
150
+ * When the resolved index has a `remoteUrl` recorded and the caller's
151
+ * `process.cwd()` is inside a *different* clone of the same repo, emit
152
+ * one stderr line per (repo, cwd) pair so the operator knows the
153
+ * graph may be stale relative to what's actually on disk under their
154
+ * cwd. Silent on path matches and on repos without a remote URL.
155
+ *
156
+ * Limitation: in MCP stdio server mode `process.cwd()` is the
157
+ * server's CWD at start time, *not* the agent client's CWD. The
158
+ * warning therefore only fires when the MCP server itself was
159
+ * launched from inside a sibling clone (typical for `npx gitnexus
160
+ * serve` from a polecat workspace). Surfacing the client's CWD
161
+ * would require a per-tool-call `cwd` parameter — out of scope for
162
+ * the current MCP contract.
163
+ *
164
+ * Pure side-effect (stderr); never affects the returned handle.
165
+ * After the first computation for a given (repo, cwd) pair the
166
+ * result is cached so subsequent `resolveRepo()` calls don't
167
+ * re-shell-out to git.
168
+ */
169
+ private maybeWarnSiblingDrift;
121
170
  callTool(method: string, params: any): Promise<any>;
122
171
  /**
123
172
  * Query tool — process-grouped search.
@@ -236,11 +285,26 @@ export declare class LocalBackend {
236
285
  includeTests: boolean;
237
286
  }): Promise<any | null>;
238
287
  private handleGroupTool;
288
+ /**
289
+ * Dispatch impact/query/context when `repo` is `@groupName` or `@groupName/memberPath`
290
+ * (group mode — not the global indexed-repo `repo` parameter).
291
+ */
292
+ private callToolAtGroupRepo;
239
293
  private groupList;
240
294
  private groupSync;
241
- private groupContracts;
242
- private groupQuery;
243
- private groupStatus;
295
+ /**
296
+ * MCP resource body for `gitnexus://group/{name}/contracts` (Issue #794).
297
+ */
298
+ readGroupContractsResource(groupName: string, filter: {
299
+ type?: string;
300
+ repo?: string;
301
+ unmatchedOnly?: boolean;
302
+ }): Promise<string>;
303
+ /**
304
+ * MCP resource body for `gitnexus://group/{name}/status` (Issue #794).
305
+ */
306
+ readGroupStatusResource(groupName: string): Promise<string>;
307
+ private static formatGroupResourcePayload;
244
308
  /**
245
309
  * Fetch Route nodes with their consumers in a single query.
246
310
  * Shared by routeMap and shapeCheck to avoid N+1 query patterns.
@@ -16,9 +16,11 @@ export { isWriteQuery };
16
16
  import { parseDiffHunks } from '../../storage/git.js';
17
17
  import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
18
18
  import { GroupService } from '../../core/group/service.js';
19
+ import { resolveAtGroupMemberRepoPath } from '../../core/group/resolve-at-member.js';
19
20
  import { collectBestChunks } from '../../core/embeddings/types.js';
20
21
  import { EMBEDDING_TABLE_NAME, EMBEDDING_INDEX_NAME } from '../../core/lbug/schema.js';
21
22
  import { PhaseTimer } from '../../core/search/phase-timer.js';
23
+ import { checkStaleness, checkCwdMatch } from '../../core/git-staleness.js';
22
24
  // AI context generation is CLI-only (gitnexus analyze)
23
25
  // import { generateAIContextFiles } from '../../cli/ai-context.js';
24
26
  /**
@@ -161,6 +163,13 @@ export class LocalBackend {
161
163
  reinitPromises = new Map();
162
164
  lastStalenessCheck = new Map();
163
165
  groupToolSvc = null;
166
+ /**
167
+ * One-shot stderr warnings for sibling-clone drift, keyed by
168
+ * `${repoId}|${cwdGitRoot}`. Without this guard every tool call
169
+ * from inside a sibling clone would print the same warning,
170
+ * making MCP stderr unreadable.
171
+ */
172
+ warnedSiblingDrift = new Set();
164
173
  /**
165
174
  * Cross-repo group tools (CLI). Shares logic with MCP `group_*` handlers.
166
175
  */
@@ -171,6 +180,7 @@ export class LocalBackend {
171
180
  impact: (r, p) => this.impact(r, p),
172
181
  query: (r, p) => this.query(r, p),
173
182
  impactByUid: (id, uid, d, o) => this.impactByUid(id, uid, d, o),
183
+ context: (r, p) => this.context(r, p),
174
184
  };
175
185
  this.groupToolSvc = new GroupService(port);
176
186
  }
@@ -216,6 +226,7 @@ export class LocalBackend {
216
226
  lbugPath,
217
227
  indexedAt: entry.indexedAt,
218
228
  lastCommit: entry.lastCommit,
229
+ remoteUrl: entry.remoteUrl,
219
230
  stats: entry.stats,
220
231
  };
221
232
  this.repos.set(id, handle);
@@ -268,23 +279,43 @@ export class LocalBackend {
268
279
  */
269
280
  async resolveRepo(repoParam) {
270
281
  const result = this.resolveRepoFromCache(repoParam);
271
- if (result)
282
+ if (result) {
283
+ // Issue: silent graph drift across sibling clones.
284
+ // If the caller's cwd lives in a *different* on-disk clone of
285
+ // the same repo (matched by `remoteUrl`), warn once per
286
+ // (repo, cwd) pair on stderr. We do not fail or refuse to
287
+ // serve — the index is still the best answer we have — but
288
+ // the operator/agent has to know the answer may be stale.
289
+ this.maybeWarnSiblingDrift(result).catch(() => {
290
+ /* best-effort; never throw from resolveRepo */
291
+ });
272
292
  return result;
293
+ }
273
294
  // Miss — refresh registry and try once more
274
295
  await this.refreshRepos();
275
296
  const retried = this.resolveRepoFromCache(repoParam);
276
- if (retried)
297
+ if (retried) {
298
+ this.maybeWarnSiblingDrift(retried).catch(() => { });
277
299
  return retried;
300
+ }
278
301
  // Still no match — throw with helpful message
279
302
  if (this.repos.size === 0) {
280
303
  throw new Error('No indexed repositories. Run: gitnexus analyze');
281
304
  }
305
+ // Build a disambiguated "Available: …" list (#829). When two handles
306
+ // share a name, annotate each colliding label with its path so the
307
+ // caller can actually pick the right one. Single-name entries render
308
+ // identically to pre-#829 output.
309
+ const nameCounts = new Map();
310
+ for (const h of this.repos.values()) {
311
+ const key = h.name.toLowerCase();
312
+ nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);
313
+ }
314
+ const labels = [...this.repos.values()].map((h) => (nameCounts.get(h.name.toLowerCase()) ?? 0) > 1 ? `${h.name} (${h.repoPath})` : h.name);
282
315
  if (repoParam) {
283
- const names = [...this.repos.values()].map((h) => h.name);
284
- throw new Error(`Repository "${repoParam}" not found. Available: ${names.join(', ')}`);
316
+ throw new Error(`Repository "${repoParam}" not found. Available: ${labels.join(', ')}`);
285
317
  }
286
- const names = [...this.repos.values()].map((h) => h.name);
287
- throw new Error(`Multiple repositories indexed. Specify which one with the "repo" parameter. Available: ${names.join(', ')}`);
318
+ throw new Error(`Multiple repositories indexed. Specify which one with the "repo" parameter. Available: ${labels.join(', ')}`);
288
319
  }
289
320
  /**
290
321
  * Try to resolve a repo from the in-memory cache. Returns null on miss.
@@ -396,16 +427,113 @@ export class LocalBackend {
396
427
  * List all registered repos with their metadata.
397
428
  * Re-reads the global registry so newly indexed repos are discovered
398
429
  * without restarting the MCP server.
430
+ *
431
+ * Each entry includes:
432
+ * - `staleness`: if the indexed clone's own HEAD has moved past
433
+ * the recorded `lastCommit` (option D in the issue's fix list).
434
+ * - `siblings`: other registered entries sharing the same
435
+ * `remoteUrl` (option B's payoff: callers can see at a glance
436
+ * that another clone of the same logical repo is registered).
437
+ * - `remoteUrl`: the canonical origin URL recorded at index time.
399
438
  */
400
439
  async listRepos() {
401
440
  await this.refreshRepos();
402
- return [...this.repos.values()].map((h) => ({
403
- name: h.name,
404
- path: h.repoPath,
405
- indexedAt: h.indexedAt,
406
- lastCommit: h.lastCommit,
407
- stats: h.stats,
408
- }));
441
+ const handles = [...this.repos.values()];
442
+ // Pre-group registered handles by `remoteUrl` so the sibling
443
+ // lookup is O(1) per handle. We reuse the in-memory `this.repos`
444
+ // (already populated by `refreshRepos`) instead of doing a fresh
445
+ // `readRegistry()` per entry — that would be N file reads for N
446
+ // registered repos.
447
+ const isWin = process.platform === 'win32';
448
+ const norm = (p) => (isWin ? path.resolve(p).toLowerCase() : path.resolve(p));
449
+ const byRemote = new Map();
450
+ for (const h of handles) {
451
+ if (!h.remoteUrl)
452
+ continue;
453
+ const list = byRemote.get(h.remoteUrl) ?? [];
454
+ list.push(h);
455
+ byRemote.set(h.remoteUrl, list);
456
+ }
457
+ return handles.map((h) => {
458
+ const stale = checkStaleness(h.repoPath, h.lastCommit);
459
+ const selfNorm = norm(h.repoPath);
460
+ const siblings = h.remoteUrl
461
+ ? (byRemote.get(h.remoteUrl) ?? []).filter((e) => norm(e.repoPath) !== selfNorm)
462
+ : [];
463
+ return {
464
+ name: h.name,
465
+ path: h.repoPath,
466
+ indexedAt: h.indexedAt,
467
+ lastCommit: h.lastCommit,
468
+ remoteUrl: h.remoteUrl,
469
+ stats: h.stats,
470
+ staleness: stale.isStale
471
+ ? { commitsBehind: stale.commitsBehind, hint: stale.hint }
472
+ : undefined,
473
+ siblings: siblings.length > 0
474
+ ? siblings.map((s) => ({
475
+ name: s.name,
476
+ path: s.repoPath,
477
+ lastCommit: s.lastCommit,
478
+ }))
479
+ : undefined,
480
+ };
481
+ });
482
+ }
483
+ /**
484
+ * Best-effort sibling-clone drift warning.
485
+ *
486
+ * When the resolved index has a `remoteUrl` recorded and the caller's
487
+ * `process.cwd()` is inside a *different* clone of the same repo, emit
488
+ * one stderr line per (repo, cwd) pair so the operator knows the
489
+ * graph may be stale relative to what's actually on disk under their
490
+ * cwd. Silent on path matches and on repos without a remote URL.
491
+ *
492
+ * Limitation: in MCP stdio server mode `process.cwd()` is the
493
+ * server's CWD at start time, *not* the agent client's CWD. The
494
+ * warning therefore only fires when the MCP server itself was
495
+ * launched from inside a sibling clone (typical for `npx gitnexus
496
+ * serve` from a polecat workspace). Surfacing the client's CWD
497
+ * would require a per-tool-call `cwd` parameter — out of scope for
498
+ * the current MCP contract.
499
+ *
500
+ * Pure side-effect (stderr); never affects the returned handle.
501
+ * After the first computation for a given (repo, cwd) pair the
502
+ * result is cached so subsequent `resolveRepo()` calls don't
503
+ * re-shell-out to git.
504
+ */
505
+ async maybeWarnSiblingDrift(handle) {
506
+ if (!handle.remoteUrl)
507
+ return;
508
+ let cwd;
509
+ try {
510
+ cwd = process.cwd();
511
+ }
512
+ catch {
513
+ return;
514
+ }
515
+ // Early-exit cache: keyed on (repo, cwd) BEFORE any git shellout.
516
+ // After the first call for a given cwd, this short-circuits the
517
+ // up-to-four `execSync`/`execFileSync` calls inside `checkCwdMatch`
518
+ // — important for MCP-server mode where `process.cwd()` is constant
519
+ // and `resolveRepo` runs on every tool call.
520
+ const cacheKey = `${handle.id}|${cwd}`;
521
+ if (this.warnedSiblingDrift.has(cacheKey))
522
+ return;
523
+ const match = await checkCwdMatch(cwd);
524
+ if (match.match !== 'sibling-by-remote' ||
525
+ !match.entry ||
526
+ !match.cwdGitRoot ||
527
+ match.entry.path !== handle.repoPath ||
528
+ !match.hint) {
529
+ // Cache "nothing to warn about" outcomes too — `checkCwdMatch`
530
+ // is deterministic for a fixed (registry, cwd) pair, so re-running
531
+ // it yields nothing new.
532
+ this.warnedSiblingDrift.add(cacheKey);
533
+ return;
534
+ }
535
+ this.warnedSiblingDrift.add(cacheKey);
536
+ console.error(`GitNexus: ${match.hint}`);
409
537
  }
410
538
  // ─── Tool Dispatch ───────────────────────────────────────────────
411
539
  async callTool(method, params) {
@@ -415,6 +543,12 @@ export class LocalBackend {
415
543
  if (method.startsWith('group_')) {
416
544
  return this.handleGroupTool(method, params || {});
417
545
  }
546
+ const p = params && typeof params === 'object' ? params : {};
547
+ if ((method === 'impact' || method === 'query' || method === 'context') &&
548
+ typeof p.repo === 'string' &&
549
+ p.repo.startsWith('@')) {
550
+ return this.callToolAtGroupRepo(method, p);
551
+ }
418
552
  // Resolve repo from optional param (re-reads registry on miss)
419
553
  const repo = await this.resolveRepo(params?.repo);
420
554
  switch (method) {
@@ -691,12 +825,22 @@ export class LocalBackend {
691
825
  for (const bm25Result of bm25Results) {
692
826
  const fullPath = bm25Result.filePath;
693
827
  try {
694
- const symbols = await executeParameterized(repo.id, `
695
- MATCH (n)
696
- WHERE n.filePath = $filePath
697
- RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
698
- LIMIT 3
699
- `, { filePath: fullPath });
828
+ // Prefer direct nodeId lookup (exact FTS-matched nodes) over filePath fallback.
829
+ // Without this, LIMIT 3 on filePath returns arbitrary symbols rather than
830
+ // the nodes that actually scored highest in the BM25 index.
831
+ const nodeIds = bm25Result.nodeIds?.length ? bm25Result.nodeIds : null;
832
+ const symbols = nodeIds
833
+ ? await executeParameterized(repo.id, `
834
+ MATCH (n)
835
+ WHERE n.id IN $nodeIds
836
+ RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
837
+ `, { nodeIds })
838
+ : await executeParameterized(repo.id, `
839
+ MATCH (n)
840
+ WHERE n.filePath = $filePath
841
+ RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
842
+ LIMIT 3
843
+ `, { filePath: fullPath });
700
844
  if (symbols.length > 0) {
701
845
  for (const sym of symbols) {
702
846
  results.push({
@@ -2293,32 +2437,150 @@ export class LocalBackend {
2293
2437
  return this.groupList(params);
2294
2438
  case 'group_sync':
2295
2439
  return this.groupSync(params);
2296
- case 'group_contracts':
2297
- return this.groupContracts(params);
2298
- case 'group_query':
2299
- return this.groupQuery(params);
2300
- case 'group_status':
2301
- return this.groupStatus(params);
2302
2440
  default:
2303
- throw new Error(`Unknown group tool: ${method}`);
2441
+ throw new Error(`Unknown group tool: ${method}. Removed tools: use repo "@<groupName>" on impact, query, or context (optional "/<memberPath>"), or MCP resources.`);
2304
2442
  }
2305
2443
  }
2444
+ /**
2445
+ * Dispatch impact/query/context when `repo` is `@groupName` or `@groupName/memberPath`
2446
+ * (group mode — not the global indexed-repo `repo` parameter).
2447
+ */
2448
+ async callToolAtGroupRepo(method, params) {
2449
+ await this.refreshRepos();
2450
+ if (params.service !== undefined &&
2451
+ params.service !== null &&
2452
+ String(params.service).trim() === '') {
2453
+ return { error: 'service must not be an empty string' };
2454
+ }
2455
+ const raw = String(params.repo).slice(1);
2456
+ const slash = raw.indexOf('/');
2457
+ const groupName = (slash === -1 ? raw : raw.slice(0, slash)).trim();
2458
+ const memberRest = slash === -1 ? undefined : raw.slice(slash + 1).trim() || undefined;
2459
+ const resolved = await resolveAtGroupMemberRepoPath(groupName, memberRest);
2460
+ if (resolved.ok === false)
2461
+ return { error: resolved.error };
2462
+ const svc = this.getGroupService();
2463
+ if (method === 'impact') {
2464
+ const impactArgs = {
2465
+ name: groupName,
2466
+ repo: resolved.repoPath,
2467
+ target: params.target,
2468
+ direction: params.direction,
2469
+ };
2470
+ if (params.maxDepth !== undefined)
2471
+ impactArgs.maxDepth = params.maxDepth;
2472
+ if (params.crossDepth !== undefined)
2473
+ impactArgs.crossDepth = params.crossDepth;
2474
+ if (params.relationTypes !== undefined)
2475
+ impactArgs.relationTypes = params.relationTypes;
2476
+ if (params.includeTests !== undefined)
2477
+ impactArgs.includeTests = params.includeTests;
2478
+ if (params.minConfidence !== undefined)
2479
+ impactArgs.minConfidence = params.minConfidence;
2480
+ if (params.service !== undefined && params.service !== null)
2481
+ impactArgs.service = params.service;
2482
+ if (typeof params.subgroup === 'string')
2483
+ impactArgs.subgroup = params.subgroup;
2484
+ if (params.timeoutMs !== undefined)
2485
+ impactArgs.timeoutMs = params.timeoutMs;
2486
+ if (params.timeout !== undefined)
2487
+ impactArgs.timeout = params.timeout;
2488
+ return svc.groupImpact(impactArgs);
2489
+ }
2490
+ if (method === 'query') {
2491
+ const queryArgs = {
2492
+ name: groupName,
2493
+ query: params.query,
2494
+ };
2495
+ if (typeof params.task_context === 'string')
2496
+ queryArgs.task_context = params.task_context;
2497
+ if (typeof params.goal === 'string')
2498
+ queryArgs.goal = params.goal;
2499
+ if (typeof params.limit === 'number')
2500
+ queryArgs.limit = params.limit;
2501
+ if (typeof params.max_symbols === 'number')
2502
+ queryArgs.max_symbols = params.max_symbols;
2503
+ if (params.include_content !== undefined)
2504
+ queryArgs.include_content = params.include_content;
2505
+ if (params.service !== undefined && params.service !== null)
2506
+ queryArgs.service = params.service;
2507
+ if (memberRest !== undefined) {
2508
+ queryArgs.subgroup = memberRest;
2509
+ queryArgs.subgroupExact = true;
2510
+ }
2511
+ return svc.groupQuery(queryArgs);
2512
+ }
2513
+ if (method === 'context') {
2514
+ const targetSym = typeof params.target === 'string' && params.target.trim() !== ''
2515
+ ? params.target.trim()
2516
+ : typeof params.name === 'string' && params.name.trim() !== ''
2517
+ ? params.name.trim()
2518
+ : undefined;
2519
+ const contextArgs = {
2520
+ name: groupName,
2521
+ target: targetSym,
2522
+ };
2523
+ if (typeof params.uid === 'string')
2524
+ contextArgs.uid = params.uid;
2525
+ if (typeof params.file_path === 'string')
2526
+ contextArgs.file_path = params.file_path;
2527
+ if (params.include_content !== undefined)
2528
+ contextArgs.include_content = params.include_content;
2529
+ if (params.service !== undefined && params.service !== null)
2530
+ contextArgs.service = params.service;
2531
+ if (memberRest !== undefined) {
2532
+ contextArgs.subgroup = memberRest;
2533
+ contextArgs.subgroupExact = true;
2534
+ }
2535
+ return svc.groupContext(contextArgs);
2536
+ }
2537
+ throw new Error(`Internal: unsupported group-repo tool ${method}`);
2538
+ }
2306
2539
  async groupList(params) {
2307
2540
  return this.getGroupService().groupList(params);
2308
2541
  }
2309
2542
  async groupSync(params) {
2310
2543
  return this.getGroupService().groupSync(params);
2311
2544
  }
2312
- async groupContracts(params) {
2313
- return this.getGroupService().groupContracts(params);
2545
+ /**
2546
+ * MCP resource body for `gitnexus://group/{name}/contracts` (Issue #794).
2547
+ */
2548
+ async readGroupContractsResource(groupName, filter) {
2549
+ try {
2550
+ const params = { name: groupName };
2551
+ if (filter.type !== undefined)
2552
+ params.type = filter.type;
2553
+ if (filter.repo !== undefined)
2554
+ params.repo = filter.repo;
2555
+ if (filter.unmatchedOnly === true)
2556
+ params.unmatchedOnly = true;
2557
+ const raw = await this.getGroupService().groupContracts(params);
2558
+ return LocalBackend.formatGroupResourcePayload(raw);
2559
+ }
2560
+ catch (e) {
2561
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
2562
+ }
2314
2563
  }
2315
- async groupQuery(params) {
2316
- await this.refreshRepos();
2317
- return this.getGroupService().groupQuery(params);
2564
+ /**
2565
+ * MCP resource body for `gitnexus://group/{name}/status` (Issue #794).
2566
+ */
2567
+ async readGroupStatusResource(groupName) {
2568
+ try {
2569
+ const raw = await this.getGroupService().groupStatus({ name: groupName });
2570
+ return LocalBackend.formatGroupResourcePayload(raw);
2571
+ }
2572
+ catch (e) {
2573
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
2574
+ }
2318
2575
  }
2319
- async groupStatus(params) {
2320
- await this.refreshRepos();
2321
- return this.getGroupService().groupStatus(params);
2576
+ static formatGroupResourcePayload(raw) {
2577
+ if (raw && typeof raw === 'object' && 'error' in raw) {
2578
+ const err = raw.error;
2579
+ if (typeof err === 'string' && err.length > 0) {
2580
+ return `error: ${err}`;
2581
+ }
2582
+ }
2583
+ return JSON.stringify(raw, null, 2);
2322
2584
  }
2323
2585
  /**
2324
2586
  * Fetch Route nodes with their consumers in a single query.
@@ -25,6 +25,37 @@ export declare function getResourceDefinitions(): ResourceDefinition[];
25
25
  * Dynamic resource templates
26
26
  */
27
27
  export declare function getResourceTemplates(): ResourceTemplate[];
28
+ /** Query parameters for `gitnexus://group/{name}/contracts` */
29
+ export type GroupContractsResourceFilter = {
30
+ type?: string;
31
+ repo?: string;
32
+ unmatchedOnly?: boolean;
33
+ };
34
+ /** Normalized parse result for GitNexus MCP resource URIs */
35
+ export type ParsedGitnexusResource = {
36
+ kind: 'repos';
37
+ } | {
38
+ kind: 'setup';
39
+ } | {
40
+ kind: 'repo';
41
+ repoName: string;
42
+ resourceType: string;
43
+ param?: string;
44
+ } | {
45
+ kind: 'group';
46
+ groupName: string;
47
+ resourceType: 'contracts';
48
+ contractsFilter: GroupContractsResourceFilter;
49
+ } | {
50
+ kind: 'group';
51
+ groupName: string;
52
+ resourceType: 'status';
53
+ };
54
+ /**
55
+ * Parse a GitNexus resource URI (repos, setup, per-repo, or per-group templates).
56
+ * Used by `readResource` and tests (round-trip / dispatch coverage).
57
+ */
58
+ export declare function parseResourceUri(uri: string): ParsedGitnexusResource;
28
59
  /**
29
60
  * Read a resource and return its content
30
61
  */