agentleFS
illegalstudioillegalstudio/elephc/AGENTS.md

elephc

AGENTS.md812 starsChanged 42 days agoMITView on GitHub

A PHP-to-native compiler written in Rust. Compiles a static subset of PHP to native assembly for the supported target matrix, producing standalone binaries or host-consumable libraries. No interpreter, no VM, no runtime dependencies. Before contributing, read CONTRIBUTING.md in full. It holds the complete step-by-step recipes — adding an operator, a statement type, a built-in function, an EIR optimization pass, and a crate-backed --with-<crate> feature — plus the assembly comment policy, the coding style, and the Pull Request workflow. This guide…

  • Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.

    Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository6 other files it gives its agents.

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

59 rules. About 0 are common, 26 are distinctive, and 33 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈26

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 33

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All supported targets are first-class targets.

    line 13
  2. Distinctive:

    When examples or internals docs use ARM64 snippets for readability, treat them as examples only.

    line 17
  3. Very specific:

    While developing a feature: run only the tests for that feature or regression (cargo test test_my_feature).

    line 65
  4. Very specific:

    Scope to a single test binary: prefer cargo test --test codegen_tests <filter> over a bare cargo test <filter>.

    line 66
  5. Distinctive:

    For target-sensitive changes: run the smallest relevant macOS/Linux focused tests locally when they materially reduce risk.

    line 67
  6. Distinctive:

    Before committing code changes, run the smallest useful focused tests and hygiene checks that cover the implementation.

    line 73
  7. Distinctive:

    Bug fix: must include a regression test that would have caught the bug

    line 98
  8. Very specific:

    Every bridge entry must declare a reviewed MonitoringPolicy.

    line 170
  9. Very specific:

    If it introduces variables or hidden temporaries, update EIR local/temp declaration in src/ir_lower/context.rs and frame-layout allocation …

    line 262
  10. Very specific:

    When adding a new ExprKind or StmtKind, check every AST-walking pass.

    line 267
  11. Very specific:

    Backend implementation stays typed and target-aware: Add or extend a RuntimeFnId/RuntimeCallTarget implementation under …

    line 314
  12. Distinctive:

    Add codegen + error tests (include a case-insensitive or namespaced call for PHP-visible builtins).

    line 327
  13. Very specific:

    Before opening a PR that adds, removes, or changes PHP-visible builtins, run the update-builtin-docs skill or the equivalent CI sequence: …

    line 333
  14. Distinctive:

    All function-like call surfaces must share the same argument rules instead of normalizing locally in individual emitters:

    line 356
  15. Very specific:

    If a codegen surface uses an internal signature with hidden parameters, such as closure captures, pass the caller-visible regular parameter …

    line 360
  16. Very specific:

    When adding or extending a builtin, verify first_class_callable_builtin_sig() as well as the direct builtin signature so first-class …

    line 365
  17. Very specific:

    If hidden named-argument temporaries are introduced, update src/ir_lower/context.rs and EIR frame placement so slots are allocated before …

    line 371
  18. Very specific:

    Update src/optimize/effects/ if purity, variable reads/writes, call effects, filesystem/runtime state, exceptions/fatals, or by-ref …

    line 377
  19. Distinctive:

    Do not mark a call as pure if it can read or write globals, files, environment, runtime heap state, object properties, array contents, …

    line 378
  20. Very specific:

    Keep constant folding in src/optimize/fold/ limited to PHP-equivalent results.

    line 379
  21. Very specific:

    Add optimizer regression tests under tests/codegen/optimizer/ when DCE, constant propagation, control-flow pruning, or folding can observe …

    line 380
  22. Distinctive:

    Keep cleanup paths balanced across normal returns, early exits, throws/fatals, and control-flow merges.

    line 391
  23. Very specific:

    Add focused tests in tests/codegen/runtime_gc/ for ownership, aliasing, cycles, heap debug, stack args, and COW changes.

    line 392
  24. Distinctive:

    Multi-responsibility files: should be split once they start accumulating unrelated concerns, even if the line count is not yet extreme.

    line 401
  25. Distinctive:

    treat 500 LOC as a warning sign

    line 418
  26. Distinctive:

    treat mixed responsibilities as the real trigger for refactoring

    line 419
  27. Distinctive:

    do not split a file that owns one coherent feature just to satisfy the number

    line 420
  28. Very specific:

    Every repo-owned Rust source file (*.rs) must start with a module-level Rustdoc preamble before any use, mod, item, or test helper code.

    line 426
  29. Very specific:

    Keep preambles concise and factual.

    line 456
  30. Distinctive:

    Every explicit Rust function in repo-owned Rust source files must have a concise docblock explaining what that function does.

    line 460
  31. Very specific:

    Prefer helpers from src/codegen_support/abi/ for registers, stack slots, frame layout, argument materialization, symbol addresses, and …

    line 470
  32. Distinctive:

    Avoid hardcoding ARM64 register names, x86_64 register names, syscall numbers, object formats, or stack alignment rules in shared lowering …

    line 473
  33. Distinctive:

    Do not add an ARM64-only runtime helper, builtin emitter, ABI path, or ownership cleanup path unless the feature is intentionally …

    line 474
  34. Very specific:

    Labels: use ctx.next_label("prefix") — global counter prevents collisions across functions

    line 486
  35. Very specific:

    Every emitter.instruction(...) call must have an inline // comment aligned to column 81, with // -- description -- block comments before …

    line 491
  36. Very specific:

    PHP-derived syntax must be 100% compatible with PHP: When elephc implements a PHP construct (variables, operators, keywords, built-ins), it …

    line 505
  37. Very specific:

    When in doubt, test with php -r '...' to verify behavior.

    line 513
  38. Very specific:

    All docs files are Markdown with YAML frontmatter compatible with Astro content collections.

    line 570
  39. Very specific:

    No navigation links ([← Back], Next:, etc.) — Astro handles navigation

    line 583
  40. Distinctive:

    Use standard Markdown (CommonMark).

    line 584
  41. Distinctive:

    Documentation must be kept up to date: When adding a new feature:

    line 588
  42. Distinctive:

    If a feature was previously listed as "not supported", remove that note.

    line 594
  43. Very specific:

    If there are known incompatibilities with PHP, document them in docs/php/types.md (incompatibilities section).

    line 595
  44. Very specific:

    Update docs/README.md index if adding a new page.

    line 596
  45. Very specific:

    Add planned work as [ ] under the version where it is expected to ship.

    line 602
  46. Very specific:

    When an implementation lands, mark its existing item [x].

    line 603
  47. Distinctive:

    When all items in a version are completed, the version is considered done.

    line 606
  48. Very specific:

    Keep [Unreleased] empty between releases.

    line 613
  49. Distinctive:

    Only an explicit release-preparation task may add, move, rewrite, or remove changelog bullets.

    line 614
  50. Very specific:

    If the target version is not known, stop and ask for it before editing the changelog.

    line 616
  51. Distinctive:

    When cutting a release:

    line 618
  52. Distinctive:

    Add a new section at the top (under the header), above the previous version:

    line 620
  53. Distinctive:

    Keep entries concise (usually one or two bullets), describe what shipped — not the implementation — and use the absolute release date.

    line 627
  54. Distinctive:

    Add a matching compare link at the bottom of the file, also newest first, immediately above the previous version's link:

    line 628
  55. Very specific:

    Never change elephc's Cargo package version in Cargo.toml or Cargo.lock.

    line 635
  56. Very specific:

    Use commit message prefixes such as feat:, fix:, chore:, docs:, refactor:, or test:

    line 640
  57. Distinctive:

    Keep commit messages concise

    line 641
  58. Distinctive:

    Run the focused pre-commit verification above before committing code changes.

    line 642
  59. Very specific:

    Never run cargo fmt in this repo.

    line 644

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

