@kudzujs/core 0.16.10 → 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.
@@ -24,6 +24,12 @@ Apache Answer's first route-shell gate replaces `RouterProvider` and nested `Out
24
24
 
25
25
  Apache Answer authoring/admin intake now builds native question creation and keyed administration without form, editor, query, or state package runtimes. Constraint-backed title/markdown fields, controlled preview, markdown file import, local draft mutation, keyed admin rows, and deletion compile through existing handlers/lists while a legal sibling remains JavaScript-free. Rich CodeMirror ownership, server validation, upload progress, and destructive confirmation remain later acceptance gates.
26
26
 
27
+ Apache Answer's reduced authentication, query, route, detail, create/edit, validation, refresh, administration, and logout paths now pass one connected deterministic browser journey. Existing layout-owned Zustand reduction, search signals, dependency effects, runtime parameters, native forms, keyed lists, enhanced navigation, and direct handlers preserve server-authoritative errors and mutations, edit persistence across reload, admin deletion refresh, accessible statuses, exact source retention, and a zero-JavaScript public sibling. No compiler, IR, runtime, package adapter, or public API changed; this remains a connected reduction rather than a whole-application migration claim.
28
+
29
+ Memos now has a first durable connected slice derived from pinned upstream source: invalid and valid login, loading and server error, overlapping-key pagination, explicit refresh, native memo creation, editing, deletion, reaction, realtime refresh, reload persistence, and logout pass one deterministic browser journey. Existing layout state, dependency effects, keyed ownership, native forms and handlers, enhanced navigation, and effect-owned WebSocket lifecycle retain one memo DOM node through error recovery, feed mutations, duplicate and stale events, reconnect, and fresh route ownership. Listener, socket, and reconnect-timer counts return to zero on release, and the public sibling remains JavaScript-free. No query runtime, Connect RPC client, React runtime, compiler semantic, or public API is added, and this remains a reduced application slice rather than a whole-Memos claim.
30
+
31
+ Actual Budget intake remains deferred at its pinned MIT revision. The available sparse acquisition omits required workspace tooling, component and backend packages, database and worker coordination, and spreadsheet implementation, so it cannot establish an executable user journey or honest whole-workspace retention denominator. No compatibility claim or speculative framework work is authorized until a complete pinned workspace is available.
32
+
27
33
  ## North Star
28
34
 
29
35
  An AI should be able to migrate ordinary React-shaped TypeScript and TSX to CDN-deployable static HTML, CSS, and only the route-specific ESM capabilities actually used, with minimal source restructuring.
@@ -51,7 +57,7 @@ Syntax compatibility does not mean reproducing React wholesale. Kudzu accepts th
51
57
  - stronger JavaScript failure resilience;
52
58
  - no remaining material large-route build-scaling weakness.
53
59
 
