@cyber-dash-tech/revela 0.11.0 → 0.13.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.
- package/README.md +35 -29
- package/README.zh-CN.md +35 -29
- package/lib/commands/brief.ts +63 -0
- package/lib/commands/designs.ts +2 -2
- package/lib/commands/domains.ts +2 -2
- package/lib/commands/enable.ts +19 -19
- package/lib/commands/help.ts +7 -3
- package/lib/commands/init.ts +30 -19
- package/lib/commands/narrative.ts +160 -0
- package/lib/commands/review.ts +115 -1
- package/lib/decks-state.ts +46 -3
- package/lib/edit/prompt.ts +3 -0
- package/lib/inspection-context/compile.ts +159 -5
- package/lib/inspection-context/project.ts +20 -0
- package/lib/narrative-state/coverage.ts +100 -0
- package/lib/narrative-state/display.ts +219 -0
- package/lib/narrative-state/executive-brief.ts +246 -0
- package/lib/narrative-state/hash.ts +61 -0
- package/lib/narrative-state/map-html.ts +348 -0
- package/lib/narrative-state/map.ts +282 -0
- package/lib/narrative-state/normalize.ts +361 -0
- package/lib/narrative-state/project-compat.ts +14 -0
- package/lib/narrative-state/queries.ts +433 -0
- package/lib/narrative-state/readiness.ts +359 -0
- package/lib/narrative-state/render-plan.ts +250 -0
- package/lib/narrative-state/research-gaps.ts +191 -0
- package/lib/narrative-state/types.ts +172 -0
- package/lib/prompt-builder.ts +59 -26
- package/lib/workspace-state/evidence-status.ts +21 -1
- package/lib/workspace-state/graph.ts +174 -2
- package/lib/workspace-state/types.ts +13 -1
- package/package.json +1 -1
- package/plugin.ts +58 -2
- package/skill/NARRATIVE_SKILL.md +64 -0
- package/tools/decks.ts +265 -2
- package/tools/narrative-view.ts +84 -0
- package/tools/workspace-scan.ts +14 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "crypto"
|
|
2
2
|
import type { DeckSpec, DecksState, EvidenceRef, NarrativeBrief, ResearchAxis, SlideSpec, SourceMaterial } from "../decks-state"
|
|
3
|
+
import type { NarrativeClaimRelation, NarrativeEvidenceBinding, NarrativeResearchGap, NarrativeStateV1 } from "../narrative-state/types"
|
|
3
4
|
import { renderTargetId } from "./render-targets"
|
|
4
5
|
import type { GraphEdge, GraphEdgeType, GraphNode, GraphNodeType, RenderTarget, WorkspaceGraph } from "./types"
|
|
5
6
|
|
|
@@ -19,7 +20,7 @@ export function projectWorkspaceGraph(state: DecksState, options: ProjectWorkspa
|
|
|
19
20
|
for (const material of state.workspace.sourceMaterials ?? []) addSourceMaterial(builder, material)
|
|
20
21
|
for (const axis of deck.researchPlan ?? []) addResearchFinding(builder, axis)
|
|
21
22
|
|
|
22
|
-
const narrativeId = addNarrative(builder, deck)
|
|
23
|
+
const narrativeId = addNarrative(builder, state, deck)
|
|
23
24
|
for (const slide of deck.slides.slice().sort((a, b) => a.index - b.index)) addSlide(builder, slide)
|
|
24
25
|
for (const slide of deck.slides.slice().sort((a, b) => a.index - b.index)) addSlideClaimsAndEvidence(builder, slide)
|
|
25
26
|
const targets = renderTargetsForDeck(state, deck)
|
|
@@ -84,7 +85,9 @@ function addResearchFinding(builder: GraphBuilder, axis: ResearchAxis): void {
|
|
|
84
85
|
})
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function addNarrative(builder: GraphBuilder, deck: DeckSpec): string | undefined {
|
|
88
|
+
function addNarrative(builder: GraphBuilder, state: DecksState, deck: DeckSpec): string | undefined {
|
|
89
|
+
if (hasCanonicalNarrative(state.narrative)) return addCanonicalNarrative(builder, state.narrative, deck)
|
|
90
|
+
|
|
88
91
|
const brief = deck.narrativeBrief
|
|
89
92
|
if (!hasNarrativeBrief(brief)) return undefined
|
|
90
93
|
|
|
@@ -122,6 +125,135 @@ function addNarrative(builder: GraphBuilder, deck: DeckSpec): string | undefined
|
|
|
122
125
|
return narrativeId
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
function addCanonicalNarrative(builder: GraphBuilder, narrative: NarrativeStateV1, deck: DeckSpec): string {
|
|
129
|
+
addNode(builder, {
|
|
130
|
+
id: narrative.id,
|
|
131
|
+
type: "narrativeIntent",
|
|
132
|
+
label: narrative.thesis?.statement || deck.goal || narrative.id,
|
|
133
|
+
data: compactData({
|
|
134
|
+
status: narrative.status,
|
|
135
|
+
goal: deck.goal,
|
|
136
|
+
audience: narrative.audience.primary || deck.audience,
|
|
137
|
+
language: deck.language,
|
|
138
|
+
beliefBefore: narrative.audience.beliefBefore,
|
|
139
|
+
beliefAfter: narrative.audience.beliefAfter,
|
|
140
|
+
decisionOrAction: narrative.decision.action,
|
|
141
|
+
thesis: narrative.thesis?.statement,
|
|
142
|
+
}),
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
for (const claim of narrative.claims) {
|
|
146
|
+
addNode(builder, {
|
|
147
|
+
id: claim.id,
|
|
148
|
+
type: "claim",
|
|
149
|
+
label: claim.text,
|
|
150
|
+
data: compactData({
|
|
151
|
+
text: claim.text,
|
|
152
|
+
kind: claim.kind,
|
|
153
|
+
importance: claim.importance,
|
|
154
|
+
evidenceRequired: claim.evidenceRequired,
|
|
155
|
+
evidenceStatus: claim.evidenceStatus,
|
|
156
|
+
supportedScope: claim.supportedScope,
|
|
157
|
+
unsupportedScope: claim.unsupportedScope,
|
|
158
|
+
caveats: claim.caveats,
|
|
159
|
+
source: "canonicalNarrative",
|
|
160
|
+
}),
|
|
161
|
+
})
|
|
162
|
+
addEdge(builder, "contains", narrative.id, claim.id)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const binding of narrative.evidenceBindings) {
|
|
166
|
+
const supportId = addNarrativeEvidenceSupportNode(builder, binding)
|
|
167
|
+
addEdge(builder, "supports", supportId, binding.claimId, compactData({
|
|
168
|
+
strength: binding.strength,
|
|
169
|
+
source: binding.source,
|
|
170
|
+
quote: binding.quote,
|
|
171
|
+
url: binding.url,
|
|
172
|
+
sourcePath: binding.sourcePath,
|
|
173
|
+
location: binding.location,
|
|
174
|
+
findingsFile: binding.findingsFile,
|
|
175
|
+
caveat: binding.caveat,
|
|
176
|
+
supportScope: binding.supportScope,
|
|
177
|
+
unsupportedScope: binding.unsupportedScope,
|
|
178
|
+
}))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const relation of narrative.claimRelations ?? []) addClaimRelation(builder, relation)
|
|
182
|
+
|
|
183
|
+
for (const objection of narrative.objections) {
|
|
184
|
+
addNode(builder, {
|
|
185
|
+
id: objection.id,
|
|
186
|
+
type: "objection",
|
|
187
|
+
label: objection.text,
|
|
188
|
+
data: compactData({ text: objection.text, priority: objection.priority, response: objection.response }),
|
|
189
|
+
})
|
|
190
|
+
addEdge(builder, "contains", narrative.id, objection.id)
|
|
191
|
+
addEdge(builder, "challenges", objection.id, objection.claimId || narrative.id)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const risk of narrative.risks) {
|
|
195
|
+
addNode(builder, {
|
|
196
|
+
id: risk.id,
|
|
197
|
+
type: "risk",
|
|
198
|
+
label: risk.text,
|
|
199
|
+
data: compactData({ text: risk.text, severity: risk.severity, mitigation: risk.mitigation }),
|
|
200
|
+
})
|
|
201
|
+
addEdge(builder, "contains", narrative.id, risk.id)
|
|
202
|
+
addEdge(builder, "constrained_by", risk.claimId || narrative.id, risk.id)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const gap of narrative.researchGaps ?? []) addResearchGap(builder, narrative, gap)
|
|
206
|
+
|
|
207
|
+
return narrative.id
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function addClaimRelation(builder: GraphBuilder, relation: NarrativeClaimRelation): void {
|
|
211
|
+
addEdge(builder, graphEdgeTypeForClaimRelation(relation.relation), relation.fromClaimId, relation.toClaimId, compactData({
|
|
212
|
+
relationId: relation.id,
|
|
213
|
+
relation: relation.relation,
|
|
214
|
+
rationale: relation.rationale,
|
|
215
|
+
source: "canonicalNarrative",
|
|
216
|
+
}))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function graphEdgeTypeForClaimRelation(relation: NarrativeClaimRelation["relation"]): GraphEdgeType {
|
|
220
|
+
if (relation === "constrains") return "constrained_by"
|
|
221
|
+
if (relation === "supports") return "supports"
|
|
222
|
+
return relation
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function addResearchGap(builder: GraphBuilder, narrative: NarrativeStateV1, gap: NarrativeResearchGap): void {
|
|
226
|
+
addNode(builder, {
|
|
227
|
+
id: gap.id,
|
|
228
|
+
type: "researchGap",
|
|
229
|
+
label: gap.question,
|
|
230
|
+
data: compactData({
|
|
231
|
+
question: gap.question,
|
|
232
|
+
status: gap.status,
|
|
233
|
+
priority: gap.priority,
|
|
234
|
+
targetType: gap.targetType,
|
|
235
|
+
targetId: gap.targetId,
|
|
236
|
+
findingsFile: gap.findingsFile,
|
|
237
|
+
evidenceBindingIds: gap.evidenceBindingIds,
|
|
238
|
+
createdFromIssueType: gap.createdFromIssueType,
|
|
239
|
+
notes: gap.notes,
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
addEdge(builder, "contains", narrative.id, gap.id)
|
|
243
|
+
addEdge(builder, "derived_from", gap.id, gapTargetNodeId(narrative, gap))
|
|
244
|
+
if (gap.findingsFile) addEdge(builder, "derived_from", gap.id, findingNodeId(gap.findingsFile), { status: gap.status })
|
|
245
|
+
for (const evidenceId of gap.evidenceBindingIds ?? []) {
|
|
246
|
+
const binding = narrative.evidenceBindings.find((item) => item.id === evidenceId)
|
|
247
|
+
if (binding) addEdge(builder, "derived_from", gap.id, binding.claimId, { evidenceBindingId: evidenceId })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function gapTargetNodeId(narrative: NarrativeStateV1, gap: NarrativeResearchGap): string {
|
|
252
|
+
if (gap.targetId) return gap.targetId
|
|
253
|
+
if (gap.targetType === "narrative" || gap.targetType === "decision") return narrative.id
|
|
254
|
+
return narrative.id
|
|
255
|
+
}
|
|
256
|
+
|
|
125
257
|
function addSlide(builder: GraphBuilder, slide: SlideSpec): void {
|
|
126
258
|
addNode(builder, {
|
|
127
259
|
id: slideNodeId(slide.index),
|
|
@@ -222,6 +354,29 @@ function addEvidenceSupportNode(builder: GraphBuilder, evidence: EvidenceRef): s
|
|
|
222
354
|
return id
|
|
223
355
|
}
|
|
224
356
|
|
|
357
|
+
function addNarrativeEvidenceSupportNode(builder: GraphBuilder, binding: NarrativeEvidenceBinding): string {
|
|
358
|
+
if (binding.findingsFile?.trim()) {
|
|
359
|
+
const id = findingNodeId(binding.findingsFile)
|
|
360
|
+
addNode(builder, {
|
|
361
|
+
id,
|
|
362
|
+
type: "finding",
|
|
363
|
+
label: binding.findingsFile,
|
|
364
|
+
data: compactData({ findingsFile: binding.findingsFile, source: binding.source, quote: binding.quote, location: binding.location, caveat: binding.caveat }),
|
|
365
|
+
})
|
|
366
|
+
return id
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const sourceKey = binding.sourcePath || binding.source || binding.url || "unknown-narrative-evidence-source"
|
|
370
|
+
const id = sourceNodeId(sourceKey)
|
|
371
|
+
addNode(builder, {
|
|
372
|
+
id,
|
|
373
|
+
type: "source",
|
|
374
|
+
label: sourceKey,
|
|
375
|
+
data: compactData({ source: binding.source, sourcePath: binding.sourcePath, url: binding.url }),
|
|
376
|
+
})
|
|
377
|
+
return id
|
|
378
|
+
}
|
|
379
|
+
|
|
225
380
|
function addArtifact(builder: GraphBuilder, deck: DeckSpec, target: RenderTarget, narrativeId: string | undefined, targets: RenderTarget[]): void {
|
|
226
381
|
const artifactId = artifactNodeId(target.outputPath ?? deck.outputPath)
|
|
227
382
|
addNode(builder, {
|
|
@@ -248,9 +403,11 @@ function renderTargetsForDeck(state: DecksState, deck: DeckSpec): RenderTarget[]
|
|
|
248
403
|
const deckOutputPath = normalizePath(deck.outputPath)
|
|
249
404
|
const htmlTargetId = renderTargetId("html_deck", deckOutputPath)
|
|
250
405
|
const htmlArtifactId = artifactNodeId(deckOutputPath)
|
|
406
|
+
const narrativeId = state.narrative?.id
|
|
251
407
|
const targets = (state.renderTargets ?? []).filter((target) => {
|
|
252
408
|
if (target.id === htmlTargetId) return true
|
|
253
409
|
if (target.type === "html_deck") return normalizePath(target.outputPath ?? "") === deckOutputPath
|
|
410
|
+
if (narrativeId && target.sourceNodeIds.includes(narrativeId)) return true
|
|
254
411
|
const data = target.data ?? {}
|
|
255
412
|
return data.sourceTargetId === htmlTargetId ||
|
|
256
413
|
data.sourceOutputPath === deckOutputPath ||
|
|
@@ -308,6 +465,21 @@ function hasNarrativeBrief(brief: NarrativeBrief | undefined): boolean {
|
|
|
308
465
|
)
|
|
309
466
|
}
|
|
310
467
|
|
|
468
|
+
function hasCanonicalNarrative(narrative: NarrativeStateV1 | undefined): narrative is NarrativeStateV1 {
|
|
469
|
+
return Boolean(
|
|
470
|
+
narrative?.audience.primary?.trim() ||
|
|
471
|
+
narrative?.audience.beliefBefore?.trim() ||
|
|
472
|
+
narrative?.audience.beliefAfter?.trim() ||
|
|
473
|
+
narrative?.decision.action?.trim() ||
|
|
474
|
+
narrative?.thesis?.statement?.trim() ||
|
|
475
|
+
narrative?.claims.length ||
|
|
476
|
+
narrative?.evidenceBindings.length ||
|
|
477
|
+
narrative?.objections.length ||
|
|
478
|
+
narrative?.risks.length ||
|
|
479
|
+
narrative?.researchGaps?.length,
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
|
|
311
483
|
function hasEvidenceDetail(evidence: EvidenceRef): boolean {
|
|
312
484
|
return Boolean(
|
|
313
485
|
evidence.quote?.trim() ||
|
|
@@ -50,6 +50,7 @@ export type GraphNodeType =
|
|
|
50
50
|
| "risk"
|
|
51
51
|
| "slide"
|
|
52
52
|
| "artifact"
|
|
53
|
+
| "researchGap"
|
|
53
54
|
|
|
54
55
|
export interface GraphEdge {
|
|
55
56
|
id: string
|
|
@@ -64,6 +65,10 @@ export type GraphEdgeType =
|
|
|
64
65
|
| "extracted_as"
|
|
65
66
|
| "produced"
|
|
66
67
|
| "supports"
|
|
68
|
+
| "leads_to"
|
|
69
|
+
| "depends_on"
|
|
70
|
+
| "contrasts_with"
|
|
71
|
+
| "answers"
|
|
67
72
|
| "appears_in"
|
|
68
73
|
| "challenges"
|
|
69
74
|
| "constrained_by"
|
|
@@ -88,14 +93,21 @@ export type WorkspaceActionType =
|
|
|
88
93
|
| "source.extracted"
|
|
89
94
|
| "research.findings_saved"
|
|
90
95
|
| "research.findings_attached"
|
|
96
|
+
| "research.gap_created"
|
|
97
|
+
| "research.gap_updated"
|
|
98
|
+
| "research.gap_closed"
|
|
99
|
+
| "narrative.upserted"
|
|
100
|
+
| "deck.plan_compiled"
|
|
101
|
+
| "artifact.coverage_backfilled"
|
|
91
102
|
| "evidence.candidate_generated"
|
|
92
103
|
| "evidence.binding_applied"
|
|
104
|
+
| "narrative.approved"
|
|
93
105
|
| "review.performed"
|
|
94
106
|
| "artifact.rendered"
|
|
95
107
|
|
|
96
108
|
export interface RenderTarget {
|
|
97
109
|
id: string
|
|
98
|
-
type: "html_deck" | "pdf" | "pptx" | "brief" | "appendix" | "qa_view" | "interactive_page"
|
|
110
|
+
type: "html_deck" | "pdf" | "pptx" | "brief" | "executive_brief" | "appendix" | "qa_view" | "interactive_page"
|
|
99
111
|
outputPath?: string
|
|
100
112
|
sourceNodeIds: string[]
|
|
101
113
|
artifactVersion?: string
|
package/package.json
CHANGED
package/plugin.ts
CHANGED
|
@@ -59,8 +59,10 @@ import {
|
|
|
59
59
|
buildDesignsEditPrompt,
|
|
60
60
|
} from "./lib/commands/designs-new"
|
|
61
61
|
import { buildInitPrompt } from "./lib/commands/init"
|
|
62
|
+
import { handleBrief, parseBriefArgs } from "./lib/commands/brief"
|
|
63
|
+
import { buildNarrativeViewPrompt, handleNarrative, parseNarrativeArgs } from "./lib/commands/narrative"
|
|
62
64
|
import { parseRememberArgs, buildRememberPrompt } from "./lib/commands/remember"
|
|
63
|
-
import { buildReviewPrompt } from "./lib/commands/review"
|
|
65
|
+
import { buildDeckPrompt, buildDeckReviewPrompt, buildReviewPrompt } from "./lib/commands/review"
|
|
64
66
|
import {
|
|
65
67
|
extractDeckHtmlTargetsFromPatch,
|
|
66
68
|
extractPatchTextArg,
|
|
@@ -85,6 +87,7 @@ import researchImagesListTool from "./tools/research-images-list"
|
|
|
85
87
|
import researchSaveTool from "./tools/research-save"
|
|
86
88
|
import inspectionContextTool from "./tools/inspection-context"
|
|
87
89
|
import inspectionResultTool from "./tools/inspection-result"
|
|
90
|
+
import narrativeViewTool from "./tools/narrative-view"
|
|
88
91
|
import workspaceScanTool from "./tools/workspace-scan"
|
|
89
92
|
import extractDocumentMaterialsTool from "./tools/extract-document-materials"
|
|
90
93
|
import qaTool from "./tools/qa"
|
|
@@ -326,6 +329,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
326
329
|
throw new Error("__REVELA_DISABLE_HANDLED__")
|
|
327
330
|
}
|
|
328
331
|
if (sub === "init") {
|
|
332
|
+
buildPrompt({ mode: "narrative" })
|
|
329
333
|
output.parts.length = 0
|
|
330
334
|
output.parts.push({
|
|
331
335
|
type: "text",
|
|
@@ -339,6 +343,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
339
343
|
await send(parsed.error)
|
|
340
344
|
throw new Error("__REVELA_REMEMBER_USAGE_HANDLED__")
|
|
341
345
|
}
|
|
346
|
+
buildPrompt({ mode: "narrative" })
|
|
342
347
|
output.parts.length = 0
|
|
343
348
|
output.parts.push({
|
|
344
349
|
type: "text",
|
|
@@ -348,9 +353,10 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
348
353
|
}
|
|
349
354
|
if (sub === "review") {
|
|
350
355
|
if (param) {
|
|
351
|
-
await send("`/revela review` no longer accepts a deck name. It reviews the current workspace deck.")
|
|
356
|
+
await send("`/revela review` no longer accepts a deck name. It reviews the current workspace narrative. Use `/revela deck --review` for deck/artifact readiness.")
|
|
352
357
|
throw new Error("__REVELA_REVIEW_USAGE_HANDLED__")
|
|
353
358
|
}
|
|
359
|
+
buildPrompt({ mode: "narrative" })
|
|
354
360
|
output.parts.length = 0
|
|
355
361
|
output.parts.push({
|
|
356
362
|
type: "text",
|
|
@@ -358,6 +364,55 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
358
364
|
} as any)
|
|
359
365
|
return
|
|
360
366
|
}
|
|
367
|
+
if (sub === "narrative") {
|
|
368
|
+
const parsed = parseNarrativeArgs(param)
|
|
369
|
+
if (!parsed.ok) {
|
|
370
|
+
await send(parsed.error)
|
|
371
|
+
throw new Error("__REVELA_NARRATIVE_USAGE_HANDLED__")
|
|
372
|
+
}
|
|
373
|
+
if (parsed.args.raw) {
|
|
374
|
+
await handleNarrative({ workspaceRoot, openBrowser: true, language: parsed.args.language }, send)
|
|
375
|
+
throw new Error("__REVELA_NARRATIVE_HANDLED__")
|
|
376
|
+
}
|
|
377
|
+
buildPrompt({ mode: "narrative" })
|
|
378
|
+
output.parts.length = 0
|
|
379
|
+
output.parts.push({
|
|
380
|
+
type: "text",
|
|
381
|
+
text: buildNarrativeViewPrompt({ workspaceRoot, language: parsed.args.language }),
|
|
382
|
+
} as any)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
if (sub === "brief") {
|
|
386
|
+
const parsed = parseBriefArgs(param)
|
|
387
|
+
if (!parsed.ok) {
|
|
388
|
+
await send(parsed.error)
|
|
389
|
+
throw new Error("__REVELA_BRIEF_USAGE_HANDLED__")
|
|
390
|
+
}
|
|
391
|
+
await handleBrief({ workspaceRoot, outputPath: parsed.args.outputPath }, send)
|
|
392
|
+
throw new Error("__REVELA_BRIEF_HANDLED__")
|
|
393
|
+
}
|
|
394
|
+
if (sub === "deck") {
|
|
395
|
+
if (param && param !== "--review") {
|
|
396
|
+
await send("Usage: `/revela deck` starts approved-narrative deck handoff; `/revela deck --review` reviews deck/artifact readiness.")
|
|
397
|
+
throw new Error("__REVELA_DECK_USAGE_HANDLED__")
|
|
398
|
+
}
|
|
399
|
+
if (!param) {
|
|
400
|
+
buildPrompt({ mode: "deck-render" })
|
|
401
|
+
output.parts.length = 0
|
|
402
|
+
output.parts.push({
|
|
403
|
+
type: "text",
|
|
404
|
+
text: buildDeckPrompt({ exists: hasDecksState(workspaceRoot), workspaceRoot }),
|
|
405
|
+
} as any)
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
buildPrompt({ mode: "deck-render" })
|
|
409
|
+
output.parts.length = 0
|
|
410
|
+
output.parts.push({
|
|
411
|
+
type: "text",
|
|
412
|
+
text: buildDeckReviewPrompt({ exists: hasDecksState(workspaceRoot), workspaceRoot }),
|
|
413
|
+
} as any)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
361
416
|
if (sub === "refine") {
|
|
362
417
|
if (param) {
|
|
363
418
|
await send("`/revela refine` does not accept a target. It opens the only HTML deck in `decks/`.")
|
|
@@ -482,6 +537,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
482
537
|
"revela-research-save": researchSaveTool,
|
|
483
538
|
"revela-inspection-context": inspectionContextTool,
|
|
484
539
|
"revela-inspection-result": inspectionResultTool,
|
|
540
|
+
"revela-narrative-view": narrativeViewTool,
|
|
485
541
|
"revela-workspace-scan": workspaceScanTool,
|
|
486
542
|
"revela-extract-document-materials": extractDocumentMaterialsTool,
|
|
487
543
|
"revela-qa": qaTool,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: revela-narrative
|
|
3
|
+
description: Build trusted narrative readiness before rendering deck artifacts
|
|
4
|
+
compatibility: opencode
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Revela — Narrative Workspace
|
|
8
|
+
|
|
9
|
+
You help the user turn source materials, research, and intent into a trusted communication narrative before any deck is rendered.
|
|
10
|
+
|
|
11
|
+
Default mode is narrative-first. Do not generate HTML slides, choose visual layouts, fetch design components, or ask for slide count unless the user explicitly enters a deck-render workflow.
|
|
12
|
+
|
|
13
|
+
## Core Job
|
|
14
|
+
|
|
15
|
+
Build and review the narrative state around:
|
|
16
|
+
- primary audience and stakeholder context
|
|
17
|
+
- audience belief before and desired belief after
|
|
18
|
+
- decision or action required
|
|
19
|
+
- thesis or central recommendation
|
|
20
|
+
- central claims and their evidence boundaries
|
|
21
|
+
- objections, risks, assumptions, caveats, and unsupported scope
|
|
22
|
+
- narrative approval state and whether approval is stale
|
|
23
|
+
|
|
24
|
+
## Workspace State
|
|
25
|
+
|
|
26
|
+
Use `DECKS.json` as Revela's current compatibility workspace-state file. Do not write or patch it directly.
|
|
27
|
+
|
|
28
|
+
Use `revela-decks` for state operations:
|
|
29
|
+
- `read` to inspect current workspace state
|
|
30
|
+
- `init` to register discovered source material candidates during workspace initialization
|
|
31
|
+
- `upsertNarrative` to preserve canonical audience, decision, thesis, claims, evidence bindings, objections, and risks
|
|
32
|
+
- `upsertDeck` or `upsertSlides` only when explicitly needed by a deck/artifact workflow prompt
|
|
33
|
+
- `reviewNarrative` to run deterministic narrative readiness
|
|
34
|
+
- `approveNarrative` only when the user explicitly approves or requests an override
|
|
35
|
+
|
|
36
|
+
Never treat `writeReadiness.status`, old review snapshots, existing `decks/*.html`, or saved research actions as narrative approval.
|
|
37
|
+
|
|
38
|
+
## Narrative Review Rules
|
|
39
|
+
|
|
40
|
+
When reviewing, call `revela-decks` action `reviewNarrative` and report the tool result as authoritative.
|
|
41
|
+
|
|
42
|
+
Use this report shape:
|
|
43
|
+
- `Narrative readiness: <status>`
|
|
44
|
+
- `Narrative hash: <hash>` when available
|
|
45
|
+
- blockers first, with issue type, claim text when available, and suggested next action
|
|
46
|
+
- warnings second, as residual risks
|
|
47
|
+
- approval state last, clearly distinguishing `ready_for_approval`, `approved`, stale approval, and render override
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
If research findings were saved but not attached or bound, describe them as unattached research state, not proof.
|
|
52
|
+
|
|
53
|
+
If the narrative is ready for approval, ask the user whether to approve or revise it. Do not approve automatically.
|
|
54
|
+
|
|
55
|
+
## Boundaries
|
|
56
|
+
|
|
57
|
+
- Do not write or overwrite `decks/*.html` in narrative mode.
|
|
58
|
+
- Do not call `revela-decks review` in narrative mode; that is the deck/artifact gate.
|
|
59
|
+
- Do not apply evidence candidates, bind evidence, or rewrite slide text unless the user explicitly asks.
|
|
60
|
+
- Do not fetch design CSS, layouts, components, chart rules, or HTML skeletons in narrative mode.
|
|
61
|
+
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
62
|
+
- Do not infer long-term user preferences from one-off tasks.
|
|
63
|
+
|
|
64
|
+
When the user wants deck/artifact readiness, direct them to `/revela deck --review`. When they want to render a deck, wait for the explicit deck workflow.
|