gitnexus 1.6.9-rc.7 → 1.6.9-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.
Files changed (38) hide show
  1. package/dist/core/group/bridge-db.js +24 -6
  2. package/dist/core/group/cross-impact.d.ts +34 -1
  3. package/dist/core/group/cross-impact.js +34 -10
  4. package/dist/core/group/cross-trace.d.ts +135 -0
  5. package/dist/core/group/cross-trace.js +930 -0
  6. package/dist/core/group/extractors/http-patterns/go.js +3 -0
  7. package/dist/core/group/extractors/http-patterns/java.js +26 -0
  8. package/dist/core/group/extractors/http-patterns/kotlin.js +6 -0
  9. package/dist/core/group/extractors/http-patterns/node.js +16 -2
  10. package/dist/core/group/extractors/http-patterns/php.js +14 -0
  11. package/dist/core/group/extractors/http-patterns/python.js +21 -0
  12. package/dist/core/group/extractors/http-patterns/types.d.ts +46 -0
  13. package/dist/core/group/extractors/http-route-extractor.d.ts +1 -1
  14. package/dist/core/group/extractors/http-route-extractor.js +281 -115
  15. package/dist/core/group/service.d.ts +56 -0
  16. package/dist/core/group/service.js +4 -0
  17. package/dist/core/group/types.d.ts +7 -0
  18. package/dist/core/ingestion/call-processor.d.ts +26 -0
  19. package/dist/core/ingestion/call-processor.js +73 -0
  20. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +4 -0
  21. package/dist/core/ingestion/pipeline-phases/parse-impl.js +5 -1
  22. package/dist/core/ingestion/pipeline-phases/parse.d.ts +3 -0
  23. package/dist/core/ingestion/pipeline-phases/routes.d.ts +2 -1
  24. package/dist/core/ingestion/pipeline-phases/routes.js +6 -7
  25. package/dist/core/ingestion/route-extractors/route-path.d.ts +15 -0
  26. package/dist/core/ingestion/route-extractors/route-path.js +20 -0
  27. package/dist/core/ingestion/route-extractors/spring.js +4 -0
  28. package/dist/core/ingestion/workers/parse-worker.d.ts +10 -0
  29. package/dist/core/lbug/csv-generator.js +2 -1
  30. package/dist/core/lbug/lbug-adapter.js +1 -1
  31. package/dist/core/lbug/schema.d.ts +1 -1
  32. package/dist/core/lbug/schema.js +1 -0
  33. package/dist/mcp/local/local-backend.d.ts +24 -0
  34. package/dist/mcp/local/local-backend.js +166 -2
  35. package/dist/mcp/tools.js +29 -3
  36. package/dist/storage/parse-cache.js +1 -1
  37. package/package.json +1 -1
  38. package/scripts/cross-platform-tests.ts +4 -0