54
- The completed 0.9 plan defines the cross-framework comparison and release gate. The active application packet is `0.19.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.
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.
55
61
 
56
62
  ### 0.10.0 Through 0.21.x: Application Capability Release Train
57
63
 
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,29 @@
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
+
16
+ ## 0.16.11 Structured Compiler Diagnostics
17
+
18
+ Measured 2026-08-31 on Linux x64 with Node 24.14.0. Structured diagnostics are
19
+ compiler and CLI output only; all representative deploy manifests, route bytes,
20
+ and hashes remain unchanged. The maintained Worker graph remains 907 raw / 477
21
+ gzip B and the window graph remains 14,456 raw / 6,160 gzip B. Seven clean
22
+ release-candidate builds record a 1,034.4 ms median on this loaded host; timing is
23
+ not compared.
24
+ The complete browser-disabled and required-Chrome suites pass 293/293 tests,
25
+ focused JSON CLI and human diagnostic tests pass, and package smoke builds one
26
+ static page with zero interactive pages.
27
+
5
28
  ## 0.16.10 Apache Answer Authentication Journey
6
29
 
7
30
  Measured 2026-08-31 on Linux x64 with Node 24.14.0 and required Chrome. The
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.10 - Apache Answer authentication journey.** The existing layout-owned session reduction now passes anonymous, invalid and valid login, reload restoration, shared settings, and 401 replacement navigation against a deterministic server without compiler or browser-output changes. Read the [release notes](./RELEASES.md#01610---apache-answer-authentication-journey), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.16.10), 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,66 @@
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
+
36
+ ## 0.16.11 - Structured Compiler Diagnostics
37
+
38
+ Kudzu 0.16.11 gives tools and agents stable machine-readable authored-source
39
+ errors without replacing concise human diagnostics or exposing compiler internals.
40
+
41
+ ### Changed in 0.16.11
42
+
43
+ - Adds `kudzu build --json`, which emits one versioned diagnostic envelope with
44
+ stable semantic code, stage, severity, project-relative source range, message,
45
+ nullable compatibility class, and nullable safe suggestion.
46
+ - Preserves readable source-located build and development errors; agents no
47
+ longer need to parse maintained error-message substrings.
48
+ - Covers unresolved imports and re-exports, unsupported dynamic imports,
49
+ TypeScript syntax errors, runtime-locale `react-i18next`, and invalid keyed-row
50
+ ref initializers through the shared diagnostic transport.
51
+ - Reuses the existing compatibility registry and keeps pass filenames, generated
52
+ source, IR slots, runtime records, and stack traces out of machine output.
53
+ - Adds no semantic primitive, compiler pass, runtime concept, browser module, or
54
+ deploy byte. Representative artifact manifests and hashes remain unchanged.
55
+ - Updates `create-kudzu@0.1.136` to generate projects on
56
+ `@kudzujs/core@^0.16.11`.
57
+
58
+ ### Upgrade
59
+
60
+ ```sh
61
+ npm install @kudzujs/core@^0.16.11
62
+ ```
63
+
3
64
  ## 0.16.10 - Apache Answer Authentication Journey
4
65
 
5
66
  Kudzu 0.16.10 promotes the existing Apache Answer authentication reduction from
package/bin/kudzu.mjs CHANGED
@@ -6,18 +6,40 @@ 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")
19
- await (command === "build" ? build : dev)()
18
+ const { build, dev, inspect } = await import("../framework/build.mjs")
19
+ const json = command === "build" && process.argv.includes("--json")
20
+ try {
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())
35
+ } catch (error) {
36
+ const { diagnosticEnvelope } = await import("../framework/compiler/diagnostics.mjs")
37
+ const envelope = diagnosticEnvelope(error)
38
+ if (!envelope) throw error
39
+ console.error(json ? JSON.stringify(envelope) : error.message)
40
+ process.exitCode = 1
41
+ }
20
42
  } else {
21
- console.error(`Unknown command: ${command}\nUse: kudzu <build|dev>`)
43
+ console.error(`Unknown command: ${command}\nUse: kudzu <build|dev|inspect>`)
22
44
  process.exitCode = 1
23
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.19.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.
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
 
@@ -1679,6 +1679,27 @@ retain the public `0.16.x` line.
1679
1679
  introduced.
1680
1680
  - **Done condition:** this is a connected browser journey, not a whole-app claim.
1681
1681
 
1682
+ Complete. One Apache Answer-derived fixture now connects invalid and valid login,
1683
+ layout-shared authentication, page/order query loading, explicit refresh, native
1684
+ creation with server validation and retained draft, runtime detail routing,
1685
+ editing, reload persistence, keyed administration, delete-and-refetch, and
1686
+ logout against one deterministic server. Native controls and status/alert
1687
+ regions preserve the accessibility boundary, every retained source file is
1688
+ checked, and the public sibling remains complete HTML with zero JavaScript.
1689
+
1690
+ The packet reuses existing Zustand normalization, search signals, dependency
1691
+ effects, runtime parameters, native handlers, bindings, keyed ownership, and
1692
+ enhanced navigation. It adds zero semantic primitives, ModuleIR kinds, compiler
1693
+ passes or core LOC, runtime concepts, package adapters, and public APIs. The new
1694
+ fixture emits 33 deploy files and 88,198 raw / 33,173 gzip bytes; `/` owns 27,196
1695
+ raw / 11,968 gzip bytes, `/questions` owns 51,577 raw / 19,801 gzip bytes,
1696
+ `/questions/add` owns 27,109 raw / 11,893 gzip bytes, runtime detail owns 32,783
1697
+ raw / 14,056 gzip bytes, admin owns 50,861 raw / 19,426 gzip bytes, and `/public`
1698
+ owns zero JavaScript. Worker and window graphs remain 907 raw / 477 gzip and
1699
+ 14,456 raw / 6,160 gzip bytes; seven clean Linux x64 builds record a 640.5 ms
1700
+ median without a timing comparison. No production logic changed, so the public
1701
+ version remains `0.16.10` and no release is consumed.
1702
+
1682
1703
  ### `0.19.3`: Memos Feed And CRUD Journey
1683
1704
 
1684
1705
  - **Purpose:** establish the first durable Memos application slice.
@@ -1689,6 +1710,26 @@ retain the public `0.16.x` line.
1689
1710
  - **Done condition:** pinned upstream provenance and exact retained source are
1690
1711
  recorded.
1691
1712
 
1713
+ Complete. A fixture pinned to Memos revision
1714
+ `e5ed6e7ec60e141d6e354f3ab59906c2c47dccac` now runs invalid and valid login,
1715
+ initial loading, a server pagination failure, explicit recovery, overlapping-key
1716
+ pagination, create, edit, delete, reload persistence, and logout against one
1717
+ deterministic server. The retained memo row keeps DOM identity through the
1718
+ failed request, successful pagination, and every CRUD refetch. Native labels,
1719
+ status and alert regions preserve the accessibility boundary, every authored
1720
+ source file is retained, and the public sibling emits zero JavaScript.
1721
+
1722
+ The packet reuses existing Zustand normalization, dependency effects, keyed
1723
+ ownership, native forms and handlers, and enhanced navigation. It adds zero
1724
+ semantic primitives, ModuleIR kinds, compiler passes or core LOC, runtime
1725
+ concepts, package adapters, and public APIs. The fixture emits 19 deploy files
1726
+ and 56,167 raw / 21,634 gzip bytes; `/` owns 25,856 raw / 11,396 gzip bytes,
1727
+ `/feed` owns 45,690 raw / 17,836 gzip bytes, and `/public` owns zero JavaScript.
1728
+ Worker and window graphs remain 907 raw / 477 gzip and 14,456 raw / 6,160 gzip
1729
+ bytes; seven clean Linux x64 builds record a 761.8 ms median without a timing
1730
+ comparison. No production logic changed, so the public version remains
1731
+ `0.16.10` and no release is consumed.
1732
+
1692
1733
  ### `0.19.4`: Memos Realtime Journey
1693
1734
 
1694
1735
  - **Purpose:** add reaction and realtime refresh using the accepted long-lived
@@ -1698,6 +1739,27 @@ retain the public `0.16.x` line.
1698
1739
  - **Stop condition:** a query or SSE package runtime is copied wholesale.
1699
1740
  - **Done condition:** browser and endurance acceptance pass.
1700
1741
 
1742
+ Complete. The connected Memos fixture now adds a native reaction mutation and
1743
+ an effect-owned WebSocket that refreshes the keyed feed from versioned server
1744
+ events. Required Chrome proves duplicate and stale version rejection, old-socket
1745
+ callback rejection after reconnect, retained memo DOM identity, exact route
1746
+ cleanup, and fresh ownership after re-entry and reload. Active sockets,
1747
+ listeners, and reconnect timers remain bounded and return to zero after each
1748
+ owner release. The public sibling remains complete HTML with zero JavaScript.
1749
+
1750
+ The packet reuses existing native handlers, keyed ownership, effect-private
1751
+ lifecycle, invalidation, timers, and route release. It adds zero semantic
1752
+ primitives, ModuleIR kinds, compiler passes or core LOC, runtime concepts,
1753
+ normalization or adapter rules, and public APIs. The fixture emits 19 deploy
1754
+ files and 57,811 raw / 22,069 gzip bytes; `/` owns 25,856 raw / 11,396 gzip
1755
+ bytes, `/feed` owns 46,740 raw / 18,184 gzip bytes, and `/public` owns zero
1756
+ JavaScript. Worker and window graphs remain 907 raw / 477 gzip and 14,456 raw /
1757
+ 6,160 gzip bytes; seven clean Linux x64 builds record a 620.7 ms median without
1758
+ a timing comparison. Browser-disabled tests pass 292/292, required-Chrome
1759
+ focused acceptance, package smoke, and artifact accounting pass. No production
1760
+ logic changed, so the public version remains `0.16.10` and no release is
1761
+ consumed.
1762
+
1701
1763
  ### `0.19.5`: Actual Budget Intake Decision
1702
1764
 
1703
1765
  - **Purpose:** determine whether a complete, legally and technically usable
@@ -1708,6 +1770,18 @@ retain the public `0.16.x` line.
1708
1770
  - **Done condition:** a later packet is authorized, or deferral is recorded with
1709
1771
  no compatibility claim.
1710
1772
 
1773
+ Deferred by the stop condition. The pinned MIT revision
1774
+ `87e33e49eb4d666c33e0c0f172faf340d1b7aa0a` remains only a sparse desktop-client
1775
+ acquisition: 810 TS/TSX modules, 163,344 LOC, and 80 route declarations without
1776
+ the workspace tooling, component library, core backend, Yarn release, database,
1777
+ worker coordinator, or spreadsheet implementation needed to install and run an
1778
+ honest user journey. The prior static report probe is an exported report, not
1779
+ Actual Budget, and its 22/906 retained-line result is not a whole-workspace
1780
+ denominator. No later application packet, compatibility claim, compiler work,
1781
+ runtime, package adapter, public API, version, or release is authorized. Intake
1782
+ may resume only from a complete pinned workspace and executable dependency
1783
+ graph.
1784
+
1711
1785
  ## `0.20.x`: AI Tooling And Delivery Cost
1712
1786
 
1713
1787
  ### `0.20.0`: Structured Diagnostics
@@ -1719,6 +1793,30 @@ retain the public `0.16.x` line.
1719
1793
  - **Done condition:** agents no longer parse message substrings for maintained
1720
1794
  fixtures.
1721
1795
 
1796
+ Complete. Authored
1797
+ source failures now propagate as one versioned diagnostic envelope with stable
1798
+ semantic code, stage, severity, project-relative source range, human message,
1799
+ nullable compatibility class, and nullable safe suggestion. `kudzu build
1800
+ --json` emits only that deterministic envelope for structured failures, while
1801
+ ordinary builds and the development overlay retain readable source-located
1802
+ messages. Unresolved imports and re-exports, unsupported dynamic imports,
1803
+ TypeScript syntax errors, runtime-locale `react-i18next`, and keyed-row ref
1804
+ initializers prove the maintained boundary without parsing message text.
1805
+
1806
+ The implementation centralizes 72 lines in `diagnostics.mjs`, reuses the
1807
+ existing compatibility classification function, and normalizes paths only at
1808
+ the project build boundary. It adds zero semantic primitives, ModuleIR kinds,
1809
+ compiler passes, normalization entries, runtime concepts, browser modules, or
1810
+ public application APIs. Production source is +111 net lines, including +8 in
1811
+ the measured core semantic file set; the increase is compiler/tooling error
1812
+ transport rather than semantic analysis. All representative deploy manifests
1813
+ and hashes remain unchanged. Worker and window graphs remain 907 raw / 477 gzip
1814
+ and 14,456 raw / 6,160 gzip bytes; seven clean Linux x64 release-candidate builds
1815
+ record a 1,034.4 ms median without a timing comparison. `npm run check`, 293/293
1816
+ browser-disabled and required-Chrome tests, focused CLI JSON tests, development error recovery, package smoke,
1817
+ artifact accounting, and `git diff --check` pass. The packet ships as
1818
+ `@kudzujs/core@0.16.11` with `create-kudzu@0.1.136`.
1819
+
1722
1820
  ### `0.20.1`: `kudzu inspect --json`
1723
1821
 
1724
1822
  - **Purpose:** expose reachable application and compatibility inventory.
@@ -1728,6 +1826,27 @@ retain the public `0.16.x` line.
1728
1826
  - **Done condition:** the first blocker can be selected without reading hundreds
1729
1827
  of source or dependency files.
1730
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
+
1731
1850
  ### `0.20.2`: `kudzu explain --route`
1732
1851
 
1733
1852
  - **Purpose:** trace one authored route to its selected browser artifacts.
@@ -1948,3 +2067,9 @@ release transaction where possible or document and publish a forward-fix patch.
1948
2067
  | `0.18.3` | Released as `0.16.8` | Preserve shared deferred ownership, interaction-only loading, native document module-map deduplication, exact owner cleanup, binding-aware import validation, and static exclusion. | Patch release retained the `0.16.x` public version line |
1949
2068
  | `0.19.0` | Released as `0.16.9` | Preserve the compatibility registry, reachable source inventory, exact ranges, and package-neutral output boundary. | Patch release retained the `0.16.x` public version line |
1950
2069
  | `0.19.1` | Released as `0.16.10` | Preserve anonymous, invalid/valid login, token restore, layout-shared session reads, 401 clear, replacement navigation, and public zero-JavaScript output. | Patch release retained the `0.16.x` public version line |
2070
+ | `0.19.2` | Closed by existing semantics | Preserve connected auth, query, route, create/edit validation, refresh, persistence, admin deletion, logout, source retention, accessibility, and public zero-JavaScript output. | No production change; no release consumed |
2071
+ | `0.19.3` | Closed by existing semantics | Preserve connected Memos login, loading/error, feed, overlapping-key pagination, refresh, CRUD, reload persistence, logout, source retention, accessibility, and public zero-JavaScript output. | No production change; no release consumed |
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 |
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 |
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.10` 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.19.2`; 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
 
@@ -12,6 +12,7 @@ This maps the current `0.16.10` architecture, built on the completed `0.9.0` sem
12
12
  | Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()`, `buildWithSession()` | Coordinates config, discovery, source compilation, RouteBuildRecord collection, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. A retained session caches source results and pre-family route renders by page graph; successful builds alone replace that cache. |
