@cyber-dash-tech/revela 0.17.0 → 0.17.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.
@@ -253,35 +253,105 @@ async function handleAssetSave(req: Request, session: EditSession): Promise<Resp
253
253
  const candidate = normalizeImageCandidate(body?.candidate ?? body)
254
254
  if (!candidate) return jsonResponse({ ok: false, error: "Valid image candidate is required" }, 400)
255
255
  const purpose = normalizeMediaPurpose(body?.purpose) || candidate.purpose || "illustration"
256
- const result = await saveMediaAsset({
257
- topic: session.deck,
256
+ const brief = body?.brief || `Saved from ${candidate.provider} for Review asset placement.`
257
+ const saved = await saveAssetCandidateUrls({
258
+ session,
259
+ candidate,
258
260
  id: body?.id || candidate.candidateId,
259
- type: "image",
260
261
  purpose,
261
- brief: body?.brief || `Saved from ${candidate.provider} for Review asset placement.`,
262
- status: "success",
263
- sourceUrl: candidate.imageUrl,
262
+ brief,
264
263
  alt: body?.alt || candidate.alt || candidate.title,
265
264
  notes: body?.notes,
266
- provider: candidate.provider,
267
- sourcePageUrl: candidate.sourcePageUrl,
268
- license: candidate.license,
269
- attribution: candidate.attribution,
270
- width: candidate.width,
271
- height: candidate.height,
272
- }, session.workspaceRoot)
265
+ })
273
266
 
274
267
  session.lastActiveAt = Date.now()
275
268
  scheduleIdleStop()
269
+ const result = saved.result
276
270
  if (!result.ok) return jsonResponse({ ok: false, error: result.error }, 400)
277
271
  if (result.status !== "success" || !result.path) {
278
- return jsonResponse({ ok: false, error: `Failed to save asset: ${result.status}` }, 400)
272
+ return jsonResponse({ ok: false, error: failedAssetSaveMessage(result.status, saved.failures) }, 400)
279
273
  }
280
274
  const asset = savedAssetForResult(session, result.assetId)
275
+ ?? savedAssetFallback(session, {
276
+ id: result.assetId,
277
+ path: result.path,
278
+ sourceUrl: saved.sourceUrl,
279
+ purpose,
280
+ brief,
281
+ candidate,
282
+ })
281
283
  if (!asset) return jsonResponse({ ok: false, error: "Saved asset was not found in workspace assets." }, 500)
282
284
  return jsonResponse({ ok: true, asset, result })
283
285
  }
284
286
 
