gitnexus 1.6.3-rc.9 → 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 (257) 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 +4 -2
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/index.js +2 -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/evidence.js +5 -0
  33. package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -1
  34. package/dist/_shared/scope-resolution/registries/lookup-core.js +21 -5
  35. package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
  36. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
  37. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  38. package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
  39. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  40. package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
  41. package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
  42. package/dist/_shared/scope-resolution/scope-tree.js +3 -2
  43. package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
  44. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
  45. package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
  46. package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
  47. package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
  48. package/dist/_shared/scope-resolution/types.d.ts +11 -0
  49. package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
  50. package/dist/cli/ai-context.js +35 -4
  51. package/dist/cli/analyze.d.ts +27 -0
  52. package/dist/cli/analyze.js +31 -1
  53. package/dist/cli/clean.js +19 -1
  54. package/dist/cli/group.js +73 -0
  55. package/dist/cli/index-repo.js +8 -1
  56. package/dist/cli/index.js +26 -1
  57. package/dist/cli/list.js +11 -1
  58. package/dist/cli/remove.d.ts +30 -0
  59. package/dist/cli/remove.js +99 -0
  60. package/dist/cli/setup.js +185 -57
  61. package/dist/cli/tool.d.ts +5 -0
  62. package/dist/cli/tool.js +42 -0
  63. package/dist/config/ignore-service.d.ts +9 -0
  64. package/dist/config/ignore-service.js +80 -13
  65. package/dist/core/embedding-mode.d.ts +30 -0
  66. package/dist/core/embedding-mode.js +30 -0
  67. package/dist/core/embeddings/ast-utils.js +22 -22
  68. package/dist/core/embeddings/chunker.js +30 -25
  69. package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
  70. package/dist/core/embeddings/embedding-pipeline.js +15 -6
  71. package/dist/core/embeddings/text-generator.d.ts +1 -1
  72. package/dist/core/embeddings/text-generator.js +33 -24
  73. package/dist/core/embeddings/types.d.ts +43 -1
  74. package/dist/core/embeddings/types.js +101 -29
  75. package/dist/core/git-staleness.d.ts +18 -0
  76. package/dist/core/git-staleness.js +108 -0
  77. package/dist/core/graph/graph.js +115 -20
  78. package/dist/core/graph/types.d.ts +12 -1
  79. package/dist/core/group/config-parser.d.ts +4 -0
  80. package/dist/core/group/config-parser.js +18 -1
  81. package/dist/core/group/cross-impact.d.ts +41 -0
  82. package/dist/core/group/cross-impact.js +441 -0
  83. package/dist/core/group/extractors/http-patterns/php.js +126 -18
  84. package/dist/core/group/group-path-utils.d.ts +17 -0
  85. package/dist/core/group/group-path-utils.js +40 -0
  86. package/dist/core/group/resolve-at-member.d.ts +10 -0
  87. package/dist/core/group/resolve-at-member.js +31 -0
  88. package/dist/core/group/service.d.ts +9 -0
  89. package/dist/core/group/service.js +259 -25
  90. package/dist/core/group/types.d.ts +30 -0
  91. package/dist/core/ingestion/ast-cache.d.ts +16 -1
  92. package/dist/core/ingestion/ast-cache.js +14 -2
  93. package/dist/core/ingestion/call-processor.js +9 -0
  94. package/dist/core/ingestion/emit-references.d.ts +88 -0
  95. package/dist/core/ingestion/emit-references.js +229 -0
  96. package/dist/core/ingestion/filesystem-walker.js +6 -4
  97. package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
  98. package/dist/core/ingestion/finalize-orchestrator.js +139 -0
  99. package/dist/core/ingestion/framework-detection.js +6 -2
  100. package/dist/core/ingestion/import-processor.js +4 -0
  101. package/dist/core/ingestion/import-resolvers/python.js +9 -6
  102. package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
  103. package/dist/core/ingestion/import-target-adapter.js +95 -0
  104. package/dist/core/ingestion/language-provider.d.ts +36 -33
  105. package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
  106. package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
  107. package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
  108. package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
  109. package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
  110. package/dist/core/ingestion/languages/csharp/arity.js +37 -0
  111. package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
  112. package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
  113. package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
  114. package/dist/core/ingestion/languages/csharp/captures.js +249 -0
  115. package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
  116. package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
  117. package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
  118. package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
  119. package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
  120. package/dist/core/ingestion/languages/csharp/index.js +82 -0
  121. package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
  122. package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
  123. package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
  124. package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
  125. package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
  126. package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
  127. package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
  128. package/dist/core/ingestion/languages/csharp/query.js +515 -0
  129. package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
  130. package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
  131. package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
  132. package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
  133. package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
  134. package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
  135. package/dist/core/ingestion/languages/csharp.js +14 -0
  136. package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
  137. package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
  138. package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
  139. package/dist/core/ingestion/languages/python/arity.js +38 -0
  140. package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
  141. package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
  142. package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
  143. package/dist/core/ingestion/languages/python/captures.js +106 -0
  144. package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
  145. package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
  146. package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
  147. package/dist/core/ingestion/languages/python/import-target.js +99 -0
  148. package/dist/core/ingestion/languages/python/index.d.ts +80 -0
  149. package/dist/core/ingestion/languages/python/index.js +80 -0
  150. package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
  151. package/dist/core/ingestion/languages/python/interpret.js +191 -0
  152. package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
  153. package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
  154. package/dist/core/ingestion/languages/python/query.d.ts +9 -0
  155. package/dist/core/ingestion/languages/python/query.js +267 -0
  156. package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
  157. package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
  158. package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
  159. package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
  160. package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
  161. package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
  162. package/dist/core/ingestion/languages/python.js +14 -0
  163. package/dist/core/ingestion/model/method-registry.d.ts +9 -0
  164. package/dist/core/ingestion/model/method-registry.js +4 -0
  165. package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
  166. package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
  167. package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
  168. package/dist/core/ingestion/model/semantic-model.js +55 -0
  169. package/dist/core/ingestion/mro-processor.js +38 -22
  170. package/dist/core/ingestion/parsing-processor.d.ts +18 -1
  171. package/dist/core/ingestion/parsing-processor.js +45 -11
  172. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  173. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  174. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
  175. package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
  176. package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
  177. package/dist/core/ingestion/pipeline.js +2 -1
  178. package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
  179. package/dist/core/ingestion/registry-primary-flag.js +111 -0
  180. package/dist/core/ingestion/resolve-references.d.ts +63 -0
  181. package/dist/core/ingestion/resolve-references.js +175 -0
  182. package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
  183. package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
  184. package/dist/core/ingestion/scope-extractor.d.ts +86 -0
  185. package/dist/core/ingestion/scope-extractor.js +758 -0
  186. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
  187. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
  188. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
  189. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
  190. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
  191. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
  192. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
  193. package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
  194. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
  195. package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
  196. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
  197. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
  198. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
  199. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
  200. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
  201. package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
  202. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
  203. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
  204. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
  205. package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
  206. package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
  207. package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
  208. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
  209. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
  210. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
  211. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
  212. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
  213. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
  214. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
  215. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
  216. package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
  217. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
  218. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
  219. package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
  220. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
  221. package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
  222. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
  223. package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
  224. package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
  225. package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
  226. package/dist/core/ingestion/shadow-harness.d.ts +113 -0
  227. package/dist/core/ingestion/shadow-harness.js +148 -0
  228. package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
  229. package/dist/core/ingestion/utils/ast-helpers.js +70 -0
  230. package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
  231. package/dist/core/ingestion/utils/max-file-size.js +52 -0
  232. package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
  233. package/dist/core/ingestion/workers/parse-worker.js +57 -21
  234. package/dist/core/lbug/lbug-adapter.d.ts +22 -2
  235. package/dist/core/lbug/lbug-adapter.js +58 -14
  236. package/dist/core/lbug/pool-adapter.d.ts +17 -0
  237. package/dist/core/lbug/pool-adapter.js +24 -14
  238. package/dist/core/run-analyze.d.ts +32 -0
  239. package/dist/core/run-analyze.js +74 -19
  240. package/dist/core/search/bm25-index.d.ts +18 -0
  241. package/dist/core/search/bm25-index.js +125 -12
  242. package/dist/core/tree-sitter/parser-loader.js +6 -1
  243. package/dist/mcp/local/local-backend.d.ts +67 -3
  244. package/dist/mcp/local/local-backend.js +296 -34
  245. package/dist/mcp/resources.d.ts +31 -0
  246. package/dist/mcp/resources.js +100 -17
  247. package/dist/mcp/tools.d.ts +4 -1
  248. package/dist/mcp/tools.js +75 -54
  249. package/dist/server/api.js +6 -2
  250. package/dist/storage/git.d.ts +49 -0
  251. package/dist/storage/git.js +111 -0
  252. package/dist/storage/repo-manager.d.ts +246 -1
  253. package/dist/storage/repo-manager.js +391 -9
  254. package/package.json +7 -6
  255. package/scripts/bench-scope-resolution.ts +134 -0
  256. package/scripts/ci-list-migrated-languages.ts +24 -0
  257. package/skills/gitnexus-cli.md +1 -0
