@cyber-dash-tech/revela 0.17.20 → 0.17.21
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 +25 -3
- package/README.zh-CN.md +25 -3
- package/designs/monet/DESIGN.md +58 -38
- package/lib/design/designs.ts +113 -2
- package/lib/domain/domains.ts +221 -1
- package/lib/runtime/index.ts +112 -1
- package/package.json +1 -1
- package/plugins/revela/.mcp.json +1 -1
- package/plugins/revela/hooks/revela_guard.ts +19 -0
- package/plugins/revela/hooks/revela_post_write_notice.ts +37 -6
- package/plugins/revela/mcp/revela-server.ts +86 -0
- package/plugins/revela/skills/revela-design/SKILL.md +4 -2
- package/plugins/revela/skills/revela-domain/SKILL.md +13 -1
- package/plugins/revela/skills/revela-upgrade/SKILL.md +33 -0
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ To install globally, add the same entry to `~/.config/opencode/opencode.json`.
|
|
|
34
34
|
Requirements:
|
|
35
35
|
|
|
36
36
|
- The Codex CLI must be installed and the `codex` command must be available in your shell.
|
|
37
|
-
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.17.
|
|
37
|
+
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.17.21 mcp` to start the MCP server.
|
|
38
38
|
- For interactive Review actions, `codex exec` must also work because the Review UI uses it for Insight and Comment/Apply Fix requests.
|
|
39
39
|
|
|
40
40
|
Optional preflight:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
Install Revela through the Codex Git marketplace:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.17.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.17.21
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.17.
|
|
62
|
+
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.17.21 mcp` so npm can fetch the published package and its dependencies.
|
|
63
63
|
|
|
64
64
|
You do not need to run `bun install` inside the Codex marketplace clone.
|
|
65
65
|
|
|
@@ -67,6 +67,28 @@ Start a new Codex thread after installing so Codex loads the Revela skills, MCP
|
|
|
67
67
|
|
|
68
68
|
For release-aligned local validation, run `bun run smoke:mcp-pack`. It packs the current checkout to a temporary npm tarball and starts the MCP server through `npx`, matching the published Codex launcher path without requiring a registry publish.
|
|
69
69
|
|
|
70
|
+
#### Codex Upgrade
|
|
71
|
+
|
|
72
|
+
In Codex, ask Revela to check the current runtime version; the plugin calls `revela_doctor` and reports the running `version`.
|
|
73
|
+
|
|
74
|
+
For a fixed release tag, reinstall the plugin from that tag:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
codex plugin remove revela@revela
|
|
78
|
+
codex plugin marketplace remove revela
|
|
79
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref vX.Y.Z
|
|
80
|
+
codex plugin add revela@revela
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a marketplace entry that intentionally tracks a branch or movable ref, upgrade the marketplace clone and re-add the plugin:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
codex plugin marketplace upgrade revela
|
|
87
|
+
codex plugin add revela@revela
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The Git marketplace ref and `.mcp.json` npm pin are part of the same release artifact. Start a new Codex thread after upgrading so Codex reloads the Revela skills, MCP tools, hooks, and runtime pin.
|
|
91
|
+
|
|
70
92
|
## Built-In Designs
|
|
71
93
|
|
|
72
94
|
Revela includes built-in deck designs:
|
package/README.zh-CN.md
CHANGED
|
@@ -34,7 +34,7 @@ Revela 可在 [OpenCode](https://opencode.ai) 和 Codex 中使用,把来源材
|
|
|
34
34
|
环境要求:
|
|
35
35
|
|
|
36
36
|
- 需要已安装 Codex CLI,并且 shell 中可以执行 `codex`。
|
|
37
|
-
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.17.
|
|
37
|
+
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.17.21 mcp` 启动 MCP server。
|
|
38
38
|
- 如果使用 Review UI 的 Insight、Comment 或 Apply Fix,需要 `codex exec` 可用。
|
|
39
39
|
|
|
40
40
|
可选的安装前检查:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
通过 Codex Git marketplace 安装 Revela:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.17.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.17.21
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.17.
|
|
62
|
+
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.17.21 mcp`,由 npm 获取已发布 package 及其 dependencies。
|
|
63
63
|
|
|
64
64
|
不需要在 Codex marketplace clone 里运行 `bun install`。
|
|
65
65
|
|
|
@@ -67,6 +67,28 @@ Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。
|
|
|
67
67
|
|
|
68
68
|
如果要按发布路径做本地验证,运行 `bun run smoke:mcp-pack`。它会把当前 checkout 打成临时 npm tarball,再通过 `npx` 启动 MCP server,不需要先发布到 registry。
|
|
69
69
|
|
|
70
|
+
#### Codex 升级
|
|
71
|
+
|
|
72
|
+
在 Codex 中,可以让 Revela 检查当前 runtime version;plugin 会调用 `revela_doctor` 并报告正在运行的 `version`。
|
|
73
|
+
|
|
74
|
+
如果要固定到某个 release tag,按该 tag 重新安装 plugin:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
codex plugin remove revela@revela
|
|
78
|
+
codex plugin marketplace remove revela
|
|
79
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref vX.Y.Z
|
|
80
|
+
codex plugin add revela@revela
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
如果 marketplace entry 本来就有意跟踪 branch 或 movable ref,升级 marketplace clone 后重新添加 plugin:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
codex plugin marketplace upgrade revela
|
|
87
|
+
codex plugin add revela@revela
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Git marketplace ref 和 `.mcp.json` npm pin 属于同一个 release artifact。升级后开启一个新的 Codex thread,让 Codex 重新加载 Revela skills、MCP tools、hooks 和 runtime pin。
|
|
91
|
+
|
|
70
92
|
## 内置设计
|
|
71
93
|
|
|
72
94
|
Revela 内置多个 deck design:
|
package/designs/monet/DESIGN.md
CHANGED
|
@@ -480,6 +480,8 @@ 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
|
+
- **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 slide-level 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.
|
|
484
|
+
- **Do not hide the page title inside a component.** Body components may have their own headings, but `text-panel`, `box`, `toc`, chart/table headers, and editorial module headings do not replace the slide-level title block.
|
|
483
485
|
- **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`.
|
|
484
486
|
- **Chart system is ECharts.** Data charts default to ECharts inside `echart-panel`. Do not use hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts as data-chart substitutes. SVG remains acceptable for decorative motifs, diagrams, or illustrations, not data charts. Before creating or changing a chart, fetch the `echart-panel` component and `section: "chart-rules"`; if chart rules or runtime are unavailable, report the gap instead of inventing a fake chart fallback.
|
|
485
487
|
- **Start from foundation.** New deck HTML starts from `@design:foundation`. Do not recreate foundation CSS, JavaScript, or the HTML skeleton from memory. Prefer a foundation helper when available; otherwise fetch `section: "foundation"` before writing a new deck shell. Existing deck edits preserve the current foundation unless the user asks for foundation repair or QA reports a foundation contract problem.
|
|
@@ -511,12 +513,14 @@ These rules are mandatory for Monet.
|
|
|
511
513
|
|
|
512
514
|
### Layout Types
|
|
513
515
|
|
|
514
|
-
Each `<section class="slide">` must set `slide-qa="true"` or `slide-qa="false"`. It must also set `data-slide-index="N"`, where `N` is the canonical positive 1-based artifact slide identity from the approved deck plan or DOM order. Indexes must be unique and strictly increase. Use the QA column to decide which `slide-qa` value to write. Fetch any layout with the `revela-designs` tool (`action: "read"`, `layout: "<name>"`).
|
|
516
|
+
Each `<section class="slide">` must set `slide-qa="true"` or `slide-qa="false"`. It must also set `data-slide-index="N"`, where `N` is the canonical positive 1-based artifact slide identity from the approved deck plan or DOM order. Indexes must be unique and strictly increase. Use the QA column to decide which `slide-qa` value to write. Choose layouts by narrative structure first, not by surface geometry. Fetch any layout with the `revela-designs` tool (`action: "read"`, `layout: "<name>"`).
|
|
517
|
+
|
|
518
|
+
Normal `qa=true` content layouts must start with a slide-level title block unless the layout marker explicitly says otherwise. Use an eyebrow for chapter/section context, then an `h2` that states the slide's claim or takeaway. Body boxes, charts, media, tables, and text panels should sit below or beside that title region rather than replacing it.
|
|
515
519
|
|
|
516
520
|
<!-- @layout:fullbleed:start qa=false -->
|
|
517
|
-
|
|
521
|
+
Atmospheric cover, closing, or divider layout with one dominant image-title component.
|
|
518
522
|
|
|
519
|
-
|
|
523
|
+
#### Fullbleed
|
|
520
524
|
|
|
521
525
|
Structural intent:
|
|
522
526
|
- Single slot: place one `image-title` component directly inside `.page`. The component is self-contained — it manages its own image, blur, overlay, and text layers internally.
|
|
@@ -542,22 +546,26 @@ Structural intent:
|
|
|
542
546
|
<!-- @layout:fullbleed:end -->
|
|
543
547
|
|
|
544
548
|
<!-- @layout:narrative:start qa=true -->
|
|
545
|
-
|
|
549
|
+
Asymmetric primary-secondary spread with a dominant visual/evidence zone on the left and a narrower explanatory or action zone on the right.
|
|
546
550
|
|
|
547
|
-
|
|
551
|
+
#### Narrative
|
|
548
552
|
|
|
549
553
|
Structural intent:
|
|
550
|
-
- left slot:
|
|
551
|
-
- right slot:
|
|
554
|
+
- left slot: primary zone (1.618fr) — dominant evidence, image, chart, table, or other high-weight component(s)
|
|
555
|
+
- right slot: secondary zone (1fr) — explanation, implication, action, TOC, vertical flow, or supporting component(s)
|
|
552
556
|
|
|
553
|
-
Every slot accepts 1 or more components
|
|
557
|
+
Every slot accepts 1 or more components, but the layout should preserve a primary-secondary hierarchy. Do not use `narrative` for two equally weighted items; use `halves` instead.
|
|
554
558
|
|
|
555
559
|
|
|
556
560
|
```html
|
|
557
561
|
<section class="slide" slide-qa="true" data-slide-index="N">
|
|
558
562
|
<div class="slide-canvas">
|
|
559
563
|
<div class="page" style="padding:0;overflow:hidden;">
|
|
560
|
-
<div
|
|
564
|
+
<div style="display:flex;flex-direction:column;gap:10px;margin:56px 64px 28px;max-width:760px;position:relative;z-index:2;">
|
|
565
|
+
<p class="eyebrow">Chapter / Section</p>
|
|
566
|
+
<h2>Slide claim or takeaway</h2>
|
|
567
|
+
</div>
|
|
568
|
+
<div class="narrative-grid" style="height:calc(100% - 150px);">
|
|
561
569
|
|
|
562
570
|
<!-- [slot: left] — 1+ components; suggested: image-title, echart-panel, text-panel -->
|
|
563
571
|
<div>
|
|
@@ -596,28 +604,32 @@ Every slot accepts 1 or more components. The LLM decides what each slot contains
|
|
|
596
604
|
|
|
597
605
|
##### Tips
|
|
598
606
|
- **Grid container uses `.narrative-grid` class.** Applies `minmax(0, Nfr)` tracks, `overflow:hidden` on all children, and `align-items:stretch` so both columns fill the full row height. Do not override with `align-items:start` — that collapses columns to content height and exposes the page background.
|
|
599
|
-
- **
|
|
607
|
+
- **Primary-secondary intent.** The wider left column should carry the stronger visual or evidentiary weight. If both sides are equally important, use `halves`.
|
|
600
608
|
- **Dark panel variant.** When a slot uses a dark background, override CSS variables on that container: `--text-primary`, `--text-secondary`, `--text-muted`, `--line`, `--line-strong` — all set to white-family values. Use `.page-number--light`.
|
|
601
609
|
- **Background image inside a slot.** Use the three-layer z-index pattern: background `z-index:0`, dark overlay `z-index:1`, content `z-index:2`.
|
|
602
610
|
<!-- @layout:narrative:end -->
|
|
603
611
|
|
|
604
612
|
<!-- @layout:narrative-reverse:start qa=true -->
|
|
605
|
-
|
|
613
|
+
Asymmetric primary-secondary spread with the narrower explanatory or action zone on the left and the dominant visual/evidence zone on the right.
|
|
606
614
|
|
|
607
|
-
|
|
615
|
+
#### Narrative Reverse
|
|
608
616
|
|
|
609
617
|
Structural intent:
|
|
610
|
-
- left slot:
|
|
611
|
-
- right slot:
|
|
618
|
+
- left slot: secondary zone (1fr) — explanation, implication, action, TOC, vertical flow, or supporting component(s)
|
|
619
|
+
- right slot: primary zone (1.618fr) — dominant evidence, image, chart, table, or other high-weight component(s)
|
|
612
620
|
|
|
613
|
-
Every slot accepts 1 or more components
|
|
621
|
+
Every slot accepts 1 or more components, but the layout should preserve a primary-secondary hierarchy. Do not use `narrative-reverse` for two equally weighted items; use `halves` instead.
|
|
614
622
|
|
|
615
623
|
|
|
616
624
|
```html
|
|
617
625
|
<section class="slide" slide-qa="true" data-slide-index="N">
|
|
618
626
|
<div class="slide-canvas">
|
|
619
627
|
<div class="page" style="padding:0;overflow:hidden;">
|
|
620
|
-
<div
|
|
628
|
+
<div style="display:flex;flex-direction:column;gap:10px;margin:56px 64px 28px;max-width:760px;position:relative;z-index:2;">
|
|
629
|
+
<p class="eyebrow">Chapter / Section</p>
|
|
630
|
+
<h2>Slide claim or takeaway</h2>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="narrative-grid narrative-grid--reverse" style="height:calc(100% - 150px);">
|
|
621
633
|
|
|
622
634
|
<!-- [slot: left] — 1+ components; suggested: text-panel, toc, flow-vertical, echart-panel -->
|
|
623
635
|
<div>
|
|
@@ -635,33 +647,33 @@ Every slot accepts 1 or more components. The LLM decides what each slot contains
|
|
|
635
647
|
|
|
636
648
|
##### Tips
|
|
637
649
|
- **Same `.narrative-grid` class as `narrative`, with `--reverse` modifier.** Add both `narrative-grid` and `narrative-grid--reverse` to the grid container. The modifier swaps column proportions to `1fr left / 1.618fr right`.
|
|
638
|
-
- **
|
|
650
|
+
- **Primary-secondary intent.** The wider right column should carry the stronger visual or evidentiary weight. If both sides are equally important, use `halves`.
|
|
639
651
|
- **Dark panel variant.** Same CSS variable override pattern as `narrative`: set `--text-primary` etc. to white-family values on the panel container, all child components inherit automatically.
|
|
640
652
|
<!-- @layout:narrative-reverse:end -->
|
|
641
653
|
|
|
642
654
|
<!-- @layout:highlight-cols:start qa=true -->
|
|
643
|
-
|
|
655
|
+
Parallel 3-5 item spread for comparable proof points, options, features, metrics, or evidence blocks.
|
|
644
656
|
|
|
645
|
-
|
|
657
|
+
#### Highlight Cols
|
|
646
658
|
|
|
647
|
-
A short section header is
|
|
659
|
+
A short section header is required for normal content uses. In Monet, that header should stay lean: eyebrow plus title only, with no intro paragraph competing with the columns below.
|
|
648
660
|
|
|
649
661
|
Structural intent:
|
|
650
662
|
- each slot: 1fr column — any component(s)
|
|
651
663
|
- column count: determined by the number of direct child divs in the grid container; `auto-fit` distributes space equally
|
|
652
664
|
|
|
653
|
-
Every slot accepts 1 or more components. Add or remove child divs to control column count — 3 is the default, but 4 or 5 columns work equally well.
|
|
665
|
+
Every slot accepts 1 or more components. Add or remove child divs to control column count — 3 is the default, but 4 or 5 columns work equally well. Use only for comparable items; do not mix unrelated content types just because the slide needs multiple blocks.
|
|
654
666
|
|
|
655
667
|
```html
|
|
656
668
|
<section class="slide" slide-qa="true" data-slide-index="N">
|
|
657
669
|
<div class="slide-canvas">
|
|
658
|
-
<div class="page">
|
|
659
|
-
<div style="display:flex;flex-direction:column;gap:10px;margin
|
|
670
|
+
<div class="page" style="overflow:hidden;">
|
|
671
|
+
<div style="display:flex;flex-direction:column;gap:10px;margin:56px 64px 28px;max-width:760px;position:relative;z-index:2;">
|
|
660
672
|
<p class="eyebrow">Section Label</p>
|
|
661
|
-
<h2 style="font-size:52px;line-height:0.94;text-transform:uppercase;">
|
|
673
|
+
<h2 style="font-size:52px;line-height:0.94;text-transform:uppercase;">Slide claim or takeaway</h2>
|
|
662
674
|
</div>
|
|
663
675
|
|
|
664
|
-
<div class="highlight-cols-grid" style="
|
|
676
|
+
<div class="highlight-cols-grid" style="height:calc(100% - 150px);">
|
|
665
677
|
|
|
666
678
|
<!-- [slot: 1] — 1+ components; suggested: editorial-image-top, editorial-text-top, echart-panel -->
|
|
667
679
|
<div>
|
|
@@ -700,28 +712,32 @@ Every slot accepts 1 or more components. Add or remove child divs to control col
|
|
|
700
712
|
- **Grid container needs `flex:1;min-height:0` inline** when inside `.page` (which is flex-column). The class handles column sizing; the inline style handles row stretch.
|
|
701
713
|
- **Header stays lean.** If you add a section header above the grid, use only `eyebrow + title`. Do not add an intro paragraph; the columns themselves should carry the explanation.
|
|
702
714
|
- **Column count = number of direct child divs.** `repeat(auto-fit, minmax(0, 1fr))` distributes available width equally across however many children exist. Add a 4th or 5th div to get 4 or 5 columns — no CSS change needed.
|
|
703
|
-
- **
|
|
715
|
+
- **Parallel items only.** All slots carry the same visual weight and should represent the same kind of thing: proof points, options, features, metrics, or evidence blocks.
|
|
704
716
|
- **When using 4-5 columns, compress the header.** Keep the title to one or two short lines so the grid retains most of the slide height.
|
|
705
717
|
- **Do not set fixed heights on editorial components.** Let components fill height via flexbox stretch.
|
|
706
718
|
<!-- @layout:highlight-cols:end -->
|
|
707
719
|
|
|
708
720
|
<!-- @layout:halves:start qa=true -->
|
|
709
|
-
|
|
721
|
+
Balanced two-up comparison for two equally important items, charts, cases, evidence blocks, or before/after states.
|
|
710
722
|
|
|
711
|
-
|
|
723
|
+
#### Halves
|
|
712
724
|
|
|
713
725
|
Structural intent:
|
|
714
726
|
- left slot: 1fr column — any component(s)
|
|
715
727
|
- right slot: 1fr column — any component(s)
|
|
716
728
|
|
|
717
|
-
Every slot accepts 1 or more components.
|
|
729
|
+
Every slot accepts 1 or more components. Both columns are fully equal by design. Do not use `halves` when one side should dominate; use `narrative` or `narrative-reverse` instead.
|
|
718
730
|
|
|
719
731
|
|
|
720
732
|
```html
|
|
721
733
|
<section class="slide" slide-qa="true" data-slide-index="N">
|
|
722
734
|
<div class="slide-canvas">
|
|
723
735
|
<div class="page" style="overflow:hidden;">
|
|
724
|
-
<div
|
|
736
|
+
<div style="display:flex;flex-direction:column;gap:10px;margin:56px 64px 28px;max-width:760px;position:relative;z-index:2;">
|
|
737
|
+
<p class="eyebrow">Chapter / Section</p>
|
|
738
|
+
<h2>Slide claim or takeaway</h2>
|
|
739
|
+
</div>
|
|
740
|
+
<div class="halves-grid" style="height:calc(100% - 150px);">
|
|
725
741
|
|
|
726
742
|
<!-- [slot: left] — 1+ components; suggested: echart-panel, data-table, editorial-image-top -->
|
|
727
743
|
<div>
|
|
@@ -755,27 +771,31 @@ Every slot accepts 1 or more components. The LLM decides what each slot contains
|
|
|
755
771
|
|
|
756
772
|
##### Tips
|
|
757
773
|
- **Grid container needs `flex:1;min-height:0` inline** when inside `.page`. The class handles column sizing.
|
|
758
|
-
- **Equal columns — no hierarchy.** Both slots carry the same weight.
|
|
774
|
+
- **Equal columns — no hierarchy.** Both slots carry the same weight. Use this for balanced comparison, not primary-secondary explanation.
|
|
759
775
|
- **Gap `40px` is intentional.** The slightly wider gap than `three-col` (32px) compensates for the larger individual column width.
|
|
760
776
|
<!-- @layout:halves:end -->
|
|
761
777
|
|
|
762
778
|
<!-- @layout:stacked:start qa=true -->
|
|
763
|
-
|
|
779
|
+
Top-bottom synthesis layout with compact framing, status, or process context above a larger evidence, table, chart, or detail zone.
|
|
764
780
|
|
|
765
|
-
|
|
781
|
+
#### Stacked
|
|
766
782
|
|
|
767
783
|
Structural intent:
|
|
768
|
-
- top slot: `1fr` height —
|
|
769
|
-
- bottom slot: `1.618fr` height — larger
|
|
784
|
+
- top slot: `1fr` height — compact framing, status row, process overview, or synthesis component
|
|
785
|
+
- bottom slot: `1.618fr` height — larger evidence, table, chart, media, or detail component
|
|
770
786
|
|
|
771
|
-
Every slot accepts 1 or more components
|
|
787
|
+
Every slot accepts 1 or more components, but the layout should preserve a synthesis-over-detail relationship. Do not use `stacked` as a generic vertical layout for unrelated blocks.
|
|
772
788
|
|
|
773
789
|
|
|
774
790
|
```html
|
|
775
791
|
<section class="slide" slide-qa="true" data-slide-index="N">
|
|
776
792
|
<div class="slide-canvas">
|
|
777
793
|
<div class="page" style="padding:0;">
|
|
778
|
-
<div
|
|
794
|
+
<div style="display:flex;flex-direction:column;gap:10px;margin:56px 64px 28px;max-width:760px;position:relative;z-index:2;">
|
|
795
|
+
<p class="eyebrow">Chapter / Section</p>
|
|
796
|
+
<h2>Slide claim or takeaway</h2>
|
|
797
|
+
</div>
|
|
798
|
+
<div class="stacked-grid" style="height:calc(100% - 150px);">
|
|
779
799
|
|
|
780
800
|
<!-- [slot: top] — 1+ components; suggested: flow-horizontal, stat-row -->
|
|
781
801
|
<div class="stacked-top">
|
|
@@ -814,7 +834,7 @@ Every slot accepts 1 or more components. The LLM decides what each slot contains
|
|
|
814
834
|
##### Tips
|
|
815
835
|
- **Top and bottom rows follow a fixed 1 : 1.618 golden-ratio proportion.** The top slot takes 1fr and the bottom takes 1.618fr — both rows are sized relative to the total canvas height, not by their content.
|
|
816
836
|
- **Both slots clip overflow.** `min-height: 0` on both `.stacked-top` and `.stacked-bottom` ensures content cannot break out of its row.
|
|
817
|
-
- **
|
|
837
|
+
- **Synthesis over detail.** The top row should frame or summarize; the bottom row should carry the larger supporting detail.
|
|
818
838
|
- **Dark background variant.** Set CSS variable overrides (`--text-primary` etc.) on `.stacked-grid` to cascade into both slots automatically.
|
|
819
839
|
<!-- @layout:stacked:end -->
|
|
820
840
|
|
package/lib/design/designs.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
statSync,
|
|
19
19
|
writeFileSync,
|
|
20
20
|
} from "fs"
|
|
21
|
-
import { join, resolve, basename } from "path"
|
|
21
|
+
import { dirname, join, resolve, basename } from "path"
|
|
22
22
|
import { tmpdir } from "os"
|
|
23
23
|
import { parseFrontmatter } from "../frontmatter"
|
|
24
24
|
import {
|
|
@@ -55,6 +55,10 @@ export interface CreateDesignPackageArgs {
|
|
|
55
55
|
overwrite?: boolean
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export interface CreateDesignDraftArgs extends CreateDesignPackageArgs {
|
|
59
|
+
workspaceRoot: string
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
export interface CreateDesignPackageResult {
|
|
59
63
|
ok: true
|
|
60
64
|
name: string
|
|
@@ -64,6 +68,16 @@ export interface CreateDesignPackageResult {
|
|
|
64
68
|
overwritten: boolean
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
export interface InstallDesignDraftArgs {
|
|
72
|
+
workspaceRoot: string
|
|
73
|
+
name: string
|
|
74
|
+
overwrite?: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface InstallDesignDraftResult extends CreateDesignPackageResult {
|
|
78
|
+
sourcePath: string
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
export interface ValidateDesignPackageResult {
|
|
68
82
|
ok: boolean
|
|
69
83
|
name: string
|
|
@@ -250,6 +264,90 @@ export function createDesignPackage(args: CreateDesignPackageArgs): CreateDesign
|
|
|
250
264
|
}
|
|
251
265
|
}
|
|
252
266
|
|
|
267
|
+
/** Create a project-local design draft under .revela/drafts/designs/<name>/. */
|
|
268
|
+
export function createDesignDraftPackage(args: CreateDesignDraftArgs): CreateDesignPackageResult {
|
|
269
|
+
const name = normalizeDesignName(args.name)
|
|
270
|
+
const designMd = args.designMd?.trim()
|
|
271
|
+
const previewHtml = args.previewHtml?.trim()
|
|
272
|
+
|
|
273
|
+
if (!designMd) throw new Error("designMd is required")
|
|
274
|
+
if (!previewHtml) throw new Error("previewHtml is required")
|
|
275
|
+
|
|
276
|
+
const target = designDraftDir(args.workspaceRoot, name)
|
|
277
|
+
const existed = existsSync(target)
|
|
278
|
+
if (existed && !args.overwrite) {
|
|
279
|
+
throw new Error(`Design draft '${name}' already exists. Pass overwrite=true to replace it.`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
283
|
+
if (existed) {
|
|
284
|
+
rmSync(target, { recursive: true, force: true })
|
|
285
|
+
}
|
|
286
|
+
mkdirSync(target, { recursive: true })
|
|
287
|
+
writeFileSync(join(target, "DESIGN.md"), `${designMd}\n`, "utf-8")
|
|
288
|
+
writeFileSync(join(target, "preview.html"), `${previewHtml}\n`, "utf-8")
|
|
289
|
+
|
|
290
|
+
const validation = validateDesignDraftPackage(args.workspaceRoot, name)
|
|
291
|
+
if (!validation.ok) {
|
|
292
|
+
throw new Error(`Created design draft is invalid: ${validation.errors.join("; ")}`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
name,
|
|
298
|
+
path: target,
|
|
299
|
+
files: ["DESIGN.md", "preview.html"],
|
|
300
|
+
base: args.base,
|
|
301
|
+
overwritten: existed,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Validate a project-local design draft. */
|
|
306
|
+
export function validateDesignDraftPackage(workspaceRoot: string, nameInput: string): ValidateDesignPackageResult {
|
|
307
|
+
let name = nameInput
|
|
308
|
+
try {
|
|
309
|
+
name = normalizeDesignName(nameInput)
|
|
310
|
+
} catch {
|
|
311
|
+
// validateDesignPackageAt records the invalid-name error.
|
|
312
|
+
}
|
|
313
|
+
return validateDesignPackageAt(nameInput, designDraftDir(workspaceRoot, name))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Install a validated project-local design draft into the user-level design registry. */
|
|
317
|
+
export function installDesignDraftPackage(args: InstallDesignDraftArgs): InstallDesignDraftResult {
|
|
318
|
+
const name = normalizeDesignName(args.name)
|
|
319
|
+
const sourcePath = designDraftDir(args.workspaceRoot, name)
|
|
320
|
+
const validation = validateDesignDraftPackage(args.workspaceRoot, name)
|
|
321
|
+
if (!validation.ok) {
|
|
322
|
+
throw new Error(`Design draft is invalid: ${validation.errors.join("; ")}`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const target = join(DESIGNS_DIR, name)
|
|
326
|
+
const existed = existsSync(target)
|
|
327
|
+
if (existed && !args.overwrite) {
|
|
328
|
+
throw new Error(`Design '${name}' already exists. Pass overwrite=true to replace it.`)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
mkdirSync(DESIGNS_DIR, { recursive: true })
|
|
333
|
+
if (existed) {
|
|
334
|
+
rmSync(target, { recursive: true, force: true })
|
|
335
|
+
}
|
|
336
|
+
cpSync(sourcePath, target, { recursive: true })
|
|
337
|
+
} catch (e) {
|
|
338
|
+
throw new Error(`Installing design draft requires write access to Revela user config at ${DESIGNS_DIR}: ${e instanceof Error ? e.message : String(e)}`)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
ok: true,
|
|
343
|
+
name,
|
|
344
|
+
path: target,
|
|
345
|
+
sourcePath,
|
|
346
|
+
files: ["DESIGN.md", "preview.html"],
|
|
347
|
+
overwritten: existed,
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
253
351
|
function hasDataAttribute(html: string, attr: string, value: string): boolean {
|
|
254
352
|
const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
255
353
|
return new RegExp(`${attr}\\s*=\\s*(["'])${escaped}\\1`).test(html)
|
|
@@ -289,6 +387,16 @@ function hasFixedSizeCssRule(html: string, className: "slide-canvas"): boolean {
|
|
|
289
387
|
|
|
290
388
|
/** Validate a local design package for the minimum Revela design contract. */
|
|
291
389
|
export function validateDesignPackage(nameInput: string): ValidateDesignPackageResult {
|
|
390
|
+
let name = nameInput
|
|
391
|
+
try {
|
|
392
|
+
name = normalizeDesignName(nameInput)
|
|
393
|
+
} catch {
|
|
394
|
+
// validateDesignPackageAt records the invalid-name error.
|
|
395
|
+
}
|
|
396
|
+
return validateDesignPackageAt(nameInput, join(DESIGNS_DIR, name))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function validateDesignPackageAt(nameInput: string, dir: string): ValidateDesignPackageResult {
|
|
292
400
|
let name = nameInput
|
|
293
401
|
const errors: string[] = []
|
|
294
402
|
try {
|
|
@@ -297,7 +405,6 @@ export function validateDesignPackage(nameInput: string): ValidateDesignPackageR
|
|
|
297
405
|
errors.push(e instanceof Error ? e.message : String(e))
|
|
298
406
|
}
|
|
299
407
|
|
|
300
|
-
const dir = join(DESIGNS_DIR, name)
|
|
301
408
|
const mdPath = join(dir, "DESIGN.md")
|
|
302
409
|
const previewPath = join(dir, "preview.html")
|
|
303
410
|
const hasDesignMd = existsSync(mdPath)
|
|
@@ -360,6 +467,10 @@ export function validateDesignPackage(nameInput: string): ValidateDesignPackageR
|
|
|
360
467
|
}
|
|
361
468
|
}
|
|
362
469
|
|
|
470
|
+
function designDraftDir(workspaceRoot: string, name: string): string {
|
|
471
|
+
return resolve(workspaceRoot, ".revela", "drafts", "designs", name)
|
|
472
|
+
}
|
|
473
|
+
|
|
363
474
|
// ---------------------------------------------------------------------------
|
|
364
475
|
// Marker-based section / component parsing
|
|
365
476
|
// ---------------------------------------------------------------------------
|
package/lib/domain/domains.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
statSync,
|
|
23
23
|
writeFileSync,
|
|
24
24
|
} from "fs"
|
|
25
|
-
import { join, resolve, basename } from "path"
|
|
25
|
+
import { dirname, join, resolve, basename } from "path"
|
|
26
26
|
import { tmpdir } from "os"
|
|
27
27
|
import { parseFrontmatter } from "../frontmatter"
|
|
28
28
|
import {
|
|
@@ -49,6 +49,44 @@ export interface DomainInfo {
|
|
|
49
49
|
skillText: string
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface CreateDomainPackageArgs {
|
|
53
|
+
name: string
|
|
54
|
+
domainMd: string
|
|
55
|
+
overwrite?: boolean
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CreateDomainDraftArgs extends CreateDomainPackageArgs {
|
|
59
|
+
workspaceRoot: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CreateDomainPackageResult {
|
|
63
|
+
ok: true
|
|
64
|
+
name: string
|
|
65
|
+
path: string
|
|
66
|
+
files: string[]
|
|
67
|
+
overwritten: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface InstallDomainDraftArgs {
|
|
71
|
+
workspaceRoot: string
|
|
72
|
+
name: string
|
|
73
|
+
overwrite?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface InstallDomainDraftResult extends CreateDomainPackageResult {
|
|
77
|
+
sourcePath: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ValidateDomainPackageResult {
|
|
81
|
+
ok: boolean
|
|
82
|
+
name: string
|
|
83
|
+
path: string
|
|
84
|
+
hasIndustryMd: boolean
|
|
85
|
+
hasRequiredFrontmatter: boolean
|
|
86
|
+
hasBody: boolean
|
|
87
|
+
errors: string[]
|
|
88
|
+
}
|
|
89
|
+
|
|
52
90
|
// ---------------------------------------------------------------------------
|
|
53
91
|
// Seed
|
|
54
92
|
// ---------------------------------------------------------------------------
|
|
@@ -148,6 +186,188 @@ export function getDomainSkillMd(name?: string): string {
|
|
|
148
186
|
return info.skillText
|
|
149
187
|
}
|
|
150
188
|
|
|
189
|
+
/** Normalize and validate a domain package name. */
|
|
190
|
+
export function normalizeDomainName(name: string): string {
|
|
191
|
+
const normalized = name.trim().toLowerCase()
|
|
192
|
+
if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(normalized)) {
|
|
193
|
+
throw new Error("Domain name must be kebab-case using lowercase letters, numbers, and hyphens")
|
|
194
|
+
}
|
|
195
|
+
return normalized
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Create a local domain package in ~/.config/revela/domains/<name>/. */
|
|
199
|
+
export function createDomainPackage(args: CreateDomainPackageArgs): CreateDomainPackageResult {
|
|
200
|
+
const name = normalizeDomainName(args.name)
|
|
201
|
+
const domainMd = args.domainMd?.trim()
|
|
202
|
+
|
|
203
|
+
if (!domainMd) throw new Error("domainMd is required")
|
|
204
|
+
|
|
205
|
+
const target = join(DOMAINS_DIR, name)
|
|
206
|
+
const existed = existsSync(target)
|
|
207
|
+
if (existed && !args.overwrite) {
|
|
208
|
+
throw new Error(`Domain '${name}' already exists. Pass overwrite=true to replace it.`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
mkdirSync(DOMAINS_DIR, { recursive: true })
|
|
212
|
+
if (existed) {
|
|
213
|
+
rmSync(target, { recursive: true, force: true })
|
|
214
|
+
}
|
|
215
|
+
mkdirSync(target, { recursive: true })
|
|
216
|
+
writeFileSync(join(target, DOMAIN_FILE), `${domainMd}\n`, "utf-8")
|
|
217
|
+
|
|
218
|
+
const validation = validateDomainPackage(name)
|
|
219
|
+
if (!validation.ok) {
|
|
220
|
+
throw new Error(`Created domain package is invalid: ${validation.errors.join("; ")}`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
ok: true,
|
|
225
|
+
name,
|
|
226
|
+
path: target,
|
|
227
|
+
files: [DOMAIN_FILE],
|
|
228
|
+
overwritten: existed,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Create a project-local domain draft under .revela/drafts/domains/<name>/. */
|
|
233
|
+
export function createDomainDraftPackage(args: CreateDomainDraftArgs): CreateDomainPackageResult {
|
|
234
|
+
const name = normalizeDomainName(args.name)
|
|
235
|
+
const domainMd = args.domainMd?.trim()
|
|
236
|
+
|
|
237
|
+
if (!domainMd) throw new Error("domainMd is required")
|
|
238
|
+
|
|
239
|
+
const target = domainDraftDir(args.workspaceRoot, name)
|
|
240
|
+
const existed = existsSync(target)
|
|
241
|
+
if (existed && !args.overwrite) {
|
|
242
|
+
throw new Error(`Domain draft '${name}' already exists. Pass overwrite=true to replace it.`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
246
|
+
if (existed) {
|
|
247
|
+
rmSync(target, { recursive: true, force: true })
|
|
248
|
+
}
|
|
249
|
+
mkdirSync(target, { recursive: true })
|
|
250
|
+
writeFileSync(join(target, DOMAIN_FILE), `${domainMd}\n`, "utf-8")
|
|
251
|
+
|
|
252
|
+
const validation = validateDomainDraftPackage(args.workspaceRoot, name)
|
|
253
|
+
if (!validation.ok) {
|
|
254
|
+
throw new Error(`Created domain draft is invalid: ${validation.errors.join("; ")}`)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
name,
|
|
260
|
+
path: target,
|
|
261
|
+
files: [DOMAIN_FILE],
|
|
262
|
+
overwritten: existed,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Validate a project-local domain draft. */
|
|
267
|
+
export function validateDomainDraftPackage(workspaceRoot: string, nameInput: string): ValidateDomainPackageResult {
|
|
268
|
+
let name = nameInput
|
|
269
|
+
try {
|
|
270
|
+
name = normalizeDomainName(nameInput)
|
|
271
|
+
} catch {
|
|
272
|
+
// validateDomainPackageAt records the invalid-name error.
|
|
273
|
+
}
|
|
274
|
+
return validateDomainPackageAt(nameInput, domainDraftDir(workspaceRoot, name))
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Install a validated project-local domain draft into the user-level domain registry. */
|
|
278
|
+
export function installDomainDraftPackage(args: InstallDomainDraftArgs): InstallDomainDraftResult {
|
|
279
|
+
const name = normalizeDomainName(args.name)
|
|
280
|
+
const sourcePath = domainDraftDir(args.workspaceRoot, name)
|
|
281
|
+
const validation = validateDomainDraftPackage(args.workspaceRoot, name)
|
|
282
|
+
if (!validation.ok) {
|
|
283
|
+
throw new Error(`Domain draft is invalid: ${validation.errors.join("; ")}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const target = join(DOMAINS_DIR, name)
|
|
287
|
+
const existed = existsSync(target)
|
|
288
|
+
if (existed && !args.overwrite) {
|
|
289
|
+
throw new Error(`Domain '${name}' already exists. Pass overwrite=true to replace it.`)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
mkdirSync(DOMAINS_DIR, { recursive: true })
|
|
294
|
+
if (existed) {
|
|
295
|
+
rmSync(target, { recursive: true, force: true })
|
|
296
|
+
}
|
|
297
|
+
cpSync(sourcePath, target, { recursive: true })
|
|
298
|
+
} catch (e) {
|
|
299
|
+
throw new Error(`Installing domain draft requires write access to Revela user config at ${DOMAINS_DIR}: ${e instanceof Error ? e.message : String(e)}`)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
name,
|
|
305
|
+
path: target,
|
|
306
|
+
sourcePath,
|
|
307
|
+
files: [DOMAIN_FILE],
|
|
308
|
+
overwritten: existed,
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Validate a local domain package for the minimum Revela domain contract. */
|
|
313
|
+
export function validateDomainPackage(nameInput: string): ValidateDomainPackageResult {
|
|
314
|
+
let name = nameInput
|
|
315
|
+
try {
|
|
316
|
+
name = normalizeDomainName(nameInput)
|
|
317
|
+
} catch {
|
|
318
|
+
// validateDomainPackageAt records the invalid-name error.
|
|
319
|
+
}
|
|
320
|
+
return validateDomainPackageAt(nameInput, join(DOMAINS_DIR, name))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function validateDomainPackageAt(nameInput: string, dir: string): ValidateDomainPackageResult {
|
|
324
|
+
let name = nameInput
|
|
325
|
+
const errors: string[] = []
|
|
326
|
+
try {
|
|
327
|
+
name = normalizeDomainName(nameInput)
|
|
328
|
+
} catch (e) {
|
|
329
|
+
errors.push(e instanceof Error ? e.message : String(e))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const mdPath = join(dir, DOMAIN_FILE)
|
|
333
|
+
const hasIndustryMd = existsSync(mdPath)
|
|
334
|
+
let hasRequiredFrontmatter = false
|
|
335
|
+
let hasBody = false
|
|
336
|
+
|
|
337
|
+
if (!existsSync(dir)) errors.push(`Domain directory does not exist: ${dir}`)
|
|
338
|
+
if (!hasIndustryMd) errors.push(`${DOMAIN_FILE} is missing`)
|
|
339
|
+
|
|
340
|
+
if (hasIndustryMd) {
|
|
341
|
+
try {
|
|
342
|
+
const text = readFileSync(mdPath, "utf-8")
|
|
343
|
+
const { meta, body } = parseFrontmatter(text)
|
|
344
|
+
const required = ["name", "description", "author", "version"]
|
|
345
|
+
const missing = required.filter((field) => !meta[field]?.trim())
|
|
346
|
+
hasRequiredFrontmatter = missing.length === 0
|
|
347
|
+
hasBody = body.trim().length > 0
|
|
348
|
+
if (!hasRequiredFrontmatter) errors.push(`INDUSTRY.md frontmatter is missing required field(s): ${missing.join(", ")}`)
|
|
349
|
+
if (!hasBody) errors.push("INDUSTRY.md body/guidance is empty")
|
|
350
|
+
if (meta.name && meta.name.trim() !== name) errors.push(`INDUSTRY.md frontmatter name '${meta.name.trim()}' does not match package name '${name}'`)
|
|
351
|
+
} catch (e) {
|
|
352
|
+
errors.push(`INDUSTRY.md could not be parsed: ${e instanceof Error ? e.message : String(e)}`)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
ok: errors.length === 0,
|
|
358
|
+
name,
|
|
359
|
+
path: dir,
|
|
360
|
+
hasIndustryMd,
|
|
361
|
+
hasRequiredFrontmatter,
|
|
362
|
+
hasBody,
|
|
363
|
+
errors,
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function domainDraftDir(workspaceRoot: string, name: string): string {
|
|
368
|
+
return resolve(workspaceRoot, ".revela", "drafts", "domains", name)
|
|
369
|
+
}
|
|
370
|
+
|
|
151
371
|
/** Remove an installed domain. Throws if not found or is the protected default. */
|
|
152
372
|
export function removeDomain(name: string): void {
|
|
153
373
|
if (name === DEFAULT_DOMAIN) {
|
package/lib/runtime/index.ts
CHANGED
|
@@ -4,18 +4,24 @@ import { dirname, resolve } from "path"
|
|
|
4
4
|
import {
|
|
5
5
|
activeDesign,
|
|
6
6
|
activateDesign,
|
|
7
|
+
createDesignDraftPackage,
|
|
7
8
|
createDesignPackage,
|
|
8
9
|
getDesignSection,
|
|
9
10
|
getDesignSkillMd,
|
|
11
|
+
installDesignDraftPackage,
|
|
10
12
|
listDesigns,
|
|
11
13
|
seedBuiltinDesigns,
|
|
14
|
+
validateDesignDraftPackage,
|
|
12
15
|
validateDesignPackage,
|
|
13
16
|
} from "../design/designs"
|
|
14
17
|
import { createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
|
|
15
|
-
import { activeDomain, activateDomain, getDomainSkillMd, listDomains, seedBuiltinDomains } from "../domain/domains"
|
|
18
|
+
import { activeDomain, activateDomain, createDomainDraftPackage, createDomainPackage, getDomainSkillMd, installDomainDraftPackage, listDomains, seedBuiltinDomains, validateDomainDraftPackage, validateDomainPackage } from "../domain/domains"
|
|
16
19
|
import { computeNarrativeHash } from "../narrative-state/hash"
|
|
17
20
|
import { compileNarrativeVault } from "../narrative-vault/compile"
|
|
21
|
+
import { autoCompileNarrativeVault } from "../narrative-vault/auto-compile"
|
|
22
|
+
import { extractNarrativeVaultMarkdownTargetsFromPatch } from "../narrative-vault/hook-targets"
|
|
18
23
|
import { runNarrativeMarkdownQa, type MarkdownQaOptions } from "../narrative-vault/markdown-qa"
|
|
24
|
+
import { formatArtifactQaUserNotice, formatMarkdownQaUserNotice } from "../hook-notifications"
|
|
19
25
|
import { readDeckPlanArtifact } from "../narrative-state/deck-plan-artifact"
|
|
20
26
|
import { extractDesignClasses } from "../design/designs"
|
|
21
27
|
import { recordRenderedArtifact, workspaceRelative } from "../workspace-state/rendered-artifacts"
|
|
@@ -32,6 +38,10 @@ export interface RuntimeFileInput extends RuntimeWorkspaceInput {
|
|
|
32
38
|
file: string
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
export interface RuntimeNarrativeAutoCompileInput extends RuntimeWorkspaceInput {
|
|
42
|
+
touched?: string[]
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
export interface RuntimeDeckFoundationInput extends RuntimeWorkspaceInput {
|
|
36
46
|
outputPath: string
|
|
37
47
|
title: string
|
|
@@ -55,12 +65,28 @@ export interface RuntimeDesignCreateInput {
|
|
|
55
65
|
overwrite?: boolean
|
|
56
66
|
}
|
|
57
67
|
|
|
68
|
+
export interface RuntimeDesignDraftCreateInput extends RuntimeDesignCreateInput, RuntimeWorkspaceInput {}
|
|
69
|
+
|
|
70
|
+
export interface RuntimeDomainCreateInput {
|
|
71
|
+
name: string
|
|
72
|
+
domainMd: string
|
|
73
|
+
overwrite?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RuntimeDomainDraftCreateInput extends RuntimeDomainCreateInput, RuntimeWorkspaceInput {}
|
|
77
|
+
|
|
78
|
+
export interface RuntimeDraftInstallInput extends RuntimeWorkspaceInput {
|
|
79
|
+
name: string
|
|
80
|
+
overwrite?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
58
83
|
export interface RuntimeNameInput {
|
|
59
84
|
name: string
|
|
60
85
|
}
|
|
61
86
|
|
|
62
87
|
export function doctor(input: RuntimeWorkspaceInput = {}) {
|
|
63
88
|
const workspaceRoot = root(input.workspaceRoot)
|
|
89
|
+
const domain = activeDomainDoctorInfo()
|
|
64
90
|
return {
|
|
65
91
|
ok: true,
|
|
66
92
|
version: pkg.version,
|
|
@@ -69,6 +95,8 @@ export function doctor(input: RuntimeWorkspaceInput = {}) {
|
|
|
69
95
|
hasDeckPlan: existsSync(resolve(workspaceRoot, "deck-plan")),
|
|
70
96
|
hasDecksJson: existsSync(resolve(workspaceRoot, "DECKS.json")),
|
|
71
97
|
activeDesign: safe(activeDesign),
|
|
98
|
+
activeDomain: domain.name,
|
|
99
|
+
activeDomainDescription: domain.description,
|
|
72
100
|
}
|
|
73
101
|
}
|
|
74
102
|
|
|
@@ -86,6 +114,18 @@ export function markdownQa(input: RuntimeWorkspaceInput & MarkdownQaOptions = {}
|
|
|
86
114
|
})
|
|
87
115
|
}
|
|
88
116
|
|
|
117
|
+
export function autoCompileNarrative(input: RuntimeNarrativeAutoCompileInput = {}) {
|
|
118
|
+
const workspaceRoot = root(input.workspaceRoot)
|
|
119
|
+
return autoCompileNarrativeVault(workspaceRoot, input.touched ?? [])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function extractNarrativeVaultMarkdownPatchTargets(input: RuntimeWorkspaceInput & { patch: string }) {
|
|
123
|
+
const workspaceRoot = root(input.workspaceRoot)
|
|
124
|
+
return extractNarrativeVaultMarkdownTargetsFromPatch(input.patch, workspaceRoot)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { formatArtifactQaUserNotice, formatMarkdownQaUserNotice }
|
|
128
|
+
|
|
89
129
|
export function readDeckPlan(input: RuntimeWorkspaceInput = {}) {
|
|
90
130
|
const workspaceRoot = root(input.workspaceRoot)
|
|
91
131
|
const compiled = compileNarrativeVault(workspaceRoot)
|
|
@@ -220,6 +260,30 @@ export function designValidate(input: RuntimeNameInput) {
|
|
|
220
260
|
return validateDesignPackage(requiredName(input, "design"))
|
|
221
261
|
}
|
|
222
262
|
|
|
263
|
+
export function designDraftCreate(input: RuntimeDesignDraftCreateInput) {
|
|
264
|
+
return createDesignDraftPackage({
|
|
265
|
+
workspaceRoot: root(input.workspaceRoot),
|
|
266
|
+
name: requiredString(input?.name, "design name"),
|
|
267
|
+
base: input.base,
|
|
268
|
+
designMd: requiredString(input?.designMd, "designMd"),
|
|
269
|
+
previewHtml: requiredString(input?.previewHtml, "previewHtml"),
|
|
270
|
+
overwrite: input.overwrite ?? false,
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function designDraftValidate(input: RuntimeWorkspaceInput & RuntimeNameInput) {
|
|
275
|
+
return validateDesignDraftPackage(root(input.workspaceRoot), requiredName(input, "design draft"))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function designDraftInstall(input: RuntimeDraftInstallInput) {
|
|
279
|
+
seedBuiltinDesigns()
|
|
280
|
+
return installDesignDraftPackage({
|
|
281
|
+
workspaceRoot: root(input.workspaceRoot),
|
|
282
|
+
name: requiredName(input, "design draft"),
|
|
283
|
+
overwrite: input.overwrite ?? false,
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
223
287
|
export interface DesignRulesReadinessResult {
|
|
224
288
|
ok: boolean
|
|
225
289
|
activeDesign: string
|
|
@@ -322,6 +386,42 @@ export function domainActivate(input: RuntimeNameInput) {
|
|
|
322
386
|
}
|
|
323
387
|
}
|
|
324
388
|
|
|
389
|
+
export function domainCreate(input: RuntimeDomainCreateInput) {
|
|
390
|
+
seedBuiltinDomains()
|
|
391
|
+
return createDomainPackage({
|
|
392
|
+
name: requiredString(input?.name, "domain name"),
|
|
393
|
+
domainMd: requiredString(input?.domainMd, "domainMd"),
|
|
394
|
+
overwrite: input.overwrite ?? false,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function domainValidate(input: RuntimeNameInput) {
|
|
399
|
+
seedBuiltinDomains()
|
|
400
|
+
return validateDomainPackage(requiredName(input, "domain"))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function domainDraftCreate(input: RuntimeDomainDraftCreateInput) {
|
|
404
|
+
return createDomainDraftPackage({
|
|
405
|
+
workspaceRoot: root(input.workspaceRoot),
|
|
406
|
+
name: requiredString(input?.name, "domain name"),
|
|
407
|
+
domainMd: requiredString(input?.domainMd, "domainMd"),
|
|
408
|
+
overwrite: input.overwrite ?? false,
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function domainDraftValidate(input: RuntimeWorkspaceInput & RuntimeNameInput) {
|
|
413
|
+
return validateDomainDraftPackage(root(input.workspaceRoot), requiredName(input, "domain draft"))
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function domainDraftInstall(input: RuntimeDraftInstallInput) {
|
|
417
|
+
seedBuiltinDomains()
|
|
418
|
+
return installDomainDraftPackage({
|
|
419
|
+
workspaceRoot: root(input.workspaceRoot),
|
|
420
|
+
name: requiredName(input, "domain draft"),
|
|
421
|
+
overwrite: input.overwrite ?? false,
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
325
425
|
function root(workspaceRoot: string | undefined): string {
|
|
326
426
|
return resolve(workspaceRoot || process.cwd())
|
|
327
427
|
}
|
|
@@ -334,6 +434,17 @@ function safe<T>(fn: () => T): T | undefined {
|
|
|
334
434
|
}
|
|
335
435
|
}
|
|
336
436
|
|
|
437
|
+
function activeDomainDoctorInfo(): { name: string; description: string } {
|
|
438
|
+
try {
|
|
439
|
+
seedBuiltinDomains()
|
|
440
|
+
const name = activeDomain()
|
|
441
|
+
const description = listDomains().find((domain) => domain.name === name)?.description ?? ""
|
|
442
|
+
return { name, description }
|
|
443
|
+
} catch {
|
|
444
|
+
return { name: safe(activeDomain) ?? "", description: "" }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
337
448
|
function requiredName(input: RuntimeNameInput, label: string): string {
|
|
338
449
|
const name = input?.name?.trim()
|
|
339
450
|
if (!name) throw new Error(`${label} name is required`)
|
package/package.json
CHANGED
package/plugins/revela/.mcp.json
CHANGED
|
@@ -17,6 +17,15 @@ export async function runPreWriteChecks(input: string): Promise<HookResult> {
|
|
|
17
17
|
messages.push(`Revela controls ${controlledStateFile}. Use Revela MCP/runtime tools or file-native narrative files instead of direct ${controlledStateFile} patches.`)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const cacheTargets = extractNarrativeCachePatchTargets(input)
|
|
21
|
+
if (cacheTargets.length > 0) {
|
|
22
|
+
messages.push([
|
|
23
|
+
"Revela narrative cache patches are blocked.",
|
|
24
|
+
`Controlled cache target(s): ${cacheTargets.map((target) => `\`${target}\``).join(", ")}`,
|
|
25
|
+
"Edit `revela-narrative/**/*.md` instead; compile/cache files under `.opencode/revela/narrative-cache/` are regenerated.",
|
|
26
|
+
].join("\n"))
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
const deckTargets = extractDeckHtmlPatchTargets(input)
|
|
21
30
|
if (deckTargets.length > 0) {
|
|
22
31
|
const pluginRoot = resolve(process.env.PLUGIN_ROOT || dirname(dirname(fileURLToPath(import.meta.url))))
|
|
@@ -55,6 +64,16 @@ export function extractDeckHtmlPatchTargets(input: string): string[] {
|
|
|
55
64
|
return [...targets].sort((a, b) => a.localeCompare(b))
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
export function extractNarrativeCachePatchTargets(input: string): string[] {
|
|
68
|
+
const targets = new Set<string>()
|
|
69
|
+
for (const patch of patchPayloads(input)) {
|
|
70
|
+
const pattern = /(?:^\*\*\* Update File: |^\*\*\* Add File: |^\*\*\* Delete File: |^\*\*\* Move to: )([^\r\n]*\.opencode\/revela\/narrative-cache\/[^\r\n]+)\s*$/gm
|
|
71
|
+
let match: RegExpExecArray | null
|
|
72
|
+
while ((match = pattern.exec(patch))) targets.add(match[1].trim())
|
|
73
|
+
}
|
|
74
|
+
return [...targets].sort((a, b) => a.localeCompare(b))
|
|
75
|
+
}
|
|
76
|
+
|
|
58
77
|
function patchPayloads(input: string): string[] {
|
|
59
78
|
try {
|
|
60
79
|
const parsed = JSON.parse(input)
|
|
@@ -24,6 +24,20 @@ export function extractDeckHtmlTargets(input: string): string[] {
|
|
|
24
24
|
return [...targets].sort((a, b) => a.localeCompare(b))
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export function patchPayloadsFromInput(input: string): string[] {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(input)
|
|
30
|
+
return [
|
|
31
|
+
parsed.patch,
|
|
32
|
+
parsed.args?.patch,
|
|
33
|
+
parsed.tool_input?.patch,
|
|
34
|
+
parsed.toolInput?.patch,
|
|
35
|
+
].filter((item): item is string => typeof item === "string")
|
|
36
|
+
} catch {
|
|
37
|
+
return [input]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
export function workspaceRootFromInput(input: string): string {
|
|
28
42
|
try {
|
|
29
43
|
const parsed = JSON.parse(input)
|
|
@@ -47,18 +61,16 @@ export function workspaceRootFromInput(input: string): string {
|
|
|
47
61
|
|
|
48
62
|
export async function runPostWriteChecks(input: string): Promise<HookResult> {
|
|
49
63
|
const messages: string[] = []
|
|
50
|
-
if (/revela-narrative\/.*\.md/.test(input)) {
|
|
51
|
-
messages.push("Revela narrative Markdown changed. Run `revela_markdown_qa` and `revela_compile_narrative` before treating the graph as usable.")
|
|
52
|
-
}
|
|
53
|
-
|
|
54
64
|
const deckTargets = extractDeckHtmlTargets(input)
|
|
55
|
-
|
|
65
|
+
const hasPossibleNarrativeMarkdown = /revela-narrative\/.*\.md/.test(input)
|
|
66
|
+
if (deckTargets.length === 0 && !hasPossibleNarrativeMarkdown) return { ok: true, messages }
|
|
56
67
|
|
|
57
68
|
const pluginRoot = resolve(process.env.PLUGIN_ROOT || dirname(dirname(fileURLToPath(import.meta.url))))
|
|
58
69
|
const runtime = resolveRevelaRuntime({ pluginRoot })
|
|
59
70
|
if (!runtime.ok || !runtime.runtimePath) {
|
|
71
|
+
const changed = deckTargets.length > 0 ? "deck HTML changed" : "narrative Markdown changed"
|
|
60
72
|
messages.push([
|
|
61
|
-
|
|
73
|
+
`Revela ${changed}, but Codex hook could not locate the Revela runtime to run write-after checks.`,
|
|
62
74
|
...runtime.diagnostics.map((item) => `- ${item}`),
|
|
63
75
|
].join("\n"))
|
|
64
76
|
return { ok: false, messages }
|
|
@@ -67,9 +79,28 @@ export async function runPostWriteChecks(input: string): Promise<HookResult> {
|
|
|
67
79
|
const workspaceRoot = workspaceRootFromInput(input)
|
|
68
80
|
const runtimeModule = await import(pathToFileURL(runtime.runtimePath).href)
|
|
69
81
|
let ok = true
|
|
82
|
+
|
|
83
|
+
if (hasPossibleNarrativeMarkdown) {
|
|
84
|
+
const touched = new Set<string>()
|
|
85
|
+
for (const patch of patchPayloadsFromInput(input)) {
|
|
86
|
+
const targets = runtimeModule.extractNarrativeVaultMarkdownPatchTargets({ workspaceRoot, patch })
|
|
87
|
+
for (const target of targets) touched.add(target)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (touched.size > 0) {
|
|
91
|
+
const result = runtimeModule.autoCompileNarrative({ workspaceRoot, touched: [...touched] })
|
|
92
|
+
messages.push(result.markdown ?? JSON.stringify(result, null, 2))
|
|
93
|
+
const notice = runtimeModule.formatMarkdownQaUserNotice?.(result)
|
|
94
|
+
if (notice) messages.push(notice)
|
|
95
|
+
if (!result.ok) ok = false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
for (const target of deckTargets) {
|
|
71
100
|
const result = await runtimeModule.runDeckQa({ workspaceRoot, file: target })
|
|
72
101
|
messages.push(result.markdown ?? JSON.stringify(result, null, 2))
|
|
102
|
+
const notice = runtimeModule.formatArtifactQaUserNotice?.(result.report)
|
|
103
|
+
if (notice) messages.push(notice)
|
|
73
104
|
if (!result.ok) ok = false
|
|
74
105
|
}
|
|
75
106
|
|
|
@@ -22,9 +22,17 @@ type RuntimeModule = {
|
|
|
22
22
|
designActivate(input: any): any
|
|
23
23
|
designCreate(input: any): any
|
|
24
24
|
designValidate(input: any): any
|
|
25
|
+
designDraftCreate(input: any): any
|
|
26
|
+
designDraftValidate(input: any): any
|
|
27
|
+
designDraftInstall(input: any): any
|
|
25
28
|
domainList(): any
|
|
26
29
|
domainRead(input?: any): any
|
|
27
30
|
domainActivate(input: any): any
|
|
31
|
+
domainCreate(input: any): any
|
|
32
|
+
domainValidate(input: any): any
|
|
33
|
+
domainDraftCreate(input: any): any
|
|
34
|
+
domainDraftValidate(input: any): any
|
|
35
|
+
domainDraftInstall(input: any): any
|
|
28
36
|
storyRead(input?: any): any
|
|
29
37
|
reviewDeckRead(input: any): Promise<any>
|
|
30
38
|
reviewDeckOpen(input: any): Promise<any>
|
|
@@ -134,6 +142,35 @@ const tools = [
|
|
|
134
142
|
description: "Validate a local Revela design package.",
|
|
135
143
|
inputSchema: objectSchema({ name: requiredStringProp("Design name to validate.") }, ["name"]),
|
|
136
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: "revela_design_draft_create",
|
|
147
|
+
description: "Create and validate a workspace-local Revela design draft package.",
|
|
148
|
+
inputSchema: objectSchema({
|
|
149
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
150
|
+
name: requiredStringProp("Design name in kebab-case."),
|
|
151
|
+
base: stringProp("Optional base design used as structural scaffold."),
|
|
152
|
+
designMd: requiredStringProp("Complete DESIGN.md content."),
|
|
153
|
+
previewHtml: requiredStringProp("Complete preview.html content."),
|
|
154
|
+
overwrite: booleanProp("Whether to replace an existing workspace draft. Defaults to false."),
|
|
155
|
+
}, ["name", "designMd", "previewHtml"]),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "revela_design_draft_validate",
|
|
159
|
+
description: "Validate a workspace-local Revela design draft package.",
|
|
160
|
+
inputSchema: objectSchema({
|
|
161
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
162
|
+
name: requiredStringProp("Design draft name to validate."),
|
|
163
|
+
}, ["name"]),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "revela_design_draft_install",
|
|
167
|
+
description: "Install a validated workspace-local Revela design draft into the user-level design registry.",
|
|
168
|
+
inputSchema: objectSchema({
|
|
169
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
170
|
+
name: requiredStringProp("Design draft name to install."),
|
|
171
|
+
overwrite: booleanProp("Whether to replace an existing user-level design package. Defaults to false."),
|
|
172
|
+
}, ["name"]),
|
|
173
|
+
},
|
|
137
174
|
{
|
|
138
175
|
name: "revela_domain_list",
|
|
139
176
|
description: "List installed Revela narrative domains and the active domain.",
|
|
@@ -149,6 +186,47 @@ const tools = [
|
|
|
149
186
|
description: "Activate a Revela narrative domain for future narrative authoring guidance.",
|
|
150
187
|
inputSchema: objectSchema({ name: requiredStringProp("Domain name to activate.") }, ["name"]),
|
|
151
188
|
},
|
|
189
|
+
{
|
|
190
|
+
name: "revela_domain_create",
|
|
191
|
+
description: "Create and validate a local Revela narrative domain package from complete INDUSTRY.md content.",
|
|
192
|
+
inputSchema: objectSchema({
|
|
193
|
+
name: requiredStringProp("Domain name in kebab-case."),
|
|
194
|
+
domainMd: requiredStringProp("Complete INDUSTRY.md content."),
|
|
195
|
+
overwrite: booleanProp("Whether to replace an existing local domain package. Defaults to false."),
|
|
196
|
+
}, ["name", "domainMd"]),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "revela_domain_validate",
|
|
200
|
+
description: "Validate a local Revela narrative domain package.",
|
|
201
|
+
inputSchema: objectSchema({ name: requiredStringProp("Domain name to validate.") }, ["name"]),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "revela_domain_draft_create",
|
|
205
|
+
description: "Create and validate a workspace-local Revela narrative domain draft package.",
|
|
206
|
+
inputSchema: objectSchema({
|
|
207
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
208
|
+
name: requiredStringProp("Domain name in kebab-case."),
|
|
209
|
+
domainMd: requiredStringProp("Complete INDUSTRY.md content."),
|
|
210
|
+
overwrite: booleanProp("Whether to replace an existing workspace draft. Defaults to false."),
|
|
211
|
+
}, ["name", "domainMd"]),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: "revela_domain_draft_validate",
|
|
215
|
+
description: "Validate a workspace-local Revela narrative domain draft package.",
|
|
216
|
+
inputSchema: objectSchema({
|
|
217
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
218
|
+
name: requiredStringProp("Domain draft name to validate."),
|
|
219
|
+
}, ["name"]),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "revela_domain_draft_install",
|
|
223
|
+
description: "Install a validated workspace-local Revela narrative domain draft into the user-level domain registry.",
|
|
224
|
+
inputSchema: objectSchema({
|
|
225
|
+
workspaceRoot: stringProp("Optional workspace root."),
|
|
226
|
+
name: requiredStringProp("Domain draft name to install."),
|
|
227
|
+
overwrite: booleanProp("Whether to replace an existing user-level domain package. Defaults to false."),
|
|
228
|
+
}, ["name"]),
|
|
229
|
+
},
|
|
152
230
|
{
|
|
153
231
|
name: "revela_story_read",
|
|
154
232
|
description: "Read a deterministic Revela Story map and optional Markdown view from the canonical narrative vault without mutating files.",
|
|
@@ -278,9 +356,17 @@ async function callTool(name: string, args: any): Promise<any> {
|
|
|
278
356
|
if (name === "revela_design_activate") return r.designActivate(args)
|
|
279
357
|
if (name === "revela_design_create") return r.designCreate(args)
|
|
280
358
|
if (name === "revela_design_validate") return r.designValidate(args)
|
|
359
|
+
if (name === "revela_design_draft_create") return r.designDraftCreate(args)
|
|
360
|
+
if (name === "revela_design_draft_validate") return r.designDraftValidate(args)
|
|
361
|
+
if (name === "revela_design_draft_install") return r.designDraftInstall(args)
|
|
281
362
|
if (name === "revela_domain_list") return r.domainList()
|
|
282
363
|
if (name === "revela_domain_read") return r.domainRead(args)
|
|
283
364
|
if (name === "revela_domain_activate") return r.domainActivate(args)
|
|
365
|
+
if (name === "revela_domain_create") return r.domainCreate(args)
|
|
366
|
+
if (name === "revela_domain_validate") return r.domainValidate(args)
|
|
367
|
+
if (name === "revela_domain_draft_create") return r.domainDraftCreate(args)
|
|
368
|
+
if (name === "revela_domain_draft_validate") return r.domainDraftValidate(args)
|
|
369
|
+
if (name === "revela_domain_draft_install") return r.domainDraftInstall(args)
|
|
284
370
|
if (name === "revela_story_read") return r.storyRead(args)
|
|
285
371
|
if (name === "revela_review_deck_read") return r.reviewDeckRead(args)
|
|
286
372
|
if (name === "revela_review_deck_open") return r.reviewDeckOpen(args)
|
|
@@ -25,10 +25,12 @@ Design changes are visual/artifact-level unless they change claim meaning, evide
|
|
|
25
25
|
|
|
26
26
|
When the user asks to create a new design, use `starter` as the default base design unless they specify another base. Interview the user before saving anything: collect visual references such as images, webpages, brands, decks, or text descriptions, plus must-have and must-avoid constraints. Summarize the design brief and visual schema, then wait for the user to confirm before creating files.
|
|
27
27
|
|
|
28
|
-
After confirmation, read the base design with `revela_design_read`. Generate complete `DESIGN.md` and complete `preview.html` content, then call `
|
|
28
|
+
After confirmation, read the base design with `revela_design_read`. Generate complete `DESIGN.md` and complete `preview.html` content, then call `revela_design_draft_create` to save a workspace-local draft under `.revela/drafts/designs/<name>/`. Always call `revela_design_draft_validate` after draft creation or overwrite. The direct registry tools `revela_design_create` and `revela_design_validate` remain available for existing workflows, but Codex design authoring should use the draft workflow before install.
|
|
29
|
+
|
|
30
|
+
Install the draft globally only after the user confirms the validated draft should be installed. Call `revela_design_draft_install` to copy the draft into the user-level design registry. If a user-level design already exists, pass `overwrite: true` only after the user confirms replacement. In sandboxed Codex sessions, the install step may require permission to write Revela user config under `~/.config/revela`.
|
|
29
31
|
|
|
30
32
|
`DESIGN.md` must include frontmatter with `name`, `description`, `author`, and `version`, plus valid marker blocks for `@design:foundation`, `@design:rules`, at least one `@layout`, and at least one `@component`.
|
|
31
33
|
|
|
32
34
|
`preview.html` must be self-contained and directly openable in a browser. Every `<section class="slide">` must include `slide-qa` and exactly one direct `.slide-canvas` child. Every direct `.slide-canvas` is the fixed 1920px x 1080px export surface and must use explicit CSS with `width: 1920px` and `height: 1080px`; `.slide` may remain a viewport/navigation wrapper. Include a cover slide with `data-slide-role="cover"`, a closing slide with `data-slide-role="closing"`, and a visible sample for every `@component:*` using `data-preview-component="<component-name>"`.
|
|
33
35
|
|
|
34
|
-
Do not automatically activate a newly created design. Report the
|
|
36
|
+
Do not automatically activate a newly created design. Do not automatically activate a newly installed design. Report the draft path, installed path when installed, and tell the user they can activate it with `revela_design_activate`.
|
|
@@ -5,7 +5,7 @@ description: Use or switch Revela narrative domain guidance in Codex for init, r
|
|
|
5
5
|
|
|
6
6
|
# Revela Domain
|
|
7
7
|
|
|
8
|
-
Use this skill when the user asks about Revela domains, wants domain-specific narrative guidance,
|
|
8
|
+
Use this skill when the user asks about Revela domains, wants domain-specific narrative guidance, asks to switch the active domain, or asks to create a new domain.
|
|
9
9
|
|
|
10
10
|
## Workflow
|
|
11
11
|
|
|
@@ -16,3 +16,15 @@ Use this skill when the user asks about Revela domains, wants domain-specific na
|
|
|
16
16
|
5. Do not treat domain guidance as evidence, source material, or proof for factual claims.
|
|
17
17
|
|
|
18
18
|
Domain changes are narrative-framing preferences. They do not rewrite existing claims, evidence boundaries, artifacts, or deck plans unless the user asks for those updates.
|
|
19
|
+
|
|
20
|
+
## Creating Or Editing Domains
|
|
21
|
+
|
|
22
|
+
When the user asks to create a new domain, interview the user before saving anything. Collect the communication context, typical audience, decisions, claim patterns, evidence expectations, common objections, risks, research-gap heuristics, terminology to use, and terminology to avoid. Summarize the domain brief, then wait for the user to confirm before creating files.
|
|
23
|
+
|
|
24
|
+
After confirmation, generate complete `INDUSTRY.md` content and call `revela_domain_draft_create` to save a workspace-local draft under `.revela/drafts/domains/<name>/`. Always call `revela_domain_draft_validate` after draft creation or overwrite.
|
|
25
|
+
|
|
26
|
+
Install the draft globally only after the user confirms the validated draft should be installed. Call `revela_domain_draft_install` to copy the draft into the user-level domain registry. If a user-level domain already exists, pass `overwrite: true` only after the user confirms replacement. In sandboxed Codex sessions, the install step may require permission to write Revela user config under `~/.config/revela`.
|
|
27
|
+
|
|
28
|
+
`INDUSTRY.md` must include frontmatter with `name`, `description`, `author`, and `version`, followed by concrete narrative guidance for audience framing, decision framing, claim standards, evidence expectations, objection/risk handling, and research-gap interpretation.
|
|
29
|
+
|
|
30
|
+
Do not automatically activate a newly installed domain. Report the draft path, installed path when installed, and tell the user they can activate it with `revela_domain_activate`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: revela-upgrade
|
|
3
|
+
description: Guide Revela Codex plugin upgrade, update, version, and reinstall requests while checking the running runtime version first.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Revela Upgrade
|
|
7
|
+
|
|
8
|
+
Use this skill when the user asks how to upgrade, update, reinstall, or check the version of the Revela Codex plugin.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
1. Call `revela_doctor` first to inspect the currently running Revela runtime version.
|
|
13
|
+
2. Report the current runtime version from doctor output. Do not check the latest version online unless the user explicitly asks you to look it up.
|
|
14
|
+
3. Explain that the Codex Git marketplace ref and `.mcp.json` npm runtime pin are published together for the same Revela release.
|
|
15
|
+
4. If the user wants a fixed release, guide them through removing the installed plugin, removing the marketplace entry, adding the desired release tag, then adding the plugin again:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
codex plugin remove revela@revela
|
|
19
|
+
codex plugin marketplace remove revela
|
|
20
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref vX.Y.Z
|
|
21
|
+
codex plugin add revela@revela
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
5. If the user already tracks a branch or movable ref, guide them through upgrading the marketplace clone, then re-adding the plugin:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
codex plugin marketplace upgrade revela
|
|
28
|
+
codex plugin add revela@revela
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
6. Tell the user to start a new Codex thread after upgrading so Codex reloads the Revela skills, MCP tools, hooks, and runtime pin.
|
|
32
|
+
|
|
33
|
+
Do not run `codex plugin remove`, `codex plugin marketplace remove`, `codex plugin marketplace add`, `codex plugin marketplace upgrade`, or `codex plugin add` unless the user explicitly asks you to perform the upgrade or reinstall.
|