Commit Graph
16 Commits
Author SHA1 Message Date
512bcf5cdb fix(javascript): analyze first-party sources before dependencies (#9341)
* fix(javascript): analyze first-party sources before dependencies

Every source in one application analysis charges the same parent result
budget, and sources were offered in lexical inventory order. On an Electron
app.asar, node_modules/ sorts before an electron-vite out-*/ directory, so
dependency files spent the budget and every first-party main, preload, and
renderer file had its semantic analysis not started and its application
projection rejected. The summary then reported zero windows, preloads, and
bridges while electron_semantics still said "recovered".

Offer application JavaScript before JavaScript under node_modules; reported
files keep inventory order. Count Electron boundary facts only from files
whose projection was admitted, and report electron_semantics "partial" (with
a limitation naming the count) when an Electron-loading source was not fully
examined or reported.

Fixes #9321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(javascript): treat limited Electron examination as partial coverage

A static pass can stop early at the Electron wrapper expansion budget or
another resource limit without failing the worker. Such a source carries no
analysis or projection failure, so its retained IPC or bridge facts still
reported electron_semantics "recovered". Read the owned examination status
and count a limited Electron-loading source as incomplete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 19:42:19 +08:00
morlutoandFelix 52fe342c73 fix(evidence): preserve subjects in derived workflows (#9492)
* fix(javascript): preserve verified trace evidence subjects

Co-authored-by: Felix <3937061056@qq.com>

* fix(evidence): preserve selected subject names in views

* fix(evidence): preserve subjects across related workflows

* fix(evidence): retain inventory projection subjects

* fix(managed): validate projected graph root identity

* style: format evidence and native command code

* fix(javascript): validate static graph root identity

* test(javascript): update feature trace graph expectations

---------

Co-authored-by: Felix <3937061056@qq.com>
2026-11-09 08:12:17 +08:00
吴佳浩Alben 99bcd3e4e0 fix(javascript): report empty Electron coverage instead of silent zeroes (#8332)
* fix(javascript): report empty Electron coverage instead of silent zeroes

`analyze_javascript_application` reported `browser_windows: 0` and
`ipc.operations: 0` with only generic partial/unknown flags, so a bundle that
imports `electron` but whose Electron facts were not recovered read exactly like
an application with no IPC at all. Issue #8201 is that false negative on a real
Rolldown bundle.

Two additions, both scoped to "Electron is referenced but nothing was recovered":

**A machine-readable indicator.** `electron_summary` gains
`electron_semantics: "recovered" | "empty"`. It is absent when no analyzed source
links an Electron module, so its presence distinguishes "not an Electron
application" from "Electron coverage is empty". It follows the existing
`protocol_registrations.exactOptional()` precedent, so previously captured
evidence stays valid.

**A graph limitation.** The limitation list already carried a note when at least
one Electron fact was recovered but said nothing when zero were. It now states
that zero IPC operations and zero BrowserWindows mean empty coverage rather than
absence.

`electronModuleReferenced` is the single owner of the module test. It reuses
`ELECTRON_MODULE`, which matches exactly the supported entry points (`electron`,
`electron/common`, `/main`, `/renderer`, `/utility`), so `electron-helper` does
not count, and it ignores type-only links because importing Electron types
promises no boundary fact.

Five tests cover recovered, empty, not-referenced, type-only, and supported
subpath versus unrelated package. The empty, recovered and type-only cases fail
on unmodified `main`.

* chore: retrigger title and readiness checks after the title was corrected

* fix(javascript): report Electron coverage from retained facts

Four review findings on the coverage indicator.

**A rejected application projection hid the reference.** A file whose expanded
relationships exceed the result budget is replaced by a placeholder with no
module links before the summary runs, while `graphLimitations` reads the raw
analysis. The same response could then warn in prose and omit the field. The
summary now takes the retained analysis for coverage only, and both outputs
read one `electronSemanticsState`, so they cannot disagree.

**A skipped semantic analysis hid the reference too.** `electronModuleReferenced`
read only `semantic.ir.moduleLinks`, which is absent when semantic analysis is
skipped for a budget and drops `import("electron")` or `module.require("electron")`
forms. It now falls back to the retained `javascript.references`, which the static
producer records for those inputs, while still excluding `type-import` links when
the richer owner is available.

`electronBoundaryFactCount` is now the single owner of the fact total, replacing a
duplicate inline reduce in the graph builder.

**Omission no longer claims absence.** The schema comment said a missing
`electron_semantics` means the application does not use Electron. The predicate
only establishes that no retained fact names one, and unread sources, failed
analysis, and dynamic module names can hide its use, so the contract now says
unknown.

Two regressions cover a rejected projection and a skipped semantic analysis; both
fail on the previous commit.

* fix(javascript): count a dynamic Electron import as coverage

Review finding: `electronModuleReferenced` returned as soon as module links
existed, so a literal dynamic import was ignored. Verified against the two fact
owners:

    import("electron")   -> references: [dynamic-import electron]
                            moduleLinks: []
    require("electron")  -> references: [require electron]
                            moduleLinks: [require electron]

An application that only loads Electron through `import("electron")` therefore
got neither `electron_semantics: "empty"` nor the empty-coverage warning, and the
zero counts stayed unexplained.

The two owners now answer their own half instead of one short-circuiting the
other. Module links answer a runtime static import or require and keep excluding
`type-import`; the retained reference is read for every kind except
`static-import`, which is exactly the type-only risk. A dynamic specifier is
still never counted by either owner, so this remains an observed-reference claim
rather than an absence claim.

The regression uses `import("electron")` and fails on the previous commit.

* fix(javascript): read only module-loading Electron references

Two review findings, both verified against the static reference producer.

**A retained runtime static import was skipped.** The excluded kind was meant to
protect the type-only distinction, but the static analyzer records a whole
`import type { IpcMain } from "electron"` declaration as no reference at all:

    import type { IpcMain } from "electron"   -> references: []
    import { app } from "electron"            -> references: [static-import electron]
    require("electron")                       -> references: [require electron]
    import("electron")                        -> references: [dynamic-import electron]
    new Worker(new URL("electron", m))        -> references: [worker electron]

When semantic analysis is skipped for a budget the module links are absent, so
that exclusion dropped a real runtime ESM import and left the zero counts
unexplained.

**A worker URL path is not an Electron module.** `new Worker(new
URL("electron", import.meta.url))` records a `worker` reference whose specifier is
a path relative to the importing source, so counting it reported an ordinary
worker file as `electron_semantics: "empty"`.

`ELECTRON_REFERENCE_KINDS` is now an explicit allowlist of the kinds that load a
module by package specifier — `static-import`, `dynamic-import` and `require` —
so `worker` and `service-worker` URL paths are excluded. Module links are still
read first for the type-only distinction.

Three regressions cover a skipped-semantics ESM import, a skipped-semantics whole
type-only import, and a worker URL path; two of them fail on the previous commit.

* fix(javascript): own Electron applicability in the static producer

Review finding. A `worker` reference records a URL relative to the importing
source, so `new Worker(new URL("electron", import.meta.url))` names a local file
and is not an Electron module. The boundary owner already read only module-loading
reference kinds, but `finalizeStaticAnalysis` still treated every matching static
reference as `analysis_coverage.applicability.electron: "observed"`, which
`javascriptStaticSourceCoverageSummary` publishes. One response could therefore
say Electron was observed in its coverage view while the Electron summary omitted
`electron_semantics`.

`ELECTRON_REFERENCE_KINDS` now lives next to `ELECTRON_MODULE` in the domain owner
and is consumed by both the static producer and the Electron boundary owner, so the
two outputs cannot disagree. A worker URL path is `unknown` for both, while a
`require("electron")` is `observed` for both.

The regression asserts the domain applicability fact and the boundary summary agree
for a worker URL path; it fails when the producer stops filtering by kind.

* fix(javascript): require a loaded Electron module for applicability

Review finding. A CommonJS `require.resolve("electron")` records a `require`
reference with `operation: "resolve"`: it locates the module without loading it,
so no Electron boundary fact is expected. Both predicates dropped the operation,
so such a source reported `electron_semantics: "empty"` and the missing-coverage
warning even though Electron was never loaded.

Measured on the static producer:

    require.resolve("electron")  -> references: [require resolve electron]
                                    applicability.electron: unknown
    require("electron")          -> references: [require load electron]
                                    applicability.electron: observed
    import("electron")           -> references: [dynamic-import load electron]
                                    applicability.electron: observed

`electronModuleReferenced` and `finalizeStaticAnalysis` now both require
`operation === "load"`, so the boundary summary and the coverage view stay aligned
and reachability-only references no longer claim missing coverage.

The regression asserts a resolve-only source produces neither the field nor the
warning, and it fails when the operation check is removed.

* fix(javascript): classify Electron loads in the static fact owner

Review finding, and the correct home for the previous two rounds of the same
complaint. A Webpack/Rspack factory may name a local module `"electron"`, and the
static producer then records a loaded `require` reference whose specifier is that
bundle module key. Treating the key as a package specifier reported
`electron_semantics: "empty"` plus the missing-coverage warning for a bundle that
never loads Electron.

Three changes, all in the domain owner, so no consumer re-derives the question:

- `JavaScriptModuleRangeIndex.hasKey` answers whether a specifier is a recovered
  bundle module key. A `require` whose specifier is such a key loads a module
  inside the artifact.
- `electronObserved` in `finalizeStaticAnalysis` now requires a loaded module-load
  reference whose specifier is not a bundle key. The published
  `analysis_coverage.applicability.electron` therefore owns every exclusion:
  `type-import`, `require.resolve`, `worker`/`service-worker` URL paths, and
  bundle-local keys.
- `electronModuleReferenced` consumes that owned fact instead of re-deriving it,
  keeping the boundary summary and the coverage view from disagreeing. Historical
  evidence without the field still falls back to the retained references.

Measured:

    bundle-local module ID "electron"  -> applicability unknown
    require("electron")                -> applicability observed
    webpack external n(45840)          -> applicability observed

The regression covers both the local key and the real external in the same
factory shape; it fails without the bundle-key exclusion.

* fix(javascript): identify a bundle module by its loader, not its key

Both review findings were right, and they show why the previous key-set approach
was wrong: bundle-key membership is a property of the file, but whether a
specifier names a module inside the artifact is a property of the reference.

`JavaScriptStaticReference` now records the loader that produced it, and
`electronObserved` excludes `factory-loader` references instead of every specifier
that happens to match a recovered module key. Measured on Node 24.22.1:

    local module keyed "electron"                 -> unknown  (factory-loader)
    external 45840 = require("electron")          -> observed (commonjs-loader)
    factory keyed "electron" that loads the pkg   -> observed (commonjs-loader)

The third case is the regression: a bundle whose factory is *named* `"electron"`
but whose body does `e.exports = require("electron")` is a real Electron load, and
the old key-set check suppressed it. The real load keeps its `commonjs-loader`
identity even when the same specifier also names a factory, so one response can
rely on the loader while the other relies on the key without either lying.

`JavaScriptModuleRangeIndex.hasKey` is removed; it answered the wrong question.

One regression covers all three shapes and fails when the `factory-loader`
exclusion is dropped.

* fix(javascript): carry the reference loader across the worker boundary

The worker schema is strict, so the new `loader` field broke the worker handshake:
`unrecognized_keys` on every `references` entry. That surfaced in CI as both a
worker-schema test failure and a package-baseline failure reporting
`worker_failure_reason: "io"` for a one-file Electron app.

`referenceShape` now accepts the same three loader identities as the domain type, so
the fact survives the worker boundary instead of collapsing every analysis to an
execution failure.

Verified: `JavaScriptAnalysisWorkerSchemas.test.ts` 10 passed, and the full
domain/application/javascript suite is 5484 passed | 10 skipped. The remaining
`SourceMapCommand` and `DarwinHelperCache` findings are present on unmodified
`main` and are not from this change.

* fix(javascript): remove the superseded key set and forward a deferred loader

Two review findings.

**The `keys` set is dead.** `JavaScriptModuleRangeIndex.keys` is left over from the
abandoned key-membership approach: the loader-identity refactor removed `hasKey` and
the write in `add`, so nothing reads it. Removed.

**The deferred require path dropped the loader identity.** `requireCallSpecifier`
returns a `loader` for `commonjs-loader`, `factory-loader` and `create-require`, and the
direct `addReference` call forwards it, but `finishModuleRequireCalls` did not. A
`createRequire` loader saved under a member literally named `require` therefore lost
its identity:

    import { createRequire } from "node:module";
    const holder = { require: createRequire(import.meta.url) };
    holder.require("x");

    before: require "x" with loader undefined
    after:  require "x" with loader "create-require"

Three regressions cover it; one fails without the change.

I did **not** change the inline-type-only import case. `isTypeOnlyImportDeclaration`
deliberately keeps a declaration whose bindings are all inline type-only, because
`hasUncertainTypeOnlyModuleSideEffect` reports that decision as
`type-only-module-side-effect` uncertainty: whether TypeScript emits the import depends
on the consumer's compiler options. Narrowing it to erased broke
`javascriptStaticCoverage.test.ts` and `javascriptSemanticAnalysis.require_links.test.ts`,
which assert exactly that contract. If Electron applicability should ignore that
reference, the decision belongs where applicability is derived, not in the shared
declaration predicate — say the word and I will do it there instead.

* fix(javascript): keep a commonjs-loader reference out of the bundle module index

Review finding. `finalizeLocatedFindings` attaches `module_runtime` to every finding
that sits inside a factory module range, whoever produced it. `resolveReference` then
sent every reference carrying `module_runtime` through the bundle-module lookup, so a
real CommonJS load inside a factory body resolved to the local module key that shared
its name:

    "electron": (e, t, n) => { e.exports = require("electron") }

    fact:  specifier "electron", loader "commonjs-loader", module_key "electron"
    before: resolved through the bundle module index to `#module:electron`
    after:  resolves as an external package specifier

Only a `factory-loader` specifier names a recovered bundle module key, which is what
`loader` already records after the loader-identity change. The module lookup is now
gated on it, and an absent `loader` keeps the previous behaviour for older findings.
2026-11-06 15:15:43 +08:00
Abdelrahman Teimaandgensokyo-dev 47fa6ab379 feat(electron): report custom URL protocol registrations
Expose static Electron protocol call facts through the shared application graph and existing CLI/MCP Evidence views. Preserve canonical case-insensitive scheme identity, original spellings, exact source provenance and explicit unknowns; keep registration success and runtime routing unproven.

Integration fixes also enforce module-only privileged API ownership, reuse the shared finding-key owner, and align the owning type documentation with the Evidence semantics. Validation and bounded real Obsidian CLI/MCP measurements are recorded in the PR.

Co-authored-by: UnknownHacker1 <35694178+UnknownHacker1@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-31 02:23:47 +08:00
gensokyo-dev 7d0efef62d feat(javascript): retain queryable static route inventories (#6019) 2026-10-22 16:54:14 +08:00
郭立 Guo Li 7590b50801 perf(schema): reduce retained integer validator allocations (#4360)
Construct integer formats directly while preserving bounds, refinements, metadata and current shared schema owners. Keep Incur-dependent CLI numeric coercion declarations unchanged.

Verify canonical advertisements, 126 numeric boundary cases, 974 actual CLI parser cases, broad source tests and real public-artifact SDK workflows. Record 72 normal tasks and 12 separate owned-inspector heap diagnostics with whole-family resource bounds; distinguish SDK spans and asymmetric audit overhead. Generate numerical report sections from receipts and reject stale tables through documentation conformance checks.

Closes #6236. Current-head CI and Greptile pass; actionable review findings are addressed. Codex review was unavailable due repository usage limits.
2026-10-22 05:11:08 +00:00
8f7d12e30d perf(analysis-view): reuse validated immutable parents
Reuse successful format validation for exact authenticated immutable native and imported JavaScript results. Preserve schema normalization and alias topology before borrowing source data; owned JavaScript graph wrappers retain their existing metadata-normalization path.

Validated with 130 source tests, 36 compiled CLI/MCP boundary tests, 27 default-deadline integration tests, full checks/docs/build, and paired real retained-Evidence tasks. The report preserves cold costs, previous negative results, incomplete resource-stopped runs, and the limits of offline token estimates.

Fixes #4810

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-18 14:38:00 +08:00
Saim ShafiqueandSaim Shafique 77c8a1ba32 fix(javascript): mark resource-skipped files parse_status skipped (#3852)
* fix(javascript): mark resource-skipped files parse_status skipped

Files above ~5 MB exhaust the worker heap during static analysis (or exceed
the 8 MB static-checkpoint budget with a larger heap). They were inventoried
with text included but never parsed, leaving parse_status null and no
counter, so the summary read as 'nothing there' instead of 'not analyzed'.

Report them as skipped: parse_status 'skipped' on the file observation and a
skipped_javascript_files statistics counter. The analysis-failure reason keeps
surfacing as a limitation entry.

Fixes #3670

* fix(javascript): default skipped_javascript_files when reading old saved evidence

Saved analysis evidence written before skipped_javascript_files existed failed validation against the strict reconstruction statistics schema, so users could not reuse old results for comparisons or runtime reconciliation. Accept the field's absence on read (defaulting to 0) while continuing to write it for new results.

* fix(javascript): preserve unknown skipped count for legacy evidence

The .default(0) gate fix fabricated a measured zero for saved
pre-change evidence, so comparisons would receive a definitive
"no files skipped" fact the producer never established. Use the
codebase's exactOptional idiom instead: legacy reads accept absence
and keep omission (unknown), while the producer still always writes
the field (required by JavaScriptArtifactReconstructionResult).

Tests: legacy payload now asserts the key stays absent/undefined;
current-output test keeps asserting the producer-written counter.

* fix(javascript): add required failure scope and status to graph fixture

---------

Co-authored-by: Saim Shafique <sx4im@users.noreply.github.com>
2026-10-17 16:33:41 +09:00
85c1d2587a feat(javascript): analyze selected application sources
Analyze exact selected sources in first-request order before unrelated
static scopes, preserving full artifact identity and byte verification.
Retain selection in CLI/MCP Evidence and summaries, with explicit unknowns
and partial coverage for unselected sources. Keep default full analysis.

Fixes #1848

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-12 04:07:14 +08:00
Ysopwnable 4999e4be2c fix(javascript): isolate source analysis and retain bounded partial evidence (#1651)
* refactor(javascript): validate transferable file projections

* test(javascript): consume decoded module and static facts

* fix(javascript): isolate source analysis and retain bounded partial evidence

* test(javascript): verify cleanup retention and repeated source paths
2026-10-11 07:22:50 +08:00
曾亮andmorluto 4d8d76b253 fix(electron): support packaged app.asar pages and rewritten unpacked companions (#1400)
* fix(electron): resolve pages served from inside app.asar

Packaged Electron applications load their renderer pages from file URLs
inside app.asar. The host filesystem cannot stat those paths, so
list_electron_targets excluded every such page as unsupported_url and
inspect_electron_page could not select it.

Resolve these URLs the way Electron's file protocol does: the first .asar
path component that is a regular file is the archive, and the remainder
must name a regular file in its header. Directories, links, and missing
members stay rejected. Parsed headers are cached by archive identity.

* fix(artifacts): extract and explain rewritten ASAR unpacked companions

Packaging tools commonly sign or strip .asar.unpacked native binaries after
writing the archive header, so their declared hashes no longer match.
analyze_javascript_application already accepts integrity_policy, but
extract_artifact did not, and inspect_artifact and extract_artifact still
told callers to "get a fresh copy", which cannot help for a shipped app.

- Accept integrity_policy on extract_artifact (MCP and CLI). The default
  still fails closed; record-and-continue writes the observed bytes and
  returns the extracted files' integrity_contradictions records.
- Name the unpacked entry and the post-packaging cause in integrity
  failures, and point every operation that accepts integrity_policy at
  record-and-continue.
- Report an active unpacked entry whose companion file is absent as
  unavailable with its logical path instead of a format failure.
- Share one integrity_policy schema across the inventory, extraction, and
  JavaScript application inputs.

* fix(electron): bound ASAR headers before allocation

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-10 23:01:54 +08:00
morluto 449d57075e refactor(javascript)!: remove obsolete scope truncation statistic
BREAKING CHANGE: remove the never-incremented truncated_scopes statistic from JavaScript application analysis results and schemas.
2026-10-10 02:49:46 +08:00
morluto 7915ee5178 feat(javascript): record Electron integrity mismatches 2026-10-10 02:49:46 +08:00
morluto 70e8a8f860 fix(contracts): preserve local inputs and comparison uncertainty 2026-10-09 06:49:02 +08:00
Ysopwnable ec6db0f063 perf(javascript): reuse validated immutable graphs (#1112)
* perf(javascript): reuse validated immutable graphs

* fix(ci): follow the moved process CLI boundary test
2026-10-09 02:40:21 +08:00
ysopwnable f6a58f1062 refactor(javascript): group domain semantics and contracts (#907) 2026-10-07 17:16:58 +08:00