Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
864ab14
docs: KERN-IL/0.2 deterministic compiler design and walkthrough
enoch3712 Jul 11, 2026
48e5e59
docs: implementation plan for KERN-IL/0.2 deterministic compiler
enoch3712 Jul 11, 2026
e901860
feat: kern_compile symbol model and Python frontend
enoch3712 Jul 11, 2026
7eaa5a5
fix: decorated classes carry decorator span and list
enoch3712 Jul 11, 2026
5b81a92
feat: control-flow extraction with risk tags
enoch3712 Jul 11, 2026
1534fcc
fix: else/finally line attribution and AST-based math risk
enoch3712 Jul 11, 2026
649b5c4
fix: call-based risk tags take priority over math
enoch3712 Jul 11, 2026
897011f
feat: effect tables and effect/raise propagation
enoch3712 Jul 11, 2026
8e80762
feat: tiered KERN-IL/0.2 emitter with fault tags and omit counts
enoch3712 Jul 11, 2026
64008b5
fix: emit one IMPORTS line per contiguous import run
enoch3712 Jul 11, 2026
8c26d1f
feat: TS/JS tree-sitter frontend with graceful absence
enoch3712 Jul 11, 2026
388a51a
fix: parse_tsjs reports tree-sitter syntax errors via parse_error
enoch3712 Jul 11, 2026
439faac
feat: kern-il/0.2 codec, deterministic baseline dispatch, size floor,…
enoch3712 Jul 11, 2026
1e59f0a
feat: verify verb traps stale symbol reads
enoch3712 Jul 11, 2026
37cec9a
fix: verify hashes raw bytes and resolves duplicate symbol names
enoch3712 Jul 11, 2026
de85ea2
feat: per-tier token benchmark with fidelity check
enoch3712 Jul 11, 2026
521c68a
fix: suppress CALLS line at L3 to remove flow duplication
enoch3712 Jul 11, 2026
bf5d2b9
fix: L3 keeps compound-expression calls; anchored fidelity check
enoch3712 Jul 11, 2026
42da38d
chore: regenerate benchmark results after L3 fix
enoch3712 Jul 11, 2026
8aaaff8
fix: identifier-boundary matching for L3 leftover calls and fidelity …
enoch3712 Jul 11, 2026
6c373e0
docs: kern-il/0.2 tiers, verify contract, dev commands
enoch3712 Jul 11, 2026
09f353a
docs: complete FAULT reason list and verify caveats
enoch3712 Jul 11, 2026
20632b0
feat: enrichment is append-only INTENT lines over deterministic IL
enoch3712 Jul 11, 2026
8233d6d
fix: enrichment splice bypass and lock validate-write window
enoch3712 Jul 11, 2026
2689b71
fix: close two-step enrichment splice; normalize IR trailing newline
enoch3712 Jul 11, 2026
9166ee5
fix: newline-only line math, secret default redaction, annotation con…
enoch3712 Jul 11, 2026
9bf4b14
fix: fault byte fidelity, enrichment header gate, generic IR line math
enoch3712 Jul 11, 2026
5421b69
chore: gitignore superpowers plans and sdd scratch
enoch3712 Jul 11, 2026
69165e7
feat: operation log and log verb for KERN cache
enoch3712 Jul 11, 2026
4601bd6
fix: redact logged errors, harden log_event, tail-0, error file context
enoch3712 Jul 11, 2026
aa33d78
fix: value-scrubbing redaction and assignment-adjacent secret gate
enoch3712 Jul 11, 2026
393fd3c
fix: broad redaction trigger for redact_line, strict gate unchanged
enoch3712 Jul 11, 2026
2bff821
feat: structure-only L2 skips bare CALL flow lines
enoch3712 Jul 11, 2026
6d394fb
docs: React frontend IR design spec
enoch3712 Jul 11, 2026
feb4a37
fix: route .tsx to tree-sitter TSX grammar via dialect param
enoch3712 Jul 11, 2026
9f28e0f
feat: React component detection adapter with props extraction
enoch3712 Jul 11, 2026
2239f39
fix: memo/forwardRef unwrap only considers first argument
enoch3712 Jul 11, 2026
3778c4b
feat: React hook extraction with aliased and conditional-hook faults
enoch3712 Jul 11, 2026
cff9e21
fix: fault hooks guarded by && and ternary at component top level
enoch3712 Jul 11, 2026
f98e490
feat: JSX render tree lowering with IF/FOR and ambiguity faults
enoch3712 Jul 11, 2026
a6104da
fix: render selection last-return, truncation off-by-one, fault stacking
enoch3712 Jul 11, 2026
f23faa8
feat: React event handler extraction with setter lowering
enoch3712 Jul 11, 2026
dee4f17
fix: event setter lowering requires single-statement handler body
enoch3712 Jul 11, 2026
de7dba7
feat: emit COMPONENT symbols with tiered render trees and fault footer
enoch3712 Jul 11, 2026
32cffcf
fix: footer faults for collapsed render nodes, kept-child inline rule
enoch3712 Jul 11, 2026
9157470
test: React no-op regression, corpus results, frontend docs
enoch3712 Jul 11, 2026
cce8249
fix: emit component body flow and EFFECTS line, spec parity
enoch3712 Jul 11, 2026
957eaaa
fix: detect ternary and logical-and returns as components
enoch3712 Jul 11, 2026
87a9736
fix: collect dropped descendant faults under L2 inline rule
enoch3712 Jul 11, 2026
d967fa0
fix: name-hinted secret redaction on React IR surfaces
enoch3712 Jul 11, 2026
2db9e81
fix: paren-unwrapped guarded hooks fault, docstring and docs sync
enoch3712 Jul 11, 2026
294b144
fix: scrub name-hinted secrets in EVENT actions and EFFECT body flow
enoch3712 Jul 11, 2026
f9b38e4
merge: main (KERN-IL squash + hardening) into react-frontend-ir
enoch3712 Jul 11, 2026
6a08759
docs: historical note on spec vs shipped parse_tsjs API
enoch3712 Jul 11, 2026
c7b7368
chore: refresh deterministic benchmark after kern_cache component kind
enoch3712 Jul 11, 2026
42181bb
fix: harden React frontend IR
enoch3712 Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions benchmarks/results/python-det-v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@
"bucket": "large(>10k)",
"fidelity_missing": [],
"file": "skills/kern/scripts/kern_cache.py",
"source_tokens": 15181,
"source_tokens": 15467,
"tiers": {
"L1": {
"fidelity_missing": [],
"fidelity_ok": true,
"ratio": 3.0,
"tokens": 5061
"ratio": 3.01,
"tokens": 5139
},
"L2": {
"fidelity_missing": [],
"fidelity_ok": true,
"ratio": 2.38,
"tokens": 6370
"tokens": 6494
},
"L3": {
"fidelity_missing": [],
"fidelity_ok": true,
"ratio": 1.18,
"tokens": 12867
"tokens": 13066
}
}
}
Expand Down
138 changes: 138 additions & 0 deletions docs/deterministic-compiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,141 @@ the generated benchmark record instead of assuming a fixed savings percentage.
size; publish per-language results under `benchmarks/results/`.
5. **Fallback:** corpus runs with the pinned tree-sitter set absent and with syntax-broken files
must degrade to the labeled generic baseline, never crash.

