@cyber-dash-tech/revela 0.10.0 → 0.11.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 +25 -5
- package/README.zh-CN.md +25 -5
- package/lib/commands/inspect.ts +1 -1
- package/lib/commands/pdf.ts +33 -5
- package/lib/commands/pptx.ts +14 -9
- package/lib/commands/refine.ts +1 -1
- package/lib/deck-html/contract.ts +252 -0
- package/lib/decks-state.ts +101 -28
- package/lib/document-materials/extract.ts +20 -0
- package/lib/edit/resolve-deck.ts +13 -2
- package/lib/inspect/open.ts +3 -1
- package/lib/qa/export-gate.ts +8 -1
- package/lib/refine/open.ts +3 -1
- package/lib/workspace-state/actions.ts +71 -0
- package/lib/workspace-state/compat.ts +10 -0
- package/lib/workspace-state/evidence-status.ts +267 -0
- package/lib/workspace-state/graph.ts +426 -0
- package/lib/workspace-state/render-targets.ts +182 -0
- package/lib/workspace-state/rendered-artifacts.ts +43 -0
- package/lib/workspace-state/repository.ts +43 -0
- package/lib/workspace-state/research-attachments.ts +130 -0
- package/lib/workspace-state/review-snapshots.ts +127 -0
- package/lib/workspace-state/types.ts +119 -0
- package/package.json +1 -1
- package/plugin.ts +26 -1
- package/tools/decks.ts +54 -5
- package/tools/pdf.ts +9 -1
- package/tools/pptx.ts +10 -0
- package/tools/research-save.ts +15 -0
- package/tools/workspace-scan.ts +15 -0
package/plugin.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { buildPptxNotesPrompt, handlePptx, parsePptxArgs, resolvePptxDeck } from
|
|
|
48
48
|
import { handleEdit } from "./lib/commands/edit"
|
|
49
49
|
import { handleInspect } from "./lib/commands/inspect"
|
|
50
50
|
import { handleRefine } from "./lib/commands/refine"
|
|
51
|
+
import { formatDeckHtmlContractReport, validateDeckHtmlContract } from "./lib/deck-html/contract"
|
|
51
52
|
import { ensureEditableDeckOpenForChange } from "./lib/edit/open"
|
|
52
53
|
import { hasLiveEditorSessionForFile } from "./lib/edit/server"
|
|
53
54
|
import { handleDesignsPreview } from "./lib/commands/designs-preview"
|
|
@@ -177,6 +178,27 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
async function appendDeckHtmlContractReport(filePath: string, output: any): Promise<void> {
|
|
182
|
+
if (!isDeckHtmlPath(filePath)) return
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const report = validateDeckHtmlContract(workspaceRoot, filePath)
|
|
186
|
+
if (report.status === "valid" || report.status === "skipped") return
|
|
187
|
+
|
|
188
|
+
appendToolResult(
|
|
189
|
+
output,
|
|
190
|
+
"---\n\n**[revela deck HTML contract]** Slide identity check failed:\n\n" +
|
|
191
|
+
formatDeckHtmlContractReport(report) +
|
|
192
|
+
"\n\nFix every `<section class=\"slide\">` to use the matching 1-based `data-slide-index` from DECKS.json before inspection or export."
|
|
193
|
+
)
|
|
194
|
+
} catch (e) {
|
|
195
|
+
childLog("deck-contract").warn("deck HTML contract report failed", {
|
|
196
|
+
filePath,
|
|
197
|
+
error: e instanceof Error ? e.message : String(e),
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
180
202
|
function extractSessionID(input: any): string {
|
|
181
203
|
return input?.sessionID ?? input?.session?.id ?? input?.context?.sessionID ?? ""
|
|
182
204
|
}
|
|
@@ -423,7 +445,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
423
445
|
throw new Error("__REVELA_DOMAINS_RM_HANDLED__")
|
|
424
446
|
}
|
|
425
447
|
if (sub === "pdf") {
|
|
426
|
-
await handlePdf(param, send)
|
|
448
|
+
await handlePdf(param, send, workspaceRoot)
|
|
427
449
|
throw new Error("__REVELA_PDF_HANDLED__")
|
|
428
450
|
}
|
|
429
451
|
if (sub === "pptx") {
|
|
@@ -742,6 +764,7 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
|
|
|
742
764
|
return
|
|
743
765
|
}
|
|
744
766
|
await appendComplianceReport(filePath, output)
|
|
767
|
+
await appendDeckHtmlContractReport(filePath, output)
|
|
745
768
|
ensureEditorOpenAfterDeckChange(filePath, extractSessionID(input))
|
|
746
769
|
return
|
|
747
770
|
}
|
|
@@ -763,6 +786,7 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
|
|
|
763
786
|
const targets = patchText ? extractDeckHtmlTargetsFromPatch(patchText) : []
|
|
764
787
|
for (const target of targets) {
|
|
765
788
|
await appendComplianceReport(target, output)
|
|
789
|
+
await appendDeckHtmlContractReport(target, output)
|
|
766
790
|
ensureEditorOpenAfterDeckChange(target, extractSessionID(input))
|
|
767
791
|
}
|
|
768
792
|
return
|
|
@@ -771,6 +795,7 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
|
|
|
771
795
|
if (input.tool === "edit") {
|
|
772
796
|
const filePath = extractEditFilePath(input.args)
|
|
773
797
|
await appendComplianceReport(filePath, output)
|
|
798
|
+
await appendDeckHtmlContractReport(filePath, output)
|
|
774
799
|
ensureEditorOpenAfterDeckChange(filePath, extractSessionID(input))
|
|
775
800
|
return
|
|
776
801
|
}
|
package/tools/decks.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
2
|
import {
|
|
3
|
-
applyEvidenceCandidates,
|
|
4
3
|
createDeckSpec,
|
|
5
4
|
DECKS_STATE_FILE,
|
|
6
5
|
normalizeWorkspaceDeckState,
|
|
@@ -18,6 +17,10 @@ import {
|
|
|
18
17
|
type SlideSpec,
|
|
19
18
|
} from "../lib/decks-state"
|
|
20
19
|
import { upsertSourceMaterial } from "../lib/source-materials"
|
|
20
|
+
import { recordWorkspaceAction } from "../lib/workspace-state/actions"
|
|
21
|
+
import { applyEvidenceBindings } from "../lib/workspace-state/evidence-status"
|
|
22
|
+
import { attachResearchFindings } from "../lib/workspace-state/research-attachments"
|
|
23
|
+
import { activeReviewTargetId, latestReviewSnapshotForTarget } from "../lib/workspace-state/review-snapshots"
|
|
21
24
|
|
|
22
25
|
export default tool({
|
|
23
26
|
description:
|
|
@@ -26,7 +29,7 @@ export default tool({
|
|
|
26
29
|
"It stores active deck specs, per-slide content/layout/components, and computes write readiness.",
|
|
27
30
|
args: {
|
|
28
31
|
action: tool.schema
|
|
29
|
-
.enum(["read", "init", "upsertDeck", "upsertSlides", "review", "applyEvidenceCandidates", "remember"])
|
|
32
|
+
.enum(["read", "init", "upsertDeck", "upsertSlides", "review", "applyEvidenceCandidates", "attachResearchFindings", "remember"])
|
|
30
33
|
.describe("Action to perform on DECKS.json."),
|
|
31
34
|
summary: tool.schema.boolean().optional().describe("For read: return a compact summary instead of full state."),
|
|
32
35
|
goal: tool.schema.string().optional().describe("For upsertDeck: deck goal."),
|
|
@@ -118,6 +121,9 @@ export default tool({
|
|
|
118
121
|
notes: tool.schema.string().optional().describe("Implementation notes for this slide."),
|
|
119
122
|
})).optional().describe("For upsertSlides: complete or partial slide specs."),
|
|
120
123
|
candidateIds: tool.schema.array(tool.schema.string()).optional().describe("For applyEvidenceCandidates: candidate IDs returned by revela-decks review to explicitly bind proposed evidenceDraft records into slide evidence."),
|
|
124
|
+
findingsFile: tool.schema.string().optional().describe("For attachResearchFindings: workspace-relative researches/{topic}/{axis}.md file to attach to researchPlan."),
|
|
125
|
+
researchAxis: tool.schema.string().optional().describe("For attachResearchFindings: researchPlan axis to attach the findings file to. Required when filename matching would be ambiguous."),
|
|
126
|
+
researchStatus: tool.schema.enum(["done", "read"]).optional().describe("For attachResearchFindings: optional explicit status to set on the matched research axis."),
|
|
121
127
|
},
|
|
122
128
|
async execute(args, context) {
|
|
123
129
|
try {
|
|
@@ -126,8 +132,20 @@ export default tool({
|
|
|
126
132
|
const defaultSlug = workspaceDeckSlug(workspaceRoot)
|
|
127
133
|
|
|
128
134
|
if (args.action === "init") {
|
|
135
|
+
const discovered: SourceMaterial[] = []
|
|
129
136
|
for (const material of (args.sourceMaterials ?? []) as SourceMaterial[]) {
|
|
130
137
|
upsertSourceMaterial(state, material, material.status ?? "discovered")
|
|
138
|
+
discovered.push(material)
|
|
139
|
+
}
|
|
140
|
+
if (discovered.length > 0) {
|
|
141
|
+
recordWorkspaceAction(state, {
|
|
142
|
+
type: "source.discovered",
|
|
143
|
+
actor: "revela-decks",
|
|
144
|
+
inputs: { count: discovered.length },
|
|
145
|
+
outputs: { paths: discovered.map((material) => material.path), statuses: discovered.map((material) => material.status ?? "discovered") },
|
|
146
|
+
summary: `Registered ${discovered.length} discovered source material${discovered.length === 1 ? "" : "s"}.`,
|
|
147
|
+
nodeIds: discovered.map((material) => `source:${material.path}`),
|
|
148
|
+
})
|
|
131
149
|
}
|
|
132
150
|
writeDecksState(workspaceRoot, state)
|
|
133
151
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, state }, null, 2)
|
|
@@ -178,6 +196,28 @@ export default tool({
|
|
|
178
196
|
|
|
179
197
|
if (args.action === "review") {
|
|
180
198
|
const reviewed = reviewDeckState(state, undefined, { workspaceRoot })
|
|
199
|
+
const targetId = activeReviewTargetId(reviewed.state)
|
|
200
|
+
const snapshot = latestReviewSnapshotForTarget(reviewed.state, targetId)
|
|
201
|
+
recordWorkspaceAction(reviewed.state, {
|
|
202
|
+
type: "review.performed",
|
|
203
|
+
actor: "revela-decks",
|
|
204
|
+
inputs: { activeDeck: state.activeDeck },
|
|
205
|
+
outputs: {
|
|
206
|
+
slug: reviewed.result.slug,
|
|
207
|
+
status: reviewed.result.status,
|
|
208
|
+
ready: reviewed.result.ready,
|
|
209
|
+
blockerCount: reviewed.result.blockers.length,
|
|
210
|
+
warningCount: reviewed.result.warnings.length,
|
|
211
|
+
issueCount: reviewed.result.issues.length,
|
|
212
|
+
evidenceCandidateCount: reviewed.result.evidenceCandidates?.length ?? 0,
|
|
213
|
+
snapshotId: snapshot?.id,
|
|
214
|
+
inputHash: snapshot?.inputHash,
|
|
215
|
+
targetId: snapshot?.targetId,
|
|
216
|
+
},
|
|
217
|
+
status: "success",
|
|
218
|
+
summary: `Reviewed deck readiness: ${reviewed.result.ready ? "ready" : "blocked"}.`,
|
|
219
|
+
nodeIds: [`artifact:${reviewed.state.decks[reviewed.result.slug]?.outputPath ?? reviewed.result.slug}`, ...(snapshot ? [snapshot.id] : [])],
|
|
220
|
+
})
|
|
181
221
|
writeDecksState(workspaceRoot, reviewed.state)
|
|
182
222
|
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: reviewed.result }, null, 2)
|
|
183
223
|
}
|
|
@@ -185,9 +225,18 @@ export default tool({
|
|
|
185
225
|
if (args.action === "applyEvidenceCandidates") {
|
|
186
226
|
const candidateIds = args.candidateIds ?? []
|
|
187
227
|
if (candidateIds.length === 0) return JSON.stringify({ ok: false, error: "candidateIds are required for applyEvidenceCandidates" })
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
228
|
+
const result = applyEvidenceBindings(workspaceRoot, candidateIds)
|
|
229
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result }, null, 2)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (args.action === "attachResearchFindings") {
|
|
233
|
+
if (!args.findingsFile?.trim()) return JSON.stringify({ ok: false, error: "findingsFile is required for attachResearchFindings" })
|
|
234
|
+
const result = attachResearchFindings(workspaceRoot, {
|
|
235
|
+
findingsFile: args.findingsFile,
|
|
236
|
+
researchAxis: args.researchAxis,
|
|
237
|
+
status: args.researchStatus,
|
|
238
|
+
})
|
|
239
|
+
return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result }, null, 2)
|
|
191
240
|
}
|
|
192
241
|
|
|
193
242
|
if (args.action === "remember") {
|
package/tools/pdf.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { existsSync } from "fs"
|
|
|
9
9
|
import { resolve } from "path"
|
|
10
10
|
import { exportToPdf } from "../lib/pdf/export"
|
|
11
11
|
import { assertExportQAPassed } from "../lib/qa/export-gate"
|
|
12
|
+
import { recordRenderedArtifact, workspaceRelative } from "../lib/workspace-state/rendered-artifacts"
|
|
12
13
|
|
|
13
14
|
export default tool({
|
|
14
15
|
description:
|
|
@@ -35,8 +36,15 @@ export default tool({
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
|
-
|
|
39
|
+
const root = directory || process.cwd()
|
|
40
|
+
await assertExportQAPassed(filePath, { workspaceRoot: root })
|
|
39
41
|
const result = await exportToPdf(filePath)
|
|
42
|
+
recordRenderedArtifact(root, {
|
|
43
|
+
sourceHtmlPath: workspaceRelative(resolve(root), filePath),
|
|
44
|
+
outputPath: result.outputPath,
|
|
45
|
+
type: "pdf",
|
|
46
|
+
actor: "revela-pdf",
|
|
47
|
+
})
|
|
40
48
|
return JSON.stringify({ ok: true, ...result }, null, 2)
|
|
41
49
|
} catch (e: any) {
|
|
42
50
|
return JSON.stringify({ ok: false, error: e?.message ?? String(e) })
|
package/tools/pptx.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import { tool } from "@opencode-ai/plugin"
|
|
8
8
|
import { existsSync } from "fs"
|
|
9
9
|
import { resolve } from "path"
|
|
10
|
+
import { assertDeckHtmlContractValid } from "../lib/deck-html/contract"
|
|
10
11
|
import { exportToPptx } from "../lib/pptx/export"
|
|
12
|
+
import { recordRenderedArtifact, workspaceRelative } from "../lib/workspace-state/rendered-artifacts"
|
|
11
13
|
|
|
12
14
|
export default tool({
|
|
13
15
|
description:
|
|
@@ -42,12 +44,20 @@ export default tool({
|
|
|
42
44
|
const progress: string[] = []
|
|
43
45
|
|
|
44
46
|
try {
|
|
47
|
+
const root = directory || process.cwd()
|
|
48
|
+
assertDeckHtmlContractValid(root, filePath)
|
|
45
49
|
const result = await exportToPptx(filePath, {
|
|
46
50
|
speakerNotes: normalizeSpeakerNotes(speakerNotes),
|
|
47
51
|
onProgress: (event) => {
|
|
48
52
|
progress.push(event.message)
|
|
49
53
|
},
|
|
50
54
|
})
|
|
55
|
+
recordRenderedArtifact(root, {
|
|
56
|
+
sourceHtmlPath: workspaceRelative(resolve(root), filePath),
|
|
57
|
+
outputPath: result.outputPath,
|
|
58
|
+
type: "pptx",
|
|
59
|
+
actor: "revela-pptx",
|
|
60
|
+
})
|
|
51
61
|
return JSON.stringify({ ok: true, ...result, progress }, null, 2)
|
|
52
62
|
} catch (e: any) {
|
|
53
63
|
return JSON.stringify({ ok: false, error: e?.message ?? String(e), progress })
|
package/tools/research-save.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
2
|
import { mkdirSync, writeFileSync } from "fs"
|
|
3
3
|
import { join } from "path"
|
|
4
|
+
import { hasDecksState, readDecksState, writeDecksState } from "../lib/decks-state"
|
|
5
|
+
import { recordWorkspaceAction } from "../lib/workspace-state/actions"
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Format today's date as YYYY-MM-DD
|
|
@@ -88,6 +90,19 @@ export default tool({
|
|
|
88
90
|
|
|
89
91
|
writeFileSync(filePath, fileContent, "utf-8")
|
|
90
92
|
|
|
93
|
+
if (hasDecksState(workspaceDir)) {
|
|
94
|
+
const state = readDecksState(workspaceDir)
|
|
95
|
+
recordWorkspaceAction(state, {
|
|
96
|
+
type: "research.findings_saved",
|
|
97
|
+
actor: "revela-research-save",
|
|
98
|
+
inputs: { topic: topicKey, axis: fileKey, sourceCount: args.sources?.length ?? 0 },
|
|
99
|
+
outputs: { path: relPath, sources: args.sources ?? [] },
|
|
100
|
+
summary: `Saved research findings for ${topicKey}/${fileKey}.`,
|
|
101
|
+
nodeIds: [`finding:${relPath}`],
|
|
102
|
+
})
|
|
103
|
+
writeDecksState(workspaceDir, state)
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
return JSON.stringify({ ok: true, path: relPath })
|
|
92
107
|
} catch (e: any) {
|
|
93
108
|
return JSON.stringify({ error: e.message || String(e) })
|
package/tools/workspace-scan.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { readdirSync, statSync, existsSync } from "fs"
|
|
|
3
3
|
import { join, relative, extname, resolve, sep, isAbsolute } from "path"
|
|
4
4
|
import { sourceMaterialMetadata } from "../lib/source-materials"
|
|
5
5
|
import type { SourceMaterial } from "../lib/decks-state"
|
|
6
|
+
import { hasDecksState, readDecksState, writeDecksState } from "../lib/decks-state"
|
|
7
|
+
import { recordWorkspaceAction } from "../lib/workspace-state/actions"
|
|
6
8
|
|
|
7
9
|
const DOC_EXTENSIONS = new Set([
|
|
8
10
|
".pdf", ".docx", ".doc", ".xlsx", ".xls",
|
|
@@ -145,6 +147,19 @@ export default tool({
|
|
|
145
147
|
const results: FileEntry[] = []
|
|
146
148
|
scanDir(scanRoot, workspaceDir, results, maxDepth, 0)
|
|
147
149
|
|
|
150
|
+
if (hasDecksState(workspaceDir)) {
|
|
151
|
+
const state = readDecksState(workspaceDir)
|
|
152
|
+
recordWorkspaceAction(state, {
|
|
153
|
+
type: "workspace.scanned",
|
|
154
|
+
actor: "revela-workspace-scan",
|
|
155
|
+
inputs: { path: args.path, maxDepth },
|
|
156
|
+
outputs: { found: results.length, paths: results.map((file) => file.path) },
|
|
157
|
+
summary: `Scanned workspace documents and found ${results.length} file${results.length === 1 ? "" : "s"}.`,
|
|
158
|
+
nodeIds: results.map((file) => `source:${file.sourceMaterial.path}`),
|
|
159
|
+
})
|
|
160
|
+
writeDecksState(workspaceDir, state)
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
if (results.length === 0) {
|
|
149
164
|
return JSON.stringify({
|
|
150
165
|
found: 0,
|