12 KiB
12 KiB
Tasks — FSM completion
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. Retroactive ticking is the failure mode this template guards against.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
Audit Note (2026-05-02)
Status: DEFERRED. packages/flow/fsm/src/ contains only core, errors, types, utils — none of hierarchy/, parallel/, history/, deferred/, or visualization/ exist. Six advertised features (HierarchicalStateMachine, ParallelStateMachine, HistoryStateManager, DeferredEventQueue, onEnter/onExit hooks, toMermaid/toDot exporters) are entirely missing. 0% of work is done.
1. Spec authoring
- 1.1 Read
../suaveplan/packages/universal/fsm/src/end-to-end (hierarchy/, parallel/, history/, deferred/, visualization/, fsm-hooks.ts) - 1.2 Read existing
openspec/specs/fsm/spec.md(none exists yet — will be created at archive) - 1.3 Read
openspec/AGENTS.mdSections 13, 14, and §0a (migration parity) - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.5 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.6 Draft
specs/fsm/spec.mdfromopenspec/templates/spec-delta.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles - 1.8
## Parity Checklisttable fully populated; every 🚫 has architectural justification ≤1 sentence
2. Implementation
2a. Hierarchical FSM
- 2.1 Add
src/hierarchy/hierarchical-fsm.types.tswithHierarchicalConfig,HierarchicalStateNode,HierarchicalTransition,HierarchicalTransitionResult - 2.2 Add
src/hierarchy/hierarchical-fsm.ts— portHierarchicalStateMachineclass verbatim from upstream L1-L196; preservestatePath,stateSegments,context,id,send,matchesPath; preserve common-ancestor exit/enter chain - 2.3 Add
createHierarchicalMachinefactory at end of file - 2.4 Add
src/hierarchy/index.tsbarrel re-exporting class + factory + types - 2.5 Wire transitions through
FSM_CODES.INVALID_HIERARCHYfor malformed nested definitions (instead of upstream's plainErrorstrings)
2b. Parallel regions
- 2.6 Add
src/parallel/parallel-fsm.types.tswithRegionConfig,RegionTransition,ParallelConfig,SyncPoint,ParallelState,ParallelTransitionResult - 2.7 Add
src/parallel/parallel-fsm.ts— portParallelStateMachineclass verbatim from upstream L1-L197; preservestates,context,getRegionState,combinedState,send,sendToRegionById,onSync,matchesAll,reset - 2.8 Replace silent
catchincheckSyncPoints(upstream L188-L192) withLoggerLike.warnvia injected logger; default to no-op logger when none injected - 2.9 Throw
FSMError(FSM_CODES.UNKNOWN_REGION, ...)fromsendToRegionByIdwhenregionIdis not registered - 2.10 Add
createParallelMachinefactory at end of file - 2.11 Add
src/parallel/index.tsbarrel
2c. History states
- 2.12 Add
src/history/history-state.types.tswithHistoryType,HistoryStateConfig,HistoryEntry - 2.13 Add
src/history/history-state.ts— portHistoryStateManagerfrom upstream L1-L133; replaceDate.now()(L52) withClock.now()from injected@suaveplan/types::Clock - 2.14 Add
createHistoryManager(options?: { clock?: Clock })factory; defaultClockusesDate.now()at the boundary, not module scope - 2.15 Throw
FSMError(FSM_CODES.INVALID_HISTORY_TYPE, ...)ifregisteris called with an unrecognizedHistoryType - 2.16 Add
src/history/index.tsbarrel
2d. Deferred event queue
- 2.17 Add
src/deferred/deferred-queue.types.tswithDeferredQueueConfig,DeferredEvent,StateDeferConfig - 2.18 Add
src/deferred/deferred-queue.ts— portDeferredEventQueuefrom upstream L1-L143; replaceDate.now()(L85) with injectedClock.now() - 2.19 Add overflow-policy option to
DeferredQueueConfig—'drop-newest' | 'drop-oldest' | 'throw'; on'throw', raiseFSMError(FSM_CODES.DEFERRED_QUEUE_OVERFLOW, ...) - 2.20 Add
createDeferredQueue(config?)factory - 2.21 Add
src/deferred/index.tsbarrel
2e. FSMInterpreter lifecycle hooks
- 2.22 Add
onEnter(state, listener): () => voidandonExit(state, listener): () => voidmethods toFSMInterpreterthat subscribe through the existing internalEventBusto newenter/exitevents - 2.23 Emit
enterafter a successful transition'sentryaction; emitexitbeforeexitaction runs (matches upstream listener ordering) - 2.24 Update
FSMEventMap<TContext>type to registerenter/exitchannels with payload{ state: StateValue, context: TContext, event: FSMEvent } - 2.25 Verify existing
transitionevent listeners still fire (regression guard)
2f. Visualization
- 2.26 Add
src/visualization/fsm-visualizer.ts— porttoMermaid/toDot/buildLabel/escDotfrom upstream L1-L130; rewrite the input walker against the genesisFSMDefinition.states: Record<...>shape (upstream's flatMachineConfig.transitionsarray becomes a recursive walk overstates[].on) - 2.27 Add
VisualizerOptionsinterface withcurrentState,showGuards,showActions,direction - 2.28 Add
src/visualization/index.tsbarrel
2g. Errors and exports
- 2.29 Add
INVALID_HIERARCHY,UNKNOWN_REGION,DEFERRED_QUEUE_OVERFLOW,INVALID_HISTORY_TYPEtoFSM_CODESarray insrc/errors/fsm-error.ts - 2.30 Update
src/index.tsroot barrel to re-export from./hierarchy,./parallel,./history,./deferred,./visualization - 2.31 Add subpath entries to
package.jsonexportsmap for the five new subpaths (matches./interpreter/./errorsshape already present) - 2.32 Update
package.jsondescription if needed (already mentions hierarchical / parallel / deferred — confirm wording matches what shipped)
3. Tests
3a. Hierarchical FSM
- 3.1
src/hierarchy/hierarchical-fsm.test.ts— initial path resolution,sendtraversal, guard blocking, action context update,matchesPath - 3.2
src/hierarchy/hierarchical-fsm.spec-conformance.test.ts— UML enter-from-ancestor, exit-from-leaf order; cross-boundary transition with action between exit and enter
3b. Parallel regions
- 3.3
src/parallel/parallel-fsm.test.ts— region independence, sync-point firing onmatchesAll,sendToRegionByIdscoping,combinedStateformatting,resetround-trip - 3.4
src/parallel/parallel-fsm.spec-conformance.test.ts— sync listener throwing routes through injected logger;UNKNOWN_REGIONerror path
3c. History states
- 3.5
src/history/history-state.test.ts— shallow vs deep recall, default fallback, clear-on-trigger, snapshot/restore round-trip with key filter - 3.6
src/history/history-state.spec-conformance.test.ts—Clock.now()is the only timestamp source (test injects a fixed clock);INVALID_HISTORY_TYPEthrown for bad config
3d. Deferred event queue
- 3.7
src/deferred/deferred-queue.test.ts— FIFO defer + replay, max-size truncation,clearEventfiltering, re-entrant_processingguard - 3.8
src/deferred/deferred-queue.spec-conformance.test.ts— overflow policies (drop-newest / drop-oldest / throw); injectedClock.now()populatesDeferredEvent.timestamp
3e. Lifecycle hooks
- 3.9
src/core/interpreter/fsm-interpreter.lifecycle.test.ts—onEnterfires afterentryaction;onExitfires beforeexitaction; subscriber unsubscribe path;enter/exitevents visible on internal bus
3f. Visualization
- 3.10
src/visualization/fsm-visualizer.test.ts— Mermaid output is valid stateDiagram-v2 (regex pin); DOT output is valid digraph; current-state highlight; nested states render with subgraphs / dot-path labels - 3.11
src/visualization/fsm-visualizer.spec-conformance.test.ts— DOT escaping for quotes/backslashes; Mermaiddirection LRvsTDhonoured; guard / action labels off by default flag
3g. Cross-package
- 3.12
tests/fsm-public-surface.test.ts— assert root barrel re-exports includeHierarchicalStateMachine,ParallelStateMachine,HistoryStateManager,DeferredEventQueue,toMermaid,toDot,createHierarchicalMachine,createParallelMachine,createHistoryManager,createDeferredQueue - 3.13 Every
it(...)carries{ timeout: 5000 }
4. Documentation
- 4.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference):hierarchy/hierarchical-fsm.md,parallel/parallel-fsm.md,history/history-state.md,deferred/deferred-queue.md,visualization/fsm-visualizer.md - 4.2 README links every new co-located
.mdunder## Modules - 4.3 Update root
README.mdquick-start section with a hierarchical machine example and a parallel-regions example - 4.4 Update
CLAUDE.md@suaveplan/fsmpackage listing — confirm it already mentions hierarchical / parallel / deferred / visualization; remove "nested state validation" wording if it implies validator-only (we ship full executor now)
5. Pre-archive verification (the 5-gate triple)
- 5.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the whole monorepo - 5.2
bunx turbo lint typecheck test --filter=@suaveplan/fsm— green - 5.3
bunx turbo test:coverage --filter=@suaveplan/fsm— 100/100/100/100 - 5.4
bun scripts/openspec-conformance.ts @suaveplan/fsm— exit 0, noerrorfindings - 5.5
bunx turbo build --filter=@suaveplan/fsm— green
6. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 6.1 Generics threaded;
expectTypeOfassertions on every literal-type claim (HierarchicalStateMachine<TEvent, TContext>infers state-path strings;ParallelStateMachine<TState, TEvent, TContext>preserves region-state union) - 6.2 Zod schemas wired at every declared boundary (none yet — these classes accept typed objects, not external JSON; if validation is required at construction add
validateHierarchicalConfig/validateParallelConfighelpers) - 6.3 Every error code in
error-codes.tsthrown by at least one production path (INVALID_HIERARCHY,UNKNOWN_REGION,DEFERRED_QUEUE_OVERFLOW,INVALID_HISTORY_TYPE) - 6.4 Strict-mode behaviors tested both on and off (queue overflow policy
'throw'vs'drop-*'; sync-listener exception bubbling vs swallowed) - 6.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider(existingfsm.transition.total/fsm.transition.duration_mspreserved; new countersfsm.hierarchy.transition.total,fsm.parallel.transition.total,fsm.deferred.queue.size) - 6.6 Lifecycle dedup tested where claimed (
onEnter/onExitunsubscribe is idempotent; subscriber removed by returned disposer never re-fires) - 6.7 No retroactive tick — every box above reflects work done in this change
7. Archive
- 7.1 Fold spec delta into
openspec/specs/fsm/spec.md(file does not exist yet — this change creates it) - 7.2 Move
openspec/changes/fsm-completion/→openspec/archive/<yyyy-mm-dd>-fsm-completion/ - 7.3 Refresh
MANIFEST.mdif package set or deps changed (no new deps; subpath exports added)