---

## React frontend (tsx/jsx)

Step 1's grammar table lists `tree-sitter` for TypeScript and JavaScript, but two
gaps sat under that row: `.tsx` files were routed to the plain TypeScript grammar
(no JSX support, so JSX-bearing `.tsx` degraded to a parse error), and `.jsx`
parsed fine but rendered every component as a truncated `FN` — hooks, state,
effects, events, and render structure were invisible. `kern_react.py` closes both
gaps as a post-pass over the same tree-sitter tree: no new parser, no new format.
`COMPONENT` is a symbol kind inside the existing `ModuleIR`/`Symbol`/`emit_il`
pipeline, so spans, slice hashes, tiers, faults, verify, cache, and redaction are
inherited verbatim.

### Grammar routing

| suffix | grammar |
| --- | --- |
| `.js` `.jsx` `.mjs` `.cjs` | `tree_sitter_javascript` (JSX built in) |
| `.ts` | `tree_sitter_typescript.language_typescript()` |
| `.tsx` | `tree_sitter_typescript.language_tsx()` |

`parse_tsjs(text, typescript: bool = False, tsx: bool = False)` selects the
grammar (`tsx=True` wins over `typescript=True`), and `tsjs_available()` takes
the same flags for per-grammar capability probes. Both call sites
(`kern_cache.py` compile and verify paths) route this way. The React adapter
runs only on the JSX-capable grammars (JavaScript and TSX; plain TypeScript has
no JSX productions). The `generator=` header line reports
`lang=typescript frontend=tree-sitter+react` for a `.tsx` module when the
adapter fires, and plain `frontend=tree-sitter` otherwise — a strict no-op on
non-component code, verified by `TestNoOpOnPlainCode` in `tests/test_react.py`.

