gitnexus 1.5.3 → 1.6.0

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 (201) hide show
  1. package/README.md +10 -0
  2. package/dist/_shared/graph/types.d.ts +1 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/language-detection.d.ts.map +1 -1
  7. package/dist/_shared/language-detection.js +2 -0
  8. package/dist/_shared/language-detection.js.map +1 -1
  9. package/dist/_shared/languages.d.ts +1 -0
  10. package/dist/_shared/languages.d.ts.map +1 -1
  11. package/dist/_shared/languages.js +1 -0
  12. package/dist/_shared/languages.js.map +1 -1
  13. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  14. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.js +3 -1
  16. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  17. package/dist/_shared/mro-strategy.d.ts +19 -0
  18. package/dist/_shared/mro-strategy.d.ts.map +1 -0
  19. package/dist/_shared/mro-strategy.js +2 -0
  20. package/dist/_shared/mro-strategy.js.map +1 -0
  21. package/dist/cli/ai-context.d.ts +1 -0
  22. package/dist/cli/ai-context.js +28 -4
  23. package/dist/cli/analyze.d.ts +2 -0
  24. package/dist/cli/analyze.js +2 -1
  25. package/dist/cli/group.d.ts +2 -0
  26. package/dist/cli/group.js +233 -0
  27. package/dist/cli/index.js +3 -0
  28. package/dist/cli/serve.js +4 -1
  29. package/dist/cli/setup.js +34 -3
  30. package/dist/config/ignore-service.js +8 -3
  31. package/dist/core/augmentation/engine.js +1 -1
  32. package/dist/core/git-staleness.d.ts +13 -0
  33. package/dist/core/git-staleness.js +29 -0
  34. package/dist/core/group/bridge-db.d.ts +82 -0
  35. package/dist/core/group/bridge-db.js +460 -0
  36. package/dist/core/group/bridge-schema.d.ts +27 -0
  37. package/dist/core/group/bridge-schema.js +55 -0
  38. package/dist/core/group/config-parser.d.ts +3 -0
  39. package/dist/core/group/config-parser.js +83 -0
  40. package/dist/core/group/contract-extractor.d.ts +7 -0
  41. package/dist/core/group/contract-extractor.js +1 -0
  42. package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
  43. package/dist/core/group/extractors/grpc-extractor.js +264 -0
  44. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  45. package/dist/core/group/extractors/http-route-extractor.js +428 -0
  46. package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
  47. package/dist/core/group/extractors/topic-extractor.js +234 -0
  48. package/dist/core/group/matching.d.ts +13 -0
  49. package/dist/core/group/matching.js +198 -0
  50. package/dist/core/group/normalization.d.ts +3 -0
  51. package/dist/core/group/normalization.js +115 -0
  52. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  53. package/dist/core/group/service-boundary-detector.js +155 -0
  54. package/dist/core/group/service.d.ts +46 -0
  55. package/dist/core/group/service.js +160 -0
  56. package/dist/core/group/storage.d.ts +9 -0
  57. package/dist/core/group/storage.js +91 -0
  58. package/dist/core/group/sync.d.ts +21 -0
  59. package/dist/core/group/sync.js +148 -0
  60. package/dist/core/group/types.d.ts +130 -0
  61. package/dist/core/group/types.js +1 -0
  62. package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
  63. package/dist/core/ingestion/binding-accumulator.js +332 -0
  64. package/dist/core/ingestion/call-processor.d.ts +155 -24
  65. package/dist/core/ingestion/call-processor.js +1129 -247
  66. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  67. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  68. package/dist/core/ingestion/class-types.d.ts +34 -0
  69. package/dist/core/ingestion/class-types.js +1 -0
  70. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  71. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  72. package/dist/core/ingestion/field-types.d.ts +2 -2
  73. package/dist/core/ingestion/filesystem-walker.js +8 -0
  74. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  75. package/dist/core/ingestion/framework-detection.js +1 -0
  76. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  77. package/dist/core/ingestion/heritage-processor.js +15 -28
  78. package/dist/core/ingestion/import-processor.d.ts +1 -11
  79. package/dist/core/ingestion/import-processor.js +0 -12
  80. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  81. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  82. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  83. package/dist/core/ingestion/language-provider.d.ts +6 -3
  84. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  85. package/dist/core/ingestion/languages/csharp.js +20 -0
  86. package/dist/core/ingestion/languages/dart.js +26 -4
  87. package/dist/core/ingestion/languages/go.js +22 -0
  88. package/dist/core/ingestion/languages/index.d.ts +1 -0
  89. package/dist/core/ingestion/languages/index.js +2 -0
  90. package/dist/core/ingestion/languages/java.js +17 -0
  91. package/dist/core/ingestion/languages/kotlin.js +24 -1
  92. package/dist/core/ingestion/languages/php.js +23 -11
  93. package/dist/core/ingestion/languages/python.js +9 -0
  94. package/dist/core/ingestion/languages/ruby.js +28 -0
  95. package/dist/core/ingestion/languages/rust.js +38 -0
  96. package/dist/core/ingestion/languages/swift.js +31 -0
  97. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  98. package/dist/core/ingestion/languages/typescript.js +52 -3
  99. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  100. package/dist/core/ingestion/languages/vue.js +81 -0
  101. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  103. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  104. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  105. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  106. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  107. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  108. package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
  109. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  110. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  111. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  112. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  113. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  114. package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
  115. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  116. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  117. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  118. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  119. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
  120. package/dist/core/ingestion/method-extractors/generic.js +38 -15
  121. package/dist/core/ingestion/method-types.d.ts +25 -0
  122. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  123. package/dist/core/ingestion/model/field-registry.js +22 -0
  124. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  125. package/dist/core/ingestion/model/heritage-map.js +159 -0
  126. package/dist/core/ingestion/model/index.d.ts +20 -0
  127. package/dist/core/ingestion/model/index.js +41 -0
  128. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  129. package/dist/core/ingestion/model/method-registry.js +130 -0
  130. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  131. package/dist/core/ingestion/model/registration-table.js +224 -0
  132. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  133. package/dist/core/ingestion/model/resolution-context.js +337 -0
  134. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  135. package/dist/core/ingestion/model/resolve.js +242 -0
  136. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  137. package/dist/core/ingestion/model/semantic-model.js +120 -0
  138. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  139. package/dist/core/ingestion/model/symbol-table.js +206 -0
  140. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  141. package/dist/core/ingestion/model/type-registry.js +62 -0
  142. package/dist/core/ingestion/mro-processor.d.ts +4 -3
  143. package/dist/core/ingestion/mro-processor.js +310 -106
  144. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  145. package/dist/core/ingestion/parsing-processor.js +210 -85
  146. package/dist/core/ingestion/pipeline.d.ts +2 -0
  147. package/dist/core/ingestion/pipeline.js +192 -68
  148. package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
  149. package/dist/core/ingestion/tree-sitter-queries.js +21 -0
  150. package/dist/core/ingestion/type-env.d.ts +15 -2
  151. package/dist/core/ingestion/type-env.js +163 -102
  152. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  153. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  154. package/dist/core/ingestion/type-extractors/php.js +0 -55
  155. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  156. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  157. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  158. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  159. package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
  160. package/dist/core/ingestion/utils/ast-helpers.js +129 -572
  161. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  162. package/dist/core/ingestion/utils/method-props.js +147 -0
  163. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  164. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  165. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  166. package/dist/core/ingestion/workers/parse-worker.js +463 -198
  167. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  168. package/dist/core/lbug/lbug-adapter.js +68 -3
  169. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  170. package/dist/core/lbug/pool-adapter.js +522 -0
  171. package/dist/core/run-analyze.d.ts +2 -0
  172. package/dist/core/run-analyze.js +1 -1
  173. package/dist/core/search/bm25-index.js +1 -1
  174. package/dist/core/tree-sitter/parser-loader.js +1 -0
  175. package/dist/core/wiki/graph-queries.js +1 -1
  176. package/dist/mcp/core/embedder.js +6 -5
  177. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  178. package/dist/mcp/core/lbug-adapter.js +3 -484
  179. package/dist/mcp/local/local-backend.d.ts +31 -2
  180. package/dist/mcp/local/local-backend.js +255 -46
  181. package/dist/mcp/resources.js +5 -4
  182. package/dist/mcp/staleness.d.ts +3 -13
  183. package/dist/mcp/staleness.js +2 -31
  184. package/dist/mcp/tools.js +80 -4
  185. package/dist/server/analyze-job.d.ts +2 -0
  186. package/dist/server/analyze-job.js +4 -0
  187. package/dist/server/api.d.ts +20 -1
  188. package/dist/server/api.js +306 -71
  189. package/dist/server/git-clone.d.ts +2 -1
  190. package/dist/server/git-clone.js +98 -5
  191. package/dist/storage/git.d.ts +13 -0
  192. package/dist/storage/git.js +25 -0
  193. package/dist/storage/repo-manager.js +1 -1
  194. package/package.json +8 -2
  195. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  196. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  197. package/dist/core/ingestion/named-binding-processor.js +0 -42
  198. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  199. package/dist/core/ingestion/resolution-context.js +0 -135
  200. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  201. package/dist/core/ingestion/symbol-table.js +0 -115
