@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.
- package/README.md +54 -9
- package/README.zh-CN.md +54 -9
- package/designs/monet/DESIGN.md +9 -9
- package/designs/starter/DESIGN.md +8 -8
- package/designs/summit/DESIGN.md +9 -9
- package/lib/commands/help.ts +2 -0
- package/lib/commands/inspect.ts +23 -0
- package/lib/commands/pdf.ts +33 -5
- package/lib/commands/pptx.ts +14 -9
- package/lib/commands/refine.ts +26 -0
- package/lib/commands/review.ts +8 -2
- package/lib/deck-html/contract.ts +252 -0
- package/lib/decks-state.ts +574 -31
- package/lib/document-materials/extract.ts +20 -0
- package/lib/edit/resolve-deck.ts +13 -2
- package/lib/inspect/open.ts +63 -0
- package/lib/inspect/prompt.ts +32 -0
- package/lib/inspect/request.ts +70 -0
- package/lib/inspect/requests.ts +86 -0
- package/lib/inspect/server.ts +1063 -0
- package/lib/inspect/slide-index.ts +12 -0
- package/lib/inspection-context/compile.ts +346 -0
- package/lib/inspection-context/match.ts +169 -0
- package/lib/inspection-context/project.ts +263 -0
- package/lib/inspection-context/result.ts +160 -0
- package/lib/qa/export-gate.ts +8 -1
- package/lib/refine/open.ts +70 -0
- package/lib/refine/server.ts +1581 -0
- 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 +48 -1
- package/skill/SKILL.md +10 -5
- package/tools/decks.ts +61 -2
- package/tools/inspection-context.ts +22 -0
- package/tools/inspection-result.ts +63 -0
- 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
|
@@ -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
|
|
package/lib/edit/resolve-deck.ts
CHANGED
|
@@ -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
|
+
}
|