A symbol is upgraded to `kind="component"` when it is a function declaration,
function expression, or arrow function, its name matches `^[A-Z]`, and a
`return` (or arrow expression body) contains a `jsx_element`,
`jsx_self_closing_element`, or `jsx_fragment`. `memo(Fn)` / `forwardRef(Fn)`
wrappers are unwrapped: the inner function is lowered, the wrapper noted.
Capitalized functions with no JSX stay plain `FN`; lowercase functions
returning JSX also stay `FN` (not components by React convention).

### Extraction vocabulary

```text
COMPONENT UserCard L3-18 #a3f9c2d1
PROPS user, onClose?=noop
STATE open=false
STATE [state, dispatch]=useReducer(reducer, init)
CTX theme=useContext(ThemeContext)
REF inputRef
HOOK data=useUserData(id)
EFFECT deps=[user.id]
EVENT Card.onClick -> set open=true
RENDER
Card
Avatar src=user.avatar
span {user.name}
IF open > UserDetails user=user
FOR item in items > Row key=item.id
```

Rules:

- **PROPS** — first parameter. Destructured pattern lists names with defaults
(`onClose?=noop` when a default exists; `?` when the TS type marks it optional
and that is syntactically visible). Non-destructured param renders as its name
(`props`).
- **STATE** — `const [x, setX] = useState(init)` → `STATE x=init`; setter name
recorded internally for EVENT lowering. `useReducer` renders the pair and
arguments.
- **CTX / REF / HOOK** — `useContext`, `useRef`, and custom `use[A-Z]\w*` calls
respectively. Custom hooks are opaque: call text only, no cross-file
resolution.
- **EFFECT** — `useEffect` / `useLayoutEffect`. Dependency array rendered
verbatim (`deps=[user.id]`, `deps=[]`); a missing array renders
`deps=EVERY-RENDER`. Effect body: L2 normally shows the head only but retains
risk-bearing operations required by the fault contract; L3 summarizes via
existing `flow()`.
- **EVENT** — JSX attribute matching `on[A-Z]\w+={expr}`. If the handler body is
a single known-setter call, lower to `set <state>=<arg>`; otherwise render the
callee name or `flow()`-style summary at L3.
- **RENDER** — JSX tree, indentation = nesting:
- `{cond && <X/>}` → `IF cond > X`; ternary → `IF cond > X ELSE > Y`
- `.map(` callback returning JSX → `FOR param in receiver > X`
- Host elements (lowercase) are structure; text/expression children render as
`{expr}` capped by existing `ntext` (secret redaction inherited)
- Capitalized JSX names are component dependencies; names that match imports
cross-link naturally through the existing import lines
- Non-hook, non-render statements in the component body flow through the
existing L3 flow-op rendering unchanged (hook calls are skipped there —
they already surface as STATE/CTX/REF/HOOK/EFFECT heads). Components also
emit the same `EFFECTS` provenance line as plain functions (effect classes
plus `unknown-calls=N`) at L2 and L3. L2 retains only risk-bearing body ops
so their inline markers and `FAULT-BEFORE` entries cannot disappear.
- All line math is `\n`-only, matching the repo rule (never `str.splitlines()`).

### Tier mapping

| Tier | Component detail |
| --- | --- |
| L1 | `COMPONENT name (props) span #hash` — one line, like current FN heads |
| L2 | + STATE/CTX/REF/HOOK/EFFECT/EVENT heads, risk-bearing body ops, and the `EFFECTS` provenance line; RENDER collapsed to components-only tree (host elements and attributes dropped; IF/FOR structure kept) |
| L3 | Full render tree with attributes, effect bodies, handler bodies, and non-hook body statements via `flow()` |

### Faulting

Ambiguity never disappears silently. Seven markers reuse the existing
`!FAULT(...)` inline channel and FAULTS footer:

