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.
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/lbug/csv-generator.js +2 -1
- package/dist/core/lbug/lbug-adapter.js +1 -1
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +1 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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)";
|
package/dist/core/lbug/schema.js
CHANGED
package/package.json
CHANGED