@cyber-dash-tech/revela 0.4.3 → 0.5.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/plugin.ts CHANGED
@@ -15,8 +15,8 @@
15
15
  */
16
16
 
17
17
  import type { Plugin } from "@opencode-ai/plugin"
18
- import { existsSync, readFileSync } from "fs"
19
- import { extname, basename } from "path"
18
+ import { existsSync, mkdirSync, readFileSync } from "fs"
19
+ import { extname, basename, join } from "path"
20
20
  import { tmpdir } from "os"
21
21
  import { seedBuiltinDesigns } from "./lib/design/designs"
22
22
  import { seedBuiltinDomains } from "./lib/domain/domains"
@@ -52,6 +52,26 @@ import {
52
52
  parseDesignsEditArgs,
53
53
  buildDesignsEditPrompt,
54
54
  } from "./lib/commands/designs-new"
55
+ import { buildInitPrompt } from "./lib/commands/init"
56
+ import { parseRememberArgs, buildRememberPrompt } from "./lib/commands/remember"
57
+ import { buildReviewPrompt } from "./lib/commands/review"
58
+ import {
59
+ buildDecksMemoryLayer,
60
+ extractDeckHtmlTargetsFromPatch,
61
+ extractPatchTextArg,
62
+ hasDecksMemory,
63
+ isDeckHtmlPath,
64
+ setPatchTextArg,
65
+ } from "./lib/decks-memory"
66
+ import {
67
+ buildDecksStatePromptLayer,
68
+ checkDeckStateWriteReadiness,
69
+ DECKS_STATE_FILE,
70
+ extractDecksStateTargetsFromPatch,
71
+ hasDecksState,
72
+ isDecksStatePath,
73
+ } from "./lib/decks-state"
74
+ import decksTool from "./tools/decks"
55
75
  import designsAuthorTool from "./tools/designs-author"
56
76
  import designsTool from "./tools/designs"
57
77
  import domainsTool from "./tools/domains"