| Construct | Marker |
| --- | --- |
| Hook called via alias or namespace (`R.useState`, renamed import) | `!FAULT(aliased-hook)` |
| Dynamic component (`<Tag/>` where Tag is a lowercase variable or member expression) | `!FAULT(dynamic-component)` |
| Spread props as the sole prop source (`{...rest}`) | rendered `...rest` + `!FAULT(spread-props)` |
| Render prop / children-as-function | `!FAULT(render-prop)`, body summarized as flow |
| Hook call inside conditional | `!FAULT(conditional-hook)` |
| Multiple or nested JSX return paths collapsed to one render root | `!FAULT(render-control-flow)` |
| Render tree exceeding op budget | explicit `…+N` + `!FAULT(render-truncated)` |

Frontend IR remains a reasoning representation, not the write authority: edits
still require faulting exact current source and verifying the slice hash, per
the existing contract (Step 5).

### Corpus run

Compiled at L2 over two real corpora — KERN's own `app/` directory (4 files:
`layout.jsx`, `page.jsx`, `docs/layout.jsx`, `docs/page.jsx`) and a fresh
shallow clone of [`vercel/commerce`](https://github.com/vercel/commerce) (65
`.js`/`.jsx`/`.ts`/`.tsx` files, `node_modules` excluded):

```text
files=69 crashes=0 components=61 faults=8
ratio min=1.9 median=3.3 max=10.1
```

Zero crashes, components detected on both corpora, ambiguous constructs (spread
props, dynamic components, render-prop children) surfaced as faults rather than
silently dropped — the done bar this spec set for Stage 1 MVP.
127 changes: 127 additions & 0 deletions docs/superpowers/specs/2026-07-11-react-frontend-ir-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# React Frontend IR — Design

**Date:** 2026-07-11
**Scope:** TSX/JSX grammar routing fix + React semantic extraction (Stage 1 MVP). Next.js adapter, type enrichment, and other frameworks are later specs.

> **Historical note:** this spec predates the compiler-hardening merge from main. The shipped implementation adopts main's `parse_tsjs(text, typescript=, tsx=)` boolean API instead of the `dialect` parameter described below; the semantic vocabulary, tiers, and faulting are as specified. Current behavior: `docs/deterministic-compiler.md`.

## Problem

KERN-IL/0.2 lowers TS/JS through Tree-sitter, but:

1. `.tsx` files are routed to the plain TypeScript grammar (`kern_cache.py` passes `typescript=True`; `parse_tsjs` loads `language_typescript()`). JSX is a syntax error in that grammar, so JSX-bearing `.tsx` files degrade to a parse-error IR. `tree_sitter_typescript.language_tsx()` exists and is unused.
2. `.jsx` parses (the JavaScript grammar includes JSX) but the extractor has no JSX semantics: components render as `FN` symbols whose return statements are truncated JSX text. Hooks, state, effects, events, and render structure — the behavioral core of a React file — are invisible.

React source is a verbose encoding of compact behavior. A deterministic semantic lowering recovers that behavior at a fraction of the tokens.

## Decision summary

- **No new parser.** Tree-sitter JS and TSX grammars provide syntax; KERN supplies React meaning. Writing or committing a custom React grammar is explicitly out of scope — React is semantics on top of JS/TSX syntax, not a new syntax.
- **Extend KERN-IL, not a new format.** `COMPONENT` becomes a new symbol kind inside the existing `ModuleIR`/`Symbol`/`emit_il` pipeline. Spans, slice hashes, tiers, faults, verify, cache, and redaction are inherited verbatim. The runtime model learns one format.
- **Adapter seam, not query packs.** React lowering lives in a new module (`skills/kern/scripts/kern_react.py`) as a post-pass over the same Tree-sitter tree, in the codebase's existing manual-visitor style. Future frameworks (Next.js, Vue, Svelte) become sibling adapters emitting the same vocabulary. Declarative `.scm` query packs are deferred until a second framework proves the need.
- **Tiers stay the compression lever.** Component detail maps onto existing L1/L2/L3; no new knobs.

## Architecture

```text
TSX/JSX source
↓
parse_tsjs(text, dialect) # dialect: "js" | "ts" | "tsx"
↓ tree-sitter CST + base symbols (FN/const/class/import)
kern_react.py post-pass # same tree, upgrades qualifying FN → COMPONENT
↓
ModuleIR → emit_il (existing) # tier rendering, faults, hashes
```