elephc — Developer Guide

What is this

A PHP-to-native compiler written in Rust. Compiles a static subset of PHP to native assembly for the supported target matrix, producing standalone binaries or host-consumable libraries. No interpreter, no VM, no runtime dependencies.

Read CONTRIBUTING.md first

Before contributing, read CONTRIBUTING.md in full. It holds the complete step-by-step recipes — adding an operator, a statement type, a built-in function, an EIR optimization pass, and a crate-backed --with-<crate> feature — plus the assembly comment policy, the coding style, and the Pull Request workflow. This guide covers the architecture, invariants, and context that go alongside those recipes; it deliberately does not repeat them.

Supported target policy

All supported targets are first-class targets. The supported target matrix is currently macos-aarch64, ios-arm64, ios-sim-arm64, linux-aarch64, and linux-x86_64.

Do not design or land codegen/runtime features as ARM64-first with x86_64 treated as a later port. New features, builtins, runtime helpers, optimizer assumptions that affect emitted code, ABI behavior, and ownership/GC paths must either support every supported target in the same change or clearly isolate an intentionally unsupported path with diagnostics, tests, and documentation. A feature is not considered done while any supported target has a missing runtime symbol, reduced semantics, stale documentation, or an untested target-specific lowering path.

When examples or internals docs use ARM64 snippets for readability, treat them as examples only. Implementation work must keep the target-aware ABI/runtime boundaries authoritative.

Build & run

cargo build              # dev build
cargo build --release    # optimized build
cargo run -- file.php    # compile a PHP file

The compiler outputs a native binary next to the source file (e.g., file.php → file).

Test policy

Every feature must have tests before it's considered done. The test suite is the primary quality gate.

Local agent runs should stay focused. By default, run only the build checks and tests needed to validate the implementation or regression you touched, then rely on CI for the complete matrix. Do not run the full local suite (cargo test, cargo test -- --include-ignored, or full Linux Docker scripts) unless the user explicitly asks for it, you are doing a release/pre-release verification, CI is unavailable for the needed signal, or a broad/high-risk change cannot be validated responsibly with focused tests.

Running tests

cargo test test_fizzbuzz                     # run a focused test by name
cargo test --test codegen_tests test_name    # run a focused end-to-end codegen test
cargo nextest run --profile ci test_name     # run a focused nextest test with per-test timeout
cargo test                                  # full local suite; only when explicitly requested/necessary
cargo test -- --include-ignored             # ignored tests; only when relevant or requested

Linux target-specific regressions can be checked through the Docker scripts in scripts/. Prefer filters during implementation; the unfiltered commands run full Linux suites and should be reserved for explicit requests or genuinely necessary local target validation.

./scripts/test-linux-x86_64.sh iterable            # run tests matching a filter
./scripts/test-linux-arm64.sh test_my_feature      # run tests matching a filter
./scripts/test-linux-x86_64.sh                     # full Linux x86_64 suite; only when necessary/requested
./scripts/test-linux-arm64.sh                      # full Linux ARM64 suite; only when necessary/requested
./scripts/test-linux-x86_64.sh --rebuild           # rebuild the Docker image first
./scripts/test-linux-arm64.sh --rebuild            # rebuild the Docker image first

Some tests are marked #[ignore] because they require external libraries (e.g., SDL2) not available in CI. Run ignored tests only when the change touches that surface, during explicit release verification, or when the user asks for them.

Test strategy during development

The full test suite is slow because each codegen test spawns as + ld + runs the binary. CI runs sharded executable codegen tests on macos-aarch64, linux-x86_64, and linux-aarch64, and covers ios-arm64 and ios-sim-arm64 through target-specific compile and emitter tests. Pull Requests run the Linux matrix, the core live database suites and the macOS build; the macOS test matrix, the iOS links and the optional-driver live database suites run in the merge queue, or on the PR with the ci:macos / ci:pdo-live labels (CONTRIBUTING.md § "CI tiers and the merge queue"). Local implementation work should optimize for fast, relevant signal:

  1. While developing a feature: run only the tests for that feature or regression (cargo test test_my_feature).
  2. Scope to a single test binary: prefer cargo test --test codegen_tests <filter> over a bare cargo test <filter>. A bare filter rebuilds and links all six test binaries every cycle (~2.5s of wasted link time); --test <binary> links only the one you need. Most codegen work lives in codegen_tests.
  3. For target-sensitive changes: run the smallest relevant macOS/Linux focused tests locally when they materially reduce risk; otherwise let CI provide complete target coverage.
  4. For broad infrastructure changes: validate the edited workflow/config directly (YAML parse, cargo metadata, nextest config checks, etc.) instead of running unrelated Rust suites.
  5. PHP cross-check: opt in narrowly with ELEPHC_PHP_CHECK=1 cargo test <filter> when PHP equivalence is the question.

