Compiler Maintenance Policy
This document records long-lived maintenance rules for the Nocter compiler. Short-term handoff notes belong in ../TODO.md. Normative language rules belong in ../../spec/.
Goal
Keep the compiler structure maintainable as it grows from a small bootstrap implementation to a multi-phase compiler.
Small diffs are not the primary goal. A change is successful when the next feature can be added without duplicating semantics, hiding phase boundaries, or increasing accidental coupling between unrelated compiler layers.
Module Boundary Rules
Split by responsibility and abstraction layer, not by line count.
Good module boundaries have these properties:
- callers can name what the module owns
- the module exposes a small API instead of forcing callers to inspect internal data
- tests can target the module without constructing unrelated compiler phases
- future features in the same phase naturally reuse the module
Avoid these shapes:
- a driver module that owns language semantics
- a type checker module that formats diagnostics directly
- a backend module that re-parses source-level syntax
- an LSP feature that resolves names without using compiler analysis
- a single file that mixes transport, document state, semantic analysis, and response rendering
LSP Structure
driver/lsp/ should move toward this structure:
driver/lsp/
mod.rs # server loop, request routing, feature orchestration
protocol.rs # JSON-RPC framing, LSP position/range helpers
documents.rs # open document state, URI/path handling, sync params
diagnostics.rs # publishDiagnostics conversion and stale clearing
semantic.rs # semantic token classification
hover.rs # hover contents and hover symbol lookup
definition.rs # go-to-definition lookup and locations
completion.rs # completion items
symbols.rs # document symbols
analysis.rs # bridge from open documents to compiler analysis
Do not create all files preemptively. Extract a module when there is a stable responsibility boundary and the extracted API is smaller than the copied context.
LSP must treat the compiler as the semantic source of truth. TextMate grammar, snippets, and editor-side code may improve presentation, but name resolution, type information, diagnostics, and syntax interpretation must come from compiler modules.
Refactoring Rules
Refactor before adding a feature when the current design would require:
- copying traversal logic
- adding protocol-specific fields to core compiler data
- exposing mutable internals across phases
- adding another large helper to an already mixed-responsibility file
- testing through a full CLI/LSP path when a phase-level test would be clearer
Prefer small APIs with owned result types over returning raw internal maps. Prefer compiler-phase tests for compiler semantics and LSP tests for protocol behavior.
Documentation Ownership
Keep one source of truth for each kind of information:
- root
README.md: short Nocter language introduction, getting-started flow, and documentation map ../../spec/: normative language rules, standard-library rules, and public design principlesREADME.md: development root entrancedocs/README.md: compiler documentation mapdocs/architecture.md: compiler phase ownership and pipeline designdocs/implementation-status.md: current user-visible implementation capabilitydocs/v0-closure.md: fixed v0 implementation completion gatesdocs/backend-v0.md: backend and ABI implementation notesdocs/std-runtime-status.md: implementation status of trackeddevelopment/std/as packaged into a Nocter homedocs/roadmap.md: recommended implementation order../TODO.md: short-lived handoff state
Do not copy large language rules into compiler docs. Link to spec/ and record only implementation status or compiler design.
Progress Recording
Every session that changes compiler behavior or structure should leave the repository easier to resume.
Use this rule:
- update
../TODO.mdfor the next concrete action and current uncommitted state - update
v0-closure.mdwhen the v0 completion gate, target scope, orship/reject/deferdecision changes - update
implementation-status.mdwhen user-visible behavior changes - update
std-runtime-status.mdwhen distributed std runtime behavior changes - update
roadmap.mdwhen the recommended next task changes - update
architecture.mdwhen module responsibilities change
Do not record every command or every passing test in long-lived documents. Record only durable facts: implemented capability, remaining gap, design decision, or next action.
Commit Hygiene
Keep unrelated user changes out of commits.
Create a commit whenever a coherent chunk of work is complete and verified before moving on to the next chunk. This applies especially when a change touches multiple compiler phases, adds a new module, or changes user-visible behavior. Do not wait for a large session to accumulate many independent changes before committing.
Prefer these commit shapes:
- one behavior change plus tests and docs
- one structural refactor with no behavior change
- one documentation update that changes development policy
Avoid commits that mix:
- generated assets and compiler logic
- root project files and compiler internals without a shared purpose
- formatting-only churn and semantic changes
When a rename plus extraction is needed, make the extraction behavior-preserving first, then continue feature work in a later change.