@cyber-dash-tech/revela 0.17.23 → 0.18.2

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 +92 -14
  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 +849 -19
  13. package/lib/narrative-state/render-plan.ts +13 -14
  14. package/lib/pdf/export.ts +84 -24
  15. package/lib/refine/server.ts +4 -3
  16. package/lib/runtime/index.ts +52 -7
  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 +118 -58
  26. package/plugins/revela/skills/revela-design/SKILL.md +9 -1
  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 +15 -34
  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 +22 -19
  34. package/plugins/revela/skills/revela-story/SKILL.md +0 -24
@@ -207,7 +207,7 @@ export function compileDeckPlanFromNarrative(state: DecksState, options: Compile
207
207
  compiled: true,
208
208
  skipped: false,
209
209
  narrativeHash,
210
- planArtifactPath: "deck-plan/index.md",
210
+ planArtifactPath: "deck-plan.md",
211
211
  slideCount: 0,
212
212
  slides: [],
213
213
  chapters: [],
@@ -236,8 +236,8 @@ function buildDeckPlanningPacket(narrative: NarrativeStateV1, narrativeHash: str
236
236
 
237
237
  function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements {
238
238
  return {
239
- planArtifactPath: "deck-plan/index.md",
240
- slidePlanDir: "deck-plan/slides",
239
+ planArtifactPath: "deck-plan.md",
240
+ slidePlanDir: "",
241
241
  defaultProfile: "executive decision deck, usually 12-18 slides unless the user confirms otherwise",
242
242
  userConfirmations: [
243
243
  "Confirm target slide count or acceptable range when it is unclear.",
@@ -245,7 +245,7 @@ function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements
245
245
  "Confirm language, emphasis, or visual style only when needed before writing the plan.",
246
246
  ],
247
247
  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.",
248
+ "LLM writes canonical deck-plan.md from the planning packet; compileDeckPlan does not generate the final slide list.",
249
249
  "Use 3-5 chapters for normal executive decks.",
250
250
  "Cover every central claim, but group related central claims into chapters instead of giving each claim its own chapter.",
251
251
  "Each substantive chapter should have framing, proof, and implication/boundary coverage.",
@@ -254,18 +254,17 @@ function buildDeckPlanRequirements(narrativeHash: string): DeckPlanRequirements
254
254
  "Preserve evidence ids, source trace, supported scope, unsupported scope, caveats, and strength where available.",
255
255
  "Do not render internal labels such as Evidence gap:, Unsupported scope:, Caveat:, Missing Data, or Evidence Boundary in executive body copy.",
256
256
  "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.",
257
+ "Use sourceLinks in slide blocks for materials, findings, assets, URLs, and caveats; do not use canonical ## Relations in deck-plan files.",
258
258
  ],
259
259
  requiredSections: [
260
+ "Goal",
261
+ "Audience",
262
+ "Design",
260
263
  "Source Authority",
261
- "Audience / Goal / Decision",
262
- "Deck Parameters",
263
264
  "Chapter Map",
264
- "Slide Plan",
265
- "Evidence Trace",
266
- "Boundary / Risk Treatment",
267
- "Chapter Writing Batches",
268
- "HTML Identity Contract",
265
+ "Slides",
266
+ "Unresolved Inputs",
267
+ "HTML Contract",
269
268
  ],
270
269
  }
271
270
  }
@@ -274,13 +273,13 @@ export function buildRenderPlanContract(deck: DeckSpec, chapters: DeckPlanChapte
274
273
  return {
275
274
  sourceAuthority: {
276
275
  meaning: "revela-narrative/ canonical narrative state",
277
- renderPlan: "deck-plan/ projection workspace plus compileDeckPlan planning packet",
276
+ renderPlan: "deck-plan.md plus compileDeckPlan planning packet",
278
277
  state: "DECKS.json compatibility/render state only; slides[] is cached projection data",
279
278
  htmlIdentity: "positive 1-based data-slide-index values, unique and strictly increasing in DOM order",
280
279
  },
281
280
  renderRules: [
282
281
  "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.",
282
+ "Use the compileDeckPlan planning packet plus deck-plan.md as the render-plan contract.",
284
283
  "Render chapter divider slides with the toc component when slideKind is chapter-divider.",
285
284
  "Chapter divider and global TOC slides are structural wayfinding and do not count toward central-claim substance.",
286
285
  "Each central claim chapter needs non-structural framing, proof, and implication/boundary slides unless the current deck-plan projection explicitly says otherwise.",
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,13 +19,12 @@ 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"
26
25
  import { runNarrativeMarkdownQa, type MarkdownQaOptions } from "../narrative-vault/markdown-qa"
27
26
  import { formatArtifactQaUserNotice, formatMarkdownQaUserNotice } from "../hook-notifications"
28
- import { readDeckPlanArtifact } from "../narrative-state/deck-plan-artifact"
27
+ import { deckPlanDesignDiagnostics, readDeckPlanArtifact, upsertDeckPlanSlideArtifact, type DeckPlanSlideUpsertInput } from "../narrative-state/deck-plan-artifact"
29
28
  import { extractDesignClasses } from "../design/designs"
30
29
  import { recordRenderedArtifact, workspaceRelative } from "../workspace-state/rendered-artifacts"
31
30
  import { checkMaterialIntake, extractMaterial, materialIntakeNoticeForCommand, prepareLocalMaterials, recordMaterialReview } from "../material-intake"
@@ -76,6 +75,10 @@ export interface RuntimeDesignComponentReadInput {
76
75
  component: string | string[]
77
76
  }
78
77
 
78
+ export interface RuntimeDeckPlanSlideUpsertInput extends RuntimeWorkspaceInput, DeckPlanSlideUpsertInput {
79
+ designName?: string
80
+ }
81
+
79
82
  export interface RuntimeDesignCreateInput {
80
83
  name: string
81
84
  base?: string
@@ -147,12 +150,39 @@ export { formatArtifactQaUserNotice, formatMarkdownQaUserNotice }
147
150
 
148
151
  export function readDeckPlan(input: RuntimeWorkspaceInput = {}) {
149
152
  const workspaceRoot = root(input.workspaceRoot)
150
- const compiled = compileNarrativeVault(workspaceRoot)
151
- const knownNodeIds = compiled.graph ? new Set(compiled.graph.nodes.map((node) => node.id)) : undefined
152
- return readDeckPlanArtifact(workspaceRoot, {
153
- narrativeHash: compiled.narrative ? computeNarrativeHash(compiled.narrative) : undefined,
154
- knownNodeIds,
153
+ const read = readDeckPlanArtifact(workspaceRoot)
154
+ if (read.projection) {
155
+ try {
156
+ const inventory = getDesignInventory(read.projection.designName || activeDesign())
157
+ const diagnostics = deckPlanDesignDiagnostics(read.projection, {
158
+ layouts: inventory.layouts.map((layout) => layout.name),
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])),
162
+ })
163
+ read.projection.diagnostics.push(...diagnostics)
164
+ read.warnings.push(...diagnostics.map((diagnostic) => diagnostic.message))
165
+ } catch {
166
+ // Design diagnostics are advisory; deck-plan reading remains available.
167
+ }
168
+ }
169
+ return read
170
+ }
171
+
172
+ export function upsertDeckPlanSlide(input: RuntimeDeckPlanSlideUpsertInput) {
173
+ const workspaceRoot = root(input.workspaceRoot)
174
+ const designName = input.designName || activeDesign()
175
+ const inventory = getDesignInventory(designName)
176
+ const result = upsertDeckPlanSlideArtifact(workspaceRoot, input, {
177
+ designLayouts: inventory.layouts.map((layout) => layout.name),
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])),
155
181
  })
182
+ return {
183
+ ...result,
184
+ designName,
185
+ }
156
186
  }
157
187
 
158
188
  export function createDeckFoundation(input: RuntimeDeckFoundationInput) {
@@ -218,6 +248,21 @@ export async function exportPptx(input: RuntimeFileInput & { speakerNotes?: Arra
218
248
  return { ok: true, ...result }
219
249
  }
220
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
+
221
266
  export async function reviewDeckRead(input: ReviewDeckReadInput) {
222
267
  const review = await import("./review")
223
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.23",
3
+ "version": "0.18.2",
4
4
  "description": "OpenCode plugin for trusted narrative artifacts from local sources, research, and evidence",
5
5
  "type": "module",
6
6
  "main": "./index.ts",