@cyber-dash-tech/revela 0.17.5 → 0.17.7

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 (40) hide show
  1. package/README.md +26 -46
  2. package/README.zh-CN.md +26 -46
  3. package/bin/revela.ts +98 -0
  4. package/lib/commands/review.ts +8 -5
  5. package/lib/deck-html/foundation.ts +190 -0
  6. package/lib/edit/prompt.ts +6 -2
  7. package/lib/edit/server.ts +2 -2
  8. package/lib/inspect/prompt.ts +5 -1
  9. package/lib/qa/index.ts +12 -0
  10. package/lib/refine/comment-requests.ts +77 -0
  11. package/lib/refine/open.ts +5 -2
  12. package/lib/refine/prompt-bridge.ts +219 -0
  13. package/lib/refine/qa-suppression.ts +41 -0
  14. package/lib/refine/server.ts +122 -34
  15. package/lib/runtime/index.ts +225 -0
  16. package/lib/runtime/research.ts +175 -0
  17. package/lib/runtime/review.ts +270 -0
  18. package/lib/runtime/story.ts +53 -0
  19. package/package.json +6 -1
  20. package/plugin.ts +6 -2
  21. package/plugins/revela/.codex-plugin/plugin.json +37 -0
  22. package/plugins/revela/.mcp.json +11 -0
  23. package/plugins/revela/assets/README.md +2 -0
  24. package/plugins/revela/hooks/hooks.json +28 -0
  25. package/plugins/revela/hooks/revela_guard.ts +10 -0
  26. package/plugins/revela/hooks/revela_post_write_notice.ts +18 -0
  27. package/plugins/revela/mcp/revela-server.ts +504 -0
  28. package/plugins/revela/mcp/runtime-resolver.ts +109 -0
  29. package/plugins/revela/skills/revela-design/SKILL.md +20 -0
  30. package/plugins/revela/skills/revela-domain/SKILL.md +18 -0
  31. package/plugins/revela/skills/revela-export/SKILL.md +21 -0
  32. package/plugins/revela/skills/revela-init/SKILL.md +36 -0
  33. package/plugins/revela/skills/revela-make-deck/SKILL.md +37 -0
  34. package/plugins/revela/skills/revela-research/SKILL.md +38 -0
  35. package/plugins/revela/skills/revela-review-deck/SKILL.md +33 -0
  36. package/plugins/revela/skills/revela-story/SKILL.md +24 -0
  37. package/skill/SKILL.md +17 -8
  38. package/tools/deck-foundation.ts +48 -0
  39. package/tools/decks.ts +10 -78
  40. package/tools/research-save.ts +8 -72
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: revela-init
3
+ description: Initialize or refresh a Revela narrative workspace in Codex from local source materials, preserving source traceability and file-native state.
4
+ ---
5
+
6
+ # Revela Init
7
+
8
+ Use this skill when the user asks to start Revela, initialize the workspace, ingest local materials, or prepare a trusted narrative graph.
9
+
10
+ ## Product Contract
11
+
12
+ - `revela-narrative/` is the editable source of truth for communication meaning when present.
13
+ - `NarrativeStateV1` is the compiled internal interface.
14
+ - `deck-plan/` is render planning, not canonical meaning.
15
+ - `decks/*.html` are artifacts.
16
+ - `DECKS.json` is compatibility/cache state, not workflow authority.
17
+ - Do not invent quotes, source paths, URLs, page references, caveats, claim ids, evidence ids, or artifact coverage.
18
+
19
+ ## Workflow
20
+
21
+ 1. Inspect the workspace with normal Codex file tools. Stay inside the current workspace root.
22
+ 2. Prefer local source materials first: Markdown, text, CSV, PDFs, Office files, existing `researches/`, existing `revela-narrative/`, `deck-plan/`, and `decks/`.
23
+ 3. Call `revela_domain_list` and `revela_domain_read` for active domain guidance before authoring narrative meaning. Treat domain guidance as framing guidance, never as evidence.
24
+ 4. If `revela-narrative/` exists, call `revela_markdown_qa` and `revela_compile_narrative`.
25
+ 5. If the narrative vault is missing, create the initial `revela-narrative/` Markdown nodes directly with valid frontmatter and plain wikilink relations.
26
+ 6. Evidence nodes must preserve source, quote/snippet, support scope, unsupported scope, caveat, and strength before being treated as support.
27
+ 7. After writing narrative Markdown, call `revela_markdown_qa` and `revela_compile_narrative` again.
28
+ 8. End with a concise init report: local materials found, active domain, narrative graph status, open gaps, Markdown QA status, and next command/action.
29
+
30
+ ## Markdown Rules
31
+
32
+ - Use node types: `index`, `audience`, `decision`, `thesis`, `claim`, `evidence`, `objection`, `risk`, `research-gap`.
33
+ - Use one leading frontmatter block per file.
34
+ - Use `## Relations` with plain node-id wikilinks, such as `- supports: [[claim-recommendation]]`.
35
+ - Do not use typed wikilinks such as `[[claim:claim-recommendation]]`.
36
+ - Do not duplicate stable headings like `## Evidence`, `## Caveats`, `## Relations`, `## Response`, or `## Mitigation`.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: revela-make-deck
3
+ description: Plan and generate Revela deck artifacts in Codex from canonical narrative state and deck-plan files.
4
+ ---
5
+
6
+ # Revela Make Deck
7
+
8
+ Use this skill when the user asks to make, generate, or update a Revela deck.
9
+
10
+ ## Source Authority
11
+
12
+ - Canonical meaning comes from `revela-narrative/`.
13
+ - Deck execution planning comes from `deck-plan/`.
14
+ - Generated artifacts live under `decks/*.html`.
15
+ - `DECKS.json.slides[]` is not the slide-count contract.
16
+
17
+ ## Workflow
18
+
19
+ 1. Call `revela_compile_narrative` and `revela_markdown_qa`.
20
+ 2. Report narrative and Markdown diagnostics, but treat only malformed/unsafe files and technical artifact validity as hard blockers.
21
+ 3. Call `revela_read_deck_plan`. If missing, author `deck-plan/index.md` and `deck-plan/slides/*.md` before HTML generation.
22
+ 4. For new HTML files, call `revela_create_deck_foundation`.
23
+ 5. Read active design guidance with `revela_design_list` and `revela_design_read` when choosing layouts/components. If the user asks to switch designs persistently, call `revela_design_activate`; if they ask for a one-off design, read that design by name and pass `designName` to `revela_create_deck_foundation`.
24
+ 6. Patch slides into the foundation between Revela slide markers. Preserve positive 1-based `data-slide-index` values.
25
+ 7. Generate chapter by chapter. Keep the HTML valid after each write.
26
+ 8. After every HTML write, call `revela_run_deck_qa` and repair hard errors before review or export.
27
+
28
+ ## QA Repair Loop
29
+
30
+ - `revela_run_deck_qa` launches a browser. In sandboxed Codex sessions, this may require user-approved command escalation.
31
+ - If QA reports `text_overflow` or `text_clipped`, reduce font size, line length, padding, or line-height before changing narrative meaning.
32
+ - Prefer conservative cover and section-title sizing in smoke or diagnostic decks.
33
+ - If QA reports that a standalone smoke artifact is not the active legacy deck target, treat it as a non-blocking warning when slide identity and canvas checks pass.
34
+
35
+ ## Deck Plan Requirements
36
+
37
+ Every deck plan should include Cover, Table of Contents, and Closing. Use 3-5 chapter headings, explicit slide ranges, low-fidelity layout sketches, narrative links, visual intent, evidence trace, and caveats.
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: revela-research
3
+ description: Research Revela story gaps and bind evidence in Codex while preserving explicit source boundaries.
4
+ ---
5
+
6
+ # Revela Research
7
+
8
+ Use this skill when the user asks to research, close evidence gaps, evaluate saved findings, or bind support to current Revela claims.
9
+
10
+ ## Contract
11
+
12
+ - Saved findings in `researches/**/*.md` are not canonical evidence until specific evidence nodes or bindings preserve source trace, quote/snippet, support scope, unsupported scope, caveat, and strength.
13
+ - Missing evidence must stay visible as a gap.
14
+ - Do not broaden claims to fit a source.
15
+ - Do not write deck artifacts during research.
16
+
17
+ ## Workflow
18
+
19
+ 1. Call `revela_research_targets` to derive target order, selected target, saved findings diagnostics, and evidence gaps.
20
+ 2. For existing saved findings, call `revela_evaluate_research_findings` before deciding whether they can support a claim.
21
+ 3. Use external research only when the user allowed or requested it and the gap is publicly researchable.
22
+ 4. After external research, call `revela_research_save` with structured Markdown findings and explicit source list.
23
+ 5. Bind only when `bindingEval.status === "bindable"` by calling `revela_bind_research_findings`; do not hand-author evidence Markdown for bindable saved findings.
24
+ 6. If a finding is incomplete, report missing fields instead of inventing them.
25
+ 7. After binding or any narrative edit, call `revela_markdown_qa` and `revela_compile_narrative`.
26
+ 8. Report evidence bound, unbound findings, remaining caveats, and the next smallest story action.
27
+
28
+ ## Binding Criteria
29
+
30
+ Bind only when the supported claim exists and the evidence includes:
31
+
32
+ - source URL/path/findings file
33
+ - quote or traceable snippet
34
+ - support scope
35
+ - unsupported scope
36
+ - caveat
37
+ - strength
38
+ - explicit supported claim context
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: revela-review-deck
3
+ description: Review Revela deck artifacts in Codex for technical validity, evidence trace, and narrative alignment.
4
+ ---
5
+
6
+ # Revela Review Deck
7
+
8
+ Use this skill when the user asks to review, inspect, diagnose, or refine a generated Revela deck.
9
+
10
+ ## Workflow
11
+
12
+ 1. Resolve the target `decks/*.html` file from the user request or unambiguous workspace state.
13
+ 2. For a plain request like `review decks/foo.html`, call `revela_review_deck_open` and let the tool open the browser by default.
14
+ 3. Use `revela_review_deck_read`, normally with `format: "markdown"`, only when the user explicitly asks to diagnose, QA, read, check, inspect source alignment, inspect evidence trace, or avoid opening a GUI.
15
+ 4. Use the read output as the deterministic diagnostics packet for artifact QA, deck-plan diagnostics, narrative/vault diagnostics, artifact coverage, and evidence trace.
16
+ 5. Pass `openBrowser: false` only for tests, no-GUI environments, or when the user explicitly asks for a link instead of opening the page.
17
+ 6. Do not call `revela_run_deck_qa`, `revela_compile_narrative`, or `revela_read_deck_plan` separately for a normal Review UI open.
18
+ 7. Call `revela_run_deck_qa` separately only for focused low-level artifact QA, after a repair, or when the user explicitly asks for QA detail.
19
+ 8. Separate technical blockers from narrative/evidence diagnostics.
20
+ 9. Pure visual/layout/export fixes may patch artifacts directly when the user asks for a change. Meaning changes must update `revela-narrative/` first.
21
+
22
+ ## QA Notes
23
+
24
+ - `revela_review_deck_read` is read-only: it must not mutate deck HTML, `revela-narrative/`, `deck-plan/`, assets, or compatibility state.
25
+ - `revela_review_deck_open` opens the local Review server from the MCP process and uses the Codex `codex-exec` bridge for Insight and Comment/Apply Fix. It returns URL/token/open state and basic file metadata, not aggregate diagnostics.
26
+ - `revela_run_deck_qa` may need browser-launch permission in Codex sandboxed sessions.
27
+ - Repair hard QA errors before treating a deck as review-ready.
28
+ - Text clipping should usually be fixed with typography and spacing changes, not by deleting evidence or changing claim meaning.
29
+ - A warning that a smoke/development artifact is not the active legacy deck target is non-blocking when the requested file passes hard artifact checks.
30
+
31
+ ## Technical Blockers
32
+
33
+ Hard blockers are limited to missing or ambiguous files, invalid HTML contract, invalid slide identity, canvas/export failure, malformed Markdown/frontmatter, or unsafe writes.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: revela-story
3
+ description: Inspect a Revela narrative graph in Codex without mutating artifacts or canonical meaning.
4
+ ---
5
+
6
+ # Revela Story
7
+
8
+ Use this skill when the user asks to view, inspect, understand, or audit the current Revela story.
9
+
10
+ ## Workflow
11
+
12
+ 1. Call `revela_story_read` first, normally with `format: "markdown"`.
13
+ 2. Use the returned deterministic map, diagnostics, narrative hash, and Markdown reading view as the authoritative Story surface.
14
+ 3. Call `revela_markdown_qa` or `revela_compile_narrative` only when you need deeper structural diagnostics than `revela_story_read` returned.
15
+ 4. If `revela_story_read.ok` is false because `revela-narrative/` is missing, report the init guidance. Do not create files from Story mode.
16
+ 5. Present audience, belief shift, decision/action, thesis, central claims, evidence, objections, risks, research gaps, artifact coverage, and diagnostics.
17
+ 6. Keep claim ids, evidence ids, source facts, quotes, URLs, numbers, and caveats exact.
18
+ 7. Do not write claims, evidence, research gaps, deck HTML, deck-plan files, assets, or artifacts from Story mode.
19
+
20
+ ## Output
21
+
22
+ Lead with the narrative status, narrative hash, and key diagnostics. Then show the claim flow and evidence boundaries.
23
+
24
+ Keep the reading evidence-first: for each claim, show source trace, support scope, unsupported scope, caveat, support strength, linked objections/risks, and remaining research gaps. Separate structural Markdown QA from evidence trust. Story is read-only; do not turn it into a mutation workflow.
package/skill/SKILL.md CHANGED
@@ -64,8 +64,13 @@ handoff exactly:
64
64
  5. Use `readDeckPlan` to inspect the current `deck-plan/` projection before
