@heart-of-gold/toolkit 0.1.34 → 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.
@@ -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.4"
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.4"
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.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.34",
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": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-fish",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Universal Translator — media generation tools for audio, image, video, and visualization",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: visualize
3
3
  description: >
4
- Render mind maps and tree visualizations from markdown. Prefer shareable HTML first for brainstorms, plans,
5
- architecture docs, and other structured workflow artifacts when share-html infrastructure is configured;
6
- otherwise fall back to terminal output for quick local inspection. Works on markdown files or any structured
7
- content. Triggers: visualize, mindmap, mind map, show me the structure, draw a map.
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,24 +14,193 @@ allowed-tools:
14
14
 
15
15
  # Visualize — Babel Fish
16
16
 
17
- Translating structured text into spatial understanding. Because walls of text hide structure that pictures reveal.
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
- ## The Renderers
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
191
+
192
+ ## Renderers
25
193
 
26
- Visualization now has two layers:
27
- - `scripts/smart-render.js` — renders one HTML artifact using the mode the coding agent chose (or a safe fallback)
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
28
196
  - `scripts/render-mindmap/index.js` — specialized mind-map renderer for branchy content
29
197
 
30
198
  **Locations:**
31
199
  - `scripts/smart-render.js`
32
200
  - `scripts/render-mindmap/index.js`
33
201
 
34
- **To find the smart renderer path**, locate it by searching for `smart-render.js`:
202
+ To find the smart renderer path:
203
+
35
204
  ```bash
36
205
  # Option 1: Use CLAUDE_PLUGIN_ROOT if available
37
206
  SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/visualize/scripts/smart-render.js"
@@ -40,7 +209,8 @@ SCRIPT="${CLAUDE_PLUGIN_ROOT}/skills/visualize/scripts/smart-render.js"
40
209
  SCRIPT=$(find ~/.claude/plugins -path "*/babel-fish/skills/visualize/scripts/smart-render.js" 2>/dev/null | head -1)
41
210
  ```
42
211
 
43
- **First run:** If `node_modules/` doesn't exist in the mind-map renderer directory, run `npm install` there first:
212
+ First run for the mind-map renderer:
213
+
44
214
  ```bash
45
215
  RENDER_DIR=$(dirname "$SCRIPT")/render-mindmap
46
216
  if [ ! -d "$RENDER_DIR/node_modules" ]; then
@@ -64,9 +234,9 @@ node "$SCRIPT" path/to/file.md --mode mindmap --out /tmp/view.html
64
234
  node "$(dirname "$SCRIPT")/render-mindmap/index.js" --html /tmp/map.html path/to/file.md
65
235
  ```
66
236
 
67
- ### Shareable HTML flow
237
+ ## HTML Share Flow
68
238
 
69
- Use the helper script when the user wants a browser URL and the share server is already configured. It now generates one polished HTML artifact first, then publishes it:
239
+ Use the helper script when the user wants a browser URL and the share server is already configured:
70
240
 
