gitnexus 1.6.9-rc.12 → 1.6.9-rc.14

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.
@@ -576,13 +576,16 @@ export const JAVA_HTTP_PLUGIN = {
576
576
  language: Java,
577
577
  // routeCoverage intentionally LEFT at the default 'partial' (#2138 Part 2).
578
578
  // The graph provider set is a strict *subset* of this scan()'s provider set —
579
- // ingestion does NOT emit a Route node for (1) array-form `@GetMapping({...})`,
580
- // (2) interface-inherited Spring routes, or (3) the 2nd verb of a same-URL
581
- // GET+POST pair (Route nodes are URL-keyed). Declaring 'complete' here would
582
- // let the parse-skip drop those group-only providers. Java flips to 'complete'
583
- // only once ingestion provider extraction matches this scan (a follow-up:
584
- // array-form query branch + interface-inheritance emission + per-verb Route
585
- // identity). `hasConsumerSignals` below is kept ready for that flip.
579
+ // ingestion does NOT emit a Route node for (1) a method-level array route
580
+ // nested under a class-level array-form `@RequestMapping` (ingestion suppresses
581
+ // it rather than drop the prefix; bare/scalar-prefixed array methods ARE now
582
+ // emitted see #2280), (2) interface-inherited Spring routes, or (3) the 2nd
583
+ // verb of a same-URL GET+POST pair (Route nodes are URL-keyed). Declaring
584
+ // 'complete' here would let the parse-skip drop those group-only providers.
585
+ // Java flips to 'complete' only once ingestion provider extraction matches this
586
+ // scan (a follow-up: class-level array-form prefix support + interface-
587
+ // inheritance emission + per-verb Route identity — tracked in #2280).
588
+ // `hasConsumerSignals` below is kept ready for that flip.
586
589
  // Consumer signals this plugin's scan() can detect: RestTemplate / WebClient /
587
590
  // OkHttp / Java-HttpClient / Apache-HttpClient call sites, OpenFeign
588
591
  // (`@FeignClient` + `@RequestLine`) interfaces, and Spring 6 HTTP Interface
@@ -31,6 +31,18 @@ import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, unquot
31
31
  * @node → enclosing declaration (class_declaration | method_declaration)
32
32
  * @value → the string-literal argument
33
33
  * @key → the named-argument member key (absent for positional form)
34
+ *
35
+ * Method-level routes accept both the bare string form `@GetMapping("/x")` and
36
+ * the array form `@GetMapping({"/a","/b"})` (positional or `path =`/`value =`):
37
+ * a multi-element array yields one match per element, so the Phase 2 loop emits
38
+ * one route per path with no special-casing. This mirrors the group-layer
39
+ * `java.ts` query so the two Spring extractors stay in parity (#2138 follow-up;
40
+ * the divergence here was the root of the #2265 array-form gap). The class-level
41
+ * `@RequestMapping` branches also match the array form, but only to *detect* it:
42
+ * an array-form class prefix can't be resolved to a single string, so Phase 2
43
+ * suppresses that class's method-level array routes rather than emit them with a
44
+ * dropped prefix (a wrong route). Full class-array cross-product support is left
45
+ * to a follow-up (#2280).
34
46
  */
35
47
  const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
36
48
  [
@@ -38,7 +50,9 @@ const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
38
50
  (modifiers
39
51
  (annotation
40
52
  name: (identifier) @ann
41
- arguments: (annotation_argument_list (string_literal) @value)))) @node
53
+ arguments: (annotation_argument_list
54
+ [(string_literal) @value
55
+ (element_value_array_initializer (string_literal) @value)])))) @node
42
56
  (class_declaration
43
57
  (modifiers
44
58
  (annotation
@@ -46,12 +60,15 @@ const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
46
60
  arguments: (annotation_argument_list
47
61
  (element_value_pair
48
62
  key: (identifier) @key
49
- value: (string_literal) @value))))) @node
63
+ value: [(string_literal) @value
64
+ (element_value_array_initializer (string_literal) @value)]))))) @node
50
65
  (method_declaration
51
66
  (modifiers
52
67
  (annotation
53
68
  name: (identifier) @ann
54
- arguments: (annotation_argument_list (string_literal) @value)))) @node
69
+ arguments: (annotation_argument_list
70
+ [(string_literal) @value
71
+ (element_value_array_initializer (string_literal) @value)])))) @node
55
72
  (method_declaration
56
73
  (modifiers
57
74
  (annotation
@@ -59,7 +76,8 @@ const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
59
76
  arguments: (annotation_argument_list
60
77
  (element_value_pair
61
78
  key: (identifier) @key
62
- value: (string_literal) @value))))) @node
79
+ value: [(string_literal) @value
80
+ (element_value_array_initializer (string_literal) @value)]))))) @node
63
81
  ]
