@cyber-dash-tech/revela 0.17.1 → 0.17.3
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 +47 -565
- package/README.zh-CN.md +49 -533
- package/designs/monet/DESIGN.md +1 -1
- package/designs/starter/DESIGN.md +1 -1
- package/designs/summit/DESIGN.md +1 -1
- package/lib/commands/help.ts +6 -6
- package/lib/commands/init.ts +19 -9
- package/lib/commands/narrative.ts +40 -19
- package/lib/commands/pdf.ts +24 -15
- package/lib/commands/pptx.ts +2 -16
- package/lib/commands/research.ts +2 -0
- package/lib/commands/review.ts +79 -95
- package/lib/deck-html/contract.ts +26 -10
- package/lib/decks-state.ts +75 -86
- package/lib/edit/deck-state.ts +3 -111
- package/lib/edit/open.ts +2 -2
- package/lib/edit/resolve-deck.ts +14 -24
- package/lib/inspect/open.ts +2 -2
- package/lib/narrative-state/deck-plan-artifact.ts +584 -0
- package/lib/narrative-state/render-plan.ts +527 -38
- package/lib/narrative-state/research-gaps.ts +5 -2
- package/lib/narrative-vault/compile.ts +16 -1
- package/lib/narrative-vault/types.ts +4 -2
- package/lib/refine/open.ts +2 -2
- package/package.json +1 -1
- package/plugin.ts +2 -2
- package/skill/NARRATIVE_SKILL.md +23 -20
- package/skill/SKILL.md +95 -36
- package/tools/decks.ts +83 -51
- package/tools/narrative-view.ts +8 -10
package/tools/decks.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
2
|
import {
|
|
3
|
+
createEmptyDecksState,
|
|
3
4
|
createDeckSpec,
|
|
4
5
|
confirmDeckPlan,
|
|
5
6
|
DECKS_STATE_FILE,
|
|
7
|
+
hasDecksState,
|
|
6
8
|
normalizeWorkspaceDeckState,
|
|
7
|
-
|
|
9
|
+
readDecksState,
|
|
8
10
|
reviewDeckState,
|
|
9
11
|
upsertDeck,
|
|
10
12
|
upsertSlides,
|
|
@@ -30,10 +32,11 @@ import {
|
|
|
30
32
|
reviewNarrativeState,
|
|
31
33
|
} from "../lib/narrative-state/readiness"
|
|
32
34
|
import { compileDeckPlanFromNarrative } from "../lib/narrative-state/render-plan"
|
|
35
|
+
import { DECK_PLAN_ARTIFACT_PATH, readDeckPlanArtifact } from "../lib/narrative-state/deck-plan-artifact"
|
|
33
36
|
import { backfillSlideClaimRefsFromCoverage } from "../lib/narrative-state/coverage"
|
|
34
37
|
import { closeResearchGapInState, deriveResearchGapsFromReadiness, deriveResearchTargets, updateResearchGapInState, upsertResearchGapsInState } from "../lib/narrative-state/research-gaps"
|
|
35
38
|
import { evaluateResearchFindingsBinding } from "../lib/narrative-state/research-binding-eval"
|
|
36
|
-
import { stableEvidenceId } from "../lib/narrative-state/hash"
|
|
39
|
+
import { computeNarrativeHash, stableEvidenceId } from "../lib/narrative-state/hash"
|
|
37
40
|
import { normalizeNarrativeState } from "../lib/narrative-state/normalize"
|
|
38
41
|
import { buildNarrativeVaultInventory, compileNarrativeVault, exportNarrativeStateToVault, formatVaultDiagnosticReport, getNarrativeVaultMigrationHint, hasNarrativeVault, initNarrativeVault, narrativeVaultAuthoringContract, narrativeVaultTimestampMs, removeVaultRelation, runNarrativeMarkdownQa, updateVaultCoreNodes, updateVaultResearchGapNode, upsertVaultClaimNode, upsertVaultEvidenceNode, upsertVaultObjectionNode, upsertVaultRelation, upsertVaultRiskNode, VAULT_MIGRATION_PRESERVED_IN_DECKS_JSON } from "../lib/narrative-vault"
|
|
39
42
|
import { compileCacheMirrorNarrativeVault } from "../lib/narrative-vault/compile-mirror"
|
|
@@ -114,10 +117,10 @@ export default tool({
|
|
|
114
117
|
description:
|
|
115
118
|
`Read and update ${DECKS_STATE_FILE}, Revela's workspace deck state file. ` +
|
|
116
119
|
"Use this tool instead of writing or patching the state file directly. " +
|
|
117
|
-
"It stores
|
|
120
|
+
"It stores compatibility/render state, narrative projections, active output paths, cached deck projections, approvals, reviews, artifact coverage, and readiness.",
|
|
118
121
|
args: {
|
|
119
122
|
action: tool.schema
|
|
120
|
-
.enum(["read", "init", "initNarrativeVault", "narrativeInventory", "vaultInventory", "markdownQa", "upsertDeck", "upsertSlides", "upsertNarrative", "compileNarrativeVault", "exportNarrativeVault", "upsertVaultEvidence", "bindResearchFindings", "updateVaultResearchGap", "upsertVaultResearchGap", "upsertVaultClaim", "upsertVaultObjection", "upsertVaultRisk", "upsertVaultRelation", "removeVaultRelation", "updateVaultCoreNarrative", "compileDeckPlan", "confirmDeckPlan", "backfillClaimRefs", "review", "reviewNarrative", "approveNarrative", "deriveResearchGaps", "deriveResearchTargets", "evaluateResearchFindings", "upsertResearchGaps", "updateResearchGap", "closeResearchGap", "applyEvidenceCandidates", "attachResearchFindings", "remember"])
|
|
123
|
+
.enum(["read", "init", "initNarrativeVault", "narrativeInventory", "vaultInventory", "markdownQa", "upsertDeck", "upsertSlides", "upsertNarrative", "compileNarrativeVault", "exportNarrativeVault", "upsertVaultEvidence", "bindResearchFindings", "updateVaultResearchGap", "upsertVaultResearchGap", "upsertVaultClaim", "upsertVaultObjection", "upsertVaultRisk", "upsertVaultRelation", "removeVaultRelation", "updateVaultCoreNarrative", "compileDeckPlan", "readDeckPlan", "confirmDeckPlan", "backfillClaimRefs", "review", "reviewNarrative", "approveNarrative", "deriveResearchGaps", "deriveResearchTargets", "evaluateResearchFindings", "upsertResearchGaps", "updateResearchGap", "closeResearchGap", "applyEvidenceCandidates", "attachResearchFindings", "remember"])
|
|
121
124
|
.describe("Action to perform on DECKS.json."),
|
|
122
125
|
summary: tool.schema.boolean().optional().describe("For read: return a compact summary instead of full state."),
|
|
123
126
|
goal: tool.schema.string().optional().describe("For upsertDeck: deck goal."),
|
|
@@ -348,7 +351,14 @@ export default tool({
|
|
|
348
351
|
async execute(args, context) {
|
|
349
352
|
try {
|
|
350
353
|
const workspaceRoot = context.directory ?? process.cwd()
|
|
351
|
-
|
|
354
|
+
const loadState = () => hasDecksState(workspaceRoot) ? readDecksState(workspaceRoot) : createEmptyDecksState()
|
|
355
|
+
let state = normalizeWorkspaceDeckState(loadState(), workspaceRoot)
|
|
356
|
+
const persistState = (next: DecksState) => {
|
|
357
|
+
state = normalizeWorkspaceDeckState(next, workspaceRoot)
|
|
358
|
+
if (hasDecksState(workspaceRoot)) writeDecksState(workspaceRoot, state)
|
|
359
|
+
return hasDecksState(workspaceRoot)
|
|
360
|
+
}
|
|
361
|
+
const mirrorOptions = (extra: Record<string, unknown> = {}) => hasDecksState(workspaceRoot) ? { state, ...extra } : extra
|
|
352
362
|
const defaultSlug = workspaceDeckSlug(workspaceRoot)
|
|
353
363
|
|
|
354
364
|
if (args.action === "init") {
|
|
@@ -368,8 +378,8 @@ export default tool({
|
|
|
368
378
|
nodeIds: discovered.map((material) => `source:${material.path}`),
|
|
369
379
|
})
|
|
370
380
|
}
|
|
371
|
-
|
|
372
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, ingest, state }, null, 2)
|
|
381
|
+
const persisted = persistState(state)
|
|
382
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, ingest, state }, null, 2)
|
|
373
383
|
}
|
|
374
384
|
|
|
375
385
|
if (args.action === "read") {
|
|
@@ -404,7 +414,7 @@ export default tool({
|
|
|
404
414
|
}
|
|
405
415
|
|
|
406
416
|
if (args.action === "compileNarrativeVault") {
|
|
407
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
417
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
408
418
|
const { result, diagnosticReport } = compiled
|
|
409
419
|
const markdownQa = hasNarrativeVault(workspaceRoot) ? runNarrativeMarkdownQa(workspaceRoot) : undefined
|
|
410
420
|
const narrativeInventory = hasNarrativeVault(workspaceRoot) ? buildNarrativeVaultInventory(workspaceRoot) : undefined
|
|
@@ -415,7 +425,7 @@ export default tool({
|
|
|
415
425
|
if (hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: "revela-narrative/ already exists. Edit Markdown nodes directly or move the existing vault before exporting." })
|
|
416
426
|
const narrative = state.narrative ?? normalizeNarrativeState(state)
|
|
417
427
|
const result = exportNarrativeStateToVault(workspaceRoot, narrative)
|
|
418
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, {
|
|
428
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions({ fallbackApprovals: narrative.approvals }))
|
|
419
429
|
return JSON.stringify({
|
|
420
430
|
ok: compiled.result.ok,
|
|
421
431
|
path: "revela-narrative",
|
|
@@ -435,7 +445,7 @@ export default tool({
|
|
|
435
445
|
|
|
436
446
|
if (args.action === "initNarrativeVault") {
|
|
437
447
|
if (hasNarrativeVault(workspaceRoot)) {
|
|
438
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
448
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
439
449
|
return JSON.stringify({ ok: true, path: "revela-narrative", created: false, files: [], diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract(), nextActions: ["Inspect narrativeInventory before authoring; reuse existing ids and relation targets.", "Maintain Markdown nodes directly when useful, then run compileNarrativeVault. Use structured vault helpers only when they reduce schema risk."], narrative: compiled.result.narrative }, null, 2)
|
|
440
450
|
}
|
|
441
451
|
const result = initNarrativeVault(workspaceRoot, {
|
|
@@ -445,7 +455,7 @@ export default tool({
|
|
|
445
455
|
decision: args.narrative?.decision as any,
|
|
446
456
|
thesis: args.narrative?.thesis as any,
|
|
447
457
|
})
|
|
448
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
458
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
449
459
|
return JSON.stringify({ ok: compiled.result.ok, path: result.path, created: result.created, files: result.files, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract(), nextActions: ["Inspect narrativeInventory before adding claims, evidence, relations, or research gaps; reuse existing ids where possible.", "Maintain Markdown nodes directly when useful. Use structured vault helpers for evidence binding or when they reduce schema risk.", "Treat workspace source material records as candidates until explicit evidence trace is written."], narrative: compiled.result.narrative }, null, 2)
|
|
450
460
|
}
|
|
451
461
|
|
|
@@ -454,7 +464,7 @@ export default tool({
|
|
|
454
464
|
if (!args.evidence) return JSON.stringify({ ok: false, error: "evidence is required for upsertVaultEvidence" })
|
|
455
465
|
const mutation = upsertVaultEvidenceNode(workspaceRoot, args.evidence as any)
|
|
456
466
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation }, null, 2)
|
|
457
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
467
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
458
468
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
459
469
|
}
|
|
460
470
|
|
|
@@ -495,7 +505,7 @@ export default tool({
|
|
|
495
505
|
notes: gap.notes,
|
|
496
506
|
})
|
|
497
507
|
: undefined
|
|
498
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
508
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
499
509
|
return JSON.stringify({
|
|
500
510
|
ok: compiled.result.ok,
|
|
501
511
|
path: mutation.file,
|
|
@@ -521,7 +531,7 @@ export default tool({
|
|
|
521
531
|
notes: args.gapNotes,
|
|
522
532
|
})
|
|
523
533
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation, recovery: researchGapHelperRecovery("updateVaultResearchGap", mutation.missingFields), narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract() }, null, 2)
|
|
524
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
534
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
525
535
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
526
536
|
}
|
|
527
537
|
|
|
@@ -544,7 +554,7 @@ export default tool({
|
|
|
544
554
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation, recovery: researchGapHelperRecovery("upsertVaultResearchGap", mutation.missingFields), narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract() }, null, 2)
|
|
545
555
|
mutations.push(mutation)
|
|
546
556
|
}
|
|
547
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
557
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
548
558
|
return JSON.stringify({ ok: compiled.result.ok, path: mutations[0]?.file, mutation: mutations[0], mutations, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, authoringContract: narrativeVaultAuthoringContract(), narrative: compiled.result.narrative }, null, 2)
|
|
549
559
|
}
|
|
550
560
|
|
|
@@ -560,7 +570,7 @@ export default tool({
|
|
|
560
570
|
rationale: relation.rationale,
|
|
561
571
|
})
|
|
562
572
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation, recovery: relationHelperRecovery("upsertVaultRelation", mutation.missingFields), narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract() }, null, 2)
|
|
563
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
573
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
564
574
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract(), narrative: compiled.result.narrative }, null, 2)
|
|
565
575
|
}
|
|
566
576
|
|
|
@@ -570,7 +580,7 @@ export default tool({
|
|
|
570
580
|
if (!relationId) return JSON.stringify({ ok: false, error: "relationId or relation.id is required for removeVaultRelation", recovery: relationHelperRecovery("removeVaultRelation"), narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract() }, null, 2)
|
|
571
581
|
const mutation = removeVaultRelation(workspaceRoot, relationId)
|
|
572
582
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation, recovery: relationHelperRecovery("removeVaultRelation", mutation.missingFields), narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract() }, null, 2)
|
|
573
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
583
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
574
584
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrativeInventory: buildNarrativeVaultInventory(workspaceRoot), authoringContract: narrativeVaultAuthoringContract(), narrative: compiled.result.narrative }, null, 2)
|
|
575
585
|
}
|
|
576
586
|
|
|
@@ -583,7 +593,7 @@ export default tool({
|
|
|
583
593
|
: undefined
|
|
584
594
|
const mutation = upsertVaultClaimNode(workspaceRoot, claim)
|
|
585
595
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation }, null, 2)
|
|
586
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
596
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
587
597
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, relationHint, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
588
598
|
}
|
|
589
599
|
|
|
@@ -593,7 +603,7 @@ export default tool({
|
|
|
593
603
|
if (!objection?.id) return JSON.stringify({ ok: false, error: "narrative.objections[0].id is required for upsertVaultObjection" })
|
|
594
604
|
const mutation = upsertVaultObjectionNode(workspaceRoot, objection)
|
|
595
605
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation }, null, 2)
|
|
596
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
606
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
597
607
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
598
608
|
}
|
|
599
609
|
|
|
@@ -603,7 +613,7 @@ export default tool({
|
|
|
603
613
|
if (!risk?.id) return JSON.stringify({ ok: false, error: "narrative.risks[0].id is required for upsertVaultRisk" })
|
|
604
614
|
const mutation = upsertVaultRiskNode(workspaceRoot, risk)
|
|
605
615
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation }, null, 2)
|
|
606
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
616
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
607
617
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
608
618
|
}
|
|
609
619
|
|
|
@@ -611,7 +621,7 @@ export default tool({
|
|
|
611
621
|
if (!hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: "updateVaultCoreNarrative requires revela-narrative/ to exist. Use initNarrativeVault first." })
|
|
612
622
|
const mutation = updateVaultCoreNodes(workspaceRoot, args.narrative as any ?? {})
|
|
613
623
|
if (!mutation.ok) return JSON.stringify({ ok: false, mutation }, null, 2)
|
|
614
|
-
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot,
|
|
624
|
+
const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
|
|
615
625
|
return JSON.stringify({ ok: compiled.result.ok, path: mutation.file, mutation, diagnostics: compiled.result.diagnostics, diagnosticReport: compiled.diagnosticReport, narrative: compiled.result.narrative }, null, 2)
|
|
616
626
|
}
|
|
617
627
|
|
|
@@ -637,16 +647,16 @@ export default tool({
|
|
|
637
647
|
researchPlan: (args.researchPlan as ResearchAxis[] | undefined) ?? existing?.researchPlan,
|
|
638
648
|
}
|
|
639
649
|
const next = upsertDeck(state, deckInput)
|
|
640
|
-
|
|
641
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, deck:
|
|
650
|
+
const persisted = persistState(next)
|
|
651
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, deck: state.activeDeck ? state.decks[state.activeDeck] : undefined }, null, 2)
|
|
642
652
|
}
|
|
643
653
|
|
|
644
654
|
if (args.action === "upsertSlides") {
|
|
645
655
|
const deckKey = state.activeDeck || defaultSlug
|
|
646
656
|
if (!args.slides) return JSON.stringify({ ok: false, error: "slides are required for upsertSlides" })
|
|
647
657
|
const next = upsertSlides(state, deckKey, args.slides as SlideSpec[])
|
|
648
|
-
|
|
649
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, deck:
|
|
658
|
+
const persisted = persistState(next)
|
|
659
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, deck: state.activeDeck ? state.decks[state.activeDeck] : undefined }, null, 2)
|
|
650
660
|
}
|
|
651
661
|
|
|
652
662
|
if (args.action === "upsertNarrative") {
|
|
@@ -679,14 +689,15 @@ export default tool({
|
|
|
679
689
|
summary: `Reviewed deck readiness: ${reviewed.result.ready ? "ready" : "blocked"}.`,
|
|
680
690
|
nodeIds: [`artifact:${reviewed.state.decks[reviewed.result.slug]?.outputPath ?? reviewed.result.slug}`, ...(snapshot ? [snapshot.id] : [])],
|
|
681
691
|
})
|
|
682
|
-
|
|
683
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: reviewed.result }, null, 2)
|
|
692
|
+
const persisted = persistState(reviewed.state)
|
|
693
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: reviewed.result }, null, 2)
|
|
684
694
|
}
|
|
685
695
|
|
|
686
696
|
if (args.action === "compileDeckPlan") {
|
|
687
697
|
const qaGate = strictVaultMarkdownQaGate(workspaceRoot, "render", "compileDeckPlan")
|
|
688
698
|
if (qaGate) return JSON.stringify(qaGate, null, 2)
|
|
689
699
|
const compiled = compileDeckPlanFromNarrative(state)
|
|
700
|
+
const planArtifact = compiled.result.compiled ? { path: DECK_PLAN_ARTIFACT_PATH } : undefined
|
|
690
701
|
if (compiled.result.compiled) {
|
|
691
702
|
recordWorkspaceAction(compiled.state, {
|
|
692
703
|
type: "deck.plan_compiled",
|
|
@@ -694,23 +705,44 @@ export default tool({
|
|
|
694
705
|
inputs: { narrativeId: compiled.state.narrative?.id, activeDeck: compiled.state.activeDeck },
|
|
695
706
|
outputs: {
|
|
696
707
|
narrativeHash: compiled.result.narrativeHash,
|
|
708
|
+
planArtifactPath: planArtifact?.path,
|
|
709
|
+
planningPacket: Boolean(compiled.result.planningPacket),
|
|
710
|
+
deckPlanRequirements: Boolean(compiled.result.deckPlanRequirements),
|
|
697
711
|
slideCount: compiled.result.slideCount,
|
|
698
712
|
outputPath: compiled.state.activeDeck ? compiled.state.decks[compiled.state.activeDeck]?.outputPath : undefined,
|
|
699
713
|
},
|
|
700
714
|
status: "success",
|
|
701
|
-
summary:
|
|
715
|
+
summary: "Prepared deck planning packet and deck-plan authoring requirements from canonical narrative.",
|
|
702
716
|
nodeIds: [compiled.state.narrative?.id, compiled.state.activeDeck ? `artifact:${compiled.state.decks[compiled.state.activeDeck]?.outputPath ?? compiled.state.activeDeck}` : undefined].filter((item): item is string => Boolean(item)),
|
|
703
717
|
})
|
|
704
718
|
}
|
|
705
|
-
|
|
706
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: compiled.result, deck:
|
|
719
|
+
const persisted = persistState(compiled.state)
|
|
720
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, planArtifact, result: compiled.result, deck: state.activeDeck ? state.decks[state.activeDeck] : undefined, narrative: state.narrative }, null, 2)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (args.action === "readDeckPlan") {
|
|
724
|
+
const narrative = normalizeNarrativeState(state)
|
|
725
|
+
const knownNodeIds = new Set([
|
|
726
|
+
...narrative.claims.map((claim) => claim.id),
|
|
727
|
+
...narrative.evidenceBindings.map((binding) => binding.id),
|
|
728
|
+
...narrative.risks.map((risk) => risk.id),
|
|
729
|
+
...narrative.objections.map((objection) => objection.id),
|
|
730
|
+
...(narrative.researchGaps ?? []).map((gap) => gap.id),
|
|
731
|
+
])
|
|
732
|
+
const read = readDeckPlanArtifact(workspaceRoot, { narrativeHash: computeNarrativeHash(narrative), knownNodeIds })
|
|
733
|
+
return JSON.stringify({ ok: read.ok, planArtifact: read }, null, 2)
|
|
707
734
|
}
|
|
708
735
|
|
|
709
736
|
if (args.action === "confirmDeckPlan") {
|
|
710
|
-
|
|
737
|
+
const deck = state.activeDeck ? state.decks[state.activeDeck] : undefined
|
|
738
|
+
if (!deck) return JSON.stringify({ ok: false, result: { confirmed: false, skipped: true, reason: "Cannot confirm because no current deck exists." } }, null, 2)
|
|
739
|
+
const narrative = normalizeNarrativeState(state)
|
|
740
|
+
const narrativeHash = computeNarrativeHash(narrative)
|
|
741
|
+
const read = readDeckPlanArtifact(workspaceRoot, { narrativeHash })
|
|
711
742
|
const confirmed = confirmDeckPlan(state, {
|
|
712
743
|
approvedBy: "user",
|
|
713
|
-
note: args.approvalNote,
|
|
744
|
+
note: args.approvalNote ?? "Deprecated confirmDeckPlan compatibility confirmation; deck-plan/ diagnostics are advisory.",
|
|
745
|
+
planHash: read.planHash,
|
|
714
746
|
})
|
|
715
747
|
if (confirmed.result.confirmed) {
|
|
716
748
|
recordWorkspaceAction(confirmed.state, {
|
|
@@ -723,18 +755,18 @@ export default tool({
|
|
|
723
755
|
planHash: confirmed.result.planHash,
|
|
724
756
|
},
|
|
725
757
|
status: "success",
|
|
726
|
-
summary: args.approvalNote?.trim() || "
|
|
758
|
+
summary: args.approvalNote?.trim() || "Recorded deck-plan compatibility confirmation; deck-plan/ remains user-directed projection state.",
|
|
727
759
|
nodeIds: [confirmed.state.narrative?.id, confirmed.result.slug ? `deck:${confirmed.result.slug}` : undefined].filter((item): item is string => Boolean(item)),
|
|
728
760
|
})
|
|
729
761
|
}
|
|
730
|
-
|
|
731
|
-
return JSON.stringify({ ok: confirmed.result.confirmed, path: DECKS_STATE_FILE, result: confirmed.result, deck:
|
|
762
|
+
const persisted = persistState(confirmed.state)
|
|
763
|
+
return JSON.stringify({ ok: confirmed.result.confirmed, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: confirmed.result, planArtifact: read, deck: state.activeDeck ? state.decks[state.activeDeck] : undefined }, null, 2)
|
|
732
764
|
}
|
|
733
765
|
|
|
734
766
|
if (args.action === "backfillClaimRefs") {
|
|
735
767
|
const backfilled = backfillSlideClaimRefsFromCoverage(state)
|
|
736
|
-
|
|
737
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: backfilled.result, deck:
|
|
768
|
+
const persisted = persistState(backfilled.state)
|
|
769
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: backfilled.result, deck: state.activeDeck ? state.decks[state.activeDeck] : undefined, narrative: state.narrative }, null, 2)
|
|
738
770
|
}
|
|
739
771
|
|
|
740
772
|
if (args.action === "reviewNarrative") {
|
|
@@ -742,8 +774,8 @@ export default tool({
|
|
|
742
774
|
if (qaGate) return JSON.stringify(qaGate, null, 2)
|
|
743
775
|
const reviewed = reviewNarrativeState(state)
|
|
744
776
|
recordNarrativeReviewAction(reviewed.state, reviewed.result)
|
|
745
|
-
|
|
746
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: reviewed.result, narrative:
|
|
777
|
+
const persisted = persistState(reviewed.state)
|
|
778
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: reviewed.result, narrative: state.narrative }, null, 2)
|
|
747
779
|
}
|
|
748
780
|
|
|
749
781
|
if (args.action === "approveNarrative") {
|
|
@@ -755,15 +787,15 @@ export default tool({
|
|
|
755
787
|
note: args.approvalNote,
|
|
756
788
|
})
|
|
757
789
|
recordNarrativeApprovalAction(approved.state, approved.result)
|
|
758
|
-
|
|
759
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: approved.result, narrative:
|
|
790
|
+
const persisted = persistState(approved.state)
|
|
791
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: approved.result, narrative: state.narrative }, null, 2)
|
|
760
792
|
}
|
|
761
793
|
|
|
762
794
|
if (args.action === "deriveResearchGaps") {
|
|
763
795
|
if (hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: forbiddenVaultCompatibilityAction("deriveResearchGaps", "deriveResearchTargets for read-only target selection, then upsertVaultResearchGap for canonical gap nodes"), authoringContract: narrativeVaultAuthoringContract() })
|
|
764
796
|
const derived = deriveResearchGapsFromReadiness(state)
|
|
765
|
-
|
|
766
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: derived.result, narrative:
|
|
797
|
+
const persisted = persistState(derived.state)
|
|
798
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: derived.result, narrative: state.narrative }, null, 2)
|
|
767
799
|
}
|
|
768
800
|
|
|
769
801
|
if (args.action === "deriveResearchTargets") {
|
|
@@ -785,8 +817,8 @@ export default tool({
|
|
|
785
817
|
if (hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: forbiddenVaultCompatibilityAction("upsertResearchGaps", "upsertVaultResearchGap for new or updated research gap nodes"), authoringContract: narrativeVaultAuthoringContract() })
|
|
786
818
|
if (!args.researchGaps?.length) return JSON.stringify({ ok: false, error: "researchGaps are required for upsertResearchGaps" })
|
|
787
819
|
const upserted = upsertResearchGapsInState(state, args.researchGaps as any[])
|
|
788
|
-
|
|
789
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: upserted.result, narrative:
|
|
820
|
+
const persisted = persistState(upserted.state)
|
|
821
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: upserted.result, narrative: state.narrative }, null, 2)
|
|
790
822
|
}
|
|
791
823
|
|
|
792
824
|
if (args.action === "updateResearchGap") {
|
|
@@ -799,16 +831,16 @@ export default tool({
|
|
|
799
831
|
evidenceBindingIds: args.evidenceBindingIds,
|
|
800
832
|
notes: args.gapNotes,
|
|
801
833
|
})
|
|
802
|
-
|
|
803
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: updated.result, narrative:
|
|
834
|
+
const persisted = persistState(updated.state)
|
|
835
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: updated.result, narrative: state.narrative }, null, 2)
|
|
804
836
|
}
|
|
805
837
|
|
|
806
838
|
if (args.action === "closeResearchGap") {
|
|
807
839
|
if (hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: forbiddenVaultCompatibilityAction("closeResearchGap", "updateVaultResearchGap with gapStatus=closed"), authoringContract: narrativeVaultAuthoringContract() })
|
|
808
840
|
if (!args.gapId?.trim()) return JSON.stringify({ ok: false, error: "gapId is required for closeResearchGap" })
|
|
809
841
|
const closed = closeResearchGapInState(state, args.gapId, args.gapNotes)
|
|
810
|
-
|
|
811
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: closed.result, narrative:
|
|
842
|
+
const persisted = persistState(closed.state)
|
|
843
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, result: closed.result, narrative: state.narrative }, null, 2)
|
|
812
844
|
}
|
|
813
845
|
|
|
814
846
|
if (args.action === "applyEvidenceCandidates") {
|
|
@@ -835,8 +867,8 @@ export default tool({
|
|
|
835
867
|
const preferenceType = args.preferenceType ?? "user"
|
|
836
868
|
const list = state.workspace.preferences[preferenceType]
|
|
837
869
|
if (!list.some((entry) => entry.trim().toLowerCase() === memory.toLowerCase())) list.push(memory)
|
|
838
|
-
|
|
839
|
-
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, preferenceType, memory }, null, 2)
|
|
870
|
+
const persisted = persistState(state)
|
|
871
|
+
return JSON.stringify({ ok: true, path: persisted ? DECKS_STATE_FILE : undefined, persisted, preferenceType, memory }, null, 2)
|
|
840
872
|
}
|
|
841
873
|
|
|
842
874
|
return JSON.stringify({ ok: false, error: `Unsupported action: ${args.action}` })
|
package/tools/narrative-view.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { hasDecksState, readDecksState } from "../lib/decks-state"
|
|
3
|
-
import { buildNarrativeMap } from "../lib/narrative-state/map"
|
|
4
2
|
import { validateNarrativeDisplayModel, type NarrativeDisplayModel, type NarrativeViewLanguage } from "../lib/narrative-state/display"
|
|
5
|
-
import { writeNarrativeMapHtml } from "../lib/commands/narrative"
|
|
3
|
+
import { loadStoryMap, writeNarrativeMapHtml } from "../lib/commands/narrative"
|
|
6
4
|
import { openUrl } from "../lib/edit/open"
|
|
7
5
|
|
|
8
6
|
export default tool({
|
|
9
7
|
description:
|
|
10
8
|
"Render Revela's read-only narrative claim-flow UI from the current deterministic narrative map plus an optional localized display model. " +
|
|
11
|
-
"This tool validates display IDs against
|
|
9
|
+
"This tool validates display IDs against the file-native narrative vault, opens a local HTML view, and never mutates workspace state.",
|
|
12
10
|
args: {
|
|
13
11
|
language: tool.schema.string().describe("UI language request from /revela story or /revela narrative. May be any language tag or language name, such as en, zh-CN, fr, de, Korean, Arabic, or Portuguese-BR."),
|
|
14
12
|
narrativeHash: tool.schema.string().optional().describe("Narrative hash from the prompt projection. Used to detect stale display prompts."),
|
|
@@ -79,11 +77,10 @@ export default tool({
|
|
|
79
77
|
async execute(args, context) {
|
|
80
78
|
const workspaceRoot = context.directory ?? process.cwd()
|
|
81
79
|
try {
|
|
82
|
-
if (!hasDecksState(workspaceRoot)) {
|
|
83
|
-
return JSON.stringify({ ok: false, error: "No DECKS.json found. Run /revela init first." })
|
|
84
|
-
}
|
|
85
80
|
const language = args.language as NarrativeViewLanguage
|
|
86
|
-
const
|
|
81
|
+
const loaded = loadStoryMap(workspaceRoot)
|
|
82
|
+
if (!loaded.ok) return JSON.stringify({ ok: false, error: loaded.error, diagnostics: loaded.diagnosticsReport, diagnosticsMarkdown: loaded.diagnosticsMarkdown })
|
|
83
|
+
const map = loaded.map
|
|
87
84
|
const stalePrompt = Boolean(args.narrativeHash && args.narrativeHash !== map.snapshot.narrativeHash)
|
|
88
85
|
const display = validateNarrativeDisplayModel(map, args.displayModel as NarrativeDisplayModel | undefined, language)
|
|
89
86
|
const htmlPath = writeNarrativeMapHtml(map, display)
|
|
@@ -92,8 +89,9 @@ export default tool({
|
|
|
92
89
|
return JSON.stringify({ ok: true, url, path: htmlPath, narrativeHash: map.snapshot.narrativeHash, stalePrompt, fallback: false }, null, 2)
|
|
93
90
|
} catch (e: any) {
|
|
94
91
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
92
|
+
const loaded = loadStoryMap(workspaceRoot)
|
|
93
|
+
if (!loaded.ok) return JSON.stringify({ ok: false, fallback: false, error: e.message || String(e), fallbackError: loaded.error, diagnostics: loaded.diagnosticsReport, diagnosticsMarkdown: loaded.diagnosticsMarkdown })
|
|
94
|
+
const map = loaded.map
|
|
97
95
|
const htmlPath = writeNarrativeMapHtml(map)
|
|
98
96
|
const url = `file://${htmlPath}`
|
|
99
97
|
openUrl(url)
|