@cyber-dash-tech/revela 0.18.2 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/README.zh-CN.md +4 -4
- package/lib/commands/review.ts +14 -13
- package/lib/narrative-state/deck-plan-artifact.ts +148 -36
- package/lib/narrative-state/render-plan.ts +41 -11
- package/package.json +1 -1
- package/plugins/revela/.mcp.json +1 -1
- package/plugins/revela/skills/revela-make-deck/SKILL.md +5 -4
- package/skill/SKILL.md +25 -19
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ To install globally, add the same entry to `~/.config/opencode/opencode.json`.
|
|
|
34
34
|
Requirements:
|
|
35
35
|
|
|
36
36
|
- The Codex CLI must be installed and the `codex` command must be available in your shell.
|
|
37
|
-
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.3 mcp` to start the MCP server.
|
|
38
38
|
- For interactive Review actions, `codex exec` must also work because the Review UI uses it for Comment/Apply Fix requests.
|
|
39
39
|
|
|
40
40
|
Optional preflight:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
Install Revela through the Codex Git marketplace:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.3
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.3 mcp` so npm can fetch the published package and its dependencies.
|
|
63
63
|
|
|
64
64
|
You do not need to run `bun install` inside the Codex marketplace clone.
|
|
65
65
|
|
|
@@ -169,7 +169,7 @@ revela, help me init this workspace from the local materials.
|
|
|
169
169
|
revela, research the public evidence and examples needed for this deck.
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
6. Create or update the deck plan before generating HTML so slide order, chapter structure, source links,
|
|
172
|
+
6. Create or update the deck plan before generating HTML so slide order, chapter structure, source links, unresolved inputs, source limitations, and visual intent are explicit.
|
|
173
173
|
|
|
174
174
|
```text
|
|
175
175
|
revela, create or update the deck plan before generating HTML.
|
package/README.zh-CN.md
CHANGED
|
@@ -34,7 +34,7 @@ Revela 可在 [OpenCode](https://opencode.ai) 和 Codex 中使用,把来源材
|
|
|
34
34
|
环境要求:
|
|
35
35
|
|
|
36
36
|
- 需要已安装 Codex CLI,并且 shell 中可以执行 `codex`。
|
|
37
|
-
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.3 mcp` 启动 MCP server。
|
|
38
38
|
- 如果使用 Review UI 的 Comment 或 Apply Fix,需要 `codex exec` 可用。
|
|
39
39
|
|
|
40
40
|
可选的安装前检查:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
通过 Codex Git marketplace 安装 Revela:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.3
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.3 mcp`,由 npm 获取已发布 package 及其 dependencies。
|
|
63
63
|
|
|
64
64
|
不需要在 Codex marketplace clone 里运行 `bun install`。
|
|
65
65
|
|
|
@@ -169,7 +169,7 @@ revela,帮我 init 这个 workspace,先读本地材料。
|
|
|
169
169
|
revela,research 这个 deck 需要的公开证据、案例和 source。
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
-
6. 先创建或更新 deck plan,明确 slide 顺序、章节结构、source links、
|
|
172
|
+
6. 先创建或更新 deck plan,明确 slide 顺序、章节结构、source links、unresolved inputs、source limitations 和 visual intent,再生成 HTML。
|
|
173
173
|
|
|
174
174
|
```text
|
|
175
175
|
revela,生成 HTML 前先 create or update deck plan。
|
package/lib/commands/review.ts
CHANGED
|
@@ -45,7 +45,7 @@ Report format:
|
|
|
45
45
|
- Start with \`Narrative readiness: <status>\`.
|
|
46
46
|
- Include \`Narrative hash: <hash>\` when returned.
|
|
47
47
|
- List diagnostics with issue type, claim text when available, and suggested next action. Keep Markdown QA repair cards separate from compiler diagnostics.
|
|
48
|
-
- If warnings exist, list them after blockers as residual
|
|
48
|
+
- If warnings exist, list them after blockers as residual diagnostics.
|
|
49
49
|
- Keep deck/artifact readiness separate. If the user wants to write or review deck artifacts, tell them to run \`/revela make --deck\`.
|
|
50
50
|
|
|
51
51
|
Rules:
|
|
@@ -74,8 +74,8 @@ export function buildDeckPlanPrompt({
|
|
|
74
74
|
Goal:
|
|
75
75
|
- Build canonical deck-plan.md directly from the user's goal, reviewed local materials, saved research findings, and active design vocabulary.
|
|
76
76
|
- Do not create, read, repair, or require revela-narrative/.
|
|
77
|
-
- Use sourceLinks in deck-plan.md to reference material paths, material review files, research findings, asset paths,
|
|
78
|
-
- Use "unresolved inputs", "research tasks", "source limitations", or "
|
|
77
|
+
- Use sourceLinks in deck-plan.md to reference material paths, material review files, research findings, asset paths, and URLs. These are source links, not narrative graph links.
|
|
78
|
+
- Use "unresolved inputs", "research tasks", "source limitations", or "user review notes"; do not use the product concept of research gaps.
|
|
79
79
|
- Do not write HTML during planning.
|
|
80
80
|
|
|
81
81
|
Current state:
|
|
@@ -88,7 +88,7 @@ Workflow:
|
|
|
88
88
|
3. Ask only for missing high-impact intent: audience, objective, decision/action, language, rough slide count, or source priority.
|
|
89
89
|
4. Read active design inventory/rules before selecting layouts/components.
|
|
90
90
|
5. Write deck-plan.md with deck objective, audience, output format, chapter outline, source authority, unresolved inputs, and an ordered slide plan.
|
|
91
|
-
6. For every slide block,
|
|
91
|
+
6. For every slide block, use a \`---\` slide separator with slide-local metadata, then include \`#### Content Plan\`, \`#### Source Links\`, and \`#### Design Plan\`.
|
|
92
92
|
7. Include Cover, Table of Contents, and Closing slides unless the user explicitly asks for a non-standard artifact.
|
|
93
93
|
8. Run/read deck-plan diagnostics after writing and report technical issues separately from source limitations.
|
|
94
94
|
|
|
@@ -115,7 +115,7 @@ Goal:
|
|
|
115
115
|
- Read the current deck-plan.md projection and generate or update decks/*.html.
|
|
116
116
|
- Do not create, compile, or require revela-narrative/.
|
|
117
117
|
- Use the deck-render prompt mode for design, layout, components, HTML, QA, and export preflight.
|
|
118
|
-
- Preserve source links and
|
|
118
|
+
- Preserve source links, unresolved inputs, source limitations, and user review notes from deck-plan in slide source notes or speaker notes where appropriate.
|
|
119
119
|
|
|
120
120
|
Current state:
|
|
121
121
|
- ${state}
|
|
@@ -126,7 +126,7 @@ Workflow:
|
|
|
126
126
|
2. If deck-plan.md is missing or structurally invalid, stop and tell the user to run /revela plan --deck.
|
|
127
127
|
3. For a new deck, call revela-deck-foundation before adding slide content.
|
|
128
128
|
4. Read active design rules and needed layouts/components before patching HTML.
|
|
129
|
-
5. Generate
|
|
129
|
+
5. Generate one \`htmlWritingBatches\` entry at a time. Each HTML write/edit/apply_patch may add or rewrite at most 5 slide sections. Keep the HTML valid after every write and preserve existing completed slides.
|
|
130
130
|
6. Each slide must have unique increasing 1-based data-slide-index, a 1920x1080 .slide-canvas, no text overflow, and no unsafe remote asset references.
|
|
131
131
|
7. Run Artifact QA after writes and fix hard errors before reporting ready.
|
|
132
132
|
|
|
@@ -152,7 +152,7 @@ export function buildDeckPrompt({
|
|
|
152
152
|
Goal:
|
|
153
153
|
- Treat this as the explicit transition from canonical narrative state to user-directed deck planning.
|
|
154
154
|
- Use the deck-render prompt mode for design, layout, component, HTML, QA, and deck artifact rules.
|
|
155
|
-
- Default behavior is two-stage: first generate or update \`deck-plan.md\` with ordered slide blocks, low-fidelity layout notes, sourceLinks, and
|
|
155
|
+
- Default behavior is two-stage: first generate or update \`deck-plan.md\` with ordered \`---\` slide blocks, low-fidelity layout notes, sourceLinks, unresolved inputs, source limitations, and user review notes, then proceed only when the user chooses to continue.
|
|
156
156
|
- Every deck plan must include Cover, Table of Contents, and Closing slides. The TOC must show 3-5 chapter headings that match the deck's slide groups.
|
|
157
157
|
- Do not write or overwrite \`decks/*.html\` until the user chooses to proceed from the current deck-plan projection.
|
|
158
158
|
- Do not treat legacy \`writeReadiness.status\`, old review snapshots, approval fields, or existing HTML decks as workflow permission.
|
|
@@ -169,7 +169,7 @@ Workflow:
|
|
|
169
169
|
4. Call \`revela-decks\` action \`compileDeckPlan\`. This returns a claim/evidence planning packet plus deck-plan authoring requirements; it must not write HTML and does not generate the final slide list. Do not infer render structure from \`DECKS.json.slides[]\`.
|
|
170
170
|
5. If \`compileDeckPlan\` returns \`skipped\`, report the reason and ask the user whether to continue manually, repair narrative files, or provide missing intent.
|
|
171
171
|
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.
|
|
172
|
-
7. Write \`deck-plan.md\` directly from the planning packet and requirements. Do not use structured upsert tools for normal plan authoring. It 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 block must
|
|
172
|
+
7. Write \`deck-plan.md\` directly from the planning packet and requirements. Do not use structured upsert tools for normal plan authoring. It 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 block must use a \`---\` separator with positive 1-based \`slideIndex\`, layout, components, and then \`#### Content Plan\`, \`#### Source Links\` for materials/findings/assets/URLs, and \`#### Design Plan\` with low-fidelity render notes. Do not generate visual images or HTML mockups during planning.
|
|
173
173
|
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.
|
|
174
174
|
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}\`.
|
|
175
175
|
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.
|
|
@@ -178,6 +178,7 @@ Workflow:
|
|
|
178
178
|
13. Run artifact diagnostics when useful, but do not treat \`writeReadiness\`, cached slide specs, unconfirmed plans, missing research, or stale coverage as workflow blockers.
|
|
179
179
|
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.
|
|
180
180
|
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.
|
|
181
|
+
15a. Use the current \`htmlWritingBatches\` entry as the HTML write boundary. Each HTML write/edit/apply_patch may add or rewrite at most 5 slide sections.
|
|
181
182
|
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.
|
|
182
183
|
|
|
183
184
|
Deck plan report format:
|
|
@@ -189,7 +190,7 @@ Deck plan report format:
|
|
|
189
190
|
- Include the required Source Authority and remind that \`DECKS.json.slides[]\` is cache/compatibility data, not the render contract.
|
|
190
191
|
- Include \`Required structure: Cover + Table of Contents + Closing\` and do not omit any of those slides.
|
|
191
192
|
- Include a \`Chapters\` section before the slide list. It must list 3-5 TOC headings, their slide ranges, and the non-structural slides assigned to each chapter.
|
|
192
|
-
- For every slide block, include: slide index, title, purpose, narrative role, low-fidelity layout note, layout, components, sourceLinks, visual intent, visual brief,
|
|
193
|
+
- For every slide block, include: slide index, title, purpose, narrative role, low-fidelity layout note, layout, components, sourceLinks, visual intent, visual brief, unresolved inputs, source limitations, user review notes, and render notes.
|
|
193
194
|
- Use this sketch style or similarly simple ASCII boxes:
|
|
194
195
|
|
|
195
196
|
\`\`\`text
|
|
@@ -213,7 +214,7 @@ Supporting claims:
|
|
|
213
214
|
Evidence bindings:
|
|
214
215
|
Visual intent:
|
|
215
216
|
Visual brief:
|
|
216
|
-
|
|
217
|
+
Unresolved inputs / source limitations / user review notes:
|
|
217
218
|
\`\`\`
|
|
218
219
|
- End by asking the user whether to proceed to HTML, revise the plan, or run more research.
|
|
219
220
|
|
|
@@ -222,7 +223,7 @@ Report format before any HTML write:
|
|
|
222
223
|
- Include which deck-plan projection and narrative hash are guiding artifact work.
|
|
223
224
|
- State that \`revela-decks readDeckPlan\` was called and the current \`deck-plan.md\` slide order/chapter groups are being followed.
|
|
224
225
|
- For new HTML files, state that \`revela-deck-foundation\` created the foundation shell and identify the output path/design before slide content is patched.
|
|
225
|
-
- Include the
|
|
226
|
+
- Include the current \`htmlWritingBatches\` entry, its slide indexes, and confirm already-written slides are being preserved.
|
|
226
227
|
- If technical artifact checks cannot be satisfied, list those blockers separately from narrative/deck-plan diagnostics.
|
|
227
228
|
- 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.
|
|
228
229
|
|
|
@@ -233,7 +234,7 @@ Rules:
|
|
|
233
234
|
- 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.
|
|
234
235
|
- 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.
|
|
235
236
|
- Cover, Table of Contents, and Closing are mandatory deck structure. TOC chapter headings must match the chapter grouping used for generation.
|
|
236
|
-
- Do not generate the complete deck content in one broad pass. Work
|
|
237
|
+
- Do not generate the complete deck content in one broad pass. Work one \`htmlWritingBatches\` entry at a time while keeping the artifact valid after each write.
|
|
237
238
|
- Applying evidence candidates or rewriting canonical claims requires explicit user instruction.
|
|
238
239
|
- If the user requests slide order, layout, component, or visual-intent changes that do not alter meaning, update only \`deck-plan.md\` or artifact-level plan content.
|
|
239
240
|
- If the user requests claim, evidence, caveat, decision, or recommendation meaning changes, update canonical narrative first, then report alignment diagnostics before compiling a new deck plan.
|
|
@@ -295,7 +296,7 @@ Technical blockers only:
|
|
|
295
296
|
Report format:
|
|
296
297
|
- Start with \`Artifact diagnostics: <status>\`.
|
|
297
298
|
- If technically blocked, list each blocker with file/slide when available, issue type, and smallest repair.
|
|
298
|
-
- If warnings exist but no technical blocker exists, say the user can proceed and note residual
|
|
299
|
+
- If warnings exist but no technical blocker exists, say the user can proceed and note residual diagnostics.
|
|
299
300
|
- Include coverage-driven make diagnostics when returned: whether the active deck artifact coverage is current/stale/partial/missing, which required claims are missing, which claims are affected, and the next command/action recommended by the tool.
|
|
300
301
|
- 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.
|
|
301
302
|
- Do not invent evidence or silently downgrade blockers. Use the tool result as authoritative.
|
|
@@ -14,6 +14,7 @@ export const DECK_PLAN_INDEX_PATH = "deck-plan/index.md"
|
|
|
14
14
|
export const DECK_PLAN_SLIDES_DIR = "deck-plan/slides"
|
|
15
15
|
export const LEGACY_DECK_PLAN_ARTIFACT_PATH = "decks/deck-plan.md"
|
|
16
16
|
export const DECK_PLAN_ARTIFACT_PATH = DECK_PLAN_MARKDOWN_PATH
|
|
17
|
+
export const MAX_HTML_SLIDES_PER_BATCH = 5
|
|
17
18
|
|
|
18
19
|
export interface DeckPlanArtifactInput {
|
|
19
20
|
deck: DeckSpec
|
|
@@ -69,11 +70,21 @@ export interface DeckPlanProjection {
|
|
|
69
70
|
designName?: string
|
|
70
71
|
outputPath?: string
|
|
71
72
|
slides: DeckPlanSlideProjection[]
|
|
73
|
+
htmlWritingBatches: DeckPlanHtmlWritingBatch[]
|
|
74
|
+
htmlWritingInstruction: string
|
|
72
75
|
graphNodes: Array<{ id: string; type: WorkspaceGraphNodeType; file: string }>
|
|
73
76
|
graphRelations: VaultRelation[]
|
|
74
77
|
diagnostics: DeckPlanProjectionDiagnostic[]
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
export interface DeckPlanHtmlWritingBatch {
|
|
81
|
+
label: string
|
|
82
|
+
chapterTitle: string
|
|
83
|
+
slideIndexes: number[]
|
|
84
|
+
maxSlides: number
|
|
85
|
+
instructions: string
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
export interface DeckPlanSlideProjection {
|
|
78
89
|
path: string
|
|
79
90
|
absolutePath: string
|
|
@@ -283,6 +294,7 @@ export function readDeckPlanProjection(workspaceRoot: string, expected?: { narra
|
|
|
283
294
|
file: slide.path,
|
|
284
295
|
source: "inline" as const,
|
|
285
296
|
})))
|
|
297
|
+
const htmlWritingBatches = buildHtmlWritingBatches(slides)
|
|
286
298
|
return {
|
|
287
299
|
path,
|
|
288
300
|
absolutePath,
|
|
@@ -294,6 +306,8 @@ export function readDeckPlanProjection(workspaceRoot: string, expected?: { narra
|
|
|
294
306
|
designName: stringField(parsed.frontmatter, "designName") || stringField(parsed.frontmatter, "design"),
|
|
295
307
|
outputPath: stringField(parsed.frontmatter, "outputPath") || stringField(parsed.frontmatter, "output"),
|
|
296
308
|
slides,
|
|
309
|
+
htmlWritingBatches,
|
|
310
|
+
htmlWritingInstruction: htmlWritingInstruction(),
|
|
297
311
|
graphNodes,
|
|
298
312
|
graphRelations,
|
|
299
313
|
diagnostics,
|
|
@@ -470,31 +484,35 @@ function writeDeckPlanSingleFile(workspaceRoot: string, input: {
|
|
|
470
484
|
|
|
471
485
|
function renderDeckPlanSlideBlock(slide: DeckPlanSlideProjection, visualIntent?: DeckPlanSlideUpsertInput["visualIntent"]): string {
|
|
472
486
|
const lines: string[] = []
|
|
473
|
-
lines.push(
|
|
474
|
-
lines.push("")
|
|
475
|
-
lines.push(
|
|
476
|
-
lines.push(
|
|
477
|
-
lines.push(
|
|
478
|
-
lines.push(
|
|
479
|
-
lines.push(
|
|
480
|
-
lines.push(
|
|
481
|
-
lines.push("")
|
|
482
|
-
lines.push("
|
|
487
|
+
lines.push("---")
|
|
488
|
+
lines.push(`slideIndex: ${slide.slideIndex ?? ""}`)
|
|
489
|
+
lines.push(`id: ${slide.id}`)
|
|
490
|
+
lines.push(`title: ${yamlScalar(slide.title)}`)
|
|
491
|
+
lines.push(`chapter: ${yamlScalar(slide.chapter || "Unassigned")}`)
|
|
492
|
+
lines.push(`role: ${yamlScalar(slide.narrativeRole || "Not specified")}`)
|
|
493
|
+
lines.push(`structural: ${slide.structural ? "true" : "false"}`)
|
|
494
|
+
lines.push(`layout: ${slide.layout || "unspecified"}`)
|
|
495
|
+
lines.push(`components: ${slide.components.join(", ") || "none"}`)
|
|
496
|
+
lines.push("---")
|
|
483
497
|
lines.push("")
|
|
484
|
-
|
|
485
|
-
else lines.push("- Brief: Not specified.")
|
|
498
|
+
lines.push("#### Content Plan")
|
|
486
499
|
lines.push("")
|
|
487
|
-
lines.push(
|
|
500
|
+
lines.push(`- Message: ${slide.narrativeRole || "Not specified."}`)
|
|
501
|
+
lines.push(`- Role: ${slide.narrativeRole || "Not specified"}`)
|
|
502
|
+
lines.push("- Speaker notes: Not specified.")
|
|
488
503
|
lines.push("")
|
|
489
|
-
for (const component of slide.componentPlan) lines.push(renderComponentPlanMarkdown(component, 5))
|
|
490
504
|
lines.push("#### Source Links")
|
|
491
505
|
lines.push("")
|
|
492
506
|
lines.push(renderSourceLinksMarkdown(slide.sourceLinks))
|
|
493
507
|
lines.push("")
|
|
494
|
-
lines.push("####
|
|
508
|
+
lines.push("#### Design Plan")
|
|
495
509
|
lines.push("")
|
|
496
|
-
|
|
497
|
-
|
|
510
|
+
lines.push(`- Layout: ${slide.layout || "unspecified"}`)
|
|
511
|
+
lines.push(`- Components: ${slide.components.join(", ") || "none"}`)
|
|
512
|
+
lines.push("- Visual intent:")
|
|
513
|
+
lines.push(...indentMultiline(visualIntent ? renderVisualIntent(visualIntent) : "- Brief: Not specified."))
|
|
514
|
+
lines.push("")
|
|
515
|
+
for (const component of slide.componentPlan) lines.push(renderComponentPlanMarkdown(component, 5))
|
|
498
516
|
lines.push("")
|
|
499
517
|
return lines.join("\n")
|
|
500
518
|
}
|
|
@@ -525,7 +543,6 @@ function renderSourceLinksMarkdown(sourceLinks: DeckPlanSourceLinks): string {
|
|
|
525
543
|
["Findings", sourceLinks.findings],
|
|
526
544
|
["Assets", sourceLinks.assets],
|
|
527
545
|
["URLs", sourceLinks.urls],
|
|
528
|
-
["Caveats", sourceLinks.caveats],
|
|
529
546
|
] as const) {
|
|
530
547
|
lines.push(`${label}:`)
|
|
531
548
|
if (values.length === 0) lines.push("- None.")
|
|
@@ -575,6 +592,8 @@ function readDeckPlanSlideFiles(workspaceRoot: string, knownNodeIds?: Set<string
|
|
|
575
592
|
}
|
|
576
593
|
|
|
577
594
|
function readDeckPlanSlidesFromSingleFile(workspaceRoot: string, absolutePath: string, markdown: string, knownNodeIds?: Set<string>): DeckPlanSlideProjection[] {
|
|
595
|
+
const slideBlocks = readDeckPlanSeparatorSlidesFromSingleFile(workspaceRoot, absolutePath, markdown, knownNodeIds)
|
|
596
|
+
if (slideBlocks.length > 0) return slideBlocks
|
|
578
597
|
const path = relativePath(workspaceRoot, absolutePath)
|
|
579
598
|
const body = parseVaultFrontmatter(markdown).body
|
|
580
599
|
const matches = [...body.matchAll(/^[ \t]*###\s+Slide\s+(\d+)\s+(?:—|-)\s+(.+?)\s*$/gm)]
|
|
@@ -620,6 +639,58 @@ function readDeckPlanSlidesFromSingleFile(workspaceRoot: string, absolutePath: s
|
|
|
620
639
|
return slides.sort((a, b) => (a.slideIndex ?? Number.MAX_SAFE_INTEGER) - (b.slideIndex ?? Number.MAX_SAFE_INTEGER))
|
|
621
640
|
}
|
|
622
641
|
|
|
642
|
+
function readDeckPlanSeparatorSlidesFromSingleFile(workspaceRoot: string, absolutePath: string, markdown: string, knownNodeIds?: Set<string>): DeckPlanSlideProjection[] {
|
|
643
|
+
const path = relativePath(workspaceRoot, absolutePath)
|
|
644
|
+
const body = parseVaultFrontmatter(markdown).body
|
|
645
|
+
const slidesHeading = /^[ \t]*##\s+Slides\s*$/mi.exec(body)
|
|
646
|
+
if (!slidesHeading || slidesHeading.index === undefined) return []
|
|
647
|
+
const headingEnd = body.indexOf("\n", slidesHeading.index)
|
|
648
|
+
const slidesStart = headingEnd === -1 ? slidesHeading.index + slidesHeading[0].length : headingEnd + 1
|
|
649
|
+
const rest = body.slice(slidesStart)
|
|
650
|
+
const nextSection = rest.search(/^[ \t]*##\s+(?!#)/m)
|
|
651
|
+
const slidesRegion = nextSection === -1 ? rest : rest.slice(0, nextSection)
|
|
652
|
+
const metadataMatches = [...slidesRegion.matchAll(/^[ \t]*---[ \t]*\n[\s\S]*?\n[ \t]*---[ \t]*(?:\n|$)/gm)]
|
|
653
|
+
const slides: DeckPlanSlideProjection[] = []
|
|
654
|
+
for (let i = 0; i < metadataMatches.length; i++) {
|
|
655
|
+
const match = metadataMatches[i]
|
|
656
|
+
const start = match.index ?? 0
|
|
657
|
+
const next = i + 1 < metadataMatches.length ? metadataMatches[i + 1].index ?? slidesRegion.length : slidesRegion.length
|
|
658
|
+
const block = slidesRegion.slice(start, next).trim()
|
|
659
|
+
const parsed = parseVaultFrontmatter(block)
|
|
660
|
+
const slideIndex = numberField(parsed.frontmatter, "slideIndex")
|
|
661
|
+
const title = stringField(parsed.frontmatter, "title") || firstHeading(parsed.body) || `Slide ${slideIndex ?? i + 1}`
|
|
662
|
+
const fields = parseSlideBlockFields(parsed.body)
|
|
663
|
+
const sourceLinks = normalizeSourceLinks(parseDeckPlanSourceLinks(singleFileSubsection(block, "Source Links")))
|
|
664
|
+
const narrativeLinks = parseDeckPlanNarrativeLinks(singleFileSubsection(block, "Narrative Links") || block, knownNodeIds)
|
|
665
|
+
const links = sourceLinksToNarrativeLinks(sourceLinks, narrativeLinks)
|
|
666
|
+
const componentPlan = parseDeckPlanComponentPlan(singleFileSubsection(block, "Component Plan") || singleFileSubsection(block, "Design Plan"))
|
|
667
|
+
slides.push({
|
|
668
|
+
path,
|
|
669
|
+
absolutePath,
|
|
670
|
+
id: stringField(parsed.frontmatter, "id") || fields.id || `slide-${slugify(title)}`,
|
|
671
|
+
slideIndex,
|
|
672
|
+
title,
|
|
673
|
+
chapter: stringField(parsed.frontmatter, "chapter") || fields.chapter || "",
|
|
674
|
+
layout: stringField(parsed.frontmatter, "layout") || fields.layout || "",
|
|
675
|
+
components: arrayField(parsed.frontmatter, "components").length > 0 ? arrayField(parsed.frontmatter, "components") : parseCsv(fields.components || componentPlan.map((component) => component.name).join(", ")),
|
|
676
|
+
componentPlan,
|
|
677
|
+
structural: booleanField(parsed.frontmatter, "structural", fields.structural === "true" || fields.structural === "yes"),
|
|
678
|
+
narrativeRole: stringField(parsed.frontmatter, "role") || stringField(parsed.frontmatter, "narrativeRole") || fields.role || fields.narrativeRole || "",
|
|
679
|
+
markdown: block,
|
|
680
|
+
frontmatter: parsed.frontmatter,
|
|
681
|
+
sections: parseMarkdownSections(block),
|
|
682
|
+
links,
|
|
683
|
+
sourceLinks,
|
|
684
|
+
caveats: uniqueStrings([...parseBulletText(singleFileSubsection(block, "Caveats")), ...sourceLinks.caveats]),
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
return slides.sort((a, b) => (a.slideIndex ?? Number.MAX_SAFE_INTEGER) - (b.slideIndex ?? Number.MAX_SAFE_INTEGER))
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function firstHeading(markdown: string): string {
|
|
691
|
+
return /^#{1,6}\s+(.+?)\s*$/m.exec(markdown)?.[1]?.trim() ?? ""
|
|
692
|
+
}
|
|
693
|
+
|
|
623
694
|
function parseSlideBlockFields(block: string): Record<string, string> {
|
|
624
695
|
const fields: Record<string, string> = {}
|
|
625
696
|
for (const rawLine of block.split(/\r?\n/)) {
|
|
@@ -787,9 +858,53 @@ function deckPlanIndexDiagnostics(slides: DeckPlanSlideProjection[]): DeckPlanPr
|
|
|
787
858
|
return diagnostics
|
|
788
859
|
}
|
|
789
860
|
|
|
861
|
+
function buildHtmlWritingBatches(slides: DeckPlanSlideProjection[]): DeckPlanHtmlWritingBatch[] {
|
|
862
|
+
const ordered = slides
|
|
863
|
+
.filter((slide) => Number.isInteger(slide.slideIndex) && (slide.slideIndex ?? 0) > 0)
|
|
864
|
+
.sort((a, b) => (a.slideIndex ?? Number.MAX_SAFE_INTEGER) - (b.slideIndex ?? Number.MAX_SAFE_INTEGER))
|
|
865
|
+
const chapterGroups: Array<{ chapterTitle: string; slideIndexes: number[] }> = []
|
|
866
|
+
for (const slide of ordered) {
|
|
867
|
+
const chapterTitle = slide.chapter || "Unassigned"
|
|
868
|
+
const current = chapterGroups[chapterGroups.length - 1]
|
|
869
|
+
if (current && current.chapterTitle === chapterTitle) current.slideIndexes.push(slide.slideIndex!)
|
|
870
|
+
else chapterGroups.push({ chapterTitle, slideIndexes: [slide.slideIndex!] })
|
|
871
|
+
}
|
|
872
|
+
const batches: DeckPlanHtmlWritingBatch[] = []
|
|
873
|
+
for (const group of chapterGroups) {
|
|
874
|
+
const chunks = chunkNumbers(group.slideIndexes, MAX_HTML_SLIDES_PER_BATCH)
|
|
875
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
876
|
+
const chunk = chunks[index]
|
|
877
|
+
const chapterSuffix = chunks.length > 1 ? ` part ${index + 1}` : ""
|
|
878
|
+
const label = batches.length === 0
|
|
879
|
+
? `Initial shell and ${group.chapterTitle}${chapterSuffix}`
|
|
880
|
+
: `${group.chapterTitle}${chapterSuffix}`
|
|
881
|
+
batches.push({
|
|
882
|
+
label,
|
|
883
|
+
chapterTitle: group.chapterTitle,
|
|
884
|
+
slideIndexes: chunk,
|
|
885
|
+
maxSlides: MAX_HTML_SLIDES_PER_BATCH,
|
|
886
|
+
instructions: batches.length === 0
|
|
887
|
+
? `Create or update the foundation if needed, then write only slide sections ${formatSlideRange(chunk)}. Do not add or rewrite more than ${MAX_HTML_SLIDES_PER_BATCH} slide sections in this write.`
|
|
888
|
+
: `Patch only slide sections ${formatSlideRange(chunk)}, preserve previously written slides, and keep the file valid after the patch. Do not add or rewrite more than ${MAX_HTML_SLIDES_PER_BATCH} slide sections in this write.`,
|
|
889
|
+
})
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return batches
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function chunkNumbers(values: number[], size: number): number[][] {
|
|
896
|
+
const chunks: number[][] = []
|
|
897
|
+
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size))
|
|
898
|
+
return chunks
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function htmlWritingInstruction(): string {
|
|
902
|
+
return `Before every HTML write/edit/apply_patch, follow htmlWritingBatches and add or rewrite at most ${MAX_HTML_SLIDES_PER_BATCH} <section class="slide"> blocks. Run Artifact QA after each batch before continuing.`
|
|
903
|
+
}
|
|
904
|
+
|
|
790
905
|
function slideDiagnostics(slide: DeckPlanSlideProjection, knownNodeIds?: Set<string>): DeckPlanProjectionDiagnostic[] {
|
|
791
906
|
const diagnostics: DeckPlanProjectionDiagnostic[] = []
|
|
792
|
-
if (!slide.structural && slide.
|
|
907
|
+
if (!slide.structural && linksCount(slide.sourceLinks) === 0) diagnostics.push({ severity: "warning", code: "slide_source_link_missing", message: `Non-structural deck-plan slide ${slide.id} has no material, finding, asset, or URL source link.`, file: slide.path, nodeId: slide.id })
|
|
793
908
|
if (!slide.layout) diagnostics.push({ severity: "warning", code: "slide_layout_missing", message: `Deck-plan slide ${slide.id} is missing a layout.`, file: slide.path, nodeId: slide.id })
|
|
794
909
|
if (slide.components.length === 0) diagnostics.push({ severity: "warning", code: "slide_components_missing", message: `Deck-plan slide ${slide.id} has no component names in frontmatter.`, file: slide.path, nodeId: slide.id })
|
|
795
910
|
if (slide.componentPlan.length === 0) diagnostics.push({ severity: "warning", code: "slide_component_plan_missing", message: `Deck-plan slide ${slide.id} is missing structured ## Component Plan entries.`, file: slide.path, nodeId: slide.id })
|
|
@@ -847,7 +962,7 @@ function validateDeckPlanSlideUpsert(input: DeckPlanSlideUpsertInput, options: {
|
|
|
847
962
|
const visual = normalizeVisualIntent(input.visualIntent)
|
|
848
963
|
if (visual.component && !componentNames.has(visual.component)) diagnostics.push(errorDiagnostic("slide_visual_component_missing", `visualIntent.component '${visual.component}' is not present in component plan.`, nodeId))
|
|
849
964
|
const sourceLinks = sourceLinksForInput(input)
|
|
850
|
-
if (!input.structural && linksCount(sourceLinks) === 0) diagnostics.push({ severity: "warning", code: "slide_source_link_missing", message: "Non-structural slides should include at least one material, finding, asset,
|
|
965
|
+
if (!input.structural && linksCount(sourceLinks) === 0) diagnostics.push({ severity: "warning", code: "slide_source_link_missing", message: "Non-structural slides should include at least one material, finding, asset, or URL source link.", nodeId })
|
|
851
966
|
return diagnostics
|
|
852
967
|
}
|
|
853
968
|
|
|
@@ -887,7 +1002,7 @@ function validateComponentInput(component: DeckPlanSlideUpsertComponentInput, co
|
|
|
887
1002
|
}
|
|
888
1003
|
|
|
889
1004
|
function linksCount(sourceLinks: DeckPlanSourceLinks): number {
|
|
890
|
-
return sourceLinks.materials.length + sourceLinks.findings.length + sourceLinks.assets.length + sourceLinks.urls.length
|
|
1005
|
+
return sourceLinks.materials.length + sourceLinks.findings.length + sourceLinks.assets.length + sourceLinks.urls.length
|
|
891
1006
|
}
|
|
892
1007
|
|
|
893
1008
|
function errorDiagnostic(code: string, message: string, nodeId?: string): DeckPlanProjectionDiagnostic {
|
|
@@ -1017,12 +1132,6 @@ function renderDeckPlanSlideMarkdown(input: DeckPlanSlideUpsertInput & { id: str
|
|
|
1017
1132
|
lines.push("")
|
|
1018
1133
|
}
|
|
1019
1134
|
lines.push(renderSourceLinksMarkdown(sourceLinksForInput(input)).replace(/^####/gm, "##"))
|
|
1020
|
-
lines.push("## Caveats")
|
|
1021
|
-
lines.push("")
|
|
1022
|
-
const caveats = input.caveats?.filter((item) => item.trim()) ?? []
|
|
1023
|
-
if (caveats.length === 0) lines.push("- None.")
|
|
1024
|
-
else for (const caveat of caveats) lines.push(`- ${caveat.trim()}`)
|
|
1025
|
-
lines.push("")
|
|
1026
1135
|
return lines.join("\n")
|
|
1027
1136
|
}
|
|
1028
1137
|
|
|
@@ -1083,10 +1192,10 @@ function renderMinimalDeckPlanIndex(narrativeHash: string | undefined, slides: D
|
|
|
1083
1192
|
if (evidenceIds.length === 0) lines.push("- No evidence links planned yet.")
|
|
1084
1193
|
else for (const id of evidenceIds) lines.push(`- [[${id}]]`)
|
|
1085
1194
|
lines.push("")
|
|
1086
|
-
lines.push("##
|
|
1195
|
+
lines.push("## Source Limitations")
|
|
1087
1196
|
lines.push("")
|
|
1088
1197
|
const boundaryIds = uniqueStrings(slides.flatMap((slide) => slide.links.filter((link) => link.relation === "addresses_risk" || link.relation === "answers_objection" || link.relation === "mentions_gap").map((link) => link.id)))
|
|
1089
|
-
if (boundaryIds.length === 0) lines.push("- No risk, objection, or gap links planned
|
|
1198
|
+
if (boundaryIds.length === 0) lines.push("- No legacy risk, objection, or gap links planned.")
|
|
1090
1199
|
else for (const id of boundaryIds) lines.push(`- [[${id}]]`)
|
|
1091
1200
|
lines.push("")
|
|
1092
1201
|
lines.push("## Chapter Writing Batches")
|
|
@@ -1130,8 +1239,8 @@ function booleanField(frontmatter: Record<string, string | string[] | boolean>,
|
|
|
1130
1239
|
|
|
1131
1240
|
function arrayField(frontmatter: Record<string, string | string[] | boolean>, key: string): string[] {
|
|
1132
1241
|
const value = frontmatter[key]
|
|
1133
|
-
if (Array.isArray(value)) return value.map((item) => item.trim()).filter(
|
|
1134
|
-
if (typeof value === "string" && value.trim()) return value.split(",").map((item) => item.trim()).filter(
|
|
1242
|
+
if (Array.isArray(value)) return value.map((item) => item.trim()).filter((item) => item && item.toLowerCase() !== "none")
|
|
1243
|
+
if (typeof value === "string" && value.trim()) return value.split(",").map((item) => item.trim()).filter((item) => item && item.toLowerCase() !== "none")
|
|
1135
1244
|
return []
|
|
1136
1245
|
}
|
|
1137
1246
|
|
|
@@ -1265,8 +1374,8 @@ function renderDeckPlanMarkdown(input: DeckPlanArtifactInput): string {
|
|
|
1265
1374
|
lines.push("")
|
|
1266
1375
|
lines.push("- Write one `<section class=\"slide\" data-slide-index=\"N\">` per planned slide in the completed deck, using positive 1-based slide indexes that are unique and strictly increase in DOM order. Partial chapter-by-chapter drafts may contain only the written prefix/range.")
|
|
1267
1376
|
lines.push("- Keep every rendered slide exactly 1920x1080px with no page-level scrollbars or hidden overflow.")
|
|
1268
|
-
lines.push("- Preserve claim-led chapters, visual intent, evidence ids, source trace,
|
|
1269
|
-
lines.push(
|
|
1377
|
+
lines.push("- Preserve claim-led chapters, visual intent, evidence ids, source trace, source limitations, unresolved inputs, and user review notes.")
|
|
1378
|
+
lines.push(`- Generate HTML in the listed writing batches; do not add or rewrite more than ${MAX_HTML_SLIDES_PER_BATCH} slide sections in one write or patch.`)
|
|
1270
1379
|
lines.push("")
|
|
1271
1380
|
lines.push("## Chapter Map")
|
|
1272
1381
|
lines.push("")
|
|
@@ -1284,14 +1393,17 @@ function renderDeckPlanMarkdown(input: DeckPlanArtifactInput): string {
|
|
|
1284
1393
|
}
|
|
1285
1394
|
lines.push("## Chapter Writing Batches")
|
|
1286
1395
|
lines.push("")
|
|
1287
|
-
lines.push(
|
|
1396
|
+
lines.push(`Use these batches for HTML generation. Each batch is capped at ${MAX_HTML_SLIDES_PER_BATCH} slide sections. Keep the HTML valid after every batch and preserve previously written slides.`)
|
|
1288
1397
|
lines.push("")
|
|
1289
1398
|
if (input.renderPlan) {
|
|
1290
|
-
for (const batch of input.renderPlan.chapterWritingBatches) lines.push(`- ${batch.label}: ${batch.chapterTitle}, slides ${formatSlideRange(batch.slideIndexes)}. ${batch.instructions}`)
|
|
1399
|
+
for (const batch of input.renderPlan.chapterWritingBatches) lines.push(`- ${batch.label}: ${batch.chapterTitle}, slides ${formatSlideRange(batch.slideIndexes)}; max ${batch.maxSlides} slides. ${batch.instructions}`)
|
|
1291
1400
|
} else {
|
|
1292
1401
|
input.chapters.forEach((chapter, index) => {
|
|
1293
1402
|
const prefix = index === 0 ? "Initial shell and first chapter" : `Chapter batch ${index + 1}`
|
|
1294
|
-
|
|
1403
|
+
for (const [chunkIndex, chunk] of chunkNumbers(chapter.slideIndexes, MAX_HTML_SLIDES_PER_BATCH).entries()) {
|
|
1404
|
+
const suffix = chunkIndex === 0 ? "" : ` part ${chunkIndex + 1}`
|
|
1405
|
+
lines.push(`- ${prefix}${suffix}: ${chapter.title}, slides ${formatSlideRange(chunk)}; max ${MAX_HTML_SLIDES_PER_BATCH} slides.`)
|
|
1406
|
+
}
|
|
1295
1407
|
})
|
|
1296
1408
|
}
|
|
1297
1409
|
if (input.renderPlan) {
|
|
@@ -93,6 +93,7 @@ export interface RenderPlanWritingBatch {
|
|
|
93
93
|
label: string
|
|
94
94
|
chapterTitle: string
|
|
95
95
|
slideIndexes: number[]
|
|
96
|
+
maxSlides: number
|
|
96
97
|
instructions: string
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -112,6 +113,7 @@ export interface RenderPlanContract {
|
|
|
112
113
|
|
|
113
114
|
type VisualIntentKind = "hero" | "toc" | "metric-stat" | "evidence-table" | "comparison-grid" | "risk-matrix" | "steps" | "text-only"
|
|
114
115
|
type ClaimChapterSlideKind = "framing" | "evidence" | "implication"
|
|
116
|
+
const MAX_HTML_SLIDES_PER_BATCH = 5
|
|
115
117
|
|
|
116
118
|
interface VisualIntent {
|
|
117
119
|
kind: VisualIntentKind
|
|
@@ -251,10 +253,11 @@ function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements
|
|
|
251
253
|
"Each substantive chapter should have framing, proof, and implication/boundary coverage.",
|
|
252
254
|
"Chapter divider or chapter TOC slides may use the toc component as structural wayfinding.",
|
|
253
255
|
"Do not create filler slides, repeated thesis pages, or generic bridge slides.",
|
|
254
|
-
"Preserve evidence ids, source trace,
|
|
256
|
+
"Preserve evidence ids, source trace, source limitations, unresolved inputs, and user review notes where available.",
|
|
255
257
|
"Do not render internal labels such as Evidence gap:, Unsupported scope:, Caveat:, Missing Data, or Evidence Boundary in executive body copy.",
|
|
256
258
|
"Do not infer plan structure from DECKS.json slides[]; it is compatibility cache only.",
|
|
257
|
-
"Use sourceLinks in slide blocks for materials, findings, assets,
|
|
259
|
+
"Use sourceLinks in slide blocks for materials, findings, assets, and URLs; do not use canonical ## Relations in deck-plan files.",
|
|
260
|
+
"Use `---` slide separators under ## Slides with slide-local metadata, followed by #### Content Plan, #### Source Links, and #### Design Plan.",
|
|
258
261
|
],
|
|
259
262
|
requiredSections: [
|
|
260
263
|
"Goal",
|
|
@@ -283,7 +286,7 @@ export function buildRenderPlanContract(deck: DeckSpec, chapters: DeckPlanChapte
|
|
|
283
286
|
"Render chapter divider slides with the toc component when slideKind is chapter-divider.",
|
|
284
287
|
"Chapter divider and global TOC slides are structural wayfinding and do not count toward central-claim substance.",
|
|
285
288
|
"Each central claim chapter needs non-structural framing, proof, and implication/boundary slides unless the current deck-plan projection explicitly says otherwise.",
|
|
286
|
-
|
|
289
|
+
`Generate HTML in chapter-bounded batches of at most ${MAX_HTML_SLIDES_PER_BATCH} slides, preserving valid HTML and already-written slides after every batch.`,
|
|
287
290
|
],
|
|
288
291
|
htmlIdentityContract: [
|
|
289
292
|
"Every written slide section uses class slide and a positive 1-based data-slide-index.",
|
|
@@ -306,18 +309,38 @@ export function buildRenderPlanContract(deck: DeckSpec, chapters: DeckPlanChapte
|
|
|
306
309
|
allowedStructuralSlides: chapter.sourceClaimId ? ["chapter-divider", "toc"] : ["cover", "toc", "ask"],
|
|
307
310
|
}
|
|
308
311
|
}),
|
|
309
|
-
chapterWritingBatches: chapters
|
|
310
|
-
label: index === 0 ? "Initial shell and first chapter" : `Chapter batch ${index + 1}`,
|
|
311
|
-
chapterTitle: chapter.title,
|
|
312
|
-
slideIndexes: chapter.slideIndexes,
|
|
313
|
-
instructions: index === 0
|
|
314
|
-
? "Create the stable HTML shell, required structural slides, and this first chapter range only."
|
|
315
|
-
: "Patch exactly this chapter range, preserve previously written slides, and keep the file valid after the patch.",
|
|
316
|
-
})),
|
|
312
|
+
chapterWritingBatches: chapterWritingBatches(chapters),
|
|
317
313
|
slideRenderMetadata: deck.slides.map((slide) => slideRenderMetadata(slide, chapters)),
|
|
318
314
|
}
|
|
319
315
|
}
|
|
320
316
|
|
|
317
|
+
function chapterWritingBatches(chapters: DeckPlanChapter[]): RenderPlanWritingBatch[] {
|
|
318
|
+
const batches: RenderPlanWritingBatch[] = []
|
|
319
|
+
for (const chapter of chapters) {
|
|
320
|
+
const chunks = chunkNumbers(chapter.slideIndexes, MAX_HTML_SLIDES_PER_BATCH)
|
|
321
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
322
|
+
const chunk = chunks[index]
|
|
323
|
+
const chapterSuffix = chunks.length > 1 ? ` part ${index + 1}` : ""
|
|
324
|
+
batches.push({
|
|
325
|
+
label: batches.length === 0 ? `Initial shell and ${chapter.title}${chapterSuffix}` : `${chapter.title}${chapterSuffix}`,
|
|
326
|
+
chapterTitle: chapter.title,
|
|
327
|
+
slideIndexes: chunk,
|
|
328
|
+
maxSlides: MAX_HTML_SLIDES_PER_BATCH,
|
|
329
|
+
instructions: batches.length === 0
|
|
330
|
+
? `Create the stable HTML shell if needed, then write only slide sections ${formatSlideRange(chunk)}. Do not add or rewrite more than ${MAX_HTML_SLIDES_PER_BATCH} slide sections in this write.`
|
|
331
|
+
: `Patch only slide sections ${formatSlideRange(chunk)}, preserve previously written slides, and keep the file valid after the patch. Do not add or rewrite more than ${MAX_HTML_SLIDES_PER_BATCH} slide sections in this write.`,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return batches
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function chunkNumbers(values: number[], size: number): number[][] {
|
|
339
|
+
const chunks: number[][] = []
|
|
340
|
+
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size))
|
|
341
|
+
return chunks
|
|
342
|
+
}
|
|
343
|
+
|
|
321
344
|
function slideRenderMetadata(slide: SlideSpec, chapters: DeckPlanChapter[]): RenderPlanSlideMetadata {
|
|
322
345
|
const chapter = chapters.find((item) => item.slideIndexes.includes(slide.index))
|
|
323
346
|
const slideKind = renderPlanSlideKind(slide, chapter)
|
|
@@ -1115,6 +1138,13 @@ function claimChapterTitle(claim: NarrativeClaim): string {
|
|
|
1115
1138
|
return title.endsWith(".") ? title.slice(0, -1) : title
|
|
1116
1139
|
}
|
|
1117
1140
|
|
|
1141
|
+
function formatSlideRange(indexes: number[]): string {
|
|
1142
|
+
if (indexes.length === 0) return "none"
|
|
1143
|
+
const sorted = [...indexes].sort((a, b) => a - b)
|
|
1144
|
+
if (sorted.length === 1) return String(sorted[0])
|
|
1145
|
+
return `${sorted[0]}-${sorted[sorted.length - 1]}`
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1118
1148
|
function hasCurrentApprovalOrOverride(narrative: NarrativeStateV1, narrativeHash: string): boolean {
|
|
1119
1149
|
return narrative.approvals.some((approval) => approval.narrativeHash === narrativeHash && (approval.scope === "narrative" && approval.approvedBy === "user" || approval.scope === "render_override" || approval.approvedBy === "override"))
|
|
1120
1150
|
}
|
package/package.json
CHANGED
package/plugins/revela/.mcp.json
CHANGED
|
@@ -19,13 +19,14 @@ Use this skill when the user asks to plan, make, generate, or update a Revela de
|
|
|
19
19
|
1. For planning requests, inspect local materials/reviews/research, then write or repair `deck-plan.md` directly. Do not use structured upsert tools for normal plan authoring.
|
|
20
20
|
2. Call `revela_design_list`, `revela_design_read` with `section: "rules"`, and `revela_design_inventory` before selecting layouts/components; use the returned layout slots and component nesting hints in `deck-plan.md`.
|
|
21
21
|
3. Call `revela_read_deck_plan` after writing or repairing `deck-plan.md`. If diagnostics report layout, slot, component, `children`, or `sourceLinks` issues, patch the Markdown directly and call `revela_read_deck_plan` again.
|
|
22
|
-
4. Call `revela_read_deck_plan` before HTML generation and follow the current projection. `revela_read_deck_plan` is QA/diagnostics, not a writer.
|
|
22
|
+
4. Call `revela_read_deck_plan` before HTML generation and follow the current projection. Read `htmlWritingBatches` from the result before any HTML write. `revela_read_deck_plan` is QA/diagnostics, not a writer.
|
|
23
23
|
5. For new HTML files, call `revela_create_deck_foundation`.
|
|
24
24
|
6. Before patching slide HTML, read the specific layouts/components with `revela_design_read_layout` and `revela_design_read_component`; fetch chart rules before ECharts.
|
|
25
25
|
7. Patch slides into the foundation between Revela slide markers. Preserve positive 1-based `data-slide-index` values. Every slide must have exactly one direct `.slide-canvas` child.
|
|
26
|
-
8. Generate
|
|
27
|
-
9.
|
|
26
|
+
8. Generate one `htmlWritingBatches` entry at a time. A single HTML write/edit/apply_patch may add or rewrite at most 5 slide sections. If a chapter is longer than 5 slides, use the consecutive batch parts returned by `revela_read_deck_plan`.
|
|
27
|
+
9. Keep the HTML valid after each write.
|
|
28
|
+
10. After every HTML write, call `revela_run_deck_qa` and repair hard errors before review or export.
|
|
28
29
|
|
|
29
30
|
## Deck Plan Requirements
|
|
30
31
|
|
|
31
|
-
Every normal deck plan should include Cover, Table of Contents, and Closing. Use 3-5 chapter headings, explicit slide ranges, `
|
|
32
|
+
Every normal deck plan should include Cover, Table of Contents, and Closing. Use 3-5 chapter headings, explicit slide ranges, and `---` slide separators under `## Slides`. Each slide block should have slide-local metadata followed by `#### Content Plan`, `#### Source Links` for materials/findings/assets/URLs, and `#### Design Plan` for layout, component plan, visual intent, placement notes, and render notes. Use unresolved inputs, source limitations, and user review notes instead of AI-authored caveat/risk judgement. Use `box.children` when multiple child components support one semantic idea; do not duplicate the same child as both nested and top-level.
|
package/skill/SKILL.md
CHANGED
|
@@ -78,12 +78,14 @@ Batches, slide plan, visual intent, evidence trace, boundaries, and narrative
|
|
|
78
78
|
links. Do not call `compileDeckPlan` merely to understand an existing plan, and
|
|
79
79
|
do not reinterpret cached `DECKS.json.slides[]` as the render contract.
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
Deck HTML must be generated in bounded batches, not in one broad `write` or
|
|
82
|
+
`apply_patch` call. Follow `htmlWritingBatches` from `readDeckPlan`; each HTML
|
|
83
|
+
write/edit/apply_patch may add or rewrite at most 5 `<section class="slide">`
|
|
84
|
+
blocks. The first HTML write may create the stable HTML shell, but its slide
|
|
85
|
+
sections are still capped at 5. Subsequent writes must patch only the next
|
|
86
|
+
listed batch, preserving already-written slides and keeping the file valid after
|
|
87
|
+
every write. Do not continue to the next batch while the current file has
|
|
88
|
+
Artifact QA hard errors.
|
|
87
89
|
|
|
88
90
|
---
|
|
89
91
|
|
|
@@ -118,15 +120,17 @@ Before writing HTML, the deck-plan projection should include:
|
|
|
118
120
|
|
|
119
121
|
- `deck-plan.md` with `designName`, `outputPath` when known, chapter map, and
|
|
120
122
|
ordered slide blocks.
|
|
121
|
-
- Each slide block uses `sourceLinks` for materials, findings, assets, URLs
|
|
122
|
-
|
|
123
|
+
- Each slide block uses `sourceLinks` for materials, findings, assets, and URLs.
|
|
124
|
+
Legacy narrative/caveat links may be read for compatibility, but new
|
|
123
125
|
plans should not use them.
|
|
126
|
+
- Use `---` slide separators under `## Slides` with slide-local metadata, then
|
|
127
|
+
`#### Content Plan`, `#### Source Links`, and `#### Design Plan`.
|
|
124
128
|
- `Required structure: Cover + Table of Contents + Closing`.
|
|
125
129
|
- A `Chapters` section with 3-5 TOC headings, slide ranges, and the
|
|
126
130
|
non-structural slides assigned to each chapter.
|
|
127
131
|
- One row/block per slide with title, purpose, narrative role, content summary,
|
|
128
132
|
layout, components, `sourceLinks`, visual intent, visual brief, render notes,
|
|
129
|
-
and
|
|
133
|
+
unresolved inputs, source limitations, and user review notes.
|
|
130
134
|
- Source Authority, Chapter Map, Slides, Unresolved Inputs, and HTML Contract
|
|
131
135
|
sections.
|
|
132
136
|
- A low-fidelity layout sketch for every slide when requested by the handoff
|
|
@@ -167,14 +171,16 @@ a required workflow gate.
|
|
|
167
171
|
|
|
168
172
|
## Chapter-By-Chapter Generation
|
|
169
173
|
|
|
170
|
-
Generate the artifact
|
|
171
|
-
one
|
|
174
|
+
Generate the artifact by following `htmlWritingBatches`. Never add or rewrite
|
|
175
|
+
more than 5 slide sections in one `write`, `edit`, or `apply_patch` call.
|
|
172
176
|
|
|
173
177
|
For decks with 5 or more slides:
|
|
174
178
|
|
|
175
179
|
- First call `revela-deck-foundation` for new files, then patch structural
|
|
176
|
-
slides and the first
|
|
177
|
-
- Then fill or revise exactly one
|
|
180
|
+
slides and the first listed batch between the `revela-slides` markers.
|
|
181
|
+
- Then fill or revise exactly one listed batch at a time.
|
|
182
|
+
- If a chapter has more than 5 slides, split it into consecutive batches from
|
|
183
|
+
`htmlWritingBatches`.
|
|
178
184
|
- Do not mix multiple central-claim chapters in the same write.
|
|
179
185
|
- Chapter divider or chapter TOC slides are allowed as structural wayfinding and
|
|
180
186
|
should usually use the `toc` component.
|
|
@@ -292,15 +298,15 @@ patch and rerun QA before considering the deck ready.
|
|
|
292
298
|
|
|
293
299
|
## Evidence And Source Rules
|
|
294
300
|
|
|
295
|
-
- Do not invent quotes, URLs, page references, source paths,
|
|
296
|
-
ids.
|
|
297
|
-
- Preserve
|
|
298
|
-
visible in
|
|
301
|
+
- Do not invent quotes, URLs, page references, source paths, source limitations,
|
|
302
|
+
user review notes, or evidence ids.
|
|
303
|
+
- Preserve source trace, explicit source limitations, and unresolved inputs when
|
|
304
|
+
visible in deck-plan source context or slide specs.
|
|
299
305
|
- Evidence-sensitive claims need visible evidence/source context when available.
|
|
300
306
|
- Never stretch partial evidence into support for future-state, recommendation,
|
|
301
307
|
roadmap, or product-vision claims.
|
|
302
|
-
- Keep missing evidence visible as
|
|
303
|
-
it with assumptions.
|
|
308
|
+
- Keep missing evidence visible as an unresolved input, source limitation, user
|
|
309
|
+
review note, or blocker instead of filling it with assumptions.
|
|
304
310
|
- Do not render internal evidence diagnostics as executive-facing body copy.
|
|
305
311
|
Avoid labels such as `Evidence gap:`, `Unsupported scope:`, `Caveat:`,
|
|
306
312
|
`Missing Data`, or `Evidence Boundary` in normal slide text unless the user
|