Pre-commit verification

Before committing code changes, run the smallest useful focused tests and hygiene checks that cover the implementation. Do not run full local suites by default; CI is responsible for the complete sharded matrix: the PR tier on every push, every target in the merge queue.

cargo build                                    # for code changes that should compile warning-free
cargo test <feature_or_regression_filter>      # or the narrower --test <binary> form
git diff --check

For docs-only, workflow-only, or configuration-only changes, replace Rust test runs with the relevant syntax/metadata checks. For codegen changes, also verify assembly-comment coverage/alignment for any files you touched. If the change can affect generated assembly, runtime helpers, ABI behavior, linking, ownership/GC, or target-specific libraries, run focused tests for affected supported targets when local evidence is needed; otherwise rely on CI for sharded Linux x86_64/Linux ARM64/macOS ARM64 executable coverage and the target-specific iOS device/Simulator compile and emitter checks. For target-sensitive Darwin work, add the ci:macos label so the macOS tier runs on the PR before it reaches the merge queue.

Test structure

FileWhat it testsHow
tests/lexer_tests.rsTokenizationAsserts token sequences from source strings
tests/parser_tests.rsAST constructionAsserts AST node structure and operator precedence
tests/codegen_tests.rs, tests/codegen/Full pipeline (end-to-end)Compiles PHP → binary, runs it, asserts stdout
tests/error_tests.rs, tests/error_tests/Error reportingAsserts that invalid programs produce the right error messages

Test coverage requirements

  • New language construct (keyword, operator, statement): needs lexer, parser, codegen, AND error tests
  • New operator: needs a Pratt parser binding power test verifying precedence relative to adjacent operators
  • New statement type: needs at least one codegen test showing correct output, one test for edge cases (empty body, nested), and one error test for malformed syntax
  • New built-in function: needs codegen tests for normal use and error test for wrong argument count/types
  • Bug fix: must include a regression test that would have caught the bug
  • Every feature also needs an example in examples/. If an existing example can showcase the new feature naturally, update it. Otherwise, create a new examples/<name>/main.php with its own .gitignore (containing *.s, *.o, main). Examples should be small, readable programs that demonstrate real use cases — not just test cases.

Writing codegen tests

Codegen tests compile inline PHP source and assert stdout:

#[test]
fn test_my_feature() {
    let out = compile_and_run("<?php echo 1 + 2;");
    assert_eq!(out, "3");
}

Each test runs in an isolated temp directory. Tests run in parallel — the compile_and_run helper handles isolation automatically.

A fixture that contains an eval() whose source is known only at runtime (the $argc > 0 ? '...' : '' pattern, eval($code)) lowers the whole builtin catalog and costs tens of seconds per compile; a literal eval('...') costs about as much as the same code inline. When several such fixtures only compare stdout, bundle them with assert_bundle / assert_bundle_with (tests/codegen/support/bundle.rs): the cases compile and run as one program, stdout is split per case, and a failure names the case. Cases share one scope and declaration namespace (unique function/class names, no declare(strict_types=1), no exit), so keep a fixture on its own when it needs per-program heap or GC statistics, a non-zero exit, or stderr assertions. Name a bundled test test_<family>_bundle: .config/nextest.toml gives that suffix a longer timeout budget, because one bundle runs every case it replaced.

Architecture

PHP source → Lexer → Parser → Magic constants → Conditional compilation → Resolver
→ NameResolver → Constant folding → Type Checker / warnings → AST optimizer passes
→ EIR lowering / validation → EIR optimization passes → EIR codegen → runtime cache
→ assembler/linker → binary

Backend policy

EIR is the active backend for compiler implementation work. New language features, builtins, runtime semantics, optimizer behavior, ownership paths, and target support must be implemented through the AST → EIR → EIR codegen path.

Shared target/runtime infrastructure remains active under src/codegen_support/, including ABI helpers, runtime emitters, runtime data emission, target convention helpers, and linking support. New PHP-visible behavior must be driven by EIR lowering and src/codegen/.

Key modules

ModuleEntry pointResponsibility
src/pipeline.rscompile()Orchestrates frontend passes, type checking, optimization, runtime cache, codegen, and linking
src/lexer/tokenize()Source → Vec<(Token, Span)>
src/parser/parse()Tokens → Program (Vec of Stmt). Pratt parser for expressions
src/magic_constants/, src/magic_constants.rssubstitute_file_and_scope_constants()Lowers PHP magic constants before resolver/name-resolver/optimizer passes
src/conditional.rsapply()Applies compiler ifdef conditional branches
src/resolver/resolve()Resolves include/require, discovers declarations, and tracks include-loaded function variants. Runs before namespace/name canonicalization
src/name_resolver/resolve()Applies namespace/use rules, rewrites references to canonical fully-qualified names, handles PHP-style builtin fallback, and flattens namespace-only AST nodes before type checking
src/types/check()Type checking, returns CheckResult with TypeEnv, function/class/interface/enum/FFI metadata, warnings, required libraries, and the internal Mixed type for heterogeneous assoc-array values
src/optimize/, src/optimize.rsfold_constants(), propagate_constants(), eliminate_dead_code()AST-level constant folding/propagation, control-flow pruning/normalization, DCE, and effect modeling
src/ir/IR types / builders / validatorEIR program, function, block, instruction, terminator, value, local, effect, ownership, and textual-format definitions
src/ir_lower/lower_program()Active AST → EIR lowering, including local slot creation, hidden temporaries, ownership annotations, and PHP call semantics
src/ir_passes/optimize_module() / allocate_registers()EIR analyses and transformations over lowered functions: fixed-point optimization pass driver (identity folding, …; gated by --ir-opt) and linear-scan register allocation, before codegen
src/codegen/generate()Active EIR → target assembly backend
src/codegen_support/abi/ABI helpersTarget-specific argument materialization, frame layout, registers, stack slots, symbols, and call helpers
src/codegen_support/runtime/Runtime emittersShared __rt_* helpers and runtime data used by generated programs
src/codegen/program_usage/Program scansCollects codegen metadata such as required classes and variables before emission
src/runtime_cache.rsprepare_runtime_object()Builds/reuses the target runtime object before final linking
src/errors/report()Error formatting with line:col
src/span.rsSpanSource position (line, col) attached to all AST nodes