@@ -7,13 +7,15 @@
7
7
  */
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
- import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, isWriteQuery, } from '../core/lbug-adapter.js';
10
+ import { initLbug, executeQuery, executeParameterized, closeLbug, isLbugReady, isWriteQuery, } from '../../core/lbug/pool-adapter.js';
11
11
  export { isWriteQuery };
12
12
  // Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
13
13
  // at MCP server startup — crashes on unsupported Node ABI versions (#89)
14
14
  // git utilities available if needed
15
15
  // import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
16
+ import { parseDiffHunks } from '../../storage/git.js';
16
17
  import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
18
+ import { GroupService } from '../../core/group/service.js';
17
19
  // AI context generation is CLI-only (gitnexus analyze)
18
20
  // import { generateAIContextFiles } from '../../cli/ai-context.js';
19
21
  /**
@@ -78,7 +80,9 @@ export const VALID_RELATION_TYPES = new Set([
78
80
  'IMPLEMENTS',
79
81
  'HAS_METHOD',
80
82
  'HAS_PROPERTY',
81
- 'OVERRIDES',
83
+ 'METHOD_OVERRIDES',
84
+ 'OVERRIDES', // Legacy alias — dual-read for pre-rename indexes
85
+ 'METHOD_IMPLEMENTS',
82
86
  'ACCESSES',
83
87
  'HANDLES_ROUTE',
84
88
  'FETCHES',
@@ -98,7 +102,8 @@ export const VALID_RELATION_TYPES = new Set([
98
102
  * CALLS / IMPORTS – direct, strongly-typed references → 0.9
99
103
  * EXTENDS – class hierarchy, statically verifiable → 0.85
100
104
  * IMPLEMENTS – interface contract, statically verifiable → 0.85
101
- * OVERRIDES – method override, statically verifiable → 0.85
105
+ * METHOD_OVERRIDES – method override, statically verifiable → 0.85
106
+ * METHOD_IMPLEMENTS – interface method implementation, statically verifiable → 0.85
102
107
  * HAS_METHOD – structural containment → 0.95
103
108
  * HAS_PROPERTY – structural containment → 0.95
104
109
  * ACCESSES – field read/write, may be indirect → 0.8
@@ -110,7 +115,8 @@ export const IMPACT_RELATION_CONFIDENCE = {
110
115
  IMPORTS: 0.9,
111
116
  EXTENDS: 0.85,
112
117
  IMPLEMENTS: 0.85,
113
- OVERRIDES: 0.85,
118
+ METHOD_OVERRIDES: 0.85,
119
+ METHOD_IMPLEMENTS: 0.85,
114
120
  HAS_METHOD: 0.95,
115
121
  HAS_PROPERTY: 0.95,
116
122
  ACCESSES: 0.8,
@@ -132,6 +138,26 @@ export class LocalBackend {
132
138
  initializedRepos = new Set();
133
139
  reinitPromises = new Map();
134
140
  lastStalenessCheck = new Map();
141
+ groupToolSvc = null;
142
+ /**
143
+ * Cross-repo group tools (CLI). Shares logic with MCP `group_*` handlers.
144
+ */
145
+ getGroupService() {
146
+ if (!this.groupToolSvc) {
147
+ const port = {
148
+ resolveRepo: (p) => this.resolveRepo(p),
149
+ impact: (r, p) => this.impact(r, p),
150
+ query: (r, p) => this.query(r, p),
151
+ impactByUid: (id, uid, d, o) => this.impactByUid(id, uid, d, o),
152
+ };
153
+ this.groupToolSvc = new GroupService(port);
154
+ }
155
+ return this.groupToolSvc;
156
+ }
157
+ /** Close all pooled LadybugDB connections (CLI one-shot; optional for long-lived MCP). */
158
+ async dispose() {
159
+ await closeLbug();
160
+ }
135
161
  // ─── Initialization ──────────────────────────────────────────────
