@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **English** | [中文](README.zh-CN.md)
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-324%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-375%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](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 Edit/Inspect refinement workspace
149
- /revela edit open visual editor for the only deck in decks/
150
- /revela inspect open Evidence Inspector for Cmd/Ctrl-click review
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 deterministic Source/Purpose preprocessing first before lazy LLM-generated cards arrive, has no chat box, and does not edit the deck.
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, and optional Source/Purpose inspection of selected deck elements.
218
- 9. Use `/revela edit` or `/revela inspect` directly only if you need the older single-purpose shells.
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 only HTML deck in `decks/` 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 Source and Purpose review. Inspect does not mutate the deck; Edit remains the mutation path.
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
- Open the visual editor for the only HTML deck in `decks/`:
596
+ Deprecated compatibility command:
597
597
 
598
598
  ```text
599
599
  /revela edit
600
600
  ```
601
601
 
602
- `/revela edit` does not accept a target in Revela 0.8. If `decks/` contains exactly one `.html` file, Revela opens it. If `decks/` contains zero or multiple `.html` files, Revela asks you to generate a deck first or move extra decks to separate workspaces.
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
- The older edit-only shell opens in your browser. Use `Ctrl`/`Cmd` + click to reference deck elements, write a natural-language comment, 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.
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. This lets the agent open the same editor when you say things like “I want to edit the deck”.
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
- Open the Evidence Inspector for the only HTML deck in `decks/`, or use the Inspect tab in `/revela refine`:
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
- The inspector opens in your browser with the deck on the left and fixed cards on the right: Source and Purpose. Use `Ctrl`/`Cmd` + click to reference deck elements exactly like `/revela edit`, then click `Inspect Selection`. Selection is locked while the request is being processed.
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 appears immediately; lazy LLM judgment then refines the Source and Purpose cards without inventing edits.
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
- Inspection and refinement use 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 inspect/refine/export workflows trust them.
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
- [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-324%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-375%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](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 打开统一的 Edit/Inspect refinement workspace
148
- /revela edit 打开 decks/ 下唯一 deck 的可视化编辑器
149
- /revela inspect 打开 Cmd/Ctrl-click Evidence Inspector
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` 会打开一个本地浏览器 workspace,里面有 Edit 和 Inspect 两个 tab,并共享同一套 Cmd/Ctrl-click 元素引用。Edit 会把精准修改评论发回当前 OpenCode 会话;Inspect 会先渲染确定性 Source/Purpose 预处理结果,再 lazy 显示 LLM 生成的卡片。它没有聊天框,也不会修改 deck
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、SourcePurpose 卡片。确定性预处理保留为 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 元素做可视化评论、精准修改,以及可选的 Source/Purpose 检查。
217
- 9. 只有在需要旧的单一功能 shell 时,才单独使用 `/revela edit` 或 `/revela inspect`。
216
+ 8. 用 `/revela refine` 对选中 deck 元素做可视化评论、精准修改、只读 Narrative Reading、有边界的 Exploratory Reading、SourcePurpose 检查,以及 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` 会打开 `decks/` 下唯一的 HTML deck,并提供两个 tab。使用 `Ctrl`/`Cmd` + click 先引用 deck 元素,然后在 Edit 里快速写自然语言修改评论,或在 Inspect 里做只读 Source 和 Purpose 检查。Inspect 不会修改 deck;真正的 mutation 仍然只走 Edit
559
+ `/revela refine` 会打开 active HTML deck,并提供两个 tab。使用 `Ctrl`/`Cmd` + click 先引用 deck 元素,然后在 Edit 里快速写自然语言修改评论,或在 Inspect 里做只读 Narrative Reading、有边界的 Exploratory Reading、Source、Purposeartifact coverage 检查。Inspect 不会修改 deck;真正的 mutation 仍然只走 Edit。这是 post-artifact 阅读、检查和编辑的推荐入口。
560
560
 
561
- 打开 `decks/` 下唯一 HTML deck 的可视化编辑器:
561
+ Deprecated 兼容命令:
562
562
 
563
563
  ```text
564
564
  /revela edit
565
565
  ```
566
566
 
567
- Revela 0.8 中 `/revela edit` 不接受 target。如果 `decks/` 下正好有一个 `.html` 文件,Revela 会打开它。如果 `decks/` 下没有 HTML 或有多个 HTML,Revela 会提示先生成 deck,或把多余 deck 移到独立 workspace。
567
+ `/revela edit` 不再打开独立的 edit-only shell。它会打开 `/revela refine` Edit mode,用于兼容旧脚本和旧使用习惯。
568
568
 
569
- 旧的 edit-only shell 会在浏览器中打开。使用 `Ctrl`/`Cmd` + 点击 deck 元素来引用它们,写一段自然语言评论,然后发送回 OpenCode。Revela 会把 deck 文件、slide 上下文、选中元素 metadata 和你的评论整理成结构化 edit prompt。
569
+ 使用 `Ctrl`/`Cmd` + 点击 deck 元素来引用它们,在 Edit tab 写一段自然语言评论,然后发送回 OpenCode。Revela 会把 deck 文件、slide 上下文、选中元素 metadata 和你的评论整理成结构化 edit prompt。
570
570
 
571
- 对应的 LLM tool:`revela-edit`,不需要 target。因此当你说“我要编辑这个 deck”时,agent 也可以主动打开同一个编辑器。
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
- 打开 `decks/` 下唯一 HTML deck Evidence Inspector,或直接使用 `/revela refine` 里的 Inspect tab:
579
+ `/revela refine` evidence inspection narrative reading。Deprecated 兼容命令:
580
580
 
581
581
  ```text
582
582
  /revela inspect
583
583
  ```
584
584
 
585
- Inspector 会在浏览器中打开,左侧是 deck,右侧是固定卡片:Source 和 Purpose。使用 `Ctrl`/`Cmd` + click `/revela edit` 一样引用 deck 元素,然后点击 `Inspect Selection`。请求处理期间,deck 选择会被锁定。
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。确定性预处理会立即显示;LLM judgment 随后 lazy 更新 Source Purpose 卡片,不会强行生成编辑动作。
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
- Inspect 和 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 会在 inspect/refine/export 工作流信任它之前被拒绝或报告。
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
 
@@ -1,22 +1,24 @@
1
- import { openEditableDeck } from "../edit/open"
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 = openEditableDeck("", {
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
- `Opened visual editor for the only deck in \`decks/\`.\n` +
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 + click in the browser to reference elements, write a comment, then send comments. Revela mode has been enabled for the edit prompt.`
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)}`)
@@ -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 Edit/Inspect refinement workspace\n` +
36
- `\`/revela edit\` — open visual editor for the only deck in decks/\n` +
37
- `\`/revela inspect\` — open Evidence Inspector for click-to-inspect review\n` +
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` +
@@ -1,21 +1,23 @@
1
- import { openInspectDeck } from "../inspect/open"
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 = openInspectDeck("", {
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
- `Opened Evidence Inspector for the active HTML deck.\n` +
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 exactly like /revela edit, then click Inspect Selection. Deterministic Source/Purpose preprocessing appears first, followed by lazy LLM-generated cards. Selection is locked while the request is processed. There is no chat box or freeform prompt.`
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)}`)
@@ -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: 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.
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
+ }
@@ -1,5 +1,5 @@
1
1
  import type { InspectionPromptProjection } from "../inspection-context/project"
2
- import type { InspectionResult } from "../inspection-context/result"
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 = { ...result, requestId }
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) return slideMatch(context, slide, snapshot.slideIndex ? "medium" : "low", snapshot.slideIndex ? "Matched by slideIndex only." : "No claim text matched; returning first candidate 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
+ }