@kudzujs/core 0.16.11 → 0.16.12

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.
@@ -57,7 +57,7 @@ Syntax compatibility does not mean reproducing React wholesale. Kudzu accepts th
57
57
  - stronger JavaScript failure resilience;
58
58
  - no remaining material large-route build-scaling weakness.
59
59
 
60
- The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.20.1` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
60
+ The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.20.2` in the capability release plan below. Benchmark-only feature omission, unmatched accessibility, weighted scores that hide losses, and unrecorded environment differences do not count as proof.
61
61
 
62
62
  ### 0.10.0 Through 0.21.x: Application Capability Release Train
63
63
 
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, `npm run benchmark:module-cache`, `npm run benchmark:project-navigation`, `npm run benchmark:project-state`, and `npm run benchmark:source-scale` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
+ ## 0.16.12 Bounded Application Inspection
6
+
7
+ Measured 2026-08-31 on Linux x64 with Node 24.14.0. Inspection is a build-time
8
+ projection over existing source, compatibility, ownership, capability, and
9
+ artifact records. Representative deploy manifests and hashes remain unchanged.
10
+ The maintained Worker graph remains 907 raw / 477 gzip B and the window graph
11
+ remains 14,456 raw / 6,160 gzip B. Seven clean builds record a 648.7 ms median
12
+ on this host without a timing comparison. Browser-disabled and required-Chrome
13
+ suites pass 295/295 tests, package smoke passes, and bounded deterministic CLI
14
+ coverage includes reachable-source filtering and structured blocker output.
15
+
5
16
  ## 0.16.11 Structured Compiler Diagnostics
6
17
 
