CLAUDE.md
July 22, 2026 · View on GitHub
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
See also: .github/copilot-instructions.md for full architecture and patterns, .github/agents.md for the agent registry.
Repository
- GitHub owner:
antonsynd - GitHub repo:
antonsynd/sharpy
Quick Reference
Prerequisites: .NET 10 SDK (
net10.0TFM). Python 3.9+ forbuild_tools/.
dotnet build sharpy.sln # Build all
dotnet test # Run all tests
dotnet format whitespace # Format code (auto-formatted on save by Claude hook)
dotnet run --project src/Sharpy.Cli -- run file.spy # Compile and execute
dotnet run --project src/Sharpy.Cli -- emit csharp file.spy # Inspect generated C#
dotnet run --project src/Sharpy.Cli -- emit ast file.spy # Inspect parsed AST
dotnet run --project src/Sharpy.Cli -- emit tokens file.spy # Inspect lexer tokens
Sandbox note: All
dotnetcommands (especiallybuildandtest) hang when run inside the default sandbox. When operating in a sandboxed environment, rundotnetcommands withdangerouslyDisableSandbox: true.
Prefer skills over raw commands: Use
/build,/run-tests,/spy-emit,/spy-run,/quick-checkinstead of rawdotnetcommands. Skills handle logging, truncation, and temp file management (avoiding bash escaping issues with#and backticks in Sharpy source).
CRITICAL — Serialized dotnet execution: When multiple agents run in parallel, NEVER call
dotnet buildordotnet testdirectly. Use the serialized wrapper.claude/scripts/dotnet-serializedwhich acquires an exclusive flock so only one dotnet process runs at a time. Concurrentdotnet testinvocations each consume 5-10 GB RAM (Roslyn + 9600 tests); three in parallel will OOM and crash the system. The wrapper is a drop-in replacement — same args, same output, same exit code. Example:.claude/scripts/dotnet-serialized test --filter "FullyQualifiedName~Lexer" --no-build. This is enforced by a PreToolUse hook (.claude/hooks/enforce-dotnet-serialized.sh) that blocks rawdotnet build/dotnet testBash commands.
Test output logs: The serialized wrapper tees all stdout+stderr to
.claude/tmp/dotnet-serialized-{0,1,2}.log(rotating deque of 3 slots)..claude/tmp/dotnet-serialized-latest.logsymlinks to the most recent run. Agents should read these logs to filter/grep test output instead of re-running the full test suite (~8 min). The 3-slot rotation means an agent reading an older log won't be clobbered by a new run writing to a different slot.
Architecture
Source (.spy) → Lexer → Parser (AST) → Semantic → ValidationPipeline → RoslynEmitter → C# → .NET IL
| Component | Location | Purpose |
|---|---|---|
| Compiler | src/Sharpy.Compiler/ | Lexer, Parser, Semantic, CodeGen |
| Core | src/Sharpy.Core/ | Runtime essentials: primitives, collections, builtins, protocol interfaces, Partial.{Type}/ |
| Stdlib | src/Sharpy.Stdlib/ | Standard library modules (60 modules: json, os, re, numpy, etc.) |
| CLI | src/Sharpy.Cli/ | Command-line interface (sharpyc, uses System.CommandLine) |
| LSP | src/Sharpy.Lsp/ | Language Server Protocol server (OmniSharp-based) |
| Tests | src/*.Tests/ | Unit and integration tests |
| Specs | docs/language_specification/ | Authoritative language specification |
| Build Tools | build_tools/ | Python-based build automation and dogfooding tools |
| Agents | .github/agents/ | Domain-specific agent guidance (copilot/AI) |
| Instructions | .github/instructions/ | Per-component contribution guides |
Critical Rules
- Never modify expected values to make tests pass — fix the implementation
- RoslynEmitter is a pure translator —
SyntaxFactoryexclusively (no string templating); makes no type/lowering decisions and performs no reflection. All such decisions are made in semantic analysis and materialized for the emitter to read via one of two patterns, each with its own materialization boundary: (a) symbol-keyed onSymbol.CodeGenInfo, frozen atMaterializeCodeGenInfo— use when a discoveredSymbolowns the fact; (b) node-keyed in aSemanticInfodictionary (e.g.BinaryOpLowering,IndexAccessLowering, resolved CLR member names), merged atSemanticInfo.MergeFrom— use when the fact belongs to an AST node with no owning symbol. CLR inspection belongs toDiscovery/ClrTypeBridge/ClrTypeHelper. Enforced byEmitterPurityConformanceTests. Any new node-keyedSemanticInfodictionary must be added toSemanticInfo.MergeFromor its entries are silently dropped in the per-file→project merge that code generation reads from. - Immutable AST — annotations go in
SemanticInfo, not AST nodes - Axiom precedence: .NET > Type Safety > Python Syntax
- C# 9.0 minimum for Sharpy.Core —
Sharpy.Coremulti-targetsnet10.0;netstandard2.1. Onnetstandard2.1:LangVersion 9.0(no global usings, file-scoped namespaces, or record structs). Onnet10.0:LangVersion 14. Use#if NET10_0_OR_GREATERfor net10.0-only code paths.Sharpy.CompilerandSharpy.Clitargetnet10.0withLangVersion latest. - Always verify Python behavior first — run
python3 -c "..."before implementing Python semantics - Language spec is authoritative — check
docs/language_specification/before implementing; change implementation to match spec, not the other way around - TODO/BUG/FIXME comments must have GitHub issues — when leaving a
TODO,BUG, orFIXMEcomment in code, always create a corresponding GitHub issue first (viagh issue create) and reference it in the comment (e.g.,// TODO(#123): ...). This makes deferred work visible at the project level, not buried in code. - Warnings are errors —
TreatWarningsAsErrorsis enabled solution-wide viaDirectory.Build.props
Semantic Analysis Pipeline
The semantic phase runs multiple ordered passes. Understanding this is critical for implementation work.
Pass 1 — Name Resolution (NameResolver.cs): Collects all top-level declarations into SymbolTable. Runs ResolveDeclarations() then ResolveInheritance().
Pass 1.5 — Import Resolution (ImportResolver.cs): Loads imported modules via ModuleLoader (which caches parsed modules and detects circular imports). Registers imported symbols in SymbolTable. PackageResolver handles __init__.spy packages.
Pass 2 — Type Resolution (TypeResolver.cs): Resolves type annotations on declarations to concrete types. Type inference provided by TypeInferenceService and GenericTypeInferenceService.
Pass 3 — Type Checking (TypeChecker.cs, split into 11 partial files: .cs, .Definitions.cs, .Expressions.cs, .Expressions.Access.cs, .Expressions.Access.Calls.cs, .Expressions.Access.Lambdas.cs, .Expressions.Literals.cs, .Expressions.Operators.cs, .Statements.cs, .Statements.Patterns.cs, .Utilities.cs): Traverses AST, infers types, records them in SemanticInfo. Then runs ValidationPipeline. Type narrowing (e.g., if x is not None: narrows T? → T): statement-level flow comes from CFG dataflow (NarrowingFlowAnalysis); expression-level scopes (ternary arms, and/or RHS) and the match-arm scope guard use _narrowingContext (TypeNarrowingContext). At every narrowed read the TypeChecker materializes a node-keyed NarrowedReadLowering in SemanticInfo (.Unwrap()/.Value/!/cast), which codegen applies verbatim — the emitter performs no narrowing flow re-derivation (#1081, #1080).
Key registries: OperatorRegistry, ProtocolRegistry, BuiltinRegistry, ModuleRegistry, PrimitiveCatalog (source of truth for primitive types and CLR mappings).
Materialization Points: After each major phase, computed data is frozen from SemanticBinding onto Symbol properties:
- After import resolution →
MaterializeInheritance()(BaseType, Interfaces) - After type checking →
MaterializeVariableTypes(),MaterializeCodeGenInfo()
Symbol Position Fields
Symbol.DeclarationLine/Column— statement start (e.g.,asyncinasync def foo). Used for diagnostics and identity comparisons.Symbol.NameDeclarationLine/Column— name token position (e.g.,fooinasync def foo). Used for text edits and highlight ranges.Symbol.EffectiveNameLine/Column— preferred accessor: returnsNameDeclarationLine ?? DeclarationLine. LSP handlers must use this for text edits and highlight ranges.
Key Data Structures
SemanticInfo— Maps AST nodes → types/symbols. UsesReferenceEqualityComparerbecause AST nodes are records (value equality) but we need identity.SemanticBinding— Stores computed semantic data (CodeGenInfo, variable types) separately from symbols, materialized at phase boundaries.SymbolTable— Global scope of all declared symbols.
Symbol Hierarchy
Symbols are mutable records that use reference equality (overridden from record default) because their properties (Type, BaseType, CodeGenInfo) are set progressively across passes.
Symbol (abstract) — DeclarationSpan, DeclaringFilePath (all symbols)
├── VariableSymbol — Type set during type checking
├── FunctionSymbol — Parameters, ReturnType, IsStatic/Abstract/Virtual/Override
├── TypeSymbol — TypeKind, BaseType, Interfaces, Fields, Methods, DefiningFilePath
├── ModuleSymbol — FilePath
├── TypeAliasSymbol — Aliased type reference
└── TypeParameterSymbol — Generic type parameters (T in class Box[T])
PropertySymbol — Standalone record (not a Symbol subclass)
EventSymbol — Standalone record (not a Symbol subclass)
ParameterSymbol — Standalone record (not a Symbol subclass)
SemanticType Hierarchy
All types are immutable records inheriting from SemanticType (Semantic/SemanticType.cs):
SemanticType (abstract)
├── BuiltinType — Int, Long, Float, Double, Float32, Bool, Str (singletons)
├── GenericType — list[int], dict[str, int] (Name + TypeArguments)
├── UserDefinedType — Classes, structs, interfaces (Name + Symbol)
├── NullableType — T? for .NET interop (UnderlyingType)
├── OptionalType — T? as safe tagged union (UnderlyingType)
├── FunctionType — Lambdas/delegates (ParameterTypes + ReturnType)
├── GenericFunctionType — Generic functions with type parameters
├── TupleType — tuple[int, str] (ElementTypes)
├── ModuleType — Imported modules as namespaces
├── TypeParameterType — Generic type parameters (T in class Box[T])
├── ResultType — T !E tagged union (OkType + ErrorType)
├── SelfType — Self type for covariant return annotations
├── UnionType — Tagged unions (v0.2.x placeholder)
├── TaskType — Async Task types (v0.2.x placeholder)
├── TemplateType — Template/format string types
├── LiteralStringType — Compile-time string literal types
├── VoidType — None return type
└── UnknownType — Error recovery
ValidationPipeline
Pluggable validators implement ISemanticValidator with an Order property (lower runs first):
- Order 50:
ModuleLevelValidator— Entry point validation - Order 52:
CircularImportUsageValidator— Circular import usage detection - Order 55:
NamingConventionValidator— Naming convention checks - Order 56:
TransitionWarningValidator— Transition hint diagnostics for Python/C# behavioral differences - Order 60:
DecoratorValidator— Decorator validation - Order 62:
BodylessSyntaxValidator— Deprecation warnings for body-less method syntax - Order 65:
SourceGeneratorValidator— Source generator attribute validation - Order 140:
ConstructorOverloadValidator— Duplicate constructor signatures - Order 145:
StructRulesValidator— Struct constructor field initialization - Order 147:
EnumRulesValidator— Enum value type consistency - Order 150:
SignatureValidator— Dunder method signatures - Order 152:
ConversionOperatorValidator— Conversion operator validation - Order 155:
GeneratorValidator— Generator function validation - Order 160:
EqualityContractValidator— Equality contract checks - Order 170:
InterfaceConflictValidator— Interface conflict detection - Order 250:
DefaultParameterValidator— Default parameter validation - Order 400:
ControlFlowValidator— CFG-based unreachable code, missing returns - Order 405:
ExhaustivenessValidator— Match statement exhaustiveness checks - Order 410:
PropertyValidator— Property validation - Order 411:
FinalFieldValidator— Final field validation - Order 412:
EventValidator— Event validation - Order 415:
VarianceValidator— Variance validation - Order 420:
UnusedVariableValidator— Unused variable warnings - Order 430:
UnusedImportValidator— Unused import warnings - Order 450:
AccessValidator— Private/protected member access - Order 460:
DunderInvocationValidator— Direct dunder call warnings - Order 480:
InterfaceImplementationValidator— Interface method implementation checks - Order 500:
ProtocolValidator— Protocol validation - Order 501:
OperatorValidator— Operator validation
Validator base classes:
ValidatingAstWalker— for validators that traverse the AST via visitor pattern (e.g., ProtocolValidator, AccessValidator). OverrideVisitXxxmethods to inspect nodes.SemanticValidatorBase— for validators with custom traversal logic (e.g., SignatureValidator, ModuleLevelValidator). OverrideValidate()and walk the AST manually.
Responsibility split: TypeChecker handles type mismatches and in-progress inference. ValidationPipeline handles self-contained AST analyses that don't need active inference state.
Diagnostic Code Ranges
All diagnostics use SPY prefix (Diagnostics/DiagnosticCodes.cs):
| Range | Level | Component |
|---|---|---|
| SPY0001–SPY0099 | Error | Lexer |
| SPY0100–SPY0199 | Error | Parser |
| SPY0200–SPY0399 | Error | Semantic |
| SPY0400–SPY0449 | Error | Validation (primary error sub-band — fully allocated) |
| SPY0450–SPY0489 | Warning | Validation (unreachable code, naming conventions) |
| SPY0490–SPY0499 | Error | Validation errors — overflow (SPY0400–SPY0449 full; e.g., property observers SPY0490/0491) |
| SPY0500–SPY0599 | Error | Code generation |
| SPY0900–SPY0999 | Error | Infrastructure (compilation, file I/O) |
| SPY1000–SPY1099 | Info | Informational (e.g., implicit interface synthesis) |
Code Generation
The RoslynEmitter is split into 25 files (~22,460 lines total): RoslynEmitter.cs (entry, name resolution), .Expressions.cs, .Expressions.Access.cs, .Expressions.Access.Calls.cs, .Expressions.Comprehensions.cs, .Expressions.Literals.cs, .Expressions.Operators.cs, .Statements.cs, .Statements.Assignments.cs, .Statements.ControlFlow.cs, .TypeDeclarations.cs, .ClassMembers.cs, .ClassMembers.Constructors.cs, .ClassMembers.Dataclass.cs, .ClassMembers.Events.cs, .ClassMembers.Iterators.cs, .ClassMembers.LruCache.cs, .ClassMembers.Methods.cs, .ClassMembers.Properties.cs, .CompilationUnit.cs, .ModuleClass.cs, .Operators.cs, .Patterns.cs, .TestFixtures.cs, RoslynEmitterFactory.cs.
Name resolution strategy:
- Module-level symbols →
Symbol.CodeGenInfo(precomputed during semantic analysis) - Local variables → runtime tracking via
_variableVersions(handles redeclarations: x, x_1, x_2) - Types → SymbolTable lookup
Type mappings (CodeGen/TypeSyntaxMapper.cs): int → int, long → long, str → string, float → double, list[T] → Sharpy.List<T>, dict[K,V] → Sharpy.Dict<K,V>, set[T] → Sharpy.Set<T> (Sharpy.Core wrappers delegate to .NET types internally). Collection type name constants live in Shared/CSharpTypeNames.cs. Note: a separate Discovery/ClrTypeMapper.cs maps CLR types back to Sharpy SemanticType instances.
Name mangling (NameMangler.cs): snake_case → PascalCase, __init__ → constructor, __add__ → operator+, __str__ → ToString()
Design Anti-Patterns
Avoid these patterns:
| Pattern | Problem |
|---|---|
| "Add X because Python has it" | Feature creep — each feature must earn its complexity |
| Runtime type checking | Should be compile-time |
| Wrapper types for Pythonic API | Use extension methods instead |
| Multiple ways to do same thing | Consistency issue |
| Magic behavior | Unpredictable; prefer explicit |
| Raw .NET collections in public APIs | Use Sharpy.List<T>, Dict<K,V>, Set<T> |
Optional<T> return from stdlib | Return nullable; users opt in via maybe |
| Throwing for expected failures | Return Result<T, E>; reserve exceptions for bugs |
Axiom Conflict Resolutions
When the three axioms conflict, precedence is: Axiom 1 (.NET) > Axiom 3 (Types) > Axiom 2 (Python). If a conflict can be resolved at zero cost, satisfy all axioms. Common resolved conflicts:
| Conflict | Resolution |
|---|---|
Integer division (//) | Axiom 1 wins — provide math.floor_div() helper |
| String indexing (code points vs UTF-16) | Axiom 1 wins — use UTF-16 with helper methods |
global/nonlocal keywords | Axiom 1 wins — C# scoping rules apply |
| Duck typing | Axiom 1+3 win — use explicit interfaces |
Feature Implementation Order
Experimental features (default-off, behind a flag) follow the lifecycle in docs/design/feature-lifecycle.md: register in
FeatureFlags.KnownFeatures, gate via theFeatureGateCheckerregistry (ungated use → SPY0331), graduate or delete per that policy.
For new language features, touch components in this order (dependencies flow left→right):
Lexer → Parser → Semantic → Validation → CodeGen → LSP → Tests
- Lexer (
Lexer/) — AddTokenTypeand recognition - Parser (
Parser/Ast/) — Add AST record, parsing rule. Parser is split into 6 partial files:.cs,.Definitions.cs,.Expressions.cs,.Primaries.cs,.Statements.cs,.Types.cs - Semantic (
Semantic/) — Add type checking inTypeChecker*.cs - Validation (
Semantic/Validation/) — Add validator if needed - CodeGen (
CodeGen/RoslynEmitter*.cs) — Emit viaSyntaxFactory - LSP (
src/Sharpy.Lsp/Handlers/) — Update handlers if new AST nodes or semantic types affect IDE features (hover, completion, semantic tokens, etc.) - Tests — Unit tests per component +
.spy/.expectedintegration tests + LSP handler tests
Multi-File Compilation
ProjectCompiler (in Project/) and ProjectFileParser (the single .spyproj parser + discovery helper, in ProjectConfig.cs) handle multi-file projects using .spyproj files:
dotnet run --project src/Sharpy.Cli -- project path/to/project.spyproj
Programmatic multi-file tests use ProjectCompilationHelper:
using var helper = new ProjectCompilationHelper(output);
helper.WithRootNamespace("Test")
.AddSourceFile("main.spy", "...")
.AddSourceFile("lib.spy", "...")
.CreateProjectFile();
var result = helper.Compile();
Incremental Compilation
Enable incremental compilation with --incremental to skip unchanged files:
dotnet run --project src/Sharpy.Cli -- project path/to/project.spyproj --incremental
How it works:
- First build: All files are compiled, symbols and generated C# are cached to
obj/{Config}/.sharpy-symbols - Subsequent builds: Files are skipped if their content hash matches the cache AND no dependencies changed
- Transitive dependencies: If file A imports B and B changes, A is recompiled (uses cached dependency graph)
Cache files (in obj/{Config}/):
.sharpy-cache— File content hashes (SHA-256) with compiler version.sharpy-symbols— Serialized symbols and generated C# per file with schema version
Cache invalidation: Caches are automatically invalidated when:
- Compiler version changes (assembly hash changes)
- Symbol cache schema version changes
- Source file content changes
Force full rebuild: Delete the cache files or use --clean flag.
Implementation: IncrementalCompilationCache, SymbolSerializer, SymbolCache (all in Project/)
Sharpy.Core Patterns (Essentials)
- Wrap .NET internally, expose Python API —
list.append()notAdd() - Partial class pattern: Types split across
Partial.{Type}/directories (e.g.,Partial.List/List.Methods.cs,List.Slicing.cs,List.Interfaces.cs) - Builtins:
partial class Builtinssplit acrossPrint.cs,Len.cs,Range.cs, etc. - Core modules: Operator (comparison helpers) and Copy (shallow/deep copy) stay in Core due to collection type dependencies
- Python semantics: Negative indexing, slicing, Python-matching exceptions
Sharpy.Stdlib (Standard Library)
- 60 stdlib modules in
src/Sharpy.Stdlib/: Argparse, Base64, Bisect, Calendar, Collections, Colorsys, Configparser, Csv, Datetime, Difflib, Email, Fnmatch, Fractions, Functools, Glob, Grapheme, Gzip, Hashlib, Heapq, Hmac, Html, Http, Io, Ipaddress, Itertools, Json, Logging, Math, Numpy, Os, Pathlib, Platform, Pprint, Random, Re, Requests, Secrets, Shlex, Shutil, Socket, Sqlite3, Statistics, String, Struct, Subprocess, Sys, Tarfile, Tempfile, Textwrap, Threading, Time, Toml, Unittest, Urllib, Uuid, Xml, Yaml, Zipfile, Zlib, Zoneinfo - Special directories:
spy/holds.spysource modules — for spy-sourced modules (listed in theMODULESmapping inbuild_tools/regenerate_spy_stdlib.sh), the C# under<Module>/is generated from the.spyfile; never hand-edit generated C#, run the script instead (CI verifies viacheck_spy_staleness.sh).modules/holds per-module.csprojpackaging. - Depends on Sharpy.Core (ProjectReference), not the other way around
- NuGet deps: MathNet.Numerics (numpy), Microsoft.Data.Sqlite (sqlite3), Tomlyn (toml), YamlDotNet (yaml)
- Multi-target:
net10.0;netstandard2.1(same as Core) - Compiler has zero compile-time dependency on Stdlib — stdlib modules are discovered at runtime via
ModuleRegistry.LoadReference()
Stdlib API Conventions
Error handling — what to return:
| Scenario | Stdlib returns | User opts into safety via |
|---|---|---|
| Absence | T? (nullable) | maybe → Optional<T> |
| Expected failure | Result<T, E> | (already safe) |
| Bugs | exception | (should crash) |
- Return nullable for absence, not Optional — e.g.,
re.search()returnsReMatch?,dict.get()returnsV?. This is Pythonic, convenient, and .NET-interop-friendly. Users who want compile-time safety addmaybeat the call site. - Return Result for expected failures with typed errors — e.g.,
json.loads[T]()returnsResult<T, JSONDecodeError>. - Throw only for bugs — e.g.,
list[i]out of bounds, null where required. - No dual APIs — don't provide both throwing and Result-returning variants of the same function.
try/maybebridge .NET interop generically.
Public API surface:
- Use Sharpy collection types in public signatures —
List<T>,Dict<K,V>,Set<T>fromSharpy.Core. Never exposeSystem.Collections.Generic.List<T>or namespace-aliasedSCG.List<T>in public APIs. Internal/private code may use raw .NET collections. - Optional parameters use nullable with
= nullin C# orT? = None()in.spydefinitions. Don't useOptional<T>as a parameter type.
Protocol Interfaces
Protocol interfaces enable builtin function dispatch (e.g., len(), bool()) via compile-time interfaces:
ISized—int Count { get; }— implemented by List, Set, Dict; synthesized when__len__is presentIBoolConvertible—bool IsTrue { get; }— synthesized when__bool__is present; enablesbool()dispatchIReverseEnumerable<T>—IEnumerator<T> GetReverseEnumerator()— synthesized when__reversed__is present
The emitter implicitly adds these interfaces to a class's base list when the corresponding dunder method is detected (emits SPY1001 info diagnostic).
Skills
Available in .claude/skills/:
Build & Test
All commands below log full output to .claude/tmp/*.log for investigation while showing truncated summaries. Test skills auto-build before running.
| Command | Purpose |
|---|---|
/run-tests [filter] | Build + run tests; shows last 80 lines on failure (log: last-test-run.log) |
/build | Build solution; shows last 100 lines on failure (log: last-build.log) |
/format | Format whitespace (auto-formatted on save by Claude hook) |
/regenerate-snapshots | Build + update .expected.cs files after codegen changes |
/property-stress [rounds] [filter] | Stress-test property tests across N rounds with fresh seeds (logs: property-stress/) |
/benchmark | Run compiler or cross-language benchmarks and compare results |
Debug & Development
All /spy-* skills accept inline source or a file path. Inline source is written to a temp file via the Write tool (no bash escaping needed), so agents should prefer these skills over raw dotnet run commands.
| Command | Purpose |
|---|---|
/spy-emit <mode> <source> | Emit compiler output: csharp, ast, tokens, or diagnostics (log: last-spy-emit.log) |
/spy-run <source> | Compile and execute (log: last-spy-run.log) |
/quick-check <source> | Emit C# + run in one shot (logs: last-quick-check-{emit,run}.log) |
/lsp-hover <pos> | Get LSP hover tooltip for a position in a .spy file (emulates VS Code hover) |
/lsp-review | Interactive LSP review session — report hover/coloring issues from VS Code |
/verify-python <expr> | Run Python 3 to verify behavior before implementing |
/clean-dotnet | Kill zombie dotnet processes that cause hangs |
/playground | Run the Sharpy playground (Blazor WASM) locally with hot reload |
Git Workflow
| Command | Purpose |
|---|---|
/commit [message] | Stage and commit changes with auto-generated message |
/push [--close-issues N,N] | Push current branch; optionally close GitHub issues |
/close-issues [N,N] | Close GitHub issues that have been implemented, with verification |
Analysis & Planning
| Command | Purpose |
|---|---|
/create-plan <issues or desc> | Create implementation plan from GitHub issues or description |
/compiler-audit [focus] | Run a comprehensive compiler health audit |
/dogfood-analyze [dir] | Analyze dogfood results and classify failures |
/dogfood-run | Run dogfooding iterations to test the compiler |
/verify-plan <plan.md> | Verify a plan for accuracy and architectural soundness |
/implement-plan <plan.md> | Implement a plan with a coordinated agent team |
/verify-implementation <plan.md> | Verify implementation, fix gaps/bugs/regressions |
/add-test-fixture <desc> | Create a file-based integration test (.spy + .expected/.error) |
/add-stdlib-module <name> | Scaffold a new stdlib module (spy-sourced or handwritten) with conventions, docs, tests |
/gap-analysis | Run all gap discovery tests and present a unified summary |
Investigate failures: Read logs with /read .claude/tmp/last-test-run.log (or other log files).
Testing
dotnet test --filter "FullyQualifiedName~Lexer" # By component
dotnet test --filter "FullyQualifiedName~FileBasedIntegrationTests" # File-based tests
dotnet test --filter "DisplayName~test_name" # By test name
dotnet test --filter "FullyQualifiedName~Lsp" # LSP tests
dotnet test --filter "FullyQualifiedName~Lsp.Tests.E2E" # LSP E2E protocol tests
File-Based Tests
Location: src/Sharpy.Compiler.Tests/Integration/TestFixtures/
Single-file tests: .spy + .expected (exact stdout match) or .spy + .error (substring match in error; line ending with @line:col also verifies diagnostic location)
Multi-file tests: A subdirectory with multiple .spy files and a main.spy entry point, plus main.expected or main.error.
Warning tests: .warning file — empty means expect no warnings, non-empty lines are expected substrings. Can combine with .expected.
C# snapshot tests: .expected.cs file — the expected generated C# output (Roslyn-normalized). Used selectively for ~100 representative fixtures to detect codegen changes that don't affect runtime output. To regenerate: UPDATE_SNAPSHOTS=true dotnet test --filter "FullyQualifiedName~FileBasedIntegrationTests".
Experimental features: .features file — one experimental feature name per line (# comments and blank lines tolerated) enables those features compilation-wide when the fixture compiles (e.g. matmul, defer). For single-file fixtures the sidecar is <stem>.features; for multi-file fixtures it is main.features next to the entry point and applies to the whole project. Unknown feature names fail fixture discovery loudly (naming the file and the bad name) rather than being silently ignored. Pair a gated .spy (with .features + .expected) against an ungated twin (same .spy, no .features, .error matching SPY0331) to cover both paths — see TestFixtures/experimental/.
Skip: Add a .skip file next to the .spy file.
Integration Test Base
Programmatic tests inherit IntegrationTestBase and use CompileAndExecute(source):
var result = CompileAndExecute("print(1 + 2)");
Assert.True(result.Success);
Assert.Equal("3\n", result.StandardOutput);
Compiler Subdirectories
Key subdirectories within src/Sharpy.Compiler/ not covered above:
| Path | Purpose |
|---|---|
Analysis/ControlFlow/ | ControlFlowGraph, ControlFlowGraphBuilder, BasicBlock |
Diagnostics/ | DiagnosticBag, DiagnosticCodes, DiagnosticRenderer, CompilationMetrics |
Discovery/ | CLR type discovery: ClrTypeMapper, CachedModuleDiscovery |
Shared/ | CSharpKeywords (keyword escaping), CSharpTypeNames (collection type constants), NameMangler |
Discovery/Caching/ | OverloadIndex, OverloadIndexCache, AssemblyIdentity |
Model/ | CompilationUnit, CompilationUnitFactory, ProjectModel |
Logging/ | ICompilerLogger, StructuredLogger, ConsoleCompilerLogger, NullLogger |
Project/ | ProjectCompiler (9 partial files), DependencyGraph (.spyproj parsing lives in ProjectConfig.cs's ProjectFileParser) |
Services/ | CompilerServices, CompilerServicesBuilder (adapter pattern) |
Text/ | ILocatable, SourceText, TextSpan |
Utilities/ | EditDistance, PathNormalizer |
CI/CD
.github/workflows/:
dotnet10.yml— Active; tests on .NET 10docs.yml— Deploy documentation (mkdocs + playground)python-build-tools.yml— Runs pytest forbuild_tools/on Python 3.12benchmarks.yml— Performance benchmarkscross-language-benchmarks.yml— Cross-language benchmark comparisonsvscode-extension.yml— VS Code extension CIauto-tag.yml— Automatic version taggingrelease.yml— Release workflow
An .editorconfig at the repo root enforces C# formatting and naming conventions.
MCP Servers for Codebase Navigation
MCP servers provide structural codebase understanding. Prefer them over raw Grep/Glob/Read for structural queries.
Availability: Project-level MCP servers are configured in
.mcp.json(currently onlycode-review-graph); CodeGraphContext, if present, is user-configured. If a server is not connected in the current session, fall back to the next option in the decision guide below.
Decision Guide
Find a file by name/pattern? → Glob
Search text/regex across files? → Grep
Search strings/comments/non-symbol text? → Grep
Code review (risk-scored)? → code-review-graph (detect_changes + get_review_context)
Impact analysis, call chains, dead code? → code-review-graph (get_impact_radius, query_graph, refactor_tool)
Architecture overview, communities? → code-review-graph (get_architecture_overview, list_communities)
Complexity triage? → CodeGraphContext (find_most_complex_functions)
code-review-graph (Knowledge Graph)
Persistent incremental graph (auto-updates via hooks). Key tools: detect_changes, get_review_context, get_impact_radius, get_affected_flows, query_graph (callers_of/callees_of/imports_of/tests_for), semantic_search_nodes, get_architecture_overview, refactor_tool.
CodeGraphContext (Architecture & Relationship Queries)
Graph database for deep analysis: analyze_code_relationships, find_dead_code, find_most_complex_functions, find_code, execute_cypher_query. First-time setup: Run add_code_to_graph on src/Sharpy.Compiler/ and src/Sharpy.Core/.