Bridge crates and --with-<crate> flags

Optional, heavyweight functionality that is naturally expressed with Rust libraries lives in a workspace crate under crates/elephc-<name>/, compiled as a staticlib and linked into the user program on demand. Every such bridge is described by one entry in the BRIDGES table in src/linker/bridges.rs; link() and the discovery/auto-build helpers are fully table-driven, so adding a bridge is a single table entry rather than new linker logic.

Every bridge entry must declare a reviewed MonitoringPolicy. Use generic timing when ordinary function timing is sufficient, typed I/O policy when the bridge performs database or network work, and an infrastructure policy with a nonempty reason only for monitor/runtime plumbing. The bridge-catalog gate rejects an unspecified policy. Bridge-backed RuntimeFnId values must also declare their operation policy, and any runtime operation carrying BLOCKING_IO or NETWORK_IO effects must use evented monitoring. I/O bridges share the dependency-neutral elephc-monitoring-contract helper and distinct database/network runtime slots, which must remain dormant outside an active capture.

A bridge is linked when its lib_name appears in extra_link_libs. That set is populated automatically by feature detection: the type checker records a needed library via require_builtin_library(...) (e.g. hash builtins → elephc_crypto) or through an injected extern "elephc_<name>" { ... } prelude block (e.g. the PDO/tz/image preludes), so programs that do not use a feature never link its bridge.

--with-<crate> force-enables a bridge regardless of detection. It force-links the staticlib (whole-archived so it is not dead-stripped) and, for crates whose PHP surface comes from a prelude (pdo, tz, image, xml), force-injects that prelude so the API is declared even when usage was not detected. The flag name is the bridge's flag_name (crate_name minus the elephc- prefix): --with-pdo, --with-tls, --with-crypto, --with-iconv, --with-phar, --with-tz, --with-image, --with-curl, --with-xml. --with-web is an alias for --web (the full server mode, which owns the program entry point). An unknown --with-<name> is a hard CLI error listing the valid crates. The end-to-end wiring is CLI (src/cli.rs, with_crates) → pipeline (src/pipeline.rs: force-link + prelude forcing) → linker (src/linker.rs: forced_whole_archive).

--with-regex is the explicit non-bridge exception: it enables RuntimeFeatures::regex for opaque dynamic eval source, resolves the managed pcre2 package, and registers its shim callbacks with Magician. Static regex use still auto-detects the same feature. Merely declaring pcre2 does not link it, and dynamic eval without the capability leaves regex builtins unavailable at runtime.

curl is the first ordinary bridge crate that ALSO needs a managed native package: --with-curl (or ordinary detection of a curl_* call, via src/curl_prelude/detect.rs) force-links elephc_curl, and src/pipeline/backend.rs mirrors that into NativeRequirement::package("curl") (from the BRIDGE_NATIVE_PACKAGES table in src/linker/native_packages.rs) so the final link also resolves the managed curl package's libcurl.a, plus the openssl/zlib archives it declares as dependencies, in the fixed order libcurl.a -> libssl.a -> libcrypto.a -> libz.a. There is no system fallback: a missing curl package fails closed with the same elephc native add curl recovery style as PCRE2, never a -lcurl.

xml is the second such bridge: --with-xml (or ordinary detection of an xml_* / xmlwriter_* call or of the XMLParser / XMLWriter classes, via src/xml_prelude/detect.rs) force-links elephc_xml, and src/pipeline/backend.rs mirrors that into NativeRequirement::package("libxml2") so the final link also resolves the managed libxml2 package's two archives in the fixed order libelephc_libxml2_shim.a -> libxml2.a. The bridge reaches libxml2 only through the recipe-compiled shim src/native_deps/recipes/libxml2_shim.c (which owns every access to libxml2's struct internals) plus libxml2's opaque public API, both declared in crates/elephc-xml/src/ffi.rs without linking — no bindgen, no -sys crate, no headers at cargo build time. Apple targets add -liconv for libxml2's encoding handlers through the entry's BridgeStaticlib.apple_libraries; glibc provides iconv from libc. libxml2 has no catalog dependencies, and a missing package fails closed with the elephc native add libxml2 recovery, never a -lxml2.

gmp, fileinfo (libmagic) and openssl (openssl-crypto) follow the same shape. Every bridge-to-package pairing lives only in BRIDGE_NATIVE_PACKAGES, whose order is the final link order; --print-capabilities reports each pairing as a native:<package> field, and the release probe adds exactly those packages, so a new pairing needs no edit in scripts/verify-release-artifact.sh.

Codegen layout

  • src/ir_lower/ is the active high-level lowering layer. Add PHP-visible semantics there.
  • src/codegen/lower_inst/ and src/codegen/lower_term.rs are the active EIR instruction/terminator assembly lowerers.
  • src/codegen/context.rs, src/codegen/frame.rs, and src/codegen/value_placement.rs carry active backend state, frame layout, and value placement.
  • src/codegen_support/runtime/mod.rs emits shared runtime code (__rt_* routines)
  • src/codegen_support/runtime/data/ emits shared runtime .data / .bss symbols and metadata tables
  • src/codegen_support/abi/ centralizes target-specific register, stack, frame, symbol, and call mechanics. Prefer these helpers over hardcoding ARM64 or x86_64 details in feature emitters.

Adding a new operator

Key invariants:

  • A new operator touches the whole pipeline: token (src/lexer/), Pratt binding power (infix_bp() in src/parser/expr/pratt.rs), BinOp variant (src/parser/ast.rs), type inference (src/types/checker/, usually inference/ops.rs), optimizer folding/effects (src/optimize/), and EIR lowering (src/ir_lower/expr/ + src/codegen/lower_inst/).
  • Precedence and associativity must match PHP; keep folds PHP-equivalent and cross-check edge cases with php -r.
  • Needs a Pratt binding-power test asserting precedence relative to adjacent operators, plus tests in all 4 test files (lexer, parser, codegen, error).

Adding a new statement type

