gitnexus 1.6.11-rc.21 → 1.6.11-rc.23

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 (64) hide show
  1. package/README.md +24 -21
  2. package/dist/_shared/graph/types.d.ts +6 -0
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/cli/ai-context.d.ts +4 -0
  5. package/dist/cli/ai-context.js +5 -2
  6. package/dist/cli/analyze-config.js +14 -0
  7. package/dist/cli/analyze-options.d.ts +5 -0
  8. package/dist/cli/analyze.js +2 -0
  9. package/dist/cli/index.js +2 -0
  10. package/dist/cli/watch.js +2 -0
  11. package/dist/core/group/cross-impact.d.ts +3 -0
  12. package/dist/core/group/cross-impact.js +26 -4
  13. package/dist/core/group/extractors/http-patterns/java.js +18 -4
  14. package/dist/core/group/extractors/http-patterns/kotlin.js +2 -0
  15. package/dist/core/group/normalization.d.ts +38 -1
  16. package/dist/core/group/normalization.js +59 -3
  17. package/dist/core/group/service.d.ts +11 -0
  18. package/dist/core/group/service.js +8 -0
  19. package/dist/core/group/sync.d.ts +21 -0
  20. package/dist/core/group/sync.js +15 -3
  21. package/dist/core/group/types.d.ts +13 -0
  22. package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
  23. package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
  24. package/dist/core/ingestion/language-provider.d.ts +38 -1
  25. package/dist/core/ingestion/language-provider.js +22 -0
  26. package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
  27. package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
  28. package/dist/core/ingestion/languages/java.js +8 -7
  29. package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
  30. package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
  31. package/dist/core/ingestion/languages/kotlin.js +3 -0
  32. package/dist/core/ingestion/pipeline-phases/parse-impl.js +6 -1
  33. package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
  34. package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
  35. package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
  36. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
  37. package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
  38. package/dist/core/ingestion/pipeline.d.ts +8 -0
  39. package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
  40. package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
  41. package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
  42. package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
  43. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +7 -8
  44. package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +209 -23
  45. package/dist/core/ingestion/workers/parse-worker.js +5 -1
  46. package/dist/core/lbug/csv-generator.js +4 -1
  47. package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
  48. package/dist/core/lbug/graph-emit-sink.js +4 -2
  49. package/dist/core/lbug/lbug-adapter.js +1 -1
  50. package/dist/core/lbug/schema.d.ts +1 -1
  51. package/dist/core/lbug/schema.js +3 -0
  52. package/dist/core/run-analyze.d.ts +5 -0
  53. package/dist/core/run-analyze.js +56 -0
  54. package/dist/mcp/local/local-backend.js +46 -3
  55. package/dist/mcp/resources.js +1 -0
  56. package/dist/mcp/tools.js +4 -4
  57. package/dist/server/analyze-launch.d.ts +1 -0
  58. package/dist/server/analyze-launch.js +1 -0
  59. package/dist/server/api.d.ts +4 -0
  60. package/dist/server/api.js +46 -4
  61. package/dist/storage/parse-cache.js +8 -1
  62. package/dist/storage/repo-meta.d.ts +12 -0
  63. package/package.json +1 -1
  64. package/skills/gitnexus-cli.md +3 -0
package/README.md CHANGED
@@ -244,6 +244,7 @@ gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexu
244
244
  gitnexus analyze --skip-skills # Skip installing standard .claude/skills/gitnexus-* skill files
245
245
  gitnexus analyze --skip-git # Index folders that are not Git repositories
246
246
  gitnexus analyze --workers <n> # Parse worker pool size (>=1; default: cores-1, capped at 16)
247
+ gitnexus analyze --spring-actuator ./actuator # Enrich with local Spring Boot Actuator JSON snapshots
247
248
  gitnexus analyze --verbose # Log skipped files when parsers are unavailable
248
249
  gitnexus analyze --max-file-size 1024 # Skip files larger than N KB (default: 512, cap: 32768)
249
250
  gitnexus analyze --worker-timeout 60 # Increase worker idle timeout for slow parses
@@ -323,6 +324,8 @@ and root fields. Dynamic decorator names, anonymous operations, and ambiguous or
323
324
  anchors are deliberately omitted. Add common infrastructure fields such as `/health` to
324
325
  `matching.exclude_links_paths` to keep those GraphQL contracts visible without cross-linking them.
325
326
 
327
+ `--spring-actuator` is explicitly opt-in. The path may be a JSON bundle keyed by `mappings`, `beans`, `conditions`, `configprops`, and/or `env`, or a directory containing endpoint-named JSON files. Runtime mappings and beans confirm matching static nodes; conditions and configuration property keys enrich existing evidence, with conservative runtime-only nodes added when no match exists. The configured input is excluded from source scanning; only normalized repository-relative exclusions are retained for future scans, never absolute paths. Env/configprops values, origins, condition messages, and source names are never persisted or printed. Enabled runs always rebuild because runtime snapshots are external to git freshness; omitting the option later rebuilds once to remove runtime evidence. Project config can set the same path with `springActuator` in `.gitnexusrc`.
328
+
326
329
  > **`gitnexus uninstall`** reverses `gitnexus setup` — it removes the GitNexus MCP entries, hooks, and skill directories it added to each detected editor. Skill directories are identified **by bundled gitnexus skill name** (e.g. `gitnexus-cli/`), so if you customized files inside an installed skill directory, back them up first. It is a dry-run preview by default and prints the exact paths it would remove; pass `--force` to apply. Per-repo indexes (`gitnexus clean --all`) and the global npm package (`npm uninstall -g gitnexus`) are left for you to remove.