71
241
  ```bash
72
242
  bash scripts/render-and-share.sh path/to/file.md
@@ -79,201 +249,107 @@ This script:
79
249
  4. publishes the artifact to the configured local share server
80
250
  5. prints the publish result so you can return the URL
81
251
 
82
- ## Rendering Behavior
83
-
84
- - **Auto-depth:** If no `--depth` is specified, the renderer tries depths 3, 2, 1 and picks the deepest that fits the terminal width.
85
- - **Pruning:** Long labels are truncated with `…`. Nodes with many children show the first few plus `+N more`.
86
- - **Colors** (via ANSI, visible in Claude Code bash output):
87
- - Root: bold white on blue
88
- - Depth 1: bold cyan
89
- - Depth 2: green
90
- - Depth 3: yellow
91
- - Depth 4+: dim
252
+ ### Recommended share flow
92
253
 
93
- ## Phase 0 Determine What to Visualize
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.
94
263
 
95
- First decide whether this should be a browser/shareable HTML view or a quick terminal view.
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.
96
265
 
97
- **Prefer browser/shareable HTML first when:**
98
- - the source is a brainstorm, plan, architecture doc, or other structured workflow artifact
99
- - the user asks to open it in a browser
100
- - the user wants to share the result with another person or device
101
- - the structure is large enough that browser navigation is more useful than terminal rendering
102
- - `share-html` is configured
266
+ ## Terminal Rendering
103
267
 
104
- **Prefer terminal rendering when:**
268
+ Use terminal rendering when:
105
269
  - share-html is not configured
106
- - the user explicitly wants a quick terminal-only look
107
- - the environment is SSH-heavy and the browser/share path is not requested
108
-
109
- When invoked as `/visualize [path]`:
110
-
111
- **If a file path is provided:**
112
- 1. Read the file
113
- 2. Decide what kind of visual artifact would help most from context
114
- 3. If confidence is high enough, choose the mode and generate/share HTML first
115
- 4. If confidence is not high enough, ask the user which direction would help most
116
- 5. If sharing is unavailable or the user explicitly wants terminal output, fall back appropriately
270
+ - the user explicitly wants terminal-only output
271
+ - a quick local structural check is more useful than a browser view
117
272
 
118
- **If no path is provided:**
119
- 1. Check if there's a recent brainstorm or plan document in the conversation context
120
- 2. If yes: use that document's path
121
- 3. If no: summarize the current conversation topic into a markdown structure with headings, write it to a temp file, then render
122
-
123
- **If the user says "visualize this" or "show me a mind map":**
124
- 1. Look at what was just discussed or created
125
- 2. Generate an appropriate markdown structure
126
- 3. Render it
127
-
128
- ## Phase 1 — Render or Share
129
-
130
- ### Path A — Terminal rendering
131
-
132
- **IMPORTANT: Output the mind map in the assistant response text, NOT as raw bash tool output.**
273
+ **IMPORTANT:** Output the mind map in the assistant response text, not as raw bash tool output.
133
274
 
134
275
  Many harness bash panels truncate long output and wrap wide content, breaking alignment. Instead:
135
276
 
136
- 1. Locate the renderer script (see above)
137
- 2. Ensure dependencies are installed
277
+ 1. Locate the renderer script.
278
+ 2. Ensure dependencies are installed.
138
279
  3. Run the renderer with `--no-color`, redirect to a temp file:
139
280
  ```bash
140
281
  node "$SCRIPT" --no-color [file] > /tmp/mindmap-result.txt 2>&1
141
282
  ```
142
- 4. Read `/tmp/mindmap-result.txt`
143
- 5. Output the contents inside a markdown fenced code block in your response text
144
- 6. Clean up: `rm -f /tmp/mindmap-result.txt`
145
-
146
- The default mode is **vertical layout** — boxes on main branches, compact leaves, ~40 chars wide.
147
-
148
- ### Path B — Shareable HTML
149
-
150
- For substantial artifacts, prefer this path first when `share-html` is configured.
151
-
152
- The coding agent should choose the mode from context. Toolkit guidance:
153
- - plans often fit `roadmap` or `outline`
154
- - architecture docs often fit `architecture` or `outline`
155
- - concise branchy brainstorms may fit `mindmap`
156
- - product/UI concepts may fit `mockup`
157
- - stakeholder-friendly summaries may fit `explainer`
158
-
159
- If uncertain, ask the user using the harness's structured choice UI when available; otherwise present concise plain-text options.
160
-
161
- ### Uncertainty protocol
162
-
163
- When the best visualization is not clear, do **not** silently guess if the choice would materially affect usefulness.
164
-
165
- Ask **one concise question at a time**:
166
- - state the decision in plain language
167
- - offer 2-4 explicit options
168
- - include a recommended option when you have one
169
- - keep option labels outcome-focused, not renderer-jargon-first
170
-
171
- Good pattern:
172
- - "Which would help most here?"
173
- - `Roadmap` — show phases, sequencing, and implementation progress
174
- - `Outline` — show the document structure clearly
175
- - `Mind map` — show branching ideas and relationships
176
- - `Architecture view` — show components, boundaries, and decisions
177
-
178
- If the harness supports structured choices, use them.
179
- If not, use a short plain-text question such as:
180
-
181
- ```text
182
- I can visualize this a few different ways. Which would be most useful?
183
- 1. Roadmap — phases and tasks
184
- 2. Outline — document structure
185
- 3. Mind map — branching ideas
186
- 4. Architecture view — components and boundaries
187
- ```
188
-
189
- If the user does not care or says "you decide," choose the safest useful mode:
190
- - default to `outline`
191
- - use `roadmap` for clearly execution-heavy plans
192
- - use `architecture` for clearly system-design-heavy docs
193
- - use `mindmap` only when the artifact is genuinely concise and branchy
194
-
195
- 1. Verify or assume the input markdown is ready
196
- 2. Run:
283
+ 4. Read `/tmp/mindmap-result.txt`.
284
+ 5. Output the contents inside a fenced code block.
285
+ 6. Clean up:
197
286
  ```bash
