Repository navigation
Exploration: .affex — face-bridging file format for cross-exolanguage interop #84
Description
⚖️ Closure rule — requirements-target issue
This issue is the requirements-target for
.affex. It does not close when a PR lands, when a draft spec is written, or when implementation begins. It closes only when Claude and the user explicitly and mutually agree, in a recorded exchange, that the issue's spec has been satisfied by the work that has landed.Until that mutual agreement: this issue stays open as the canonical definition of done for
.affex. PRs / drafts / partial implementations link back to it but do not close it.This rule applies to every
requirements-target-labelled issue in this repo and (per the user's request) to the corresponding project-board state.
Type: Exploration / sketch idea, not yet specified.
Surfaced by: cross-referenced from hyperpolymath/accessibility-everywhere PR #16 work, 2026-05-03. Externalising it here so it survives across Claude Code sessions and can be developed in a dedicated thread.
Problem statement
AffineScript supports multiple faces (canonical, cafe, jaffa, lucid, pseudo, rattle, …). All faces lower to canonical, so the compiler doesn't care — but humans reading code in a face they don't speak are stuck.
"Someone fluent in canonical and rattle looks at lucid or pseudo and can't tell what is going on."
The friction shows up:
- At review time: PRs touching files in unfamiliar faces become opaque to teammates who use different faces.
- At transpile time: when sections need to move between faces (e.g. rattle → canonical, or canonical → cafe for ergonomics), the writer has to mentally re-encode the whole file.
- At onboarding: a programmer joining an estate has to learn N faces just to read existing code, even though semantically there is one language.
Half-recalled sketch idea: .affex
Original framing (user, paraphrased): a file extension and file type — .affex = "affine exolanguage", in the sense that the named faces (rattle, lucid, etc.) are exolanguage variants of canonical AffineScript. The file format would have properties analogous to ABI/FFI conventions, but applied between AffineScript faces rather than between distinct languages.
Goals:
- Continuity of coding in
.affinefiles for the programmer using their preferred face — no need to leave their face to interop. - ABI/FFI-like benefits between faces — predictable bridging contract instead of ad-hoc translation.
- Non-canonical-language-system support — i.e., lifts the bridging concern out of canonical itself and into a dedicated layer.
Speculative shapes (not specified, just where the design space looks open)
These are Claude's extrapolations from the problem statement — not part of the original sketch — to seed the dedicated discussion thread:
- Rosetta-stone listing: a
.affexfile holds the same source in N faces side-by-side (or as a primary face + per-other-face footnotes). Reader picks the column they speak. Auto-generatable from the lowering pass since the compiler already has the bidirectional info. - Canonical-equivalence annotations: a
.affexfile pairs face source with the canonical AST it lowers to, with shape correspondences marked (which canonical decl maps to which face span). Helps a reader translate small windows on demand instead of the whole file. - Face-interop manifest:
.affexdeclares how a face's idioms (e.g. rattle's indentation blocks) correspond to canonical structures, so tooling can render any.affinein any reader-preferred face. The "ABI" analogue: contract for round-tripping syntax. - Mixed: an
.affexfile can both be hand-written (for tricky cross-face boundaries the compiler can't auto-generate cleanly) and be auto-generated (for bulk).
Where this fits in the cardinal stance
The cardinal stance for the AffineScript ecosystem is "affine is the heart" — canonical is the centre, faces are peripheral. .affex reinforces rather than competes with this: canonical stays the lowering target, faces remain surface conveniences, and .affex is a strictly additive bridging layer that makes the periphery liveable without diluting the core.
What this is NOT
- Not a Affine↔non-Affine FFI mechanism. That's a separate concern (cross-package imports / extern decls), currently handled by per-package
Externs.affineshims in the accessibility-everywhere migration..affexdoes not solve or replace that. - Not blocking any current migration. Estates working in a single face (e.g. accessibility-everywhere, all canonical per its convention lock) feel zero friction here.
.affexonly earns its keep once an estate has multi-face adoption. - Not a compiler concern. The compiler already lowers all faces to canonical AST — it doesn't need
.affex. The audience is humans.
What's needed before this can be built
- Pick one of the speculative shapes above, or define a new one
- File extension policy: is
.affexparsed by the compiler at all, or is it a docs/tooling artifact only? - If compiler-aware: how does it relate to the existing face lowering pipeline?
- If tooling-only: which tool generates / consumes it (LSP? CLI? web renderer)?
- Generation strategy: hand-written, auto, or both?
- Estate-scope: per-package, per-monorepo, or freestanding?
Status
Parked. Not blocking any migration. Will be developed in a dedicated thread when a concrete trigger surfaces (probably first multi-face adoption inside an estate).
🤖 Drafted by Claude Code in a session where the original sketch resurfaced; externalised to this issue so it cannot get lost again. See feedback_affinescript_conventions.md "mentioned-but-undefined" section in the user's auto-memory for the local pointer.
Activity
hyperpolymath commentedon May 11, 2026
Spec draft: PR #93 (
docs/specs/affex-spec.md)After reviewing the four speculative shapes, I went with Shape 3 (face-interop manifest) as the primary form. Short rationale:
.affexfiles small and non-redundantaffinescript affex diff) rather than a stored artifactKey decisions in the draft:
check,compile,eval) does not parse.affex<package>.affexat the package rootaffex/1JSON format — per-declaration, per-face signature renderings with body digests and hand-written override blocksgenerate,render,diffPR #93 has explicit satisfaction criteria at §9. To close this issue per the requirements-target rule: please review the spec and, if it looks good, reply here with explicit agreement (or redirect with what needs to change). I'll match your agreement with mine and we can close.
Four open questions in §8 are flagged for your decision — the most load-bearing one is whether
headrendering should use the compiler's pretty-printer or store the raw source span.docs(spec): rename affex-spec.md to .adoc and convert to AsciiDoc
docs: .affex face-interop manifest spec draft (#93)