Guidance for any agent (or human) developing this repo. Keep it current: when a convention changes, update this file in the same commit.
An npx-distributed CLI (visually-3d) that generates 3D machinery
visualizations via the user's local Claude/Codex CLI — the tool holds no API
keys. It ships a built React GUI plus a Node CLI. Focus areas are hardware,
chip/architecture, and algorithm subjects (organic/sculpture modes were
deliberately dropped — do not reintroduce them).
- Compile-time over runtime. Prefer static types and abstractions that make
bad states unrepresentable. Don't lean on runtime tests to catch what the
type-checker could. Everything in
lib/ server/ bin/ src/is TypeScript understrict. - Parse, don't validate. Validate external/untrusted input (model output,
HTTP bodies, JSON files) once at the boundary with zod, then pass typed data
inward.
anyis allowed only at those boundaries and must carry an// eslint-disable-next-line @typescript-eslint/no-explicit-anywith a reason; never letanyleak past the parse. - No floating promises.
@typescript-eslint/no-floating-promisesis an error. Useawait, orvoid fn()for deliberate fire-and-forget (e.g. React event handlers). - Incremental, verified commits. Small commits, conventional-commit
messages, each one passing the full gate (below). End commit messages with
the
Co-Authored-Bytrailer. - Never push or publish without explicit per-version authorization. Commit
on
mainis fine;git pushandnpm publishrequire the user to ask, each time, for that version.
npm run lint # eslint lib server bin src (flat config, type-aware)
npm run build # build:cli (tsc -p tsconfig.cli.json) + tsc -b + vite build
npm test # build:cli + node --test test/*.test.mjs
npm run smoke # test/smoke.mjs
All four must be green. npm test and npm run build both run build:cli
first, so a type error anywhere fails them.
The CLI (lib/, server/, bin/) is authored in .ts and compiled in
place to sibling .js by tsconfig.cli.json (no outDir/rootDir — setting
outDir:"." breaks the build with TS18003). The generated .js are gitignored
(see .gitignore) and shipped via the files allowlist in package.json.
- Module system is
NodeNext; relative imports use.jsextensions even from.tssource (e.g.import { x } from './paths.js'). - Runtime path resolution relies on compiled
lib/foo.jssitting wherelib/foo.tsdid —paths.tsderivesPKG_ROOTfrom__dirname. Don't add anoutDir. - The web app (
src/) is a separate project built by Vite undertsconfig.json.
When converting/adding a CLI file: write .ts, add its generated .js to
.gitignore, run npm run build:cli, then the gate.
bin/visually.ts— command dispatcher. Barevisuallyin a TTY launches the Ink TUI; otherwise/with args it routes to subcommands. The core loop is THREE commands —visualize→verify→refine— plus the INVENTION loop (invent) and infra (serve,check,upload,tui). Keeps a#!/usr/bin/env nodeshebang (preserved through tsc).lib/visualize.ts— the VISUALIZE leg: fetch ground-truth evidence (reference paper + real source code) up front, then build/improve the 3D model GROUNDED in it (visualizeStep); births a draft viacreateif the scene is new.lib/verify.ts— the VERIFY leg: formally verify the REAL gathered SOURCE with the backend (verifyStep— one agent writes a z3/sim self-check grounded in the source; the backend runs it). Errors if no source evidence exists (runvisualizefirst). This is NOT reverse-implementation from the spec.lib/refine.ts— the REFINE leg: the closed loop that runs visualize → verify each round; goal = visual ≥ goal AND the source verifies. Visual-budget taper + best-scene ratchet.lib/invent.ts— the INVENT leg:visually invent <id>runs the invention loop (hassou → jissou → verify → visualize). Each round ideates concept candidates from the cached evidence (five delta operators: subtraction / status-change / re-representation / unification / decomposition; every 3rd round is a variance round that lifts the one-atypical-ingredient constraint), implements the chosen concept as a self-checking program whose pass/fail tests the concept's falsifiable PREDICTION, runs it on the verify backend (a falsified prediction is an honest kill, recorded — the fix loop repairs implementation bugs but must never weaken the check), and appends VERIFIED inventions to the scene's evidence notes so the next visualize pass renders them. Concept log + per-concept impls under~/.visually-3d/impls/<id>/inventions/(never re-proposes a tried slug). Method background: NyxFoundation/lean4-speedupdocs/invention-theory.md.lib/upload.ts—visually upload <id>: publish a scene into the WEB GALLERYpublic/samples/(what the site serves:<id>.json+index.jsonentry +runs/<id>/history; full as-is,--scrubfor the lean set,--webfor only what the detail page's self-improvement timeline renders + itsmanifest.json). ONE verb that branches onisRepoCheckout()(doesPKG_ROOThave a.git?): repo checkout (dev/bun) → write public/samples + commit those paths +git push origin <branch>(--no-push/--dry-runavailable); installed (npx/npm) → fork+clone upstream, write its public/samples, push a branch, open a PR (user's owngh). NOTE: the web readspublic/samples, NOTexamples/—examples/<id>is only the curated evidence seed the loop reads.lib/tui/app.ts— Ink + htm control panel (JSX without a build step). htm template markup is opaque to the type-checker; put real types on component props, hooks, and effects.lib/scene.ts— zod schemas +parseScene/validateScene/extractScene.lib/create.ts,lib/improve.ts— internal building blocks (no standalone commands): create drafts, run the visual self-improve pass. Used byvisualize.lib/reproduce.ts,lib/amend.ts— LEGACY (the old reverse-implement-from-spec reproducibility loop). Not in the current loop:verifyprovidesparseImpl's consumer but does source-grounded verification instead;amendis dormant. Kept for now (helpers/tests); candidates for removal once the source-grounded path is settled.lib/backends/— composable verification backends behind theBackendinterface (available/implementInstructions/verify).python-smt(Python+Z3 viauv run --with z3-solver) andsim(MuJoCo). Add backends by implementing the interface and registering inbackends/index.ts;defaultBackendFor(mode)picks one (algorithm→python-smt, else→sim).lib/impls.ts— canonical per-scene impl store under~/.visually-3d/impls/<id>/(impl.<ext>+verify.txt+meta.json).lib/evidence.ts— accumulating, cache-first source-evidence substrate (no standalone command).visualizefetches it UP FRONT (gatherEvidence, paper + source code) the first time a scene with sources has none cached. Policy: reference the cache first (~/.visually-3d/evidence/<id>/, falling back to the checked-inexamples/<id>/seed); on a miss, fetch (paper + GitHub refs) via the runner's web tools + Bash, appending topaper.md(never overwrite; seednotes.mdalways merged), with each pass logged inindex.json → attempts[]. For refs itgit clones the reference repo wholesale intoevidence/<id>/source/(the real files, not hand-copied fragments) —verifythen reads that cloned tree directly (Read/Grep/Bash). Evidence feedsamend(quotes it to ground the spec) and the visual pass (visualize'sbuildImproveSeedinjectssourceGroundingso the 3D model depicts the REAL architecture) — but NOT reproduce's reverse-implementers, which must keep grading the SPEC, not the paper. Only tool-enabled step (runClaudeStreaming({ tools: [...] })); the rest is tool-less. (Direction: zero-from-scratch implementation / RTL generation is not a goal — the loop grounds on the real source instead.)lib/serve.ts— static GUI server + SSE bridge to the local CLI. Endpoints:/api/health,/api/analyze/stream,/samples/...,/api/impl/<id>,POST /api/impl/<id>/verify(streams a live backend run).lib/paths.ts— package vs workspace paths. Workspace is$VISUALLY_HOME(default~/.visually-3d):scenes/,runs/,impls/,evidence/. Curated seed evidence ships checked-in under the package'sexamples/<id>/.
- Hash router (
src/router.ts):#/gallery,#/s/<id>detail. Hash routing is deliberate — zero SPA-fallback config, works the same under localserveand the static Cloudflare deploy. The synthetic id__live__holds an Analyze-generated scene in app state. src/App.tsxis the shell: owns shared data (samples, categories, backend status, live scene, analyze stream) and rendersGalleryPageorDetailPage. Detail pages are remounted per id viakeyso transient UI resets — don't reset state in an effect (react-hooks/set-state-in-effectis an error).src/sse.ts— shared SSE parsing +streamPostSsefor fetch-based streams.- WebGL context budget: browsers cap live
<Canvas>contexts. The global thumbnail-slot pool inSceneCard.tsxkeeps the gallery from exhausting them and whiting out the main viewer. Preserve it when touching gallery rendering.
- Validation: zod 4. Use
.loose()(not the deprecated.passthrough()). - Tests:
node:test(zero-dep), files intest/*.test.mjs, importing the compiled.js. Modules that read$VISUALLY_HOMEat load time must have it set before a dynamicimport()in the test. - ESLint: flat config in
eslint.config.js. Syntacticrecommendedover all TS; type-aware promise rules over anything in a tsconfig;react-hooksforsrc/. We intentionally skiprecommendedTypeChecked— the boundary-anywould drown inno-unsafe-*. - CI (
.github/workflows/ci.yml) gates on lint → build → test → smoke across Node 20/22, plus a Node-18 runtime smoke (the package targetsnode>=18; the Vite build needs 20+).