gitnexus 1.6.11-rc.8 → 1.6.11-rc.9
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.
- package/README.md +1 -1
- package/dist/mcp/local/local-backend.d.ts +24 -7
- package/dist/mcp/local/local-backend.js +78 -31
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +2 -1
- package/dist/mcp/server.js +6 -5
- package/dist/mcp/tools.js +26 -16
- package/package.json +1 -1
- package/scripts/cross-platform-tests.ts +3 -0
package/README.md
CHANGED
|
@@ -204,7 +204,7 @@ Your AI agent gets **17 tools** (15 per-repo + 2 group) automatically:
|
|
|
204
204
|
| `group_list` | List configured repository groups |
|
|
205
205
|
| `group_sync` | Rebuild a group's Contract Registry and cross-repo links |
|
|
206
206
|
|
|
207
|
-
>
|
|
207
|
+
> Read-only tools can omit `repo` when one repo is indexed, an MCP default is configured, or the GitNexus process cwd is inside a registered path without crossing into an unindexed nested Git checkout. Otherwise—and for mutating tools with multiple indexed repos and no MCP default—specify it explicitly: `query({search_query: "auth", repo: "my-app"})`. Per-repo tools also take an optional `branch` for indexes pinned with `gitnexus analyze --branch`; omitting it queries the workspace index, which follows your checked-out working tree. `explain` and `pdg_query` need an index built with `gitnexus analyze --pdg`.
|
|
208
208
|
|
|
209
209
|
## MCP Resources
|
|
210
210
|
|
|
@@ -371,10 +371,26 @@ export declare class LocalBackend {
|
|
|
371
371
|
* - If only 1 repo, use it
|
|
372
372
|
* - If 0 or multiple without param, throw with helpful message
|
|
373
373
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
374
|
+
* Re-reads the registry before an omitted implicit target or after an
|
|
375
|
+
* explicit miss, so long-running servers see newly indexed repositories.
|
|
376
376
|
*/
|
|
377
377
|
resolveRepo(repoParam?: string, branch?: string): Promise<RepoHandle>;
|
|
378
|
+
/**
|
|
379
|
+
* Internal resolver variant for CLI/MCP tool routing and discovery.
|
|
380
|
+
* - If repoParam is given, match by name or path
|
|
381
|
+
* - If only 1 repo, use it
|
|
382
|
+
* - If multiple repos exist and repoParam is omitted, callers may opt in to
|
|
383
|
+
* the registered repo containing process.cwd()
|
|
384
|
+
* - If 0 repos exist, or cwd cannot disambiguate multiple repos, throw
|
|
385
|
+
*
|
|
386
|
+
* Omitted-repo resolution re-reads the registry before accepting any
|
|
387
|
+
* implicit target, including a cached singleton. A caller that just obtained
|
|
388
|
+
* a fresh registry snapshot may disable that refresh explicitly.
|
|
389
|
+
*/
|
|
390
|
+
selectToolRepository(repoParam?: string, branch?: string, options?: {
|
|
391
|
+
allowCwdDefault?: boolean;
|
|
392
|
+
refreshRegistry?: boolean;
|
|
393
|
+
}): Promise<RepoHandle>;
|
|
378
394
|
/**
|
|
379
395
|
* Re-point a resolved repo handle at a specific branch index (#2106).
|
|
380
396
|
*
|
|
@@ -409,12 +425,13 @@ export declare class LocalBackend {
|
|
|
409
425
|
*/
|
|
410
426
|
private resolveRepoFromCache;
|
|
411
427
|
/**
|
|
412
|
-
*
|
|
428
|
+
* Match process.cwd() against indexed repositories.
|
|
413
429
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
430
|
+
* Explicit duplicate aliases use exact Git-root matching only. Omitted
|
|
431
|
+
* read-only calls opt into deepest containing-path selection. In that mode a
|
|
432
|
+
* candidate must not sit above cwd's Git root, so an unindexed nested checkout
|
|
433
|
+
* cannot fall through to an indexed ancestor. The `.git` ancestor fallback
|
|
434
|
+
* preserves that boundary when the git executable is unavailable.
|
|
418
435
|
*/
|
|
419
436
|
private pickRepoHandleForCwd;
|
|
420
437
|
private handleToRegistryEntry;
|
|
@@ -24,7 +24,7 @@ import { isWalCorruptionError, WAL_RECOVERY_SUGGESTION } from '../../core/lbug/l
|
|
|
24
24
|
// at MCP server startup — crashes on unsupported Node ABI versions (#89)
|
|
25
25
|
// git utilities available if needed
|
|
26
26
|
// import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
|
|
27
|
-
import { parseDiffHunks, coalesceHunksByPath, hunksOverlapRange, getCanonicalRepoRoot, getGitRoot, } from '../../storage/git.js';
|
|
27
|
+
import { parseDiffHunks, coalesceHunksByPath, hunksOverlapRange, findGitRootByDotGit, getCanonicalRepoRoot, getGitRoot, } from '../../storage/git.js';
|
|
28
28
|
import { realpathSync } from 'fs';
|
|
29
29
|
import { listRegisteredRepos, cleanupOldKuzuFiles, canonicalizePath, getStoragePaths, loadMeta, RegistryAmbiguousTargetError, } from '../../storage/repo-manager.js';
|
|
30
30
|
import { GroupService, } from '../../core/group/service.js';
|
|
@@ -1179,23 +1179,56 @@ export class LocalBackend {
|
|
|
1179
1179
|
* - If only 1 repo, use it
|
|
1180
1180
|
* - If 0 or multiple without param, throw with helpful message
|
|
1181
1181
|
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1182
|
+
* Re-reads the registry before an omitted implicit target or after an
|
|
1183
|
+
* explicit miss, so long-running servers see newly indexed repositories.
|
|
1184
1184
|
*/
|
|
1185
1185
|
async resolveRepo(repoParam, branch) {
|
|
1186
|
-
|
|
1186
|
+
return this.selectToolRepository(repoParam, branch);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Internal resolver variant for CLI/MCP tool routing and discovery.
|
|
1190
|
+
* - If repoParam is given, match by name or path
|
|
1191
|
+
* - If only 1 repo, use it
|
|
1192
|
+
* - If multiple repos exist and repoParam is omitted, callers may opt in to
|
|
1193
|
+
* the registered repo containing process.cwd()
|
|
1194
|
+
* - If 0 repos exist, or cwd cannot disambiguate multiple repos, throw
|
|
1195
|
+
*
|
|
1196
|
+
* Omitted-repo resolution re-reads the registry before accepting any
|
|
1197
|
+
* implicit target, including a cached singleton. A caller that just obtained
|
|
1198
|
+
* a fresh registry snapshot may disable that refresh explicitly.
|
|
1199
|
+
*/
|
|
1200
|
+
async selectToolRepository(repoParam, branch, options = {}) {
|
|
1201
|
+
const allowCwdDefault = options.allowCwdDefault === true;
|
|
1202
|
+
const mayRefresh = options.refreshRegistry !== false;
|
|
1203
|
+
let refreshed = false;
|
|
1204
|
+
// A cached singleton is also an implicit choice: another process may have
|
|
1205
|
+
// registered a second repo since init, which must not let a repo-less
|
|
1206
|
+
// mutating call bypass the multi-repo ambiguity guard.
|
|
1207
|
+
if (!repoParam && mayRefresh) {
|
|
1208
|
+
await this.refreshRepos();
|
|
1209
|
+
refreshed = true;
|
|
1210
|
+
}
|
|
1187
1211
|
let result;
|
|
1188
1212
|
try {
|
|
1189
|
-
result = this.resolveRepoFromCache(repoParam);
|
|
1213
|
+
result = this.resolveRepoFromCache(repoParam, allowCwdDefault);
|
|
1190
1214
|
}
|
|
1191
1215
|
catch (err) {
|
|
1192
1216
|
if (!(err instanceof RegistryAmbiguousTargetError))
|
|
1193
1217
|
throw err;
|
|
1218
|
+
if (!mayRefresh || refreshed)
|
|
1219
|
+
throw err;
|
|
1194
1220
|
// Stale in-memory duplicate siblings can linger after unregister; refresh
|
|
1195
1221
|
// once before re-throwing so a resolved registry can disambiguate (#1658).
|
|
1196
1222
|
await this.refreshRepos();
|
|
1197
|
-
|
|
1198
|
-
result = this.resolveRepoFromCache(repoParam);
|
|
1223
|
+
refreshed = true;
|
|
1224
|
+
result = this.resolveRepoFromCache(repoParam, allowCwdDefault);
|
|
1225
|
+
}
|
|
1226
|
+
// Explicit misses retain the existing one-refresh retry. Omitted targets
|
|
1227
|
+
// already refreshed above unless a same-snapshot caller opted out.
|
|
1228
|
+
if (!result && mayRefresh && !refreshed) {
|
|
1229
|
+
await this.refreshRepos();
|
|
1230
|
+
refreshed = true;
|
|
1231
|
+
result = this.resolveRepoFromCache(repoParam, allowCwdDefault);
|
|
1199
1232
|
}
|
|
1200
1233
|
if (result) {
|
|
1201
1234
|
// Issue: silent graph drift across sibling clones.
|
|
@@ -1209,15 +1242,6 @@ export class LocalBackend {
|
|
|
1209
1242
|
});
|
|
1210
1243
|
return this.applyBranchScope(result, branch);
|
|
1211
1244
|
}
|
|
1212
|
-
// Miss — refresh registry and try once more (skip if already refreshed above)
|
|
1213
|
-
if (!refreshedAfterAmbiguity) {
|
|
1214
|
-
await this.refreshRepos();
|
|
1215
|
-
}
|
|
1216
|
-
const retried = this.resolveRepoFromCache(repoParam);
|
|
1217
|
-
if (retried) {
|
|
1218
|
-
this.maybeWarnSiblingDrift(retried).catch(() => { });
|
|
1219
|
-
return this.applyBranchScope(retried, branch);
|
|
1220
|
-
}
|
|
1221
1245
|
// Still no match — throw with helpful message
|
|
1222
1246
|
if (this.repos.size === 0) {
|
|
1223
1247
|
throw new Error('No indexed repositories. Run: gitnexus analyze');
|
|
@@ -1350,7 +1374,7 @@ export class LocalBackend {
|
|
|
1350
1374
|
* Throws {@link RegistryAmbiguousTargetError} when `repoParam` matches
|
|
1351
1375
|
* multiple handles by name and cwd cannot disambiguate (#1658).
|
|
1352
1376
|
*/
|
|
1353
|
-
resolveRepoFromCache(repoParam) {
|
|
1377
|
+
resolveRepoFromCache(repoParam, allowCwdDefault = false) {
|
|
1354
1378
|
if (this.repos.size === 0)
|
|
1355
1379
|
return null;
|
|
1356
1380
|
if (repoParam) {
|
|
@@ -1379,6 +1403,9 @@ export class LocalBackend {
|
|
|
1379
1403
|
if (nameMatches.length === 1)
|
|
1380
1404
|
return nameMatches[0];
|
|
1381
1405
|
if (nameMatches.length > 1) {
|
|
1406
|
+
// Explicit duplicate aliases retain the legacy fail-closed contract:
|
|
1407
|
+
// only an exact cwd Git-root match may disambiguate them. Deepest path
|
|
1408
|
+
// containment is reserved for an omitted read-only repo (#3073).
|
|
1382
1409
|
const cwdPick = this.pickRepoHandleForCwd(nameMatches);
|
|
1383
1410
|
if (cwdPick)
|
|
1384
1411
|
return cwdPick;
|
|
@@ -1403,26 +1430,46 @@ export class LocalBackend {
|
|
|
1403
1430
|
if (this.repos.size === 1) {
|
|
1404
1431
|
return this.repos.values().next().value;
|
|
1405
1432
|
}
|
|
1433
|
+
if (allowCwdDefault) {
|
|
1434
|
+
const cwdPick = this.pickRepoHandleForCwd([...this.repos.values()], true);
|
|
1435
|
+
if (cwdPick)
|
|
1436
|
+
return cwdPick;
|
|
1437
|
+
}
|
|
1406
1438
|
return null; // Multiple repos, no param — ambiguous
|
|
1407
1439
|
}
|
|
1408
1440
|
/**
|
|
1409
|
-
*
|
|
1441
|
+
* Match process.cwd() against indexed repositories.
|
|
1410
1442
|
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1443
|
+
* Explicit duplicate aliases use exact Git-root matching only. Omitted
|
|
1444
|
+
* read-only calls opt into deepest containing-path selection. In that mode a
|
|
1445
|
+
* candidate must not sit above cwd's Git root, so an unindexed nested checkout
|
|
1446
|
+
* cannot fall through to an indexed ancestor. The `.git` ancestor fallback
|
|
1447
|
+
* preserves that boundary when the git executable is unavailable.
|
|
1415
1448
|
*/
|
|
1416
|
-
pickRepoHandleForCwd(candidates) {
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1449
|
+
pickRepoHandleForCwd(candidates, allowContaining = false) {
|
|
1450
|
+
const cwd = process.cwd();
|
|
1451
|
+
const normalize = (value) => {
|
|
1452
|
+
const canonical = canonicalizePath(value);
|
|
1453
|
+
return process.platform === 'win32' ? canonical.toLowerCase() : canonical;
|
|
1454
|
+
};
|
|
1455
|
+
const isSameOrDescendant = (parent, child) => child === parent ||
|
|
1456
|
+
child.startsWith(parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`);
|
|
1457
|
+
const canonicalCwd = normalize(cwd);
|
|
1458
|
+
const cwdRoot = getGitRoot(cwd) ?? findGitRootByDotGit(cwd);
|
|
1459
|
+
const canonicalRoot = cwdRoot ? normalize(cwdRoot) : null;
|
|
1460
|
+
if (allowContaining) {
|
|
1461
|
+
const containing = candidates
|
|
1462
|
+
.map((handle) => ({ handle, repoPath: normalize(handle.repoPath) }))
|
|
1463
|
+
.filter(({ repoPath }) => isSameOrDescendant(repoPath, canonicalCwd))
|
|
1464
|
+
.filter(({ repoPath }) => !canonicalRoot || isSameOrDescendant(canonicalRoot, repoPath))
|
|
1465
|
+
.sort((a, b) => b.repoPath.length - a.repoPath.length);
|
|
1466
|
+
if (containing.length > 0)
|
|
1467
|
+
return containing[0].handle;
|
|
1468
|
+
}
|
|
1469
|
+
if (!canonicalRoot)
|
|
1419
1470
|
return null;
|
|
1420
|
-
const canonicalCwd = canonicalizePath(cwdRoot);
|
|
1421
1471
|
const cwdMatches = candidates.filter((handle) => {
|
|
1422
|
-
|
|
1423
|
-
return process.platform === 'win32'
|
|
1424
|
-
? stored.toLowerCase() === canonicalCwd.toLowerCase()
|
|
1425
|
-
: stored === canonicalCwd;
|
|
1472
|
+
return normalize(handle.repoPath) === canonicalRoot;
|
|
1426
1473
|
});
|
|
1427
1474
|
return cwdMatches.length === 1 ? cwdMatches[0] : null;
|
|
1428
1475
|
}
|
|
@@ -1819,7 +1866,7 @@ export class LocalBackend {
|
|
|
1819
1866
|
}
|
|
1820
1867
|
// Resolve repo from optional param (re-reads registry on miss). An optional
|
|
1821
1868
|
// `branch` param scopes the resolved handle to that branch's index (#2106).
|
|
1822
|
-
const repo = await this.
|
|
1869
|
+
const repo = await this.selectToolRepository(p.repo, p.branch, { allowCwdDefault: method !== 'rename' });
|
|
1823
1870
|
switch (method) {
|
|
1824
1871
|
case 'query':
|
|
1825
1872
|
return this.withToolStaleness(repo, await this.query(repo, p));
|
|
@@ -20,10 +20,14 @@ export declare class McpRepositoryPolicy {
|
|
|
20
20
|
private repoForArgs;
|
|
21
21
|
private normalizeToolArgs;
|
|
22
22
|
private listAllowedRepos;
|
|
23
|
-
|
|
23
|
+
toolSchemaRepoRequirements(backend: LocalBackend): Promise<{
|
|
24
|
+
readOnlyRequiresRepo: boolean;
|
|
25
|
+
mutatingRequiresRepo: boolean;
|
|
26
|
+
}>;
|
|
24
27
|
private listReposPage;
|
|
25
28
|
private callTool;
|
|
26
29
|
private resolveRepo;
|
|
30
|
+
private selectToolRepository;
|
|
27
31
|
assertResourceUri(uri: string): void;
|
|
28
32
|
resourceTemplateAllowed(uriTemplate: string): boolean;
|
|
29
33
|
toolAllowed(toolName: string): boolean;
|
|
@@ -134,10 +134,40 @@ export class McpRepositoryPolicy {
|
|
|
134
134
|
};
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
async
|
|
138
|
-
if (this.defaultRepo)
|
|
139
|
-
return false;
|
|
140
|
-
|
|
137
|
+
async toolSchemaRepoRequirements(backend) {
|
|
138
|
+
if (this.defaultRepo) {
|
|
139
|
+
return { readOnlyRequiresRepo: false, mutatingRequiresRepo: false };
|
|
140
|
+
}
|
|
141
|
+
// Runtime selection is based on the configured allowlist, not on which
|
|
142
|
+
// entries happen to remain visible in a later registry refresh. Keep the
|
|
143
|
+
// advertised schema aligned with repoForArgs() when that listing shrinks.
|
|
144
|
+
if (this.restricted) {
|
|
145
|
+
const requiresRepo = this.allowed.length > 1;
|
|
146
|
+
return {
|
|
147
|
+
readOnlyRequiresRepo: requiresRepo,
|
|
148
|
+
mutatingRequiresRepo: requiresRepo,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// One fresh listing supplies both schema decisions. Besides keeping the
|
|
152
|
+
// advertised contract internally consistent, this avoids doing two full
|
|
153
|
+
// per-repo staleness fan-outs for every tools/list request.
|
|
154
|
+
const visibleRepos = await this.listAllowedRepos(backend);
|
|
155
|
+
if (visibleRepos.length <= 1) {
|
|
156
|
+
return { readOnlyRequiresRepo: false, mutatingRequiresRepo: false };
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
// listAllowedRepos() refreshed this backend immediately above. Resolve
|
|
160
|
+
// against that exact cache snapshot instead of racing another registry
|
|
161
|
+
// read; only read-only schemas may advertise the cwd-derived default.
|
|
162
|
+
await backend.selectToolRepository(undefined, undefined, {
|
|
163
|
+
allowCwdDefault: true,
|
|
164
|
+
refreshRegistry: false,
|
|
165
|
+
});
|
|
166
|
+
return { readOnlyRequiresRepo: false, mutatingRequiresRepo: true };
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return { readOnlyRequiresRepo: true, mutatingRequiresRepo: true };
|
|
170
|
+
}
|
|
141
171
|
}
|
|
142
172
|
async listReposPage(backend, params) {
|
|
143
173
|
const { limit, offset } = parseListReposPagination(params, {
|
|
@@ -186,6 +216,17 @@ export class McpRepositoryPolicy {
|
|
|
186
216
|
const selected = this.repoForArgs(repo === undefined ? undefined : { repo });
|
|
187
217
|
return backend.resolveRepo(selected?.path, branch);
|
|
188
218
|
}
|
|
219
|
+
async selectToolRepository(backend, repo, branch, options) {
|
|
220
|
+
if (!this.configured)
|
|
221
|
+
return backend.selectToolRepository(repo, branch, options);
|
|
222
|
+
if (!this.restricted) {
|
|
223
|
+
return backend.selectToolRepository(repo ?? this.defaultRepo?.path, branch, options);
|
|
224
|
+
}
|
|
225
|
+
const selected = this.repoForArgs(repo === undefined ? undefined : { repo });
|
|
226
|
+
// Restricted policies never allow cwd to select outside the configured
|
|
227
|
+
// set; once policy supplies an explicit path, the public resolver is enough.
|
|
228
|
+
return backend.resolveRepo(selected?.path, branch);
|
|
229
|
+
}
|
|
189
230
|
assertResourceUri(uri) {
|
|
190
231
|
if (!this.restricted)
|
|
191
232
|
return;
|
|
@@ -255,6 +296,9 @@ export class McpRepositoryPolicy {
|
|
|
255
296
|
if (property === 'resolveRepo') {
|
|
256
297
|
return (repo, branch) => policy.resolveRepo(target, repo, branch);
|
|
257
298
|
}
|
|
299
|
+
if (property === 'selectToolRepository') {
|
|
300
|
+
return (repo, branch, options) => policy.selectToolRepository(target, repo, branch, options);
|
|
301
|
+
}
|
|
258
302
|
if (property === 'getContext' && policy.restricted) {
|
|
259
303
|
return (repoId) => {
|
|
260
304
|
if (!repoId || !policy.uniqueAllowedContextNames.has(repoId.toLowerCase()))
|
package/dist/mcp/resources.js
CHANGED
|
@@ -254,7 +254,8 @@ async function getReposResource(backend) {
|
|
|
254
254
|
}
|
|
255
255
|
if (repos.length > 1) {
|
|
256
256
|
lines.push('');
|
|
257
|
-
lines.push('# Multiple repos indexed.
|
|
257
|
+
lines.push('# Multiple repos indexed. Read-only tools may omit repo when an MCP default is configured or GitNexus process.cwd() is inside one listed path without crossing an unindexed nested Git checkout.');
|
|
258
|
+
lines.push('# Otherwise—and for mutating tools without an MCP default—pass repo explicitly:');
|
|
258
259
|
lines.push(`# query({search_query: "auth", repo: "${repos[0].name}"})`);
|
|
259
260
|
}
|
|
260
261
|
return lines.join('\n');
|
package/dist/mcp/server.js
CHANGED
|
@@ -136,11 +136,11 @@ export function createMCPServer(backend, options = {}) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
139
|
+
// Make the effective routing contract machine-readable. Read-only tools may
|
|
140
|
+
// use a cwd-derived default; mutating rename remains explicit unless policy
|
|
141
|
+
// supplies a single/default repository.
|
|
142
142
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
143
|
-
const
|
|
143
|
+
const { readOnlyRequiresRepo, mutatingRequiresRepo } = await repositoryPolicy.toolSchemaRepoRequirements(backend);
|
|
144
144
|
return {
|
|
145
145
|
tools: GITNEXUS_TOOLS.filter((tool) => (!readOnly || MCP_READ_ONLY_TOOLS.has(tool.name)) &&
|
|
146
146
|
repositoryPolicy.toolAllowed(tool.name))
|
|
@@ -148,7 +148,8 @@ export function createMCPServer(backend, options = {}) {
|
|
|
148
148
|
.map((tool) => ({
|
|
149
149
|
name: tool.name,
|
|
150
150
|
description: tool.description,
|
|
151
|
-
inputSchema:
|
|
151
|
+
inputSchema: (tool.name === 'rename' ? mutatingRequiresRepo : readOnlyRequiresRepo) &&
|
|
152
|
+
REPO_SCOPED_TOOLS.has(tool.name)
|
|
152
153
|
? {
|
|
153
154
|
...tool.inputSchema,
|
|
154
155
|
required: [...new Set([...tool.inputSchema.required, 'repo'])],
|
package/dist/mcp/tools.js
CHANGED
|
@@ -49,6 +49,8 @@ export const PDG_QUERY_MAX_LIMIT = 200;
|
|
|
49
49
|
// Shared impact traversal depth cap. The MCP schema advertises this bound;
|
|
50
50
|
// PDG direct backend callers also enforce it before running traversal.
|
|
51
51
|
export const IMPACT_MAX_DEPTH = 32;
|
|
52
|
+
const CWD_AWARE_REPO_OMISSION = 'Omit when only one repo is indexed, an MCP default is configured, or the GitNexus process cwd is inside a registered path without crossing an unindexed nested Git checkout; otherwise specify it explicitly.';
|
|
53
|
+
const MUTATING_REPO_OMISSION = 'Omit only when one repo is indexed or an MCP default is configured; otherwise mutating tools require an explicit repo.';
|
|
52
54
|
export const GITNEXUS_TOOLS = [
|
|
53
55
|
{
|
|
54
56
|
name: 'list_repos',
|
|
@@ -61,8 +63,10 @@ PAGINATION: Results are paginated so a large registry is not truncated by MCP/LL
|
|
|
61
63
|
WHEN TO USE: First step when multiple repos are indexed, or to discover available repos.
|
|
62
64
|
AFTER THIS: READ gitnexus://repo/{name}/context for the repo you want to work with.
|
|
63
65
|
|
|
64
|
-
When multiple repos are indexed,
|
|
65
|
-
|
|
66
|
+
When multiple repos are indexed, repo-scoped read-only tools use the configured
|
|
67
|
+
MCP default or the registered path containing the GitNexus process cwd, unless
|
|
68
|
+
cwd has crossed into an unindexed nested Git checkout. If neither applies,
|
|
69
|
+
specify the "repo" parameter explicitly.`,
|
|
66
70
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
67
71
|
inputSchema: {
|
|
68
72
|
type: 'object',
|
|
@@ -148,7 +152,7 @@ SERVICE: optional monorepo path prefix (POSIX-style, case-sensitive segments). W
|
|
|
148
152
|
},
|
|
149
153
|
repo: {
|
|
150
154
|
type: 'string',
|
|
151
|
-
description:
|
|
155
|
+
description: `Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>" (member path keys from group.yaml). ${CWD_AWARE_REPO_OMISSION}`,
|
|
152
156
|
},
|
|
153
157
|
service: {
|
|
154
158
|
type: 'string',
|
|
@@ -227,7 +231,7 @@ TIPS:
|
|
|
227
231
|
},
|
|
228
232
|
repo: {
|
|
229
233
|
type: 'string',
|
|
230
|
-
description:
|
|
234
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
231
235
|
},
|
|
232
236
|
},
|
|
233
237
|
required: ['statement'],
|
|
@@ -290,7 +294,7 @@ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "rep
|
|
|
290
294
|
},
|
|
291
295
|
repo: {
|
|
292
296
|
type: 'string',
|
|
293
|
-
description:
|
|
297
|
+
description: `Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". ${CWD_AWARE_REPO_OMISSION}`,
|
|
294
298
|
},
|
|
295
299
|
service: {
|
|
296
300
|
type: 'string',
|
|
@@ -334,7 +338,7 @@ Returns: changed symbols, affected processes, and a risk summary.
|
|
|
334
338
|
},
|
|
335
339
|
repo: {
|
|
336
340
|
type: 'string',
|
|
337
|
-
description:
|
|
341
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
338
342
|
},
|
|
339
343
|
},
|
|
340
344
|
required: [],
|
|
@@ -373,7 +377,7 @@ A graph too large to analyze at all returns \`{ error, truncated: true }\` with
|
|
|
373
377
|
},
|
|
374
378
|
repo: {
|
|
375
379
|
type: 'string',
|
|
376
|
-
description:
|
|
380
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
377
381
|
},
|
|
378
382
|
},
|
|
379
383
|
required: [],
|
|
@@ -410,7 +414,7 @@ Handles disambiguation via context()'s payload verbatim: an ambiguous symbol_nam
|
|
|
410
414
|
},
|
|
411
415
|
repo: {
|
|
412
416
|
type: 'string',
|
|
413
|
-
description:
|
|
417
|
+
description: `Repository name or path. ${MUTATING_REPO_OMISSION}`,
|
|
414
418
|
},
|
|
415
419
|
},
|
|
416
420
|
required: ['new_name'],
|
|
@@ -542,7 +546,7 @@ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "rep
|
|
|
542
546
|
},
|
|
543
547
|
repo: {
|
|
544
548
|
type: 'string',
|
|
545
|
-
description:
|
|
549
|
+
description: `Indexed repository name or path, or group mode "@<groupName>" / "@<groupName>/<memberPath>". ${CWD_AWARE_REPO_OMISSION}`,
|
|
546
550
|
},
|
|
547
551
|
service: {
|
|
548
552
|
type: 'string',
|
|
@@ -630,7 +634,7 @@ Findings are deliberately NOT part of impact()'s traversal or the web schema —
|
|
|
630
634
|
},
|
|
631
635
|
repo: {
|
|
632
636
|
type: 'string',
|
|
633
|
-
description:
|
|
637
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
634
638
|
},
|
|
635
639
|
},
|
|
636
640
|
required: [],
|
|
@@ -679,7 +683,7 @@ CONTRACT CAVEATS:
|
|
|
679
683
|
},
|
|
680
684
|
repo: {
|
|
681
685
|
type: 'string',
|
|
682
|
-
description:
|
|
686
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
683
687
|
},
|
|
684
688
|
},
|
|
685
689
|
required: ['mode', 'target'],
|
|
@@ -703,7 +707,7 @@ Returns: route nodes with their handlers, middleware wrapper chains (e.g., withA
|
|
|
703
707
|
},
|
|
704
708
|
repo: {
|
|
705
709
|
type: 'string',
|
|
706
|
-
description:
|
|
710
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
707
711
|
},
|
|
708
712
|
},
|
|
709
713
|
required: [],
|
|
@@ -721,7 +725,10 @@ Returns: tool nodes with their handler files and descriptions.`,
|
|
|
721
725
|
type: 'object',
|
|
722
726
|
properties: {
|
|
723
727
|
tool: { type: 'string', description: 'Filter by tool name. Omit for all tools.' },
|
|
724
|
-
repo: {
|
|
728
|
+
repo: {
|
|
729
|
+
type: 'string',
|
|
730
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
731
|
+
},
|
|
725
732
|
},
|
|
726
733
|
required: [],
|
|
727
734
|
},
|
|
@@ -744,7 +751,7 @@ Returns routes that have both detected response keys AND consumers. Shows top-le
|
|
|
744
751
|
},
|
|
745
752
|
repo: {
|
|
746
753
|
type: 'string',
|
|
747
|
-
description:
|
|
754
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
748
755
|
},
|
|
749
756
|
},
|
|
750
757
|
required: [],
|
|
@@ -769,7 +776,10 @@ Response shape is keyed on how many routes match, not on the data: exactly one m
|
|
|
769
776
|
type: 'string',
|
|
770
777
|
description: 'Optional HTTP verb — GET, POST, PUT, PATCH, DELETE, etc. — to narrow a multi-verb route or file lookup to a single method. Returns an error if no matched route uses that verb.',
|
|
771
778
|
},
|
|
772
|
-
repo: {
|
|
779
|
+
repo: {
|
|
780
|
+
type: 'string',
|
|
781
|
+
description: `Repository name or path. ${CWD_AWARE_REPO_OMISSION}`,
|
|
782
|
+
},
|
|
773
783
|
},
|
|
774
784
|
required: [],
|
|
775
785
|
},
|
|
@@ -879,7 +889,7 @@ DESTINATION TRACE (cross-repo): for an "@groupName" trace, OMIT to/to_uid/to_fil
|
|
|
879
889
|
},
|
|
880
890
|
repo: {
|
|
881
891
|
type: 'string',
|
|
882
|
-
description:
|
|
892
|
+
description: `Repository name or path, or "@groupName" / "@groupName/memberPath" for a cross-repo trace over a group. ${CWD_AWARE_REPO_OMISSION}`,
|
|
883
893
|
},
|
|
884
894
|
},
|
|
885
895
|
required: [],
|
package/package.json
CHANGED
|
@@ -114,6 +114,9 @@ const PLATFORM_LOGIC = [
|
|
|
114
114
|
// POSIX and Windows — the fail-closed path-claim semantics must hold on the
|
|
115
115
|
// real windows-latest path implementation (#2419/#2420).
|
|
116
116
|
'test/unit/server-api-repo-resolution.test.ts',
|
|
117
|
+
// #3073: cwd-based repository selection canonicalizes real paths, compares
|
|
118
|
+
// platform separators/case, and rejects nested Git-boundary fallthrough.
|
|
119
|
+
'test/unit/calltool-dispatch.test.ts',
|
|
117
120
|
// The index write-lock (#2658) selects its backend by process.platform — the
|
|
118
121
|
// OS socket lock (Windows named pipe / Linux abstract socket) vs the file
|
|
119
122
|
// fallback — and its socket-backend describe block is gated to linux/win32.
|