@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.
Files changed (37) hide show
  1. package/README.md +35 -29
  2. package/README.zh-CN.md +35 -29
  3. package/lib/commands/brief.ts +63 -0
  4. package/lib/commands/designs.ts +2 -2
  5. package/lib/commands/domains.ts +2 -2
  6. package/lib/commands/enable.ts +19 -19
  7. package/lib/commands/help.ts +7 -3
  8. package/lib/commands/init.ts +30 -19
  9. package/lib/commands/narrative.ts +160 -0
  10. package/lib/commands/review.ts +115 -1
  11. package/lib/decks-state.ts +46 -3
  12. package/lib/edit/prompt.ts +3 -0
  13. package/lib/inspection-context/compile.ts +159 -5
  14. package/lib/inspection-context/project.ts +20 -0
  15. package/lib/narrative-state/coverage.ts +100 -0
  16. package/lib/narrative-state/display.ts +219 -0
  17. package/lib/narrative-state/executive-brief.ts +246 -0
  18. package/lib/narrative-state/hash.ts +61 -0
  19. package/lib/narrative-state/map-html.ts +348 -0
  20. package/lib/narrative-state/map.ts +282 -0
  21. package/lib/narrative-state/normalize.ts +361 -0
  22. package/lib/narrative-state/project-compat.ts +14 -0
  23. package/lib/narrative-state/queries.ts +433 -0
  24. package/lib/narrative-state/readiness.ts +359 -0
  25. package/lib/narrative-state/render-plan.ts +250 -0
  26. package/lib/narrative-state/research-gaps.ts +191 -0
  27. package/lib/narrative-state/types.ts +172 -0
  28. package/lib/prompt-builder.ts +59 -26
  29. package/lib/workspace-state/evidence-status.ts +21 -1
  30. package/lib/workspace-state/graph.ts +174 -2
  31. package/lib/workspace-state/types.ts +13 -1
  32. package/package.json +1 -1
  33. package/plugin.ts +58 -2
  34. package/skill/NARRATIVE_SKILL.md +64 -0
  35. package/tools/decks.ts +265 -2
  36. package/tools/narrative-view.ts +84 -0
  37. package/tools/workspace-scan.ts +14 -1
