@cyber-dash-tech/revela 0.8.9 → 0.10.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.
@@ -0,0 +1,263 @@
1
+ import type { NarrativeBrief, NarrativeRole } from "../decks-state"
2
+ import type { InspectionContext, InspectionEvidenceTrace, InspectionGap } from "./compile"
3
+ import type { InspectionElementMatch, InspectionElementSnapshot, InspectionMatchConfidence } from "./match"
4
+
5
+ export interface InspectionPromptProjection {
6
+ version: 1
7
+ deck: InspectionProjectionDeck
8
+ selectedElement: InspectionProjectionElement
9
+ match: InspectionProjectionMatch
10
+ cards: {
11
+ source: InspectionSourceProjection
12
+ evidence: InspectionEvidenceProjection
13
+ caveats: InspectionCaveatsProjection
14
+ objective: InspectionObjectiveProjection
15
+ appendix: InspectionAppendixProjection
16
+ }
17
+ }
18
+
19
+ export interface InspectionProjectionDeck {
20
+ slug: string
21
+ goal: string
22
+ audience?: string
23
+ language?: string
24
+ narrativeBrief?: NarrativeBrief
25
+ }
26
+
27
+ export interface InspectionProjectionElement {
28
+ scope?: "element" | "selection" | "slide"
29
+ slideIndex?: number
30
+ text?: string
31
+ elements?: Array<{
32
+ text?: string
33
+ tagName?: string
34
+ classList: string[]
35
+ role?: string
36
+ }>
37
+ tagName?: string
38
+ classList: string[]
39
+ role?: string
40
+ }
41
+
42
+ export interface InspectionProjectionMatch {
43
+ confidence: InspectionMatchConfidence
44
+ reason: string
45
+ slide?: {
46
+ index: number
47
+ title: string
48
+ purpose?: string
49
+ narrativeRole?: NarrativeRole
50
+ }
51
+ claim?: {
52
+ id: string
53
+ origin: string
54
+ text: string
55
+ evidenceSensitive: boolean
56
+ evidenceSupport: string
57
+ }
58
+ }
59
+
60
+ export interface InspectionSourceProjection {
61
+ evidence: InspectionEvidenceProjectionTrace[]
62
+ missingSourceGaps: InspectionGapProjection[]
63
+ weakSourceGaps: InspectionGapProjection[]
64
+ }
65
+
66
+ export interface InspectionEvidenceProjection {
67
+ matchedClaim?: string
68
+ evidenceSupport?: string
69
+ traces: InspectionEvidenceProjectionTrace[]
70
+ gaps: InspectionGapProjection[]
71
+ }
72
+
73
+ export interface InspectionCaveatsProjection {
74
+ caveats: string[]
75
+ }
76
+
77
+ export interface InspectionObjectiveProjection {
78
+ slidePurpose?: string
79
+ narrativeRole?: NarrativeRole
80
+ deckGoal: string
81
+ audience?: string
82
+ decisionOrAction?: string
83
+ audienceBeliefBefore?: string
84
+ audienceBeliefAfter?: string
85
+ }
86
+
87
+ export interface InspectionAppendixProjection {
88
+ candidates: Array<{
89
+ slideIndex: number
90
+ slideTitle: string
91
+ reason: string
92
+ evidence: InspectionEvidenceProjectionTrace[]
93
+ }>
94
+ relatedRisks: string[]
95
+ relatedObjections: string[]
96
+ }
97
+
98
+ export interface InspectionEvidenceProjectionTrace {
99
+ source: string
100
+ sourcePath?: string
101
+ findingsFile?: string
102
+ location?: string
103
+ page?: string
104
+ url?: string
105
+ quote?: string
106
+ caveat?: string
107
+ extractedTextPath?: string
108
+ extractedManifestPath?: string
109
+ hasDetail: boolean
110
+ }
111
+
112
+ export interface InspectionGapProjection {
113
+ type: string
114
+ claimText: string
115
+ message: string
116
+ }
117
+
118
+ export function projectInspectionMatch(
119
+ context: InspectionContext,
120
+ match: InspectionElementMatch,
121
+ snapshot: InspectionElementSnapshot = {},
122
+ ): InspectionPromptProjection {
123
+ const slide = match.slide
124
+ const claim = match.claim
125
+ const traces = match.evidence.map(projectEvidenceTrace)
126
+ const gaps = match.gaps.map(projectGap)
127
+ const narrativeBrief = context.narrativeBrief
128
+
129
+ return {
130
+ version: 1,
131
+ deck: {
132
+ slug: context.slug,
133
+ goal: truncate(context.goal, 320),
134
+ audience: truncateOptional(context.audience, 160),
135
+ language: truncateOptional(context.language, 80),
136
+ narrativeBrief: narrativeBrief ? projectNarrativeBrief(narrativeBrief) : undefined,
137
+ },
138
+ selectedElement: {
139
+ slideIndex: snapshot.slideIndex,
140
+ scope: snapshot.scope,
141
+ text: truncateOptional(snapshot.selectedText || snapshot.text, 700),
142
+ elements: snapshot.elements?.slice(0, 12).map((item) => ({
143
+ text: truncateOptional(item.text, 320),
144
+ tagName: truncateOptional(item.tagName, 40),
145
+ classList: (item.classList ?? []).slice(0, 8).map((className) => truncate(className, 80)),
146
+ role: truncateOptional(item.role, 80),
147
+ })),
148
+ tagName: truncateOptional(snapshot.tagName, 40),
149
+ classList: (snapshot.classList ?? []).slice(0, 12).map((item) => truncate(item, 80)),
150
+ role: truncateOptional(snapshot.role, 80),
151
+ },
152
+ match: {
153
+ confidence: match.confidence,
154
+ reason: match.reason,
155
+ slide: slide
156
+ ? {
157
+ index: slide.index,
158
+ title: truncate(slide.title, 180),
159
+ purpose: truncateOptional(slide.purpose, 240),
160
+ narrativeRole: slide.narrativeRole,
161
+ }
162
+ : undefined,
163
+ claim: claim
164
+ ? {
165
+ id: claim.id,
166
+ origin: claim.origin,
167
+ text: truncate(claim.text, 500),
168
+ evidenceSensitive: claim.evidenceSensitive,
169
+ evidenceSupport: claim.evidenceSupport,
170
+ }
171
+ : undefined,
172
+ },
173
+ cards: {
174
+ source: {
175
+ evidence: traces,
176
+ missingSourceGaps: gaps.filter((gap) => gap.type === "missing_evidence"),
177
+ weakSourceGaps: gaps.filter((gap) => gap.type === "weak_evidence"),
178
+ },
179
+ evidence: {
180
+ matchedClaim: claim ? truncate(claim.text, 500) : undefined,
181
+ evidenceSupport: claim?.evidenceSupport,
182
+ traces,
183
+ gaps,
184
+ },
185
+ caveats: {
186
+ caveats: match.caveats.map((item) => truncate(item, 280)).slice(0, 8),
187
+ },
188
+ objective: {
189
+ slidePurpose: truncateOptional(slide?.purpose, 240),
190
+ narrativeRole: slide?.narrativeRole,
191
+ deckGoal: truncate(context.goal, 320),
192
+ audience: truncateOptional(context.audience, 160),
193
+ decisionOrAction: truncateOptional(narrativeBrief?.decisionOrAction, 240),
194
+ audienceBeliefBefore: truncateOptional(narrativeBrief?.audienceBeliefBefore, 240),
195
+ audienceBeliefAfter: truncateOptional(narrativeBrief?.audienceBeliefAfter, 240),
196
+ },
197
+ appendix: {
198
+ candidates: match.appendixCandidates.slice(0, 5).map((candidate) => ({
199
+ slideIndex: candidate.slideIndex,
200
+ slideTitle: truncate(candidate.slideTitle, 180),
201
+ reason: truncate(candidate.reason, 240),
202
+ evidence: candidate.evidence.map(projectEvidenceTrace),
203
+ })),
204
+ relatedRisks: relatedNarrativeText(context.riskContext, slide?.index),
205
+ relatedObjections: relatedNarrativeText(context.objectionContext, slide?.index),
206
+ },
207
+ },
208
+ }
209
+ }
210
+
211
+ function projectEvidenceTrace(trace: InspectionEvidenceTrace): InspectionEvidenceProjectionTrace {
212
+ return {
213
+ source: truncate(trace.source, 180),
214
+ sourcePath: truncateOptional(trace.sourcePath, 220),
215
+ findingsFile: truncateOptional(trace.findingsFile, 220),
216
+ location: truncateOptional(trace.location, 120),
217
+ page: truncateOptional(trace.page, 80),
218
+ url: truncateOptional(trace.url, 240),
219
+ quote: truncateOptional(trace.quote, 500),
220
+ caveat: truncateOptional(trace.caveat, 280),
221
+ extractedTextPath: truncateOptional(trace.extractedTextPath, 220),
222
+ extractedManifestPath: truncateOptional(trace.extractedManifestPath, 220),
223
+ hasDetail: trace.hasDetail,
224
+ }
225
+ }
226
+
227
+ function projectGap(gap: InspectionGap): InspectionGapProjection {
228
+ return {
229
+ type: gap.type,
230
+ claimText: truncate(gap.claimText, 500),
231
+ message: truncate(gap.message, 280),
232
+ }
233
+ }
234
+
235
+ function projectNarrativeBrief(brief: NarrativeBrief): NarrativeBrief {
236
+ return {
237
+ audienceBeliefBefore: truncateOptional(brief.audienceBeliefBefore, 240),
238
+ audienceBeliefAfter: truncateOptional(brief.audienceBeliefAfter, 240),
239
+ decisionOrAction: truncateOptional(brief.decisionOrAction, 240),
240
+ narrativeArc: truncateOptional(brief.narrativeArc, 240),
241
+ keyClaims: brief.keyClaims.map((item) => truncate(item, 240)).slice(0, 8),
242
+ objections: brief.objections.map((item) => truncate(item, 240)).slice(0, 8),
243
+ risks: brief.risks.map((item) => truncate(item, 240)).slice(0, 8),
244
+ }
245
+ }
246
+
247
+ function relatedNarrativeText(items: InspectionContext["riskContext"], slideIndex: number | undefined): string[] {
248
+ return items
249
+ .filter((item) => item.slideIndex === undefined || item.slideIndex === slideIndex)
250
+ .map((item) => truncate(item.text, 240))
251
+ .slice(0, 6)
252
+ }
253
+
254
+ function truncateOptional(value: string | undefined, max: number): string | undefined {
255
+ if (value === undefined) return undefined
256
+ return truncate(value, max)
257
+ }
258
+
259
+ function truncate(value: string, max: number): string {
260
+ const text = String(value ?? "").replace(/\s+/g, " ").trim()
261
+ if (text.length <= max) return text
262
+ return text.slice(0, Math.max(0, max - 3)).trimEnd() + "..."
263
+ }
@@ -0,0 +1,160 @@
1
+ import type { InspectionMatchConfidence } from "./match"
2
+ import type { InspectionPromptProjection } from "./project"
3
+
4
+ export type InspectionResultStatus = "success" | "no_match"
5
+ export type InspectionPurposeStatus = "clear" | "weak" | "misplaced" | "unknown"
6
+ export type InspectionSourceStatus = "supported" | "weak" | "unsupported" | "not_needed" | "unknown"
7
+
8
+ export interface InspectionResult {
9
+ version: 1
10
+ requestId?: string
11
+ status: InspectionResultStatus
12
+ selectedText?: string
13
+ slide?: {
14
+ index: number
15
+ title: string
16
+ }
17
+ matchConfidence: InspectionMatchConfidence
18
+ cards: {
19
+ purpose: PurposeCard
20
+ source: SourceCard
21
+ }
22
+ stale?: {
23
+ stale: boolean
24
+ reason?: string
25
+ }
26
+ }
27
+
28
+ export interface SupportSourceItem {
29
+ source: string
30
+ sourcePath?: string
31
+ findingsFile?: string
32
+ location?: string
33
+ page?: string
34
+ url?: string
35
+ quote?: string
36
+ caveat?: string
37
+ }
38
+
39
+ export interface PurposeCard {
40
+ status: InspectionPurposeStatus
41
+ role?: string
42
+ rationale: string
43
+ whyItMatters: string
44
+ }
45
+
46
+ export interface SourceCard {
47
+ status: InspectionSourceStatus
48
+ matchedClaim?: string
49
+ sources: SupportSourceItem[]
50
+ warnings: string[]
51
+ gaps: string[]
52
+ caveats: string[]
53
+ rationale: string
54
+ }
55
+
56
+ export function buildDeterministicInspectionResult(
57
+ projection: InspectionPromptProjection,
58
+ options: { requestId?: string; staleReason?: string } = {},
59
+ ): InspectionResult {
60
+ const slide = projection.match.slide
61
+ const evidence = projection.cards.source.evidence
62
+ const gaps = projection.cards.evidence.gaps
63
+ const missingGaps = projection.cards.source.missingSourceGaps
64
+ const weakGaps = projection.cards.source.weakSourceGaps
65
+ const noMatch = projection.match.confidence === "none" || !slide
66
+
67
+ return {
68
+ version: 1,
69
+ requestId: options.requestId,
70
+ status: noMatch ? "no_match" : "success",
71
+ selectedText: projection.selectedElement.text,
72
+ slide: slide ? { index: slide.index, title: slide.title } : undefined,
73
+ matchConfidence: projection.match.confidence,
74
+ cards: {
75
+ purpose: {
76
+ status: purposeStatus(projection, noMatch),
77
+ role: projection.cards.objective.narrativeRole,
78
+ rationale: purposeRationale(projection, noMatch),
79
+ whyItMatters: purposeWhyItMatters(projection, noMatch),
80
+ },
81
+ source: {
82
+ status: sourceStatus(projection, noMatch),
83
+ matchedClaim: projection.cards.evidence.matchedClaim,
84
+ sources: evidence.map((item) => ({
85
+ source: item.source,
86
+ sourcePath: item.sourcePath,
87
+ findingsFile: item.findingsFile,
88
+ location: item.location,
89
+ page: item.page,
90
+ url: item.url,
91
+ quote: item.quote,
92
+ caveat: item.caveat,
93
+ })),
94
+ warnings: sourceWarnings(missingGaps.length, weakGaps.length, noMatch),
95
+ gaps: gaps.map((gap) => gap.message),
96
+ caveats: sourceCaveats(projection),
97
+ rationale: sourceRationale(projection, noMatch),
98
+ },
99
+ },
100
+ stale: options.staleReason ? { stale: true, reason: options.staleReason } : undefined,
101
+ }
102
+ }
103
+
104
+ function purposeStatus(projection: InspectionPromptProjection, noMatch: boolean): InspectionPurposeStatus {
105
+ if (noMatch) return "unknown"
106
+ if (projection.cards.objective.narrativeRole || projection.cards.objective.slidePurpose) return "clear"
107
+ return "unknown"
108
+ }
109
+
110
+ function purposeRationale(projection: InspectionPromptProjection, noMatch: boolean): string {
111
+ if (noMatch) return "No matched slide is available to explain why this selection appears here."
112
+ const role = projection.cards.objective.narrativeRole
113
+ const purpose = projection.cards.objective.slidePurpose
114
+ if (role && purpose) return `The selection appears inside a ${role} slide whose recorded purpose is: ${purpose}`
115
+ if (purpose) return `The selection appears on a slide whose recorded purpose is: ${purpose}`
116
+ if (role) return `The selection appears inside a slide with recorded narrative role: ${role}`
117
+ return "The deterministic fallback cannot explain placement because the slide has no recorded purpose or narrative role."
118
+ }
119
+
120
+ function purposeWhyItMatters(projection: InspectionPromptProjection, noMatch: boolean): string {
121
+ if (noMatch) return "Without a matched slide, the inspector cannot connect this selection to the deck narrative."
122
+ const deckGoal = projection.cards.objective.deckGoal
123
+ const audience = projection.cards.objective.audience
124
+ if (deckGoal && audience) return `It matters because this selection contributes to the deck goal (${deckGoal}) for ${audience}.`
125
+ if (deckGoal) return `It matters because this selection contributes to the deck goal: ${deckGoal}`
126
+ if (audience) return `It matters because this selection is part of the message being shaped for ${audience}.`
127
+ return "It matters as part of this slide's communication job, but the deterministic fallback has limited deck-level intent metadata."
128
+ }
129
+
130
+ function sourceStatus(projection: InspectionPromptProjection, noMatch: boolean): InspectionSourceStatus {
131
+ if (noMatch) return "unknown"
132
+ if (projection.cards.source.missingSourceGaps.length > 0) return "unsupported"
133
+ if (projection.cards.source.weakSourceGaps.length > 0) return "weak"
134
+ if (projection.cards.evidence.traces.some((item) => item.hasDetail)) return "supported"
135
+ return projection.match.claim?.evidenceSensitive ? "unsupported" : "not_needed"
136
+ }
137
+
138
+ function sourceWarnings(missingGapCount: number, weakGapCount: number, noMatch: boolean): string[] {
139
+ if (noMatch) return ["No slide or claim matched the selected element."]
140
+ const warnings: string[] = []
141
+ if (missingGapCount > 0) warnings.push("Matched evidence-sensitive claim has no slide-level evidence trace.")
142
+ if (weakGapCount > 0) warnings.push("Matched evidence is source-only and lacks quote, location, URL, caveat, findings file, or source path detail.")
143
+ return warnings
144
+ }
145
+
146
+ function sourceRationale(projection: InspectionPromptProjection, noMatch: boolean): string {
147
+ if (noMatch) return "No matched claim is available for support assessment."
148
+ if (projection.cards.source.missingSourceGaps.length > 0) return "The selected evidence-sensitive wording is unsupported in the current slide state."
149
+ if (projection.cards.source.weakSourceGaps.length > 0) return "The selected wording has evidence, but the trace is source-only or missing inspection detail."
150
+ if (projection.cards.evidence.traces.some((item) => item.hasDetail)) return "The selected wording has detailed slide-level evidence trace, including source detail where available."
151
+ return "The selection is not clearly evidence-sensitive, so source support is not needed by the deterministic fallback."
152
+ }
153
+
154
+ function sourceCaveats(projection: InspectionPromptProjection): string[] {
155
+ return [
156
+ ...projection.cards.caveats.caveats,
157
+ ...projection.cards.appendix.relatedRisks,
158
+ ...projection.cards.appendix.relatedObjections,
159
+ ].slice(0, 10)
160
+ }
@@ -0,0 +1,68 @@
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 { seedBuiltinDomains } from "../domain/domains"
6
+ import { ensureEditableDeckState } from "../edit/deck-state"
7
+ import { openUrl } from "../edit/open"
8
+ import { resolveEditableDeck, type EditableDeck } from "../edit/resolve-deck"
9
+ import { buildPrompt } from "../prompt-builder"
10
+ import { startRefineServer, type RefineMode } from "./server"
11
+
12
+ export interface OpenRefineDeckResult {
13
+ deck: EditableDeck
14
+ url: string
15
+ source: string
16
+ stateNote: string
17
+ preflightChanged: boolean
18
+ reusedSession: boolean
19
+ liveSession: boolean
20
+ openedBrowser: boolean
21
+ mode: RefineMode
22
+ }
23
+
24
+ export interface OpenRefineDeckOptions {
25
+ client: any
26
+ sessionID: string
27
+ workspaceRoot: string
28
+ mode?: RefineMode
29
+ openBrowser?: boolean
30
+ openUrl?: (url: string) => void
31
+ }
32
+
33
+ export function openRefineDeck(target: string, options: OpenRefineDeckOptions): OpenRefineDeckResult {
34
+ const deck = resolveEditableDeck(options.workspaceRoot, target)
35
+ const preflight = ensureEditableDeckState(options.workspaceRoot, deck)
36
+ const mode = options.mode ?? "edit"
37
+
38
+ ctx.enabled = true
39
+ if (!existsSync(ACTIVE_PROMPT_FILE)) {
40
+ seedBuiltinDesigns()
41
+ seedBuiltinDomains()
42
+ buildPrompt()
43
+ }
44
+
45
+ const refineServer = startRefineServer()
46
+ const session = refineServer.getOrCreateSession({
47
+ client: options.client,
48
+ sessionID: options.sessionID,
49
+ workspaceRoot: options.workspaceRoot,
50
+ deck,
51
+ mode,
52
+ })
53
+ const url = `${refineServer.baseUrl}/refine?token=${encodeURIComponent(session.token)}`
54
+ const shouldOpen = options.openBrowser !== false
55
+ if (shouldOpen) (options.openUrl ?? openUrl)(url)
56
+
57
+ return {
58
+ deck,
59
+ url,
60
+ source: deck.source === "decks-state" ? "DECKS.json" : deck.source === "file-path" ? "file path" : "fallback path",
61
+ stateNote: preflight.changed ? "Deck state was prepared in DECKS.json for refinement." : "Deck state already points to this refinement target.",
62
+ preflightChanged: preflight.changed,
63
+ reusedSession: session.reused,
64
+ liveSession: session.live,
65
+ openedBrowser: shouldOpen,
66
+ mode,
67
+ }
68
+ }