287
+ async function saveAssetCandidateUrls(input: {
288
+ session: EditSession
289
+ candidate: ImageCandidate
290
+ id: string
291
+ purpose: MediaPurpose
292
+ brief: string
293
+ alt?: string
294
+ notes?: string
295
+ }): Promise<{
296
+ result: Awaited<ReturnType<typeof saveMediaAsset>>
297
+ sourceUrl?: string
298
+ failures: Array<{ url: string; status: string }>
299
+ }> {
300
+ const urls = uniqueAssetUrls([input.candidate.imageUrl, input.candidate.thumbnailUrl])
301
+ const failures: Array<{ url: string; status: string }> = []
302
+ let lastResult: Awaited<ReturnType<typeof saveMediaAsset>> | undefined
303
+
304
+ for (const sourceUrl of urls) {
305
+ const result = await saveMediaAsset({
306
+ topic: input.session.deck,
307
+ id: input.id,
308
+ type: "image",
309
+ purpose: input.purpose,
310
+ brief: input.brief,
311
+ status: "success",
312
+ sourceUrl,
313
+ alt: input.alt,
314
+ notes: input.notes,
315
+ provider: input.candidate.provider,
316
+ sourcePageUrl: input.candidate.sourcePageUrl,
317
+ license: input.candidate.license,
318
+ attribution: input.candidate.attribution,
319
+ width: input.candidate.width,
320
+ height: input.candidate.height,
321
+ }, input.session.workspaceRoot)
322
+ lastResult = result
323
+ if (result.ok && result.status === "success" && result.path) return { result, sourceUrl, failures }
324
+ failures.push({ url: sourceUrl, status: result.ok ? result.failureReason ?? result.status : result.error })
325
+ }
326
+
327
+ return {
328
+ result: lastResult ?? { ok: false, error: "No downloadable image URL was provided" },
329
+ failures,
330
+ }
331
+ }
332
+
333
+ function uniqueAssetUrls(values: Array<string | undefined>): string[] {
334
+ const seen = new Set<string>()
335
+ return values.flatMap((value) => {
336
+ const url = value?.trim()
337
+ if (!url || seen.has(url)) return []
338
+ seen.add(url)
339
+ return [url]
340
+ })
341
+ }
342
+
343
+ function failedAssetSaveMessage(status: string, failures: Array<{ url: string; status: string }>): string {
344
+ if (!failures.length) return `Failed to save asset: ${status}`
345
+ const details = failures
346
+ .map((failure) => `${shortUrl(failure.url)}: ${failure.status}`)
347
+ .join("; ")
348
+ return `Failed to save asset: ${status} (${details})`
349
+ }
350
+
351
+ function shortUrl(value: string): string {
352
+ return value.length <= 96 ? value : `${value.slice(0, 93)}...`
353
+ }
354
+
285
355
  function handleAssetList(session: EditSession): Response {
286
356
  session.lastActiveAt = Date.now()
287
357
  scheduleIdleStop()
@@ -292,6 +362,39 @@ function savedAssetForResult(session: EditSession, assetId: string): (MediaAsset
292
362
  return listSavedAssets(session).find((asset) => asset.id === assetId) ?? null
293
363
  }
294
364
 
365
+ function savedAssetFallback(
366
+ session: EditSession,
367
+ input: {
368
+ id: string
369
+ path: string | null
370
+ sourceUrl?: string
371
+ purpose: MediaPurpose
372
+ brief: string
373
+ candidate: ImageCandidate
374
+ },
375
+ ): (MediaAssetRecord & { previewUrl?: string; deckPath?: string }) | null {
376
+ if (!input.path) return null
377
+ return {
378
+ id: input.id,
379
+ type: "image",
380
+ purpose: input.purpose,
381
+ brief: input.brief,
382
+ status: "success",
383
+ path: input.path,
384
+ sourceUrl: input.sourceUrl ?? input.candidate.imageUrl,
385
+ alt: input.candidate.alt || input.candidate.title,
386
+ provider: input.candidate.provider,
387
+ sourcePageUrl: input.candidate.sourcePageUrl,
388
+ license: input.candidate.license,
389
+ attribution: input.candidate.attribution,
390
+ width: input.candidate.width,
391
+ height: input.candidate.height,
392
+ savedAt: new Date().toISOString(),
393
+ previewUrl: assetUrlForRef(input.path, session, session.workspaceRoot) ?? undefined,
394
+ deckPath: relative(dirname(session.absoluteFile), resolve(session.workspaceRoot, input.path)).replace(/\\/g, "/"),
395
+ }
396
+ }
397
+
295
398
  function listSavedAssets(session: EditSession): Array<MediaAssetRecord & { previewUrl?: string; deckPath?: string }> {
296
399
  const manifestPath = resolve(session.workspaceRoot, "assets", slugify(session.deck), "media-manifest.json")
297
400
  if (!existsSync(manifestPath)) return []
@@ -891,6 +994,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
891
994
  .skeleton-line.long { width: 92%; }
892
995
  .asset-card.is-saving::after { content: ""; position: absolute; inset: 0; background: rgba(15,23,42,.32); }
893
996
  .asset-card.is-saving .asset-save { z-index: 1; }
997
+ .asset-card.is-saved-candidate .asset-thumb { opacity: .72; }
894
998
  @keyframes spin { to { transform: rotate(360deg); } }
895
999
  @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
896
1000
  .asset-search { display: grid; grid-template-columns: minmax(0, 1fr) 118px; gap: 8px; }
@@ -913,6 +1017,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
913
1017
  .asset-search-title span { color: #756f66; font-size: 12px; line-height: 1.35; }
914
1018
  .asset-back { width: auto; padding: 9px 11px; border-radius: 999px; background: #ebe4d8; color: #111827; box-shadow: none; }
915
1019
  .asset-save { position: absolute; left: 7px; right: 7px; bottom: 7px; width: auto; padding: 7px 8px; border-radius: 10px; font-size: 11px; background: rgba(17,24,39,.9); color: #fbfaf7; box-shadow: 0 8px 16px rgba(31,41,51,.2); opacity: .96; }
1020
+ .asset-save.saved { background: rgba(77,97,56,.94); color: #fbfaf7; cursor: default; }
916
1021
  .asset-empty { grid-column: 1 / -1; margin: 0; color: #756f66; font-size: 12px; line-height: 1.45; }
917
1022
  .edit-assets { padding: 10px; border: 1px solid #d8d2c6; border-radius: 16px; background: #f7f3ea; }
918
1023
  .edit-assets .panel { gap: 8px; }
@@ -2000,9 +2105,13 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2000
2105
  }
2001
2106
  state.assetCandidates.forEach((candidate, index) => {
2002
2107
  const card = assetCard(candidate, false, index);
2108
+ const savedAsset = savedAssetForCandidate(candidate);
2003
2109
  if (state.assetSavingIndex === index) {
2004
2110
  card.classList.add('is-saving');
2005
2111
  appendAssetSaveButton(card, 'Saving...', 'Saving to workspace', () => {}, true);
2112
+ } else if (savedAsset) {
2113
+ card.classList.add('is-saved-candidate');
2114
+ appendAssetSaveButton(card, '✅ Saved', 'Asset already saved to Local Assets', () => {}, false, 'saved');
2006
2115
  } else {
2007
2116
  appendAssetSaveButton(card, 'Save', 'Save to workspace', () => saveCandidate(index));
2008
2117
  }
@@ -2025,7 +2134,11 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2025
2134
  });
2026
2135
  const body = await res.json().catch(() => ({}));
2027
2136
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to save asset');
2028
- await loadSavedAssets();
2137
+ const listed = await loadSavedAssets();
2138
+ if (body.asset && (!listed || !findSavedAsset(body.asset.id))) {
2139
+ mergeSavedAsset(body.asset);
2140
+ renderSavedAssets();
2141
+ }
2029
2142
  const path = body.asset && (body.asset.path || body.asset.deckPath);
2030
2143
  setStatus(path ? 'Saved to ' + path + '. Use it from Local Assets.' : 'Asset saved. Use it from Local Assets.');
2031
2144
  } catch (error) {
@@ -2043,12 +2156,36 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2043
2156
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to list assets');
2044
2157
  state.savedAssets = Array.isArray(body.assets) ? body.assets : [];
2045
2158
  renderSavedAssets();
2159
+ return true;
2046
2160
  } catch (error) {
2047
- const message = '<p class="asset-empty">' + escapeHtml(error && error.message ? error.message : String(error)) + '</p>';
2048
- els.editSavedAssets.innerHTML = message;
2161
+ if (!state.savedAssets.length) {
2162
+ const message = '<p class="asset-empty">' + escapeHtml(error && error.message ? error.message : String(error)) + '</p>';
2163
+ els.editSavedAssets.innerHTML = message;
2164
+ }
2165
+ return false;
2049
2166
  }
2050
2167
  }
2051
2168
 
2169
+ function mergeSavedAsset(asset) {
2170
+ if (!asset || !asset.id) return;
2171
+ const next = state.savedAssets.filter((existing) => existing.id !== asset.id);
2172
+ next.unshift(asset);
2173
+ state.savedAssets = next;
2174
+ }
2175
+
2176
+ function savedAssetForCandidate(candidate) {
2177
+ const id = slugifyAssetId(candidate && candidate.candidateId);
2178
+ if (!id) return null;
2179
+ return state.savedAssets.find((asset) => asset.id === id) || null;
2180
+ }
2181
+
2182
+ function slugifyAssetId(value) {
2183
+ return String(value || '')
2184
+ .toLowerCase()
2185
+ .replace(/[^a-z0-9]+/g, '-')
2186
+ .replace(/^-+|-+$/g, '');
2187
+ }
2188
+
2052
2189
  function renderSavedAssets() {
2053
2190
  renderSavedAssetGrid(els.editSavedAssets, 'No local assets yet. Click + to search assets.');
2054
2191
  }
@@ -2108,12 +2245,12 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2108
2245
  }
2109
2246
  }
2110
2247
 
2111
- function appendAssetSaveButton(card, text, label, onClick, loading) {
2248
+ function appendAssetSaveButton(card, text, label, onClick, loading, variant) {
2112
2249
  const button = document.createElement('button');
2113
2250
  button.type = 'button';
2114
- button.className = 'asset-save';
2251
+ button.className = variant ? 'asset-save ' + variant : 'asset-save';
2115
2252
  button.innerHTML = loading ? '<span class="spinner" aria-hidden="true"></span><span>' + escapeHtml(text) + '</span>' : escapeHtml(text);
2116
- button.disabled = !!loading;
2253
+ button.disabled = !!loading || variant === 'saved';
2117
2254
  button.setAttribute('aria-label', label);
2118
2255
  button.title = label;
2119
2256
  button.addEventListener('click', onClick);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.17.0",
3
+ "version": "0.17.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",
package/plugin.ts CHANGED
@@ -784,7 +784,7 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
784
784
  // Handles PDF and images — read tool succeeds with base64 attachment.
785
785
  // PDF: extract text, remove base64. Images: jimp compress.
786
786
  //
787
- // Also reports writes/patches blocked by the DECKS.json prewrite gate and
787
+ // Also reports writes/patches blocked by the DECKS.json state gate and
788
788
  // runs artifact QA before opening Refine after successful deck changes.
789
789
  "tool.execute.after": async (input, output) => {
790
790
  // ── Post-read processing ───────────────────────────────────────────
@@ -830,7 +830,7 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
830
830
  if (blockedPath) blockedPatches.delete(blockedPath)
831
831
  appendToolResult(
832
832
  output,
833
- "---\n\n**[revela prewrite gate]** Patch was blocked.\n\n" +
833
+ "---\n\n**[revela state gate]** Patch was blocked.\n\n" +
834
834
  `${blockedReason}\n\n` +
835
835
  "Use the `revela-decks` tool for controlled workspace state changes."
836
836
  )
@@ -8,7 +8,7 @@ compatibility: opencode
8
8
 
9
9
  You help the user turn source materials, research, data, and intent into trusted, traceable, presentation-ready decision artifacts.
10
10
 
11
- Decks are important, but they are render targets. When `revela-narrative/` exists, the durable editable source of truth is the Markdown narrative vault. Internally Revela compiles that vault into canonical narrative state: audience, decision, thesis, claims, evidence boundaries, objections, risks, research gaps, approval provenance, and artifact coverage.
11
+ Decks are important, but they are render targets. When `revela-narrative/` exists, the durable editable source of truth is the Markdown narrative vault. Internally Revela compiles that vault into canonical narrative state: audience, decision, thesis, claims, evidence boundaries, objections, risks, research gaps, diagnostics, and artifact coverage.
12
12
 
13
13
  Default mode is narrative-first. Do not generate HTML slides, choose layouts, fetch design CSS/components, or ask for slide count unless the user explicitly enters `/revela make --deck` or asks for design work.
14
14
 
@@ -18,8 +18,8 @@ Use the same phase semantics whether the user invokes a slash command or asks in
18
18
 
19
19
  - `Init` discovers local workspace materials, captures intent, initializes or refreshes workspace state, and creates conservative narrative state only from explicit user statements or source traces.
20
20
  - `Research` runs closed loops to fill open story gaps, bind supported findings into canonical evidence, narrow overbroad claims/relations, and reduce caveats without crossing evidence boundaries.
21
- - `Story` opens the read-only story workspace UI for inspecting claim flow, evidence strength, unsupported scope, caveats, objections, risks, research gaps, approval state, and affected artifacts.
22
- - `Make` renders an artifact from approved or explicitly overridden narrative state. Supported 0.15 targets are deck and executive brief.
21
+ - `Story` opens the read-only story workspace UI for inspecting claim flow, evidence strength, unsupported scope, caveats, objections, risks, research gaps, diagnostics, and affected artifacts.
22
+ - `Make` renders an artifact from canonical narrative state and current diagnostics. Supported targets are deck and executive brief.
23
23
  - `Review` is the post-artifact workspace for reading, insight, and targeted commenting. Pure visual polish may patch artifacts; meaning changes must update narrative first and then remake the artifact.
24
24
 
25
25
  Public command surface:
@@ -39,7 +39,7 @@ Deprecated compatibility aliases such as `/revela review`, `/revela narrative`,
39
39
 
40
40
  ## Workspace State
41
41
 
42
- Use `DECKS.json` as Revela's compatibility workspace-state and render-state file. Do not write or patch it directly. Use `revela-narrative/**/*.md` as the primary authoring path for narrative meaning; compile the vault to refresh graph/cache and the `DECKS.json.narrative` mirror.
42
+ Use `revela-narrative/**/*.md` as the primary authoring path for narrative meaning. `DECKS.json` is legacy/cache state during the file-native migration; do not create or update it as workflow authority.
43
43
 
44
44
  Use `revela-decks` for state operations:
45
45
 
@@ -55,12 +55,12 @@ Use `revela-decks` for state operations:
55
55
  - `deriveResearchGaps`, `upsertResearchGaps`, `updateResearchGap`, and `closeResearchGap` are compatibility helpers; prefer `updateVaultResearchGap` for canonical gap lifecycle updates
56
56
  - `attachResearchFindings` to attach saved findings to research state
57
57
  - `applyEvidenceCandidates` is compatibility-only; prefer `upsertVaultEvidence` with explicit source trace for canonical support
58
- - `approveNarrative` only when the user explicitly approves or requests an override
59
- - `compileDeckPlan`, `upsertDeck`, `upsertSlides`, and `review` only inside make-deck or artifact-readiness workflows
58
+ - `compileDeckPlan` and `readDeckPlan` inside make-deck workflows
59
+ - `upsertDeck`, `upsertSlides`, `approveNarrative`, `confirmDeckPlan`, and `review` are legacy/compatibility helpers; do not use them as workflow gates
60
60
 
61
- Never treat `writeReadiness.status`, old review snapshots, existing `decks/*.html`, workspace scans, extraction cache paths, or saved research actions as narrative approval or proof by themselves.
61
+ Never treat `writeReadiness.status`, old review snapshots, existing `decks/*.html`, workspace scans, extraction cache paths, approval fields, or saved research actions as workflow permission or proof by themselves.
62
62
 
63
- When a tool returns `vaultDiagnostics` or `diagnosticReport`, report blockers before narrative readiness or artifact work. Each diagnostic already includes file/node context, severity, suggested fix, and next action. Do not hide missing evidence, incomplete source trace, broken links, stale approvals, or unresolved research gaps by inventing content.
63
+ When a tool returns `vaultDiagnostics` or `diagnosticReport`, report diagnostics before artifact work. Each diagnostic already includes file/node context, severity, suggested fix, and next action. Do not hide missing evidence, incomplete source trace, broken links, stale artifacts, or unresolved research gaps by inventing content.
64
64
 
65
65
  ## Init Rules
66
66
 
@@ -75,7 +75,7 @@ During init:
75
75
  - write `## Relations` sections with plain node-id wikilinks such as `- supports: [[claim-example]]`, `- depends_on: [[evidence-example]]`, `- answers: [[claim-example]]`, or `- constrains: [[claim-example]]` when the relation is explicit; do not use typed wikilinks or hand-written relation ids; compile and fix diagnostics after editing Markdown
76
76
  - ask the smallest missing intent questions after local evidence has been considered
77
77
  - do not require slide count, design choice, layout choice, output path, or visual style unless the user explicitly asks to make an artifact immediately
78
- - when exporting a vault, say that approvals, render targets, reviews, artifact coverage, actions, deck specs, and source material records remain in `DECKS.json`
78
+ - when exporting a vault, say that any legacy render targets, reviews, artifact coverage, actions, deck specs, and source material records in `DECKS.json` are cache/provenance only
79
79
 
80
80
  ## Research Rules
81
81
 
@@ -108,27 +108,27 @@ Use this report shape:
108
108
  - blockers first, with issue type, claim text when available, and suggested next action
109
109
  - warnings second, as residual risks
110
110
  - research gaps and unattached findings as next work
111
- - approval state last, clearly distinguishing `ready_for_approval`, `approved`, stale approval, and render override
111
+ - artifact/deck-plan alignment diagnostics last, without approval-gate language
112
112
 
113
113
  If evidence is missing, say what is missing and what should happen next. Do not invent quotes, sources, page locations, URLs, caveats, or research findings.
114
114
 
115
- If the narrative is ready for approval, ask the user whether to approve or revise it. Do not approve automatically.
115
+ Do not ask the user for narrative approval. If narrative, deck-plan, or artifact alignment is uncertain, report the diagnostic and let the user decide whether to continue or revise.
116
116
 
117
117
  ## Make Rules
118
118
 
119
119
  For `/revela make --deck` deck handoff:
120
120
 
121
121
  - switch to deck-render mode through the command workflow
122
- - check narrative readiness and current approval before compiling deck specs
123
- - stop before deck planning when `vaultDiagnostics.blockers` exist; report the Markdown file/node/code and suggested next action
122
+ - report narrative and evidence diagnostics before compiling deck-plan requirements
123
+ - report `vaultDiagnostics` with Markdown file/node/code and suggested next action; only malformed or unsafe files are hard blockers
124
124
  - use `compileDeckPlan` as the canonical narrative-to-deck planning path
125
- - run the deck/artifact gate with `revela-decks review` before writing HTML
126
- - fetch design layouts/components only after narrative handoff is valid
127
- - keep the HTML deck contract valid: one `<section class="slide">` per slide, canonical 1-based `data-slide-index`, and matching `DECKS.json` slide specs
125
+ - run artifact diagnostics when useful before writing HTML
126
+ - fetch design layouts/components before HTML writing
127
+ - keep the HTML deck contract valid: one `<section class="slide">` per slide, canonical 1-based `data-slide-index`, unique indexes, increasing DOM order, and valid canvas
128
128
 
129
129
  For `/revela make --brief`, render the executive brief from canonical narrative state and graph-backed claim/evidence relationships, not from a deck summary.
130
130
 
131
- If story readiness, approval, evidence, or artifact blockers remain, report the blocker and suggest `/revela story`, `/revela research`, or a targeted user answer. Do not bypass with invented state.
131
+ If narrative, evidence, deck-plan, or artifact diagnostics remain, report them and suggest `/revela story`, `/revela research`, or a targeted user answer. Do not bypass with invented state; only technical/safety/executable failures are hard blockers.
132
132
 
133
133
  ## Review Rules
134
134
 
@@ -136,7 +136,7 @@ Use `/revela review --deck` for post-artifact reading, insight, and commenting.
136
136
 
137
137
  - Reading should explain source, support strength, caveat, unsupported scope, narrative purpose, related risks/objections, research gaps, and artifact coverage.
138
138
  - Pure artifact polish may stay artifact-level: layout, typography, spacing, crop, visual hierarchy, export mechanics, and deck contract fixes.
139
- - Meaning-changing edits must update canonical narrative first, then run story readiness/approval or explicit override, then remake affected artifacts.
139
+ - Meaning-changing edits must update canonical narrative first, then report alignment diagnostics before remaking affected artifacts.
140
140
  - `/revela edit` and `/revela inspect` have been removed from the public surface; use `/revela review --deck`.
141
141
 
142
142
  ## Design Surface
@@ -148,7 +148,7 @@ Do not inject design CSS, layout catalogs, component indexes, chart rules, or de
148
148
  ## Boundaries
149
149
 
150
150
  - Do not write or overwrite `decks/*.html` in narrative mode.
151
- - Do not call `revela-decks review` in story mode; that is the deck/artifact gate.
151
+ - Do not call `revela-decks review` in story mode; it is legacy deck/artifact diagnostics.
152
152
  - Do not apply evidence candidates, bind evidence, or rewrite slide text unless the user explicitly asks or the active workflow requires it with clear support.
153
153
  - Do not store secrets, credentials, tokens, or sensitive personal information.
154
154
  - Do not infer long-term user preferences from one-off tasks.
package/skill/SKILL.md CHANGED
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: revela
3
- description: Render approved Revela narrative state into HTML slide decks
3
+ description: Render Revela narrative state and deck-plan projections into HTML slide decks
4
4
  compatibility: opencode
5
5
  ---
6
6
 
7
7
  # Revela — AI Presentation Generator
8
8
 
9
- You are Revela's deck-render assistant. Your job is to turn an approved
10
- canonical narrative and confirmed deck plan into a trusted, presentation-ready
9
+ You are Revela's deck-render assistant. Your job is to turn canonical narrative
10
+ state and the current deck-plan projection into a trusted, presentation-ready
11
11
  HTML deck.
12
12
 
13
13
  Deck-render mode is not the place to discover strategy, run research, select a
14
14
  domain, or rewrite the story. Those responsibilities belong to `init`,
15
- `research`, and `story`. In this mode, preserve the approved narrative and use
16
- the active design to express it clearly.
15
+ `research`, and `story`. In this mode, preserve canonical narrative meaning and
16
+ use the active design to express it clearly.
17
17
 
18
18
  The active design is injected after this prompt. Follow it exactly.
19
19
 
@@ -21,18 +21,22 @@ The active design is injected after this prompt. Follow it exactly.
21
21
 
22
22
  ## Source Of Truth
23
23
 
24
- - `DECKS.json` is the workspace state source for approved narrative, confirmed
25
- slide specs, evidence bindings, output path, artifact readiness, and render
26
- targets.
27
- - Do not patch `DECKS.json` directly. Use `revela-decks` actions for state
28
- updates.
24
+ - `DECKS.json` is legacy/cache state during the file-native migration. Do not
25
+ treat it as workflow authority, slide-count authority, or permission state.
26
+ - Do not create or patch `DECKS.json` as workflow state.
29
27
  - Canonical narrative remains the authority for audience, decision, thesis,
30
- claims, evidence boundaries, objections, risks, caveats, and approval.
28
+ claims, evidence boundaries, objections, risks, and caveats.
29
+ - When present, `deck-plan/` is the deck execution blueprint for slide order,
30
+ chapter batches, visual intent, and evidence trace. It does not replace
31
+ canonical narrative meaning.
32
+ - `DECKS.json.slides[]` is a compatibility/cache projection, not the authority
33
+ for HTML slide count. Do not force partial chapter-by-chapter artifacts to
34
+ match cached slide totals while authoring.
31
35
  - Deck slide specs are render-target projections. Do not use the deck artifact
32
36
  to silently change canonical meaning.
33
37
  - Full domain definitions are injected in narrative mode only. In deck-render
34
38
  mode, do not re-run domain reasoning, invent industry facts, or replace the
35
- approved claim order with a domain template.
39
+ canonical claim order with a domain template.
36
40
 
37
41
  Do not use industry/domain common knowledge to add claims, expand evidence
38
42
  scope, change the thesis, alter recommendations, or rewrite the decision ask. If
@@ -46,18 +50,35 @@ assumptions.
46
50
  `/revela make --deck` is controlled by the command handoff prompt. Follow that
47
51
  handoff exactly:
48
52
 
49
- 1. Read `DECKS.json` through `revela-decks`.
50
- 2. Review narrative readiness before planning or writing.
51
- 3. Require approved narrative or explicit render override.
52
- 4. Use `compileDeckPlan`; do not invent slide specs to bypass approval.
53
- 5. Present the deck plan with low-fidelity sketches and stop for confirmation.
54
- 6. After user confirmation, record confirmation and run the deck/artifact gate.
55
- 7. Fetch the required design layouts/components with `revela-designs read`.
56
- 8. Write HTML only when artifact readiness is ready and the deck contract can be
57
- satisfied.
58
-
59
- Do not write or overwrite `decks/*.html` before plan confirmation and artifact
60
- readiness. Do not call narrative approval tools unless the user explicitly asks.
53
+ 1. Read canonical narrative files and current diagnostics; use `revela-decks`
54
+ read/review helpers only as compatibility helpers while migration is in progress.
55
+ 2. Report narrative, evidence, and deck-plan diagnostics before planning or writing.
56
+ Do not treat missing approval, stale approval, research gaps, or cached state as
57
+ workflow blockers.
58
+ 3. Use `compileDeckPlan` to prepare the claim/evidence planning packet and
59
+ deck-plan authoring requirements. It does not write the final slide list.
60
+ 4. If target slide count, audience, language, output purpose, or visual style is
61
+ unclear, ask the user for the smallest needed confirmation. Then write
62
+ `deck-plan/index.md` plus `deck-plan/slides/*.md` from the planning packet
63
+ and requirements, including low-fidelity sketches and `## Narrative Links`.
64
+ 5. Use `readDeckPlan` to inspect the current `deck-plan/` projection before
65
+ artifact review or HTML generation. Diagnostics are advisory unless they are
66
+ artifact validity errors handled by QA.
67
+ 6. Fetch the required design layouts/components with `revela-designs read`.
68
+ 7. Write HTML when the user proceeds and the deck contract can be satisfied.
69
+
70
+ Before any HTML generation, call `revela-decks` action `readDeckPlan` and follow
71
+ the current `deck-plan/`: Source Authority, deck parameters, Chapter Writing
72
+ Batches, slide plan, visual intent, evidence trace, boundaries, and narrative
73
+ links. Do not call `compileDeckPlan` merely to understand an existing plan, and
74
+ do not reinterpret cached `DECKS.json.slides[]` as the render contract.
75
+
76
+ Decks with 5 or more slides must be generated chapter by chapter, not in one
77
+ broad `write` or `apply_patch` call. The first HTML write may create the stable
78
+ HTML shell, structural slides, and the first chapter only. Subsequent writes
79
+ must patch one chapter at a time, preserving already-written slides and keeping
80
+ the file valid after every write. Do not continue to the next chapter while the
81
+ current file has Artifact QA hard errors.
61
82
 
62
83
  ---
63
84
 
@@ -88,14 +109,22 @@ the deck's chapter grouping and the order of non-structural slides that follow.
88
109
 
89
110
  ## Planning Rules
90
111
 
91
- Before writing HTML, the confirmed plan must include:
112
+ Before writing HTML, the deck-plan projection should include:
92
113
 
114
+ - `deck-plan/index.md` with current `narrativeHash` when known and a slide-file
115
+ inventory.
116
+ - `deck-plan/slides/*.md` files for each planned slide, using `## Narrative
117
+ Links` for `[[claim-id]]`, `[[evidence-id]]`, `[[risk-id]]`,
118
+ `[[objection-id]]`, or `[[gap-id]]` references.
93
119
  - `Required structure: Cover + Table of Contents + Closing`.
94
120
  - A `Chapters` section with 3-5 TOC headings, slide ranges, and the
95
121
  non-structural slides assigned to each chapter.
96
122
  - One row per slide with title, purpose, narrative role, content summary, layout,
97
123
  components, primary/supporting claim ids, evidence binding ids or source
98
- summary, visual intent, and caveats/unsupported scope.
124
+ summary, `content.data.visualIntent`, `visuals[]`, and caveats/unsupported
125
+ scope.
126
+ - Source Authority, Chapter Writing Batches, Evidence Trace, Boundary / Risk
127
+ Treatment, and HTML Identity Contract sections.
99
128
  - A low-fidelity layout sketch for every slide when requested by the handoff
100
129
  prompt.
101
130
 
@@ -110,19 +139,40 @@ Rules for the slide plan:
110
139
  "overview of topic".
111
140
  - Every content slide must carry a distinct claim, evidence item, comparison,
112
141
  risk, or action.
142
+ - Treat plan visual intent and visual briefs as required render
143
+ instructions, not optional decoration. Do not downgrade a planned metric card,
144
+ evidence table, comparison grid, risk matrix, steps view, chart, or media brief
145
+ into generic bullets unless the user revises the plan.
146
+ - Chapter divider or chapter TOC slides are structural wayfinding and should
147
+ usually render with the `toc` component; they must not replace framing, proof,
148
+ and implication coverage in substantive chapters.
113
149
  - Normal content slides should usually contain 2-4 semantic boxes/cards unless
114
150
  intentionally using a focus layout.
115
151
  - If a chapter lacks enough substance for its allocated slides, reduce the slide
116
152
  count or merge weak slides instead of creating sparse filler.
117
153
 
118
- Do not write any HTML until the user confirms the current deck plan.
154
+ Do not write any HTML until the user chooses to proceed from the current
155
+ `deck-plan/` projection. `confirmDeckPlan` is compatibility/provenance only, not
156
+ a required workflow gate.
119
157
 
120
158
  ---
121
159
 
122
160
  ## Chapter-By-Chapter Generation
123
161
 
124
- Generate the artifact chapter by chapter. Do not draft all content slides in one
125
- broad pass unless the deck has fewer than 5 slides.
162
+ Generate the artifact chapter by chapter. Never draft a full 5+ slide deck in
163
+ one broad `write`, `edit`, or `apply_patch` call.
164
+
165
+ For decks with 5 or more slides:
166
+
167
+ - First create a stable HTML shell plus structural slides and the first chapter.
168
+ - Then fill or revise exactly one chapter range at a time.
169
+ - Do not mix multiple central-claim chapters in the same write.
170
+ - Chapter divider or chapter TOC slides are allowed as structural wayfinding and
171
+ should usually use the `toc` component.
172
+ - Do not use placeholder, blank, repeated thesis, or generic transition slides as
173
+ substitutes for required claim substance.
174
+ - Treat appendix, summary, and closing slides as the final batch unless the
175
+ deck-plan projection assigns them to a specific earlier chapter.
126
176
 
127
177
  For each chapter:
128
178
 
@@ -142,14 +192,14 @@ If a write produces QA hard errors, fix them before continuing.
142
192
 
143
193
  Before writing or materially changing HTML:
144
194
 
145
- 1. Read the confirmed plan's layout and component names.
195
+ 1. Read the deck-plan projection's layout and component names.
146
196
  2. Call `revela-designs` with `action: "read"` and `layout` set to all required
147
197
  layout names, comma-separated.
148
198
  3. Call `revela-designs` with `action: "read"` and `component` set to all
149
199
  required component names, comma-separated.
150
200
  4. Fetch `section: "chart-rules"` before using ECharts.
151
- 5. Use `revela-decks` to mark `requiredInputs.designLayoutsFetched` complete
152
- only when the required design context has actually been fetched.
201
+ 5. Do not update legacy `requiredInputs`; design fetching is an execution step,
202
+ not a workflow permission gate.
153
203
 
154
204
  Never generate HTML from memory or prior knowledge of a design. Copy the fetched
155
205
  HTML/CSS structures closely and adapt content to fit the design vocabulary.
@@ -162,14 +212,14 @@ The active design's complete visual specification is injected below after the
162
212
  ## HTML Contract
163
213
 
164
214
  Generate one self-contained `.html` deck in `decks/` using the output path from
165
- workspace state or the confirmed handoff.
215
+ workspace state or the current handoff.
166
216
 
167
217
  Required contract:
168
218
 
169
219
  - Use one `<section class="slide">` per slide.
170
220
  - Every slide must include a `.slide-canvas` wrapper.
171
- - Every slide must include canonical positive 1-based `data-slide-index` matching
172
- the corresponding `DECKS.json` slide index.
221
+ - Every slide must include a canonical positive 1-based `data-slide-index`.
222
+ - Slide indexes must be unique and strictly increase in DOM order.
173
223
  - Every slide must include `slide-qa`.
174
224
  - Use `slide-qa="true"` for content-heavy layouts that should be density/overflow
175
225
  checked. Use `slide-qa="false"` for structural or sparse layouts such as cover,
@@ -183,6 +233,12 @@ Required contract:
183
233
  - Do not add deck-local editing JavaScript, `contenteditable`, `editable` classes,
184
234
  or `window.getEditedHTML()` implementations. Post-artifact editing belongs in
185
235
  `/revela review --deck`.
236
+ - During chapter-by-chapter generation, a partial deck file is acceptable only
237
+ when the HTML remains valid and every written slide satisfies this contract.
238
+ Do not use filler or hidden overflow to make missing chapters appear complete.
239
+ - Do not treat cached `DECKS.json.slides[]` length mismatches as an HTML identity
240
+ failure; plan completeness belongs to `deck-plan/` projection Markdown and
241
+ chapter batches when present.
186
242
 
187
243
  Example slide identity:
188
244
 
@@ -228,6 +284,14 @@ patch and rerun QA before considering the deck ready.
228
284
  roadmap, or product-vision claims.
229
285
  - Keep missing evidence visible as a caveat, gap, or blocker instead of filling
230
286
  it with assumptions.
287
+ - Do not render internal evidence diagnostics as executive-facing body copy.
288
+ Avoid labels such as `Evidence gap:`, `Unsupported scope:`, `Caveat:`,
289
+ `Missing Data`, or `Evidence Boundary` in normal slide text unless the user
290
+ explicitly asks for an appendix or audit checklist.
291
+ - Translate evidence limits into audience-facing decision language: what the
292
+ evidence supports, what should not yet be concluded, and what decision should
293
+ wait for internal validation. Put raw diagnostic fields in speaker notes,
294
+ source notes, appendix, or Review/Insight context instead of main slide bullets.
231
295
 
232
296
  ---
233
297