@cyber-dash-tech/revela 0.8.8 → 0.9.0
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 +20 -57
- package/README.zh-CN.md +20 -57
- package/lib/agents/narrative-reviewer-prompt.ts +143 -0
- package/lib/commands/review.ts +14 -6
- package/lib/decks-state.ts +253 -0
- package/package.json +1 -1
- package/plugin.ts +25 -2
- package/skill/SKILL.md +45 -10
- package/tools/decks.ts +12 -0
package/README.md
CHANGED
|
@@ -20,8 +20,8 @@ Enable it for the current session, assign a presentation task, and the agent can
|
|
|
20
20
|
- injects a presentation-specific system prompt into your current agent with `/revela enable`
|
|
21
21
|
- builds that prompt from 3 layers: core skill, active domain, active design
|
|
22
22
|
- supports workspace document discovery, transparent text extraction for `.pdf`, `.docx`, `.pptx`, and `.xlsx`, and cached embedded-material extraction for those formats
|
|
23
|
-
-
|
|
24
|
-
-
|
|
23
|
+
- keeps track of deck context, slide structure, sources, and readiness across the current workspace
|
|
24
|
+
- checks for missing context, weak evidence, and incomplete structure before writing `decks/*.html`
|
|
25
25
|
- runs fast design compliance checks whenever the agent writes, patches, or edits `decks/*.html`
|
|
26
26
|
- opens a visual comment editor for existing decks so users can Ctrl/Cmd-click elements and send precise edit requests back to OpenCode
|
|
27
27
|
- exports finished decks to PDF and editable PPTX
|
|
@@ -89,7 +89,7 @@ Enable Revela in the current session:
|
|
|
89
89
|
/revela enable
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
Prepare the workspace when starting a new deck project:
|
|
93
93
|
|
|
94
94
|
```text
|
|
95
95
|
/revela init
|
|
@@ -109,7 +109,7 @@ Then give the agent a deck task:
|
|
|
109
109
|
Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main market drivers, use the active design faithfully, and save the result to decks/humanoid-robotics.html.
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
If the task depends on sources, research, or a confirmed slide plan, you can ask Revela to review whether the deck has enough context, structure, and evidence before writing:
|
|
113
113
|
|
|
114
114
|
```text
|
|
115
115
|
/revela review
|
|
@@ -137,8 +137,8 @@ Disable presentation mode when done:
|
|
|
137
137
|
/revela enable enable presentation mode for this session
|
|
138
138
|
/revela disable disable presentation mode
|
|
139
139
|
|
|
140
|
-
/revela init
|
|
141
|
-
/revela review
|
|
140
|
+
/revela init prepare the workspace for a deck project
|
|
141
|
+
/revela review check whether context, structure, and evidence are ready
|
|
142
142
|
/revela remember <text> save an explicit user/workflow preference
|
|
143
143
|
/revela edit open visual editor for the only deck in decks/
|
|
144
144
|
|
|
@@ -178,59 +178,22 @@ The enabled or disabled state is session-level only.
|
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
181
|
-
##
|
|
181
|
+
## Recommended Workflow
|
|
182
182
|
|
|
183
|
-
Revela
|
|
183
|
+
Use Revela as a guided deck-production mode:
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
1. Enable Revela with `/revela enable`.
|
|
186
|
+
2. Run `/revela init` when starting in a new project or when the workspace has changed significantly.
|
|
187
|
+
3. Choose a design or domain if the default style is not right.
|
|
188
|
+
4. Give the agent a clear deck task: audience, goal, language, number of slides, source requirements, and output path.
|
|
189
|
+
5. Use `/revela review` before writing if the deck needs research, citations, or a confirmed slide plan.
|
|
190
|
+
6. Let the agent write the HTML deck under `decks/`.
|
|
191
|
+
7. Use `/revela edit` for visual comments and targeted revisions.
|
|
192
|
+
8. Export with `/revela pdf <file>` or `/revela pptx <file>`.
|
|
186
193
|
|
|
187
|
-
|
|
188
|
-
- active deck spec: current deck output path, prerequisites, research plan, per-slide content, layouts, components, evidence, visuals, blockers, and write readiness
|
|
194
|
+
`/revela review` checks for practical readiness problems: unclear audience, missing source material, unfinished research, unsupported claims, weak source trace, incomplete slide structure, missing design/layout information, or lightweight narrative issues such as weak so-what, missing risk/assumption handling, and abrupt transitions. It does not write the final deck.
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
Create or refresh it with:
|
|
193
|
-
|
|
194
|
-
```text
|
|
195
|
-
/revela init
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Review the current deck state with:
|
|
199
|
-
|
|
200
|
-
```text
|
|
201
|
-
/revela review
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
`/revela review` does not write the final HTML deck. It reads and updates `DECKS.json` through the `revela-decks` tool, checks what is missing, and sets `writeReadiness.status` to `ready` only when the deck is ready to generate.
|
|
205
|
-
|
|
206
|
-
Minimum readiness conditions:
|
|
207
|
-
|
|
208
|
-
- topic, audience, language, visual style/design, and slide plan are decided
|
|
209
|
-
- source materials are identified or explicitly deemed unnecessary
|
|
210
|
-
- research need is assessed
|
|
211
|
-
- needed research findings have been read and reflected in the slide specs
|
|
212
|
-
- evidence-sensitive claims have slide-level evidence with useful source trace when available, such as `findingsFile`, `sourcePath`, `location`, `quote`, `url`, or `caveat`
|
|
213
|
-
- the user has confirmed the slide plan
|
|
214
|
-
- required design layouts and components have been fetched
|
|
215
|
-
- every slide has a title, layout, components, and structured content
|
|
216
|
-
- no blockers remain
|
|
217
|
-
|
|
218
|
-
The plugin enforces this before deck HTML is written. A write or patch touching `decks/*.html` is allowed only when the matching deck in `DECKS.json` passes the readiness gate. Direct writes or patches to `DECKS.json` are blocked; use `revela-decks` instead.
|
|
219
|
-
|
|
220
|
-
The gate checks:
|
|
221
|
-
|
|
222
|
-
- `writeReadiness.status` is `ready`
|
|
223
|
-
- `writeReadiness.blockers` is empty
|
|
224
|
-
- the deck `outputPath` exactly matches the target `decks/*.html` path
|
|
225
|
-
- all `requiredInputs` booleans are true
|
|
226
|
-
- every slide has title, layout, components, and structured content
|
|
227
|
-
- every needed research axis is `done`, `read`, or `skipped`
|
|
228
|
-
|
|
229
|
-
`/revela review` distinguishes missing evidence from weak source-only evidence. A claim with no evidence remains a blocker; evidence that only names a source is reported as a warning so the agent can add source trace before writing the deck.
|
|
230
|
-
|
|
231
|
-
If the gate blocks a write, Revela writes a marker file under `.opencode/revela/blocked-writes/` instead of creating or overwriting the deck HTML. This makes the failure visible to the agent while keeping the real deck file untouched.
|
|
232
|
-
|
|
233
|
-
For `apply_patch`, Revela only checks whether the patch touches `decks/*.html`. If not ready, the whole patch is replaced with a blocked marker patch. The `edit` tool is not gated.
|
|
196
|
+
If Revela blocks a deck write, ask the agent to run `/revela review`, resolve the reported gaps, and try again. This protects the deck file from being overwritten before the plan, evidence, and structure are ready.
|
|
234
197
|
|
|
235
198
|
To remember long-term preferences, use:
|
|
236
199
|
|
|
@@ -238,7 +201,7 @@ To remember long-term preferences, use:
|
|
|
238
201
|
/revela remember Prefer concise Chinese consulting-style decks.
|
|
239
202
|
```
|
|
240
203
|
|
|
241
|
-
Do not use `remember` for temporary checklist state;
|
|
204
|
+
Do not use `remember` for temporary checklist state; use it only for durable user or workflow preferences.
|
|
242
205
|
|
|
243
206
|
---
|
|
244
207
|
|
|
@@ -607,7 +570,7 @@ The editor opens in your browser. Use `Ctrl`/`Cmd` + click to reference deck ele
|
|
|
607
570
|
|
|
608
571
|
LLM tool equivalent: `revela-edit` with no target. This lets the agent open the same editor when you say things like “I want to edit the deck”.
|
|
609
572
|
|
|
610
|
-
`/revela edit` prepares minimal
|
|
573
|
+
For existing decks, `/revela edit` prepares whatever minimal project context is needed so targeted edits can still use the normal safety checks.
|
|
611
574
|
|
|
612
575
|
---
|
|
613
576
|
|
package/README.zh-CN.md
CHANGED
|
@@ -20,8 +20,8 @@ Revela 是一个 [OpenCode](https://opencode.ai) 插件,可以把你当前使
|
|
|
20
20
|
- 通过 `/revela enable` 向当前 agent 注入演示文稿专用 system prompt
|
|
21
21
|
- prompt 由 3 层组成:核心 skill、当前 domain、当前 design
|
|
22
22
|
- 支持工作区文档扫描,以及 `.pdf`、`.docx`、`.pptx`、`.xlsx` 的透明文本提取和嵌入素材缓存提取
|
|
23
|
-
-
|
|
24
|
-
-
|
|
23
|
+
- 在当前工作区持续跟踪 deck 背景、页面结构、来源材料和 readiness
|
|
24
|
+
- 写入 `decks/*.html` 前检查是否缺上下文、证据或结构
|
|
25
25
|
- agent 每次写入、patch 或 edit `decks/*.html` 时自动执行快速 design compliance 检查
|
|
26
26
|
- 为已有 deck 打开可视化评论编辑器,用户可以 Ctrl/Cmd + 点击元素,并把精确修改意见发回 OpenCode
|
|
27
27
|
- 支持导出成 PDF 和可编辑 PPTX
|
|
@@ -88,7 +88,7 @@ export { default } from "/absolute/path/to/revela/index.ts";
|
|
|
88
88
|
/revela enable
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
在新项目里可以先准备工作区:
|
|
92
92
|
|
|
93
93
|
```text
|
|
94
94
|
/revela init
|
|
@@ -108,7 +108,7 @@ export { default } from "/absolute/path/to/revela/index.ts";
|
|
|
108
108
|
Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main market drivers, use the active design faithfully, and save the result to decks/humanoid-robotics.html.
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
如果任务依赖来源材料、调研或已确认的 slide plan,可以先让 Revela 检查上下文、结构和证据是否足够:
|
|
112
112
|
|
|
113
113
|
```text
|
|
114
114
|
/revela review
|
|
@@ -136,8 +136,8 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
136
136
|
/revela enable 为当前会话启用演示文稿模式
|
|
137
137
|
/revela disable 关闭演示文稿模式
|
|
138
138
|
|
|
139
|
-
/revela init
|
|
140
|
-
/revela review
|
|
139
|
+
/revela init 为当前 deck 项目准备工作区
|
|
140
|
+
/revela review 检查上下文、结构和证据是否 ready
|
|
141
141
|
/revela remember <text> 保存明确的用户/工作流偏好
|
|
142
142
|
/revela edit 打开 decks/ 下唯一 deck 的可视化编辑器
|
|
143
143
|
|
|
@@ -177,59 +177,22 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
177
177
|
|
|
178
178
|
---
|
|
179
179
|
|
|
180
|
-
##
|
|
180
|
+
## 推荐使用流程
|
|
181
181
|
|
|
182
|
-
Revela
|
|
182
|
+
把 Revela 当成一个有检查环节的 deck 生产模式:
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
1. 用 `/revela enable` 启用 Revela。
|
|
185
|
+
2. 新项目或工作区明显变化时,运行 `/revela init`。
|
|
186
|
+
3. 如果默认风格不合适,先选择 design 或 domain。
|
|
187
|
+
4. 给 agent 一个清楚的 deck 任务:受众、目标、语言、页数、来源要求和输出路径。
|
|
188
|
+
5. 如果 deck 需要调研、引用或已确认的 slide plan,写作前先运行 `/revela review`。
|
|
189
|
+
6. 让 agent 把 HTML deck 写到 `decks/` 下。
|
|
190
|
+
7. 用 `/revela edit` 做可视化评论和精准修改。
|
|
191
|
+
8. 用 `/revela pdf <file>` 或 `/revela pptx <file>` 导出。
|
|
185
192
|
|
|
186
|
-
-
|
|
187
|
-
- active deck 规格:当前 deck 输出路径、前置条件、research plan、逐页内容、layout、component、证据、视觉需求、blocker 和 write readiness
|
|
193
|
+
`/revela review` 检查的是实际生产问题:受众是否清楚、是否缺来源材料、调研是否完成、关键 claim 是否有证据、source trace 是否太弱、页面结构是否完整、design/layout 信息是否齐全,以及轻量叙事问题,例如 so-what 不清晰、缺少风险/假设处理或转场突兀。它不会写最终 deck。
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
创建或刷新:
|
|
192
|
-
|
|
193
|
-
```text
|
|
194
|
-
/revela init
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
检查当前 deck 状态:
|
|
198
|
-
|
|
199
|
-
```text
|
|
200
|
-
/revela review
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
`/revela review` 不会写最终 HTML deck。它通过 `revela-decks` 工具读取并更新 `DECKS.json`,检查缺失项,并且只有在 deck 真的可以生成时才把 `writeReadiness.status` 设为 `ready`。
|
|
204
|
-
|
|
205
|
-
最小 readiness 条件:
|
|
206
|
-
|
|
207
|
-
- topic、audience、language、visual style/design 和 slide plan 已确定
|
|
208
|
-
- source materials 已识别,或明确不需要源材料
|
|
209
|
-
- research need 已评估
|
|
210
|
-
- 需要调研时,相关 findings 已读取并反映到逐页规格中
|
|
211
|
-
- evidence-sensitive claim 尽量带有逐页 evidence 和可用 source trace,例如 `findingsFile`、`sourcePath`、`location`、`quote`、`url` 或 `caveat`
|
|
212
|
-
- 用户已确认 slide plan
|
|
213
|
-
- 需要的 design layouts 和 components 已获取
|
|
214
|
-
- 每页都有 title、layout、components 和结构化 content
|
|
215
|
-
- 没有 unresolved blockers
|
|
216
|
-
|
|
217
|
-
插件会在写 deck HTML 前强制检查这些条件。任何触碰 `decks/*.html` 的 `write` 或 `apply_patch`,只有在 `DECKS.json` 里匹配的 deck 通过 readiness gate 后才允许执行。直接写入或 patch `DECKS.json` 会被阻止;必须使用 `revela-decks` 工具更新状态。
|
|
218
|
-
|
|
219
|
-
门禁会检查:
|
|
220
|
-
|
|
221
|
-
- `writeReadiness.status` 是 `ready`
|
|
222
|
-
- `writeReadiness.blockers` 为空
|
|
223
|
-
- deck `outputPath` 精确匹配目标 `decks/*.html` 路径
|
|
224
|
-
- 所有 `requiredInputs` 布尔值都是 true
|
|
225
|
-
- 每页都有 title、layout、components 和结构化 content
|
|
226
|
-
- 每个 needed research axis 都是 `done`、`read` 或 `skipped`
|
|
227
|
-
|
|
228
|
-
`/revela review` 会区分缺失 evidence 和较弱的 source-only evidence。完全没有 evidence 的 claim 仍是 blocker;只写了 source 名称但缺少 source trace 的 evidence 会作为 warning 报告,方便 agent 在写 deck 前补足溯源信息。
|
|
229
|
-
|
|
230
|
-
如果门禁阻止写入,Revela 会在 `.opencode/revela/blocked-writes/` 下写一个 marker 文件,而不是创建或覆盖真实 HTML deck。这样 agent 能看到失败原因,同时真实 deck 文件不会被污染。
|
|
231
|
-
|
|
232
|
-
对 `apply_patch`,Revela 只检查 patch 是否触碰 `decks/*.html`。如果未 ready,整个 patch 会被替换成 blocked marker patch。`edit` 工具不做门禁。
|
|
195
|
+
如果 Revela 阻止写入 deck,直接让 agent 运行 `/revela review`,根据报告补齐缺口后再写。这样可以避免在计划、证据或结构还不完整时覆盖真实 deck 文件。
|
|
233
196
|
|
|
234
197
|
记住长期偏好请使用:
|
|
235
198
|
|
|
@@ -237,7 +200,7 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
|
|
|
237
200
|
/revela remember 我偏好中文、咨询风格、每页只表达一个核心观点。
|
|
238
201
|
```
|
|
239
202
|
|
|
240
|
-
不要用 `remember` 保存临时 checklist
|
|
203
|
+
不要用 `remember` 保存临时 checklist 状态;它只适合保存长期用户偏好或工作流偏好。
|
|
241
204
|
|
|
242
205
|
---
|
|
243
206
|
|
|
@@ -572,7 +535,7 @@ Revela 0.8 中 `/revela edit` 不接受 target。如果 `decks/` 下正好有一
|
|
|
572
535
|
|
|
573
536
|
对应的 LLM tool:`revela-edit`,不需要 target。因此当你说“我要编辑这个 deck”时,agent 也可以主动打开同一个编辑器。
|
|
574
537
|
|
|
575
|
-
|
|
538
|
+
对于已有 HTML deck,`/revela edit` 会自动准备必要的最小项目上下文,让后续精准修改仍然经过正常安全检查。
|
|
576
539
|
|
|
577
540
|
---
|
|
578
541
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revela Narrative Reviewer — system prompt
|
|
3
|
+
*
|
|
4
|
+
* Injected via plugin config hook into the `revela-narrative-reviewer` subagent.
|
|
5
|
+
* The NARRATIVE_REVIEWER_SIGNATURE is used by the system.transform hook to
|
|
6
|
+
* detect this agent and skip injecting the SKILL+DESIGN deck-writing prompt.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const NARRATIVE_REVIEWER_SIGNATURE = "[[REVELA-NARRATIVE-REVIEWER]]"
|
|
10
|
+
|
|
11
|
+
export const NARRATIVE_REVIEWER_PROMPT = `${NARRATIVE_REVIEWER_SIGNATURE}
|
|
12
|
+
|
|
13
|
+
# Revela Narrative Reviewer
|
|
14
|
+
|
|
15
|
+
You are a specialized read-only narrative reviewer for Revela.
|
|
16
|
+
Your sole job is to run a fixed narrative rubric against the narrative brief and
|
|
17
|
+
slide-plan alignment for a workspace deck. You do NOT write state, generate
|
|
18
|
+
slides, rewrite the deck, or decide authoritative write readiness.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Mission
|
|
23
|
+
|
|
24
|
+
Given a review brief from the primary agent, assess whether the current deck's
|
|
25
|
+
\`narrativeBrief\`, slide plan, narrative roles, and evidence references pass the
|
|
26
|
+
fixed rubric below.
|
|
27
|
+
|
|
28
|
+
Prefer repeatability over creativity. You are not a brainstorming partner, copy
|
|
29
|
+
editor, or slide-polish agent. Do not search for optional improvements when the
|
|
30
|
+
rubric passes.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Allowed Context
|
|
35
|
+
|
|
36
|
+
- Use \`revela-decks\` action \`read\` to inspect \`DECKS.json\`.
|
|
37
|
+
- Read existing workspace files only when the primary brief points to them or when
|
|
38
|
+
they are referenced by slide evidence, \`sourceMaterials\`, or research findings.
|
|
39
|
+
- You may read existing \`researches/{topic}/*.md\` files when they are referenced
|
|
40
|
+
by \`slides[].evidence[]\` or \`researchPlan[].findingsFile\`.
|
|
41
|
+
- Treat \`revela-decks review\` as the authoritative readiness gate owned by the
|
|
42
|
+
primary agent and tool layer. Your critique is advisory only.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Hard Boundaries
|
|
47
|
+
|
|
48
|
+
- NEVER write, patch, or edit any file.
|
|
49
|
+
- NEVER call \`revela-decks\` actions \`init\`, \`upsertDeck\`, \`upsertSlides\`, \`review\`, or \`remember\`.
|
|
50
|
+
- NEVER call \`revela-research-save\`, \`revela-media-save\`, or any asset-writing tool.
|
|
51
|
+
- NEVER generate, write, patch, or edit \`decks/*.html\`.
|
|
52
|
+
- NEVER use \`websearch\` or \`webfetch\`; critique only from existing workspace state and files.
|
|
53
|
+
- NEVER invent evidence, quotes, page references, URLs, stakeholder beliefs, objections, or risks.
|
|
54
|
+
- NEVER claim the deck is ready or blocked. Only the primary agent reports readiness from \`revela-decks review\`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Review Method
|
|
59
|
+
|
|
60
|
+
1. Read the current deck state with \`revela-decks\` action \`read\`.
|
|
61
|
+
2. Inspect \`narrativeBrief\`, deck goal, audience, required decision/action, slide titles, purposes, narrative roles, content, and evidence references.
|
|
62
|
+
3. Read referenced research findings only when needed to evaluate overclaim or support concerns.
|
|
63
|
+
4. Run the rubric in the exact order below.
|
|
64
|
+
5. Produce only rubric-tied findings with stable IDs. If all checks pass, output exactly \`Findings: none\`.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Stable Rubric
|
|
69
|
+
|
|
70
|
+
Evaluate only these checks, in this order:
|
|
71
|
+
|
|
72
|
+
1. \`NB-001\` Narrative brief completeness
|
|
73
|
+
- Trigger only when a substantial decision deck lacks enough \`narrativeBrief\` fields to evaluate story intent.
|
|
74
|
+
- Relevant fields: \`audienceBeliefBefore\`, \`audienceBeliefAfter\`, \`decisionOrAction\`, \`narrativeArc\`, \`keyClaims\`, \`objections\`, and \`risks\`.
|
|
75
|
+
|
|
76
|
+
2. \`AB-001\` Audience belief shift not reflected
|
|
77
|
+
- Trigger only when \`audienceBeliefBefore\` or \`audienceBeliefAfter\` is present but the opener/early context or close/ask does not reflect that belief shift.
|
|
78
|
+
|
|
79
|
+
3. \`KC-001\` Key claim not represented in slides
|
|
80
|
+
- Trigger only when a \`narrativeBrief.keyClaims[]\` item has no clear corresponding slide content or role.
|
|
81
|
+
|
|
82
|
+
4. \`OBJ-001\` Objection not handled
|
|
83
|
+
- Trigger only when \`narrativeBrief.objections[]\` exists but no slide addresses the objection, caveat, risk, tradeoff, or response.
|
|
84
|
+
|
|
85
|
+
5. \`RISK-001\` Risk or assumption not carried
|
|
86
|
+
- Trigger only when \`narrativeBrief.risks[]\` exists but no slide carries the risk, assumption, caveat, or tradeoff.
|
|
87
|
+
|
|
88
|
+
6. \`ASK-001\` Decision/action not reflected in ask
|
|
89
|
+
- Trigger only when \`narrativeBrief.decisionOrAction\` exists but the ending, close, recommendation, or ask does not make the action concrete enough for the audience.
|
|
90
|
+
|
|
91
|
+
7. \`EV-001\` Recommendation overreaches evidence
|
|
92
|
+
- Trigger only when a recommendation, investment conclusion, or strong claim is materially stronger than the recorded slide evidence or cited research findings.
|
|
93
|
+
|
|
94
|
+
8. \`FLOW-001\` Declared narrative arc is broken
|
|
95
|
+
- Trigger only when the slide sequence materially violates \`narrativeBrief.narrativeArc\` or jumps from context to ask/recommendation without sufficient tension, evidence, or risk handling.
|
|
96
|
+
|
|
97
|
+
Do not create new IDs. Do not rename IDs. Do not output duplicate findings for the
|
|
98
|
+
same root issue; choose the first matching rubric ID in the order above.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Stability Rules
|
|
103
|
+
|
|
104
|
+
- Do not brainstorm optional improvements.
|
|
105
|
+
- Do not suggest copy edits, slide polish, stronger phrasing, or extra examples unless tied to one stable rubric ID.
|
|
106
|
+
- Do not output a finding just because something could be clearer. Output only when the current deck state fails a rubric check.
|
|
107
|
+
- Do not introduce new advisory suggestions after a prior issue appears addressed by the current slide specs.
|
|
108
|
+
- Do not change IDs or severity between runs for the same underlying issue.
|
|
109
|
+
- If all rubric checks pass, write exactly \`Findings: none\` and stop after the required closing line.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Output Format
|
|
114
|
+
|
|
115
|
+
Start exactly with:
|
|
116
|
+
|
|
117
|
+
\`Narrative review complete.\`
|
|
118
|
+
|
|
119
|
+
Then include:
|
|
120
|
+
|
|
121
|
+
\`Findings:\`
|
|
122
|
+
|
|
123
|
+
If all rubric checks pass, write exactly:
|
|
124
|
+
|
|
125
|
+
\`Findings: none\`
|
|
126
|
+
|
|
127
|
+
For each rubric finding, use this structure:
|
|
128
|
+
- \`id\`: one of \`NB-001\`, \`AB-001\`, \`KC-001\`, \`OBJ-001\`, \`RISK-001\`, \`ASK-001\`, \`EV-001\`, or \`FLOW-001\`
|
|
129
|
+
- \`severity\`: \`advisory\` or \`risk\`
|
|
130
|
+
- \`area\`: one of \`narrativeBrief\`, \`keyClaim\`, \`objection\`, \`risk\`, \`decisionAction\`, \`audienceBelief\`, \`evidenceOverreach\`, or \`flow\`
|
|
131
|
+
- \`finding\`: concise description of the narrative issue
|
|
132
|
+
- \`briefField\`: related \`narrativeBrief\` field, or \`none\`
|
|
133
|
+
- \`slideRefs\`: slide indexes/titles involved, or \`none\`
|
|
134
|
+
- \`evidenceConcern\`: evidence/source concern if any, or \`none\`
|
|
135
|
+
- \`suggestedAction\`: specific next improvement
|
|
136
|
+
|
|
137
|
+
Do not include general praise, a summary of strengths, or optional pre-write
|
|
138
|
+
improvements outside the finding structure.
|
|
139
|
+
|
|
140
|
+
End exactly with:
|
|
141
|
+
|
|
142
|
+
\`No direct state changes were made.\`
|
|
143
|
+
`
|
package/lib/commands/review.ts
CHANGED
|
@@ -15,10 +15,12 @@ export function buildReviewPrompt({
|
|
|
15
15
|
|
|
16
16
|
Goal:
|
|
17
17
|
- Use ${DECKS_STATE_FILE} as the source of truth for whether the current workspace deck is ready to be written to \`decks/*.html\`.
|
|
18
|
-
- Preserve the deck spec for future sessions: every slide's content, layout, components, evidence, visuals, and
|
|
18
|
+
- Preserve the deck spec for future sessions: every slide's content, layout, components, evidence, visuals, production status, and the 0.9 narrative compiler brief when available.
|
|
19
19
|
- Do not write, patch, or directly edit ${DECKS_STATE_FILE}. Use the \`revela-decks\` tool for all state changes.
|
|
20
20
|
- Let \`revela-decks\` action \`review\` compute writeReadiness; do not manually set readiness to ready.
|
|
21
|
-
- Treat this as an evidence
|
|
21
|
+
- Treat this as an evidence and Narrative Compiler readiness review, not only a checklist review: unsupported numbers, market sizing, recommendations, competitor comparisons, technical assertions, investment conclusions, missing audience belief change, unclear decision/action, unproven key claims, unhandled objections, weak so-what, missing risk/assumption handling, or abrupt narrative transitions should be made visible before writing.
|
|
22
|
+
- For substantial decision decks, use the read-only Task subagent \`revela-narrative-reviewer\` for independent rubric-based critique of narrative brief and slide-plan alignment. Do not self-certify semantic narrative quality in the primary agent.
|
|
23
|
+
- Treat \`revela-narrative-reviewer\` findings as advisory critique only. Do not represent them as \`revela-decks\` readiness issues, blockers, or authoritative \`writeReadiness\`.
|
|
22
24
|
- Treat source trace mapping as part of evidence readiness: when research findings have been read, relevant findings should appear in slide-level \`slides[].evidence[]\` records rather than only in raw research files.
|
|
23
25
|
|
|
24
26
|
Current state:
|
|
@@ -33,14 +35,16 @@ Workspace boundary rules:
|
|
|
33
35
|
|
|
34
36
|
Workflow:
|
|
35
37
|
1. Call \`revela-decks\` with action \`read\` for the current workspace deck.
|
|
36
|
-
2. If no current deck exists but the conversation contains enough deck context, call \`revela-decks\` action \`upsertDeck\` with goal, outputPath, theme, requiredInputs, and
|
|
38
|
+
2. If no current deck exists but the conversation contains enough deck context, call \`revela-decks\` action \`upsertDeck\` with goal, outputPath, theme, requiredInputs, researchPlan, and narrativeBrief if the story intent is clear. Do not invent or ask for a deck key; the tool uses the workspace folder name internally.
|
|
37
39
|
3. If \`researchPlan[].status\` is \`done\` or \`read\` and \`researchPlan[].findingsFile\` exists, verify that evidence-sensitive slide claims are backed by compact \`slides[].evidence[]\` records that reference the relevant findings file or source material where known.
|
|
38
|
-
4. If a user-confirmed slide plan is available, call \`revela-decks\` action \`upsertSlides\` with every slide's title, purpose, layout, components, structured content, evidence, visuals, and status.
|
|
40
|
+
4. If a user-confirmed slide plan is available, call \`revela-decks\` action \`upsertSlides\` with every slide's title, purpose, narrativeRole, layout, components, structured content, evidence, visuals, and status. Use only lightweight narrativeRole values that are clear from the plan: \`context\`, \`tension\`, \`evidence\`, \`recommendation\`, \`risk\`, \`ask\`, \`appendix\`, or \`close\`.
|
|
39
41
|
5. Prefer evidence records with \`findingsFile\`, \`sourcePath\`, \`location\`, \`quote\`, \`url\`, \`caveat\`, \`extractedTextPath\`, or \`extractedManifestPath\` when those fields are known from research files or extracted workspace materials.
|
|
40
42
|
6. Do not invent quotes, page references, locations, URLs, caveats, or extraction paths. If source trace is missing, preserve the blocker or warning and report exactly what trace is needed.
|
|
41
43
|
7. Only set requiredInputs fields true when explicit conversation state, files read, research findings read, selected design, fetched layouts/components, or user confirmation supports them. Do not infer completion.
|
|
42
|
-
8.
|
|
43
|
-
9.
|
|
44
|
+
8. For substantial decision decks, preserve a compact \`narrativeBrief\` through \`upsertDeck\` when the conversation or confirmed plan supports it. Do not invent stakeholder beliefs, objections, or risks; leave gaps visible if unknown.
|
|
45
|
+
9. For substantial decision decks, launch the Task subagent with \`subagent_type: "revela-narrative-reviewer"\` after deck/slides are up to date. Ask it to read the current \`DECKS.json\`, run only its fixed rubric, use stable finding IDs, return \`Findings: none\` when all checks pass, and avoid optional pre-write improvements. Do not ask it to write state, call \`revela-decks review\`, or produce HTML.
|
|
46
|
+
10. Call \`revela-decks\` action \`review\`. The tool computes and writes \`writeReadiness\` plus structured readiness issues for the current workspace deck.
|
|
47
|
+
11. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool. If warnings exist, list them after blockers as residual risks; separate evidence/source warnings from narrative warnings when possible. If the reviewer returned findings, include them in a separate \`Narrative reviewer notes\` section and label them advisory.
|
|
44
48
|
|
|
45
49
|
Minimum conditions for \`ready\`:
|
|
46
50
|
- Topic, audience, slide count, language, and visual style/design are decided.
|
|
@@ -51,6 +55,8 @@ Minimum conditions for \`ready\`:
|
|
|
51
55
|
- The user has confirmed the slide plan.
|
|
52
56
|
- ${DECKS_STATE_FILE} contains per-slide specs with content, layout, components, and evidence where applicable.
|
|
53
57
|
- Evidence-sensitive slide claims have compact evidence references with source trace where available. Numeric claims and strong recommendations should not be unsupported or source-only when trace exists.
|
|
58
|
+
- Multi-slide decision decks have a practical narrative flow: context/tension before evidence, recommendations after support, risk or assumption handling when recommending action, and a clear so-what or ask at the end. Narrative gaps are normally warnings, not hard blockers.
|
|
59
|
+
- Substantial decision decks should have a compact \`narrativeBrief\` that states the intended audience belief change, required decision/action, key claims, likely objections, and risks/assumptions. Missing fields are narrative warnings, not hard blockers.
|
|
54
60
|
- The needed design layouts and components have been fetched with \`revela-designs read\`.
|
|
55
61
|
- No unresolved blockers remain.
|
|
56
62
|
|
|
@@ -58,7 +64,9 @@ Report format:
|
|
|
58
64
|
- Start with \`Ready: yes/no\`.
|
|
59
65
|
- If blocked, list each blocker with slide index/title when the tool provides it, the issue type, and the suggested next action.
|
|
60
66
|
- If warnings exist but the deck is otherwise ready, say the deck can be written but note the residual risks.
|
|
67
|
+
- Report \`narrative_gap\` warnings as story-structure risks such as weak so-what, missing risk/assumption handling, conclusion before support, missing audience framing, or abrupt transition.
|
|
61
68
|
- Do not invent evidence or silently downgrade blockers. Use the tool result as authoritative.
|
|
69
|
+
- Do not convert \`revela-narrative-reviewer\` advisory findings into tool readiness issues. Keep them separate from \`revela-decks review\` blockers and warnings, and preserve the reviewer's stable finding IDs when reporting them.
|
|
62
70
|
- When reporting weak evidence, say whether the missing trace is \`findingsFile\`, \`sourcePath\`, \`location\`, \`quote\`, \`url\`, or \`caveat\` if that is clear from the reviewed materials.
|
|
63
71
|
|
|
64
72
|
Rules:
|
package/lib/decks-state.ts
CHANGED
|
@@ -6,6 +6,7 @@ export const DECKS_STATE_FILE = "DECKS.json"
|
|
|
6
6
|
export type DeckProductionStatus = "planning" | "blocked" | "ready" | "written"
|
|
7
7
|
export type SlideProductionStatus = "planned" | "ready" | "written" | "qa_passed" | "qa_failed"
|
|
8
8
|
export type WriteReadinessStatus = "blocked" | "ready" | "written"
|
|
9
|
+
export type NarrativeRole = "context" | "tension" | "evidence" | "recommendation" | "risk" | "ask" | "appendix" | "close"
|
|
9
10
|
|
|
10
11
|
export interface DecksState {
|
|
11
12
|
version: 1
|
|
@@ -57,6 +58,7 @@ export interface DeckSpec {
|
|
|
57
58
|
audience?: string
|
|
58
59
|
language?: string
|
|
59
60
|
outputPath: string
|
|
61
|
+
narrativeBrief?: NarrativeBrief
|
|
60
62
|
theme: {
|
|
61
63
|
design?: string
|
|
62
64
|
domain?: string
|
|
@@ -72,6 +74,16 @@ export interface DeckSpec {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
export interface NarrativeBrief {
|
|
78
|
+
audienceBeliefBefore?: string
|
|
79
|
+
audienceBeliefAfter?: string
|
|
80
|
+
decisionOrAction?: string
|
|
81
|
+
narrativeArc?: string
|
|
82
|
+
keyClaims: string[]
|
|
83
|
+
objections: string[]
|
|
84
|
+
risks: string[]
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
export interface RequiredInputs {
|
|
76
88
|
topicClarified: boolean
|
|
77
89
|
audienceClarified: boolean
|
|
@@ -96,6 +108,7 @@ export interface SlideSpec {
|
|
|
96
108
|
index: number
|
|
97
109
|
title: string
|
|
98
110
|
purpose?: string
|
|
111
|
+
narrativeRole?: NarrativeRole
|
|
99
112
|
layout: string
|
|
100
113
|
qa?: boolean
|
|
101
114
|
components: string[]
|
|
@@ -159,6 +172,7 @@ export type ReadinessIssueType =
|
|
|
159
172
|
| "missing_evidence"
|
|
160
173
|
| "weak_evidence"
|
|
161
174
|
| "source_not_processed"
|
|
175
|
+
| "narrative_gap"
|
|
162
176
|
|
|
163
177
|
export interface ReadinessIssue {
|
|
164
178
|
type: ReadinessIssueType
|
|
@@ -239,6 +253,7 @@ export function createDeckSpec(input: Partial<DeckSpec> & { slug: string }): Dec
|
|
|
239
253
|
audience: input.audience,
|
|
240
254
|
language: input.language,
|
|
241
255
|
outputPath: normalizeDeckPath(input.outputPath || `decks/${slug}.html`),
|
|
256
|
+
narrativeBrief: normalizeNarrativeBrief(input.narrativeBrief),
|
|
242
257
|
theme: input.theme ?? {},
|
|
243
258
|
requiredInputs: defaultRequiredInputs(input.requiredInputs),
|
|
244
259
|
researchPlan: input.researchPlan ?? [],
|
|
@@ -465,6 +480,7 @@ function compactWorkspaceForPrompt(workspace: DecksState["workspace"]): DecksSta
|
|
|
465
480
|
function compactDeckForPrompt(deck: DeckSpec): DeckSpec {
|
|
466
481
|
return {
|
|
467
482
|
...deck,
|
|
483
|
+
narrativeBrief: compactNarrativeBriefForPrompt(deck.narrativeBrief),
|
|
468
484
|
slides: deck.slides.map((slide) => ({
|
|
469
485
|
...slide,
|
|
470
486
|
content: {
|
|
@@ -477,6 +493,19 @@ function compactDeckForPrompt(deck: DeckSpec): DeckSpec {
|
|
|
477
493
|
}
|
|
478
494
|
}
|
|
479
495
|
|
|
496
|
+
function compactNarrativeBriefForPrompt(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
|
|
497
|
+
if (!brief) return undefined
|
|
498
|
+
return {
|
|
499
|
+
audienceBeliefBefore: truncatePromptText(brief.audienceBeliefBefore),
|
|
500
|
+
audienceBeliefAfter: truncatePromptText(brief.audienceBeliefAfter),
|
|
501
|
+
decisionOrAction: truncatePromptText(brief.decisionOrAction),
|
|
502
|
+
narrativeArc: truncatePromptText(brief.narrativeArc),
|
|
503
|
+
keyClaims: brief.keyClaims.map((claim) => truncatePromptText(claim)).filter(Boolean) as string[],
|
|
504
|
+
objections: brief.objections.map((objection) => truncatePromptText(objection)).filter(Boolean) as string[],
|
|
505
|
+
risks: brief.risks.map((risk) => truncatePromptText(risk)).filter(Boolean) as string[],
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
480
509
|
function compactEvidenceForPrompt(evidence: EvidenceRef): EvidenceRef {
|
|
481
510
|
return {
|
|
482
511
|
...evidence,
|
|
@@ -580,6 +609,8 @@ function computeDeckReadinessIssues(deck: DeckSpec, workspace: DecksState["works
|
|
|
580
609
|
}
|
|
581
610
|
}
|
|
582
611
|
|
|
612
|
+
issues.push(...computeNarrativeReadinessIssues(deck))
|
|
613
|
+
|
|
583
614
|
for (const axis of deck.researchPlan) {
|
|
584
615
|
if (axis.needed && axis.status !== "done" && axis.status !== "read" && axis.status !== "skipped") {
|
|
585
616
|
issues.push(blockerIssue(
|
|
@@ -612,6 +643,156 @@ function computeDeckReadinessIssues(deck: DeckSpec, workspace: DecksState["works
|
|
|
612
643
|
return issues
|
|
613
644
|
}
|
|
614
645
|
|
|
646
|
+
function computeNarrativeReadinessIssues(deck: DeckSpec): ReadinessIssue[] {
|
|
647
|
+
const issues: ReadinessIssue[] = []
|
|
648
|
+
const slides = deck.slides.filter((slide) => slide.index > 0).sort((a, b) => a.index - b.index)
|
|
649
|
+
if (slides.length === 0) return issues
|
|
650
|
+
const decisionOriented = isDecisionOrientedDeck(deck, slides)
|
|
651
|
+
|
|
652
|
+
if (decisionOriented && !hasNarrativeBriefContent(deck.narrativeBrief)) {
|
|
653
|
+
issues.push(warningIssue(
|
|
654
|
+
"narrative_gap",
|
|
655
|
+
"Narrative brief is missing for a decision-oriented deck",
|
|
656
|
+
"Add a 0.9 narrativeBrief with audience belief before/after, decisionOrAction, narrativeArc, keyClaims, objections, and risks so review can compile the deck against explicit story intent.",
|
|
657
|
+
))
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (decisionOriented && deck.narrativeBrief) {
|
|
661
|
+
if (!deck.narrativeBrief.audienceBeliefAfter?.trim()) {
|
|
662
|
+
issues.push(warningIssue(
|
|
663
|
+
"narrative_gap",
|
|
664
|
+
"Narrative brief is missing the intended audience belief after the deck",
|
|
665
|
+
"Set narrativeBrief.audienceBeliefAfter so the deck can be reviewed against the belief change it is meant to create.",
|
|
666
|
+
))
|
|
667
|
+
}
|
|
668
|
+
if (!deck.narrativeBrief.decisionOrAction?.trim() && slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide))) {
|
|
669
|
+
issues.push(warningIssue(
|
|
670
|
+
"narrative_gap",
|
|
671
|
+
"Narrative brief is missing the decision or action the deck should drive",
|
|
672
|
+
"Set narrativeBrief.decisionOrAction so recommendation and ask slides have an explicit communication target.",
|
|
673
|
+
))
|
|
674
|
+
}
|
|
675
|
+
if (deck.narrativeBrief.keyClaims.length === 0 && slides.some(isRecommendationSlide)) {
|
|
676
|
+
issues.push(warningIssue(
|
|
677
|
+
"narrative_gap",
|
|
678
|
+
"Narrative brief has no key claims for the recommendation to prove",
|
|
679
|
+
"Add narrativeBrief.keyClaims that capture the main claims the deck must support with slide evidence.",
|
|
680
|
+
))
|
|
681
|
+
}
|
|
682
|
+
if (deck.narrativeBrief.objections.length === 0 && slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide))) {
|
|
683
|
+
issues.push(warningIssue(
|
|
684
|
+
"narrative_gap",
|
|
685
|
+
"Narrative brief has no stakeholder objections to handle",
|
|
686
|
+
"Add likely objections or questions to narrativeBrief.objections so the story can anticipate resistance before the ask.",
|
|
687
|
+
))
|
|
688
|
+
}
|
|
689
|
+
if (deck.narrativeBrief.risks.length === 0 && slides.some(isRecommendationSlide)) {
|
|
690
|
+
issues.push(warningIssue(
|
|
691
|
+
"narrative_gap",
|
|
692
|
+
"Narrative brief has no risks, assumptions, or tradeoffs for the recommendation",
|
|
693
|
+
"Add risks, assumptions, caveats, or tradeoffs to narrativeBrief.risks so the recommendation does not overclaim certainty.",
|
|
694
|
+
))
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (slides.length >= 4 && slides.every((slide) => !slide.narrativeRole)) {
|
|
699
|
+
issues.push(warningIssue(
|
|
700
|
+
"narrative_gap",
|
|
701
|
+
"No slide narrativeRole values are recorded for a multi-slide deck",
|
|
702
|
+
"Add lightweight narrativeRole values such as context, tension, evidence, recommendation, risk, ask, appendix, or close to improve story-structure review.",
|
|
703
|
+
))
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (slides.length >= 4 && deck.audience?.trim() && slides.every(hasWeakNarrativePurpose)) {
|
|
707
|
+
issues.push(warningIssue(
|
|
708
|
+
"narrative_gap",
|
|
709
|
+
`Slide purposes do not clearly frame the story for the audience: ${deck.audience}`,
|
|
710
|
+
"Rewrite slide purpose fields to explain what this audience should understand, believe, decide, or do after each slide.",
|
|
711
|
+
))
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const firstRecommendationIndex = slides.findIndex(isRecommendationSlide)
|
|
715
|
+
if (firstRecommendationIndex >= 0) {
|
|
716
|
+
const recommendation = slides[firstRecommendationIndex]
|
|
717
|
+
const priorSlides = slides.slice(0, firstRecommendationIndex)
|
|
718
|
+
const earlyBoundary = Math.max(1, Math.ceil(slides.length * 0.3))
|
|
719
|
+
if (firstRecommendationIndex < earlyBoundary && !priorSlides.some(hasEvidenceOrTensionRole)) {
|
|
720
|
+
issues.push(warningIssue(
|
|
721
|
+
"narrative_gap",
|
|
722
|
+
`Slide ${recommendation.index} presents a recommendation before context, tension, or evidence has been established`,
|
|
723
|
+
"Consider moving the recommendation later or adding preceding context, tension, or evidence slides so the conclusion does not arrive before support.",
|
|
724
|
+
{ slideIndex: recommendation.index, slideTitle: recommendation.title },
|
|
725
|
+
))
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (!slides.some(hasRiskOrAssumptionHandling)) {
|
|
729
|
+
issues.push(warningIssue(
|
|
730
|
+
"narrative_gap",
|
|
731
|
+
"Recommendation has no visible risk, assumption, caveat, or tradeoff handling",
|
|
732
|
+
"Add a risk/assumption/tradeoff slide or make the relevant caveats explicit before writing a decision-oriented recommendation deck.",
|
|
733
|
+
{ slideIndex: recommendation.index, slideTitle: recommendation.title },
|
|
734
|
+
))
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (slides.length >= 4 && !hasClearEnding(slides)) {
|
|
739
|
+
const last = slides[slides.length - 1]
|
|
740
|
+
issues.push(warningIssue(
|
|
741
|
+
"narrative_gap",
|
|
742
|
+
"Deck may end without a clear so-what, ask, or closing takeaway",
|
|
743
|
+
"Use the final slide or final section to state the decision, action request, recommendation, or closing takeaway explicitly.",
|
|
744
|
+
{ slideIndex: last.index, slideTitle: last.title },
|
|
745
|
+
))
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const firstAskIndex = slides.findIndex(isAskSlide)
|
|
749
|
+
if (firstAskIndex === 0 && slides.length > 2) {
|
|
750
|
+
const ask = slides[firstAskIndex]
|
|
751
|
+
issues.push(warningIssue(
|
|
752
|
+
"narrative_gap",
|
|
753
|
+
`Slide ${ask.index} asks for action before the deck has established the case`,
|
|
754
|
+
"Consider moving the ask later or opening with context before requesting a decision or action.",
|
|
755
|
+
{ slideIndex: ask.index, slideTitle: ask.title },
|
|
756
|
+
))
|
|
757
|
+
} else if (firstAskIndex > 0) {
|
|
758
|
+
const contextIndex = slides.findIndex((slide) => slide.narrativeRole === "context")
|
|
759
|
+
if (contextIndex >= 0 && contextIndex < firstAskIndex) {
|
|
760
|
+
const bridgeSlides = slides.slice(contextIndex + 1, firstAskIndex)
|
|
761
|
+
if (!bridgeSlides.some((slide) => hasEvidenceOrTensionRole(slide) || isRecommendationSlide(slide))) {
|
|
762
|
+
const ask = slides[firstAskIndex]
|
|
763
|
+
issues.push(warningIssue(
|
|
764
|
+
"narrative_gap",
|
|
765
|
+
`Slide ${ask.index} jumps from context to ask without evidence, tension, or recommendation in between`,
|
|
766
|
+
"Add an evidence, tension, or recommendation bridge before the ask so the narrative transition is easier to follow.",
|
|
767
|
+
{ slideIndex: ask.index, slideTitle: ask.title },
|
|
768
|
+
))
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return issues
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function isDecisionOrientedDeck(deck: DeckSpec, slides: SlideSpec[]): boolean {
|
|
777
|
+
return Boolean(
|
|
778
|
+
deck.narrativeBrief?.decisionOrAction?.trim() ||
|
|
779
|
+
slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide)) ||
|
|
780
|
+
/\b(decision|approve|approval|recommend(?:ation)?|go\/?no-go|action)\b|决策|批准|建议|行动/.test(deck.goal.toLowerCase()),
|
|
781
|
+
)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function hasNarrativeBriefContent(brief: NarrativeBrief | undefined): boolean {
|
|
785
|
+
return Boolean(
|
|
786
|
+
brief?.audienceBeliefBefore?.trim() ||
|
|
787
|
+
brief?.audienceBeliefAfter?.trim() ||
|
|
788
|
+
brief?.decisionOrAction?.trim() ||
|
|
789
|
+
brief?.narrativeArc?.trim() ||
|
|
790
|
+
brief?.keyClaims.length ||
|
|
791
|
+
brief?.objections.length ||
|
|
792
|
+
brief?.risks.length,
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
|
|
615
796
|
function blockerIssue(type: ReadinessIssueType, message: string, suggestedAction: string, extra: Partial<ReadinessIssue> = {}): ReadinessIssue {
|
|
616
797
|
return { type, severity: "blocker", message, suggestedAction, ...extra }
|
|
617
798
|
}
|
|
@@ -634,6 +815,46 @@ function findEvidenceSensitiveClaim(slide: SlideSpec): string | undefined {
|
|
|
634
815
|
return candidates.find(isEvidenceSensitiveClaim)
|
|
635
816
|
}
|
|
636
817
|
|
|
818
|
+
function isRecommendationSlide(slide: SlideSpec): boolean {
|
|
819
|
+
return slide.narrativeRole === "recommendation" || /\b(recommend(?:ation|ed)?|should|must|go\/?no-go)\b|建议|必须/.test(slideSearchText(slide))
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function isAskSlide(slide: SlideSpec): boolean {
|
|
823
|
+
return slide.narrativeRole === "ask" || /\b(ask|decision|approve|approval|next step|action required|call to action)\b|请求|决策|批准|下一步|行动/.test(slideSearchText(slide))
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function hasEvidenceOrTensionRole(slide: SlideSpec): boolean {
|
|
827
|
+
return slide.narrativeRole === "evidence" || slide.narrativeRole === "tension"
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function hasRiskOrAssumptionHandling(slide: SlideSpec): boolean {
|
|
831
|
+
return slide.narrativeRole === "risk" || /\b(risk|assumption|caveat|trade-?off|constraint|limitation|uncertainty)\b|风险|假设|取舍|限制|不确定|前提/.test(slideSearchText(slide))
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function hasWeakNarrativePurpose(slide: SlideSpec): boolean {
|
|
835
|
+
const purpose = slide.purpose?.trim().toLowerCase()
|
|
836
|
+
if (!purpose) return true
|
|
837
|
+
return /^(explain|show|introduce|present|describe|overview|clarify)\b/.test(purpose) || /^(说明|展示|介绍|呈现|概述)/.test(purpose)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function hasClearEnding(slides: SlideSpec[]): boolean {
|
|
841
|
+
const finalSlides = slides.slice(-2)
|
|
842
|
+
return finalSlides.some((slide) => slide.narrativeRole === "recommendation" || slide.narrativeRole === "ask" || slide.narrativeRole === "close" || /\b(so what|takeaway|recommend(?:ation)?|decision|ask|next step|conclusion|close)\b|结论|建议|决策|请求|下一步|收尾|总结/.test(slideSearchText(slide)))
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function slideSearchText(slide: SlideSpec): string {
|
|
846
|
+
return [
|
|
847
|
+
slide.title,
|
|
848
|
+
slide.purpose,
|
|
849
|
+
slide.content?.headline,
|
|
850
|
+
...(slide.content?.body ?? []),
|
|
851
|
+
...(slide.content?.bullets ?? []),
|
|
852
|
+
]
|
|
853
|
+
.filter((item): item is string => Boolean(item))
|
|
854
|
+
.join("\n")
|
|
855
|
+
.toLowerCase()
|
|
856
|
+
}
|
|
857
|
+
|
|
637
858
|
function isEvidenceSensitiveClaim(text: string): boolean {
|
|
638
859
|
const normalized = text.toLowerCase()
|
|
639
860
|
return hasNumericClaim(normalized) || EVIDENCE_SENSITIVE_TERMS.some((pattern) => pattern.test(normalized))
|
|
@@ -711,6 +932,38 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
|
|
|
711
932
|
.sort((a, b) => a.index - b.index)
|
|
712
933
|
}
|
|
713
934
|
|
|
935
|
+
function normalizeNarrativeBrief(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
|
|
936
|
+
if (!brief) return undefined
|
|
937
|
+
const normalized: NarrativeBrief = {
|
|
938
|
+
audienceBeliefBefore: cleanOptionalText(brief.audienceBeliefBefore),
|
|
939
|
+
audienceBeliefAfter: cleanOptionalText(brief.audienceBeliefAfter),
|
|
940
|
+
decisionOrAction: cleanOptionalText(brief.decisionOrAction),
|
|
941
|
+
narrativeArc: cleanOptionalText(brief.narrativeArc),
|
|
942
|
+
keyClaims: normalizeTextList(brief.keyClaims),
|
|
943
|
+
objections: normalizeTextList(brief.objections),
|
|
944
|
+
risks: normalizeTextList(brief.risks),
|
|
945
|
+
}
|
|
946
|
+
if (
|
|
947
|
+
!normalized.audienceBeliefBefore &&
|
|
948
|
+
!normalized.audienceBeliefAfter &&
|
|
949
|
+
!normalized.decisionOrAction &&
|
|
950
|
+
!normalized.narrativeArc &&
|
|
951
|
+
normalized.keyClaims.length === 0 &&
|
|
952
|
+
normalized.objections.length === 0 &&
|
|
953
|
+
normalized.risks.length === 0
|
|
954
|
+
) return undefined
|
|
955
|
+
return normalized
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function normalizeTextList(values: string[] | undefined): string[] {
|
|
959
|
+
return [...new Set((values ?? []).map(cleanOptionalText).filter(Boolean) as string[])]
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function cleanOptionalText(value: string | undefined): string | undefined {
|
|
963
|
+
const text = String(value ?? "").trim()
|
|
964
|
+
return text || undefined
|
|
965
|
+
}
|
|
966
|
+
|
|
714
967
|
function hasSlideContent(slide: SlideSpec): boolean {
|
|
715
968
|
const content = slide.content ?? {}
|
|
716
969
|
return Boolean(
|
package/package.json
CHANGED
package/plugin.ts
CHANGED
|
@@ -87,6 +87,7 @@ import pdfTool from "./tools/pdf"
|
|
|
87
87
|
import pptxTool from "./tools/pptx"
|
|
88
88
|
import createEditTool from "./tools/edit"
|
|
89
89
|
import { RESEARCH_PROMPT, RESEARCH_AGENT_SIGNATURE } from "./lib/agents/research-prompt"
|
|
90
|
+
import { NARRATIVE_REVIEWER_PROMPT, NARRATIVE_REVIEWER_SIGNATURE } from "./lib/agents/narrative-reviewer-prompt"
|
|
90
91
|
import { formatReport, runComplianceQA } from "./lib/qa"
|
|
91
92
|
import { extractDesignClasses } from "./lib/design/designs"
|
|
92
93
|
import { log, childLog } from "./lib/log"
|
|
@@ -204,7 +205,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
return {
|
|
207
|
-
// ── Register /revela command +
|
|
208
|
+
// ── Register /revela command + Revela subagents ───────────────────────
|
|
208
209
|
config: async (opencodeConfig) => {
|
|
209
210
|
opencodeConfig.command ??= {}
|
|
210
211
|
opencodeConfig.command["revela"] = {
|
|
@@ -235,6 +236,24 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
235
236
|
// Give revela-research explicit websearch allow (overrides global deny below)
|
|
236
237
|
;(opencodeConfig.agent["revela-research"].permission as any).websearch = "allow"
|
|
237
238
|
|
|
239
|
+
// Register the read-only narrative reviewer subagent.
|
|
240
|
+
// It can inspect workspace state and referenced files, but cannot write or browse.
|
|
241
|
+
opencodeConfig.agent["revela-narrative-reviewer"] = {
|
|
242
|
+
description: "Revela narrative reviewer — read-only critique of narrative brief and slide-plan alignment",
|
|
243
|
+
mode: "subagent",
|
|
244
|
+
prompt: NARRATIVE_REVIEWER_PROMPT,
|
|
245
|
+
permission: {
|
|
246
|
+
edit: "deny",
|
|
247
|
+
bash: {
|
|
248
|
+
"*": "deny",
|
|
249
|
+
"ls *": "allow",
|
|
250
|
+
"ls": "allow",
|
|
251
|
+
},
|
|
252
|
+
webfetch: "deny",
|
|
253
|
+
websearch: "deny",
|
|
254
|
+
} as any,
|
|
255
|
+
}
|
|
256
|
+
|
|
238
257
|
// Block websearch for the primary agent globally.
|
|
239
258
|
// permission.ask hook is not triggered by OpenCode (no R.trigger call in binary).
|
|
240
259
|
// tool.execute.before throw is swallowed (trigger().catch(()=>{})).
|
|
@@ -481,7 +500,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
481
500
|
|
|
482
501
|
// ── Inject three-layer prompt when enabled ─────────────────────────────
|
|
483
502
|
// Skip injection for:
|
|
484
|
-
// 1.
|
|
503
|
+
// 1. Revela subagents (they have focused prompts)
|
|
485
504
|
// 2. OpenCode internal agents (title, summary, compaction)
|
|
486
505
|
"experimental.chat.system.transform": async (input, output) => {
|
|
487
506
|
if (!ctx.enabled) return
|
|
@@ -498,6 +517,10 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
498
517
|
}
|
|
499
518
|
ctx.isResearchAgent = false
|
|
500
519
|
|
|
520
|
+
// Skip revela-narrative-reviewer subagent — it is read-only critique,
|
|
521
|
+
// not a deck-writing agent and not a research agent.
|
|
522
|
+
if (systemText.includes(NARRATIVE_REVIEWER_SIGNATURE)) return
|
|
523
|
+
|
|
501
524
|
// Skip OpenCode internal system agents (title generator, summary, compaction)
|
|
502
525
|
if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) return
|
|
503
526
|
|
package/skill/SKILL.md
CHANGED
|
@@ -83,6 +83,7 @@ Create or update the active deck in `DECKS.json` through `revela-decks` actions
|
|
|
83
83
|
`upsertDeck` and `upsertSlides`. Keep the deck spec current as work progresses:
|
|
84
84
|
- `goal` — purpose and decision/context
|
|
85
85
|
- `audience`, `language`, `outputPath`, and `theme`
|
|
86
|
+
- `narrativeBrief` — for substantial decision decks, the 0.9 compiler brief: audience belief before/after, decisionOrAction, narrativeArc, keyClaims, objections, and risks
|
|
86
87
|
- `requiredInputs` — checklist state for prewrite readiness
|
|
87
88
|
- `researchPlan` — axes, status, and findings files
|
|
88
89
|
- `slides` — confirmed per-slide title, purpose, layout, components, content, evidence, visuals, and status
|
|
@@ -200,6 +201,26 @@ state updates. Do not write temporary hypotheses, unsupported conclusions,
|
|
|
200
201
|
secrets, or inferred user preferences. User and workflow preferences require
|
|
201
202
|
explicit user intent to remember.
|
|
202
203
|
|
|
204
|
+
#### Narrative Review via `revela-narrative-reviewer`
|
|
205
|
+
|
|
206
|
+
`revela-narrative-reviewer` is a read-only OpenCode subagent, **not a tool**.
|
|
207
|
+
Launch it through the Task tool with `subagent_type: "revela-narrative-reviewer"`
|
|
208
|
+
when a substantial decision deck needs independent rubric-based critique of the
|
|
209
|
+
Narrative Compiler brief and slide-plan alignment.
|
|
210
|
+
|
|
211
|
+
Use it after the narrative brief and slide specs are recorded in `DECKS.json`,
|
|
212
|
+
and before treating narrative quality as reviewed. The primary agent should not
|
|
213
|
+
self-certify semantic narrative quality. `revela-decks review` remains the
|
|
214
|
+
authoritative write-readiness gate; reviewer findings are advisory notes only.
|
|
215
|
+
The reviewer uses stable finding IDs such as `NB-001`, `KC-001`, `ASK-001`, and
|
|
216
|
+
`EV-001`. If the fixed rubric passes, it should return `Findings: none` rather
|
|
217
|
+
than inventing optional improvements.
|
|
218
|
+
|
|
219
|
+
The reviewer may read `DECKS.json`, slide specs, evidence refs, and existing
|
|
220
|
+
`researches/{workspace-key}/*.md` files referenced by the deck. It must not write
|
|
221
|
+
state, call `upsertDeck`, call `upsertSlides`, call `revela-decks review`, use
|
|
222
|
+
websearch/webfetch, or generate/edit HTML.
|
|
223
|
+
|
|
203
224
|
#### AI Knowledge and User Questions
|
|
204
225
|
|
|
205
226
|
Use AI knowledge only to fill remaining gaps around verified sources. Mark it
|
|
@@ -213,6 +234,8 @@ already checked and what specific missing information is needed.
|
|
|
213
234
|
|
|
214
235
|
- **NEVER** use `websearch` directly from the primary agent; delegate web research to `revela-research` subagents
|
|
215
236
|
- **NEVER** call `revela-research` as a tool; use Task with `subagent_type: "revela-research"`
|
|
237
|
+
- **NEVER** call `revela-narrative-reviewer` as a tool; use Task with `subagent_type: "revela-narrative-reviewer"`
|
|
238
|
+
- **NEVER** present `revela-narrative-reviewer` findings as authoritative `revela-decks review` blockers or readiness issues
|
|
216
239
|
- **NEVER** collapse distinct research axes into one broad agent brief when parallel focused briefs would be clearer
|
|
217
240
|
- **ALWAYS** use `revela-decks` action `read` before deciding what research is needed
|
|
218
241
|
- **ALWAYS** read each `researches/{workspace-key}/{axis}.md` after agents complete
|
|
@@ -287,20 +310,31 @@ core rules and the visual design below.
|
|
|
287
310
|
|
|
288
311
|
### Phase 4 — Presentation Plan
|
|
289
312
|
|
|
290
|
-
After all research is complete and findings have been read, present a
|
|
291
|
-
slide plan to the user **before writing any HTML**.
|
|
313
|
+
After all research is complete and findings have been read, present a compact narrative brief
|
|
314
|
+
and a detailed slide plan to the user **before writing any HTML**.
|
|
315
|
+
|
|
316
|
+
For substantial decision decks, first summarize the Narrative Compiler brief:
|
|
317
|
+
- Audience belief before: what the audience currently believes, assumes, or does not yet understand
|
|
318
|
+
- Audience belief after: what the audience should believe or understand after the deck
|
|
319
|
+
- Decision/action: the approval, decision, behavior, or next step the deck should drive
|
|
320
|
+
- Narrative arc: the intended story path, such as context -> tension -> evidence -> recommendation -> risk -> ask
|
|
321
|
+
- Key claims: the main claims the deck must prove
|
|
322
|
+
- Likely objections: stakeholder resistance or questions the story should handle
|
|
323
|
+
- Risks/assumptions: caveats, tradeoffs, or uncertainty that should travel with the recommendation
|
|
292
324
|
|
|
293
325
|
Format the plan as a markdown table:
|
|
294
326
|
|
|
295
|
-
| # | Title | Content Summary | Layout | Components |
|
|
296
|
-
|
|
297
|
-
| 1 | Cover | Topic title, subtitle, presenter, date | `cover` | `gradient-text`, `deco-blob`, `accent-line` |
|
|
298
|
-
| 2 | Table of Contents | 5 chapter headings | `toc` | `toc-list` |
|
|
299
|
-
| 3 | Market Background | Key problem, 3 pain points, $4.2B TAM | `two-col` | `evidence-list`, `card` |
|
|
300
|
-
| 4 | Key Metrics | Growth 85%, TAM $12B, NPS 72 | `stats` | `stat-card ×3`, `gradient-text` |
|
|
327
|
+
| # | Title | Narrative Role | Content Summary | Layout | Components |
|
|
328
|
+
|---|-------|----------------|-----------------|--------|------------|
|
|
329
|
+
| 1 | Cover | `context` | Topic title, subtitle, presenter, date | `cover` | `gradient-text`, `deco-blob`, `accent-line` |
|
|
330
|
+
| 2 | Table of Contents | `context` | 5 chapter headings | `toc` | `toc-list` |
|
|
331
|
+
| 3 | Market Background | `tension` | Key problem, 3 pain points, $4.2B TAM | `two-col` | `evidence-list`, `card` |
|
|
332
|
+
| 4 | Key Metrics | `evidence` | Growth 85%, TAM $12B, NPS 72 | `stats` | `stat-card ×3`, `gradient-text` |
|
|
301
333
|
|
|
302
334
|
Rules for filling the table:
|
|
303
335
|
- **Layout**: use the exact layout name from the Layout Index (e.g. `cover`, `two-col`, `card-grid`, `stats`)
|
|
336
|
+
- **Narrative Role**: use one lightweight role when clear: `context`, `tension`, `evidence`,
|
|
337
|
+
`recommendation`, `risk`, `ask`, `appendix`, or `close`
|
|
304
338
|
- **Components**: list component names from the Component Index — no CSS details
|
|
305
339
|
(e.g. `card ×3`, `stat-card`, `evidence-list`, `step-flow`, `quote-block`)
|
|
306
340
|
- **Content Summary**: 1 sentence of actual content — specific numbers, key points, or
|
|
@@ -318,8 +352,9 @@ Then ask:
|
|
|
318
352
|
- On change request → update the table and ask again
|
|
319
353
|
|
|
320
354
|
After the user confirms the slide plan, update `DECKS.json` through `revela-decks`:
|
|
321
|
-
- Call `upsertDeck` to mark completed `requiredInputs` only when explicitly satisfied.
|
|
322
|
-
- Call `upsertSlides` with the confirmed per-slide content, layout, components, and evidence.
|
|
355
|
+
- Call `upsertDeck` to preserve `narrativeBrief` when available and mark completed `requiredInputs` only when explicitly satisfied.
|
|
356
|
+
- Call `upsertSlides` with the confirmed per-slide content, narrativeRole, layout, components, and evidence.
|
|
357
|
+
- For substantial decision decks, use Task with `subagent_type: "revela-narrative-reviewer"` for read-only rubric-based critique of narrativeBrief and slide-plan alignment. Ask for stable finding IDs and `Findings: none` when the rubric passes; do not ask the reviewer to write state, determine readiness, or brainstorm optional improvements.
|
|
323
358
|
- Keep write readiness blocked until Phase 5 calls `revela-decks review` and the tool returns ready.
|
|
324
359
|
|
|
325
360
|
---
|
package/tools/decks.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
writeDecksState,
|
|
11
11
|
workspaceDeckSlug,
|
|
12
12
|
type DeckSpec,
|
|
13
|
+
type NarrativeBrief,
|
|
13
14
|
type RequiredInputs,
|
|
14
15
|
type ResearchAxis,
|
|
15
16
|
type SourceMaterial,
|
|
@@ -31,6 +32,15 @@ export default tool({
|
|
|
31
32
|
audience: tool.schema.string().optional().describe("For upsertDeck: deck audience."),
|
|
32
33
|
language: tool.schema.string().optional().describe("For upsertDeck: deck language."),
|
|
33
34
|
outputPath: tool.schema.string().optional().describe("For upsertDeck: target output path, normally decks/{workspace-name}.html."),
|
|
35
|
+
narrativeBrief: tool.schema.object({
|
|
36
|
+
audienceBeliefBefore: tool.schema.string().optional().describe("What the audience currently believes, assumes, or does not yet understand."),
|
|
37
|
+
audienceBeliefAfter: tool.schema.string().optional().describe("What the audience should believe or understand after the deck."),
|
|
38
|
+
decisionOrAction: tool.schema.string().optional().describe("The decision, approval, action, or behavioral change the deck is meant to drive."),
|
|
39
|
+
narrativeArc: tool.schema.string().optional().describe("Compact story arc, such as context -> tension -> evidence -> recommendation -> ask."),
|
|
40
|
+
keyClaims: tool.schema.array(tool.schema.string()).optional().describe("Main claims the deck must prove or communicate."),
|
|
41
|
+
objections: tool.schema.array(tool.schema.string()).optional().describe("Likely stakeholder objections or questions the narrative should handle."),
|
|
42
|
+
risks: tool.schema.array(tool.schema.string()).optional().describe("Risks, assumptions, caveats, or tradeoffs that should travel with the narrative."),
|
|
43
|
+
}).optional().describe("For upsertDeck: 0.9 Narrative Compiler brief used to review story intent before writing."),
|
|
34
44
|
design: tool.schema.string().optional().describe("For upsertDeck: active design name."),
|
|
35
45
|
domain: tool.schema.string().optional().describe("For upsertDeck: active domain name."),
|
|
36
46
|
memory: tool.schema.string().optional().describe("For remember: explicit user or workflow preference to store."),
|
|
@@ -75,6 +85,7 @@ export default tool({
|
|
|
75
85
|
index: tool.schema.number().describe("1-based slide index."),
|
|
76
86
|
title: tool.schema.string().describe("Slide title."),
|
|
77
87
|
purpose: tool.schema.string().optional().describe("Narrative purpose of this slide."),
|
|
88
|
+
narrativeRole: tool.schema.enum(["context", "tension", "evidence", "recommendation", "risk", "ask", "appendix", "close"]).optional().describe("Lightweight narrative role for review guidance."),
|
|
78
89
|
layout: tool.schema.string().describe("Design layout name."),
|
|
79
90
|
qa: tool.schema.boolean().optional().describe("Whether the slide is marked QA-relevant deck metadata."),
|
|
80
91
|
components: tool.schema.array(tool.schema.string()).describe("Design components used by this slide."),
|
|
@@ -139,6 +150,7 @@ export default tool({
|
|
|
139
150
|
audience: args.audience ?? existing?.audience,
|
|
140
151
|
language: args.language ?? existing?.language,
|
|
141
152
|
outputPath: args.outputPath ?? existing?.outputPath,
|
|
153
|
+
narrativeBrief: (args.narrativeBrief as NarrativeBrief | undefined) ?? existing?.narrativeBrief,
|
|
142
154
|
theme: {
|
|
143
155
|
design: args.design ?? existing?.theme?.design,
|
|
144
156
|
domain: args.domain ?? existing?.theme?.domain,
|