@cyber-dash-tech/revela 0.12.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.
Files changed (37) hide show
  1. package/README.md +16 -16
  2. package/README.zh-CN.md +16 -16
  3. package/lib/commands/brief.ts +63 -0
  4. package/lib/commands/edit.ts +7 -5
  5. package/lib/commands/help.ts +5 -3
  6. package/lib/commands/inspect.ts +7 -5
  7. package/lib/commands/narrative.ts +160 -0
  8. package/lib/decks-state.ts +33 -0
  9. package/lib/edit/prompt.ts +3 -0
  10. package/lib/inspect/prompt.ts +15 -2
  11. package/lib/inspect/requests.ts +21 -2
  12. package/lib/inspection-context/compile.ts +230 -10
  13. package/lib/inspection-context/match.ts +71 -1
  14. package/lib/inspection-context/project.ts +131 -8
  15. package/lib/inspection-context/result.ts +183 -0
  16. package/lib/narrative-state/coverage.ts +100 -0
  17. package/lib/narrative-state/display.ts +219 -0
  18. package/lib/narrative-state/executive-brief.ts +246 -0
  19. package/lib/narrative-state/hash.ts +9 -0
  20. package/lib/narrative-state/map-html.ts +348 -0
  21. package/lib/narrative-state/map.ts +282 -0
  22. package/lib/narrative-state/normalize.ts +54 -0
  23. package/lib/narrative-state/queries.ts +434 -0
  24. package/lib/narrative-state/readiness.ts +71 -1
  25. package/lib/narrative-state/render-plan.ts +44 -1
  26. package/lib/narrative-state/research-gaps.ts +191 -0
  27. package/lib/narrative-state/types.ts +33 -0
  28. package/lib/refine/server.ts +91 -13
  29. package/lib/workspace-state/evidence-status.ts +21 -1
  30. package/lib/workspace-state/graph.ts +56 -2
  31. package/lib/workspace-state/types.ts +10 -1
  32. package/package.json +1 -1
  33. package/plugin.ts +33 -2
  34. package/tools/decks.ts +86 -1
  35. package/tools/edit.ts +10 -8
  36. package/tools/inspection-result.ts +37 -0
  37. package/tools/narrative-view.ts +84 -0
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
 
@@ -0,0 +1,63 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs"
2
+ import { dirname, isAbsolute, join, normalize, resolve } from "path"
3
+ import { readDecksState, writeDecksState } from "../decks-state"
4
+ import { compileExecutiveBrief, DEFAULT_EXECUTIVE_BRIEF_PATH } from "../narrative-state/executive-brief"
5
+
6
+ export interface BriefArgs {
7
+ outputPath?: string
8
+ }
9
+
10
+ export type ParseBriefArgsResult = { ok: true; args: BriefArgs } | { ok: false; error: string }
11
+
12
+ export function parseBriefArgs(input: string): ParseBriefArgsResult {
13
+ const value = input.trim()
14
+ if (!value) return { ok: true, args: {} }
15
+ if (value.startsWith("--")) return { ok: false, error: "Usage: `/revela brief [workspace-relative-output.md]`" }
16
+ if (!value.endsWith(".md")) return { ok: false, error: "Executive brief output must be a Markdown file ending in `.md`." }
17
+ if (isAbsolute(value) || value.split(/[\\/]+/).includes("..")) return { ok: false, error: "Executive brief output must be a safe workspace-relative path." }
18
+ return { ok: true, args: { outputPath: normalize(value).replace(/\\/g, "/") } }
19
+ }
20
+
21
+ export async function handleBrief(
22
+ input: { workspaceRoot: string; outputPath?: string },
23
+ send: (text: string) => Promise<void>,
24
+ ): Promise<void> {
25
+ const statePath = join(input.workspaceRoot, "DECKS.json")
26
+ if (!existsSync(statePath)) {
27
+ await send("No `DECKS.json` found. Run `/revela init` before rendering an executive brief.")
28
+ return
29
+ }
30
+
31
+ const state = readDecksState(input.workspaceRoot)
32
+ const result = compileExecutiveBrief(state, { outputPath: input.outputPath })
33
+ if (!result.ok) {
34
+ await send(
35
+ `**Executive brief not rendered**\n\n${result.reason}\n\n` +
36
+ (result.narrativeHash ? `Narrative hash: \`${result.narrativeHash}\`\n\n` : "") +
37
+ "Run `/revela review` and approve the current narrative, or record an explicit render override before retrying."
38
+ )
39
+ return
40
+ }
41
+
42
+ const filePath = safeWorkspaceFilePath(input.workspaceRoot, result.outputPath)
43
+ mkdirSync(dirname(filePath), { recursive: true })
44
+ writeFileSync(filePath, result.content, "utf-8")
45
+ writeDecksState(input.workspaceRoot, result.state)
46
+
47
+ await send(
48
+ `**Executive brief rendered**\n\n` +
49
+ `- Output: \`${result.outputPath}\`\n` +
50
+ `- Render target: \`${result.target.id}\`\n` +
51
+ `- Narrative hash: \`${result.narrativeHash}\`\n\n` +
52
+ "The brief was compiled from canonical narrative state, not from a deck summary."
53
+ )
54
+ }
55
+
56
+ function safeWorkspaceFilePath(workspaceRoot: string, outputPath: string): string {
57
+ const relative = outputPath || DEFAULT_EXECUTIVE_BRIEF_PATH
58
+ if (isAbsolute(relative) || relative.split(/[\\/]+/).includes("..")) throw new Error("Executive brief output must be a safe workspace-relative path.")
59
+ const root = resolve(workspaceRoot)
60
+ const target = resolve(root, relative)
61
+ if (target !== root && !target.startsWith(`${root}/`)) throw new Error("Executive brief output must stay inside the workspace.")
62
+ return target
63
+ }
@@ -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)}`)
@@ -28,11 +28,13 @@ export async function handleHelp(
28
28
  `\`/revela disable\` — disable Revela mode\n` +
