machina-inspect 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,69 @@
1
+ Copyright (c) 2011-2023 Jim Cowart (MIT License)
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ See http://opensource.org/licenses/MIT for more details.
25
+
26
+ --------------------------------------------------------------------
27
+ Also available under GPL 2.0
28
+ Copyright (c) 2011-2015 Jim Cowart (GPL-2.0 License)
29
+
30
+ This program is free software; you can redistribute it and/or modify
31
+ it under the terms of the GNU General Public License as published by
32
+ the Free Software Foundation; either version 2 of the License, or (at
33
+ your option) any later version.
34
+
35
+ This program is distributed in the hope that it will be useful, but
36
+ WITHOUT ANY WARRANTY; without even the implied warranty of
37
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
38
+ General Public License for more details.
39
+
40
+ See http://opensource.org/licenses/GPL-2.0 for more details
41
+
42
+ --------------------------------------------------------------------
43
+
44
+ The extend function included in machina was very heavily borrowed
45
+ from the backbone.js project, which can be found at http://backbonejs.org.
46
+ Due to its origin, the backbone license is included below as well.
47
+
48
+ Copyright (c) 2010-2012 Jeremy Ashkenas, DocumentCloud
49
+
50
+ Permission is hereby granted, free of charge, to any person
51
+ obtaining a copy of this software and associated documentation
52
+ files (the "Software"), to deal in the Software without
53
+ restriction, including without limitation the rights to use,
54
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
55
+ copies of the Software, and to permit persons to whom the
56
+ Software is furnished to do so, subject to the following
57
+ conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
64
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
65
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
66
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
67
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
68
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
69
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # machina-inspect
2
+
3
+ Static analysis for [machina](https://machina-js.org) FSM configs. Catches structural bugs — unreachable states, infinite `_onEnter` loops, missing handlers — without running the machine.
4
+
5
+ Parses your FSM config (or a live instance) into a directed graph IR, then runs checks against it. The graph is a first-class export, so other tools (diagram generators, linters, [eslint-plugin-machina](https://www.npmjs.com/package/eslint-plugin-machina)) can build on the same representation.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install machina-inspect
11
+ # or
12
+ pnpm add machina-inspect
13
+ ```
14
+
15
+ `machina` >= 6.0.0 is a peer dependency.
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { createFsm } from "machina";
21
+ import { inspect } from "machina-inspect";
22
+
23
+ const config = {
24
+ id: "traffic-light",
25
+ initialState: "green",
26
+ states: {
27
+ green: { timeout: "yellow" },
28
+ yellow: { timeout: "red" },
29
+ red: { timeout: "green" },
30
+ broken: {}, // unreachable — no transitions lead here
31
+ },
32
+ };
33
+
34
+ const findings = inspect(config);
35
+ // [{ type: "unreachable-state", states: ["broken"], ... }]
36
+ ```
37
+
38
+ Works with live instances too:
39
+
40
+ ```ts
41
+ const fsm = createFsm(config);
42
+ const findings = inspect(fsm);
43
+ ```
44
+
45
+ ## API
46
+
47
+ ### `inspect(input): Finding[]`
48
+
49
+ Build a state graph and run all checks. Returns an array of findings — empty means no issues.
50
+
51
+ `input` is either a config object (`{ id, initialState, states }`) or a live `Fsm` / `BehavioralFsm` instance.
52
+
53
+ ### `buildStateGraph(input): StateGraph`
54
+
55
+ Build the graph IR without running checks. Use this when you want the graph for other purposes (visualization, custom analysis) and want to avoid building it twice.
56
+
57
+ ### `inspectGraph(graph): Finding[]`
58
+
59
+ Run all checks against a pre-built `StateGraph`. Pair with `buildStateGraph()` when you need both the graph and the findings.
60
+
61
+ ```ts
62
+ import { buildStateGraph, inspectGraph } from "machina-inspect";
63
+
64
+ const graph = buildStateGraph(config);
65
+ // use `graph` for diagram export, etc.
66
+ const findings = inspectGraph(graph);
67
+ ```
68
+
69
+ ### `analyzeHandler(handler): HandlerTarget[]`
70
+
71
+ Parse a handler function for transition targets via acorn AST analysis of `handler.toString()`. Returns `{ target, confidence }` pairs.
72
+
73
+ ```ts
74
+ import { analyzeHandler } from "machina-inspect";
75
+
76
+ const targets = analyzeHandler(({ ctx }) => {
77
+ if (ctx.ready) {
78
+ return "active";
79
+ }
80
+ });
81
+ // [{ target: "active", confidence: "possible" }]
82
+ ```
83
+
84
+ ### `walkHandlerAst(node): HandlerTarget[]`
85
+
86
+ Walk a pre-parsed ESTree/acorn function AST node to extract transition targets. This is the shared core used by both the runtime path (`analyzeHandler`) and the ESLint plugin. Accepts `AstFunctionNode` — a union of `FunctionDeclaration`, `FunctionExpression`, and `ArrowFunctionExpression`.
87
+
88
+ ```ts
89
+ import { walkHandlerAst } from "machina-inspect";
90
+ import type { AstFunctionNode } from "machina-inspect";
91
+
92
+ // Use with a pre-parsed AST node (e.g., from ESLint or acorn)
93
+ const targets = walkHandlerAst(functionNode as AstFunctionNode);
94
+ ```
95
+
96
+ ## Checks
97
+
98
+ ### Unreachable States
99
+
100
+ BFS from `initialState`. Any state with no inbound path is reported. Both `"definite"` and `"possible"` edges count — if there's any path, the state is reachable.
101
+
102
+ ### `_onEnter` Loops
103
+
104
+ DFS cycle detection on the subgraph of `_onEnter` transitions. Only reports cycles where **every** edge is `"definite"` (unconditional). Conditional bounces like `if (ctx.error) return "failed"` are intentional patterns, not bugs.
105
+
106
+ Same-state self-loops are excluded — the machina runtime ignores same-state transitions, so they're no-ops.
107
+
108
+ ### Missing Handlers
109
+
110
+ Collects the union of all input names across all states in the FSM. States that don't handle inputs present elsewhere are flagged. This is a best-effort check — only inputs visible as graph edges (string shorthand or statically extractable function returns) are included.
111
+
112
+ States with a `*` catch-all handler are excluded (they implicitly handle everything). `_onEnter`, `_onExit`, and `*` are excluded from the input union. Child graphs are checked independently with their own input sets.
113
+
114
+ ## Graph IR
115
+
116
+ The `StateGraph` is designed to be consumed by downstream tools:
117
+
118
+ ```ts
119
+ interface StateGraph {
120
+ fsmId: string;
121
+ initialState: string;
122
+ nodes: Record<string, StateNode>;
123
+ children: Record<string, StateGraph>; // child FSMs, keyed by parent state
124
+ }
125
+
126
+ interface StateNode {
127
+ name: string;
128
+ edges: TransitionEdge[];
129
+ }
130
+
131
+ interface TransitionEdge {
132
+ inputName: string; // handler name, "_onEnter", or "*"
133
+ from: string;
134
+ to: string;
135
+ confidence: "definite" | "possible";
136
+ }
137
+ ```
138
+
139
+ ### Confidence Levels
140
+
141
+ - **`"definite"`** — Unconditional transition. String shorthands (`timeout: "yellow"`) and functions with a single top-level return.
142
+ - **`"possible"`** — Conditional transition. Returns inside `if`, `switch`, ternary, logical expressions, or `try` blocks. Also applies when a function has multiple return statements.
143
+
144
+ Function handlers are analyzed via [acorn](https://github.com/acornjs/acorn) (AST parsing of `handler.toString()`). Non-string returns, template literals, and variable returns are ignored — the analysis is best-effort, not exhaustive.
145
+
146
+ ## Child FSMs
147
+
148
+ `_child` declarations are followed recursively. The resulting child graphs appear in `StateGraph.children`, keyed by the parent state name. Each child graph is analyzed independently (its own `initialState`, its own reachability).
149
+
150
+ ## Findings
151
+
152
+ Findings are a discriminated union — narrow on `finding.type` to access type-specific fields.
153
+
154
+ ```ts
155
+ interface BaseFinding {
156
+ message: string;
157
+ fsmId: string;
158
+ states: string[];
159
+ parentState?: string; // set for child FSM findings
160
+ }
161
+
162
+ interface UnreachableFinding extends BaseFinding {
163
+ type: "unreachable-state";
164
+ }
165
+
166
+ interface OnEnterLoopFinding extends BaseFinding {
167
+ type: "onenter-loop";
168
+ }
169
+
170
+ interface MissingHandlerFinding extends BaseFinding {
171
+ type: "missing-handler";
172
+ inputs: string[]; // the input names this state is missing
173
+ }
174
+
175
+ type Finding = UnreachableFinding | OnEnterLoopFinding | MissingHandlerFinding;
176
+ ```
177
+
178
+ ## See also
179
+
180
+ - [eslint-plugin-machina](https://www.npmjs.com/package/eslint-plugin-machina) — get these checks inline in your editor via ESLint
181
+ - [machina-explorer](https://machina-js.org/examples/machina-explorer/) — browser-based paste-and-analyze UI built on machina-inspect
182
+
183
+ ## License
184
+
185
+ MIT