64
82
  `);
65
83
  /**
@@ -76,8 +94,15 @@ const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
76
94
  */
77
95
  export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
78
96
  const matches = ROUTE_ANNOTATION_QUERY.matches(tree.rootNode);
79
- // Phase 1: collect class-level @RequestMapping prefixes keyed by node id
97
+ // Phase 1: collect class-level @RequestMapping prefixes keyed by node id.
98
+ // A scalar prefix (`@RequestMapping("/base")`) is stored in prefixByClassId.
99
+ // A class whose @RequestMapping uses the array form (`@RequestMapping({...})`)
100
+ // is instead recorded in classesWithArrayPrefix: there is no single prefix to
101
+ // store, and Phase 2 uses this to suppress that class's method-level array
102
+ // routes rather than emit them unprefixed (a wrong route — see #2280). Full
103
+ // class-array cross-product support is out of scope here.
80
104
  const prefixByClassId = new Map();
105
+ const classesWithArrayPrefix = new Set();
81
106
  for (const match of matches) {
82
107
  const caps = {};
83
108
  for (const { name, node } of match.captures) {
@@ -92,6 +117,10 @@ export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
92
117
  if (node.type === 'class_declaration' && annNode.text === 'RequestMapping') {
93
118
  if (!isRouteMemberKey(keyNode))
94
119
  continue;
120
+ if (valueNode.parent?.type === 'element_value_array_initializer') {
121
+ classesWithArrayPrefix.add(node.id);
122
+ continue;
123
+ }
95
124
  const prefix = unquoteSpringLiteral(valueNode.text);
96
125
  if (prefix !== null)
97
126
  prefixByClassId.set(node.id, prefix);
@@ -122,6 +151,18 @@ export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
122
151
  if (routePath === null)
123
152
  continue;
124
153
  const enclosingClass = findEnclosingClass(node);
154
+ // Suppress a method-level *array-form* route nested under a class-level
155
+ // array-form @RequestMapping. The class prefix is one of several values that
156
+ // cannot be resolved to a single string here, so emitting the route would
157
+ // drop the prefix and yield a wrong unprefixed Route (a false signal, worse
158
+ // than a missing one). Skipping keeps ingestion a strict subset of the group
159
+ // scan — safe under routeCoverage:'partial'. Full class-array cross-product
160
+ // support is tracked in #2280. (Scalar method paths under an array class
161
+ // prefix are left unchanged: that pre-existing divergence is out of scope.)
162
+ const isArrayElement = valueNode.parent?.type === 'element_value_array_initializer';
163
+ if (isArrayElement && enclosingClass && classesWithArrayPrefix.has(enclosingClass.id)) {
164
+ continue;
165
+ }
125
166
  const classPrefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
126
167
  // `node` is the annotated `method_declaration`; its name field is the
127
168
  // handler method name (resolved to a symbol UID later by the routes phase).
@@ -36,6 +36,12 @@ export interface CreateLoggerOptions {
36
36
  debugEnvVar?: string;
37
37
  /** Override destination stream — primarily for tests. */
38
38
  destination?: DestinationStream;
39
+ /**
40
+ * Explicit level for the destination-override path — primarily for tests that
41
+ * need to capture below the default `info` (e.g. asserting a `debug` record).
42
+ * Ignored unless `destination` is set; `debugEnvVar` still wins when truthy.
43
+ */
44
+ level?: string;
39
45
  }
40
46
  /**
41
47
  * Flush any buffered records on the default destination. Entry-point
@@ -114,7 +120,10 @@ export interface LoggerCapture {
114
120
  * expect(cap.records().some(r => r.msg?.includes('clamping'))).toBe(true);
115
121
  * });
116
122
  *
123
+ * Pass `level` (e.g. 'debug') to capture below the default 'info' — needed to
124
+ * assert that a record was emitted at debug rather than merely absent.
125
+ *
117
126
  * Not a public API; underscore-prefixed and called only from test code.
118
127
  * Throws if a previous capture is still active — see the body for context.
119
128
  */
120
- export declare function _captureLogger(): LoggerCapture;
129
+ export declare function _captureLogger(level?: string): LoggerCapture;
@@ -202,7 +202,7 @@ function buildBaseOptions() {
202
202
  export function createLogger(name, opts) {
203
203
  const debugRequested = opts?.debugEnvVar ? isTruthyEnv(process.env[opts.debugEnvVar]) : false;
204
204
  if (opts?.destination) {
205
- return pino({ level: debugRequested ? 'debug' : 'info', base: undefined, name }, opts.destination);
205
+ return pino({ level: debugRequested ? 'debug' : (opts.level ?? 'info'), base: undefined, name }, opts.destination);
206
206
  }
207
207
  const base = buildBaseOptions();
208
208
  // When using a transport (pino-pretty), pino manages the destination
@@ -226,6 +226,7 @@ export function createLogger(name, opts) {
226
226
  /* Default singleton (Proxy-backed for test capture) */
227
227
  /* ------------------------------------------------------------------ */
228
228
  let _activeDestination;
229
+ let _activeLevel;
229
230
  let _cached;
230
231
  function _getInner() {
231
232
  if (_cached)
@@ -233,7 +234,7 @@ function _getInner() {
233
234
  // Always go through createLogger so future defaults (serializers, redaction,
234
235
  // formatters) apply uniformly. The destination override is honored when set
235
236
  // by `_captureLogger()` below.
236
- _cached = createLogger('gitnexus', _activeDestination ? { destination: _activeDestination } : undefined);
237
+ _cached = createLogger('gitnexus', _activeDestination ? { destination: _activeDestination, level: _activeLevel } : undefined);
237
238
  return _cached;
238
239
  }
239
240
  /**
@@ -293,10 +294,13 @@ export class MemoryWritable extends Writable {
293
294
  * expect(cap.records().some(r => r.msg?.includes('clamping'))).toBe(true);
294
295
  * });
295
296
  *
297
+ * Pass `level` (e.g. 'debug') to capture below the default 'info' — needed to
298
+ * assert that a record was emitted at debug rather than merely absent.
299
+ *
296
300
  * Not a public API; underscore-prefixed and called only from test code.
297
301
  * Throws if a previous capture is still active — see the body for context.
298
302
  */
299
- export function _captureLogger() {
303
+ export function _captureLogger(level) {
300
304
  // Guard against double-capture: forgetting `restore()` between two
301
305
  // `_captureLogger()` calls silently abandoned the previous capture and
302
306
  // corrupted logger state for the rest of the vitest worker. Throwing here
@@ -307,6 +311,7 @@ export function _captureLogger() {
307
311
  }
308
312
  const w = new MemoryWritable();
309
313
  _activeDestination = w;
314
+ _activeLevel = level;
310
315
  _cached = undefined;
311
316
  return {
312
317
  records: () => w.chunks
@@ -317,6 +322,7 @@ export function _captureLogger() {
317
322
  text: () => w.chunks.join(''),
318
323
  restore: () => {
319
324
  _activeDestination = undefined;
325
+ _activeLevel = undefined;
320
326
  _cached = undefined;
321
327
  },
322
328
  };
@@ -193,10 +193,39 @@ export const IMPACT_RELATION_CONFIDENCE = {
193
193
  * Falls back to 0.5 for unknown types so they are not silently elevated.
194
194
  */
195
195
  const confidenceForRelType = (relType) => IMPACT_RELATION_CONFIDENCE[relType ?? ''] ?? 0.5;
196
- /** Structured error logging for query failures — replaces empty catch blocks */
196
+ /**
197
+ * Structured logging for *swallowed* query failures — replaces empty catch
198
+ * blocks. The level reflects telemetry severity, NOT a promise about the
199
+ * caller: most callers catch the failure and degrade to a genuinely safe
200
+ * fallback (a usable result, usually with a caller-visible `partial`/`ftsUsed`
201
+ * flag), so these are not operation-level errors and must not log at `error`:
202
+ *
203
+ * - A benign missing optional table/label/column — a repo analyzed without
204
+ * processes/communities, or a pre-v3 PDG index lacking the `calleeIds`
205
+ * column — is a normal configuration, not a failure. Logged at `debug`
206
+ * (suppressed at the default `info` level; surfaced only when troubleshooting).
207
+ * - Any other swallowed failure is an unexpected-but-handled degradation:
208
+ * logged at `warn` so it stays observable without raising a false `error`
209
+ * alarm that would drown genuine, operation-aborting failures.
210
+ *
211
+ * `error` is intentionally NOT used here — it is reserved for failures that
212
+ * actually abort an operation, which log directly rather than through this
213
+ * best-effort-degradation helper.
214
+ *
215
+ * Contract for callers (#2283 review): only route a failure here when the
216
+ * caller ALSO surfaces the degradation in its result (a `partial` flag,
217
+ * `failed_files`, `traversalComplete:false`, …). A mutating or safety-critical
218
+ * path that would otherwise report success/clean (e.g. `rename` apply, the
219
+ * `detect_changes` safety gate) MUST set that result-level signal — `warn`
220
+ * alone is not a substitute for an honest result.
221
+ */
197
222
  function logQueryError(context, err) {
198
223
  const msg = err instanceof Error ? err.message : String(err);
199
- logger.error({ context, err: msg }, 'GitNexus query failed');
224
+ if (isBenignMissingTableError(err)) {
225
+ logger.debug({ context, err: msg }, 'GitNexus query skipped (missing optional data)');
226
+ return;
227
+ }
228
+ logger.warn({ context, err: msg }, 'GitNexus query failed (degraded)');
200
229
  }
201
230
  /**
202
231
  * A "missing table/label/relation" prepare error is benign for the query tool's
@@ -208,7 +237,12 @@ function logQueryError(context, err) {
208
237
  */
209
238
  function isBenignMissingTableError(err) {
210
239
  const msg = err instanceof Error ? err.message : String(err ?? '');
211
- return /does not exist|no such (table|label|rel)|unknown (table|label)|not (defined|found)/i.test(msg);
240
+ // The `not (defined|found)` arm is scoped to a schema object (table/label/
241
+ // rel/column/property), mirroring lbug-adapter's isMissingColumnError
242
+ // (`/(table|column|property).*not found/i`): an unscoped "not found" matched
243
+ // operation failures like `rg: not found` (ripgrep absent) or `Symbol not
244
+ // found`, which this helper would then silently demote to `debug` (#2283).
245
+ return /does not exist|no such (table|label|rel)|unknown (table|label)|(table|label|rel|column|property)[^\n]*\bnot (defined|found)\b/i.test(msg);
212
246
  }