29
29
  `\`/revela init\` — initialize or refresh workspace DECKS.json\n` +
30
30
  `\`/revela review\` — review narrative readiness and approval state\n` +
31
+ `\`/revela narrative\` — open read-only narrative workspace map\n` +
32
+ `\`/revela brief [file.md]\` — render executive brief from approved narrative\n` +
31
33
  `\`/revela deck\` — start deck handoff from approved narrative\n` +
32
34
  `\`/revela deck --review\` — review deck/artifact readiness before writing HTML\n` +
33
- `\`/revela refine\` — open unified Edit/Inspect refinement workspace\n` +
34
- `\`/revela edit\` — open visual editor for the only deck in decks/\n` +
35
- `\`/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` +
36
38
  `\`/revela remember <text>\` — save an explicit preference to DECKS.json\n` +
37
39
  `\`/revela designs\` — list installed designs\n` +
38
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)}`)
@@ -0,0 +1,160 @@
1
+ import { mkdirSync, writeFileSync } from "fs"
2
+ import { tmpdir } from "os"
3
+ import { join } from "path"
4
+ import { openUrl as defaultOpenUrl } from "../edit/open"
5
+ import { hasDecksState, readDecksState } from "../decks-state"
6
+ import { buildNarrativeMap, formatNarrativeMap } from "../narrative-state/map"
7
+ import { renderNarrativeMapHtmlWithDisplay } from "../narrative-state/map-html"
8
+ import { emptyDisplayModel, type NarrativeViewLanguage, type ValidatedNarrativeDisplayModel } from "../narrative-state/display"
9
+
10
+ export interface NarrativeArgs {
11
+ language: NarrativeViewLanguage
12
+ raw: boolean
13
+ }
14
+
15
+ export type ParseNarrativeArgsResult = { ok: true; args: NarrativeArgs } | { ok: false; error: string }
16
+
17
+ export function parseNarrativeArgs(param: string): ParseNarrativeArgsResult {
18
+ const tokens = param.trim().split(/\s+/).filter(Boolean)
19
+ let language: NarrativeViewLanguage = "en"
20
+ let raw = false
21
+ const languageParts: string[] = []
22
+ for (const token of tokens) {
23
+ const normalized = token.toLowerCase()
24
+ if (normalized === "--raw") {
25
+ raw = true
26
+ continue
27
+ }
28
+ if (token.startsWith("--") && token.length > 2) {
29
+ language = normalizeLanguageRequest(token.slice(2))
30
+ continue
31
+ }
32
+ languageParts.push(token)
33
+ }
34
+ if (languageParts.length > 0) language = normalizeLanguageRequest(languageParts.join(" "))
35
+ return { ok: true, args: { language, raw } }
36
+ }
37
+
38
+ function normalizeLanguageRequest(value: string): NarrativeViewLanguage {
39
+ const trimmed = value.trim()
40
+ const normalized = trimmed.toLowerCase()
41
+ if (["en", "eng", "english"].includes(normalized)) return "en"
42
+ if (["cn", "zh", "zh-cn", "chinese"].includes(normalized)) return "zh-CN"
43
+ if (["jp", "ja", "ja-jp", "japanese"].includes(normalized)) return "ja-JP"
44
+ return trimmed || "en"
45
+ }
46
+
47
+ export async function handleNarrative(
48
+ options: { workspaceRoot: string; openBrowser?: boolean; openUrl?: (url: string) => void; language?: NarrativeViewLanguage; display?: ValidatedNarrativeDisplayModel },
49
+ send: (text: string) => Promise<void>,
50
+ ): Promise<void> {
51
+ try {
52
+ if (!hasDecksState(options.workspaceRoot)) {
53
+ await send("No `DECKS.json` found. Run `/revela init` first to initialize the narrative workspace.")
54
+ return
55
+ }
56
+
57
+ const state = readDecksState(options.workspaceRoot)
58
+ const map = buildNarrativeMap(state)
59
+ const markdown = formatNarrativeMap(map)
60
+
61
+ if (options.openBrowser) {
62
+ const htmlPath = writeNarrativeMapHtml(map, options.display ?? emptyDisplayModel(options.language ?? "en"))
63
+ const url = `file://${htmlPath}`
64
+ try {
65
+ ;(options.openUrl ?? defaultOpenUrl)(url)
66
+ await send(`Opened read-only narrative workspace: ${url}\n\n${markdown}`)
67
+ } catch (e: any) {
68
+ await send(`Read-only narrative workspace generated but could not open automatically: ${url}\n\n${e.message || String(e)}\n\n${markdown}`)
69
+ }
70
+ return
71
+ }
72
+
73
+ await send(markdown)
74
+ } catch (e: any) {
75
+ await send(`**Narrative map failed:** ${e.message || String(e)}`)
76
+ }
77
+ }
78
+
79
+ export function buildNarrativeViewPrompt(options: { workspaceRoot: string; language: NarrativeViewLanguage }): string {
80
+ if (!hasDecksState(options.workspaceRoot)) {
81
+ return "No `DECKS.json` found. Tell the user to run `/revela init` before opening the narrative view. Do not call any tool."
82
+ }
83
+
84
+ const map = buildNarrativeMap(readDecksState(options.workspaceRoot))
85
+ const projection = {
86
+ narrativeHash: map.snapshot.narrativeHash,
87
+ language: options.language,
88
+ snapshot: map.snapshot,
89
+ claims: map.claimFlow.map((claim) => ({
90
+ id: claim.id,
91
+ kind: claim.kind,
92
+ importance: claim.importance,
93
+ evidenceStatus: claim.evidenceStatus,
94
+ text: claim.text,
95
+ supportedScope: claim.supportedScope,
96
+ unsupportedScope: claim.unsupportedScope,
97
+ evidence: claim.evidence.map((evidence) => ({ source: evidence.source, strength: evidence.strength, findingsFile: evidence.findingsFile, location: evidence.location, quote: evidence.quote, caveat: evidence.caveat, unsupportedScope: evidence.unsupportedScope })),
98
+ })),
99
+ relations: map.claimRelations.map((relation) => ({ id: relation.id, fromClaimId: relation.fromClaimId, toClaimId: relation.toClaimId, relation: relation.relation, rationale: relation.rationale, inferred: relation.inferred })),
100
+ researchGaps: map.researchGaps.map((gap) => ({ id: gap.id, targetType: gap.targetType, targetId: gap.targetId, status: gap.status, priority: gap.priority, question: gap.question })),
101
+ artifactCoverage: map.artifactCoverage.map((artifact) => ({ type: artifact.type, outputPath: artifact.outputPath, stale: artifact.stale, slideRefs: artifact.slideRefs.map((ref) => ({ claimId: ref.claimId, slideIndex: ref.slideIndex, role: ref.role, match: ref.match, location: ref.location })) })),
102
+ }
103
+
104
+ return `Prepare the read-only Revela narrative UI display model.
105
+
106
+ Target language request: ${options.language}
107
+ - The language value is passed from the user's /revela narrative arguments. Interpret it as the desired UI/display language.
108
+ - Examples: --cn maps to zh-CN, --jp maps to ja-JP, while --fr, --de, --es, --ko, --Arabic, --Portuguese-BR, or a written language name should be localized normally into that requested language.
109
+ - Default /revela narrative language is en when the user provides no language request.
110
+
111
+ You must call the \`revela-narrative-view\` tool exactly once.
112
+
113
+ Hard rules:
114
+ - Do not mutate DECKS.json, deck HTML, evidence, claims, relations, approvals, or artifacts.
115
+ - Do not invent new claims, evidence, relations, slide coverage, source paths, findings files, quotes, or caveats.
116
+ - Preserve every claimId exactly.
117
+ - Preserve every relation endpoint exactly: fromClaimId, toClaimId, relation.
118
+ - You may only organize and localize display copy for the UI: pageTitle, summaryLine, section labels, claim card displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
119
+ - For inferred relations, do not provide relation displayLabel or displayRationale; inferred relations are unconfirmed order notes, not causal/support/dependency judgments.
120
+ - relation displayRationale may only localize or clarify an existing canonical relation rationale. If relation.rationale is missing or the relation is inferred, do not provide displayRationale; the UI will show the missing or inferred status.
121
+ - Keep source paths, findings files, claim IDs, narrative hash, and numbers unchanged.
122
+ - Translate normal UI/display text into the target language request: pageTitle, summaryLine, labels, claim displayTitle, roleLabel, narrativeJob, evidenceSummary, riskOrGapSummary, relation displayLabel, and relation displayRationale.
123
+ - Do not translate claim IDs, relation endpoints, narrative hash, source paths, findings files, URLs, numbers, or quoted/source facts.
124
+ - Use natural business and manufacturing terminology in the target language, not word-by-word machine translation.
125
+ - If a fact is missing, describe it as missing instead of filling it in.
126
+
127
+ Chinese localization rules when the target language request is Chinese, zh, zh-CN, --cn, 中文, or Simplified Chinese:
128
+ - Use natural business/manufacturing Chinese, not word-by-word machine translation.
129
+ - In manufacturing, industrial AI, automation, and autonomous systems context, translate "autonomy" as "自主化", "自主能力", or "自主系统". Do not translate it as "自治".
130
+ - Translate "autonomous" as "自主的" / "自主化的" where appropriate, not "自治的".
131
+ - Translate "architectural" as "架构层面的", "架构性", or "架构问题" according to context.
132
+ - Slug-like or kebab-case claim text such as "autonomy-is-architectural" should become a readable displayTitle such as "自主化是架构问题" or "自主化必须作为架构问题处理", not a literal token-by-token translation.
133
+ - If the canonical claim text is only a slug, preserve the claimId exactly but write displayTitle as a readable claim title.
134
+
135
+ Call \`revela-narrative-view\` with:
136
+ - language: ${options.language}
137
+ - narrativeHash: ${map.snapshot.narrativeHash}
138
+ - displayModel.version: 1
139
+ - displayModel.language: ${options.language}
140
+ - displayModel.claimCards only for claim IDs listed below
141
+ - displayModel.relations only for relations listed below
142
+
143
+ Compact deterministic narrative map:
144
+
145
+ \`\`\`json
146
+ ${JSON.stringify(projection, null, 2)}
147
+ \`\`\``
148
+ }
149
+
150
+ export function writeNarrativeMapHtml(map: ReturnType<typeof buildNarrativeMap>, display: ValidatedNarrativeDisplayModel = emptyDisplayModel("en")): string {
151
+ const dir = join(tmpdir(), "revela-narrative")
152
+ mkdirSync(dir, { recursive: true })
153
+ const file = join(dir, `${safeFilePart(map.snapshot.narrativeId)}-${map.snapshot.narrativeHash}.html`)
154
+ writeFileSync(file, renderNarrativeMapHtmlWithDisplay(map, display), "utf-8")
155
+ return file
156
+ }
157
+
158
+ function safeFilePart(value: string): string {
159
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "narrative"
160
+ }
@@ -26,6 +26,7 @@ export type DeckProductionStatus = "planning" | "blocked" | "ready" | "written"
26
26
  export type SlideProductionStatus = "planned" | "ready" | "written" | "qa_passed" | "qa_failed"
