Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .agents/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Architecture

Read this for language, compiler, runtime, dependency, or project-boundary changes. This describes the checked-out implementation; [ADRs](../docs/adr/README.md) explain its history.

## Purpose and entry points

HydraScript is a TypeScript/Go-inspired interpreter with strong static structural typing. The JavaScript-like syntax is its own language: do not assume JavaScript coercion, class semantics, or complete ECMAScript support.

- [Readme](../Readme.md): language behavior and examples.
- [TokenTypes](../src/Domain/HydraScript.Domain.Constants/TokenTypes.cs): lexical definitions.
- [grammar.txt](../docs/grammar.txt): grammar.
- [Program](../src/HydraScript/Program.cs) and [ExecuteCommand](../src/HydraScript/ExecuteCommand.cs): CLI composition and the input path/`--dump` options.
- [Executor](../src/Infrastructure/HydraScript.Infrastructure/Executor.cs): application execution and exit codes.

## Execution pipeline

```text
Program / ExecuteCommand
-> per-invocation ServiceProvider
-> Executor.Invoke
-> ISourceCodeProvider.GetText
-> TopDownParser.Parse -> RegexLexer -> AST
-> CodeGenerator.GetInstructions
-> StaticAnalyzer.Analyze
-> SymbolTableInitializer
-> TypeSystemLoader
-> DeclarationVisitor
-> SemanticChecker
-> InstructionProvider / ExpressionInstructionProvider
-> AddressedInstructions
-> VirtualMachine.Run -> instruction.Execute -> next address
```

The pre-analysis order is defined by registrations in [AddStaticAnalysis](../src/Application/HydraScript.Application.StaticAnalysis/ServiceCollectionExtensions.cs). Code generation calls static analysis before emitting instructions; preserve that sequencing.

## Ownership and dependency rules