@@ -0,0 +1,246 @@
1
+ import type { DecksState } from "../decks-state"
2
+ import { recordWorkspaceAction } from "../workspace-state/actions"
3
+ import {
4
+ artifactNodeIdForRenderTarget,
5
+ normalizeWorkspacePath,
6
+ renderTargetId,
7
+ upsertRenderTarget,
8
+ } from "../workspace-state/render-targets"
9
+ import type { RenderTarget } from "../workspace-state/types"
10
+ import { computeNarrativeHash } from "./hash"
11
+ import { normalizeNarrativeState } from "./normalize"
12
+ import { reviewNarrativeState } from "./readiness"
13
+ import type { NarrativeApproval, NarrativeClaim, NarrativeEvidenceBinding, NarrativeStateV1 } from "./types"
14
+
15
+ export const DEFAULT_EXECUTIVE_BRIEF_PATH = "briefs/executive-brief.md"
16
+
17
+ export interface CompileExecutiveBriefOptions {
18
+ outputPath?: string
19
+ now?: string
20
+ }
21
+
22
+ export type CompileExecutiveBriefResult =
23
+ | {
24
+ ok: true
25
+ state: DecksState
26
+ outputPath: string
27
+ content: string
28
+ target: RenderTarget
29
+ narrativeHash: string
30
+ }
31
+ | {
32
+ ok: false
33
+ state: DecksState
34
+ reason: string
35
+ narrativeHash?: string
36
+ }
37
+
38
+ export function compileExecutiveBrief(state: DecksState, options: CompileExecutiveBriefOptions = {}): CompileExecutiveBriefResult {
39
+ const now = options.now ?? new Date().toISOString()
40
+ const reviewed = reviewNarrativeState(state, { now })
41
+ const narrative = reviewed.state.narrative ?? normalizeNarrativeState(reviewed.state)
42
+ const narrativeHash = reviewed.result.narrativeHash || computeNarrativeHash(narrative)
43
+ const allowedApproval = currentNarrativeApprovalOrOverride(narrative, narrativeHash)
44
+
45
+ if (!allowedApproval) {
46
+ return {
47
+ ok: false,
48
+ state: reviewed.state,
49
+ reason: "Executive brief rendering requires current narrative approval or an explicit render override.",
50
+ narrativeHash,
51
+ }
52
+ }
53
+
54
+ const outputPath = normalizeWorkspacePath(options.outputPath || DEFAULT_EXECUTIVE_BRIEF_PATH)
55
+ const content = renderExecutiveBriefMarkdown(narrative, narrativeHash, now, allowedApproval)
56
+ const claimIds = narrative.claims.map((claim) => claim.id).sort()
57
+ const evidenceBindingIds = narrative.evidenceBindings.map((binding) => binding.id).sort()
58
+ const target: RenderTarget = {
59
+ id: renderTargetId("executive_brief", outputPath),
60
+ type: "executive_brief",
61
+ outputPath,
62
+ sourceNodeIds: [narrative.id, ...claimIds, ...evidenceBindingIds],
63
+ artifactVersion: narrativeHash,
64
+ contractStatus: "valid",
65
+ data: {
66
+ narrativeHash,
67
+ generatedAt: now,
68
+ format: "markdown",
69
+ claimIds,
70
+ evidenceBindingIds,
71
+ approvalId: allowedApproval.id,
72
+ approvalScope: allowedApproval.scope,
73
+ },
74
+ }
75
+
76
+ const next: DecksState = { ...reviewed.state, narrative }
77
+ upsertRenderTarget(next, target)
78
+ recordWorkspaceAction(next, {
79
+ type: "artifact.rendered",
80
+ actor: "revela-brief",
81
+ inputs: {
82
+ type: "executive_brief",
83
+ narrativeId: narrative.id,
84
+ narrativeHash,
85
+ approvalId: allowedApproval.id,
86
+ },
87
+ outputs: {
88
+ outputPath,
89
+ targetId: target.id,
90
+ claimCount: claimIds.length,
91
+ evidenceBindingCount: evidenceBindingIds.length,
92
+ },
93
+ status: "success",
94
+ summary: "Rendered executive brief from approved narrative state.",
95
+ nodeIds: [target.id, artifactNodeIdForRenderTarget(target), narrative.id, ...claimIds],
96
+ timestamp: now,
97
+ })
98
+
99
+ return { ok: true, state: next, outputPath, content, target, narrativeHash }
100
+ }
101
+
102
+ function currentNarrativeApprovalOrOverride(narrative: NarrativeStateV1, narrativeHash: string): NarrativeApproval | undefined {
103
+ const approvals = [...(narrative.approvals ?? [])]
104
+ for (let index = approvals.length - 1; index >= 0; index -= 1) {
105
+ const approval = approvals[index]
106
+ if (approval.narrativeHash !== narrativeHash) continue
107
+ if (approval.scope === "narrative" && approval.approvedBy === "user") return approval
108
+ if (approval.scope === "render_override" || approval.approvedBy === "override") return approval
109
+ }
110
+ return undefined
111
+ }
112
+
113
+ function renderExecutiveBriefMarkdown(narrative: NarrativeStateV1, narrativeHash: string, generatedAt: string, approval: NarrativeApproval): string {
114
+ const evidenceByClaim = groupEvidenceByClaim(narrative.evidenceBindings)
115
+ const centralClaims = narrative.claims.filter((claim) => claim.importance === "central")
116
+ const supportingClaims = narrative.claims.filter((claim) => claim.importance !== "central")
117
+ const lines: string[] = []
118
+
119
+ lines.push("# Executive Brief")
120
+ lines.push("")
121
+ lines.push(`Generated: ${generatedAt}`)
122
+ lines.push(`Narrative ID: ${narrative.id}`)
123
+ lines.push(`Narrative hash: ${narrativeHash}`)
124
+ lines.push(`Approval: ${approval.id} (${approval.scope}, ${approval.approvedBy})`)
125
+ lines.push("")
126
+ lines.push("## Decision Context")
127
+ lines.push(`- Audience: ${fallback(narrative.audience.primary)}`)
128
+ lines.push(`- Belief before: ${fallback(narrative.audience.beliefBefore)}`)
129
+ lines.push(`- Belief after: ${fallback(narrative.audience.beliefAfter)}`)
130
+ lines.push(`- Decision/action: ${fallback(narrative.decision.action)}`)
131
+ if (narrative.decision.owner) lines.push(`- Owner: ${narrative.decision.owner}`)
132
+ if (narrative.decision.deadline) lines.push(`- Deadline: ${narrative.decision.deadline}`)
133
+ if (narrative.decision.consequenceOfNoDecision) lines.push(`- Consequence of no decision: ${narrative.decision.consequenceOfNoDecision}`)
134
+ lines.push("")
135
+ lines.push("## Thesis")
136
+ lines.push(narrative.thesis?.statement ? `${narrative.thesis.statement} (${narrative.thesis.confidence} confidence)` : "Not recorded.")
137
+ if (narrative.thesis?.caveat) lines.push(`Caveat: ${narrative.thesis.caveat}`)
138
+ lines.push("")
139
+
140
+ appendClaims(lines, "Central Claims", centralClaims, evidenceByClaim)
141
+ appendClaims(lines, "Supporting Claims", supportingClaims, evidenceByClaim)
142
+ appendObjections(lines, narrative)
143
+ appendRisks(lines, narrative)
144
+ appendResearchGaps(lines, narrative)
145
+ lines.push("## Provenance")
146
+ lines.push(`- Render target: executive_brief`)
147
+ lines.push(`- Source narrative: ${narrative.id}`)
148
+ lines.push(`- Narrative hash: ${narrativeHash}`)
149
+ lines.push(`- Approval id: ${approval.id}`)
150
+ lines.push("- This brief is compiled from canonical narrative state, not from a deck summary.")
151
+ lines.push("")
152
+
153
+ return lines.join("\n")
154
+ }
155
+
156
+ function appendClaims(lines: string[], title: string, claims: NarrativeClaim[], evidenceByClaim: Map<string, NarrativeEvidenceBinding[]>): void {
157
+ lines.push(`## ${title}`)
158
+ if (claims.length === 0) {
159
+ lines.push("Not recorded.")
160
+ lines.push("")
161
+ return
162
+ }
163
+
164
+ for (const claim of claims) {
165
+ lines.push(`### ${claim.text}`)
166
+ lines.push(`- Claim ID: ${claim.id}`)
167
+ lines.push(`- Kind: ${claim.kind}`)
168
+ lines.push(`- Evidence status: ${claim.evidenceStatus}`)
169
+ if (claim.supportedScope) lines.push(`- Supported scope: ${claim.supportedScope}`)
170
+ if (claim.unsupportedScope) lines.push(`- Unsupported scope: ${claim.unsupportedScope}`)
171
+ for (const caveat of claim.caveats ?? []) lines.push(`- Caveat: ${caveat}`)
172
+ const bindings = evidenceByClaim.get(claim.id) ?? []
173
+ if (bindings.length === 0) lines.push("- Evidence: none bound")
174
+ else {
175
+ lines.push("- Evidence:")
176
+ for (const binding of bindings) appendEvidence(lines, binding)
177
+ }
178
+ lines.push("")
179
+ }
180
+ }
181
+
182
+ function appendEvidence(lines: string[], binding: NarrativeEvidenceBinding): void {
183
+ lines.push(` - ${binding.id} (${binding.strength})`)
184
+ lines.push(` - Source: ${binding.source}`)
185
+ if (binding.findingsFile) lines.push(` - Findings file: ${binding.findingsFile}`)
186
+ if (binding.sourcePath) lines.push(` - Source path: ${binding.sourcePath}`)
187
+ if (binding.location) lines.push(` - Location: ${binding.location}`)
188
+ if (binding.url) lines.push(` - URL: ${binding.url}`)
189
+ if (binding.quote) lines.push(` - Quote: ${binding.quote}`)
190
+ if (binding.supportScope) lines.push(` - Support scope: ${binding.supportScope}`)
191
+ if (binding.unsupportedScope) lines.push(` - Unsupported scope: ${binding.unsupportedScope}`)
192
+ if (binding.caveat) lines.push(` - Caveat: ${binding.caveat}`)
193
+ }
194
+
195
+ function appendObjections(lines: string[], narrative: NarrativeStateV1): void {
196
+ lines.push("## Objections")
197
+ if (narrative.objections.length === 0) lines.push("Not recorded.")
198
+ for (const objection of narrative.objections) {
199
+ lines.push(`- ${objection.text}`)
200
+ lines.push(` - Objection ID: ${objection.id}`)
201
+ if (objection.claimId) lines.push(` - Challenges claim: ${objection.claimId}`)
202
+ lines.push(` - Priority: ${objection.priority}`)
203
+ if (objection.response) lines.push(` - Response: ${objection.response}`)
204
+ }
205
+ lines.push("")
206
+ }
207
+
208
+ function appendRisks(lines: string[], narrative: NarrativeStateV1): void {
209
+ lines.push("## Risks")
210
+ if (narrative.risks.length === 0) lines.push("Not recorded.")
211
+ for (const risk of narrative.risks) {
212
+ lines.push(`- ${risk.text}`)
213
+ lines.push(` - Risk ID: ${risk.id}`)
214
+ if (risk.claimId) lines.push(` - Constrains claim: ${risk.claimId}`)
215
+ lines.push(` - Severity: ${risk.severity}`)
216
+ if (risk.mitigation) lines.push(` - Mitigation: ${risk.mitigation}`)
217
+ }
218
+ lines.push("")
219
+ }
220
+
221
+ function appendResearchGaps(lines: string[], narrative: NarrativeStateV1): void {
222
+ lines.push("## Research Gaps")
223
+ const gaps = narrative.researchGaps ?? []
224
+ if (gaps.length === 0) lines.push("Not recorded.")
225
+ for (const gap of gaps) {
226
+ lines.push(`- ${gap.question}`)
227
+ lines.push(` - Gap ID: ${gap.id}`)
228
+ lines.push(` - Status: ${gap.status}`)
229
+ lines.push(` - Priority: ${gap.priority}`)
230
+ if (gap.targetId) lines.push(` - Target: ${gap.targetType}:${gap.targetId}`)
231
+ if (gap.findingsFile) lines.push(` - Findings file: ${gap.findingsFile}`)
232
+ if (gap.evidenceBindingIds?.length) lines.push(` - Evidence bindings: ${gap.evidenceBindingIds.join(", ")}`)
233
+ if (gap.notes) lines.push(` - Notes: ${gap.notes}`)
234
+ }
235
+ lines.push("")
236
+ }
237
+
238
+ function groupEvidenceByClaim(bindings: NarrativeEvidenceBinding[]): Map<string, NarrativeEvidenceBinding[]> {
239
+ const grouped = new Map<string, NarrativeEvidenceBinding[]>()
240
+ for (const binding of bindings) grouped.set(binding.claimId, [...(grouped.get(binding.claimId) ?? []), binding])
241
+ return grouped
242
+ }
243
+
244
+ function fallback(value: string | undefined): string {
245
+ return value?.trim() || "Not recorded"
246
+ }
@@ -0,0 +1,61 @@
1
+ import { createHash } from "crypto"
2
+ import type { NarrativeStateV1 } from "./types"
3
+
4
+ export function stableNarrativeId(seed: string): string {
5
+ return `narrative:${stableHash(seed || "workspace")}`
6
+ }
7
+
8
+ export function stableClaimId(text: string): string {
9
+ return `claim:${stableHash(text)}`
10
+ }
11
+
12
+ export function stableClaimRelationId(fromClaimId: string, toClaimId: string, relation: string): string {
13
+ return `claim-relation:${stableHash(`${fromClaimId}:${toClaimId}:${relation}`)}`
14
+ }
15
+
16
+ export function stableEvidenceId(claimId: string, seed: string): string {
17
+ return `evidence:${claimId}:${stableHash(seed)}`
18
+ }
19
+
20
+ export function stableObjectionId(text: string): string {
21
+ return `objection:${stableHash(text)}`
22
+ }
23
+
24
+ export function stableRiskId(text: string): string {
25
+ return `risk:${stableHash(text)}`
26
+ }
27
+
28
+ export function stableResearchGapId(seed: string): string {
29
+ return `research-gap:${stableHash(seed)}`
30
+ }
31
+
32
+ export function computeNarrativeHash(narrative: NarrativeStateV1): string {
33
+ return stableHash(stableStringify({
34
+ version: narrative.version,
35
+ id: narrative.id,
36
+ audience: narrative.audience,
37
+ decision: narrative.decision,
38
+ thesis: narrative.thesis,
39
+ claims: narrative.claims,
40
+ claimRelations: narrative.claimRelations ?? [],
41
+ evidenceBindings: narrative.evidenceBindings,
42
+ objections: narrative.objections,
43
+ risks: narrative.risks,
44
+ }))
45
+ }
46
+
47
+ export function stableHash(input: unknown): string {
48
+ const text = typeof input === "string" ? input : stableStringify(input)
49
+ return createHash("sha1").update(text).digest("hex").slice(0, 12)
50
+ }
51
+
52
+ function stableStringify(value: unknown): string {
53
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`
54
+ if (value && typeof value === "object") {
55
+ const entries = Object.entries(value as Record<string, unknown>)
56
+ .filter(([, item]) => item !== undefined)
57
+ .sort(([a], [b]) => a.localeCompare(b))
58
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`
59
+ }
60
+ return JSON.stringify(value)
61
+ }