327
330
 
328
331
  ## Remote Embeddings
@@ -431,7 +434,7 @@ Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setu
431
434
  LadybugDB native binary ships as a prebuild against that floor, so on an older host it cannot
432
435
  load and reinstalling does not help — see
433
436
  [Linux: `GLIBC_2.34' not found`](#linux-glibc_234-not-found).
434
- - **Windows, for full-text search:** the Microsoft Visual C++ 2015-2022 Redistributable (x64) *and*
437
+ - **Windows, for full-text search:** the Microsoft Visual C++ 2015-2022 Redistributable (x64) _and_
435
438
  OpenSSL 3 (`libssl-3-x64.dll`, `libcrypto-3-x64.dll`) resolvable on `PATH` — see
436
439
  [Windows: full-text search unavailable](#windows-full-text-search-unavailable).
437
440
 
@@ -708,17 +711,17 @@ For repositories with very large source files, `GITNEXUS_WORKER_SUB_BATCH_MAX_BY
708
711
 
709
712
  Four env vars expose the pool's resilience layers (respawn budget, cumulative-timeout cap, circuit breaker, startup handshake). Defaults are tuned for typical repos; bump them when an analyze legitimately needs more retries, or lower them to fail-fast on a known-bad shape.
710
713
 
711
- | Variable | Default | Effect |
712
- | ----------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
713
- | `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3` | Max replacement spawns per slot before the slot is dropped from the active rotation. |
714
- | `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits. |
715
- | `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool. |
716
- | `GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS` | `30000` | Max wait at pool shutdown for a retired worker still inside native code — terminated at its next JS-safe point instead of mid-native-call, which would abort the process (`Napi::Error`, #2432). |
717
- | `GITNEXUS_WORKER_READY_TIMEOUT_MS` | `5000` | Startup budget for a parse worker to load its grammar bindings and report `{type:'ready'}`. Slots that miss it are treated as startup crashes. Raise it on a slow or heavily loaded host where a full pool cold-starting concurrently needs more than 5s. |
718
- | `GITNEXUS_MEMORY` | `off` | unset (autopilot on) | `off` declines GitNexus's memory autopilot: analyze will neither re-run itself with a RAM-aware heap cap nor abort the parse before V8 enters its ineffective-mark-compact death spiral. Use it when you want to drive memory manually; to simply pin a heap size, pass Node's own `--max-old-space-size`, which is already honoured as your decision. |
719
- | `GITNEXUS_WORKER_HEAP_MB` | `clamp(512, RAM/2/poolSize, 4096)` | Per-worker V8 old-generation heap cap (#2649). Bounds pool RSS on large repos; a worker exceeding it dies with a real heap error handled by quarantine/respawn. |
720
- | `GITNEXUS_SERVER_ANALYZE_HEAP_MB` | `min(8192, auto cap)` | Heap for the web/MCP server's forked analyze worker (#2649). Defaults to the historical 8192 MB bounded by the machine/container's RAM-aware auto cap; set an absolute MB value to override. |
721
- | `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000` | Per-file wall-clock budget for C++ capture extraction; on breach the file keeps partial captures with a warning (#2432). `0` expires immediately. |
714
+ | Variable | Default | Effect |
715
+ | ----------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
716
+ | `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3` | Max replacement spawns per slot before the slot is dropped from the active rotation. |
717
+ | `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits. |
718
+ | `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool. |
719
+ | `GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS` | `30000` | Max wait at pool shutdown for a retired worker still inside native code — terminated at its next JS-safe point instead of mid-native-call, which would abort the process (`Napi::Error`, #2432). |
720
+ | `GITNEXUS_WORKER_READY_TIMEOUT_MS` | `5000` | Startup budget for a parse worker to load its grammar bindings and report `{type:'ready'}`. Slots that miss it are treated as startup crashes. Raise it on a slow or heavily loaded host where a full pool cold-starting concurrently needs more than 5s. |
721
+ | `GITNEXUS_MEMORY` | `off` | unset (autopilot on) | `off` declines GitNexus's memory autopilot: analyze will neither re-run itself with a RAM-aware heap cap nor abort the parse before V8 enters its ineffective-mark-compact death spiral. Use it when you want to drive memory manually; to simply pin a heap size, pass Node's own `--max-old-space-size`, which is already honoured as your decision. |
722
+ | `GITNEXUS_WORKER_HEAP_MB` | `clamp(512, RAM/2/poolSize, 4096)` | Per-worker V8 old-generation heap cap (#2649). Bounds pool RSS on large repos; a worker exceeding it dies with a real heap error handled by quarantine/respawn. |
723
+ | `GITNEXUS_SERVER_ANALYZE_HEAP_MB` | `min(8192, auto cap)` | Heap for the web/MCP server's forked analyze worker (#2649). Defaults to the historical 8192 MB bounded by the machine/container's RAM-aware auto cap; set an absolute MB value to override. |
724
+ | `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000` | Per-file wall-clock budget for C++ capture extraction; on breach the file keeps partial captures with a warning (#2432). `0` expires immediately. |
722
725
 
723
726
  ### Graph cleanup tuning
724
727
 
@@ -732,8 +735,8 @@ Programmatic callers can pass `keepLocalValueSymbols: true` in `PipelineOptions`
732
735
 
733
736
  ### Scope-resolution property-key dispatch cap
734
737
 
735
- During scope resolution GitNexus synthesizes CALLS edges through *property-key
736
- dispatch* — call sites like `hooks.emitScopeCaptures()` where a property key is
738
+ During scope resolution GitNexus synthesizes CALLS edges through _property-key
739
+ dispatch_ — call sites like `hooks.emitScopeCaptures()` where a property key is
737
740
  registered by multiple definitions across the codebase. To keep this fan-in
738
741
  bounded, each property key is capped at **32 registrations**: a key registered
739
742
  by more than 32 distinct functions is skipped entirely (no CALLS are synthesized
@@ -741,8 +744,8 @@ through it), and the dropped key names are surfaced in the analyze log for
741
744
  operator visibility. The cap is calibrated at 2× this repo's own provider table
742
745
  (16 legitimate registrations, one per language provider).
743
746
 
744
- | Variable | Default | Effect |
745
- | --------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
747
+ | Variable | Default | Effect |
748
+ | --------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
746
749
  | `GITNEXUS_MAX_PROPERTY_DISPATCH_FANOUT` | `32` | Per-property-key registration cap in the property-dispatch scope-resolution pass. Set to a positive integer to raise it for repositories whose provider/hook tables exceed the default and lose CALLS coverage on a legitimate key; non-integer or `< 1` values fall back to `32`. Lowering it tightens the overflow budget. |
747
750
 
748
751
  ```bash
@@ -755,11 +758,11 @@ npx gitnexus analyze --force
755
758
 
756
759
  ### Scope-resolution dispatch-target cap
757
760
 
758
- During scope resolution GitNexus resolves calls that flow through *callable
759
- values* — function/method references bound to variables, passed as arguments,
761
+ During scope resolution GitNexus resolves calls that flow through _callable
762
+ values_ — function/method references bound to variables, passed as arguments,
760
763
  or stored in maps/tables. To keep that inclusion-based resolution finite, each
761
764
  callable site is capped at **32 dispatch targets**. When a site gathers more
762
- candidates than the cap it is treated as **overflowed** and *all* of its call
765
+ candidates than the cap it is treated as **overflowed** and _all_ of its call
763
766
  edges are dropped — a cliff, not a tail, so a repository with a legitimately
764
767
  wide dispatch table (a single callable site resolving to 33+ targets) loses
765
768
  that site's whole call chain. In that case `analyze` logs
@@ -769,8 +772,8 @@ candidate count, and the cap (32).
769
772
 
770
773
  Raise the cap for such repositories:
771
774
 
772
- | Variable | Default | Effect |
773
- | ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
775
+ | Variable | Default | Effect |
776
+ | ------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
774
777
  | `GITNEXUS_MAX_CALLABLE_VALUE_TARGETS` | `32` | Per-callable-site dispatch-target cap in the callable-value-flow scope-resolution pass. Set to a positive integer to raise it for repositories whose wide dispatch tables overflow the default and lose a whole call chain; non-integer or `< 1` values fall back to `32`. Lowering it tightens the overflow budget. |
775
778
 
776
779
  ```bash
@@ -48,6 +48,12 @@ export type NodeProperties = {
48
48
  responseKeys?: string[];
49
49
  errorKeys?: string[];
50
50
  middleware?: string[];
51
+ /** Route runtime evidence is authoritative only when this is exactly true. */
52
+ runtimeConfirmed?: boolean;
53
+ /** Provenance of runtime evidence; presence alone does not imply confirmation. */
54
+ runtimeSource?: string;
55
+ /** Runtime result such as runtime-confirmed or handler-conflict. */
56
+ runtimeStatus?: string;
51
57
  text?: string;
52
58
  /** BasicBlock: space-joined leaf callee names invoked in the block — the
53
59
  * statement-precise inter-procedural reach substrate for impact mode. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,8 @@ export interface AIContextOptions {
30
30
  * "no PDG layer" note, so advertising it on a non-`--pdg` index is noise.
31
31
  */
32
32
  hasPdg?: boolean;
33
+ /** Whether this index includes opt-in Spring Actuator runtime evidence. */
34
+ hasSpringActuator?: boolean;
33
35
  }
34
36
  /**
35
37
  * Strip backticks from a branch name before it is embedded in a Markdown
@@ -62,6 +64,8 @@ export interface GitNexusContentOptions {
62
64
  * line below — false (default) omits it, so a non-pdg index doesn't advertise
63
65
  * a tool that only returns a "no PDG layer" note. */
64
66
  hasPdg?: boolean;
67
+ /** Whether Route nodes may carry Spring Actuator runtime evidence. */
68
+ hasSpringActuator?: boolean;
65
69
  }
66
70
  export declare function generateGitNexusContent(projectName: string, stats: RepoStats, opts?: GitNexusContentOptions): string;
67
71
  /**
@@ -79,7 +79,7 @@ export function markdownSafeBranch(branch) {
79
79
  return branch.replace(/`/g, '');
80
80
  }