@@ -103,6 +123,9 @@ async function sendIgnoredMessage(
103
123
 
104
124
  const server: Plugin = (async (pluginCtx) => {
105
125
  const client = pluginCtx.client
126
+ const workspaceRoot = pluginCtx.directory
127
+ const blockedDeckWrites = new Map<string, string>()
128
+ const blockedDeckPatches = new Map<string, string>()
106
129
 
107
130
  // ── Startup: seed + build initial prompt ────────────────────────────────
108
131
  try {
@@ -191,6 +214,35 @@ const server: Plugin = (async (pluginCtx) => {
191
214
  await handleDisable(send)
192
215
  throw new Error("__REVELA_DISABLE_HANDLED__")
193
216
  }
217
+ if (sub === "init") {
218
+ output.parts.length = 0
219
+ output.parts.push({
220
+ type: "text",
221
+ text: buildInitPrompt({ exists: hasDecksState(workspaceRoot), legacyExists: hasDecksMemory(workspaceRoot), workspaceRoot }),
222
+ } as any)
223
+ return
224
+ }
225
+ if (sub === "remember") {
226
+ const parsed = parseRememberArgs(param)
227
+ if (!parsed.ok) {
228
+ await send(parsed.error)
229
+ throw new Error("__REVELA_REMEMBER_USAGE_HANDLED__")
230
+ }
231
+ output.parts.length = 0
232
+ output.parts.push({
233
+ type: "text",
234
+ text: buildRememberPrompt({ memory: parsed.memory, exists: hasDecksState(workspaceRoot), legacyExists: hasDecksMemory(workspaceRoot) }),
235
+ } as any)
236
+ return
237
+ }
238
+ if (sub === "review") {
239
+ output.parts.length = 0
240
+ output.parts.push({
241
+ type: "text",
242
+ text: buildReviewPrompt({ slug: param || undefined, exists: hasDecksState(workspaceRoot), legacyExists: hasDecksMemory(workspaceRoot), workspaceRoot }),
243
+ } as any)
244
+ return
245
+ }
194
246
  if (sub === "designs" && !param) {
195
247
  await handleDesignsList(send)
196
248
  throw new Error("__REVELA_DESIGNS_LIST_HANDLED__")
@@ -268,6 +320,7 @@ const server: Plugin = (async (pluginCtx) => {
268
320
 
269
321
  // ── LLM tools: designs, domains, research, document materials, qa ─────
270
322
  tool: {
323
+ "revela-decks": decksTool,
271
324
  "revela-designs": designsTool,
272
325
  "revela-designs-author": designsAuthorTool,
273
326
  "revela-domains": domainsTool,
@@ -354,7 +407,23 @@ const server: Plugin = (async (pluginCtx) => {
354
407
  // Skip OpenCode internal system agents (title generator, summary, compaction)
355
408
  if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) return
356
409
 
357
- const prompt = readFileSync(ACTIVE_PROMPT_FILE, "utf-8")
410
+ let prompt = readFileSync(ACTIVE_PROMPT_FILE, "utf-8")
411
+ try {
412
+ const stateLayer = buildDecksStatePromptLayer(workspaceRoot)
413
+ if (stateLayer) prompt += "\n\n" + stateLayer
414
+ } catch (e) {
415
+ childLog("decks-state").warn("failed to load DECKS.json state", {
416
+ error: e instanceof Error ? e.message : String(e),
417
+ })
418
+ }
419
+ try {
420
+ const memoryLayer = buildDecksMemoryLayer(workspaceRoot)
421
+ if (memoryLayer) prompt += "\n\n" + memoryLayer
422
+ } catch (e) {
423
+ childLog("decks-memory").warn("failed to load DECKS.md memory", {
424
+ error: e instanceof Error ? e.message : String(e),
425
+ })
426
+ }
358
427
  if (output.system.length > 0) {
359
428
  output.system[output.system.length - 1] += "\n\n" + prompt
360
429
  } else {
@@ -371,21 +440,135 @@ const server: Plugin = (async (pluginCtx) => {
371
440
  }
372
441
  },
373
442
 
374
- // ── Pre-read: intercept binary files before read executes ──────────────
375
- // Handles DOCX/PPTX/XLSX read tool would Effect.fail on these.
376
- // Extracts text writes temp .txt → redirects args.filePath.
443
+ // ── Pre-tool processing ────────────────────────────────────────────────
444
+ // - read: intercept DOCX/PPTX/XLSX before read executes.
445
+ // - write/apply_patch: gate decks/*.html on DECKS.json readiness.
377
446
  "tool.execute.before": async (input, output) => {
378
447
  log.info("[hook] tool.execute.before fired", { tool: input.tool, enabled: ctx.enabled, isResearch: ctx.isResearchAgent })
379
448
  if (!ctx.enabled) return
380
449
 
381
- if (input.tool !== "read") return
382
- try {
383
- await preRead(output.args)
384
- } catch (e) {
385
- childLog("preRead").warn("extraction failed", {
386
- filePath: (output.args as any)?.filePath,
387
- error: e instanceof Error ? e.message : String(e),
450
+ if (input.tool === "write") {
451
+ const filePath: string = (output.args as any)?.filePath ?? ""
452
+ if (isDecksStatePath(filePath)) {
453
+ const blockedDir = join(workspaceRoot, ".opencode", "revela", "blocked-writes")
454
+ mkdirSync(blockedDir, { recursive: true })
455
+ const blockedPath = join(blockedDir, "DECKS-json-direct-write.blocked.md")
456
+ const blocker = `${DECKS_STATE_FILE} is a controlled Revela state file. Use the revela-decks tool instead of write/apply_patch.`
457
+ ;(output.args as any).filePath = blockedPath
458
+ ;(output.args as any).content = `# Revela Blocked State Write
459
+
460
+ The attempted write to \`${filePath}\` was blocked.
461
+
462
+ Reason: ${blocker}
463
+
464
+ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSlides\`, or \`review\`.
465
+ `
466
+ blockedDeckWrites.set(filePath, blocker)
467
+ childLog("decks-state").warn("blocked direct DECKS.json write", { filePath, blockedPath })
468
+ return
469
+ }
470
+ if (!isDeckHtmlPath(filePath)) return
471
+
472
+ const readiness = checkDeckStateWriteReadiness(workspaceRoot, filePath) ?? {
473
+ ready: false,
474
+ slug: basename(filePath, ".html") || "deck",
475
+ blocker: `No ${DECKS_STATE_FILE} exists. Use revela-decks init/upsertDeck/upsertSlides/review before writing deck HTML.`,
476
+ blockers: [`No ${DECKS_STATE_FILE} exists.`],
477
+ }
478
+ if (readiness.ready) return
479
+
480
+ const blockedDir = join(workspaceRoot, ".opencode", "revela", "blocked-writes")
481
+ mkdirSync(blockedDir, { recursive: true })
482
+ const blockedPath = join(blockedDir, `${readiness.slug}.blocked.md`)
483
+ ;(output.args as any).filePath = blockedPath
484
+ ;(output.args as any).content = `# Revela Blocked Deck Write
485
+
486
+ The attempted write to \`${filePath}\` was blocked.
487
+
488
+ Reason: ${readiness.blocker}
489
+
490
+ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FILE}, then write only after the matching deck has \`writeReadiness.status\` set to \`ready\` and no blockers.
491
+ `
492
+ blockedDeckWrites.set(filePath, readiness.blocker)
493
+ childLog("decks-memory").warn("blocked deck write", { filePath, blockedPath, blocker: readiness.blocker })
494
+ return
495
+ }
496
+
497
+ if (input.tool === "apply_patch") {
498
+ const args = output.args as Record<string, unknown>
499
+ const patchText = extractPatchTextArg(args)
500
+ if (!patchText) return
501
+
502
+ const stateTargets = extractDecksStateTargetsFromPatch(patchText)
503
+ if (stateTargets.length > 0) {
504
+ const blockedDir = join(workspaceRoot, ".opencode", "revela", "blocked-writes")
505
+ mkdirSync(blockedDir, { recursive: true })
506
+ const blockedRelativePath = `.opencode/revela/blocked-writes/DECKS-json-direct-patch-${Date.now()}.blocked.md`
507
+ const blocker = `${DECKS_STATE_FILE} is a controlled Revela state file. Use the revela-decks tool instead of write/apply_patch.`
508
+ const blockedPatch = `*** Begin Patch
509
+ *** Add File: ${blockedRelativePath}
510
+ +# Revela Blocked State Patch
511
+ +
512
+ +The attempted patch touching \`${stateTargets.join(", ")}\` was blocked.
513
+ +
514
+ +Reason: ${blocker}
515
+ +
516
+ +Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSlides\`, or \`review\`.
517
+ *** End Patch`
518
+ setPatchTextArg(args, blockedPatch)
519
+ blockedDeckPatches.set(blockedRelativePath, blocker)
520
+ childLog("decks-state").warn("blocked direct DECKS.json patch", { targets: stateTargets, blockedPath: blockedRelativePath })
521
+ return
522
+ }
523
+
524
+ const targets = extractDeckHtmlTargetsFromPatch(patchText)
525
+ if (targets.length === 0) return
526
+
527
+ const blocked = targets
528
+ .map((target) => ({
529
+ target,
530
+ readiness: checkDeckStateWriteReadiness(workspaceRoot, target) ?? {
531
+ ready: false,
532
+ slug: basename(target, ".html") || "deck",
533
+ blocker: `No ${DECKS_STATE_FILE} exists. Use revela-decks init/upsertDeck/upsertSlides/review before patching deck HTML.`,
534
+ blockers: [`No ${DECKS_STATE_FILE} exists.`],
535
+ },
536
+ }))
537
+ .find((item) => !item.readiness.ready)
538
+ if (!blocked) return
539
+
540
+ const blockedDir = join(workspaceRoot, ".opencode", "revela", "blocked-writes")
541
+ mkdirSync(blockedDir, { recursive: true })
542
+ const blockedRelativePath = `.opencode/revela/blocked-writes/${blocked.readiness.slug}-${Date.now()}.blocked.md`
543
+ const blockedPatch = `*** Begin Patch
544
+ *** Add File: ${blockedRelativePath}
545
+ +# Revela Blocked Deck Patch
546
+ +
547
+ +The attempted patch touching \`${blocked.target}\` was blocked.
548
+ +
549
+ +Reason: ${blocked.readiness.blocker}
550
+ +
551
+ +Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FILE}, then patch only after the matching deck has \`writeReadiness.status\` set to \`ready\` and no blockers.
552
+ *** End Patch`
553
+ setPatchTextArg(args, blockedPatch)
554
+ blockedDeckPatches.set(blockedRelativePath, blocked.readiness.blocker)
555
+ childLog("decks-memory").warn("blocked deck patch", {
556
+ target: blocked.target,
557
+ blockedPath: blockedRelativePath,
558
+ blocker: blocked.readiness.blocker,
388
559
  })
560
+ return
561
+ }
562
+
563
+ if (input.tool === "read") {
564
+ try {
565
+ await preRead(output.args)
566
+ } catch (e) {
567
+ childLog("preRead").warn("extraction failed", {
568
+ filePath: (output.args as any)?.filePath,
569
+ error: e instanceof Error ? e.message : String(e),
570
+ })
571
+ }
389
572
  }
390
573
  },
391
574
 
@@ -413,8 +596,19 @@ const server: Plugin = (async (pluginCtx) => {
413
596
  // ── Auto layout QA after writing decks/*.html ─────────────────────
414
597
  if (input.tool === "write") {
415
598
  const filePath: string = input.args?.filePath ?? ""
599
+ const blockedReason = blockedDeckWrites.get(filePath)
600
+ if (blockedReason) {
601
+ blockedDeckWrites.delete(filePath)
602
+ const existing = (output as any).result ?? ""
603
+ ;(output as any).result =
604
+ (existing ? existing + "\n\n" : "") +
605
+ "---\n\n**[revela state gate]** Write was blocked.\n\n" +
606
+ `${blockedReason}\n\n` +
607
+ "Use the `revela-decks` tool or complete the DECKS.json review workflow instead."
608
+ return
609
+ }
416
610
  // Only trigger for HTML files inside a decks/ directory
417
- if (!filePath.match(/decks\/[^/]+\.html$/)) return
611
+ if (!isDeckHtmlPath(filePath)) return
418
612
 
419
613
  try {
420
614
  // Extract design's allowed class vocabulary for compliance checking
@@ -444,6 +638,18 @@ const server: Plugin = (async (pluginCtx) => {
444
638
  }
445
639
  return
446
640
  }
641
+
642
+ if (input.tool === "apply_patch" && blockedDeckPatches.size > 0) {
643
+ const [blockedPath, blockedReason] = blockedDeckPatches.entries().next().value ?? []
644
+ if (blockedPath) blockedDeckPatches.delete(blockedPath)
645
+ const existing = (output as any).result ?? ""
646
+ ;(output as any).result =
647
+ (existing ? existing + "\n\n" : "") +
648
+ "---\n\n**[revela prewrite gate]** Deck HTML patch was blocked.\n\n" +
649
+ `${blockedReason}\n\n` +
650
+ "Run `/revela review` or complete the same DECKS.json review workflow before patching the deck."
651
+ return
652
+ }
447
653
  },
448
654
  }
449
655
  }) satisfies Plugin
package/skill/SKILL.md CHANGED
@@ -36,38 +36,57 @@ If the user's first message already answers most of these, skip what's clear and
36
36
  only ask about what's missing. If the message is detailed enough, proceed directly
37
37
  to Phase 1.5.
38
38
 
39
- Once you have the user's answers, **derive the deck slug** from the topic:
40
- lowercase, hyphens, no spaces (e.g. "AI Investment Shift" → `ai-investment-shift`).
41
- Tell the user: "I'll save this deck as `decks/{slug}.html`." They can correct the
42
- name at this point.
43
-
44
- ### Phase 1.5 Deck Initialization & Resume Check
45
-
46
- After confirming the deck slug, check whether this deck has been worked on before:
47
-
48
- 1. Run `ls researches/{slug}/` (or `glob researches/{slug}/*.md`).
49
- 2. **If the directory does not exist (new deck):** proceed to Phase 2.
50
- 3. **If research files already exist (resuming):** list the files and ask the user:
51
-
52
- > 我发现 `researches/{slug}/` 下已有以下研究文件:
53
- > - `market-data.md`
54
- > - `competitor-profile.md`
55
- > - _(etc.)_
56
- >
57
- > 你想:
58
- > a. 直接使用现有研究,跳到幻灯片计划阶段
59
- > b. 补充某些方向的研究(请告诉我哪些方向)
60
- > c. 全部重新研究
61
-
62
- Then act based on the user's reply:
63
- - **a** skip Phase 3, go directly to Phase 4 (read existing files first)
64
- - **b** → run research agents only for the specified axes, then Phase 4
65
- - **c** → proceed to Phase 2 normally (full research)
66
-
67
- All subsequent file paths in this session use the confirmed slug:
39
+ Once you have the user's answers, form a concise **Research Brief** before doing
40
+ research or writing HTML. The brief should capture:
41
+ - user goal and decision/context the deck must support
42
+ - audience and language
43
+ - working thesis or angle, if one has emerged
44
+ - key questions the deck must answer
45
+ - known workspace sources from `DECKS.json`, user attachments, or visible files
46
+ - desired output shape, slide count, and visual direction
47
+
48
+ If the brief is unclear, ask 1–3 targeted clarification questions. Do not force
49
+ the user to provide a research topic command; the working topic emerges from the
50
+ conversation.
51
+
52
+ ### Phase 1.5 — Project State, Working Slug & Active Deck
53
+
54
+ Before research, use the `revela-decks` tool with action `read` or `init` to
55
+ inspect `DECKS.json`. Treat it as the source of truth for project context,
56
+ source material index, explicit user preferences, prior deck history, active
57
+ deck specs, per-slide content/layout/components, write readiness, and open
58
+ questions. Do not write or patch `DECKS.json` directly.
59
+
60
+ Derive a **working slug** from the Research Brief: lowercase, hyphens, no spaces
61
+ (e.g. "AI Investment Shift" → `ai-investment-shift`). The slug is only a file
62
+ path handle for this deck/research cache; the user does not need to supply it as
63
+ a command. Tell the user: "I'll save this deck as `decks/{slug}.html`." They can
64
+ correct the name at this point.
65
+
66
+ Check whether this deck has been worked on before:
67
+ 1. Run `glob researches/{slug}/*.md`.
68
+ 2. If research files already exist, list them and ask whether to reuse, supplement,
69
+ or replace the existing research.
70
+ 3. If the user chooses reuse, read the existing files before Phase 4.
71
+ 4. If the user chooses supplement or replace, use the existing files to avoid
72
+ duplicate work and proceed through Phase 3 only for missing or stale axes.
73
+
74
+ All subsequent file paths in this session use the working slug:
68
75
  - Slides file: `decks/{slug}.html`
69
76
  - Research dir: `researches/{slug}/`
70
77
 
78
+ Create or update the active deck in `DECKS.json` through `revela-decks` actions
79
+ `upsertDeck` and `upsertSlides`. Keep the deck spec current as work progresses:
80
+ - `goal` — purpose and decision/context
81
+ - `audience`, `language`, `slideCount`, `outputPath`, and `theme`
82
+ - `requiredInputs` — checklist state for prewrite readiness
83
+ - `researchPlan` — axes, status, and findings files
84
+ - `slides` — confirmed per-slide title, purpose, layout, components, content, evidence, visuals, and status
85
+ - `writeReadiness` — computed by `revela-decks review`, never manually set by the LLM
86
+
87
+ Do not store temporary Active Deck checklist state in `User Preferences` or
88
+ `Workflow Preferences`.
89
+
71
90
  ### Phase 2 — Select Design
72
91
 
73
92
  Once you have the user's answers (especially topic, audience, and visual style),
@@ -99,151 +118,90 @@ Do not proceed to Phase 3 until the user has replied to the design question.
99
118
 
100
119
  ---
101
120
 
102
- ### Phase 3 — Research-First Protocol (自主调研)
103
-
104
- **Always execute this phase — regardless of whether the user mentions reference
105
- files.** Your job is to proactively gather all available information before
106
- writing a single slide.
107
-
108
- #### Execution Model — Parallel, Not Sequential
109
-
110
- Research layers are **NOT** a sequential fallback chain where you stop once
111
- "enough" data is collected. Execute them as parallel workstreams:
112
-
113
- ```
114
- ┌─────────────────────────────────────────────┐
115
- │ LAUNCH TOGETHER (as your first action): │
116
- │ │
117
- │ ┌──────────────┐ ┌─────────────────────┐ │
118
- │ │ Layer 1 │ │ Layer 2 │ │
119
- │ │ Workspace │ │ Research agents │ │
120
- │ │ scan │ │ (parallel per axis) │ │
121
- │ └──────────────┘ └─────────────────────┘ │
122
- │ │
123
- │ After both complete: │
124
- │ ┌──────────────┐ │
125
- │ │ Layer 3 │ AI knowledge fills gaps │
126
- │ └──────────────┘ │
127
- │ │
128
- │ Only if still missing: │
129
- │ ┌──────────────┐ │
130
- │ │ Layer 4 │ Ask the user │
131
- │ └──────────────┘ │
132
- └─────────────────────────────────────────────┘
133
- ```
134
-
135
- **Layer 1 and Layer 2 launch in parallel as the FIRST action after Phase 2.**
136
- Do not wait for Layer 1 results before launching Layer 2. Do not use Layer 3
137
- (AI knowledge) as an excuse to skip Layer 2.
121
+ ### Phase 3 — Conversation-Driven Research Protocol (自主调研)
138
122
 
139
- ---
123
+ Research is gated by the Research Brief. Do not launch research just because a
124
+ phase says so; launch it when the deck needs facts, numbers, case studies,
125
+ competitive profiles, market data, external validation, or image/source leads
126
+ that are not already available in the conversation and `DECKS.json`.
140
127
 
141
- #### Layer 1 Workspace Documents
128
+ If the deck is simple, internal, or fully specified by the user, you may proceed
129
+ to Phase 4 without new research. If the brief is too vague to research, ask the
130
+ user 1–3 focused questions before launching agents.
142
131
 
143
- Scan the workspace for reference documents using the built-in file tools
144
- (`ls`, `glob`). Look for files with extensions:
145
- `.pdf`, `.xlsx`, `.xls`, `.docx`, `.doc`, `.pptx`, `.ppt`, `.csv`
146
- and images: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`
132
+ #### Research Brief Before Agents
147
133
 
148
- Use the `read` tool to read each relevant file. The Revela plugin transparently
149
- extracts text from binary formats (PDF, Excel, Word, PowerPoint) — just call
150
- `read` normally on any file type.
134
+ Before starting research agents, write a brief for yourself with:
135
+ - working slug for `researches/{slug}/`
136
+ - user goal and audience
137
+ - thesis or decision the deck should support
138
+ - key questions and time period
139
+ - relevant `DECKS.json` sourceMaterials or user-provided files
140
+ - axes to research and desired output for each axis
151
141
 
152
- ---
142
+ You do not need to ask the user to approve the slug unless the filename matters.
153
143
 
154
- #### Layer 2 — Deep Research via Research Agents (MANDATORY)
144
+ #### Deep Research via `revela-research` Subagents
155
145
 
156
- **This layer is mandatory whenever the `@revela-research` subagent (Task tool
157
- with `subagent_type: "revela-research"`) is available.** It is the primary
158
- research workhorse — not an optional enhancement.
146
+ `revela-research` is an OpenCode subagent, **not a tool**. Launch it through the
147
+ Task tool with `subagent_type: "revela-research"`. Do not write or imply a
148
+ `revela-research(...)` tool call.
159
149
 
160
- The research agent searches the web using `websearch` for broad discovery and
161
- `webfetch` for depth on specific pages, reads workspace documents, and writes
162
- structured findings to a single file `researches/{slug}/{axis-name}.md`
163
- in the workspace. Use the deck slug confirmed in Phase 1.5 — do not invent a
164
- different slug at this point.
150
+ Decompose the Research Brief into independent axes before launching agents. Each
151
+ axis gets one focused subagent brief. When multiple axes are needed, launch all
152
+ agents in a single message with parallel Task tool calls.
165
153
 
166
- ##### Parallelization Rule
154
+ Each subagent brief must specify:
155
+ - shared working slug for `researches/{slug}/`
156
+ - axis filename, such as `market-data`, `competitor-profile`, or `technology-trends`
157
+ - the research question, time period, geography, and evidence standard
158
+ - relevant `DECKS.json` sourceMaterials or user files to prioritize
159
+ - whether web research is needed and what types of sources are preferred
167
160
 
168
- Decompose the topic into **independent research axes** before launching agents.
169
- Each axis gets its own dedicated agent with a focused brief. Launch ALL agents
170
- in a single message (parallel Task tool calls).
161
+ The subagent writes exactly one file through `revela-research-save`:
162
+ `researches/{slug}/{axis-name}.md`.
171
163
 
172
- **How to decompose:** Look at what the presentation needs to cover. Each major
173
- entity, comparison dimension, or macro question is a separate axis. Decompose
174
- based on topic breadth and the depth each axis warrants — a narrow topic may
175
- need 2 axes; a complex comparison may need 4 or more. Typical decompositions:
164
+ #### Workspace Memory and Freshness
176
165
 
177
- | Topic type | Example axes |
178
- |---|---|
179
- | Company comparison | Company A data, Company B data, market context |
180
- | Industry analysis | Market sizing, competitive landscape, technology trends, regulatory |
181
- | Investment thesis | Opportunity metrics, risk factors, comparable deals, macro trends |
182
- | Product strategy | User research, competitor features, technology feasibility, go-to-market |
166
+ Use `revela-decks` action `read` before scanning from scratch. Its
167
+ `workspace.sourceMaterials` state is the workspace material index created by
168
+ `/revela init`. Use it to choose candidate files and avoid repeated deep reading.
183
169
 
184
- Launch ALL agents in a single message (parallel Task tool calls).
170
+ Use `revela-workspace-scan` or file tools as a freshness check when needed:
171
+ - discover files added after `/revela init`
172
+ - verify that listed source files still exist
173
+ - find user-provided attachments or topic-specific files not in `DECKS.json`
185
174
 
186
- Each agent's brief should specify:
187
- - The deck slug from Phase 1.5 (e.g. `ai-investment-shift`) — all agents share the same slug
188
- - The axis name for their file (e.g. `anthropic-profile`, `openai-challenges`, `market-trends`)
189
- - What to research and what time period to focus on
190
- - An explicit instruction to use `websearch` (e.g. "Use the websearch tool to find relevant market reports, news, and data for this axis.")
175
+ Avoid repeated expensive work. Only call `revela-extract-document-materials` or
176
+ deep-read files that are relevant to the current Research Brief.
191
177
 
192
- ##### After Agents Complete
178
+ #### After Agents Complete
193
179
 
194
- List and read the findings files: `ls researches/{slug}/`, then `read`
195
- each `.md` file. Each file contains structured `## Data`, `## Cases`,
196
- `## Images`, and `## Gaps` sections use these directly as slide material.
197
- Cross-reference agent findings with workspace documents (Layer 1). Flag any
198
- contradictions. Once all findings are read, proceed to Phase 4 to present the
199
- slide plan.
180
+ List and read the findings files in `researches/{slug}/`. Each file contains
181
+ structured `## Data`, `## Cases`, `## Images`, and `## Gaps` sections. Use these
182
+ directly as slide material, cross-reference them with workspace documents, and
183
+ flag contradictions.
200
184
 
201
- **Anti-pattern NEVER do this:**
202
- - Do NOT use `websearch` directly it is blocked by the Revela plugin;
203
- use research agents instead.
204
- - Do NOT run a few quick searches, decide "that's enough data", and skip the
205
- research agent. The agent's job is deep, systematic research — ad-hoc
206
- fetches cannot replace it.
185
+ After research is complete, use `revela-decks` only for stable, cross-session
186
+ state updates. Do not write temporary hypotheses, unsupported conclusions,
187
+ secrets, or inferred user preferences. User and workflow preferences require
188
+ explicit user intent to remember.
207
189
 
208
- ---
190
+ #### AI Knowledge and User Questions
209
191
 
210
- #### Layer 3 AI Knowledge (Supplementary)
192
+ Use AI knowledge only to fill remaining gaps around verified sources. Mark it
193
+ with `[Source: AI 公开知识,建议核实]` and never present it as verified fact.
211
194
 
212
- After Layer 1 and Layer 2 results are in, use your training data to fill
213
- remaining gaps: industry context, historical background, technical explanations.
214
-
215
- **Critical:** Always mark AI-sourced information with
216
- `[Source: AI 公开知识,建议核实]`. Never present AI knowledge as verified fact.
217
-
218
- This layer is supplementary — it adds context around the hard data from
219
- Layers 1 and 2. It must never be the primary source for quantitative claims
220
- (market size, revenue, growth rates, etc.).
221
-
222
- ---
223
-
224
- #### Layer 4 — Ask the User (Last Resort Only)
225
-
226
- Only ask the user for information that Layers 1, 2, and 3 cannot cover.
227
- When asking, first report what you already know:
228
-
229
- > 我已从 workspace 文档和在线调研中获取了以下信息:
230
- > [brief list of covered topics with source counts]
231
- >
232
- > 以下关键信息我无法从现有资料中获取,需要您补充:
233
- > 1. [specific missing item]
234
- > 2. [specific missing item]
235
-
236
- ---
195
+ Ask the user only for information that `DECKS.json`, workspace files, research
196
+ agents, and AI knowledge cannot cover. When asking, briefly state what you have
197
+ already checked and what specific missing information is needed.
237
198
 
238
199
  #### Rules
239
200
 
240
- - **NEVER** ask the user for information that exists in workspace documents
241
- - **NEVER** skip workspace scanning even if the user's message seems self-contained
242
- - **NEVER** ask "do you have reference files?" just scan and find out
243
- - **NEVER** use `websearch` it is blocked; delegate to research agents instead
244
- - **NEVER** collapse multiple research axes into a single agent call
245
- - **ALWAYS** launch research agents as your first action (parallel with workspace scan)
246
- - **ALWAYS** decompose the topic into independent axes before launching agents
201
+ - **NEVER** use `websearch` directly from the primary agent; delegate web research to `revela-research` subagents
202
+ - **NEVER** call `revela-research` as a tool; use Task with `subagent_type: "revela-research"`
203
+ - **NEVER** collapse distinct research axes into one broad agent brief when parallel focused briefs would be clearer
204
+ - **ALWAYS** use `revela-decks` action `read` before deciding what research is needed
247
205
  - **ALWAYS** read each `researches/{slug}/{axis}.md` after agents complete
248
206
  - Use the `read` tool for all file types — binary formats are handled transparently
249
207
  ---
@@ -327,6 +285,11 @@ Then ask:
327
285
  - On confirmation → proceed to Phase 5
328
286
  - On change request → update the table and ask again
329
287
 
288
+ After the user confirms the slide plan, update `DECKS.json` through `revela-decks`:
289
+ - Call `upsertDeck` to mark completed `requiredInputs` only when explicitly satisfied.
290
+ - Call `upsertSlides` with the confirmed per-slide content, layout, components, and evidence.
291
+ - Keep write readiness blocked until Phase 5 calls `revela-decks review` and the tool returns ready.
292
+
330
293
  ---
331
294
 
332
295
  ### Phase 5 — Generate
@@ -338,9 +301,13 @@ Then ask:
338
301
  you plan to use (comma-separated, e.g. `layout: "cover,two-col,stats,card-grid"`).
339
302
  3. Call `revela-designs` tool with `action: "read"` and `component` set to ALL component
340
303
  names you plan to use (comma-separated, e.g. `component: "card,stat-card,evidence-list"`).
341
- 4. Generate HTML that **exactly matches** the fetched examples — copy the HTML structure verbatim.
304
+ 4. Use `revela-decks` action `upsertDeck` to mark `requiredInputs.designLayoutsFetched` complete.
305
+ 5. Run `/revela review {slug}` or call `revela-decks` action `review` yourself. The tool must compute readiness from `DECKS.json`.
306
+ 6. Use `revela-decks` action `read` and confirm `writeReadiness.status` is `ready` with no blockers.
307
+ 7. Generate HTML that **exactly matches** the fetched examples — copy the HTML structure verbatim.
342
308
 
343
- **NEVER skip steps 2–3. NEVER generate HTML from memory or prior knowledge of the design.**
309
+ **NEVER skip steps 2–6. NEVER generate HTML from memory or prior knowledge of the design.**
310
+ **NEVER write `decks/*.html` while `DECKS.json` says `writeReadiness.status` is `blocked`.**
344
311
 
345
312
  Once the fetch is complete, generate the complete HTML file in one shot.
346
313
 
@@ -356,6 +323,9 @@ After generating, briefly tell the user:
356
323
  - How to navigate (arrow keys / swipe)
357
324
  - One line invitation to request changes
358
325
 
326
+ Then use `revela-decks` to record written/QA status when available. Preserve
327
+ stable decisions in deck memory when useful.
328
+
359
329
  For change requests: re-generate the **entire** file (don't patch). Apply the
360
330
  change and silently overwrite the same `decks/{slug}.html` filename.
361
331