### Grammar routing

| suffix | grammar |
| --- | --- |
| `.js` `.jsx` `.mjs` `.cjs` | `tree_sitter_javascript` (JSX built in) |
| `.ts` | `tree_sitter_typescript.language_typescript()` |
| `.tsx` | `tree_sitter_typescript.language_tsx()` |

`parse_tsjs(text, typescript: bool)` becomes `parse_tsjs(text, dialect: str)`. Both call sites (`kern_cache.py` compile and verify paths) updated. `generator=` header line reports `lang=tsx frontend=tree-sitter+react` when the adapter fires.

### Component detection (deterministic)

A symbol is upgraded to `kind="component"` when:

- it is a function declaration, function expression, or arrow function, AND
- its name matches `^[A-Z]`, AND
- a `return` (or arrow expression body) contains `jsx_element`, `jsx_self_closing_element`, or `jsx_fragment`.

Also detected: `memo(Fn)`, `forwardRef(Fn)` wrappers — the inner function is lowered, the wrapper noted. Capitalized functions with no JSX stay plain `FN`. Lowercase functions returning JSX stay `FN` (not components by React convention).

## Extraction vocabulary

```text
COMPONENT UserCard L3-18 #a3f9c2d1
PROPS user, onClose?=noop
STATE open=false
STATE [state, dispatch]=useReducer(reducer, init)
CTX theme=useContext(ThemeContext)
REF inputRef
HOOK data=useUserData(id)
EFFECT deps=[user.id]
EVENT Card.onClick -> set open=true
RENDER
Card
Avatar src=user.avatar
span {user.name}
IF open > UserDetails user=user
FOR item in items > Row key=item.id
```

Rules:

- **PROPS** — first parameter. Destructured pattern lists names with defaults (`onClose?=noop` when a default exists; `?` when the TS type marks it optional and that is syntactically visible). Non-destructured param renders as its name (`props`).
- **STATE** — `const [x, setX] = useState(init)` → `STATE x=init`; setter name recorded internally for EVENT lowering. `useReducer` renders the pair and arguments.
- **CTX / REF / HOOK** — `useContext`, `useRef`, and custom `use[A-Z]\w*` calls respectively. Custom hooks are opaque: call text only, no cross-file resolution.
- **EFFECT** — `useEffect` / `useLayoutEffect`. Dependency array rendered verbatim (`deps=[user.id]`, `deps=[]`); a missing array renders `deps=EVERY-RENDER`. Effect body: L2 shows the head plus any risk-bearing ops required by the fault contract; L3 summarizes via existing `flow()`.
- **EVENT** — JSX attribute matching `on[A-Z]\w+={expr}`. If the handler body is a single known-setter call, lower to `set <state>=<arg>`; otherwise render the callee name or `flow()`-style summary at L3.
- **RENDER** — JSX tree, indentation = nesting:
- `{cond && <X/>}` → `IF cond > X`; ternary → `IF cond > X ELSE > Y`
- `.map(` callback returning JSX → `FOR param in receiver > X`
- Host elements (lowercase) are structure; text/expression children render as `{expr}` capped by existing `ntext` (secret redaction inherited)
- Capitalized JSX names are component dependencies; names that match imports cross-link naturally through the existing import lines
- Non-hook, non-render statements in the component body flow through the existing L3 flow-op rendering unchanged; L2 retains only risk-bearing ops.
- All line math is `\n`-only, matching the repo rule (never `str.splitlines()`).

## Tier mapping

| Tier | Component detail |
| --- | --- |
| L1 | `COMPONENT name (props) span #hash` — one line, like current FN heads |
| L2 | + STATE/CTX/REF/HOOK/EFFECT/EVENT heads and risk-bearing body ops; RENDER collapsed to components-only tree (host elements and attributes dropped; IF/FOR structure kept) |
| L3 | Full render tree with attributes, effect bodies and handler bodies via `flow()` |

## Faulting

Ambiguity never disappears silently. Reuses the existing `!FAULT(...)` inline channel and FAULTS footer:

