@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "fs"
|
|
2
|
+
import { tmpdir } from "os"
|
|
3
|
+
import { join } from "path"
|
|
4
|
+
import { openUrl as defaultOpenUrl } from "../edit/open"
|
|
5
|
+
import { hasDecksState, readDecksState } from "../decks-state"
|
|
6
|
+
import { buildNarrativeMap, formatNarrativeMap } from "../narrative-state/map"
|
|
7
|
+
import { renderNarrativeMapHtmlWithDisplay } from "../narrative-state/map-html"
|
|
8
|
+
import { emptyDisplayModel, type NarrativeViewLanguage, type ValidatedNarrativeDisplayModel } from "../narrative-state/display"
|
|
9
|
+
|
|
10
|
+
export interface NarrativeArgs {
|
|
11
|
+
language: NarrativeViewLanguage
|
|
12
|
+
raw: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ParseNarrativeArgsResult = { ok: true; args: NarrativeArgs } | { ok: false; error: string }
|
|
16
|
+
|
|
17
|
+
export function parseNarrativeArgs(param: string): ParseNarrativeArgsResult {
|
|
18
|
+
const tokens = param.trim().split(/\s+/).filter(Boolean)
|
|
19
|
+
let language: NarrativeViewLanguage = "en"
|
|
20
|
+
let raw = false
|
|
21
|
+
const languageParts: string[] = []
|
|
22
|
+
for (const token of tokens) {
|
|
23
|
+
const normalized = token.toLowerCase()
|
|
24
|
+
if (normalized === "--raw") {
|
|
25
|
+
raw = true
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
28
|
+
if (token.startsWith("--") && token.length > 2) {
|
|
29
|
+
language = normalizeLanguageRequest(token.slice(2))
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
languageParts.push(token)
|
|
33
|
+
}
|
|
34
|
+
if (languageParts.length > 0) language = normalizeLanguageRequest(languageParts.join(" "))
|
|
35
|
+
return { ok: true, args: { language, raw } }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeLanguageRequest(value: string): NarrativeViewLanguage {
|
|
39
|
+
const trimmed = value.trim()
|
|
40
|
+
const normalized = trimmed.toLowerCase()
|
|
41
|
+
if (["en", "eng", "english"].includes(normalized)) return "en"
|
|
42
|
+
if (["cn", "zh", "zh-cn", "chinese"].includes(normalized)) return "zh-CN"
|
|
43
|
+
if (["jp", "ja", "ja-jp", "japanese"].includes(normalized)) return "ja-JP"
|
|
44
|
+
return trimmed || "en"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function handleNarrative(
|
|
48
|
+
options: { workspaceRoot: string; openBrowser?: boolean; openUrl?: (url: string) => void; language?: NarrativeViewLanguage; display?: ValidatedNarrativeDisplayModel },
|
|
49
|
+
send: (text: string) => Promise<void>,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
if (!hasDecksState(options.workspaceRoot)) {
|
|
53
|
+
await send("No `DECKS.json` found. Run `/revela init` first to initialize the narrative workspace.")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const state = readDecksState(options.workspaceRoot)
|
|
58
|
+
const map = buildNarrativeMap(state)
|
|
59
|
+
const markdown = formatNarrativeMap(map)
|
|
60
|
+
|
|
61
|
+
if (options.openBrowser) {
|
|
62
|
+
const htmlPath = writeNarrativeMapHtml(map, options.display ?? emptyDisplayModel(options.language ?? "en"))
|
|
63
|
+
const url = `file://${htmlPath}`
|
|
64
|
+
try {
|
|
65
|
+
;(options.openUrl ?? defaultOpenUrl)(url)
|
|
66
|
+
await send(`Opened read-only narrative workspace: ${url}\n\n${markdown}`)
|
|
67
|
+
} catch (e: any) {
|
|
68
|
+
await send(`Read-only narrative workspace generated but could not open automatically: ${url}\n\n${e.message || String(e)}\n\n${markdown}`)
|
|
69
|
+
}
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await send(markdown)
|
|
74
|
+
} catch (e: any) {
|
|
75
|
+
await send(`**Narrative map failed:** ${e.message || String(e)}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildNarrativeViewPrompt(options: { workspaceRoot: string; language: NarrativeViewLanguage }): string {
|
|
80
|
+
if (!hasDecksState(options.workspaceRoot)) {
|
|
81
|
+
return "No `DECKS.json` found. Tell the user to run `/revela init` before opening the narrative view. Do not call any tool."
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const map = buildNarrativeMap(readDecksState(options.workspaceRoot))
|
|
85
|
+
const projection = {
|
|
86
|
+
narrativeHash: map.snapshot.narrativeHash,
|
|
87
|
+
language: options.language,
|
|
88
|
+
snapshot: map.snapshot,
|
|
89
|
+
claims: map.claimFlow.map((claim) => ({
|
|
90
|
+
id: claim.id,
|
|
91
|
+
kind: claim.kind,
|
|
92
|
+
importance: claim.importance,
|
|
93
|
+
evidenceStatus: claim.evidenceStatus,
|
|
94
|
+
text: claim.text,
|
|
95
|
+
supportedScope: claim.supportedScope,
|
|
96
|
+
unsupportedScope: claim.unsupportedScope,
|
|
97
|
+
evidence: claim.evidence.map((evidence) => ({ source: evidence.source, strength: evidence.strength, findingsFile: evidence.findingsFile, location: evidence.location, quote: evidence.quote, caveat: evidence.caveat, unsupportedScope: evidence.unsupportedScope })),
|
|
98
|
+
})),
|
|
99
|
+
relations: map.claimRelations.map((relation) => ({ id: relation.id, fromClaimId: relation.fromClaimId, toClaimId: relation.toClaimId, relation: relation.relation, rationale: relation.rationale, inferred: relation.inferred })),
|
|
100
|
+
researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question })),
|
|
101
|
+
artifactCoverage: map.artifactCoverage.map((artifact) => ({ type: artifact.type, outputPath: artifact.outputPath, stale: artifact.stale, slideRefs: artifact.slideRefs.map((ref) => ({ claimId: ref.claimId, slideIndex: ref.slideIndex, role: ref.role, match: ref.match, location: ref.location })) })),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return `Prepare the read-only Revela narrative UI display model.
|
|
105
|
+
|
|
106
|
+
Target language request: ${options.language}
|
|
107
|
+
- The language value is passed from the user's /revela narrative arguments. Interpret it as the desired UI/display language.
|
|
108
|
+
- Examples: --cn maps to zh-CN, --jp maps to ja-JP, while --fr, --de, --es, --ko, --Arabic, --Portuguese-BR, or a written language name should be localized normally into that requested language.
|
|
109
|
+
- Default /revela narrative language is en when the user provides no language request.
|
|
110
|
+
|
|
111
|
+
You must call the \`revela-narrative-view\` tool exactly once.
|
|
112
|
+
|
|
113
|
+
Hard rules:
|
|
114
|
+
- Do not mutate DECKS.json, deck HTML, evidence, claims, relations, approvals, or artifacts.
|
|
115
|
+
- Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
|
|
116
|
+
- Preserve every claimId exactly.
|
|
117
|
+
- Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
|
|
118
|
+
- You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
119
|
+
- For inferred relations, do not provide relation displayLabel or displayRationale; inferred relations are unconfirmed order notes, not causal/support/dependency judgments.
|
|
120
|
+
- relation displayRationale may only localize or clarify an existing canonical relation rationale. If relation.rationale is missing or the relation is inferred, do not provide displayRationale; the UI will show the missing or inferred status.
|
|
121
|
+
- Keep source paths, findings files, claim IDs, narrative hash, and numbers unchanged.
|
|
122
|
+
- Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
|
|
123
|
+
- Do not translate claim IDs, relation endpoints, narrative hash, source paths, findings files, URLs, numbers, or quoted/source facts.
|
|
124
|
+
- Use natural business and manufacturing terminology in the target language, not word-by-word machine translation.
|
|
125
|
+
- If a fact is missing, describe it as missing instead of filling it in.
|
|
126
|
+
|
|
127
|
+
Chinese localization rules when the target language request is Chinese, zh, zh-CN, --cn, 中文, or Simplified Chinese:
|
|
128
|
+
- Use natural business/manufacturing Chinese, not word-by-word machine translation.
|
|
129
|
+
- In manufacturing, industrial AI, automation, and autonomous systems context, translate "autonomy" as "自主化", "自主能力", or "自主系统". Do not translate it as "自治".
|
|
130
|
+
- Translate "autonomous" as "自主的" / "自主化的" where appropriate, not "自治的".
|
|
131
|
+
- Translate "architectural" as "架构层面的", "架构性", or "架构问题" according to context.
|
|
132
|
+
- Slug-like or kebab-case claim text such as "autonomy-is-architectural" should become a readable displayTitle such as "自主化是架构问题" or "自主化必须作为架构问题处理", not a literal token-by-token translation.
|
|
133
|
+
- If the canonical claim text is only a slug, preserve the claimId exactly but write displayTitle as a readable claim title.
|
|
134
|
+
|
|
135
|
+
Call \`revela-narrative-view\` with:
|
|
136
|
+
- language: ${options.language}
|
|
137
|
+
- narrativeHash: ${map.snapshot.narrativeHash}
|
|
138
|
+
- displayModel.version: 1
|
|
139
|
+
- displayModel.language: ${options.language}
|
|
140
|
+
- displayModel.claimCards only for claim IDs listed below
|
|
141
|
+
- displayModel.relations only for relations listed below
|
|
142
|
+
|
|
143
|
+
Compact deterministic narrative map:
|
|
144
|
+
|
|
145
|
+
\`\`\`json
|
|
146
|
+
${JSON.stringify(projection, null, 2)}
|
|
147
|
+
\`\`\``
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function writeNarrativeMapHtml(map: ReturnType<typeof buildNarrativeMap>, display: ValidatedNarrativeDisplayModel = emptyDisplayModel("en")): string {
|
|
151
|
+
const dir = join(tmpdir(), "revela-narrative")
|
|
152
|
+
mkdirSync(dir, { recursive: true })
|
|
153
|
+
const file = join(dir, `${safeFilePart(map.snapshot.narrativeId)}-${map.snapshot.narrativeHash}.html`)
|
|
154
|
+
writeFileSync(file, renderNarrativeMapHtmlWithDisplay(map, display), "utf-8")
|
|
155
|
+
return file
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function safeFilePart(value: string): string {
|
|
159
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "narrative"
|
|
160
|
+
}
|
package/lib/commands/review.ts
CHANGED
|
@@ -6,15 +6,129 @@ export function buildReviewPrompt({
|
|
|
6
6
|
}: {
|
|
7
7
|
exists: boolean
|
|
8
8
|
workspaceRoot?: string
|
|
9
|
+
}): string {
|
|
10
|
+
const state = exists
|
|
11
|
+
? `${DECKS_STATE_FILE} exists. Read it through the revela-decks tool.`
|
|
12
|
+
: `${DECKS_STATE_FILE} does not exist yet. Create or normalize it through the revela-decks tool only if there is enough workspace narrative context.`
|
|
13
|
+
|
|
14
|
+
return `Review Revela narrative readiness.
|
|
15
|
+
|
|
16
|
+
Goal:
|
|
17
|
+
- Use ${DECKS_STATE_FILE} as the compatibility workspace-state file, but review the canonical narrative state first: audience, belief shift, decision/action, thesis, central claims, evidence boundaries, objections, risks, and approval state.
|
|
18
|
+
- Treat this as a narrative readiness review, not a deck HTML write-readiness review.
|
|
19
|
+
- Do not write, patch, or directly edit ${DECKS_STATE_FILE}. Use the \`revela-decks\` tool for all state changes.
|
|
20
|
+
- Call \`revela-decks\` action \`reviewNarrative\` as the authoritative deterministic readiness engine.
|
|
21
|
+
- Do not call \`revela-decks\` action \`review\` here. That action is the deck/artifact gate and belongs to \`/revela deck --review\`.
|
|
22
|
+
- Do not treat legacy \`writeReadiness.status\`, old review snapshots, or an existing HTML deck as narrative approval.
|
|
23
|
+
- Do not write or overwrite \`decks/*.html\` during narrative review.
|
|
24
|
+
- If the narrative is \`ready_for_approval\`, ask whether the user wants to approve it or revise it. Do not approve automatically.
|
|
25
|
+
- Only call \`revela-decks\` action \`approveNarrative\` when the user explicitly asks to approve or override.
|
|
26
|
+
|
|
27
|
+
Current state:
|
|
28
|
+
- ${state}
|
|
29
|
+
${workspaceRoot ? `- Current workspace root: \`${workspaceRoot}\`` : ""}
|
|
30
|
+
|
|
31
|
+
Workspace boundary rules:
|
|
32
|
+
- Stay strictly inside the current workspace root for every scan, glob, read, and write.
|
|
33
|
+
- Do not search parent directories, home directories, or unrelated absolute directories.
|
|
34
|
+
- Do not use \`~\`, \`..\`, or parent-directory traversal to discover files.
|
|
35
|
+
- For Glob/file searches, use the current workspace as the search root. Do not set the search root to a parent directory or home directory.
|
|
36
|
+
|
|
37
|
+
Workflow:
|
|
38
|
+
1. Call \`revela-decks\` with action \`read\` to inspect the current workspace state.
|
|
39
|
+
2. If ${DECKS_STATE_FILE} is missing or empty, do not invent a deck plan, slide count, design, output path, or visual style. Report the smallest narrative inputs needed, usually audience, belief-before, belief-after, decision/action, thesis, central claims, evidence availability, objections, and risks.
|
|
40
|
+
3. If legacy deck state exists, let the tool-normalized canonical narrative derived from \`narrativeBrief\`, slide roles, slide content, and slide evidence be reviewed. Do not assume old deck readiness means approval.
|
|
41
|
+
4. Call \`revela-decks\` action \`reviewNarrative\`. Use its returned \`status\`, \`blockers\`, \`warnings\`, \`issues\`, \`narrativeHash\`, \`approval\`, and \`nextActions\` as authoritative.
|
|
42
|
+
5. If research findings have been saved but not attached or evidence-bound, report them as unattached research state, not proof.
|
|
43
|
+
6. If central claims lack required evidence, report the named claim and the exact next action: attach findings, bind evidence, run targeted research, narrow unsupported scope, or rewrite the claim.
|
|
44
|
+
7. If approval is missing or stale, clearly distinguish \`ready_for_approval\`, \`approved\`, and render override.
|
|
45
|
+
|
|
46
|
+
Report format:
|
|
47
|
+
- Start with \`Narrative readiness: <status>\`.
|
|
48
|
+
- Include \`Narrative hash: <hash>\` when returned.
|
|
49
|
+
- If blocked or needs research, list each blocker with issue type, claim text when available, and suggested next action.
|
|
50
|
+
- If warnings exist, list them after blockers as residual risks.
|
|
51
|
+
- If approval is missing, ask whether the user wants to approve the narrative or revise it.
|
|
52
|
+
- If approval is stale, say the prior approval no longer matches the current narrative hash.
|
|
53
|
+
- Keep deck/artifact readiness separate. If the user wants to review slide-writing readiness, tell them to run \`/revela deck --review\`.
|
|
54
|
+
|
|
55
|
+
Rules:
|
|
56
|
+
- Do not write or overwrite \`decks/*.html\` during narrative review.
|
|
57
|
+
- Do not call \`revela-decks review\` during narrative review.
|
|
58
|
+
- Do not apply evidence candidates, bind evidence, or rewrite slide text unless the user explicitly asks.
|
|
59
|
+
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
60
|
+
- Do not add inferred user preferences to long-term preference state.
|
|
61
|
+
|
|
62
|
+
Start now by reading ${DECKS_STATE_FILE} through \`revela-decks\`, then call \`revela-decks\` action \`reviewNarrative\`.`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildDeckPrompt({
|
|
66
|
+
exists,
|
|
67
|
+
workspaceRoot,
|
|
68
|
+
}: {
|
|
69
|
+
exists: boolean
|
|
70
|
+
workspaceRoot?: string
|
|
71
|
+
}): string {
|
|
72
|
+
const state = exists
|
|
73
|
+
? `${DECKS_STATE_FILE} exists. Read it through the revela-decks tool.`
|
|
74
|
+
: `${DECKS_STATE_FILE} does not exist yet. Do not invent a deck; initialize narrative state first with /revela init.`
|
|
75
|
+
|
|
76
|
+
return `Begin Revela deck render handoff.
|
|
77
|
+
|
|
78
|
+
Goal:
|
|
79
|
+
- Treat this as the explicit transition from approved narrative state to deck render planning.
|
|
80
|
+
- Use the deck-render prompt mode for design, layout, component, HTML, QA, and deck artifact rules.
|
|
81
|
+
- Do not write or overwrite \`decks/*.html\` until the narrative handoff and deck/artifact gate are both satisfied.
|
|
82
|
+
- Do not treat legacy \`writeReadiness.status\`, old review snapshots, or existing HTML decks as narrative approval.
|
|
83
|
+
- Do not bypass the deck HTML contract, review snapshot freshness, source-trace expectations, or export preflight protections.
|
|
84
|
+
|
|
85
|
+
Current state:
|
|
86
|
+
- ${state}
|
|
87
|
+
${workspaceRoot ? `- Current workspace root: \`${workspaceRoot}\`` : ""}
|
|
88
|
+
|
|
89
|
+
Workflow:
|
|
90
|
+
1. Call \`revela-decks\` action \`read\`.
|
|
91
|
+
2. Call \`revela-decks\` action \`reviewNarrative\` before planning deck slides.
|
|
92
|
+
3. If narrative readiness is \`approved\`, continue. If it is \`ready_for_approval\`, ask the user for explicit approval before continuing. If it is blocked, stale, or needs research, stop and report the smallest next action. Do not call \`approveNarrative\` unless the user explicitly approves or requests a render override.
|
|
93
|
+
4. After approval or explicit render override exists, call \`revela-decks\` action \`compileDeckPlan\`. This projects canonical narrative claims and evidence bindings into compatibility \`slides[]\` and \`slides[].evidence[]\`; it must not write HTML.
|
|
94
|
+
5. If \`compileDeckPlan\` returns \`skipped\`, stop and report the reason. Do not invent slide specs manually to bypass approval.
|
|
95
|
+
6. Ask for or confirm visual design only after the narrative deck plan exists. Fetch required design layouts/components with \`revela-designs read\` as needed.
|
|
96
|
+
7. Update only deck/artifact metadata through \`revela-decks upsertDeck\` / \`upsertSlides\` when required by confirmed design/layout choices. Do not change canonical narrative claims unless the user asks to revise the narrative.
|
|
97
|
+
8. Call \`revela-decks\` action \`review\` as the artifact gate. It computes \`writeReadiness\` and review snapshots for deck HTML writing.
|
|
98
|
+
9. Write \`decks/*.html\` only if the deck/artifact gate is ready and all deck HTML contract requirements can be satisfied. If not ready, report blockers and stop.
|
|
99
|
+
|
|
100
|
+
Report format before any HTML write:
|
|
101
|
+
- Start with \`Deck handoff: <status>\`.
|
|
102
|
+
- Include narrative readiness status and narrative hash when available.
|
|
103
|
+
- Include whether \`compileDeckPlan\` compiled or skipped.
|
|
104
|
+
- If deck/artifact review is blocked, list blockers separately from narrative blockers.
|
|
105
|
+
- If proceeding to HTML writing, state which approved narrative hash and deck review snapshot authorized the artifact work.
|
|
106
|
+
|
|
107
|
+
Rules:
|
|
108
|
+
- \`compileDeckPlan\` is the canonical narrative-to-deck planning path. Do not manually invent slide specs to avoid it.
|
|
109
|
+
- Deck slide specs are render-target projections. Canonical narrative remains the authority for audience, decision, claims, evidence boundaries, objections, risks, and approval.
|
|
110
|
+
- Applying evidence candidates, rewriting canonical claims, or approving narratives requires explicit user instruction.
|
|
111
|
+
- Do not store secrets, credentials, tokens, or sensitive personal information.
|
|
112
|
+
|
|
113
|
+
Start now by reading ${DECKS_STATE_FILE}, reviewing narrative readiness, and then compiling the deck plan only if approval or explicit render override is current.`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function buildDeckReviewPrompt({
|
|
117
|
+
exists,
|
|
118
|
+
workspaceRoot,
|
|
119
|
+
}: {
|
|
120
|
+
exists: boolean
|
|
121
|
+
workspaceRoot?: string
|
|
9
122
|
}): string {
|
|
10
123
|
const state = exists
|
|
11
124
|
? `${DECKS_STATE_FILE} exists. Read it through the revela-decks tool.`
|
|
12
125
|
: `${DECKS_STATE_FILE} does not exist yet. Create it through the revela-decks tool if there is enough deck context.`
|
|
13
126
|
|
|
14
|
-
return `Review Revela deck write readiness.
|
|
127
|
+
return `Review Revela deck/artifact write readiness.
|
|
15
128
|
|
|
16
129
|
Goal:
|
|
17
130
|
- Use ${DECKS_STATE_FILE} as the source of truth for whether the current workspace deck is ready to be written to \`decks/*.html\`.
|
|
131
|
+
- Treat this as an artifact gate for deck rendering, not strategic narrative approval. Narrative readiness is reviewed by \`/revela review\`.
|
|
18
132
|
- 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
133
|
- Do not write, patch, or directly edit ${DECKS_STATE_FILE}. Use the \`revela-decks\` tool for all state changes.
|
|
20
134
|
- Let \`revela-decks\` action \`review\` compute writeReadiness; do not manually set readiness to ready.
|
package/lib/decks-state.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
latestReviewSnapshotForTarget,
|
|
18
18
|
} from "./workspace-state/review-snapshots"
|
|
19
19
|
import { WORKSPACE_STATE_FILE, type RenderTarget, type ReviewSnapshot, type WorkspaceAction } from "./workspace-state/types"
|
|
20
|
+
import { normalizeCanonicalNarrativeState, normalizeNarrativeState } from "./narrative-state/normalize"
|
|
21
|
+
import type { NarrativeStateV1 } from "./narrative-state/types"
|
|
20
22
|
|
|
21
23
|
export const DECKS_STATE_FILE = WORKSPACE_STATE_FILE
|
|
22
24
|
|
|
@@ -24,10 +26,12 @@ export type DeckProductionStatus = "planning" | "blocked" | "ready" | "written"
|
|
|
24
26
|
export type SlideProductionStatus = "planned" | "ready" | "written" | "qa_passed" | "qa_failed"
|
|
25
27
|
export type WriteReadinessStatus = "blocked" | "ready" | "written"
|
|
26
28
|
export type NarrativeRole = "context" | "tension" | "evidence" | "recommendation" | "risk" | "ask" | "appendix" | "close"
|
|
29
|
+
export type SlideClaimRefRole = "primary" | "supporting" | "evidence" | "risk" | "objection"
|
|
27
30
|
|
|
28
31
|
export interface DecksState {
|
|
29
32
|
version: 1
|
|
30
33
|
activeDeck?: string
|
|
34
|
+
narrative?: NarrativeStateV1
|
|
31
35
|
workspace: {
|
|
32
36
|
brief?: string
|
|
33
37
|
sourceMaterials: SourceMaterial[]
|
|
@@ -132,6 +136,9 @@ export interface SlideSpec {
|
|
|
132
136
|
layout: string
|
|
133
137
|
qa?: boolean
|
|
134
138
|
components: string[]
|
|
139
|
+
claimIds?: string[]
|
|
140
|
+
claimRefs?: SlideClaimRef[]
|
|
141
|
+
evidenceBindingIds?: string[]
|
|
135
142
|
content: {
|
|
136
143
|
headline?: string
|
|
137
144
|
body?: string[]
|
|
@@ -145,6 +152,12 @@ export interface SlideSpec {
|
|
|
145
152
|
notes?: string
|
|
146
153
|
}
|
|
147
154
|
|
|
155
|
+
export interface SlideClaimRef {
|
|
156
|
+
claimId: string
|
|
157
|
+
role: SlideClaimRefRole
|
|
158
|
+
note?: string
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
export interface EvidenceRef {
|
|
149
162
|
source: string
|
|
150
163
|
quote?: string
|
|
@@ -349,15 +362,15 @@ export function createDeckSpec(input: Partial<DeckSpec> & { slug: string }): Dec
|
|
|
349
362
|
}
|
|
350
363
|
|
|
351
364
|
export function readDecksState(workspaceRoot: string): DecksState {
|
|
352
|
-
return readWorkspaceState(workspaceRoot, { fileName: DECKS_STATE_FILE, normalize:
|
|
365
|
+
return readWorkspaceState(workspaceRoot, { fileName: DECKS_STATE_FILE, normalize: normalizeDecksStateWithNarrative })
|
|
353
366
|
}
|
|
354
367
|
|
|
355
368
|
export function writeDecksState(workspaceRoot: string, state: DecksState): void {
|
|
356
|
-
writeWorkspaceState(workspaceRoot, state, { fileName: DECKS_STATE_FILE, normalize:
|
|
369
|
+
writeWorkspaceState(workspaceRoot, state, { fileName: DECKS_STATE_FILE, normalize: normalizeDecksStateWithNarrative })
|
|
357
370
|
}
|
|
358
371
|
|
|
359
372
|
export function readOrCreateDecksState(workspaceRoot: string): DecksState {
|
|
360
|
-
return readOrCreateWorkspaceState(workspaceRoot, createEmptyDecksState, { fileName: DECKS_STATE_FILE, normalize:
|
|
373
|
+
return readOrCreateWorkspaceState(workspaceRoot, createEmptyDecksState, { fileName: DECKS_STATE_FILE, normalize: normalizeDecksStateWithNarrative })
|
|
361
374
|
}
|
|
362
375
|
|
|
363
376
|
export function upsertDeck(state: DecksState, input: Partial<DeckSpec> & { slug: string }): DecksState {
|
|
@@ -717,6 +730,7 @@ function normalizeDecksState(input: DecksState): DecksState {
|
|
|
717
730
|
const state: DecksState = {
|
|
718
731
|
version: 1,
|
|
719
732
|
activeDeck: input.activeDeck ? normalizeSlug(input.activeDeck) : undefined,
|
|
733
|
+
narrative: normalizeCanonicalNarrativeState(input.narrative, input.activeDeck || "workspace"),
|
|
720
734
|
workspace: {
|
|
721
735
|
brief: input.workspace?.brief,
|
|
722
736
|
sourceMaterials: input.workspace?.sourceMaterials ?? [],
|
|
@@ -745,6 +759,12 @@ function normalizeDecksState(input: DecksState): DecksState {
|
|
|
745
759
|
return state
|
|
746
760
|
}
|
|
747
761
|
|
|
762
|
+
function normalizeDecksStateWithNarrative(input: DecksState): DecksState {
|
|
763
|
+
const state = normalizeDecksState(input)
|
|
764
|
+
if (!state.narrative && currentDeckKey(state)) state.narrative = normalizeNarrativeState(state)
|
|
765
|
+
return state
|
|
766
|
+
}
|
|
767
|
+
|
|
748
768
|
function currentDeckKey(state: DecksState): string | undefined {
|
|
749
769
|
if (state.activeDeck && state.decks[state.activeDeck]) return state.activeDeck
|
|
750
770
|
const keys = Object.keys(state.decks)
|
|
@@ -1468,6 +1488,9 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
|
|
|
1468
1488
|
title: slide.title ?? "",
|
|
1469
1489
|
layout: slide.layout ?? "",
|
|
1470
1490
|
components: slide.components ?? [],
|
|
1491
|
+
claimIds: normalizeTextList(slide.claimIds),
|
|
1492
|
+
claimRefs: normalizeSlideClaimRefs(slide.claimRefs),
|
|
1493
|
+
evidenceBindingIds: normalizeTextList(slide.evidenceBindingIds),
|
|
1471
1494
|
content: slide.content ?? {},
|
|
1472
1495
|
evidence: slide.evidence ?? [],
|
|
1473
1496
|
status: slide.status ?? "planned",
|
|
@@ -1475,6 +1498,26 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
|
|
|
1475
1498
|
.sort((a, b) => a.index - b.index)
|
|
1476
1499
|
}
|
|
1477
1500
|
|
|
1501
|
+
function normalizeSlideClaimRefs(refs: SlideClaimRef[] | undefined): SlideClaimRef[] {
|
|
1502
|
+
const seen = new Set<string>()
|
|
1503
|
+
const out: SlideClaimRef[] = []
|
|
1504
|
+
for (const ref of refs ?? []) {
|
|
1505
|
+
const claimId = cleanOptionalText(ref.claimId)
|
|
1506
|
+
if (!claimId) continue
|
|
1507
|
+
const role = isSlideClaimRefRole(ref.role) ? ref.role : "supporting"
|
|
1508
|
+
const key = `${claimId}:${role}`
|
|
1509
|
+
if (seen.has(key)) continue
|
|
1510
|
+
seen.add(key)
|
|
1511
|
+
const note = cleanOptionalText(ref.note)
|
|
1512
|
+
out.push({ claimId, role, ...(note ? { note } : {}) })
|
|
1513
|
+
}
|
|
1514
|
+
return out
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function isSlideClaimRefRole(value: string | undefined): value is SlideClaimRefRole {
|
|
1518
|
+
return value === "primary" || value === "supporting" || value === "evidence" || value === "risk" || value === "objection"
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1478
1521
|
function normalizeNarrativeBrief(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
|
|
1479
1522
|
if (!brief) return undefined
|
|
1480
1523
|
const normalized: NarrativeBrief = {
|
package/lib/edit/prompt.ts
CHANGED
|
@@ -71,6 +71,9 @@ Instructions:
|
|
|
71
71
|
- Make the smallest targeted change that satisfies the user's comment.
|
|
72
72
|
- If there are multiple comments, apply them as one coherent edit pass and avoid changes from one comment overwriting another.
|
|
73
73
|
- Each comment may reference one or more selected elements. Treat the elements in a single comment as a group.
|
|
74
|
+
- Preserve the narrative boundary: if the requested edit changes audience framing, belief shift, decision/action, thesis, recommendation, claim wording, evidence scope, caveat, risk, objection, or decision ask, do not patch the HTML directly. Explain that the canonical narrative must be updated first through ${"`revela-decks`"} action ${"`upsertNarrative`"}, then reviewed/approved or explicitly overridden before updating the deck projection.
|
|
75
|
+
- Pure artifact polish such as layout, spacing, typography, alignment, color, image crop, animation, export fidelity, or deck HTML contract fixes may remain an artifact-level edit.
|
|
76
|
+
- If the request mixes content meaning and visual polish, treat it as narrative-impacting unless the user clarifies otherwise.
|
|
74
77
|
- Preserve the existing deck structure, active design language, typography, spacing system, animations, and slide count unless the comment explicitly asks otherwise.
|
|
75
78
|
- Do not rewrite unrelated slides or broad sections of the deck.
|
|
76
79
|
- Locate each target primarily with slideIndex, slideTitle, selected text, nearbyText, and outerHTMLExcerpt. Use selector/domPath as hints; they may be approximate.
|