136
162
  /**
137
163
  * Initialize from the global registry.
@@ -364,6 +390,9 @@ export class LocalBackend {
364
390
  if (method === 'list_repos') {
365
391
  return this.listRepos();
366
392
  }
393
+ if (method.startsWith('group_')) {
394
+ return this.handleGroupTool(method, params || {});
395
+ }
367
396
  // Resolve repo from optional param (re-reads registry on miss)
368
397
  const repo = await this.resolveRepo(params?.repo);
369
398
  switch (method) {
@@ -974,7 +1003,7 @@ export class LocalBackend {
974
1003
  // Categorized incoming refs
975
1004
  const incomingRows = await executeParameterized(repo.id, `
976
1005
  MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
977
- WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'OVERRIDES', 'ACCESSES']
1006
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
978
1007
  RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
979
1008
  LIMIT 30
980
1009
  `, { symId });
@@ -1045,7 +1074,7 @@ export class LocalBackend {
1045
1074
  // Categorized outgoing refs
1046
1075
  const outgoingRows = await executeParameterized(repo.id, `
1047
1076
  MATCH (n {id: $symId})-[r:CodeRelation]->(target)
1048
- WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'OVERRIDES', 'ACCESSES']
1077
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
1049
1078
  RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
1050
1079
  LIMIT 30
1051
1080
  `, { symId });
@@ -1077,16 +1106,50 @@ export class LocalBackend {
1077
1106
  }
1078
1107
  return cats;
1079
1108
  };
1109
+ // Method/Function/Constructor enrichment: fetch method-specific properties
1110
+ const symKind = isClassLike ? resolvedLabel || 'Class' : sym.type || sym[2];
1111
+ const isMethodLike = symKind === 'Method' || symKind === 'Function' || symKind === 'Constructor';
1112
+ let methodMetadata;
1113
+ if (isMethodLike) {
1114
+ try {
1115
+ const metaRows = await executeParameterized(repo.id, `
1116
+ MATCH (n {id: $symId})
1117
+ RETURN n.visibility AS visibility, n.isStatic AS isStatic, n.isAbstract AS isAbstract,
1118
+ n.isFinal AS isFinal, n.isVirtual AS isVirtual, n.isOverride AS isOverride,
1119
+ n.isAsync AS isAsync, n.isPartial AS isPartial, n.returnType AS returnType,
1120
+ n.parameterCount AS parameterCount, n.isVariadic AS isVariadic,
1121
+ n.requiredParameterCount AS requiredParameterCount,
1122
+ n.parameterTypes AS parameterTypes, n.annotations AS annotations
1123
+ LIMIT 1
1124
+ `, { symId });
1125
+ if (metaRows.length > 0) {
1126
+ const row = metaRows[0];
1127
+ const meta = {};
1128
+ // Only include defined properties to distinguish "not applicable" from "not enriched"
1129
+ for (const key of Object.keys(row)) {
1130
+ const val = row[key];
1131
+ if (val !== null && val !== undefined)
1132
+ meta[key] = val;
1133
+ }
1134
+ if (Object.keys(meta).length > 0)
1135
+ methodMetadata = meta;
1136
+ }
1137
+ }
1138
+ catch {
1139
+ /* method metadata unavailable — omit silently */
1140
+ }
1141
+ }
1080
1142
  return {
1081
1143
  status: 'found',
1082
1144
  symbol: {
1083
1145
  uid: sym.id || sym[0],
1084
1146
  name: sym.name || sym[1],
1085
- kind: isClassLike ? resolvedLabel || 'Class' : sym.type || sym[2],
1147
+ kind: symKind,
1086
1148
  filePath: sym.filePath || sym[3],
1087
1149
  startLine: sym.startLine || sym[4],
1088
1150
  endLine: sym.endLine || sym[5],
1089
1151
  ...(include_content && (sym.content || sym[6]) ? { content: sym.content || sym[6] } : {}),
1152
+ ...(methodMetadata ? { methodMetadata } : {}),
1090
1153
  },
1091
1154
  incoming: categorize(incomingRows),
1092
1155
  outgoing: categorize(outgoingRows),
@@ -1197,33 +1260,30 @@ export class LocalBackend {
1197
1260
  let diffArgs;
1198
1261
  switch (scope) {
1199
1262
  case 'staged':
1200
- diffArgs = ['diff', '--staged', '--name-only'];
1263
+ diffArgs = ['diff', '--staged', '-U0'];
1201
1264
  break;
1202
1265
  case 'all':
1203
- diffArgs = ['diff', 'HEAD', '--name-only'];
1266
+ diffArgs = ['diff', 'HEAD', '-U0'];
1204
1267
  break;
1205
1268
  case 'compare':
1206
1269
  if (!params.base_ref)
1207
1270
  return { error: 'base_ref is required for "compare" scope' };
1208
- diffArgs = ['diff', params.base_ref, '--name-only'];
1271
+ diffArgs = ['diff', params.base_ref, '-U0'];
1209
1272
  break;
1210
1273
  case 'unstaged':
1211
1274
  default:
1212
- diffArgs = ['diff', '--name-only'];
1275
+ diffArgs = ['diff', '-U0'];
1213
1276
  break;
1214
1277
  }
1215
- let changedFiles;
1278
+ let diffOutput;
1216
1279
  try {
1217
- const output = execFileSync('git', diffArgs, { cwd: repo.repoPath, encoding: 'utf-8' });
1218
- changedFiles = output
1219
- .trim()
1220
- .split('\n')
1221
- .filter((f) => f.length > 0);
1280
+ diffOutput = execFileSync('git', diffArgs, { cwd: repo.repoPath, encoding: 'utf-8' });
1222
1281
  }
1223
1282
  catch (err) {
1224
1283
  return { error: `Git diff failed: ${err.message}` };
1225
1284
  }
1226
- if (changedFiles.length === 0) {
1285
+ const fileDiffs = parseDiffHunks(diffOutput);
1286
+ if (fileDiffs.length === 0) {
1227
1287
  return {
1228
1288
  summary: {
1229
1289
  changed_count: 0,
@@ -1235,23 +1295,36 @@ export class LocalBackend {
1235
1295
  affected_processes: [],
1236
1296
  };
1237
1297
  }
1238
- // Map changed files to indexed symbols
1298
+ // Map diff hunks to indexed symbols via range overlap
1239
1299
  const changedSymbols = [];
1240
- for (const file of changedFiles) {
1241
- const normalizedFile = file.replace(/\\/g, '/');
1300
+ for (const fileDiff of fileDiffs) {
1301
+ if (fileDiff.hunks.length === 0)
1302
+ continue;
1303
+ // Build range overlap conditions for all hunks in this file
1304
+ const overlapConditions = fileDiff.hunks
1305
+ .map((_, i) => `(n.startLine <= $hunkEnd${i} AND n.endLine >= $hunkStart${i})`)
1306
+ .join(' OR ');
1307
+ const queryParams = { filePath: fileDiff.filePath };
1308
+ fileDiff.hunks.forEach((hunk, i) => {
1309
+ queryParams[`hunkStart${i}`] = hunk.startLine;
1310
+ queryParams[`hunkEnd${i}`] = hunk.endLine;
1311
+ });
1312
+ const symbolQuery = `
1313
+ MATCH (n) WHERE n.filePath ENDS WITH $filePath
1314
+ AND n.startLine IS NOT NULL AND n.endLine IS NOT NULL
1315
+ AND (${overlapConditions})
1316
+ RETURN n.id AS id, n.name AS name, labels(n)[0] AS type,
1317
+ n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
1318
+ `;
1242
1319
  try {
1243
- const symbols = await executeParameterized(repo.id, `
1244
- MATCH (n) WHERE n.filePath CONTAINS $filePath
1245
- RETURN n.id AS id, n.name AS name, labels(n)[0] AS type, n.filePath AS filePath
1246
- LIMIT 20
1247
- `, { filePath: normalizedFile });
1248
- for (const sym of symbols) {
1320
+ const rows = await executeParameterized(repo.id, symbolQuery, queryParams);
1321
+ for (const sym of rows) {
1249
1322
  changedSymbols.push({
1250
1323
  id: sym.id || sym[0],
1251
1324
  name: sym.name || sym[1],
1252
1325
  type: sym.type || sym[2],
1253
1326
  filePath: sym.filePath || sym[3],
1254
- change_type: 'Modified',
1327
+ change_type: 'touched',
1255
1328
  });
1256
1329
  }
1257
1330
  }
@@ -1259,28 +1332,33 @@ export class LocalBackend {
1259
1332
  logQueryError('detect-changes:file-symbols', e);
1260
1333
  }
1261
1334
  }
1262
- // Find affected processes
1335
+ // Find affected processes -- single batched query instead of N+1
1263
1336
  const affectedProcesses = new Map();
1264
- for (const sym of changedSymbols) {
1337
+ if (changedSymbols.length > 0) {
1338
+ const symIds = changedSymbols.map((s) => s.id);
1339
+ const symNameById = new Map(changedSymbols.map((s) => [s.id, s.name]));
1265
1340
  try {
1266
1341
  const procs = await executeParameterized(repo.id, `
1267
- MATCH (n {id: $nodeId})-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
1268
- RETURN p.id AS pid, p.heuristicLabel AS label, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
1269
- `, { nodeId: sym.id });
1342
+ MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
1343
+ WHERE n.id IN $ids
1344
+ RETURN n.id AS nodeId, p.id AS pid, p.heuristicLabel AS label,
1345
+ p.processType AS processType, p.stepCount AS stepCount, r.step AS step
1346
+ `, { ids: symIds });
1270
1347
  for (const proc of procs) {
1271
- const pid = proc.pid || proc[0];
1348
+ const nodeId = proc.nodeId || proc[0];
1349
+ const pid = proc.pid || proc[1];
1272
1350
  if (!affectedProcesses.has(pid)) {
1273
1351
  affectedProcesses.set(pid, {
1274
1352
  id: pid,
1275
- name: proc.label || proc[1],
1276
- process_type: proc.processType || proc[2],
1277
- step_count: proc.stepCount || proc[3],
1353
+ name: proc.label || proc[2],
1354
+ process_type: proc.processType || proc[3],
1355
+ step_count: proc.stepCount || proc[4],
1278
1356
  changed_steps: [],
1279
1357
  });
1280
1358
  }
1281
1359
  affectedProcesses.get(pid).changed_steps.push({
1282
- symbol: sym.name,
1283
- step: proc.step || proc[4],
1360
+ symbol: symNameById.get(nodeId) ?? nodeId,
1361
+ step: proc.step || proc[5],
1284
1362
  });
1285
1363
  }
1286
1364
  }
@@ -1300,7 +1378,7 @@ export class LocalBackend {
1300
1378
  summary: {
1301
1379
  changed_count: changedSymbols.length,
1302
1380
  affected_count: processCount,
1303
- changed_files: changedFiles.length,
1381
+ changed_files: fileDiffs.length,
1304
1382
  risk_level: risk,
1305
1383
  },
1306
1384
  changed_symbols: changedSymbols,
@@ -1494,14 +1572,32 @@ export class LocalBackend {
1494
1572
  await this.ensureInitialized(repo.id);
1495
1573
  const { target, direction } = params;
1496
1574
  const maxDepth = params.maxDepth || 3;
1497
- const rawRelTypes = params.relationTypes && params.relationTypes.length > 0
1498
- ? params.relationTypes.filter((t) => VALID_RELATION_TYPES.has(t))
1499
- : ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS'];
1500
- const relationTypes = rawRelTypes.length > 0 ? rawRelTypes : ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS'];
1575
+ // Map legacy relation type names before filtering (backward compat for OVERRIDES → METHOD_OVERRIDES)
1576
+ const mappedRelTypes = params.relationTypes?.flatMap((t) => t === 'OVERRIDES' ? ['OVERRIDES', 'METHOD_OVERRIDES'] : [t]);
1577
+ const rawRelTypes = mappedRelTypes && mappedRelTypes.length > 0
1578
+ ? mappedRelTypes.filter((t) => VALID_RELATION_TYPES.has(t))
1579
+ : [
1580
+ 'CALLS',
1581
+ 'IMPORTS',
1582
+ 'EXTENDS',
1583
+ 'IMPLEMENTS',
1584
+ 'METHOD_OVERRIDES',
1585
+ 'OVERRIDES',
1586
+ 'METHOD_IMPLEMENTS',
1587
+ ];
1588
+ const relationTypes = rawRelTypes.length > 0
1589
+ ? rawRelTypes
1590
+ : [
1591
+ 'CALLS',
1592
+ 'IMPORTS',
1593
+ 'EXTENDS',
1594
+ 'IMPLEMENTS',
1595
+ 'METHOD_OVERRIDES',
1596
+ 'OVERRIDES',
1597
+ 'METHOD_IMPLEMENTS',
1598
+ ];
1501
1599
  const includeTests = params.includeTests ?? false;
1502
1600
  const minConfidence = params.minConfidence ?? 0;
1503
- const relTypeFilter = relationTypes.map((t) => `'${t}'`).join(', ');
1504
- const confidenceFilter = minConfidence > 0 ? ` AND r.confidence >= ${minConfidence}` : '';
1505
1601
  // Resolve target by name, preferring Class/Interface over Constructor
1506
1602
  // (fix #480: Java class and constructor share the same name).
1507
1603
  // labels(n)[0] returns empty string in LadybugDB, so we use explicit
@@ -1550,6 +1646,20 @@ export class LocalBackend {
1550
1646
  }
1551
1647
  if (!sym)
1552
1648
  return { error: `Target '${target}' not found` };
1649
+ return this._runImpactBFS(repo, sym, symType, direction, {
1650
+ maxDepth,
1651
+ relationTypes,
1652
+ includeTests,
1653
+ minConfidence,
1654
+ });
1655
+ }
1656
+ /**
1657
+ * Shared BFS traversal for impact analysis (name-resolved or UID-resolved symbol).
1658
+ */
1659
+ async _runImpactBFS(repo, sym, symType, direction, opts) {
1660
+ const { maxDepth, relationTypes, includeTests, minConfidence } = opts;
1661
+ const relTypeFilter = relationTypes.map((t) => `'${t}'`).join(', ');
1662
+ const confidenceFilter = minConfidence > 0 ? ` AND r.confidence >= ${minConfidence}` : '';
1553
1663
  const symId = sym.id || sym[0];
1554
1664
  const impacted = [];
1555
1665
  const visited = new Set([symId]);
@@ -1891,6 +2001,105 @@ export class LocalBackend {
1891
2001
  byDepth: grouped,
1892
2002
  };
1893
2003
  }
2004
+ /**
2005
+ * UID-based impact for cross-repo fan-out. Same result shape as `impact`.
2006
+ * Returns null if the repo is unknown, the UID is missing, or analysis fails.
2007
+ */
2008
+ async impactByUid(repoId, uid, direction, opts) {
2009
+ try {
2010
+ await this.refreshRepos();
2011
+ await this.ensureInitialized(repoId);
2012
+ }
2013
+ catch {
2014
+ return null;
2015
+ }
2016
+ const repo = this.repos.get(repoId);
2017
+ if (!repo)
2018
+ return null;
2019
+ const dir = direction === 'downstream' ? 'downstream' : 'upstream';
2020
+ let rows;
2021
+ try {
2022
+ rows = await executeParameterized(repoId, `MATCH (n) WHERE n.id = $uid
2023
+ RETURN n.id AS id, n.name AS name, n.filePath AS filePath, labels(n)[0] AS type
2024
+ LIMIT 1`, { uid });
2025
+ }
2026
+ catch {
2027
+ return null;
2028
+ }
2029
+ if (!rows?.length)
2030
+ return null;
2031
+ const sym = rows[0];
2032
+ const labelRaw = sym.type ?? sym[3];
2033
+ const symType = typeof labelRaw === 'string' && labelRaw.trim().length > 0 ? labelRaw.trim() : '';
2034
+ // Map legacy relation type names (backward compat for OVERRIDES → METHOD_OVERRIDES)
2035
+ const mappedRelTypes = opts.relationTypes?.flatMap((t) => t === 'OVERRIDES' ? ['OVERRIDES', 'METHOD_OVERRIDES'] : [t]);
2036
+ const rawRelTypes = mappedRelTypes && mappedRelTypes.length > 0
2037
+ ? mappedRelTypes.filter((t) => VALID_RELATION_TYPES.has(t))
2038
+ : [
2039
+ 'CALLS',
2040
+ 'IMPORTS',
2041
+ 'EXTENDS',
2042
+ 'IMPLEMENTS',
2043
+ 'METHOD_OVERRIDES',
2044
+ 'OVERRIDES',
2045
+ 'METHOD_IMPLEMENTS',
2046
+ ];
2047
+ const relationTypes = rawRelTypes.length > 0
2048
+ ? rawRelTypes
2049
+ : [
2050
+ 'CALLS',
2051
+ 'IMPORTS',
2052
+ 'EXTENDS',
2053
+ 'IMPLEMENTS',
2054
+ 'METHOD_OVERRIDES',
2055
+ 'OVERRIDES',
2056
+ 'METHOD_IMPLEMENTS',
2057
+ ];
2058
+ try {
2059
+ return await this._runImpactBFS(repo, sym, symType, dir, {
2060
+ maxDepth: opts.maxDepth,
2061
+ relationTypes,
2062
+ includeTests: opts.includeTests,
2063
+ minConfidence: opts.minConfidence,
2064
+ });
2065
+ }
2066
+ catch {
2067
+ return null;
2068
+ }
2069
+ }
2070
+ handleGroupTool(method, params) {
2071
+ switch (method) {
2072
+ case 'group_list':
2073
+ return this.groupList(params);
2074
+ case 'group_sync':
2075
+ return this.groupSync(params);
2076
+ case 'group_contracts':
2077
+ return this.groupContracts(params);
2078
+ case 'group_query':
2079
+ return this.groupQuery(params);
2080
+ case 'group_status':
2081
+ return this.groupStatus(params);
2082
+ default:
2083
+ throw new Error(`Unknown group tool: ${method}`);
2084
+ }
2085
+ }
2086
+ async groupList(params) {
2087
+ return this.getGroupService().groupList(params);
2088
+ }
2089
+ async groupSync(params) {
2090
+ return this.getGroupService().groupSync(params);
2091
+ }
2092
+ async groupContracts(params) {
2093
+ return this.getGroupService().groupContracts(params);
2094
+ }
2095
+ async groupQuery(params) {
2096
+ await this.refreshRepos();
2097
+ return this.getGroupService().groupQuery(params);
2098
+ }
2099
+ async groupStatus(params) {
2100
+ await this.refreshRepos();
2101
+ return this.getGroupService().groupStatus(params);
2102
+ }
1894
2103
  /**
1895
2104
  * Fetch Route nodes with their consumers in a single query.
1896
2105
  * Shared by routeMap and shapeCheck to avoid N+1 query patterns.
@@ -281,10 +281,10 @@ additional_node_types: "Multi-language: Struct, Enum, Macro, Typedef, Union, Nam
281
281
 
282
282
  node_properties:
283
283
  common: "name (STRING), filePath (STRING), startLine (INT32), endLine (INT32)"
284
- Method: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL)"
285
- Function: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL)"
284
+ Method: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL), visibility (STRING), isStatic (BOOL), isAbstract (BOOL), isFinal (BOOL), isVirtual (BOOL), isOverride (BOOL), isAsync (BOOL), isPartial (BOOL), requiredParameterCount (INT32), parameterTypes (STRING[]), annotations (STRING[])"
285
+ Function: "parameterCount (INT32), returnType (STRING), isVariadic (BOOL), visibility (STRING), isStatic (BOOL), isAbstract (BOOL), isFinal (BOOL), isAsync (BOOL), parameterTypes (STRING[]), annotations (STRING[])"
286
286
  Property: "declaredType (STRING) — the field's type annotation (e.g., 'Address', 'City'). Used for field-access chain resolution."
287
- Constructor: "parameterCount (INT32)"
287
+ Constructor: "parameterCount (INT32), visibility (STRING), isStatic (BOOL), parameterTypes (STRING[])"
288
288
  Community: "heuristicLabel (STRING), cohesion (DOUBLE), symbolCount (INT32), keywords (STRING[]), description (STRING), enrichedBy (STRING)"
289
289
  Process: "heuristicLabel (STRING), processType (STRING — 'intra_community' or 'cross_community'), stepCount (INT32), communities (STRING[]), entryPointId (STRING), terminalId (STRING)"
290
290
 
@@ -298,7 +298,8 @@ relationships:
298
298
  - HAS_METHOD: Class/Struct/Interface owns a Method
299
299
  - HAS_PROPERTY: Class/Struct/Interface owns a Property (field)
300
300
  - ACCESSES: Function/Method reads or writes a Property (reason: 'read' or 'write')
301
- - OVERRIDES: Method overrides another Method (MRO)
301
+ - METHOD_OVERRIDES: Method overrides another Method (MRO)
302
+ - METHOD_IMPLEMENTS: ConcreteMethod implements InterfaceMethod (matched by name + parameterTypes)
302
303
  - MEMBER_OF: Symbol belongs to community
303
304
  - STEP_IN_PROCESS: Symbol is step N in process
304
305
 
@@ -1,15 +1,5 @@
1
1
  /**
2
- * Staleness Check
3
- *
4
- * Checks if the GitNexus index is behind the current git HEAD.
5
- * Returns a hint for the LLM to call analyze if stale.
2
+ * Staleness Check — re-export from core (see `core/git-staleness.ts`).
6
3
  */
7
- export interface StalenessInfo {
8
- isStale: boolean;
9
- commitsBehind: number;
10
- hint?: string;
11
- }
12
- /**
13
- * Check how many commits the index is behind HEAD
14
- */
15
- export declare function checkStaleness(repoPath: string, lastCommit: string): StalenessInfo;
4
+ export type { StalenessInfo } from '../core/git-staleness.js';
5
+ export { checkStaleness } from '../core/git-staleness.js';
@@ -1,33 +1,4 @@
1
1
  /**
2
- * Staleness Check
3
- *
4
- * Checks if the GitNexus index is behind the current git HEAD.
5
- * Returns a hint for the LLM to call analyze if stale.
2
+ * Staleness Check — re-export from core (see `core/git-staleness.ts`).
6
3
  */
7
- import { execFileSync } from 'child_process';
8
- /**
9
- * Check how many commits the index is behind HEAD
10
- */
11
- export function checkStaleness(repoPath, lastCommit) {
12
- try {
13
- // Get count of commits between lastCommit and HEAD
14
- const result = execFileSync('git', ['rev-list', '--count', `${lastCommit}..HEAD`], {
15
- cwd: repoPath,
16
- encoding: 'utf-8',
17
- stdio: ['pipe', 'pipe', 'pipe'],
18
- }).trim();
19
- const commitsBehind = parseInt(result, 10) || 0;
20
- if (commitsBehind > 0) {
21
- return {
22
- isStale: true,
23
- commitsBehind,
24
- hint: `⚠️ Index is ${commitsBehind} commit${commitsBehind > 1 ? 's' : ''} behind HEAD. Run analyze tool to update.`,
25
- };
26
- }
27
- return { isStale: false, commitsBehind: 0 };
28
- }
29
- catch {
30
- // If git command fails, assume not stale (fail open)
31
- return { isStale: false, commitsBehind: 0 };
32
- }
33
- }
4
+ export { checkStaleness } from '../core/git-staleness.js';
package/dist/mcp/tools.js CHANGED
@@ -78,7 +78,7 @@ SCHEMA:
78
78
  - Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process, Route, Tool
79
79
  - Multi-language nodes (use backticks): \`Struct\`, \`Enum\`, \`Trait\`, \`Impl\`, etc.
80
80
  - All edges via single CodeRelation table with 'type' property
81
- - Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, OVERRIDES, MEMBER_OF, STEP_IN_PROCESS, HANDLES_ROUTE, FETCHES, HANDLES_TOOL, ENTRY_POINT_OF
81
+ - Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, METHOD_OVERRIDES, METHOD_IMPLEMENTS, MEMBER_OF, STEP_IN_PROCESS, HANDLES_ROUTE, FETCHES, HANDLES_TOOL, ENTRY_POINT_OF
82
82
  - Edge properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)
83
83
 
84
84
  EXAMPLES:
@@ -101,7 +101,7 @@ EXAMPLES:
101
101
  MATCH (f:Function)-[r:CodeRelation {type: 'ACCESSES', reason: 'write'}]->(p:Property) WHERE p.name = "address" RETURN f.name, f.filePath
102
102
 
103
103
  • Find method overrides (MRO resolution):
104
- MATCH (winner:Method)-[r:CodeRelation {type: 'OVERRIDES'}]->(loser:Method) RETURN winner.name, winner.filePath, loser.filePath, r.reason
104
+ MATCH (winner:Method)-[r:CodeRelation {type: 'METHOD_OVERRIDES'}]->(loser:Method) RETURN winner.name, winner.filePath, loser.filePath, r.reason
105
105
 
106
106
  • Detect diamond inheritance:
107
107
  MATCH (d:Class)-[:CodeRelation {type: 'EXTENDS'}]->(b1), (d)-[:CodeRelation {type: 'EXTENDS'}]->(b2), (b1)-[:CodeRelation {type: 'EXTENDS'}]->(a), (b2)-[:CodeRelation {type: 'EXTENDS'}]->(a) WHERE b1 <> b2 RETURN d.name, b1.name, b2.name, a.name
@@ -244,7 +244,7 @@ Depth groups:
244
244
 
245
245
  TIP: Default traversal uses CALLS/IMPORTS/EXTENDS/IMPLEMENTS. For class members, include HAS_METHOD and HAS_PROPERTY in relationTypes. For field access analysis, include ACCESSES in relationTypes.
246
246
 
247
- EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, OVERRIDES, ACCESSES
247
+ EdgeType: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES
248
248
  Confidence: 1.0 = certain, <0.8 = fuzzy match`,
249
249
  inputSchema: {
250
250
  type: 'object',
@@ -262,7 +262,7 @@ Confidence: 1.0 = certain, <0.8 = fuzzy match`,
262
262
  relationTypes: {
263
263
  type: 'array',
264
264
  items: { type: 'string' },
265
- description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, OVERRIDES, ACCESSES (default: usage-based, ACCESSES excluded by default)',
265
+ description: 'Filter: CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, METHOD_OVERRIDES, METHOD_IMPLEMENTS, ACCESSES (default: usage-based, ACCESSES excluded by default)',
266
266
  },
267
267
  includeTests: { type: 'boolean', description: 'Include test files (default: false)' },
268
268
  minConfidence: { type: 'number', description: 'Minimum confidence 0-1 (default: 0.7)' },
@@ -355,4 +355,80 @@ Returns: single route object when one match, or { routes: [...], total: N } for
355
355
  required: [],
356
356
  },
357
357
  },
358
+ {
359
+ name: 'group_list',
360
+ description: `List all configured repository groups, or return details for one group (repos, manifest links).
361
+
362
+ WHEN TO USE: Discover groups before group_sync. Optional "name" returns a single group's config.`,
363
+ inputSchema: {
364
+ type: 'object',
365
+ properties: {
366
+ name: { type: 'string', description: 'Group name. Omit to list all groups.' },
367
+ },
368
+ required: [],
369
+ },
370
+ },
371
+ {
372
+ name: 'group_sync',
373
+ description: `Rebuild the Contract Registry (contracts.json) for a group: extract HTTP contracts, apply manifest links, exact-match cross-links.
374
+
375
+ WHEN TO USE: After changing group.yaml or re-indexing member repos.`,
376
+ inputSchema: {
377
+ type: 'object',
378
+ properties: {
379
+ name: { type: 'string', description: 'Group name' },
380
+ skipEmbeddings: {
381
+ type: 'boolean',
382
+ description: 'Exact + BM25 only (Demo PR: same as default exact path)',
383
+ },
384
+ exactOnly: { type: 'boolean', description: 'Exact match only in cascade' },
385
+ },
386
+ required: ['name'],
387
+ },
388
+ },
389
+ {
390
+ name: 'group_contracts',
391
+ description: `Inspect contracts and cross-links from the group's contracts.json.
392
+
393
+ WHEN TO USE: Debug cross-repo links after group_sync.`,
394
+ inputSchema: {
395
+ type: 'object',
396
+ properties: {
397
+ name: { type: 'string', description: 'Group name' },
398
+ type: { type: 'string', description: 'Filter by contract type (http, topic, …)' },
399
+ repo: { type: 'string', description: 'Filter by group repo path (e.g. app/backend)' },
400
+ unmatchedOnly: { type: 'boolean', description: 'Only contracts with no cross-link' },
401
+ },
402
+ required: ['name'],
403
+ },
404
+ },
405
+ {
406
+ name: 'group_query',
407
+ description: `Run the query tool across all repos in a group and merge process results via reciprocal rank fusion.
408
+
409
+ WHEN TO USE: Semantic / hybrid search across a whole product group.`,
410
+ inputSchema: {
411
+ type: 'object',
412
+ properties: {
413
+ name: { type: 'string', description: 'Group name' },
414
+ query: { type: 'string', description: 'Search query' },
415
+ subgroup: { type: 'string', description: 'Limit to repo paths under this prefix' },
416
+ limit: { type: 'number', description: 'Max merged results (default 5)' },
417
+ },
418
+ required: ['name', 'query'],
419
+ },
420
+ },
421
+ {
422
+ name: 'group_status',
423
+ description: `Report index staleness (commit vs HEAD) and Contract Registry staleness (indexedAt) for each repo in a group.
424
+
425
+ WHEN TO USE: Before group_sync or when agents should refresh indexes.`,
426
+ inputSchema: {
427
+ type: 'object',
428
+ properties: {
429
+ name: { type: 'string', description: 'Group name' },
430
+ },
431
+ required: ['name'],
432
+ },
433
+ },
358
434
  ];
@@ -40,6 +40,8 @@ export declare class JobManager {
40
40
  repoPath?: string;
41
41
  }): AnalyzeJob;
42
42
  getJob(id: string): AnalyzeJob | undefined;
43
+ /** Return a snapshot of all tracked jobs for inspection. */
44
+ listJobs(): AnalyzeJob[];
43
45
  updateJob(id: string, update: Partial<Pick<AnalyzeJob, 'status' | 'progress' | 'error' | 'repoPath' | 'repoName' | 'completedAt'>>): void;
44
46
  /** Register a child process for a job — enables cancellation and timeout. */
45
47
  registerChild(jobId: string, child: ChildProcess): void;