Skip to content

Latest commit

 

History

History
239 lines (175 loc) · 13.5 KB

File metadata and controls

239 lines (175 loc) · 13.5 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

See docs/VISION.md for the long-term goals and direction behind each crate in this workspace — read it when a task touches roadmap, priorities, or "why does this exist" questions.

Setup

After cloning, activate the shared git hooks (one-time):

git config core.hooksPath .githooks

Commands

# Format (required before every commit; enforced by pre-commit hook)
cargo fmt --all

# Build
cargo build --workspace

# Test (all, including doc tests)
cargo test --workspace
cargo test --doc --workspace

# Run a single test
cargo test --workspace <test_name>

# Lint (warnings are errors)
# --all-targets is required so tests, doctests, and benches are linted too —
# without it, clippy silently skips everything behind #[cfg(test)] and tests/.
# The begin crate is excluded from the workspace commands and checked separately:
# once with --no-default-features (to avoid platform-specific renderer dependencies)
# and once with its default features (desktop) so #[cfg(feature = "desktop")] code —
# the code path the app actually ships — is linted too. Neither of the other two
# invocations covers that code, so skipping this one lets desktop-only warnings
# through unnoticed.
cargo clippy --workspace --exclude begin --all-targets -- -D warnings
cargo clippy -p begin --no-default-features --all-targets -- -D warnings
cargo clippy -p begin --all-targets -- -D warnings
# The ez-adam crate is likewise checked separately: once with --no-default-features
# (to catch issues in the non-desktop build) and once with its default features
# (desktop) so #[cfg(feature = "desktop")] code -- the code path the app actually
# ships -- is linted too.
cargo clippy -p ez-adam --no-default-features --all-targets -- -D warnings
cargo clippy -p ez-adam --all-targets -- -D warnings
cargo clippy --fix --workspace --exclude begin --all-targets

# Docs
cargo doc --lib --no-deps --open --workspace

# Docs, as CI checks them (rustdoc warnings -- broken/private intra-doc links, bare
# URLs, etc. -- promoted to errors; plain `cargo doc` above exits 0 on these)
RUSTDOCFLAGS="-D warnings" cargo doc --lib --no-deps --workspace

Sanitizer runs require nightly and a target triple (e.g. x86_64-apple-darwin or x86_64-unknown-linux-gnu):

RUSTFLAGS=-Zsanitizer=address cargo +nightly test -Zbuild-std --target <triple> --lib --workspace
RUSTFLAGS=-Zsanitizer=thread  cargo +nightly test -Zbuild-std --target <triple> --workspace
RUSTFLAGS=-Zsanitizer=leak    cargo +nightly test -Zbuild-std --target <triple> --workspace

Superpowers Workflow

When executing an implementation plan (from writing-plans or a design doc) that breaks down into independent or semi-independent tasks, default to the subagent-driven-development skill rather than executing-plans — dispatch each task to a subagent with its own context, review each result, and update the plan/ledger as tasks complete. Only fall back to a single-session, non-delegated execution when the user explicitly asks for it or the plan has no tasks that benefit from separate context (e.g. a single tightly-coupled edit).

Git Workflow

If a request is made that requires any modification, additions, or deletions to files in the project, stop and suggest the user create a worktree first.

Create project worktrees under .claude/worktrees/; do not place them outside the repository.

Never commit directly to main.

Before creating a PR, run the full check suite locally — every command in the Commands section above, including all five clippy invocations (workspace, and begin and ez-adam each with and without their default features).

cargo build --workspace and cargo test --workspace must produce zero compiler warnings — clippy's -D warnings does not catch everything a plain build/test compile can warn about (e.g. an unused mut). Read the build/test output and fix any warnings before opening the PR.

For any multi-phase or multi-step piece of work (a design doc phased into several sub-plans, a plan executed across multiple sessions), create or update a dated handoff document under docs/superpowers/ (e.g. docs/superpowers/YYYY-MM-DD-phase-N-handoff.md) summarizing what's done, what's deliberately deferred, and what's left, before opening a PR for that step — see docs/superpowers/2026-07-18-phase-3-handoff.md for the established format. This is what lets a new conversation/context pick up the remaining work without re-deriving status from git history.

Code Review Findings

When a review (self-review, subagent task review, or a final whole-branch/PR review) surfaces a finding, address it immediately rather than deferring it — this applies to Minor findings just as much as Critical/Important ones. "Minor, park it" is not a default; fix it in the same pass if the fix is small and in scope.

If a finding is genuinely large (a real design change, a non-trivial refactor, a new feature) or out of scope for the current task/PR, don't just leave it noted in a ledger or PR comment — open a GitHub issue for it (gh issue create) so it survives past the current session/worktree, then reference the issue number in the PR/commit rather than silently dropping the finding.

Project Status

This project has not been released yet and has no clients. The API is not stable and may change at any time. The project is in active development and is not yet feature-complete. Prefer redesigning any components rather than patching them or layering on top of them. The goal is to have a clean, correct, and efficient implementation.

Library-First Design

cel-runtime, cel-parser, cel-rs-macros, adam-rs, and adam-lang are general-purpose components meant to be consumed by other, future projects — not just this repository's own begin UI. begin and its bundled examples/*.adm2 files exist to exercise and demonstrate these libraries; they are a test harness, not the target audience.

When designing or implementing a feature in these library crates, solve the general problem the feature actually describes — not just whatever narrower case a specific begin example happens to exercise. Reaching for words like "the practical case" or "the case that matters here" to scope a design down to what one example needs is a signal to stop and generalize instead. Where a fully general solution genuinely isn't feasible in one pass, delineate the boundary explicitly (a documented precondition, a clear error or diagnostic when it's crossed — never silent wrong behavior) and track the remaining generalization as a GitHub issue, rather than quietly special-casing behavior around whatever example is currently at hand.

