@cyber-dash-tech/revela 0.17.2 → 0.17.4

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/README.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  **English** | [中文](README.zh-CN.md)
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-546%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
6
+
5
7
  <p align="center">
6
8
  <img src="assets/img/logo.png" alt="Revela" width="560" />
7
9
  </p>
8
10
 
9
- Revela is an [OpenCode](https://opencode.ai) plugin for turning workspace sources, research, evidence, and user intent into trusted narrative artifacts. Its first render target is HTML slide decks.
11
+ Revela is an [OpenCode](https://opencode.ai) plugin that turns local sources and research into a traceable narrative graph, then renders that graph into briefs and presentation decks.
12
+
13
+ The narrative graph records the core elements needed to generate a brief or deck: audience, decision, claims, evidence, sources, risks, objections, and open gaps.
10
14
 
11
15
  ## Install
12
16
 
@@ -30,17 +34,17 @@ Revela includes built-in deck designs:
30
34
  ### [summit](designs/summit/preview.html)
31
35
 
32
36
  <p align="center">
33
- <img src="assets/img/summit-01.png" alt="Summit design preview 1" width="32%" />
34
- <img src="assets/img/summit-02.png" alt="Summit design preview 2" width="32%" />
35
- <img src="assets/img/summit-03.png" alt="Summit design preview 3" width="32%" />
37
+ <img src="assets/img/summit-01.jpg" alt="Summit design preview 1" width="32%" />
38
+ <img src="assets/img/summit-02.jpg" alt="Summit design preview 2" width="32%" />
39
+ <img src="assets/img/summit-03.jpg" alt="Summit design preview 3" width="32%" />
36
40
  </p>
37
41
 
38
42
  ### [monet](designs/monet/preview.html)
39
43
 
40
44
  <p align="center">
41
- <img src="assets/img/monet-01.png" alt="Monet design preview 1" width="32%" />
42
- <img src="assets/img/monet-02.png" alt="Monet design preview 2" width="32%" />
43
- <img src="assets/img/monet-03.png" alt="Monet design preview 3" width="32%" />
45
+ <img src="assets/img/monet-01.jpg" alt="Monet design preview 1" width="32%" />
46
+ <img src="assets/img/monet-02.jpg" alt="Monet design preview 2" width="32%" />
47
+ <img src="assets/img/monet-03.jpg" alt="Monet design preview 3" width="32%" />
44
48
  </p>
45
49
 
46
50
  `starter` is the clean default presentation style.
package/README.zh-CN.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  [English](README.md) | **中文**
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-546%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
6
+
5
7
  <p align="center">
6
8
  <img src="assets/img/logo.png" alt="Revela" width="560" />
7
9
  </p>
8
10
 
9
- Revela 是一个 [OpenCode](https://opencode.ai) 插件,用来把工作区来源材料、调研、证据和用户意图转成可信的叙事型沟通 artifact。它的第一个 render target 是 HTML slide deck。
11
+ Revela 是一个 [OpenCode](https://opencode.ai) 插件,用来把本地材料和调研结果转成可追踪的叙事图谱,再基于这个图谱生成 brief presentation deck。
12
+
13
+ 叙事图谱用 graph 方式记录生成 brief 或 deck 所需的关键要素:受众、决策目标、论点、论据、资料来源、风险、潜在质疑和待补齐的信息。
10
14
 
11
15
  ## 安装
12
16
 
@@ -30,17 +34,17 @@ Revela 内置多个 deck design:
30
34
  ### [summit](designs/summit/preview.html)
31
35
 
32
36
  <p align="center">
33
- <img src="assets/img/summit-01.png" alt="Summit design preview 1" width="32%" />
34
- <img src="assets/img/summit-02.png" alt="Summit design preview 2" width="32%" />
35
- <img src="assets/img/summit-03.png" alt="Summit design preview 3" width="32%" />
37
+ <img src="assets/img/summit-01.jpg" alt="Summit design preview 1" width="32%" />
38
+ <img src="assets/img/summit-02.jpg" alt="Summit design preview 2" width="32%" />
39
+ <img src="assets/img/summit-03.jpg" alt="Summit design preview 3" width="32%" />
36
40
  </p>
37
41
 
38
42
  ### [monet](designs/monet/preview.html)
39
43
 
40
44
  <p align="center">
41
- <img src="assets/img/monet-01.png" alt="Monet design preview 1" width="32%" />
42
- <img src="assets/img/monet-02.png" alt="Monet design preview 2" width="32%" />
43
- <img src="assets/img/monet-03.png" alt="Monet design preview 3" width="32%" />
45
+ <img src="assets/img/monet-01.jpg" alt="Monet design preview 1" width="32%" />
46
+ <img src="assets/img/monet-02.jpg" alt="Monet design preview 2" width="32%" />
47
+ <img src="assets/img/monet-03.jpg" alt="Monet design preview 3" width="32%" />
44
48
  </p>
45
49
 
46
50
  `starter` 是简洁默认演示风格。
@@ -480,6 +480,7 @@ These rules are mandatory for Monet.
480
480
  - **Sparse slides depend on image weight.** If content is light, the photo or page framing must hold the composition.
481
481
  - **No glass cards, neon KPI styling, or startup-product chrome.** Monet is editorial and print-adjacent.
482
482
  - **Visual hierarchy is strict:** eyebrow -> heading -> body -> caption.
483
+ - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
483
484
 
484
485
  ### Common Mistakes
485
486
 
@@ -823,6 +824,31 @@ Every slot accepts 1 or more components. The LLM decides what each slot contains
823
824
 
824
825
  Use these components when a page needs repeatable editorial modules inside a larger layout. Components define the block itself, not the page grid around it.
825
826
 
827
+ <!-- @component:box:start -->
828
+ #### Box
829
+
830
+ Card/group primitive for one idea, case, evidence item, metric, objection, risk, or action. Put `text-panel`, `media`, `echart-panel`, `data-table`, `stat-card`, or `quote` inside a box when they support the same idea.
831
+
832
+ ```html
833
+ <div class="box">
834
+ <div class="text-panel text-panel--plain">
835
+ <div class="text-panel-body">
836
+ <p class="eyebrow">Evidence</p>
837
+ <h3>One clear idea</h3>
838
+ <p>Short supporting copy or source-bound explanation.</p>
839
+ </div>
840
+ </div>
841
+ </div>
842
+ ```
843
+
844
+ ```css
845
+ .box { height: 100%; min-height: 0; padding: 28px; border: 1px solid var(--line); background: rgba(255,255,255,0.46); display: flex; flex-direction: column; gap: 18px; overflow: hidden; }
846
+ .box--quiet { background: transparent; }
847
+ .box--mist { background: rgba(240,244,247,0.72); }
848
+ .box--dark { background: #0d1a24; --text-primary:#f0f4f7; --text-secondary:rgba(240,244,247,0.72); --text-muted:rgba(240,244,247,0.55); --line:rgba(240,244,247,0.16); }
849
+ ```
850
+ <!-- @component:box:end -->
851
+
826
852
 
827
853
 
828
854
  <!-- @component:text-panel:start -->
@@ -937,6 +963,35 @@ Rules:
937
963
  - **`editorial-list` inside `--dark`.** Add `style="--accent-earth:rgba(240,244,247,0.72)"` on the `<ul>` wrapper so the bullet squares read against the dark background.
938
964
  <!-- @component:text-panel:end -->
939
965
 
966
+ <!-- @component:media:start -->
967
+ #### Media
968
+
969
+ Normal image, screenshot, diagram, logo, or portrait component. Keep important visual information understandable. Do not use `media` for full-bleed covers/dividers/closings; use `hero` for those.
970
+
971
+ ```html
972
+ <figure class="media">
973
+ <div class="media-frame">
974
+ <img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?q=80&w=1200&auto=format&fit=crop" alt="Atmospheric water garden detail">
975
+ </div>
976
+ <figcaption class="media-caption source-note">Optional source or field note</figcaption>
977
+ </figure>
978
+ ```
979
+
980
+ ```css
981
+ .media { height: 100%; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
982
+ .media-frame { position: relative; overflow: hidden; background: transparent; width: 100%; flex: 1; min-height: 0; }
983
+ .media-frame img { width: 100%; height: 100%; display: block; object-fit: cover; }
984
+ .media--contain .media-frame img { object-fit: contain; }
985
+ .media-caption { margin-top: 0; font-size: 11px; line-height: 1.45; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
986
+ .media-caption.source, .media-caption.source-note { font-family: "Times New Roman", Times, serif; font-size: 11px; line-height: 1.35; letter-spacing: 0; text-transform: none; }
987
+ ```
988
+
989
+ Rules:
990
+ - Use `media` for screenshots, charts exported as images, diagrams, logos, portraits, and evidence visuals that must stay readable.
991
+ - Use `object-fit: contain` through `media--contain` for screenshots, diagrams, and logos when cropping would remove information.
992
+ - Put `media` inside a `box` when the visual and text support one semantic idea.
993
+ <!-- @component:media:end -->
994
+
940
995
  <!-- @component:stat-card:start -->
941
996
  #### Stat Card
942
997
 
@@ -1010,7 +1065,7 @@ Rules:
1010
1065
  - **Do not over-explain.** If the description starts to become paragraph-length, switch to `text-panel` or pair the stat card with a narrative component in the neighboring slot.
1011
1066
  <!-- @component:stat-card:end -->
1012
1067
 
1013
- <!-- @component:editorial-image-top:start -->
1068
+ <!-- @compat:editorial-image-top:start -->
1014
1069
  #### Editorial Image Top
1015
1070
 
1016
1071
  Image-first editorial module: image on top, text below. Best for highlight grids, product/material stories, and any module where the picture should lead before the reader enters the copy.
@@ -1094,9 +1149,9 @@ Rules:
1094
1149
  - **Image aspect ratio.** Aim for 16:9 or 3:2 crops for the image block. Portrait crops create tall image zones that push text down and unbalance the composition.
1095
1150
  - **Kicker icon size.** Keep Lucide SVG icons at 16–20px. Larger icons shift visual weight from the image to the label zone.
1096
1151
  - **`editorial-list` font-size.** Override to `font-size:13px;gap:10px` inline when used inside a very narrow copy zone — the base `editorial-list` is `17px/gap:14px`, which may be too large for tight columns.
1097
- <!-- @component:editorial-image-top:end -->
1152
+ <!-- @compat:editorial-image-top:end -->
1098
1153
 
1099
- <!-- @component:editorial-text-top:start -->
1154
+ <!-- @compat:editorial-text-top:start -->
1100
1155
  #### Editorial Text Top
1101
1156
 
1102
1157
  Text-first editorial module: text on top, image below. Best for narrative snippets, report-style explanations, or blocks where the image serves as evidence rather than the primary hook.
@@ -1160,9 +1215,9 @@ Rules:
1160
1215
  - **Same height/stretch rule as `editorial-image-top`.** Do not set fixed heights; let parent grid stretch control the column.
1161
1216
  - **When used as a center spine in `highlight-cols`,** this is the one component that may legitimately be taller than its neighbors. That density imbalance is intentional — do not try to equalize it with padding or extra content in the outer columns.
1162
1217
  - **`editorial-list` font-size.** Override to `font-size:13px;gap:10px` inline when used inside a very narrow copy zone — the base `editorial-list` is `17px/gap:14px`, which may be too large for tight columns.
1163
- <!-- @component:editorial-text-top:end -->
1218
+ <!-- @compat:editorial-text-top:end -->
1164
1219
 
1165
- <!-- @component:editorial-text-left:start -->
1220
+ <!-- @compat:editorial-text-left:start -->
1166
1221
  #### Editorial Text Left
1167
1222
 
1168
1223
  Horizontal editorial module: a full-width title band on top, with text on the left and a visual slot on the right below. Best for compact feature rows or any slot where a wide-but-short frame suits a side-by-side composition with a clear heading above.
@@ -1274,7 +1329,7 @@ Rules:
1274
1329
  - **`echart-container` in visual slot.** Set `width:100%;height:100%` on the container and call `echarts.init()` after `SlidePresentation` is instantiated. The `position:relative;overflow:hidden` on `.editorial-text-left-visual` contains the canvas correctly.
1275
1330
  - **`image-title` in visual slot.** The component is self-contained and fills `width:100%;height:100%` automatically. Use `image-title--right` modifier with a bottom-heavy overlay and right-biased blur mask for the most common editorial orientation.
1276
1331
  - **Dark background.** Override CSS variables on `.editorial-text-left` to cascade into both the copy and visual zones: `--text-primary`, `--text-secondary`, `--text-muted`, `--line`, `--line-strong` — all set to white-family values.
1277
- <!-- @component:editorial-text-left:end -->
1332
+ <!-- @compat:editorial-text-left:end -->
1278
1333
 
1279
1334
  <!-- @component:echart-panel:start -->
1280
1335
  #### EChart Panel
@@ -1357,7 +1412,11 @@ Rules:
1357
1412
  - **Chart sizing for `narrative-hero-left-dark`.** The left 7.8fr column is wide. A donut or candlestick chart works best centered with some breathing room. Add `padding: 24px 32px` to `.echart-container` to prevent the chart from touching the column edges.
1358
1413
  <!-- @component:echart-panel:end -->
1359
1414
 
1360
- <!-- @component:flow-horizontal:start -->
1415
+ <!-- @component:steps:start -->
1416
+ #### Steps
1417
+
1418
+ Monet step and phase sequences use the flow-horizontal and flow-vertical implementations. Use `.flow-horizontal` for left-to-right process stages and `.flow-vertical` for top-to-bottom phases.
1419
+
1361
1420
  #### Flow Horizontal
1362
1421
 
1363
1422
  Horizontal step or phase sequence. Use for process stages, numbered definitions, or parallel concepts that should be read left to right. Suitable for 2–5 items.
@@ -1487,9 +1546,9 @@ Rules:
1487
1546
  - **Step copy length directly affects column balance.** One step with a long paragraph will push its column taller than the others and break the horizontal rhythm. Trim all steps to roughly equal length (2–4 lines each).
1488
1547
  - **Number card design.** Each `.flow-number` uses two overlapping squares: a white `::before` base square (with border) and a blue `::after` top square (`--accent-earth`) rotated by `--fn-rot`. Set `data-n="01"` on the element (the `::after` reads it via `content: attr(data-n)`). Vary `--fn-rot` per item (e.g. `-9deg`, `7deg`, `-6deg`, `10deg`) for a hand-placed impression.
1489
1548
  - **Horizontal rule connector.** The `::before` pseudo-element on `.flow-horizontal` draws a full-width line at `top: 18px` (vertical centre of the 36px number box). `.flow-number` sits above it via `z-index: 1`; its `::before` background is set to `var(--bg-page)` to mask the line behind the white square. On dark backgrounds, override `.flow-horizontal .flow-number::before { background: <dark-bg-color>; }` and set `.flow-horizontal::before { background: rgba(240,244,247,0.15); }`.
1490
- <!-- @component:flow-horizontal:end -->
1549
+ <!-- flow-horizontal implementation ends -->
1491
1550
 
1492
- <!-- @component:flow-vertical:start -->
1551
+ <!-- flow-vertical implementation starts -->
1493
1552
  #### Flow Vertical
1494
1553
 
1495
1554
  Vertical step or timeline sequence. Use for chronological phases, execution stages, or progress narratives that should be read top to bottom. Suitable for 2–6 items.
@@ -1587,7 +1646,7 @@ Rules:
1587
1646
  - **Number card design (same as flow-horizontal).** Use `data-n="01"` and `style="--fn-rot:-8deg;"` on each `.flow-number`. Vary the rotation angle per item.
1588
1647
  - **Dark text overrides.** Flow-number `::before` border: `rgba(240,244,247,0.3)`; `::after` background: a darker accent or `rgba(41,128,175,0.85)`. h4: `color:#f0f4f7`; p: `color:rgba(240,244,247,0.7)`. Also override the connecting line: `background:rgba(240,244,247,0.2)`.
1589
1648
  - **Column height constraint.** `flow-vertical` expands naturally with content. In a two-column layout, ensure the opposing column (`text-panel` or `echart-panel`) has enough content to avoid a large height mismatch.
1590
- <!-- @component:flow-vertical:end -->
1649
+ <!-- @component:steps:end -->
1591
1650
 
1592
1651
  <!-- @component:data-table:start -->
1593
1652
  #### Data Table
@@ -1781,7 +1840,11 @@ Rules:
1781
1840
 
1782
1841
 
1783
1842
 
1784
- <!-- @component:image-title:start -->
1843
+ <!-- @component:hero:start -->
1844
+ #### Hero
1845
+
1846
+ Monet hero slides use the image-title implementation for full-bleed cover, closing, atmospheric section divider, or strong visual statement slides.
1847
+
1785
1848
  #### Image Title
1786
1849
 
1787
1850
  Self-contained full-canvas component: a dominant photograph with a directional blur layer, a gradient overlay, and a foreground text stack — all composited inside one element. Use for cover slides, closing slides, atmospheric section dividers, or any full-bleed spread where a single image should dominate the entire canvas.
@@ -1945,7 +2008,7 @@ Rules:
1945
2008
  - **`--right` closing variant.** Mirror the blur mask: use `mask-image:linear-gradient(to right, transparent 0%, transparent 20%, black 100%)`. The right side stays blurred (text zone); the left side of the image stays sharp.
1946
2009
  - **`--center` variant.** Use a radial or symmetric overlay: `radial-gradient(ellipse at center, rgba(5,5,5,0.72) 0%, rgba(5,5,5,0.20) 100%)` or a flat `rgba(5,5,5,0.55)`. Center blur mask: `mask-image:radial-gradient(ellipse at center, black 0%, transparent 80%)`.
1947
2010
  - **Subtitle width on `--right`.** `.image-title-subtitle` inherits `max-width:480px` which is set for left-aligned text. On `--right`, override to match the `.image-title-body` width: `style="max-width:520px;margin-left:auto;"`.
1948
- <!-- @component:image-title:end -->
2011
+ <!-- @component:hero:end -->
1949
2012
 
1950
2013
  <!-- @component:toc:start -->
1951
2014
  #### TOC Panel
@@ -2368,7 +2431,11 @@ Omit `--light` only on slides with a white/light background.
2368
2431
 
2369
2432
  <!-- @component:page-number:end -->
2370
2433
 
2371
- <!-- @component:timeline-journey-horizontal:start -->
2434
+ <!-- @component:roadmap-horizontal:start -->
2435
+ #### Roadmap Horizontal
2436
+
2437
+ Monet horizontal roadmaps use the timeline-journey-horizontal implementation.
2438
+
2372
2439
  #### Timeline Journey Horizontal
2373
2440
 
2374
2441
  A horizontal milestone timeline with a central axis line. Nodes sit on the axis; a dashed vertical stem leads to a tip node, with date, title, and description text alongside. Alternate nodes above and below the axis for rhythm. Suitable for 4–8 milestones across a chronological arc, transformation story, or multi-year programme recap.
@@ -2534,9 +2601,13 @@ Rules:
2534
2601
  - **Adjust stem length**: Change `--tjh-stem-h` to lengthen or shorten the dashed connector.
2535
2602
  - **Dark background overrides**: Set `--line-strong: rgba(240,244,247,0.25)` on `.tjh`, override `.tjh-axis { background }`, set `.tjh-title { color: #f0f4f7 }`, `.tjh-text { color: rgba(240,244,247,0.7) }`. The `--tjh-item-color` accent colours work on dark backgrounds without change.
2536
2603
  - **Fewer nodes**: For 4–5 nodes, widen `--tjh-col` by using a smaller denominator (e.g. `calc(100% / 5)`), and space `left` values accordingly.
2537
- <!-- @component:timeline-journey-horizontal:end -->
2604
+ <!-- @component:roadmap-horizontal:end -->
2605
+
2606
+ <!-- @component:roadmap-vertical:start -->
2607
+ #### Roadmap Vertical
2608
+
2609
+ Monet vertical roadmaps use the timeline-journey-vertical implementation.
2538
2610
 
2539
- <!-- @component:timeline-journey-vertical:start -->
2540
2611
  #### Timeline Journey Vertical
2541
2612
 
2542
2613
  A vertical milestone timeline with a central axis line. Nodes sit on the axis; a horizontal dashed stem leads to a tip dot, with date, title, and description text alongside. Alternate nodes left and right of the axis for rhythm. Suitable for 3–8 milestones across a chronological arc, transformation story, or multi-year programme recap.
@@ -2731,6 +2802,6 @@ Rules:
2731
2802
  - **Dark background overrides**: set on the `.tjv` wrapper — `--line-strong: rgba(240,244,247,0.25)` (axis + stem), `.tjv-title { color: #f0f4f7 }`, `.tjv-text { color: rgba(240,244,247,0.7) }`. The `--tjv-item-color` accent colours work on dark backgrounds without change.
2732
2803
  - **Fewer nodes (3–4)**: increase spacing — use `top` values like `15%, 35%, 55%, 75%` to prevent the timeline from clustering at the top.
2733
2804
  - **More nodes (6–8)**: keep `.tjv-text` to 1-2 lines and increase vertical spacing between nodes if needed. Treat `17px` as the default body size; only use a smaller local override in exceptional dense layouts.
2734
- <!-- @component:timeline-journey-vertical:end -->
2805
+ <!-- @component:roadmap-vertical:end -->
2735
2806
 
2736
2807
  <!-- @design:components:end -->
@@ -233,6 +233,7 @@ new SlidePresentation();
233
233
  - **Stable layout CSS.** Do not rewrite base layout/container CSS unless the structure itself must change. Prefer tokens, typography, component skins, and small motif components.
234
234
  - **Reusable class vocabulary.** New classes must be documented in this DESIGN.md. Avoid many one-off selectors in generated decks.
235
235
  - **SVG is exceptional.** Use decorative SVG only when the user explicitly asks for an illustration/icon-like visual or when design authoring requires a motif.
236
+ - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
236
237
  - **Images for photographic references.** Use image treatment rules rather than fake SVG when the reference is photographic, UI, webpage, or product imagery.
237
238
  - **Content pages need a stable title block.** Except cover, TOC, closing, section divider, and full-bleed hero slides, every normal content slide should include a visible title block from the upper-left safe area. It should contain a compact chapter/section label plus a slide title written as the page's claim or takeaway.
238
239
  - **Do not hide the page title inside a card.** Body components may have their own headings, but the slide-level title block should remain separate and easy to scan unless the chosen layout explicitly defines a compact side-title variant.
@@ -443,6 +443,7 @@ These rules are mandatory for Summit.
443
443
  - **Text panels are not decorative rule panels.** Do not add a default left border, vertical accent bar, yellow/gold line, or inline rule to `text-panel`. Use typography, spacing, boxes, stats, quotes, or layout-level dividers for emphasis.
444
444
  - **Titles are Title Case.** Do not set `text-transform:uppercase` on `h1`, `h2`, `h3`, or `h4` titles. Uppercase is reserved for eyebrows, captions, metadata labels, short codes, and date/code-like markers.
445
445
  - **Components are transparent by default.** Component primitives should not bring their own paper/background fill. Let `.page`, layout containers, or explicit modifier variants provide background color when needed.
446
+ - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
446
447
 
447
448
  ### Common Mistakes
448
449
 
@@ -27,7 +27,7 @@ export async function handleHelp(
27
27
  `Run \`/revela enable\` to load Revela context without starting a workflow, or run \`/revela disable\` to pause it. Workflow commands still auto-enable Revela.\n\n` +
28
28
  `---\n\n` +
29
29
  `**Workflow**\n\n` +
30
- `1. \`init\` — discover workspace sources and capture intent\n` +
30
+ `1. \`init\` — discover sources, refresh the graph, ask key questions, and recommend next steps\n` +
31
31
  `2. \`research\` — close evidence gaps and bind support\n` +
32
32
  `3. \`story\` — inspect audience, thesis, claims, evidence, risks, and diagnostics\n` +
33
33
  `4. \`make\` — generate deck or brief from canonical story state\n` +
@@ -38,7 +38,7 @@ export async function handleHelp(
38
38
  `\`/revela\` — show REVELA help\n` +
39
39
  `\`/revela enable\` — enable Revela prompt/context without starting a workflow\n` +
40
40
  `\`/revela disable\` — disable Revela prompt/context for this session\n` +
41
- `\`/revela init\` — initialize or refresh workspace story state\n` +
41
+ `\`/revela init\` — start Revela: discover sources, refresh story state, ask key questions, and recommend next steps\n` +
42
42
  `\`/revela research\` — research, bind evidence, and reduce story gaps\n` +
43
43
  `\`/revela story [-l language]\` — open the read-only story workspace UI\n` +
44
44
  `\`/revela make --deck\` — make a deck from story state and deck-plan/\n` +
@@ -11,12 +11,13 @@ export function buildInitPrompt({
11
11
  ? `A ${DECKS_STATE_FILE} file already exists as legacy/cache state. Read it first through the revela-decks tool and update it conservatively only for compatibility metadata.`
12
12
  : `No ${DECKS_STATE_FILE} file exists yet. Keep this workspace file-native: initialize the Markdown narrative vault before writing narrative meaning and do not create ${DECKS_STATE_FILE}.`
13
13
 
14
- return `Initialize Revela narrative workspace state.
14
+ return `Start Revela on the current workspace.
15
15
 
16
16
  Goal:
17
17
  - Initialize or refresh the Markdown narrative vault and file-native source inventory from local workspace evidence.
18
18
  - Treat init as repeatable ingest: discover files, register source materials, follow returned ingest task hints, and distill stable narrative meaning.
19
19
  - Capture primary audience, belief before/after, decision/action, thesis, central claims, evidence availability, objections, risks, source materials, artifact history, and open questions.
20
+ - End init with a guided completion report: what local discovery found, what the narrative graph currently contains, what gaps remain, what user clarification is needed, and which command should run next.
20
21
  - Do not treat initialization as permission to write a deck. Do not require slide count, visual style, design selection, output path, layout choices, or component choices unless the user explicitly asks to render.
21
22
  - Treat central claims as chapter-ready claims, not evidence fragments: a central claim should be able to support framing/context, proof/evidence, decision implication, and explicit boundary/gap/risk material. If local material only supports a narrow fact, record it as supporting evidence or a supporting claim instead of promoting it to central importance.
22
23
 
@@ -60,7 +61,7 @@ Required workflow:
60
61
  6. Before writing narrative meaning, inspect \`narrativeInventory\` from the latest \`read(summary: true)\` result or call \`revela-decks narrativeInventory\`. Then distill stable findings into \`revela-narrative/**/*.md\` using the Markdown authoring guide. Completeness is not a gate: write partial claims, caveats, unsupported scope, and research gaps rather than waiting for a complete story. Use optional helpers such as \`upsertVaultResearchGap\`, \`upsertVaultEvidence\`, or \`bindResearchFindings\` only when they fit the exact update. Preserve frontmatter ids and existing section headings when editing Markdown. Write nodes first; add inline \`## Relations\` edges afterward only when explicit.
61
62
  7. After Markdown changes, rely on the vault write hook or call \`revela-decks markdownQa\`, then \`revela-decks compileNarrativeVault\`; keep \`markdownQa.repairCards\` separate from compiler blockers and fix both before treating the narrative as usable. If no explicit \`markdownQa\` result is visible after compile, call \`revela-decks markdownQa\` as a manual fallback. Do not use \`upsertNarrative\`.
62
63
  8. If explicit deck/artifact information exists, record conservative artifact context in file-native outputs or existing compatibility state only from visible information. Do not infer hidden evidence from generated artifacts.
63
- 9. Report initialized/updated/migrated state plus counts and paths for added, changed, newer-than-vault, unchanged, \`ingest.ingestCandidates\`, and \`ingest.suggestedTasks\`. Always include \`Markdown QA: clean\` or \`Markdown QA blockers:\` in the final report. If Markdown QA blockers remain, do not say the workspace initialized cleanly; say the vault was initialized but Markdown repairs remain.
64
+ 9. Complete an Init Completion Report before ending. Do not end with only a technical success message. Include local discovery counts and paths for added, changed, newer-than-vault, unchanged, \`ingest.ingestCandidates\`, and \`ingest.suggestedTasks\`; a narrative graph summary; open evidence/research gaps; any Markdown QA status; user clarification questions; and recommended next commands. Always include \`Markdown QA: clean\` or \`Markdown QA blockers:\` in the final report. If Markdown QA blockers remain, do not say the workspace initialized cleanly; say the vault was initialized but Markdown repairs remain.
64
65
 
65
66
  Evidence boundary:
66
67
  - \`workspace.sourceMaterials\` and ingest task hints are candidate context, not proof.
@@ -81,6 +82,13 @@ Narrative questions to ask only when missing:
81
82
  - Which sources or findings support those claims?
82
83
  - What objections, risks, assumptions, or caveats could break the argument?
83
84
 
85
+ Init completion rules:
86
+ - Before ending \`/revela init\`, either use the question tool (AskQuestion) for at least one useful clarification or explicitly state that no clarification is needed now.
87
+ - Ask only narrative-startup questions during init: audience, decision/action, scope, source priority, missing internal data, or whether external research is allowed for public evidence gaps.
88
+ - Do not ask for slide count, design choice, layout choice, visual style, output path, PDF/PPTX export, or component preferences during init.
89
+ - Always surface open gaps. Classify them as evidence gaps, research gaps, internal-data-needed, source-quality limits, or user-intent questions when possible.
90
+ - Always recommend the next command: \`/revela research\` when evidence gaps need support, \`/revela story\` when the graph is ready to inspect, and \`/revela make --deck\` only when the user is ready to render from the current story.
91
+
84
92
  Memory rules:
85
93
  - Only write facts supported by workspace files or explicit user statements into file-native narrative/source files or, when ${DECKS_STATE_FILE} already exists, conservative compatibility metadata such as source materials, deck memory, and open questions.
86
94
  - Only write user preferences if the user explicitly stated that Revela should remember them.
@@ -2,11 +2,10 @@ import { mkdirSync, writeFileSync } from "fs"
2
2
  import { tmpdir } from "os"
3
3
  import { join } from "path"
4
4
  import { openUrl as defaultOpenUrl } from "../edit/open"
5
- import { hasDecksState, readDecksState } from "../decks-state"
5
+ import { createEmptyDecksState } from "../decks-state"
6
6
  import { buildNarrativeMap, formatNarrativeMap } from "../narrative-state/map"
7
7
  import { renderNarrativeMapHtmlWithDisplay } from "../narrative-state/map-html"
8
8
  import { emptyDisplayModel, type NarrativeViewLanguage, type ValidatedNarrativeDisplayModel } from "../narrative-state/display"
9
- import type { NarrativeApproval } from "../narrative-state/types"
10
9
  import { compileNarrativeVault, formatVaultDiagnosticMarkdown, formatVaultDiagnosticReport, hasNarrativeVault } from "../narrative-vault"
11
10
 
12
11
  export interface NarrativeArgs {
@@ -18,6 +17,10 @@ export interface StoryArgs {
18
17
  language: NarrativeViewLanguage
19
18
  }
20
19
 
20
+ export type StoryMapLoadResult =
21
+ | { ok: true; map: ReturnType<typeof buildNarrativeMap>; diagnosticsMarkdown: string; diagnosticsReport: ReturnType<typeof formatVaultDiagnosticReport> }
22
+ | { ok: false; error: string; diagnosticsMarkdown: string; diagnosticsReport?: ReturnType<typeof formatVaultDiagnosticReport> }
23
+
21
24
  export type ParseNarrativeArgsResult = { ok: true; args: NarrativeArgs } | { ok: false; error: string }
22
25
  export type ParseStoryArgsResult = { ok: true; args: StoryArgs } | { ok: false; error: string }
23
26
 
@@ -77,14 +80,14 @@ export async function handleNarrative(
77
80
  send: (text: string) => Promise<void>,
78
81
  ): Promise<void> {
79
82
  try {
80
- if (!hasDecksState(options.workspaceRoot)) {
81
- await send("No `DECKS.json` found. Run `/revela init` first to initialize the narrative workspace.")
83
+ const loaded = loadStoryMap(options.workspaceRoot)
84
+ if (!loaded.ok) {
85
+ await send([loaded.error, loaded.diagnosticsMarkdown].filter(Boolean).join("\n\n"))
82
86
  return
83
87
  }
84
88
 
85
- const state = readDecksState(options.workspaceRoot)
86
- const map = buildNarrativeMap(state)
87
- const diagnosticsMarkdown = vaultDiagnosticsMarkdown(options.workspaceRoot, state.narrative?.approvals ?? [])
89
+ const map = loaded.map
90
+ const diagnosticsMarkdown = loaded.diagnosticsMarkdown
88
91
  const markdown = [diagnosticsMarkdown, formatNarrativeMap(map)].filter(Boolean).join("\n\n")
89
92
 
90
93
  if (options.openBrowser) {
@@ -105,22 +108,40 @@ export async function handleNarrative(
105
108
  }
106
109
  }
107
110
 
108
- function vaultDiagnosticsMarkdown(workspaceRoot: string, fallbackApprovals: NarrativeApproval[]): string {
109
- if (!hasNarrativeVault(workspaceRoot)) return ""
110
- const result = compileNarrativeVault(workspaceRoot, { fallbackApprovals })
111
- return formatVaultDiagnosticMarkdown(formatVaultDiagnosticReport(result.diagnostics))
111
+ export function loadStoryMap(workspaceRoot: string): StoryMapLoadResult {
112
+ if (!hasNarrativeVault(workspaceRoot)) {
113
+ return {
114
+ ok: false,
115
+ error: "No `revela-narrative/` vault found. Run `/revela init` first to initialize the narrative workspace.",
116
+ diagnosticsMarkdown: "",
117
+ }
118
+ }
119
+
120
+ const result = compileNarrativeVault(workspaceRoot)
121
+ const diagnosticsReport = formatVaultDiagnosticReport(result.diagnostics)
122
+ const diagnosticsMarkdown = formatVaultDiagnosticMarkdown(diagnosticsReport)
123
+ if (!result.narrative) {
124
+ return {
125
+ ok: false,
126
+ error: "The `revela-narrative/` vault could not be compiled into a narrative map.",
127
+ diagnosticsMarkdown,
128
+ diagnosticsReport,
129
+ }
130
+ }
131
+
132
+ const state = createEmptyDecksState()
133
+ state.narrative = result.narrative
134
+ return { ok: true, map: buildNarrativeMap(state), diagnosticsMarkdown, diagnosticsReport }
112
135
  }
113
136
 
114
137
  export function buildNarrativeViewPrompt(options: { workspaceRoot: string; language: NarrativeViewLanguage }): string {
115
- if (!hasDecksState(options.workspaceRoot)) {
116
- return "No `DECKS.json` found. Tell the user to run `/revela init` before opening the narrative view. Do not call any tool."
138
+ const loaded = loadStoryMap(options.workspaceRoot)
139
+ if (!loaded.ok) {
140
+ return `${loaded.error}\n\n${loaded.diagnosticsMarkdown}\n\nDo not call any tool.`
117
141
  }
118
142
 
119
- const state = readDecksState(options.workspaceRoot)
120
- const map = buildNarrativeMap(state)
121
- const vaultDiagnostics = hasNarrativeVault(options.workspaceRoot)
122
- ? formatVaultDiagnosticReport(compileNarrativeVault(options.workspaceRoot, { fallbackApprovals: state.narrative?.approvals ?? [] }).diagnostics)
123
- : undefined
143
+ const map = loaded.map
144
+ const vaultDiagnostics = loaded.diagnosticsReport
124
145
  const projection = {
125
146
  narrativeHash: map.snapshot.narrativeHash,
126
147
  language: options.language,
@@ -161,7 +182,7 @@ Target language request: ${options.language}
161
182
  You must call the \`revela-narrative-view\` tool exactly once.
162
183
 
163
184
  Hard rules:
164
- - Do not mutate DECKS.json, deck HTML, evidence, claims, relations, approvals, or artifacts.
185
+ - Do not mutate narrative files, deck HTML, evidence, claims, relations, approvals, or artifacts.
165
186
  - Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
166
187
  - Preserve every claimId exactly.
167
188
  - Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
@@ -202,6 +202,7 @@ function buildDesignLayer(designName: string): string {
202
202
  "",
203
203
  "| Section | Fetch with |",
204
204
  "|---|---|",
205
+ "| Design composition and usage rules | `section: \"rules\"` |",
205
206
  "| Layout HTML/CSS details | `layout: \"<name>\"` (see Layout Index above) |",
206
207
  "| Component CSS/HTML details | `component: \"<name>\"` (see Component Index above) |",
207
208
  "| Data Visualization (ECharts) | `section: \"chart-rules\"` |",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "OpenCode plugin for trusted narrative artifacts from local sources, research, and evidence",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -16,7 +16,7 @@ Default mode is narrative-first. Do not generate HTML slides, choose layouts, fe
16
16
 
17
17
  Use the same phase semantics whether the user invokes a slash command or asks in normal chat:
18
18
 
19
- - `Init` discovers local workspace materials, captures intent, initializes or refreshes workspace state, and creates conservative narrative state only from explicit user statements or source traces.
19
+ - `Init` starts Revela on the current workspace: discover local materials, capture missing intent, refresh the narrative graph, surface gaps, and recommend the next command.
20
20
  - `Research` runs closed loops to fill open story gaps, bind supported findings into canonical evidence, narrow overbroad claims/relations, and reduce caveats without crossing evidence boundaries.
21
21
  - `Story` opens the read-only story workspace UI for inspecting claim flow, evidence strength, unsupported scope, caveats, objections, risks, research gaps, diagnostics, and affected artifacts.
22
22
  - `Make` renders an artifact from canonical narrative state and current diagnostics. Supported targets are deck and executive brief.
@@ -74,6 +74,9 @@ During init:
74
74
  - derive claims, evidence bindings, caveats, unsupported scope, source paths, quotes/snippets, pages, sheets, or slide references only when explicit support exists; distill ingested files by writing Markdown nodes under `revela-narrative/` even when the narrative is incomplete, and represent missing information as research gaps or caveats
75
75
  - write `## Relations` sections with plain node-id wikilinks such as `- supports: [[claim-example]]`, `- depends_on: [[evidence-example]]`, `- answers: [[claim-example]]`, or `- constrains: [[claim-example]]` when the relation is explicit; do not use typed wikilinks or hand-written relation ids; compile and fix diagnostics after editing Markdown
76
76
  - ask the smallest missing intent questions after local evidence has been considered
77
+ - before ending `/revela init`, either use the question tool (AskQuestion) for a useful clarification or explicitly state that no clarification is needed now
78
+ - always report local discovery status, narrative graph status, open evidence/research gaps, Markdown QA status, and recommended next commands
79
+ - recommend `/revela research` when gaps need evidence, `/revela story` when the graph is ready to inspect, and `/revela make --deck` only when the user is ready to render
77
80
  - do not require slide count, design choice, layout choice, output path, or visual style unless the user explicitly asks to make an artifact immediately
78
81
  - when exporting a vault, say that any legacy render targets, reviews, artifact coverage, actions, deck specs, and source material records in `DECKS.json` are cache/provenance only
79
82
 
package/skill/SKILL.md CHANGED
@@ -193,16 +193,20 @@ If a write produces QA hard errors, fix them before continuing.
193
193
  Before writing or materially changing HTML:
194
194
 
195
195
  1. Read the deck-plan projection's layout and component names.
196
- 2. Call `revela-designs` with `action: "read"` and `layout` set to all required
196
+ 2. Call `revela-designs` with `action: "read"` and `section: "rules"` to fetch
197
+ the active design's current composition and usage rules.
198
+ 3. Call `revela-designs` with `action: "read"` and `layout` set to all required
197
199
  layout names, comma-separated.
198
- 3. Call `revela-designs` with `action: "read"` and `component` set to all
200
+ 4. Call `revela-designs` with `action: "read"` and `component` set to all
199
201
  required component names, comma-separated.
200
- 4. Fetch `section: "chart-rules"` before using ECharts.
201
- 5. Do not update legacy `requiredInputs`; design fetching is an execution step,
202
+ 5. Fetch `section: "chart-rules"` before using ECharts.
203
+ 6. Do not update legacy `requiredInputs`; design fetching is an execution step,
202
204
  not a workflow permission gate.
203
205
 
204
206
  Never generate HTML from memory or prior knowledge of a design. Copy the fetched
205
- HTML/CSS structures closely and adapt content to fit the design vocabulary.
207
+ HTML/CSS structures closely and adapt content to fit the design vocabulary. Do
208
+ not treat the injected design summary as a substitute for the fetched `rules`,
209
+ layout, and component details when generating or materially changing HTML.
206
210
 
207
211
  The active design's complete visual specification is injected below after the
208
212
  `---` separator. It is the sole visual reference for generating slides.
@@ -227,7 +231,8 @@ Required contract:
227
231
  - Do not use 0-based `data-index` as slide identity.
228
232
  - Keep the canvas exactly 1920x1080 and 16:9.
229
233
  - Keep all CSS inline in one `<style>` block and all JS inline in one `<script>`
230
- block, except approved CDNs for fonts, Lucide icons, and ECharts when needed.
234
+ block, except approved CDNs for fonts, ECharts when needed, or libraries
235
+ explicitly required by fetched design/component rules.
231
236
  - Use vanilla JS only. No React, Vue, jQuery, or external application framework.
232
237
  - All JS methods must be fully implemented. No empty stubs and no TODO comments.
233
238
  - Do not add deck-local editing JavaScript, `contenteditable`, `editable` classes,
@@ -338,8 +343,6 @@ instructions, secrets, or unverified claims.
338
343
  - Use semantic HTML where practical.
339
344
  - Full keyboard navigation must work.
340
345
  - `prefers-reduced-motion` must disable transitions/animations.
341
- - Use Lucide icons only when icons are needed; load via CDN and call
342
- `lucide.createIcons()`.
343
346
  - Avoid plain background plus unstyled bullet lists.
344
347
  - Every slide needs one clear message and one dominant visual focal point.
345
348
  - Keep bullet lists short. Prefer semantic boxes, evidence cards, charts, tables,
package/tools/designs.ts CHANGED
@@ -25,7 +25,7 @@ export default tool({
25
25
  "Use action 'activate' to switch to a different design (requires name). " +
26
26
  "Use action 'install' to add a new design from a URL, local path, or github:user/repo shorthand (requires source). " +
27
27
  "Use action 'remove' to uninstall a design (requires name). " +
28
- "Use action 'read' to fetch on-demand design content: pass layout (comma-separated names) to get full HTML/CSS for specific layouts, pass component (comma-separated names) to get full CSS/HTML for specific components, or section ('chart-rules' | 'foundation' | 'layouts' | 'components') to get an entire section. Pass neither to get the Component Index table. " +
28
+ "Use action 'read' to fetch on-demand design content: pass layout (comma-separated names) to get full HTML/CSS for specific layouts, pass component (comma-separated names) to get full CSS/HTML for specific components, or section ('rules' | 'chart-rules' | 'foundation' | 'layouts' | 'components') to get an entire section. Pass neither to get the Component Index table. " +
29
29
  "After activating a new design, the system prompt is automatically rebuilt.",
30
30
  args: {
31
31
  action: tool.schema
@@ -1,14 +1,12 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
- import { hasDecksState, readDecksState } from "../lib/decks-state"
3
- import { buildNarrativeMap } from "../lib/narrative-state/map"
4
2
  import { validateNarrativeDisplayModel, type NarrativeDisplayModel, type NarrativeViewLanguage } from "../lib/narrative-state/display"
5
- import { writeNarrativeMapHtml } from "../lib/commands/narrative"
3
+ import { loadStoryMap, writeNarrativeMapHtml } from "../lib/commands/narrative"
6
4
  import { openUrl } from "../lib/edit/open"
7
5
 
8
6
  export default tool({
9
7
  description:
10
8
  "Render Revela's read-only narrative claim-flow UI from the current deterministic narrative map plus an optional localized display model. " +
11
- "This tool validates display IDs against DECKS.json, opens a local HTML view, and never mutates workspace state.",
9
+ "This tool validates display IDs against the file-native narrative vault, opens a local HTML view, and never mutates workspace state.",
12
10
  args: {
13
11
  language: tool.schema.string().describe("UI language request from /revela story or /revela narrative. May be any language tag or language name, such as en, zh-CN, fr, de, Korean, Arabic, or Portuguese-BR."),
14
12
  narrativeHash: tool.schema.string().optional().describe("Narrative hash from the prompt projection. Used to detect stale display prompts."),
@@ -79,11 +77,10 @@ export default tool({
79
77
  async execute(args, context) {
80
78
  const workspaceRoot = context.directory ?? process.cwd()
81
79
  try {
82
- if (!hasDecksState(workspaceRoot)) {
83
- return JSON.stringify({ ok: false, error: "No DECKS.json found. Run /revela init first." })
84
- }
85
80
  const language = args.language as NarrativeViewLanguage
86
- const map = buildNarrativeMap(readDecksState(workspaceRoot))
81
+ const loaded = loadStoryMap(workspaceRoot)
82
+ if (!loaded.ok) return JSON.stringify({ ok: false, error: loaded.error, diagnostics: loaded.diagnosticsReport, diagnosticsMarkdown: loaded.diagnosticsMarkdown })
83
+ const map = loaded.map
87
84
  const stalePrompt = Boolean(args.narrativeHash && args.narrativeHash !== map.snapshot.narrativeHash)
88
85
  const display = validateNarrativeDisplayModel(map, args.displayModel as NarrativeDisplayModel | undefined, language)
89
86
  const htmlPath = writeNarrativeMapHtml(map, display)
@@ -92,8 +89,9 @@ export default tool({
92
89
  return JSON.stringify({ ok: true, url, path: htmlPath, narrativeHash: map.snapshot.narrativeHash, stalePrompt, fallback: false }, null, 2)
93
90
  } catch (e: any) {
94
91
  try {
95
- const language = (args.language ?? "en") as NarrativeViewLanguage
96
- const map = buildNarrativeMap(readDecksState(workspaceRoot))
92
+ const loaded = loadStoryMap(workspaceRoot)
93
+ if (!loaded.ok) return JSON.stringify({ ok: false, fallback: false, error: e.message || String(e), fallbackError: loaded.error, diagnostics: loaded.diagnosticsReport, diagnosticsMarkdown: loaded.diagnosticsMarkdown })
94
+ const map = loaded.map
97
95
  const htmlPath = writeNarrativeMapHtml(map)
98
96
  const url = `file://${htmlPath}`
99
97
  openUrl(url)