13
13
  | Reachability/import resolution | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `reachableSourceFiles()`; [`framework/compiler/source-graph.mjs`](../../framework/compiler/source-graph.mjs), `ordinaryRuntimeDependencies()`, `resolveSourceImport()` | Starts from page entries, follows relative runtime imports/re-exports and validated Worker references, excludes unreachable migration source, and fails unresolved ordinary edges or dynamic imports at the importer source location before code generation. A lazy package edge must resolve lexically to a named React or Kudzu `useEffect` import. |
14
14
  | Compatibility inventory | [`framework/compiler/compatibility-registry.mjs`](../../framework/compiler/compatibility-registry.mjs), `createCompatibilityReport()` | Classifies package/API sites in reachable original source as Native, Compiled, Normalized, Adapter, Owned External UI, Partial, or Unsupported and writes deterministic source ranges to `.kudzu/kudzu-compatibility.json`. Package provenance ends here and never enters semantic IR or browser output. |
15
+ | Structured diagnostics | [`framework/compiler/diagnostics.mjs`](../../framework/compiler/diagnostics.mjs), `KudzuDiagnosticError`, `diagnosticEnvelope()` | Carries versioned stable codes, semantic stages, severity, authored source ranges, messages, nullable compatibility classes, and safe suggestions through programmatic builds. Build-boundary path normalization keeps JSON project-relative; human compiler and development messages retain their existing source context. Internal IR and artifact invariants remain ordinary errors. |
15
16
  | Ordered normalization | [`framework/compiler/normalization-pipeline.mjs`](../../framework/compiler/normalization-pipeline.mjs), `applyNormalizationPasses()`; [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `normalizeCompilerSource()` | Applies migration/resource passes in order and repairs TypeScript parent pointers after every structural change. Imported source uses the same pipeline. |
16
17
  | Focused normalization passes | [`framework/compiler/`](../../framework/compiler/) | React, Router, browser signals, animation-frame refs, custom-hook timers, Zustand, and render control each validate and lower a narrow source shape. |
17
18
  | Shared AST/scope helpers | [`framework/compiler/ast-helpers.mjs`](../../framework/compiler/ast-helpers.mjs) | Binding, scope, reference, inclusive ancestry, effect-return, and source-location analysis. |
@@ -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.19.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.
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.19.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.
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.19.2` Apache Answer Connected Authoring Journey 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.19.2`; 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.135` retains the explicit install instructions and generates projects with `@kudzujs/core@^0.16.10`.
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,6 +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 { diagnosticEnvelope, normalizeDiagnosticError } from "./compiler/diagnostics.mjs"
9
+ import { createInspectionReport } from "./compiler/inspection-report.mjs"
8
10
  import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
9
11
  import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
10
12
  import { createProjectSession } from "./compiler/project-session.mjs"
@@ -37,7 +39,12 @@ export async function build({ quiet = false, minify = true, root: projectRoot =
37
39
  return buildWithSession(project, { quiet, minify, retainCache: false })
38
40
  }
39
41
 
40
- 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 } = {}) {
41
48
  const { root, outputDirectory } = project
42
49
  const stagedOutput = join(root, ".kudzu-dist-staging")
43
50
  const backupOutput = join(root, ".kudzu-dist-backup")
@@ -46,11 +53,16 @@ export async function buildWithSession(project, { changedFiles, quiet = false, m
46
53
  try {
47
54
  await recoverOutput(outputDirectory, backupOutput)
48
55
  await rm(stagedOutput, { recursive: true, force: true })
49
- 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 })
50
57
  await promoteOutput(stagedOutput, outputDirectory, backupOutput)
51
58
  project.buildCache = retainCache ? cache : undefined
52
59
  if (!quiet) console.log(`Built ${pageCount} page(s), ${behaviorCount} interactive page(s) into dist/`)
53
- return result
60
+ return inspection ? createInspectionReport(inspectionData) : result
61
+ } catch (error) {
62
+ const normalized = normalizeDiagnosticError(error, root)
63
+ const envelope = inspection && diagnosticEnvelope(normalized)
64
+ if (envelope) return createInspectionReport({ diagnostics: envelope.diagnostics })
65
+ throw normalized
54
66
  } finally {
55
67
  try {
56
68
  await rm(stagedOutput, { recursive: true, force: true })
@@ -104,7 +116,8 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
104
116
  if (!pageFiles.length) throw new Error("No pages found in src/pages/")
105
117
  const pageSources = new Map(pageFiles.map(file => [file, new Set(reachableSourceFiles([file], allSourceFileSet, sourceIndex))]))
106
118
  const sourceFiles = [...new Set([...pageSources.values()].flatMap(files => [...files]))].sort()
107
- 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)
108
121
  const sourceFileSet = project.sourceFiles
109
122
  sourceFileSet.clear()
110
123
  for (const file of sourceFiles) sourceFileSet.add(file)
@@ -524,6 +537,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
524
537
  const incremental = { compiledModules, renderedPages }
525
538
  return {
526
539
  result: { sourceResults, incremental },
540
+ inspectionData: { sourceFiles: sourceFiles.map(file => relative(root, file).replaceAll(sep, "/")), sourceResults, compatibility, artifacts },
527
541
  pageCount: routeRecords.length,
528
542
  behaviorCount,
529
543
  cache: { pageRenders, pageSources, placeholders, sourceResults: sourceResultsByFile }
@@ -1,4 +1,5 @@
1
1
  import ts from "typescript"
2
+ import { createDiagnosticError } from "./diagnostics.mjs"
2
3
 
3
4
  export function importDeclarationNames(statement) {
4
5
  const names = []
@@ -146,11 +147,22 @@ export function functionVarDeclaresName(fn, name) {
146
147
  return found
147
148
  }
148
149
 
149
- export function sourceNodeError(node, fallbackSource, message) {
150
+ export function sourceNodeError(node, fallbackSource, message, metadata = {}) {
150
151
  const original = ts.getOriginalNode(node)
151
152
  const sourceFile = original.getSourceFile?.()?.fileName ? original.getSourceFile() : fallbackSource
152
- const position = sourceFile.getLineAndCharacterOfPosition(original.getStart(sourceFile))
153
- return new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} ${message}`)
153
+ const startOffset = original.getStart(sourceFile)
154
+ const endOffset = original.getEnd()
155
+ const start = sourceFile.getLineAndCharacterOfPosition(startOffset)
156
+ const end = sourceFile.getLineAndCharacterOfPosition(endOffset)
157
+ return createDiagnosticError({
158
+ message,
159
+ source: {
160
+ file: sourceFile.fileName,
161
+ start: { line: start.line + 1, column: start.character + 1, offset: startOffset },
162
+ end: { line: end.line + 1, column: end.character + 1, offset: endOffset },
163
+ },
164
+ ...metadata,
165
+ })
154
166
  }
