# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`Workflow` is a CPAN Perl module providing a simple, flexible system for implementing state-machine-style workflows. It lives on CPAN as the `Workflow` distribution and is managed with [Dist::Zilla](https://metacpan.org/pod/Dist::Zilla).

The core concepts are: **Workflow** (state machine), **State** (named node), **Action** (transition), **Condition** (guard), **Validator** (input validation), **Persister** (storage backend), and **Context** (runtime data bag). Configurations are loaded from XML, YAML, or Perl data structures.

## Development Commands

This project uses Dist::Zilla (`dzil`) as its build tool. You need it installed to build, run author tests, or release.

```bash
# Install Dist::Zilla and author/build dependencies
cpm install --global Dist::Zilla
dzil authordeps | cpm install --global -
dzil listdeps --develop | cpanm

# Build the distribution into ./build/
dzil build --no-tgz --in build

# Install runtime + test deps from the built dist
cd build && cpanm --installdeps .

# Run all tests (from inside ./build/)
cd build && prove --timer --lib --recurse t/

# Run a single test file
cd build && prove --timer --lib t/workflow.t

# Run author/extended/release tests (requires AUTHOR_TESTING=1 etc.)
# Note: dzil build filters xt/ into t/ (as author-*.t / release-*.t), so only t/ exists post-build
cd build && AUTHOR_TESTING=1 EXTENDED_TESTING=1 RELEASE_TESTING=1 prove --timer --lib --recurse t/

# Run Perl::Critic linter against the source
perlcritic --profile t/perlcriticrc lib/
```

Without a full `dzil build`, you can still run tests directly from the repo root if deps are installed locally:

```bash
prove --timer --lib t/workflow.t
```

## Architecture

### Module Hierarchy

```
lib/Workflow.pm              # Core workflow object (state machine runner)
lib/Workflow/
  Factory.pm                 # Singleton factory — loads configs, creates workflow instances
  Context.pm                 # Key/value bag passed through actions
  State.pm                   # Represents a named state with its available actions
  Action.pm                  # Base class for user-defined actions; Action/Null.pm is a no-op action
  Condition.pm               # Base class for guard conditions
  Condition/                 # Built-ins: Negated (!foo), LazyAND, LazyOR, IsTrue, IsFalse, Evaluate, HasUser, Result
  Validator.pm               # Base class for field validators
  Validator/                 # Built-ins: HasRequiredField, InEnumeratedType, MatchesDateFormat
  History.pm                 # Audit-trail entry
  InputField.pm              # Describes a required/optional field on an action
  Config.pm                  # Config dispatcher; detects XML/YAML/Perl format
  Config/XML.pm              # XML config parser
  Config/YAML.pm             # YAML config parser
  Config/Perl.pm             # Perl data-structure config parser
  Persister.pm               # Base class for persistence backends
  Persister/DBI.pm           # DBI (SQL) persister
  Persister/DBI/             # ID strategies: AutoGeneratedId, SequenceId; ExtraData mixin
  Persister/File.pm          # Flat-file persister
  Persister/RandomId.pm      # Mixin for random ID generation
  Persister/UUID.pm          # Mixin for UUID ID generation
  Exception.pm               # Exception::Class hierarchy
  Base.pm                    # Shared base (Class::Accessor, Class::Factory)
```

### Key Design Patterns

- **Class::Accessor** generates accessors for all `@FIELDS`.
- **Class::Factory** backs `Workflow::Factory` for pluggable action/condition/validator/persister registration.
- **Exception::Class** is used throughout — catch with `eval`/`try` blocks or `Syntax::Keyword::Try`.
- **Log::Any** is the logging interface; wire in any backend (`Log::Any::Adapter::*`) in your application.
- The `Workflow::Factory` is a per-class singleton imported via `use Workflow::Factory qw(FACTORY)`. Multiple independent factory instances are possible by subclassing.

### Configuration Loading Flow

1. Call `FACTORY->add_config_from_file(...)` (or `add_config(...)`) with workflow, action, condition, and persister configs.
2. `Workflow::Config` auto-detects format (XML/YAML/Perl) and delegates to the appropriate parser.
3. `FACTORY->create_workflow($type)` instantiates a `Workflow` object in its `INITIAL` state.
4. `$wf->execute_action($action_name)` transitions state after evaluating conditions and validators.

### Persister Strategy

Persisters are swappable backends. The DBI persister stores workflow + history rows in SQL tables. File persister serializes to disk. All implement `create_workflow`, `fetch_workflow`, `update_workflow`, `create_history`, `fetch_history` interface defined in `Workflow::Persister`.

## Testing Notes

- Tests live in `t/` and are named after the component they test (`factory.t`, `state.t`, etc.).
- Test fixture data (YAML/XML/Perl workflow configs) lives in subdirectories like `t/workflow.d/`, `t/config.d/`, etc.
- DBI tests use `DBD::Mock` (via `DBI:Mock:` DSNs) for most `persister_dbi*.t` files, and `DBD::SQLite` specifically in `persister_dbi_sqlite.t`.
- `AUTHOR_TESTING`, `EXTENDED_TESTING`, and `RELEASE_TESTING` env vars gate the `xt/` author tests (POD, Perl::Critic, kwalitee, etc.).

## CI / GitHub Actions

- **`zizmor`** audits workflow files for security issues; run locally with `zizmor .github/workflows/`
- All action `uses:` references are pinned to commit SHAs. To get the SHA for a tag: lightweight tags return the commit SHA directly via `gh api repos/<owner>/<repo>/git/refs/tags/<tag> --jq '.object.sha'`; annotated tags (e.g. `shogo82148/actions-setup-perl`) return a tag-object SHA — dereference it with a second call to `gh api repos/<owner>/<repo>/git/tags/<sha> --jq '.object.sha'`
- The `build` job uses `cpm` (runs on latest Perl); matrix jobs (`linux`, `macos`, `windows`) use `cpanm --installdeps .` because `cpm` in `actions-setup-perl@v1` requires Perl ≥ 5.24 while the matrix tests 5.14+

## Code Style

- Perl::Critic severity 5 with project overrides in `t/perlcriticrc`.
- `.editorconfig` is present — respect it (spaces, line endings, etc.).
- All modules require `use strict; use warnings; use v5.14.0;`.
- Error handling uses `Syntax::Keyword::Try` (`try/catch`) rather than bare `eval`.
- Version strings are maintained in each module (`$Workflow::VERSION`, `$Workflow::Factory::VERSION`, etc.) and must be kept in sync; `dzil` reads the version from the main `Workflow.pm` via `[VersionFromMainModule]`. Also update the prose version number in `lib/Workflow.pm`'s `=head1 VERSION` POD section — it's not a `$VERSION` string so `grep VERSION = ` won't catch it.
- `dzil build` regenerates root `README.md` from POD via `[MarkdownInRoot]` — expect and commit this diff alongside version bumps.
- `Changes.md` follows CPAN changelog conventions and must be updated for releases.