7
18
  Measured 2026-08-31 on Linux x64 with Node 24.14.0. Structured diagnostics are
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.16.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.16.11 - Structured compiler diagnostics.** `kudzu build --json` now emits stable codes, semantic stages, exact authored-source ranges, compatibility classes, and safe suggestions while ordinary human diagnostics and browser output remain unchanged. Read the [release notes](./RELEASES.md#01611---structured-compiler-diagnostics), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.11), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.16.12 - Bounded application inspection.** `kudzu inspect --json` now projects reachable modules, routes, packages, capabilities, semantic owners, and first blockers from the existing build graph in deterministic bounded output without dumping raw IR. Read the [release notes](./RELEASES.md#01612---bounded-application-inspection), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.12), or follow the [architecture packet](./docs/next-architecture/README.md).
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.16.12 - Bounded Application Inspection
4
+
5
+ Kudzu 0.16.12 lets tools select the first migration blocker from a compact
6
+ reachable-application inventory instead of reading the compiler's raw semantic
7
+ records or scanning an entire source tree.
8
+
9
+ ### Changed in 0.16.12
10
+
11
+ - Adds `kudzu inspect --json`, which runs the authoritative build graph and emits
12
+ one versioned report of reachable modules, routes, package compatibility,
13
+ capability families, semantic owners, and blocker candidates.
14
+ - Sorts every section before applying fixed limits and reports total and omitted
15
+ counts so large applications retain bounded deterministic context.
16
+ - Distinguishes confirmed structured diagnostic blockers from unsupported
17
+ compatibility sites and partial-package review candidates.
18
+ - Excludes unreachable source, generated module code, HTML, RouteIR, ModuleIR,
19
+ captures, state values, and complete artifact closures from inspection output.
20
+ - Reuses existing source, compatibility, ownership, capability, and artifact
21
+ records; it adds no analyzer, semantic primitive, runtime concept, public
22
+ application API, or browser byte.
23
+ - Updates `create-kudzu@0.1.137` to generate projects on
24
+ `@kudzujs/core@^0.16.12`.
25
+
26
+ `inspect` performs the ordinary authoritative build, including configured
27
+ `afterBuild()` behavior. A structured compiler failure returns a bounded blocked
28
+ report with incomplete inventory rather than pretending that later facts exist.
29
+
30
+ ### Upgrade
31
+
32
+ ```sh
33
+ npm install @kudzujs/core@^0.16.12
34
+ ```
35
+
3
36
  ## 0.16.11 - Structured Compiler Diagnostics
4
37
 
5
38
  Kudzu 0.16.11 gives tools and agents stable machine-readable authored-source
package/bin/kudzu.mjs CHANGED
@@ -6,19 +6,32 @@ import { fileURLToPath } from "node:url"
6
6
 
7
7
  const command = process.argv[2] ?? "dev"
8
8
 
9
- if (command === "build" && !process.env.KUDZU_BUILD_CHILD) {
9
+ if ((command === "build" || command === "inspect") && !process.env.KUDZU_BUILD_CHILD) {
10
10
  const child = spawnSync(process.execPath, ["--expose-gc", "--max-semi-space-size=8", fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
11
11
  stdio: "inherit",
12
12
  env: { ...process.env, KUDZU_BUILD_CHILD: "1" }
13
13
  })
14
14
  if (child.error) throw child.error
15
15
  process.exitCode = child.status ?? 1
16
- } else if (command === "build" || command === "dev") {
16
+ } else if (command === "build" || command === "dev" || command === "inspect") {
17
17
  module.enableCompileCache?.()
18
- const { build, dev } = await import("../framework/build.mjs")
18
+ const { build, dev, inspect } = await import("../framework/build.mjs")
19
19
  const json = command === "build" && process.argv.includes("--json")
20
20
  try {
21
- await (command === "build" ? build({ quiet: json }) : dev())
21
+ if (command === "inspect") {
22
+ if (!process.argv.includes("--json")) {
23
+ console.error("Use: kudzu inspect --json")
24
+ process.exitCode = 1
25
+ } else {
26
+ const log = console.log
27
+ console.log = console.error
28
+ try {
29
+ process.stdout.write(`${JSON.stringify(await inspect())}\n`)
30
+ } finally {
31
+ console.log = log
32
+ }
33
+ }
34
+ } else await (command === "build" ? build({ quiet: json }) : dev())
22
35
  } catch (error) {
23
36
  const { diagnosticEnvelope } = await import("../framework/compiler/diagnostics.mjs")
24
37
  const envelope = diagnosticEnvelope(error)
@@ -27,6 +40,6 @@ if (command === "build" && !process.env.KUDZU_BUILD_CHILD) {
27
40
  process.exitCode = 1
28
41
  }
29
42
  } else {
30
- console.error(`Unknown command: ${command}\nUse: kudzu <build|dev>`)
43
+ console.error(`Unknown command: ${command}\nUse: kudzu <build|dev|inspect>`)
31
44
  process.exitCode = 1
32
45
  }
@@ -15,7 +15,7 @@ The completed `0.9.0` milestone is recorded in [`0.9-semantic-compression.md`](.
15
15
 
16
16
  [`1.0-large-application-compatibility-audit.md`](./1.0-large-application-compatibility-audit.md) records the first post-0.9 probes against Memos, Apache Answer, and Actual Budget. The audit finds that reduced slices build but whole-application source retention and behavior parity do not yet pass.
17
17
 
18
- [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.20.1`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
18
+ [`application-capability-release-plan.md`](./application-capability-release-plan.md) is the authoritative post-0.9 execution queue, currently at `0.20.2`. It assigns one application-capability section to each minor release and one independently accepted evidence packet to each patch release from `0.10.0` through the `1.0.0` gate. It supersedes the provisional 0.10/0.11/0.12 tool-first ordering in the completed 0.9 handoff without rewriting that historical record.
19
19
 
20
20
  ## Required Invariants
21
21
 
@@ -1826,6 +1826,27 @@ artifact accounting, and `git diff --check` pass. The packet ships as
1826
1826
  - **Done condition:** the first blocker can be selected without reading hundreds
1827
1827
  of source or dependency files.
1828
1828
 
1829
+ Complete. `kudzu inspect --json` now runs the authoritative build and projects
1830
+ the existing reachable source, compatibility, ComponentAnalysis, ModuleIR,
1831
+ CapabilityIR, and route-artifact records into one versioned report. Modules,
1832
+ routes, packages, compatibility sites, capability families, semantic owners,
1833
+ and blockers sort before fixed section limits; totals and omitted counts keep
1834
+ large-project context explicit. Unreachable source, generated code, HTML, raw
1835
+ IR, captures, state values, and full artifact closures remain excluded.
1836
+
1837
+ One real CLI fixture proves reachable filtering, static and interactive route
1838
+ facts, native package classification, state ownership, project-relative paths,
1839
+ deterministic output, and structured blocked inventory. A synthetic 101-module
1840
+ and 51-blocker check proves sort-before-truncation and first-blocker retention.
1841
+ The implementation is one 154-line projection over existing records plus the
1842
+ existing build/CLI seam; it adds no analyzer, semantic primitive, compiler pass,
1843
+ normalization entry, runtime concept, or browser module. Representative deploy
1844
+ manifests and hashes remain unchanged. Worker and window graphs remain 907 raw /
1845
+ 477 gzip and 14,456 raw / 6,160 gzip bytes; seven clean builds record a 648.7 ms
1846
+ median without a timing comparison. Browser-disabled and required-Chrome suites
1847
+ pass 295/295 tests, package smoke and artifact accounting pass, and the packet
1848
+ ships as `@kudzujs/core@0.16.12` with `create-kudzu@0.1.137`.
1849
+
1829
1850
  ### `0.20.2`: `kudzu explain --route`
1830
1851
 
1831
1852
  - **Purpose:** trace one authored route to its selected browser artifacts.
@@ -2051,3 +2072,4 @@ release transaction where possible or document and publish a forward-fix patch.
2051
2072
  | `0.19.4` | Closed by existing effect ownership | Preserve reaction mutation, version deduplication, stale-socket rejection, reconnect, keyed identity, exact route cleanup, fresh ownership, bounded handles/listeners/timers, and public zero-JavaScript output. | No production change; no release consumed |
2052
2073
  | `0.19.5` | Deferred by stop condition | Resume only from a complete pinned Actual Budget workspace with an executable dependency graph and honest retention denominator. | Sparse acquisition omits required workspace and core packages; no compatibility claim |
2053
2074
  | `0.20.0` | Released as `0.16.11` | Preserve stable diagnostic schema/codes, authored ranges, human errors, compatibility reuse, and zero browser/output delta. | Patch release retained the `0.16.x` public version line |
2075
+ | `0.20.1` | Released as `0.16.12` | Preserve bounded deterministic reachable inventory, first-blocker retention, existing-record reuse, and zero browser/output delta. | Patch release retained the `0.16.x` public version line |
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.16.11` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.20.1`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
3
+ This maps the current `0.16.12` architecture, built on the completed `0.9.0` semantic-compression release and `0.8.23` Goal A compiler foundation. The active application packet is `0.20.2`; file and function names are the stable references, while line numbers are intentionally omitted because later work may still move code.
4
4
 
5
5
  ## Responsibility Map
6
6
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.20.1`. This document does not mark any remaining planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
5
+ Completed compiler-foundation record and longer-term plan after `0.8.35`. The [`0.9-semantic-compression.md`](./0.9-semantic-compression.md) execution queue is complete. The active queue for current work is [`application-capability-release-plan.md`](./application-capability-release-plan.md) at packet `0.20.2`. This document does not mark any remaining planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
6
6
 
7
- [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.20.1`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
7
+ [`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains the product authority for product invariants and fixture-driven feature selection. [`application-capability-release-plan.md`](./application-capability-release-plan.md) is authoritative for current work order and evidence at packet `0.20.2`; this plan retains the completed foundation, deferred program, and long-term production gates. If implementation evidence changes either boundary, update the relevant document before broadening a patch.
8
8
 
9
9
  ## Product Outcome
10
10
 
@@ -483,4 +483,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
483
483
 
484
484
  ## Immediate Decision
485
485
 
486
- All listed foundation and `0.9` slices are complete. Current work continues at `0.20.1` `kudzu inspect --json` under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
486
+ All listed foundation and `0.9` slices are complete. Current work continues at `0.20.2` `kudzu explain --route` under the [`application-capability-release-plan.md`](./application-capability-release-plan.md); keep ResourceIR limited to qualifying independent fixtures, and do not add range ownership, virtualization, optimistic transactions, a public adapter/store API, or a router before evidence justifies them.
@@ -1,6 +1,6 @@
1
1
  # Planned Version Sequence
2
2
 
3
- This is an execution sequence, not release history. `0.8.16` through `0.8.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.20.1`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.62` are completed scopes represented by package/release records. The `0.10.0` through `1.0.0` minor/patch sequence is maintained in [`application-capability-release-plan.md`](./application-capability-release-plan.md), currently at `0.20.2`; that plan supersedes the provisional tool-first 0.10/0.11/0.12 ordering in the completed 0.9 handoff.
4
4
 
5
5
  Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
6
6
 
@@ -66,7 +66,7 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
66
66
 
67
67
  ## Generator Versions
68
68
 
69
- `create-kudzu@0.1.136` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.11`.
69
+ `create-kudzu@0.1.137` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.12`.
70
70
 
71
71
  ## Release Boundary
72
72
 
@@ -5,7 +5,8 @@ import { pathToFileURL } from "node:url"
5
5
  import { build as bundle, transform } from "esbuild"
6
6
  import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
7
7
  import { createCompatibilityReport } from "./compiler/compatibility-registry.mjs"
8
- import { normalizeDiagnosticError } from "./compiler/diagnostics.mjs"
8
+ import { diagnosticEnvelope, normalizeDiagnosticError } from "./compiler/diagnostics.mjs"
9
+ import { createInspectionReport } from "./compiler/inspection-report.mjs"
9
10
  import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
10
11
  import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
11
12
  import { createProjectSession } from "./compiler/project-session.mjs"
@@ -38,7 +39,12 @@ export async function build({ quiet = false, minify = true, root: projectRoot =
38
39
  return buildWithSession(project, { quiet, minify, retainCache: false })
39
40
  }
40
41
 
41
- export async function buildWithSession(project, { changedFiles, quiet = false, minify = true, retainCache = true } = {}) {
42
+ export async function inspect({ minify = true, root: projectRoot = process.cwd() } = {}) {
43
+ const project = createProjectSession(projectRoot)
44
+ return buildWithSession(project, { quiet: true, minify, retainCache: false, inspection: true })
45
+ }
46
+
47
+ export async function buildWithSession(project, { changedFiles, quiet = false, minify = true, retainCache = true, inspection = false } = {}) {
42
48
  const { root, outputDirectory } = project
43
49
  const stagedOutput = join(root, ".kudzu-dist-staging")
44
50
  const backupOutput = join(root, ".kudzu-dist-backup")
@@ -47,13 +53,16 @@ export async function buildWithSession(project, { changedFiles, quiet = false, m
47
53
  try {
48
54
  await recoverOutput(outputDirectory, backupOutput)
49
55
  await rm(stagedOutput, { recursive: true, force: true })
50
- const { result, pageCount, behaviorCount, cache } = await buildInto(project, stagedOutput, { changedFiles, minify, quiet, retainCache })
56
+ const { result, pageCount, behaviorCount, cache, inspectionData } = await buildInto(project, stagedOutput, { changedFiles, minify, quiet, retainCache })
51
57
  await promoteOutput(stagedOutput, outputDirectory, backupOutput)
52
58
  project.buildCache = retainCache ? cache : undefined
53
59
  if (!quiet) console.log(`Built ${pageCount} page(s), ${behaviorCount} interactive page(s) into dist/`)
54
- return result
60
+ return inspection ? createInspectionReport(inspectionData) : result
55
61
  } catch (error) {
56
- throw normalizeDiagnosticError(error, root)
62
+ const normalized = normalizeDiagnosticError(error, root)
63
+ const envelope = inspection && diagnosticEnvelope(normalized)
64
+ if (envelope) return createInspectionReport({ diagnostics: envelope.diagnostics })
65
+ throw normalized
57
66
  } finally {
58
67
  try {
59
68
  await rm(stagedOutput, { recursive: true, force: true })
@@ -107,7 +116,8 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
107
116
  if (!pageFiles.length) throw new Error("No pages found in src/pages/")
108
117
  const pageSources = new Map(pageFiles.map(file => [file, new Set(reachableSourceFiles([file], allSourceFileSet, sourceIndex))]))
109
118
  const sourceFiles = [...new Set([...pageSources.values()].flatMap(files => [...files]))].sort()
110
- await writePrettyJson(join(workDirectory, "kudzu-compatibility.json"), createCompatibilityReport(sourceFiles.map(file => ({ file: relative(root, file).replaceAll(sep, "/"), source: sourceIndex.get(file) }))))
119
+ const compatibility = createCompatibilityReport(sourceFiles.map(file => ({ file: relative(root, file).replaceAll(sep, "/"), source: sourceIndex.get(file) })))
120
+ await writePrettyJson(join(workDirectory, "kudzu-compatibility.json"), compatibility)
111
121
  const sourceFileSet = project.sourceFiles
112
122
  sourceFileSet.clear()
113
123
  for (const file of sourceFiles) sourceFileSet.add(file)
@@ -527,6 +537,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
527
537
  const incremental = { compiledModules, renderedPages }
528
538
  return {
529
539
  result: { sourceResults, incremental },
540
+ inspectionData: { sourceFiles: sourceFiles.map(file => relative(root, file).replaceAll(sep, "/")), sourceResults, compatibility, artifacts },
530
541
  pageCount: routeRecords.length,
531
542
  behaviorCount,
532
543
  cache: { pageRenders, pageSources, placeholders, sourceResults: sourceResultsByFile }
@@ -0,0 +1,156 @@
1
+ const limits = Object.freeze({ modules: 100, routes: 100, packages: 100, compatibilitySites: 100, capabilities: 50, owners: 100, blockers: 50 })
2
+
3
+ export function createInspectionReport({ sourceFiles = [], sourceResults = [], compatibility, artifacts, diagnostics = [] } = {}) {
4
+ const complete = diagnostics.length === 0
5
+ const modules = complete ? moduleFacts(sourceFiles, sourceResults) : []
6
+ const routes = complete ? routeFacts(artifacts?.routes ?? []) : []
7
+ const packages = complete ? [...(compatibility?.packages ?? [])] : []
8
+ const compatibilitySites = complete ? [...(compatibility?.sites ?? [])] : []
9
+ const capabilities = complete ? capabilityFacts(artifacts?.runtimeFamilies ?? []) : []
10
+ const owners = complete ? ownerFacts(sourceResults) : []
11
+ const blockers = complete ? compatibilityBlockers(compatibilitySites) : diagnosticBlockers(diagnostics)
12
+ const sections = { modules, routes, packages, compatibilitySites, capabilities, owners, blockers }
13
+
14
+ return {
15
+ version: 1,
16
+ status: blockers.some(blocker => blocker.severity === "error") ? "blocked" : "ready",
17
+ inventoryComplete: complete,
18
+ limits,
19
+ summary: Object.fromEntries(Object.entries(sections).map(([name, entries]) => [name, entries.length])),
20
+ ...Object.fromEntries(Object.entries(sections).map(([name, entries]) => [name, entries.slice(0, limits[name])])),
21
+ omitted: Object.fromEntries(Object.entries(sections).map(([name, entries]) => [name, Math.max(0, entries.length - limits[name])])),
22
+ }
23
+ }
24
+
25
+ function moduleFacts(sourceFiles, sourceResults) {
26
+ const results = new Map(sourceResults.map(result => [result.file, result]))
27
+ return [...sourceFiles].sort(compareText).map(file => {
28
+ const result = results.get(file)
29
+ const moduleIR = result?.moduleIR
30
+ return compact({
31
+ file,
32
+ kind: file.endsWith(".worker.ts") ? "worker" : file.startsWith("src/pages/") && file.endsWith(".tsx") ? "page" : "module",
33
+ owners: result?.componentAnalysis.owners.length ?? 0,
34
+ specializations: result?.componentAnalysis.specializations.length ?? 0,
35
+ signals: moduleIR?.signals.length ?? 0,
36
+ sharedStates: moduleIR?.sharedStates.length ?? 0,
37
+ sharedActions: moduleIR?.sharedActions.length ?? 0,
38
+ handlers: moduleIR?.handlers.length ?? 0,
39
+ bindings: moduleIR?.bindings.length ?? 0,
40
+ derived: moduleIR?.derived.length ?? 0,
41
+ effects: moduleIR?.effects.length ?? 0,
42
+ keyedBlocks: moduleIR?.keyedBlocks.length ?? 0,
43
+ imports: moduleIR?.imports.length ?? 0,
44
+ })
45
+ })
46
+ }
47
+
48
+ function routeFacts(routes) {
49
+ return [...routes].sort((left, right) => compareText(left.route, right.route)).map(route => ({
50
+ route: route.route,
51
+ runtimeFamily: route.runtime.family,
52
+ static: route.runtime.family === null && route.runtime.entries.length === 0 && route.handlers.entries.length === 0 && route.handlers.chunks.length === 0 && route.handlers.lazyChunks.length === 0 && route.workers.length === 0,
53
+ capabilities: enabledFacts(route.capability.manifest),
54
+ artifacts: {
55
+ runtimeEntries: route.runtime.entries.length,
56
+ runtimeRequirements: route.runtime.requirements.length,
57
+ handlerEntries: route.handlers.entries.length,
58
+ chunks: route.handlers.chunks.length,
59
+ lazyChunks: route.handlers.lazyChunks.length,
60
+ workers: route.workers.length,
61
+ styles: route.styles.length,
62
+ },
63
+ }))
64
+ }
65
+
66
+ function capabilityFacts(families) {
67
+ return [...families].sort((left, right) => compareText(left.id, right.id)).map(family => ({
68
+ id: family.id,
69
+ navigation: family.navigation,
70
+ routes: family.routes.length,
71
+ facts: enabledFacts(family.manifest),
72
+ requirements: family.requirements.length,
73
+ }))
74
+ }
75
+
76
+ function ownerFacts(sourceResults) {
77
+ const owners = []
78
+ for (const result of sourceResults) {
79
+ const effects = result.moduleIR.effects
80
+ const blocks = result.moduleIR.keyedBlocks
81
+ for (const owner of result.componentAnalysis.owners) owners.push(ownerFact(result.file, "component", owner, effects, blocks))
82
+ for (const owner of result.componentAnalysis.specializations) owners.push(ownerFact(result.file, "specialization", owner, effects, blocks))
83
+ }
84
+ return owners.sort((left, right) => compareText(left.module, right.module) || compareText(left.kind, right.kind) || left.slot - right.slot)
85
+ }
86
+
87
+ function ownerFact(module, kind, owner, effects, blocks) {
88
+ return {
89
+ module,
90
+ kind,
91
+ slot: owner.slot,
92
+ name: typeof owner.name === "string" ? owner.name : typeof owner.component === "string" ? owner.component : owner.component?.name ?? "anonymous",
93
+ ...compact({
94
+ stateCount: owner.states?.length ?? 0,
95
+ refCount: owner.refs?.length ?? 0,
96
+ idCount: owner.ids?.length ?? 0,
97
+ effectCount: effects.filter(effect => effect.ownership?.owner?.kind === kind && effect.ownership.owner.slot === owner.slot).length,
98
+ keyedBlockCount: kind === "specialization" ? blocks.filter(block => block.specializations?.includes(owner.slot)).length : 0,
99
+ }),
100
+ }
101
+ }
102
+
103
+ function compatibilityBlockers(sites) {
104
+ return sites.filter(site => site.classification === "Unsupported" || site.classification === "Partial").map(site => ({
105
+ kind: "compatibility",
106
+ severity: site.classification === "Unsupported" ? "error" : "review",
107
+ code: site.rule,
108
+ file: site.file,
109
+ location: site.location,
110
+ package: site.package,
111
+ imported: site.imported,
112
+ classification: site.classification,
113
+ })).sort(compareBlockers)
114
+ }
115
+
116
+ function diagnosticBlockers(diagnostics) {
117
+ return diagnostics.map(diagnostic => ({
118
+ kind: "diagnostic",
119
+ severity: diagnostic.severity,
120
+ code: diagnostic.code,
121
+ stage: diagnostic.stage,
122
+ message: diagnostic.message,
123
+ ...(diagnostic.source ? { source: diagnostic.source } : {}),
124
+ compatibilityClass: diagnostic.compatibilityClass,
125
+ suggestion: diagnostic.suggestion,
126
+ })).sort(compareBlockers)
127
+ }
128
+
129
+ function enabledFacts(value, prefix = "") {
130
+ const facts = []
131
+ for (const [key, entry] of Object.entries(value ?? {}).sort(([left], [right]) => compareText(left, right))) {
132
+ if (key === "version") continue
133
+ const path = prefix ? `${prefix}.${key}` : key
134
+ if (entry === true) facts.push(path)
135
+ else if (typeof entry === "number" && entry > 0) facts.push(`${path}=${entry}`)
136
+ else if (Array.isArray(entry)) for (const item of entry) facts.push(`${path}=${item}`)
137
+ else if (entry && typeof entry === "object") facts.push(...enabledFacts(entry, path))
138
+ }
139
+ return facts
140
+ }
141
+
142
+ function compact(record) {
143
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== 0))
144
+ }
145
+
146
+ function compareBlockers(left, right) {
147
+ const leftFile = left.file ?? left.source?.file ?? ""
148
+ const rightFile = right.file ?? right.source?.file ?? ""
149
+ const leftLocation = left.location ?? left.source?.start ?? {}
150
+ const rightLocation = right.location ?? right.source?.start ?? {}
151
+ return compareText(leftFile, rightFile) || (leftLocation.line ?? 0) - (rightLocation.line ?? 0) || (leftLocation.column ?? 0) - (rightLocation.column ?? 0) || compareText(left.code, right.code)
152
+ }
153
+
154
+ function compareText(left, right) {
155
+ return left < right ? -1 : left > right ? 1 : 0
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.11",
3
+ "version": "0.16.12",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",