claude-workflow-viz 1.0.0a1__tar.gz

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.
Files changed (36) hide show
  1. claude_workflow_viz-1.0.0a1/.gitignore +26 -0
  2. claude_workflow_viz-1.0.0a1/CHANGELOG.md +36 -0
  3. claude_workflow_viz-1.0.0a1/LICENSE +201 -0
  4. claude_workflow_viz-1.0.0a1/PKG-INFO +382 -0
  5. claude_workflow_viz-1.0.0a1/README.md +351 -0
  6. claude_workflow_viz-1.0.0a1/pyproject.toml +79 -0
  7. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/__init__.py +3 -0
  8. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/__main__.py +5 -0
  9. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/cli.py +86 -0
  10. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/__init__.py +1 -0
  11. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/connection_file.py +147 -0
  12. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/server.py +379 -0
  13. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/__init__.py +1 -0
  14. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/graph.py +519 -0
  15. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/node_status.py +30 -0
  16. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/__init__.py +1 -0
  17. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/parse_failures.py +249 -0
  18. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/parser.py +586 -0
  19. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/install/__init__.py +1 -0
  20. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/install/installer.py +259 -0
  21. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/__init__.py +1 -0
  22. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/_platform.py +147 -0
  23. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/bootstrap.py +207 -0
  24. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/VENDORED.md +14 -0
  25. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/app.js +387 -0
  26. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/index.html +48 -0
  27. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/mermaid.LICENSE +21 -0
  28. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/mermaid.min.js +3636 -0
  29. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/status_rules.json +15 -0
  30. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/styles.css +190 -0
  31. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/viz-core.js +557 -0
  32. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/__init__.py +1 -0
  33. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/events.py +252 -0
  34. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/hook_adapter.py +342 -0
  35. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/hook_command.py +71 -0
  36. claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/run_state.py +282 -0
