@heart-of-gold/toolkit 0.1.33 → 0.1.35
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/.claude-plugin/marketplace.json +3 -3
- package/package.json +2 -1
- package/plugins/babel-fish/.claude-plugin/plugin.json +1 -1
- package/plugins/babel-fish/skills/visualize/SKILL.md +280 -146
- package/plugins/babel-fish/skills/visualize/scripts/render-and-share.sh +7 -4
- package/plugins/babel-fish/skills/visualize/scripts/smart-render.js +563 -0
- package/plugins/deep-thought/.claude-plugin/plugin.json +1 -1
- package/plugins/marvin/.claude-plugin/plugin.json +1 -1
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
"name": "deep-thought",
|
|
16
16
|
"source": "./plugins/deep-thought",
|
|
17
17
|
"description": "The Answer Computer — reasoning tools for brainstorming, planning, and deep thinking",
|
|
18
|
-
"version": "0.2.
|
|
18
|
+
"version": "0.2.5"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "marvin",
|
|
22
22
|
"source": "./plugins/marvin",
|
|
23
23
|
"description": "The Paranoid Android — quality tools for code review, knowledge compounding, and work execution",
|
|
24
|
-
"version": "0.3.
|
|
24
|
+
"version": "0.3.5"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "babel-fish",
|
|
28
28
|
"source": "./plugins/babel-fish",
|
|
29
29
|
"description": "Universal Translator — media generation tools for audio, image, and video content",
|
|
30
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.3"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"name": "quellis",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heart-of-gold/toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and more",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"check:security": "python3 scripts/check-security-regressions.py",
|
|
12
12
|
"check:compat": "python3 scripts/check-harness-compatibility.py",
|
|
13
13
|
"test:pi-guided": "node --test tests/pi-guided-workflows.test.mjs",
|
|
14
|
+
"test:visualize": "node --test tests/visualize-smart-render.test.mjs",
|
|
14
15
|
"prepublishOnly": "npm run check:publish-safety && npm run check:compat"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: visualize
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Create shareable HTML visual artifacts from markdown, plans, architecture docs, brainstorms, and other structured
|
|
5
|
+
content. Prefer browser-viewable HTML first when it will materially improve clarity or sharing; otherwise fall back
|
|
6
|
+
to terminal rendering. Triggers: visualize, mindmap, mind map, show me the structure, draw a map, make this clear,
|
|
7
|
+
make this visual.
|
|
8
8
|
allowed-tools:
|
|
9
9
|
- Read
|
|
10
10
|
- Bash
|
|
@@ -14,31 +14,205 @@ allowed-tools:
|
|
|
14
14
|
|
|
15
15
|
# Visualize — Babel Fish
|
|
16
16
|
|
|
17
|
-
Translating structured text into spatial understanding.
|
|
17
|
+
Translating structured text into spatial understanding. The job is not to "turn markdown into HTML." The job is to create a visual artifact that helps a human understand the material faster.
|
|
18
|
+
|
|
19
|
+
## Mission
|
|
20
|
+
|
|
21
|
+
Create one clear, polished, shareable visual artifact that:
|
|
22
|
+
- matches the user's actual need
|
|
23
|
+
- feels intentionally designed, not markdown restyled in boxes
|
|
24
|
+
- summarizes before detailing
|
|
25
|
+
- uses browser HTML as the primary medium for substantial artifacts
|
|
26
|
+
- falls back to terminal rendering only when that is the better fit
|
|
18
27
|
|
|
19
28
|
## Boundaries
|
|
20
29
|
|
|
21
30
|
- MAY: read files, generate terminal mind maps, generate temporary HTML artifacts, run renderer/share scripts via bash
|
|
22
31
|
- MAY NOT: modify project files, create persistent files outside temp/output artifacts, install unrelated packages
|
|
23
32
|
|
|
24
|
-
##
|
|
33
|
+
## Core Principle
|
|
34
|
+
|
|
35
|
+
Do **not** mirror the source document structure one-to-one unless the user explicitly wants a document view.
|
|
36
|
+
|
|
37
|
+
Instead:
|
|
38
|
+
1. understand the source
|
|
39
|
+
2. decide what the artifact is trying to communicate
|
|
40
|
+
3. choose the visual form that best serves that goal
|
|
41
|
+
4. compress and reshape the content into a stronger visual story
|
|
42
|
+
5. keep raw/source detail secondary or collapsible when possible
|
|
43
|
+
|
|
44
|
+
HTML should feel like:
|
|
45
|
+
- a dashboard
|
|
46
|
+
- an explainer
|
|
47
|
+
- a roadmap
|
|
48
|
+
- an architecture brief
|
|
49
|
+
- a deliberate mind map
|
|
50
|
+
|
|
51
|
+
Not like:
|
|
52
|
+
- a markdown page with nicer CSS
|
|
53
|
+
|
|
54
|
+
## Artifact Families
|
|
55
|
+
|
|
56
|
+
These are guidance categories for the coding agent. They are not rigid parser outputs.
|
|
57
|
+
|
|
58
|
+
### `outline`
|
|
59
|
+
Use when the user wants:
|
|
60
|
+
- document structure
|
|
61
|
+
- a reading aid
|
|
62
|
+
- a faithful but polished source-oriented view
|
|
63
|
+
- a safe fallback when a richer artifact is not justified
|
|
64
|
+
|
|
65
|
+
### `roadmap`
|
|
66
|
+
Use when the source is best understood as:
|
|
67
|
+
- phases
|
|
68
|
+
- priorities
|
|
69
|
+
- sequencing
|
|
70
|
+
- workstreams
|
|
71
|
+
- execution flow
|
|
72
|
+
|
|
73
|
+
### `architecture`
|
|
74
|
+
Use when the source is best understood as:
|
|
75
|
+
- components
|
|
76
|
+
- boundaries
|
|
77
|
+
- integrations
|
|
78
|
+
- decisions
|
|
79
|
+
- responsibilities
|
|
80
|
+
|
|
81
|
+
### `mindmap`
|
|
82
|
+
Use only when the content is genuinely:
|
|
83
|
+
- concise
|
|
84
|
+
- branchy
|
|
85
|
+
- idea-oriented
|
|
86
|
+
- better understood spatially than sequentially
|
|
87
|
+
|
|
88
|
+
### `explainer`
|
|
89
|
+
Use when the artifact should help another human quickly understand:
|
|
90
|
+
- the recommendation
|
|
91
|
+
- tradeoffs
|
|
92
|
+
- key decisions
|
|
93
|
+
- what matters and why
|
|
94
|
+
|
|
95
|
+
### `mockup`
|
|
96
|
+
Use when the user really wants:
|
|
97
|
+
- product/UI concept visualization
|
|
98
|
+
- believable interface framing
|
|
99
|
+
- layout and interaction-oriented representation
|
|
100
|
+
|
|
101
|
+
## Style Foundations
|
|
102
|
+
|
|
103
|
+
Apply these defaults unless the user asks for something else:
|
|
104
|
+
|
|
105
|
+
- calm, high-contrast visual language
|
|
106
|
+
- restrained accent usage
|
|
107
|
+
- strong hierarchy and generous spacing
|
|
108
|
+
- summary-first information architecture
|
|
109
|
+
- progressive disclosure for dense detail
|
|
110
|
+
- cards, panels, lanes, chips, and callouts over markdown-heavy paragraphs
|
|
111
|
+
- readable max-widths for prose
|
|
112
|
+
- sticky navigation only when it helps, never as the dominant element
|
|
113
|
+
- polished but restrained effects; no gimmicky AI-demo chrome
|
|
114
|
+
|
|
115
|
+
See also:
|
|
116
|
+
- `docs/architecture/visualize-design-rules.md`
|
|
117
|
+
|
|
118
|
+
## Rules: Do
|
|
119
|
+
|
|
120
|
+
- Decide the communication goal before choosing the renderer.
|
|
121
|
+
- Prefer shareable HTML for substantial workflow artifacts when `share-html` is configured.
|
|
122
|
+
- Transform the source into a view model in your head before rendering: summary, priorities, risks, dependencies, decisions, outcomes.
|
|
123
|
+
- Lead with a strong first screen: title, one-line mission, key takeaways, and obvious next scan targets.
|
|
124
|
+
- Convert dense content into visual units where appropriate: cards, grouped sections, lanes, side panels, chips, callouts, expandable details.
|
|
125
|
+
- Use `roadmap` or richer execution-oriented views for plans when that improves understanding.
|
|
126
|
+
- Use `architecture` views for system/design-heavy documents.
|
|
127
|
+
- Keep raw source detail available, but secondary.
|
|
128
|
+
- Briefly explain why you chose the visualization mode when sharing the result.
|
|
129
|
+
|
|
130
|
+
## Rules: Don't
|
|
131
|
+
|
|
132
|
+
- Do not treat HTML generation as a markdown restyling task.
|
|
133
|
+
- Do not dump long raw paragraphs into large cards as the main UI.
|
|
134
|
+
- Do not let the table of contents dominate the page.
|
|
135
|
+
- Do not force a mind map onto content that is not naturally branch-shaped.
|
|
136
|
+
- Do not use flashy gradients, glass, shadows, or color noise unless they clearly improve hierarchy.
|
|
137
|
+
- Do not silently guess when the visualization choice is materially ambiguous.
|
|
138
|
+
- Do not create multiple competing artifacts unless the user explicitly asks for comparison.
|
|
139
|
+
|
|
140
|
+
## Expected Behavior
|
|
141
|
+
|
|
142
|
+
When invoked, behave like a visual editor, not a format converter.
|
|
143
|
+
|
|
144
|
+
1. Read the source or infer the source from context.
|
|
145
|
+
2. Decide whether the user needs:
|
|
146
|
+
- structure comprehension
|
|
147
|
+
- execution clarity
|
|
148
|
+
- system understanding
|
|
149
|
+
- stakeholder explanation
|
|
150
|
+
- UI/product visualization
|
|
151
|
+
3. Choose the best artifact family.
|
|
152
|
+
4. If uncertain, ask one concise question.
|
|
153
|
+
5. Generate one HTML artifact first when browser rendering will help.
|
|
154
|
+
6. Fall back to terminal rendering when browser/share is unavailable or explicitly not wanted.
|
|
155
|
+
|
|
156
|
+
If the user says "you decide," choose the clearest non-gimmicky artifact, not the fanciest one.
|
|
157
|
+
|
|
158
|
+
## Uncertainty Protocol
|
|
159
|
+
|
|
160
|
+
When the best visualization is not clear, do **not** silently guess if the choice would materially affect usefulness.
|
|
161
|
+
|
|
162
|
+
Ask **one concise question at a time**:
|
|
163
|
+
- state the decision in plain language
|
|
164
|
+
- offer 2-4 explicit options
|
|
165
|
+
- include a recommended option when you have one
|
|
166
|
+
- keep option labels outcome-focused, not renderer-jargon-first
|
|
167
|
+
|
|
168
|
+
Good pattern:
|
|
169
|
+
- "Which would help most here?"
|
|
170
|
+
- `Roadmap` — show phases, sequencing, and implementation progress
|
|
171
|
+
- `Outline` — show the document structure clearly
|
|
172
|
+
- `Mind map` — show branching ideas and relationships
|
|
173
|
+
- `Architecture view` — show components, boundaries, and decisions
|
|
174
|
+
|
|
175
|
+
If the harness supports structured choices, use them.
|
|
176
|
+
If not, use a short plain-text question such as:
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
I can visualize this a few different ways. Which would be most useful?
|
|
180
|
+
1. Roadmap — phases and tasks
|
|
181
|
+
2. Outline — document structure
|
|
182
|
+
3. Mind map — branching ideas
|
|
183
|
+
4. Architecture view — components and boundaries
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If the user does not care or says "you decide," choose the safest useful mode:
|
|
187
|
+
- default to `outline`
|
|
188
|
+
- use `roadmap` for clearly execution-heavy plans
|
|
189
|
+
- use `architecture` for clearly system-design-heavy docs
|
|
190
|
+
- use `mindmap` only when the artifact is genuinely concise and branchy
|
|
25
191
|
|
|
26
|
-
|
|
192
|
+
## Renderers
|
|
27
193
|
|
|
28
|
-
|
|
194
|
+
Visualization has two implementation layers:
|
|
195
|
+
- `scripts/smart-render.js` — renders one HTML artifact using the mode the coding agent chose, with a safe fallback
|
|
196
|
+
- `scripts/render-mindmap/index.js` — specialized mind-map renderer for branchy content
|
|
197
|
+
|
|
198
|
+
**Locations:**
|
|
199
|
+
- `scripts/smart-render.js`
|
|
200
|
+
- `scripts/render-mindmap/index.js`
|
|
201
|
+
|
|
202
|
+
To find the smart renderer path:
|
|
29
203
|
|
|
30
|
-
**To find the script path**, locate it by searching for `render-mindmap/index.js`:
|
|
31
204
|
```bash
|
|
32
205
|
# Option 1: Use CLAUDE_PLUGIN_ROOT if available
|
|
33
|
-
SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/visualize/scripts/render
|
|
206
|
+
SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/visualize/scripts/smart-render.js"
|
|
34
207
|
|
|
35
208
|
# Option 2: Search for it
|
|
36
|
-
SCRIPT=$(find ~/.claude/plugins -path "*/babel-fish/skills/visualize/scripts/render
|
|
209
|
+
SCRIPT=$(find ~/.claude/plugins -path "*/babel-fish/skills/visualize/scripts/smart-render.js" 2>/dev/null | head -1)
|
|
37
210
|
```
|
|
38
211
|
|
|
39
|
-
|
|
212
|
+
First run for the mind-map renderer:
|
|
213
|
+
|
|
40
214
|
```bash
|
|
41
|
-
RENDER_DIR=$(dirname "$SCRIPT")
|
|
215
|
+
RENDER_DIR=$(dirname "$SCRIPT")/render-mindmap
|
|
42
216
|
if [ ! -d "$RENDER_DIR/node_modules" ]; then
|
|
43
217
|
(cd "$RENDER_DIR" && npm install --silent)
|
|
44
218
|
fi
|
|
@@ -47,21 +221,20 @@ fi
|
|
|
47
221
|
## Usage
|
|
48
222
|
|
|
49
223
|
```bash
|
|
50
|
-
#
|
|
51
|
-
node "$SCRIPT" path/to/file.md
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
node "$SCRIPT"
|
|
55
|
-
node "$SCRIPT"
|
|
56
|
-
node "$SCRIPT"
|
|
57
|
-
node "$SCRIPT"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
echo "# Root\n## Branch A\n## Branch B" | node "$SCRIPT"
|
|
224
|
+
# Generate a safe default HTML visualization for a markdown file
|
|
225
|
+
node "$SCRIPT" path/to/file.md --out /tmp/view.html
|
|
226
|
+
|
|
227
|
+
# Usually the coding agent should choose the mode from context
|
|
228
|
+
node "$SCRIPT" path/to/file.md --mode roadmap --out /tmp/view.html
|
|
229
|
+
node "$SCRIPT" path/to/file.md --mode outline --out /tmp/view.html
|
|
230
|
+
node "$SCRIPT" path/to/file.md --mode architecture --out /tmp/view.html
|
|
231
|
+
node "$SCRIPT" path/to/file.md --mode mindmap --out /tmp/view.html
|
|
232
|
+
|
|
233
|
+
# Use the specialized mind-map renderer directly when needed
|
|
234
|
+
node "$(dirname "$SCRIPT")/render-mindmap/index.js" --html /tmp/map.html path/to/file.md
|
|
62
235
|
```
|
|
63
236
|
|
|
64
|
-
|
|
237
|
+
## HTML Share Flow
|
|
65
238
|
|
|
66
239
|
Use the helper script when the user wants a browser URL and the share server is already configured:
|
|
67
240
|
|
|
@@ -70,152 +243,113 @@ bash scripts/render-and-share.sh path/to/file.md
|
|
|
70
243
|
```
|
|
71
244
|
|
|
72
245
|
This script:
|
|
73
|
-
1. generates
|
|
74
|
-
2.
|
|
75
|
-
3.
|
|
76
|
-
4.
|
|
77
|
-
|
|
78
|
-
## Rendering Behavior
|
|
79
|
-
|
|
80
|
-
- **Auto-depth:** If no `--depth` is specified, the renderer tries depths 3, 2, 1 and picks the deepest that fits the terminal width.
|
|
81
|
-
- **Pruning:** Long labels are truncated with `…`. Nodes with many children show the first few plus `+N more`.
|
|
82
|
-
- **Colors** (via ANSI, visible in Claude Code bash output):
|
|
83
|
-
- Root: bold white on blue
|
|
84
|
-
- Depth 1: bold cyan
|
|
85
|
-
- Depth 2: green
|
|
86
|
-
- Depth 3: yellow
|
|
87
|
-
- Depth 4+: dim
|
|
88
|
-
|
|
89
|
-
## Phase 0 — Determine What to Visualize
|
|
90
|
-
|
|
91
|
-
First decide whether this should be a browser/shareable HTML view or a quick terminal view.
|
|
92
|
-
|
|
93
|
-
**Prefer browser/shareable HTML first when:**
|
|
94
|
-
- the source is a brainstorm, plan, architecture doc, or other structured workflow artifact
|
|
95
|
-
- the user asks to open it in a browser
|
|
96
|
-
- the user wants to share the result with another person or device
|
|
97
|
-
- the structure is large enough that browser navigation is more useful than terminal rendering
|
|
98
|
-
- `share-html` is configured
|
|
99
|
-
|
|
100
|
-
**Prefer terminal rendering when:**
|
|
101
|
-
- share-html is not configured
|
|
102
|
-
- the user explicitly wants a quick terminal-only look
|
|
103
|
-
- the environment is SSH-heavy and the browser/share path is not requested
|
|
246
|
+
1. generates one HTML artifact via the smart renderer
|
|
247
|
+
2. uses the mode the coding agent chose (or the renderer's safe default)
|
|
248
|
+
3. locates `share-html/scripts/publish.sh`
|
|
249
|
+
4. publishes the artifact to the configured local share server
|
|
250
|
+
5. prints the publish result so you can return the URL
|
|
104
251
|
|
|
105
|
-
|
|
252
|
+
### Recommended share flow
|
|
106
253
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
254
|
+
1. Verify or assume the input markdown is ready.
|
|
255
|
+
2. Choose the mode from context.
|
|
256
|
+
3. Run:
|
|
257
|
+
```bash
|
|
258
|
+
bash scripts/render-and-share.sh --mode <chosen-mode> --url-only [file]
|
|
259
|
+
```
|
|
260
|
+
4. Read the returned URL from stdout.
|
|
261
|
+
5. Return that URL to the user as the primary result.
|
|
262
|
+
6. Briefly explain what was published and why this mode was chosen.
|
|
116
263
|
|
|
117
|
-
|
|
118
|
-
1. Look at what was just discussed or created
|
|
119
|
-
2. Generate an appropriate markdown structure
|
|
120
|
-
3. Render it
|
|
264
|
+
If publishing fails because the share server is not configured, say so clearly and fall back to terminal rendering unless the user wants to stop and run `share-server-setup` first.
|
|
121
265
|
|
|
122
|
-
##
|
|
266
|
+
## Terminal Rendering
|
|
123
267
|
|
|
124
|
-
|
|
268
|
+
Use terminal rendering when:
|
|
269
|
+
- share-html is not configured
|
|
270
|
+
- the user explicitly wants terminal-only output
|
|
271
|
+
- a quick local structural check is more useful than a browser view
|
|
125
272
|
|
|
126
|
-
**IMPORTANT
|
|
273
|
+
**IMPORTANT:** Output the mind map in the assistant response text, not as raw bash tool output.
|
|
127
274
|
|
|
128
275
|
Many harness bash panels truncate long output and wrap wide content, breaking alignment. Instead:
|
|
129
276
|
|
|
130
|
-
1. Locate the renderer script
|
|
131
|
-
2. Ensure dependencies are installed
|
|
277
|
+
1. Locate the renderer script.
|
|
278
|
+
2. Ensure dependencies are installed.
|
|
132
279
|
3. Run the renderer with `--no-color`, redirect to a temp file:
|
|
133
280
|
```bash
|
|
134
281
|
node "$SCRIPT" --no-color [file] > /tmp/mindmap-result.txt 2>&1
|
|
135
282
|
```
|
|
136
|
-
4. Read `/tmp/mindmap-result.txt
|
|
137
|
-
5. Output the contents inside a
|
|
138
|
-
6. Clean up:
|
|
139
|
-
|
|
140
|
-
The default mode is **vertical layout** — boxes on main branches, compact leaves, ~40 chars wide.
|
|
141
|
-
|
|
142
|
-
### Path B — Shareable HTML
|
|
143
|
-
|
|
144
|
-
For brainstorms, plans, architecture docs, and other structured workflow artifacts, prefer this path first when `share-html` is configured.
|
|
145
|
-
|
|
146
|
-
1. Verify or assume the input markdown is ready
|
|
147
|
-
2. Run:
|
|
283
|
+
4. Read `/tmp/mindmap-result.txt`.
|
|
284
|
+
5. Output the contents inside a fenced code block.
|
|
285
|
+
6. Clean up:
|
|
148
286
|
```bash
|
|
149
|
-
|
|
287
|
+
rm -f /tmp/mindmap-result.txt
|
|
150
288
|
```
|
|
151
|
-
3. Read the returned URL from stdout
|
|
152
|
-
4. Return that URL to the user as the primary result
|
|
153
|
-
5. Briefly explain what was published
|
|
154
|
-
|
|
155
|
-
If you need more detail for debugging, you may run the helper without `--url-only` and inspect the returned JSON.
|
|
156
|
-
|
|
157
|
-
If publishing fails because the share server is not configured, say so clearly and fall back to terminal rendering unless the user wants to stop and run `share-server-setup` first.
|
|
158
289
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
##
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
290
|
+
The default mode is vertical layout — boxes on main branches, compact leaves, about 40 chars wide.
|
|
291
|
+
|
|
292
|
+
## Required Output Structure
|
|
293
|
+
|
|
294
|
+
For substantial HTML artifacts, prefer this structure:
|
|
295
|
+
- strong title + one-line framing
|
|
296
|
+
- summary layer first
|
|
297
|
+
- main visual body second
|
|
298
|
+
- dense details compressed or collapsible
|
|
299
|
+
- source-faithful appendix only if needed
|
|
300
|
+
|
|
301
|
+
### Plan-oriented artifact target shape
|
|
302
|
+
- title + mission
|
|
303
|
+
- key stats or scope summary
|
|
304
|
+
- priorities / phases / workstreams
|
|
305
|
+
- dependencies / risks / acceptance gates
|
|
306
|
+
- expandable detail or appendix
|
|
307
|
+
|
|
308
|
+
### Architecture-oriented artifact target shape
|
|
309
|
+
- title + system framing
|
|
310
|
+
- major components / boundaries / integrations
|
|
311
|
+
- key decisions and tradeoffs
|
|
312
|
+
- risks / assumptions
|
|
313
|
+
- supporting detail below
|
|
314
|
+
|
|
315
|
+
### Explainer target shape
|
|
316
|
+
- title + recommendation
|
|
317
|
+
- why this matters
|
|
318
|
+
- options / comparison / decision
|
|
319
|
+
- what happens next
|
|
320
|
+
- supporting source detail below
|
|
321
|
+
|
|
322
|
+
## Quality Gates
|
|
323
|
+
|
|
324
|
+
Before returning a shared HTML result, check mentally:
|
|
325
|
+
- Does the first screen explain the artifact in under 10 seconds?
|
|
326
|
+
- Does this feel designed, not like markdown with nicer CSS?
|
|
327
|
+
- Is hierarchy obvious?
|
|
328
|
+
- Is summary ahead of detail?
|
|
329
|
+
- Are dense sections compressed into meaningful visual units?
|
|
330
|
+
- Is the chosen mode actually appropriate for the content?
|
|
331
|
+
- If this is a plan, does it foreground execution rather than document order?
|
|
332
|
+
- If this is a brainstorm, is it actually branch-shaped enough for a mind map?
|
|
333
|
+
|
|
334
|
+
If the answer to several of these is no, reconsider the mode or ask the user.
|
|
169
335
|
|
|
170
336
|
## Input Formats
|
|
171
337
|
|
|
172
338
|
### Markdown (primary)
|
|
173
|
-
Standard markdown with `#` headings defining hierarchy
|
|
174
|
-
```markdown
|
|
175
|
-
# Root Topic
|
|
176
|
-
## Branch A
|
|
177
|
-
- Detail 1
|
|
178
|
-
- Detail 2
|
|
179
|
-
## Branch B
|
|
180
|
-
### Sub-branch
|
|
181
|
-
- Detail 3
|
|
182
|
-
```
|
|
339
|
+
Standard markdown with `#` headings defining hierarchy.
|
|
183
340
|
|
|
184
341
|
### JSON
|
|
185
|
-
Tree structure with `label` and `children
|
|
186
|
-
```json
|
|
187
|
-
{
|
|
188
|
-
"label": "Root",
|
|
189
|
-
"children": [
|
|
190
|
-
{ "label": "Branch A", "children": [] },
|
|
191
|
-
{ "label": "Branch B", "children": [
|
|
192
|
-
{ "label": "Sub-branch", "children": [] }
|
|
193
|
-
]}
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Generating Structure from Context
|
|
199
|
-
|
|
200
|
-
When visualizing conversation context (no file path), create a markdown structure that captures:
|
|
201
|
-
|
|
202
|
-
**For brainstorm content:**
|
|
203
|
-
- Root = topic
|
|
204
|
-
- Branches = key decisions or themes
|
|
205
|
-
- Leaves = specific choices or details
|
|
342
|
+
Tree structure with `label` and `children`.
|
|
206
343
|
|
|
207
|
-
|
|
208
|
-
- Root = project name
|
|
209
|
-
- Branches = phases
|
|
210
|
-
- Leaves = tasks within each phase
|
|
344
|
+
## Generating Structure From Context
|
|
211
345
|
|
|
212
|
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
346
|
+
When visualizing conversation context with no file path:
|
|
347
|
+
- brainstorms: root = topic, branches = key themes, leaves = concrete ideas
|
|
348
|
+
- plans: root = project, branches = priorities/phases, leaves = tasks
|
|
349
|
+
- general discussion: root = main topic, branches = subtopics, leaves = key takeaways
|
|
216
350
|
|
|
217
351
|
Write the generated markdown to `/tmp/mindmap-XXXXXX.md`, render it, then clean up.
|
|
218
352
|
|
|
219
353
|
## What Makes This Babel Fish
|
|
220
354
|
|
|
221
|
-
The Babel Fish translates between languages. This skill translates between
|
|
355
|
+
The Babel Fish translates between languages. This skill translates between modalities — from linear text to spatial understanding, and from private working notes to clear shareable browser artifacts.
|
|
@@ -3,11 +3,12 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
5
|
RENDER_DIR="$SCRIPT_DIR/render-mindmap"
|
|
6
|
-
RENDER_SCRIPT="$
|
|
6
|
+
RENDER_SCRIPT="$SCRIPT_DIR/smart-render.js"
|
|
7
7
|
INPUT_PATH=""
|
|
8
8
|
SLUG=""
|
|
9
9
|
TITLE=""
|
|
10
10
|
ALIAS=""
|
|
11
|
+
MODE="auto"
|
|
11
12
|
KEEP_HTML=0
|
|
12
13
|
URL_ONLY=0
|
|
13
14
|
HTML_OUT=""
|
|
@@ -15,15 +16,17 @@ TEMP_DIR=""
|
|
|
15
16
|
|
|
16
17
|
usage() {
|
|
17
18
|
cat <<'EOF'
|
|
18
|
-
Usage: render-and-share.sh <markdown-file> [--slug STEM] [--title TITLE] [--alias ALIAS] [--html-out PATH] [--keep-html] [--url-only]
|
|
19
|
+
Usage: render-and-share.sh <markdown-file> [--mode MODE] [--slug STEM] [--title TITLE] [--alias ALIAS] [--html-out PATH] [--keep-html] [--url-only]
|
|
19
20
|
|
|
20
|
-
Generate
|
|
21
|
+
Generate a polished HTML visualization from a markdown file, publish it via share-html, and print the publish JSON.
|
|
22
|
+
Use --mode to force a renderer (mindmap, outline, roadmap, architecture, mockup, explainer).
|
|
21
23
|
Use --url-only to print only the final browser URL.
|
|
22
24
|
EOF
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
while [[ $# -gt 0 ]]; do
|
|
26
28
|
case "$1" in
|
|
29
|
+
--mode) MODE="$2"; shift 2 ;;
|
|
27
30
|
--slug) SLUG="$2"; shift 2 ;;
|
|
28
31
|
--title) TITLE="$2"; shift 2 ;;
|
|
29
32
|
--alias) ALIAS="$2"; shift 2 ;;
|
|
@@ -75,7 +78,7 @@ cleanup() {
|
|
|
75
78
|
}
|
|
76
79
|
trap cleanup EXIT
|
|
77
80
|
|
|
78
|
-
node "$RENDER_SCRIPT" --
|
|
81
|
+
node "$RENDER_SCRIPT" "$INPUT_PATH" --mode "$MODE" --out "$HTML_OUT" >/dev/null
|
|
79
82
|
|
|
80
83
|
find_share_publish_script() {
|
|
81
84
|
local candidates=(
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { basename, dirname, join } from 'node:path';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
const opts = { file: null, out: null, mode: 'auto' };
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
if (arg === '--out' && i + 1 < args.length) opts.out = args[++i];
|
|
12
|
+
else if (arg === '--mode' && i + 1 < args.length) opts.mode = args[++i];
|
|
13
|
+
else if (!arg.startsWith('-')) opts.file = arg;
|
|
14
|
+
}
|
|
15
|
+
return opts;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseDocument(markdown) {
|
|
19
|
+
const lines = markdown.split(/\r?\n/);
|
|
20
|
+
let i = 0;
|
|
21
|
+
const frontmatter = {};
|
|
22
|
+
if (lines[0]?.trim() === '---') {
|
|
23
|
+
i = 1;
|
|
24
|
+
for (; i < lines.length; i++) {
|
|
25
|
+
const line = lines[i].trim();
|
|
26
|
+
if (line === '---') { i++; break; }
|
|
27
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
28
|
+
if (match) frontmatter[match[1]] = match[2].replace(/^"|"$/g, '');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sections = [];
|
|
33
|
+
let current = null;
|
|
34
|
+
for (; i < lines.length; i++) {
|
|
35
|
+
const line = lines[i];
|
|
36
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
37
|
+
if (heading) {
|
|
38
|
+
current = { level: heading[1].length, title: heading[2].trim(), lines: [] };
|
|
39
|
+
sections.push(current);
|
|
40
|
+
} else if (current) {
|
|
41
|
+
current.lines.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { frontmatter, sections, raw: markdown };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function textFromLines(lines) {
|
|
48
|
+
return lines.join('\n').trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeWhitespace(text) {
|
|
52
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function firstParagraph(lines) {
|
|
56
|
+
const blocks = textFromLines(lines).split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
|
|
57
|
+
return blocks[0] || '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function splitBlocks(lines) {
|
|
61
|
+
return textFromLines(lines).split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function classifyMode(_filePath, _doc, preferredMode) {
|
|
65
|
+
if (preferredMode && preferredMode !== 'auto') return preferredMode;
|
|
66
|
+
return 'outline';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function escapeHtml(value) {
|
|
70
|
+
return String(value)
|
|
71
|
+
.replace(/&/g, '&')
|
|
72
|
+
.replace(/</g, '<')
|
|
73
|
+
.replace(/>/g, '>')
|
|
74
|
+
.replace(/"/g, '"')
|
|
75
|
+
.replace(/'/g, ''');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function slugify(value) {
|
|
79
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderShell({ title, eyebrow, summary, nav, content, badge = '' }) {
|
|
83
|
+
return `<!doctype html>
|
|
84
|
+
<html lang="en">
|
|
85
|
+
<head>
|
|
86
|
+
<meta charset="utf-8" />
|
|
87
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
88
|
+
<title>${escapeHtml(title)}</title>
|
|
89
|
+
<style>
|
|
90
|
+
:root {
|
|
91
|
+
--bg: #09111f;
|
|
92
|
+
--bg-2: #0f1a31;
|
|
93
|
+
--panel: rgba(255,255,255,0.06);
|
|
94
|
+
--panel-strong: rgba(255,255,255,0.1);
|
|
95
|
+
--panel-soft: rgba(255,255,255,0.04);
|
|
96
|
+
--text: #eef2ff;
|
|
97
|
+
--muted: #b8c0d9;
|
|
98
|
+
--muted-2: #93a0bf;
|
|
99
|
+
--border: rgba(255,255,255,0.11);
|
|
100
|
+
--accent: #7c9cff;
|
|
101
|
+
--accent-2: #80e0d0;
|
|
102
|
+
--success: #86efac;
|
|
103
|
+
--warn: #fbbf24;
|
|
104
|
+
--danger: #fca5a5;
|
|
105
|
+
--shadow: 0 24px 80px rgba(0,0,0,0.34);
|
|
106
|
+
--radius-xl: 28px;
|
|
107
|
+
--radius-lg: 22px;
|
|
108
|
+
--radius-md: 16px;
|
|
109
|
+
}
|
|
110
|
+
* { box-sizing: border-box; }
|
|
111
|
+
html, body { margin: 0; padding: 0; }
|
|
112
|
+
html { color-scheme: dark; }
|
|
113
|
+
body {
|
|
114
|
+
background:
|
|
115
|
+
radial-gradient(circle at top left, rgba(124,156,255,0.18), transparent 32%),
|
|
116
|
+
radial-gradient(circle at top right, rgba(128,224,208,0.12), transparent 24%),
|
|
117
|
+
linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 100%);
|
|
118
|
+
color: var(--text);
|
|
119
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
120
|
+
line-height: 1.55;
|
|
121
|
+
}
|
|
122
|
+
main { max-width: 1480px; margin: 0 auto; padding: 36px 22px 84px; }
|
|
123
|
+
.hero {
|
|
124
|
+
display: grid;
|
|
125
|
+
gap: 18px;
|
|
126
|
+
padding: 28px;
|
|
127
|
+
border-radius: 32px;
|
|
128
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.04));
|
|
129
|
+
border: 1px solid rgba(255,255,255,0.14);
|
|
130
|
+
box-shadow: var(--shadow);
|
|
131
|
+
margin-bottom: 24px;
|
|
132
|
+
}
|
|
133
|
+
.eyebrow { color: var(--accent-2); text-transform: uppercase; letter-spacing: 0.16em; font-size: 12px; font-weight: 700; }
|
|
134
|
+
.hero h1 { margin: 0; font-size: clamp(34px, 4vw, 62px); line-height: 1.02; text-wrap: balance; }
|
|
135
|
+
.hero p { margin: 0; max-width: 78ch; color: var(--muted); font-size: 17px; }
|
|
136
|
+
.hero-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
|
|
137
|
+
.badge, .pill {
|
|
138
|
+
display: inline-flex; align-items: center; gap: 8px; width: fit-content;
|
|
139
|
+
padding: 8px 12px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.14);
|
|
140
|
+
background: rgba(255,255,255,0.06); color: #dce5ff; font-size: 13px;
|
|
141
|
+
}
|
|
142
|
+
.page {
|
|
143
|
+
display: grid;
|
|
144
|
+
grid-template-columns: minmax(0, 1fr) minmax(260px, 320px);
|
|
145
|
+
gap: 24px;
|
|
146
|
+
align-items: start;
|
|
147
|
+
}
|
|
148
|
+
main > nav, .rail {
|
|
149
|
+
position: sticky;
|
|
150
|
+
top: 16px;
|
|
151
|
+
align-self: start;
|
|
152
|
+
display: grid;
|
|
153
|
+
gap: 14px;
|
|
154
|
+
}
|
|
155
|
+
.panel {
|
|
156
|
+
padding: 18px;
|
|
157
|
+
border-radius: var(--radius-lg);
|
|
158
|
+
background: var(--panel);
|
|
159
|
+
border: 1px solid var(--border);
|
|
160
|
+
box-shadow: var(--shadow);
|
|
161
|
+
backdrop-filter: blur(14px);
|
|
162
|
+
}
|
|
163
|
+
.panel h2, .panel h3 { margin: 0 0 10px; }
|
|
164
|
+
.panel h2 { font-size: 15px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
|
|
165
|
+
.nav-links { display: grid; gap: 8px; }
|
|
166
|
+
.nav-links a {
|
|
167
|
+
display: block; padding: 10px 12px; color: var(--text); text-decoration: none;
|
|
168
|
+
border-radius: 12px; background: rgba(255,255,255,0.02);
|
|
169
|
+
}
|
|
170
|
+
.nav-links a:hover { background: rgba(255,255,255,0.07); }
|
|
171
|
+
.section {
|
|
172
|
+
margin-bottom: 20px; padding: 24px; border-radius: var(--radius-xl);
|
|
173
|
+
background: var(--panel); border: 1px solid var(--border); box-shadow: var(--shadow);
|
|
174
|
+
backdrop-filter: blur(16px);
|
|
175
|
+
}
|
|
176
|
+
.section h2 { margin: 0 0 14px; font-size: clamp(22px, 2vw, 32px); text-wrap: balance; }
|
|
177
|
+
.section h3 { margin: 0 0 10px; font-size: 18px; }
|
|
178
|
+
.section p, .section li, .section td, .section th { color: var(--muted); font-size: 15px; }
|
|
179
|
+
.section ul { margin: 0; padding-left: 18px; }
|
|
180
|
+
.summary-grid, .stats-grid, .aside-grid, .card-grid, .lane-grid, .dependency-grid { display: grid; gap: 16px; }
|
|
181
|
+
.summary-grid, .stats-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
182
|
+
.card-grid.cols-2, .aside-grid, .dependency-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
183
|
+
.card, .stat, .task-card, .lane, .detail-card, .mini-card {
|
|
184
|
+
padding: 18px; border-radius: var(--radius-md); background: var(--panel-soft); border: 1px solid var(--border);
|
|
185
|
+
}
|
|
186
|
+
.stat .label, .mini-card .label, .task-meta, .kicker {
|
|
187
|
+
font-size: 12px; text-transform: uppercase; letter-spacing: 0.11em; color: var(--accent-2);
|
|
188
|
+
}
|
|
189
|
+
.stat .value { margin-top: 8px; font-size: 28px; font-weight: 700; color: var(--text); }
|
|
190
|
+
.stat .hint, .mini-card p { margin: 8px 0 0; color: var(--muted-2); font-size: 13px; }
|
|
191
|
+
.card p, .task-card p, .lane p { margin: 0; }
|
|
192
|
+
.kicker { margin-bottom: 8px; }
|
|
193
|
+
.lane-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); align-items: start; }
|
|
194
|
+
.lane {
|
|
195
|
+
padding: 20px;
|
|
196
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.035));
|
|
197
|
+
}
|
|
198
|
+
.lane h3 { margin: 0 0 4px; font-size: 22px; }
|
|
199
|
+
.lane .lane-copy { color: var(--muted-2); margin-bottom: 14px; }
|
|
200
|
+
.stack { display: grid; gap: 12px; }
|
|
201
|
+
.task-card {
|
|
202
|
+
background: rgba(7, 14, 27, 0.44);
|
|
203
|
+
}
|
|
204
|
+
.task-head { display: flex; gap: 10px; align-items: flex-start; justify-content: space-between; margin-bottom: 10px; }
|
|
205
|
+
.task-id {
|
|
206
|
+
display: inline-flex; align-items: center; justify-content: center; min-width: 42px;
|
|
207
|
+
padding: 6px 10px; border-radius: 999px; background: rgba(124,156,255,0.18);
|
|
208
|
+
color: #dbe5ff; font-weight: 700; font-size: 13px;
|
|
209
|
+
}
|
|
210
|
+
.task-title { margin: 0; font-size: 16px; line-height: 1.35; }
|
|
211
|
+
.task-body { color: var(--muted); font-size: 14px; }
|
|
212
|
+
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
213
|
+
.dependency-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
|
|
214
|
+
.dependency-pill {
|
|
215
|
+
display: inline-flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 14px;
|
|
216
|
+
background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); color: var(--text); font-size: 13px;
|
|
217
|
+
}
|
|
218
|
+
.dependency-pill .arrow { color: var(--accent-2); font-weight: 700; }
|
|
219
|
+
.chip {
|
|
220
|
+
display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px;
|
|
221
|
+
background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); color: var(--muted-2); font-size: 12px;
|
|
222
|
+
}
|
|
223
|
+
.chip.warn { color: #ffd88d; border-color: rgba(251,191,36,0.22); background: rgba(251,191,36,0.08); }
|
|
224
|
+
.chip.good { color: #c7ffd7; border-color: rgba(134,239,172,0.24); background: rgba(134,239,172,0.08); }
|
|
225
|
+
.chip.danger { color: #ffd1d1; border-color: rgba(252,165,165,0.24); background: rgba(252,165,165,0.09); }
|
|
226
|
+
.callout {
|
|
227
|
+
padding: 18px; border-radius: var(--radius-md); background: rgba(124,156,255,0.09);
|
|
228
|
+
border: 1px solid rgba(124,156,255,0.18);
|
|
229
|
+
}
|
|
230
|
+
.callout strong { display: block; margin-bottom: 8px; }
|
|
231
|
+
.details { margin-top: 14px; }
|
|
232
|
+
.details summary {
|
|
233
|
+
cursor: pointer; list-style: none; color: var(--text); font-weight: 600;
|
|
234
|
+
}
|
|
235
|
+
.details summary::-webkit-details-marker { display: none; }
|
|
236
|
+
.details .detail-body { margin-top: 12px; color: var(--muted); font-size: 14px; }
|
|
237
|
+
.raw {
|
|
238
|
+
white-space: pre-wrap; background: rgba(0,0,0,0.24); padding: 16px; border-radius: 14px; border: 1px solid var(--border);
|
|
239
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; color: #d8e1fb;
|
|
240
|
+
}
|
|
241
|
+
.metric-list { display: grid; gap: 12px; }
|
|
242
|
+
.metric-list .mini-card { padding: 14px 16px; }
|
|
243
|
+
.metric-list .value { margin-top: 6px; font-size: 15px; color: var(--text); font-weight: 600; }
|
|
244
|
+
@media (max-width: 1080px) {
|
|
245
|
+
.page { grid-template-columns: 1fr; }
|
|
246
|
+
main > nav, .rail { position: static; }
|
|
247
|
+
.summary-grid, .stats-grid, .card-grid.cols-2, .aside-grid { grid-template-columns: 1fr 1fr; }
|
|
248
|
+
}
|
|
249
|
+
@media (max-width: 720px) {
|
|
250
|
+
main { padding: 20px 14px 60px; }
|
|
251
|
+
.hero, .section { padding: 20px; }
|
|
252
|
+
.summary-grid, .stats-grid, .card-grid.cols-2, .aside-grid { grid-template-columns: 1fr; }
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
255
|
+
</head>
|
|
256
|
+
<body>
|
|
257
|
+
<main>
|
|
258
|
+
<section class="hero">
|
|
259
|
+
<div class="eyebrow">${escapeHtml(eyebrow)}</div>
|
|
260
|
+
<h1>${escapeHtml(title)}</h1>
|
|
261
|
+
<p>${escapeHtml(summary || 'Shareable HTML artifact generated by Heart of Gold Visualize.')}</p>
|
|
262
|
+
<div class="hero-row">
|
|
263
|
+
${badge ? `<div class="badge">${escapeHtml(badge)}</div>` : ''}
|
|
264
|
+
</div>
|
|
265
|
+
</section>
|
|
266
|
+
<div class="page">
|
|
267
|
+
<section>${content}</section>
|
|
268
|
+
<aside class="rail">${nav}</aside>
|
|
269
|
+
</div>
|
|
270
|
+
</main>
|
|
271
|
+
</body>
|
|
272
|
+
</html>`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderListBlock(block) {
|
|
276
|
+
const items = block.split(/\n/).map((line) => line.match(/^\s*[-*]\s+(.+)$/)?.[1]).filter(Boolean);
|
|
277
|
+
if (!items.length) return null;
|
|
278
|
+
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function renderOutline(doc, title, eyebrow, badge) {
|
|
282
|
+
const nav = `
|
|
283
|
+
<div class="panel"><h2>View</h2><div class="metric-list"><div class="mini-card"><div class="label">Mode</div><div class="value">Structured outline</div><p>Safe default when the agent does not force a richer artifact family.</p></div></div></div>
|
|
284
|
+
<div class="panel"><h2>Sections</h2><div class="nav-links">${doc.sections.map((s, i) => `<a href="#section-${i}">${escapeHtml(s.title)}</a>`).join('')}</div></div>`;
|
|
285
|
+
const content = doc.sections.map((section, i) => {
|
|
286
|
+
const blocks = splitBlocks(section.lines).map((block) => renderListBlock(block) || `<p>${escapeHtml(block)}</p>`).join('');
|
|
287
|
+
return `<article id="section-${i}" class="section"><div class="kicker">Structured View</div><h2>${escapeHtml(section.title)}</h2>${blocks || '<p>No details captured.</p>'}</article>`;
|
|
288
|
+
}).join('');
|
|
289
|
+
return renderShell({ title, eyebrow, summary: firstParagraph(doc.sections[0]?.lines || []), nav, content, badge });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function parseTaskSection(section) {
|
|
293
|
+
const source = textFromLines(section.lines);
|
|
294
|
+
if (!source) return [];
|
|
295
|
+
const regex = /- \[( |x)\]\s+\*\*([^*]+)\*\*\s*([\s\S]*?)(?=(?:\s+- \[(?: |x)\]\s+\*\*)|$)/g;
|
|
296
|
+
const tasks = [];
|
|
297
|
+
let match;
|
|
298
|
+
while ((match = regex.exec(source))) {
|
|
299
|
+
const done = match[1].toLowerCase() === 'x';
|
|
300
|
+
const bold = normalizeWhitespace(match[2]);
|
|
301
|
+
const detail = normalizeWhitespace(match[3] || '');
|
|
302
|
+
const idMatch = bold.match(/^([A-Z][A-Z0-9-]*)\.\s*(.+)$/);
|
|
303
|
+
const id = idMatch ? idMatch[1] : null;
|
|
304
|
+
const title = idMatch ? idMatch[2] : bold;
|
|
305
|
+
const depMatch = detail.match(/\*\*\[depends on ([^\]]+)\]\*\*/i);
|
|
306
|
+
const parallel = /\*\*\[parallel(?: with [^\]]+)?\]\*\*/i.test(detail);
|
|
307
|
+
const notInAcceptance = /not in this plan'?s acceptance/i.test(detail);
|
|
308
|
+
tasks.push({
|
|
309
|
+
done,
|
|
310
|
+
id,
|
|
311
|
+
title,
|
|
312
|
+
detail,
|
|
313
|
+
dependency: depMatch ? depMatch[1] : null,
|
|
314
|
+
parallel,
|
|
315
|
+
deferred: notInAcceptance,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return tasks;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function compactDetail(detail) {
|
|
322
|
+
return detail
|
|
323
|
+
.replace(/\*\*\[parallel(?: with [^\]]+)?\]\*\*/ig, '')
|
|
324
|
+
.replace(/\*\*\[depends on ([^\]]+)\]\*\*/ig, 'Depends on $1.')
|
|
325
|
+
.replace(/\*\*/g, '')
|
|
326
|
+
.replace(/\s+/g, ' ')
|
|
327
|
+
.trim();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function inferTaskIdFromDependency(value) {
|
|
331
|
+
const match = String(value).match(/([A-Z][A-Z0-9-]*)/);
|
|
332
|
+
return match ? match[1] : value;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function extractPlanModel(doc) {
|
|
336
|
+
const byTitle = new Map(doc.sections.map((s) => [s.title.toLowerCase(), s]));
|
|
337
|
+
const find = (name) => byTitle.get(name.toLowerCase());
|
|
338
|
+
const prioritySections = doc.sections.filter((s) => /^(P\d+|Deferred)/.test(s.title));
|
|
339
|
+
const workstreams = prioritySections.map((section) => ({
|
|
340
|
+
title: section.title,
|
|
341
|
+
summary: firstParagraph(section.lines),
|
|
342
|
+
tasks: parseTaskSection(section),
|
|
343
|
+
}));
|
|
344
|
+
|
|
345
|
+
const tasks = workstreams.flatMap((lane) => lane.tasks.map((task) => ({ ...task, lane: lane.title })));
|
|
346
|
+
const dependencies = tasks.filter((t) => t.dependency).map((t) => ({
|
|
347
|
+
from: inferTaskIdFromDependency(t.dependency),
|
|
348
|
+
to: t.id || t.title,
|
|
349
|
+
lane: t.lane,
|
|
350
|
+
}));
|
|
351
|
+
const risksText = firstParagraph(find('Risk Analysis')?.lines || []);
|
|
352
|
+
const acceptanceText = firstParagraph(find('Acceptance Criteria')?.lines || []);
|
|
353
|
+
const constraintsText = firstParagraph(find('Constraints and Boundaries')?.lines || []);
|
|
354
|
+
const proposedSolution = firstParagraph(find('Proposed Solution')?.lines || []);
|
|
355
|
+
const scope = firstParagraph(find('Scope and Non-Goals')?.lines || []);
|
|
356
|
+
const rationale = firstParagraph(find('Decision Rationale')?.lines || []);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
mission: firstParagraph(doc.sections[0]?.lines || []),
|
|
360
|
+
problem: firstParagraph(find('Problem Statement')?.lines || []),
|
|
361
|
+
targetEndState: firstParagraph(find('Target End State')?.lines || []),
|
|
362
|
+
proposedSolution,
|
|
363
|
+
scope,
|
|
364
|
+
rationale,
|
|
365
|
+
risksText,
|
|
366
|
+
acceptanceText,
|
|
367
|
+
constraintsText,
|
|
368
|
+
workstreams,
|
|
369
|
+
tasks,
|
|
370
|
+
dependencies,
|
|
371
|
+
dependencyCount: dependencies.length,
|
|
372
|
+
parallelCount: tasks.filter((t) => t.parallel).length,
|
|
373
|
+
deferredCount: tasks.filter((t) => t.deferred).length,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renderTaskCard(task) {
|
|
378
|
+
const chips = [];
|
|
379
|
+
chips.push(`<span class="chip ${task.done ? 'good' : 'warn'}">${task.done ? 'Done' : 'Planned'}</span>`);
|
|
380
|
+
if (task.dependency) chips.push(`<span class="chip">Depends on ${escapeHtml(task.dependency)}</span>`);
|
|
381
|
+
if (task.parallel) chips.push('<span class="chip good">Parallel</span>');
|
|
382
|
+
if (task.deferred) chips.push('<span class="chip danger">Deferred</span>');
|
|
383
|
+
const detail = compactDetail(task.detail);
|
|
384
|
+
const short = detail.slice(0, 220) + (detail.length > 220 ? '…' : '');
|
|
385
|
+
return `<article class="task-card">
|
|
386
|
+
<div class="task-head">
|
|
387
|
+
<div>
|
|
388
|
+
<div class="task-meta">${escapeHtml(task.lane || 'Task')}</div>
|
|
389
|
+
<h4 class="task-title">${task.id ? `<span class="task-id">${escapeHtml(task.id)}</span> ` : ''}${escapeHtml(task.title)}</h4>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
${short ? `<p class="task-body">${escapeHtml(short)}</p>` : ''}
|
|
393
|
+
<div class="chip-row">${chips.join('')}</div>
|
|
394
|
+
${detail.length > 220 ? `<details class="details"><summary>Show Full Task Detail</summary><div class="detail-body">${escapeHtml(detail)}</div></details>` : ''}
|
|
395
|
+
</article>`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function renderRoadmap(doc, title, badge) {
|
|
399
|
+
const model = extractPlanModel(doc);
|
|
400
|
+
const nav = `
|
|
401
|
+
<div class="panel">
|
|
402
|
+
<h2>Execution Snapshot</h2>
|
|
403
|
+
<div class="metric-list">
|
|
404
|
+
<div class="mini-card"><div class="label">Mode</div><div class="value">Plan dashboard</div><p>Optimized for execution clarity rather than markdown fidelity.</p></div>
|
|
405
|
+
<div class="mini-card"><div class="label">Workstreams</div><div class="value">${model.workstreams.length}</div></div>
|
|
406
|
+
<div class="mini-card"><div class="label">Tasks</div><div class="value">${model.tasks.length}</div></div>
|
|
407
|
+
<div class="mini-card"><div class="label">Dependencies</div><div class="value">${model.dependencyCount}</div></div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
<div class="panel">
|
|
411
|
+
<h2>Jump</h2>
|
|
412
|
+
<div class="nav-links">
|
|
413
|
+
<a href="#summary">Summary</a>
|
|
414
|
+
<a href="#workstreams">Priority Lanes</a>
|
|
415
|
+
<a href="#acceptance">Acceptance & Risks</a>
|
|
416
|
+
<a href="#appendix">Source Appendix</a>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<div class="panel">
|
|
420
|
+
<h2>Decision</h2>
|
|
421
|
+
<p>${escapeHtml(model.rationale || 'Execution-first plan view selected to foreground phases, tasks, and dependencies.')}</p>
|
|
422
|
+
</div>`;
|
|
423
|
+
|
|
424
|
+
const heroStats = [
|
|
425
|
+
['Workstreams', model.workstreams.length, 'Priority lanes in this plan'],
|
|
426
|
+
['Tasks', model.tasks.length, 'Tracked implementation actions'],
|
|
427
|
+
['Parallel', model.parallelCount, 'Tasks marked as parallelizable'],
|
|
428
|
+
['Deferred', model.deferredCount, 'Explicitly out of this pass'],
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
const topTasks = model.tasks.slice(0, 4).map(renderTaskCard).join('');
|
|
432
|
+
const dependencyHtml = model.dependencies.length
|
|
433
|
+
? model.dependencies.slice(0, 10).map((dep) => `<div class="dependency-pill"><span>${escapeHtml(dep.from)}</span><span class="arrow">→</span><span>${escapeHtml(dep.to)}</span><span class="chip">${escapeHtml(dep.lane)}</span></div>`).join('')
|
|
434
|
+
: '<div class="card"><p>No explicit dependencies extracted.</p></div>';
|
|
435
|
+
const laneHtml = model.workstreams.map((lane) => {
|
|
436
|
+
const laneId = slugify(lane.title);
|
|
437
|
+
const intro = lane.summary || `${lane.tasks.length} task${lane.tasks.length === 1 ? '' : 's'} in this lane.`;
|
|
438
|
+
return `<section class="lane" id="lane-${laneId}">
|
|
439
|
+
<div class="kicker">Priority Lane</div>
|
|
440
|
+
<h3>${escapeHtml(lane.title)}</h3>
|
|
441
|
+
<p class="lane-copy">${escapeHtml(intro)}</p>
|
|
442
|
+
<div class="stack">${lane.tasks.length ? lane.tasks.map(renderTaskCard).join('') : '<div class="task-card"><p class="task-body">No parsed tasks found. See appendix for source detail.</p></div>'}</div>
|
|
443
|
+
</section>`;
|
|
444
|
+
}).join('');
|
|
445
|
+
|
|
446
|
+
const appendixSections = doc.sections.map((section, i) => {
|
|
447
|
+
const raw = textFromLines(section.lines);
|
|
448
|
+
const snippet = raw.length > 1400 ? `${raw.slice(0, 1400)}\n…` : raw;
|
|
449
|
+
return `<article class="detail-card">
|
|
450
|
+
<div class="kicker">Source Section</div>
|
|
451
|
+
<h3>${escapeHtml(section.title)}</h3>
|
|
452
|
+
<p>${escapeHtml(firstParagraph(section.lines) || 'No summary captured.')}</p>
|
|
453
|
+
<details class="details"><summary>Show Source Detail</summary><div class="detail-body"><div class="raw">${escapeHtml(snippet || 'No detail captured.')}</div></div></details>
|
|
454
|
+
</article>`;
|
|
455
|
+
}).join('');
|
|
456
|
+
|
|
457
|
+
const content = `
|
|
458
|
+
<article class="section" id="summary">
|
|
459
|
+
<div class="kicker">Plan Dashboard</div>
|
|
460
|
+
<h2>Execution Summary</h2>
|
|
461
|
+
<div class="callout"><strong>Mission</strong>${escapeHtml(model.mission || 'No summary captured.')}</div>
|
|
462
|
+
<div class="summary-grid" style="margin-top:16px;">
|
|
463
|
+
${heroStats.map(([label, value, hint]) => `<div class="stat"><div class="label">${escapeHtml(label)}</div><div class="value">${escapeHtml(value)}</div><div class="hint">${escapeHtml(hint)}</div></div>`).join('')}
|
|
464
|
+
</div>
|
|
465
|
+
<div class="aside-grid" style="margin-top:16px;">
|
|
466
|
+
<div class="card"><div class="kicker">Problem</div><h3>Why This Exists</h3><p>${escapeHtml(model.problem || 'See source document for full problem framing.')}</p></div>
|
|
467
|
+
<div class="card"><div class="kicker">Target</div><h3>What Good Looks Like</h3><p>${escapeHtml(model.targetEndState || 'See source document for target end state.')}</p></div>
|
|
468
|
+
</div>
|
|
469
|
+
</article>
|
|
470
|
+
|
|
471
|
+
<article class="section">
|
|
472
|
+
<div class="kicker">Plan Strategy</div>
|
|
473
|
+
<h2>Scope, Approach & Key Moves</h2>
|
|
474
|
+
<div class="card-grid cols-2">
|
|
475
|
+
<div class="card"><div class="kicker">Scope</div><p>${escapeHtml(model.scope || 'See source document for scope detail.')}</p></div>
|
|
476
|
+
<div class="card"><div class="kicker">Approach</div><p>${escapeHtml(model.proposedSolution || 'See source document for solution framing.')}</p></div>
|
|
477
|
+
</div>
|
|
478
|
+
</article>
|
|
479
|
+
|
|
480
|
+
<article class="section" id="workstreams">
|
|
481
|
+
<div class="kicker">Priority Lanes</div>
|
|
482
|
+
<h2>Workstreams & Tasks</h2>
|
|
483
|
+
<p>The plan is shown as priority lanes so execution order, task grouping, and dependency shape are visible without reading the full markdown in sequence.</p>
|
|
484
|
+
<div class="lane-grid" style="margin-top:16px;">${laneHtml}</div>
|
|
485
|
+
</article>
|
|
486
|
+
|
|
487
|
+
<article class="section">
|
|
488
|
+
<div class="kicker">Immediate Scan</div>
|
|
489
|
+
<h2>Representative Tasks</h2>
|
|
490
|
+
<div class="card-grid cols-2">${topTasks || '<div class="card"><p>No tasks captured.</p></div>'}</div>
|
|
491
|
+
</article>
|
|
492
|
+
|
|
493
|
+
<article class="section">
|
|
494
|
+
<div class="kicker">Dependency Shape</div>
|
|
495
|
+
<h2>Key Sequencing</h2>
|
|
496
|
+
<p>Dependencies are surfaced explicitly here so the execution order is visible without reading task prose.</p>
|
|
497
|
+
<div class="dependency-row">${dependencyHtml}</div>
|
|
498
|
+
</article>
|
|
499
|
+
|
|
500
|
+
<article class="section" id="acceptance">
|
|
501
|
+
<div class="kicker">Execution Guardrails</div>
|
|
502
|
+
<h2>Acceptance, Risks & Constraints</h2>
|
|
503
|
+
<div class="card-grid cols-2">
|
|
504
|
+
<div class="card"><div class="kicker">Acceptance Gates</div><p>${escapeHtml(model.acceptanceText || 'See source markdown for explicit acceptance criteria.')}</p></div>
|
|
505
|
+
<div class="card"><div class="kicker">Primary Risk</div><p>${escapeHtml(model.risksText || 'No risk summary extracted.')}</p></div>
|
|
506
|
+
<div class="card"><div class="kicker">Constraint</div><p>${escapeHtml(model.constraintsText || 'No constraint summary extracted.')}</p></div>
|
|
507
|
+
<div class="card"><div class="kicker">Decision Rationale</div><p>${escapeHtml(model.rationale || 'No rationale summary extracted.')}</p></div>
|
|
508
|
+
</div>
|
|
509
|
+
</article>
|
|
510
|
+
|
|
511
|
+
<article class="section" id="appendix">
|
|
512
|
+
<div class="kicker">Source Appendix</div>
|
|
513
|
+
<h2>Source Detail</h2>
|
|
514
|
+
<p>The raw plan remains canonical. This appendix keeps source detail available without letting markdown dominate the primary reading path.</p>
|
|
515
|
+
<div class="stack" style="margin-top:16px;">${appendixSections}</div>
|
|
516
|
+
</article>`;
|
|
517
|
+
|
|
518
|
+
return renderShell({ title, eyebrow: 'Plan Dashboard', summary: model.mission, nav, content, badge });
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function renderArchitecture(doc, title, badge) {
|
|
522
|
+
const nav = `
|
|
523
|
+
<div class="panel"><h2>View</h2><div class="metric-list"><div class="mini-card"><div class="label">Mode</div><div class="value">Architecture brief</div><p>Focused on boundaries, sections, and key decisions.</p></div></div></div>
|
|
524
|
+
<div class="panel"><h2>Sections</h2><div class="nav-links">${doc.sections.map((s, i) => `<a href="#section-${i}">${escapeHtml(s.title)}</a>`).join('')}</div></div>`;
|
|
525
|
+
const cards = doc.sections.slice(0, 6).map((section) => `<div class="card"><div class="kicker">${escapeHtml(section.title)}</div><p>${escapeHtml(firstParagraph(section.lines) || 'See source markdown for detail.')}</p></div>`).join('');
|
|
526
|
+
const detail = doc.sections.map((section, i) => `<article id="section-${i}" class="section"><div class="kicker">Architecture Section</div><h2>${escapeHtml(section.title)}</h2><p>${escapeHtml(firstParagraph(section.lines) || 'See source markdown for detail.')}</p></article>`).join('');
|
|
527
|
+
const content = `<article class="section"><div class="kicker">Architecture View</div><h2>System Overview</h2><div class="card-grid cols-2">${cards}</div></article>${detail}`;
|
|
528
|
+
return renderShell({ title, eyebrow: 'Architecture View', summary: firstParagraph(doc.sections[0]?.lines || []), nav, content, badge });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function renderMindmap(filePath, outFile) {
|
|
532
|
+
const script = join(dirname(new URL(import.meta.url).pathname), 'render-mindmap', 'index.js');
|
|
533
|
+
execFileSync('node', [script, '--html', outFile, filePath], { stdio: 'ignore' });
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function main() {
|
|
537
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
538
|
+
if (!opts.file || !opts.out) {
|
|
539
|
+
console.error('Usage: smart-render.js <file.md> --out <file.html> [--mode auto|mindmap|outline|roadmap|architecture|mockup|explainer]');
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
const markdown = readFileSync(opts.file, 'utf8');
|
|
543
|
+
const doc = parseDocument(markdown);
|
|
544
|
+
const mode = classifyMode(opts.file, doc, opts.mode);
|
|
545
|
+
const title = doc.frontmatter.title || doc.sections[0]?.title || basename(opts.file);
|
|
546
|
+
const badge = opts.mode && opts.mode !== 'auto'
|
|
547
|
+
? `Mode: ${mode}`
|
|
548
|
+
: 'Mode: outline (safe default — agent may override)';
|
|
549
|
+
|
|
550
|
+
if (mode === 'mindmap') {
|
|
551
|
+
renderMindmap(opts.file, opts.out);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const html = mode === 'roadmap'
|
|
556
|
+
? renderRoadmap(doc, title, badge)
|
|
557
|
+
: mode === 'architecture'
|
|
558
|
+
? renderArchitecture(doc, title, badge)
|
|
559
|
+
: renderOutline(doc, title, 'Structured View', badge);
|
|
560
|
+
writeFileSync(opts.out, html, 'utf8');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
main();
|