81
81
  export function generateGitNexusContent(projectName, stats, opts = {}) {
82
- const { generatedSkills, groupNames, noStats, skipSkills, runnerPath = '.gitnexus/run.cjs', defaultBranch = 'main', hasPdg = false, } = opts;
82
+ const { generatedSkills, groupNames, noStats, skipSkills, runnerPath = '.gitnexus/run.cjs', defaultBranch = 'main', hasPdg = false, hasSpringActuator = false, } = opts;
83
83
  const generatedRows = generatedSkills && generatedSkills.length > 0
84
84
  ? generatedSkills
85
85
  .map((s) =>
@@ -146,7 +146,9 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
146
146
  - MUST warn on HIGH/CRITICAL \`risk\` pre-edit; never use \`riskSharedAxes\` to waive a HIGH/CRITICAL \`risk\` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
147
147
  - **MUST treat \`risk: UNKNOWN\` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). \`impact\` pairs \`UNKNOWN\` with a \`riskNote\` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
148
148
  - Explore with \`query({search_query: "concept"})\` for process-grouped flows.
149
- - Use \`context({name: "symbolName"})\` for callers, callees, and flows.
149
+ - Use \`context({name: "symbolName"})\` for callers, callees, and flows.${hasSpringActuator
150
+ ? '\n- Spring Actuator runtime evidence is enabled. A Route is authoritative only when `runtimeConfirmed === true`; `runtimeSource` is provenance and may also describe conflicts. Snapshot values are never persisted.'
151
+ : ''}
150
152
  - For security review, \`explain({target: "fileOrSymbol"})\` lists taint findings (source→sink flows; needs \`analyze --pdg\`).${hasPdg
151
153
  ? `\n- For control/data dependence, \`pdg_query({mode: "controls", target: "fileOrSymbol"})\` answers "under what condition does X run?" (CDG, incl. guard clauses) and \`pdg_query({mode: "flows", target, variable})\` traces "where does variable Y flow?" (REACHING_DEF). \`--pdg\` layer.`
152
154
  : ''}
@@ -414,6 +416,7 @@ export async function generateAIContextFiles(repoPath, storagePath, projectName,
414
416
  runnerPath,
415
417
  defaultBranch: options?.defaultBranch ?? 'main',
416
418
  hasPdg: options?.hasPdg ?? false,
419
+ hasSpringActuator: options?.hasSpringActuator ?? false,
417
420
  });
