gitnexus 1.6.8-rc.55 → 1.6.8-rc.56

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.
@@ -1,5 +1,29 @@
1
1
  import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
2
2
  import type { ExtractedContract, RepoHandle } from '../types.js';
3
+ /**
4
+ * Language-agnostic orchestrator for HTTP route (provider + consumer)
5
+ * contract extraction. Two strategies, in order of preference per role:
6
+ *
7
+ * 1. **Graph-assisted (Strategy A)** — if a per-repo LadybugDB executor
8
+ * is available, read `HANDLES_ROUTE` / `FETCHES` Cypher edges that
9
+ * the ingestion pipeline already produced via tree-sitter. This is
10
+ * the preferred path because the graph has richer symbol metadata
11
+ * (real uids, class/method structure, etc.).
12
+ *
13
+ * 2. **Source-scan supplement (Strategy B)** — parse files directly with
14
+ * the per-language plugin registry in `./http-patterns/`. Used to
15
+ * fill gaps when graph extraction only covers part of a polyglot repo
16
+ * (e.g. Java graph routes plus Go source-scan routes). Graph entries
17
+ * remain authoritative for duplicate contract IDs because they carry
18
+ * richer symbol metadata. Each plugin owns its tree-sitter grammar
19
+ * and query sources — this orchestrator imports NO grammars or query
20
+ * strings.
21
+ *
22
+ * Adding a new language for Strategy B is a one-file edit in
23
+ * `http-patterns/index.ts`: register a new `HttpLanguagePlugin` and
24
+ * widen `HTTP_SCAN_GLOB` if needed.
25
+ */
26
+ export declare const HANDLES_ROUTE_QUERY = "\nMATCH (handlerFile:File)-[r:CodeRelation {type: 'HANDLES_ROUTE'}]->(route:Route)\nRETURN handlerFile.id AS fileId, handlerFile.filePath AS filePath,\n route.name AS routePath, route.id AS routeId,\n route.method AS routeMethod,\n route.responseKeys AS responseKeys,\n r.reason AS routeSource";
3
27
  /**
4
28
  * Canonicalize a provider-side HTTP path for contract-id generation:
5
29
  * - strip query string
@@ -4,6 +4,7 @@ import Parser from 'tree-sitter';
4
4
  import { createIgnoreFilter } from '../../../config/ignore-service.js';
5
5
  import { readSafe } from './fs-utils.js';
6
6
  import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
7
+ import { logger } from '../../logger.js';
7
8
  import { getPluginForFile, HTTP_SCAN_GLOB, } from './http-patterns/index.js';
8
9
  /**
9
10
  * Language-agnostic orchestrator for HTTP route (provider + consumer)
@@ -29,10 +30,14 @@ import { getPluginForFile, HTTP_SCAN_GLOB, } from './http-patterns/index.js';
29
30
  * widen `HTTP_SCAN_GLOB` if needed.
30
31
  */
31
32
  // ─── Graph-assisted queries ──────────────────────────────────────────
32
- const HANDLES_ROUTE_QUERY = `
33
+ // Exported so integration tests can run the exact production query against a
34
+ // real LadybugDB (guards the Route.method column contract — see
35
+ // route-method-roundtrip.test.ts).
36
+ export const HANDLES_ROUTE_QUERY = `
33
37
  MATCH (handlerFile:File)-[r:CodeRelation {type: 'HANDLES_ROUTE'}]->(route:Route)
34
38
  RETURN handlerFile.id AS fileId, handlerFile.filePath AS filePath,
35
39
  route.name AS routePath, route.id AS routeId,
40
+ route.method AS routeMethod,
36
41
  route.responseKeys AS responseKeys,
37
42
  r.reason AS routeSource`;
38
43
  const FETCHES_QUERY = `