[ADR 0001: OOP, DDD, and Clean Architecture](../docs/adr/0001-oop-ddd-clean-architecture.md#project-boundaries) owns the project-boundary table. Read it before adding references or moving responsibilities. Domain stays independent of Application/Infrastructure/CLI; Application connects the compiler subdomains, and Infrastructure supplies adapters and composition.

## Constraints to preserve

- AST nodes describe syntax and expose [Visitor.NET dispatch](../docs/adr/0002-visitor-net-adoption.md); each visitor controls its traversal. Put analysis/emission logic in the corresponding visitors. Review parent/scope propagation and visitors when adding nodes.
- [Structural type rules](../docs/adr/0006-structural-type-system.md) govern compatibility and operator checking. Type declarations are built before references are resolved; overload lookup uses typed symbol IDs and signatures.
- [Addressed instructions and the VM](../docs/adr/0003-addressed-instructions-and-virtual-machine.md) separate instruction identity from position; control flow follows returned addresses. Review jump targets and address identity when editing instruction collections.
- Runtime `IValue.Get()` still returns `object?`. Do not describe the backend as fully typed. Numeric length results were normalized to `double` in PR #242; type-system changes must agree with runtime values and operators.
- Compound assignments are desugared by the parser. Check evaluation order and side effects when changing member access or assignment lowering.
- `PatternGenerator` produces `PatternContainer.g.cs`; `GeneratedRegexContainer` consumes the constant. Change token definitions/generator inputs, not files under `obj/`. See [ADR 0004](../docs/adr/0004-generated-lexer-pattern.md). The generator links Constants sources and is referenced with `OutputItemType="Analyzer"`, `ReferenceOutputAssembly="false"`, and `PrivateAssets="all"`.
- DI registrations are predominantly singleton within a service provider. The CLI creates/disposes a provider for each invocation; tests and benchmarks can reuse state differently. Inspect storage/frame lifetime before introducing repeated execution or concurrency.
- Dumping wraps lexer, parser, and VM using keyed services. Preserve ordinary execution behavior and verify dump behavior for affected changes.
- `Executor` returns 0 on success, 1 for lexer/parser/semantic errors, and 2 for other .NET runtime errors. Tests should distinguish these paths.

## Build and platform

[Directory.Build.props](../Directory.Build.props) defines `net10.0`, latest C#, nullable references, implicit usings, warnings as errors, AOT compatibility, and currently `BuildInParallel=false`. Constants and the lexer generator override to `netstandard2.0`; tests/generator/constants opt out of AOT compatibility. Preserve child props' parent imports.

[Directory.Packages.props](../Directory.Packages.props) owns package versions, enables transitive pinning, and disables version overrides. Do not put independent package versions into project files.

[global.json](../global.json) selects Microsoft.Testing.Platform but does not pin the SDK. CI requests .NET 10.x; check the installed SDK before running commands.

The CLI publishes Native AOT executables for `win-x64`, `linux-x64`, and `osx-arm64`. Tool packaging also has an `any` managed fallback. Preserve trim/AOT compatibility and explicit DI; assess new reflection-dependent libraries against [ADR 0005](../docs/adr/0005-native-aot-and-performance.md).

## Change routing

| Change | Inspect together |
| --- | --- |
| Syntax or operator | TokenTypes, grammar, parser/AST, semantic operator rules, emission/runtime, language docs, regression tests |
| Type, overload, scope, return | IR types/symbol IDs, static-analysis visitors/storages, runtime representation, error-program tests |
| Instructions or calls | AddressedInstructions, code-generation visitors, frames/values/VM, recursion and nested-call tests |
| Host I/O or dumping | Infrastructure adapters, DI, Executor, TestHostFixture, input/dump tests |
| Build, dependencies, publishing | All inherited props, generator analyzer reference, CI workflows, ADRs 0004/0005 |

[Master CI](../.github/workflows/master.yml) derives version tags with [GitVersion](../GitVersion.yml). The [release-branch workflow](../.github/workflows/release.yml) uses GitReleaseManager and matching milestones to publish releases and packages. Tags are not themselves published releases; treat release-branch pushes as publishing actions requiring authorization.

Use GitHub MCP to search for relevant implementation context when working on one of these areas.
92 changes: 92 additions & 0 deletions .agents/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Testing

Read this before changing executable code/tests or choosing validation commands. Use the applicable Rider and .NET skills required by [AGENTS.md](../AGENTS.md).

## Testing model

Use component tests to isolate compiler/runtime contracts, generator tests to verify build-time output, and interpreter regressions to verify language behavior across the real pipeline. These suites complement one another: a script sample's successful exit is a smoke check, not a substitute for output/error assertions.

NSubstitute is the shared test-double library; use the existing AutoFixture and assertion helpers where applicable. xUnit provides fixture/test-context support, while `TestHostFixture` explicitly composes interpreter services. The current setup does not use Moq or XUnit.DependencyInjection; see the [test-double choice](https://github.com/Stepami/hydrascript/pull/75) and [runner/fixture migration](https://github.com/Stepami/hydrascript/pull/176) for implementation context.

## Detect the runner

The checked-in configuration is SDK-style .NET 10, **xUnit on Microsoft.Testing.Platform (MTP)**, in native MTP `dotnet test` mode. [global.json](../global.json) selects the runner; [tests/Directory.Build.props](../tests/Directory.Build.props) enables the xUnit MTP runner and executable test projects. The current package is `xunit.v3.mtp-v2`; its version is centrally managed and should be read from [Directory.Packages.props](../Directory.Packages.props).

Use `dotnet-test:platform-detection` and `dotnet-test:run-tests` before selecting commands; use `dotnet-test:filter-syntax` for unfamiliar filters. Recheck configuration after dependency/SDK changes.

## Where tests live

| Project | Responsibility | Conventions |
| --- | --- | --- |
| [HydraScript.UnitTests](../tests/HydraScript.UnitTests) | Domain and Application behavior | Assembly-wide `Category=Unit`; xUnit, AwesomeAssertions, NSubstitute, AutoFixture |
| [LexerRegexGenerator.UnitTests](../tests/HydraScript.Infrastructure.LexerRegexGenerator.UnitTests) | Generated pattern source and ordering | Explicit `Category=Unit`; Roslyn GeneratorDriver and xUnit assertions |
| [HydraScript.IntegrationTests](../tests/HydraScript.IntegrationTests) | Real interpreter pipeline, success/error programs, console/input/dumps | TestHostFixture composes production services and substitutes external dependencies |

Before locating C# tests, use `rider-skills:finding-tests` and its documented Rider `findTests` route. Read the returned tests and fixture before adding coverage. Follow the skill's fallback rules when tooling is unavailable.

## Writing tests

- Use `dotnet-test:code-testing-agent` for test implementation and `dotnet-test:assertion-quality` for assertions; use the relevant gap-analysis skill when the task is specifically about missing coverage.
- Follow `MethodName_Scenario_ExpectedBehavior` (Roy Osherove). The happy-path convention requested in issue #205 is `MethodName_Always_Success`; prefer a specific scenario/outcome when it adds information.
- Add a minimal regression at the stage that owns the defect. For language behavior, also verify the interpreter's observable result where unit tests cannot establish it.
- Reuse [AutoHydraScriptDataAttribute](../tests/HydraScript.UnitTests/AutoHydraScriptDataAttribute.cs) for applicable unit fixtures and [TestHostFixture](../tests/HydraScript.IntegrationTests/TestHostFixture.cs) for pipeline tests. Keep semantic inputs explicit when randomized data would hide the case.
- Use a disposable runner from `fixture.GetRunner(new TestHostFixture.Options(InMemoryScript: script))`. Assert the appropriate `Executor.ExitCodes` and relevant output/diagnostics, as in [ArithmeticTests](../tests/HydraScript.IntegrationTests/SuccessPrograms/ArithmeticTests.cs) and [FunctionWithoutReturnStatementTests](../tests/HydraScript.IntegrationTests/ErrorPrograms/FunctionWithoutReturnStatementTests.cs).
- [SuccessfulProgramsTests](../tests/HydraScript.IntegrationTests/SuccessPrograms/SuccessfulProgramsTests.cs) automatically enumerates files directly in `Samples/`; those cases assert successful exit only. A new sample joins that smoke suite, but does not establish expected output. Add a focused assertion for behavior-sensitive regressions. Samples are copied to test output with `PreserveNewest`.
- Fixture defaults mock files and environment variables; sample tests explicitly use real ones. Avoid introducing machine-dependent state, input waits, or real environment mutation into new cases.
- New diagnostics need targeted negative cases; do not assume the existing sample suite covers them.

## Fixture ownership and isolation

- `TestHostFixture.GetRunner` begins with production `AddDomain`, `AddApplication`, and `AddInfrastructure` registrations. Keep the interpreter stages real in integration tests; substitute external dependencies rather than the behavior under test.
- Each returned runner owns a new service provider. Dispose the runner after use; do not assume invoking the same runner again resets mutable interpreter state.
- Use `configureTestServices` for scenario-specific replacements after the fixture defaults. File-system and environment doubles are enabled by default; an in-memory script replaces source loading.
- A class fixture can outlive individual tests and theory rows. `LogMessages` is cleared only when the fixture is disposed, not when a runner is created or disposed. Restrict assertions to messages captured for the current invocation, or use a separate fixture when isolation is required.
- Captured messages come from the fixture's fake logger, with error messages including exception text. The production CLI uses its own console logging setup; tests of captured messages are not byte-for-byte CLI-format tests.

## Commands from the repository root

Prefer Rider build/run tools for development checks and focused tests. Use Rider's terminal for the following CLI/CI commands when needed. A build start is not a result: poll its returned session; record test exit status and actual executed counts.

```powershell
dotnet --version
dotnet restore ExtendedJavaScriptSubset.slnx
dotnet build ExtendedJavaScriptSubset.slnx --no-restore -c Debug
dotnet test --solution ExtendedJavaScriptSubset.slnx -c Debug --no-build
```

Run unit-category tests (includes generator tests):

```powershell
dotnet test --solution ExtendedJavaScriptSubset.slnx -c Debug --no-build --filter-trait "Category=Unit"
```

Run one class or the integration project:

```powershell
dotnet test --project tests/HydraScript.UnitTests -c Debug --filter-class "HydraScript.UnitTests.Application.TypeDeclarationsResolverTests"
dotnet test --project tests/HydraScript.IntegrationTests -c Debug
```

Native MTP uses `--project`/`--solution` and passes MTP flags directly. xUnit filters use `--filter-class`, `--filter-method`, or `--filter-trait`. VSTest's `--filter FullyQualifiedName=...`, `--logger trx`, and `--collect` are not the commands for this setup.

Only use `--no-build` after matching outputs were built. The shared test props ignore MTP exit code 8 (no tests); therefore a successful exit alone does not prove a selected test ran. Verify a nonzero count for the intended project/class; an empty nonmatching project in a solution-wide trait run can be expected.

## Coverage and completion

The integration project references the MTP CodeCoverage extension. Do not pass `--coverage` to the whole solution: the other test projects do not register that extension.

```powershell
dotnet test --project tests/HydraScript.IntegrationTests -c Debug --no-build --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml --coverage-settings tests/coverage-exclude.xml
```

[PR CI](../.github/workflows/pr.yml) runs unit-category tests and integration coverage on Ubuntu, then requires **80% changed-line coverage** against `origin/master` using `diff-cover`. It reads `TestResults/coverage.cobertura.xml`. The threshold is not an overall project percentage. [Push CI](../.github/workflows/push.yml) runs all tests on Windows; [master CI](../.github/workflows/master.yml) collects integration coverage on Windows.

Treat the unit suite and integration coverage gate as separate checks: an integration coverage report does not establish that component or generator tests passed.

Respect [coverage-exclude.xml](../tests/coverage-exclude.xml). Do not expand exclusions simply to pass the gate. Local coverage collection alone does not verify the CI diff threshold.

For behavior changes, run the focused regression first, then affected project tests; run the full suite for changes crossing pipeline stages or shared infrastructure. Check Rider diagnostics and build where applicable. Pure documentation changes need link/path, structure, and formatting validation, without running the interpreter suite. For an isolated semantic refactor, follow the Rider skill's validation rules; behavior changes still need the relevant checks.

For performance tasks, use [HydraScript.Benchmarks](../benchmarks/HydraScript.Benchmarks) and report configuration, SDK/runtime, workload, timing, and allocations. The current harness shuffles samples and processes a subset while reusing services; control those variables before attributing a performance difference. Benchmark numbers are not correctness tests.

Report checks actually run, passed/failed counts, skipped or unmatched tests, and any limitations.
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Agent entry point

HydraScript is a statically typed scripting-language interpreter in C#. Keep this file short; load the follow-ups needed for the task.

## Route the task

- Read [architecture](.agents/architecture.md) before changing language behavior, dependencies, or project boundaries.
- Read [testing](.agents/testing.md) before changing executable code or tests, or choosing validation commands.
- Read the [ADR index](docs/adr/README.md) and relevant records before making architectural decisions. Keep checked-in guidance focused on the current implementation.
- Follow [.editorconfig](.editorconfig), [CONTRIBUTING.md](CONTRIBUTING.md), and the existing local conventions. New guidance and ADRs use English.

## Required tools and skills

- **Use Rider MCP for repository development:** inspect the open solution and worktree first; use its symbol navigation, usages, diagnostics, refactoring, build, and run capabilities where applicable. Run CLI-only operations through Rider's terminal when available.
- **Read and use the applicable Rider skills:** `rider-skills:refactoring-code` for semantic refactoring, `rider-skills:debugging-code` for runtime investigation needing debugger evidence, and `rider-skills:finding-tests` before locating tests for C# changes. Equivalent unprefixed Rider skills are valid. Follow their documented `execute_tool` routing.
- **Read and use applicable .NET skills before acting:** `dotnet-test:platform-detection` and `dotnet-test:run-tests` for test commands; `code-testing-agent` and `assertion-quality` from `dotnet-test` when authoring tests; relevant `dotnet-msbuild`, `dotnet-nuget`, `dotnet-upgrade`, or `dotnet-advanced` skills for their respective tasks. Resolve names from the installed skill catalog; read referenced instructions as required.
- Skill use is mandatory when applicable, not a requirement to run unrelated workflows. Prose changes do not require a debugger, refactoring, or test generation.
- If Rider MCP or a required skill is unavailable, report the exact limitation and use an available documented fallback. Never silently skip the requirement or claim a tool ran.

## Work and handoff

1. Establish acceptance criteria, inspect existing changes, and identify affected stages and tests.
2. Use GitHub MCP to search for context relevant to the task, affected symbols, or linked issue/PR. Read matching discussions, reviews, and release/milestone context as needed. Keep only concise evidence links for implemented decisions in ADRs; do not copy GitHub inventories or planned work into Markdown.
3. Implement the scoped change, preserving unrelated work. Use semantic refactoring tools for symbol changes and patch-based edits for other content.
4. Validate using [testing](.agents/testing.md). Update affected follow-ups and add or supersede a consistently structured ADR for a material decision.
5. Report the result, checks actually completed, and remaining limitations. Keep GitHub writes and release actions within the user's authorization.
Loading