Architecture

This workspace is centered on cel-runtime and is split into libraries, a façade crate, and supporting tools:

  • cel-rs — root façade crate that re-exports cel-runtime and depends on cel-parser and cel-rs-macros
  • cel-runtime — core stack-based runtime; all evaluation and stack machinery lives here
  • cel-parser — recursive-descent CEL parser, lexer, and parser error types
  • cel-rs-macros — proc-macro crate for compile-time CEL expression validation
  • adam-rs, adam-lang, and adam-lsp — supporting crates for the Adam property model system: the constraint-graph runtime, its DSL, and its language server
  • begin — Dioxus-based UI application
  • editors/vscode-adam-lang — VS Code extension providing syntax highlighting and diagnostics for adam-lang, backed by adam-lsp
  • xtask — repository automation and maintenance tasks

Four-layer stack abstraction (cel-runtime/src/)

The runtime is a stack-based expression evaluator built in four layers of increasing type safety:

Layer File Role
RawStack raw_stack.rs Byte-aligned unsafe stack; push<T> returns padding bool, pop<T> requires it
RawSegment raw_segment.rs Op list + closure storage + per-op dropper functions
DynSegment dyn_segment.rs Runtime type-checking wrapper; maintains stack_ids: Vec<StackInfo>
Segment<Args, Stack> segment.rs Zero-cost compile-time phantom wrapper; Args: IntoList, Stack: List

The compile-time type system uses cons-cell heterogeneous lists (CStackList<H,T> / CNil) defined in c_stack_list.rs and list_traits.rs.

Parser pipeline (cel-parser/src/)

&str → TokenStream (proc_macro2) → LexLexer (flatten + combine multi-char ops) → CELParser (recursive descent) → DynSegment

cel-parser/src/lib.rs contains the grammar entry points and parser pipeline. Function names mirror grammar productions directly (e.g. is_additive_expression).

cel-parser/src/op_table.rs implements OpLookup: a stack of custom ScopeFn scopes (LIFO) backed by static phf_map built-in ops. Overloading is by arity + TypeId.

cel-parser/src/error.rs defines CELError and SourceSpan, plus format_rustc_style() for caret diagnostics.

Code Style

Avoid heap allocations

  • Pass &str / &[T] rather than cloning into String / Vec<T>
  • Use generics or fn pointers instead of Box<dyn Trait> when the type set is statically known
  • Return &[T] or impl Iterator over owned collections when the data already lives elsewhere
  • Borrow inside a block to release the borrow before the next mutable use rather than collecting into a Vec

Documentation comments

Every class, type, and function in every language used by the repository must have an adjacent contract written in the language's documentation or comment syntax. The contract lives adjacent to the declaration so it stays synchronized with the code. Each contract states type invariants at public boundaries, valid inputs, observable postconditions, errors where applicable, and non-constant complexity.

For Rust functions, use a /// doc comment written in contract style.

Required sections (include only those that apply):

  1. Summary — A concise present-tense sentence fragment describing what the function does or returns, ending with a period.
  2. Preconditions — Non-obvious preconditions not implied by the summary, as /// - Precondition: <condition> bullets. Preconditions implied by the summary need not be restated. Violation has unspecified behavior (which may include a panic); do NOT document what happens on violation — instead use debug_assert!() to check preconditions in debug builds.
    • # Errors — conditions that cause an Err return (runtime errors, not precondition violations).
    • # Safety — invariants the caller must uphold for unsafe functions. This is the one place where the consequence of violation (undefined behavior) must be documented.
  3. Postconditions — /// - Postcondition: <condition> bullet in the body, only when not implicit in the summary.
  4. Complexity — /// - Complexity: <description> bullet, required whenever the operation is not O(1). Default assumption is O(1) time and space.

If you cannot write a simple contract for a function, treat that as a signal that the design needs improvement.

Additional rules:

  • For parser functions, the grammar production is the summary: /// \additive_expression = multiplicative_expression { ("+" | "-") multiplicative_expression }.``
  • Use # Examples for all public APIs.
  • Modules use //! with a usage tutorial.

Example:

/// Removes and returns the top element.
///
/// - Precondition: `padding` matches the value returned by the corresponding `push`.
///
/// - Complexity: O(1).
pub fn pop<T>(&mut self, padding: bool) -> T

Unit tests

Derive tests from the contract and public interface only — do not read or consider the implementation. The test suite verifies observable behavior as specified by the contract. If an implementation detail is needed to write a test, clarify the contract or redesign the interface instead.

  • Each # Errors condition should assert the Err variant is returned.
  • Each postcondition should be asserted.
  • Edge cases implied by the summary (empty input, single element, boundary values) should be covered.

Precondition violations have unspecified behavior and should not be tested. Tests written against the implementation risk encoding bugs rather than verifying intent.

Every component needs a contract and unit tests wherever one can be given, including UI/framework glue — "it's just glue code" is not by itself a reason to skip testing. When logic embedded in framework-coupled code (a Dioxus component, an event handler, a callback) amounts to more than a direct passthrough of an existing call — any branching, combining, or suppressing of multiple conditions — extract it into a small pure function with its own doc comment and contract-derived unit tests, and have the framework-coupled code call it. Reserve "no dedicated test" for code that is genuinely a trivial passthrough with no branching of its own (e.g. mapping one existing boolean straight onto one element attribute); anything with its own decision to make should have a contract, even if the surrounding component still can't be tested directly.

Fallible ops

Operations that can fail use .op1r / .op2r variants (returning Result) rather than .op1 / .op2. Arithmetic on signed integers must use checked_* operations, not wrapping arithmetic.