Key invariants:

  • A new statement touches parser (StmtKind in src/parser/ast.rs + src/parser/stmt.rs), resolver/name-resolver (if it holds names, declarations, includes, function variants, or expressions), type checker (src/types/checker/), optimizer/effects/warnings (src/optimize/), and EIR lowering (src/ir_lower/stmt/ + src/codegen/).
  • If it introduces variables or hidden temporaries, update EIR local/temp declaration in src/ir_lower/context.rs and frame-layout allocation before frame sizing.
  • Also audit every AST-walking pass (see "Adding or changing an AST node") — a missed pass usually causes silent miscompilation rather than a compile error.

Adding or changing an AST node

When adding a new ExprKind or StmtKind, check every AST-walking pass. The compiler has many passes that deliberately recurse by variant, and missing one usually creates silent miscompilation rather than a compile error.

Common places to audit:

  • Parser construction and lowering in src/parser/
  • Resolver/include discovery and function-variant handling in src/resolver/
  • Namespace/use/FQN rewriting in src/name_resolver/
  • Type checking, inference, return analysis, warnings, and type compatibility in src/types/
  • Optimizer folding, propagation, DCE, control-flow normalization, and effect modeling in src/optimize/
  • Program usage scans in src/codegen/program_usage/
  • Local/hidden-slot declaration in src/ir_lower/context.rs and frame placement in src/codegen/
  • Ownership metadata in src/ir_lower/ownership.rs, EIR ownership lowering in src/codegen/lower_inst/ownership.rs, and related runtime/GC paths
  • EIR lowering in src/ir_lower/ plus EIR backend lowering in src/codegen/
  • Lexer/parser/codegen/error/regression tests, depending on the surface area

Adding a new built-in function

PHP builtin surfaces are declared once in the dependency-neutral crates/elephc-builtin-contract catalog. The compiler home file at src/builtins/<area>/<name>.rs joins AOT semantics with builtin!; Magician's home file joins eval hooks with eval_builtin!. Both bindings key on the same stable BuiltinId. Name lookup, FunctionSig, strict-PHP visibility, backend support, and generated docs derive from that contract. Do not re-add builtin names to hand-maintained tables (catalog.rs, signatures.rs, per-area check_builtin arms) — they are superseded by the registry.

Key invariants:

  • One contract, one home file per backend. The neutral contract owns PHP-visible names, parameters, defaults, passing modes, arity, returns, visibility, requirements, and docs. The AOT home file repeats none of that metadata; its mandatory semantics: descriptor owns validation, result typing, effects, ownership/aliasing, requirements, target strategy/support, runtime-function inventory, argument lowering, callable policy, and backend-neutral EIR lowering. Home files must not import crate::codegen.
  • Use typed EIR targets. Most builtins use runtime_fn_semantics(RuntimeFnId::...); reusable predicates use type_predicate_semantics(...); a genuinely compositional builtin may provide a BuiltinLowering::Eir hook that emits EIR primitives/graphs through BuiltinLoweringContext. PHP names never select backend emitters.
  • Result types are shared contracts. BuiltinResultType::Declared, Checked, or Shared is authoritative for checker/EIR agreement. A check hook is embedded into the descriptor automatically; use a shared result resolver when the backend storage representation differs from the checker-facing PHP type.
  • Ownership belongs in the semantic descriptor. Choose NonHeap, Fresh, Borrowed, Independent, Aliases, or MayAliasArguments according to the actual returned storage. Cleanup, optimizer, and callable consumers read that same contract.
  • Backend implementation stays typed and target-aware. Add or extend a RuntimeFnId/RuntimeCallTarget implementation under src/codegen/lower_inst/runtime_functions/ or runtime_calls.rs, plus any shared __rt_* routine under src/codegen_support/runtime/. Every supported target must be handled by that typed backend path.
  • isset/unset/empty/exit/die are language constructs that stay checker-resident (numeric/arrays check_builtin), not in the registry. buffer_new is catalog-name-only (its call form is dedicated syntax); buffer_len and buffer_free are ordinary registry builtins.
  • elephc-only contracts declare extension: true so --strict-php hides them from AOT and eval without a second pinned list. Injected preludes must call internal: true __elephc_* aliases instead of PHP-visible extension builtins; a parity gate scans the prelude sources to enforce this.
  • Add codegen + error tests (include a case-insensitive or namespaced call for PHP-visible builtins); keep shared-contract/backend-support parity gates green.
  • Magician bindings contribute only contract, area, and direct/value hooks. Use RuntimeBuiltinId plus the versioned boxed-cell C ABI when runtime helper semantics agree, remove superseded hooks, and retain documented adapters only for by-ref/lvalue, callable, reflection, resource, eval-declaration, or strict signature-subset behavior.
  • Before opening a PR that adds, removes, or changes PHP-visible builtins, run the update-builtin-docs skill or the equivalent CI sequence: cargo build --example gen_builtins --features curl, python3 scripts/docs/extract_builtins.py --render --force, python3 scripts/docs/audit_builtins.py, and python3 scripts/docs/elephc_builtins/validate_site_compat.py, plus python3 scripts/audit_builtin_eir_boundary.py --enforce-target-architecture. Commit the generated docs and registry.

Adding a new EIR optimization pass

IR-level transformations run after EIR lowering/validation through a fixed-point driver (src/ir_passes/), not in the AST optimizer.

Key invariants:

  • Implement the IrPass trait in src/ir_passes/<pass>.rs and register it in default_passes() (src/ir_passes/driver.rs); the driver re-runs the whole set per function to a fixed point, capped by MAX_PASS_ITERATIONS.
  • Reuse src/ir_passes/rewrite.rs (RAUW via replace_all_uses + shared fold helpers) instead of re-walking operands/terminators. Keep rewrites dominance-safe and PHP-equivalent; cross-check edge cases (division by zero, signed-zero/NaN floats) with php -r.
  • Debug/test builds re-validate each function after every pass and panic (naming the pass) on malformed IR or non-convergence; both guards compile out of --release.
  • Behavior must be identical with --ir-opt on or off except for performance (gate: --ir-opt=on|off / --no-ir-opt, env ELEPHC_IR_OPT, default on). Add unit tests (src/ir_passes/tests/) and e2e tests (tests/codegen/optimizer/) using runtime-unknown values (e.g. $argc) so the construct survives AST folding.

Call argument semantics