198
- bash scripts/render-and-share.sh --mode <chosen-mode> --url-only [file]
287
+ rm -f /tmp/mindmap-result.txt
199
288
  ```
200
- 3. Read the returned URL from stdout
201
- 4. Return that URL to the user as the primary result
202
- 5. Briefly explain what was published and why this mode was chosen
203
289
 
204
- If you need more detail for debugging, you may run the helper without `--url-only` and inspect the returned JSON.
205
-
206
- 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.
207
-
208
- **For shell usage** (not through assistant panels): terminal rendering can use ANSI colors, or `--horizontal` for the wide spatial layout.
209
-
210
- ## Current HTML modes
211
-
212
- - `outline` safe default for dense or unknown structured docs
213
- - `roadmap` useful for plans and phased execution views
214
- - `architecture` — useful for architecture docs and architect outputs
215
- - `mindmap` useful for concise branchy artifacts where it truly helps
216
- - `mockup` reserved for future product/UI concept views
217
- - `explainer` reserved for future stakeholder/narrative views
218
-
219
- ## Phase 2 Offer Next Steps
220
-
221
- After rendering or sharing, briefly note:
222
- - for terminal mode: "Use `--depth N` to see more/less detail"
223
- - for terminal mode: "Use `--width N` to fit a different terminal size"
224
- - for shared HTML: return the final browser URL as the main result and say whether it is local-only or publicly reachable on the user's tailnet
225
- - if publishing failed due to missing share infrastructure: suggest `share-server-setup`
226
- - if the source was a brainstorm/plan/architecture doc, offer to continue the workflow (e.g., proceed to `/plan`, `/work`, or implementation)
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.
227
335
 
228
336
  ## Input Formats
229
337
 
230
338
  ### Markdown (primary)
231
- Standard markdown with `#` headings defining hierarchy:
232
- ```markdown
233
- # Root Topic
234
- ## Branch A
235
- - Detail 1
236
- - Detail 2
237
- ## Branch B
238
- ### Sub-branch
239
- - Detail 3
240
- ```
339
+ Standard markdown with `#` headings defining hierarchy.
241
340
 
242
341
  ### JSON
243
- Tree structure with `label` and `children`:
244
- ```json
245
- {
246
- "label": "Root",
247
- "children": [
248
- { "label": "Branch A", "children": [] },
249
- { "label": "Branch B", "children": [
250
- { "label": "Sub-branch", "children": [] }
251
- ]}
252
- ]
253
- }
254
- ```
255
-
256
- ## Generating Structure from Context
257
-
258
- When visualizing conversation context (no file path), create a markdown structure that captures:
259
-
260
- **For brainstorm content:**
261
- - Root = topic
262
- - Branches = key decisions or themes
263
- - Leaves = specific choices or details
342
+ Tree structure with `label` and `children`.
264
343
 
265
- **For plan content:**
266
- - Root = project name
267
- - Branches = phases
268
- - Leaves = tasks within each phase
344
+ ## Generating Structure From Context
269
345
 
270
- **For general discussion:**
271
- - Root = main topic
272
- - Branches = subtopics discussed
273
- - Leaves = key points or conclusions
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
274
350
 
275
351
  Write the generated markdown to `/tmp/mindmap-XXXXXX.md`, render it, then clean up.