@@ -171,11 +171,19 @@ export async function closeBridgeDb(handle) {
171
171
  // pending on disk, which makes a subsequent read-side open either race
172
172
  // with the WAL replay or trip the database-id check on the sidecars.
173
173
  // CHECKPOINT is a no-op when there's nothing pending, so it's cheap.
174
- try {
175
- await handle._conn.query('CHECKPOINT');
176
- }
177
- catch {
178
- /* ignore older LadybugDB or schemaless DB may not accept it */
174
+ //
175
+ // ONLY on a writable handle. A read-only connection has nothing to flush,
176
+ // and issuing CHECKPOINT on it leaves a WAL/shadow lock artifact that makes
177
+ // the very next read-only open of the same path fail in-process — which broke
178
+ // repeated `@group` impact/trace calls in a long-lived MCP server (the read
179
+ // path opens read-only, queries, and closes per call).
180
+ if (!handle._readOnly) {
181
+ try {
182
+ await handle._conn.query('CHECKPOINT');
183
+ }
184
+ catch {
185
+ /* ignore — older LadybugDB or schemaless DB may not accept it */
186
+ }
179
187
  }
180
188
  try {
181
189
  await handle._conn.close();
@@ -189,6 +197,16 @@ export async function closeBridgeDb(handle) {
189
197
  catch {
190
198
  /* ignore */
191
199
  }
200
+ // NOTE: Windows in-process write→read reopen of the SAME bridge.lbug is still a
201
+ // known limitation (the writable close's OS file handle is not released before
202
+ // the read open races; the existing open-side LBUG_OPEN_RETRY only retries
203
+ // lock-pattern errors, not the post-rename sidecar database-id mismatch). The
204
+ // bridge's close-then-reopen tests stay Windows-skipped. A close-side
205
+ // waitForWindowsHandleRelease + finalizeLbugSidecarsAfterClose probe (mirroring
206
+ // safeClose) was tried and did NOT close that gap on Windows CI, so it was
207
+ // removed rather than carry latency/duplication for no Windows benefit. The
208
+ // read-only CHECKPOINT skip above is the load-bearing fix and works on
209
+ // Linux/macOS (the platforms where in-process reopen is supported).
192
210
  }
193
211
  /* ------------------------------------------------------------------ */
194
212
  /* retryRename — handles transient EBUSY/EPERM/EACCES on Windows */
@@ -577,7 +595,7 @@ export async function openBridgeDbReadOnly(groupDir) {
577
595
  // (where we can retry) instead of on the first user query.
578
596
  await handle.db.init();
579
597
  await handle.conn.init();
580
- return { _db: handle.db, _conn: handle.conn, groupDir };
598
+ return { _db: handle.db, _conn: handle.conn, groupDir, _readOnly: true };
581
599
  }
582
600
  catch (err) {
583
601
  lastErr = err;
@@ -2,12 +2,21 @@
2
2
  * Cross-repo impact (Phase 1 local walk + Phase 2 bridge fan-out).
3
3
  * All bridge Cypher for this feature lives in this module.
4
4
  */
5
- import type { CrossRepoImpact, GroupImpactResult } from './types.js';
5
+ import type { BridgeHandle, CrossRepoImpact, GroupImpactResult } from './types.js';
6
6
  import type { GroupToolPort } from './service.js';
7
7
  /** Cross-boundary hops beyond this value are clamped (multi-hop reserved for future work). */
8
8
  export declare const MAX_SUPPORTED_CROSS_DEPTH = 1;
9
9
  /** Default wall-clock budget for the Phase 1 `impact` leg when callers omit `timeoutMs`. */
10
10
  export declare const DEFAULT_LOCAL_IMPACT_TIMEOUT_MS = 30000;
11
+ export type BridgeNeighborRow = {
12
+ neighborRepo: string;
13
+ neighborUid: string;
14
+ neighborFilePath?: string;
15
+ matchType: string;
16
+ confidence: number;
17
+ contractId: string;
18
+ contractType: string;
19
+ };
11
20
  export interface RunGroupImpactDeps {
12
21
  port: GroupToolPort;
13
22
  gitnexusDir: string;
@@ -80,6 +89,30 @@ export declare function collectImpactSymbolUids(local: unknown, servicePrefix: s
80
89
  targetFilePath?: string;
81
90
  };
82
91
  export declare function mergeRisk(localRisk: string, cross: CrossRepoImpact[]): string;
92
+ export declare function ensureBridgeReady(groupDir: string): Promise<{
93
+ handle: BridgeHandle;
94
+ } | {
95
+ error: string;
96
+ }>;
97
+ /**
98
+ * Resolve cross-repo neighbors over `ContractLink` for a set of local symbol
99
+ * UIDs, in a single direction, sorted by descending confidence.
100
+ *
101
+ * This is the one shared consumer↔provider bridge join. `runGroupImpact`'s
102
+ * Phase-2 fan-out uses it directly; the cross-repo trace path (`cross-trace.ts`)
103
+ * reuses the same `queryBridge` + row-normalization primitives but issues a
104
+ * distinct *pair* query, because a trace must keep BOTH endpoints of a crossing
105
+ * (this neighbor join intentionally returns only the far side, which is lossy
106
+ * for stitching a path). Keeping this helper as the single uid-filtered join
107
+ * means impact never forks its own copy of the neighbor Cypher.
108
+ *
109
+ * Returns `[]` for an empty `uids` set without touching the DB.
110
+ */
111
+ export declare function resolveBridgeNeighbors(handle: BridgeHandle, opts: {
112
+ localRepo: string;
113
+ uids: string[];
114
+ direction: 'upstream' | 'downstream';
115
+ }): Promise<BridgeNeighborRow[]>;
83
116
  export declare function runGroupImpact(deps: RunGroupImpactDeps, params: Record<string, unknown>): Promise<GroupImpactResult | {
84
117
  error: string;
85
118
  }>;
@@ -267,7 +267,7 @@ export function mergeRisk(localRisk, cross) {
267
267
  return 'MEDIUM';
268
268
  return localRisk;
269
269
  }
270
- async function ensureBridgeReady(groupDir) {
270
+ export async function ensureBridgeReady(groupDir) {
271
271
  const meta = await readBridgeMeta(groupDir);
272
272
  if (meta.version > 0 && meta.version !== BRIDGE_SCHEMA_VERSION) {
273
273
  return {
@@ -306,6 +306,37 @@ function rowToNeighbor(r) {
306
306
  contractType: String(r.contractType ?? r[6] ?? 'custom'),
307
307
  };
308
308
  }
309
+ /**
310
+ * Resolve cross-repo neighbors over `ContractLink` for a set of local symbol
311
+ * UIDs, in a single direction, sorted by descending confidence.
312
+ *
313
+ * This is the one shared consumer↔provider bridge join. `runGroupImpact`'s
314
+ * Phase-2 fan-out uses it directly; the cross-repo trace path (`cross-trace.ts`)
315
+ * reuses the same `queryBridge` + row-normalization primitives but issues a
316
+ * distinct *pair* query, because a trace must keep BOTH endpoints of a crossing
317
+ * (this neighbor join intentionally returns only the far side, which is lossy
318
+ * for stitching a path). Keeping this helper as the single uid-filtered join
319
+ * means impact never forks its own copy of the neighbor Cypher.
320
+ *
321
+ * Returns `[]` for an empty `uids` set without touching the DB.
322
+ */
323
+ export async function resolveBridgeNeighbors(handle, opts) {
324
+ if (opts.uids.length === 0)
325
+ return [];
326
+ const cypher = opts.direction === 'upstream' ? CY_NEIGHBORS_UPSTREAM : CY_NEIGHBORS_DOWNSTREAM;
327
+ const rows = await queryBridge(handle, cypher, {
328
+ localRepo: opts.localRepo,
329
+ uids: opts.uids,
330
+ });
331
+ const neighbors = [];
332
+ for (const raw of rows) {
333
+ const n = rowToNeighbor(raw);
334
+ if (n)
335
+ neighbors.push(n);
336
+ }
337
+ neighbors.sort((a, b) => b.confidence - a.confidence);
338
+ return neighbors;
339
+ }
309
340
  export async function runGroupImpact(deps, params) {
310
341
  const parsed = validateGroupImpactParams(params);
311
342
  if (parsed.ok === false)
@@ -418,18 +449,11 @@ export async function runGroupImpact(deps, params) {
418
449
  const outOfScope = [];
419
450
  const truncatedRepos = [];
420
451
  try {
421
- const cypher = direction === 'upstream' ? CY_NEIGHBORS_UPSTREAM : CY_NEIGHBORS_DOWNSTREAM;
422
- const rows = await queryBridge(handle, cypher, {
452
+ const neighbors = await resolveBridgeNeighbors(handle, {
423
453
  localRepo: repoPath,
424
454
  uids,
455
+ direction,
425
456
  });
426
- const neighbors = [];
427
- for (const raw of rows) {
428
- const n = rowToNeighbor(raw);
429
- if (n)
430
- neighbors.push(n);
431
- }
432
- neighbors.sort((a, b) => b.confidence - a.confidence);
433
457
  const seen = new Set();
434
458
  for (const n of neighbors) {
435
459
  if (servicePrefix && !fileMatchesServicePrefix(n.neighborFilePath, servicePrefix)) {
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Cross-repo call trace.
3
+ *
4
+ * Stitches per-repo directed-path segments (CALLS + HAS_METHOD, via the
5
+ * existing single-repo `trace`) across a single `ContractLink` boundary in the
6
+ * group bridge, and — when a `--pdg` layer is present and the caller opts in —
7
+ * enriches the boundary-adjacent segments with their intra-procedural
8
+ * REACHING_DEF data-flow.
9
+ *
10
+ * Design notes:
11
+ * - The crossing is a single bridge hop joined on `symbolUid` (the symbol node
12
+ * id is the same value the bridge stores as `Contract.symbolUid`). This
13
+ * mirrors `cross-impact.ts` and is clamped to one boundary
14
+ * (`MAX_SUPPORTED_CROSS_DEPTH`); multi-hop is deferred.
15
+ * - All trace-specific bridge Cypher lives in THIS module (mirroring the
16
+ * "all bridge Cypher for this feature lives here" convention in
17
+ * cross-impact.ts). The trace needs BOTH endpoints of a crossing to stitch a
18
+ * path, so it issues its own pair query rather than the lossy uid-filtered
19
+ * neighbor join exported by cross-impact (`resolveBridgeNeighbors`), which
20
+ * intentionally returns only the far side.
21
+ * - PDG is enrichment only: data flow never crosses the repo boundary. Full
22
+ * cross-program (SDG-like) data flow is deferred — see
23
+ * docs/plans/2026-06-18-002-feat-unified-pdg-impact-evaluation-plan.md.
24
+ */
25
+ import type { GroupPdgFlowHop, GroupToolPort } from './service.js';
26
+ export interface TraceHop {
27
+ name: string;
28
+ filePath: string;
29
+ startLine: number;
30
+ /** The member repo path (group.yaml key) this hop belongs to. */
31
+ repo: string;
32
+ }
33
+ export interface TraceEdge {
34
+ relType: string;
35
+ confidence: number;
36
+ }
37
+ export interface SegmentDataFlow {
38
+ /** Member repo path of the enriched segment. */
39
+ repo: string;
40
+ /** The boundary-adjacent symbol the flow was anchored on. */
41
+ anchor: string;
42
+ variable?: string;
43
+ hops: GroupPdgFlowHop[];
44
+ truncated?: boolean;
45
+ }
46
+ export interface BridgeCrossing {
47
+ fromRepo: string;
48
+ toRepo: string;
49
+ contractId: string;
50
+ contractType: string;
51
+ matchType: string;
52
+ confidence: number;
53
+ }
54
+ export interface GroupTraceEndpoint {
55
+ name: string;
56
+ filePath: string;
57
+ startLine: number;
58
+ repo: string;
59
+ }
60
+ export interface GroupTraceOkResult {
61
+ status: 'ok';
62
+ group: string;
63
+ from: GroupTraceEndpoint;
64
+ to: GroupTraceEndpoint;
65
+ /** 0 for a same-repo trace, 1 for a single boundary crossing. */
66
+ crossings: BridgeCrossing[];
67
+ hopCount: number;
68
+ hops: TraceHop[];
69
+ edges: TraceEdge[];
70
+ /** Present only when PDG enrichment ran for at least one segment. */
71
+ dataFlow?: SegmentDataFlow[];
72
+ truncated?: boolean;
73
+ notes: string[];
74
+ }
75
+ export interface GroupTraceCandidate {
76
+ repo: string;
77
+ id: string;
78
+ name: string;
79
+ filePath: string;
80
+ startLine: number;
81
+ }
82
+ export interface GroupTraceNotFoundResult {
83
+ status: 'not_found';
84
+ group: string;
85
+ role?: 'from' | 'to';
86
+ query?: string;
87
+ /**
88
+ * True when the answer is NOT authoritative: the crossing cap
89
+ * (`MAX_CROSSINGS_TO_TRY`) was hit, so a connecting ContractLink ranked beyond
90
+ * the cap may have been skipped. A consumer should treat this as "unknown",
91
+ * not "no path exists".
92
+ */
93
+ truncated?: boolean;
94
+ notes: string[];
95
+ suggestion?: string;
96
+ }
97
+ export interface GroupTraceAmbiguousResult {
98
+ status: 'ambiguous';
99
+ group: string;
100
+ role: 'from' | 'to';
101
+ candidates: GroupTraceCandidate[];
102
+ notes: string[];
103
+ }
104
+ export interface GroupTraceErrorResult {
105
+ status: 'error';
106
+ group: string;
107
+ error: string;
108
+ notes: string[];
109
+ }
110
+ export type GroupTraceResult = GroupTraceOkResult | GroupTraceNotFoundResult | GroupTraceAmbiguousResult | GroupTraceErrorResult;
111
+ /**
112
+ * Centralized degraded-state messages so wording stays consistent and
113
+ * testable. Kept as named constants/builders rather than inline strings.
114
+ */
115
+ export declare const TRACE_NOTES: {
116
+ readonly noBridgeLink: string;
117
+ readonly crossDepthClamped: string;
118
+ readonly noLocalPath: (repo: string) => string;
119
+ readonly noPdgLayer: (repo: string) => string;
120
+ readonly pdgRequested: string;
121
+ readonly pdgSameRepoNoop: string;
122
+ readonly crossingsCapped: (cap: number) => string;
123
+ readonly degradedMembers: (repos: string[]) => string;
124
+ readonly fileBoundaryFallback: string;
125
+ readonly anonymousHandler: (route: string, location: string) => string;
126
+ readonly destinationNoLink: string;
127
+ readonly destinationNoReach: string;
128
+ readonly destinationMultiple: string;
129
+ readonly destinationAmbiguousFile: string;
130
+ };
131
+ export interface RunGroupTraceDeps {
132
+ port: GroupToolPort;
133
+ gitnexusDir: string;
134
+ }
135
+ export declare function runGroupTrace(deps: RunGroupTraceDeps, params: Record<string, unknown>): Promise<GroupTraceResult>;