@cyber-dash-tech/revela 0.4.6 → 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/README.md +82 -2
- package/README.zh-CN.md +85 -2
- package/lib/agents/research-prompt.ts +37 -10
- package/lib/commands/help.ts +3 -0
- package/lib/commands/init.ts +68 -0
- package/lib/commands/remember.ts +46 -0
- package/lib/commands/review.ts +68 -0
- package/lib/decks-memory.ts +509 -0
- package/lib/decks-state.ts +452 -0
- package/package.json +1 -1
- package/plugin.ts +220 -14
- package/skill/SKILL.md +123 -153
- package/tools/decks.ts +158 -0
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
|
-
|
|
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-
|
|
375
|
-
//
|
|
376
|
-
//
|
|
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
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
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,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
1
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
####
|
|
144
|
+
#### Deep Research via `revela-research` Subagents
|
|
155
145
|
|
|
156
|
-
|
|
157
|
-
with `subagent_type: "revela-research"
|
|
158
|
-
research
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
-
|
|
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
|
-
|
|
178
|
+
#### After Agents Complete
|
|
193
179
|
|
|
194
|
-
List and read the findings files
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
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**
|
|
241
|
-
- **NEVER**
|
|
242
|
-
- **NEVER**
|
|
243
|
-
- **
|
|
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.
|
|
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–
|
|
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
|
|