276
352
 
277
353
  ## What Makes This Babel Fish
278
354
 
279
- The Babel Fish translates between languages. This skill translates between *modalities* — from linear text to spatial structure, and now from private working docs to shareable browser views. It makes the invisible visible: the hierarchy, the relationships, the gaps that only show up when you see the shape of the thinking.
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.
@@ -44,22 +44,23 @@ function parseDocument(markdown) {
44
44
  return { frontmatter, sections, raw: markdown };
45
45
  }
46
46
 
47
- function collectChecklists(lines) {
48
- return lines
49
- .map((line) => line.match(/^\s*- \[( |x)\]\s+(.+)$/i))
50
- .filter(Boolean)
51
- .map((m) => ({ done: m[1].toLowerCase() === 'x', text: m[2].trim() }));
52
- }
53
-
54
47
  function textFromLines(lines) {
55
48
  return lines.join('\n').trim();
56
49
  }
57
50
 
51
+ function normalizeWhitespace(text) {
52
+ return text.replace(/\s+/g, ' ').trim();
53
+ }
54
+
58
55
  function firstParagraph(lines) {
59
56
  const blocks = textFromLines(lines).split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
60
57
  return blocks[0] || '';
61
58
  }
62
59
 
60
+ function splitBlocks(lines) {
61
+ return textFromLines(lines).split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
62
+ }
63
+
63
64
  function classifyMode(_filePath, _doc, preferredMode) {
64
65
  if (preferredMode && preferredMode !== 'auto') return preferredMode;
65
66
  return 'outline';
@@ -74,7 +75,11 @@ function escapeHtml(value) {
74
75
  .replace(/'/g, '&#39;');
75
76
  }
76
77
 
77
- function renderShell({ title, eyebrow, summary, toc, content, badge = '' }) {
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 = '' }) {
78
83
  return `<!doctype html>
79
84
  <html lang="en">
80
85
  <head>
@@ -83,56 +88,169 @@ function renderShell({ title, eyebrow, summary, toc, content, badge = '' }) {
83
88
  <title>${escapeHtml(title)}</title>
84
89
  <style>
85
90
  :root {
86
- --bg: #0b1020;
91
+ --bg: #09111f;
92
+ --bg-2: #0f1a31;
87
93
  --panel: rgba(255,255,255,0.06);
88
94
  --panel-strong: rgba(255,255,255,0.1);
95
+ --panel-soft: rgba(255,255,255,0.04);
89
96
  --text: #eef2ff;
90
97
  --muted: #b8c0d9;
91
- --border: rgba(255,255,255,0.12);
98
+ --muted-2: #93a0bf;
99
+ --border: rgba(255,255,255,0.11);
92
100
  --accent: #7c9cff;
93
101
  --accent-2: #80e0d0;
94
102
  --success: #86efac;
95
103
  --warn: #fbbf24;
96
104
  --danger: #fca5a5;
97
- --shadow: 0 20px 60px rgba(0,0,0,0.35);
105
+ --shadow: 0 24px 80px rgba(0,0,0,0.34);
106
+ --radius-xl: 28px;
107
+ --radius-lg: 22px;
108
+ --radius-md: 16px;
98
109
  }
99
110
  * { box-sizing: border-box; }
100
- html, body { margin: 0; padding: 0; background: radial-gradient(circle at top, #16213d 0%, var(--bg) 48%); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
101
- body { line-height: 1.55; }
102
- main { max-width: 1440px; margin: 0 auto; padding: 40px 24px 72px; }
103
- .hero { display: grid; gap: 16px; margin-bottom: 28px; }
104
- .eyebrow { color: var(--accent-2); text-transform: uppercase; letter-spacing: 0.14em; font-size: 12px; font-weight: 700; }
105
- .hero h1 { margin: 0; font-size: clamp(32px, 4vw, 56px); line-height: 1.03; }
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; }
106
135
  .hero p { margin: 0; max-width: 78ch; color: var(--muted); font-size: 17px; }
107
- .badge { display: inline-flex; align-items: center; gap: 8px; width: fit-content; padding: 8px 12px; border-radius: 999px; background: rgba(124,156,255,0.18); border: 1px solid rgba(124,156,255,0.25); color: #dce5ff; font-size: 13px; }
108
- .layout { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: 24px; }
109
- nav { position: sticky; top: 16px; align-self: start; padding: 18px; border-radius: 20px; background: var(--panel); border: 1px solid var(--border); box-shadow: var(--shadow); backdrop-filter: blur(16px); }
110
- nav h2 { margin: 0 0 10px; font-size: 14px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.1em; }
111
- nav a { display: block; padding: 10px 12px; margin: 4px 0; color: var(--text); text-decoration: none; border-radius: 12px; }
112
- nav a:hover { background: rgba(255,255,255,0.05); }
113
- .section { margin-bottom: 18px; padding: 24px; border-radius: 24px; background: var(--panel); border: 1px solid var(--border); box-shadow: var(--shadow); backdrop-filter: blur(16px); }
114
- .section h2 { margin: 0 0 14px; font-size: clamp(22px, 2vw, 30px); }
115
- .section h3 { margin: 20px 0 8px; font-size: 18px; }
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; }
116
178
  .section p, .section li, .section td, .section th { color: var(--muted); font-size: 15px; }
117
- .section ul { padding-left: 18px; }
118
- .grid { display: grid; gap: 16px; }
119
- .grid.cols-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
120
- .card { padding: 18px; border-radius: 18px; background: var(--panel-strong); border: 1px solid var(--border); }
121
- .card h3, .card h4 { margin-top: 0; }
122
- .kicker { font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--accent-2); margin-bottom: 8px; }
123
- .checklist { display: grid; gap: 10px; }
124
- .item { padding: 14px 16px; border-radius: 16px; background: rgba(255,255,255,0.04); border: 1px solid var(--border); }
125
- .item.done { border-color: rgba(134,239,172,0.35); }
126
- .item .status { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
127
- .item.done .status { color: var(--success); }
128
- .item.todo .status { color: var(--warn); }
129
- .meta-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
130
- .meta { padding: 14px 16px; border-radius: 16px; background: rgba(255,255,255,0.04); border: 1px solid var(--border); }
131
- .meta .label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
132
- .meta .value { margin-top: 8px; font-weight: 600; font-size: 15px; color: var(--text); }
133
- code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
134
- pre { white-space: pre-wrap; background: rgba(0,0,0,0.24); padding: 16px; border-radius: 16px; border: 1px solid var(--border); }
135
- @media (max-width: 980px) { .layout { grid-template-columns: 1fr; } nav { position: static; } .grid.cols-2 { grid-template-columns: 1fr; } }
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
+ }
136
254
  </style>
137
255
  </head>
138
256
  <body>
@@ -141,63 +259,273 @@ pre { white-space: pre-wrap; background: rgba(0,0,0,0.24); padding: 16px; border
141
259
  <div class="eyebrow">${escapeHtml(eyebrow)}</div>
142
260
  <h1>${escapeHtml(title)}</h1>
143
261
  <p>${escapeHtml(summary || 'Shareable HTML artifact generated by Heart of Gold Visualize.')}</p>
144
- ${badge ? `<div class="badge">${escapeHtml(badge)}</div>` : ''}
262
+ <div class="hero-row">
263
+ ${badge ? `<div class="badge">${escapeHtml(badge)}</div>` : ''}
264
+ </div>
145
265
  </section>
146
- <div class="layout">
147
- <nav>
148
- <h2>Contents</h2>
149
- ${toc}
150
- </nav>
266
+ <div class="page">
151
267
  <section>${content}</section>
268
+ <aside class="rail">${nav}</aside>
152
269
  </div>
153
270
  </main>
154
271
  </body>
155
272
  </html>`;
156
273
  }
157
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
+
158
281
  function renderOutline(doc, title, eyebrow, badge) {
159
- const toc = doc.sections.map((s, i) => `<a href="#section-${i}">${escapeHtml(s.title)}</a>`).join('');
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>`;
160
285
  const content = doc.sections.map((section, i) => {
161
- const body = textFromLines(section.lines);
162
- const paragraphs = body.split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean).map((b) => {
163
- if (b.includes('\n- ') || b.match(/^-/m)) {
164
- const items = b.split(/\n/).map((line) => line.match(/^\s*[-*]\s+(.+)$/)?.[1]).filter(Boolean);
165
- if (items.length) return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>`;
166
- }
167
- return `<p>${escapeHtml(b)}</p>`;
168
- }).join('');
169
- return `<article id="section-${i}" class="section"><h2>${escapeHtml(section.title)}</h2>${paragraphs || '<p>No details captured.</p>'}</article>`;
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>`;
170
288
  }).join('');
171
- return renderShell({ title, eyebrow, summary: firstParagraph(doc.sections[0]?.lines || []), toc, content, badge });
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>`;
172
396
  }