65
65
  artifact review or HTML generation. Diagnostics are advisory unless they are
66
66
  artifact validity errors handled by QA.
67
- 6. Fetch the required design layouts/components with `revela-designs read`.
68
- 7. Write HTML when the user proceeds and the deck contract can be satisfied.
67
+ 6. For a new deck HTML file, call `revela-deck-foundation` to create the
68
+ active-design foundation shell. The helper is file-native and must not create
69
+ narrative slide content, choose layouts/components, or read/write `DECKS.json`.
70
+ 7. Fetch the required design rules, layouts, and components with
71
+ `revela-designs read`.
72
+ 8. Patch slides between the foundation shell's `revela-slides` markers when the
73
+ user proceeds and the deck contract can be satisfied.
69
74
 
70
75
  Before any HTML generation, call `revela-decks` action `readDeckPlan` and follow
71
76
  the current `deck-plan/`: Source Authority, deck parameters, Chapter Writing
@@ -164,7 +169,8 @@ one broad `write`, `edit`, or `apply_patch` call.
164
169
 
165
170
  For decks with 5 or more slides:
166
171
 
167
- - First create a stable HTML shell plus structural slides and the first chapter.
172
+ - First call `revela-deck-foundation` for new files, then patch structural
173
+ slides and the first chapter between the `revela-slides` markers.
168
174
  - Then fill or revise exactly one chapter range at a time.