213
247
  const isReadOnlyDbError = (err) => {
214
248
  // Walk the `cause` chain (bounded) so a wrapped read-only error (e.g. the
@@ -1574,7 +1608,12 @@ export class LocalBackend {
1574
1608
  ftsResponse = await searchFTSFromLbug(query, limit, repo.lbugPath);
1575
1609
  }
1576
1610
  catch (err) {
1577
- logger.error({ err: err.message }, 'GitNexus: BM25/FTS search failed (FTS indexes may not exist) -');
1611
+ // Swallowed, gracefully-degraded failure: the search falls back to
1612
+ // semantic-only (a valid result), and the most common cause is simply an
1613
+ // un-indexed FTS extension — a normal configuration, not an operation
1614
+ // error. Logged at warn (matching the sibling import-failure fallback
1615
+ // above), never error, so it does not raise a false alarm.
1616
+ logger.warn({ err: err.message }, 'GitNexus: BM25/FTS search failed (FTS indexes may not exist) — falling back to semantic-only');
1578
1617
  return { results: [], ftsUsed: false };
1579
1618
  }
1580
1619
  // Guard against unexpected response shape (#1489) — ftsResponse.results
@@ -3115,6 +3154,9 @@ export class LocalBackend {
3115
3154
  }
3116
3155
  // Map diff hunks to indexed symbols via range overlap
3117
3156
  const changedSymbols = [];
3157
+ // Set if a swallowed graph query fails below — surfaces `partial:true` so a
3158
+ // degraded run cannot report a false-clean `risk_level:'low'` (#2283).
3159
+ let queryDegraded = false;
3118
3160
  for (const fileDiff of fileDiffs) {
3119
3161
  if (fileDiff.hunks.length === 0)
3120
3162
  continue;
@@ -3160,6 +3202,12 @@ export class LocalBackend {
3160
3202
  }
3161
3203
  catch (e) {
3162
3204
  logQueryError('detect-changes:file-symbols', e);
3205
+ // The symbol query failed: changedSymbols stays empty and the result
3206
+ // would otherwise look like a clean no-op (`changed_count:0`,
3207
+ // `risk_level:'low'`). detect_changes is the pre-commit safety gate, so
3208
+ // flag the result `partial` rather than let a swallowed failure
3209
+ // masquerade as "nothing changed" (#2283).
3210
+ queryDegraded = true;
3163
3211
  }
3164
3212
  }
3165
3213
  // Find affected processes -- single batched query instead of N+1
@@ -3194,6 +3242,7 @@ export class LocalBackend {
3194
3242
  }
3195
3243
  catch (e) {
3196
3244
  logQueryError('detect-changes:process-lookup', e);
3245
+ queryDegraded = true;
3197
3246
  }
3198
3247
  }
