@cyber-dash-tech/revela 0.1.1 → 0.1.3
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 +23 -16
- package/README.zh-CN.md +30 -22
- package/lib/config.ts +1 -1
- package/lib/design/designs.ts +97 -7
- package/lib/prompt-builder.ts +29 -50
- package/lib/qa/checks.ts +359 -350
- package/lib/qa/measure.ts +8 -7
- package/package.json +1 -1
- package/skill/SKILL.md +19 -195
- package/tools/designs.ts +21 -5
- package/designs/default/DESIGN.md +0 -1100
- package/designs/editorial-ribbon/DESIGN.md +0 -1092
- package/designs/minimal/DESIGN.md +0 -1079
package/README.md
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
5
11
|
An [OpenCode](https://opencode.ai) plugin that turns your AI into a presentation assistant.
|
|
6
|
-
|
|
12
|
+
Tell Revela what's on your mind — it'll finish the research and analysis, and deliver a complete slide deck in a couple of minutes.
|
|
13
|
+
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
[
|
|
10
|
-
[](tests/)
|
|
11
|
-
[](https://opencode.ai)
|
|
12
|
-
[](https://bun.sh)
|
|
15
|
+
|
|
16
|
+
**[Live Demo — The AI Power Shift](https://cyber-dash-tech.github.io/revela/assets/html/ai-power-shift.html)** · A 5-slide investment brief generated entirely by Revela.
|
|
13
17
|
|
|
14
18
|
---
|
|
15
19
|
|
|
@@ -55,18 +59,21 @@ export { default } from "/absolute/path/to/revela/index.ts";
|
|
|
55
59
|
|
|
56
60
|
## Quick Start
|
|
57
61
|
|
|
62
|
+
Enable OpenCode's web search (recommended):
|
|
63
|
+
```bash
|
|
64
|
+
OPENCODE_ENABLE_EXA=1 opencode
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Enable Revela in your OpenCode session — turns the primary agent into a slide design expert:
|
|
58
68
|
```
|
|
59
69
|
/revela enable
|
|
60
70
|
```
|
|
61
71
|
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
To turn it off and restore the primary agent to normal:
|
|
64
73
|
```
|
|
65
74
|
/revela disable
|
|
66
75
|
```
|
|
67
76
|
|
|
68
|
-
Turns off Revela's system prompt for the current session.
|
|
69
|
-
|
|
70
77
|
---
|
|
71
78
|
|
|
72
79
|
## Commands
|
|
@@ -93,11 +100,11 @@ All commands execute locally — zero LLM cost, instant feedback.
|
|
|
93
100
|
|
|
94
101
|
Three designs are bundled. Switch with `/revela designs <name>`.
|
|
95
102
|
|
|
96
|
-
| Name | Description |
|
|
97
|
-
|
|
98
|
-
| `default` | Dark executive style — deep navy/slate, sharp typography, ECharts data visualization |
|
|
99
|
-
| `minimal` | Clean light theme — high contrast, generous whitespace, professional look |
|
|
100
|
-
| `editorial-ribbon` | Bold editorial layout — accent ribbons, strong headlines, high visual impact |
|
|
103
|
+
| Name | Description | Preview |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `default` | Dark executive style — deep navy/slate, sharp typography, ECharts data visualization |  |
|
|
106
|
+
| `minimal` | Clean light theme — high contrast, generous whitespace, professional look |  |
|
|
107
|
+
| `editorial-ribbon` | Bold editorial layout — accent ribbons, strong headlines, high visual impact |  |
|
|
101
108
|
|
|
102
109
|
---
|
|
103
110
|
|
|
@@ -141,7 +148,7 @@ Checks performed on every slide:
|
|
|
141
148
|
| **Density imbalance** | Columns where CSS `align-items: stretch` hides content imbalance |
|
|
142
149
|
| **Sparse** | Slides with too few visible elements |
|
|
143
150
|
|
|
144
|
-
|
|
151
|
+
Structural slides (cover, TOC, quote, summary, closing) set `slide-qa="false"` and are automatically exempted from fill/spacing checks. Content-heavy slides set `slide-qa="true"` to opt in.
|
|
145
152
|
|
|
146
153
|
You can also invoke QA manually: ask the AI to "run QA on slides/my-deck.html" or use the `revela-qa` tool directly.
|
|
147
154
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
10
|
+
</p>
|
|
11
|
+
|
|
5
12
|
Revela 是一款 [OpenCode](https://opencode.ai) 插件,让 AI 成为你的PPT助手。
|
|
6
13
|
用对话方式描述你的需求,Revela 会自动调研、分析、洞察,最后呈现你心中的PPT。
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[
|
|
11
|
-
[](https://opencode.ai)
|
|
12
|
-
[](https://bun.sh)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
**[在线演示 — AI 权力转移](https://cyber-dash-tech.github.io/revela/assets/html/ai-power-shift.html)** · 一份由 Revela 全程生成的 5 页投资简报。
|
|
13
18
|
|
|
14
19
|
---
|
|
15
20
|
|
|
@@ -85,18 +90,21 @@ export { default } from "/path/to/revela/index.ts";
|
|
|
85
90
|
|
|
86
91
|
## 快速开始
|
|
87
92
|
|
|
93
|
+
启用opencode搜索功能(推荐)
|
|
94
|
+
```Bash
|
|
95
|
+
OPENCODE_ENABLE_EXA=1 opencode
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
在 opencode 中启动 Revela(默认关闭),将 primary agent 变为演讲稿设计专家
|
|
88
99
|
```
|
|
89
100
|
/revela enable
|
|
90
101
|
```
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
关闭当前会话中 Revela,primary agent 恢复正常
|
|
94
104
|
```
|
|
95
105
|
/revela disable
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
关闭当前会话中 Revela 的系统提示注入。
|
|
99
|
-
|
|
100
108
|
---
|
|
101
109
|
|
|
102
110
|
## 命令
|
|
@@ -119,19 +127,19 @@ export { default } from "/path/to/revela/index.ts";
|
|
|
119
127
|
|
|
120
128
|
---
|
|
121
129
|
|
|
122
|
-
##
|
|
130
|
+
## 内置设计模版
|
|
123
131
|
|
|
124
132
|
插件内置三套设计,用 `/revela designs <name>` 切换。
|
|
125
133
|
|
|
126
|
-
| 名称 | 说明 |
|
|
127
|
-
|
|
128
|
-
| `default` | 深色商务风格 —— 深海军蓝/石板色,锐利字体,ECharts 数据可视化 |
|
|
129
|
-
| `minimal` | 简洁浅色主题 —— 高对比度,充足留白,专业外观 |
|
|
130
|
-
| `editorial-ribbon` | 大胆的编辑版式 —— 强调色横幅,醒目标题,高视觉冲击力 |
|
|
134
|
+
| 名称 | 说明 | 预览 |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `default` | 深色商务风格 —— 深海军蓝/石板色,锐利字体,ECharts 数据可视化 |  |
|
|
137
|
+
| `minimal` | 简洁浅色主题 —— 高对比度,充足留白,专业外观 |  |
|
|
138
|
+
| `editorial-ribbon` | 大胆的编辑版式 —— 强调色横幅,醒目标题,高视觉冲击力 |  |
|
|
131
139
|
|
|
132
140
|
---
|
|
133
141
|
|
|
134
|
-
##
|
|
142
|
+
## 内置行业SOP
|
|
135
143
|
|
|
136
144
|
领域为 AI 的上下文提供特定行业的报告框架和术语。
|
|
137
145
|
|
|
@@ -156,9 +164,9 @@ export { default } from "/path/to/revela/index.ts";
|
|
|
156
164
|
|
|
157
165
|
---
|
|
158
166
|
|
|
159
|
-
##
|
|
167
|
+
## 排版 QA
|
|
160
168
|
|
|
161
|
-
每次 AI 写入幻灯片文件时,Revela 会自动在 1920×1080 分辨率下运行基于 Puppeteer
|
|
169
|
+
每次 AI 写入幻灯片文件时,Revela 会自动在 1920×1080 分辨率下运行基于 Puppeteer 的排版质检。发现问题后立即将报告反馈给 AI,AI 自行修正,无需人工干预。(**功能持续更新中 ...**)
|
|
162
170
|
|
|
163
171
|
每张幻灯片的检查项:
|
|
164
172
|
|
|
@@ -171,7 +179,7 @@ export { default } from "/path/to/revela/index.ts";
|
|
|
171
179
|
| **密度失衡** | CSS `align-items: stretch` 列布局中隐藏的内容不平衡 |
|
|
172
180
|
| **稀疏** | 可见元素过少的幻灯片 |
|
|
173
181
|
|
|
174
|
-
|
|
182
|
+
结构性幻灯片(封面、目录、引言、总结、结语)设置 `slide-qa="false"`,自动豁免填充/间距检查。内容型幻灯片设置 `slide-qa="true"` 启用 QA 检查。
|
|
175
183
|
|
|
176
184
|
也可以手动触发:让 AI "对 slides/my-deck.html 运行 QA",或直接使用 `revela-qa` 工具。
|
|
177
185
|
|
|
@@ -179,7 +187,7 @@ export { default } from "/path/to/revela/index.ts";
|
|
|
179
187
|
|
|
180
188
|
---
|
|
181
189
|
|
|
182
|
-
##
|
|
190
|
+
## 自定义模版
|
|
183
191
|
|
|
184
192
|
设计是包含 `DESIGN.md` 文件的文件夹,frontmatter 声明元数据:
|
|
185
193
|
|
|
@@ -232,7 +240,7 @@ ECharts / 数据可视化规范...
|
|
|
232
240
|
|
|
233
241
|
没有标记时,整个 `DESIGN.md` 内容每轮全量注入(向后兼容)。
|
|
234
242
|
|
|
235
|
-
###
|
|
243
|
+
### 自定义模版安装
|
|
236
244
|
|
|
237
245
|
```
|
|
238
246
|
/revela designs-add github:your-org/your-design
|
|
@@ -242,7 +250,7 @@ ECharts / 数据可视化规范...
|
|
|
242
250
|
|
|
243
251
|
---
|
|
244
252
|
|
|
245
|
-
##
|
|
253
|
+
## 自定义行业SOP
|
|
246
254
|
|
|
247
255
|
领域为 AI 增加特定行业的报告框架、术语和结构化指导。
|
|
248
256
|
|
package/lib/config.ts
CHANGED
|
@@ -28,7 +28,7 @@ export const CONFIG_FILE = join(CONFIG_DIR, "config.json")
|
|
|
28
28
|
export const ACTIVE_PROMPT_FILE = join(CONFIG_DIR, "_active-prompt.md")
|
|
29
29
|
|
|
30
30
|
/** Default design name. */
|
|
31
|
-
export const DEFAULT_DESIGN = "
|
|
31
|
+
export const DEFAULT_DESIGN = "aurora"
|
|
32
32
|
|
|
33
33
|
/** Default domain name. */
|
|
34
34
|
export const DEFAULT_DOMAIN = "general"
|
package/lib/design/designs.ts
CHANGED
|
@@ -145,28 +145,41 @@ export function getDesignSkillMd(name?: string): string {
|
|
|
145
145
|
// Marker-based section / component parsing
|
|
146
146
|
// ---------------------------------------------------------------------------
|
|
147
147
|
|
|
148
|
+
export interface LayoutInfo {
|
|
149
|
+
/** Full text content of the layout block (without marker lines). */
|
|
150
|
+
content: string
|
|
151
|
+
/** Whether this layout type should be QA-checked for balance/rhythm. */
|
|
152
|
+
qa: boolean
|
|
153
|
+
}
|
|
154
|
+
|
|
148
155
|
export interface DesignSections {
|
|
149
|
-
/** Map of section
|
|
156
|
+
/** Map of @design:<name> section → extracted content (without marker lines). */
|
|
150
157
|
sections: Record<string, string>
|
|
151
|
-
/** Map of
|
|
158
|
+
/** Map of @layout:<name> → LayoutInfo with content + qa flag. */
|
|
159
|
+
layouts: Record<string, LayoutInfo>
|
|
160
|
+
/** Map of @component:<name> → extracted content (without marker lines). */
|
|
152
161
|
components: Record<string, string>
|
|
153
162
|
/** Whether the DESIGN.md has any markers at all. */
|
|
154
163
|
hasMarkers: boolean
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
/**
|
|
158
|
-
* Parse a DESIGN.md body (no frontmatter) into sections and components
|
|
159
|
-
* using the
|
|
160
|
-
* <!-- @
|
|
167
|
+
* Parse a DESIGN.md body (no frontmatter) into sections, layouts, and components
|
|
168
|
+
* using the three-layer HTML comment marker convention:
|
|
169
|
+
* <!-- @design:<name>:start --> … <!-- @design:<name>:end -->
|
|
170
|
+
* <!-- @layout:<name>:start qa=true|false --> … <!-- @layout:<name>:end -->
|
|
161
171
|
* <!-- @component:<name>:start --> … <!-- @component:<name>:end -->
|
|
162
172
|
*
|
|
173
|
+
* The `qa` attribute on layout markers defaults to `true` when omitted.
|
|
163
174
|
* Returns an object with empty maps and hasMarkers=false when no markers found.
|
|
164
175
|
*/
|
|
165
176
|
export function parseDesignSections(body: string): DesignSections {
|
|
166
177
|
const sections: Record<string, string> = {}
|
|
178
|
+
const layouts: Record<string, LayoutInfo> = {}
|
|
167
179
|
const components: Record<string, string> = {}
|
|
168
180
|
|
|
169
|
-
const sectionRe
|
|
181
|
+
const sectionRe = /<!--\s*@design:(\w[\w-]*):start\s*-->([\s\S]*?)<!--\s*@design:\1:end\s*-->/g
|
|
182
|
+
const layoutRe = /<!--\s*@layout:(\w[\w-]*):start(?:\s+qa=(true|false))?\s*-->([\s\S]*?)<!--\s*@layout:\1:end\s*-->/g
|
|
170
183
|
const componentRe = /<!--\s*@component:(\w[\w-]*):start\s*-->([\s\S]*?)<!--\s*@component:\1:end\s*-->/g
|
|
171
184
|
|
|
172
185
|
let hasMarkers = false
|
|
@@ -177,12 +190,20 @@ export function parseDesignSections(body: string): DesignSections {
|
|
|
177
190
|
sections[match[1]] = match[2].trim()
|
|
178
191
|
}
|
|
179
192
|
|
|
193
|
+
while ((match = layoutRe.exec(body)) !== null) {
|
|
194
|
+
hasMarkers = true
|
|
195
|
+
const qaAttr = match[2]
|
|
196
|
+
// qa defaults to true when attribute is omitted
|
|
197
|
+
const qa = qaAttr === "false" ? false : true
|
|
198
|
+
layouts[match[1]] = { content: match[3].trim(), qa }
|
|
199
|
+
}
|
|
200
|
+
|
|
180
201
|
while ((match = componentRe.exec(body)) !== null) {
|
|
181
202
|
hasMarkers = true
|
|
182
203
|
components[match[1]] = match[2].trim()
|
|
183
204
|
}
|
|
184
205
|
|
|
185
|
-
return { sections, components, hasMarkers }
|
|
206
|
+
return { sections, layouts, components, hasMarkers }
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
/**
|
|
@@ -219,6 +240,75 @@ export function generateComponentIndex(components: Record<string, string>): stri
|
|
|
219
240
|
].join("\n")
|
|
220
241
|
}
|
|
221
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Generate a compact Layout Index table from parsed layouts.
|
|
245
|
+
* Lists each layout name with the QA flag and a one-line description.
|
|
246
|
+
*/
|
|
247
|
+
export function generateLayoutIndex(layouts: Record<string, LayoutInfo>): string {
|
|
248
|
+
const names = Object.keys(layouts)
|
|
249
|
+
if (names.length === 0) return ""
|
|
250
|
+
|
|
251
|
+
const rows = names.map((name) => {
|
|
252
|
+
const { content, qa } = layouts[name]
|
|
253
|
+
const firstLine = content
|
|
254
|
+
.split("\n")
|
|
255
|
+
.map((l) => l.trim())
|
|
256
|
+
.find((l) => l && !l.startsWith("<!--") && !l.startsWith("```"))
|
|
257
|
+
const desc = firstLine
|
|
258
|
+
? firstLine.replace(/^#+\s*/, "").replace(/\(.*?\)/, "").trim()
|
|
259
|
+
: ""
|
|
260
|
+
const qaIcon = qa ? "✓" : "—"
|
|
261
|
+
return `| \`${name}\` | ${qaIcon} | ${desc} |`
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return [
|
|
265
|
+
"### Layout Index",
|
|
266
|
+
"",
|
|
267
|
+
"| Layout | QA | Description |",
|
|
268
|
+
"|---|---|---|",
|
|
269
|
+
...rows,
|
|
270
|
+
"",
|
|
271
|
+
"_Use `revela-designs` tool with `action: \"read\"` and `layout: \"<name>\"` to get full HTML/CSS for any layout._",
|
|
272
|
+
].join("\n")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get the raw text of one or more named layouts from a DESIGN.md.
|
|
277
|
+
* @param layoutNames - Comma-separated layout names or an array.
|
|
278
|
+
* @param designName - Design to read from (defaults to active).
|
|
279
|
+
*/
|
|
280
|
+
export function getDesignLayout(
|
|
281
|
+
layoutNames: string | string[],
|
|
282
|
+
designName?: string,
|
|
283
|
+
): string {
|
|
284
|
+
const name = designName || activeDesign()
|
|
285
|
+
const mdPath = join(DESIGNS_DIR, name, "DESIGN.md")
|
|
286
|
+
if (!existsSync(mdPath)) {
|
|
287
|
+
throw new Error(`Design '${name}' is not installed`)
|
|
288
|
+
}
|
|
289
|
+
const text = readFileSync(mdPath, "utf-8")
|
|
290
|
+
const { body } = parseFrontmatter(text)
|
|
291
|
+
const { layouts, hasMarkers } = parseDesignSections(body)
|
|
292
|
+
|
|
293
|
+
if (!hasMarkers) {
|
|
294
|
+
throw new Error(`Design '${name}' has no markers — use getDesignSkillMd() for full text`)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const names = Array.isArray(layoutNames)
|
|
298
|
+
? layoutNames
|
|
299
|
+
: layoutNames.split(",").map((s) => s.trim())
|
|
300
|
+
|
|
301
|
+
const parts: string[] = []
|
|
302
|
+
for (const layoutName of names) {
|
|
303
|
+
if (!(layoutName in layouts)) {
|
|
304
|
+
throw new Error(`Layout '${layoutName}' not found in design '${name}'`)
|
|
305
|
+
}
|
|
306
|
+
const { content, qa } = layouts[layoutName]
|
|
307
|
+
parts.push(`### Layout: ${layoutName} (qa=${qa})\n\n${content}`)
|
|
308
|
+
}
|
|
309
|
+
return parts.join("\n\n---\n\n")
|
|
310
|
+
}
|
|
311
|
+
|
|
222
312
|
/**
|
|
223
313
|
* Get the raw text of a named section from a DESIGN.md.
|
|
224
314
|
* Throws if the design is not installed or the section doesn't exist.
|
package/lib/prompt-builder.ts
CHANGED
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
getDesignSkillMd,
|
|
30
30
|
parseDesignSections,
|
|
31
31
|
generateComponentIndex,
|
|
32
|
+
generateLayoutIndex,
|
|
32
33
|
} from "./design/designs"
|
|
33
34
|
import { activeDomain, getDomainSkillMd } from "./domain/domains"
|
|
34
35
|
import { parseFrontmatter } from "./frontmatter"
|
|
35
|
-
import { SLIDE_TYPES, type SlideType } from "./qa/checks"
|
|
36
36
|
import { childLog } from "./log"
|
|
37
37
|
|
|
38
38
|
const promptLog = childLog("prompt-builder")
|
|
@@ -40,21 +40,6 @@ const promptLog = childLog("prompt-builder")
|
|
|
40
40
|
/** Path to SKILL.md shipped with this package. */
|
|
41
41
|
const SKILL_MD_PATH = resolve(__dirname, "..", "skill", "SKILL.md")
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
* Human-readable descriptions for each slide type.
|
|
45
|
-
* Used to generate the data-slide-type table injected into SKILL.md.
|
|
46
|
-
* Kept here (not in checks.ts) — these are prose for the AI, not QA logic.
|
|
47
|
-
*/
|
|
48
|
-
const SLIDE_TYPE_DESCRIPTIONS: Record<SlideType, string> = {
|
|
49
|
-
cover: "Title / opening slide",
|
|
50
|
-
toc: "Table of contents",
|
|
51
|
-
content: "Regular content slides (default)",
|
|
52
|
-
summary: "Key takeaways slide",
|
|
53
|
-
closing: "Thank-you / Q&A / contact slide",
|
|
54
|
-
divider: "Section-break / transition slide",
|
|
55
|
-
"thank-you": "Alias for `closing`",
|
|
56
|
-
}
|
|
57
|
-
|
|
58
43
|
/**
|
|
59
44
|
* Build the combined system prompt and write it to _active-prompt.md.
|
|
60
45
|
*
|
|
@@ -66,9 +51,8 @@ export function buildPrompt(designName?: string, domainName?: string): string {
|
|
|
66
51
|
const design = designName || activeDesign()
|
|
67
52
|
const domain = domainName || activeDomain()
|
|
68
53
|
|
|
69
|
-
// Layer 1 — SKILL.md
|
|
70
|
-
|
|
71
|
-
coreSkill = injectSlideTypes(coreSkill)
|
|
54
|
+
// Layer 1 — SKILL.md
|
|
55
|
+
const coreSkill = readFileSync(SKILL_MD_PATH, "utf-8")
|
|
72
56
|
|
|
73
57
|
// Check for preview.html
|
|
74
58
|
const designDir = join(DESIGNS_DIR, design)
|
|
@@ -121,13 +105,14 @@ export function buildPrompt(designName?: string, domainName?: string): string {
|
|
|
121
105
|
* Build the design layer text.
|
|
122
106
|
*
|
|
123
107
|
* If the DESIGN.md has markers:
|
|
124
|
-
* - Always include @
|
|
125
|
-
* - Always include @
|
|
126
|
-
* -
|
|
127
|
-
* -
|
|
108
|
+
* - Always include @design:foundation (colors, fonts, CSS, JS, HTML skeleton)
|
|
109
|
+
* - Always include @design:rules (composition rules, do/don't — always resident)
|
|
110
|
+
* - Always include generated Layout Index (with QA column)
|
|
111
|
+
* - Always include generated Component Index
|
|
112
|
+
* - Omit individual layout details, component details, @design:chart-rules
|
|
128
113
|
* (available on demand via revela-designs tool)
|
|
129
114
|
*
|
|
130
|
-
* If no markers: return the full DESIGN.md body unchanged.
|
|
115
|
+
* If no markers: return the full DESIGN.md body unchanged (backward compat).
|
|
131
116
|
*/
|
|
132
117
|
function buildDesignLayer(designName: string): string {
|
|
133
118
|
const mdPath = join(DESIGNS_DIR, designName, "DESIGN.md")
|
|
@@ -137,7 +122,7 @@ function buildDesignLayer(designName: string): string {
|
|
|
137
122
|
|
|
138
123
|
const raw = readFileSync(mdPath, "utf-8")
|
|
139
124
|
const { body } = parseFrontmatter(raw)
|
|
140
|
-
const { sections, components, hasMarkers } = parseDesignSections(body)
|
|
125
|
+
const { sections, layouts, components, hasMarkers } = parseDesignSections(body)
|
|
141
126
|
|
|
142
127
|
if (!hasMarkers) {
|
|
143
128
|
// Backward-compatible: full text injection
|
|
@@ -146,23 +131,29 @@ function buildDesignLayer(designName: string): string {
|
|
|
146
131
|
|
|
147
132
|
const layerParts: string[] = []
|
|
148
133
|
|
|
149
|
-
// 1.
|
|
150
|
-
if (sections["
|
|
151
|
-
layerParts.push(sections["
|
|
134
|
+
// 1. Foundation section (colors, typography, CSS vars, JS, HTML skeleton)
|
|
135
|
+
if (sections["foundation"]) {
|
|
136
|
+
layerParts.push(sections["foundation"])
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. Rules section (composition rules, do/don't — always resident)
|
|
140
|
+
if (sections["rules"]) {
|
|
141
|
+
layerParts.push(sections["rules"])
|
|
152
142
|
}
|
|
153
143
|
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
layerParts.push(
|
|
144
|
+
// 3. Layout Index — compact catalog with QA column
|
|
145
|
+
const layoutIndex = generateLayoutIndex(layouts)
|
|
146
|
+
if (layoutIndex) {
|
|
147
|
+
layerParts.push(layoutIndex)
|
|
158
148
|
}
|
|
159
149
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
150
|
+
// 4. Component Index — compact catalog
|
|
151
|
+
const componentIndex = generateComponentIndex(components)
|
|
152
|
+
if (componentIndex) {
|
|
153
|
+
layerParts.push(componentIndex)
|
|
163
154
|
}
|
|
164
155
|
|
|
165
|
-
//
|
|
156
|
+
// 5. On-demand note
|
|
166
157
|
layerParts.push(
|
|
167
158
|
[
|
|
168
159
|
"### On-Demand Design Sections",
|
|
@@ -172,23 +163,11 @@ function buildDesignLayer(designName: string): string {
|
|
|
172
163
|
"",
|
|
173
164
|
"| Section | Fetch with |",
|
|
174
165
|
"|---|---|",
|
|
166
|
+
"| Layout HTML/CSS details | `layout: \"<name>\"` (see Layout Index above) |",
|
|
175
167
|
"| Component CSS/HTML details | `component: \"<name>\"` (see Component Index above) |",
|
|
176
|
-
"| Data Visualization (ECharts) | `section: \"
|
|
177
|
-
"| Composition Guide & Do/Don't | `section: \"guide\"` |",
|
|
168
|
+
"| Data Visualization (ECharts) | `section: \"chart-rules\"` |",
|
|
178
169
|
].join("\n"),
|
|
179
170
|
)
|
|
180
171
|
|
|
181
172
|
return layerParts.join("\n\n---\n\n")
|
|
182
173
|
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Replace the <!-- @slide-types --> placeholder in SKILL.md with a generated
|
|
186
|
-
* markdown table built from SLIDE_TYPES (single source of truth in checks.ts).
|
|
187
|
-
*/
|
|
188
|
-
function injectSlideTypes(skillMd: string): string {
|
|
189
|
-
const rows = SLIDE_TYPES.map(
|
|
190
|
-
(t) => `| \`${t}\` | ${SLIDE_TYPE_DESCRIPTIONS[t]} |`,
|
|
191
|
-
)
|
|
192
|
-
const table = ["| Value | Use for |", "|-------|---------|", ...rows].join("\n")
|
|
193
|
-
return skillMd.replace("<!-- @slide-types -->", table)
|
|
194
|
-
}
|