@cyber-dash-tech/revela 0.13.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/edit.ts +7 -5
- package/lib/commands/help.ts +3 -3
- package/lib/commands/inspect.ts +7 -5
- package/lib/inspect/prompt.ts +15 -2
- package/lib/inspect/requests.ts +21 -2
- package/lib/inspection-context/compile.ts +71 -5
- package/lib/inspection-context/match.ts +71 -1
- package/lib/inspection-context/project.ts +116 -13
- package/lib/inspection-context/result.ts +183 -0
- package/lib/narrative-state/queries.ts +1 -0
- package/lib/refine/server.ts +91 -13
- package/package.json +1 -1
- package/plugin.ts +2 -2
- package/tools/edit.ts +10 -8
- package/tools/inspection-result.ts +37 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
@@ -145,9 +145,9 @@ Disable presentation mode when done:
|
|
|
145
145
|
/revela deck start deck handoff from approved narrative
|
|
146
146
|
/revela deck --review review deck/artifact readiness before writing HTML
|
|
147
147
|
/revela remember <text> save an explicit user/workflow preference
|
|
148
|
-
/revela refine open unified
|
|
149
|
-
/revela edit
|
|
150
|
-
/revela inspect
|
|
148
|
+
/revela refine open unified reading, inspection, and editing workspace
|
|
149
|
+
/revela edit deprecated shim to /revela refine Edit mode
|
|
150
|
+
/revela inspect deprecated shim to /revela refine Inspect mode
|
|
151
151
|
|
|
152
152
|
/revela designs list installed designs
|
|
153
153
|
/revela designs <name> activate a design
|
|
@@ -166,7 +166,7 @@ Disable presentation mode when done:
|
|
|
166
166
|
/revela pptx <file> export an HTML deck to editable PPTX in the same directory
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
Most `/revela` commands run locally with zero LLM cost. `/revela init`, `/revela review`, `/revela deck`, `/revela remember`, `/revela designs-new`, and `/revela designs-edit` start AI-assisted workflows because they need to read or update project files. `/revela refine` opens a local browser workspace with Edit and Inspect tabs that share the same Cmd/Ctrl-click element references. Edit sends targeted comments back into the current OpenCode session; Inspect renders
|
|
169
|
+
Most `/revela` commands run locally with zero LLM cost. `/revela init`, `/revela review`, `/revela deck`, `/revela remember`, `/revela designs-new`, and `/revela designs-edit` start AI-assisted workflows because they need to read or update project files. `/revela refine` is the unified post-artifact workspace. It opens a local browser workspace with Edit and Inspect tabs that share the same Cmd/Ctrl-click element references. Edit sends targeted comments back into the current OpenCode session; Inspect sends grounded selection context to the current OpenCode session and renders localized Narrative Reading, Exploratory Reading, Source, and Purpose cards, has no chat box, and does not edit the deck. Deterministic preprocessing is kept as fallback context rather than the normal first UI. If a generated result omits newer reading cards, Refine keeps the deterministic Narrative Reading and Exploratory Reading cards instead of dropping context. Narrative Reading also shows artifact coverage for the selected canonical claim, including whether each recorded artifact contains the claim and whether coverage is current, stale, partial, or missing. Exploratory Reading is explicitly non-official and bounded to recorded claims, evidence, caveats, objections, risks, and artifact coverage. `/revela edit` and `/revela inspect` remain only as deprecated compatibility shims to Refine.
|
|
170
170
|
|
|
171
171
|
---
|
|
172
172
|
|
|
@@ -214,8 +214,8 @@ Use Revela as a narrative-first artifact workflow:
|
|
|
214
214
|
5. Run `/revela deck` to compile the approved narrative into deck slide specs and enter deck-render mode.
|
|
215
215
|
6. Choose or confirm design only during deck handoff, then run the deck/artifact gate with `/revela deck --review` or the handoff workflow.
|
|
216
216
|
7. Let the agent write the HTML deck under `decks/` only after the artifact gate is ready.
|
|
217
|
-
8. Use `/revela refine` for visual comments, targeted revisions,
|
|
218
|
-
9. Use `/revela edit` or `/revela inspect`
|
|
217
|
+
8. Use `/revela refine` for visual comments, targeted revisions, read-only Narrative Reading, bounded Exploratory Reading, Source, and Purpose inspection, and claim-to-artifact coverage for selected deck elements.
|
|
218
|
+
9. Use `/revela edit` or `/revela inspect` only for old scripts or habits; both open `/revela refine` in the matching mode.
|
|
219
219
|
10. Export with `/revela pdf <file>` or `/revela pptx <file>`.
|
|
220
220
|
|
|
221
221
|
`/revela review` checks narrative readiness: unclear audience, missing belief shift, missing decision/action, weak thesis, unsupported central claims, weak evidence, unsupported scope, unhandled objections, missing risk/assumption handling, stale approval, or missing approval. It does not review design/layout readiness and does not write the final deck.
|
|
@@ -591,19 +591,19 @@ Use the unified refinement workspace for normal post-write review and revision:
|
|
|
591
591
|
/revela refine
|
|
592
592
|
```
|
|
593
593
|
|
|
594
|
-
`/revela refine` opens the
|
|
594
|
+
`/revela refine` opens the active HTML deck with two tabs. Use `Ctrl`/`Cmd` + click once to reference deck elements, then choose Edit for fast natural-language change comments or Inspect for read-only Narrative Reading, bounded Exploratory Reading, Source, Purpose, and artifact coverage review. Inspect does not mutate the deck; Edit remains the mutation path. This is the recommended entry for post-artifact reading, inspection, and editing.
|
|
595
595
|
|
|
596
|
-
|
|
596
|
+
Deprecated compatibility command:
|
|
597
597
|
|
|
598
598
|
```text
|
|
599
599
|
/revela edit
|
|
600
600
|
```
|
|
601
601
|
|
|
602
|
-
`/revela edit`
|
|
602
|
+
`/revela edit` no longer opens a separate edit-only shell. It opens `/revela refine` in Edit mode for compatibility with old scripts and user habits.
|
|
603
603
|
|
|
604
|
-
|
|
604
|
+
Use `Ctrl`/`Cmd` + click to reference deck elements, write a natural-language comment in the Edit tab, then send it back to OpenCode. Revela sends a structured edit prompt that includes the deck file, slide context, selected element metadata, and your comment.
|
|
605
605
|
|
|
606
|
-
LLM tool equivalent: `revela-edit` with no target.
|
|
606
|
+
LLM tool equivalent: `revela-edit` with no target. The tool is also a compatibility shim and opens Refine in Edit mode when you say things like “I want to edit the deck”.
|
|
607
607
|
|
|
608
608
|
For existing decks, `/revela edit` prepares whatever minimal project context is needed so targeted edits can still use the normal safety checks.
|
|
609
609
|
|
|
@@ -611,17 +611,17 @@ For existing decks, `/revela edit` prepares whatever minimal project context is
|
|
|
611
611
|
|
|
612
612
|
## Evidence Inspector
|
|
613
613
|
|
|
614
|
-
|
|
614
|
+
Use `/revela refine` for evidence inspection and narrative reading. Deprecated compatibility command:
|
|
615
615
|
|
|
616
616
|
```text
|
|
617
617
|
/revela inspect
|
|
618
618
|
```
|
|
619
619
|
|
|
620
|
-
|
|
620
|
+
`/revela inspect` no longer opens a separate inspector shell. It opens `/revela refine` in Inspect mode. The Inspect tab shows Narrative Reading and Exploratory Reading cards alongside the fixed Source and Purpose cards. Narrative Reading preserves canonical claim ids, evidence binding ids, supported scope, unsupported scope, caveats, objections, risks, and artifact coverage when the selected element maps to canonical narrative state. Coverage shows whether the selected claim appears in recorded deck/brief/export artifacts and whether those artifacts are current, stale, partial, or missing against the current narrative hash. Exploratory Reading provides non-official objection prep, audience reframing boundaries, appendix leads, and meeting-prep cues from the same recorded context only. Use `Ctrl`/`Cmd` + click to reference deck elements, then click `Inspect Selection`. Selection is locked while the request is being processed.
|
|
621
621
|
|
|
622
|
-
The inspector is not chat and has no freeform prompt. It does not mutate `DECKS.json` or the deck HTML. It uses recorded slide specs, narrative state, and slide-level evidence trace as grounded context. Deterministic preprocessing
|
|
622
|
+
The inspector is not chat and has no freeform prompt. It does not mutate `DECKS.json` or the deck HTML. It uses recorded slide specs, narrative state, and slide-level evidence trace as grounded context. Inspect is LLM-first in the UI: it shows a reading/loading state, then renders structured generated cards. Deterministic preprocessing remains internal fallback context and is shown only if generation fails or times out. The Inspect tab includes a fixed display-language selector; language changes affect card copy only and never alter claim ids, evidence ids, source paths, URLs, numbers, quotes, or canonical facts. When an older or partial generated result only returns Source/Purpose, Refine preserves the deterministic reading cards so generated inspection cannot silently remove claim, evidence-boundary, artifact-coverage, or exploratory context.
|
|
623
623
|
|
|
624
|
-
|
|
624
|
+
Refine uses the active HTML deck render target recorded in workspace state. The deck HTML must satisfy Revela's slide identity contract: every `<section class="slide">` in the active artifact needs a positive 1-based `data-slide-index` matching the current slide specs. Invalid active artifacts are refused or reported before refine/export workflows trust them.
|
|
625
625
|
|
|
626
626
|
---
|
|
627
627
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="800" />
|
|
@@ -144,9 +144,9 @@ export { default } from "/absolute/path/to/revela/index.ts";
|
|
|
144
144
|
/revela deck 从已批准 narrative 开始 deck handoff
|
|
145
145
|
/revela deck --review 写 HTML 前检查 deck/artifact readiness
|
|
146
146
|
/revela remember <text> 保存明确的用户/工作流偏好
|
|
147
|
-
/revela refine
|
|
148
|
-
/revela edit
|
|
149
|
-
/revela inspect
|
|
147
|
+
/revela refine 打开统一的阅读、检查和编辑 workspace
|
|
148
|
+
/revela edit deprecated,兼容到 /revela refine Edit mode
|
|
149
|
+
/revela inspect deprecated,兼容到 /revela refine Inspect mode
|
|
150
150
|
|
|
151
151
|
/revela designs 列出已安装 design
|
|
152
152
|
/revela designs <name> 激活某个 design
|
|
@@ -165,7 +165,7 @@ export { default } from "/absolute/path/to/revela/index.ts";
|
|
|
165
165
|
/revela pptx <file> 将 HTML deck 导出为同目录可编辑 PPTX
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
大多数 `/revela` 命令都在本地执行,不消耗 LLM token。`/revela init`、`/revela review`、`/revela deck`、`/revela remember`、`/revela designs-new` 和 `/revela designs-edit` 会启动 AI 辅助流程,因为它们需要读取或更新项目状态。`/revela refine`
|
|
168
|
+
大多数 `/revela` 命令都在本地执行,不消耗 LLM token。`/revela init`、`/revela review`、`/revela deck`、`/revela remember`、`/revela designs-new` 和 `/revela designs-edit` 会启动 AI 辅助流程,因为它们需要读取或更新项目状态。`/revela refine` 是统一的 post-artifact workspace,会打开一个本地浏览器 workspace,里面有 Edit 和 Inspect 两个 tab,并共享同一套 Cmd/Ctrl-click 元素引用。Edit 会把精准修改评论发回当前 OpenCode 会话;Inspect 会把 grounded selection context 发给当前 OpenCode 会话,并渲染本地化的 Narrative Reading、Exploratory Reading、Source、Purpose 卡片。确定性预处理保留为 fallback context,而不是默认先展示的 UI。如果生成结果缺少较新的 reading 卡片,Refine 会保留确定性 Narrative Reading 和 Exploratory Reading,而不是丢掉这些上下文。Narrative Reading 还会显示所选 canonical claim 的 artifact coverage,包括每个已记录 artifact 是否包含该 claim,以及 coverage 是 current、stale、partial 还是 missing。Exploratory Reading 明确是非官方阅读辅助,只能基于已记录 claim、evidence、caveat、objection、risk 和 artifact coverage。它没有聊天框,也不会修改 deck。`/revela edit` 和 `/revela inspect` 只作为 deprecated 兼容入口保留。
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
@@ -213,8 +213,8 @@ Deck 仍然是主要 authored artifact,但现在它是从同一份 workspace s
|
|
|
213
213
|
5. 运行 `/revela deck`,把已批准 narrative 编译成 deck slide specs,并进入 deck-render mode。
|
|
214
214
|
6. 只在 deck handoff 阶段选择或确认 design,然后通过 handoff workflow 或 `/revela deck --review` 运行 deck/artifact gate。
|
|
215
215
|
7. 只有 artifact gate ready 后,才让 agent 把 HTML deck 写到 `decks/` 下。
|
|
216
|
-
8. 用 `/revela refine` 对选中 deck
|
|
217
|
-
9.
|
|
216
|
+
8. 用 `/revela refine` 对选中 deck 元素做可视化评论、精准修改、只读 Narrative Reading、有边界的 Exploratory Reading、Source、Purpose 检查,以及 claim-to-artifact coverage 查看。
|
|
217
|
+
9. 只有旧脚本或旧习惯需要时,才使用 `/revela edit` 或 `/revela inspect`;两者都会打开对应模式的 `/revela refine`。
|
|
218
218
|
10. 用 `/revela pdf <file>` 或 `/revela pptx <file>` 导出。
|
|
219
219
|
|
|
220
220
|
`/revela review` 检查的是 narrative readiness:受众不清、缺信念变化、缺 decision/action、thesis 弱、central claims 无证据、evidence 弱、unsupported scope、objection 未处理、缺风险/假设处理、approval stale 或缺 approval。它不检查 design/layout readiness,也不会写最终 deck。
|
|
@@ -556,19 +556,19 @@ Prompt 注入规则:
|
|
|
556
556
|
/revela refine
|
|
557
557
|
```
|
|
558
558
|
|
|
559
|
-
`/revela refine` 会打开
|
|
559
|
+
`/revela refine` 会打开 active HTML deck,并提供两个 tab。使用 `Ctrl`/`Cmd` + click 先引用 deck 元素,然后在 Edit 里快速写自然语言修改评论,或在 Inspect 里做只读 Narrative Reading、有边界的 Exploratory Reading、Source、Purpose 和 artifact coverage 检查。Inspect 不会修改 deck;真正的 mutation 仍然只走 Edit。这是 post-artifact 阅读、检查和编辑的推荐入口。
|
|
560
560
|
|
|
561
|
-
|
|
561
|
+
Deprecated 兼容命令:
|
|
562
562
|
|
|
563
563
|
```text
|
|
564
564
|
/revela edit
|
|
565
565
|
```
|
|
566
566
|
|
|
567
|
-
|
|
567
|
+
`/revela edit` 不再打开独立的 edit-only shell。它会打开 `/revela refine` 的 Edit mode,用于兼容旧脚本和旧使用习惯。
|
|
568
568
|
|
|
569
|
-
|
|
569
|
+
使用 `Ctrl`/`Cmd` + 点击 deck 元素来引用它们,在 Edit tab 写一段自然语言评论,然后发送回 OpenCode。Revela 会把 deck 文件、slide 上下文、选中元素 metadata 和你的评论整理成结构化 edit prompt。
|
|
570
570
|
|
|
571
|
-
对应的 LLM tool:`revela-edit`,不需要 target
|
|
571
|
+
对应的 LLM tool:`revela-edit`,不需要 target。这个 tool 也是兼容入口,当你说“我要编辑这个 deck”时,agent 会打开 Refine 的 Edit mode。
|
|
572
572
|
|
|
573
573
|
对于已有 HTML deck,`/revela edit` 会自动准备必要的最小项目上下文,让后续精准修改仍然经过正常安全检查。
|
|
574
574
|
|
|
@@ -576,17 +576,17 @@ Revela 0.8 中 `/revela edit` 不接受 target。如果 `decks/` 下正好有一
|
|
|
576
576
|
|
|
577
577
|
## Evidence Inspector
|
|
578
578
|
|
|
579
|
-
|
|
579
|
+
用 `/revela refine` 做 evidence inspection 和 narrative reading。Deprecated 兼容命令:
|
|
580
580
|
|
|
581
581
|
```text
|
|
582
582
|
/revela inspect
|
|
583
583
|
```
|
|
584
584
|
|
|
585
|
-
|
|
585
|
+
`/revela inspect` 不再打开独立 inspector shell。它会打开 `/revela refine` 的 Inspect mode。Inspect tab 会在固定 Source 和 Purpose 卡片之外显示 Narrative Reading 和 Exploratory Reading 卡片。当选中元素能映射到 canonical narrative state 时,Narrative Reading 会保留 canonical claim id、evidence binding id、supported scope、unsupported scope、caveat、objection、risk 和 artifact coverage。Coverage 会显示所选 claim 是否出现在已记录的 deck/brief/export artifact 中,以及这些 artifact 相对当前 narrative hash 是 current、stale、partial 还是 missing。Exploratory Reading 提供非官方的 objection prep、audience reframing 边界、appendix leads 和 meeting-prep cues,并且只能使用同一份已记录 context。使用 `Ctrl`/`Cmd` + click 引用 deck 元素,然后点击 `Inspect Selection`。请求处理期间,deck 选择会被锁定。
|
|
586
586
|
|
|
587
|
-
Inspector 不是聊天,也没有自由输入框。它不会修改 `DECKS.json` 或 deck HTML。它使用已记录的 slide spec、narrative state 和 slide-level evidence trace 作为 grounded context
|
|
587
|
+
Inspector 不是聊天,也没有自由输入框。它不会修改 `DECKS.json` 或 deck HTML。它使用已记录的 slide spec、narrative state 和 slide-level evidence trace 作为 grounded context。Inspect 的用户界面是 LLM-first:先显示 reading/loading 状态,再渲染结构化生成卡片。确定性预处理仍作为内部 fallback context,仅在生成失败或超时时显示。Inspect tab 提供固定 display-language 下拉选项;语言只影响卡片文案,不会改变 claim id、evidence id、source path、URL、数字、引用或 canonical facts。如果旧版或不完整的生成结果只返回 Source/Purpose,Refine 会保留确定性 reading 卡片,避免 generated inspection 静默移除 claim、evidence boundary、artifact coverage 或 exploratory context。
|
|
588
588
|
|
|
589
|
-
|
|
589
|
+
Refine 会使用 workspace state 中记录的 active HTML deck render target。Deck HTML 必须满足 Revela 的 slide identity contract:active artifact 中每个 `<section class="slide">` 都需要有正数、1-based 的 `data-slide-index`,并且要匹配当前 slide specs。无效的 active artifact 会在 refine/export 工作流信任它之前被拒绝或报告。
|
|
590
590
|
|
|
591
591
|
---
|
|
592
592
|
|
package/lib/commands/edit.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { openRefineDeck } from "../refine/open"
|
|
2
2
|
|
|
3
3
|
export async function handleEdit(
|
|
4
|
-
options: { client: any; sessionID: string; workspaceRoot: string },
|
|
4
|
+
options: { client: any; sessionID: string; workspaceRoot: string; openBrowser?: boolean },
|
|
5
5
|
send: (text: string) => Promise<void>,
|
|
6
6
|
): Promise<void> {
|
|
7
7
|
try {
|
|
8
|
-
const result =
|
|
8
|
+
const result = openRefineDeck("", {
|
|
9
9
|
client: options.client,
|
|
10
10
|
sessionID: options.sessionID,
|
|
11
11
|
workspaceRoot: options.workspaceRoot,
|
|
12
|
+
mode: "edit",
|
|
13
|
+
openBrowser: options.openBrowser,
|
|
12
14
|
})
|
|
13
15
|
|
|
14
16
|
await send(
|
|
15
|
-
|
|
17
|
+
`\`/revela edit\` is deprecated. Opened \`/revela refine\` in Edit mode for the active HTML deck.\n` +
|
|
16
18
|
`File: \`${result.deck.file}\`\n` +
|
|
17
19
|
`${result.stateNote}\n` +
|
|
18
20
|
`URL: ${result.url}\n\n` +
|
|
19
|
-
`Use Ctrl/Cmd
|
|
21
|
+
`Use \`/revela refine\` directly going forward. Use Ctrl/Cmd-click in the browser to reference elements, then use the Edit tab to send targeted change comments.`
|
|
20
22
|
)
|
|
21
23
|
} catch (e: any) {
|
|
22
24
|
await send(`**Edit failed:** ${e.message || String(e)}`)
|
package/lib/commands/help.ts
CHANGED
|
@@ -32,9 +32,9 @@ export async function handleHelp(
|
|
|
32
32
|
`\`/revela brief [file.md]\` — render executive brief from approved narrative\n` +
|
|
33
33
|
`\`/revela deck\` — start deck handoff from approved narrative\n` +
|
|
34
34
|
`\`/revela deck --review\` — review deck/artifact readiness before writing HTML\n` +
|
|
35
|
-
`\`/revela refine\` — open unified
|
|
36
|
-
`\`/revela edit\` —
|
|
37
|
-
`\`/revela inspect\` —
|
|
35
|
+
`\`/revela refine\` — open unified reading, inspection, and editing workspace\n` +
|
|
36
|
+
`\`/revela edit\` — deprecated compatibility shim to /revela refine Edit\n` +
|
|
37
|
+
`\`/revela inspect\` — deprecated compatibility shim to /revela refine Inspect\n` +
|
|
38
38
|
`\`/revela remember <text>\` — save an explicit preference to DECKS.json\n` +
|
|
39
39
|
`\`/revela designs\` — list installed designs\n` +
|
|
40
40
|
`\`/revela designs <name>\` — activate a design\n` +
|
package/lib/commands/inspect.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { openRefineDeck } from "../refine/open"
|
|
2
2
|
|
|
3
3
|
export async function handleInspect(
|
|
4
|
-
options: { client: any; sessionID: string; workspaceRoot: string },
|
|
4
|
+
options: { client: any; sessionID: string; workspaceRoot: string; openBrowser?: boolean },
|
|
5
5
|
send: (text: string) => Promise<void>,
|
|
6
6
|
): Promise<void> {
|
|
7
7
|
try {
|
|
8
|
-
const result =
|
|
8
|
+
const result = openRefineDeck("", {
|
|
9
9
|
client: options.client,
|
|
10
10
|
sessionID: options.sessionID,
|
|
11
11
|
workspaceRoot: options.workspaceRoot,
|
|
12
|
+
mode: "inspect",
|
|
13
|
+
openBrowser: options.openBrowser,
|
|
12
14
|
})
|
|
13
15
|
await send(
|
|
14
|
-
|
|
16
|
+
`\`/revela inspect\` is deprecated. Opened \`/revela refine\` in Inspect mode for the active HTML deck.\n` +
|
|
15
17
|
`File: \`${result.deck.file}\`\n` +
|
|
16
18
|
`${result.stateNote}\n` +
|
|
17
19
|
`URL: ${result.url}\n\n` +
|
|
18
|
-
`Use Ctrl/Cmd-click in the browser to reference deck elements
|
|
20
|
+
`Use \`/revela refine\` directly going forward. Use Ctrl/Cmd-click in the browser to reference deck elements, then use the Inspect tab for read-only Source/Purpose review. There is no chat box or freeform prompt.`
|
|
19
21
|
)
|
|
20
22
|
} catch (e: any) {
|
|
21
23
|
await send(`**Inspect failed:** ${e.message || String(e)}`)
|
package/lib/inspect/prompt.ts
CHANGED
|
@@ -4,23 +4,31 @@ export function buildInspectionPrompt(input: {
|
|
|
4
4
|
requestId: string
|
|
5
5
|
file: string
|
|
6
6
|
projection: InspectionPromptProjection
|
|
7
|
+
language?: string
|
|
7
8
|
}): string {
|
|
9
|
+
const language = normalizeInspectLanguage(input.language)
|
|
8
10
|
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
11
|
|
|
10
12
|
Target file: ${input.file}
|
|
11
13
|
Inspection request id: ${input.requestId}
|
|
14
|
+
Display language: ${language}
|
|
12
15
|
|
|
13
|
-
Use the structured projection below to produce the final inspector cards. This is LLM judgment with grounded boundaries:
|
|
16
|
+
Use the structured projection below to produce the final inspector cards. This is LLM judgment with grounded boundaries: explain the selected object's narrative reading context, bounded exploratory reading context, purpose, and source credibility only. Do not edit files. Do not mutate DECKS.json. Do not invent claim ids, evidence binding ids, sources, quotes, URLs, page references, caveats, objections, risks, artifact coverage, or evidence not present in the projection.
|
|
17
|
+
|
|
18
|
+
Language boundary: the selected display language affects only human-readable card copy. Preserve all claim ids, canonical claim ids, evidence binding ids, source paths, findings files, URLs, numbers, quoted/source facts, caveats, artifact ids, and coverage statuses exactly as grounded in the projection. If the display language is Auto, use projection.deck.language when available; otherwise follow the user's/browser context or default to English.
|
|
14
19
|
|
|
15
20
|
Return the result only by calling the \`revela-inspection-result\` tool with this request id. Do not answer in chat.
|
|
16
21
|
|
|
17
22
|
Required card model:
|
|
23
|
+
- Narrative Reading: when the projection includes a matched claim, preserve its claim id, canonical claim id, evidence binding ids, supported scope, unsupported scope, caveats, related objections, related risks, and artifact coverage. Artifact coverage must come only from projection.cards.artifacts; do not invent where a claim appears or whether an artifact is stale/current/partial/missing. If canonical narrative linkage is missing, say so and fall back to the matched slide claim; do not invent canonical ids.
|
|
24
|
+
- Candidate boundary: when projection.match.claim is absent but projection.match.candidateClaims is present, explain the selected child element only within those candidate claim boundaries. You may describe that the child element functions as a detail, prerequisite, source note, risk cue, or evidence cue inside the slide, but you must not select one candidate claim id by semantic guess. If projection.match.confidence is none or candidateClaims is empty, explain the mapping gap instead of inventing a plausible claim.
|
|
25
|
+
- Exploratory Reading: provide bounded, non-official reading cues for objection prep, audience reframing, appendix leads, and meeting prep only from the projection. Mark official as false. Keep missing evidence, caveats, unsupported scope, and stale artifacts visible. Do not make exploratory text sound like approved artifact content, and do not turn this into chat or a fix plan.
|
|
18
26
|
- 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
27
|
- 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
28
|
|
|
21
29
|
Boundaries:
|
|
22
30
|
- 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.
|
|
31
|
+
- Do not recommend edits or fixes; this inspector view only explains narrative context, bounded exploratory reading context, purpose, and source credibility.
|
|
24
32
|
- Do not turn every caveat into a problem.
|
|
25
33
|
- If confidence is low, use unclear or unknown instead of pretending certainty.
|
|
26
34
|
|
|
@@ -30,3 +38,8 @@ Projection JSON:
|
|
|
30
38
|
${JSON.stringify(input.projection, null, 2)}
|
|
31
39
|
\`\`\``
|
|
32
40
|
}
|
|
41
|
+
|
|
42
|
+
function normalizeInspectLanguage(language: string | undefined): string {
|
|
43
|
+
const value = typeof language === "string" ? language.trim() : ""
|
|
44
|
+
return value || "Auto"
|
|
45
|
+
}
|
package/lib/inspect/requests.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InspectionPromptProjection } from "../inspection-context/project"
|
|
2
|
-
import type
|
|
2
|
+
import { buildDeterministicInspectionResult, type InspectionResult } from "../inspection-context/result"
|
|
3
3
|
|
|
4
4
|
export type InspectRequestStatus = "pending" | "completed" | "failed" | "expired"
|
|
5
5
|
|
|
@@ -53,11 +53,30 @@ export function completeInspectRequest(requestId: string, result: InspectionResu
|
|
|
53
53
|
if (!request) throw new Error(`Unknown inspection request: ${requestId}`)
|
|
54
54
|
if (request.status !== "pending") throw new Error(`Inspection request is not pending: ${request.status}`)
|
|
55
55
|
request.status = "completed"
|
|
56
|
-
request.result =
|
|
56
|
+
request.result = normalizeInspectionResult(request.projection, result, requestId)
|
|
57
57
|
request.updatedAt = Date.now()
|
|
58
58
|
return request
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function normalizeInspectionResult(
|
|
62
|
+
projection: InspectionPromptProjection,
|
|
63
|
+
result: InspectionResult,
|
|
64
|
+
requestId: string,
|
|
65
|
+
): InspectionResult {
|
|
66
|
+
const deterministic = buildDeterministicInspectionResult(projection, { requestId })
|
|
67
|
+
return {
|
|
68
|
+
...result,
|
|
69
|
+
requestId,
|
|
70
|
+
cards: {
|
|
71
|
+
reading: result.cards.reading ?? deterministic.cards.reading,
|
|
72
|
+
exploratory: result.cards.exploratory ?? deterministic.cards.exploratory,
|
|
73
|
+
purpose: result.cards.purpose,
|
|
74
|
+
source: result.cards.source,
|
|
75
|
+
},
|
|
76
|
+
stale: result.stale ?? deterministic.stale,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
61
80
|
export function failInspectRequest(requestId: string, error: string): PendingInspectRequest | undefined {
|
|
62
81
|
const request = getInspectRequest(requestId)
|
|
63
82
|
if (!request || request.status !== "pending") return request
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeckSpec, DecksState, EvidenceRef, NarrativeBrief, NarrativeRole, SlideSpec, SourceMaterial } from "../decks-state"
|
|
2
|
+
import { getArtifactClaimRefs, type ArtifactClaimRef, type ClaimSlideRef } from "../narrative-state/queries"
|
|
2
3
|
import type { NarrativeClaim, NarrativeEvidenceBinding, NarrativeStateV1 } from "../narrative-state/types"
|
|
3
4
|
|
|
4
5
|
export type InspectionClaimOrigin = "narrative" | "title" | "headline" | "body" | "bullet" | "purpose"
|
|
@@ -20,6 +21,7 @@ export interface InspectionContext {
|
|
|
20
21
|
appendixCandidates: InspectionAppendixCandidate[]
|
|
21
22
|
objectionContext: InspectionNarrativeContext[]
|
|
22
23
|
riskContext: InspectionNarrativeContext[]
|
|
24
|
+
artifactCoverage: InspectionArtifactCoverage[]
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface InspectionNarrativeStateContext {
|
|
@@ -98,11 +100,35 @@ export interface InspectionAppendixCandidate {
|
|
|
98
100
|
|
|
99
101
|
export interface InspectionNarrativeContext {
|
|
100
102
|
text: string
|
|
101
|
-
source: "narrativeBrief" | "slide"
|
|
103
|
+
source: "narrative" | "narrativeBrief" | "slide"
|
|
102
104
|
slideIndex?: number
|
|
103
105
|
slideTitle?: string
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
export interface InspectionArtifactCoverage {
|
|
109
|
+
artifactId: string
|
|
110
|
+
type: ArtifactClaimRef["type"]
|
|
111
|
+
outputPath?: string
|
|
112
|
+
coverageStatus: ArtifactClaimRef["coverageStatus"]
|
|
113
|
+
claimIds: string[]
|
|
114
|
+
affectedClaimIds: string[]
|
|
115
|
+
missingClaimIds: string[]
|
|
116
|
+
stale: boolean
|
|
117
|
+
staleReason?: string
|
|
118
|
+
staleReasons: string[]
|
|
119
|
+
note?: string
|
|
120
|
+
slideRefs: InspectionArtifactSlideRef[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface InspectionArtifactSlideRef {
|
|
124
|
+
claimId: string
|
|
125
|
+
slideIndex: number
|
|
126
|
+
slideTitle: string
|
|
127
|
+
role: ClaimSlideRef["role"]
|
|
128
|
+
match: ClaimSlideRef["match"]
|
|
129
|
+
location: string
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
export function compileInspectionContext(state: DecksState, slug?: string): InspectionContext {
|
|
107
133
|
const deck = activeDeck(state, slug)
|
|
108
134
|
const narrative = state.narrative
|
|
@@ -127,11 +153,36 @@ export function compileInspectionContext(state: DecksState, slug?: string): Insp
|
|
|
127
153
|
slides,
|
|
128
154
|
gaps,
|
|
129
155
|
appendixCandidates: compileAppendixCandidates(slides),
|
|
130
|
-
objectionContext: compileNarrativeList(deck, "objections"),
|
|
131
|
-
riskContext: compileNarrativeList(deck, "risks"),
|
|
156
|
+
objectionContext: compileNarrativeList(deck, "objections", narrative),
|
|
157
|
+
riskContext: compileNarrativeList(deck, "risks", narrative),
|
|
158
|
+
artifactCoverage: compileArtifactCoverage(state),
|
|
132
159
|
}
|
|
133
160
|
}
|
|
134
161
|
|
|
162
|
+
function compileArtifactCoverage(state: DecksState): InspectionArtifactCoverage[] {
|
|
163
|
+
return getArtifactClaimRefs(state).map((artifact) => ({
|
|
164
|
+
artifactId: artifact.artifactId,
|
|
165
|
+
type: artifact.type,
|
|
166
|
+
outputPath: artifact.outputPath,
|
|
167
|
+
coverageStatus: artifact.coverageStatus,
|
|
168
|
+
claimIds: artifact.claimIds,
|
|
169
|
+
affectedClaimIds: artifact.affectedClaimIds,
|
|
170
|
+
missingClaimIds: artifact.missingClaimIds,
|
|
171
|
+
stale: artifact.stale,
|
|
172
|
+
staleReason: artifact.staleReason,
|
|
173
|
+
staleReasons: artifact.staleReasons,
|
|
174
|
+
note: artifact.note,
|
|
175
|
+
slideRefs: artifact.slideRefs.map((ref) => ({
|
|
176
|
+
claimId: ref.claimId,
|
|
177
|
+
slideIndex: ref.slideIndex,
|
|
178
|
+
slideTitle: ref.slideTitle,
|
|
179
|
+
role: ref.role,
|
|
180
|
+
match: ref.match,
|
|
181
|
+
location: ref.location,
|
|
182
|
+
})),
|
|
183
|
+
}))
|
|
184
|
+
}
|
|
185
|
+
|
|
135
186
|
function activeDeck(state: DecksState, slug?: string): DeckSpec {
|
|
136
187
|
const key = slug || state.activeDeck || (Object.keys(state.decks).length === 1 ? Object.keys(state.decks)[0] : undefined)
|
|
137
188
|
if (!key || !state.decks[key]) throw new Error("No active deck is available for inspection context compilation.")
|
|
@@ -389,7 +440,10 @@ function appendixReason(slide: InspectionSlideContext): string {
|
|
|
389
440
|
return "Slide has recorded evidence that may be useful for source excerpts or backup detail."
|
|
390
441
|
}
|
|
391
442
|
|
|
392
|
-
function compileNarrativeList(deck: DeckSpec, key: "objections" | "risks"): InspectionNarrativeContext[] {
|
|
443
|
+
function compileNarrativeList(deck: DeckSpec, key: "objections" | "risks", narrative: NarrativeStateV1 | undefined): InspectionNarrativeContext[] {
|
|
444
|
+
const fromNarrative = key === "objections"
|
|
445
|
+
? (narrative?.objections ?? []).map((item) => ({ text: item.text, source: "narrative" as const }))
|
|
446
|
+
: (narrative?.risks ?? []).map((item) => ({ text: item.text, source: "narrative" as const }))
|
|
393
447
|
const fromBrief = (deck.narrativeBrief?.[key] ?? []).map((text) => ({ text, source: "narrativeBrief" as const }))
|
|
394
448
|
const role = key === "risks" ? "risk" : undefined
|
|
395
449
|
const fromSlides = deck.slides
|
|
@@ -400,7 +454,19 @@ function compileNarrativeList(deck: DeckSpec, key: "objections" | "risks"): Insp
|
|
|
400
454
|
slideIndex: slide.index,
|
|
401
455
|
slideTitle: slide.title,
|
|
402
456
|
})))
|
|
403
|
-
return [...fromBrief, ...fromSlides]
|
|
457
|
+
return dedupeNarrativeContext([...fromNarrative, ...fromBrief, ...fromSlides])
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function dedupeNarrativeContext(values: InspectionNarrativeContext[]): InspectionNarrativeContext[] {
|
|
461
|
+
const seen = new Set<string>()
|
|
462
|
+
const result: InspectionNarrativeContext[] = []
|
|
463
|
+
for (const value of values) {
|
|
464
|
+
const key = `${normalizeText(value.text)}:${value.slideIndex ?? "global"}`
|
|
465
|
+
if (seen.has(key)) continue
|
|
466
|
+
seen.add(key)
|
|
467
|
+
result.push(value)
|
|
468
|
+
}
|
|
469
|
+
return result
|
|
404
470
|
}
|
|
405
471
|
|
|
406
472
|
function slideTextList(slide: SlideSpec): string[] {
|
|
@@ -45,6 +45,7 @@ export interface InspectionElementSnapshot {
|
|
|
45
45
|
export interface InspectionElementMatch {
|
|
46
46
|
slide?: InspectionSlideContext
|
|
47
47
|
claim?: InspectionClaimCandidate
|
|
48
|
+
candidateClaims?: InspectionClaimCandidate[]
|
|
48
49
|
evidence: InspectionEvidenceTrace[]
|
|
49
50
|
gaps: InspectionGap[]
|
|
50
51
|
caveats: string[]
|
|
@@ -55,8 +56,12 @@ export interface InspectionElementMatch {
|
|
|
55
56
|
|
|
56
57
|
export function matchInspectionElement(context: InspectionContext, snapshot: InspectionElementSnapshot): InspectionElementMatch {
|
|
57
58
|
const selectedText = normalizeText(snapshot.text)
|
|
59
|
+
const surroundingText = normalizeText(snapshot.nearbyText || snapshot.outerHTMLExcerpt)
|
|
58
60
|
const candidateSlides = candidateSlidesForSnapshot(context, snapshot)
|
|
59
61
|
|
|
62
|
+
const anchoredClaim = findAnchoredClaimMatch(candidateSlides, snapshot)
|
|
63
|
+
if (anchoredClaim) return claimMatch(context, anchoredClaim.slide, anchoredClaim.claim, "high", "Matched explicit claim anchor from selection snapshot.")
|
|
64
|
+
|
|
60
65
|
if (selectedText) {
|
|
61
66
|
const exactClaim = findClaimMatch(candidateSlides, selectedText, "exact")
|
|
62
67
|
if (exactClaim) return claimMatch(context, exactClaim.slide, exactClaim.claim, "high", "Exact normalized text match.")
|
|
@@ -77,8 +82,27 @@ export function matchInspectionElement(context: InspectionContext, snapshot: Ins
|
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
if (surroundingText && surroundingText !== selectedText) {
|
|
86
|
+
const contextualClaim = findClaimMatch(candidateSlides, surroundingText, "contains")
|
|
87
|
+
if (contextualClaim) return claimMatch(context, contextualClaim.slide, contextualClaim.claim, "medium", "Matched claim using surrounding slide context.")
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
const slide = candidateSlides[0]
|
|
81
|
-
if (slide)
|
|
91
|
+
if (slide) {
|
|
92
|
+
const canonicalClaims = slide.claims.filter((claim) => claim.canonicalClaimId || claim.origin === "narrative")
|
|
93
|
+
if (canonicalClaims.length === 1) {
|
|
94
|
+
return claimMatch(context, slide, canonicalClaims[0], "medium", "Selected element matched the slide; the slide has one canonical narrative claim candidate.", canonicalClaims)
|
|
95
|
+
}
|
|
96
|
+
return slideMatch(
|
|
97
|
+
context,
|
|
98
|
+
slide,
|
|
99
|
+
snapshot.slideIndex ? "medium" : "low",
|
|
100
|
+
canonicalClaims.length > 1
|
|
101
|
+
? "Matched slide only; multiple canonical claim candidates are available, so no claim id was chosen by semantic guess."
|
|
102
|
+
: snapshot.slideIndex ? "Matched by slideIndex only." : "No claim text matched; returning first candidate slide.",
|
|
103
|
+
canonicalClaims,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
82
106
|
|
|
83
107
|
return {
|
|
84
108
|
evidence: [],
|
|
@@ -114,6 +138,37 @@ function findClaimMatch(
|
|
|
114
138
|
return undefined
|
|
115
139
|
}
|
|
116
140
|
|
|
141
|
+
function findAnchoredClaimMatch(
|
|
142
|
+
slides: InspectionSlideContext[],
|
|
143
|
+
snapshot: InspectionElementSnapshot,
|
|
144
|
+
): { slide: InspectionSlideContext; claim: InspectionClaimCandidate } | undefined {
|
|
145
|
+
const claimIds = explicitClaimIds(snapshot)
|
|
146
|
+
if (claimIds.length === 0) return undefined
|
|
147
|
+
for (const slide of slides) {
|
|
148
|
+
for (const claim of slide.claims) {
|
|
149
|
+
const ids = [claim.id, claim.canonicalClaimId].filter((item): item is string => Boolean(item))
|
|
150
|
+
if (ids.some((id) => claimIds.includes(id))) return { slide, claim }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function explicitClaimIds(snapshot: InspectionElementSnapshot): string[] {
|
|
157
|
+
const values = [
|
|
158
|
+
snapshot.selector,
|
|
159
|
+
snapshot.domPath,
|
|
160
|
+
snapshot.outerHTMLExcerpt,
|
|
161
|
+
...(snapshot.elements ?? []).flatMap((item) => [item.selector, item.domPath, item.outerHTMLExcerpt]),
|
|
162
|
+
]
|
|
163
|
+
const ids: string[] = []
|
|
164
|
+
for (const value of values) {
|
|
165
|
+
if (!value) continue
|
|
166
|
+
for (const match of value.matchAll(/data-claim-id\s*=\s*["']([^"']+)["']/gi)) ids.push(match[1])
|
|
167
|
+
for (const match of value.matchAll(/data-claim-id=([^\]\s>"']+)/gi)) ids.push(match[1])
|
|
168
|
+
}
|
|
169
|
+
return dedupe(ids.map((item) => item.trim()).filter(Boolean))
|
|
170
|
+
}
|
|
171
|
+
|
|
117
172
|
function conservativeContains(claimText: string, selectedText: string): boolean {
|
|
118
173
|
if (selectedText.length < 12 && claimText.length < 12) return false
|
|
119
174
|
return claimText.includes(selectedText) || selectedText.includes(claimText)
|
|
@@ -125,10 +180,12 @@ function claimMatch(
|
|
|
125
180
|
claim: InspectionClaimCandidate,
|
|
126
181
|
confidence: InspectionMatchConfidence,
|
|
127
182
|
reason: string,
|
|
183
|
+
candidateClaims: InspectionClaimCandidate[] = [],
|
|
128
184
|
): InspectionElementMatch {
|
|
129
185
|
return {
|
|
130
186
|
slide,
|
|
131
187
|
claim,
|
|
188
|
+
candidateClaims: candidateClaims.length ? candidateClaims : [claim],
|
|
132
189
|
evidence: claim.evidence,
|
|
133
190
|
gaps: claim.gaps,
|
|
134
191
|
caveats: slide.caveats,
|
|
@@ -143,9 +200,11 @@ function slideMatch(
|
|
|
143
200
|
slide: InspectionSlideContext,
|
|
144
201
|
confidence: InspectionMatchConfidence,
|
|
145
202
|
reason: string,
|
|
203
|
+
candidateClaims: InspectionClaimCandidate[] = [],
|
|
146
204
|
): InspectionElementMatch {
|
|
147
205
|
return {
|
|
148
206
|
slide,
|
|
207
|
+
candidateClaims,
|
|
149
208
|
evidence: slide.evidence,
|
|
150
209
|
gaps: slide.claims.flatMap((claim) => claim.gaps),
|
|
151
210
|
caveats: slide.caveats,
|
|
@@ -167,3 +226,14 @@ function normalizeText(text: string | undefined): string {
|
|
|
167
226
|
.trim()
|
|
168
227
|
.toLowerCase()
|
|
169
228
|
}
|
|
229
|
+
|
|
230
|
+
function dedupe(values: string[]): string[] {
|
|
231
|
+
const seen = new Set<string>()
|
|
232
|
+
const result: string[] = []
|
|
233
|
+
for (const value of values) {
|
|
234
|
+
if (seen.has(value)) continue
|
|
235
|
+
seen.add(value)
|
|
236
|
+
result.push(value)
|
|
237
|
+
}
|
|
238
|
+
return result
|
|
239
|
+
}
|