@cyber-dash-tech/revela 0.16.4 → 0.17.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 +7 -5
- package/README.zh-CN.md +7 -5
- package/lib/commands/brief.ts +9 -0
- package/lib/commands/help.ts +5 -2
- package/lib/commands/init.ts +42 -27
- package/lib/commands/narrative.ts +26 -2
- package/lib/commands/research.ts +36 -20
- package/lib/commands/review.ts +21 -18
- package/lib/ctx.ts +1 -1
- package/lib/decks-state.ts +38 -4
- package/lib/edit/prompt.ts +1 -1
- package/lib/hook-notifications.ts +53 -0
- package/lib/narrative-state/render-plan.ts +114 -27
- package/lib/narrative-state/research-binding-eval.ts +260 -0
- package/lib/narrative-state/research-gaps.ts +2 -88
- package/lib/narrative-vault/authoring-contract.ts +127 -0
- package/lib/narrative-vault/authoring-guard.ts +122 -0
- package/lib/narrative-vault/auto-compile.ts +134 -0
- package/lib/narrative-vault/bootstrap.ts +63 -0
- package/lib/narrative-vault/cache.ts +14 -0
- package/lib/narrative-vault/compile-mirror.ts +45 -0
- package/lib/narrative-vault/compile.ts +350 -0
- package/lib/narrative-vault/constants.ts +6 -0
- package/lib/narrative-vault/diagnostic-report.ts +117 -0
- package/lib/narrative-vault/export.ts +71 -0
- package/lib/narrative-vault/frontmatter.ts +41 -0
- package/lib/narrative-vault/hook-targets.ts +40 -0
- package/lib/narrative-vault/index.ts +18 -0
- package/lib/narrative-vault/inventory.ts +392 -0
- package/lib/narrative-vault/markdown-qa.ts +237 -0
- package/lib/narrative-vault/markdown.ts +34 -0
- package/lib/narrative-vault/migration.ts +52 -0
- package/lib/narrative-vault/mutate.ts +361 -0
- package/lib/narrative-vault/paths.ts +19 -0
- package/lib/narrative-vault/read.ts +52 -0
- package/lib/narrative-vault/relations.ts +32 -0
- package/lib/narrative-vault/source-loader.ts +19 -0
- package/lib/narrative-vault/timestamp.ts +32 -0
- package/lib/narrative-vault/types.ts +44 -0
- package/lib/source-materials.ts +98 -0
- package/lib/tool-result.ts +34 -0
- package/package.json +2 -2
- package/plugin.ts +60 -22
- package/skill/NARRATIVE_SKILL.md +25 -10
- package/tools/decks.ts +363 -67
- package/tools/research-save.ts +3 -0
- package/tools/workspace-scan.ts +1 -0
package/lib/source-materials.ts
CHANGED
|
@@ -46,6 +46,103 @@ export function sourceMaterialMetadata(filePath: string, workspaceRoot: string):
|
|
|
46
46
|
type: sourceMaterialType(resolvedFile),
|
|
47
47
|
size: stat.size,
|
|
48
48
|
fingerprint: computeSourceFingerprint(resolvedFile),
|
|
49
|
+
lastModified: new Date(stat.mtimeMs).toISOString(),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function sourceMaterialModifiedMs(material: SourceMaterial, workspaceRoot: string): number {
|
|
54
|
+
if (material.lastModified) {
|
|
55
|
+
const parsed = Date.parse(material.lastModified)
|
|
56
|
+
if (Number.isFinite(parsed)) return parsed
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return statSync(ensureWorkspaceFile(material.path, workspaceRoot)).mtimeMs
|
|
61
|
+
} catch {
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SourceMaterialIngestPlan {
|
|
67
|
+
vaultTimestamp: string | null
|
|
68
|
+
vaultTimestampMs: number
|
|
69
|
+
addedSourceMaterials: SourceMaterial[]
|
|
70
|
+
changedSourceMaterials: SourceMaterial[]
|
|
71
|
+
newerThanVaultSourceMaterials: SourceMaterial[]
|
|
72
|
+
unchangedSourceMaterials: SourceMaterial[]
|
|
73
|
+
ingestCandidates: SourceMaterial[]
|
|
74
|
+
suggestedTasks: SourceMaterialIngestTask[]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface SourceMaterialIngestTask {
|
|
78
|
+
path: string
|
|
79
|
+
reason: Array<"added" | "changed" | "newer_than_vault">
|
|
80
|
+
materialType: string
|
|
81
|
+
needsExtraction: boolean
|
|
82
|
+
suggestedAction: "read_directly" | "extract_then_read"
|
|
83
|
+
note: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function classifySourceMaterialIngest(
|
|
87
|
+
state: DecksState,
|
|
88
|
+
materials: SourceMaterial[],
|
|
89
|
+
workspaceRoot: string,
|
|
90
|
+
vaultTimestampMs: number,
|
|
91
|
+
): SourceMaterialIngestPlan {
|
|
92
|
+
const existingByPath = new Map((state.workspace.sourceMaterials ?? []).map((item) => [item.path.replace(/\\/g, "/"), item]))
|
|
93
|
+
const addedSourceMaterials: SourceMaterial[] = []
|
|
94
|
+
const changedSourceMaterials: SourceMaterial[] = []
|
|
95
|
+
const newerThanVaultSourceMaterials: SourceMaterial[] = []
|
|
96
|
+
const unchangedSourceMaterials: SourceMaterial[] = []
|
|
97
|
+
const ingestByPath = new Map<string, SourceMaterial>()
|
|
98
|
+
const reasonsByPath = new Map<string, SourceMaterialIngestTask["reason"]>()
|
|
99
|
+
|
|
100
|
+
for (const material of materials) {
|
|
101
|
+
const path = material.path.replace(/\\/g, "/")
|
|
102
|
+
const existing = existingByPath.get(path)
|
|
103
|
+
const added = !existing
|
|
104
|
+
const changed = Boolean(existing?.fingerprint && material.fingerprint && existing.fingerprint !== material.fingerprint)
|
|
105
|
+
const newerThanVault = sourceMaterialModifiedMs(material, workspaceRoot) > vaultTimestampMs
|
|
106
|
+
const reasons: SourceMaterialIngestTask["reason"] = []
|
|
107
|
+
|
|
108
|
+
if (added) addedSourceMaterials.push(material)
|
|
109
|
+
if (changed) changedSourceMaterials.push(material)
|
|
110
|
+
if (newerThanVault) newerThanVaultSourceMaterials.push(material)
|
|
111
|
+
if (added) reasons.push("added")
|
|
112
|
+
if (changed) reasons.push("changed")
|
|
113
|
+
if (newerThanVault) reasons.push("newer_than_vault")
|
|
114
|
+
if (added || changed || newerThanVault) {
|
|
115
|
+
ingestByPath.set(path, material)
|
|
116
|
+
reasonsByPath.set(path, reasons)
|
|
117
|
+
}
|
|
118
|
+
else unchangedSourceMaterials.push(material)
|
|
119
|
+
}
|
|
120
|
+
const ingestCandidates = [...ingestByPath.values()].sort((a, b) => a.path.localeCompare(b.path))
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
vaultTimestamp: vaultTimestampMs > 0 ? new Date(vaultTimestampMs).toISOString() : null,
|
|
124
|
+
vaultTimestampMs,
|
|
125
|
+
addedSourceMaterials,
|
|
126
|
+
changedSourceMaterials,
|
|
127
|
+
newerThanVaultSourceMaterials,
|
|
128
|
+
unchangedSourceMaterials,
|
|
129
|
+
ingestCandidates,
|
|
130
|
+
suggestedTasks: ingestCandidates.map((material) => sourceMaterialIngestTask(material, reasonsByPath.get(material.path.replace(/\\/g, "/")) ?? [])),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function sourceMaterialIngestTask(material: SourceMaterial, reason: SourceMaterialIngestTask["reason"]): SourceMaterialIngestTask {
|
|
135
|
+
const materialType = (material.type || sourceMaterialType(material.path)).toLowerCase()
|
|
136
|
+
const needsExtraction = ["pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx"].includes(materialType)
|
|
137
|
+
return {
|
|
138
|
+
path: material.path,
|
|
139
|
+
reason,
|
|
140
|
+
materialType,
|
|
141
|
+
needsExtraction,
|
|
142
|
+
suggestedAction: needsExtraction ? "extract_then_read" : "read_directly",
|
|
143
|
+
note: needsExtraction
|
|
144
|
+
? "Use revela-extract-document-materials before synthesizing stable narrative findings when this file is relevant."
|
|
145
|
+
: "Read directly when relevant and distill stable narrative findings into the Markdown vault.",
|
|
49
146
|
}
|
|
50
147
|
}
|
|
51
148
|
|
|
@@ -77,6 +174,7 @@ export function upsertSourceMaterial(
|
|
|
77
174
|
path,
|
|
78
175
|
type: material.type ?? existing?.type,
|
|
79
176
|
status: nextStatus,
|
|
177
|
+
lastModified: material.lastModified ?? existing?.lastModified,
|
|
80
178
|
firstSeen: existing?.firstSeen ?? material.firstSeen ?? now,
|
|
81
179
|
lastChecked: now,
|
|
82
180
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function appendToolResult(output: any, text: string): void {
|
|
2
|
+
if (!output || typeof output !== "object") return
|
|
3
|
+
|
|
4
|
+
if (typeof output.output === "string") {
|
|
5
|
+
output.output = appendText(output.output, text)
|
|
6
|
+
return
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (typeof output.result === "string") {
|
|
10
|
+
output.result = appendText(output.result, text)
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof output.text === "string") {
|
|
15
|
+
output.text = appendText(output.text, text)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof output.message === "string") {
|
|
20
|
+
output.message = appendText(output.message, text)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(output.content)) {
|
|
25
|
+
output.content.push({ type: "text", text })
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
output.output = text
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function appendText(existing: string, text: string): string {
|
|
33
|
+
return (existing ? `${existing}\n\n` : "") + text
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyber-dash-tech/revela",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenCode plugin
|
|
3
|
+
"version": "0.17.0",
|
|
4
|
+
"description": "OpenCode plugin for trusted narrative artifacts from local sources, research, and evidence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"exports": {
|
package/plugin.ts
CHANGED
|
@@ -72,6 +72,11 @@ import {
|
|
|
72
72
|
hasDecksState,
|
|
73
73
|
isDecksStatePath,
|
|
74
74
|
} from "./lib/decks-state"
|
|
75
|
+
import { autoCompileNarrativeVault } from "./lib/narrative-vault/auto-compile"
|
|
76
|
+
import {
|
|
77
|
+
extractNarrativeVaultMarkdownTargetsFromPatch,
|
|
78
|
+
normalizeNarrativeVaultMarkdownPath,
|
|
79
|
+
} from "./lib/narrative-vault/hook-targets"
|
|
75
80
|
import decksTool from "./tools/decks"
|
|
76
81
|
import designsAuthorTool from "./tools/designs-author"
|
|
77
82
|
import designsTool from "./tools/designs"
|
|
@@ -93,6 +98,8 @@ import { RESEARCH_PROMPT, RESEARCH_AGENT_SIGNATURE } from "./lib/agents/research
|
|
|
93
98
|
import { NARRATIVE_REVIEWER_PROMPT, NARRATIVE_REVIEWER_SIGNATURE } from "./lib/agents/narrative-reviewer-prompt"
|
|
94
99
|
import { extractDesignClasses } from "./lib/design/designs"
|
|
95
100
|
import { log, childLog } from "./lib/log"
|
|
101
|
+
import { appendToolResult } from "./lib/tool-result"
|
|
102
|
+
import { formatArtifactQaUserNotice, formatMarkdownQaUserNotice, formatStateGateUserNotice } from "./lib/hook-notifications"
|
|
96
103
|
|
|
97
104
|
// OpenCode internal agent signatures — used to skip system prompt injection
|
|
98
105
|
// for built-in system agents (title, summary, compaction).
|
|
@@ -102,16 +109,6 @@ const INTERNAL_AGENT_SIGNATURES = [
|
|
|
102
109
|
"Summarize what was done in this conversation",
|
|
103
110
|
]
|
|
104
111
|
|
|
105
|
-
function appendToolResult(output: any, text: string): void {
|
|
106
|
-
if (typeof output.output === "string") {
|
|
107
|
-
output.output = (output.output ? output.output + "\n\n" : "") + text
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const existing = output.result ?? ""
|
|
112
|
-
output.result = (existing ? existing + "\n\n" : "") + text
|
|
113
|
-
}
|
|
114
|
-
|
|
115
112
|
function extractEditFilePath(args: any): string {
|
|
116
113
|
return args?.filePath ?? args?.file_path ?? args?.path ?? args?.file ?? ""
|
|
117
114
|
}
|
|
@@ -148,7 +145,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
148
145
|
const blockedDeckWrites = new Map<string, string>()
|
|
149
146
|
const blockedPatches = new Map<string, string>()
|
|
150
147
|
|
|
151
|
-
async function runPostWriteArtifactQA(filePath: string, output: any): Promise<boolean> {
|
|
148
|
+
async function runPostWriteArtifactQA(filePath: string, output: any, sessionID = ""): Promise<boolean> {
|
|
152
149
|
if (!isDeckHtmlPath(filePath)) return true
|
|
153
150
|
|
|
154
151
|
try {
|
|
@@ -161,6 +158,8 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
161
158
|
|
|
162
159
|
const report = await runArtifactQA({ workspaceRoot, filePath, vocabulary })
|
|
163
160
|
appendToolResult(output, "---\n\n" + formatArtifactQAReport(report))
|
|
161
|
+
const notice = formatArtifactQaUserNotice(report)
|
|
162
|
+
if (notice && sessionID) await sendIgnoredMessage(client, sessionID, notice)
|
|
164
163
|
return report.passed
|
|
165
164
|
} catch (e) {
|
|
166
165
|
childLog("artifact-qa").warn("post-write artifact QA failed", {
|
|
@@ -168,10 +167,27 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
168
167
|
error: e instanceof Error ? e.message : String(e),
|
|
169
168
|
})
|
|
170
169
|
appendToolResult(output, "---\n\n## Artifact QA: FAILED\n\nError running artifact QA: " + (e instanceof Error ? e.message : String(e)))
|
|
170
|
+
if (sessionID) await sendIgnoredMessage(client, sessionID, `**Artifact QA failed**\nFile: \`${filePath}\`\nError: ${e instanceof Error ? e.message : String(e)}`)
|
|
171
171
|
return false
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
async function runPostWriteNarrativeVaultCompile(touched: string[], output: any, sessionID = ""): Promise<void> {
|
|
176
|
+
if (touched.length === 0) return
|
|
177
|
+
|
|
178
|
+
const result = autoCompileNarrativeVault(workspaceRoot, touched)
|
|
179
|
+
appendToolResult(output, "---\n\n" + result.markdown)
|
|
180
|
+
const notice = formatMarkdownQaUserNotice(result)
|
|
181
|
+
if (notice && sessionID) await sendIgnoredMessage(client, sessionID, notice)
|
|
182
|
+
if (!result.ok) {
|
|
183
|
+
childLog("narrative-vault").warn("auto-compile reported blockers", {
|
|
184
|
+
touched,
|
|
185
|
+
mirror: result.mirrored,
|
|
186
|
+
error: result.error,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
175
191
|
function extractSessionID(input: any): string {
|
|
176
192
|
return input?.sessionID ?? input?.session?.id ?? input?.context?.sessionID ?? ""
|
|
177
193
|
}
|
|
@@ -314,6 +330,17 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
314
330
|
await handleHelp(send)
|
|
315
331
|
throw new Error("__REVELA_STATUS_HANDLED__")
|
|
316
332
|
}
|
|
333
|
+
if (sub === "enable") {
|
|
334
|
+
ctx.enabled = true
|
|
335
|
+
buildPrompt()
|
|
336
|
+
await send("Revela enabled. Workflow commands and Revela context are active.")
|
|
337
|
+
throw new Error("__REVELA_ENABLE_HANDLED__")
|
|
338
|
+
}
|
|
339
|
+
if (sub === "disable") {
|
|
340
|
+
ctx.enabled = false
|
|
341
|
+
await send("Revela disabled. Run `/revela enable` or any workflow command to reactivate.")
|
|
342
|
+
throw new Error("__REVELA_DISABLE_HANDLED__")
|
|
343
|
+
}
|
|
317
344
|
if (sub === "make") {
|
|
318
345
|
const target = args[1]?.toLowerCase() ?? ""
|
|
319
346
|
const makeParam = args.slice(2).join(" ")
|
|
@@ -500,7 +527,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
500
527
|
throw new Error("__REVELA_DOMAIN_USAGE_HANDLED__")
|
|
501
528
|
}
|
|
502
529
|
const legacyCommands = new Set([
|
|
503
|
-
"
|
|
530
|
+
"review", "narrative", "deck", "brief", "edit", "inspect", "remember",
|
|
504
531
|
"designs", "designs-new", "designs-edit", "designs-preview", "designs-add", "designs-rm",
|
|
505
532
|
"domains", "domains-add", "domains-rm", "pdf", "pptx",
|
|
506
533
|
])
|
|
@@ -689,8 +716,6 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
689
716
|
// - write/apply_patch: protect DECKS.json, but do not block deck HTML edits.
|
|
690
717
|
"tool.execute.before": async (input, output) => {
|
|
691
718
|
log.info("[hook] tool.execute.before fired", { tool: input.tool, enabled: ctx.enabled, isResearch: ctx.isResearchAgent })
|
|
692
|
-
if (!ctx.enabled) return
|
|
693
|
-
|
|
694
719
|
if (input.tool === "write") {
|
|
695
720
|
const filePath: string = (output.args as any)?.filePath ?? ""
|
|
696
721
|
if (isDecksStatePath(filePath)) {
|
|
@@ -741,6 +766,8 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
|
|
|
741
766
|
}
|
|
742
767
|
}
|
|
743
768
|
|
|
769
|
+
if (!ctx.enabled) return
|
|
770
|
+
|
|
744
771
|
if (input.tool === "read") {
|
|
745
772
|
try {
|
|
746
773
|
await preRead(output.args)
|
|
@@ -760,10 +787,9 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
|
|
|
760
787
|
// Also reports writes/patches blocked by the DECKS.json prewrite gate and
|
|
761
788
|
// runs artifact QA before opening Refine after successful deck changes.
|
|
762
789
|
"tool.execute.after": async (input, output) => {
|
|
763
|
-
if (!ctx.enabled) return
|
|
764
|
-
|
|
765
790
|
// ── Post-read processing ───────────────────────────────────────────
|
|
766
791
|
if (input.tool === "read") {
|
|
792
|
+
if (!ctx.enabled) return
|
|
767
793
|
try {
|
|
768
794
|
await postRead(input.args, output)
|
|
769
795
|
} catch (e) {
|
|
@@ -787,39 +813,51 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
|
|
|
787
813
|
`${blockedReason}\n\n` +
|
|
788
814
|
"Use the `revela-decks` tool or complete the DECKS.json review workflow instead."
|
|
789
815
|
)
|
|
816
|
+
await sendIgnoredMessage(client, extractSessionID(input), formatStateGateUserNotice("write", blockedReason))
|
|
790
817
|
return
|
|
791
818
|
}
|
|
792
|
-
const
|
|
819
|
+
const vaultTarget = normalizeNarrativeVaultMarkdownPath(filePath, workspaceRoot)
|
|
820
|
+
const sessionID = extractSessionID(input)
|
|
821
|
+
if (vaultTarget) await runPostWriteNarrativeVaultCompile([vaultTarget], output, sessionID)
|
|
822
|
+
const qaPassed = await runPostWriteArtifactQA(filePath, output, sessionID)
|
|
793
823
|
if (qaPassed) ensureRefineOpenAfterDeckChange(filePath, extractSessionID(input))
|
|
794
824
|
return
|
|
795
825
|
}
|
|
796
826
|
|
|
797
827
|
if (input.tool === "apply_patch" && blockedPatches.size > 0) {
|
|
798
828
|
const [blockedPath, blockedReason] = blockedPatches.entries().next().value ?? []
|
|
829
|
+
if (!blockedPath || !blockedReason) return
|
|
799
830
|
if (blockedPath) blockedPatches.delete(blockedPath)
|
|
800
831
|
appendToolResult(
|
|
801
832
|
output,
|
|
802
833
|
"---\n\n**[revela prewrite gate]** Patch was blocked.\n\n" +
|
|
803
834
|
`${blockedReason}\n\n` +
|
|
804
|
-
|
|
835
|
+
"Use the `revela-decks` tool for controlled workspace state changes."
|
|
805
836
|
)
|
|
837
|
+
await sendIgnoredMessage(client, extractSessionID(input), formatStateGateUserNotice("patch", blockedReason))
|
|
806
838
|
return
|
|
807
839
|
}
|
|
808
840
|
|
|
809
841
|
if (input.tool === "apply_patch") {
|
|
810
842
|
const patchText = extractPatchTextArg(input.args as Record<string, unknown>)
|
|
843
|
+
const vaultTargets = patchText ? extractNarrativeVaultMarkdownTargetsFromPatch(patchText, workspaceRoot) : []
|
|
844
|
+
const sessionID = extractSessionID(input)
|
|
845
|
+
await runPostWriteNarrativeVaultCompile(vaultTargets, output, sessionID)
|
|
811
846
|
const targets = patchText ? extractDeckHtmlTargetsFromPatch(patchText) : []
|
|
812
847
|
for (const target of targets) {
|
|
813
|
-
const qaPassed = await runPostWriteArtifactQA(target, output)
|
|
814
|
-
if (qaPassed) ensureRefineOpenAfterDeckChange(target,
|
|
848
|
+
const qaPassed = await runPostWriteArtifactQA(target, output, sessionID)
|
|
849
|
+
if (qaPassed) ensureRefineOpenAfterDeckChange(target, sessionID)
|
|
815
850
|
}
|
|
816
851
|
return
|
|
817
852
|
}
|
|
818
853
|
|
|
819
854
|
if (input.tool === "edit") {
|
|
820
855
|
const filePath = extractEditFilePath(input.args)
|
|
821
|
-
const
|
|
822
|
-
|
|
856
|
+
const vaultTarget = normalizeNarrativeVaultMarkdownPath(filePath, workspaceRoot)
|
|
857
|
+
const sessionID = extractSessionID(input)
|
|
858
|
+
if (vaultTarget) await runPostWriteNarrativeVaultCompile([vaultTarget], output, sessionID)
|
|
859
|
+
const qaPassed = await runPostWriteArtifactQA(filePath, output, sessionID)
|
|
860
|
+
if (qaPassed) ensureRefineOpenAfterDeckChange(filePath, sessionID)
|
|
823
861
|
return
|
|
824
862
|
}
|
|
825
863
|
},
|
package/skill/NARRATIVE_SKILL.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
|
@@ -16,7 +16,7 @@ Default mode is narrative-first. Do not generate HTML slides, choose layouts, fe
|
|
|
16
16
|
|
|
17
17
|
Use the same phase semantics whether the user invokes a slash command or asks in normal chat:
|
|
18
18
|
|
|
19
|
-
- `Init` discovers local workspace materials, captures intent, initializes or refreshes
|
|
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
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
22
|
- `Make` renders an artifact from approved or explicitly overridden narrative state. Supported 0.15 targets are deck and executive brief.
|
|
@@ -39,32 +39,43 @@ Deprecated compatibility aliases such as `/revela review`, `/revela narrative`,
|
|
|
39
39
|
|
|
40
40
|
## Workspace State
|
|
41
41
|
|
|
42
|
-
Use `DECKS.json` as Revela's
|
|
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.
|
|
43
43
|
|
|
44
44
|
Use `revela-decks` for state operations:
|
|
45
45
|
|
|
46
46
|
- `read` to inspect current workspace state
|
|
47
|
+
- `read` with `summary: true` to inspect compact deck state plus `vaultDiagnostics` when a Markdown vault exists and `migration` guidance when JSON narrative state can be exported to a vault
|
|
47
48
|
- `init` to register discovered source material candidates during workspace initialization
|
|
48
|
-
- `
|
|
49
|
+
- `initNarrativeVault` to create `revela-narrative/` and draft core Markdown files before writing canonical narrative meaning in a new workspace
|
|
50
|
+
- `exportNarrativeVault` to export existing `DECKS.json.narrative` into `revela-narrative/` when no vault exists; its result states which Markdown files were written and which provenance/render fields remain in `DECKS.json`
|
|
51
|
+
- `compileNarrativeVault` to compile Markdown vault nodes, refresh cache, and mirror compiled narrative into `DECKS.json`
|
|
52
|
+
- `updateVaultCoreNarrative`, `upsertVaultClaim`, `upsertVaultEvidence`, `upsertVaultObjection`, `upsertVaultRisk`, and `updateVaultResearchGap` are deterministic helper actions for common Markdown vault edits; prefer direct Markdown node edits when you can preserve surrounding content safely
|
|
53
|
+
- `upsertNarrative` is deprecated and should not be used; create or update canonical narrative meaning through Markdown vault files, then compile
|
|
49
54
|
- `reviewNarrative` to run deterministic story readiness
|
|
50
|
-
- `deriveResearchGaps`, `upsertResearchGaps`, `updateResearchGap`, and `closeResearchGap`
|
|
55
|
+
- `deriveResearchGaps`, `upsertResearchGaps`, `updateResearchGap`, and `closeResearchGap` are compatibility helpers; prefer `updateVaultResearchGap` for canonical gap lifecycle updates
|
|
51
56
|
- `attachResearchFindings` to attach saved findings to research state
|
|
52
|
-
- `applyEvidenceCandidates` only
|
|
57
|
+
- `applyEvidenceCandidates` is compatibility-only; prefer `upsertVaultEvidence` with explicit source trace for canonical support
|
|
53
58
|
- `approveNarrative` only when the user explicitly approves or requests an override
|
|
54
59
|
- `compileDeckPlan`, `upsertDeck`, `upsertSlides`, and `review` only inside make-deck or artifact-readiness workflows
|
|
55
60
|
|
|
56
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.
|
|
57
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.
|
|
64
|
+
|
|
58
65
|
## Init Rules
|
|
59
66
|
|
|
60
67
|
During init:
|
|
61
68
|
|
|
62
|
-
-
|
|
69
|
+
- if no `revela-narrative/` exists, call `initNarrativeVault` before writing canonical narrative meaning; use `exportNarrativeVault` only for developer workspaces that already have a JSON narrative mirror and no vault
|
|
70
|
+
- scan local workspace materials before asking broad questions, and treat init as repeatable ingest for both first discovery and user-added or user-modified files
|
|
71
|
+
- after `revela-decks init`, inspect `ingest.ingestCandidates`; these include added files, files whose fingerprint changed, and files newer than the vault timestamp, and they must be considered for extraction/reading in this init pass
|
|
63
72
|
- reuse `workspace.sourceMaterials` and extraction cache when fingerprints match
|
|
64
73
|
- extract or read only relevant local materials; do not exhaustively process large workspaces
|
|
65
|
-
- derive claims, evidence bindings, caveats, unsupported scope, source paths, quotes/snippets, pages, sheets, or slide references only when explicit support exists
|
|
74
|
+
- derive claims, evidence bindings, caveats, unsupported scope, source paths, quotes/snippets, pages, sheets, or slide references only when explicit support exists; distill ingested files by writing Markdown nodes under `revela-narrative/` even when the narrative is incomplete, and represent missing information as research gaps or caveats
|
|
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
|
|
66
76
|
- ask the smallest missing intent questions after local evidence has been considered
|
|
67
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`
|
|
68
79
|
|
|
69
80
|
## Research Rules
|
|
70
81
|
|
|
@@ -76,8 +87,9 @@ During research:
|
|
|
76
87
|
- delegate external web search to the `revela-research` subagent
|
|
77
88
|
- save findings through `revela-research-save`
|
|
78
89
|
- treat `/revela research` as permission to attach findings and bind clearly supported evidence without item-by-item user confirmation
|
|
79
|
-
-
|
|
80
|
-
-
|
|
90
|
+
- create canonical evidence bindings only when the supported claim id, quote/snippet, source, support scope, unsupported scope, caveat, and strength are explicit; new evidence should express support with `## Relations` such as `- supports: [[claim-id]]`, while `claimId` remains compatibility fallback
|
|
91
|
+
- compile after Markdown edits, inspect returned `diagnosticReport`, and report remaining blockers or warnings in the research summary
|
|
92
|
+
- narrow overbroad claim scope by editing `revela-narrative/claims/*.md` only when the narrower wording preserves strategic meaning and better matches the evidence; report relation rewrites or strategic claim changes for Story/user confirmation
|
|
81
93
|
- preserve source path, URL, location/page/sheet/slide, quote/snippet, support scope, unsupported scope, and caveat
|
|
82
94
|
- keep missing or partial evidence visible instead of filling it with model assumptions; classify remaining caveats as internal-data-needed, not-publicly-researchable, source-quality-limit, or still-open
|
|
83
95
|
|
|
@@ -85,6 +97,8 @@ During research:
|
|
|
85
97
|
|
|
86
98
|
When the user invokes `/revela story`, open the read-only story workspace UI. Do not turn that command into a blocking readiness report.
|
|
87
99
|
|
|
100
|
+
If a Markdown vault has compile diagnostics, surface the diagnostic summary alongside the Story output without mutating state. Errors should identify the Markdown file/node and the next smallest fix.
|
|
101
|
+
|
|
88
102
|
When the user explicitly asks for a readiness report, call `revela-decks` action `reviewNarrative` and report the tool result as authoritative.
|
|
89
103
|
|
|
90
104
|
Use this report shape:
|
|
@@ -106,6 +120,7 @@ For `/revela make --deck` deck handoff:
|
|
|
106
120
|
|
|
107
121
|
- switch to deck-render mode through the command workflow
|
|
108
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
|
|
109
124
|
- use `compileDeckPlan` as the canonical narrative-to-deck planning path
|
|
110
125
|
- run the deck/artifact gate with `revela-decks review` before writing HTML
|
|
111
126
|
- fetch design layouts/components only after narrative handoff is valid
|