@cyber-dash-tech/revela 0.8.9 → 0.9.0

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.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Revela Narrative Reviewer — system prompt
3
+ *
4
+ * Injected via plugin config hook into the `revela-narrative-reviewer` subagent.
5
+ * The NARRATIVE_REVIEWER_SIGNATURE is used by the system.transform hook to
6
+ * detect this agent and skip injecting the SKILL+DESIGN deck-writing prompt.
7
+ */
8
+
9
+ export const NARRATIVE_REVIEWER_SIGNATURE = "[[REVELA-NARRATIVE-REVIEWER]]"
10
+
11
+ export const NARRATIVE_REVIEWER_PROMPT = `${NARRATIVE_REVIEWER_SIGNATURE}
12
+
13
+ # Revela Narrative Reviewer
14
+
15
+ You are a specialized read-only narrative reviewer for Revela.
16
+ Your sole job is to run a fixed narrative rubric against the narrative brief and
17
+ slide-plan alignment for a workspace deck. You do NOT write state, generate
18
+ slides, rewrite the deck, or decide authoritative write readiness.
19
+
20
+ ---
21
+
22
+ ## Mission
23
+
24
+ Given a review brief from the primary agent, assess whether the current deck's
25
+ \`narrativeBrief\`, slide plan, narrative roles, and evidence references pass the
26
+ fixed rubric below.
27
+
28
+ Prefer repeatability over creativity. You are not a brainstorming partner, copy
29
+ editor, or slide-polish agent. Do not search for optional improvements when the
30
+ rubric passes.
31
+
32
+ ---
33
+
34
+ ## Allowed Context
35
+
36
+ - Use \`revela-decks\` action \`read\` to inspect \`DECKS.json\`.
37
+ - Read existing workspace files only when the primary brief points to them or when
38
+ they are referenced by slide evidence, \`sourceMaterials\`, or research findings.
39
+ - You may read existing \`researches/{topic}/*.md\` files when they are referenced
40
+ by \`slides[].evidence[]\` or \`researchPlan[].findingsFile\`.
41
+ - Treat \`revela-decks review\` as the authoritative readiness gate owned by the
42
+ primary agent and tool layer. Your critique is advisory only.
43
+
44
+ ---
45
+
46
+ ## Hard Boundaries
47
+
48
+ - NEVER write, patch, or edit any file.
49
+ - NEVER call \`revela-decks\` actions \`init\`, \`upsertDeck\`, \`upsertSlides\`, \`review\`, or \`remember\`.
50
+ - NEVER call \`revela-research-save\`, \`revela-media-save\`, or any asset-writing tool.
51
+ - NEVER generate, write, patch, or edit \`decks/*.html\`.
52
+ - NEVER use \`websearch\` or \`webfetch\`; critique only from existing workspace state and files.
53
+ - NEVER invent evidence, quotes, page references, URLs, stakeholder beliefs, objections, or risks.
54
+ - NEVER claim the deck is ready or blocked. Only the primary agent reports readiness from \`revela-decks review\`.
55
+
56
+ ---
57
+
58
+ ## Review Method
59
+
60
+ 1. Read the current deck state with \`revela-decks\` action \`read\`.
61
+ 2. Inspect \`narrativeBrief\`, deck goal, audience, required decision/action, slide titles, purposes, narrative roles, content, and evidence references.
62
+ 3. Read referenced research findings only when needed to evaluate overclaim or support concerns.
63
+ 4. Run the rubric in the exact order below.
64
+ 5. Produce only rubric-tied findings with stable IDs. If all checks pass, output exactly \`Findings: none\`.
65
+
66
+ ---
67
+
68
+ ## Stable Rubric
69
+
70
+ Evaluate only these checks, in this order:
71
+
72
+ 1. \`NB-001\` Narrative brief completeness
73
+ - Trigger only when a substantial decision deck lacks enough \`narrativeBrief\` fields to evaluate story intent.
74
+ - Relevant fields: \`audienceBeliefBefore\`, \`audienceBeliefAfter\`, \`decisionOrAction\`, \`narrativeArc\`, \`keyClaims\`, \`objections\`, and \`risks\`.
75
+
76
+ 2. \`AB-001\` Audience belief shift not reflected
77
+ - Trigger only when \`audienceBeliefBefore\` or \`audienceBeliefAfter\` is present but the opener/early context or close/ask does not reflect that belief shift.
78
+
79
+ 3. \`KC-001\` Key claim not represented in slides
80
+ - Trigger only when a \`narrativeBrief.keyClaims[]\` item has no clear corresponding slide content or role.
81
+
82
+ 4. \`OBJ-001\` Objection not handled
83
+ - Trigger only when \`narrativeBrief.objections[]\` exists but no slide addresses the objection, caveat, risk, tradeoff, or response.
84
+
85
+ 5. \`RISK-001\` Risk or assumption not carried
86
+ - Trigger only when \`narrativeBrief.risks[]\` exists but no slide carries the risk, assumption, caveat, or tradeoff.
87
+
88
+ 6. \`ASK-001\` Decision/action not reflected in ask
89
+ - Trigger only when \`narrativeBrief.decisionOrAction\` exists but the ending, close, recommendation, or ask does not make the action concrete enough for the audience.
90
+
91
+ 7. \`EV-001\` Recommendation overreaches evidence
92
+ - Trigger only when a recommendation, investment conclusion, or strong claim is materially stronger than the recorded slide evidence or cited research findings.
93
+
94
+ 8. \`FLOW-001\` Declared narrative arc is broken
95
+ - Trigger only when the slide sequence materially violates \`narrativeBrief.narrativeArc\` or jumps from context to ask/recommendation without sufficient tension, evidence, or risk handling.
96
+
97
+ Do not create new IDs. Do not rename IDs. Do not output duplicate findings for the
98
+ same root issue; choose the first matching rubric ID in the order above.
99
+
100
+ ---
101
+
102
+ ## Stability Rules
103
+
104
+ - Do not brainstorm optional improvements.
105
+ - Do not suggest copy edits, slide polish, stronger phrasing, or extra examples unless tied to one stable rubric ID.
106
+ - Do not output a finding just because something could be clearer. Output only when the current deck state fails a rubric check.
107
+ - Do not introduce new advisory suggestions after a prior issue appears addressed by the current slide specs.
108
+ - Do not change IDs or severity between runs for the same underlying issue.
109
+ - If all rubric checks pass, write exactly \`Findings: none\` and stop after the required closing line.
110
+
111
+ ---
112
+
113
+ ## Output Format
114
+
115
+ Start exactly with:
116
+
117
+ \`Narrative review complete.\`
118
+
119
+ Then include:
120
+
121
+ \`Findings:\`
122
+
123
+ If all rubric checks pass, write exactly:
124
+
125
+ \`Findings: none\`
126
+
127
+ For each rubric finding, use this structure:
128
+ - \`id\`: one of \`NB-001\`, \`AB-001\`, \`KC-001\`, \`OBJ-001\`, \`RISK-001\`, \`ASK-001\`, \`EV-001\`, or \`FLOW-001\`
129
+ - \`severity\`: \`advisory\` or \`risk\`
130
+ - \`area\`: one of \`narrativeBrief\`, \`keyClaim\`, \`objection\`, \`risk\`, \`decisionAction\`, \`audienceBelief\`, \`evidenceOverreach\`, or \`flow\`
131
+ - \`finding\`: concise description of the narrative issue
132
+ - \`briefField\`: related \`narrativeBrief\` field, or \`none\`
133
+ - \`slideRefs\`: slide indexes/titles involved, or \`none\`
134
+ - \`evidenceConcern\`: evidence/source concern if any, or \`none\`
135
+ - \`suggestedAction\`: specific next improvement
136
+
137
+ Do not include general praise, a summary of strengths, or optional pre-write
138
+ improvements outside the finding structure.
139
+
140
+ End exactly with:
141
+
142
+ \`No direct state changes were made.\`
143
+ `
@@ -15,10 +15,12 @@ export function buildReviewPrompt({
15
15
 
16
16
  Goal:
17
17
  - Use ${DECKS_STATE_FILE} as the source of truth for whether the current workspace deck is ready to be written to \`decks/*.html\`.
18
- - Preserve the deck spec for future sessions: every slide's content, layout, components, evidence, visuals, and production status.
18
+ - Preserve the deck spec for future sessions: every slide's content, layout, components, evidence, visuals, production status, and the 0.9 narrative compiler brief when available.
19
19
  - Do not write, patch, or directly edit ${DECKS_STATE_FILE}. Use the \`revela-decks\` tool for all state changes.
20
20
  - Let \`revela-decks\` action \`review\` compute writeReadiness; do not manually set readiness to ready.
21
- - Treat this as an evidence and lightweight narrative readiness review, not only a checklist review: unsupported numbers, market sizing, recommendations, competitor comparisons, technical assertions, investment conclusions, weak so-what, missing risk/assumption handling, or abrupt narrative transitions should be made visible before writing.
21
+ - Treat this as an evidence and Narrative Compiler readiness review, not only a checklist review: unsupported numbers, market sizing, recommendations, competitor comparisons, technical assertions, investment conclusions, missing audience belief change, unclear decision/action, unproven key claims, unhandled objections, weak so-what, missing risk/assumption handling, or abrupt narrative transitions should be made visible before writing.
22
+ - For substantial decision decks, use the read-only Task subagent \`revela-narrative-reviewer\` for independent rubric-based critique of narrative brief and slide-plan alignment. Do not self-certify semantic narrative quality in the primary agent.
23
+ - Treat \`revela-narrative-reviewer\` findings as advisory critique only. Do not represent them as \`revela-decks\` readiness issues, blockers, or authoritative \`writeReadiness\`.
22
24
  - Treat source trace mapping as part of evidence readiness: when research findings have been read, relevant findings should appear in slide-level \`slides[].evidence[]\` records rather than only in raw research files.
23
25
 
24
26
  Current state:
@@ -33,14 +35,16 @@ Workspace boundary rules:
33
35
 
34
36
  Workflow:
35
37
  1. Call \`revela-decks\` with action \`read\` for the current workspace deck.
36
- 2. If no current deck exists but the conversation contains enough deck context, call \`revela-decks\` action \`upsertDeck\` with goal, outputPath, theme, requiredInputs, and researchPlan. Do not invent or ask for a deck key; the tool uses the workspace folder name internally.
38
+ 2. If no current deck exists but the conversation contains enough deck context, call \`revela-decks\` action \`upsertDeck\` with goal, outputPath, theme, requiredInputs, researchPlan, and narrativeBrief if the story intent is clear. Do not invent or ask for a deck key; the tool uses the workspace folder name internally.
37
39
  3. If \`researchPlan[].status\` is \`done\` or \`read\` and \`researchPlan[].findingsFile\` exists, verify that evidence-sensitive slide claims are backed by compact \`slides[].evidence[]\` records that reference the relevant findings file or source material where known.
38
40
  4. If a user-confirmed slide plan is available, call \`revela-decks\` action \`upsertSlides\` with every slide's title, purpose, narrativeRole, layout, components, structured content, evidence, visuals, and status. Use only lightweight narrativeRole values that are clear from the plan: \`context\`, \`tension\`, \`evidence\`, \`recommendation\`, \`risk\`, \`ask\`, \`appendix\`, or \`close\`.
39
41
  5. Prefer evidence records with \`findingsFile\`, \`sourcePath\`, \`location\`, \`quote\`, \`url\`, \`caveat\`, \`extractedTextPath\`, or \`extractedManifestPath\` when those fields are known from research files or extracted workspace materials.
40
42
  6. Do not invent quotes, page references, locations, URLs, caveats, or extraction paths. If source trace is missing, preserve the blocker or warning and report exactly what trace is needed.
41
43
  7. Only set requiredInputs fields true when explicit conversation state, files read, research findings read, selected design, fetched layouts/components, or user confirmation supports them. Do not infer completion.
42
- 8. Call \`revela-decks\` action \`review\`. The tool computes and writes \`writeReadiness\` plus structured readiness issues for the current workspace deck.
43
- 9. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool. If warnings exist, list them after blockers as residual risks; separate evidence/source warnings from narrative warnings when possible.
44
+ 8. For substantial decision decks, preserve a compact \`narrativeBrief\` through \`upsertDeck\` when the conversation or confirmed plan supports it. Do not invent stakeholder beliefs, objections, or risks; leave gaps visible if unknown.
45
+ 9. For substantial decision decks, launch the Task subagent with \`subagent_type: "revela-narrative-reviewer"\` after deck/slides are up to date. Ask it to read the current \`DECKS.json\`, run only its fixed rubric, use stable finding IDs, return \`Findings: none\` when all checks pass, and avoid optional pre-write improvements. Do not ask it to write state, call \`revela-decks review\`, or produce HTML.
46
+ 10. Call \`revela-decks\` action \`review\`. The tool computes and writes \`writeReadiness\` plus structured readiness issues for the current workspace deck.
47
+ 11. Briefly report whether the deck is ready. If blocked, list the exact blockers returned by the tool. If warnings exist, list them after blockers as residual risks; separate evidence/source warnings from narrative warnings when possible. If the reviewer returned findings, include them in a separate \`Narrative reviewer notes\` section and label them advisory.
44
48
 
45
49
  Minimum conditions for \`ready\`:
46
50
  - Topic, audience, slide count, language, and visual style/design are decided.
@@ -52,6 +56,7 @@ Minimum conditions for \`ready\`:
52
56
  - ${DECKS_STATE_FILE} contains per-slide specs with content, layout, components, and evidence where applicable.
53
57
  - Evidence-sensitive slide claims have compact evidence references with source trace where available. Numeric claims and strong recommendations should not be unsupported or source-only when trace exists.
54
58
  - Multi-slide decision decks have a practical narrative flow: context/tension before evidence, recommendations after support, risk or assumption handling when recommending action, and a clear so-what or ask at the end. Narrative gaps are normally warnings, not hard blockers.
59
+ - Substantial decision decks should have a compact \`narrativeBrief\` that states the intended audience belief change, required decision/action, key claims, likely objections, and risks/assumptions. Missing fields are narrative warnings, not hard blockers.
55
60
  - The needed design layouts and components have been fetched with \`revela-designs read\`.
56
61
  - No unresolved blockers remain.
57
62
 
@@ -61,6 +66,7 @@ Report format:
61
66
  - If warnings exist but the deck is otherwise ready, say the deck can be written but note the residual risks.
62
67
  - Report \`narrative_gap\` warnings as story-structure risks such as weak so-what, missing risk/assumption handling, conclusion before support, missing audience framing, or abrupt transition.
63
68
  - Do not invent evidence or silently downgrade blockers. Use the tool result as authoritative.
69
+ - Do not convert \`revela-narrative-reviewer\` advisory findings into tool readiness issues. Keep them separate from \`revela-decks review\` blockers and warnings, and preserve the reviewer's stable finding IDs when reporting them.
64
70
  - When reporting weak evidence, say whether the missing trace is \`findingsFile\`, \`sourcePath\`, \`location\`, \`quote\`, \`url\`, or \`caveat\` if that is clear from the reviewed materials.
65
71
 
66
72
  Rules:
@@ -58,6 +58,7 @@ export interface DeckSpec {
58
58
  audience?: string
59
59
  language?: string
60
60
  outputPath: string
61
+ narrativeBrief?: NarrativeBrief
61
62
  theme: {
62
63
  design?: string
63
64
  domain?: string
@@ -73,6 +74,16 @@ export interface DeckSpec {
73
74
  }
74
75
  }
75
76
 
77
+ export interface NarrativeBrief {
78
+ audienceBeliefBefore?: string
79
+ audienceBeliefAfter?: string
80
+ decisionOrAction?: string
81
+ narrativeArc?: string
82
+ keyClaims: string[]
83
+ objections: string[]
84
+ risks: string[]
85
+ }
86
+
76
87
  export interface RequiredInputs {
77
88
  topicClarified: boolean
78
89
  audienceClarified: boolean
@@ -242,6 +253,7 @@ export function createDeckSpec(input: Partial<DeckSpec> & { slug: string }): Dec
242
253
  audience: input.audience,
243
254
  language: input.language,
244
255
  outputPath: normalizeDeckPath(input.outputPath || `decks/${slug}.html`),
256
+ narrativeBrief: normalizeNarrativeBrief(input.narrativeBrief),
245
257
  theme: input.theme ?? {},
246
258
  requiredInputs: defaultRequiredInputs(input.requiredInputs),
247
259
  researchPlan: input.researchPlan ?? [],
@@ -468,6 +480,7 @@ function compactWorkspaceForPrompt(workspace: DecksState["workspace"]): DecksSta
468
480
  function compactDeckForPrompt(deck: DeckSpec): DeckSpec {
469
481
  return {
470
482
  ...deck,
483
+ narrativeBrief: compactNarrativeBriefForPrompt(deck.narrativeBrief),
471
484
  slides: deck.slides.map((slide) => ({
472
485
  ...slide,
473
486
  content: {
@@ -480,6 +493,19 @@ function compactDeckForPrompt(deck: DeckSpec): DeckSpec {
480
493
  }
481
494
  }
482
495
 
496
+ function compactNarrativeBriefForPrompt(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
497
+ if (!brief) return undefined
498
+ return {
499
+ audienceBeliefBefore: truncatePromptText(brief.audienceBeliefBefore),
500
+ audienceBeliefAfter: truncatePromptText(brief.audienceBeliefAfter),
501
+ decisionOrAction: truncatePromptText(brief.decisionOrAction),
502
+ narrativeArc: truncatePromptText(brief.narrativeArc),
503
+ keyClaims: brief.keyClaims.map((claim) => truncatePromptText(claim)).filter(Boolean) as string[],
504
+ objections: brief.objections.map((objection) => truncatePromptText(objection)).filter(Boolean) as string[],
505
+ risks: brief.risks.map((risk) => truncatePromptText(risk)).filter(Boolean) as string[],
506
+ }
507
+ }
508
+
483
509
  function compactEvidenceForPrompt(evidence: EvidenceRef): EvidenceRef {
484
510
  return {
485
511
  ...evidence,
@@ -621,6 +647,53 @@ function computeNarrativeReadinessIssues(deck: DeckSpec): ReadinessIssue[] {
621
647
  const issues: ReadinessIssue[] = []
622
648
  const slides = deck.slides.filter((slide) => slide.index > 0).sort((a, b) => a.index - b.index)
623
649
  if (slides.length === 0) return issues
650
+ const decisionOriented = isDecisionOrientedDeck(deck, slides)
651
+
652
+ if (decisionOriented && !hasNarrativeBriefContent(deck.narrativeBrief)) {
653
+ issues.push(warningIssue(
654
+ "narrative_gap",
655
+ "Narrative brief is missing for a decision-oriented deck",
656
+ "Add a 0.9 narrativeBrief with audience belief before/after, decisionOrAction, narrativeArc, keyClaims, objections, and risks so review can compile the deck against explicit story intent.",
657
+ ))
658
+ }
659
+
660
+ if (decisionOriented && deck.narrativeBrief) {
661
+ if (!deck.narrativeBrief.audienceBeliefAfter?.trim()) {
662
+ issues.push(warningIssue(
663
+ "narrative_gap",
664
+ "Narrative brief is missing the intended audience belief after the deck",
665
+ "Set narrativeBrief.audienceBeliefAfter so the deck can be reviewed against the belief change it is meant to create.",
666
+ ))
667
+ }
668
+ if (!deck.narrativeBrief.decisionOrAction?.trim() && slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide))) {
669
+ issues.push(warningIssue(
670
+ "narrative_gap",
671
+ "Narrative brief is missing the decision or action the deck should drive",
672
+ "Set narrativeBrief.decisionOrAction so recommendation and ask slides have an explicit communication target.",
673
+ ))
674
+ }
675
+ if (deck.narrativeBrief.keyClaims.length === 0 && slides.some(isRecommendationSlide)) {
676
+ issues.push(warningIssue(
677
+ "narrative_gap",
678
+ "Narrative brief has no key claims for the recommendation to prove",
679
+ "Add narrativeBrief.keyClaims that capture the main claims the deck must support with slide evidence.",
680
+ ))
681
+ }
682
+ if (deck.narrativeBrief.objections.length === 0 && slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide))) {
683
+ issues.push(warningIssue(
684
+ "narrative_gap",
685
+ "Narrative brief has no stakeholder objections to handle",
686
+ "Add likely objections or questions to narrativeBrief.objections so the story can anticipate resistance before the ask.",
687
+ ))
688
+ }
689
+ if (deck.narrativeBrief.risks.length === 0 && slides.some(isRecommendationSlide)) {
690
+ issues.push(warningIssue(
691
+ "narrative_gap",
692
+ "Narrative brief has no risks, assumptions, or tradeoffs for the recommendation",
693
+ "Add risks, assumptions, caveats, or tradeoffs to narrativeBrief.risks so the recommendation does not overclaim certainty.",
694
+ ))
695
+ }
696
+ }
624
697
 
625
698
  if (slides.length >= 4 && slides.every((slide) => !slide.narrativeRole)) {
626
699
  issues.push(warningIssue(
@@ -700,6 +773,26 @@ function computeNarrativeReadinessIssues(deck: DeckSpec): ReadinessIssue[] {
700
773
  return issues
701
774
  }
702
775
 
776
+ function isDecisionOrientedDeck(deck: DeckSpec, slides: SlideSpec[]): boolean {
777
+ return Boolean(
778
+ deck.narrativeBrief?.decisionOrAction?.trim() ||
779
+ slides.some((slide) => isAskSlide(slide) || isRecommendationSlide(slide)) ||
780
+ /\b(decision|approve|approval|recommend(?:ation)?|go\/?no-go|action)\b|决策|批准|建议|行动/.test(deck.goal.toLowerCase()),
781
+ )
782
+ }
783
+
784
+ function hasNarrativeBriefContent(brief: NarrativeBrief | undefined): boolean {
785
+ return Boolean(
786
+ brief?.audienceBeliefBefore?.trim() ||
787
+ brief?.audienceBeliefAfter?.trim() ||
788
+ brief?.decisionOrAction?.trim() ||
789
+ brief?.narrativeArc?.trim() ||
790
+ brief?.keyClaims.length ||
791
+ brief?.objections.length ||
792
+ brief?.risks.length,
793
+ )
794
+ }
795
+
703
796
  function blockerIssue(type: ReadinessIssueType, message: string, suggestedAction: string, extra: Partial<ReadinessIssue> = {}): ReadinessIssue {
704
797
  return { type, severity: "blocker", message, suggestedAction, ...extra }
705
798
  }
@@ -839,6 +932,38 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
839
932
  .sort((a, b) => a.index - b.index)
840
933
  }
841
934
 
935
+ function normalizeNarrativeBrief(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
936
+ if (!brief) return undefined
937
+ const normalized: NarrativeBrief = {
938
+ audienceBeliefBefore: cleanOptionalText(brief.audienceBeliefBefore),
939
+ audienceBeliefAfter: cleanOptionalText(brief.audienceBeliefAfter),
940
+ decisionOrAction: cleanOptionalText(brief.decisionOrAction),
941
+ narrativeArc: cleanOptionalText(brief.narrativeArc),
942
+ keyClaims: normalizeTextList(brief.keyClaims),
943
+ objections: normalizeTextList(brief.objections),
944
+ risks: normalizeTextList(brief.risks),
945
+ }
946
+ if (
947
+ !normalized.audienceBeliefBefore &&
948
+ !normalized.audienceBeliefAfter &&
949
+ !normalized.decisionOrAction &&
950
+ !normalized.narrativeArc &&
951
+ normalized.keyClaims.length === 0 &&
952
+ normalized.objections.length === 0 &&
953
+ normalized.risks.length === 0
954
+ ) return undefined
955
+ return normalized
956
+ }
957
+
958
+ function normalizeTextList(values: string[] | undefined): string[] {
959
+ return [...new Set((values ?? []).map(cleanOptionalText).filter(Boolean) as string[])]
960
+ }
961
+
962
+ function cleanOptionalText(value: string | undefined): string | undefined {
963
+ const text = String(value ?? "").trim()
964
+ return text || undefined
965
+ }
966
+
842
967
  function hasSlideContent(slide: SlideSpec): boolean {
843
968
  const content = slide.content ?? {}
844
969
  return Boolean(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.8.9",
3
+ "version": "0.9.0",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/plugin.ts CHANGED
@@ -87,6 +87,7 @@ import pdfTool from "./tools/pdf"
87
87
  import pptxTool from "./tools/pptx"
88
88
  import createEditTool from "./tools/edit"
89
89
  import { RESEARCH_PROMPT, RESEARCH_AGENT_SIGNATURE } from "./lib/agents/research-prompt"
90
+ import { NARRATIVE_REVIEWER_PROMPT, NARRATIVE_REVIEWER_SIGNATURE } from "./lib/agents/narrative-reviewer-prompt"
90
91
  import { formatReport, runComplianceQA } from "./lib/qa"
91
92
  import { extractDesignClasses } from "./lib/design/designs"
92
93
  import { log, childLog } from "./lib/log"
@@ -204,7 +205,7 @@ const server: Plugin = (async (pluginCtx) => {
204
205
  }
205
206
 
206
207
  return {
207
- // ── Register /revela command + revela-research subagent ───────────────
208
+ // ── Register /revela command + Revela subagents ───────────────────────
208
209
  config: async (opencodeConfig) => {
209
210
  opencodeConfig.command ??= {}
210
211
  opencodeConfig.command["revela"] = {
@@ -235,6 +236,24 @@ const server: Plugin = (async (pluginCtx) => {
235
236
  // Give revela-research explicit websearch allow (overrides global deny below)
236
237
  ;(opencodeConfig.agent["revela-research"].permission as any).websearch = "allow"
237
238
 
239
+ // Register the read-only narrative reviewer subagent.
240
+ // It can inspect workspace state and referenced files, but cannot write or browse.
241
+ opencodeConfig.agent["revela-narrative-reviewer"] = {
242
+ description: "Revela narrative reviewer — read-only critique of narrative brief and slide-plan alignment",
243
+ mode: "subagent",
244
+ prompt: NARRATIVE_REVIEWER_PROMPT,
245
+ permission: {
246
+ edit: "deny",
247
+ bash: {
248
+ "*": "deny",
249
+ "ls *": "allow",
250
+ "ls": "allow",
251
+ },
252
+ webfetch: "deny",
253
+ websearch: "deny",
254
+ } as any,
255
+ }
256
+
238
257
  // Block websearch for the primary agent globally.
239
258
  // permission.ask hook is not triggered by OpenCode (no R.trigger call in binary).
240
259
  // tool.execute.before throw is swallowed (trigger().catch(()=>{})).
@@ -481,7 +500,7 @@ const server: Plugin = (async (pluginCtx) => {
481
500
 
482
501
  // ── Inject three-layer prompt when enabled ─────────────────────────────
483
502
  // Skip injection for:
484
- // 1. revela-research subagent (has its own research-focused prompt)
503
+ // 1. Revela subagents (they have focused prompts)
485
504
  // 2. OpenCode internal agents (title, summary, compaction)
486
505
  "experimental.chat.system.transform": async (input, output) => {
487
506
  if (!ctx.enabled) return
@@ -498,6 +517,10 @@ const server: Plugin = (async (pluginCtx) => {
498
517
  }
499
518
  ctx.isResearchAgent = false
500
519
 
520
+ // Skip revela-narrative-reviewer subagent — it is read-only critique,
521
+ // not a deck-writing agent and not a research agent.
522
+ if (systemText.includes(NARRATIVE_REVIEWER_SIGNATURE)) return
523
+
501
524
  // Skip OpenCode internal system agents (title generator, summary, compaction)
502
525
  if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) return
503
526
 
package/skill/SKILL.md CHANGED
@@ -83,6 +83,7 @@ Create or update the active deck in `DECKS.json` through `revela-decks` actions
83
83
  `upsertDeck` and `upsertSlides`. Keep the deck spec current as work progresses:
84
84
  - `goal` — purpose and decision/context
85
85
  - `audience`, `language`, `outputPath`, and `theme`
86
+ - `narrativeBrief` — for substantial decision decks, the 0.9 compiler brief: audience belief before/after, decisionOrAction, narrativeArc, keyClaims, objections, and risks
86
87
  - `requiredInputs` — checklist state for prewrite readiness
87
88
  - `researchPlan` — axes, status, and findings files
88
89
  - `slides` — confirmed per-slide title, purpose, layout, components, content, evidence, visuals, and status
@@ -200,6 +201,26 @@ state updates. Do not write temporary hypotheses, unsupported conclusions,
200
201
  secrets, or inferred user preferences. User and workflow preferences require
201
202
  explicit user intent to remember.
202
203
 
204
+ #### Narrative Review via `revela-narrative-reviewer`
205
+
206
+ `revela-narrative-reviewer` is a read-only OpenCode subagent, **not a tool**.
207
+ Launch it through the Task tool with `subagent_type: "revela-narrative-reviewer"`
208
+ when a substantial decision deck needs independent rubric-based critique of the
209
+ Narrative Compiler brief and slide-plan alignment.
210
+
211
+ Use it after the narrative brief and slide specs are recorded in `DECKS.json`,
212
+ and before treating narrative quality as reviewed. The primary agent should not
213
+ self-certify semantic narrative quality. `revela-decks review` remains the
214
+ authoritative write-readiness gate; reviewer findings are advisory notes only.
215
+ The reviewer uses stable finding IDs such as `NB-001`, `KC-001`, `ASK-001`, and
216
+ `EV-001`. If the fixed rubric passes, it should return `Findings: none` rather
217
+ than inventing optional improvements.
218
+
219
+ The reviewer may read `DECKS.json`, slide specs, evidence refs, and existing
220
+ `researches/{workspace-key}/*.md` files referenced by the deck. It must not write
221
+ state, call `upsertDeck`, call `upsertSlides`, call `revela-decks review`, use
222
+ websearch/webfetch, or generate/edit HTML.
223
+
203
224
  #### AI Knowledge and User Questions
204
225
 
205
226
  Use AI knowledge only to fill remaining gaps around verified sources. Mark it
@@ -213,6 +234,8 @@ already checked and what specific missing information is needed.
213
234
 
214
235
  - **NEVER** use `websearch` directly from the primary agent; delegate web research to `revela-research` subagents
215
236
  - **NEVER** call `revela-research` as a tool; use Task with `subagent_type: "revela-research"`
237
+ - **NEVER** call `revela-narrative-reviewer` as a tool; use Task with `subagent_type: "revela-narrative-reviewer"`
238
+ - **NEVER** present `revela-narrative-reviewer` findings as authoritative `revela-decks review` blockers or readiness issues
216
239
  - **NEVER** collapse distinct research axes into one broad agent brief when parallel focused briefs would be clearer
217
240
  - **ALWAYS** use `revela-decks` action `read` before deciding what research is needed
218
241
  - **ALWAYS** read each `researches/{workspace-key}/{axis}.md` after agents complete
@@ -287,8 +310,17 @@ core rules and the visual design below.
287
310
 
288
311
  ### Phase 4 — Presentation Plan
289
312
 
290
- After all research is complete and findings have been read, present a detailed
291
- slide plan to the user **before writing any HTML**.
313
+ After all research is complete and findings have been read, present a compact narrative brief
314
+ and a detailed slide plan to the user **before writing any HTML**.
315
+
316
+ For substantial decision decks, first summarize the Narrative Compiler brief:
317
+ - Audience belief before: what the audience currently believes, assumes, or does not yet understand
318
+ - Audience belief after: what the audience should believe or understand after the deck
319
+ - Decision/action: the approval, decision, behavior, or next step the deck should drive
320
+ - Narrative arc: the intended story path, such as context -> tension -> evidence -> recommendation -> risk -> ask
321
+ - Key claims: the main claims the deck must prove
322
+ - Likely objections: stakeholder resistance or questions the story should handle
323
+ - Risks/assumptions: caveats, tradeoffs, or uncertainty that should travel with the recommendation
292
324
 
293
325
  Format the plan as a markdown table:
294
326
 
@@ -320,8 +352,9 @@ Then ask:
320
352
  - On change request → update the table and ask again
321
353
 
322
354
  After the user confirms the slide plan, update `DECKS.json` through `revela-decks`:
323
- - Call `upsertDeck` to mark completed `requiredInputs` only when explicitly satisfied.
355
+ - Call `upsertDeck` to preserve `narrativeBrief` when available and mark completed `requiredInputs` only when explicitly satisfied.
324
356
  - Call `upsertSlides` with the confirmed per-slide content, narrativeRole, layout, components, and evidence.
357
+ - For substantial decision decks, use Task with `subagent_type: "revela-narrative-reviewer"` for read-only rubric-based critique of narrativeBrief and slide-plan alignment. Ask for stable finding IDs and `Findings: none` when the rubric passes; do not ask the reviewer to write state, determine readiness, or brainstorm optional improvements.
325
358
  - Keep write readiness blocked until Phase 5 calls `revela-decks review` and the tool returns ready.
326
359
 
327
360
  ---
package/tools/decks.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  writeDecksState,
11
11
  workspaceDeckSlug,
12
12
  type DeckSpec,
13
+ type NarrativeBrief,
13
14
  type RequiredInputs,
14
15
  type ResearchAxis,
15
16
  type SourceMaterial,
@@ -31,6 +32,15 @@ export default tool({
31
32
  audience: tool.schema.string().optional().describe("For upsertDeck: deck audience."),
32
33
  language: tool.schema.string().optional().describe("For upsertDeck: deck language."),
33
34
  outputPath: tool.schema.string().optional().describe("For upsertDeck: target output path, normally decks/{workspace-name}.html."),
35
+ narrativeBrief: tool.schema.object({
36
+ audienceBeliefBefore: tool.schema.string().optional().describe("What the audience currently believes, assumes, or does not yet understand."),
37
+ audienceBeliefAfter: tool.schema.string().optional().describe("What the audience should believe or understand after the deck."),
38
+ decisionOrAction: tool.schema.string().optional().describe("The decision, approval, action, or behavioral change the deck is meant to drive."),
39
+ narrativeArc: tool.schema.string().optional().describe("Compact story arc, such as context -> tension -> evidence -> recommendation -> ask."),
40
+ keyClaims: tool.schema.array(tool.schema.string()).optional().describe("Main claims the deck must prove or communicate."),
41
+ objections: tool.schema.array(tool.schema.string()).optional().describe("Likely stakeholder objections or questions the narrative should handle."),
42
+ risks: tool.schema.array(tool.schema.string()).optional().describe("Risks, assumptions, caveats, or tradeoffs that should travel with the narrative."),
43
+ }).optional().describe("For upsertDeck: 0.9 Narrative Compiler brief used to review story intent before writing."),
34
44
  design: tool.schema.string().optional().describe("For upsertDeck: active design name."),
35
45
  domain: tool.schema.string().optional().describe("For upsertDeck: active domain name."),
36
46
  memory: tool.schema.string().optional().describe("For remember: explicit user or workflow preference to store."),
@@ -140,6 +150,7 @@ export default tool({
140
150
  audience: args.audience ?? existing?.audience,
141
151
  language: args.language ?? existing?.language,
142
152
  outputPath: args.outputPath ?? existing?.outputPath,
153
+ narrativeBrief: (args.narrativeBrief as NarrativeBrief | undefined) ?? existing?.narrativeBrief,
143
154
  theme: {
144
155
  design: args.design ?? existing?.theme?.design,
145
156
  domain: args.domain ?? existing?.theme?.domain,