@@ -273,14 +278,26 @@ export class HttpRouteExtractor {
273
278
  try {
274
279
  rows = await db(HANDLES_ROUTE_QUERY);
275
280
  }
276
- catch {
281
+ catch (err) {
282
+ // A failure here silently disables the entire graph-assisted HTTP
283
+ // provider path (the source-scan fallback still runs and masks most
284
+ // of the damage), so surface it at debug level to make a total
285
+ // outage observable instead of invisible.
286
+ logger.debug(`[http-route-extractor] HANDLES_ROUTE query failed; graph providers skipped: ${err instanceof Error ? err.message : String(err)}`);
277
287
  return [];
278
288
  }
279
289
  for (const row of rows) {
280
290
  const filePath = String(row.filePath ?? '');
281
291
  const routePath = String(row.routePath ?? '');
282
292
  const routeSource = String(row.routeSource ?? row.routeReason ?? '');
283
- let method = methodFromRouteReason(routeSource);
293
+ // Prefer the HTTP verb persisted on the Route node by the ingestion
294
+ // routes phase (Spring/Laravel framework routes and decorator routes
295
+ // carry it). Fall back to parsing it out of the edge reason for
296
+ // older indexes or filesystem routes that never stored a method.
297
+ const graphMethod = String(row.routeMethod ?? '')
298
+ .trim()
299
+ .toUpperCase();
300
+ let method = (graphMethod || null) ?? methodFromRouteReason(routeSource);
284
301
  // Look up handler name (and backfill method if missing) from the
285
302
  // plugin's scan of the handler file. This replaces the old
286
303
  // regex-based `inferMethodFromFileScan` and `pickJavaHandlerName`
@@ -396,7 +413,8 @@ export class HttpRouteExtractor {
396
413
  try {
397
414
  rows = await db(FETCHES_QUERY);
398
415
  }
399
- catch {
416
+ catch (err) {
417
+ logger.debug(`[http-route-extractor] FETCHES query failed; graph consumers skipped: ${err instanceof Error ? err.message : String(err)}`);
400
418
  return [];
401
419
  }
402
420
  for (const row of rows) {
@@ -14,6 +14,15 @@ import type { PipelinePhase } from './types.js';
14
14
  export interface RouteEntry {
15
15
  filePath: string;
16
16
  source: string;
17
+ /**
18
+ * HTTP verb for this route when ingestion knows it structurally
19
+ * (Spring/Laravel framework routes and decorator routes carry
20
+ * `httpMethod`; filesystem-derived routes — Next.js/Expo/PHP file
21
+ * routes — do not, so this stays undefined for them). Persisted onto
22
+ * the Route node so downstream contract extraction can read the verb
23
+ * from the graph instead of re-parsing the handler source.
24
+ */
25
+ method?: string;
17
26
  }
18
27
  export interface RoutesOutput {
19
28
  routeRegistry: Map<string, RouteEntry>;
@@ -26,4 +35,5 @@ export interface TemplateFetchCall {
26
35
  export declare const isTemplateRouteCandidate: (filePath: string) => boolean;
27
36
  export declare function extractTemplateStaticFetchCalls(filePath: string, content: string, namedRouteUrls?: ReadonlyMap<string, string>): TemplateFetchCall[];
28
37
  export declare function normalizeExtractedRoutePath(routePath: string, prefix: string | null): string;
38
+ export declare function normalizeRouteMethod(raw: string | null | undefined): string | undefined;
29
39
  export declare const routesPhase: PipelinePhase<RoutesOutput>;
@@ -97,6 +97,32 @@ export function normalizeExtractedRoutePath(routePath, prefix) {
97
97
  function escapeRegex(s) {
98
98
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
99
99
  }
100
+ /**
101
+ * Canonicalize a route's HTTP verb for persistence on the Route node.
102
+ * Returns an upper-cased standard method, or `undefined` when the value
103
+ * is not a real HTTP verb. Laravel `Route::resource` / `apiResource`
104
+ * surface `httpMethod` values like `resource` / `apiResource` (they
105
+ * expand to several verbs at runtime), so they must not be stored as a
106
+ * method — leaving them `undefined` keeps the column clean and lets the
107
+ * contract extractor fall back to its source-scan path for those routes.
108
+ */
109
+ const VALID_HTTP_METHODS = new Set([
110
+ 'GET',
111
+ 'POST',
112
+ 'PUT',
113
+ 'PATCH',
114
+ 'DELETE',
115
+ 'HEAD',
116
+ 'OPTIONS',
117
+ 'TRACE',
118
+ 'CONNECT',
119
+ ]);
120
+ export function normalizeRouteMethod(raw) {
121
+ if (typeof raw !== 'string')
122
+ return undefined;
123
+ const verb = raw.trim().toUpperCase();
124
+ return VALID_HTTP_METHODS.has(verb) ? verb : undefined;
125
+ }
100
126
  export const routesPhase = {
101
127
  name: 'routes',
102
128
  deps: ['parse'],
@@ -166,6 +192,7 @@ export const routesPhase = {
166
192
  addRoute(routeUrl, {
167
193
  filePath: route.filePath,
168
194
  source: 'framework-route',
195
+ method: normalizeRouteMethod(route.httpMethod),
169
196
  });
170
197
  if (route.routeName && !namedRouteRegistry.has(route.routeName)) {
171
198
  namedRouteRegistry.set(route.routeName, routeUrl);
@@ -176,6 +203,7 @@ export const routesPhase = {
176
203
  addRoute(url, {
177
204
  filePath: dr.filePath,
178
205
  source: `decorator-${dr.decoratorName}`,
206
+ method: normalizeRouteMethod(dr.httpMethod),
179
207
  });
180
208
  }
181
209
  let handlerContents;
@@ -183,7 +211,7 @@ export const routesPhase = {
183
211
  const handlerPaths = [...routeRegistry.values()].map((e) => e.filePath);
184
212
  handlerContents = await readFileContents(ctx.repoPath, handlerPaths);
185
213
  for (const [routeURL, entry] of routeRegistry) {
186
- const { filePath: handlerPath, source: routeSource } = entry;
214
+ const { filePath: handlerPath, source: routeSource, method: routeMethod } = entry;
187
215
  const content = handlerContents.get(handlerPath);
188
216
  const { responseKeys, errorKeys } = content
189
217
  ? handlerPath.endsWith('.php')
@@ -199,6 +227,7 @@ export const routesPhase = {
199
227
  properties: {
200
228
  name: routeURL,
201
229
  filePath: handlerPath,
230
+ ...(routeMethod ? { method: routeMethod } : {}),
202
231
  ...(responseKeys ? { responseKeys } : {}),
203
232
  ...(errorKeys ? { errorKeys } : {}),
204
233
  ...(middleware && middleware.length > 0 ? { middleware } : {}),
@@ -297,7 +297,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir, onNodePhaseCo
297
297
  // Section nodes have an extra 'level' column
298
298
  const sectionWriter = new BufferedCSVWriter(path.join(csvDir, 'section.csv'), 'id,name,filePath,startLine,endLine,level,content,description');
299
299
  // Route nodes for API endpoint mapping
300
- const routeWriter = new BufferedCSVWriter(path.join(csvDir, 'route.csv'), 'id,name,filePath,responseKeys,errorKeys,middleware');
300
+ const routeWriter = new BufferedCSVWriter(path.join(csvDir, 'route.csv'), 'id,name,filePath,responseKeys,errorKeys,middleware,method');
301
301
  // Tool nodes for MCP tool definitions
302
302
  const toolWriter = new BufferedCSVWriter(path.join(csvDir, 'tool.csv'), 'id,name,filePath,description');
303
303
  // BasicBlock nodes — taint/PDG substrate (issue #2080). No `name` column;
@@ -444,6 +444,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir, onNodePhaseCo
444
444
  escapeCSVField(keysStr),
445
445
  escapeCSVField(errorKeysStr),
446
446
  escapeCSVField(middlewareStr),
447
+ escapeCSVField(String(node.properties.method ?? '')),
447
448
  ].join(','));
448
449
  break;
449
450
  }
@@ -1119,7 +1119,7 @@ export const getCopyQuery = (table, filePath) => {
1119
1119
  return `COPY ${t}(id, name, filePath, startLine, endLine, level, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1120
1120
  }
1121
1121
  if (table === 'Route') {
1122
- return `COPY ${t}(id, name, filePath, responseKeys, errorKeys, middleware) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1122
+ return `COPY ${t}(id, name, filePath, responseKeys, errorKeys, middleware, method) FROM "${filePath}" ${COPY_CSV_OPTS}`;
1123
1123
  }
1124
1124
  if (table === 'Tool') {
1125
1125
  return `COPY ${t}(id, name, filePath, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
@@ -39,7 +39,7 @@ export declare const ANNOTATION_SCHEMA: string;
39
39
  export declare const CONSTRUCTOR_SCHEMA: string;
40
40
  export declare const TEMPLATE_SCHEMA: string;
41
41
  export declare const MODULE_SCHEMA: string;
42
- export declare const ROUTE_SCHEMA = "\nCREATE NODE TABLE Route (\n id STRING,\n name STRING,\n filePath STRING,\n responseKeys STRING[],\n errorKeys STRING[],\n middleware STRING[],\n PRIMARY KEY (id)\n)";
42
+ export declare const ROUTE_SCHEMA = "\nCREATE NODE TABLE Route (\n id STRING,\n name STRING,\n filePath STRING,\n responseKeys STRING[],\n errorKeys STRING[],\n middleware STRING[],\n method STRING,\n PRIMARY KEY (id)\n)";
43
43
  export declare const TOOL_SCHEMA = "\nCREATE NODE TABLE Tool (\n id STRING,\n name STRING,\n filePath STRING,\n description STRING,\n PRIMARY KEY (id)\n)";
44
44
  export declare const SECTION_SCHEMA = "\nCREATE NODE TABLE Section (\n id STRING,\n name STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n level INT64,\n content STRING,\n description STRING,\n PRIMARY KEY (id)\n)";
45
45
  export declare const BASICBLOCK_SCHEMA = "\nCREATE NODE TABLE BasicBlock (\n id STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n text STRING,\n PRIMARY KEY (id)\n)";
@@ -177,6 +177,7 @@ CREATE NODE TABLE Route (
177
177
  responseKeys STRING[],
178
178
  errorKeys STRING[],
179
179
  middleware STRING[],
180
+ method STRING,
180
181
  PRIMARY KEY (id)
181
182
  )`;
182
183
  // MCP tool definitions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.8-rc.55",
3
+ "version": "1.6.8-rc.56",
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",