173
397
 
174
398
  function renderRoadmap(doc, title, badge) {
175
- const toc = ['Summary', ...doc.sections.map((s) => s.title)].map((s, i) => `<a href="#section-${i}">${escapeHtml(s)}</a>`).join('');
176
- const tasksSection = doc.sections.find((s) => /implementation tasks/i.test(s.title));
177
- const tasks = tasksSection ? collectChecklists(tasksSection.lines) : [];
178
- const meta = [
179
- ['Status', doc.frontmatter.status || 'unknown'],
180
- ['Confidence', doc.frontmatter.confidence || 'n/a'],
181
- ['Date', doc.frontmatter.date || 'n/a'],
182
- ['Type', doc.frontmatter.type || 'plan'],
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'],
183
429
  ];
184
- let content = `<article id="section-0" class="section"><h2>Summary</h2><div class="meta-grid">${meta.map(([l,v]) => `<div class="meta"><div class="label">${escapeHtml(l)}</div><div class="value">${escapeHtml(v)}</div></div>`).join('')}</div></article>`;
185
- if (tasks.length) {
186
- content += `<article id="section-1" class="section"><h2>Implementation Tasks</h2><div class="checklist">${tasks.map((t) => `<div class="item ${t.done ? 'done' : 'todo'}"><div class="status">${t.done ? 'Done' : 'Planned'}</div><div>${escapeHtml(t.text)}</div></div>`).join('')}</div></article>`;
187
- }
188
- const remaining = doc.sections.filter((s) => !/implementation tasks/i.test(s.title));
189
- content += remaining.map((section, idx) => {
190
- const body = firstParagraph(section.lines);
191
- return `<article id="section-${idx + 2}" class="section"><div class="kicker">Plan Section</div><h2>${escapeHtml(section.title)}</h2><p>${escapeHtml(body || 'See source markdown for full details.')}</p></article>`;
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>`;
192
444
  }).join('');
193
- return renderShell({ title, eyebrow: 'Plan Dashboard', summary: firstParagraph(doc.sections[0]?.lines || []), toc, content, badge });
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 });
194
519
  }
195
520
 
196
521
  function renderArchitecture(doc, title, badge) {
197
- const toc = doc.sections.map((s, i) => `<a href="#section-${i}">${escapeHtml(s.title)}</a>`).join('');
198
- const cards = doc.sections.slice(0, 4).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('');
199
- const content = `<article class="section"><h2>Architecture Overview</h2><div class="grid cols-2">${cards}</div></article>` + doc.sections.map((section, i) => `<article id="section-${i}" class="section"><h2>${escapeHtml(section.title)}</h2><p>${escapeHtml(firstParagraph(section.lines) || 'See source markdown for detail.')}</p></article>`).join('');
200
- return renderShell({ title, eyebrow: 'Architecture View', summary: firstParagraph(doc.sections[0]?.lines || []), toc, content, 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 });
201
529
  }
202
530
 
203
531
  function renderMindmap(filePath, outFile) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deep-thought",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "The Answer Computer — reasoning tools for brainstorming, planning, architecture design, and deep thinking",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marvin",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "The Paranoid Android — quality tools for code review, knowledge compounding, and work execution",
5
5
  "author": {
6
6
  "name": "ondrej-svec",