@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.
Files changed (34) hide show
  1. package/README.md +24 -25
  2. package/README.zh-CN.md +25 -26
  3. package/bin/revela.ts +2 -4
  4. package/lib/commands/help.ts +13 -13
  5. package/lib/commands/init.ts +24 -0
  6. package/lib/commands/png.ts +29 -0
  7. package/lib/commands/refine.ts +1 -1
  8. package/lib/commands/research.ts +24 -0
  9. package/lib/commands/review.ts +98 -19
  10. package/lib/decks-state.ts +7 -7
  11. package/lib/design/designs.ts +44 -0
  12. package/lib/narrative-state/deck-plan-artifact.ts +631 -114
  13. package/lib/narrative-state/render-plan.ts +53 -24
  14. package/lib/pdf/export.ts +84 -24
  15. package/lib/refine/server.ts +4 -3
  16. package/lib/runtime/index.ts +21 -14
  17. package/lib/runtime/review.ts +4 -104
  18. package/lib/workspace-state/render-targets.ts +2 -2
  19. package/lib/workspace-state/rendered-artifacts.ts +1 -1
  20. package/lib/workspace-state/types.ts +1 -1
  21. package/package.json +1 -1
  22. package/plugin.ts +31 -42
  23. package/plugins/revela/.codex-plugin/plugin.json +2 -2
  24. package/plugins/revela/.mcp.json +1 -1
  25. package/plugins/revela/mcp/revela-server.ts +58 -80
  26. package/plugins/revela/skills/revela-design/SKILL.md +4 -2
  27. package/plugins/revela/skills/revela-domain/SKILL.md +1 -1
  28. package/plugins/revela/skills/revela-export/SKILL.md +4 -5
  29. package/plugins/revela/skills/revela-init/SKILL.md +19 -34
  30. package/plugins/revela/skills/revela-make-deck/SKILL.md +16 -41
  31. package/plugins/revela/skills/revela-research/SKILL.md +17 -26
  32. package/plugins/revela/skills/revela-review-deck/SKILL.md +11 -29
  33. package/skill/SKILL.md +44 -35
  34. 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/index.md",
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/index.md",
240
- slidePlanDir: "deck-plan/slides",
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/index.md and deck-plan/slides/*.md from the planning packet; compileDeckPlan does not generate the final slide list.",
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, supported scope, unsupported scope, caveats, and strength where available.",
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 ## Narrative Links in slide files for [[claim-id]], [[evidence-id]], [[risk-id]], [[objection-id]], or [[gap-id]] references; do not use canonical ## Relations in deck-plan files.",
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
- "Slide Plan",
265
- "Evidence Trace",
266
- "Boundary / Risk Treatment",
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/ projection workspace plus compileDeckPlan planning packet",
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/ projection Markdown as the render-plan contract.",
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
- "Generate HTML chapter by chapter, preserving valid HTML and already-written slides after every batch.",
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.map((chapter, index) => ({
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-pdf-${randomBytes(6).toString("hex")}`)
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
- // ── Step 3: Assemble PDF with pdf-lib ─────────────────────────────────────
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
  }
@@ -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 handleInspect(req, session.value)
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 handleInspectResult(url.searchParams.get("requestId"), session.value)
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 handleInspectEvents(url.searchParams.get("requestId"), session.value)
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; }
@@ -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 compiled = compileNarrativeVault(workspaceRoot)
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)
@@ -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 narrativeRead = readNarrative(workspaceRoot)
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, narrative, inspectionContext })
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 readNarrative(workspaceRoot: string): any {
162
- if (!existsSync(resolve(workspaceRoot, "revela-narrative"))) {
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)
@@ -8,7 +8,7 @@ export function recordRenderedArtifact(
8
8
  input: {
9
9
  sourceHtmlPath: string
10
10
  outputPath: string
11
- type: "pdf" | "pptx"
11
+ type: "pdf" | "pptx" | "png"
12
12
  actor: string
13
13
  artifactVersion?: string
14
14
  },
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.17.24",
3
+ "version": "0.18.3",
4
4
  "description": "OpenCode plugin for trusted narrative artifacts from local sources, research, and evidence",
5
5
  "type": "module",
6
6
  "main": "./index.ts",