All function-like call surfaces must share the same argument rules instead of normalizing locally in individual emitters:

  • Shared named/positional/spread semantics live in src/types/call_args/ whenever they are not codegen-specific.
  • src/types/call_args/ owns the semantic planner (CallArgPlan / plan_call_args). The checker and EIR lowering should consume that plan; they should not rebuild named-argument matching, duplicate detection, static associative-spread expansion, spread bounds, or the regular/variadic split locally.
  • If a codegen surface uses an internal signature with hidden parameters, such as closure captures, pass the caller-visible regular parameter count through plan_call_args_with_regular_param_count() instead of letting the planner infer it from the full internal signature.
  • Type-checker validation and diagnostic mapping lives in src/types/checker/functions/call_validation.rs; it maps planner errors to CompileError diagnostics instead of owning the semantic rules.
  • src/ir_lower/expr/ owns active EIR call-argument lowering: planner consumption, source-order named/spread lowering, spread checks, and hidden-temp creation. src/codegen/ then materializes the lowered call through target-aware ABI helpers.
  • User-defined calls, builtins, and extern calls must use the same named/spread normalization rules before any callee-specific lowering runs.
  • PHP call unpacking with static string keys maps to named arguments (f(...["a" => 1]) behaves like f(a: 1)). Static numeric keys remain positional, and duplicate static string keys inside one unpack use PHP's last-wins behavior before planning.
  • When adding or extending a builtin, verify first_class_callable_builtin_sig() as well as the direct builtin signature so first-class callable syntax and callable aliases stay coherent.
  • PHP source evaluation order is distinct from ABI parameter order. Preserve side effects in source order, then materialize arguments in parameter/ABI order; extern calls follow the same rule before C ABI register loading.
  • Spread arguments before named arguments must be evaluated once, length/overwrite checks must happen at the PHP-observable point, and later named-argument side effects must not be skipped by early codegen checks. Too-short spreads for required parameters must fail instead of reading past the array payload.
  • A positional spread into a variadic callee fills visible regular parameters first; only the remaining tail becomes the variadic array.
  • User-defined variadics accept unknown named arguments as string-keyed variadic entries; internal/builtin variadics reject unknown named arguments like PHP internals.
  • Ref-like parameters, including mutating builtin parameters, must avoid value-temp preevaluation so the original storage is passed/mutated.
  • If hidden named-argument temporaries are introduced, update src/ir_lower/context.rs and EIR frame placement so slots are allocated before frame-size calculation.

Optimizer and effects

The optimizer assumes side effects are modeled conservatively. When changing calls, operators, expressions, statements, or builtins:

  • Update src/optimize/effects/ if purity, variable reads/writes, call effects, filesystem/runtime state, exceptions/fatals, or by-ref mutation behavior changes.
  • Do not mark a call as pure if it can read or write globals, files, environment, runtime heap state, object properties, array contents, argument storage, or can emit visible output.
  • Keep constant folding in src/optimize/fold/ limited to PHP-equivalent results. If PHP behavior is edge-case sensitive, cross-check with php -r.
  • Add optimizer regression tests under tests/codegen/optimizer/ when DCE, constant propagation, control-flow pruning, or folding can observe the change.
  • Magic constants must be lowered before optimizer passes. Do not introduce optimizer paths that expect raw ExprKind::MagicConstant.
  • src/optimize/ is the AST optimizer only. IR-level (EIR) transformations live in src/ir_passes/ behind the fixed-point pass driver; see "Adding a new EIR optimization pass". Folds that need value identity, basic blocks, or dominance belong there, not in src/optimize/.

Runtime ownership, GC, and COW

Refcounted runtime values are not plain scalars. When changing arrays, strings, objects, Mixed, Iterable, call returns, or temporaries:

  • Preserve the boxed Mixed cell contract: the runtime tag and payload shape must stay consistent across codegen and runtime helpers.
  • Respect copy-on-write before mutating arrays or hashes. Use the existing ensure-unique helpers instead of mutating shared storage directly.
  • Track whether a value is owned, borrowed, persistent, or a temporary result. Release only values this code path owns.
  • Keep cleanup paths balanced across normal returns, early exits, throws/fatals, and control-flow merges.
  • Add focused tests in tests/codegen/runtime_gc/ for ownership, aliasing, cycles, heap debug, stack args, and COW changes.

File size policy

As a general rule, aim to keep source files under 500 lines of code. This is a maintainability guideline, not a blind numeric rule.

The real goal is to avoid files that become hard to reason about because they mix multiple responsibilities. In practice:

  • Dispatcher/orchestration files (mod.rs, top-level drivers, large checker/codegen coordinators) should stay slim. If they grow large, split them aggressively.
  • Multi-responsibility files should be split once they start accumulating unrelated concerns, even if the line count is not yet extreme.
  • Leaf files that implement one cohesive feature are allowed to exceed 500 lines when splitting them would create artificial fragmentation.

Examples of files that may reasonably stay above the soft limit:

  • a single runtime emitter implementing one substantial builtin or runtime routine
  • a single compiler pass file that is still clearly about one feature and one code path
  • a self-contained parser/lowering/runtime leaf where splitting would only spread one mental model across several tiny files

Examples of files that should usually be split:

  • a file that mixes dispatch, validation, data collection, and post-processing
  • a file that contains several unrelated builtins or runtime helpers
  • a file that acts as a “miscellaneous bucket” for code that did not get a home

So the policy is:

  • treat 500 LOC as a warning sign
  • treat mixed responsibilities as the real trigger for refactoring
  • do not split a file that owns one coherent feature just to satisfy the number

In short: prefer cohesion over mechanical line-count compliance. A 650-line mono-feature leaf is acceptable; a 350-line multi-purpose orchestrator is already a refactor candidate.

Rust module preamble policy

Every repo-owned Rust source file (*.rs) must start with a module-level Rustdoc preamble before any use, mod, item, or test helper code. Use //! comments so the explanation is attached to the module in rustdoc.

The preamble is mandatory for all new Rust files and must be added or preserved when touching existing Rust files. Release verification should report any Rust file that is missing it.

Standard format:

//! Purpose:
//! Explain what this file owns in 2-4 lines.
//!
//! Called from:
//! - `crate::path::caller()` or the relevant test/module entry point.
//!
//! Key details:
//! - Important invariants, ordering constraints, ownership/ABI/runtime rules, or coupling.

