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.
- package/README.md +24 -21
- package/dist/_shared/graph/types.d.ts +6 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +4 -0
- package/dist/cli/ai-context.js +5 -2
- package/dist/cli/analyze-config.js +14 -0
- package/dist/cli/analyze-options.d.ts +5 -0
- package/dist/cli/analyze.js +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/watch.js +2 -0
- package/dist/core/group/cross-impact.d.ts +3 -0
- package/dist/core/group/cross-impact.js +26 -4
- package/dist/core/group/extractors/http-patterns/java.js +18 -4
- package/dist/core/group/extractors/http-patterns/kotlin.js +2 -0
- package/dist/core/group/normalization.d.ts +38 -1
- package/dist/core/group/normalization.js +59 -3
- package/dist/core/group/service.d.ts +11 -0
- package/dist/core/group/service.js +8 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +15 -3
- package/dist/core/group/types.d.ts +13 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
- package/dist/core/ingestion/language-provider.d.ts +38 -1
- package/dist/core/ingestion/language-provider.js +22 -0
- package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
- package/dist/core/ingestion/languages/java.js +8 -7
- package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +6 -1
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
- package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
- package/dist/core/ingestion/pipeline.d.ts +8 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +7 -8
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +209 -23
- package/dist/core/ingestion/workers/parse-worker.js +5 -1
- package/dist/core/lbug/csv-generator.js +4 -1
- package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
- package/dist/core/lbug/graph-emit-sink.js +4 -2
- package/dist/core/lbug/lbug-adapter.js +1 -1
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/run-analyze.d.ts +5 -0
- package/dist/core/run-analyze.js +56 -0
- package/dist/mcp/local/local-backend.js +46 -3
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.js +4 -4
- package/dist/server/analyze-launch.d.ts +1 -0
- package/dist/server/analyze-launch.js +1 -0
- package/dist/server/api.d.ts +4 -0
- package/dist/server/api.js +46 -4
- package/dist/storage/parse-cache.js +8 -1
- package/dist/storage/repo-meta.d.ts +12 -0
- package/package.json +1 -1
- 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)
|
|
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
|
|
712
|
-
| ----------------------------------------------- |
|
|
713
|
-
| `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3`
|
|
714
|
-
| `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs`
|
|
715
|
-
| `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)`
|
|
716
|
-
| `GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS` | `30000`
|
|
717
|
-
| `GITNEXUS_WORKER_READY_TIMEOUT_MS` | `5000`
|
|
718
|
-
| `GITNEXUS_MEMORY`
|
|
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)`
|
|
721
|
-
| `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000`
|
|
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
|
|
736
|
-
|
|
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
|
|
759
|
-
|
|
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
|
|
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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,QAAQ,GACR,UAAU,GACV,WAAW,GACX,MAAM,GACN,WAAW,GACX,QAAQ,GACR,MAAM,GACN,aAAa,GACb,WAAW,GACX,SAAS,GAET,QAAQ,GACR,OAAO,GACP,SAAS,GACT,OAAO,GACP,WAAW,GACX,OAAO,GACP,MAAM,GACN,WAAW,GACX,OAAO,GACP,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,aAAa,GACb,UAAU,GACV,SAAS,GACT,OAAO,GACP,MAAM,GAGN,YAAY,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAEjC,WAAW,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,QAAQ,GACR,UAAU,GACV,WAAW,GACX,MAAM,GACN,WAAW,GACX,QAAQ,GACR,MAAM,GACN,aAAa,GACb,WAAW,GACX,SAAS,GAET,QAAQ,GACR,OAAO,GACP,SAAS,GACT,OAAO,GACP,WAAW,GACX,OAAO,GACP,MAAM,GACN,WAAW,GACX,OAAO,GACP,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,aAAa,GACb,UAAU,GACV,SAAS,GACT,OAAO,GACP,MAAM,GAGN,YAAY,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAEjC,WAAW,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;4EACwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;8EAC0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,OAAO,GACP,UAAU,GACV,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,MAAM,GACN,SAAS,GACT,WAAW,GACX,YAAY,GACZ,SAAS,GACT,YAAY,GACZ,cAAc,GACd,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,eAAe,GACf,SAAS,GACT,cAAc,GACd,gBAAgB,GAChB,OAAO,GACP,SAAS;AACX;;;;;;;;;;;0EAW0E;GACxE,SAAS;AACX;;;;;qBAKqB;GACnB,gBAAgB;AAClB;;;;+BAI+B;GAC7B,UAAU;AACZ;;;;6EAI6E;GAC3E,YAAY;AACd;;;;;;qEAMqE;GACnE,qBAAqB;AACvB;;;;;;gEAMgE;GAC9D,aAAa;AAOf,6EAA6E;GAC3E,KAAK;AACP;;;;qCAIqC;GACnC,cAAc;AAChB,qDAAqD;GACnD,SAAS;AACX,6CAA6C;GAC3C,WAAW;AACb;8EAC8E;GAC5E,YAAY;AACd;;;;qEAIqE;GACnE,KAAK;AACP;;;;;;0EAM0E;GACxE,eAAe;AACjB;;;;;;;;;;;oCAWoC;GAClC,cAAc,CAAC;AAEnB,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,SAAS;QAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;KACxB,EAAE,CAAC;CACL"}
|
package/dist/cli/ai-context.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -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. */
|
package/dist/cli/analyze.js
CHANGED
|
@@ -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
|
-
|
|
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 (!
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|