27
27
  export type WriteReadinessStatus = "blocked" | "ready" | "written"
28
28
  export type NarrativeRole = "context" | "tension" | "evidence" | "recommendation" | "risk" | "ask" | "appendix" | "close"
29
+ export type SlideClaimRefRole = "primary" | "supporting" | "evidence" | "risk" | "objection"
29
30
 
30
31
  export interface DecksState {
31
32
  version: 1
@@ -135,6 +136,9 @@ export interface SlideSpec {
135
136
  layout: string
136
137
  qa?: boolean
137
138
  components: string[]
139
+ claimIds?: string[]
140
+ claimRefs?: SlideClaimRef[]
141
+ evidenceBindingIds?: string[]
138
142
  content: {
139
143
  headline?: string
140
144
  body?: string[]
@@ -148,6 +152,12 @@ export interface SlideSpec {
148
152
  notes?: string
149
153
  }
150
154
 
155
+ export interface SlideClaimRef {
156
+ claimId: string
157
+ role: SlideClaimRefRole
158
+ note?: string
159
+ }
160
+
151
161
  export interface EvidenceRef {
152
162
  source: string
153
163
  quote?: string
@@ -1478,6 +1488,9 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
1478
1488
  title: slide.title ?? "",
1479
1489
  layout: slide.layout ?? "",
1480
1490
  components: slide.components ?? [],
1491
+ claimIds: normalizeTextList(slide.claimIds),
1492
+ claimRefs: normalizeSlideClaimRefs(slide.claimRefs),
1493
+ evidenceBindingIds: normalizeTextList(slide.evidenceBindingIds),
1481
1494
  content: slide.content ?? {},
1482
1495
  evidence: slide.evidence ?? [],
1483
1496
  status: slide.status ?? "planned",
@@ -1485,6 +1498,26 @@ function normalizeSlides(slides: SlideSpec[]): SlideSpec[] {
1485
1498
  .sort((a, b) => a.index - b.index)
1486
1499
  }
1487
1500
 
1501
+ function normalizeSlideClaimRefs(refs: SlideClaimRef[] | undefined): SlideClaimRef[] {
1502
+ const seen = new Set<string>()
1503
+ const out: SlideClaimRef[] = []
1504
+ for (const ref of refs ?? []) {
1505
+ const claimId = cleanOptionalText(ref.claimId)
1506
+ if (!claimId) continue
1507
+ const role = isSlideClaimRefRole(ref.role) ? ref.role : "supporting"
1508
+ const key = `${claimId}:${role}`
1509
+ if (seen.has(key)) continue
1510
+ seen.add(key)
1511
+ const note = cleanOptionalText(ref.note)
1512
+ out.push({ claimId, role, ...(note ? { note } : {}) })
1513
+ }
1514
+ return out
1515
+ }
1516
+
1517
+ function isSlideClaimRefRole(value: string | undefined): value is SlideClaimRefRole {
1518
+ return value === "primary" || value === "supporting" || value === "evidence" || value === "risk" || value === "objection"
1519
+ }
1520
+
1488
1521
  function normalizeNarrativeBrief(brief: NarrativeBrief | undefined): NarrativeBrief | undefined {
1489
1522
  if (!brief) return undefined
1490
1523
  const normalized: NarrativeBrief = {
@@ -71,6 +71,9 @@ Instructions:
71
71
  - Make the smallest targeted change that satisfies the user's comment.
72
72
  - If there are multiple comments, apply them as one coherent edit pass and avoid changes from one comment overwriting another.
73
73
  - Each comment may reference one or more selected elements. Treat the elements in a single comment as a group.
74
+ - Preserve the narrative boundary: if the requested edit changes audience framing, belief shift, decision/action, thesis, recommendation, claim wording, evidence scope, caveat, risk, objection, or decision ask, do not patch the HTML directly. Explain that the canonical narrative must be updated first through ${"`revela-decks`"} action ${"`upsertNarrative`"}, then reviewed/approved or explicitly overridden before updating the deck projection.
75
+ - Pure artifact polish such as layout, spacing, typography, alignment, color, image crop, animation, export fidelity, or deck HTML contract fixes may remain an artifact-level edit.
76
+ - If the request mixes content meaning and visual polish, treat it as narrative-impacting unless the user clarifies otherwise.
74
77
  - Preserve the existing deck structure, active design language, typography, spacing system, animations, and slide count unless the comment explicitly asks otherwise.
75
78
  - Do not rewrite unrelated slides or broad sections of the deck.
76
79
  - Locate each target primarily with slideIndex, slideTitle, selected text, nearbyText, and outerHTMLExcerpt. Use selector/domPath as hints; they may be approximate.