@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.
- package/MIGRATION_ROADMAP.md +7 -1
- package/PERFORMANCE.md +23 -0
- package/README.md +1 -1
- package/RELEASES.md +61 -0
- package/bin/kudzu.mjs +27 -5
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/application-capability-release-plan.md +125 -0
- package/docs/next-architecture/compiler-current-architecture.md +2 -1
- package/docs/next-architecture/large-application-ai-native-roadmap.md +3 -3
- package/docs/next-architecture/versioning.md +2 -2
- package/framework/build.mjs +18 -4
- package/framework/compiler/ast-helpers.mjs +15 -3
- package/framework/compiler/compatibility-registry.mjs +2 -2
- package/framework/compiler/diagnostics.mjs +72 -0
- package/framework/compiler/inspection-report.mjs +156 -0
- package/framework/compiler/source-compiler.mjs +12 -4
- package/framework/compiler/source-graph.mjs +10 -2
- package/package.json +1 -1
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
package/framework/build.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
153
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|