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.
- claude_workflow_viz-1.0.0a1/.gitignore +26 -0
- claude_workflow_viz-1.0.0a1/CHANGELOG.md +36 -0
- claude_workflow_viz-1.0.0a1/LICENSE +201 -0
- claude_workflow_viz-1.0.0a1/PKG-INFO +382 -0
- claude_workflow_viz-1.0.0a1/README.md +351 -0
- claude_workflow_viz-1.0.0a1/pyproject.toml +79 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/__init__.py +3 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/__main__.py +5 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/cli.py +86 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/connection_file.py +147 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/dashboard/server.py +379 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/graph.py +519 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph/node_status.py +30 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/parse_failures.py +249 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/graph_source/parser.py +586 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/install/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/install/installer.py +259 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/_platform.py +147 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/invoke/bootstrap.py +207 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/VENDORED.md +14 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/app.js +387 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/index.html +48 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/mermaid.LICENSE +21 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/mermaid.min.js +3636 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/status_rules.json +15 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/styles.css +190 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/static/viz-core.js +557 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/__init__.py +1 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/events.py +252 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/hook_adapter.py +342 -0
- claude_workflow_viz-1.0.0a1/src/claude_workflow_viz/workflow/hook_command.py +71 -0
- 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).
|