169
175
  - Do not mix multiple central-claim chapters in the same write.
170
176
  - Chapter divider or chapter TOC slides are allowed as structural wayfinding and
@@ -193,14 +199,17 @@ If a write produces QA hard errors, fix them before continuing.
193
199
  Before writing or materially changing HTML:
194
200
 
195
201
  1. Read the deck-plan projection's layout and component names.
196
- 2. Call `revela-designs` with `action: "read"` and `section: "rules"` to fetch
202
+ 2. For a new deck HTML file, call `revela-deck-foundation` before adding slide
203
+ content. Use `mode: "repair"` only for explicit foundation repair or QA
204
+ foundation contract fixes, not normal Review Comment edits.
205
+ 3. Call `revela-designs` with `action: "read"` and `section: "rules"` to fetch
197
206
  the active design's current composition and usage rules.
198
- 3. Call `revela-designs` with `action: "read"` and `layout` set to all required
207
+ 4. Call `revela-designs` with `action: "read"` and `layout` set to all required
199
208
  layout names, comma-separated.
200
- 4. Call `revela-designs` with `action: "read"` and `component` set to all
209
+ 5. Call `revela-designs` with `action: "read"` and `component` set to all
201
210
  required component names, comma-separated.
202
- 5. Fetch `section: "chart-rules"` before using ECharts.
203
- 6. Do not update legacy `requiredInputs`; design fetching is an execution step,
211
+ 6. Fetch `section: "chart-rules"` before using ECharts.
212
+ 7. Do not update legacy `requiredInputs`; design fetching is an execution step,
204
213
  not a workflow permission gate.
