@cyber-dash-tech/revela 0.12.0 → 0.14.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 +16 -16
- package/README.zh-CN.md +16 -16
- package/lib/commands/brief.ts +63 -0
- package/lib/commands/edit.ts +7 -5
- package/lib/commands/help.ts +5 -3
- package/lib/commands/inspect.ts +7 -5
- package/lib/commands/narrative.ts +160 -0
- package/lib/decks-state.ts +33 -0
- package/lib/edit/prompt.ts +3 -0
- package/lib/inspect/prompt.ts +15 -2
- package/lib/inspect/requests.ts +21 -2
- package/lib/inspection-context/compile.ts +230 -10
- package/lib/inspection-context/match.ts +71 -1
- package/lib/inspection-context/project.ts +131 -8
- package/lib/inspection-context/result.ts +183 -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 +9 -0
- package/lib/narrative-state/map-html.ts +348 -0
- package/lib/narrative-state/map.ts +282 -0
- package/lib/narrative-state/normalize.ts +54 -0
- package/lib/narrative-state/queries.ts +434 -0
- package/lib/narrative-state/readiness.ts +71 -1
- package/lib/narrative-state/render-plan.ts +44 -1
- package/lib/narrative-state/research-gaps.ts +191 -0
- package/lib/narrative-state/types.ts +33 -0
- package/lib/refine/server.ts +91 -13
- package/lib/workspace-state/evidence-status.ts +21 -1
- package/lib/workspace-state/graph.ts +56 -2
- package/lib/workspace-state/types.ts +10 -1
- package/package.json +1 -1
- package/plugin.ts +33 -2
- package/tools/decks.ts +86 -1
- package/tools/edit.ts +10 -8
- package/tools/inspection-result.ts +37 -0
- package/tools/narrative-view.ts +84 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "crypto"
|
|
2
2
|
import type { DeckSpec, DecksState, EvidenceRef, NarrativeBrief, ResearchAxis, SlideSpec, SourceMaterial } from "../decks-state"
|
|
3
|
-
import type { NarrativeEvidenceBinding, NarrativeStateV1 } from "../narrative-state/types"
|
|
3
|
+
import type { NarrativeClaimRelation, NarrativeEvidenceBinding, NarrativeResearchGap, NarrativeStateV1 } from "../narrative-state/types"
|
|
4
4
|
import { renderTargetId } from "./render-targets"
|
|
5
5
|
import type { GraphEdge, GraphEdgeType, GraphNode, GraphNodeType, RenderTarget, WorkspaceGraph } from "./types"
|
|
6
6
|
|
|
@@ -178,6 +178,8 @@ function addCanonicalNarrative(builder: GraphBuilder, narrative: NarrativeStateV
|
|
|
178
178
|
}))
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
for (const relation of narrative.claimRelations ?? []) addClaimRelation(builder, relation)
|
|
182
|
+
|
|
181
183
|
for (const objection of narrative.objections) {
|
|
182
184
|
addNode(builder, {
|
|
183
185
|
id: objection.id,
|
|
@@ -200,6 +202,55 @@ function addCanonicalNarrative(builder: GraphBuilder, narrative: NarrativeStateV
|
|
|
200
202
|
addEdge(builder, "constrained_by", risk.claimId || narrative.id, risk.id)
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
for (const gap of narrative.researchGaps ?? []) addResearchGap(builder, narrative, gap)
|
|
206
|
+
|
|
207
|
+
return narrative.id
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function addClaimRelation(builder: GraphBuilder, relation: NarrativeClaimRelation): void {
|
|
211
|
+
addEdge(builder, graphEdgeTypeForClaimRelation(relation.relation), relation.fromClaimId, relation.toClaimId, compactData({
|
|
212
|
+
relationId: relation.id,
|
|
213
|
+
relation: relation.relation,
|
|
214
|
+
rationale: relation.rationale,
|
|
215
|
+
source: "canonicalNarrative",
|
|
216
|
+
}))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function graphEdgeTypeForClaimRelation(relation: NarrativeClaimRelation["relation"]): GraphEdgeType {
|
|
220
|
+
if (relation === "constrains") return "constrained_by"
|
|
221
|
+
if (relation === "supports") return "supports"
|
|
222
|
+
return relation
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function addResearchGap(builder: GraphBuilder, narrative: NarrativeStateV1, gap: NarrativeResearchGap): void {
|
|
226
|
+
addNode(builder, {
|
|
227
|
+
id: gap.id,
|
|
228
|
+
type: "researchGap",
|
|
229
|
+
label: gap.question,
|
|
230
|
+
data: compactData({
|
|
231
|
+
question: gap.question,
|
|
232
|
+
status: gap.status,
|
|
233
|
+
priority: gap.priority,
|
|
234
|
+
targetType: gap.targetType,
|
|
235
|
+
targetId: gap.targetId,
|
|
236
|
+
findingsFile: gap.findingsFile,
|
|
237
|
+
evidenceBindingIds: gap.evidenceBindingIds,
|
|
238
|
+
createdFromIssueType: gap.createdFromIssueType,
|
|
239
|
+
notes: gap.notes,
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
addEdge(builder, "contains", narrative.id, gap.id)
|
|
243
|
+
addEdge(builder, "derived_from", gap.id, gapTargetNodeId(narrative, gap))
|
|
244
|
+
if (gap.findingsFile) addEdge(builder, "derived_from", gap.id, findingNodeId(gap.findingsFile), { status: gap.status })
|
|
245
|
+
for (const evidenceId of gap.evidenceBindingIds ?? []) {
|
|
246
|
+
const binding = narrative.evidenceBindings.find((item) => item.id === evidenceId)
|
|
247
|
+
if (binding) addEdge(builder, "derived_from", gap.id, binding.claimId, { evidenceBindingId: evidenceId })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function gapTargetNodeId(narrative: NarrativeStateV1, gap: NarrativeResearchGap): string {
|
|
252
|
+
if (gap.targetId) return gap.targetId
|
|
253
|
+
if (gap.targetType === "narrative" || gap.targetType === "decision") return narrative.id
|
|
203
254
|
return narrative.id
|
|
204
255
|
}
|
|
205
256
|
|
|
@@ -352,9 +403,11 @@ function renderTargetsForDeck(state: DecksState, deck: DeckSpec): RenderTarget[]
|
|
|
352
403
|
const deckOutputPath = normalizePath(deck.outputPath)
|
|
353
404
|
const htmlTargetId = renderTargetId("html_deck", deckOutputPath)
|
|
354
405
|
const htmlArtifactId = artifactNodeId(deckOutputPath)
|
|
406
|
+
const narrativeId = state.narrative?.id
|
|
355
407
|
const targets = (state.renderTargets ?? []).filter((target) => {
|
|
356
408
|
if (target.id === htmlTargetId) return true
|
|
357
409
|
if (target.type === "html_deck") return normalizePath(target.outputPath ?? "") === deckOutputPath
|
|
410
|
+
if (narrativeId && target.sourceNodeIds.includes(narrativeId)) return true
|
|
358
411
|
const data = target.data ?? {}
|
|
359
412
|
return data.sourceTargetId === htmlTargetId ||
|
|
360
413
|
data.sourceOutputPath === deckOutputPath ||
|
|
@@ -422,7 +475,8 @@ function hasCanonicalNarrative(narrative: NarrativeStateV1 | undefined): narrati
|
|
|
422
475
|
narrative?.claims.length ||
|
|
423
476
|
narrative?.evidenceBindings.length ||
|
|
424
477
|
narrative?.objections.length ||
|
|
425
|
-
narrative?.risks.length
|
|
478
|
+
narrative?.risks.length ||
|
|
479
|
+
narrative?.researchGaps?.length,
|
|
426
480
|
)
|
|
427
481
|
}
|
|
428
482
|
|
|
@@ -50,6 +50,7 @@ export type GraphNodeType =
|
|
|
50
50
|
| "risk"
|
|
51
51
|
| "slide"
|
|
52
52
|
| "artifact"
|
|
53
|
+
| "researchGap"
|
|
53
54
|
|
|
54
55
|
export interface GraphEdge {
|
|
55
56
|
id: string
|
|
@@ -64,6 +65,10 @@ export type GraphEdgeType =
|
|
|
64
65
|
| "extracted_as"
|
|
65
66
|
| "produced"
|
|
66
67
|
| "supports"
|
|
68
|
+
| "leads_to"
|
|
69
|
+
| "depends_on"
|
|
70
|
+
| "contrasts_with"
|
|
71
|
+
| "answers"
|
|
67
72
|
| "appears_in"
|
|
68
73
|
| "challenges"
|
|
69
74
|
| "constrained_by"
|
|
@@ -88,8 +93,12 @@ export type WorkspaceActionType =
|
|
|
88
93
|
| "source.extracted"
|
|
89
94
|
| "research.findings_saved"
|
|
90
95
|
| "research.findings_attached"
|
|
96
|
+
| "research.gap_created"
|
|
97
|
+
| "research.gap_updated"
|
|
98
|
+
| "research.gap_closed"
|
|
91
99
|
| "narrative.upserted"
|
|
92
100
|
| "deck.plan_compiled"
|
|
101
|
+
| "artifact.coverage_backfilled"
|
|
93
102
|
| "evidence.candidate_generated"
|
|
94
103
|
| "evidence.binding_applied"
|
|
95
104
|
| "narrative.approved"
|
|
@@ -98,7 +107,7 @@ export type WorkspaceActionType =
|
|
|
98
107
|
|
|
99
108
|
export interface RenderTarget {
|
|
100
109
|
id: string
|
|
101
|
-
type: "html_deck" | "pdf" | "pptx" | "brief" | "appendix" | "qa_view" | "interactive_page"
|
|
110
|
+
type: "html_deck" | "pdf" | "pptx" | "brief" | "executive_brief" | "appendix" | "qa_view" | "interactive_page"
|
|
102
111
|
outputPath?: string
|
|
103
112
|
sourceNodeIds: string[]
|
|
104
113
|
artifactVersion?: string
|
package/package.json
CHANGED
package/plugin.ts
CHANGED
|
@@ -59,6 +59,8 @@ import {
|
|
|
59
59
|
buildDesignsEditPrompt,
|
|
60
60
|
} from "./lib/commands/designs-new"
|
|
61
61
|
import { buildInitPrompt } from "./lib/commands/init"
|
|
62
|
+
import { handleBrief, parseBriefArgs } from "./lib/commands/brief"
|
|
63
|
+
import { buildNarrativeViewPrompt, handleNarrative, parseNarrativeArgs } from "./lib/commands/narrative"
|
|
62
64
|
import { parseRememberArgs, buildRememberPrompt } from "./lib/commands/remember"
|
|
63
65
|
import { buildDeckPrompt, buildDeckReviewPrompt, buildReviewPrompt } from "./lib/commands/review"
|
|
64
66
|
import {
|
|
@@ -85,6 +87,7 @@ import researchImagesListTool from "./tools/research-images-list"
|
|
|
85
87
|
import researchSaveTool from "./tools/research-save"
|
|
86
88
|
import inspectionContextTool from "./tools/inspection-context"
|
|
87
89
|
import inspectionResultTool from "./tools/inspection-result"
|
|
90
|
+
import narrativeViewTool from "./tools/narrative-view"
|
|
88
91
|
import workspaceScanTool from "./tools/workspace-scan"
|
|
89
92
|
import extractDocumentMaterialsTool from "./tools/extract-document-materials"
|
|
90
93
|
import qaTool from "./tools/qa"
|
|
@@ -361,6 +364,33 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
361
364
|
} as any)
|
|
362
365
|
return
|
|
363
366
|
}
|
|
367
|
+
if (sub === "narrative") {
|
|
368
|
+
const parsed = parseNarrativeArgs(param)
|
|
369
|
+
if (!parsed.ok) {
|
|
370
|
+
await send(parsed.error)
|
|
371
|
+
throw new Error("__REVELA_NARRATIVE_USAGE_HANDLED__")
|
|
372
|
+
}
|
|
373
|
+
if (parsed.args.raw) {
|
|
374
|
+
await handleNarrative({ workspaceRoot, openBrowser: true, language: parsed.args.language }, send)
|
|
375
|
+
throw new Error("__REVELA_NARRATIVE_HANDLED__")
|
|
376
|
+
}
|
|
377
|
+
buildPrompt({ mode: "narrative" })
|
|
378
|
+
output.parts.length = 0
|
|
379
|
+
output.parts.push({
|
|
380
|
+
type: "text",
|
|
381
|
+
text: buildNarrativeViewPrompt({ workspaceRoot, language: parsed.args.language }),
|
|
382
|
+
} as any)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
if (sub === "brief") {
|
|
386
|
+
const parsed = parseBriefArgs(param)
|
|
387
|
+
if (!parsed.ok) {
|
|
388
|
+
await send(parsed.error)
|
|
389
|
+
throw new Error("__REVELA_BRIEF_USAGE_HANDLED__")
|
|
390
|
+
}
|
|
391
|
+
await handleBrief({ workspaceRoot, outputPath: parsed.args.outputPath }, send)
|
|
392
|
+
throw new Error("__REVELA_BRIEF_HANDLED__")
|
|
393
|
+
}
|
|
364
394
|
if (sub === "deck") {
|
|
365
395
|
if (param && param !== "--review") {
|
|
366
396
|
await send("Usage: `/revela deck` starts approved-narrative deck handoff; `/revela deck --review` reviews deck/artifact readiness.")
|
|
@@ -393,7 +423,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
393
423
|
}
|
|
394
424
|
if (sub === "edit") {
|
|
395
425
|
if (param) {
|
|
396
|
-
await send("`/revela edit`
|
|
426
|
+
await send("`/revela edit` is deprecated and does not accept a target. Use `/revela refine` for the unified refinement workspace.")
|
|
397
427
|
throw new Error("__REVELA_EDIT_USAGE_HANDLED__")
|
|
398
428
|
}
|
|
399
429
|
await handleEdit({ client, sessionID, workspaceRoot }, send)
|
|
@@ -401,7 +431,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
401
431
|
}
|
|
402
432
|
if (sub === "inspect") {
|
|
403
433
|
if (param) {
|
|
404
|
-
await send("`/revela inspect` does not accept a target.
|
|
434
|
+
await send("`/revela inspect` is deprecated and does not accept a target. Use `/revela refine` for the unified reading and refinement workspace.")
|
|
405
435
|
throw new Error("__REVELA_INSPECT_USAGE_HANDLED__")
|
|
406
436
|
}
|
|
407
437
|
await handleInspect({ client, sessionID, workspaceRoot }, send)
|
|
@@ -507,6 +537,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
507
537
|
"revela-research-save": researchSaveTool,
|
|
508
538
|
"revela-inspection-context": inspectionContextTool,
|
|
509
539
|
"revela-inspection-result": inspectionResultTool,
|
|
540
|
+
"revela-narrative-view": narrativeViewTool,
|
|
510
541
|
"revela-workspace-scan": workspaceScanTool,
|
|
511
542
|
"revela-extract-document-materials": extractDocumentMaterialsTool,
|
|
512
543
|
"revela-qa": qaTool,
|
package/tools/decks.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
reviewNarrativeState,
|
|
29
29
|
} from "../lib/narrative-state/readiness"
|
|
30
30
|
import { compileDeckPlanFromNarrative } from "../lib/narrative-state/render-plan"
|
|
31
|
+
import { backfillSlideClaimRefsFromCoverage } from "../lib/narrative-state/coverage"
|
|
32
|
+
import { closeResearchGapInState, deriveResearchGapsFromReadiness, updateResearchGapInState, upsertResearchGapsInState } from "../lib/narrative-state/research-gaps"
|
|
31
33
|
import { normalizeCanonicalNarrativeState, normalizeNarrativeState } from "../lib/narrative-state/normalize"
|
|
32
34
|
import { narrativeToBrief } from "../lib/narrative-state/project-compat"
|
|
33
35
|
import type { NarrativeStateV1 } from "../lib/narrative-state/types"
|
|
@@ -48,9 +50,11 @@ function mergeNarrativeInput(current: NarrativeStateV1, input: Partial<Narrative
|
|
|
48
50
|
},
|
|
49
51
|
thesis: input.thesis ? { ...current.thesis, ...input.thesis } as NarrativeStateV1["thesis"] : current.thesis,
|
|
50
52
|
claims: input.claims ?? current.claims,
|
|
53
|
+
claimRelations: input.claimRelations ?? current.claimRelations,
|
|
51
54
|
evidenceBindings: input.evidenceBindings ?? current.evidenceBindings,
|
|
52
55
|
objections: input.objections ?? current.objections,
|
|
53
56
|
risks: input.risks ?? current.risks,
|
|
57
|
+
researchGaps: input.researchGaps ?? current.researchGaps,
|
|
54
58
|
approvals: current.approvals,
|
|
55
59
|
updatedAt: new Date().toISOString(),
|
|
56
60
|
}
|
|
@@ -63,7 +67,7 @@ export default tool({
|
|
|
63
67
|
"It stores workspace narrative state, active deck specs, per-slide content/layout/components, and computes narrative or deck readiness.",
|
|
64
68
|
args: {
|
|
65
69
|
action: tool.schema
|
|
66
|
-
.enum(["read", "init", "upsertDeck", "upsertSlides", "upsertNarrative", "compileDeckPlan", "review", "reviewNarrative", "approveNarrative", "applyEvidenceCandidates", "attachResearchFindings", "remember"])
|
|
70
|
+
.enum(["read", "init", "upsertDeck", "upsertSlides", "upsertNarrative", "compileDeckPlan", "backfillClaimRefs", "review", "reviewNarrative", "approveNarrative", "deriveResearchGaps", "upsertResearchGaps", "updateResearchGap", "closeResearchGap", "applyEvidenceCandidates", "attachResearchFindings", "remember"])
|
|
67
71
|
.describe("Action to perform on DECKS.json."),
|
|
68
72
|
summary: tool.schema.boolean().optional().describe("For read: return a compact summary instead of full state."),
|
|
69
73
|
goal: tool.schema.string().optional().describe("For upsertDeck: deck goal."),
|
|
@@ -113,6 +117,13 @@ export default tool({
|
|
|
113
117
|
unsupportedScope: tool.schema.string().optional(),
|
|
114
118
|
caveats: tool.schema.array(tool.schema.string()).optional(),
|
|
115
119
|
})).optional(),
|
|
120
|
+
claimRelations: tool.schema.array(tool.schema.object({
|
|
121
|
+
id: tool.schema.string().optional(),
|
|
122
|
+
fromClaimId: tool.schema.string().describe("Canonical claim id the relation starts from."),
|
|
123
|
+
toClaimId: tool.schema.string().describe("Canonical claim id the relation points to."),
|
|
124
|
+
relation: tool.schema.enum(["leads_to", "supports", "depends_on", "contrasts_with", "constrains", "answers"]).optional(),
|
|
125
|
+
rationale: tool.schema.string().optional().describe("Short explanation of why this narrative relation exists."),
|
|
126
|
+
})).optional().describe("Canonical claim-to-claim narrative progression relationships. These affect narrative approval hash."),
|
|
116
127
|
evidenceBindings: tool.schema.array(tool.schema.object({
|
|
117
128
|
id: tool.schema.string().optional(),
|
|
118
129
|
claimId: tool.schema.string().describe("Canonical claim id this evidence supports."),
|
|
@@ -141,6 +152,18 @@ export default tool({
|
|
|
141
152
|
severity: tool.schema.enum(["high", "medium", "low"]).optional(),
|
|
142
153
|
mitigation: tool.schema.string().optional(),
|
|
143
154
|
})).optional(),
|
|
155
|
+
researchGaps: tool.schema.array(tool.schema.object({
|
|
156
|
+
id: tool.schema.string().optional(),
|
|
157
|
+
targetType: tool.schema.enum(["claim", "objection", "risk", "decision", "narrative"]).optional(),
|
|
158
|
+
targetId: tool.schema.string().optional(),
|
|
159
|
+
question: tool.schema.string().describe("Research question or gap to resolve."),
|
|
160
|
+
status: tool.schema.enum(["open", "in_progress", "findings_saved", "attached", "evidence_bound", "closed"]).optional(),
|
|
161
|
+
priority: tool.schema.enum(["high", "medium", "low"]).optional(),
|
|
162
|
+
findingsFile: tool.schema.string().optional(),
|
|
163
|
+
evidenceBindingIds: tool.schema.array(tool.schema.string()).optional(),
|
|
164
|
+
createdFromIssueType: tool.schema.string().optional(),
|
|
165
|
+
notes: tool.schema.string().optional(),
|
|
166
|
+
})).optional(),
|
|
144
167
|
}).optional().describe("For upsertNarrative: canonical narrative state fields to merge into DECKS.json. Replaces provided arrays, preserves approvals."),
|
|
145
168
|
design: tool.schema.string().optional().describe("For upsertDeck: active design name."),
|
|
146
169
|
domain: tool.schema.string().optional().describe("For upsertDeck: active domain name."),
|
|
@@ -190,6 +213,13 @@ export default tool({
|
|
|
190
213
|
layout: tool.schema.string().describe("Design layout name."),
|
|
191
214
|
qa: tool.schema.boolean().optional().describe("Whether the slide is marked QA-relevant deck metadata."),
|
|
192
215
|
components: tool.schema.array(tool.schema.string()).describe("Design components used by this slide."),
|
|
216
|
+
claimIds: tool.schema.array(tool.schema.string()).optional().describe("Canonical narrative claim ids directly expressed by this slide."),
|
|
217
|
+
claimRefs: tool.schema.array(tool.schema.object({
|
|
218
|
+
claimId: tool.schema.string().describe("Canonical narrative claim id referenced by this slide."),
|
|
219
|
+
role: tool.schema.enum(["primary", "supporting", "evidence", "risk", "objection"]).describe("How the slide uses this claim."),
|
|
220
|
+
note: tool.schema.string().optional().describe("Optional short rationale for this claim-slide relationship."),
|
|
221
|
+
})).optional().describe("Structured canonical claim references for this slide; preferred over flat claimIds when available."),
|
|
222
|
+
evidenceBindingIds: tool.schema.array(tool.schema.string()).optional().describe("Canonical narrative evidence binding ids used by this slide."),
|
|
193
223
|
content: tool.schema.object({
|
|
194
224
|
headline: tool.schema.string().optional(),
|
|
195
225
|
body: tool.schema.array(tool.schema.string()).optional(),
|
|
@@ -224,6 +254,22 @@ export default tool({
|
|
|
224
254
|
approvalNote: tool.schema.string().optional().describe("For approveNarrative: optional note explaining the approval or override."),
|
|
225
255
|
approvalBy: tool.schema.enum(["user", "override"]).optional().describe("For approveNarrative: use override only for explicit render overrides, not normal strategic approval."),
|
|
226
256
|
approvalScope: tool.schema.enum(["narrative", "render_override"]).optional().describe("For approveNarrative: narrative approval or explicit render override scope."),
|
|
257
|
+
gapId: tool.schema.string().optional().describe("For updateResearchGap/closeResearchGap: canonical research gap id."),
|
|
258
|
+
researchGaps: tool.schema.array(tool.schema.object({
|
|
259
|
+
id: tool.schema.string().optional(),
|
|
260
|
+
targetType: tool.schema.enum(["claim", "objection", "risk", "decision", "narrative"]).optional(),
|
|
261
|
+
targetId: tool.schema.string().optional(),
|
|
262
|
+
question: tool.schema.string().describe("Research question or gap to resolve."),
|
|
263
|
+
status: tool.schema.enum(["open", "in_progress", "findings_saved", "attached", "evidence_bound", "closed"]).optional(),
|
|
264
|
+
priority: tool.schema.enum(["high", "medium", "low"]).optional(),
|
|
265
|
+
findingsFile: tool.schema.string().optional(),
|
|
266
|
+
evidenceBindingIds: tool.schema.array(tool.schema.string()).optional(),
|
|
267
|
+
createdFromIssueType: tool.schema.string().optional(),
|
|
268
|
+
notes: tool.schema.string().optional(),
|
|
269
|
+
})).optional().describe("For upsertResearchGaps: explicit canonical research gaps to create or update."),
|
|
270
|
+
gapStatus: tool.schema.enum(["open", "in_progress", "findings_saved", "attached", "evidence_bound", "closed"]).optional().describe("For updateResearchGap: lifecycle status."),
|
|
271
|
+
gapNotes: tool.schema.string().optional().describe("For updateResearchGap/closeResearchGap: notes or close reason."),
|
|
272
|
+
evidenceBindingIds: tool.schema.array(tool.schema.string()).optional().describe("For updateResearchGap: canonical narrative evidence binding ids associated with the gap."),
|
|
227
273
|
},
|
|
228
274
|
async execute(args, context) {
|
|
229
275
|
try {
|
|
@@ -382,6 +428,12 @@ export default tool({
|
|
|
382
428
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: compiled.result, deck: compiled.state.activeDeck ? compiled.state.decks[compiled.state.activeDeck] : undefined, narrative: compiled.state.narrative }, null, 2)
|
|
383
429
|
}
|
|
384
430
|
|
|
431
|
+
if (args.action === "backfillClaimRefs") {
|
|
432
|
+
const backfilled = backfillSlideClaimRefsFromCoverage(state)
|
|
433
|
+
writeDecksState(workspaceRoot, backfilled.state)
|
|
434
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: backfilled.result, deck: backfilled.state.activeDeck ? backfilled.state.decks[backfilled.state.activeDeck] : undefined, narrative: backfilled.state.narrative }, null, 2)
|
|
435
|
+
}
|
|
436
|
+
|
|
385
437
|
if (args.action === "reviewNarrative") {
|
|
386
438
|
const reviewed = reviewNarrativeState(state)
|
|
387
439
|
recordNarrativeReviewAction(reviewed.state, reviewed.result)
|
|
@@ -400,6 +452,39 @@ export default tool({
|
|
|
400
452
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: approved.result, narrative: approved.state.narrative }, null, 2)
|
|
401
453
|
}
|
|
402
454
|
|
|
455
|
+
if (args.action === "deriveResearchGaps") {
|
|
456
|
+
const derived = deriveResearchGapsFromReadiness(state)
|
|
457
|
+
writeDecksState(workspaceRoot, derived.state)
|
|
458
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: derived.result, narrative: derived.state.narrative }, null, 2)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (args.action === "upsertResearchGaps") {
|
|
462
|
+
if (!args.researchGaps?.length) return JSON.stringify({ ok: false, error: "researchGaps are required for upsertResearchGaps" })
|
|
463
|
+
const upserted = upsertResearchGapsInState(state, args.researchGaps as any[])
|
|
464
|
+
writeDecksState(workspaceRoot, upserted.state)
|
|
465
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: upserted.result, narrative: upserted.state.narrative }, null, 2)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (args.action === "updateResearchGap") {
|
|
469
|
+
if (!args.gapId?.trim()) return JSON.stringify({ ok: false, error: "gapId is required for updateResearchGap" })
|
|
470
|
+
const updated = updateResearchGapInState(state, {
|
|
471
|
+
id: args.gapId,
|
|
472
|
+
status: args.gapStatus as any,
|
|
473
|
+
findingsFile: args.findingsFile,
|
|
474
|
+
evidenceBindingIds: args.evidenceBindingIds,
|
|
475
|
+
notes: args.gapNotes,
|
|
476
|
+
})
|
|
477
|
+
writeDecksState(workspaceRoot, updated.state)
|
|
478
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: updated.result, narrative: updated.state.narrative }, null, 2)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (args.action === "closeResearchGap") {
|
|
482
|
+
if (!args.gapId?.trim()) return JSON.stringify({ ok: false, error: "gapId is required for closeResearchGap" })
|
|
483
|
+
const closed = closeResearchGapInState(state, args.gapId, args.gapNotes)
|
|
484
|
+
writeDecksState(workspaceRoot, closed.state)
|
|
485
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: closed.result, narrative: closed.state.narrative }, null, 2)
|
|
486
|
+
}
|
|
487
|
+
|
|
403
488
|
if (args.action === "applyEvidenceCandidates") {
|
|
404
489
|
const candidateIds = args.candidateIds ?? []
|
|
405
490
|
if (candidateIds.length === 0) return JSON.stringify({ ok: false, error: "candidateIds are required for applyEvidenceCandidates" })
|
package/tools/edit.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* tools/edit.ts
|
|
3
3
|
*
|
|
4
|
-
* revela-edit —
|
|
4
|
+
* revela-edit — Compatibility tool that opens Revela Refine in Edit mode.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { tool } from "@opencode-ai/plugin"
|
|
8
|
-
import {
|
|
8
|
+
import { openRefineDeck } from "../lib/refine/open"
|
|
9
9
|
|
|
10
10
|
export function createEditTool(options: { client: any; workspaceRoot: string; openBrowser?: boolean }) {
|
|
11
11
|
return tool({
|
|
12
12
|
description:
|
|
13
|
-
"Open Revela
|
|
13
|
+
"Open Revela Refine in Edit mode for an existing slide deck. " +
|
|
14
14
|
"Use this when the user asks to edit, revise, annotate, or visually comment on a deck, " +
|
|
15
15
|
"including when they reference the current deck. " +
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"This is a compatibility tool for the older edit-only workflow; the user-facing entry is /revela refine. " +
|
|
17
|
+
"It opens a local browser workspace where the user can Ctrl/Cmd-click deck elements, use the Edit tab, " +
|
|
18
18
|
"and send precise edit requests back to the current OpenCode session.",
|
|
19
19
|
args: {},
|
|
20
20
|
async execute(_args, context: any) {
|
|
@@ -27,10 +27,11 @@ export function createEditTool(options: { client: any; workspaceRoot: string; op
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
|
-
const result =
|
|
30
|
+
const result = openRefineDeck("", {
|
|
31
31
|
client: options.client,
|
|
32
32
|
sessionID,
|
|
33
33
|
workspaceRoot: options.workspaceRoot,
|
|
34
|
+
mode: "edit",
|
|
34
35
|
openBrowser: options.openBrowser,
|
|
35
36
|
})
|
|
36
37
|
|
|
@@ -40,9 +41,10 @@ export function createEditTool(options: { client: any; workspaceRoot: string; op
|
|
|
40
41
|
file: result.deck.file,
|
|
41
42
|
source: result.source,
|
|
42
43
|
url: result.url,
|
|
44
|
+
mode: result.mode,
|
|
43
45
|
message:
|
|
44
|
-
`${result.stateNote} Opened
|
|
45
|
-
"Ask the user to use Ctrl/Cmd
|
|
46
|
+
`${result.stateNote} Opened Revela Refine in Edit mode. ` +
|
|
47
|
+
"Ask the user to use Ctrl/Cmd-click in the browser to reference elements, then use the Edit tab to send comments.",
|
|
46
48
|
}, null, 2)
|
|
47
49
|
} catch (error) {
|
|
48
50
|
return JSON.stringify({
|
|
@@ -29,6 +29,43 @@ export default tool({
|
|
|
29
29
|
}).optional(),
|
|
30
30
|
matchConfidence: tool.schema.enum(["none", "low", "medium", "high"]),
|
|
31
31
|
cards: tool.schema.object({
|
|
32
|
+
reading: tool.schema.object({
|
|
33
|
+
status: tool.schema.enum(["matched", "no_match"]),
|
|
34
|
+
claimId: tool.schema.string().optional(),
|
|
35
|
+
canonicalClaimId: tool.schema.string().optional(),
|
|
36
|
+
claimText: tool.schema.string().optional(),
|
|
37
|
+
evidenceStatus: tool.schema.string().optional(),
|
|
38
|
+
evidenceBindingIds: tool.schema.array(tool.schema.string()),
|
|
39
|
+
supportedScope: tool.schema.string().optional(),
|
|
40
|
+
unsupportedScope: tool.schema.string().optional(),
|
|
41
|
+
caveats: tool.schema.array(tool.schema.string()),
|
|
42
|
+
relatedObjections: tool.schema.array(tool.schema.string()),
|
|
43
|
+
relatedRisks: tool.schema.array(tool.schema.string()),
|
|
44
|
+
artifactCoverage: tool.schema.array(tool.schema.object({
|
|
45
|
+
artifactId: tool.schema.string(),
|
|
46
|
+
type: tool.schema.string(),
|
|
47
|
+
outputPath: tool.schema.string().optional(),
|
|
48
|
+
coverageStatus: tool.schema.enum(["current", "stale", "partial", "missing"]),
|
|
49
|
+
containsClaim: tool.schema.boolean(),
|
|
50
|
+
stale: tool.schema.boolean(),
|
|
51
|
+
staleReason: tool.schema.string().optional(),
|
|
52
|
+
locations: tool.schema.array(tool.schema.string()),
|
|
53
|
+
note: tool.schema.string().optional(),
|
|
54
|
+
})).optional(),
|
|
55
|
+
rationale: tool.schema.string(),
|
|
56
|
+
}).optional(),
|
|
57
|
+
exploratory: tool.schema.object({
|
|
58
|
+
status: tool.schema.enum(["available", "limited", "unavailable"]),
|
|
59
|
+
official: tool.schema.boolean().describe("Must be false; exploratory reading is not official artifact content."),
|
|
60
|
+
audience: tool.schema.string().optional(),
|
|
61
|
+
claimFocus: tool.schema.string().optional(),
|
|
62
|
+
objectionPrompts: tool.schema.array(tool.schema.string()),
|
|
63
|
+
audienceReframe: tool.schema.string(),
|
|
64
|
+
appendixLeads: tool.schema.array(tool.schema.string()),
|
|
65
|
+
meetingPrep: tool.schema.array(tool.schema.string()),
|
|
66
|
+
boundaries: tool.schema.array(tool.schema.string()),
|
|
67
|
+
rationale: tool.schema.string(),
|
|
68
|
+
}).optional(),
|
|
32
69
|
purpose: tool.schema.object({
|
|
33
70
|
status: tool.schema.enum(["clear", "weak", "misplaced", "unknown"]),
|
|
34
71
|
role: tool.schema.string().optional(),
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin"
|
|
2
|
+
import { hasDecksState, readDecksState } from "../lib/decks-state"
|
|
3
|
+
import { buildNarrativeMap } from "../lib/narrative-state/map"
|
|
4
|
+
import { validateNarrativeDisplayModel, type NarrativeDisplayModel, type NarrativeViewLanguage } from "../lib/narrative-state/display"
|
|
5
|
+
import { writeNarrativeMapHtml } from "../lib/commands/narrative"
|
|
6
|
+
import { openUrl } from "../lib/edit/open"
|
|
7
|
+
|
|
8
|
+
export default tool({
|
|
9
|
+
description:
|
|
10
|
+
"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 DECKS.json, opens a local HTML view, and never mutates workspace state.",
|
|
12
|
+
args: {
|
|
13
|
+
language: tool.schema.string().describe("UI language request from /revela narrative. May be any language tag or language name, such as en, zh-CN, fr, de, Korean, Arabic, or Portuguese-BR."),
|
|
14
|
+
narrativeHash: tool.schema.string().optional().describe("Narrative hash from the prompt projection. Used to detect stale display prompts."),
|
|
15
|
+
displayModel: tool.schema.object({
|
|
16
|
+
version: tool.schema.number().describe("Must be 1."),
|
|
17
|
+
language: tool.schema.string().describe("Must exactly match the top-level language request."),
|
|
18
|
+
pageTitle: tool.schema.string().optional(),
|
|
19
|
+
summaryLine: tool.schema.string().optional(),
|
|
20
|
+
labels: tool.schema.object({
|
|
21
|
+
eyebrow: tool.schema.string().optional(),
|
|
22
|
+
claimFlow: tool.schema.string().optional(),
|
|
23
|
+
flowNote: tool.schema.string().optional(),
|
|
24
|
+
selectedClaim: tool.schema.string().optional(),
|
|
25
|
+
claim: tool.schema.string().optional(),
|
|
26
|
+
claimId: tool.schema.string().optional(),
|
|
27
|
+
status: tool.schema.string().optional(),
|
|
28
|
+
supportedScope: tool.schema.string().optional(),
|
|
29
|
+
unsupportedScope: tool.schema.string().optional(),
|
|
30
|
+
incomingRelations: tool.schema.string().optional(),
|
|
31
|
+
outgoingRelations: tool.schema.string().optional(),
|
|
32
|
+
evidence: tool.schema.string().optional(),
|
|
33
|
+
objections: tool.schema.string().optional(),
|
|
34
|
+
risks: tool.schema.string().optional(),
|
|
35
|
+
researchGaps: tool.schema.string().optional(),
|
|
36
|
+
coveredSlides: tool.schema.string().optional(),
|
|
37
|
+
noClaims: tool.schema.string().optional(),
|
|
38
|
+
none: tool.schema.string().optional(),
|
|
39
|
+
}).optional(),
|
|
40
|
+
claimCards: tool.schema.array(tool.schema.object({
|
|
41
|
+
claimId: tool.schema.string().describe("Existing canonical claim id. Must match the deterministic map."),
|
|
42
|
+
displayTitle: tool.schema.string().optional().describe("Display-only localized claim title in the requested language. For Chinese manufacturing/industrial AI context, translate autonomy as 自主化/自主能力, not 自治; convert slug-like claim text into a readable title."),
|
|
43
|
+
roleLabel: tool.schema.string().optional(),
|
|
44
|
+
narrativeJob: tool.schema.string().optional(),
|
|
45
|
+
evidenceSummary: tool.schema.string().optional(),
|
|
46
|
+
riskOrGapSummary: tool.schema.string().optional(),
|
|
47
|
+
})).optional(),
|
|
48
|
+
relations: tool.schema.array(tool.schema.object({
|
|
49
|
+
fromClaimId: tool.schema.string(),
|
|
50
|
+
toClaimId: tool.schema.string(),
|
|
51
|
+
relation: tool.schema.enum(["leads_to", "supports", "depends_on", "contrasts_with", "constrains", "answers"]),
|
|
52
|
+
displayLabel: tool.schema.string().optional().describe("Display-only localization of an existing canonical relation label. Omit for inferred relations."),
|
|
53
|
+
displayRationale: tool.schema.string().optional().describe("Display-only localization of an existing canonical rationale. Omit when canonical rationale is missing or the relation is inferred."),
|
|
54
|
+
})).optional(),
|
|
55
|
+
}).optional().describe("Localized and organized display-only projection. It must not add facts or alter IDs."),
|
|
56
|
+
},
|
|
57
|
+
async execute(args, context) {
|
|
58
|
+
const workspaceRoot = context.directory ?? process.cwd()
|
|
59
|
+
try {
|
|
60
|
+
if (!hasDecksState(workspaceRoot)) {
|
|
61
|
+
return JSON.stringify({ ok: false, error: "No DECKS.json found. Run /revela init first." })
|
|
62
|
+
}
|
|
63
|
+
const language = args.language as NarrativeViewLanguage
|
|
64
|
+
const map = buildNarrativeMap(readDecksState(workspaceRoot))
|
|
65
|
+
const stalePrompt = Boolean(args.narrativeHash && args.narrativeHash !== map.snapshot.narrativeHash)
|
|
66
|
+
const display = validateNarrativeDisplayModel(map, args.displayModel as NarrativeDisplayModel | undefined, language)
|
|
67
|
+
const htmlPath = writeNarrativeMapHtml(map, display)
|
|
68
|
+
const url = `file://${htmlPath}`
|
|
69
|
+
openUrl(url)
|
|
70
|
+
return JSON.stringify({ ok: true, url, path: htmlPath, narrativeHash: map.snapshot.narrativeHash, stalePrompt, fallback: false }, null, 2)
|
|
71
|
+
} catch (e: any) {
|
|
72
|
+
try {
|
|
73
|
+
const language = (args.language ?? "en") as NarrativeViewLanguage
|
|
74
|
+
const map = buildNarrativeMap(readDecksState(workspaceRoot))
|
|
75
|
+
const htmlPath = writeNarrativeMapHtml(map)
|
|
76
|
+
const url = `file://${htmlPath}`
|
|
77
|
+
openUrl(url)
|
|
78
|
+
return JSON.stringify({ ok: false, fallback: true, url, path: htmlPath, error: e.message || String(e) }, null, 2)
|
|
79
|
+
} catch (fallbackError: any) {
|
|
80
|
+
return JSON.stringify({ ok: false, fallback: false, error: e.message || String(e), fallbackError: fallbackError.message || String(fallbackError) })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
})
|