gitnexus 1.6.3-rc.21 → 1.6.3-rc.23

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.
@@ -16,6 +16,7 @@ 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';
@@ -171,6 +172,7 @@ export class LocalBackend {
171
172
  impact: (r, p) => this.impact(r, p),
172
173
  query: (r, p) => this.query(r, p),
173
174
  impactByUid: (id, uid, d, o) => this.impactByUid(id, uid, d, o),
175
+ context: (r, p) => this.context(r, p),
174
176
  };
175
177
  this.groupToolSvc = new GroupService(port);
176
178
  }
@@ -423,6 +425,12 @@ export class LocalBackend {
423
425
  if (method.startsWith('group_')) {
424
426
  return this.handleGroupTool(method, params || {});
425
427
  }
428
+ const p = params && typeof params === 'object' ? params : {};
429
+ if ((method === 'impact' || method === 'query' || method === 'context') &&
430
+ typeof p.repo === 'string' &&
431
+ p.repo.startsWith('@')) {
432
+ return this.callToolAtGroupRepo(method, p);
433
+ }
426
434
  // Resolve repo from optional param (re-reads registry on miss)
427
435
  const repo = await this.resolveRepo(params?.repo);
428
436
  switch (method) {
@@ -2301,32 +2309,150 @@ export class LocalBackend {
2301
2309
  return this.groupList(params);
2302
2310
  case 'group_sync':
2303
2311
  return this.groupSync(params);
2304
- case 'group_contracts':
2305
- return this.groupContracts(params);
2306
- case 'group_query':
2307
- return this.groupQuery(params);
2308
- case 'group_status':
2309
- return this.groupStatus(params);
2310
2312
  default:
2311
- throw new Error(`Unknown group tool: ${method}`);
2313
+ throw new Error(`Unknown group tool: ${method}. Removed tools: use repo "@<groupName>" on impact, query, or context (optional "/<memberPath>"), or MCP resources.`);
2312
2314
  }
2313
2315
  }
2316
+ /**
2317
+ * Dispatch impact/query/context when `repo` is `@groupName` or `@groupName/memberPath`
2318
+ * (group mode — not the global indexed-repo `repo` parameter).
2319
+ */
2320
+ async callToolAtGroupRepo(method, params) {
2321
+ await this.refreshRepos();
2322
+ if (params.service !== undefined &&
2323
+ params.service !== null &&
2324
+ String(params.service).trim() === '') {
2325
+ return { error: 'service must not be an empty string' };
2326
+ }
2327
+ const raw = String(params.repo).slice(1);
2328
+ const slash = raw.indexOf('/');
2329
+ const groupName = (slash === -1 ? raw : raw.slice(0, slash)).trim();
2330
+ const memberRest = slash === -1 ? undefined : raw.slice(slash + 1).trim() || undefined;
2331
+ const resolved = await resolveAtGroupMemberRepoPath(groupName, memberRest);
2332
+ if (resolved.ok === false)
2333
+ return { error: resolved.error };
2334
+ const svc = this.getGroupService();
2335
+ if (method === 'impact') {
2336
+ const impactArgs = {
2337
+ name: groupName,
2338
+ repo: resolved.repoPath,
2339
+ target: params.target,
2340
+ direction: params.direction,
2341
+ };
2342
+ if (params.maxDepth !== undefined)
2343
+ impactArgs.maxDepth = params.maxDepth;
2344
+ if (params.crossDepth !== undefined)
2345
+ impactArgs.crossDepth = params.crossDepth;
2346
+ if (params.relationTypes !== undefined)
2347
+ impactArgs.relationTypes = params.relationTypes;
2348
+ if (params.includeTests !== undefined)
2349
+ impactArgs.includeTests = params.includeTests;
2350
+ if (params.minConfidence !== undefined)
2351
+ impactArgs.minConfidence = params.minConfidence;
2352
+ if (params.service !== undefined && params.service !== null)
2353
+ impactArgs.service = params.service;
2354
+ if (typeof params.subgroup === 'string')
2355
+ impactArgs.subgroup = params.subgroup;
2356
+ if (params.timeoutMs !== undefined)
2357
+ impactArgs.timeoutMs = params.timeoutMs;
2358
+ if (params.timeout !== undefined)
2359
+ impactArgs.timeout = params.timeout;
2360
+ return svc.groupImpact(impactArgs);
2361
+ }
2362
+ if (method === 'query') {
2363
+ const queryArgs = {
2364
+ name: groupName,
2365
+ query: params.query,
2366
+ };
2367
+ if (typeof params.task_context === 'string')
2368
+ queryArgs.task_context = params.task_context;
2369
+ if (typeof params.goal === 'string')
2370
+ queryArgs.goal = params.goal;
2371
+ if (typeof params.limit === 'number')
2372
+ queryArgs.limit = params.limit;
2373
+ if (typeof params.max_symbols === 'number')
2374
+ queryArgs.max_symbols = params.max_symbols;
2375
+ if (params.include_content !== undefined)
2376
+ queryArgs.include_content = params.include_content;
2377
+ if (params.service !== undefined && params.service !== null)
2378
+ queryArgs.service = params.service;
2379
+ if (memberRest !== undefined) {
2380
+ queryArgs.subgroup = memberRest;
2381
+ queryArgs.subgroupExact = true;
2382
+ }
2383
+ return svc.groupQuery(queryArgs);
2384
+ }
2385
+ if (method === 'context') {
2386
+ const targetSym = typeof params.target === 'string' && params.target.trim() !== ''
2387
+ ? params.target.trim()
2388
+ : typeof params.name === 'string' && params.name.trim() !== ''
2389
+ ? params.name.trim()
2390
+ : undefined;
2391
+ const contextArgs = {
2392
+ name: groupName,
2393
+ target: targetSym,
2394
+ };
2395
+ if (typeof params.uid === 'string')
2396
+ contextArgs.uid = params.uid;
2397
+ if (typeof params.file_path === 'string')
2398
+ contextArgs.file_path = params.file_path;
2399
+ if (params.include_content !== undefined)
2400
+ contextArgs.include_content = params.include_content;
2401
+ if (params.service !== undefined && params.service !== null)
2402
+ contextArgs.service = params.service;
2403
+ if (memberRest !== undefined) {
2404
+ contextArgs.subgroup = memberRest;
2405
+ contextArgs.subgroupExact = true;
2406
+ }
2407
+ return svc.groupContext(contextArgs);
2408
+ }
2409
+ throw new Error(`Internal: unsupported group-repo tool ${method}`);
2410
+ }
2314
2411
  async groupList(params) {
2315
2412
  return this.getGroupService().groupList(params);
2316
2413
  }
2317
2414
  async groupSync(params) {
2318
2415
  return this.getGroupService().groupSync(params);
2319
2416
  }
2320
- async groupContracts(params) {
2321
- return this.getGroupService().groupContracts(params);
2417
+ /**
2418
+ * MCP resource body for `gitnexus://group/{name}/contracts` (Issue #794).
2419
+ */
2420
+ async readGroupContractsResource(groupName, filter) {
2421
+ try {
2422
+ const params = { name: groupName };
2423
+ if (filter.type !== undefined)
2424
+ params.type = filter.type;
2425
+ if (filter.repo !== undefined)
2426
+ params.repo = filter.repo;
2427
+ if (filter.unmatchedOnly === true)
2428
+ params.unmatchedOnly = true;
2429
+ const raw = await this.getGroupService().groupContracts(params);
2430
+ return LocalBackend.formatGroupResourcePayload(raw);
2431
+ }
2432
+ catch (e) {
2433
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
2434
+ }
2322
2435
  }
2323
- async groupQuery(params) {
2324
- await this.refreshRepos();
2325
- return this.getGroupService().groupQuery(params);
2436
+ /**
2437
+ * MCP resource body for `gitnexus://group/{name}/status` (Issue #794).
2438
+ */
2439
+ async readGroupStatusResource(groupName) {
2440
+ try {
2441
+ const raw = await this.getGroupService().groupStatus({ name: groupName });
2442
+ return LocalBackend.formatGroupResourcePayload(raw);
2443
+ }
2444
+ catch (e) {
2445
+ return `error: ${e instanceof Error ? e.message : String(e)}`;
2446
+ }
2326
2447
  }
2327
- async groupStatus(params) {
2328
- await this.refreshRepos();
2329
- return this.getGroupService().groupStatus(params);
2448
+ static formatGroupResourcePayload(raw) {
2449
+ if (raw && typeof raw === 'object' && 'error' in raw) {
2450
+ const err = raw.error;
2451
+ if (typeof err === 'string' && err.length > 0) {
2452
+ return `error: ${err}`;
2453
+ }
2454
+ }
2455
+ return JSON.stringify(raw, null, 2);
2330
2456
  }
2331
2457
  /**
2332
2458
  * 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
  */
@@ -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
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.3-rc.21",
3
+ "version": "1.6.3-rc.23",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",