205
214
 
206
215
  Never generate HTML from memory or prior knowledge of a design. Copy the fetched
@@ -0,0 +1,48 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import { createDeckFoundation } from "../lib/deck-html/foundation"
3
+
4
+ export default tool({
5
+ description:
6
+ "Create or repair a file-native Revela deck HTML foundation shell from the active design. " +
7
+ "Writes a deterministic empty deck shell with doctype, html/head/body, active design foundation CSS, complete SlidePresentation JavaScript, and stable slide insertion markers. " +
8
+ "It does not create narrative slide content, choose layouts/components, or read/write DECKS.json.",
9
+ args: {
10
+ outputPath: tool.schema
11
+ .string()
12
+ .describe("Workspace-relative HTML output path, usually decks/{name}.html."),
13
+ title: tool.schema
14
+ .string()
15
+ .describe("Presentation title for the HTML <title> tag only; this does not create a cover slide."),
16
+ language: tool.schema
17
+ .string()
18
+ .describe("HTML language tag, e.g. en or zh-CN."),
19
+ designName: tool.schema
20
+ .string()
21
+ .optional()
22
+ .describe("Optional design name. Defaults to the active design."),
23
+ mode: tool.schema
24
+ .enum(["create", "repair"])
25
+ .optional()
26
+ .describe("create protects existing files unless overwrite=true; repair may replace an existing foundation shell."),
27
+ overwrite: tool.schema
28
+ .boolean()
29
+ .optional()
30
+ .describe("Whether create mode may overwrite an existing HTML file. Defaults to false."),
31
+ },
32
+ async execute(args, { directory }) {
33
+ try {
34
+ const result = createDeckFoundation({
35
+ workspaceRoot: directory || process.cwd(),
36
+ outputPath: args.outputPath,
37
+ title: args.title,
38
+ language: args.language,
39
+ designName: args.designName,
40
+ mode: args.mode,
41
+ overwrite: args.overwrite ?? false,
42
+ })
43
+ return JSON.stringify(result, null, 2)
44
+ } catch (e: any) {
45
+ return JSON.stringify({ error: e?.message || String(e) })
46
+ }
47
+ },
48
+ })
package/tools/decks.ts CHANGED
@@ -34,30 +34,12 @@ import {
34
34
  import { compileDeckPlanFromNarrative } from "../lib/narrative-state/render-plan"
35
35
  import { DECK_PLAN_ARTIFACT_PATH, readDeckPlanArtifact } from "../lib/narrative-state/deck-plan-artifact"
36
36
  import { backfillSlideClaimRefsFromCoverage } from "../lib/narrative-state/coverage"
37
- import { closeResearchGapInState, deriveResearchGapsFromReadiness, deriveResearchTargets, updateResearchGapInState, upsertResearchGapsInState } from "../lib/narrative-state/research-gaps"
38
- import { evaluateResearchFindingsBinding } from "../lib/narrative-state/research-binding-eval"
39
- import { computeNarrativeHash, stableEvidenceId } from "../lib/narrative-state/hash"
37
+ import { closeResearchGapInState, deriveResearchGapsFromReadiness, updateResearchGapInState, upsertResearchGapsInState } from "../lib/narrative-state/research-gaps"
38
+ import { computeNarrativeHash } from "../lib/narrative-state/hash"
40
39
  import { normalizeNarrativeState } from "../lib/narrative-state/normalize"
41
40
  import { buildNarrativeVaultInventory, compileNarrativeVault, exportNarrativeStateToVault, formatVaultDiagnosticReport, getNarrativeVaultMigrationHint, hasNarrativeVault, initNarrativeVault, narrativeVaultAuthoringContract, narrativeVaultTimestampMs, removeVaultRelation, runNarrativeMarkdownQa, updateVaultCoreNodes, updateVaultResearchGapNode, upsertVaultClaimNode, upsertVaultEvidenceNode, upsertVaultObjectionNode, upsertVaultRelation, upsertVaultRiskNode, VAULT_MIGRATION_PRESERVED_IN_DECKS_JSON } from "../lib/narrative-vault"
42
41
  import { compileCacheMirrorNarrativeVault } from "../lib/narrative-vault/compile-mirror"
43
-
44
- function missingBindableEvidenceFields(input: Record<string, unknown>): string[] {
45
- const missing: string[] = []
46
- for (const key of ["id", "claimId", "source", "quote", "supportScope", "unsupportedScope", "caveat", "strength"] as const) {
47
- if (!String(input[key] ?? "").trim()) missing.push(key)
48
- }
49
- if (!String(input.sourcePath ?? "").trim() && !String(input.url ?? "").trim() && !String(input.findingsFile ?? "").trim()) missing.push("sourcePath|url|findingsFile")
50
- return missing
51
- }
52
-
53
- function exactResearchGapForBinding(state: DecksState, findingsFile: string, claimId: string) {
54
- const gaps = state.narrative?.researchGaps ?? []
55
- const exact = gaps.filter((gap) => gap.targetType === "claim" && gap.targetId === claimId && gap.findingsFile === findingsFile)
56
- if (exact.length === 1) return exact[0]
57
- if (exact.length > 1) return undefined
58
- const byClaim = gaps.filter((gap) => gap.targetType === "claim" && gap.targetId === claimId && !gap.findingsFile)
59
- return byClaim.length === 1 ? byClaim[0] : undefined
60
- }
42
+ import { bindResearchFindings as bindResearchFindingsRuntime, evaluateResearchFindings as evaluateResearchFindingsRuntime, researchTargets as researchTargetsRuntime } from "../lib/runtime/research"
61
43
 
62
44
  function forbiddenVaultCompatibilityAction(action: string, replacement: string): string {
63
45
  return `${action} is a JSON-era compatibility action and is blocked in vault workspaces. Use ${replacement}, or patch the existing Markdown node only for a small read-before-edit repair.`
@@ -469,54 +451,11 @@ export default tool({
469
451
  }
470
452
 
471
453
  if (args.action === "bindResearchFindings") {
472
- if (!hasNarrativeVault(workspaceRoot)) return JSON.stringify({ ok: false, error: "bindResearchFindings requires revela-narrative/ to exist. Use initNarrativeVault first, then evaluateResearchFindings." })
473
- if (!args.findingsFile?.trim()) return JSON.stringify({ ok: false, error: "findingsFile is required for bindResearchFindings" })
474
- const bindingEval = evaluateResearchFindingsBinding(state, workspaceRoot, args.findingsFile)
475
- if (bindingEval.status !== "bindable" || !bindingEval.claimId || !bindingEval.recommendedEvidenceDraft) {
476
- return JSON.stringify({ ok: false, skipped: true, reason: "findings are not safely bindable", bindingEval }, null, 2)
477
- }
478
- const draft = bindingEval.recommendedEvidenceDraft
479
- const evidence = {
480
- id: args.evidence?.id?.trim() || stableEvidenceId(bindingEval.claimId, `${bindingEval.findingsFile}:${draft.quote ?? ""}`),
481
- claimId: bindingEval.claimId,
482
- source: draft.source,
483
- sourcePath: draft.sourcePath,
484
- findingsFile: draft.findingsFile ?? bindingEval.findingsFile,
485
- quote: draft.quote,
486
- location: draft.location,
487
- url: draft.url,
488
- caveat: draft.caveat,
489
- supportScope: draft.supportScope,
490
- unsupportedScope: draft.unsupportedScope,
491
- strength: draft.strength,
492
- }
493
- const missing = missingBindableEvidenceFields(evidence)
494
- if (missing.length > 0) return JSON.stringify({ ok: false, skipped: true, reason: "recommended evidence draft is incomplete", missingFields: missing, bindingEval }, null, 2)
495
-
496
- const mutation = upsertVaultEvidenceNode(workspaceRoot, evidence as any)
497
- if (!mutation.ok) return JSON.stringify({ ok: false, mutation, bindingEval }, null, 2)
498
- const gap = exactResearchGapForBinding(state, bindingEval.findingsFile, bindingEval.claimId)
499
- const gapMutation = gap
500
- ? updateVaultResearchGapNode(workspaceRoot, {
501
- id: gap.id,
502
- status: "evidence_bound",
503
- findingsFile: bindingEval.findingsFile,
504
- evidenceBindingIds: [...new Set([...(gap.evidenceBindingIds ?? []), evidence.id])],
505
- notes: gap.notes,
506
- })
507
- : undefined
508
- const compiled = compileCacheMirrorNarrativeVault(workspaceRoot, mirrorOptions())
509
- return JSON.stringify({
510
- ok: compiled.result.ok,
511
- path: mutation.file,
512
- bindingEval,
513
- mutation,
514
- gapMutation: gapMutation ?? { ok: true, skipped: true, reason: "no exact single research gap matched this findings file and claim" },
515
- evidence,
516
- diagnostics: compiled.result.diagnostics,
517
- diagnosticReport: compiled.diagnosticReport,
518
- narrative: compiled.result.narrative,
519
- }, null, 2)
454
+ return JSON.stringify(bindResearchFindingsRuntime({
455
+ workspaceRoot,
456
+ findingsFile: args.findingsFile ?? "",
457
+ evidenceId: args.evidence?.id,
458
+ }), null, 2)
520
459
  }
521
460
 
522
461
  if (args.action === "updateVaultResearchGap") {
@@ -799,18 +738,11 @@ export default tool({
799
738
  }
800
739
 
801
740
  if (args.action === "deriveResearchTargets") {
802
- const result = deriveResearchTargets(state, { workspaceRoot })
803
- return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result }, null, 2)
741
+ return JSON.stringify(researchTargetsRuntime({ workspaceRoot }), null, 2)
804
742
  }
805
743
 
806
744
  if (args.action === "evaluateResearchFindings") {
807
- if (!args.findingsFile?.trim()) return JSON.stringify({ ok: false, error: "findingsFile is required for evaluateResearchFindings" })
808
- const bindingEval = evaluateResearchFindingsBinding(state, workspaceRoot, args.findingsFile)
809
- const targets = deriveResearchTargets(state, { workspaceRoot })
810
- const vaultDiagnostics = hasNarrativeVault(workspaceRoot)
811
- ? formatVaultDiagnosticReport(compileNarrativeVault(workspaceRoot, { fallbackApprovals: state.narrative?.approvals ?? [] }).diagnostics)
812
- : undefined
813
- return JSON.stringify({ ok: true, path: DECKS_STATE_FILE, result: { bindingEval, selected: targets.selected, vaultDiagnostics } }, null, 2)
745
+ return JSON.stringify(evaluateResearchFindingsRuntime({ workspaceRoot, findingsFile: args.findingsFile ?? "" }), null, 2)
814
746
  }
815
747
 
816
748
  if (args.action === "upsertResearchGaps") {
@@ -1,46 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
- import { mkdirSync, writeFileSync } from "fs"
3
- import { join } from "path"
4
- import { hasDecksState, readDecksState, writeDecksState } from "../lib/decks-state"
5
- import { evaluateResearchFindingsBinding } from "../lib/narrative-state/research-binding-eval"
6
- import { recordWorkspaceAction } from "../lib/workspace-state/actions"
7
-
8
- /**
9
- * Format today's date as YYYY-MM-DD
10
- */
11
- function today(): string {
12
- return new Date().toISOString().slice(0, 10)
13
- }
14
-
15
- /**
16
- * Sanitize a key: lowercase, alphanumeric + hyphens only.
17
- */
18
- function keyify(s: string): string {
19
- return s
20
- .toLowerCase()
21
- .replace(/[^a-z0-9]+/g, "-")
22
- .replace(/^-+|-+$/g, "")
23
- }
24
-
25
- /**
26
- * Build YAML frontmatter string.
27
- */
28
- function buildFrontmatter(topic: string, axis: string, sources: string[]): string {
29
- const lines = [
30
- "---",
31
- `topic: ${topic}`,
32
- `axis: ${axis}`,
33
- `date: ${today()}`,
34
- ]
35
- if (sources.length > 0) {
36
- lines.push("sources:")
37
- for (const s of sources) {
38
- lines.push(` - "${s.replace(/"/g, '\\"')}"`)
39
- }
40
- }
41
- lines.push("---")
42
- return lines.join("\n")
43
- }
2
+ import { researchSave } from "../lib/runtime/research"
44
3
 
45
4
  export default tool({
46
5
  description:
@@ -77,36 +36,13 @@ export default tool({
77
36
  },
78
37
  async execute(args, context) {
79
38
  try {
80
- const topicKey = keyify(args.topic || "research")
81
- const fileKey = keyify(args.filename || "findings")
82
- const workspaceDir = context.directory ?? process.cwd()
83
- const topicDir = join(workspaceDir, "researches", topicKey)
84
-
85
- mkdirSync(topicDir, { recursive: true })
86
-
87
- const frontmatter = buildFrontmatter(args.topic, fileKey, args.sources ?? [])
88
- const fileContent = `${frontmatter}\n\n${args.content ?? ""}\n`
89
- const filePath = join(topicDir, `${fileKey}.md`)
90
- const relPath = `researches/${topicKey}/${fileKey}.md`
91
-
92
- writeFileSync(filePath, fileContent, "utf-8")
93
-
94
- if (hasDecksState(workspaceDir)) {
95
- const state = readDecksState(workspaceDir)
96
- recordWorkspaceAction(state, {
97
- type: "research.findings_saved",
98
- actor: "revela-research-save",
99
- inputs: { topic: topicKey, axis: fileKey, sourceCount: args.sources?.length ?? 0 },
100
- outputs: { path: relPath, sources: args.sources ?? [] },
101
- summary: `Saved research findings for ${topicKey}/${fileKey}.`,
102
- nodeIds: [`finding:${relPath}`],
103
- })
104
- const bindingEval = evaluateResearchFindingsBinding(state, workspaceDir, relPath)
105
- writeDecksState(workspaceDir, state)
106
- return JSON.stringify({ ok: true, path: relPath, bindingEval })
107
- }
108
-
109
- return JSON.stringify({ ok: true, path: relPath })
39
+ return JSON.stringify(researchSave({
40
+ topic: args.topic,
41
+ filename: args.filename,
42
+ content: args.content,
43
+ sources: args.sources,
44
+ workspaceRoot: context.directory ?? process.cwd(),
45
+ }))
110
46
  } catch (e: any) {
111
47
  return JSON.stringify({ error: e.message || String(e) })
112
48
  }