@@ -65,36 +65,113 @@ export function getResourceTemplates() {
65
65
  description: 'Step-by-step execution trace',
66
66
  mimeType: 'text/yaml',
67
67
  },
68
+ {
69
+ uriTemplate: 'gitnexus://group/{name}/contracts',
70
+ name: 'Group Contract Registry',
71
+ description: 'Cross-repo contract registry for a repository group. Optional query: type, repo, unmatchedOnly (true|false).',
72
+ mimeType: 'text/yaml',
73
+ },
74
+ {
75
+ uriTemplate: 'gitnexus://group/{name}/status',
76
+ name: 'Group Index Status',
77
+ description: 'Per-repo index and contract-registry staleness for a repository group',
78
+ mimeType: 'text/yaml',
79
+ },
68
80
  ];
69
81
  }
82
+ function parseUnmatchedOnlyParam(raw) {
83
+ if (raw === null)
84
+ return undefined;
85
+ const v = raw.trim().toLowerCase();
86
+ if (v === 'true' || v === '1')
87
+ return true;
88
+ if (v === 'false' || v === '0')
89
+ return false;
90
+ return undefined;
91
+ }
70
92
  /**
71
- * Parse a resource URI to extract the repo name and resource type.
93
+ * Parse a GitNexus resource URI (repos, setup, per-repo, or per-group templates).
94
+ * Used by `readResource` and tests (round-trip / dispatch coverage).
72
95
  */