| Construct | Marker |
| --- | --- |
| Hook called via alias or namespace (`R.useState`, renamed import) | `!FAULT(aliased-hook)` |
| Dynamic component (`<Tag/>` where Tag is a lowercase variable or member expression) | `!FAULT(dynamic-component)` |
| Spread props as the sole prop source (`{...rest}`) | rendered `...rest` + `!FAULT(spread-props)` |
| Render prop / children-as-function | `!FAULT(render-prop)`, body summarized as flow |
| Hook call inside conditional | `!FAULT(conditional-hook)` |
| Multiple or nested JSX return paths collapsed to one render root | `!FAULT(render-control-flow)` |
| Render tree exceeding op budget | explicit `…+N` + `!FAULT(render-truncated)` |

Frontend IR remains a reasoning representation, not the write authority: edits still require faulting exact current source and verifying the slice hash, per the existing contract.

## Testing

- **`tests/test_react.py`** — golden fixtures per construct: useState, useReducer, useContext, useRef, custom hook, effect with/without deps, events (setter and non-setter), IF/FOR render lowering, fragments, memo/forwardRef, spread props, dynamic component, aliased hook, conditional hook, `.jsx` and `.tsx` dialects, TSX generics ambiguity (`<T,>` arrow).
- **Regression** — existing `test_tsjs.py` fixtures (no React constructs) must emit byte-identical IR; the adapter must be a strict no-op on non-component code.
- **Corpus** — run compile over KERN's own `app/*.jsx` and a cloned real-world Next.js repo: zero crashes, all ambiguous constructs surface as faults (grep the FAULTS footer), compression ratios recorded per tier for the record. No hard ratio gate — compression is a large-file property and the `min_ir_tokens=600` source-cheaper stub already handles small files.

## Non-goals (this spec)

- Next.js concepts: routes, layouts, `use client`/`use server` boundaries, server actions, metadata (Stage 2 spec)
- Type-aware enrichment via the TypeScript compiler API or LSP (Stage 3 spec)
- Vue / Svelte / Angular / Astro adapters
- Declarative `.scm` query-pack architecture
- A custom Tree-sitter React grammar (never needed)
35 changes: 34 additions & 1 deletion skills/kern/scripts/kern_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import argparse
import copy
import hashlib
import importlib
import importlib.metadata
import json
import os
Expand All @@ -24,7 +25,7 @@
BASELINE_GENERATOR = "kern-det/0.2"
DERIVATION_MARKER = "kern-derivation/semantic16-v1"
TSJS_SUFFIXES = {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"}
ADDRESSABLE_SYMBOL_KINDS = {"function", "class", "type", "enum", "namespace", "module", "export"}
ADDRESSABLE_SYMBOL_KINDS = {"function", "class", "component", "type", "enum", "namespace", "module", "export"}
CACHE_DIRNAME = ".kern"
DEFAULT_CONFIG: dict[str, Any] = {
"schema": SCHEMA,
Expand Down Expand Up @@ -546,6 +547,31 @@ def _distribution_version(name: str) -> str:
return "unknown"


def _module_source_fingerprint(module_name: str, fallback: Path | None = None) -> str:
"""Return a module source digest, or a deterministic availability marker.

Optional compiler adapters are still fingerprint inputs when they cannot be
imported: if their source is present, hash the source directly; otherwise
record a stable marker instead of making cache validation fail.
"""
path = fallback
try:
module = importlib.import_module(module_name)
module_file = getattr(module, "__file__", None)
if module_file:
path = Path(module_file)
except Exception:
pass
if path is None:
return "unavailable"
try:
return sha256_file(path)
except FileNotFoundError:
return "missing"
except (OSError, ValueError):
return "unavailable"


def compiler_fingerprint(
source: Path,
config: dict[str, Any],
Expand All @@ -562,6 +588,10 @@ def compiler_fingerprint(
"python": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
}
if suffix in TSJS_SUFFIXES:
try:
react_fallback = compiler_path.with_name("kern_react.py")
except ValueError:
react_fallback = None
capabilities.update(
{
"tree-sitter": _distribution_version("tree-sitter"),
Expand All @@ -573,6 +603,9 @@ def compiler_fingerprint(
capabilities["tsjs"] = kern_compile.tsjs_capability_fingerprint()
except Exception:
capabilities["tsjs"] = "unavailable"
capabilities["kern-react-sha256"] = _module_source_fingerprint(
"kern_react", react_fallback
)
payload = {
"codec": CODEC_VERSION,
"generator": BASELINE_GENERATOR,
Expand Down
Loading
Loading