@cyber-dash-tech/revela 0.17.5 → 0.17.7
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 +26 -46
- package/README.zh-CN.md +26 -46
- package/bin/revela.ts +98 -0
- package/lib/commands/review.ts +8 -5
- package/lib/deck-html/foundation.ts +190 -0
- package/lib/edit/prompt.ts +6 -2
- package/lib/edit/server.ts +2 -2
- package/lib/inspect/prompt.ts +5 -1
- package/lib/qa/index.ts +12 -0
- package/lib/refine/comment-requests.ts +77 -0
- package/lib/refine/open.ts +5 -2
- package/lib/refine/prompt-bridge.ts +219 -0
- package/lib/refine/qa-suppression.ts +41 -0
- package/lib/refine/server.ts +122 -34
- package/lib/runtime/index.ts +225 -0
- package/lib/runtime/research.ts +175 -0
- package/lib/runtime/review.ts +270 -0
- package/lib/runtime/story.ts +53 -0
- package/package.json +6 -1
- package/plugin.ts +6 -2
- package/plugins/revela/.codex-plugin/plugin.json +37 -0
- package/plugins/revela/.mcp.json +11 -0
- package/plugins/revela/assets/README.md +2 -0
- package/plugins/revela/hooks/hooks.json +28 -0
- package/plugins/revela/hooks/revela_guard.ts +10 -0
- package/plugins/revela/hooks/revela_post_write_notice.ts +18 -0
- package/plugins/revela/mcp/revela-server.ts +504 -0
- package/plugins/revela/mcp/runtime-resolver.ts +109 -0
- package/plugins/revela/skills/revela-design/SKILL.md +20 -0
- package/plugins/revela/skills/revela-domain/SKILL.md +18 -0
- package/plugins/revela/skills/revela-export/SKILL.md +21 -0
- package/plugins/revela/skills/revela-init/SKILL.md +36 -0
- package/plugins/revela/skills/revela-make-deck/SKILL.md +37 -0
- package/plugins/revela/skills/revela-research/SKILL.md +38 -0
- package/plugins/revela/skills/revela-review-deck/SKILL.md +33 -0
- package/plugins/revela/skills/revela-story/SKILL.md +24 -0
- package/skill/SKILL.md +17 -8
- package/tools/deck-foundation.ts +48 -0
- package/tools/decks.ts +10 -78
- package/tools/research-save.ts +8 -72
package/README.md
CHANGED
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
Revela
|
|
11
|
+
Revela works from [OpenCode](https://opencode.ai) and Codex to turn source materials, research, data, and intent into trusted, traceable, presentation-ready decision artifacts.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Its narrative workspace records the core elements needed to generate a brief or deck: audience, decision, claims, evidence, sources, risks, objections, and open gaps.
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### OpenCode
|
|
18
|
+
|
|
19
|
+
Install Revela through `opencode.json` with the npm package `@cyber-dash-tech/revela`:
|
|
18
20
|
|
|
19
21
|
```json
|
|
20
22
|
{
|
|
@@ -27,6 +29,19 @@ Restart OpenCode.
|
|
|
27
29
|
|
|
28
30
|
To install globally, add the same entry to `~/.config/opencode/opencode.json`.
|
|
29
31
|
|
|
32
|
+
### Codex
|
|
33
|
+
|
|
34
|
+
Install Revela through the Codex Git marketplace:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref main
|
|
38
|
+
codex plugin add revela@revela
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Install from the full repository ref. Do not use a sparse checkout limited to `plugins/revela`; the Codex plugin resolves the shared runtime, built-in designs, and domains from the repository snapshot.
|
|
42
|
+
|
|
43
|
+
Use a release tag instead of `main` when you want a pinned install.
|
|
44
|
+
|
|
30
45
|
## Built-In Designs
|
|
31
46
|
|
|
32
47
|
Revela includes built-in deck designs:
|
|
@@ -55,6 +70,8 @@ Switch designs with:
|
|
|
55
70
|
/revela design --use summit
|
|
56
71
|
```
|
|
57
72
|
|
|
73
|
+
In Codex, ask Revela to list or switch designs; the plugin uses the active design when making decks.
|
|
74
|
+
|
|
58
75
|
## Domains
|
|
59
76
|
|
|
60
77
|
Domains add topic-specific narrative guidance, such as consulting, product, or investor communication. Use them when you want Revela to adapt story framing to a specific context.
|
|
@@ -63,52 +80,15 @@ Domains add topic-specific narrative guidance, such as consulting, product, or i
|
|
|
63
80
|
/revela domain
|
|
64
81
|
```
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
In Codex, ask Revela to list or switch domains; the active domain guides narrative framing during init, research, and story work.
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
## Quick Start
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
/revela init
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
2. Research missing evidence and bind useful findings to claims.
|
|
75
|
-
|
|
76
|
-
```text
|
|
77
|
-
/revela research
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Skip this step if your local materials already provide enough support.
|
|
87
|
+
Start with the local source materials and intent that should ground the communication. Revela identifies the audience, decision, claims, evidence, risks, objections, and gaps that shape the narrative.
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
When evidence is missing, use research to gather findings and bind only the supported parts back to the narrative. Before rendering, read the Story view to inspect the claim flow, evidence support, caveats, and open gaps.
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
/revela story
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Use this to check the audience, decision, claims, evidence, gaps, risks, and objections.
|
|
89
|
-
|
|
90
|
-
4. Generate the HTML deck.
|
|
91
|
-
|
|
92
|
-
```text
|
|
93
|
-
/revela make --deck
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Revela writes the deck under `decks/` and uses the current narrative, deck plan, and active design.
|
|
97
|
-
|
|
98
|
-
5. Review or revise the deck.
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
/revela review --deck
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
See [Review A Deck](#review-a-deck) for Insight and Comment.
|
|
105
|
-
|
|
106
|
-
6. Export if needed.
|
|
107
|
-
|
|
108
|
-
```text
|
|
109
|
-
/revela export --deck pdf decks/example.html
|
|
110
|
-
/revela export --deck pptx decks/example.html
|
|
111
|
-
```
|
|
91
|
+
Make a deck or brief from the canonical narrative when the story is ready to present. Review the artifact with Insight to understand support and traceability, use Comment for targeted changes, and export to PDF or PPTX when you need a shareable file.
|
|
112
92
|
|
|
113
93
|
## Review A Deck
|
|
114
94
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
Revela
|
|
11
|
+
Revela 可在 [OpenCode](https://opencode.ai) 和 Codex 中使用,把来源材料、调研、数据和用户意图转成可信、可追踪、可直接用于决策沟通的 narrative artifact。
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
它的 narrative workspace 会记录生成 brief 或 deck 所需的关键要素:受众、决策目标、论点、论据、资料来源、风险、潜在质疑和待补齐的信息。
|
|
14
14
|
|
|
15
15
|
## 安装
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### OpenCode
|
|
18
|
+
|
|
19
|
+
通过 `opencode.json` 安装 npm package `@cyber-dash-tech/revela`:
|
|
18
20
|
|
|
19
21
|
```json
|
|
20
22
|
{
|
|
@@ -27,6 +29,19 @@ Revela 是一个 [OpenCode](https://opencode.ai) 插件,用来把本地材料
|
|
|
27
29
|
|
|
28
30
|
如果想全局安装,把同样配置写到 `~/.config/opencode/opencode.json`。
|
|
29
31
|
|
|
32
|
+
### Codex
|
|
33
|
+
|
|
34
|
+
通过 Codex Git marketplace 安装 Revela:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref main
|
|
38
|
+
codex plugin add revela@revela
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
请从完整仓库 ref 安装。不要使用仅包含 `plugins/revela` 的 sparse checkout;Codex plugin 会从仓库快照中解析 shared runtime、内置 designs 和 domains。
|
|
42
|
+
|
|
43
|
+
如果需要固定版本,把 `main` 换成 release tag。
|
|
44
|
+
|
|
30
45
|
## 内置设计
|
|
31
46
|
|
|
32
47
|
Revela 内置多个 deck design:
|
|
@@ -55,6 +70,8 @@ Revela 内置多个 deck design:
|
|
|
55
70
|
/revela design --use summit
|
|
56
71
|
```
|
|
57
72
|
|
|
73
|
+
在 Codex 中,可以直接让 Revela 列出或切换 design;生成 deck 时会使用 active design。
|
|
74
|
+
|
|
58
75
|
## Domains
|
|
59
76
|
|
|
60
77
|
Domain 提供特定场景的叙事 guidance,例如 consulting、product 或 investor communication。需要让 Revela 按具体沟通场景调整 story framing 时使用。
|
|
@@ -63,52 +80,15 @@ Domain 提供特定场景的叙事 guidance,例如 consulting、product 或 in
|
|
|
63
80
|
/revela domain
|
|
64
81
|
```
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
在 Codex 中,可以直接让 Revela 列出或切换 domain;active domain 会用于 init、research 和 story 阶段的叙事 framing。
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
## Quick Start
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
/revela init
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
2. 补充缺失证据,并把有效 findings 绑定到 claims。
|
|
75
|
-
|
|
76
|
-
```text
|
|
77
|
-
/revela research
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
如果本地材料已经足够支撑 story,可以跳过这一步。
|
|
87
|
+
从本地来源材料和沟通意图开始,让 Revela 识别受众、决策目标、论点、论据、风险、潜在质疑和信息缺口。
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
证据不足时,用 research 补充 findings,并只把被来源明确支持的部分绑定回 narrative。渲染前先阅读 Story view,检查 claim flow、证据支撑、caveat 和 open gap。
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
/revela story
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
用它检查 audience、decision、claims、evidence、gaps、risks 和 objections。
|
|
89
|
-
|
|
90
|
-
4. 生成 HTML deck。
|
|
91
|
-
|
|
92
|
-
```text
|
|
93
|
-
/revela make --deck
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Revela 会把 deck 写到 `decks/`,并使用当前 narrative、deck plan 和 active design。
|
|
97
|
-
|
|
98
|
-
5. Review 或修改 deck。
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
/revela review --deck
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
详见 [Review Deck](#review-deck)。
|
|
105
|
-
|
|
106
|
-
6. 按需导出。
|
|
107
|
-
|
|
108
|
-
```text
|
|
109
|
-
/revela export --deck pdf decks/example.html
|
|
110
|
-
/revela export --deck pptx decks/example.html
|
|
111
|
-
```
|
|
91
|
+
当 story 已经适合呈现时,从 canonical narrative 生成 deck 或 brief。之后用 Insight 理解 artifact 的支撑关系和可追踪性,用 Comment 做定向修改,需要分享文件时再导出为 PDF 或 PPTX。
|
|
112
92
|
|
|
113
93
|
## Review Deck
|
|
114
94
|
|
package/bin/revela.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
type CommandResult = unknown | Promise<unknown>
|
|
4
|
+
|
|
5
|
+
const [argvCommand, ...args] = process.argv.slice(2)
|
|
6
|
+
const command = process.env.REVELA_CLI_COMMAND || argvCommand
|
|
7
|
+
|
|
8
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
9
|
+
printHelp()
|
|
10
|
+
process.exit(0)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (command === "mcp") {
|
|
14
|
+
await import("../plugins/revela/mcp/revela-server")
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const runtime = await import("../lib/runtime/index")
|
|
18
|
+
const options = parseArgs(args)
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
let result: CommandResult
|
|
22
|
+
if (command === "doctor") result = runtime.doctor(options)
|
|
23
|
+
else if (command === "compile") result = runtime.compileNarrative(options)
|
|
24
|
+
else if (command === "markdown-qa") result = runtime.markdownQa(options)
|
|
25
|
+
else if (command === "deck-plan") result = runtime.readDeckPlan(options)
|
|
26
|
+
else if (command === "deck-foundation") result = runtime.createDeckFoundation(required(options, ["outputPath", "title", "language"]))
|
|
27
|
+
else if (command === "qa") result = runtime.runDeckQa(required(options, ["file"]))
|
|
28
|
+
else if (command === "review-read") result = runtime.reviewDeckRead(required(options, ["file"]))
|
|
29
|
+
else if (command === "export-pdf") result = runtime.exportPdf(required(options, ["file"]))
|
|
30
|
+
else if (command === "export-pptx") result = runtime.exportPptx(required(options, ["file"]))
|
|
31
|
+
else if (command === "design-list") result = runtime.designList()
|
|
32
|
+
else if (command === "design-read") result = runtime.designRead(options)
|
|
33
|
+
else if (command === "design-use") result = runtime.designActivate(required(options, ["name"]))
|
|
34
|
+
else if (command === "domain-list") result = runtime.domainList()
|
|
35
|
+
else if (command === "domain-read") result = runtime.domainRead(options)
|
|
36
|
+
else if (command === "domain-use") result = runtime.domainActivate(required(options, ["name"]))
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Unknown command: ${command}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
process.stdout.write(`${JSON.stringify(await result, null, 2)}\n`)
|
|
42
|
+
} catch (e) {
|
|
43
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseArgs(values: string[]): Record<string, any> {
|
|
49
|
+
const result: Record<string, any> = {}
|
|
50
|
+
for (let i = 0; i < values.length; i++) {
|
|
51
|
+
const arg = values[i]
|
|
52
|
+
if (!arg.startsWith("--")) throw new Error(`Unexpected argument: ${arg}`)
|
|
53
|
+
const key = arg.slice(2)
|
|
54
|
+
const next = values[i + 1]
|
|
55
|
+
if (next === undefined || next.startsWith("--")) {
|
|
56
|
+
result[key] = true
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
result[key] = parseValue(next)
|
|
60
|
+
i++
|
|
61
|
+
}
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseValue(value: string): unknown {
|
|
66
|
+
if (value === "true") return true
|
|
67
|
+
if (value === "false") return false
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function required(input: Record<string, any>, keys: string[]): Record<string, any> {
|
|
72
|
+
const missing = keys.filter((key) => input[key] === undefined || input[key] === "")
|
|
73
|
+
if (missing.length > 0) throw new Error(`Missing required option(s): ${missing.map((key) => `--${key}`).join(", ")}`)
|
|
74
|
+
return input
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function printHelp(): void {
|
|
78
|
+
process.stdout.write(`Revela CLI
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
revela mcp
|
|
82
|
+
revela doctor [--workspaceRoot <path>]
|
|
83
|
+
revela compile [--workspaceRoot <path>]
|
|
84
|
+
revela markdown-qa [--workspaceRoot <path>] [--scope touched|affected|full] [--strictness authoring|readiness|render]
|
|
85
|
+
revela deck-plan [--workspaceRoot <path>]
|
|
86
|
+
revela deck-foundation --outputPath <path> --title <title> --language <tag> [--workspaceRoot <path>] [--designName <name>] [--mode create|repair] [--overwrite true]
|
|
87
|
+
revela qa --file <path> [--workspaceRoot <path>]
|
|
88
|
+
revela review-read --file <path> [--workspaceRoot <path>] [--format json|markdown]
|
|
89
|
+
revela export-pdf --file <path> [--workspaceRoot <path>]
|
|
90
|
+
revela export-pptx --file <path> [--workspaceRoot <path>]
|
|
91
|
+
revela design-list
|
|
92
|
+
revela design-read [--name <design>]
|
|
93
|
+
revela design-use --name <design>
|
|
94
|
+
revela domain-list
|
|
95
|
+
revela domain-read [--name <domain>]
|
|
96
|
+
revela domain-use --name <domain>
|
|
97
|
+
`)
|
|
98
|
+
}
|
package/lib/commands/review.ts
CHANGED
|
@@ -93,11 +93,12 @@ Workflow:
|
|
|
93
93
|
6. If target slide count, audience, language, output purpose, or visual style is unclear, ask the user for the smallest needed confirmation before writing the plan.
|
|
94
94
|
7. Write \`deck-plan/index.md\` and one file per planned slide under \`deck-plan/slides/*.md\` from the planning packet and requirements. The index must identify the chapter structure first: 3-5 chapter headings, each chapter's slide range, and which non-structural slides belong to each chapter. Each slide file must include frontmatter with positive 1-based \`slideIndex\` and \`## Narrative Links\` using plain wikilinks to canonical claim/evidence/risk/objection/gap ids. Include a low-fidelity ASCII/text layout sketch for every slide; do not generate visual images or HTML mockups.
|
|
95
95
|
8. Stop after presenting the plan unless the user already asked to proceed. Ask whether to continue, revise the plan, or run more research. Do not require an Approval block or \`confirmDeckPlan\` gate; \`confirmDeckPlan\` is compatibility/provenance only.
|
|
96
|
-
9. Ask for or confirm visual design only after the narrative deck plan exists.
|
|
97
|
-
10.
|
|
98
|
-
11.
|
|
99
|
-
12.
|
|
100
|
-
13.
|
|
96
|
+
9. Ask for or confirm visual design only after the narrative deck plan exists. For a new deck HTML file, call \`revela-deck-foundation\` first to create the active-design foundation shell; it must not create narrative slide content, choose layouts/components, or read/write \`${DECKS_STATE_FILE}\`.
|
|
97
|
+
10. Fetch active design rules plus required layouts/components with \`revela-designs read\` as needed before patching slides into the foundation shell. Fetch chart rules before ECharts.
|
|
98
|
+
11. Do not update cached \`DECKS.json\` slide specs for plan authoring. Use \`deck-plan/\` files and artifact files as the execution surface.
|
|
99
|
+
12. Call \`revela-decks\` action \`readDeckPlan\` before artifact review or HTML writing; use it to inspect the current deck-plan projection without regenerating it. Treat stale hashes, missing links, or incomplete coverage as advisory diagnostics unless the user asks to stop.
|
|
100
|
+
13. Run artifact diagnostics when useful, but do not treat \`writeReadiness\`, cached slide specs, unconfirmed plans, missing research, or stale coverage as workflow blockers.
|
|
101
|
+
14. Write \`decks/*.html\` when the user chooses to proceed and all deck HTML contract requirements can be satisfied. For new files, patch slide sections between the \`revela-slides\` markers created by \`revela-deck-foundation\`. Generate the artifact chapter by chapter instead of drafting all content slides in one broad pass. Partial decks are allowed during chapter-by-chapter authoring when written slide sections have unique, increasing 1-based \`data-slide-index\` values and valid canvases; do not pad missing planned chapters with filler to match cached \`DECKS.json.slides[]\` length. Keep the HTML file valid after every write, preserve already-written slides, and update one chapter's slide sections at a time.
|
|
101
102
|
15. For each chapter, make every content slide carry a distinct claim, evidence item, comparison, risk, or action. If a chapter lacks enough substance for its allocated slides, merge weak slides or reduce the slide count instead of creating sparse filler.
|
|
102
103
|
16. After each HTML write, the system automatically runs artifact QA before opening Review. If post-write artifact QA reports hard errors, fix them and let QA run again. Review opens only after hard errors pass. Density warnings about thin claim/evidence substance should be reported and improved when useful, but they do not block Review.
|
|
103
104
|
|
|
@@ -142,6 +143,7 @@ Report format before any HTML write:
|
|
|
142
143
|
- Start with \`Deck handoff: <status>\`.
|
|
143
144
|
- Include which deck-plan projection and narrative hash are guiding artifact work.
|
|
144
145
|
- State that \`revela-decks readDeckPlan\` was called and the current \`deck-plan/\` Chapter Writing Batches are being followed.
|
|
146
|
+
- For new HTML files, state that \`revela-deck-foundation\` created the foundation shell and identify the output path/design before slide content is patched.
|
|
145
147
|
- Include the chapter currently being generated and confirm already-written slides are being preserved.
|
|
146
148
|
- If technical artifact checks cannot be satisfied, list those blockers separately from narrative/deck-plan diagnostics.
|
|
147
149
|
- After writing HTML, read the appended \`Artifact QA\` report from the tool output. If it failed, fix hard errors before considering the deck ready for Review.
|
|
@@ -149,6 +151,7 @@ Report format before any HTML write:
|
|
|
149
151
|
Rules:
|
|
150
152
|
- \`compileDeckPlan\` prepares the canonical narrative claim/evidence packet and deck-plan requirements. The LLM authors \`deck-plan/index.md\` and \`deck-plan/slides/*.md\` from that packet and asks the user for page count, audience, language, output purpose, or visual style when unclear.
|
|
151
153
|
- \`deck-plan/\` is the execution blueprint for HTML generation when present. It must be read before writing HTML and followed chapter by chapter; \`DECKS.json.slides[]\` is compatibility/cache data, not the HTML slide-count authority.
|
|
154
|
+
- \`revela-deck-foundation\` is the file-native foundation helper for new deck shells. Use it before adding slides to a new \`decks/*.html\` file; do not use \`DECKS.json\` or \`revela-decks\` to create the shell.
|
|
152
155
|
- Visual intent is part of the deck-plan projection. During HTML generation, satisfy the planned component/visual brief using fetched design components; do not collapse planned visuals into prose-only bullets.
|
|
153
156
|
- Cached deck slide specs in \`DECKS.json\` are legacy projections only. Canonical narrative remains the authority for audience, decision, claims, evidence boundaries, objections, and risks.
|
|
154
157
|
- Cover, Table of Contents, and Closing are mandatory deck structure. TOC chapter headings must match the chapter grouping used for generation.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs"
|
|
2
|
+
import { dirname, isAbsolute, normalize, resolve } from "path"
|
|
3
|
+
import { activeDesign, getDesignSection } from "../design/designs"
|
|
4
|
+
|
|
5
|
+
export type DeckFoundationMode = "create" | "repair"
|
|
6
|
+
export type DeckFoundationStatus = "created" | "updated"
|
|
7
|
+
|
|
8
|
+
export interface CreateDeckFoundationInput {
|
|
9
|
+
workspaceRoot: string
|
|
10
|
+
outputPath: string
|
|
11
|
+
title: string
|
|
12
|
+
language: string
|
|
13
|
+
designName?: string
|
|
14
|
+
mode?: DeckFoundationMode
|
|
15
|
+
overwrite?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CreateDeckFoundationResult {
|
|
19
|
+
ok: true
|
|
20
|
+
outputPath: string
|
|
21
|
+
design: string
|
|
22
|
+
includedSections: string[]
|
|
23
|
+
status: DeckFoundationStatus
|
|
24
|
+
next: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FoundationParts {
|
|
28
|
+
fontLinks: string[]
|
|
29
|
+
cssBlocks: string[]
|
|
30
|
+
scriptBlocks: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SLIDES_START = "<!-- revela-slides:start -->"
|
|
34
|
+
const SLIDES_END = "<!-- revela-slides:end -->"
|
|
35
|
+
|
|
36
|
+
export function createDeckFoundation(input: CreateDeckFoundationInput): CreateDeckFoundationResult {
|
|
37
|
+
const outputPath = normalizeOutputPath(input.outputPath)
|
|
38
|
+
const targetPath = safeWorkspaceFilePath(input.workspaceRoot, outputPath)
|
|
39
|
+
const mode = input.mode ?? "create"
|
|
40
|
+
const canOverwrite = input.overwrite === true || mode === "repair"
|
|
41
|
+
const existed = existsSync(targetPath)
|
|
42
|
+
|
|
43
|
+
if (existed && !canOverwrite) {
|
|
44
|
+
throw new Error(`Deck HTML already exists at ${outputPath}. Pass overwrite=true or mode=repair to replace the foundation shell.`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const design = input.designName || activeDesign()
|
|
48
|
+
const foundation = getDesignSection("foundation", design)
|
|
49
|
+
const parts = parseFoundationParts(foundation)
|
|
50
|
+
if (parts.cssBlocks.length === 0) throw new Error(`Design '${design}' foundation does not include a CSS code block.`)
|
|
51
|
+
if (parts.scriptBlocks.length === 0) throw new Error(`Design '${design}' foundation does not include a SlidePresentation JavaScript code block.`)
|
|
52
|
+
|
|
53
|
+
const html = renderFoundationHtml({
|
|
54
|
+
language: input.language || "en",
|
|
55
|
+
title: input.title || "Revela Deck",
|
|
56
|
+
fontLinks: parts.fontLinks,
|
|
57
|
+
css: parts.cssBlocks.join("\n\n"),
|
|
58
|
+
script: parts.scriptBlocks.map(guardEmptyDeckNavigation).join("\n\n"),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
mkdirSync(dirname(targetPath), { recursive: true })
|
|
62
|
+
writeFileSync(targetPath, html, "utf-8")
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
outputPath,
|
|
67
|
+
design,
|
|
68
|
+
includedSections: [
|
|
69
|
+
"design:foundation",
|
|
70
|
+
parts.fontLinks.length > 0 ? "foundation:font-links" : "foundation:font-links:none",
|
|
71
|
+
"foundation:css",
|
|
72
|
+
"foundation:SlidePresentation",
|
|
73
|
+
],
|
|
74
|
+
status: existed ? "updated" : "created",
|
|
75
|
+
next: [
|
|
76
|
+
"Fetch active design rules before patching slides.",
|
|
77
|
+
"Fetch required layouts and components from the design before adding slide content.",
|
|
78
|
+
"Patch slides between the revela-slides markers chapter by chapter, then run artifact QA.",
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function normalizeOutputPath(outputPath: string): string {
|
|
84
|
+
const trimmed = outputPath.trim()
|
|
85
|
+
if (!trimmed) throw new Error("outputPath is required")
|
|
86
|
+
if (!trimmed.endsWith(".html")) throw new Error("Deck foundation outputPath must end in .html")
|
|
87
|
+
if (isAbsolute(trimmed)) throw new Error("Deck foundation outputPath must be workspace-relative")
|
|
88
|
+
const segments = trimmed.split(/[\\/]+/)
|
|
89
|
+
if (segments.includes("..")) throw new Error("Deck foundation outputPath must not contain parent-directory traversal")
|
|
90
|
+
return normalize(trimmed).replace(/\\/g, "/")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function safeWorkspaceFilePath(workspaceRoot: string, outputPath: string): string {
|
|
94
|
+
const root = resolve(workspaceRoot)
|
|
95
|
+
const target = resolve(root, outputPath)
|
|
96
|
+
if (target !== root && !target.startsWith(`${root}/`)) {
|
|
97
|
+
throw new Error("Deck foundation outputPath must stay inside the workspace")
|
|
98
|
+
}
|
|
99
|
+
return target
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseFoundationParts(foundation: string): FoundationParts {
|
|
103
|
+
const fontLinks = extractFontLinks(foundation)
|
|
104
|
+
const cssBlocks: string[] = []
|
|
105
|
+
const scriptBlocks: string[] = []
|
|
106
|
+
const fenceRe = /```([\w-]*)\n([\s\S]*?)```/g
|
|
107
|
+
let match: RegExpExecArray | null
|
|
108
|
+
|
|
109
|
+
while ((match = fenceRe.exec(foundation)) !== null) {
|
|
110
|
+
const lang = (match[1] || "").toLowerCase()
|
|
111
|
+
const body = match[2].trim()
|
|
112
|
+
if (!body) continue
|
|
113
|
+
if (lang === "css") cssBlocks.push(body)
|
|
114
|
+
if ((lang === "javascript" || lang === "js") && body.includes("class SlidePresentation")) {
|
|
115
|
+
scriptBlocks.push(body)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { fontLinks, cssBlocks, scriptBlocks }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function extractFontLinks(foundation: string): string[] {
|
|
123
|
+
const links: string[] = []
|
|
124
|
+
const seen = new Set<string>()
|
|
125
|
+
const linkRe = /<link\b[^>]*(?:fonts\.googleapis|fonts\.gstatic|rel=["']preconnect["'])[^>]*>/gi
|
|
126
|
+
let match: RegExpExecArray | null
|
|
127
|
+
while ((match = linkRe.exec(foundation)) !== null) {
|
|
128
|
+
const link = match[0].trim()
|
|
129
|
+
if (seen.has(link)) continue
|
|
130
|
+
seen.add(link)
|
|
131
|
+
links.push(link)
|
|
132
|
+
}
|
|
133
|
+
return links
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function guardEmptyDeckNavigation(script: string): string {
|
|
137
|
+
return script.replace(
|
|
138
|
+
/new\s+SlidePresentation\s*\(\s*\)\s*;?/g,
|
|
139
|
+
'if (document.querySelector(".slide")) { new SlidePresentation(); }',
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderFoundationHtml(input: {
|
|
144
|
+
language: string
|
|
145
|
+
title: string
|
|
146
|
+
fontLinks: string[]
|
|
147
|
+
css: string
|
|
148
|
+
script: string
|
|
149
|
+
}): string {
|
|
150
|
+
const fontLinks = input.fontLinks.map((link) => ` ${link}`).join("\n")
|
|
151
|
+
return [
|
|
152
|
+
"<!DOCTYPE html>",
|
|
153
|
+
`<html lang="${escapeAttribute(input.language)}">`,
|
|
154
|
+
"<head>",
|
|
155
|
+
" <meta charset=\"UTF-8\">",
|
|
156
|
+
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
|
|
157
|
+
` <title>${escapeHtml(input.title)}</title>`,
|
|
158
|
+
fontLinks,
|
|
159
|
+
" <style>",
|
|
160
|
+
input.css,
|
|
161
|
+
" </style>",
|
|
162
|
+
"</head>",
|
|
163
|
+
"<body>",
|
|
164
|
+
` ${SLIDES_START}`,
|
|
165
|
+
` ${SLIDES_END}`,
|
|
166
|
+
" <script>",
|
|
167
|
+
input.script,
|
|
168
|
+
" </script>",
|
|
169
|
+
"</body>",
|
|
170
|
+
"</html>",
|
|
171
|
+
"",
|
|
172
|
+
].filter((line) => line !== "").join("\n")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function escapeHtml(value: string): string {
|
|
176
|
+
return value
|
|
177
|
+
.replace(/&/g, "&")
|
|
178
|
+
.replace(/</g, "<")
|
|
179
|
+
.replace(/>/g, ">")
|
|
180
|
+
.replace(/"/g, """)
|
|
181
|
+
.replace(/'/g, "'")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function escapeAttribute(value: string): string {
|
|
185
|
+
return escapeHtml(value.trim() || "en")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function deckFoundationMarkers(): { start: string; end: string } {
|
|
189
|
+
return { start: SLIDES_START, end: SLIDES_END }
|
|
190
|
+
}
|
package/lib/edit/prompt.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface EditCommentPayload extends EditSelectedElementPayload {
|
|
|
26
26
|
comments?: EditCommentDraftPayload[]
|
|
27
27
|
asset?: Record<string, unknown>
|
|
28
28
|
drop?: Record<string, unknown>
|
|
29
|
+
suppressAutomaticArtifactQa?: boolean
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function buildEditPrompt(payload: EditCommentPayload): string {
|
|
@@ -59,6 +60,10 @@ export function buildEditPrompt(payload: EditCommentPayload): string {
|
|
|
59
60
|
asset: payload.asset,
|
|
60
61
|
drop: payload.drop,
|
|
61
62
|
}
|
|
63
|
+
const qaInstruction = payload.suppressAutomaticArtifactQa
|
|
64
|
+
? `- Do not run artifact QA after this edit and do not keep editing just to satisfy post-write QA. The Review UI will refresh from the deck file version change; QA can be run later through an explicit Review, QA, or export workflow.`
|
|
65
|
+
: `- Artifact QA runs automatically after deck writes/patches/edits. It checks deck HTML contract, design component compliance, exact 1920x1080 slide geometry, scrollbars, element overflow, text clipping, and claim/evidence content-density warnings.
|
|
66
|
+
- If the tool result reports hard QA errors, fix them with the smallest targeted patch and let the post-write QA run again. Refine opens automatically only after hard errors pass; warnings such as thin claim/evidence substance do not block opening.`
|
|
62
67
|
|
|
63
68
|
return `The user left a visual edit comment on a Revela slide deck.
|
|
64
69
|
|
|
@@ -95,7 +100,6 @@ Instructions:
|
|
|
95
100
|
- For targeted artifact-level edits, patch ${"`decks/*.html`"} directly. Do not call ${"`revela-decks`"} action ${"`review`"} as a precondition, and do not let ${"`writeReadiness`"}, ${"`planReview`"}, or ${"`slide_plan_unconfirmed`"} block the patch.
|
|
96
101
|
- Do not patch or write ${"`DECKS.json`"} directly. If state must change, use the ${"`revela-decks`"} tool.
|
|
97
102
|
- Apply the edit to ${payload.file} with the smallest targeted HTML patch that satisfies the comment.
|
|
98
|
-
|
|
99
|
-
- If the tool result reports hard QA errors, fix them with the smallest targeted patch and let the post-write QA run again. Refine opens automatically only after hard errors pass; warnings such as thin claim/evidence substance do not block opening.
|
|
103
|
+
${qaInstruction}
|
|
100
104
|
- If the comment is ambiguous, ask one concise clarification question instead of guessing.`
|
|
101
105
|
}
|
package/lib/edit/server.ts
CHANGED
|
@@ -1003,8 +1003,8 @@ export function renderEditorShell(token: string): string {
|
|
|
1003
1003
|
if (status === 'updated') return 'Deck file updated';
|
|
1004
1004
|
if (status === 'stale') return 'Still waiting for deck file update';
|
|
1005
1005
|
if (status === 'failed') return 'Failed to send';
|
|
1006
|
-
if (status === 'sending') return 'Sending to
|
|
1007
|
-
return '
|
|
1006
|
+
if (status === 'sending') return 'Sending to Review agent...';
|
|
1007
|
+
return 'Sent to Review agent';
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
1010
|
function targetFromPointer(event) {
|
package/lib/inspect/prompt.ts
CHANGED
|
@@ -6,9 +6,11 @@ export function buildInspectionPrompt(input: {
|
|
|
6
6
|
projection: InspectionPromptProjection
|
|
7
7
|
language?: string
|
|
8
8
|
comment?: string
|
|
9
|
+
delivery?: "tool" | "json"
|
|
9
10
|
}): string {
|
|
10
11
|
const language = normalizeInspectLanguage(input.language)
|
|
11
12
|
const comment = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : ""
|
|
13
|
+
const delivery = input.delivery ?? "tool"
|
|
12
14
|
return `A user selected slide content in Revela Evidence Inspector. The selection may contain one referenced element, a whole slide, or multiple referenced elements selected with Cmd/Ctrl-click.
|
|
13
15
|
|
|
14
16
|
Target file: ${input.file}
|
|
@@ -20,7 +22,9 @@ Use the structured projection below to produce the final inspector cards. This i
|
|
|
20
22
|
|
|
21
23
|
Language boundary: the selected display language affects only human-readable card copy. Preserve all claim ids, canonical claim ids, evidence binding ids, source paths, findings files, URLs, numbers, quoted/source facts, caveats, artifact ids, and coverage statuses exactly as grounded in the projection. If the display language is Auto, use projection.deck.language when available; otherwise follow the user's/browser context or default to English.
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
${delivery === "json"
|
|
26
|
+
? "Return only a single JSON object that matches the final inspector result schema. Do not wrap it in Markdown. Do not call tools. Do not edit files."
|
|
27
|
+
: "Return the result only by calling the `revela-inspection-result` tool with this request id. Do not answer in chat."}
|
|
24
28
|
|
|
25
29
|
Required card model:
|
|
26
30
|
- User inspect comment: if present, answer it through the Purpose and Source cards first. If it asks about trust, provenance, evidence, factuality, or where a number came from, prioritize Source. If it asks why something is on the slide or what it is doing, prioritize Purpose.
|
package/lib/qa/index.ts
CHANGED
|
@@ -79,6 +79,7 @@ function scanAssetRefs(htmlFilePath: string): LayoutIssue[] {
|
|
|
79
79
|
const ref = raw.trim()
|
|
80
80
|
if (!ref || ref.startsWith("data:") || ref.startsWith("#") || ref.startsWith("mailto:") || ref.startsWith("tel:")) continue
|
|
81
81
|
if (/^https?:\/\//i.test(ref) || ref.startsWith("//")) {
|
|
82
|
+
if (isAllowedRemoteRuntimeRef(ref)) continue
|
|
82
83
|
issues.push({ type: "asset", sub: "remote_url", severity: "error", detail: `Deck HTML references remote asset URL \`${ref}\`. Save network images to workspace assets and reference the local file instead.` })
|
|
83
84
|
continue
|
|
84
85
|
}
|
|
@@ -96,6 +97,17 @@ function scanAssetRefs(htmlFilePath: string): LayoutIssue[] {
|
|
|
96
97
|
return issues
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function isAllowedRemoteRuntimeRef(ref: string): boolean {
|
|
101
|
+
try {
|
|
102
|
+
const url = new URL(ref.startsWith("//") ? `https:${ref}` : ref)
|
|
103
|
+
if (url.hostname === "fonts.googleapis.com" || url.hostname === "fonts.gstatic.com") return true
|
|
104
|
+
if (url.hostname === "cdn.jsdelivr.net" && url.pathname.startsWith("/npm/echarts@")) return true
|
|
105
|
+
} catch {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
99
111
|
function looksLikeImageRef(ref: string): boolean {
|
|
100
112
|
return /\.(?:png|jpe?g|webp|gif|svg)(?:[?#].*)?$/i.test(ref)
|
|
101
113
|
}
|