73
- function parseUri(uri) {
96
+ export function parseResourceUri(uri) {
74
97
  if (uri === 'gitnexus://repos')
75
- return { resourceType: 'repos' };
98
+ return { kind: 'repos' };
76
99
  if (uri === 'gitnexus://setup')
77
- return { resourceType: 'setup' };
78
- // Repo-scoped: gitnexus://repo/{name}/context
79
- const repoMatch = uri.match(/^gitnexus:\/\/repo\/([^/]+)\/(.+)$/);
80
- if (repoMatch) {
81
- const repoName = decodeURIComponent(repoMatch[1]);
82
- const rest = repoMatch[2];
100
+ return { kind: 'setup' };
101
+ let u;
102
+ try {
103
+ u = new URL(uri);
104
+ }
105
+ catch {
106
+ throw new Error(`Unknown resource URI: ${uri}`);
107
+ }
108
+ if (u.protocol !== 'gitnexus:') {
109
+ throw new Error(`Unknown resource URI: ${uri}`);
110
+ }
111
+ if (u.hostname === 'group') {
112
+ const segments = u.pathname
113
+ .replace(/^\/+|\/+$/g, '')
114
+ .split('/')
115
+ .filter(Boolean);
116
+ if (segments.length < 2) {
117
+ throw new Error(`Invalid group resource URI (expected gitnexus://group/{name}/contracts or .../status): ${uri}`);
118
+ }
119
+ const tail = segments[segments.length - 1];
120
+ if (tail !== 'contracts' && tail !== 'status') {
121
+ throw new Error(`Unknown group resource path in URI: ${uri}`);
122
+ }
123
+ const groupName = segments
124
+ .slice(0, -1)
125
+ .map((s) => decodeURIComponent(s))
126
+ .join('/');
127
+ if (!groupName) {
128
+ throw new Error(`Invalid group resource URI (empty group name): ${uri}`);
129
+ }
130
+ if (tail === 'status') {
131
+ return { kind: 'group', groupName, resourceType: 'status' };
132
+ }
133
+ const contractsFilter = {};
134
+ const type = u.searchParams.get('type');
135
+ if (type && type.trim())
136
+ contractsFilter.type = type.trim();
137
+ const repo = u.searchParams.get('repo');
138
+ if (repo && repo.trim())
139
+ contractsFilter.repo = repo.trim();
140
+ if (u.searchParams.has('unmatchedOnly')) {
141
+ const coerced = parseUnmatchedOnlyParam(u.searchParams.get('unmatchedOnly'));
142
+ if (coerced !== undefined)
143
+ contractsFilter.unmatchedOnly = coerced;
144
+ }
145
+ return { kind: 'group', groupName, resourceType: 'contracts', contractsFilter };
146
+ }
147
+ if (u.hostname === 'repo') {
148
+ const segments = u.pathname
149
+ .replace(/^\/+|\/+$/g, '')
150
+ .split('/')
151
+ .filter(Boolean);
152
+ if (segments.length < 2) {
153
+ throw new Error(`Unknown resource URI: ${uri}`);
154
+ }
155
+ const repoName = decodeURIComponent(segments[0]);
156
+ const restEncoded = segments.slice(1);
157
+ const rest = restEncoded.map((s) => decodeURIComponent(s)).join('/');
83
158
  if (rest.startsWith('cluster/')) {
84
159
  return {
160
+ kind: 'repo',
85
161
  repoName,
86
162
  resourceType: 'cluster',
87
- param: decodeURIComponent(rest.replace('cluster/', '')),
163
+ param: rest.replace(/^cluster\//, ''),
88
164
  };
89
165
  }
90
166
  if (rest.startsWith('process/')) {
91
167
  return {
168
+ kind: 'repo',
92
169
  repoName,
93
170
  resourceType: 'process',
94
- param: decodeURIComponent(rest.replace('process/', '')),
171
+ param: rest.replace(/^process\//, ''),
95
172
  };
96
173
  }
97
- return { repoName, resourceType: rest };
174
+ return { kind: 'repo', repoName, resourceType: rest };
98
175
  }
99
176
  throw new Error(`Unknown resource URI: ${uri}`);
100
177
  }
@@ -102,15 +179,19 @@ function parseUri(uri) {
102
179
  * Read a resource and return its content
103
180
  */
104
181
  export async function readResource(uri, backend) {
105
- const parsed = parseUri(uri);
106
- // Global repos list — no repo context needed
107
- if (parsed.resourceType === 'repos') {
182
+ const parsed = parseResourceUri(uri);
183
+ if (parsed.kind === 'repos') {
108
184
  return getReposResource(backend);
109
185
  }
110
- // Setup resource — returns AGENTS.md content for all repos
111
- if (parsed.resourceType === 'setup') {
186
+ if (parsed.kind === 'setup') {
112
187
  return getSetupResource(backend);
113
188
  }
189
+ if (parsed.kind === 'group') {
190
+ if (parsed.resourceType === 'contracts') {
191
+ return backend.readGroupContractsResource(parsed.groupName, parsed.contractsFilter);
192
+ }
193
+ return backend.readGroupStatusResource(parsed.groupName);
194
+ }
114
195
  const repoName = parsed.repoName;
115
196
  switch (parsed.resourceType) {
116
197
  case 'context':
@@ -202,6 +283,8 @@ async function getContextResource(backend, repoName) {
202
283
  lines.push(` - gitnexus://repo/${context.projectName}/processes: All execution flows`);
203
284
  lines.push(` - gitnexus://repo/${context.projectName}/cluster/{name}: Module details`);
204
285
  lines.push(` - gitnexus://repo/${context.projectName}/process/{name}: Process trace`);
286
+ lines.push(' - gitnexus://group/{name}/contracts: Group contract registry (optional ?type=&repo=&unmatchedOnly=)');
287
+ lines.push(' - gitnexus://group/{name}/status: Group index / contract staleness');
205
288
  return lines.join('\n');
206
289
  }
207
290
  /**
@@ -12,11 +12,14 @@ export interface ToolDefinition {
12
12
  properties: Record<string, {
13
13
  type: string;
14
14
  description?: string;
15
- default?: any;
15
+ default?: unknown;
16
16
  items?: {
17
17
  type: string;
18
18
  };
19
19
  enum?: string[];
20
+ minimum?: number;
21
+ maximum?: number;
22
+ minLength?: number;
20
23
  }>;
21
24
  required: string[];
22
25
  };
package/dist/mcp/tools.js CHANGED
@@ -35,7 +35,11 @@ Returns results grouped by process (execution flow):
35
35
  - process_symbols: all symbols in those flows with file locations and module (functional area)
36
36
  - definitions: standalone types/interfaces not in any process
37
37
 
38
- Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.`,
38
+ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank Fusion.
39
+
40
+ GROUP MODE: set "repo" to "@<groupName>" to search all member repos in that group (merged via RRF), or "@<groupName>/<groupRepoPath>" to run against a single member (same path keys as in group.yaml). If you use "@<groupName>" only, the member repo defaults to the lexicographically first key in group.yaml "repos". Prefer resources for contracts/status (see migration from legacy group_* tools).
41
+
42
+ SERVICE: optional monorepo path prefix (POSIX-style, case-sensitive segments). When "repo" starts with "@", only processes whose symbols fall under that prefix are included. For a normal indexed repo name (no leading @), this field is currently ignored by the server.`,
39
43
  inputSchema: {
40
44
  type: 'object',
41
45
  properties: {
@@ -48,11 +52,19 @@ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank
48
52
  type: 'string',
49
53
  description: 'What you want to find (e.g., "existing auth validation logic"). Helps ranking.',
50
54
  },
51
- limit: { type: 'number', description: 'Max processes to return (default: 5)', default: 5 },
55
+ limit: {
56
+ type: 'number',
57
+ description: 'Max processes to return (default: 5)',
58
+ default: 5,
59
+ minimum: 1,
60
+ maximum: 100,
61
+ },
52
62
  max_symbols: {
53
63
  type: 'number',
54
64
  description: 'Max symbols per process (default: 10)',
55
65
  default: 10,
66
+ minimum: 1,
67
+ maximum: 200,
56
68
  },
57
69
  include_content: {
58
70
  type: 'boolean',
@@ -61,7 +73,12 @@ Hybrid ranking: BM25 keyword + semantic vector search, ranked by Reciprocal Rank
61
73
  },
62
74
  repo: {
63
75
  type: 'string',
64
- description: 'Repository name or path. Omit if only one repo is indexed.',
76
+ description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>" (member path keys from group.yaml). Omit when only one indexed repo exists.',
77
+ },
78
+ service: {
79
+ type: 'string',
80
+ minLength: 1,
81
+ description: 'Optional monorepo service root (relative path, "/" separators). In group mode (@repo), prefix-matches symbol file paths; ignored for a normal repo name. Empty string is rejected server-side.',
65
82
  },
66
83
  },
67
84
  required: ['query'],
@@ -135,7 +152,11 @@ AFTER THIS: Use impact() if planning changes, or READ gitnexus://repo/{name}/pro
135
152
 
136
153
  Handles disambiguation: if multiple symbols share the same name, returns ranked candidates (each with a relevance score) for you to pick from. Use uid for zero-ambiguity lookup, or narrow the search with file_path and/or kind hints.
137
154
 
138
- NOTE: ACCESSES edges (field read/write tracking) are included in context results with reason 'read' or 'write'. CALLS edges resolve through field access chains and method-call chains (e.g., user.address.getCity().save() produces CALLS edges at each step).`,
155
+ NOTE: ACCESSES edges (field read/write tracking) are included in context results with reason 'read' or 'write'. CALLS edges resolve through field access chains and method-call chains (e.g., user.address.getCity().save() produces CALLS edges at each step).
156
+
157
+ GROUP MODE: set "repo" to "@<groupName>" to run context in each member repo (aggregated list), or "@<groupName>/<groupRepoPath>" for one member. If you use "@<groupName>" only, the member defaults to the lexicographically first key in group.yaml "repos".
158
+
159
+ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "repo" starts with "@", prefix-matches resolved symbol file paths; when a hit is outside the prefix, that member returns an empty payload for the symbol. Ignored for a normal indexed repo name.`,
139
160
  inputSchema: {
140
161
  type: 'object',
141
162
  properties: {
@@ -156,7 +177,12 @@ NOTE: ACCESSES edges (field read/write tracking) are included in context results
156
177
  },
157
178
  repo: {
158
179
  type: 'string',
159
- description: 'Repository name or path. Omit if only one repo is indexed.',
180
+ description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". Omit if only one repo is indexed.',
181
+ },
182
+ service: {
183
+ type: 'string',
184
+ minLength: 1,
185
+ description: 'Optional monorepo service root (relative path). Applies in group mode (@repo) only; ignored for a normal repo name. Empty string is rejected server-side.',
160
186
  },
161
187
  },
162
188
  required: [],
@@ -251,7 +277,11 @@ TIP: Default traversal uses CALLS/IMPORTS/EXTENDS/IMPLEMENTS. For class members,
251
277
  Handles disambiguation: when multiple symbols share the target name, returns ranked candidates (each with a relevance score) instead of silently picking one. Use target_uid for zero-ambiguity lookup, or narrow with file_path and/or kind hints.
252
278
 
253
279
  EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES
254
- Confidence: 1.0 = certain, <0.8 = fuzzy match`,
280
+ Confidence: 1.0 = certain, <0.8 = fuzzy match
281
+
282
+ GROUP MODE: set "repo" to "@<groupName>" for cross-repo impact anchored at the default member (lexicographically first key in group.yaml "repos"), or "@<groupName>/<groupRepoPath>" to choose the member (same path keys as in group.yaml). Phase-1 walk runs in that member; cross-boundary fan-out uses the group bridge.
283
+
284
+ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "repo" starts with "@", scopes the local impact walk and cross-repo symbol paths to files under that prefix; ignored for a normal indexed repo name.`,
255
285
  inputSchema: {
256
286
  type: 'object',
257
287
  properties: {
@@ -274,8 +304,17 @@ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
274
304
  },
275
305
  maxDepth: {
276
306
  type: 'number',
277
- description: 'Max relationship depth (default: 3)',
307
+ description: 'Max relationship depth (default: 3, server clamps to 1–32)',
278
308
  default: 3,
309
+ minimum: 1,
310
+ maximum: 32,
311
+ },
312
+ crossDepth: {
313
+ type: 'number',
314
+ description: 'Cross-repository hop depth via contract bridge (default: 1; values above server maximum are clamped)',
315
+ default: 1,
316
+ minimum: 1,
317
+ maximum: 32,
279
318
  },
280
319
  relationTypes: {
281
320
  type: 'array',
@@ -283,10 +322,37 @@ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
283
322
  description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES (default: usage-based, ACCESSES excluded by default)',
284
323
  },
285
324
  includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
286
- minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
325
+ minConfidence: {
326
+ type: 'number',
327
+ description: 'Minimum edge confidence 0–1 (default: 0 when omitted; server clamps to 0–1)',
328
+ default: 0,
329
+ minimum: 0,
330
+ maximum: 1,
331
+ },
287
332
  repo: {
288
333
  type: 'string',
289
- description: 'Repository name or path. Omit if only one repo is indexed.',
334
+ description: 'Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". Omit if only one repo is indexed.',
335
+ },
336
+ service: {
337
+ type: 'string',
338
+ minLength: 1,
339
+ description: 'Optional monorepo service root (relative path). Applies when "repo" is group mode (@…); ignored for a normal repo name. Empty string is rejected server-side.',
340
+ },
341
+ subgroup: {
342
+ type: 'string',
343
+ description: 'Optional group subgroup prefix (member repo paths) limiting which repos participate in cross fan-out.',
344
+ },
345
+ timeoutMs: {
346
+ type: 'number',
347
+ description: 'Wall-clock budget in milliseconds for the Phase-1 local impact leg (default 30000)',
348
+ minimum: 1,
349
+ maximum: 3600000,
350
+ },
351
+ timeout: {
352
+ type: 'number',
353
+ description: 'Alias of timeoutMs (milliseconds) when timeoutMs is omitted',
354
+ minimum: 1,
355
+ maximum: 3600000,
290
356
  },
291
357
  },
292
358
  required: ['target', 'direction'],
@@ -404,49 +470,4 @@ WHEN TO USE: After changing group.yaml or re-indexing member repos.`,
404
470
  required: ['name'],
405
471
  },
406
472
  },
407
- {
408
- name: 'group_contracts',
409
- description: `Inspect contracts and cross-links from the group's contracts.json.
410
-
411
- WHEN TO USE: Debug cross-repo links after group_sync.`,
412
- inputSchema: {
413
- type: 'object',
414
- properties: {
415
- name: { type: 'string', description: 'Group name' },
416
- type: { type: 'string', description: 'Filter by contract type (http, topic, …)' },
417
- repo: { type: 'string', description: 'Filter by group repo path (e.g. app/backend)' },
418
- unmatchedOnly: { type: 'boolean', description: 'Only contracts with no cross-link' },
419
- },
420
- required: ['name'],
421
- },
422
- },
423
- {
424
- name: 'group_query',
425
- description: `Run the query tool across all repos in a group and merge process results via reciprocal rank fusion.
426
-
427
- WHEN TO USE: Semantic / hybrid search across a whole product group.`,
428
- inputSchema: {
429
- type: 'object',
430
- properties: {
431
- name: { type: 'string', description: 'Group name' },
432
- query: { type: 'string', description: 'Search query' },
433
- subgroup: { type: 'string', description: 'Limit to repo paths under this prefix' },
434
- limit: { type: 'number', description: 'Max merged results (default 5)' },
435
- },
436
- required: ['name', 'query'],
437
- },
438
- },
439
- {
440
- name: 'group_status',
441
- description: `Report index staleness (commit vs HEAD) and Contract Registry staleness (indexedAt) for each repo in a group.
442
-
443
- WHEN TO USE: Before group_sync or when agents should refresh indexes.`,
444
- inputSchema: {
445
- type: 'object',
446
- properties: {
447
- name: { type: 'string', description: 'Group name' },
448
- },
449
- required: ['name'],
450
- },
451
- },
452
473
  ];
@@ -1002,7 +1002,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
1002
1002
  // POST /api/analyze — start a new analysis job
1003
1003
  app.post('/api/analyze', async (req, res) => {
1004
1004
  try {
1005
- const { url: repoUrl, path: repoLocalPath, force, embeddings } = req.body;
1005
+ const { url: repoUrl, path: repoLocalPath, force, embeddings, dropEmbeddings } = req.body;
1006
1006
  // Input type validation
1007
1007
  if (repoUrl !== undefined && typeof repoUrl !== 'string') {
1008
1008
  res.status(400).json({ error: '"url" must be a string' });
@@ -1176,7 +1176,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
1176
1176
  child.send({
1177
1177
  type: 'start',
1178
1178
  repoPath: targetPath,
1179
- options: { force: !!force, embeddings: !!embeddings },
1179
+ options: {
1180
+ force: !!force,
1181
+ embeddings: !!embeddings,
1182
+ dropEmbeddings: !!dropEmbeddings,
1183
+ },
1180
1184
  });
1181
1185
  };
1182
1186
  forkWorker();
@@ -1,5 +1,29 @@
1
1
  export declare const isGitRepo: (repoPath: string) => boolean;
2
2
  export declare const getCurrentCommit: (repoPath: string) => string;
3
+ /**
4
+ * Get a stable canonical identifier for the repo's `origin` remote, if any.
5
+ *
6
+ * Used to fingerprint two on-disk clones as the same logical repository
7
+ * (issue #XXX — silent graph drift across sibling clones). `path` alone
8
+ * is unreliable: worktrees, "clean clone for indexing" hygiene, and
9
+ * multi-agent workspaces routinely have the same repo at multiple
10
+ * absolute paths. The remote URL is the only on-disk signal that
11
+ * survives those conventions.
12
+ *
13
+ * Normalisation strategy:
14
+ * - Strip a trailing `.git` so `https://x/y` and `https://x/y.git` collapse.
15
+ * - Strip a trailing `/` for the same reason.
16
+ * - `git@github.com:foo/bar` and `https://github.com/foo/bar` are
17
+ * intentionally NOT collapsed — they are different remotes from
18
+ * git's perspective and we don't want to assert equivalence.
19
+ * - Lower-case the host portion so `GitHub.com` and `github.com`
20
+ * don't desync; preserves case in path because some hosts
21
+ * (Bitbucket Server) treat repo paths case-sensitively.
22
+ *
23
+ * Returns `undefined` when there is no origin remote, the directory
24
+ * isn't a git repo, or git itself isn't available.
25
+ */
26
+ export declare const getRemoteUrl: (repoPath: string) => string | undefined;
3
27
  /**
4
28
  * Find the git repository root from any path inside the repo
5
29
  */
@@ -16,6 +40,31 @@ export declare const getGitRoot: (fromPath: string) => string | null;
16
40
  * @returns `true` when `.git` is present, `false` otherwise.
17
41
  */
18
42
  export declare const hasGitDir: (dirPath: string) => boolean;
43
+ /**
44
+ * Read `remote.origin.url` from a git repository, or `null` if not a
45
+ * git repo, has no `origin` remote, or git is unavailable.
46
+ *
47
+ * Used by the registry-name inference path (#979) to recover a
48
+ * meaningful repo name when `path.basename(repoPath)` is generic
49
+ * (e.g. monorepo subprojects, git worktrees, Gas-Town-style
50
+ * `<rig>/refinery/rig/` layouts).
51
+ */
52
+ export declare const getRemoteOriginUrl: (repoPath: string) => string | null;
53
+ /**
54
+ * Parse a repository name out of a git remote URL. Handles the common
55
+ * SSH (`git@host:owner/repo.git`), HTTPS (`https://host/owner/repo.git`),
56
+ * `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
57
+ * unparseable input.
58
+ *
59
+ * The heuristic: strip a trailing `.git` and trailing slashes, then
60
+ * take the segment after the last `/` or `:`.
61
+ */
62
+ export declare const parseRepoNameFromUrl: (url: string | null | undefined) => string | null;
63
+ /**
64
+ * Convenience wrapper: derive a registry-friendly name from the repo's
65
+ * `origin` remote, or `null` when it cannot be inferred.
66
+ */
67
+ export declare const getInferredRepoName: (repoPath: string) => string | null;
19
68
  export interface DiffHunk {
20
69
  startLine: number;
21
70
  endLine: number;
@@ -19,6 +19,65 @@ export const getCurrentCommit = (repoPath) => {
19
19
  return '';
20
20
  }
21
21
  };
22
+ /**
23
+ * Get a stable canonical identifier for the repo's `origin` remote, if any.
24
+ *
25
+ * Used to fingerprint two on-disk clones as the same logical repository
26
+ * (issue #XXX — silent graph drift across sibling clones). `path` alone
27
+ * is unreliable: worktrees, "clean clone for indexing" hygiene, and
28
+ * multi-agent workspaces routinely have the same repo at multiple
29
+ * absolute paths. The remote URL is the only on-disk signal that
30
+ * survives those conventions.
31
+ *
32
+ * Normalisation strategy:
33
+ * - Strip a trailing `.git` so `https://x/y` and `https://x/y.git` collapse.
34
+ * - Strip a trailing `/` for the same reason.
35
+ * - `git@github.com:foo/bar` and `https://github.com/foo/bar` are
36
+ * intentionally NOT collapsed — they are different remotes from
37
+ * git's perspective and we don't want to assert equivalence.
38
+ * - Lower-case the host portion so `GitHub.com` and `github.com`
39
+ * don't desync; preserves case in path because some hosts
40
+ * (Bitbucket Server) treat repo paths case-sensitively.
41
+ *
42
+ * Returns `undefined` when there is no origin remote, the directory
43
+ * isn't a git repo, or git itself isn't available.
44
+ */
45
+ export const getRemoteUrl = (repoPath) => {
46
+ let raw;
47
+ try {
48
+ raw = execSync('git config --get remote.origin.url', {
49
+ cwd: repoPath,
50
+ stdio: ['ignore', 'pipe', 'ignore'],
51
+ })
52
+ .toString()
53
+ .trim();
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ if (!raw)
59
+ return undefined;
60
+ let normalised = raw.replace(/\/$/, '').replace(/\.git$/, '');
61
+ // Lower-case the host segment of `scheme://[user@]host[:port]/...`
62
+ // and the host segment of `git@host:owner/repo` SCP form.
63
+ // SSH user-segment regex deliberately accepts the common
64
+ // `git@`/`<alnum>-_@` cases. Less common usernames (e.g. with
65
+ // dots) fall through to the URL-form branch — they will simply
66
+ // not get host-case normalisation, which is acceptable: the raw
67
+ // `git config` output is still a valid fingerprint, just slightly
68
+ // less collapsible across host casings.
69
+ const sshMatch = normalised.match(/^(git@|[a-zA-Z0-9_-]+@)([^:/]+)(:.+)$/);
70
+ if (sshMatch) {
71
+ normalised = `${sshMatch[1]}${sshMatch[2].toLowerCase()}${sshMatch[3]}`;
72
+ }
73
+ else {
74
+ const urlMatch = normalised.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/]+)(\/.*)?$/);
75
+ if (urlMatch) {
76
+ normalised = `${urlMatch[1]}${urlMatch[2].toLowerCase()}${urlMatch[3] ?? ''}`;
77
+ }
78
+ }
79
+ return normalised;
80
+ };
22
81
  /**
23
82
  * Find the git repository root from any path inside the repo
24
83
  */
@@ -52,6 +111,58 @@ export const hasGitDir = (dirPath) => {
52
111
  return false;
53
112
  }
54
113
  };
114
+ /**
115
+ * Read `remote.origin.url` from a git repository, or `null` if not a
116
+ * git repo, has no `origin` remote, or git is unavailable.
117
+ *
118
+ * Used by the registry-name inference path (#979) to recover a
119
+ * meaningful repo name when `path.basename(repoPath)` is generic
120
+ * (e.g. monorepo subprojects, git worktrees, Gas-Town-style
121
+ * `<rig>/refinery/rig/` layouts).
122
+ */
123
+ export const getRemoteOriginUrl = (repoPath) => {
124
+ try {
125
+ const url = execSync('git config --get remote.origin.url', {
126
+ cwd: repoPath,
127
+ stdio: ['ignore', 'pipe', 'ignore'],
128
+ })
129
+ .toString()
130
+ .trim();
131
+ return url || null;
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ };
137
+ /**
138
+ * Parse a repository name out of a git remote URL. Handles the common
139
+ * SSH (`git@host:owner/repo.git`), HTTPS (`https://host/owner/repo.git`),
140
+ * `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
141
+ * unparseable input.
142
+ *
143
+ * The heuristic: strip a trailing `.git` and trailing slashes, then
144
+ * take the segment after the last `/` or `:`.
145
+ */
146
+ export const parseRepoNameFromUrl = (url) => {
147
+ if (!url)
148
+ return null;
149
+ const trimmed = url.trim();
150
+ if (!trimmed)
151
+ return null;
152
+ // Strip `.git` suffix (case-insensitive) and any trailing slashes.
153
+ const withoutSuffix = trimmed.replace(/\.git\/*$/i, '').replace(/\/+$/, '');
154
+ // Last path segment, splitting on either `/` or `:` (covers SSH form).
155
+ const m = withoutSuffix.match(/[/:]([^/:]+)$/);
156
+ const candidate = m ? m[1] : withoutSuffix;
157
+ return candidate || null;
158
+ };
159
+ /**
160
+ * Convenience wrapper: derive a registry-friendly name from the repo's
161
+ * `origin` remote, or `null` when it cannot be inferred.
162
+ */
163
+ export const getInferredRepoName = (repoPath) => {
164
+ return parseRepoNameFromUrl(getRemoteOriginUrl(repoPath));
165
+ };
55
166
  /**
56
167
  * Parse unified diff output (with -U0) into per-file hunk ranges.
57
168
  * Extracts the new-file line ranges from @@ hunk headers.