Historical migration proposal, retained below without rewriting its original scope. Current implementation contract:
docs/compiler-architecture.mdandCOMPILER_FRAMEWORK_REPORT.md. The old branch/workflow, effects-based usage target and blanket reflection ban below are not current delivery requirements: usage remains lexical; bounded typed-nil capability reflection is retained. New reference-bearing shapes still require provenance audits even when effects exist. Go baseline is 1.23.2. This history authorizes no branch changes, commits or shipping.
Make Peeper compiler clean, readable, and difficult to extend incorrectly.
When contributor adds or changes syntax that uses a symbol, contributor should declare semantic meaning once. Existing infrastructure should then handle ordinary reads, writes, moves, copies, borrows, initialization, usage, and cleanup without new feature-specific logic in every analysis.
Goal is not zero edits. Syntax, scope, type rules, genuinely new control flow, and new runtime representation still need explicit owners. Goal is eliminating repeated decisions and silent omissions.
No new Peeper language feature is part of this task. Do not add catch, error-union syntax, or copy another language's syntax or semantics. Existing optional, enum, loop, call, match, ownership, and default-argument behavior are validation cases, not feature targets.
Follow AGENTS.md, RULES.md, and go-style.md. Repository rules override this
handoff.
- Read this file and
compiler-maintainability.localplan.mdcompletely. - Work one numbered step only after explicit maintainer approval.
- Before each patch, re-run full
AGENTS.mdpre-patch gate against live source and current diff. - After each patch, run post-patch gate and record explicit Rules check in local plan.
- Keep
compiler-maintainability.localplan.mdcomplete. Never commit it. - Do not commit, push, create/update PR, merge, or change GitHub tracking without explicit maintainer authorization.
- Preserve unrelated changes. Stop if unexpected work overlaps planned files.
- Use
rg/rg --filesfirst. Useapply_patchfor hand edits. - Verify behavior from current source and tests, not only docs or donor commits.
- Do not proceed to next step when current step is merely compiling. Meet its validation and review stop condition.
Plan was produced on docs/compiler-framework. That branch contains useful work
mixed with broad experimental migration. It is read-only donor, not implementation
trunk.
Before Step 1 implementation:
- run
git status --short --branch; - record current HEAD and
origin/mainSHA; - create
feature/compiler-maintainabilityfrom verifiedorigin/main; - preserve untracked
task.mdand ignoredcompiler-maintainability.localplan.md; - inspect donor commits with
git show; - reproduce only approved behavior;
- do not merge or wholesale cherry-pick
docs/compiler-framework; - do not fetch, rebase, reset, stash, clean, delete, or rewrite history without permission.
| Entity | Owns | Must not own |
|---|---|---|
| AST node | Parsed shape, location, canonical children | Type/ownership/backend policy |
| Module identity | Stable source/import identity | Phase-local semantic facts |
| NodeID | Syntax occurrence identity | Symbol/storage meaning |
| SymbolID | Declaration/binding identity | Flow state |
| Place/origin | Storage root and projections | Typechecking or cleanup policy |
| Type model | Source type structure and nominal identity | Per-use flow state |
| Type capability | Canonical derived copy/move/drop/etc. answer | Physical backend emission |
| Typechecker result | Resolved semantic decisions needing type knowledge | CFG topology |
| CFG | Blocks, sites, edges, reachability | Type or ownership re-derivation |
| Semantic operation | Ordered read/write/move/borrow/define/discard meaning | Analysis-specific state |
| Definite initialization | Initialized-state dataflow and diagnostics | Ownership/drop policy |
| Ownership | Move/loan/liveness state and cleanup decisions | Syntax parsing, physical layout |
| Usage | Used/unused accounting and warnings | Ownership state |
| Cleanup plan | Exact source-level destruction sites | Backend layout traversal |
| HIR/MIR | Lowering established evidence | Rediscovering source semantics |
| Backend layout | Physical representation | Source semantic acceptance |
| Artifact validator | Shape and cross-reference invariants | Duplicate analysis algorithm |
source
-> AST
-> binding/resolution artifacts
-> typechecker artifact + published semantic meaning
-> CFG artifact
-> flow-refined artifact
-> definite initialization / ownership / usage over CFG + semantic meaning
-> cleanup plan
-> HIR
-> MIR
-> backend
No lower phase reaches backward to recompute an upstream decision when explicit evidence can cross boundary.
For future syntax node that evaluates value then stores it in symbol:
required feature-specific work:
parser shape
resolver scope/binding
typechecker rule and semantic publication
CFG only if control flow differs
HIR only if existing lowering shapes cannot represent it
automatic common work:
symbol reads and writes
definite initialization
copy/move selection already published by typechecker
borrow conflicts
used/unused accounting
cleanup planning
MIR/backend when existing operations suffice
If future construct maps to existing semantic operations but still needs custom ownership, definite-init, usage, or backend cases, migration is incomplete.
- No pass-through wrappers or old names forwarding to new names.
- No ignored parameters retained to avoid caller updates.
- No duplicate maps during migration after one slice completes.
- No
map[type]any,Publish[T], service locator, global registry, reflection, or generic phase engine. - No visitor method on every AST node for each compiler phase.
- No phase behavior stored on syntax nodes.
- No new helper/package/file unless it removes current duplication, protects invariant, or represents real phase/lifetime boundary.
- No one-field organizational struct.
- No backend source-policy decision.
- No validator that reimplements ownership or dataflow.
- No refactor mixed with newly discovered behavior change. Report bug and request separate approval.
- No Go 1.27 upgrade in this task. Existing Go generics may be used only when same implementation truly works across current types and improves clarity.
- No language syntax or semantics copied from external languages.
Inspect these commits only when their step is active:
| Commit | Useful evidence | Known caution |
|---|---|---|
0d1f4ee |
explicit binding/typechecking result ownership | broad 48-file migration; split smaller |
03b4004 |
canonical module identity and constant result | do not combine with Step 1 |
de8185a |
AST dispatch contract | test-tier, not compile-tier |
18b4800 |
child traversal enforcement | structural check can be fooled; mutation-test it |
26600b0 |
attribute/substructure traversal hardening | keep recovery semantics |
8ad69ae |
ownership capability vocabulary | final code still composes three walkers |
c8eca73 |
published value-use kinds | incomplete coverage; validator only checks some uses |
c687b50 |
cleanup-plan consolidation | verify every removed channel behavior |
52ff007 |
ownership artifact validator | does not prove exactly-once drop paths |
7ec06e9 |
removal of unused result channel | confirm no consumer before deletion |
7b11a33 |
CFG topology validation | validator distinct from CFG analysis |
6fa713a |
closed iteration evidence | useful valid-state model; avoid ceremonial interfaces |
2a5fcf0 |
sealed MIR node families | does not itself enforce backend dispatch completeness |
Use isolated cache because normal ccache/cache paths can be read-only:
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go vet ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go build ./...
git diff --checkWhen packaging or source behavior can be affected:
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go run ./scripts/bundle.go
PEEPER_BIN="$PWD/build/bin/peeper" GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./x_testRun focused packages first, then full suite. Record command, exit code, and meaningful output. Environment failures stay separate from source failures.
Create one explicit typechecker generation artifact on fresh branch from
origin/main. This is first narrow slice of phase-result migration, not full donor
commit replay.
Current origin/main project.SemanticInfo mixes binding, resolution,
typechecking, constant, method-index, and operation-catalog state. Common semantic
publication cannot be reliable until its owner and reset lifetime are clear.
Answer in local plan before editing:
- Every
SemanticInfofield and all writers/consumers. - Exact phase that first makes each field valid.
- Whether field is base typechecking, flow-refined, binding/resolution, constant-evaluation, or shared catalog state.
- Which fields donor
typecheckresult.Resultmoved. - Which donor moves were later corrected; inspect subsequent commits touching
typecheckresult,bindingresult, andproject.Module. - Reset behavior in
Module.resetToPhase,ResetSemanticData, andCompilerContext.ResetModule. - LSP/invalid-source paths that read partial typechecking evidence.
- Eager const-evaluation paths that run before typechecking completes.
Move only facts produced as base typechecker decisions and consumed downstream. Expected candidates, subject to live proof:
ExprTypes
CaseTests
Matches
ImplicitConversions
ImplicitCallArguments
CompilerCalls
StringConcatenations
VariantConstructions
ForIterations
InterfaceImplementations
Do not move merely because donor moved it. Keep outside this slice unless live writer/consumer/lifecycle evidence proves typechecker ownership:
BlockScopes
ResolvedSymbols
ExpandedDefaultBindings
ConstValues
MethodSets
MethodSymbol
OperationFunctions
- Add dedicated typechecker result only if it owns several coherent facts.
- Constructor initializes all required maps and protects valid empty state.
- Typechecker owns creation/population.
- Consumers read new owner directly; no old-field fallback.
- Remove migrated fields from old mixed structure in same slice.
- Move associated data models with their owner; no type aliases.
- Keep base type evidence distinct from flow-refined evidence.
- Preserve nil/partial behavior before typecheck and during invalid source.
- Preserve default-argument expansion NodeID provenance.
- Preserve eager consteval behavior when typechecker result is absent/incomplete.
- Preserve semantic export fingerprint and LSP hover/completion behavior.
- Update reset lifecycle atomically: retain through its producing phase, clear on reset before that phase.
- Add lifecycle tests for nil, initialized-empty, populated, retained, and cleared states.
- Do not introduce generic result container.
- Do not move binding/resolution fields as incidental cleanup.
At minimum:
gofmt -w <touched-go-files>
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./internal/semantics/typechecker ./internal/semantics/consteval ./internal/project ./internal/pipeline ./internal/lsp
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -race -count=1 ./internal/project ./internal/pipeline ./internal/lsp
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go run ./scripts/bundle.go
PEEPER_BIN="$PWD/build/bin/peeper" GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./x_test
git diff --check
git status --short --branchStop for Codex review. Report:
- branch and base SHA;
- field producer/consumer/lifecycle table;
- exact fields moved and explicitly deferred;
- all old reads/writes removed;
- reset and partial-source behavior;
- diff stat;
- exact validation results;
- mandatory Rules check.
Do not begin Step 2. Do not commit.
Give remaining semantic facts one honest owner and stable lifetime, then replace duplicated module identity with canonical identity.
This step is too broad for one patch. Claude must propose ordered sub-slices from live inventory and wait for approval before each:
- binding/collection/resolution generation artifact;
- constant result versus query/cache state;
- remaining shared catalogs whose multi-writer lifecycle is genuine;
- canonical module identity and indexes;
- reset/invalidation convergence.
- One fact stored once.
- Producer, consumers, valid-from phase, reset-before phase documented.
- Reuse
NodeID,SymbolID, module identity, and existing binding indexes. - Keep generated/default provenance paired with binding identity.
- No name/path/pointer reconstruction when stable ID exists.
- No compatibility field, shadow map, or stale alias.
- No false phase split: if collector/binder/resolver truly build one staged symbol graph with same lifetime, keep coherent result and document multi-writer contract.
- Directly update all consumers.
- Preserve diagnostics discard alongside artifact reset.
- Preserve incremental module reuse and dependency invalidation.
After each approved sub-slice, show artifact ownership matrix and reset tests. Do not continue automatically.
Adding AST statement/expression/type kind or child must create immediate named failures at every phase requiring a decision.
- Reuse existing
ast.Inspect,ir.InspectExpr,ir.InspectPlace, andhir.InspectStmt; do not add parallel visitors. - AST node owns canonical children only.
- Do not add resolver/typechecker/ownership methods to AST nodes.
- Add or transplant structural child-field tests only after understanding recovery nodes, substructures, attributes, and generated/default AST.
- Add phase-dispatch contracts for real switch sites.
- Missing node must be handled or explicitly classified:
traverse
ignore
reject
contextual
- Every omission classification needs concrete reason.
- Test must fail when stale classification remains after real handler added.
- Mutation-prove each contract: remove one real case/child locally, capture expected failure, restore probe before stop.
- Do not claim compile-time exhaustiveness from source-inspection tests.
- Evaluate compile-tier sealed interfaces only if boilerplate and phase-state costs are lower than current checked-table approach. Do not implement visitor spike by default.
Produce matrix of node families versus phase sites, with guard strength: automatic, visible test, loud runtime validation, or manual gap.
Types receive one canonical derived answer for each shared semantic capability. Consumers stop independently walking type structure.
Audit current predicates and every caller, including:
copy / explicit-copy / no-copy
needs-drop
sized / lowerable
equatable / orderable
contains-reference / contains-stored-reference
pointer/reference target
collection shape
target-width representability
backend structural drop/layout traversal
- Centralize only semantic questions shared by multiple callers.
- One recursive walker may return several tightly coupled ownership properties when they share traversal and invariants.
- Do not create giant capability object for unrelated properties.
- Preserve aliases, nominal types, generic instantiation, recursive types, cycle guards, aggregates, enums/optionals, interfaces, functions, arrays/slices, ownership carriers, and invalid types.
- Update consumers directly and delete replaced walkers.
- Do not leave
IsXwrappers around canonical function solely for old callers. - Backend layout traversal may remain when it answers physical emission rather than source policy. Rename/comment/test boundary if unclear.
- Type capability does not own per-expression use decision.
- Add exhaustive current-type table tests and recursion tests.
- If consolidation reveals inconsistent existing behavior, report it. Do not choose new language semantics inside refactor.
Show old/new behavior table, removed duplicate walkers, consumers, and remaining backend-only traversal with justification.
Represent common effect of existing syntax using small stable vocabulary so later analyses do not each inspect AST shape.
Trace existing behavior for:
- declarations with and without initializers;
- assignment and replacement;
- return values;
- expression statements/discarded values;
- conditions;
- calls, implicit receivers, piped calls, default arguments, intrinsics;
- address/shared/mutable borrow;
- selector/index/deref places;
- variant construction, case tests, match payload bindings;
- loops, break, continue, scope exits;
- explicit free/drop operations;
- string/array/collection operations.
Record exact evaluation order and which phase currently decides read/copy/move.
Candidate concepts—not mandated names:
UseValue(Read | Copy | Move)
DefinePlace
ReadPlace
WritePlace
BorrowPlace(shared | mutable)
UnpackPayload
DiscardValue
- Add operation only when current behavior needs it.
- Operation stores stable
NodeID/SymbolID/place identity and diagnostic location, not raw syntax pointer when avoidable. - Typechecker publishes type-dependent decisions.
- Purely structural decisions may be normalized after CFG if that avoids redundant evidence without losing completeness.
- Preserve ordered evaluation.
- Result owner and reset phase explicit.
- Missing required operation for valid accepted source fails validator.
- Invalid source uses deliberate recovery, not false compiler bug.
- Avoid one struct with unrelated optional fields; use valid closed shapes where they clarify real alternatives.
- Do not create a framework package until boundary has multiple real producers or consumers.
Choose one existing construct whose symbol effects are currently repeated across at least two analyses. Migrate producer plus one consumer only. Prove parity, stop, and request review before expanding.
Show old repeated decisions, new canonical publication, operation ordering tests, validator behavior, and exact remaining consumers.
Definite initialization, ownership, and usage become separate state machines over shared CFG sites and semantic operations.
- Migrate one construct family and one consumer per approved slice.
- Delete old AST switch/re-derivation only after parity tests pass.
- Definite initialization owns initialized-state lattice and diagnostics.
- Ownership owns liveness, moves, loans, conflicts, and cleanup decisions.
- Usage owns used/unused accounting and warnings.
- Share operation stream, not analysis state or diagnostics.
- Preserve CFG reachability, joins, loops/fixed points, and branch-local state.
- Preserve stable-place overlap and alias invalidation.
- Preserve unreachable-source semantics when a warning is syntax/typecheck-owned.
- Preserve exact source locations and diagnostic codes/messages unless separately approved.
- New existing operation kind must force every consumer to handle, reject, or explicitly ignore it.
- No default switch branch that silently does nothing.
Use an existing symbol-bearing construct as extension simulation. Modify only its upstream normalization to emit already-known operations; confirm migrated consumers need no construct-specific code.
Do not add source syntax.
One cleanup plan owns source destruction decisions. HIR, MIR, and backend consume published facts without rebuilding source semantics.
- Inventory every drop channel, flag, temporary cleanup path, and explicit free.
- Distinguish programmer-requested free, source cleanup plan, and MIR temporary destruction.
- Remove write-only/dead/duplicate cleanup channels after consumer audit.
- Preserve evaluation-before-unwind and reverse lexical cleanup order.
- Preserve return, assignment replacement, discarded value, projection base, match/payload, loop exit, and scope exit behavior.
- Ownership publishes decisions at stable NodeID/SiteID.
- HIR/MIR lower exact evidence.
- Backend only emits physical destruction for supplied MIR/layout.
- Backend may recursively traverse layout to emit nested destruction; it cannot decide source liveness or invent missing cleanup.
- Avoid moving HIR earlier in pipeline if ownership still needs typed AST/CFG and established phase order.
- Add exactly-once runtime regressions for owned nested values and every changed cleanup path.
Provide decision/emission ownership map and prove no duplicate policy remains in touched paths.
Normal go test ./... and pipeline execution catch missing contributor work.
- Each phase artifact documents producer, consumers, valid-from phase, reset rule, and validator.
- Validate keys reference current AST nodes, symbols, CFG sites, or types.
- Validate legal operation/use/capability combinations.
- Validate CFG topology separately from user-facing CFG analysis.
- Validate cleanup keys and result generation; do not duplicate ownership dataflow to “prove” exactly-once behavior.
- Seal IR/MIR node families where useful.
- Add dispatch contracts where Go cannot provide exhaustiveness.
- Add result reset/invalidation tests.
- Mutation-prove guards with temporary probes, then remove probes.
- Create no test-only production API.
- Simulate addition of a node, type, and semantic operation in tests/build probes. Record which failure guides contributor at each missing step.
- Explicitly list any remaining unguarded work.
change kind | required decision | owner | guard | failure message | manual risk
Prove architecture is easier to understand and requires fewer repeated edits.
- Every touched function still matches name, parameters, return, and behavior.
- No pass-through wrappers, stale aliases, ignored params, duplicate maps, shadow registries, or temporary bridges.
- No files/modules split only for appearance.
- No unrelated refactor.
- No source behavior change hidden inside architecture work.
- AST traversal is canonical.
- Semantic facts published once.
- Identity stable across phases.
- Analyses consume operations, not source syntax, when semantics are common.
- Cleanup has one source decision owner.
- HIR/MIR/backend do not re-derive upstream facts.
- Invalid source/LSP recovery remains safe.
- Docs match live source and name honest enforcement gaps.
Choose at least three existing change shapes:
- symbol-bearing statement/expression;
- composite/owned type;
- control-flow construct.
For each, show before/after required files and decisions. Do not claim fewer touches when same work merely moved behind wrappers or generated tables.
gofmt -w <all-touched-go-files>
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -race -count=1 ./internal/project ./internal/pipeline ./internal/lsp ./internal/semantics/...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go vet ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go build ./...
GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go run ./scripts/bundle.go
PEEPER_BIN="$PWD/build/bin/peeper" GOCACHE=/tmp/peeper-maintainability-go-cache CCACHE_DISABLE=1 go test -count=1 ./x_test
git diff --check
git status --short --branchReturn phase-owner map, omission matrix, before/after change paths, validation evidence, Rules check, and honest remaining risks. Wait for explicit commit and publication approval.
Codex will review Claude output after every approved step against:
- live base SHA and exact diff;
- approved step boundary;
- producer/consumer/reset ownership;
- all touched functions, fields, aliases, parameters, and behavior;
- wrapper/duplication/helper rules;
- diagnostics and invalid-source/LSP recovery;
- stable identity and absence of re-derivation;
- evaluation order, CFG topology, flow joins, moves, loans, and cleanup;
- HIR/MIR/backend phase discipline;
- focused/full/race/vet/build/bundle/source-fixture results;
- unrelated work and Git publication state.
Only Step 1 is authorized. Complete Step 1, update
compiler-maintainability.localplan.md, and stop for Codex review. Do not start
Step 2. Do not commit.