diff --git a/.credo.exs b/.credo.exs index 63133c0..e533ac4 100644 --- a/.credo.exs +++ b/.credo.exs @@ -84,7 +84,7 @@ # Priority values are: `low, normal, high, higher` # {Credo.Check.Design.AliasUsage, - [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + [priority: :low, if_nested_deeper_than: 1, if_called_more_often_than: 0]}, {Credo.Check.Design.TagFIXME, []}, # You can also customize the exit_status of each check. # If you don't want TODO comments to cause `mix credo` to fail, just diff --git a/.githooks/pre-push b/.githooks/pre-push index 8a488b9..448dd93 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -26,6 +26,31 @@ if ! mix format --check-formatted; then fi fi +# Check for markdown files and lint them if markdownlint-cli2 is available +if command -v markdownlint-cli2 >/dev/null 2>&1; then + # Check if any .md files are in the repository + if find . -name "*.md" -not -path "./deps/*" -not -path "./_build/*" | head -1 | grep -q .; then + echo "๐Ÿ“‹ Checking markdown formatting..." + # Get list of project markdown files (excluding deps and _build) + md_files=$(find . -name "*.md" -not -path "./deps/*" -not -path "./_build/*") + if ! echo "$md_files" | xargs markdownlint-cli2 --config .markdownlint.json 2>/dev/null; then + echo "โŒ Markdown linting failed!" + echo "๐Ÿ’ก Fix markdown issues manually or run:" + echo " find . -name \"*.md\" -not -path \"./deps/*\" -not -path \"./_build/*\" | xargs markdownlint-cli2 --config .markdownlint.json --fix" + echo "๐Ÿ’ก Install markdownlint-cli2 with: npm install -g markdownlint-cli2" + exit 1 + fi + echo "โœ… Markdown formatting looks good" + fi +else + # Check if any .md files exist to give helpful message + if find . -name "*.md" -not -path "./deps/*" -not -path "./_build/*" | head -1 | grep -q .; then + echo "โ„น๏ธ markdownlint-cli2 not found - skipping markdown linting" + echo "๐Ÿ’ก Install with: npm install -g markdownlint-cli2" + echo "๐Ÿ’ก Or run manually: find . -name \"*.md\" -not -path \"./deps/*\" -not -path \"./_build/*\" | xargs markdownlint-cli2" + fi +fi + # Run regression tests (critical - these should never break) echo "๐Ÿงช Running regression tests..." if ! mix test.regression; then @@ -48,4 +73,4 @@ fi # exit 1 # fi -echo "โœ… All validation checks passed! Ready to push." \ No newline at end of file +echo "โœ… All validation checks passed! Ready to push." diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..24432c8 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,59 @@ +# GitHub Workflows + +This directory contains the CI/CD workflows for the SC project. + +## Workflow Strategy + +### **Conditional Execution Based on File Changes** + +The workflows are designed to run conditionally based on what files have been changed: + +#### **Code Changes** โ†’ `ci.yml` + +- **Triggers**: Changes to `.ex`, `.exs`, `mix.exs`, `mix.lock`, `config/`, etc. +- **Skips**: Documentation files (`.md`), license files, `.gitignore` +- **Jobs**: Full CI pipeline (compile, format, test, credo, dialyzer, regression tests) +- **Purpose**: Validates code quality, functionality, and compatibility + +#### **Documentation Changes** โ†’ `docs.yml` + +- **Triggers**: Changes to `.md` files, `docs/` directory +- **Jobs**: Markdown linting, link checking, documentation validation +- **Purpose**: Ensures documentation quality and consistency + +### **Benefits** + +- โœ… **Faster feedback** - Documentation changes don't run expensive code tests +- โœ… **Resource efficient** - Saves CI minutes on documentation-only changes +- โœ… **Focused validation** - Each workflow validates what actually changed +- โœ… **Clear separation** - Code and documentation validation are distinct concerns + +### **Example Scenarios** + +| Change | ci.yml | docs.yml | Result | +|--------|--------|----------|---------| +| `lib/sc/validator.ex` | โœ… Runs | โŒ Skipped | Full code validation | +| `README.md` | โŒ Skipped | โœ… Runs | Documentation validation only | +| `lib/sc/state.ex` + `CLAUDE.md` | โœ… Runs | โœ… Runs | Both workflows run | + +## Workflows + +### **`ci.yml`** - Main CI Pipeline + +- **Compilation** - Compile with warnings as errors +- **Code Formatting** - Verify `mix format` compliance +- **Testing** - Multi-version testing with coverage (Elixir 1.17+ / OTP 26+) +- **Static Analysis** - Credo strict mode validation +- **Type Checking** - Dialyzer static analysis +- **Regression Testing** - Critical functionality validation + +### **`docs.yml`** - Documentation Pipeline + +- **Markdown Linting** - Style and structure validation +- **Link Checking** - Verify external links are valid +- **Memory Validation** - Ensure CLAUDE.md and README.md have required sections +- **API Consistency** - Check that documentation references current API + +## Configuration Files + +- **`markdown-link-check-config.json`** - Link checker configuration with retry policies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 239dfbb..79053fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,22 @@ name: CI on: push: branches: [ main ] + paths-ignore: + - '**/*.md' + - 'CLAUDE.md' + - 'README.md' + - 'LICENSE*' + - 'docs/**' + - '.gitignore' pull_request: branches: [ main ] + paths-ignore: + - '**/*.md' + - 'CLAUDE.md' + - 'README.md' + - 'LICENSE*' + - 'docs/**' + - '.gitignore' env: MIX_ENV: test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..510c478 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,87 @@ +name: Documentation + +on: + push: + branches: [ main ] + paths: + - '**/*.md' + - 'CLAUDE.md' + - 'README.md' + - 'docs/**' + pull_request: + branches: [ main ] + paths: + - '**/*.md' + - 'CLAUDE.md' + - 'README.md' + - 'docs/**' + +jobs: + lint-docs: + name: Lint Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Lint Markdown files + uses: DavidAnson/markdownlint-cli2-action@v13 + with: + globs: | + **/*.md + !node_modules/**/*.md + + - name: Check for broken links + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + config-file: '.github/workflows/markdown-link-check-config.json' + + validate-memory: + name: Validate Memory Files + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check CLAUDE.md structure + run: | + echo "Validating CLAUDE.md structure..." + if ! grep -q "## Commands" CLAUDE.md; then + echo "โŒ CLAUDE.md missing Commands section" + exit 1 + fi + if ! grep -q "## Architecture" CLAUDE.md; then + echo "โŒ CLAUDE.md missing Architecture section" + exit 1 + fi + echo "โœ… CLAUDE.md structure looks good" + + - name: Check README.md structure + run: | + echo "Validating README.md structure..." + if ! grep -q "## Features" README.md; then + echo "โŒ README.md missing Features section" + exit 1 + fi + if ! grep -q "## Current Status" README.md; then + echo "โŒ README.md missing Current Status section" + exit 1 + fi + echo "โœ… README.md structure looks good" + + - name: Validate documentation consistency + run: | + echo "Checking for API consistency between CLAUDE.md and README.md..." + + # Check that both files reference SC.Validator (not SC.Document.Validator) + if grep -q "SC\.Document\.Validator" CLAUDE.md README.md; then + echo "โŒ Found outdated SC.Document.Validator references - should be SC.Validator" + grep -n "SC\.Document\.Validator" CLAUDE.md README.md || true + exit 1 + fi + + echo "โœ… API references look consistent" \ No newline at end of file diff --git a/.github/workflows/markdown-link-check-config.json b/.github/workflows/markdown-link-check-config.json new file mode 100644 index 0000000..5a5717d --- /dev/null +++ b/.github/workflows/markdown-link-check-config.json @@ -0,0 +1,22 @@ +{ + "ignorePatterns": [ + { + "pattern": "^http://localhost" + }, + { + "pattern": "^https://codecov.io" + } + ], + "timeout": "20s", + "retryOn429": true, + "retryCount": 3, + "fallbackHttpStatus": [ + 400, + 401, + 403, + 404, + 500, + 502, + 503 + ] +} \ No newline at end of file diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..d865928 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,33 @@ +{ + "default": true, + "line-length": false, + "no-duplicate-heading": false, + "no-inline-html": { + "allowed_elements": ["br", "kbd", "sub", "sup"] + }, + "whitespace": true, + "no-hard-tabs": true, + "no-trailing-spaces": true, + "no-multiple-blanks": { + "maximum": 2 + }, + "blanks-around-headings": { + "lines_above": 1, + "lines_below": 1 + }, + "heading-increment": true, + "no-missing-space-atx": true, + "no-missing-space-closed-atx": true, + "ul-style": { + "style": "dash" + }, + "ol-prefix": { + "style": "one_or_ordered" + }, + "list-indent": true, + "blanks-around-fences": true, + "fenced-code-language": false, + "first-line-h1": false, + "heading-start-left": true, + "single-trailing-newline": true +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 9be6e47..6eee849 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,24 +6,28 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **Local Validation Workflow:** When verifying code changes, always follow this sequence (also automated via pre-push git hook): + 1. `mix format` - Auto-fix formatting issues (trailing whitespace, final newlines, etc.) 2. `mix test --cover` - Ensure functionality and maintain 95%+ test coverage 3. `mix credo --strict` - Run static code analysis only after tests pass 4. `mix dialyzer` - Run Dialyzer static analysis for type checking **Git Hooks:** + - `./scripts/setup-git-hooks.sh` - Install pre-push hook for validation pipeline - Pre-push hook automatically runs the validation workflow to catch issues before CI - Located at `.git/hooks/pre-push` (executable) - Blocks push if any validation step fails **Regression Testing:** + - `test/passing_tests.json` - Registry of tests that should always pass - Tracks internal tests, SCION tests, and W3C tests separately - Updated manually when new tests start passing consistently - Used by CI pipeline to catch regressions early **Testing:** + - `mix test` - Run all internal tests (excludes SCION/W3C by default) - `mix test --include scion --include scxml_w3` - Run all tests including SCION and W3C tests - `mix test.regression` - Run regression tests that should always pass @@ -36,6 +40,7 @@ When verifying code changes, always follow this sequence (also automated via pre - `mix test test/sc/interpreter/compound_state_test.exs` - Run compound state tests **Development:** + - `mix deps.get` - Install dependencies - `mix compile` - Compile the project - `mix docs` - Generate documentation @@ -48,15 +53,16 @@ When verifying code changes, always follow this sequence (also automated via pre This is an Elixir implementation of SCXML (State Chart XML) state machines with a focus on W3C compliance. -The State Chart reference XML is here: https://www.w3.org/TR/scxml/ +The State Chart reference XML is here: This project uses Elixir Structs for the data structures, and MapSets for sets. -Also use this initial Elixir implementation as reference: https://github.com/camshaft/ex_statechart +Also use this initial Elixir implementation as reference: ## Core Components ### Data Structures + - **`SC.Document`** - Root SCXML document structure with: - Attributes: `name`, `initial`, `datamodel`, `version`, `xmlns` - Collections: `states`, `datamodel_elements` @@ -67,6 +73,7 @@ Also use this initial Elixir implementation as reference: https://github.com/cam - **`SC.DataElement`** - Datamodel elements with required `id` and optional `expr` and `src` attributes ### Parsers (Parse Phase) + - **`SC.Parser.SCXML`** - Main SCXML parser using Saxy SAX parser for accurate location tracking - Parses XML strings into `SC.Document` structs with precise source location information - Event-driven SAX parsing for better memory efficiency and location tracking @@ -84,14 +91,21 @@ Also use this initial Elixir implementation as reference: https://github.com/cam - **`SC.Parser.SCXML.StateStack`** - Manages parsing state stack for hierarchical document construction ### Validation and Optimization (Validate + Optimize Phases) -- **`SC.Document.Validator`** - Document validation and optimization + +- **`SC.Validator`** - Main validation orchestrator + - **Modular architecture**: Split into focused sub-validators for maintainability - **Validation**: Structural correctness, semantic consistency, reference validation - **Optimization**: Builds O(1) lookup maps via `finalize/2` for valid documents only - Returns `{:ok, optimized_document, warnings}` or `{:error, errors, warnings}` - **Clean architecture**: Only optimizes documents that pass validation - - Uses `find_state_by_id_linear/2` during validation, switches to O(1) after optimization +- **`SC.Validator.StateValidator`** - State ID uniqueness and validation +- **`SC.Validator.TransitionValidator`** - Transition target validation +- **`SC.Validator.InitialStateValidator`** - All initial state constraints (attributes, elements, conflicts) +- **`SC.Validator.ReachabilityAnalyzer`** - State reachability graph analysis +- **`SC.Validator.Utils`** - Shared utilities across validators ### Interpreter and Runtime + - **`SC.Interpreter`** - Core SCXML interpreter with synchronous API - Initializes state charts from validated + optimized documents - **Compound state support**: Automatically enters initial child states recursively @@ -110,6 +124,7 @@ Also use this initial Elixir implementation as reference: https://github.com/cam - **`SC.Event`** - Event representation with internal/external origins - Supports event data and origin tracking - Used for state machine event processing + ### Architecture Flow The implementation follows a clean **Parse โ†’ Validate โ†’ Optimize** architecture: @@ -119,19 +134,26 @@ The implementation follows a clean **Parse โ†’ Validate โ†’ Optimize** architect {:ok, document} = SC.Parser.SCXML.parse(xml_string) # 2. Validate + Optimize Phase: Check semantics + build lookup maps -{:ok, optimized_document, warnings} = SC.Document.Validator.validate(document) +{:ok, optimized_document, warnings} = SC.Validator.validate(document) # 3. Interpret Phase: Use optimized document for runtime {:ok, state_chart} = SC.Interpreter.initialize(optimized_document) ``` **Benefits:** + - Parsers focus purely on structure (supports future JSON/YAML parsers) - Validation catches semantic errors before optimization - Only valid documents get expensive optimization treatment - Clear separation of concerns across phases -### Test Infrastructure +### Feature Detection and Test Infrastructure + +- **`SC.FeatureDetector`** - Detects SCXML features used in documents + - Enables proper test validation by failing tests that depend on unsupported features + - Prevents false positive test results from unsupported feature usage + - Supports both XML string and parsed document analysis + - Tracks feature support status (`:supported`, `:unsupported`, `:partial`) - **`SC.Case`** - Test case template module for SCXML testing - Provides `test_scxml/4` function for testing state machine behavior - Uses SC.Interpreter for document initialization and event processing @@ -139,6 +161,7 @@ The implementation follows a clean **Parse โ†’ Validate โ†’ Optimize** architect - Used by both SCION and W3C test suites ### Location Tracking + All parsed SCXML elements include precise source location information for validation error reporting: - **Element locations**: Each parsed element (`SC.Document`, `SC.State`, `SC.Transition`, `SC.DataElement`) includes a `source_location` field with line/column information @@ -160,18 +183,21 @@ All parsed SCXML elements include precise source location information for valida This project includes comprehensive test coverage: ### SCION Test Suite (`test/scion_tests/`) + - 127+ test files from the SCION project - Module naming: `SCIONTest.Category.TestNameTest` (e.g., `SCIONTest.ActionSend.Send1Test`) - Uses `SC.Case` for test infrastructure - Tests cover basic state machines, transitions, parallel states, history, etc. ### W3C SCXML Test Suite (`test/scxml_tests/`) + - 59+ test files from W3C SCXML conformance tests - Module naming: `Test.StateChart.W3.Category.TestName` (e.g., `Test.StateChart.W3.Events.Test396`) - Uses `SC.Case` for test infrastructure - Organized by SCXML specification sections (mandatory tests) ### Parser Tests (`test/sc/parser/scxml_test.exs`) + - Unit tests for `SC.Parser.SCXML` - **Uses pattern matching** instead of multiple individual asserts for cleaner, more informative tests - Tests parsing of simple documents, transitions, datamodels, nested states @@ -179,6 +205,7 @@ This project includes comprehensive test coverage: - Ensures proper attribute handling (nil for empty values) ### Location Tracking Tests (`test/sc/location_test.exs`) + - Tests for precise source location tracking in SCXML documents - Validates line number accuracy for elements and attributes - Tests both single-line and multiline XML element definitions @@ -215,15 +242,19 @@ XML content within triple quotes uses 4-space base indentation. **Current Status:** 107/225 tests passing (47.6% pass rate) **Working Features:** + - โœ… Basic state transitions (basic1, basic2 tests pass) - โœ… **Compound states** with automatic initial child entry +- โœ… **Initial state elements** (`` with transitions) - W3C compliant - โœ… Hierarchical states with O(1) optimized lookups - โœ… Event-driven state changes -- โœ… Initial state configuration +- โœ… Initial state configuration (both `initial="id"` attributes and `` elements) - โœ… Document validation and error reporting - โœ… **Parse โ†’ Validate โ†’ Optimize** architecture +- โœ… **Modular validator architecture** with focused sub-validators **Main Failure Categories:** + - **Document parsing failures**: Complex SCXML with parallel states, history states, executable content - **Validation too strict**: Rejecting valid but complex SCXML documents - **Missing SCXML features**: Parallel states, conditional transitions, targetless transitions, internal transitions @@ -233,6 +264,7 @@ XML content within triple quotes uses 4-space base indentation. ## Implementation Status โœ… **Completed:** + - Core data structures (Document, State, Transition, DataElement) with location tracking - SCXML parser using Saxy SAX parser for accurate position tracking - **Parse โ†’ Validate โ†’ Optimize architecture** with clean separation of concerns @@ -251,8 +283,12 @@ XML content within triple quotes uses 4-space base indentation. - Active state tracking with hierarchical ancestor computation using O(1) lookups - **Git pre-push hook** for automated local validation workflow - 95%+ test coverage maintained +- **Initial state elements** (`` with ``) with comprehensive validation +- **Modular validator architecture** - refactored from 386-line monolith into focused modules +- **Full Credo compliance** - all 43 alias-related issues resolved ๐Ÿšง **Future Extensions:** + - Parallel states (``) - major gap in current implementation - History states (``) - missing from parser and interpreter - Conditional transitions with `cond` attribute evaluation @@ -266,4 +302,4 @@ XML content within triple quotes uses 4-space base indentation. The implementation follows the W3C SCXML specification closely and includes comprehensive test coverage from both W3C and SCION test suites. The current interpreter provides a solid foundation for basic SCXML functionality with clear areas identified for future enhancement. - Always refer to state machines as state charts -- Always run 'mix format' after writing an Elixir file. \ No newline at end of file +- Always run 'mix format' after writing an Elixir file. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6538f2c --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2025 John Thornton + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 96c6218..aba534f 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,18 @@ An Elixir implementation of SCXML (State Chart XML) state charts with a focus on - โœ… **Complete SCXML Parser** - Converts XML documents to structured data with precise location tracking - โœ… **State Chart Interpreter** - Runtime engine for executing SCXML state charts -- โœ… **Comprehensive Validation** - Document validation with detailed error reporting +- โœ… **Modular Validation** - Document validation with focused sub-validators for maintainability - โœ… **Compound States** - Support for hierarchical states with automatic initial child entry +- โœ… **Initial State Elements** - Full support for `` elements with transitions (W3C compliant) - โœ… **Parallel States** - Support for concurrent state regions with simultaneous execution - โœ… **O(1) Performance** - Optimized state and transition lookups via Maps - โœ… **Event Processing** - Internal and external event queues per SCXML specification - โœ… **Parse โ†’ Validate โ†’ Optimize Architecture** - Clean separation of concerns +- โœ… **Feature Detection** - Automatic SCXML feature detection for test validation - โœ… **Regression Testing** - Automated tracking of passing tests to prevent regressions - โœ… **Git Hooks** - Pre-push validation workflow to catch issues early - โœ… **Test Infrastructure** - Compatible with SCION and W3C test suites +- โœ… **Code Quality** - Full Credo compliance with proper module aliasing ## Current Status @@ -26,54 +29,75 @@ An Elixir implementation of SCXML (State Chart XML) state charts with a focus on **Regression Suite:** 22 tests (all critical functionality) ### Working Features + - โœ… **Basic state transitions** and event-driven changes - โœ… **Hierarchical states** with optimized O(1) state lookup and automatic initial child entry +- โœ… **Initial state elements** - Full `` element support with transitions and comprehensive validation - โœ… **Parallel states** with concurrent execution of multiple regions -- โœ… **Document validation** and error reporting with comprehensive structural checks +- โœ… **Modular validation** - Refactored from 386-line monolith into focused sub-validators +- โœ… **Feature detection** - Automatic SCXML feature detection prevents false positive test results - โœ… **SAX-based XML parsing** with accurate location tracking for error reporting - โœ… **Performance optimizations** - O(1) state/transition lookups, optimized active configuration - โœ… **Source field optimization** - Transitions include source state for faster event processing +- โœ… **Code quality** - Full Credo compliance with proper module aliasing throughout codebase ### Planned Features -- History states (``) + +- History states (``) - Conditional transitions with expression evaluation (`cond` attribute) - Internal and targetless transitions - Executable content (`