From 0ece2359d6cf7de9ea2e0733b90613befea08dc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:29:03 +0000 Subject: [PATCH 1/4] Initial plan From d6a86ded4719e0076f6f0477d77ab395a3a180d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:32:51 +0000 Subject: [PATCH 2/4] Add comprehensive AI-focused documentation for package consumers Co-authored-by: leeoades <2321091+leeoades@users.noreply.github.com> --- .copilot-instructions.md | 568 +++++++++++++++++++++++++++++++++++++++ AI-USAGE-GUIDE.md | 258 ++++++++++++++++++ Directory.Build.props | 1 + README.md | 30 +++ docs/index.md | 3 +- 5 files changed, 859 insertions(+), 1 deletion(-) create mode 100644 .copilot-instructions.md create mode 100644 AI-USAGE-GUIDE.md diff --git a/.copilot-instructions.md b/.copilot-instructions.md new file mode 100644 index 0000000..233e7c2 --- /dev/null +++ b/.copilot-instructions.md @@ -0,0 +1,568 @@ +# AI Assistant Instructions for FunctionalStateMachine + +## Overview + +FunctionalStateMachine is a persistence-friendly state machine library for .NET that returns **commands** instead of executing side effects. It's designed for actor-model architectures, event sourcing, and scenarios where state machines need to be persisted and rehydrated. + +## Package Installation + +```bash +# Core state machine (required) +dotnet add package FunctionalStateMachine.Core + +# Optional: DI-based command dispatcher with source generation +dotnet add package FunctionalStateMachine.CommandRunner + +# Optional: Build-time Mermaid diagram generator +dotnet add package FunctionalStateMachine.Diagrams +``` + +## Core Type Pattern + +Every state machine requires 3-4 generic type parameters: + +```csharp +// With state data (4 types) +StateMachine + +// Without state data (3 types) +StateMachine +``` + +### Type Definitions + +1. **TState** - Enum or record representing states +2. **TTrigger** - Abstract record hierarchy for trigger types +3. **TData** - Record holding state-associated data (optional) +4. **TCommand** - Abstract record hierarchy for output commands + +### Standard Pattern Example + +```csharp +// 1. Define states (enum or record) +public enum OrderState +{ + Cart, + Processing, + Completed, + Cancelled +} + +// 2. Define triggers (abstract record with sealed subtypes) +public abstract record OrderTrigger +{ + public sealed record AddItem(string ProductId, int Quantity) : OrderTrigger; + public sealed record Checkout : OrderTrigger; + public sealed record PaymentReceived(string TransactionId) : OrderTrigger; + public sealed record Cancel : OrderTrigger; +} + +// 3. Define state data (sealed record) +public sealed record OrderData( + List Items, + decimal Total, + string? TransactionId); + +// 4. Define commands (abstract record with sealed subtypes) +public abstract record OrderCommand +{ + public sealed record AddToCart(string ProductId) : OrderCommand; + public sealed record ChargeCard(decimal Amount) : OrderCommand; + public sealed record SendConfirmation(string Email) : OrderCommand; + public sealed record RefundPayment(string TransactionId) : OrderCommand; +} +``` + +## Fluent Builder Pattern + +### Basic Structure + +```csharp +var machine = StateMachine.Create() + .StartWith(initialState) + .For(stateA) + .On() + .TransitionTo(stateB) + .ModifyData((data, trigger) => data with { /* updates */ }) + .Execute((data, trigger) => new CommandType()) + .Build(); +``` + +### Key Builder Methods + +| Method | Purpose | Chain Order | +|--------|---------|-------------| +| `.StartWith(state)` | Set initial state | First, before any `.For()` | +| `.For(state)` | Configure a state | After `.StartWith()` | +| `.On()` | Handle a trigger | Inside `.For()` | +| `.TransitionTo(state)` | Target state | Inside `.On<>()` | +| `.ModifyData(func)` | Update state data | Inside `.On<>()`, before `.Execute()` | +| `.Execute(func)` | Return command(s) | Inside `.On<>()` | +| `.ExecuteSteps(func)` | Return multiple commands | Inside `.On<>()` | +| `.OnEntry(func)` | Entry commands | Inside `.For()` | +| `.OnExit(func)` | Exit commands | Inside `.For()` | +| `.Guard(func)` | Conditional routing | Inside `.On<>()`, before other actions | +| `.If(condition)` | Conditional branch | Inside `.On<>()` | +| `.Ignore()` | Ignore trigger | Instead of `.TransitionTo()` | +| `.Build()` | Build and validate | Last | + +### Firing the State Machine + +```csharp +// With data +var (newState, newData, commands) = machine.Fire(trigger, currentState, currentData); + +// Without data +var (newState, commands) = machine.Fire(trigger, currentState); + +// Execute commands (manual) +foreach (var command in commands) +{ + // Dispatch to handlers +} +``` + +## Common Patterns + +### Pattern 1: Internal Transition (No State Change) + +```csharp +.For(State.Active) + .On() + .ModifyData(data => data with { LastUpdate = DateTime.UtcNow }) + .Execute(() => new Command.RecordMetrics()) + // No .TransitionTo() = internal transition +``` + +**Entry/exit actions are skipped** for internal transitions. + +### Pattern 2: Guards for Conditional Routing + +```csharp +.For(State.Review) + .On() + .Guard(data => data.Amount > 10000) + .TransitionTo(State.ManagerReview) + .On() + .Guard(data => data.Amount <= 10000) + .TransitionTo(State.Approved) +``` + +**First matching guard wins.** If no guard matches, trigger is unhandled. + +### Pattern 3: Conditional Steps (If/Else) + +```csharp +.For(State.Processing) + .On() + .If((data, trigger) => data.IsValid) + .ModifyData(data => data with { Status = "Success" }) + .Execute(() => new Command.SendSuccessNotification()) + .TransitionTo(State.Completed) + .Else() + .Execute(() => new Command.SendErrorNotification()) + .TransitionTo(State.Failed) + .Done() +``` + +### Pattern 4: Entry and Exit Actions + +```csharp +.For(State.Active) + .OnEntry(() => new Command.StartTimer()) + .OnEntry(data => new Command.LogActivity($"User {data.UserId} active")) + .OnExit(() => new Command.StopTimer()) + .On() + .TransitionTo(State.Inactive) +``` + +Multiple `OnEntry`/`OnExit` can be chained. They execute in order. + +### Pattern 5: Multiple Commands + +```csharp +.For(State.Checkout) + .On() + .Execute(data => new Command.ChargeCard(data.Amount)) + .Execute(data => new Command.SendConfirmation(data.Email)) + .Execute(() => new Command.UpdateInventory()) + .TransitionTo(State.Completed) +``` + +Or use `ExecuteSteps`: + +```csharp +.ExecuteSteps(data => new[] +{ + new Command.ChargeCard(data.Amount), + new Command.SendConfirmation(data.Email), + new Command.UpdateInventory() +}) +``` + +### Pattern 6: Hierarchical States + +```csharp +.For(ParentState.Connected) + .StartsWith(ChildState.Idle) // Default child state + .On() + .TransitionTo(ParentState.Disconnected) // Works from any child +.For(ChildState.Idle) + .SubStateOf(ParentState.Connected) + .On() + .TransitionTo(ChildState.Working) +.For(ChildState.Working) + .SubStateOf(ParentState.Connected) + .On() + .TransitionTo(ChildState.Idle) +``` + +Parent transitions apply to all children. Parent handles take precedence over child. + +### Pattern 7: Immediate Transitions + +```csharp +.For(State.Initializing) + .OnEntry(() => new Command.LoadConfig()) + .Immediately() + .Guard(data => data.ConfigLoaded) + .TransitionTo(State.Ready) + .Done() +``` + +State automatically transitions without waiting for a trigger. + +### Pattern 8: Unhandled Trigger Handling + +```csharp +var machine = StateMachine.Create() + .OnUnhandled() + .Execute((trigger, state) => + new Command.LogWarning($"Unhandled {trigger} in {state}")) + .StartWith(State.Initial) + // ... rest of configuration + .Build(); +``` + +## Command Dispatching (Optional Package) + +### Define Command Runners + +```csharp +public sealed class ChargeCardRunner : IAsyncCommandRunner +{ + private readonly IPaymentGateway _gateway; + + public ChargeCardRunner(IPaymentGateway gateway) => _gateway = gateway; + + public async Task RunAsync(PaymentCommand.ChargeCard command) + { + await _gateway.ChargeAsync(command.Amount, command.CardToken); + } +} + +public sealed class SendEmailRunner : IAsyncCommandRunner +{ + private readonly IEmailService _email; + + public SendEmailRunner(IEmailService email) => _email = email; + + public async Task RunAsync(PaymentCommand.SendEmail command) + { + await _email.SendAsync(command.To, command.Subject, command.Body); + } +} +``` + +### Register with DI + +```csharp +// In Program.cs or Startup.cs +services.AddCommandRunners(); +``` + +**NOTE:** You must add the `FunctionalStateMachine.CommandRunner.Generator` package as an analyzer: + +```xml + + + + +``` + +### Use the Dispatcher + +```csharp +public class OrderService +{ + private readonly IAsyncCommandDispatcher _dispatcher; + + public OrderService(IAsyncCommandDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task ProcessOrderAsync(Order order) + { + var (newState, newData, commands) = + _machine.Fire(new OrderTrigger.Submit(), order.State, order.Data); + + // Dispatch all commands + await _dispatcher.RunAsync(commands); + + // Save new state + order.State = newState; + order.Data = newData; + } +} +``` + +## Testing State Machines + +State machines are pure functions, making them easy to test: + +```csharp +[Fact] +public void Checkout_ChargesCard_AndSendsConfirmation() +{ + // Arrange + var machine = BuildOrderMachine(); + var data = new OrderData(Items: ["item1"], Total: 99.99m, TransactionId: null); + var trigger = new OrderTrigger.Checkout(); + + // Act + var (newState, newData, commands) = machine.Fire(trigger, OrderState.Cart, data); + + // Assert + Assert.Equal(OrderState.Processing, newState); + Assert.Contains(commands, c => c is OrderCommand.ChargeCard); + Assert.Contains(commands, c => c is OrderCommand.SendConfirmation); +} +``` + +## Common Mistakes to Avoid + +### ❌ Don't: Perform side effects in the state machine + +```csharp +// BAD - Don't do this! +.On() + .Execute(() => { + database.Save(data); // ❌ Side effect! + return new Command.DoSomething(); + }) +``` + +### ✅ Do: Return commands describing what to do + +```csharp +// GOOD - Return a command +.On() + .Execute(data => new Command.SaveToDatabase(data)) +``` + +### ❌ Don't: Mutate data in-place + +```csharp +// BAD - Don't mutate! +.ModifyData(data => { + data.Counter++; // ❌ Mutation! + return data; +}) +``` + +### ✅ Do: Use immutable updates with 'with' expression + +```csharp +// GOOD - Immutable update +.ModifyData(data => data with { Counter = data.Counter + 1 }) +``` + +### ❌ Don't: Use .TransitionTo() for internal transitions + +```csharp +// BAD - Unnecessary state change +.For(State.Active) + .On() + .TransitionTo(State.Active) // ❌ Triggers entry/exit actions +``` + +### ✅ Do: Omit .TransitionTo() for internal transitions + +```csharp +// GOOD - Internal transition +.For(State.Active) + .On() + .Execute(() => new Command.UpdateMetrics()) + // No .TransitionTo() = stays in same state, skips entry/exit +``` + +## Data Access Patterns + +### Execute Functions Have Multiple Overloads + +```csharp +// No parameters +.Execute(() => new Command.DoSomething()) + +// Access to current data (after ModifyData) +.Execute(data => new Command.LogActivity(data.UserId)) + +// Access to trigger +.Execute(trigger => new Command.ProcessValue(trigger.Value)) + +// Access to both +.Execute((data, trigger) => new Command.Process(data.Id, trigger.Value)) + +// Access to state, data, and trigger +.Execute((state, data, trigger) => new Command.Full(state, data, trigger)) +``` + +### ModifyData Functions + +```csharp +// Access to current data only +.ModifyData(data => data with { Counter = data.Counter + 1 }) + +// Access to trigger +.ModifyData((data, trigger) => data with { LastAction = trigger.GetType().Name }) + +// Access to current state +.ModifyData((state, data) => data with { StateName = state.ToString() }) + +// Access to all +.ModifyData((state, data, trigger) => data with { + Counter = data.Counter + 1, + LastState = state, + LastTrigger = trigger.GetType().Name +}) +``` + +## Validation and Analysis + +The `.Build()` method validates: +- ✅ Initial state is set +- ✅ All referenced states are defined +- ✅ Hierarchical state relationships are valid +- ✅ No orphaned states (unless explicitly configured) +- ✅ No duplicate transition configurations + +Validation errors throw `InvalidOperationException` with detailed messages. + +## Diagram Generation (Optional) + +```csharp +using FunctionalStateMachine.Diagrams; + +[StateMachineDiagram("diagrams/OrderFlow.md")] +public static StateMachine BuildMachine() +{ + return StateMachine.Create() + // ... configuration + .Build(); +} +``` + +Generates Mermaid flowchart at compile time in the specified path. + +## When to Use This Library + +✅ **Good fits:** +- Actor-model architectures +- Event-sourced systems +- Workflow engines +- Business process automation +- Saga orchestration +- State that needs persistence between operations +- Scenarios requiring audit trails or replay + +❌ **Not ideal for:** +- In-memory-only state machines +- Simple if/else logic (use regular code) +- Real-time systems requiring microsecond latency +- Scenarios where commands are overkill + +## Quick Reference: File Structure + +When creating a new state machine in a project: + +``` +MyProject/ +├── StateMachine/ +│ ├── MyStates.cs # enum or record +│ ├── MyTriggers.cs # abstract record with sealed subtypes +│ ├── MyData.cs # sealed record +│ ├── MyCommands.cs # abstract record with sealed subtypes +│ ├── MyStateMachine.cs # builder and machine definition +│ └── CommandRunners/ # optional +│ ├── MyCommandRunner1.cs +│ └── MyCommandRunner2.cs +└── MyProject.csproj +``` + +## Version Information + +Check the [CHANGELOG.md](CHANGELOG.md) for feature additions and breaking changes. + +Current major features: +- Fluent configuration +- Guards and conditional flows +- Conditional steps (If/ElseIf/Else) +- Entry/exit actions +- Internal transitions +- Immediate transitions +- Hierarchical states +- Unhandled trigger handling +- Command runners with DI +- Mermaid diagram generation +- Static analysis + +## Further Documentation + +- [README.md](README.md) - Overview and getting started +- [docs/index.md](docs/index.md) - Complete feature documentation +- [ARCHITECTURE.md](ARCHITECTURE.md) - Technical deep-dive +- [samples/](samples/) - Working examples + +## Quick Troubleshooting + +**Issue:** "State X is not reachable" +- **Fix:** Add a transition to state X, or mark it as a valid initial/substate + +**Issue:** "Trigger Y is configured multiple times for state Z" +- **Fix:** Remove duplicate `.On()` handlers or use guards to differentiate + +**Issue:** "Commands not dispatching" +- **Fix:** Ensure `FunctionalStateMachine.CommandRunner.Generator` is added as analyzer: + ```xml + + ``` + +**Issue:** "ModifyData not updating" +- **Fix:** Ensure you return the modified data using `with` expression, not mutating in-place + +**Issue:** "Entry/exit actions firing on internal transitions" +- **Fix:** Remove `.TransitionTo(currentState)` - omit it entirely for internal transitions + +--- + +## Summary Checklist for New Implementations + +When implementing a state machine, ensure: + +- [ ] Define state enum/record +- [ ] Create trigger abstract record hierarchy +- [ ] Create command abstract record hierarchy +- [ ] Create data record (if needed) +- [ ] Build machine with `.Create().StartWith().For()...Build()` +- [ ] Use `.On()` with type-safe triggers +- [ ] Return commands, don't perform side effects +- [ ] Use immutable updates with `with` expressions +- [ ] Test by asserting on returned (state, data, commands) +- [ ] Optional: Add command runners with `IAsyncCommandRunner` +- [ ] Optional: Register with `.AddCommandRunners()` +- [ ] Optional: Annotate with `[StateMachineDiagram]` for auto-docs diff --git a/AI-USAGE-GUIDE.md b/AI-USAGE-GUIDE.md new file mode 100644 index 0000000..fe59cae --- /dev/null +++ b/AI-USAGE-GUIDE.md @@ -0,0 +1,258 @@ +# AI Assistant Quick Guide: FunctionalStateMachine + +> **For AI coding assistants:** This guide provides essential patterns for using FunctionalStateMachine in external projects. + +## Package + +```bash +dotnet add package FunctionalStateMachine.Core +``` + +## Essential Type Pattern + +```csharp +// 1. States (enum) +public enum MyState { StateA, StateB, StateC } + +// 2. Triggers (abstract record with sealed subtypes) +public abstract record MyTrigger +{ + public sealed record DoAction(string Value) : MyTrigger; + public sealed record Complete : MyTrigger; +} + +// 3. Data (sealed record) - optional +public sealed record MyData(int Counter, string Status); + +// 4. Commands (abstract record with sealed subtypes) +public abstract record MyCommand +{ + public sealed record SaveData(string Data) : MyCommand; + public sealed record SendNotification(string Message) : MyCommand; +} +``` + +## Builder Pattern + +```csharp +var machine = StateMachine.Create() + .StartWith(MyState.StateA) + .For(MyState.StateA) + .On() + .ModifyData((data, trigger) => data with { Status = trigger.Value }) + .Execute(data => new MyCommand.SaveData(data.Status)) + .TransitionTo(MyState.StateB) + .For(MyState.StateB) + .On() + .Execute(() => new MyCommand.SendNotification("Done")) + .TransitionTo(MyState.StateC) + .Build(); +``` + +## Execution + +```csharp +// Fire a trigger +var (newState, newData, commands) = machine.Fire( + new MyTrigger.DoAction("value"), + currentState, + currentData +); + +// Execute commands (your choice how) +foreach (var command in commands) +{ + switch (command) + { + case MyCommand.SaveData cmd: + await repository.SaveAsync(cmd.Data); + break; + case MyCommand.SendNotification cmd: + await notifier.SendAsync(cmd.Message); + break; + } +} +``` + +## Key Patterns + +### Internal Transition (No State Change) +```csharp +.On() + .ModifyData(data => data with { Counter = data.Counter + 1 }) + .Execute(() => new Command.Log()) +// No .TransitionTo() = stays in same state +``` + +### Guards (Conditional Routing) +```csharp +.On() + .Guard(data => data.Amount > 1000) + .TransitionTo(State.HighValue) +.On() + .Guard(data => data.Amount <= 1000) + .TransitionTo(State.LowValue) +``` + +### Conditional Steps (If/Else) +```csharp +.On() + .If((data, trigger) => data.IsValid) + .Execute(() => new Command.ProcessSuccess()) + .TransitionTo(State.Success) + .Else() + .Execute(() => new Command.ProcessError()) + .TransitionTo(State.Failed) + .Done() +``` + +### Entry/Exit Actions +```csharp +.For(State.Active) + .OnEntry(() => new Command.Start()) + .OnExit(() => new Command.Stop()) + .On() + .TransitionTo(State.Inactive) +``` + +### Multiple Commands +```csharp +.On() + .Execute(data => new Command.Save(data)) + .Execute(() => new Command.Notify()) + .Execute(() => new Command.UpdateMetrics()) + .TransitionTo(State.Done) +``` + +## Critical Rules + +✅ **DO:** +- Return commands from state machine (describe *what* to do) +- Use immutable updates: `data with { Counter = data.Counter + 1 }` +- Test by asserting on returned (state, data, commands) + +❌ **DON'T:** +- Perform I/O or side effects inside state machine +- Mutate data in-place: `data.Counter++` +- Use `.TransitionTo(currentState)` for internal transitions + +## Command Dispatching (Optional) + +```bash +dotnet add package FunctionalStateMachine.CommandRunner +``` + +```csharp +// Define runner +public class SaveDataRunner : IAsyncCommandRunner +{ + public async Task RunAsync(MyCommand.SaveData cmd) => + await repository.SaveAsync(cmd.Data); +} + +// Register (also add Generator package as analyzer) +services.AddCommandRunners(); + +// Use +await dispatcher.RunAsync(commands); +``` + +## Execute Function Signatures + +```csharp +.Execute(() => ...) // No params +.Execute(data => ...) // Data access +.Execute(trigger => ...) // Trigger access +.Execute((data, trigger) => ...) // Both +.Execute((state, data, trigger) => ...) // All +``` + +## ModifyData Function Signatures + +```csharp +.ModifyData(data => ...) // Data only +.ModifyData((data, trigger) => ...) // Data + trigger +.ModifyData((state, data, trigger) => ...) // All +``` + +## Common Pitfalls + +| Issue | Cause | Fix | +|-------|-------|-----| +| Entry/exit fire on internal | Used `.TransitionTo(currentState)` | Remove `.TransitionTo()` | +| Data not updating | Mutating in-place | Use `with` expression | +| Commands not dispatching | Generator not configured | Add as `OutputItemType="Analyzer"` | +| Unhandled trigger error | No handler configured | Add `.On<>()` or `.OnUnhandled()` | + +## Complete Example + +```csharp +public enum OrderState { Cart, Processing, Completed } + +public abstract record OrderTrigger +{ + public sealed record AddItem(string Id) : OrderTrigger; + public sealed record Checkout : OrderTrigger; +} + +public sealed record OrderData(List Items, decimal Total); + +public abstract record OrderCommand +{ + public sealed record AddToCart(string Id) : OrderCommand; + public sealed record ChargeCard(decimal Amount) : OrderCommand; +} + +var machine = StateMachine + .Create() + .StartWith(OrderState.Cart) + .For(OrderState.Cart) + .On() + .ModifyData((data, trigger) => data with + { + Items = data.Items.Append(trigger.Id).ToList() + }) + .Execute(trigger => new OrderCommand.AddToCart(trigger.Id)) + .On() + .Execute(data => new OrderCommand.ChargeCard(data.Total)) + .TransitionTo(OrderState.Processing) + .For(OrderState.Processing) + // ... more configuration + .Build(); + +// Usage +var data = new OrderData(new List(), 0m); +var (state, newData, commands) = machine.Fire( + new OrderTrigger.AddItem("item1"), + OrderState.Cart, + data); +``` + +## Documentation + +- Full documentation: https://github.com/leeoades/FunctionalStateMachine +- Feature guides: `/docs` folder in repository +- Samples: `/samples` folder in repository + +## Version Detection + +To provide version-specific guidance, check: +- `CHANGELOG.md` for feature availability +- Package version in project file +- Feature detection via namespace checking + +## When Updating for New Features + +When the library adds new features: + +1. **Check CHANGELOG.md** for version and feature details +2. **Review corresponding feature documentation** in `/docs` +3. **Update implementations** to leverage new capabilities +4. **Validate backwards compatibility** for existing state machines +5. **Consider deprecations** mentioned in release notes + +--- + +For complete documentation and advanced features, see: +- Repository: https://github.com/leeoades/FunctionalStateMachine +- Full AI Guide: `.copilot-instructions.md` in repository root diff --git a/Directory.Build.props b/Directory.Build.props index 06fbcc4..3108111 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,6 +32,7 @@ + diff --git a/README.md b/README.md index 062e512..46316ed 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Build deterministic, testable workflows where transitions produce logical comman - [Documentation](docs/index.md) - Complete feature guides - [Samples](samples/) - Example applications - [Architecture](ARCHITECTURE.md) - Technical deep-dive +- [AI Usage Guide](AI-USAGE-GUIDE.md) - Guide for AI coding assistants - [Contributing](CONTRIBUTING.md) - How to contribute - [Changelog](CHANGELOG.md) - Release history @@ -499,6 +500,35 @@ dotnet add package FunctionalStateMachine.Diagrams --- +## Using with AI Coding Assistants + +This library includes comprehensive AI-friendly documentation to help coding assistants understand how to effectively use and integrate FunctionalStateMachine in your projects. + +### For Developers + +When working with AI assistants (GitHub Copilot, Claude, ChatGPT, etc.): + +1. **Share the AI Usage Guide** - Point your AI assistant to [AI-USAGE-GUIDE.md](AI-USAGE-GUIDE.md) for quick reference patterns +2. **Reference Full Instructions** - The [.copilot-instructions.md](.copilot-instructions.md) file contains comprehensive guidance +3. **Use in Prompts** - Include context like: "Using FunctionalStateMachine library, create a state machine for [your use case]" + +### For AI Assistants + +When a user is working with FunctionalStateMachine: + +- **Quick Reference**: See [AI-USAGE-GUIDE.md](AI-USAGE-GUIDE.md) for essential patterns +- **Complete Guide**: See [.copilot-instructions.md](.copilot-instructions.md) for comprehensive instructions +- **Feature Updates**: Check [CHANGELOG.md](CHANGELOG.md) for version-specific features + +These documents provide: +- Type pattern templates (State, Trigger, Data, Command) +- Fluent API usage patterns +- Common pitfalls and solutions +- Testing approaches +- Command dispatching patterns + +--- + ## Contributing Contributions welcome! Please open an issue or PR on GitHub. diff --git a/docs/index.md b/docs/index.md index bca5913..ad20613 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,9 +2,10 @@ Welcome to the Functional State Machine docs. Each guide introduces a feature, why it is useful, and simple to advanced examples. -## Core Concepts +## Getting Started - [Packages and when to reference them](Packages.md) +- [AI Usage Guide](../AI-USAGE-GUIDE.md) - Quick reference for AI coding assistants - [Commands instead of side effects](Commands-Instead-of-Side-Effects.md) ## Core Features From 2ed55a003e9d697800208bfdaee8006ed45af47d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:34:50 +0000 Subject: [PATCH 3/4] Add AI documentation maintenance guide for contributors Co-authored-by: leeoades <2321091+leeoades@users.noreply.github.com> --- CONTRIBUTING.md | 9 + docs/AI-Documentation-Maintenance.md | 252 +++++++++++++++++++++++++++ docs/index.md | 4 + 3 files changed, 265 insertions(+) create mode 100644 docs/AI-Documentation-Maintenance.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d57ca43..b8132c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,15 @@ dotnet test ## How to Contribute +### Updating AI Documentation + +When adding features or making API changes, please update the AI-focused documentation: + +- **`.copilot-instructions.md`** - Comprehensive guide for AI assistants +- **`AI-USAGE-GUIDE.md`** - Quick reference guide (included in NuGet packages) + +See [docs/AI-Documentation-Maintenance.md](docs/AI-Documentation-Maintenance.md) for detailed guidance on keeping AI documentation current. + ### Reporting Bugs Before creating a bug report, please check existing issues to avoid duplicates. When creating a bug report, include: diff --git a/docs/AI-Documentation-Maintenance.md b/docs/AI-Documentation-Maintenance.md new file mode 100644 index 0000000..7c57e58 --- /dev/null +++ b/docs/AI-Documentation-Maintenance.md @@ -0,0 +1,252 @@ +# Maintaining AI Documentation + +This document provides guidance for maintainers on keeping the AI-focused documentation up-to-date as new features are added to FunctionalStateMachine. + +## AI Documentation Files + +1. **`.copilot-instructions.md`** - Comprehensive guide for AI assistants in the repository +2. **`AI-USAGE-GUIDE.md`** - Condensed quick-start guide included in NuGet packages + +## When to Update AI Documentation + +Update the AI documentation files when: + +- ✅ Adding a new feature or capability +- ✅ Changing existing API behavior +- ✅ Adding new builder methods or patterns +- ✅ Deprecating features +- ✅ Adding new packages +- ✅ Changing recommended patterns or best practices +- ✅ Fixing common pitfalls or issues + +## Update Process + +### 1. For New Features + +When adding a new feature: + +1. **Update Feature Documentation First** + - Create or update the relevant file in `/docs` + - Add entry to `/docs/index.md` + - Update examples in `/samples` if applicable + +2. **Update `.copilot-instructions.md`** + - Add new pattern to "Common Patterns" section if it's a major feature + - Update "Key Builder Methods" table if adding new builder methods + - Add to "Execute Function Signatures" or "ModifyData Function Signatures" if adding new overloads + - Update "Quick Reference: File Structure" if it affects project structure + - Add to "Common Mistakes to Avoid" if there are common pitfalls + - Update "Version Information" section to list the new feature + +3. **Update `AI-USAGE-GUIDE.md`** + - Add to "Key Patterns" section if it's a commonly-used feature + - Update "Complete Example" if it demonstrates a fundamental pattern + - Add to "Common Pitfalls" table if applicable + - Update "When Updating for New Features" section if there are migration considerations + +4. **Update `README.md`** + - Add feature to appropriate section with example + - Link to detailed documentation + - Update feature list if it's a major addition + +### 2. For API Changes + +When modifying existing APIs: + +1. **Update all examples** that use the changed API +2. **Update pattern descriptions** in both AI documentation files +3. **Add deprecation notes** if deprecating old patterns +4. **Update "Common Mistakes"** if the change prevents a common error + +### 3. For Breaking Changes + +When making breaking changes: + +1. **Update CHANGELOG.md** with clear migration instructions +2. **Add migration notes** to both AI documentation files +3. **Update all examples** to use new patterns +4. **Consider adding a "Migration Guide"** section if the change is significant + +## Documentation Structure Guidelines + +### `.copilot-instructions.md` Structure + +This file should maintain these sections in order: + +1. **Overview** - Brief description of the library +2. **Package Installation** - How to install +3. **Core Type Pattern** - The fundamental type system +4. **Fluent Builder Pattern** - How to build state machines +5. **Common Patterns** - Numbered patterns for frequent use cases +6. **Command Dispatching** - Optional package usage +7. **Testing State Machines** - How to test +8. **Common Mistakes to Avoid** - Anti-patterns +9. **Data Access Patterns** - Function signatures +10. **Validation and Analysis** - Build-time checks +11. **Diagram Generation** - Optional tooling +12. **When to Use This Library** - Use cases +13. **Quick Reference: File Structure** - Project organization +14. **Version Information** - Feature list +15. **Further Documentation** - Links +16. **Quick Troubleshooting** - Common issues +17. **Summary Checklist** - Implementation steps + +### `AI-USAGE-GUIDE.md` Structure + +This file should maintain these sections in order: + +1. **Package** - Quick install command +2. **Essential Type Pattern** - Core type definitions +3. **Builder Pattern** - Basic building example +4. **Execution** - How to fire and execute +5. **Key Patterns** - Most common patterns only +6. **Critical Rules** - Do's and don'ts +7. **Command Dispatching** - Optional package +8. **Execute Function Signatures** - Available overloads +9. **ModifyData Function Signatures** - Available overloads +10. **Common Pitfalls** - Table format +11. **Complete Example** - End-to-end example +12. **Documentation** - Links to more info +13. **Version Detection** - How to check versions +14. **When Updating for New Features** - Change process + +## Writing Style Guidelines + +### For AI Assistants + +- ✅ Use imperative mood: "Add trigger type" not "You should add trigger type" +- ✅ Provide concrete examples for every pattern +- ✅ Include type signatures and parameter names +- ✅ Show both simple and complex variants +- ✅ Use consistent naming in examples (MyState, MyTrigger, MyData, MyCommand) +- ✅ Explain *why* a pattern exists, not just *how* to use it +- ✅ Include anti-patterns with ❌ markers +- ✅ Include best practices with ✅ markers +- ✅ Use code blocks with `csharp` language identifier +- ✅ Keep examples self-contained and copyable + +### For Patterns + +When documenting a pattern: + +1. **Title** - Clear, descriptive name +2. **Example** - Working code snippet +3. **Explanation** - Key behaviors or notes in bold +4. **Use case** - When to apply this pattern + +Example: +```markdown +### Pattern N: Pattern Name + +[code example] + +**Key behavior description.** Additional context. +``` + +## Validation Checklist + +Before committing AI documentation updates: + +- [ ] Build solution successfully: `dotnet build` +- [ ] All code examples are syntactically correct +- [ ] All internal links work (e.g., to /docs files) +- [ ] Examples use consistent naming conventions +- [ ] New features are mentioned in both AI documentation files +- [ ] CHANGELOG.md is updated with the feature +- [ ] Feature documentation exists in `/docs` +- [ ] Version Information section lists the new feature + +## Testing AI Documentation + +To validate that AI documentation is effective: + +1. **Use with an AI Assistant** + - Share the documentation with an AI coding assistant + - Ask it to create a state machine using a new feature + - Verify it follows the documented patterns correctly + +2. **Check Examples** + - Copy examples into a test project + - Verify they compile without errors + - Run examples to ensure they work as described + +3. **Review Coverage** + - Compare feature list in documentation with actual features in code + - Ensure all public APIs are documented + - Verify all patterns in `/docs` are reflected in AI docs + +## Example: Adding a New Feature + +Let's say you're adding a new feature called "Parallel Transitions" that allows multiple state changes at once: + +### Step 1: Create Feature Documentation + +Create `/docs/Parallel-Transitions.md`: +```markdown +# Parallel Transitions + +Parallel transitions allow a state to transition to multiple target states simultaneously... + +[Full documentation with examples] +``` + +Update `/docs/index.md`: +```markdown +- [Parallel transitions](Parallel-Transitions.md) +``` + +### Step 2: Update `.copilot-instructions.md` + +Add to "Key Builder Methods" table: +```markdown +| `.ParallelTransitionTo(states)` | Multiple target states | Inside `.On<>()` | +``` + +Add to "Common Patterns": +```markdown +### Pattern N: Parallel Transitions + +[code example with explanation] +``` + +Add to "Version Information": +```markdown +- Parallel transitions +``` + +### Step 3: Update `AI-USAGE-GUIDE.md` + +Add to "Key Patterns": +```markdown +### Parallel Transitions +[brief example] +``` + +### Step 4: Update README.md + +Add section with example and link to docs. + +### Step 5: Update CHANGELOG.md + +Add entry under unreleased or current version. + +### Step 6: Build and Validate + +```bash +dotnet build +dotnet pack +# Test with AI assistant +``` + +## Keeping Documentation Discoverable + +Ensure AI assistants can find the documentation: + +1. **Reference in README.md** - Main entry point should link to AI docs +2. **Include in Packages** - AI-USAGE-GUIDE.md in NuGet package via Directory.Build.props +3. **Repository Root** - Keep `.copilot-instructions.md` at root for IDE integration +4. **Link from docs/index.md** - Make discoverable from main docs + +## Questions? + +For questions about AI documentation maintenance, open an issue or discussion on GitHub. diff --git a/docs/index.md b/docs/index.md index ad20613..f8d1592 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,3 +30,7 @@ Welcome to the Functional State Machine docs. Each guide introduces a feature, w - [Mermaid diagram generation](Mermaid-Diagram-Generation.md) - [Command runners](Command-Runners.md) + +## For Contributors + +- [AI documentation maintenance guide](AI-Documentation-Maintenance.md) - How to keep AI docs up-to-date From faa6fb045c44ca3689de083a60136aafd6e07cc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:59:19 +0000 Subject: [PATCH 4/4] Clarify that sealed keyword is optional, not required for record subtypes Co-authored-by: leeoades <2321091+leeoades@users.noreply.github.com> --- .copilot-instructions.md | 30 ++++++++++++++++++------------ AI-USAGE-GUIDE.md | 27 ++++++++++++++------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/.copilot-instructions.md b/.copilot-instructions.md index 233e7c2..4d5d246 100644 --- a/.copilot-instructions.md +++ b/.copilot-instructions.md @@ -36,6 +36,10 @@ StateMachine 3. **TData** - Record holding state-associated data (optional) 4. **TCommand** - Abstract record hierarchy for output commands +**Note:** Using `sealed` on record subtypes is a best practice for preventing further inheritance, but it's not required. Both patterns work: +- `public record MyTrigger : BaseTrigger` ✅ Works +- `public sealed record MyTrigger : BaseTrigger` ✅ Works (prevents further inheritance) + ### Standard Pattern Example ```csharp @@ -48,28 +52,30 @@ public enum OrderState Cancelled } -// 2. Define triggers (abstract record with sealed subtypes) +// 2. Define triggers (abstract record with subtypes) +// Note: 'sealed' keyword is optional but recommended to prevent further inheritance public abstract record OrderTrigger { - public sealed record AddItem(string ProductId, int Quantity) : OrderTrigger; - public sealed record Checkout : OrderTrigger; - public sealed record PaymentReceived(string TransactionId) : OrderTrigger; - public sealed record Cancel : OrderTrigger; + public record AddItem(string ProductId, int Quantity) : OrderTrigger; + public record Checkout : OrderTrigger; + public record PaymentReceived(string TransactionId) : OrderTrigger; + public record Cancel : OrderTrigger; } -// 3. Define state data (sealed record) -public sealed record OrderData( +// 3. Define state data (record) +// Note: 'sealed' is optional here too +public record OrderData( List Items, decimal Total, string? TransactionId); -// 4. Define commands (abstract record with sealed subtypes) +// 4. Define commands (abstract record with subtypes) public abstract record OrderCommand { - public sealed record AddToCart(string ProductId) : OrderCommand; - public sealed record ChargeCard(decimal Amount) : OrderCommand; - public sealed record SendConfirmation(string Email) : OrderCommand; - public sealed record RefundPayment(string TransactionId) : OrderCommand; + public record AddToCart(string ProductId) : OrderCommand; + public record ChargeCard(decimal Amount) : OrderCommand; + public record SendConfirmation(string Email) : OrderCommand; + public record RefundPayment(string TransactionId) : OrderCommand; } ``` diff --git a/AI-USAGE-GUIDE.md b/AI-USAGE-GUIDE.md index fe59cae..d9eb177 100644 --- a/AI-USAGE-GUIDE.md +++ b/AI-USAGE-GUIDE.md @@ -14,21 +14,22 @@ dotnet add package FunctionalStateMachine.Core // 1. States (enum) public enum MyState { StateA, StateB, StateC } -// 2. Triggers (abstract record with sealed subtypes) +// 2. Triggers (abstract record with subtypes) +// Note: 'sealed' is optional but recommended public abstract record MyTrigger { - public sealed record DoAction(string Value) : MyTrigger; - public sealed record Complete : MyTrigger; + public record DoAction(string Value) : MyTrigger; + public record Complete : MyTrigger; } -// 3. Data (sealed record) - optional -public sealed record MyData(int Counter, string Status); +// 3. Data (record) - optional +public record MyData(int Counter, string Status); -// 4. Commands (abstract record with sealed subtypes) +// 4. Commands (abstract record with subtypes) public abstract record MyCommand { - public sealed record SaveData(string Data) : MyCommand; - public sealed record SendNotification(string Message) : MyCommand; + public record SaveData(string Data) : MyCommand; + public record SendNotification(string Message) : MyCommand; } ``` @@ -191,16 +192,16 @@ public enum OrderState { Cart, Processing, Completed } public abstract record OrderTrigger { - public sealed record AddItem(string Id) : OrderTrigger; - public sealed record Checkout : OrderTrigger; + public record AddItem(string Id) : OrderTrigger; + public record Checkout : OrderTrigger; } -public sealed record OrderData(List Items, decimal Total); +public record OrderData(List Items, decimal Total); public abstract record OrderCommand { - public sealed record AddToCart(string Id) : OrderCommand; - public sealed record ChargeCard(decimal Amount) : OrderCommand; + public record AddToCart(string Id) : OrderCommand; + public record ChargeCard(decimal Amount) : OrderCommand; } var machine = StateMachine