weavatrix 0.2.15 → 0.2.18
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 +99 -13
- package/docs/adr/0001-v0.3-offline-online-split.md +58 -0
- package/docs/releases/v0.2.18.md +41 -0
- package/package.json +7 -2
- package/src/analysis/cargo-dependency-evidence.js +105 -0
- package/src/analysis/cargo-manifests.js +74 -0
- package/src/analysis/dep-check-ecosystems.js +3 -0
- package/src/analysis/dependency/scoped-dependencies.js +13 -7
- package/src/analysis/duplicates.tokenize.js +13 -6
- package/src/analysis/endpoints-java.js +2 -5
- package/src/analysis/findings.js +12 -0
- package/src/analysis/git-history/options.js +2 -4
- package/src/analysis/go-dependency-evidence.js +62 -0
- package/src/analysis/health-capabilities.js +1 -1
- package/src/analysis/hot-path-review.js +2 -4
- package/src/analysis/http-contracts/analysis.js +2 -0
- package/src/analysis/http-contracts/client-call-detection.js +1 -0
- package/src/analysis/http-contracts/client-call-parser.js +18 -4
- package/src/analysis/http-contracts/shared.js +3 -4
- package/src/analysis/internal-audit/dependency-health.js +15 -13
- package/src/analysis/internal-audit/python-manifests.js +18 -5
- package/src/analysis/internal-audit/repo-files.js +5 -6
- package/src/analysis/internal-audit/supply-chain.js +2 -0
- package/src/analysis/internal-audit.reach.js +14 -0
- package/src/analysis/internal-audit.run.js +15 -10
- package/src/analysis/jvm-dependency-evidence.js +102 -56
- package/src/analysis/jvm-manifests.js +114 -0
- package/src/analysis/manifests.js +0 -49
- package/src/analysis/source-correctness.js +2 -5
- package/src/analysis/structure/dependency-graph.js +3 -8
- package/src/analysis/transport-contracts.js +157 -0
- package/src/graph/builder/lang-go.js +4 -2
- package/src/graph/builder/lang-java.js +12 -3
- package/src/graph/builder/lang-rust.js +22 -3
- package/src/graph/freshness-probe.js +1 -1
- package/src/graph/internal-builder.build.js +2 -2
- package/src/graph/internal-builder.langs.js +19 -0
- package/src/graph/internal-builder.resolvers.js +18 -8
- package/src/graph/node-id.js +7 -0
- package/src/mcp/actions/advisories.mjs +2 -3
- package/src/mcp/architecture-bootstrap.mjs +3 -3
- package/src/mcp/catalog.mjs +7 -4
- package/src/mcp/company-contract-verdict.mjs +42 -0
- package/src/mcp/evidence-snapshot.common.mjs +1 -3
- package/src/mcp/evidence-snapshot.health.mjs +11 -4
- package/src/mcp/evidence-snapshot.mjs +1 -1
- package/src/mcp/graph-diff.mjs +2 -1
- package/src/mcp/tools-architecture.mjs +2 -6
- package/src/mcp/tools-company.mjs +28 -53
- package/src/mcp/tools-impact-change.mjs +3 -8
- package/src/mcp/tools-impact-precision.mjs +89 -0
- package/src/mcp/tools-impact.mjs +52 -94
- package/src/precision/lsp-overlay/build.js +10 -0
- package/src/precision/lsp-overlay/contract.js +1 -1
- package/src/precision/symbol-query.js +39 -0
- package/src/precision/typescript-provider/client.js +16 -3
- package/src/precision/typescript-provider/discovery.js +1 -1
- package/src/precision/typescript-provider/isolated-runtime.js +39 -0
- package/src/precision/typescript-provider/project-host.js +11 -6
- package/src/precision/typescript-provider/project-safety.js +5 -0
- package/src/security/advisory-store.js +2 -2
- package/src/security/installed-jvm-rust.js +46 -0
- package/src/security/installed.js +21 -1
- package/src/security/typosquat.js +15 -6
- package/src/util.js +13 -0
- package/docs/releases/v0.2.15.md +0 -37
package/README.md
CHANGED
|
@@ -14,6 +14,26 @@ the defaults, no repository data leaves your machine.**
|
|
|
14
14
|
- Source: [github.com/sergii-ziborov/weavatrix](https://github.com/sergii-ziborov/weavatrix)
|
|
15
15
|
- npm: [`weavatrix`](https://www.npmjs.com/package/weavatrix) — `npx -y weavatrix <repoRoot>`
|
|
16
16
|
|
|
17
|
+
## 0.3 boundary: offline stays MIT
|
|
18
|
+
|
|
19
|
+
The next major boundary makes this package the complete offline engine: graph,
|
|
20
|
+
local semantic precision, Health, dependencies, duplicates, impact, history and
|
|
21
|
+
architecture safeguards remain here under the existing MIT license. The MIT
|
|
22
|
+
license is not changing.
|
|
23
|
+
|
|
24
|
+
Every outbound HTTP capability moves to a separately versioned public package,
|
|
25
|
+
`weavatrix-online`, beginning at 0.1.0. That connector targets either the
|
|
26
|
+
managed Weavatrix Cloud or a commercially licensed self-hosted Weavatrix
|
|
27
|
+
Enterprise deployment through one source-free wire contract. It is a thin
|
|
28
|
+
extension of this package, not a fork of the parser/graph/Health engine, so core
|
|
29
|
+
updates arrive as normal dependency updates.
|
|
30
|
+
|
|
31
|
+
Version 0.2.18 still contains the explicit opt-in compatibility profiles
|
|
32
|
+
documented below. The paired 0.3/0.1 release removes their network
|
|
33
|
+
implementations from this package; until then the new connector is not
|
|
34
|
+
publishable. The decision and release gates are in
|
|
35
|
+
[docs/adr/0001-v0.3-offline-online-split.md](docs/adr/0001-v0.3-offline-online-split.md).
|
|
36
|
+
|
|
17
37
|
## One graph, many views
|
|
18
38
|
|
|
19
39
|
The 38 MCP methods are not one linear workflow. They project the same reusable graph into the
|
|
@@ -117,10 +137,20 @@ Every dead-code, orphan, dependency and duplicate item remains review evidence.
|
|
|
117
137
|
conventions and source use before editing; Weavatrix does not auto-delete or merge findings.
|
|
118
138
|
|
|
119
139
|
`run_audit` also returns an explicit capability matrix. `STRUCTURE CHECKED` and a supported package
|
|
120
|
-
ecosystem do not imply `RUNTIME_CORRECTNESS` or `CONCURRENCY` is complete.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
ecosystem do not imply `RUNTIME_CORRECTNESS` or `CONCURRENCY` is complete. npm, nested Python and Go
|
|
141
|
+
modules, Maven properties, common Gradle declarations/version catalogs, and Cargo workspace/renamed
|
|
142
|
+
packages are compared with indexed imports. JVM reflection/generated/runtime-only use, Cargo
|
|
143
|
+
features/proc macros and unresolved build logic remain review evidence; bounded Go/Java checks are
|
|
144
|
+
neither compiler proof nor a race detector.
|
|
145
|
+
|
|
146
|
+
### Where this saves agent context — and where it does not
|
|
147
|
+
|
|
148
|
+
The largest saving comes from graph operations that replace repeated discovery: one
|
|
149
|
+
`change_impact`, `get_dependents`, `context_bundle`, contract trace or dependency audit can collapse
|
|
150
|
+
many search/read hops into a bounded evidence set. `read_source` is mainly a convenient exact window
|
|
151
|
+
and is not inherently cheaper than a client's native offset read. Database queries, runtime state,
|
|
152
|
+
disk forensics and background workflow investigation remain outside a static repository graph.
|
|
153
|
+
Typecheck, tests and runtime checks remain the release authority.
|
|
124
154
|
|
|
125
155
|
### Trace an event, queue, worker, cron or CLI flow
|
|
126
156
|
|
|
@@ -259,8 +289,9 @@ and builds a missing graph automatically. A normal
|
|
|
259
289
|
probes without building and refuses a legacy graph. Retargeting is offline but intentionally changes
|
|
260
290
|
the filesystem boundary for subsequent tools; select `pinned` when that boundary must not move.
|
|
261
291
|
|
|
262
|
-
An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — install as
|
|
263
|
-
`~/.claude/skills/weavatrix/SKILL.md
|
|
292
|
+
An agent skill with recipes ships in [skill/SKILL.md](skill/SKILL.md) — install it as
|
|
293
|
+
`~/.claude/skills/weavatrix/SKILL.md` for Claude Code or
|
|
294
|
+
`~/.codex/skills/weavatrix/SKILL.md` for Codex.
|
|
264
295
|
|
|
265
296
|
## Tools
|
|
266
297
|
|
|
@@ -281,6 +312,13 @@ disabled and only static evidence is active. Java and Rust language-server provi
|
|
|
281
312
|
their edges never become `EXACT_LSP`, even when a mixed repository reports a complete
|
|
282
313
|
TypeScript/JavaScript overlay.
|
|
283
314
|
|
|
315
|
+
Configured TypeScript plugins (including Next.js) are recorded but suppressed; repository-local
|
|
316
|
+
plugin code is never loaded. The broad overlay is deliberately budgeted, so `PARTIAL` can mean the
|
|
317
|
+
candidate cap was reached. `get_dependents` then spends a bounded point query on the requested JS/TS
|
|
318
|
+
symbol and replaces direct heuristic references only when exact absence/presence is proven.
|
|
319
|
+
`change_impact` batches the same exact query for changed symbols. Further transitive hops remain
|
|
320
|
+
graph-backed and are labelled as such.
|
|
321
|
+
|
|
284
322
|
The bounded JS/TS provider is enabled by default for new graphs. Set `WEAVATRIX_PRECISION=off`
|
|
285
323
|
before starting the MCP server for parser-only operation from the first build, or pass
|
|
286
324
|
`precision:"off"` to `rebuild_graph` / `open_repo`. The MCPB installer exposes the same `lsp` / `off`
|
|
@@ -302,8 +340,8 @@ functions, methods, and symbols, with evidence tiers, completed/remaining verifi
|
|
|
302
340
|
public/framework/dynamic caveats),
|
|
303
341
|
`run_audit` (an explicit capability matrix plus unused files/exports/dependencies with per-finding manifest/indexed-source/script/config verification;
|
|
304
342
|
`category:dependencies` isolates missing/unused/duplicate declarations, unresolved imports and lockfile drift,
|
|
305
|
-
missing npm/Go/Python deps,
|
|
306
|
-
Go/Java correctness candidates, runtime
|
|
343
|
+
missing npm/Go/Python/Maven/Gradle/Cargo deps, bounded import-to-artifact/crate verification, concrete
|
|
344
|
+
npm/PyPI/Go/Maven/crates.io advisory pins, Go/Java correctness candidates, runtime
|
|
307
345
|
cycles, type-only/compile-only coupling, orphans, boundary rules, offline OSV vulnerabilities + typosquat +
|
|
308
346
|
lockfile drift; accepts an immutable `base_ref`, `changed_files`, and `debt: new|existing|all` for
|
|
309
347
|
review-scoped results; production paths are the default and `include_classified:true` opts into
|
|
@@ -392,6 +430,47 @@ metrics are not persisted or transmitted by Weavatrix. If a source checkout's pa
|
|
|
392
430
|
while an old daemon remains alive, `initialize`, `tools/list`, and tool calls fail loudly with
|
|
393
431
|
`STALE_RUNTIME` until the client reconnects; the opt-out is reserved for deliberate development.
|
|
394
432
|
|
|
433
|
+
### 0.2.18 repository-root and self-audit trust patch
|
|
434
|
+
|
|
435
|
+
- A directory nested under an ignored parent Git repository is no longer mistaken for an empty
|
|
436
|
+
repository. The internal builder now falls back to its boundary-safe walker for that ambiguous
|
|
437
|
+
case while preserving Git ignore semantics at a real repository root.
|
|
438
|
+
- This is the first npm release containing the 0.2.17 self-audit work: Drizzle reachability,
|
|
439
|
+
scoped typosquat precision, regex-aware clone anchors, dead parser removal and shared owners.
|
|
440
|
+
- The 0.3 boundary remains unchanged: this offline package stays MIT, and the separately licensed
|
|
441
|
+
online connector owns outbound HTTP after the major split.
|
|
442
|
+
|
|
443
|
+
Full patch notes: [docs/releases/v0.2.18.md](docs/releases/v0.2.18.md).
|
|
444
|
+
|
|
445
|
+
### 0.2.17 self-audit trust patch (tagged, not published to npm)
|
|
446
|
+
|
|
447
|
+
- Health no longer treats configured Drizzle schema modules as orphaned/test-only production code,
|
|
448
|
+
and scoped typosquat checks no longer compare legitimate scoped packages with unrelated unscoped
|
|
449
|
+
names.
|
|
450
|
+
- Duplicate detection retains equality anchors for executable regular-expression bodies, preventing
|
|
451
|
+
unrelated validation/normalization pipelines from becoming perfect renamed clones.
|
|
452
|
+
- Dogfooding removed obsolete test-only parsers and consolidated dependency-scope, graph-ID,
|
|
453
|
+
architecture-contract, bounded-option and safe repository-read helpers.
|
|
454
|
+
- The accepted 0.3 product boundary keeps the offline engine MIT and moves all outbound HTTP tools
|
|
455
|
+
into the separately licensed `weavatrix-online` connector.
|
|
456
|
+
|
|
457
|
+
Full patch notes: [docs/releases/v0.2.17.md](docs/releases/v0.2.17.md).
|
|
458
|
+
|
|
459
|
+
### 0.2.16 exact dependents, multi-ecosystem dependencies and transport contracts
|
|
460
|
+
|
|
461
|
+
- Safe TypeScript project plugins no longer disable semantic precision. Plugin loading remains
|
|
462
|
+
suppressed, while `get_dependents` and `change_impact` can obtain exact direct JS/TS references on
|
|
463
|
+
demand and identify graph-backed transitive hops separately.
|
|
464
|
+
- Dependency evidence covers nested npm/Python/Go scopes, Maven properties, Gradle catalogs/locks,
|
|
465
|
+
and Cargo workspace inheritance/renames/locks. Explicit OSV refresh accepts npm, PyPI, Go, Maven
|
|
466
|
+
and crates.io pins.
|
|
467
|
+
- Cross-repository tracing adds static GraphQL, gRPC and event-topic joins alongside HTTP and keeps
|
|
468
|
+
runtime configuration, reflection and other dynamic targets explicitly `UNKNOWN`.
|
|
469
|
+
- npm/MCPB metadata now calls out Codex/OpenAI Codex workflows, and Hosted distinguishes aggregate
|
|
470
|
+
folder-boundary feedback from proven file-level runtime cycles.
|
|
471
|
+
|
|
472
|
+
Full patch notes: [docs/releases/v0.2.16.md](docs/releases/v0.2.16.md).
|
|
473
|
+
|
|
395
474
|
### 0.2.15 self-audit precision patch
|
|
396
475
|
|
|
397
476
|
- Dependency review now scopes nested manifests correctly, recognizes framework peer packages and
|
|
@@ -658,13 +737,19 @@ Java graphs:
|
|
|
658
737
|
agent review queue. Public/exported methods, framework entries, decorators, dynamic loading and
|
|
659
738
|
reflection lower confidence; the result always says `REVIEW_REQUIRED` and `autoDelete:false`.
|
|
660
739
|
|
|
661
|
-
###
|
|
740
|
+
### Cross-repository transports
|
|
662
741
|
|
|
663
742
|
`trace_api_contract` recognizes built-in object clients such as `axios.get(...)`, explicit bare or
|
|
664
743
|
object/member wrappers, and simple auto-discovered functions that forward a URL parameter directly
|
|
665
744
|
to a known HTTP client. Auto-discovered wrappers are restricted to their bounded reverse-import
|
|
666
745
|
scope; ambiguous same-name definitions are skipped and reported as incomplete evidence.
|
|
667
746
|
|
|
747
|
+
The same tool can select GraphQL, gRPC or event evidence. It joins static GraphQL schema fields and
|
|
748
|
+
operations, proto service methods and typed stub calls, and static Kafka/event-bus topic producers
|
|
749
|
+
and consumers across registered repositories. Computed URLs, runtime configuration, dynamic
|
|
750
|
+
GraphQL documents/topics, reflection and dynamic stubs remain `UNKNOWN`; they are not converted into
|
|
751
|
+
an absence claim.
|
|
752
|
+
|
|
668
753
|
Persistent per-client-repository configuration lives in `.weavatrix.json`:
|
|
669
754
|
|
|
670
755
|
```json
|
|
@@ -826,16 +911,17 @@ loader) behind the thin stdio entry `src/mcp-server.mjs`.
|
|
|
826
911
|
cross-repository wrapper/liveness fixture, framework/convention fixture, full MCP lifecycle gate
|
|
827
912
|
and a portable real-repository runner. Six source-free 0.2.1 real-repository baselines are
|
|
828
913
|
recorded; edge provenance is gated end-to-end and the strict six-repository release command passes.
|
|
829
|
-
- **
|
|
830
|
-
|
|
831
|
-
hosted increment joins privacy-safe contract identities across separately synced services.
|
|
914
|
+
- **Cross-transport contracts** cover bounded HTTP wrappers plus static GraphQL, gRPC and event-bus
|
|
915
|
+
identities across registered local repositories, with explicit runtime/dynamic unknown states.
|
|
832
916
|
- **Hosted architecture workbench** at
|
|
833
917
|
[app.weavatrix.com](https://app.weavatrix.com) is an access-controlled preview for
|
|
834
918
|
owner-authenticated source-free evidence and revision history. Its UI and backend evolve
|
|
835
919
|
independently from the public MCP release; local use remains fully optional.
|
|
836
920
|
- **Semantic precision bridge** shipped for TypeScript/JavaScript in 0.2.4: a bounded, revision-bound
|
|
837
921
|
local overlay validates references with the bundled language server while the parser graph remains
|
|
838
|
-
the fallback.
|
|
922
|
+
the fallback. 0.2.16 adds safe configured-plugin suppression plus exact on-demand point and changed-
|
|
923
|
+
symbol batch queries. Java and Rust language-server providers are not bundled and stay explicitly
|
|
924
|
+
unavailable as semantic providers; their parser/dependency evidence is still active.
|
|
839
925
|
- **Git-native architecture history** — bounded tag/ref timelines and branch
|
|
840
926
|
reports built outside the worktree; graph artifacts stay out of Git.
|
|
841
927
|
- **Cross-repository company evidence** — endpoints, events and internal
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ADR 0001: Weavatrix 0.3 offline/online split
|
|
2
|
+
|
|
3
|
+
Status: accepted direction; extraction in progress
|
|
4
|
+
|
|
5
|
+
Date: 2026-07-19
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
The public `weavatrix` package remains MIT licensed and becomes a strictly
|
|
10
|
+
offline MCP in 0.3. Its analyzers, graph, local LSP provider, cache, repository
|
|
11
|
+
registry, Health, dependency, duplicate, impact, history and architecture tools
|
|
12
|
+
remain available without a network capability.
|
|
13
|
+
|
|
14
|
+
All outbound HTTP tools move to the separate public-source `weavatrix-online`
|
|
15
|
+
package, starting at 0.1.0. Its permission-required source license must be
|
|
16
|
+
approved before publication; the scaffold remains unpublished and
|
|
17
|
+
`UNLICENSED`. It extends the MIT engine rather than copying or relicensing it.
|
|
18
|
+
|
|
19
|
+
The managed Cloud and licensed self-hosted Enterprise products are private
|
|
20
|
+
server implementations of the same source-free wire contract. Neither is
|
|
21
|
+
bundled into the offline package.
|
|
22
|
+
|
|
23
|
+
## Tool ownership after extraction
|
|
24
|
+
|
|
25
|
+
Offline owns local advisory matching but not remote refresh. Online owns:
|
|
26
|
+
|
|
27
|
+
- `refresh_advisories`;
|
|
28
|
+
- `preview_sync`;
|
|
29
|
+
- `sync_graph`;
|
|
30
|
+
- `pull_architecture_contract`;
|
|
31
|
+
- endpoint capability negotiation and authentication.
|
|
32
|
+
|
|
33
|
+
The exact final catalog is locked by tests before release. There is no legacy
|
|
34
|
+
`online`, `hosted` or `full` alias in the offline artifact that can re-enable an
|
|
35
|
+
HTTP implementation.
|
|
36
|
+
|
|
37
|
+
## Update model
|
|
38
|
+
|
|
39
|
+
`weavatrix-online` depends on a compatible `weavatrix` release and uses a
|
|
40
|
+
supported tool-extension API. Parser, graph schema, LSP and Health fixes are
|
|
41
|
+
therefore updated once in the MIT core. The online repository contains only
|
|
42
|
+
transport, endpoint/authentication and consent-flow code.
|
|
43
|
+
|
|
44
|
+
## Release gates
|
|
45
|
+
|
|
46
|
+
The two public packages are released as a compatible pair only after:
|
|
47
|
+
|
|
48
|
+
1. offline npm, MCPB, skill, server metadata and docs expose no HTTP tools or
|
|
49
|
+
hosted URL/token inputs;
|
|
50
|
+
2. network action modules and fetch paths are absent from the offline package;
|
|
51
|
+
3. the online connector passes the same local graph/Health regression suite
|
|
52
|
+
through its core dependency;
|
|
53
|
+
4. Cloud and Enterprise pass one wire-contract suite;
|
|
54
|
+
5. license metadata clearly distinguishes MIT core from permission-required,
|
|
55
|
+
source-available connector code;
|
|
56
|
+
6. stale 0.2 processes fail clearly rather than advertising mixed catalogs.
|
|
57
|
+
|
|
58
|
+
Existing MIT releases are unchanged and remain usable under MIT.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Weavatrix 0.2.18
|
|
2
|
+
|
|
3
|
+
0.2.18 is the publishable repository-root and self-audit trust patch. The `v0.2.17` source tag was
|
|
4
|
+
created, but its release validation exposed the nested-root discovery regression before an npm
|
|
5
|
+
package was published. The tag remains immutable; 0.2.18 contains that release plus the fix.
|
|
6
|
+
|
|
7
|
+
## Repository discovery
|
|
8
|
+
|
|
9
|
+
- The internal builder distinguishes an actual Git root from a directory that merely lives below
|
|
10
|
+
another Git repository.
|
|
11
|
+
- When the selected nested directory is ignored by its parent and Git returns an ambiguous empty
|
|
12
|
+
file universe, Weavatrix uses its boundary-safe filesystem walker instead of building a zero-node
|
|
13
|
+
graph.
|
|
14
|
+
- A real Git root with an empty tracked/unignored universe stays empty, so ignored build output and
|
|
15
|
+
secrets are not reintroduced.
|
|
16
|
+
- The live stdio hot-reload regression now exercises the corrected path and passes independently,
|
|
17
|
+
matching the Linux release runner rather than depending on full-suite ordering.
|
|
18
|
+
|
|
19
|
+
## Included 0.2.17 trust work
|
|
20
|
+
|
|
21
|
+
- Drizzle configuration roots its schema modules in production reachability.
|
|
22
|
+
- Scoped typosquat review compares only compatible scoped targets.
|
|
23
|
+
- Executable regular expressions retain equality anchors for clone fingerprints without exposing
|
|
24
|
+
their source text.
|
|
25
|
+
- Obsolete test-only dependency parsers were removed and repeated dependency, graph and architecture
|
|
26
|
+
helpers were consolidated under shared owners.
|
|
27
|
+
- Hosted evidence applies the same production-first classification and recomputes bounded summaries
|
|
28
|
+
after filtering.
|
|
29
|
+
|
|
30
|
+
## Product boundary
|
|
31
|
+
|
|
32
|
+
- `weavatrix` remains MIT and offline-first. No license change is made here.
|
|
33
|
+
- The accepted 0.3 split moves outbound HTTP into the separately versioned `weavatrix-online`
|
|
34
|
+
connector, whose permission-required license remains a release gate.
|
|
35
|
+
|
|
36
|
+
## Verification
|
|
37
|
+
|
|
38
|
+
Release validation covers the full Node test suite on supported Node versions, independent hot
|
|
39
|
+
reload execution, release-manifest checks, npm/MCPB packing and runtime smoke tests. The paired
|
|
40
|
+
Hosted deployment is rebuilt from this graph, then verified through its live Health and History
|
|
41
|
+
views.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weavatrix",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local repository intelligence MCP for AI coding agents: reusable architecture graph for fast application understanding, Health, dead code, clones, history, blast radius and architecture safeguards.",
|
|
6
6
|
"author": "Sergii Ziborov <sergii.ziborov@gmail.com>",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"skill",
|
|
27
27
|
"scripts/run-agent-task-benchmark.mjs",
|
|
28
28
|
"docs/agent-task-benchmark.md",
|
|
29
|
-
"docs/
|
|
29
|
+
"docs/adr/0001-v0.3-offline-online-split.md",
|
|
30
|
+
"docs/releases/v0.2.18.md",
|
|
30
31
|
"README.md",
|
|
31
32
|
"SECURITY.md",
|
|
32
33
|
"LICENSE"
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"benchmark:real:release": "node scripts/run-real-benchmark.mjs --require-all",
|
|
40
41
|
"benchmark:agent": "node scripts/run-agent-task-benchmark.mjs",
|
|
41
42
|
"benchmark:agent:release": "node scripts/run-agent-task-benchmark.mjs --require-independent",
|
|
43
|
+
"sync:private": "node scripts/sync-private.mjs",
|
|
42
44
|
"build:mcpb": "node scripts/build-mcpb.mjs",
|
|
43
45
|
"verify:release": "node scripts/verify-release.mjs"
|
|
44
46
|
},
|
|
@@ -56,6 +58,9 @@
|
|
|
56
58
|
"dependency-graph",
|
|
57
59
|
"tree-sitter",
|
|
58
60
|
"claude",
|
|
61
|
+
"codex",
|
|
62
|
+
"openai-codex",
|
|
63
|
+
"coding-agents",
|
|
59
64
|
"ai-agents"
|
|
60
65
|
],
|
|
61
66
|
"dependencies": {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createRepoBoundary } from "../repo-path.js";
|
|
2
|
+
import { cargoName, parseCargoToml } from "./cargo-manifests.js";
|
|
3
|
+
import { dependencyVerification, makeFinding } from "./findings.js";
|
|
4
|
+
import { listRepoFiles, readRepoText } from "./internal-audit.collect.js";
|
|
5
|
+
import { dependencyScopeOwnsFile, dependencyScopeRoot } from "./dependency/scoped-dependencies.js";
|
|
6
|
+
|
|
7
|
+
export function collectCargoDependencyEvidence(repoRoot, { files = listRepoFiles(repoRoot), externalImports = [] } = {}) {
|
|
8
|
+
const boundary = createRepoBoundary(repoRoot);
|
|
9
|
+
const manifests = files.filter((file) => /(^|\/)Cargo\.toml$/i.test(file));
|
|
10
|
+
const scopes = manifests.map((file) => ({ file, root: dependencyScopeRoot(file), ...parseCargoToml(readRepoText(boundary, file)) }))
|
|
11
|
+
.sort((left, right) => right.root.length - left.root.length || left.root.localeCompare(right.root));
|
|
12
|
+
const workspaceDeps = new Map();
|
|
13
|
+
for (const scope of scopes) for (const dependency of scope.workspaceDependencies) workspaceDeps.set(cargoName(dependency.alias), dependency);
|
|
14
|
+
const issues = [];
|
|
15
|
+
for (const scope of scopes) {
|
|
16
|
+
scope.dependencies = scope.dependencies.map((dependency) => {
|
|
17
|
+
if (!dependency.inherited) return dependency;
|
|
18
|
+
const inherited = workspaceDeps.get(cargoName(dependency.alias));
|
|
19
|
+
if (!inherited) { issues.push(`${scope.file}: workspace dependency ${dependency.alias} is unresolved`); return dependency; }
|
|
20
|
+
return { ...dependency, name: inherited.name, version: inherited.version, inherited: true };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const importsByScope = new Map(scopes.map((scope) => [scope, []]));
|
|
24
|
+
for (const entry of externalImports) {
|
|
25
|
+
if (entry.ecosystem !== "crates.io" || !entry.pkg || entry.builtin || entry.unresolved) continue;
|
|
26
|
+
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, entry.file));
|
|
27
|
+
if (owner) importsByScope.get(owner).push(entry);
|
|
28
|
+
}
|
|
29
|
+
const findings = [], declared = new Set();
|
|
30
|
+
let mappedImports = 0, unmappedImports = 0;
|
|
31
|
+
if (!scopes.length) {
|
|
32
|
+
const missing = new Map();
|
|
33
|
+
for (const entry of externalImports.filter((item) => item.ecosystem === "crates.io" && item.pkg && !item.builtin && !item.unresolved)) {
|
|
34
|
+
if (!missing.has(cargoName(entry.pkg))) missing.set(cargoName(entry.pkg), entry);
|
|
35
|
+
}
|
|
36
|
+
for (const [name, entry] of missing) findings.push(makeFinding({
|
|
37
|
+
category: "unused", rule: "missing-dep", severity: "low", confidence: "high",
|
|
38
|
+
title: `Rust crate path without Cargo.toml: ${name}`,
|
|
39
|
+
reason: "An indexed external Rust crate path exists, but no Cargo.toml was discovered.",
|
|
40
|
+
detail: `"${entry.spec || name}" is used by ${entry.file}, but no Cargo manifest owns the file.`,
|
|
41
|
+
package: name, file: entry.file, line: entry.line || 0, source: "internal",
|
|
42
|
+
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_PRESENT" }, indexedSourceImports: { status: "FOUND", count: 1, files: [entry.file] }, mapping: "Cargo crate name normalization" },
|
|
43
|
+
fixHint: "initialize or restore the owning Cargo manifest",
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
for (const scope of scopes) {
|
|
47
|
+
const imports = importsByScope.get(scope);
|
|
48
|
+
const used = new Map(), missingSeen = new Set();
|
|
49
|
+
for (const entry of imports) {
|
|
50
|
+
const imported = cargoName(entry.pkg);
|
|
51
|
+
const dependency = scope.dependencies.find((item) => cargoName(item.alias) === imported || cargoName(item.name) === imported);
|
|
52
|
+
if (dependency) {
|
|
53
|
+
const evidence = used.get(dependency.alias) || [];
|
|
54
|
+
evidence.push(entry); used.set(dependency.alias, evidence); mappedImports++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
unmappedImports++;
|
|
58
|
+
if (missingSeen.has(imported)) continue;
|
|
59
|
+
missingSeen.add(imported);
|
|
60
|
+
findings.push(makeFinding({
|
|
61
|
+
category: "unused", rule: "missing-dep", severity: "medium", confidence: "high",
|
|
62
|
+
title: `Missing Cargo dependency: ${entry.pkg}`,
|
|
63
|
+
reason: "An indexed external Rust crate path has no matching dependency alias or package name in the nearest Cargo.toml.",
|
|
64
|
+
detail: `"${entry.spec || entry.pkg}" is used by ${entry.file}, but ${scope.file} does not declare crate "${entry.pkg}" (including workspace inheritance and renamed packages).`,
|
|
65
|
+
package: entry.pkg, file: entry.file, line: entry.line || 0, manifest: scope.file,
|
|
66
|
+
evidence: [{ file: entry.file, line: entry.line || 0, snippet: entry.spec || "" }], source: "internal",
|
|
67
|
+
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_FOUND", file: scope.file }, indexedSourceImports: { status: "FOUND", count: 1, files: [entry.file] }, mapping: "Cargo alias/package normalization" },
|
|
68
|
+
fixHint: `cargo add ${entry.pkg}`,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
for (const dependency of scope.dependencies) {
|
|
72
|
+
const identity = cargoName(dependency.name);
|
|
73
|
+
if (!identity) continue;
|
|
74
|
+
declared.add(`${scope.root || "."}:${identity}`);
|
|
75
|
+
const evidence = used.get(dependency.alias) || [];
|
|
76
|
+
if (evidence.length || dependency.optional || dependency.build) continue;
|
|
77
|
+
findings.push(makeFinding({
|
|
78
|
+
category: "unused", rule: "unused-dep", severity: dependency.dev ? "info" : "low", confidence: "low",
|
|
79
|
+
title: `Unused Cargo dependency: ${dependency.name}`,
|
|
80
|
+
reason: "No indexed Rust use/path/extern-crate evidence maps to this dependency; feature-only, proc-macro, generated and reflective registration remain possible.",
|
|
81
|
+
detail: `"${dependency.name}" (${dependency.alias}) is declared in ${scope.file}, but no indexed .rs file in that Cargo scope references its crate path. Confirm with cargo check/test and feature combinations before removal.`,
|
|
82
|
+
package: dependency.name, version: dependency.version, manifest: scope.file, source: "internal",
|
|
83
|
+
verification: dependencyVerification(scope.file, [], "REVIEW_REQUIRED", "Cargo alias/package normalization"),
|
|
84
|
+
fixHint: `cargo remove ${dependency.alias} after checking all features and targets`,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const present = manifests.length > 0;
|
|
89
|
+
return {
|
|
90
|
+
present,
|
|
91
|
+
status: present ? "CHECKED" : "NOT_PRESENT",
|
|
92
|
+
completeness: present ? (issues.length ? "PARTIAL" : "COMPLETE") : "NOT_APPLICABLE",
|
|
93
|
+
manifests,
|
|
94
|
+
declared,
|
|
95
|
+
mappedImports,
|
|
96
|
+
unmappedImports,
|
|
97
|
+
findings,
|
|
98
|
+
reasons: issues,
|
|
99
|
+
reason: !present
|
|
100
|
+
? "No Cargo.toml was discovered."
|
|
101
|
+
: issues.length
|
|
102
|
+
? `Cargo declarations and indexed crate paths were checked, but ${issues.length} workspace inheritance reference(s) remain unresolved.`
|
|
103
|
+
: `Cargo declarations from ${manifests.length} scope(s), workspace inheritance, renamed packages and every indexed external crate path were compared.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const clean = (value) => String(value || "").trim().replace(/^['"]|['"]$/g, "");
|
|
2
|
+
export const cargoName = (value) => clean(value).toLowerCase().replace(/_/g, "-");
|
|
3
|
+
|
|
4
|
+
function inlineTable(value) {
|
|
5
|
+
const fields = new Map();
|
|
6
|
+
for (const match of String(value || "").matchAll(/([A-Za-z][\w.-]*)\s*=\s*("(?:\\.|[^"])*"|'(?:\\.|[^'])*'|true|false)/g)) {
|
|
7
|
+
fields.set(match[1], clean(match[2]));
|
|
8
|
+
}
|
|
9
|
+
return fields;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Bounded Cargo.toml parser for package/workspace dependency tables. It intentionally owns
|
|
13
|
+
// dependency identity, aliases and concrete versions; feature activation is not treated as usage.
|
|
14
|
+
export function parseCargoToml(text) {
|
|
15
|
+
const source = String(text || "");
|
|
16
|
+
const dependencies = [];
|
|
17
|
+
const workspaceDependencies = [];
|
|
18
|
+
let section = "", packageName = "", workspace = false;
|
|
19
|
+
for (const raw of source.split(/\r?\n/)) {
|
|
20
|
+
const line = raw.replace(/(^|\s)#.*$/, "").trim();
|
|
21
|
+
if (!line) continue;
|
|
22
|
+
const header = /^\[([^\]]+)]$/.exec(line);
|
|
23
|
+
if (header) { section = header[1].trim(); workspace ||= section === "workspace"; continue; }
|
|
24
|
+
const kv = /^([A-Za-z0-9_-]+)\s*=\s*(.+)$/.exec(line);
|
|
25
|
+
if (!kv) continue;
|
|
26
|
+
if (section === "package" && kv[1] === "name") { packageName = clean(kv[2]); continue; }
|
|
27
|
+
const dependencyTable = /^(?:target\..+\.)?(dependencies|dev-dependencies|build-dependencies)$/.exec(section);
|
|
28
|
+
const workspaceTable = section === "workspace.dependencies";
|
|
29
|
+
if (!dependencyTable && !workspaceTable) continue;
|
|
30
|
+
const alias = kv[1], value = kv[2].trim();
|
|
31
|
+
const table = value.startsWith("{") ? inlineTable(value) : new Map();
|
|
32
|
+
const version = value.startsWith("{") ? clean(table.get("version")) : clean(value);
|
|
33
|
+
const name = clean(table.get("package")) || alias;
|
|
34
|
+
const record = {
|
|
35
|
+
alias,
|
|
36
|
+
name,
|
|
37
|
+
version: /^\d/.test(version) || /^=?\s*\d/.test(version) ? version.replace(/^=\s*/, "") : "",
|
|
38
|
+
dev: dependencyTable?.[1] === "dev-dependencies",
|
|
39
|
+
build: dependencyTable?.[1] === "build-dependencies",
|
|
40
|
+
optional: table.get("optional") === "true",
|
|
41
|
+
inherited: table.get("workspace") === "true",
|
|
42
|
+
};
|
|
43
|
+
(workspaceTable ? workspaceDependencies : dependencies).push(record);
|
|
44
|
+
}
|
|
45
|
+
return { packageName, workspace, dependencies, workspaceDependencies };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Cargo.lock v3/v4 [[package]] records. Registry packages are exact installed crates; workspace
|
|
49
|
+
// and git packages are excluded because OSV crates.io versions must not be inferred for them.
|
|
50
|
+
export function parseCargoLockPackages(text) {
|
|
51
|
+
const packages = [];
|
|
52
|
+
let current = null;
|
|
53
|
+
const flush = () => {
|
|
54
|
+
if (current?.name && current.version && /^registry\+https:\/\/github\.com\/rust-lang\/crates\.io-index|^registry\+https:\/\/index\.crates\.io\//.test(current.source)) {
|
|
55
|
+
packages.push({ ecosystem: "crates.io", name: current.name, version: current.version, dev: false, integrity: current.checksum || "", source: "cargo-lock" });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
59
|
+
const line = raw.trim();
|
|
60
|
+
if (line === "[[package]]") { flush(); current = { name: "", version: "", source: "", checksum: "" }; continue; }
|
|
61
|
+
if (!current) continue;
|
|
62
|
+
if (/^\[/.test(line)) { flush(); current = null; continue; }
|
|
63
|
+
const match = /^(name|version|source|checksum)\s*=\s*"([^"]+)"/.exec(line);
|
|
64
|
+
if (match) current[match[1]] = match[2];
|
|
65
|
+
}
|
|
66
|
+
flush();
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
return packages.filter((pkg) => {
|
|
69
|
+
const key = `${pkg.name}@${pkg.version}`;
|
|
70
|
+
if (seen.has(key)) return false;
|
|
71
|
+
seen.add(key);
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -194,6 +194,9 @@ export function computePyDepFindings(options = {}) {
|
|
|
194
194
|
findings.push(...result.findings.map((finding) => ({
|
|
195
195
|
...finding,
|
|
196
196
|
...(scope.manifests?.length ? {manifest: scope.manifests[0]} : {}),
|
|
197
|
+
verification: finding.rule === "missing-dep"
|
|
198
|
+
? { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: scope.present ? "NOT_FOUND" : "NOT_PRESENT", files: scope.manifests || [] }, indexedSourceImports: { status: "FOUND", count: finding.evidence?.length || 1, files: (finding.evidence || []).map((item) => item.file) }, mapping: "PEP 503 plus bounded import-to-distribution aliases" }
|
|
199
|
+
: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "REVIEW_REQUIRED", manifestDeclaration: { status: "FOUND", files: scope.manifests || [] }, indexedSourceImports: { status: "ZERO_FOUND", completeness: "COMPLETE_FOR_GRAPH_SCOPE", count: 0, files: [] }, mapping: "PEP 503 plus bounded import-to-distribution aliases" },
|
|
197
200
|
})));
|
|
198
201
|
for (const name of result.declared) declared.add(`${scope.root || "."}:${name}`);
|
|
199
202
|
for (const name of result.managed) managed.add(name);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {posix} from 'node:path'
|
|
2
|
+
|
|
3
|
+
export const normalizeDependencyScope = (root) => String(root || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+|\/+$/g, '')
|
|
4
|
+
export const dependencyScopeRoot = (manifest) => {
|
|
5
|
+
const root = posix.dirname(normalizeDependencyScope(manifest))
|
|
6
|
+
return root === '.' ? '' : root
|
|
7
|
+
}
|
|
8
|
+
export const dependencyScopeOwnsFile = (scope, file) => !scope || file === scope || String(file || '').startsWith(`${scope}/`)
|
|
3
9
|
|
|
4
10
|
// Bind the ecosystem-specific core without introducing a facade/helper import cycle.
|
|
5
11
|
export function createScopedDepFindings(computeDepFindings) {
|
|
@@ -8,25 +14,25 @@ export function createScopedDepFindings(computeDepFindings) {
|
|
|
8
14
|
sourceTexts = new Map(), nonRuntimeRoots = [], sourceFiles = [],
|
|
9
15
|
} = {}) {
|
|
10
16
|
const scopes = packageScopes.length
|
|
11
|
-
? packageScopes.map((scope) => ({...scope, root:
|
|
17
|
+
? packageScopes.map((scope) => ({...scope, root: normalizeDependencyScope(scope.root)})).sort((a, b) => b.root.length - a.root.length)
|
|
12
18
|
: [{root: '', manifest: 'package.json', pkg: {}, aliases: []}]
|
|
13
19
|
const importsByScope = new Map(scopes.map((scope) => [scope, []]))
|
|
14
20
|
const sourceFilesByScope = new Map(scopes.map((scope) => [scope, []]))
|
|
15
21
|
const sourceTextsByScope = new Map(scopes.map((scope) => [scope, new Map()]))
|
|
16
22
|
for (const item of externalImports) {
|
|
17
|
-
const owner = scopes.find((scope) =>
|
|
23
|
+
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, item.file)) || scopes[scopes.length - 1]
|
|
18
24
|
importsByScope.get(owner).push(item)
|
|
19
25
|
}
|
|
20
26
|
for (const file of sourceFiles) {
|
|
21
|
-
const owner = scopes.find((scope) =>
|
|
27
|
+
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1]
|
|
22
28
|
sourceFilesByScope.get(owner).push(file)
|
|
23
29
|
}
|
|
24
30
|
for (const [file, text] of sourceTexts) {
|
|
25
|
-
const owner = scopes.find((scope) =>
|
|
31
|
+
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1]
|
|
26
32
|
sourceTextsByScope.get(owner).set(file, text)
|
|
27
33
|
}
|
|
28
34
|
const configOwner = new Map()
|
|
29
|
-
for (const [file] of configTexts) configOwner.set(file, scopes.find((scope) =>
|
|
35
|
+
for (const [file] of configTexts) configOwner.set(file, scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1])
|
|
30
36
|
const findings = [], usedPackages = new Map(), declared = new Set()
|
|
31
37
|
for (const scope of scopes) {
|
|
32
38
|
const scopeConfig = new Map([...configTexts].filter(([file]) => configOwner.get(file) === scope))
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
const K = 8; // k-gram length (tokens)
|
|
5
5
|
const W = 4; // winnowing window → guaranteed detection of matches ≥ K+W-1 tokens
|
|
6
|
-
const STRLIT = String.fromCharCode(0); // opaque-literal sentinel for
|
|
6
|
+
const STRLIT = String.fromCharCode(0); // opaque-literal sentinel for string bodies: a NUL
|
|
7
7
|
// its own class (never an identifier "I" nor a number "N"), so a literal can
|
|
8
8
|
// never masquerade as code in either mode
|
|
9
|
+
const REGEXLIT = String.fromCharCode(1); // regex bodies are executable behavior, so retain an equality anchor
|
|
10
|
+
const literalHash = (value) => {
|
|
11
|
+
let hash = 2166136261;
|
|
12
|
+
for (const char of String(value || "")) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
|
|
13
|
+
return (hash >>> 0).toString(16);
|
|
14
|
+
};
|
|
15
|
+
const regexMarker = (value) => ` ${REGEXLIT} 0x${literalHash(value)} `;
|
|
9
16
|
|
|
10
17
|
const KEYWORDS = new Set(("if else for while do switch case break continue return function const let var new class extends async await try catch finally throw import from export default typeof instanceof in of delete void yield static get set this super null undefined true false def elif except lambda pass raise with as is not and or None True False func go defer chan map range struct interface type package nil err string int bool byte float64 public private protected final void long double boolean").split(" "));
|
|
11
18
|
|
|
@@ -16,9 +23,9 @@ const KEYWORDS = new Set(("if else for while do switch case break continue retur
|
|
|
16
23
|
// regex and ate the rest of the line.
|
|
17
24
|
const REGEX_PREV = new Set("(,=:[!&|?{};~*%^<>".split(""));
|
|
18
25
|
|
|
19
|
-
// Replace comments with nothing and string
|
|
20
|
-
//
|
|
21
|
-
//
|
|
26
|
+
// Replace comments with nothing and string bodies with an opaque sentinel. Regex bodies retain only
|
|
27
|
+
// a one-way equality anchor: their source is executable behavior, and collapsing every pattern to the
|
|
28
|
+
// same token creates false clone groups for unrelated replace/validate pipelines.
|
|
22
29
|
export function stripNonCode(text, py) {
|
|
23
30
|
let out = "", i = 0, prevSig = "";
|
|
24
31
|
const s = String(text || "");
|
|
@@ -34,7 +41,7 @@ export function stripNonCode(text, py) {
|
|
|
34
41
|
if (!py && two === "//") { while (i < s.length && s[i] !== "\n") i++; continue; }
|
|
35
42
|
if (!py && two === "/*") { i += 2; while (i < s.length && s.slice(i, i + 2) !== "*/") i++; i += 2; continue; }
|
|
36
43
|
if (!py && ch === "/" && REGEX_PREV.has(prevSig)) {
|
|
37
|
-
i++; let inClass = false; // '/' inside a [...] char class is literal, not the terminator
|
|
44
|
+
i++; const regexStart = i; let inClass = false; // '/' inside a [...] char class is literal, not the terminator
|
|
38
45
|
while (i < s.length && s[i] !== "\n") {
|
|
39
46
|
const c = s[i];
|
|
40
47
|
if (c === "\\") { i += 2; continue; }
|
|
@@ -44,7 +51,7 @@ export function stripNonCode(text, py) {
|
|
|
44
51
|
i++;
|
|
45
52
|
}
|
|
46
53
|
while (i < s.length && /[a-z]/i.test(s[i])) i++; // regex flags (gimsuy)
|
|
47
|
-
push(
|
|
54
|
+
push(regexMarker(s.slice(regexStart, i))); continue;
|
|
48
55
|
}
|
|
49
56
|
if (ch === '"' || ch === "'" || ch === "`") {
|
|
50
57
|
const q = ch; i++;
|
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
// literal annotation paths: inventing values for constants would make an architecture inventory look
|
|
3
3
|
// more complete than the evidence permits.
|
|
4
4
|
import { maskJavaNonCode } from "./java-source.js";
|
|
5
|
+
import { lineNumberAt } from "../util.js";
|
|
5
6
|
|
|
6
7
|
const SPRING_MAPPING = /@(?:org\.springframework\.web\.bind\.annotation\.)?(RequestMapping|GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping)\b/g;
|
|
7
8
|
const SPRING_CONDITION = /@(?:org\.springframework\.boot\.autoconfigure\.condition\.)?(ConditionalOnExpression|ConditionalOnProperty)\b/g;
|
|
8
9
|
const REQUEST_METHOD = /\bRequestMethod\s*\.\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE)\b/g;
|
|
9
10
|
|
|
10
|
-
const lineAt =
|
|
11
|
-
let line = 1;
|
|
12
|
-
for (let i = 0; i < index && i < text.length; i++) if (text[i] === "\n") line++;
|
|
13
|
-
return line;
|
|
14
|
-
};
|
|
11
|
+
const lineAt = lineNumberAt;
|
|
15
12
|
|
|
16
13
|
function skipTrivia(text, start) {
|
|
17
14
|
let i = start;
|
package/src/analysis/findings.js
CHANGED
|
@@ -6,6 +6,18 @@ import { createHash } from "node:crypto";
|
|
|
6
6
|
export const SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
|
|
7
7
|
export const FINDING_CATEGORIES = ["unused", "structure", "vulnerability", "malware"];
|
|
8
8
|
|
|
9
|
+
export function dependencyVerification(manifest, imports, decision, mapping) {
|
|
10
|
+
return {
|
|
11
|
+
evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE",
|
|
12
|
+
decision,
|
|
13
|
+
manifestDeclaration: { status: "FOUND", file: manifest },
|
|
14
|
+
indexedSourceImports: imports.length
|
|
15
|
+
? { status: "FOUND", count: imports.length, files: [...new Set(imports.map((item) => item.file))].slice(0, 10) }
|
|
16
|
+
: { status: "ZERO_FOUND", completeness: "COMPLETE_FOR_GRAPH_SCOPE", count: 0, files: [] },
|
|
17
|
+
mapping,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
// Stable id: survives re-runs so the UI can persist expand/dismiss state per finding.
|
|
10
22
|
export function makeFinding(f) {
|
|
11
23
|
const cycleIdentity = Array.isArray(f.cycleMembers)
|