Plato repo
August 3, 2026 · View on GitHub
This is the canonical guide for agents and developers working in submodules/Plato.
CLAUDE.md points here; there is one copy of these rules.
Start here for the language and multi-target codegen: docs/plato-for-agents.md.
Confused by the Plato.Generated / Intrinsics artifacts? docs/plato-library-map.md maps every artifact and who consumes it.
Process / monorepo coupling: studio's docs/working-on-plato.md
(only the whole-monorepo gates still run from the studio checkout — the stdlib inner loop is
repo-local; see Commands). Docs and work tracking live in this repo.
Plato: pure language for geometry libraries, compiled to C# (TS/Rust/GLSL/C++/CUDA writers exist as POCs).
Also checked out as submodules/Plato inside the studio monorepo.
Ara3D SDK is consumed as a NuGet package. Every project that needs
Ara3D.Collections / Ara3D.Logging / Ara3D.Memory / Ara3D.Utils carries a single
<PackageReference Include="Ara3D.SDK.Core" Version="$(Ara3DVersion)" />; the version lives in
Directory.Build.props at the repo root. Do not add project references into a sibling
ara3d-sdk checkout, and do not switch to the Ara3D.SDK meta-package — that one is
net8.0-windows and pulls WPF, IFC and Studio.API into the compiler.
Work tracking lives here under tracker/ (python tools/track.py list --open).
Durable docs live under docs/ — docs/README.md is the index and
says which tier each document belongs to (ALL-CAPS.md = normative; design/, reports/,
essays/, discussions/, archive/ are not authority). They are written to
docs/documentation-conventions.md — chiefly: a durable doc
states design, not measurements. No file counts, finding counts, ratchet ceilings or test tallies
in prose; name the constant, command or log that holds the number. The repo-local write-docs skill
loads those rules. The stdlib gate scripts are repo-local, in this checkout's tools\; only
check-all.ps1 and regen-forward-conformance.ps1 still live in the studio repo
(C:\Users\cdigg\git\studio\tools\).
Plan + status: docs/archive/plato-execution-plan-2026-07-09.md.
Bug catalog: docs/reports/plato-library-review.md.
(docs/plato-roadmap.md was superseded and archived 2026-07-16 — it is now
docs/archive/plato-roadmap.md, historical only.)
C# style: for handwritten compiler C# (src/Plato.Compiler/, writers/Plato.CSharpWriter/, etc.) follow the
csharp-style skill — full reference in the studio repo docs/csharp-style-guide-for-agents.md.
Does NOT govern Plato-language .plato source, nor the C# the writers emit (that shape is set by
the writer code).
Layout (what matters)
Stdlib mapping: stdlib = forward vocabulary (the next-generation library);
stdlib-legacy = shipping generation (drives Plato.Generated / Studio). Do not confuse the two.
stdlib/— Forward stdlib vocabulary (ex-plato-src-v3). New vocabulary goes here. Since the plato-293 re-partition it carries both declarations and implementation bodies:<stem>.plato= types,<stem>.concepts.plato= interface declarations,<stem>.library.plato= exactly onelibraryblock. One kind of declaration per file; there is no cap on how many declarations a file may hold, so group by subject. The folder is partitioned into four subfolders —foundation/,geometry/,graphics/andfuture/(aspirational vocabulary) — each of which is itself flat.future/is not linted and not converted to C# (stdlib-377): the default tier list everywhere isfoundation geometry graphics, andfuturejoins only under an explicit flag (-IncludeFutureon the PowerShell gates,--include-futureontools/record-gates.py). It must still PARSE and TYPE-CHECK —ForwardStdLib*intests/PlatoTestsreads all four tiers unconditionally. Nothing in a shipping tier may reference afuturedeclaration. Folder rules:stdlib/AGENTS.md— including the standing obligation to regeneratestdlib/types-and-concepts.txt(the generated index of every shipping type and interface) in the same commit as any declaration change. Read before editing:stdlib/README.md(what the folder is, counts, partition rules),stdlib/CONVENTIONS.md(domain semantics — frames, winding, units, the no-generic-Optional<T>rule; when two files disagree, this one wins),stdlib/STYLE_GUIDE.md(authoring style for bodies, comments, literals, formulas),stdlib/LIBRARIES.md(how library files relate to declaration files). Inner loop:.\tools\check-stdlib-fast.ps1from THIS repo's root. Do not reach for the studio copy of that script: it resolves paths understudio\submodules\Platoand is stale. How it is tested and validated:stdlib/VERIFICATION.md— the seven rungs (parse → resolve → lint → style → type-check → codegen → law execution), which command runs each, the three ratchets and their scopes, and the wrong-greens to avoid.stdlib/tests/— forward law packet (Law_*functions) forstdlib/. It lives inside the folder but is not a tier, so no gate over the library sees it: the lint gate, the checker ratchet and the codegen recipe all name the tiers explicitly (stdlib-398). Merged with the tiers bytools\regen-forward-conformance.ps1; a consumer that wants the laws namesstdlib/tests.legacy/stdlib-legacy/— Shipping stdlib (ex-plato-src). WRITABLE as of 2026-07-09 (content-leads refactor; the old Phase-4 freeze is retired). Edit freely for runtime/body fixes; gate =lint --strict+check-all.ps1green (the golden-refresh step retired 2026-07-30). Plan:docs/archive/plato-execution-plan-2026-07-09.md. (legacy/stdlib-snapshot-2026-07-09/andlegacy/stdlib-legacy-tests/were deleted in0fea49a;legacy/now holdsstdlib-legacyalone. Recover either from git history if a diff against the pre-refactor library is ever needed.)src/Plato.CLI/— entry point.Program.csargs:[input]... [output|--out=<folder>] [--typescript|--rust|--glsl|--cpp|--cuda] [--csharp-style=extensions] [--optimize] [--optimize-arrays] [--inline] [--loops] [--static-abstract] [--dump-tir=<dir>] [--inline-report]andlint <folder>... [--strict]. Every input root is enumerated TOP-DIRECTORY-ONLY and the union compiles as one program;--out=is what frees more than two positionals. Exits 1 on parse/compile failure (fixed 2026-07-10) and on a retired flag (--scalar=, any other--csharp-stylevalue), but 0 when a body fails to lower — those are logged asDEGRADED bodiesand emitted as throwing stubs. The legacy default C# style and--no-tirwere retired at C4 (the TIR is the sole body writer);--methods/--no-propertieswere retired atcompiler-387(property-free emission is unconditional).--inlineis wired for the C# writer today; GLSL/C++/CUDA skip lambdas until that lowering is shared.src/Plato.Compiler/— compilation +Analysis/Linter.cs(LINT001–005) +Checking/(the type checker + Typed IR: Normalize → Constrain → Solve → Elaborate → Monomorphize; handoff docdocs/type-checker-handoff.md).src/Plato.AST/— the old associativity bug was FIXED in392dfa8(2026-07-09);docs/archive/plato-assoc-bug-diagnosis.mdis historical.writers/Plato.CSharpWriter/—CSharpWriter.cs(flags:ExtensionStyle,Optimize,OptimizeArrays,InlineCalls,LowerLoops,StaticAbstract),TirCSharpBodyWriter.cs(the SOLE C# body writer — every function body renders from the monomorphized Typed IR; the legacyCSharpFunctionBodyWriterwas deleted at C4),ExtensionStyleWriter.cs(classic extension methods, one static class per Plato library; moved no-arg fns are METHODSv.Magnitude()),ComponentUnroller.cs(--optimizefield-wise unrolling table). Scalar erasure (--scalar=float,ScalarErase,TirScalarLowerer) was retired 2026-08-01 — wrapper scalars are the only representation.writers/Plato.GlslWriter//writers/Plato.CppWriter/— TIR-only POC backends (GLSL ES 3.00; C++17 / CUDA with shared bodies + dialect preamble). Compile-gated by their*.Testsprojects; not inAra3D.Studio.sln. See each project'sREADME.md.src/Plato.Intrinsics/— the handwritten C# runtime (System.Numerics-backed, method-form), shared-project form, imported bygenerated/and the smoke/experiment projects. The old V1 runtime (Plato.Intrinsics.Legacy) and its freeze were deleted 2026-07-31; the copies still living inara3d-sdkbelong to that repo and are not this repo's concern.— RETIRED 2026-07-30 together with the golden diff-gate (tests/conformance/Ara3D.SDK.ConformanceTests/tracker/decisions/2026-07-30-retire-legacy-conformance-and-goldens.md). The forward suite below is the sole conformance target; making it run isplato-308. Until then, executable coverage = PlatoTests + GeometryTests.tests/conformance/Plato.ForwardConformanceTests/— forward-stdlib harness driven bytools\regen-forward-conformance.ps1. Stage 1 (type-check thestdlibtiers merged withstdlib/tests) is the gating stage and passes; Stage 2 (codegen + law runner) generates but does not compile — tracked asplato-308, detail in that folder'sREADME.md. A red Stage 2 is not your fault unless your error count exceeds the number in the issue.generated/— buildable generated projects.Plato.Generated.Foundation.Unoptimized(forwardstdlib/foundation, extension-style, wrapper scalars, optimizers off) is the only one; the two scalar-erased legacy projects were retired 2026-08-01. Not a golden (2026-07-30 retirement): the byte-identity diff-gate and itsregen-generated.ps1script are gone; this is ordinary cached output anyone may regenerate, and staleness is acceptable. Docs ingenerated/README.md.src/Plato.Navigation/(+src/Plato.Navigation.CLI/,tests/Plato.Navigation.Tests/) — navigation index over a source snapshot: go-to-def, find-refs, outline, name search, JSON export, and anIncrementalIndexerwith a per-file parse cache. Reuses the parser and binder; adds no second resolver. Its README lists the known imprecisions. Consumer:labs/PlatoNavigationMcpin the studio repo — that server is launched by hand; its README has the command, the repeatable--rootflag, and the default port 8768.parakeet/— submodule (github.com/ara3d/parakeet), the parser generator this compiler is built on. A plain clone leaves it empty;git submodule update --init --recursivefills it. Its contents belong to that repo: grammar changes are committed and pushed there, and a commit here only records which parakeet commit to use. Never stage files inside it from this repo.
Commands
Rows marked (studio) are the only ones that still run from C:\Users\cdigg\git\studio;
everything else runs from this repo's root and derives its paths from its own location.
Iterate on the one gate relevant to your workstream; run check-all.ps1 once, at the end.
.\tools\check-stdlib-fast.ps1— the forward-stdlib inner loop (seconds). Three gates:lint --strictover the three shipping tiers (-IncludeFutureaddsfuture; 0 parse / 0 resolution errors), the checker ratchet (ForwardStdLibDiagnosticCountDoesNotRegressintests/PlatoTests/ForwardStdLibCheckerTests.cs) — your change may not raise the diagnostic count, and when you lower it you lower the ceiling in the same commit — and index freshness, which fails whenstdlib/types-and-concepts.txtno longer matches the source (stdlib/AGENTS.md).-SkipIndexopts out..\tools\regen-foundation.ps1— the codegen rung (rung 5 ofstdlib/VERIFICATION.md) for the foundation tier, in one command: clear stale*.g.cs, run the C# recipe overstdlib\foundation, then build the result on net8.0. It fails on aDEGRADED bodiesline, which the CLI itself reports with exit code 0.-TestaddsPlato.Generated.Foundation.Tests;-WhatIfgenerates into.temp\and reports changed / added / removed instead of writing the tracked output;-Flagspasses an experimental recipe (--optimize,--inline, …) the.csprojheader does not describe. Not a golden gate — a non-zero-WhatIfdiff is information, not a failure.- (studio)
.\tools\regen-forward-conformance.ps1— forward-stdlib milestone gate. Stage 1 gating (seetests/conformance/Plato.ForwardConformanceTests/above);-Codegen/-Testrun the diagnostic stages. - (studio)
.\tools\check-all.ps1— full gate battery, PASS/FAIL table. Run once at the end of a mission; iterate on a single relevant gate during development. .\tools\gate-timings.ps1— how long the gates take. Every gate script records its duration (and failures) viatools\gate-timing.ps1into%LOCALAPPDATA%\ara3d\gate-timings.csv; this reports runs / median / P90 / max / total per gate, sorted by total time.-Days,-Gate,-Tail,-Failures. Include the seconds in your gate table when you report results.dotnet run --project submodules\Plato\Plato.CLI -c Release -- lint submodules\Plato\stdlib-legacy— exit 0 unless--strict; the finding count drifts with library content, so compare against the previous run, not a hardcoded baseline.
Every gate is PowerShell and Windows-pathed. An agent on Linux/CI is limited to dotnet build
and dotnet test.
Recording what the gates said: python tools/record-gates.py [--full] runs them and writes
the results down — current state into docs/status-report-snapshot.json, one appended history row
into docs/gate-log.md. --full adds a clean regeneration of the forward
stdlib plus the conformance law runner (~7 min); without it you get lint + PlatoTests (~2 min).
Run it at the end of a mission instead of hand-copying numbers into a report. Note that it
REGENERATES before it tests: a suite that passes against a stale Generated/ folder is the
easiest wrong green in this repo to produce.
Ratchets are enforced in tests, not in the log: ForwardStdLibLintTests (lint findings) and
ForwardStdLibCheckerTests (type-checker diagnostics), both in tests/PlatoTests. Each holds a
ceiling constant to LOWER, never raise, in the commit that earns it.
Status report (optional HTML): from the Plato repo root,
python tools/gen-status-report.py refreshes docs/status-report.html (live git/tracker;
gate/lint/C# build rows from docs/status-report-snapshot.json). Not required on every commit.
C# builds: use powershell tools/dotnet-build-record.ps1 -Project <csproj> -TargetName <name>
(or the studio gates that already call it) so error totals by category land in the snapshot
after every build — do not bare-dotnet build Plato projects when you care about the report.
Hard rules
- Commits. Commit your own work at clear milestones, on the current branch — never create a
branch unless asked. This working tree is SHARED with concurrent sessions, so always
git statusfirst and commit by explicit pathspec:git commit -- <file> <file>. Nevergit add -A,git add ., or a baregit commit— that sweeps someone else's staged work into your commit. Never stageparakeet/or pre-existing dirty files you did not touch. Committing here does not update the parent: bump the submodule pointer in studio as its own commit (git commit -- submodules/Plato), and push both remotes. - Nothing in this repo is frozen any more. The V1 runtime, its SHA-256 tripwire and the golden diff-gate are all retired (2026-07-31 / 2026-07-30);
generated/is ordinary cached output that anyone may regenerate. - Generated code must compile with DEFAULT LangVersion on net8.0. No C# 14 features.
- Known bugs are now BEING fixed (content-leads, from 2026-07-09). The
KnownFailures.jsonmanifest is the burn-down queue: when you fix a bug, REMOVE its manifest entry in the same change (a passing still-listed entry fails the runner with "remove from manifest"). - The conformance law runner reflects instance members;
Law_*functions stay in structs.
Language facts that are easy to get wrong
The normative reference is docs/SEMANTICS.md.
The ones agents most often rediscover the hard way:
TupleNexists up to 10 fields. Tuple expressions resolve as aTupleNcall, so a compilation withoutprimitives-tuples.platorejects them outright (docs/SEMANTICS.md, "Tuples construct types structurally").- Sum types are non-generic. A generic sum is rejected with
CHK306;matchlowers to conditionals with no new TIR node. Design doc:docs/design/plato-sum-types-design-2026-07-27.md. Consequence for the stdlib: no genericOptional<T>/Maybe<T>— seestdlib/CONVENTIONS.mdfor the three sanctioned partial-operation styles. - Generated C# is property-free, unconditionally. Every no-arg member emits as a method and
no indexers are emitted; there is no flag and no property-ful variant to test. The
--methodsand--no-propertiesflags were retired atcompiler-387(2026-08-01) along with theCSharpWriter.NoPropertiesfield — passing either is now an unrecognised argument. Decision:tracker/decisions/2026-08-01-property-free-emission-is-unconditional.md. Property-vs-method spelling was independent of scalar representation before erasure itself went. Recipe per artifact:docs/plato-library-map.md. - Scalars are always WRAPPER structs (2026-08-01).
Number/Integer/Boolean/Character/Stringstay distinct types;--scalar=<anything>is now a hard CLI error. Erasure to native primitives, and theTirScalarLowererpass that implemented it, are gone. Decision + rationale:tracker/decisions/2026-08-01-wrapper-scalars-are-the-only-representation.md. - Intrinsics may mention only
primitivetypes (2026-07-30). A bodiless signature is legal only insidestdlib/foundation/intrinsics.library.platoand only over the set declared with theprimitivekeyword instdlib/foundation/primitives.plato. Operations onAngle,Number2/3/4/8,Vector2D/3D, the matrices andQuaternionare reference bodies in*-ops.library.plato— write ordinary Plato there, do not add a bodiless declaration. Every intrinsic you do add must have asrc/Plato.Intrinsicscounterpart orIntrinsicObligationTestsfails. Full contract:docs/plato-intrinsics-surface.md. - An intrinsic must not be expressible in Plato from the other intrinsics (plato-378,
2026-07-31). The contract is an irreducible kernel — the scalar operators and transcendentals,
five array functions (
Count,At,MapRange,Reduce,FlatMap), three Boolean connectives, and theList/Bufferbuilders. If a portable reference body exists the function belongs in a*.library.platofile (primitives-number,primitives-integer,primitives-arrays), and a backend recovers native speed through its override table (plato-368) — never by re-adding a bodiless declaration. Before adding one, write the body; if the body compiles, that is your answer.
Mission protocol
- Maintain
PROGRESS.mdin your workspace (10 lines max, updated as you go) so a crashed session resumes cheaply. - On completion: close your tracker issue (
python tools/track.py close <id> --outcome "...") and record any lasting decision in the relevantdocs/plan, and keep the final report under ~300 words using: files touched / gates table / surprises / rerun commands.
Writing the final report
The report is read by a human who was not in your session. It is not a diff summary and not a set of notes to yourself. Terse is fine; unexplained is not — the reader must not have to ask "what does that mean?" about any line of it.
- Every named thing gets a clause saying what it is, the first time it appears. Constants,
tables, allowlists, test names, gate names, issue ids. Not
re-pinned to the five scalarsbutre-pinned to the five scalars (the gate's scope list — Number/Integer/Boolean/Character/String; the other receivers left the runtime, so they are out of scope rather than fixed). - Say the consequence, not just the mechanism. A reader wants to know what is now true that was not true before, and what would break if the change were wrong.
- Name the decision behind every allowlist entry. Adding a name to a known-missing / ignored / override list is a judgement call. Report which call you made and why it is not a regression — that is the part a reviewer is actually checking.
- A tracker item filed is not a finding reported. One sentence per filed issue, in plain terms: what is wrong, what it costs, how big the fix is. The issue file holds the detail; the report has to make the reader able to decide whether to care.
- Distinguish verified / done-but-unverified / not started, and say which gates you actually ran versus inherited from a previous run.
- No arrow chains, no invented abbreviations, no compound noun phrases standing in for a sentence. If a phrase needs a paragraph to unpack, you are at the wrong altitude: describe the effect instead of the mechanism.
Build output and logs
Compile errors are evidence; do not let them exist only in your scrollback. Any C# build whose
result you intend to report — especially a RED one — goes through
powershell tools/dotnet-build-record.ps1 -Project <csproj> -TargetName <name> (add -RecordOnly
when you expect failure and want the run recorded rather than aborted). That writes the full build
log to .temp/csharp-build-logs/<target>.log and the error totals, by CS code and by category,
into docs/status-report-snapshot.json. .temp/ is git-ignored and per-target files are
overwritten, so it is a working store, not a history: when a red build matters beyond the session,
quote the distinct error codes and one representative message per code in the tracker issue or
the commit message, which are the durable records. A bare dotnet build on a Plato project
leaves nothing behind and is the wrong tool when you care about the result.
PROGRESS.mdandCOMMIT_MSG.txtat the repo root are single-slot scratch files. Sessions run concurrently here — if one already holds another mission's notes, leave it alone and put yours in your own commit message rather than overwriting someone's work in progress.
Work tracking — tracker/ (WorkQuarry)
All trackable work (features, bugs, debt, ideas, open design problems, retire candidates) lives in tracker/ — one file per issue, indexed in tracker/BACKLOG.md, completed work logged in tracker/DONE.md, decisions in tracker/decisions/. Full process: tracker/readme.md.
Non-negotiable agent rules:
-
Never execute a plan doc without checking its status. A plan without a linked
ready/in-progressissue in the tracker, or carrying anEXECUTEDbanner, is historical — ask before acting on it. -
File what you find. Out-of-scope debt, bugs, or retire candidates discovered mid-task: file via
/track-issue(capture-only short form) orpython tools/track.py new. Do not fix inline, silently drop, or hand-edit BACKLOG.md. -
Name the item in the commit. Any commit doing work on a tracked item names its id in the Conventional-Commits scope —
fix(studio-149): atomic menu clear+rebuild. This is the join key between git history and tracker state; without it, landed work is invisible to the tracker. Tracker bookkeeping commits (docs(tracker):,chore(tracker):) are not work commits and do not count as progress on an item. -
Mark progress at the commit that makes it. In the same commit that lands work, tick every
## Done meanscheckbox that commit satisfies. Progress is read from those boxes — never store a percentage. Then:- All boxes ticked → close it now:
python tools/track.py close <id> --outcome "done (<work-commit-sha>)"and commit that tracker change (see rule 5). The sha is unknown until the work commit exists, so closing is the immediately-following commit, not a deferred chore. - Any box unticked → the item stays
in-progress. Say which box is outstanding. A landed fix awaiting verification is not done.
Never close an item from commit history alone — an unticked box is the item telling you it is not finished. Status/priority/sprint changes go through
track.py set; BACKLOG.md is generated, never hand-edited, and the executed plan doc gets bannered + archived at close. - All boxes ticked → close it now:
-
Commit tracker changes immediately. Any time you create or update an issue,
git addthe issue file(s) andBACKLOG.mdtogether and commit. Never leave them uncommitted or commit one without the other. -
Capture user ideas ("we should someday…") as
type: ideaissues immediately. -
Check
tracker/decisions/before proposing architecture changes; disagree via a superseding ADR, not silent divergence.