155
167
 
156
168
  export function sourceLocation(node, fallbackSource) {
@@ -46,7 +46,7 @@ function compatibilitySites(file, source) {
46
46
  const sites = []
47
47
  const add = (target, imported, local, kind, node) => {
48
48
  if (!isBarePackage(target) || target === "@kudzujs/core" || target.startsWith("@kudzujs/core/")) return
49
- const rule = classify(target, imported, kind)
49
+ const rule = classifyCompatibility(target, imported, kind)
50
50
  const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
51
51
  const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd())
52
52
  sites.push({
@@ -94,7 +94,7 @@ function compatibilitySites(file, source) {
94
94
  return sites
95
95
  }
96
96
 
97
- function classify(target, imported, kind) {
97
+ export function classifyCompatibility(target, imported, kind) {
98
98
  if (kind === "re-export") return { id: "package.re-export.unsupported", classification: "Unsupported" }
99
99
  if (kind === "side-effect-import") return { id: "package.side-effect.unsupported", classification: "Unsupported" }
100
100
  const root = packageRoot(target)
@@ -0,0 +1,72 @@
1
+ import { isAbsolute, relative, sep } from "node:path"
2
+
3
+ export class KudzuDiagnosticError extends Error {
4
+ constructor(diagnostics) {
5
+ super(diagnostics.map(formatDiagnostic).join("\n"))
6
+ this.diagnostics = diagnostics
7
+ }
8
+ }
9
+
10
+ export function createDiagnosticError(diagnostic) {
11
+ return new KudzuDiagnosticError([{
12
+ code: "source.unsupported",
13
+ stage: "analyze",
14
+ severity: "error",
15
+ compatibilityClass: null,
16
+ suggestion: null,
17
+ ...diagnostic,
18
+ }])
19
+ }
20
+
21
+ export function createTypeScriptDiagnosticError(errors, flattenMessage) {
22
+ return new KudzuDiagnosticError(errors.map(error => {
23
+ const sourceFile = error.file
24
+ const startOffset = error.start ?? 0
25
+ const endOffset = startOffset + (error.length ?? 0)
26
+ const start = sourceFile?.getLineAndCharacterOfPosition(startOffset)
27
+ const end = sourceFile?.getLineAndCharacterOfPosition(endOffset)
28
+ return {
29
+ code: "source.syntax.invalid",
30
+ stage: "analyze",
31
+ severity: "error",
32
+ compatibilityClass: null,
33
+ suggestion: null,
34
+ message: flattenMessage(error.messageText),
35
+ ...(sourceFile && start && end ? { source: {
36
+ file: sourceFile.fileName,
37
+ start: { line: start.line + 1, column: start.character + 1, offset: startOffset },
38
+ end: { line: end.line + 1, column: end.character + 1, offset: endOffset },
39
+ } } : {}),
40
+ }
41
+ }))
42
+ }
43
+
44
+ export function normalizeDiagnosticError(error, root) {
45
+ if (!(error instanceof KudzuDiagnosticError)) return error
46
+ const normalized = new KudzuDiagnosticError(error.diagnostics.map(diagnostic => ({
47
+ ...diagnostic,
48
+ source: diagnostic.source && {
49
+ ...diagnostic.source,
50
+ file: normalizeFile(diagnostic.source.file, root),
51
+ },
52
+ })))
53
+ normalized.message = error.message
54
+ return normalized
55
+ }
56
+
57
+ export function diagnosticEnvelope(error) {
58
+ if (!(error instanceof KudzuDiagnosticError)) return undefined
59
+ return { version: 1, diagnostics: error.diagnostics }
60
+ }
61
+
62
+ function normalizeFile(file, root) {
63
+ if (!isAbsolute(file)) return file.replaceAll(sep, "/")
64
+ const path = relative(root, file)
65
+ return (path.startsWith("..") ? file : path).replaceAll(sep, "/")
66
+ }
67
+
68
+ function formatDiagnostic(diagnostic) {
69
+ const start = diagnostic.source?.start
70
+ const location = diagnostic.source ? `${diagnostic.source.file}${start ? `:${start.line}:${start.column}` : ""} ` : ""
71
+ return `${location}${diagnostic.message}`
72
+ }
@@ -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
+ }
@@ -11,6 +11,7 @@ import { analyzeCollectionPipeline, collectionExpression, collectionParameters,
11
11
  import { compatibilityPackages } from "./compatibility-registry.mjs"
12
12
  import { normalizeCustomHookTimerRefs } from "./custom-hook-timer-pass.mjs"
13
13
  import { captureNames, createDescriptorSession, createSemanticArtifact, nativeCaptureNames, referencedReducerDispatches, referencedStateNames } from "./descriptor-session.mjs"
14
+ import { createTypeScriptDiagnosticError } from "./diagnostics.mjs"
14
15
  import { analyzeEffectDependencies, validateEffectOwnedBrowserResources } from "./effect-analysis.mjs"
15
16
  import { createHandlerCodegen } from "./handler-codegen.mjs"
16
17
  import { createHandlerLowering } from "./handler-lowering.mjs"
@@ -62,7 +63,7 @@ function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules,
62
63
 
63
64
  const errors = result.diagnostics?.filter(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error) ?? []
64
65
  if (errors.length) {
65
- throw new Error(errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n"))
66
+ throw createTypeScriptDiagnosticError(errors, message => ts.flattenDiagnosticMessageText(message, "\n"))
66
67
  }
67
68
  const packageReference = emittedPackageReference(result.outputText, file, new Set(["react", "react-router-dom"]))
68
69
  if (packageReference) throw new Error(`${relative(root, file)} Runtime ${packageReference} module references are not supported`)
@@ -404,7 +405,11 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
404
405
  for (const [name, binding] of packageBindings) {
405
406
  const references = referenceIdentifiers(sourceFile, name)
406
407
  const invalid = references.find(reference => !insideJsxEventHandler(reference, sourceFile) && !insideOwnedEffectCallback(reference, sourceFile))
407
- if (invalid && binding.target === compatibilityPackages.reactI18next && binding.imported === "useTranslation") throw sourceNodeError(invalid, sourceFile, "React i18next useTranslation() depends on runtime locale resources; migrate build-known locales through getStaticPaths() and props, or browser-only locale reads through an owned effect")
408
+ if (invalid && binding.target === compatibilityPackages.reactI18next && binding.imported === "useTranslation") throw sourceNodeError(invalid, sourceFile, "React i18next useTranslation() depends on runtime locale resources; migrate build-known locales through getStaticPaths() and props, or browser-only locale reads through an owned effect", {
409
+ code: "react-i18next.runtime-locale.unsupported",
410
+ compatibilityClass: "Unsupported",
411
+ suggestion: "Use getStaticPaths() and props for build-known locales, or read browser-only locale state in an owned effect.",
412
+ })
408
413
  if (invalid) throw sourceNodeError(invalid, sourceFile, `Package import ${JSON.stringify(name)} may only be referenced directly inside JSX event handlers or owned effect setup/cleanup callbacks`)
409
414
  }
410
415
  const hasUseEffectImport = sourceFile.statements.some(statement => ts.isImportDeclaration(statement) && ["@kudzujs/core", "react"].includes(statement.moduleSpecifier.text) && statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some(entry => !entry.propertyName && entry.name.text === "useEffect"))
@@ -3028,7 +3033,10 @@ function specializeComponentCall(call, component, sourceFile, factory, context,
3028
3033
  }
3029
3034
  if (declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "useRef") {
3030
3035
  const hookLabel = ordinaryHooks ? "Setter-callback component" : "Keyed row"
3031
- if (declaration.initializer.arguments.length !== 1 || declaration.initializer.arguments[0].kind !== ts.SyntaxKind.NullKeyword) throw sourceNodeError(declaration.initializer, component.getSourceFile(), `${hookLabel} useRef() must use the direct initial value null`)
3036
+ if (declaration.initializer.arguments.length !== 1 || declaration.initializer.arguments[0].kind !== ts.SyntaxKind.NullKeyword) throw sourceNodeError(declaration.initializer, component.getSourceFile(), `${hookLabel} useRef() must use the direct initial value null`, {
3037
+ code: "keyed.ref.initializer.unsupported",
3038
+ suggestion: "Initialize the object ref directly with null.",
3039
+ })
3032
3040
  if (!ts.isIdentifier(declaration.name)) throw sourceNodeError(declaration.name, component.getSourceFile(), `${hookLabel} useRef() must be assigned to one identifier`)
3033
3041
  const refs = ordinaryHooks ? ordinaryRefs : rowRefs
3034
3042
  const name = `${ordinaryHooks ? "__kComponentRef" : "__kRowRef"}${Math.max(0, call.pos)}_${refs.length}`
@@ -3573,7 +3581,7 @@ async function compileClientModule(file, sourceFiles, staticFiles, cssModules, b
3573
3581
  reportDiagnostics: true
3574
3582
  })
3575
3583
  const errors = result.diagnostics?.filter(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error) ?? []
3576
- if (errors.length) throw new Error(errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n"))
3584
+ if (errors.length) throw createTypeScriptDiagnosticError(errors, message => ts.flattenDiagnosticMessageText(message, "\n"))
3577
3585
  return { file: relative(root, file).replaceAll(sep, "/"), path: clientModulePath(file), code: result.outputText, importedAssets: [...importedAssets].map(file => relative(root, file).replaceAll(sep, "/")).sort() }
3578
3586
  }
3579
3587
 
@@ -24,7 +24,11 @@ export function createSourceGraph(root) {
24
24
  const argument = node.arguments[0]
25
25
  const specifier = node.arguments.length === 1 && ts.isStringLiteralLike(argument) ? JSON.stringify(argument.text) : argument?.getText(sourceFile) ?? "<missing>"
26
26
  if (ownedLazyPackageImport(node, bindingIndex)) return
27
- throw sourceNodeError(node, sourceFile, `Dynamic import ${specifier} is not supported in ordinary source modules`)
27
+ throw sourceNodeError(node, sourceFile, `Dynamic import ${specifier} is not supported in ordinary source modules`, {
28
+ code: "source.dynamic-import.unsupported",
29
+ stage: "graph",
30
+ suggestion: "Use a reachable static import, or the supported guarded package import inside an owned effect.",
31
+ })
28
32
  }
29
33
  ts.forEachChild(node, rejectDynamicImports)
30
34
  }
@@ -38,7 +42,11 @@ export function createSourceGraph(root) {
38
42
  } catch (error) {
39
43
  const detail = error.message.slice(error.message.indexOf("Relative import"))
40
44
  const edge = ts.isExportDeclaration(node) ? "re-export" : "import"
41
- throw sourceNodeError(specifier, sourceFile, detail.replace("Relative import", `Relative runtime ${edge}`))
45
+ throw sourceNodeError(specifier, sourceFile, detail.replace("Relative import", `Relative runtime ${edge}`), {
46
+ code: edge === "import" ? "source.import.unresolved" : "source.reexport.unresolved",
47
+ stage: "graph",
48
+ suggestion: "Point the relative runtime edge to exactly one .ts or .tsx source file under src/.",
49
+ })
42
50
  }
43
51
  }
44
52
  return dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.10",
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",