For test files, use the same structure but describe the test surface instead of production callers:

//! Purpose:
//! Integration or regression tests for the relevant feature area.
//!
//! Called from:
//! - `cargo test` through Rust's test harness.
//!
//! Key details:
//! - Fixture layout, platform assumptions, ignored-test requirements, or why edge cases exist.

Keep preambles concise and factual. Do not include refactor history, stale line numbers, or broad architecture prose that belongs in docs/internals/.

Rust function docblock policy

Every explicit Rust function in repo-owned Rust source files must have a concise docblock explaining what that function does. This applies equally to public functions, restricted-visibility functions (pub(crate), pub(super), etc.), private helper functions, impl methods, trait methods, and test functions.

Use /// Rustdoc comments immediately before the function item or its item attributes. Keep the docblock specific to the function's actual responsibility, inputs, outputs, side effects, ownership/ABI/runtime constraints, and failure behavior when those details matter. Do not use vague filler such as "handles logic" or "processes data".

When documenting test functions, describe the behavior or regression being verified and any important fixture/platform assumptions. For explanatory comments inside a function body, use normal // comments, not ///; Rustdoc comments inside function bodies produce warnings or errors because they do not document an item.

Adding or updating function docblocks must not change code behavior. Do not alter function signatures, visibility, attributes, derives, module declarations, control-flow braces, strings, assembly instructions, or instruction-comment alignment while adding documentation. If a doc-only change causes cargo check, cargo check --tests, or git diff --check to fail, fix the documentation placement or comment style rather than changing code to fit the comment.

Codegen conventions (target-aware)

  • Prefer helpers from src/codegen_support/abi/ for registers, stack slots, frame layout, argument materialization, symbol addresses, and calls.
  • New feature emitters belong in src/codegen/.
  • New feature emitters must support every supported target through emitter.target or clearly isolate target-specific code behind existing target helpers with explicit tests and diagnostics.
  • Avoid hardcoding ARM64 register names, x86_64 register names, syscall numbers, object formats, or stack alignment rules in shared lowering code.
  • Do not add an ARM64-only runtime helper, builtin emitter, ABI path, or ownership cleanup path unless the feature is intentionally target-gated and documented as unsupported elsewhere.
  • Target-sensitive changes need coverage for every supported target they can affect. During local implementation, run focused target checks only when they are needed for confidence; rely on CI for the complete supported-target matrix unless the user requests local Docker runs or CI cannot provide the needed signal.

ARM64 quick reference

  • Integers: result in x0
  • Floats: result in d0
  • Strings: pointer in x1, length in x2
  • Function args: x0-x7 (int = 1 reg, string = 2 regs), d0-d7 (floats)
  • Return value: same as expression result (x0, d0, or x1/x2)
  • Stack frame: x29 = frame pointer, x30 = link register, locals at negative offsets from x29
  • ABI helpers: src/codegen_support/abi/ centralizes load/store/write per type
  • Labels: use ctx.next_label("prefix") — global counter prevents collisions across functions
  • Mixed values: PhpType::Mixed is an internal boxed runtime shape used for heterogeneous associative-array values; codegen/runtime must preserve the boxed cell contract instead of treating it like a plain scalar

Assembly comment policy

Every emitter.instruction(...) call must have an inline // comment aligned to column 81, with // -- description -- block comments before related groups.

Examples

Each example lives in examples/<name>/main.php with its own .gitignore. To run:

cargo run -- examples/fizzbuzz/main.php
./examples/fizzbuzz/main

PHP compatibility

PHP-derived syntax must be 100% compatible with PHP. When elephc implements a PHP construct (variables, operators, keywords, built-ins), it must behave identically to PHP. This means:

  • Variable names, keywords, operators, and built-in function names must match PHP exactly
  • Superglobals ($argc, $argv) must use PHP's syntax (e.g., $argv[0], not argv(0))
  • Operator precedence and associativity must match PHP
  • String escape sequences must match PHP behavior
  • Built-in function signatures must match PHP (argument count, order, types)

When in doubt, test with php -r '...' to verify behavior.

elephc also provides compiler-specific extensions beyond standard PHP (e.g., ptr, extern, buffer<T>, packed class). These features have no PHP equivalent and are not expected to run under the PHP interpreter. They are clearly distinguishable from PHP syntax and exist to enable use cases (FFI, game development, low-level memory access) that PHP cannot address.

Documentation

The docs/ directory is the project's complete documentation, organized into the following sections:

docs/
├── README.md              # Main index
├── getting-started/       # Installation and first program
│   ├── installation.md
│   └── your-first-program.md
├── compiling/             # The compiler CLI: flags and the full compilation process
│   ├── overview.md
│   ├── compilation-pipeline.md
│   ├── cli-reference.md
│   ├── targets.md
│   ├── optimization.md
│   ├── output-and-diagnostics.md
│   └── linking-and-conditional-compilation.md
├── php/                   # PHP syntax (standard PHP features)
│   ├── types.md
│   ├── operators.md
│   ├── control-structures.md
│   ├── functions.md
│   ├── strings.md
│   ├── arrays.md
│   ├── math.md
│   ├── classes.md
│   ├── namespaces.md
│   ├── magic-constants.md
│   └── system-and-io.md
├── beyond-php/            # Compiler extensions (not valid PHP)
│   ├── pointers.md
│   ├── buffers.md
│   ├── packed-classes.md
│   ├── extern.md
│   └── ifdef.md
└── internals/             # Compiler internals
    ├── what-is-a-compiler.md
    ├── how-elephc-works.md
    ├── the-lexer.md
    ├── the-parser.md
    ├── the-type-checker.md
    ├── the-optimizer.md
    ├── the-codegen.md
    ├── the-runtime.md
    ├── memory-model.md
    ├── architecture.md
    ├── arm64-assembly.md
    └── arm64-instructions.md

Astro compatibility

All docs files are Markdown with YAML frontmatter compatible with Astro content collections. Every .md file must have title and description. Handwritten pages and generated area indexes also require sidebar.order, following this format:

---
title: "Page Title"
description: "One-line description of the page."
sidebar:
  order: N
