@cyber-dash-tech/revela 0.9.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.
Files changed (48) hide show
  1. package/README.md +54 -9
  2. package/README.zh-CN.md +54 -9
  3. package/designs/monet/DESIGN.md +9 -9
  4. package/designs/starter/DESIGN.md +8 -8
  5. package/designs/summit/DESIGN.md +9 -9
  6. package/lib/commands/help.ts +2 -0
  7. package/lib/commands/inspect.ts +23 -0
  8. package/lib/commands/pdf.ts +33 -5
  9. package/lib/commands/pptx.ts +14 -9
  10. package/lib/commands/refine.ts +26 -0
  11. package/lib/commands/review.ts +8 -2
  12. package/lib/deck-html/contract.ts +252 -0
  13. package/lib/decks-state.ts +574 -31
  14. package/lib/document-materials/extract.ts +20 -0
  15. package/lib/edit/resolve-deck.ts +13 -2
  16. package/lib/inspect/open.ts +63 -0
  17. package/lib/inspect/prompt.ts +32 -0
  18. package/lib/inspect/request.ts +70 -0
  19. package/lib/inspect/requests.ts +86 -0
  20. package/lib/inspect/server.ts +1063 -0
  21. package/lib/inspect/slide-index.ts +12 -0
  22. package/lib/inspection-context/compile.ts +346 -0
  23. package/lib/inspection-context/match.ts +169 -0
  24. package/lib/inspection-context/project.ts +263 -0
  25. package/lib/inspection-context/result.ts +160 -0
  26. package/lib/qa/export-gate.ts +8 -1
  27. package/lib/refine/open.ts +70 -0
  28. package/lib/refine/server.ts +1581 -0
  29. package/lib/workspace-state/actions.ts +71 -0
  30. package/lib/workspace-state/compat.ts +10 -0
  31. package/lib/workspace-state/evidence-status.ts +267 -0
  32. package/lib/workspace-state/graph.ts +426 -0
  33. package/lib/workspace-state/render-targets.ts +182 -0
  34. package/lib/workspace-state/rendered-artifacts.ts +43 -0
  35. package/lib/workspace-state/repository.ts +43 -0
  36. package/lib/workspace-state/research-attachments.ts +130 -0
  37. package/lib/workspace-state/review-snapshots.ts +127 -0
  38. package/lib/workspace-state/types.ts +119 -0
  39. package/package.json +1 -1
  40. package/plugin.ts +48 -1
  41. package/skill/SKILL.md +10 -5
  42. package/tools/decks.ts +61 -2
  43. package/tools/inspection-context.ts +22 -0
  44. package/tools/inspection-result.ts +63 -0
  45. package/tools/pdf.ts +9 -1
  46. package/tools/pptx.ts +10 -0
  47. package/tools/research-save.ts +15 -0
  48. package/tools/workspace-scan.ts +15 -0
@@ -10,6 +10,7 @@ import { extractPptx } from "../read-hooks/extractors/pptx"
10
10
  import { extractXlsx } from "../read-hooks/extractors/xlsx"
11
11
  import { hasDecksState, readDecksState, writeDecksState } from "../decks-state"
12
12
  import { computeSourceFingerprint, sourceMaterialMetadata, upsertSourceMaterial } from "../source-materials"
13
+ import { recordWorkspaceAction } from "../workspace-state/actions"
13
14
 
14
15
  export type DocumentMaterial = {
15
16
  path: string
@@ -180,6 +181,25 @@ function updateDecksSourceMaterialIndex(
180
181
  : undefined,
181
182
  lastExtracted: extracted ? (result.cache_status === "hit" ? existing?.lastExtracted ?? now : now) : undefined,
182
183
  }, extracted ? "extracted" : "discovered")
184
+ recordWorkspaceAction(state, {
185
+ type: "source.extracted",
186
+ actor: "revela-extract-document-materials",
187
+ inputs: { file: base.path, type: base.type, fingerprint: base.fingerprint },
188
+ outputs: {
189
+ status: result.status,
190
+ cacheStatus: result.cache_status,
191
+ source: result.source,
192
+ manifestPath: result.manifest_path,
193
+ textPath: result.text_path,
194
+ cacheDir: result.cache_dir,
195
+ reason: result.reason,
196
+ imageCount: result.images?.length ?? 0,
197
+ tableCount: result.tables?.length ?? 0,
198
+ },
199
+ status: result.status === "failed" ? "failed" : result.status === "skipped" ? "skipped" : "success",
200
+ summary: extracted ? `Extracted reusable materials from ${base.path}.` : `Registered ${base.path} without reusable extraction output.`,
201
+ nodeIds: [`source:${base.path}`],
202
+ })
183
203
  writeDecksState(workspaceDir, state)