418
421
  const createdFiles = [];
419
422
  if (!options?.skipAgentsMd) {
@@ -86,6 +86,9 @@ const KEY_SPECS = {
86
86
  // built-in convention set, is otherwise invisible to route_map consumers.
87
87
  // Listing it here adds it to the cross-file consumer scan.
88
88
  fetchWrappers: { target: 'fetchWrappers', kind: 'string-array' },
89
+ // Explicit local Actuator snapshot input (#2418). The path itself is safe in
90
+ // project config; payload contents are never copied into the graph wholesale.
91
+ springActuator: { target: 'springActuator', kind: 'path' },
89
92
  // Auth token AND dims are intentionally CLI/env-only — no embeddingAuthToken
90
93
  // or embeddingDims key here:
91
94
  // - the token keeps secrets out of a committed .gitnexusrc;
@@ -192,6 +195,17 @@ const normalizeValue = (kind, value, key) => {
192
195
  throw new GitNexusRcError(`${source} must be a string branch name.`);
193
196
  }
194
197
  return validateBranchName(value, source);
198
+ case 'path': {
199
+ if (typeof value !== 'string') {
200
+ throw new GitNexusRcError(`${source} must be a file or directory path.`);
201
+ }
202
+ const trimmed = value.trim();
203
+ if (!trimmed) {
204
+ throw new GitNexusRcError(`${source} must not be empty.`);
205
+ }
206
+ assertNoHiddenChars(trimmed, source);
207
+ return trimmed;
208
+ }
195
209
  case 'string': {
196
210
  if (typeof value !== 'string') {
197
211
  throw new GitNexusRcError(`${source} must be a string.`);
@@ -124,6 +124,11 @@ export interface AnalyzeOptions {
124
124
  * outside the built-in convention still produces `route_map` consumers.
125
125
  */
126
126
  fetchWrappers?: string[];
127
+ /**
128
+ * Explicit local Spring Boot Actuator snapshot input (#2418). Accepts a JSON
129
+ * bundle or a directory containing endpoint JSON files. Disabled by default.
130
+ */
131
+ springActuator?: string;
127
132
  /** OpenAI-compatible embeddings base URL (incl. /v1). Overrides GITNEXUS_EMBEDDING_URL. */
128
133
  embeddingBaseUrl?: string;
129
134
  /** Embedding model name. Overrides GITNEXUS_EMBEDDING_MODEL. */
@@ -1112,6 +1112,7 @@ const analyzeCommandImpl = async (inputPath, cliOptions, runnerIdentityAtBootstr
1112
1112
  // Extra fetch-wrapper names from `.gitnexusrc` (#1589/#1852 residual);
1113
1113
  // forwarded to the routes phase consumer scan.
1114
1114
  fetchWrappers: options.fetchWrappers,
1115
+ springActuatorPath: options.springActuator,
1115
1116
  // The CLI always process.exit()s after this returns (success path at the
1116
1117
  // end of analyzeCommandImpl, error/interrupt paths via process.exit too),
1117
1118
  // so the finalize close skips the native conn/db close — it can double-free
@@ -1250,6 +1251,7 @@ const analyzeCommandImpl = async (inputPath, cliOptions, runnerIdentityAtBootstr
1250
1251
  // exercised on the `--skills` path by analyze-no-stats-bridge.test.ts.
1251
1252
  noStats: options.stats === false,
1252
1253
  hasPdg: options.pdg === true,
1254
+ hasSpringActuator: options.springActuator !== undefined,
1253
1255
  });
1254
1256
  }
1255
1257
  }
package/dist/cli/index.js CHANGED
@@ -73,6 +73,8 @@ program
73
73
  .option('--wal-checkpoint-threshold <bytes>', 'LadybugDB WAL auto-checkpoint threshold in bytes during analyze ' +
74
74
  '(integer >= -1; default: 67108864 = 64 MiB; -1 keeps Ladybug stock ~16 MiB).')
75
75
  .option('--workers <n>', 'Parse worker pool size (>=1). Default: cores-1 capped at 16, auto-sized to the repo.')
76
+ .option('--spring-actuator <path>', 'Import local Spring Boot Actuator JSON snapshots (mappings, beans, conditions, ' +
77
+ 'configprops, env). Explicit opt-in; disabled by default.')
76
78
  .option('--embedding-threads <n>', 'Limit local ONNX embedding CPU threads')
77
79
  .option('--embedding-batch-size <n>', 'Number of nodes per embedding batch')
78
80
  .option('--embedding-sub-batch-size <n>', 'Number of chunks per embedding model call')
package/dist/cli/watch.js CHANGED
@@ -74,6 +74,7 @@ export async function resolveWatchOptions(repoPath, cli, baseline, reportIgnored
74
74
  ['--self-commit', cli.selfCommit],
75
75
  ['--index-only', cli.indexOnly],
76
76
  ['--skip-git', cli.skipGit],
77
+ ['--spring-actuator', cli.springActuator],
77
78
  ['walCheckpointThreshold', cli.walCheckpointThreshold],
78
79
  ['embeddingThreads', cli.embeddingThreads],
79
80
  ['embeddingBatchSize', cli.embeddingBatchSize],
@@ -94,6 +95,7 @@ export async function resolveWatchOptions(repoPath, cli, baseline, reportIgnored
94
95
  ['skipAgentsMd', config.skipAgentsMd !== undefined],
95
96
  ['skipSkills', config.skipSkills !== undefined],
96
97
  ['stats', config.stats !== undefined],
98
+ ['springActuator', config.springActuator],
97
99
  ['walCheckpointThreshold', config.walCheckpointThreshold],
98
100
  ['embeddingThreads', config.embeddingThreads],
99
101
  ['embeddingBatchSize', config.embeddingBatchSize],
@@ -62,6 +62,9 @@ export declare function validateGroupImpactParams(params: Record<string, unknown
62
62
  name: string;
63
63
  repoPath: string;
64
64
  target: string;
65
+ target_uid?: string;
66
+ file_path?: string;
67
+ kind?: string;
65
68
  direction: 'upstream' | 'downstream';
66
69
  maxDepth: number;
67
70
  crossDepth: number;
@@ -103,13 +103,21 @@ export function clampTimeout(timeoutMs) {
103
103
  export function validateGroupImpactParams(params) {
104
104
  const name = String(params.name ?? '').trim();
105
105
  const repoPath = String(params.repo ?? '').trim();
106
- const target = String(params.target ?? '').trim();
106
+ // Optional string, same helper shape as cross-trace's `str()`: empty/blank
107
+ // counts as absent so `target_uid: ''` degrades to the name lookup rather
108
+ // than a zero-ambiguity lookup of the empty uid. Parsed before the required
109
+ // check so UID-only callers (MCP impact schema requires `direction`, not
110
+ // `target`) are accepted.
111
+ const str = (v) => typeof v === 'string' && v.trim() !== '' ? v : undefined;
112
+ const targetName = String(params.target ?? '').trim();
113
+ const target_uidEarly = str(params.target_uid);
107
114
  if (!name)
108
115
  return { ok: false, error: 'name is required' };
109
116
  if (!repoPath)
110
117
  return { ok: false, error: 'repo is required (group repo path, e.g. app/backend)' };
111
- if (!target)
112
- return { ok: false, error: 'target is required' };
118
+ if (!targetName && !target_uidEarly)
119
+ return { ok: false, error: 'target or target_uid is required' };
120
+ const target = targetName || target_uidEarly;
113
121
  if (params.service !== undefined &&
114
122
  params.service !== null &&
115
123
  String(params.service).trim() === '') {
@@ -133,6 +141,9 @@ export function validateGroupImpactParams(params) {
133
141
  minConfidence = 1;
134
142
  const service = normalizeServicePrefix(params.service);
135
143
  const subgroup = typeof params.subgroup === 'string' ? params.subgroup : undefined;
144
+ const target_uid = target_uidEarly;
145
+ const file_path = str(params.file_path);
146
+ const kind = str(params.kind);
136
147
  // Clamp at the validate boundary so the downstream `deadline` (line
137
148
  // ~366) and `safeLocalImpact`'s `setTimeout` both see a single
138
149
  // bounded value. Without this, the outer deadline budgeted Phase-2
@@ -150,6 +161,9 @@ export function validateGroupImpactParams(params) {
150
161
  name,
151
162
  repoPath,
152
163
  target,
164
+ target_uid,
165
+ file_path,
166
+ kind,
153
167
  direction,
154
168
  maxDepth,
155
169
  crossDepth,
@@ -448,7 +462,7 @@ export async function runGroupImpact(deps, params) {
448
462
  const parsed = validateGroupImpactParams(params);
449
463
  if (parsed.ok === false)
450
464
  return { error: parsed.error };
451
- const { name, repoPath, target, direction, maxDepth, crossDepth: _crossDepth, crossDepthWarning, relationTypes, includeTests, minConfidence, service: servicePrefix, subgroup, timeoutMs, } = parsed;
465
+ const { name, repoPath, target, target_uid, file_path, kind, direction, maxDepth, crossDepth: _crossDepth, crossDepthWarning, relationTypes, includeTests, minConfidence, service: servicePrefix, subgroup, timeoutMs, } = parsed;
452
466
  const groupDir = getGroupDir(deps.gitnexusDir, name);
453
467
  let config;
454
468
  try {
@@ -464,6 +478,14 @@ export async function runGroupImpact(deps, params) {
464
478
  return { error: resolved.error };
465
479
  const impactParams = {
466
480
  target,
481
+ // Selector params pass through to the member repo's impact (the port
482
+ // contract in service.ts documents them), so the single-repo tool's
483
+ // "re-call with target_uid to disambiguate" loop works unchanged in
484
+ // group mode. `undefined` keeps the call shape flat — same convention
485
+ // as the relationTypes line below.
486
+ target_uid,
487
+ file_path,
488
+ kind,
467
489
  direction,
468
490
  maxDepth,
469
491
  relationTypes: relationTypes && relationTypes.length > 0 ? relationTypes : undefined,
@@ -2,7 +2,7 @@ import Java from 'tree-sitter-java';
2
2
  import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
3
3
  import { springAnnotationHttpMethods, intersectSpringHttpMethods, isRouteMemberKey, findEnclosingClass, joinPath, } from '../../../ingestion/route-extractors/spring-shared.js';
4
4
  import { REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP, WEB_CLIENT_LONG_VERB_RE, EXCHANGE_ANNOTATION_TO_HTTP, parseRequestLine, pushPrefix, scanSpringInheritanceProject, OPENFEIGN_FRAMEWORK, HTTP_INTERFACE_FRAMEWORK, FEIGN_CONFIDENCE, REQUEST_LINE_CONFIDENCE, EXCHANGE_CONFIDENCE, } from './spring-consumer-shared.js';
5
- import { extractJavaModuleConstants, foldJavaOperands, isJavaConstantFile, parseJavaConstOperands, } from '../../../ingestion/route-extractors/java-const-resolver.js';
5
+ import { expandJavaWildcardStaticImports, extractJavaModuleConstants, foldJavaOperands, isJavaConstantFile, parseJavaConstOperands, prepareJavaRouteConstants, } from '../../../ingestion/route-extractors/java-const-resolver.js';
6
6
  import { extractStaticPathExpression, inferOkHttpMethod, inferHttpClientMethod, okHttpUrlRootsAtBuilder, httpClientUriRootsAtNewBuilder, httpClientChainHasUriCall, } from './java-static-path.js';
7
7
  /**
8
8
  * Java HTTP plugin. Handles:
@@ -789,7 +789,10 @@ export const JAVA_HTTP_PLUGIN = {
789
789
  if (!tree)
790
790
  continue;
791
791
  const mc = extractJavaModuleConstants(tree);
792
- if (mc.literals.size > 0 || mc.exprs.size > 0 || mc.imports.size > 0) {
792
+ if (mc.literals.size > 0 ||
793
+ mc.exprs.size > 0 ||
794
+ mc.imports.size > 0 ||
795
+ (mc.wildcardImports?.length ?? 0) > 0) {
793
796
  constants.set(rel, mc);
794
797
  }
795
798
  }
@@ -800,7 +803,14 @@ export const JAVA_HTTP_PLUGIN = {
800
803
  continue;
801
804
  }
802
805
  }
803
- return { constants };
806
+ // On-demand static imports (`import static a.b.C.*`) were recorded as
807
+ // pending class FQNs during extraction; materialize their bare-name
808
+ // bindings now that the whole map exists. A wildcard's target is itself
809
+ // a constants file, so it is necessarily a map entry — anything else
810
+ // degrades to the fold's skip floor. In-place: each entry is owned by
811
+ // this map, and every file is expanded exactly once.
812
+ const constantIndex = prepareJavaRouteConstants(constants);
813
+ return { constants, constantIndex };
804
814
  },
805
815
  scan(tree, repoContext, fileRel) {
806
816
  const out = [];
@@ -832,8 +842,12 @@ export const JAVA_HTTP_PLUGIN = {
832
842
  return foldConstants;
833
843
  try {
834
844
  const mc = extractJavaModuleConstants(tree);
835
- if (mc.imports.size > 0) {
845
+ // A file carrying ONLY wildcard static imports has an empty import
846
+ // table pre-expansion — overlay it too, then materialize the promised
847
+ // bindings against the repo map before it becomes a fold target.
848
+ if (mc.imports.size > 0 || (mc.wildcardImports?.length ?? 0) > 0) {
836
849
  const merged = new Map(javaCtx.constants);
850
+ expandJavaWildcardStaticImports(mc, fileRel, merged, javaCtx.constantIndex);
837
851
  merged.set(fileRel, mc);
838
852
  foldConstants = merged;
839
853
  }
@@ -1260,6 +1260,7 @@ function buildKotlinPlugin(language) {
1260
1260
  if (mc.literals.size > 0 ||
1261
1261
  mc.exprs.size > 0 ||
1262
1262
  mc.imports.size > 0 ||
1263
+ (mc.wildcardImports?.length ?? 0) > 0 ||
1263
1264
  unfoldableDeclarationsOf(mc).size > 0) {
1264
1265
  // POSIX key (see `normalizeRel`); `readFile` above got the raw `rel`.
1265
1266
  constants.set(normalizeRel(rel), mc);
@@ -1303,6 +1304,7 @@ function buildKotlinPlugin(language) {
1303
1304
  if (mc.literals.size > 0 ||
1304
1305
  mc.exprs.size > 0 ||
1305
1306
  mc.imports.size > 0 ||
1307
+ (mc.wildcardImports?.length ?? 0) > 0 ||
1306
1308
  unfoldableDeclarationsOf(mc).size > 0) {
1307
1309
  foldIndex = overlayKotlinConstantIndex(kotlinCtx.index, fileKey, mc);
1308
1310
  }
@@ -1,3 +1,40 @@
1
- import type { CrossLink, StoredContract } from './types.js';
1
+ import type { CrossLink, CrossLinkEndpoint, StoredContract } from './types.js';
2
+ /**
3
+ * True when a link endpoint carries no resolved graph symbol — empty
4
+ * `symbolUid` or a missing/empty `symbolRef`.
5
+ *
6
+ * Sync marks a cross-link `degraded: true` when this holds for the PROVIDER
7
+ * endpoint (`to`): the contract boundary is proven, but the empty uid can
8
+ * never match a Phase-1 impact symbol id, so cross-repo fan-out across the
9
+ * link silently yields nothing (the classic case is a provider whose handler
10
+ * failed to resolve, leaving `symbolName` degraded to the file name with one
11
+ * pseudo-symbol carrying every route in that file). Consumer-side (`from`)
12
+ * emptiness is deliberately NOT degraded — several extractors (topics, grpc)
13
+ * legitimately emit consumer contracts without a per-call symbol, and the
14
+ * anchor that matters for far-side fan-out is the provider's.
15
+ *
16
+ * Kept next to the endpoint merge logic because `dedupeCrossLinks` must
17
+ * re-derive the flag after a merge: `mergeEndpoints` backfills `symbolUid`
18
+ * from the losing twin, which can invalidate a flag carried in from the winner.
19
+ *
20
+ * NOT unresolved: a deterministic `manifest::<repo>::<contractId>` synthetic
21
+ * uid (see `manifestSymbolUid`). Manifest endpoints fall back to it precisely
22
+ * when the graph has no symbol for them — its empty `symbolRef.filePath` would
23
+ * otherwise trip the check below — yet cross-impact anchors those links by
24
+ * design (#2722: the crossing is preserved with `fanout_status:
25
+ * 'not_attempted'` instead of silently yielding cross=0). The prefix is the
26
+ * canonical discriminator — real indexer uids never start with `manifest::`
27
+ * — and `cross-impact.ts` branches on the same test. Encoding the exemption
28
+ * HERE (not at the sync marking call site) keeps marking and the post-merge
29
+ * re-derivation from drifting apart, and keeps the flag's meaning exactly what
30
+ * `types.ts` documents: "distinct from manifest::… synthetic UIDs".
31
+ */
32
+ export declare function isUnresolvedEndpoint(endpoint: CrossLinkEndpoint): boolean;
33
+ /**
34
+ * Derive `degraded` from the provider endpoint. Present (`true`) only when
35
+ * unresolved; deleted otherwise so contracts.json stays "carried only when
36
+ * meaningful" (`'degraded' in link === false` for anchored links).
37
+ */
38
+ export declare function applyDegradedFlag(link: CrossLink): CrossLink;
2
39
  export declare function dedupeContracts(items: StoredContract[]): StoredContract[];
3
40
  export declare function dedupeCrossLinks(items: CrossLink[]): CrossLink[];
@@ -83,6 +83,59 @@ function crossLinkKey(link) {
83
83
  endpointKey(link.to),
84
84
  ].join('\0');
85
85
  }
86
+ /**
87
+ * True when a link endpoint carries no resolved graph symbol — empty
88
+ * `symbolUid` or a missing/empty `symbolRef`.
89
+ *
90
+ * Sync marks a cross-link `degraded: true` when this holds for the PROVIDER
91
+ * endpoint (`to`): the contract boundary is proven, but the empty uid can
92
+ * never match a Phase-1 impact symbol id, so cross-repo fan-out across the
93
+ * link silently yields nothing (the classic case is a provider whose handler
94
+ * failed to resolve, leaving `symbolName` degraded to the file name with one
95
+ * pseudo-symbol carrying every route in that file). Consumer-side (`from`)
96
+ * emptiness is deliberately NOT degraded — several extractors (topics, grpc)
97
+ * legitimately emit consumer contracts without a per-call symbol, and the
98
+ * anchor that matters for far-side fan-out is the provider's.
99
+ *
100
+ * Kept next to the endpoint merge logic because `dedupeCrossLinks` must
101
+ * re-derive the flag after a merge: `mergeEndpoints` backfills `symbolUid`
102
+ * from the losing twin, which can invalidate a flag carried in from the winner.
103
+ *
104
+ * NOT unresolved: a deterministic `manifest::<repo>::<contractId>` synthetic
105
+ * uid (see `manifestSymbolUid`). Manifest endpoints fall back to it precisely
106
+ * when the graph has no symbol for them — its empty `symbolRef.filePath` would
107
+ * otherwise trip the check below — yet cross-impact anchors those links by
108
+ * design (#2722: the crossing is preserved with `fanout_status:
109
+ * 'not_attempted'` instead of silently yielding cross=0). The prefix is the
110
+ * canonical discriminator — real indexer uids never start with `manifest::`
111
+ * — and `cross-impact.ts` branches on the same test. Encoding the exemption
112
+ * HERE (not at the sync marking call site) keeps marking and the post-merge
113
+ * re-derivation from drifting apart, and keeps the flag's meaning exactly what
114
+ * `types.ts` documents: "distinct from manifest::… synthetic UIDs".
115
+ */
116
+ export function isUnresolvedEndpoint(endpoint) {
117
+ if (endpoint.symbolUid.startsWith('manifest::'))
118
+ return false;
119
+ return (!endpoint.symbolUid ||
120
+ !endpoint.symbolRef ||
121
+ !endpoint.symbolRef.filePath ||
122
+ !endpoint.symbolRef.name);
123
+ }
124
+ /**
125
+ * Derive `degraded` from the provider endpoint. Present (`true`) only when
126
+ * unresolved; deleted otherwise so contracts.json stays "carried only when
127
+ * meaningful" (`'degraded' in link === false` for anchored links).
128
+ */
129
+ export function applyDegradedFlag(link) {
130
+ const next = { ...link };
131
+ if (isUnresolvedEndpoint(next.to)) {
132
+ next.degraded = true;
133
+ }
134
+ else {
135
+ delete next.degraded;
136
+ }
137
+ return next;
138
+ }
86
139
  export function dedupeContracts(items) {
87
140
  const deduped = new Map();
88
141
  for (const contract of items) {
@@ -104,12 +157,15 @@ export function dedupeCrossLinks(items) {
104
157
  const keepIncoming = link.confidence > existing.confidence;
105
158
  const primary = keepIncoming ? link : existing;
106
159
  const secondary = keepIncoming ? existing : link;
107
- deduped.set(key, {
160
+ const merged = {
108
161
  ...primary,
109
162
  confidence: Math.max(existing.confidence, link.confidence),
110
163
  from: mergeEndpoints(primary.from, secondary.from),
111
164
  to: mergeEndpoints(primary.to, secondary.to),
112
- });
165
+ };
166
+ // Re-derive after mergeEndpoints: a richer twin can backfill `to.symbolUid`
167
+ // and must not leave a stale `degraded` flag on an now-anchored link.
168
+ deduped.set(key, applyDegradedFlag(merged));
113
169
  }
114
- return [...deduped.values()];
170
+ return [...deduped.values()].map(applyDegradedFlag);
115
171
  }
@@ -15,6 +15,17 @@ export interface GroupToolPort {
15
15
  resolveRepo(repoParam?: string): Promise<GroupRepoHandle>;
16
16
  impact(repo: GroupRepoHandle, params: {
17
17
  target: string;
18
+ /**
19
+ * Target-selector params, same semantics as the single-repo `impact`
20
+ * tool: `target_uid` is the zero-ambiguity lookup (it wins over the
21
+ * name), `file_path`/`kind` narrow a name shared by several symbols
22
+ * (e.g. same-named Api/Impl/Controller layers). The port implementation
23
+ * consumes them directly; the Phase-1 caller in cross-impact.ts is
24
+ * responsible for threading them from the MCP `impact` args.
25
+ */
26
+ target_uid?: string;
27
+ file_path?: string;
28
+ kind?: string;
18
29
  direction: 'upstream' | 'downstream';
19
30
  maxDepth?: number;
20
31
  relationTypes?: string[];
@@ -341,6 +341,14 @@ export class GroupService {
341
341
  // can otherwise see contract counts that disagree with this payload, with
342
342
  // nothing here explaining why the write was skipped.
343
343
  registryOutcome: result.registryOutcome,
344
+ // Data-quality signals surfaced from the sync run: links whose provider
345
+ // endpoint never resolved to a graph symbol, per-repo extraction
346
+ // failures with reasons, and operator warnings (e.g. bridge.lbug write
347
+ // failed after contracts.json was written). Always present so MCP
348
+ // consumers can branch on them without existence checks.
349
+ degradedLinks: result.degradedLinks,
350
+ failedRepos: result.failedRepos,
351
+ warnings: result.warnings,
344
352
  };
345
353
  }
346
354
  async groupContracts(params) {
@@ -47,6 +47,27 @@ export interface SyncResult {
47
47
  * none of that repo's contracts are in `contracts`.
48
48
  */
49
49
  unreadableRepos: string[];
50
+ /**
51
+ * Cross-links whose provider endpoint has no resolved graph symbol
52
+ * (`degraded: true` on the link — see `isUnresolvedEndpoint`). The boundary
53
+ * is proven but cross-impact fan-out cannot anchor it; the usual remedy is
54
+ * re-analyzing the provider repo so its handlers resolve.
55
+ */
56
+ degradedLinks: number;
57
+ /**
58
+ * Repos whose per-repo extraction threw (init, an extractor, or the
59
+ * snapshot read). Each still lands in `unreadableRepos` (group path) —
60
+ * unchanged downstream semantics — but carries its failure reason here: the
61
+ * catch used to swallow the exception, leaving contracts already pushed by
62
+ * earlier extractors in this iteration as silent half-repo data. `repo` is
63
+ * that same group path (e.g. `app/backend`), not the registry display name.
64
+ */
65
+ failedRepos: Array<{
66
+ repo: string;
67
+ reason: string;
68
+ }>;
69
+ /** Operator-facing run warnings (e.g. bridge.lbug write failed after contracts.json was written). */
70
+ warnings: string[];
50
71
  repoSnapshots: Record<string, RepoSnapshot>;
51
72
  /**
52
73
  * Matching stages this run was asked to skip. Populated on EVERY outcome,