---
  • title replaces the # Heading — do not add a top-level # Title in the body (Astro renders it from frontmatter)
  • sidebar.order controls page ordering within its section. Generated per-builtin pages omit it and use the site's alphabetical ordering, so adding a builtin does not renumber unrelated pages.
  • No navigation links ([← Back], Next:, etc.) — Astro handles navigation
  • Use standard Markdown (CommonMark). No custom shortcodes or Astro components inside docs

Keeping docs up to date

Documentation must be kept up to date. When adding a new feature:

  1. PHP syntax feature (operator, built-in, statement, etc.) → update the relevant page in docs/php/. Add the function signature, parameters, return type, and a short example. For builtins, prefer the generated-docs path: run the update-builtin-docs skill before opening a PR, or run the manual builtins docs sequence above. Commit updates under docs/php/builtins*, docs/internals/builtins/, and scripts/docs/builtin_registry.json.
  2. Compiler extension (pointer, buffer, extern, ifdef) → update the relevant page in docs/beyond-php/.
  3. Compiler internals change (pipeline, type checker, optimizer, codegen, runtime, ABI, memory model) → update the relevant page in docs/internals/.
  4. Compilation flow or CLI change (new/changed flag, env var, pipeline phase, target, output mode) → update the relevant page in docs/compiling/, keeping docs/compiling/cli-reference.md authoritative and in sync with src/cli.rs. Mirror user-facing flag examples in README.md.
  5. If a feature was previously listed as "not supported", remove that note.
  6. If there are known incompatibilities with PHP, document them in docs/php/types.md (incompatibilities section).
  7. Update docs/README.md index if adding a new page.

Roadmap management

ROADMAP.md tracks planned work, organized by version. Per-version delivered work belongs in CHANGELOG.md, not the roadmap.

  • Add planned work as [ ] under the version where it is expected to ship.
  • When an implementation lands, mark its existing item [x].
  • During release preparation, reconcile the current version section with the audited release range by marking planned items that shipped [x]. Do not add a roadmap item for work that was not planned; record that delivery in CHANGELOG.md.
  • Never remove completed items from a version section. Leave them under the version that delivered them so the roadmap preserves that version's scope.
  • When all items in a version are completed, the version is considered done; do not move items elsewhere.

Changelog management

CHANGELOG.md is a release artifact, not a development log. It records every released version, newest first, in Keep a Changelog style. Per-version delivered work belongs here, including work that was not planned on the roadmap.

  • Do not edit CHANGELOG.md during ordinary development. Feature, fix, refactor, documentation, dependency, and maintenance Pull Requests must not add entries under [Unreleased] or a numbered release.
  • Keep [Unreleased] empty between releases. Do not accumulate one changelog bullet per Pull Request.
  • Only an explicit release-preparation task may add, move, rewrite, or remove changelog bullets.
  • Before cutting a release, run the prepare-release-changelog skill. It must reconcile every merged Pull Request and direct commit since the latest published release against the exact candidate SHA, then write the approved user-facing bullets directly under the numbered candidate release section. An incomplete source ledger or unresolved commit is a release blocker.
  • If the target version is not known, stop and ask for it before editing the changelog. Do not use [Unreleased] as a staging section for a known release.

When cutting a release:

  • Add a new section at the top (under the header), above the previous version:

    ## [X.Y.Z] - YYYY-MM-DD
    - One terse, user-facing bullet per notable change.

    Keep entries concise (usually one or two bullets), describe what shipped — not the implementation — and use the absolute release date.

  • Add a matching compare link at the bottom of the file, also newest first, immediately above the previous version's link:

    [X.Y.Z]: https://github.com/illegalstudio/elephc/compare/v<previous>...vX.Y.Z

    The first-ever release uses the releases/tag/v0.1.0 form instead of a compare range. Every version section must have its link; do not leave the link out.

  • Never change elephc's Cargo package version in Cargo.toml or Cargo.lock. Release automation in CI owns Cargo version bumps; agent changes should leave those files' version numbers untouched unless the user explicitly overrides this policy.

Conventions

  • No Co-Authored-By lines in commits
  • Use commit message prefixes such as feat:, fix:, chore:, docs:, refactor:, or test:
  • Keep commit messages concise
  • Run the focused pre-commit verification above before committing code changes. Do not knowingly commit with relevant focused tests failing; the full suite must pass in CI.
  • Zero compiler warnings policy (cargo build must be clean)
  • Never run cargo fmt in this repo. Use targeted manual edits only; global formatting creates noisy churn here.
  • Bridge C-ABI return buffers are never process-global: a static Mutex<CString> handing out as_ptr() is a cross-thread use-after-free (the next call frees what an earlier caller is still reading). Use thread_local! cells or caller-owned allocations. Enforced by tests/ffi_buffer_hygiene.rs; rationale in CONTRIBUTING.md § "Expose a stable C ABI".

Cursor Cloud specific instructions

The dependency-refresh update script runs cargo build on startup, which also materializes the bridge libelephc_*.a staticlibs that linker.rs links into compiled programs. Standard build/test/run commands are documented above and in CONTRIBUTING.md; the notes below are only the non-obvious cloud caveats.

  • Rust toolchain is version-pinned. CI pins Rust 1.98 in .github/docker/ci.Dockerfile (RUST_VERSION) and workflow RUST_TOOLCHAIN. The "no warnings" gate greps build output, so use that pin rather than a floating stable. The cloud VM's rustup default may lag that pin until the environment image is rebuilt.
  • The compiler shells out to the host as + gcc/ld. These are present. On Linux, compiling a program prints benign linker warnings (gethostbyname in statically linked apps, missing .note.GNU-stack, executable stack). They are not errors — the produced binary runs fine.
  • php is not installed. PHP-equivalence cross-check tests (ELEPHC_PHP_CHECK=1 cargo test ...) will not run without installing a PHP interpreter first. Normal codegen tests do not need it.
  • Docker-based Linux cross-test scripts are unavailable by default. scripts/test-linux-x86_64.sh / -arm64.sh require Docker, which is not set up here. Rely on host cargo test/cargo nextest (this VM is linux-x86_64) and CI for the full target matrix.
  • Quick end-to-end sanity check: cargo run -- examples/fizzbuzz/main.php then ./examples/fizzbuzz/main.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.