@cyber-dash-tech/revela 0.17.24 → 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 +24 -25
- package/README.zh-CN.md +25 -26
- package/bin/revela.ts +2 -4
- package/lib/commands/help.ts +13 -13
- package/lib/commands/init.ts +24 -0
- package/lib/commands/png.ts +29 -0
- package/lib/commands/refine.ts +1 -1
- package/lib/commands/research.ts +24 -0
- package/lib/commands/review.ts +98 -19
- package/lib/decks-state.ts +7 -7
- package/lib/design/designs.ts +44 -0
- package/lib/narrative-state/deck-plan-artifact.ts +631 -114
- package/lib/narrative-state/render-plan.ts +53 -24
- package/lib/pdf/export.ts +84 -24
- package/lib/refine/server.ts +4 -3
- package/lib/runtime/index.ts +21 -14
- package/lib/runtime/review.ts +4 -104
- package/lib/workspace-state/render-targets.ts +2 -2
- package/lib/workspace-state/rendered-artifacts.ts +1 -1
- package/lib/workspace-state/types.ts +1 -1
- package/package.json +1 -1
- package/plugin.ts +31 -42
- package/plugins/revela/.codex-plugin/plugin.json +2 -2
- package/plugins/revela/.mcp.json +1 -1
- package/plugins/revela/mcp/revela-server.ts +58 -80
- package/plugins/revela/skills/revela-design/SKILL.md +4 -2
- package/plugins/revela/skills/revela-domain/SKILL.md +1 -1
- package/plugins/revela/skills/revela-export/SKILL.md +4 -5
- package/plugins/revela/skills/revela-init/SKILL.md +19 -34
- package/plugins/revela/skills/revela-make-deck/SKILL.md +16 -41
- package/plugins/revela/skills/revela-research/SKILL.md +17 -26
- package/plugins/revela/skills/revela-review-deck/SKILL.md +11 -29
- package/skill/SKILL.md +44 -35
- package/plugins/revela/skills/revela-story/SKILL.md +0 -24
|
@@ -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
|
|
@@ -207,7 +209,7 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
|
|
|
207
209
|
compiled: true,
|
|
208
210
|
skipped: false,
|
|
209
211
|
narrativeHash,
|
|
210
|
-
planArtifactPath: "deck-plan
|
|
212
|
+
planArtifactPath: "deck-plan.md",
|
|
211
213
|
slideCount: 0,
|
|
212
214
|
slides: [],
|
|
213
215
|
chapters: [],
|
|
@@ -236,8 +238,8 @@ function buildDeckPlanningPacket(narrative: NarrativeStateV1, narrativeHash: str
|
|
|
236
238
|
|
|
237
239
|
function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements {
|
|
238
240
|
return {
|
|
239
|
-
planArtifactPath: "deck-plan
|
|
240
|
-
slidePlanDir: "
|
|
241
|
+
planArtifactPath: "deck-plan.md",
|
|
242
|
+
slidePlanDir: "",
|
|
241
243
|
defaultProfile: "executive decision deck, usually 12-18 slides unless the user confirms otherwise",
|
|
242
244
|
userConfirmations: [
|
|
243
245
|
"Confirm target slide count or acceptable range when it is unclear.",
|
|
@@ -245,27 +247,27 @@ function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements
|
|
|
245
247
|
"Confirm language, emphasis, or visual style only when needed before writing the plan.",
|
|
246
248
|
],
|
|
247
249
|
authoringRules: [
|
|
248
|
-
"LLM writes deck-plan
|
|
250
|
+
"LLM writes canonical deck-plan.md from the planning packet; compileDeckPlan does not generate the final slide list.",
|
|
249
251
|
"Use 3-5 chapters for normal executive decks.",
|
|
250
252
|
"Cover every central claim, but group related central claims into chapters instead of giving each claim its own chapter.",
|
|
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
|
|
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: [
|
|
263
|
+
"Goal",
|
|
264
|
+
"Audience",
|
|
265
|
+
"Design",
|
|
260
266
|
"Source Authority",
|
|
261
|
-
"Audience / Goal / Decision",
|
|
262
|
-
"Deck Parameters",
|
|
263
267
|
"Chapter Map",
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
"
|
|
267
|
-
"Chapter Writing Batches",
|
|
268
|
-
"HTML Identity Contract",
|
|
268
|
+
"Slides",
|
|
269
|
+
"Unresolved Inputs",
|
|
270
|
+
"HTML Contract",
|
|
269
271
|
],
|
|
270
272
|
}
|
|
271
273
|
}
|
|
@@ -274,17 +276,17 @@ export function buildRenderPlanContract(deck: DeckSpec, chapters: DeckPlanChapte
|
|
|
274
276
|
return {
|
|
275
277
|
sourceAuthority: {
|
|
276
278
|
meaning: "revela-narrative/ canonical narrative state",
|
|
277
|
-
renderPlan: "deck-plan
|
|
279
|
+
renderPlan: "deck-plan.md plus compileDeckPlan planning packet",
|
|
278
280
|
state: "DECKS.json compatibility/render state only; slides[] is cached projection data",
|
|
279
281
|
htmlIdentity: "positive 1-based data-slide-index values, unique and strictly increasing in DOM order",
|
|
280
282
|
},
|
|
281
283
|
renderRules: [
|
|
282
284
|
"Do not infer deck structure, slide count, or chapter substance from DECKS.json slides[].",
|
|
283
|
-
"Use the compileDeckPlan planning packet plus deck-plan
|
|
285
|
+
"Use the compileDeckPlan planning packet plus deck-plan.md as the render-plan contract.",
|
|
284
286
|
"Render chapter divider slides with the toc component when slideKind is chapter-divider.",
|
|
285
287
|
"Chapter divider and global TOC slides are structural wayfinding and do not count toward central-claim substance.",
|
|
286
288
|
"Each central claim chapter needs non-structural framing, proof, and implication/boundary slides unless the current deck-plan projection explicitly says otherwise.",
|
|
287
|
-
|
|
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.`,
|
|
288
290
|
],
|
|
289
291
|
htmlIdentityContract: [
|
|
290
292
|
"Every written slide section uses class slide and a positive 1-based data-slide-index.",
|
|
@@ -307,18 +309,38 @@ export function buildRenderPlanContract(deck: DeckSpec, chapters: DeckPlanChapte
|
|
|
307
309
|
allowedStructuralSlides: chapter.sourceClaimId ? ["chapter-divider", "toc"] : ["cover", "toc", "ask"],
|
|
308
310
|
}
|
|
309
311
|
}),
|
|
310
|
-
chapterWritingBatches: chapters
|
|
311
|
-
label: index === 0 ? "Initial shell and first chapter" : `Chapter batch ${index + 1}`,
|
|
312
|
-
chapterTitle: chapter.title,
|
|
313
|
-
slideIndexes: chapter.slideIndexes,
|
|
314
|
-
instructions: index === 0
|
|
315
|
-
? "Create the stable HTML shell, required structural slides, and this first chapter range only."
|
|
316
|
-
: "Patch exactly this chapter range, preserve previously written slides, and keep the file valid after the patch.",
|
|
317
|
-
})),
|
|
312
|
+
chapterWritingBatches: chapterWritingBatches(chapters),
|
|
318
313
|
slideRenderMetadata: deck.slides.map((slide) => slideRenderMetadata(slide, chapters)),
|
|
319
314
|
}
|
|
320
315
|
}
|
|
321
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
|
+
|
|
322
344
|
function slideRenderMetadata(slide: SlideSpec, chapters: DeckPlanChapter[]): RenderPlanSlideMetadata {
|
|
323
345
|
const chapter = chapters.find((item) => item.slideIndexes.includes(slide.index))
|
|
324
346
|
const slideKind = renderPlanSlideKind(slide, chapter)
|
|
@@ -1116,6 +1138,13 @@ function claimChapterTitle(claim: NarrativeClaim): string {
|
|
|
1116
1138
|
return title.endsWith(".") ? title.slice(0, -1) : title
|
|
1117
1139
|
}
|
|
1118
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
|
+
|
|
1119
1148
|
function hasCurrentApprovalOrOverride(narrative: NarrativeStateV1, narrativeHash: string): boolean {
|
|
1120
1149
|
return narrative.approvals.some((approval) => approval.narrativeHash === narrativeHash && (approval.scope === "narrative" && approval.approvedBy === "user" || approval.scope === "render_override" || approval.approvedBy === "override"))
|
|
1121
1150
|
}
|
package/lib/pdf/export.ts
CHANGED
|
@@ -226,6 +226,18 @@ export interface ExportResult {
|
|
|
226
226
|
warnings?: string[]
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
export interface ExportPngResult {
|
|
230
|
+
outputDir: string
|
|
231
|
+
files: string[]
|
|
232
|
+
slideCount: number
|
|
233
|
+
durationMs: number
|
|
234
|
+
exportMode: "deck"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface ExportPngOptions {
|
|
238
|
+
outputDir?: string
|
|
239
|
+
}
|
|
240
|
+
|
|
229
241
|
/**
|
|
230
242
|
* Export an HTML slide deck to PDF.
|
|
231
243
|
*
|
|
@@ -268,8 +280,78 @@ export async function exportDeckToPdf(htmlFilePath: string): Promise<Omit<Export
|
|
|
268
280
|
|
|
269
281
|
const outputPath = derivePdfPath(abs)
|
|
270
282
|
|
|
283
|
+
const screenshots = await screenshotDeckSlides(abs, "pdf")
|
|
284
|
+
|
|
285
|
+
// ── Step 3: Assemble PDF with pdf-lib ─────────────────────────────────────
|
|
286
|
+
const pdfDoc = await PDFDocument.create()
|
|
287
|
+
|
|
288
|
+
for (const pngBuf of screenshots) {
|
|
289
|
+
const pngImage = await pdfDoc.embedPng(new Uint8Array(pngBuf))
|
|
290
|
+
// Each page is exactly the canvas size (points = pixels at 1:1 for screen PDF)
|
|
291
|
+
const page = pdfDoc.addPage([CANVAS_W, CANVAS_H])
|
|
292
|
+
page.drawImage(pngImage, {
|
|
293
|
+
x: 0,
|
|
294
|
+
y: 0,
|
|
295
|
+
width: CANVAS_W,
|
|
296
|
+
height: CANVAS_H,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const pdfBytes = await pdfDoc.save()
|
|
301
|
+
writeFileSync(outputPath, pdfBytes)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
outputPath,
|
|
305
|
+
slideCount: screenshots.length,
|
|
306
|
+
durationMs: Date.now() - startMs,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function exportDeckToPng(htmlFilePath: string, options: ExportPngOptions = {}): Promise<ExportPngResult> {
|
|
311
|
+
const startMs = Date.now()
|
|
312
|
+
const abs = resolve(htmlFilePath)
|
|
313
|
+
|
|
314
|
+
if (!existsSync(abs)) {
|
|
315
|
+
throw new Error(`File not found: ${abs}`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!/\.html?$/i.test(abs)) {
|
|
319
|
+
throw new Error(`Not an HTML file: ${abs}`)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const outputDir = options.outputDir ?? join(dirname(abs), `${basename(abs).replace(/\.html?$/i, "")}-png`)
|
|
323
|
+
mkdirSync(outputDir, { recursive: true })
|
|
324
|
+
const screenshots = await screenshotDeckSlides(abs, "png")
|
|
325
|
+
const files: string[] = []
|
|
326
|
+
|
|
327
|
+
screenshots.forEach((pngBuf, index) => {
|
|
328
|
+
const outputPath = join(outputDir, `slide-${String(index + 1).padStart(3, "0")}.png`)
|
|
329
|
+
writeFileSync(outputPath, new Uint8Array(pngBuf))
|
|
330
|
+
files.push(outputPath)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
outputDir,
|
|
335
|
+
files,
|
|
336
|
+
slideCount: screenshots.length,
|
|
337
|
+
durationMs: Date.now() - startMs,
|
|
338
|
+
exportMode: "deck",
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function screenshotDeckSlides(htmlFilePath: string, label: "pdf" | "png"): Promise<Buffer[]> {
|
|
343
|
+
const abs = resolve(htmlFilePath)
|
|
344
|
+
|
|
345
|
+
if (!existsSync(abs)) {
|
|
346
|
+
throw new Error(`File not found: ${abs}`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!/\.html?$/i.test(abs)) {
|
|
350
|
+
throw new Error(`Not an HTML file: ${abs}`)
|
|
351
|
+
}
|
|
352
|
+
|
|
271
353
|
// ── Step 1: Download external images and rewrite HTML ─────────────────────
|
|
272
|
-
const tmpDir = join("/tmp", `revela
|
|
354
|
+
const tmpDir = join("/tmp", `revela-${label}-${randomBytes(6).toString("hex")}`)
|
|
273
355
|
mkdirSync(tmpDir, { recursive: true })
|
|
274
356
|
|
|
275
357
|
let tmpHtmlPath: string
|
|
@@ -373,27 +455,5 @@ export async function exportDeckToPdf(htmlFilePath: string): Promise<Omit<Export
|
|
|
373
455
|
}
|
|
374
456
|
}
|
|
375
457
|
|
|
376
|
-
|
|
377
|
-
const pdfDoc = await PDFDocument.create()
|
|
378
|
-
|
|
379
|
-
for (const pngBuf of screenshots) {
|
|
380
|
-
const pngImage = await pdfDoc.embedPng(new Uint8Array(pngBuf))
|
|
381
|
-
// Each page is exactly the canvas size (points = pixels at 1:1 for screen PDF)
|
|
382
|
-
const page = pdfDoc.addPage([CANVAS_W, CANVAS_H])
|
|
383
|
-
page.drawImage(pngImage, {
|
|
384
|
-
x: 0,
|
|
385
|
-
y: 0,
|
|
386
|
-
width: CANVAS_W,
|
|
387
|
-
height: CANVAS_H,
|
|
388
|
-
})
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const pdfBytes = await pdfDoc.save()
|
|
392
|
-
writeFileSync(outputPath, pdfBytes)
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
outputPath,
|
|
396
|
-
slideCount: screenshots.length,
|
|
397
|
-
durationMs: Date.now() - startMs,
|
|
398
|
-
}
|
|
458
|
+
return screenshots
|
|
399
459
|
}
|
package/lib/refine/server.ts
CHANGED
|
@@ -217,19 +217,19 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
217
217
|
if (url.pathname === "/api/inspect" && req.method === "POST") {
|
|
218
218
|
const session = validateSession(url.searchParams.get("token"))
|
|
219
219
|
if (!session.ok) return session.response
|
|
220
|
-
return
|
|
220
|
+
return jsonResponse({ ok: false, error: "Review Insight/Inspect was removed in Revela 0.18. Use Comment for deck edits." }, 410)
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
if (url.pathname === "/api/inspect-result" && req.method === "GET") {
|
|
224
224
|
const session = validateSession(url.searchParams.get("token"))
|
|
225
225
|
if (!session.ok) return session.response
|
|
226
|
-
return
|
|
226
|
+
return jsonResponse({ ok: false, error: "Review Insight/Inspect was removed in Revela 0.18." }, 410)
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
if (url.pathname === "/api/inspect-events" && req.method === "GET") {
|
|
230
230
|
const session = validateSession(url.searchParams.get("token"))
|
|
231
231
|
if (!session.ok) return session.response
|
|
232
|
-
return
|
|
232
|
+
return jsonResponse({ ok: false, error: "Review Insight/Inspect was removed in Revela 0.18." }, 410)
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
if (url.pathname === "/api/deck-version" && req.method === "GET") {
|
|
@@ -1133,6 +1133,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1133
1133
|
.tab.active { position: relative; top: 1px; background: #fbfaf7; border-color: #d8d2c6; color: #111827; box-shadow: 0 -7px 16px rgba(31,41,51,.05); }
|
|
1134
1134
|
.tab-panel { display: none; flex-direction: column; gap: 12px; padding-top: 12px; }
|
|
1135
1135
|
.tab-panel.active { display: flex; }
|
|
1136
|
+
#inspectTab, #inspectPanel { display: none !important; }
|
|
1136
1137
|
.sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
|
|
1137
1138
|
.selection-summary { padding: 10px 12px; border: 1px solid #d8d2c6; border-radius: 14px; background: #fbfaf7; color: #3f3a33; font-size: 13px; line-height: 1.45; box-shadow: 0 8px 22px rgba(31,41,51,.05); }
|
|
1138
1139
|
.selection-summary strong { display: block; margin-bottom: 7px; color: #756f66; font-size: 11px; letter-spacing: .09em; text-transform: uppercase; }
|
package/lib/runtime/index.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from "../design/designs"
|
|
20
20
|
import { createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
|
|
21
21
|
import { activeDomain, activateDomain, createDomainDraftPackage, createDomainPackage, getDomainSkillMd, installDomainDraftPackage, listDomains, seedBuiltinDomains, validateDomainDraftPackage, validateDomainPackage } from "../domain/domains"
|
|
22
|
-
import { computeNarrativeHash } from "../narrative-state/hash"
|
|
23
22
|
import { compileNarrativeVault } from "../narrative-vault/compile"
|
|
24
23
|
import { autoCompileNarrativeVault } from "../narrative-vault/auto-compile"
|
|
25
24
|
import { extractNarrativeVaultMarkdownTargetsFromPatch } from "../narrative-vault/hook-targets"
|
|
@@ -151,18 +150,15 @@ export { formatArtifactQaUserNotice, formatMarkdownQaUserNotice }
|
|
|
151
150
|
|
|
152
151
|
export function readDeckPlan(input: RuntimeWorkspaceInput = {}) {
|
|
153
152
|
const workspaceRoot = root(input.workspaceRoot)
|
|
154
|
-
const
|
|
155
|
-
const knownNodeIds = compiled.graph ? new Set(compiled.graph.nodes.map((node) => node.id)) : undefined
|
|
156
|
-
const read = readDeckPlanArtifact(workspaceRoot, {
|
|
157
|
-
narrativeHash: compiled.narrative ? computeNarrativeHash(compiled.narrative) : undefined,
|
|
158
|
-
knownNodeIds,
|
|
159
|
-
})
|
|
153
|
+
const read = readDeckPlanArtifact(workspaceRoot)
|
|
160
154
|
if (read.projection) {
|
|
161
155
|
try {
|
|
162
|
-
const inventory = getDesignInventory(activeDesign())
|
|
156
|
+
const inventory = getDesignInventory(read.projection.designName || activeDesign())
|
|
163
157
|
const diagnostics = deckPlanDesignDiagnostics(read.projection, {
|
|
164
158
|
layouts: inventory.layouts.map((layout) => layout.name),
|
|
165
159
|
components: inventory.components.map((component) => component.name),
|
|
160
|
+
layoutSlots: Object.fromEntries(inventory.layouts.map((layout) => [layout.name, layout.slots])),
|
|
161
|
+
componentNesting: Object.fromEntries(inventory.components.map((component) => [component.name, component.nesting])),
|
|
166
162
|
})
|
|
167
163
|
read.projection.diagnostics.push(...diagnostics)
|
|
168
164
|
read.warnings.push(...diagnostics.map((diagnostic) => diagnostic.message))
|
|
@@ -177,19 +173,15 @@ export function upsertDeckPlanSlide(input: RuntimeDeckPlanSlideUpsertInput) {
|
|
|
177
173
|
const workspaceRoot = root(input.workspaceRoot)
|
|
178
174
|
const designName = input.designName || activeDesign()
|
|
179
175
|
const inventory = getDesignInventory(designName)
|
|
180
|
-
const compiled = compileNarrativeVault(workspaceRoot)
|
|
181
|
-
const knownNodeIds = compiled.graph ? new Set(compiled.graph.nodes.map((node) => node.id)) : undefined
|
|
182
|
-
const narrativeHash = compiled.narrative ? computeNarrativeHash(compiled.narrative) : undefined
|
|
183
176
|
const result = upsertDeckPlanSlideArtifact(workspaceRoot, input, {
|
|
184
|
-
narrativeHash,
|
|
185
|
-
knownNodeIds,
|
|
186
177
|
designLayouts: inventory.layouts.map((layout) => layout.name),
|
|
187
178
|
designComponents: inventory.components.map((component) => component.name),
|
|
179
|
+
layoutSlots: Object.fromEntries(inventory.layouts.map((layout) => [layout.name, layout.slots])),
|
|
180
|
+
componentNesting: Object.fromEntries(inventory.components.map((component) => [component.name, component.nesting])),
|
|
188
181
|
})
|
|
189
182
|
return {
|
|
190
183
|
...result,
|
|
191
184
|
designName,
|
|
192
|
-
narrativeHash,
|
|
193
185
|
}
|
|
194
186
|
}
|
|
195
187
|
|
|
@@ -256,6 +248,21 @@ export async function exportPptx(input: RuntimeFileInput & { speakerNotes?: Arra
|
|
|
256
248
|
return { ok: true, ...result }
|
|
257
249
|
}
|
|
258
250
|
|
|
251
|
+
export async function exportPng(input: RuntimeFileInput & { outputDir?: string }) {
|
|
252
|
+
const { exportDeckToPng } = await import("../pdf/export")
|
|
253
|
+
const workspaceRoot = root(input.workspaceRoot)
|
|
254
|
+
const filePath = resolve(workspaceRoot, input.file)
|
|
255
|
+
const outputDir = input.outputDir ? resolve(workspaceRoot, input.outputDir) : undefined
|
|
256
|
+
const result = await exportDeckToPng(filePath, { outputDir })
|
|
257
|
+
recordRenderedArtifact(workspaceRoot, {
|
|
258
|
+
sourceHtmlPath: workspaceRelative(resolve(workspaceRoot), filePath),
|
|
259
|
+
outputPath: result.outputDir,
|
|
260
|
+
type: "png",
|
|
261
|
+
actor: "revela-codex-mcp",
|
|
262
|
+
})
|
|
263
|
+
return { ok: true, ...result }
|
|
264
|
+
}
|
|
265
|
+
|
|
259
266
|
export async function reviewDeckRead(input: ReviewDeckReadInput) {
|
|
260
267
|
const review = await import("./review")
|
|
261
268
|
return review.reviewDeckRead(input)
|
package/lib/runtime/review.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "fs"
|
|
2
2
|
import { resolve } from "path"
|
|
3
|
-
import { DECKS_STATE_FILE, hasDecksState, normalizeWorkspaceDeckState, readDecksState } from "../decks-state"
|
|
4
3
|
import { extractDesignClasses } from "../design/designs"
|
|
5
|
-
import { compileInspectionContext, type InspectionContext } from "../inspection-context/compile"
|
|
6
|
-
import { computeNarrativeHash } from "../narrative-state/hash"
|
|
7
4
|
import { readDeckPlanArtifact } from "../narrative-state/deck-plan-artifact"
|
|
8
|
-
import { compileNarrativeVault } from "../narrative-vault/compile"
|
|
9
|
-
import { formatVaultDiagnosticMarkdown, formatVaultDiagnosticReport } from "../narrative-vault/diagnostic-report"
|
|
10
5
|
import { formatArtifactQAReport, runArtifactQA } from "../qa/artifact"
|
|
11
6
|
import { openRefineDeck } from "../refine/open"
|
|
12
7
|
import { createCodexExecReviewPromptBridge } from "../refine/prompt-bridge"
|
|
@@ -24,13 +19,6 @@ export interface ReviewDeckOpenInput extends ReviewDeckReadInput {
|
|
|
24
19
|
openUrl?: (url: string) => void
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
export interface ReviewDeckInspectionContextResult {
|
|
28
|
-
ok: boolean
|
|
29
|
-
skipped: boolean
|
|
30
|
-
reason?: string
|
|
31
|
-
context?: InspectionContext
|
|
32
|
-
}
|
|
33
|
-
|
|
34
22
|
export async function reviewDeckRead(input: ReviewDeckReadInput): Promise<any> {
|
|
35
23
|
const workspaceRoot = root(input.workspaceRoot)
|
|
36
24
|
const requestedFile = input.file?.trim()
|
|
@@ -55,20 +43,13 @@ export async function reviewDeckRead(input: ReviewDeckReadInput): Promise<any> {
|
|
|
55
43
|
}
|
|
56
44
|
|
|
57
45
|
const artifactQa = await readArtifactQa(workspaceRoot, filePath)
|
|
58
|
-
const
|
|
59
|
-
const deckPlan = readDeckPlan(workspaceRoot, narrativeRead.knownNodeIds, narrativeRead.narrativeHash)
|
|
60
|
-
const { knownNodeIds: _knownNodeIds, ...narrative } = narrativeRead
|
|
61
|
-
const inspectionContext = readInspectionContext(workspaceRoot, file)
|
|
46
|
+
const deckPlan = readDeckPlan(workspaceRoot)
|
|
62
47
|
const diagnostics = {
|
|
63
48
|
artifactQa: artifactQa.summary,
|
|
64
49
|
deckPlan: summarizeDeckPlan(deckPlan),
|
|
65
|
-
narrative: narrative.summary,
|
|
66
|
-
inspectionContext: inspectionContext.ok
|
|
67
|
-
? { ok: true, skipped: false }
|
|
68
|
-
: { ok: false, skipped: true, reason: inspectionContext.reason },
|
|
69
50
|
}
|
|
70
51
|
const markdown = input.format === "markdown"
|
|
71
|
-
? formatReviewDeckReadMarkdown({ file, artifactQa, deckPlan
|
|
52
|
+
? formatReviewDeckReadMarkdown({ file, artifactQa, deckPlan })
|
|
72
53
|
: undefined
|
|
73
54
|
|
|
74
55
|
return {
|
|
@@ -76,11 +57,7 @@ export async function reviewDeckRead(input: ReviewDeckReadInput): Promise<any> {
|
|
|
76
57
|
file,
|
|
77
58
|
artifactQa,
|
|
78
59
|
deckPlan,
|
|
79
|
-
narrative,
|
|
80
60
|
diagnostics,
|
|
81
|
-
inspectionContext,
|
|
82
|
-
artifactCoverage: inspectionContext.context?.artifactCoverage ?? [],
|
|
83
|
-
evidenceTrace: inspectionContext.context?.slides.flatMap((slide) => slide.evidence) ?? narrative.evidenceTrace,
|
|
84
61
|
markdown,
|
|
85
62
|
}
|
|
86
63
|
}
|
|
@@ -158,76 +135,8 @@ async function readArtifactQa(workspaceRoot: string, filePath: string) {
|
|
|
158
135
|
}
|
|
159
136
|
}
|
|
160
137
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
ok: false,
|
|
165
|
-
skipped: true,
|
|
166
|
-
reason: "No revela-narrative/ vault exists; narrative diagnostics skipped.",
|
|
167
|
-
summary: { ok: false, skipped: true, reason: "No revela-narrative/ vault exists; narrative diagnostics skipped." },
|
|
168
|
-
evidenceTrace: [],
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const compiled = compileNarrativeVault(workspaceRoot)
|
|
173
|
-
const report = formatVaultDiagnosticReport(compiled.diagnostics)
|
|
174
|
-
const narrative = compiled.narrative
|
|
175
|
-
return {
|
|
176
|
-
ok: compiled.ok,
|
|
177
|
-
skipped: false,
|
|
178
|
-
narrativeHash: narrative ? computeNarrativeHash(narrative) : undefined,
|
|
179
|
-
summary: report,
|
|
180
|
-
diagnostics: compiled.diagnostics,
|
|
181
|
-
diagnosticsMarkdown: formatVaultDiagnosticMarkdown(report),
|
|
182
|
-
evidenceTrace: narrative?.evidenceBindings.map((binding) => ({
|
|
183
|
-
id: binding.id,
|
|
184
|
-
claimId: binding.claimId,
|
|
185
|
-
source: binding.source,
|
|
186
|
-
sourcePath: binding.sourcePath,
|
|
187
|
-
findingsFile: binding.findingsFile,
|
|
188
|
-
quote: binding.quote,
|
|
189
|
-
location: binding.location,
|
|
190
|
-
url: binding.url,
|
|
191
|
-
supportScope: binding.supportScope,
|
|
192
|
-
unsupportedScope: binding.unsupportedScope,
|
|
193
|
-
caveat: binding.caveat,
|
|
194
|
-
strength: binding.strength,
|
|
195
|
-
})) ?? [],
|
|
196
|
-
knownNodeIds: compiled.graph ? new Set(compiled.graph.nodes.map((node) => node.id)) : undefined,
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function readDeckPlan(workspaceRoot: string, knownNodeIds: Set<string> | undefined, narrativeHash: string | undefined) {
|
|
201
|
-
return readDeckPlanArtifact(workspaceRoot, { knownNodeIds, narrativeHash })
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function readInspectionContext(workspaceRoot: string, file: string): ReviewDeckInspectionContextResult {
|
|
205
|
-
if (!hasDecksState(workspaceRoot)) {
|
|
206
|
-
return {
|
|
207
|
-
ok: false,
|
|
208
|
-
skipped: true,
|
|
209
|
-
reason: `No ${DECKS_STATE_FILE} exists; legacy inspection context skipped for file-native deck.`,
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
const state = normalizeWorkspaceDeckState(readDecksState(workspaceRoot), workspaceRoot)
|
|
215
|
-
const slug = Object.entries(state.decks).find(([, deck]) => normalizePath(deck.outputPath) === normalizePath(file))?.[0]
|
|
216
|
-
if (!slug) {
|
|
217
|
-
return {
|
|
218
|
-
ok: false,
|
|
219
|
-
skipped: true,
|
|
220
|
-
reason: `No ${DECKS_STATE_FILE} deck outputPath matches ${file}; legacy inspection context skipped.`,
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return { ok: true, skipped: false, context: compileInspectionContext(state, slug) }
|
|
224
|
-
} catch (e) {
|
|
225
|
-
return {
|
|
226
|
-
ok: false,
|
|
227
|
-
skipped: true,
|
|
228
|
-
reason: `Could not compile legacy inspection context: ${e instanceof Error ? e.message : String(e)}`,
|
|
229
|
-
}
|
|
230
|
-
}
|
|
138
|
+
function readDeckPlan(workspaceRoot: string) {
|
|
139
|
+
return readDeckPlanArtifact(workspaceRoot)
|
|
231
140
|
}
|
|
232
141
|
|
|
233
142
|
function summarizeDeckPlan(deckPlan: ReturnType<typeof readDeckPlanArtifact>) {
|
|
@@ -243,8 +152,6 @@ function formatReviewDeckReadMarkdown(input: {
|
|
|
243
152
|
file: string
|
|
244
153
|
artifactQa: Awaited<ReturnType<typeof readArtifactQa>>
|
|
245
154
|
deckPlan: ReturnType<typeof readDeckPlanArtifact>
|
|
246
|
-
narrative: ReturnType<typeof readNarrative>
|
|
247
|
-
inspectionContext: ReviewDeckInspectionContextResult
|
|
248
155
|
}): string {
|
|
249
156
|
const lines = [
|
|
250
157
|
"# Review Deck Read",
|
|
@@ -253,19 +160,12 @@ function formatReviewDeckReadMarkdown(input: {
|
|
|
253
160
|
"",
|
|
254
161
|
`Artifact QA: ${input.artifactQa.summary.passed ? "passed" : "failed"} (${input.artifactQa.summary.errors} hard error(s), ${input.artifactQa.summary.warnings} warning(s))`,
|
|
255
162
|
`Deck-plan: ${input.deckPlan.ok ? "read" : `skipped/diagnostic - ${input.deckPlan.reason ?? "not available"}`}`,
|
|
256
|
-
`Narrative: ${input.narrative.skipped ? input.narrative.reason : input.narrative.summary.summary}`,
|
|
257
|
-
`Inspection context: ${input.inspectionContext.ok ? "read" : `skipped - ${input.inspectionContext.reason}`}`,
|
|
258
163
|
"",
|
|
259
164
|
input.artifactQa.markdown,
|
|
260
165
|
]
|
|
261
|
-
if (!input.narrative.skipped && input.narrative.diagnosticsMarkdown) lines.push("", input.narrative.diagnosticsMarkdown)
|
|
262
166
|
return lines.join("\n")
|
|
263
167
|
}
|
|
264
168
|
|
|
265
|
-
function normalizePath(value: string): string {
|
|
266
|
-
return value.replace(/\\/g, "/").replace(/^\.\/+/, "")
|
|
267
|
-
}
|
|
268
|
-
|
|
269
169
|
function root(workspaceRoot: string | undefined): string {
|
|
270
170
|
return resolve(workspaceRoot || process.cwd())
|
|
271
171
|
}
|
|
@@ -47,7 +47,7 @@ export function upsertRenderTarget(state: DecksState, target: RenderTarget): Dec
|
|
|
47
47
|
return state
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
export function deriveExportRenderTarget(htmlTarget: RenderTarget, type: "pdf" | "pptx", outputPath: string): RenderTarget {
|
|
50
|
+
export function deriveExportRenderTarget(htmlTarget: RenderTarget, type: "pdf" | "pptx" | "png", outputPath: string): RenderTarget {
|
|
51
51
|
const normalizedOutput = normalizeWorkspacePath(outputPath)
|
|
52
52
|
return cleanRenderTarget({
|
|
53
53
|
id: renderTargetId(type, normalizedOutput),
|
|
@@ -64,7 +64,7 @@ export function deriveExportRenderTarget(htmlTarget: RenderTarget, type: "pdf" |
|
|
|
64
64
|
|
|
65
65
|
export function recordArtifactRenderTarget(
|
|
66
66
|
state: DecksState,
|
|
67
|
-
input: { sourceHtmlPath: string; type: "pdf" | "pptx"; outputPath: string; artifactVersion?: string },
|
|
67
|
+
input: { sourceHtmlPath: string; type: "pdf" | "pptx" | "png"; outputPath: string; artifactVersion?: string },
|
|
68
68
|
): RenderTarget {
|
|
69
69
|
const normalizedSource = normalizeWorkspacePath(input.sourceHtmlPath)
|
|
70
70
|
const activeTarget = ensureActiveHtmlDeckRenderTarget(state)
|
|
@@ -108,7 +108,7 @@ export type WorkspaceActionType =
|
|
|
108
108
|
|
|
109
109
|
export interface RenderTarget {
|
|
110
110
|
id: string
|
|
111
|
-
type: "html_deck" | "pdf" | "pptx" | "brief" | "executive_brief" | "appendix" | "qa_view" | "interactive_page"
|
|
111
|
+
type: "html_deck" | "pdf" | "pptx" | "png" | "brief" | "executive_brief" | "appendix" | "qa_view" | "interactive_page"
|
|
112
112
|
outputPath?: string
|
|
113
113
|
sourceNodeIds: string[]
|
|
114
114
|
artifactVersion?: string
|
package/package.json
CHANGED