gitnexus 1.6.9-rc.23 → 1.6.9-rc.25

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.
@@ -25,6 +25,9 @@ export const TYPESCRIPT_QUERIES = `
25
25
  (function_declaration
26
26
  name: (identifier) @name) @definition.function
27
27
 
28
+ (generator_function_declaration
29
+ name: (identifier) @name) @definition.function
30
+
28
31
  ; TypeScript overload signatures (function_signature is a separate node type from function_declaration)
29
32
  (function_signature
30
33
  name: (identifier) @name) @definition.function
@@ -371,6 +374,9 @@ export const JAVASCRIPT_QUERIES = `
371
374
  (function_declaration
372
375
  name: (identifier) @name) @definition.function
373
376
 
377
+ (generator_function_declaration
378
+ name: (identifier) @name) @definition.function
379
+
374
380
  (method_definition
375
381
  name: (property_identifier) @name) @definition.method
376
382
 
@@ -5157,7 +5157,7 @@ export class LocalBackend {
5157
5157
  RETURN n.id AS routeId, n.name AS routeName, n.filePath AS handlerFile,
5158
5158
  n.responseKeys AS responseKeys, n.errorKeys AS errorKeys, n.middleware AS middleware,
5159
5159
  consumer.name AS consumerName, consumer.filePath AS consumerFile,
5160
- r.reason AS fetchReason
5160
+ r.reason AS fetchReason, n.method AS method
5161
5161
  `, params);
5162
5162
  // Strip wrapping quotes from DB array elements — CSV COPY stores ['key'] which
5163
5163
  // LadybugDB may return as "'key'" rather than "key"
@@ -5173,10 +5173,16 @@ export class LocalBackend {
5173
5173
  const consumerName = row.consumerName ?? row[6];
5174
5174
  const consumerFile = row.consumerFile ?? row[7];
5175
5175
  const fetchReason = row.fetchReason ?? row[8] ?? null;
5176
+ // Verb is the literal '*' for method-agnostic routes (Django function
5177
+ // views) and absent (null) for method-less routes (filesystem, Laravel
5178
+ // resource). Appended last in RETURN so positional fallbacks for the
5179
+ // consumer/reason columns above stay stable.
5180
+ const method = row.method ?? row[9] ?? null;
5176
5181
  if (!routeMap.has(id)) {
5177
5182
  routeMap.set(id, {
5178
5183
  id,
5179
5184
  name,
5185
+ method,
5180
5186
  filePath,
5181
5187
  responseKeys,
5182
5188
  errorKeys,
@@ -5260,6 +5266,7 @@ export class LocalBackend {
5260
5266
  return {
5261
5267
  routes: routes.map((r) => ({
5262
5268
  route: r.name,
5269
+ method: r.method,
5263
5270
  handler: r.filePath,
5264
5271
  middleware: r.middleware || [],
5265
5272
  consumers: r.consumers,
@@ -5314,6 +5321,7 @@ export class LocalBackend {
5314
5321
  const hasMismatches = consumers.some((c) => 'mismatched' in c && c.mismatched.length > 0);
5315
5322
  return {
5316
5323
  route: r.name,
5324
+ method: r.method,
5317
5325
  handler: r.filePath,
5318
5326
  ...(responseKeys.length > 0 ? { responseKeys } : {}),
5319
5327
  ...(errorKeys.length > 0 ? { errorKeys } : {}),
@@ -5381,15 +5389,32 @@ export class LocalBackend {
5381
5389
  routeFilter = `AND n.filePath CONTAINS $file`;
5382
5390
  queryParams.file = params.file;
5383
5391
  }
5384
- const routes = await this.fetchRoutesWithConsumers(repo.lbugPath, routeFilter, queryParams);
5392
+ // After #2302 the same URL/handler can expose one Route node per HTTP verb.
5393
+ // An optional `method` narrows to that one verb so the response collapses to
5394
+ // the singular shape. A method-agnostic route (method `'*'`, e.g. a Django
5395
+ // function view) matches any selector; verbless routes (null method) never do.
5396
+ // `method` arrives unvalidated from the MCP envelope (the JSON schema is
5397
+ // advisory), so reject a non-string verb with a structured error instead of
5398
+ // throwing on `.toUpperCase()`; empty/whitespace collapses to no selector.
5399
+ const rawMethod = params.method;
5400
+ if (rawMethod !== undefined && typeof rawMethod !== 'string') {
5401
+ return { error: '"method" must be a string (e.g. "GET", "POST").' };
5402
+ }
5403
+ const wantedMethod = typeof rawMethod === 'string' ? rawMethod.trim().toUpperCase() || undefined : undefined;
5404
+ const matched = await this.fetchRoutesWithConsumers(repo.lbugPath, routeFilter, queryParams);
5405
+ const routes = matched.filter((r) => !wantedMethod || r.method === '*' || r.method?.toUpperCase() === wantedMethod);
5385
5406
  if (routes.length === 0) {
5386
5407
  const target = params.route || params.file;
5387
- return { error: `No routes found matching "${target}".` };
5408
+ // Only append the verb when the URL/file matched routes but none used it;
5409
+ // a non-existent URL/file gets the plain "no routes found" message.
5410
+ const verb = wantedMethod && matched.length > 0 ? ` with method "${wantedMethod}"` : '';
5411
+ return { error: `No routes found matching "${target}"${verb}.` };
5388
5412
  }
5389
5413
  const flowMap = await this.fetchLinkedFlowsBatch(repo.lbugPath, routes.map((r) => r.id));
5390
- // Count how many routes share the same handler file (for middleware partial detection)
5414
+ // Count verbs per handler from the FULL match (before the method filter) so a
5415
+ // method-scoped query still flags a multi-verb handler's partial middleware.
5391
5416
  const routeCountByHandler = new Map();
5392
- for (const r of routes) {
5417
+ for (const r of matched) {
5393
5418
  if (r.filePath) {
5394
5419
  routeCountByHandler.set(r.filePath, (routeCountByHandler.get(r.filePath) ?? 0) + 1);
5395
5420
  }
@@ -5459,6 +5484,7 @@ export class LocalBackend {
5459
5484
  const middlewarePartial = middlewareArr.length > 0 && handlerRouteCount > 1;
5460
5485
  return {
5461
5486
  route: r.name,
5487
+ method: r.method,
5462
5488
  handler: r.filePath,
5463
5489
  responseShape: {
5464
5490
  success: responseKeys,
@@ -5468,7 +5494,7 @@ export class LocalBackend {
5468
5494
  ...(middlewarePartial
5469
5495
  ? {
5470
5496
  middlewareDetection: 'partial',
5471
- middlewareNote: 'Middleware captured from first HTTP method export only — other methods in this handler may use different middleware chains.',
5497
+ middlewareNote: 'Middleware captured from the first route export only — other route exports in this handler may use different middleware chains.',
5472
5498
  }
5473
5499
  : {}),
5474
5500
  consumers,
@@ -24,6 +24,14 @@ export interface ToolDefinition {
24
24
  minLength?: number;
25
25
  }>;
26
26
  required: string[];
27
+ /**
28
+ * JSON-Schema `anyOf` for cross-property constraints `required` cannot express
29
+ * — e.g. "at least one of route/file". Forwarded verbatim to clients by the
30
+ * server's ListTools handler, so MCP clients see the constraint.
31
+ */
32
+ anyOf?: Array<{
33
+ required: string[];
34
+ }>;
27
35
  };
28
36
  }
29
37
  /**
package/dist/mcp/tools.js CHANGED
@@ -615,7 +615,7 @@ CONTRACT CAVEATS:
615
615
  WHEN TO USE: Understanding API consumption patterns, finding orphaned routes. For pre-change analysis, prefer \`api_impact\` which combines this data with mismatch detection and risk assessment.
616
616
  AFTER THIS: Use impact() on specific route handlers to see full blast radius.
617
617
 
618
- Returns: route nodes with their handlers, middleware wrapper chains (e.g., withAuth, withRateLimit), and consumers.`,
618
+ Returns: route nodes with their handlers, middleware wrapper chains (e.g., withAuth, withRateLimit), and consumers. Each route object includes its "method" (the HTTP verb, "*" for method-agnostic routes, or null for method-less routes).`,
619
619
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
620
620
  inputSchema: {
621
621
  type: 'object',
@@ -656,7 +656,7 @@ Returns: tool nodes with their handler files and descriptions.`,
656
656
  WHEN TO USE: Detecting mismatches between what an API route returns and what consumers expect. Finding shape drift. For pre-change analysis, prefer \`api_impact\` which combines this data with mismatch detection and risk assessment.
657
657
  REQUIRES: Route nodes with responseKeys (extracted from .json({...}) calls during indexing).
658
658
 
659
- Returns routes that have both detected response keys AND consumers. Shows top-level keys each endpoint returns (e.g., data, pagination, error) and what keys each consumer accesses. Reports MISMATCH status when a consumer accesses keys not present in the route's response shape.`,
659
+ Returns routes that have both detected response keys AND consumers. Shows top-level keys each endpoint returns (e.g., data, pagination, error) and what keys each consumer accesses. Reports MISMATCH status when a consumer accesses keys not present in the route's response shape. Each route object includes its "method" (the HTTP verb, "*" for method-agnostic routes, or null for method-less routes).`,
660
660
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
661
661
  inputSchema: {
662
662
  type: 'object',
@@ -681,16 +681,23 @@ WHEN TO USE: BEFORE modifying any API route handler. Shows what consumers depend
681
681
 
682
682
  Risk levels: LOW (0-3 consumers), MEDIUM (4-9 or any mismatches), HIGH (10+ consumers or mismatches with 4+ consumers). Mismatches with confidence "low" indicate the consumer file fetches multiple routes — property attribution is approximate.
683
683
 
684
- Returns: single route object when one match, or { routes: [...], total: N } for multiple matches. Combines route_map, shape_check, and impact data.`,
684
+ Response shape is keyed on how many routes match, not on the data: exactly one match returns a single route object; two or more return { routes: [...], total: N }. The same URL can expose multiple HTTP verbs (e.g. GET and POST /api/orders are distinct routes that share the URL), so a bare-URL lookup may return the wrapped form — every route object carries its own "method" so verbs are distinguishable. Pass "method" to narrow to one verb; the single-object shape is returned only when exactly one route remains after filtering — a substring route/file match spanning several URLs can still return the wrapped form. A URL/file that exists but has no route for the given verb returns an error. Each route's "method" is the literal "*" for method-agnostic routes (e.g. Django function views), which match any "method" selector, or null for method-less routes (filesystem, Laravel resource), which never match a selector. Combines route_map, shape_check, and impact data.`,
685
685
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
686
686
  inputSchema: {
687
687
  type: 'object',
688
688
  properties: {
689
689
  route: { type: 'string', description: 'Route path (e.g., "/api/grants")' },
690
690
  file: { type: 'string', description: 'Handler file path (alternative to route)' },
691
+ method: {
692
+ type: 'string',
693
+ 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.',
694
+ },
691
695
  repo: { type: 'string', description: 'Repository name or path.' },
692
696
  },
693
697
  required: [],
698
+ // Exactly one lookup key is needed, but either works (route wins if both
699
+ // are passed) — so the structural constraint is "at least one of route/file".
700
+ anyOf: [{ required: ['route'] }, { required: ['file'] }],
694
701
  },
695
702
  },
696
703
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.23",
3
+ "version": "1.6.9-rc.25",
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",