3199
3248
  const processCount = affectedProcesses.size;
@@ -3213,6 +3262,9 @@ export class LocalBackend {
3213
3262
  },
3214
3263
  changed_symbols: changedSymbols,
3215
3264
  affected_processes: Array.from(affectedProcesses.values()),
3265
+ // A swallowed query failure makes the counts/risk above incomplete — tell
3266
+ // the caller so the safety gate isn't trusted as a clean result (#2283).
3267
+ ...(queryDegraded && { partial: true }),
3216
3268
  };
3217
3269
  }
3218
3270
  /**
@@ -3358,6 +3410,7 @@ export class LocalBackend {
3358
3410
  // Step 4: Apply or preview
3359
3411
  const allChanges = Array.from(changes.values());
3360
3412
  const totalEdits = allChanges.reduce((sum, c) => sum + c.edits.length, 0);
3413
+ const failedFiles = [];
3361
3414
  if (!dry_run) {
3362
3415
  // Apply edits to files
3363
3416
  for (const change of allChanges) {
@@ -3369,12 +3422,16 @@ export class LocalBackend {
3369
3422
  await fs.writeFile(fullPath, content, 'utf-8');
3370
3423
  }
3371
3424
  catch (e) {
3425
+ // A swallowed write failure must not be reported as a full success
3426
+ // (#2283): record the file so the result can degrade to 'partial'
3427
+ // with the unwritten files listed, rather than masquerading as done.
3372
3428
  logQueryError('rename:apply-edit', e);
3429
+ failedFiles.push(change.file_path);
3373
3430
  }
3374
3431
  }
3375
3432
  }
3376
3433
  return {
3377
- status: 'success',
3434
+ status: failedFiles.length > 0 ? 'partial' : 'success',
3378
3435
  old_name: oldName,
3379
3436
  new_name,
3380
3437
  files_affected: allChanges.length,
@@ -3383,6 +3440,7 @@ export class LocalBackend {
3383
3440
  text_search_edits: astSearchEdits,
3384
3441
  changes: allChanges,
3385
3442
  applied: !dry_run,
3443
+ ...(failedFiles.length > 0 && { failed_files: failedFiles }),
3386
3444
  };
3387
3445
  }
3388
3446
  async trace(repo, params) {
@@ -3655,9 +3713,20 @@ export class LocalBackend {
3655
3713
  };
3656
3714
  }
3657
3715
  const mode = modeResult.mode;
3716
+ // #2279: some MCP client/agent adapters serialize an *omitted* optional
3717
+ // numeric field as `0` rather than dropping it, so callgraph calls arrive
3718
+ // carrying a spurious `line: 0`. `line` is meaningless on the callgraph path
3719
+ // (the symbol→symbol BFS has no statement notion), so treat a literal `0`
3720
+ // there as omitted and let the normal traversal run. The coercion is
3721
+ // deliberately narrow — only the literal `0`, only when mode !== 'pdg':
3722
+ // a genuine positive `line` on callgraph still errors (real mode mistake),
3723
+ // negative/fractional values still error, and pdg mode is untouched (the
3724
+ // normalization is an identity there, so `line: 0` is still rejected below —
3725
+ // there is no 1-based source line `0` to anchor on).
3726
+ const effectiveLine = mode !== 'pdg' && params.line === 0 ? undefined : params.line;
3658
3727
  // `line` is a PDG-only statement anchor. Reject it on the callgraph path
3659
3728
  // rather than silently ignore (the symbol→symbol BFS has no statement notion).
3660
- if (params.line !== undefined && mode !== 'pdg') {
3729
+ if (effectiveLine !== undefined && mode !== 'pdg') {
3661
3730
  return {
3662
3731
  error: `Parameter 'line' is only supported with mode:'pdg' (it anchors the dependence slice on a statement). Remove it or set mode:'pdg'.`,
3663
3732
  target: { name: params.target },
@@ -3667,8 +3736,8 @@ export class LocalBackend {
3667
3736
  };
3668
3737
  }
3669
3738
  // A provided `line` must be a positive integer.
3670
- if (params.line !== undefined &&
3671
- (!Number.isInteger(params.line) || params.line < 1)) {
3739
+ if (effectiveLine !== undefined &&
3740
+ (!Number.isInteger(effectiveLine) || effectiveLine < 1)) {
3672
3741
  // Line param fails validation before target resolution → partial-but-typed
3673
3742
  // target on the pdg path (typed PdgImpactTarget, not an inline literal).
3674
3743
  const badLineTarget = { name: params.target };
@@ -3965,7 +4034,11 @@ export class LocalBackend {
3965
4034
  symType,
3966
4035
  direction,
3967
4036
  maxDepth,
3968
- line: params.line,
4037
+ // Use the normalized line, not raw params.line, so the gate and the
4038
+ // engine share one source of truth (#2283). Identity in pdg mode today
4039
+ // — effectiveLine === params.line when mode === 'pdg' — but this stays
4040
+ // correct if the normalization ever stops being an identity here.
4041
+ line: effectiveLine,
3969
4042
  limit: Number.isFinite(params.limit) ? params.limit : 100,
3970
4043
  // KTD2 extraction-seam discipline: hand the engine its DB dependency
3971
4044
  // explicitly rather than `this.`-binding it. LocalBackend owns repo
package/dist/mcp/tools.js CHANGED
@@ -425,8 +425,13 @@ SERVICE: optional monorepo path prefix (case-sensitive path segments). When "rep
425
425
  },
426
426
  line: {
427
427
  type: 'integer',
428
- minimum: 1,
429
- description: "1-based source line PDG statement anchor (mode:'pdg'). Seeds affectedStatements on the statement at this line; inter-procedural symbols are still returned in interproceduralByDepth/pdgInterprocedural and the compatibility byDepth bucket.",
428
+ // `minimum: 0` (not 1) so strict client/agent adapters that materialize
429
+ // an omitted optional numeric field as `0` do not reject the request
430
+ // before sending (#2279). A positive line is still required for a real
431
+ // pdg anchor — the backend enforces that — but `0`/omitted means "no
432
+ // statement anchor" and is tolerated on the callgraph path.
433
+ minimum: 0,
434
+ description: "1-based source line — PDG statement anchor (mode:'pdg'). Seeds affectedStatements on the statement at this line; inter-procedural symbols are still returned in interproceduralByDepth/pdgInterprocedural and the compatibility byDepth bucket. Omit line for whole-symbol pdg (whole-symbol reach + diagnostics); a positive line anchors a statement slice. Literal 0 is tolerated only as an omitted-line compatibility sentinel on the callgraph path and is rejected for mode:'pdg'.",
430
435
  },
431
436
  file_path: {
432
437
  type: 'string',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.9-rc.12",
3
+ "version": "1.6.9-rc.14",
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",