184
204
  }
185
205
 
@@ -1,12 +1,13 @@
1
1
  import { existsSync, readdirSync } from "fs"
2
2
  import { relative, resolve, sep } from "path"
3
- import { DECKS_STATE_FILE, isDeckHtmlPath, workspaceDeckSlug } from "../decks-state"
3
+ import { DECKS_STATE_FILE, hasDecksState, isDeckHtmlPath, readDecksState, workspaceDeckSlug } from "../decks-state"
4
+ import { resolveActiveHtmlDeckPath } from "../workspace-state/render-targets"
4
5
 
5
6
  export interface EditableDeck {
6
7
  slug: string
7
8
  file: string
8
9
  absoluteFile: string
9
- source: "decks-state" | "fallback" | "file-path"
10
+ source: "render-target" | "decks-state" | "fallback" | "file-path"
10
11
  }
11
12
 
12
13
  export function resolveEditableDeck(workspaceRoot: string, input = ""): EditableDeck {
@@ -14,6 +15,16 @@ export function resolveEditableDeck(workspaceRoot: string, input = ""): Editable
14
15
  throw new Error("/revela edit no longer accepts a target. It opens the only HTML deck in decks/.")
15
16
  }
16
17
 
18
+ if (hasDecksState(workspaceRoot)) {
19
+ const state = readDecksState(workspaceRoot)
20
+ const deckPath = resolveActiveHtmlDeckPath(state)
21
+ const source = state.renderTargets.some((target) => target.type === "html_deck" && target.outputPath === deckPath) ? "render-target" : "decks-state"
22
+ if (deckPath && isDeckHtmlPath(deckPath)) {
23
+ const absoluteFile = resolve(workspaceRoot, deckPath)
24
+ if (existsSync(absoluteFile)) return resolveDeckFile(workspaceRoot, workspaceDeckSlug(workspaceRoot), deckPath, source)
25
+ }
26
+ }
27
+
17
28
  const htmlFiles = listDeckHtmlFiles(workspaceRoot)