@@ -0,0 +1,26 @@
1
+ .venv/
2
+ venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .fsm/
11
+ node_modules/
12
+ .env
13
+ .env.*
14
+ .DS_Store
15
+ .claude-local/
16
+ .tmp/
17
+ .venv-sandbox/
18
+ .agentic-dev-loop/
19
+ .pr/
20
+ agentic-dev-loop/.pr/
21
+ .tmp-exec/
22
+ .review-venv/
23
+ .opencode/package.json
24
+ .opencode/package-lock.json
25
+ .opencode/bun.lock
26
+ .hypothesis/
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0a1] - 2026-10-25
11
+
12
+ ### Changed
13
+
14
+ - Log the shutdown race fix in the audit
15
+ - Fix /shutdown race: close the socket only on the main thread
16
+ - Publishing machinery
17
+ - Mark AI-DLC session complete in state file
18
+ - Close AI-DLC session: operations stage has no work for a desktop tool
19
+ - Add Build and Test gate artifacts and stage bookkeeping
20
+ - Fix serve-shutdown test connection read and viz-dir path assertions for Windows
21
+ - Sort imports after platform-aware test edits
22
+ - Make POSIX-only tests platform-aware so the Windows CI job passes
23
+ - Log demo terminal fix in audit
24
+ - Fix demo terminal rendering: fixture events now satisfy the ingest contract
25
+ - Light terminal nodes green when the run hits them
26
+ - Fix demo skip rendering: replay events with run_id; add demo-progression regression test
27
+ - Extend run-demo.sh with a skipped stage (Unit D demo); Unit D approval bookkeeping
28
+ - feat: construct unit unit-d-skipped-stage (agent dev loop)
29
+ - feat: construct unit unit-c-install (agent dev loop)
30
+ - Install unit docs
31
+ - Extend run-demo.sh to the full progression with hold, fix interpreter default
32
+ - feat: construct unit unit-b-hook-path (agent dev loop)
33
+ - Docs
34
+ - feat: construct unit unit-a-graph-source (agent dev loop)
35
+ - Initial commit
36
+
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,382 @@
1
+ Metadata-Version: 2.5
2
+ Name: claude-workflow-viz
3
+ Version: 1.0.0a1
4
+ Summary: Live Mermaid flowchart of a Claude Code Workflow run, driven by hooks
5
+ Project-URL: Homepage, https://github.com/lukedawilson/claude-workflow-viz
6
+ Author: Luke Wilson
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: pyyaml>=6.0.3
25
+ Provides-Extra: dev
26
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
27
+ Requires-Dist: packaging>=24; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # claude-workflow-viz
33
+
34
+ > **Turn any Claude Code workflow run into a live Mermaid flowchart — passively, from hooks.**
35
+
36
+ Register one hook command, run `claude-workflow-viz serve`, and every workflow run in the
37
+ repo lights up a graph in your browser: stages running, passing, failing, and retrying as the
38
+ workflow's agents do the work. The visualiser observes the run from the outside; it never
39
+ touches the workflow itself. The step graph is configuration data, not tied to any one workflow.
40
+
41
+ ```mermaid
42
+ flowchart LR
43
+ run["Claude Code workflow run"] -- "hook event (stdin)" --> hook["claude-workflow-viz hook"]
44
+ hook -- "POST /ingest (Bearer token)" --> serve["claude-workflow-viz serve"]
45
+ serve -- "SSE snapshot + events" --> ui["browser UI (vendored Mermaid)"]
46
+ ```
47
+
48
+ - **An observer, not a driver.** Hooks forward events to a local server. If the visualiser
49
+ dies mid-run, the workflow continues unaffected.
50
+ - **Silent by design.** The hook path always exits 0 and writes nothing to stdout or stderr —
51
+ it can never disturb a session.
52
+ - **Zero build step.** One dependency (PyYAML), stdlib HTTP, and Mermaid vendored locally.
53
+ No CDN, no bundler, no network at runtime.
54
+
55
+ ---
56
+
57
+ ## Core features
58
+
59
+ | Feature | What it gives you |
60
+ | --- | --- |
61
+ | **Live step graph** | Nodes light up as your workflow's agents run: running steps pulse, finished steps show pass/fail, retries render on the node. |
62
+ | **Verdict-driven colours** | A finished step's status is derived from its journal verdict (`APPROVED`, `CHANGES_REQUESTED`, `FAIL`, …) via a shared JSON rules file — one home for the strings the UI matches. |
63
+ | **Configurable graph** | One `.viz.yaml` file maps your workflow's stages and step keys to named nodes with pass/fail routing. Without one, a linear graph is built from the stages as they first appear. |
64
+ | **Session-scoped state** | Concurrent sessions are tracked separately: per-session steps, status history, run summaries. Bounded (64 sessions LRU, 5000 steps) so a long-lived server stays small. |
65
+ | **Locked-down transport** | Random 32-byte token, loopback binding only, `Host` header allowlist, body-size caps, and a connection file written 0600 and removed on shutdown — while it still holds the server's own token. |
66
+ | **Self-contained UI** | Static files served from the package with Mermaid vendored in-tree. Everything works offline. |
67
+
68
+ The fixed policies *are* the product: the token is never logged, an invalid or
69
+ missing token is a 401, an over-large payload is a 413, a malformed event is a
70
+ 400 with a typed error code, and the hook side swallows everything — the
71
+ visualiser gets events only when everything is well-formed.
72
+
73
+ ## Installation
74
+
75
+ **Prerequisites**
76
+
77
+ | Requirement | Detail |
78
+ | --- | --- |
79
+ | Python | ≥ 3.12 |
80
+ | Claude Code | Only for the hook side — the `serve` side runs anywhere. |
81
+
82
+ ```sh
83
+ pipx install .
84
+ claude-workflow-viz --help
85
+ ```
86
+
87
+ ## Usage
88
+
89
+ ### Subcommands
90
+
91
+ | Command | Purpose |
92
+ | --- | --- |
93
+ | `claude-workflow-viz serve [--port N] [--graph FILE]` | Run the local server (127.0.0.1 only) and the web UI. |
94
+ | `claude-workflow-viz hook` | Receive a Claude Code hook event on stdin and forward it to the server. |
95
+ | `claude-workflow-viz install [--viz-dir PATH] [--force]` | Wire a repo: verify, register the five hooks, create the `/viz` command. |
96
+
97
+ **1. Start the visualiser.**
98
+
99
+ ```sh
100
+ claude-workflow-viz serve
101
+ # claude-workflow-viz listening on http://127.0.0.1:8322
102
+ # open the UI at http://127.0.0.1:8322/?token=<random-token>
103
+ ```
104
+
105
+ `serve` prefers port 8322; when that port is busy it falls back to an OS-picked free port.
106
+ `--port 0` forces an OS-picked port. The actual bound port is printed on start and written
107
+ to the connection file. Open the printed URL — the page reads the token from the query
108
+ string and uses it for `/graph` and `/stream`. Ctrl-C shuts the server down and removes
109
+ the connection file.
110
+
111
+ `hook` finds `serve` through the connection file, so the two sides never share a config file:
112
+
113
+ | State directory, first match wins | |
114
+ | --- | --- |
115
+ | 1. `$CLAUDE_WORKFLOW_VIZ_STATE_DIR/server.json` | used as is |
116
+ | 2. `$XDG_STATE_HOME/claude-workflow-viz/server.json` | standard XDG state |
117
+ | 3. `~/.local/state/claude-workflow-viz/server.json` | default |
118
+
119
+ The file has mode 0600 and the directory (when created by the tool) mode 0700. It is created
120
+ with that mode from the start in a temporary file next to the target and moved into place with
121
+ `os.replace`, so a reader never sees a partial file and the token is never briefly world
122
+ readable. `serve` removes it on shutdown only while it still holds this server's token; a file
123
+ written by a newer server is left in place. A missing, unreadable or malformed file means no
124
+ server is running, and `hook` quietly does nothing.
125
+
126
+ **2. Register the hooks** in the repo where the workflow runs
127
+ (`.claude/settings.local.json`). The `install` subcommand writes these for you
128
+ (see "Installer" below); this JSON is the contract it writes:
129
+
130
+ ```json
131
+ {
132
+ "hooks": {
133
+ "PreToolUse": [
134
+ {"matcher": "Workflow", "hooks": [{"type": "command", "command": "claude-workflow-viz hook", "async": true}]}
135
+ ],
136
+ "PostToolUse": [
137
+ {"matcher": "Workflow", "hooks": [{"type": "command", "command": "claude-workflow-viz hook", "async": true}]}
138
+ ],
139
+ "SubagentStart": [
140
+ {"hooks": [{"type": "command", "command": "claude-workflow-viz hook", "async": true}]}
141
+ ],
142
+ "SubagentStop": [
143
+ {"hooks": [{"type": "command", "command": "claude-workflow-viz hook", "async": true}]}
144
+ ]
145
+ }
146
+ }
147
+ ```
148
+
149
+ `async` keeps the hook off the workflow's critical path; `claude-workflow-viz hook` reads the
150
+ payload from stdin, forwards it, and exits 0 no matter what.
151
+
152
+ **3. Run the workflow.** The graph updates live.
153
+
154
+ **What a run looks like:**
155
+
156
+ 1. **serve** binds 127.0.0.1, generates the token, writes the 0600 connection
157
+ file, and prints the UI URL.
158
+ 2. **The workflow runs.** Each `SubagentStart`/`SubagentStop` and Workflow
159
+ `PreToolUse`/`PostToolUse` becomes an event and lands in the server's run
160
+ state, keyed by session.
161
+ 3. **Nodes light up.** Steps are matched to nodes by `stage` or exact
162
+ `step_key`; a node without a step renders pending.
163
+ 4. **Verdicts paint the graph.** When a step finishes, its journal verdict
164
+ (`APPROVED`, `PASS`, …) decides the node's pass/fail colour; unknown
165
+ verdicts fall back to the step's reported status.
166
+ 5. **Routes fire.** Pass and fail edges highlight, retries re-open nodes, and
167
+ exhausting `max_rounds` routes to the `on_rounds_exhausted` terminal.
168
+ 6. **Skipped stages grey out.** A non-terminal stage the run bypassed renders skipped — grey dashed stroke and muted fill — once a later stage arrives or the run ends.
169
+
170
+ ### Installer
171
+
172
+ `claude-workflow-viz install` wires a repo in one command:
173
+
174
+ ```sh
175
+ claude-workflow-viz install [--viz-dir <path>] [--force]
176
+ ```
177
+
178
+ It first verifies the tool by running `claude-workflow-viz hook` once with empty stdin.
179
+ Then it writes the five hook registrations into `.claude/settings.local.json`.
180
+ The registrations are the four hooks shown above plus `SessionEnd`.
181
+ It also creates `.claude/commands/viz.md`, the `/viz` command the agent fallback runs.
182
+ Existing unrelated hooks are preserved, and an already-wired repo is left byte-for-byte unchanged.
183
+ Re-running is safe: the second install re-verifies and reprints the summary but writes nothing.
184
+
185
+ - `--viz-dir <path>` picks the directory for agent-generated `.viz.yaml` files.
186
+ The default is `.claude-local/viz/`. The choice is recorded in the `/viz` command file.
187
+ - `--force` overwrites an existing `.claude/commands/viz.md`; without it the command is skipped.
188
+
189
+ On the next workflow run the `PreToolUse` hook bootstraps the visualiser, as described in
190
+ "Invoke-time bootstrap".
191
+
192
+ ### Invoke-time bootstrap
193
+
194
+ When a workflow starts, the Workflow `PreToolUse` hook bootstraps the visualiser.
195
+ No manual `serve` step is needed:
196
+
197
+ 1. The hook reads the connection file and probes `GET /graph` to see whether a server is
198
+ already running. A live server is reused unchanged.
199
+ 2. The hook statically parses the invoked workflow's JavaScript
200
+ (`<repo>/.claude/workflows/<name>.js`) into the graph contract and writes
201
+ `<state>/graphs/<name>.viz.yaml`. Other workflows' caches are left untouched.
202
+ 3. If no server is live, the hook spawns `serve` detached on the preferred port 8322,
203
+ falling back to a free port when busy. The server's output goes to the state-dir debug log,
204
+ never the hook's stream.
205
+ 4. The hook waits at most 10 seconds for the connection file, then opens the dashboard at
206
+ `/?token=<token>&workflow=<name>` in the default browser.
207
+
208
+ If the workflow cannot be parsed, no cache is written and the server starts in linear mode;
209
+ the dashboard shows the unmapped-step warning, and the state-dir debug log carries the
210
+ parser's failure summary. `SessionEnd` stops the server by posting `/shutdown`.
211
+
212
+ ### Server endpoints
213
+
214
+ | Endpoint | Auth | Behaviour |
215
+ | --- | --- | --- |
216
+ | `POST /ingest` | `Authorization: Bearer <token>` | One JSON event, at most 64 KiB. 204 applied, 400 bad JSON or invalid event, 401 missing or wrong token, 413 body too large. |
217
+ | `POST /shutdown` | `Authorization: Bearer <token>` | Stops the server and removes its connection file (the same sequence as Ctrl-C). 401 on a missing or wrong token, 204 on success. |
218
+ | `GET /` | none (the page itself holds no run data) | The web UI HTML. Open it as `/?token=<token>`; the page reads the token from the URL and uses it for `/graph` and `/stream`. |
219
+ | `GET /static/<file>` | none | A file from the package's `static` directory (`app.js`, `viz-core.js`, `styles.css`, `mermaid.min.js`, `status_rules.json`, ...). Only existing `.html`, `.js`, `.css` and `.json` files are served; anything else, including `VENDORED.md` and `mermaid.LICENSE`, is 404. |
220
+ | `GET /graph?token=<token>[&workflow=<name>]` | `token` query parameter | JSON `{"graph", "source", "status_rules"}`. `source` is `config` when a graph was supplied (`serve --graph` or the per-workflow registry), else `linear`. `workflow=<name>` serves that workflow's graph from `<state>/graphs/<name>.viz.yaml`; an unknown name falls back to linear. 401 on a missing or wrong token. |
221
+ | `GET /stream?token=<token>` | `token` query parameter (a browser `EventSource` cannot set headers) | `text/event-stream`. First a `snapshot` event with the current run state, then one `event` per applied event, with a `: keep-alive` comment every 15 seconds. 401 on a missing or wrong token. |
222
+
223
+ Every endpoint accepts only a `Host` header of `127.0.0.1:<port>` or `localhost:<port>` (the
224
+ IPv6 form `[::1]:<port>` is not accepted because the server binds IPv4 only); a missing or any
225
+ other `Host` gets 421 before the token check. A request body that stalls for 10 seconds, such
226
+ as fewer bytes than its `Content-Length`, closes the connection. Any other path returns 404.
227
+
228
+ ### Run state and limits
229
+
230
+ The `snapshot` event (and `RunState.snapshot()`) has this shape per session:
231
+
232
+ ```json
233
+ {"sessions": {"<session_id>": {
234
+ "steps": {"<step_key>": {"statuses": ["running", "pass"], "summary": "", "finished": true,
235
+ "agent_id": "", "run_id": "", "slice": "", "stage": "",
236
+ "round": null, "phase": "", "result": null,
237
+ "node_status": "pending | running | pass | fail | retry"}},
238
+ "runs": {"<run_id>": {"status": "running | finished", "step_keys": ["<step_key>"]}},
239
+ "events": []}}}
240
+ ```
241
+
242
+ Steps with an `agent_id` are paired by it, so a start and stop pair even when their step keys
243
+ differ; a stop with no start creates a finished step, and a start with an `agent_id` arriving
244
+ after its stop does not reopen it. A start without an `agent_id` on a finished step is a retry
245
+ round: it appends its status and reopens the step. A run is `running` while any step is
246
+ unstopped and `finished` once it has steps and all have stopped; a step start that opens a step
247
+ returns a finished run to `running`. An explicit `run_stop` is final for that run id: a later
248
+ `run_start` or step start never returns it to `running` (step fields still update). The snapshot
249
+ flags each such run with `stopped: true`, so a client that reconnects after the `run_stop` event
250
+ has aged out of the event log keeps the run finished.
251
+
252
+ Run state is bounded so a misbehaving client cannot grow server memory without limit:
253
+
254
+ | Limit | Value | When exceeded |
255
+ | --- | --- | --- |
256
+ | Sessions | 64 | A new session evicts the least recently updated one (every applied event updates its session; this is not the oldest created). |
257
+ | Steps per session | 5000 | An event that resolves to a new step key creates no step and does not change run status. It is still logged in `events` and delivered to `/stream` subscribers. Existing steps keep updating. |
258
+ | Statuses per step | 100 | Only the last 100 are kept, in order; the oldest is dropped. |
259
+
260
+ A step's `result` is copied when stored, so later changes to the incoming event do not affect it.
261
+
262
+ ### Graph configuration
263
+
264
+ `serve --graph FILE` loads the step graph from a `.viz.yaml` file; an invalid file makes `serve`
265
+ exit with an error instead of starting. Without `--graph` the server builds a linear graph: one
266
+ node per distinct stage seen in events (first-seen order), ending in `done`. A stage named
267
+ `done`, `blocked` or `needs-context` gets the node id `stage-<stage>` (plus a numeric suffix if
268
+ that id is taken) so it never collides with a terminal node; the node keeps the original stage
269
+ and label.
270
+
271
+ Schema (see `examples/implement.viz.yaml`):
272
+
273
+ | Key | Meaning |
274
+ | --- | --- |
275
+ | `name`, `entry` | Graph name and the id of the first node. |
276
+ | `max_rounds`, `on_rounds_exhausted` | Optional retry limit and the node to go to when it is used up. |
277
+ | `step_pattern` | Optional regex with named groups `slice`, `stage`, `round`; `stage` is required. Rejected: longer than 200 characters; nested quantifiers such as `(a+)+`, `(a*)*`, `(?:a+)*`, `(a{1,})+`; Python-only syntax the UI cannot convert (`(?P=name)`, inline flags `(?i)` or `(?i:...)`); anything Python cannot compile, including variable-width lookbehind. `(?P<name>...)` is allowed. |
278
+ | `nodes[]` | `id`, `label`, one of `stage` or `step_key` to match events, `terminal: true` for end nodes, `on_pass` / `on_fail` as the ids to go to next. |
279
+
280
+ A value may not be one node's `stage` and another node's `step_key`; `serve --graph` rejects such
281
+ a file and names both nodes and the value. One node may use the same value for both. A duplicate
282
+ key anywhere in the file is rejected with the key and its line number.
283
+
284
+ ```yaml
285
+ name: implement
286
+ entry: pre-flight
287
+ max_rounds: 3
288
+ on_rounds_exhausted: blocked
289
+ nodes:
290
+ - {id: pre-flight, label: Pre-flight, step_key: pre-flight/head-check, on_pass: planner, on_fail: blocked}
291
+ - {id: develop, label: Develop, stage: develop, on_pass: gate, on_fail: blocked}
292
+ - {id: gate, label: Gate, stage: gate, on_pass: review, on_fail: develop}
293
+ - {id: done, label: Done, terminal: true}
294
+ - {id: blocked, label: Blocked, terminal: true}
295
+ ```
296
+
297
+ Every validation failure returns a typed error code (`GraphError`) naming the node and field —
298
+ never a stack trace.
299
+
300
+ ### Static parse subset
301
+
302
+ At invoke time the visualiser statically parses the invoked workflow's JavaScript into the same `.viz.yaml` schema above.
303
+ The parser code is the authoritative subset definition.
304
+ Anything outside the subset is a named failure — never a guessed graph — and degrades to a hand-authored fallback.
305
+
306
+ | Construct | Parsed as |
307
+ | --- | --- |
308
+ | `export const meta = { name }` | graph `name` |
309
+ | `const MAX_ROUNDS = <int>` | `max_rounds`; `if (round >= MAX_ROUNDS) return X` → `on_rounds_exhausted` |
310
+ | `agent(...)` with a string-literal label | a `step_key` node matched on the whole label |
311
+ | `agent(...)` with a `${slice}/<stage>:r${round}` template label | a `stage` node matched on the literal stage |
312
+ | `while`/`for` with `continue` back to an earlier agent | a loop-back `on_fail` edge to the re-entered node |
313
+ | early `return DONE` / `BLOCKED` / `NEEDS_CONTEXT` | terminal nodes `done` / `blocked` / `needs-context` |
314
+ | an `if`-guarded agent with no agent in the `else` | a normal node (the guard is not encoded) |
315
+ | `Promise.all` of `agent(...)` calls | one node per member |
316
+
317
+ Each construct outside the subset produces one typed `ParseFailure`, collected exhaustively in one pass.
318
+ The codes are `unsupported-label`, `missing-max-rounds`, `unsupported-control-flow`, `unsupported-terminal`, `unsupported-conditional`, and `unsupported-parallel`, plus `missing-meta` and `no-agent-calls`.
319
+ A report, not a partial graph, is the result whenever any failure is found.
320
+
321
+ ## Development environment
322
+
323
+ ```sh
324
+ uv run pytest
325
+ uv run ruff check # lint (E, F, W, I, B, UP, SIM, N)
326
+ uv run ruff format --check # formatting
327
+ node --test tests/js # the browser-side status logic in static/viz-core.js; needs node
328
+ ```
329
+
330
+ - Development is strict TDD — the history is red/green slices; each slice's red commit names
331
+ the test it fails.
332
+ - `tests/fixtures/probe_payloads.jsonl` holds real Claude hook payloads captured from a live
333
+ run; the adapter tests replay them.
334
+ - `docs/plan.md` records the architecture decisions — the hook-driven design and why the
335
+ transcript-tail adapter was deferred (the Phase 0 probe proved it unnecessary).
336
+
337
+ ### Agentic dev loop
338
+
339
+ The repo's default development mode is the user driving the [agentic-dev-loop](https://pypi.org/project/agentic-dev-loop/) construction pipeline — see `AGENTS.md` ("Default development mode — ASK BEFORE CODING").
340
+
341
+ ```sh
342
+ pipx install agentic-dev-loop # outside the workspace — never editable, never in the repo .venv
343
+ export OPENAI_API_KEY=... # no .env files — the engine refuses a run when the workspace has .env*
344
+ ./trigger-agentic-dev-loop.sh --plan planning-docs/your-plan.md
345
+ ```
346
+
347
+ - The trigger pipx-upgrades the engine on every run, so a stale install can't reject newer pipeline configs.
348
+ - The pipeline prints a thread id on every run;
349
+ resume a crashed run with `--resume <thread-id>`.
350
+ - The viz web server runs on `http://localhost:8321` by default;
351
+ disable with `--no-viz`.
352
+ - `agentic-dev-loop/` is an example to adapt:
353
+ `pipeline.yaml` (steps `dev → restore → test → review → qa → commit`, `deps_policy: open`)
354
+ and `sdk_agents/{dev,review,qa}.agent.md`.
355
+ The example restore/test commands install the project and the dev tools
356
+ directly (`pytest`, `ruff`, `pip-audit`) — dev deps live in uv's `[dependency-groups]`, so there is no `.[dev]` extra to install.
357
+
358
+ ### Agent instructions and standards
359
+
360
+ - `AGENTS.md` — ask-before-coding default, TDD, mock policy, writing/tone rules.
361
+ - `coding-standards-ddd.yaml` — DDD + simple-code standards;
362
+ `/load-backend-rules` loads it into a session before backend work.
363
+ - `.aidlc/aidlc-rules/` — AWS AI-DLC ruleset;
364
+ the entry point is `aws-aidlc-rules/core-workflow.md`.
365
+ Working docs (plans, user stories, unit-of-work, state) live in `aidlc-docs/`.
366
+ - `.opencode/` — Builder (default), Planner, and Readonly agents;
367
+ commands `/apply-aidlc-patches`, `/epic-status`, `/export-opencode-tools-to-claude`, `/figma`, `/load-backend-rules`, `/postmortem`, `/software-selection`, `/ticket-details`, `/unit-status`, `/update-subtask`;
368
+ skills `brainstorming`, `hypothesis-generation`, `writing-plans`.
369
+
370
+ ## Contributing
371
+
372
+ - Keep the two contracts sacred: **the hook is always silent** (exit 0, empty stdout/stderr,
373
+ swallows `BaseException`), and **the snapshot JSON shape is the UI's API** — it must not
374
+ change without the JS tests.
375
+ - All validation returns typed result objects (`EventError`, `GraphError`) — no exceptions for
376
+ expected conditions.
377
+ - Mermaid is vendored (`src/claude_workflow_viz/static/`) — no CDN.
378
+
379
+ ---
380
+
381
+ Built on [Claude Code hooks](https://docs.claude.com/en/docs/claude-code/hooks)
382
+ and [Mermaid](https://mermaid.js.org/) (vendored, MIT).