18
29
  if (htmlFiles.length === 0) {
19
30
  throw new Error("No deck HTML found in decks/. Generate a deck first.")
@@ -0,0 +1,63 @@
1
+ import { existsSync } from "fs"
2
+ import { ACTIVE_PROMPT_FILE } from "../config"
3
+ import { ctx } from "../ctx"
4
+ import { seedBuiltinDesigns } from "../design/designs"
5
+ import { assertDeckHtmlContractValid } from "../deck-html/contract"
6
+ import { seedBuiltinDomains } from "../domain/domains"
7
+ import { ensureEditableDeckState } from "../edit/deck-state"
8
+ import { openUrl } from "../edit/open"
9
+ import { resolveEditableDeck, type EditableDeck } from "../edit/resolve-deck"
10
+ import { buildPrompt } from "../prompt-builder"
11
+ import { startInspectServer } from "./server"
12
+
13
+ export interface OpenInspectDeckResult {
14
+ deck: EditableDeck
15
+ url: string
16
+ source: string
17
+ stateNote: string
18
+ preflightChanged: boolean
19
+ reusedSession: boolean
20
+ openedBrowser: boolean
21
+ }
22
+
23
+ export interface OpenInspectDeckOptions {
24
+ client: any
25
+ sessionID: string
26
+ workspaceRoot: string
27
+ openBrowser?: boolean
28
+ openUrl?: (url: string) => void
29
+ }
30
+
31
+ export function openInspectDeck(target: string, options: OpenInspectDeckOptions): OpenInspectDeckResult {
32
+ const deck = resolveEditableDeck(options.workspaceRoot, target)
33
+ const preflight = ensureEditableDeckState(options.workspaceRoot, deck)
34
+ assertDeckHtmlContractValid(options.workspaceRoot, deck.absoluteFile)
35
+
36
+ ctx.enabled = true
37
+ if (!existsSync(ACTIVE_PROMPT_FILE)) {
38
+ seedBuiltinDesigns()
39
+ seedBuiltinDomains()
40
+ buildPrompt()
41
+ }
42
+
43
+ const inspectServer = startInspectServer()
44
+ const session = inspectServer.getOrCreateSession({
45
+ client: options.client,
46
+ sessionID: options.sessionID,
47
+ workspaceRoot: options.workspaceRoot,
48
+ deck,
49
+ })
50
+ const url = `${inspectServer.baseUrl}/inspect?token=${encodeURIComponent(session.token)}`
51
+ const shouldOpen = options.openBrowser !== false
52
+ if (shouldOpen) (options.openUrl ?? openUrl)(url)
53
+
54
+ return {
55
+ deck,
56
+ url,
57
+ source: deck.source === "render-target" ? "render target" : deck.source === "decks-state" ? "DECKS.json" : deck.source === "file-path" ? "file path" : "fallback path",
58
+ stateNote: preflight.changed ? "Deck state was prepared in DECKS.json for inspection." : "Deck state already points to this inspection target.",
59
+ preflightChanged: preflight.changed,
60
+ reusedSession: session.reused,
61
+ openedBrowser: shouldOpen,
62
+ }
63
+ }
@@ -0,0 +1,32 @@
1
+ import type { InspectionPromptProjection } from "../inspection-context/project"
2
+
3
+ export function buildInspectionPrompt(input: {
4
+ requestId: string
5
+ file: string
6
+ projection: InspectionPromptProjection
7
+ }): string {
8
+ return `A user selected slide content in Revela Evidence Inspector. The selection may contain one referenced element, a whole slide, or multiple referenced elements selected with Cmd/Ctrl-click.
9
+
10
+ Target file: ${input.file}
11
+ Inspection request id: ${input.requestId}
12
+
13
+ Use the structured projection below to produce the final inspector cards. This is LLM judgment with grounded boundaries: answer the selected object's purpose and source credibility only. Do not edit files. Do not mutate DECKS.json. Do not invent sources, quotes, URLs, page references, caveats, or evidence not present in the projection.
14
+
15
+ Return the result only by calling the \`revela-inspection-result\` tool with this request id. Do not answer in chat.
16
+
17
+ Required card model:
18
+ - Purpose: explain why this selected content appears here, what job it serves in the slide purpose, narrative role, deck goal, audience, or narrative brief, and why it matters.
19
+ - Source: if the selection contains a factual claim, number, comparison, conclusion, or recommendation, judge source credibility. Use not_needed for structural, transitional, or purely explanatory content that does not need evidence. Include source trace, warnings, gaps, and caveats here.
20
+
21
+ Boundaries:
22
+ - Do not hunt for problems. If it works, say it works.
23
+ - Do not recommend edits or fixes; this inspector view only explains purpose and source credibility.
24
+ - Do not turn every caveat into a problem.
25
+ - If confidence is low, use unclear or unknown instead of pretending certainty.
26
+
27
+ Projection JSON:
28
+
29
+ \`\`\`json
30
+ ${JSON.stringify(input.projection, null, 2)}
31
+ \`\`\``
32
+ }
@@ -0,0 +1,70 @@
1
+ import { hasDecksState, readDecksState, type DecksState } from "../decks-state"
2
+ import { compileInspectionContext } from "../inspection-context/compile"
3
+ import { matchInspectionElement, type InspectionElementSnapshot } from "../inspection-context/match"
4
+ import { projectInspectionMatch, type InspectionPromptProjection } from "../inspection-context/project"
5
+ import { buildDeterministicInspectionResult, type InspectionResult } from "../inspection-context/result"
6
+
7
+ export interface InspectElementResult {
8
+ requestId?: string
9
+ result: InspectionResult
10
+ }
11
+
12
+ export interface InspectElementProjectionResult {
13
+ requestId?: string
14
+ projection: InspectionPromptProjection
15
+ preprocess: InspectionResult
16
+ }
17
+
18
+ export function inspectElementInState(
19
+ state: DecksState,
20
+ snapshot: InspectionElementSnapshot,
21
+ options: { requestId?: string; staleReason?: string; slug?: string } = {},
22
+ ): InspectElementResult {
23
+ const context = compileInspectionContext(state, options.slug)
24
+ const match = matchInspectionElement(context, snapshot)
25
+ const projection = projectInspectionMatch(context, match, snapshot)
26
+ return {
27
+ requestId: options.requestId,
28
+ result: buildDeterministicInspectionResult(projection, {
29
+ requestId: options.requestId,
30
+ staleReason: options.staleReason,
31
+ }),
32
+ }
33
+ }
34
+
35
+ export function projectElementInState(
36
+ state: DecksState,
37
+ snapshot: InspectionElementSnapshot,
38
+ options: { requestId?: string; slug?: string } = {},
39
+ ): InspectElementProjectionResult {
40
+ const context = compileInspectionContext(state, options.slug)
41
+ const match = matchInspectionElement(context, snapshot)
42
+ const projection = projectInspectionMatch(context, match, snapshot)
43
+ return {
44
+ requestId: options.requestId,
45
+ projection,
46
+ preprocess: buildDeterministicInspectionResult(projection, { requestId: options.requestId }),
47
+ }
48
+ }
49
+
50
+ export function inspectWorkspaceElement(
51
+ workspaceRoot: string,
52
+ snapshot: InspectionElementSnapshot,
53
+ options: { requestId?: string; staleReason?: string; slug?: string } = {},
54
+ ): InspectElementResult {
55
+ if (!hasDecksState(workspaceRoot)) {
56
+ throw new Error("DECKS.json is required before inspection. Run /revela init first.")
57
+ }
58
+ return inspectElementInState(readDecksState(workspaceRoot), snapshot, options)
59
+ }
60
+
61
+ export function projectWorkspaceElement(
62
+ workspaceRoot: string,
63
+ snapshot: InspectionElementSnapshot,
64
+ options: { requestId?: string; slug?: string } = {},
65
+ ): InspectElementProjectionResult {
66
+ if (!hasDecksState(workspaceRoot)) {
67
+ throw new Error("DECKS.json is required before inspection. Run /revela init first.")
68
+ }
69
+ return projectElementInState(readDecksState(workspaceRoot), snapshot, options)
70
+ }
@@ -0,0 +1,86 @@
1
+ import type { InspectionPromptProjection } from "../inspection-context/project"
2
+ import type { InspectionResult } from "../inspection-context/result"
3
+
4
+ export type InspectRequestStatus = "pending" | "completed" | "failed" | "expired"
5
+
6
+ export interface PendingInspectRequest {
7
+ requestId: string
8
+ status: InspectRequestStatus
9
+ projection: InspectionPromptProjection
10
+ deckVersion: string
11
+ createdAt: number
12
+ updatedAt: number
13
+ result?: InspectionResult
14
+ error?: string
15
+ }
16
+
17
+ const REQUEST_TTL_MS = 90 * 1000
18
+ const requests = new Map<string, PendingInspectRequest>()
19
+
20
+ export function createInspectRequest(input: {
21
+ requestId: string
22
+ projection: InspectionPromptProjection
23
+ deckVersion: string
24
+ }): PendingInspectRequest {
25
+ cleanupInspectRequests()
26
+ const now = Date.now()
27
+ const request: PendingInspectRequest = {
28
+ requestId: input.requestId,
29
+ status: "pending",
30
+ projection: input.projection,
31
+ deckVersion: input.deckVersion,
32
+ createdAt: now,
33
+ updatedAt: now,
34
+ }
35
+ requests.set(input.requestId, request)
36
+ return request
37
+ }
38
+
39
+ export function getInspectRequest(requestId: string): PendingInspectRequest | undefined {
40
+ cleanupInspectRequests()
41
+ const request = requests.get(requestId)
42
+ if (!request) return undefined
43
+ if (request.status === "pending" && Date.now() - request.createdAt > REQUEST_TTL_MS) {
44
+ request.status = "expired"
45
+ request.error = "Inspection timed out before the LLM submitted a result."
46
+ request.updatedAt = Date.now()
47
+ }
48
+ return request
49
+ }
50
+
51
+ export function completeInspectRequest(requestId: string, result: InspectionResult): PendingInspectRequest {
52
+ const request = getInspectRequest(requestId)
53
+ if (!request) throw new Error(`Unknown inspection request: ${requestId}`)
54
+ if (request.status !== "pending") throw new Error(`Inspection request is not pending: ${request.status}`)
55
+ request.status = "completed"
56
+ request.result = { ...result, requestId }
57
+ request.updatedAt = Date.now()
58
+ return request
59
+ }
60
+
61
+ export function failInspectRequest(requestId: string, error: string): PendingInspectRequest | undefined {
62
+ const request = getInspectRequest(requestId)
63
+ if (!request || request.status !== "pending") return request
64
+ request.status = "failed"
65
+ request.error = error
66
+ request.updatedAt = Date.now()
67
+ return request
68
+ }
69
+
70
+ export function cleanupInspectRequests(now = Date.now()): void {
71
+ for (const [requestId, request] of requests) {
72
+ if (request.status === "pending" && now - request.createdAt > REQUEST_TTL_MS) {
73
+ request.status = "expired"
74
+ request.error = "Inspection timed out before the LLM submitted a result."
75
+ request.updatedAt = now
76
+ continue
77
+ }
78
+ if (request.status !== "pending" && now - request.updatedAt > REQUEST_TTL_MS) {
79
+ requests.delete(requestId)
80
+ }
81
+ }
82
+ }
83
+
84
+ export function clearInspectRequestsForTests(): void {
85
+ requests.clear()
86
+ }