@cyber-dash-tech/revela 0.18.3 → 0.18.4
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 +4 -4
- package/README.zh-CN.md +4 -4
- package/lib/decks-state.ts +1 -0
- package/lib/document-materials/extract.ts +25 -20
- package/lib/material-intake.ts +9 -4
- package/lib/narrative-vault/constants.ts +1 -1
- package/lib/narrative-vault/paths.ts +7 -2
- package/lib/runtime/index.ts +3 -2
- package/lib/workspace-meta.ts +32 -0
- package/package.json +1 -1
- package/plugin.ts +4 -3
- package/plugins/revela/.mcp.json +1 -1
- package/plugins/revela/hooks/revela_guard.ts +2 -2
- package/tools/workspace-scan.ts +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
@@ -34,7 +34,7 @@ To install globally, add the same entry to `~/.config/opencode/opencode.json`.
|
|
|
34
34
|
Requirements:
|
|
35
35
|
|
|
36
36
|
- The Codex CLI must be installed and the `codex` command must be available in your shell.
|
|
37
|
-
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.4 mcp` to start the MCP server.
|
|
38
38
|
- For interactive Review actions, `codex exec` must also work because the Review UI uses it for Comment/Apply Fix requests.
|
|
39
39
|
|
|
40
40
|
Optional preflight:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
Install Revela through the Codex Git marketplace:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.4
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.4 mcp` so npm can fetch the published package and its dependencies.
|
|
63
63
|
|
|
64
64
|
You do not need to run `bun install` inside the Codex marketplace clone.
|
|
65
65
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
@@ -34,7 +34,7 @@ Revela 可在 [OpenCode](https://opencode.ai) 和 Codex 中使用,把来源材
|
|
|
34
34
|
环境要求:
|
|
35
35
|
|
|
36
36
|
- 需要已安装 Codex CLI,并且 shell 中可以执行 `codex`。
|
|
37
|
-
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.4 mcp` 启动 MCP server。
|
|
38
38
|
- 如果使用 Review UI 的 Comment 或 Apply Fix,需要 `codex exec` 可用。
|
|
39
39
|
|
|
40
40
|
可选的安装前检查:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
通过 Codex Git marketplace 安装 Revela:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.4
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.4 mcp`,由 npm 获取已发布 package 及其 dependencies。
|
|
63
63
|
|
|
64
64
|
不需要在 Codex marketplace clone 里运行 `bun install`。
|
|
65
65
|
|
package/lib/decks-state.ts
CHANGED
|
@@ -1651,6 +1651,7 @@ function isIgnorableSourceMaterial(path: string): boolean {
|
|
|
1651
1651
|
normalized.startsWith("decks/") ||
|
|
1652
1652
|
normalized.startsWith("researches/") ||
|
|
1653
1653
|
normalized.startsWith("assets/") ||
|
|
1654
|
+
normalized.startsWith(".revela/") ||
|
|
1654
1655
|
normalized.startsWith(".opencode/"),
|
|
1655
1656
|
)
|
|
1656
1657
|
}
|
|
@@ -11,6 +11,7 @@ import { extractXlsx } from "../read-hooks/extractors/xlsx"
|
|
|
11
11
|
import { hasDecksState, readDecksState, writeDecksState } from "../decks-state"
|
|
12
12
|
import { computeSourceFingerprint, sourceMaterialMetadata, upsertSourceMaterial } from "../source-materials"
|
|
13
13
|
import { recordWorkspaceAction } from "../workspace-state/actions"
|
|
14
|
+
import { existingWorkspaceMetaPath, workspaceMetaPath } from "../workspace-meta"
|
|
14
15
|
|
|
15
16
|
export type DocumentMaterial = {
|
|
16
17
|
path: string
|
|
@@ -852,7 +853,7 @@ async function extractPdfImages(buf: Buffer, cacheDir: string, workspaceDir: str
|
|
|
852
853
|
async function processPdfFile(filePath: string, workspaceDir: string): Promise<DocumentMaterialsResult> {
|
|
853
854
|
const relativeSource = workspaceRelative(filePath, workspaceDir)
|
|
854
855
|
const fingerprint = buildFingerprint(filePath)
|
|
855
|
-
const cacheDir =
|
|
856
|
+
const cacheDir = existingWorkspaceMetaPath(workspaceDir, "doc-materials", fingerprint)
|
|
856
857
|
const manifestPath = join(cacheDir, "manifest.json")
|
|
857
858
|
|
|
858
859
|
if (existsSync(manifestPath)) {
|
|
@@ -874,19 +875,21 @@ async function processPdfFile(filePath: string, workspaceDir: string): Promise<D
|
|
|
874
875
|
}
|
|
875
876
|
}
|
|
876
877
|
|
|
877
|
-
|
|
878
|
-
|
|
878
|
+
const writeCacheDir = workspaceMetaPath(workspaceDir, "doc-materials", fingerprint)
|
|
879
|
+
const writeManifestPath = join(writeCacheDir, "manifest.json")
|
|
880
|
+
mkdirSync(join(writeCacheDir, "images"), { recursive: true })
|
|
881
|
+
mkdirSync(join(writeCacheDir, "tables"), { recursive: true })
|
|
879
882
|
|
|
880
883
|
const buf = readFileSync(filePath)
|
|
881
884
|
const text = await extractPdfText(buf)
|
|
882
|
-
const textPath = join(
|
|
885
|
+
const textPath = join(writeCacheDir, "text.txt")
|
|
883
886
|
writeFileSync(textPath, `[Extracted from: ${basename(filePath)}]\n\n${text}`, "utf-8")
|
|
884
887
|
|
|
885
|
-
const images = await extractPdfImages(buf,
|
|
886
|
-
const relativeManifestPath = workspaceRelative(
|
|
888
|
+
const images = await extractPdfImages(buf, writeCacheDir, workspaceDir)
|
|
889
|
+
const relativeManifestPath = workspaceRelative(writeManifestPath, workspaceDir)
|
|
887
890
|
const relativeTextPath = workspaceRelative(textPath, workspaceDir)
|
|
888
891
|
const readViewPath = writeReadView({
|
|
889
|
-
cacheDir,
|
|
892
|
+
cacheDir: writeCacheDir,
|
|
890
893
|
workspaceDir,
|
|
891
894
|
source: relativeSource,
|
|
892
895
|
type: "pdf",
|
|
@@ -905,7 +908,7 @@ async function processPdfFile(filePath: string, workspaceDir: string): Promise<D
|
|
|
905
908
|
cache_status: "miss",
|
|
906
909
|
source: relativeSource,
|
|
907
910
|
type: "pdf",
|
|
908
|
-
cache_dir: workspaceRelative(
|
|
911
|
+
cache_dir: workspaceRelative(writeCacheDir, workspaceDir),
|
|
909
912
|
manifest_path: relativeManifestPath,
|
|
910
913
|
text_path: relativeTextPath,
|
|
911
914
|
read_view_path: readViewPath,
|
|
@@ -929,14 +932,14 @@ async function processPdfFile(filePath: string, workspaceDir: string): Promise<D
|
|
|
929
932
|
tables: [],
|
|
930
933
|
}
|
|
931
934
|
|
|
932
|
-
writeFileSync(
|
|
935
|
+
writeFileSync(writeManifestPath, JSON.stringify(manifest, null, 2), "utf-8")
|
|
933
936
|
return result
|
|
934
937
|
}
|
|
935
938
|
|
|
936
939
|
async function processOfficeFile(filePath: string, workspaceDir: string, type: SupportedType): Promise<DocumentMaterialsResult> {
|
|
937
940
|
const relativeSource = workspaceRelative(filePath, workspaceDir)
|
|
938
941
|
const fingerprint = buildFingerprint(filePath)
|
|
939
|
-
const cacheDir =
|
|
942
|
+
const cacheDir = existingWorkspaceMetaPath(workspaceDir, "doc-materials", fingerprint)
|
|
940
943
|
const manifestPath = join(cacheDir, "manifest.json")
|
|
941
944
|
|
|
942
945
|
if (existsSync(manifestPath)) {
|
|
@@ -958,8 +961,10 @@ async function processOfficeFile(filePath: string, workspaceDir: string, type: S
|
|
|
958
961
|
}
|
|
959
962
|
}
|
|
960
963
|
|
|
961
|
-
|
|
962
|
-
|
|
964
|
+
const writeCacheDir = workspaceMetaPath(workspaceDir, "doc-materials", fingerprint)
|
|
965
|
+
const writeManifestPath = join(writeCacheDir, "manifest.json")
|
|
966
|
+
mkdirSync(join(writeCacheDir, "images"), { recursive: true })
|
|
967
|
+
mkdirSync(join(writeCacheDir, "tables"), { recursive: true })
|
|
963
968
|
|
|
964
969
|
const buf = readFileSync(filePath)
|
|
965
970
|
const files = unzipSync(new Uint8Array(buf))
|
|
@@ -970,26 +975,26 @@ async function processOfficeFile(filePath: string, workspaceDir: string, type: S
|
|
|
970
975
|
? await extractDocx(buf)
|
|
971
976
|
: await extractXlsx(buf)
|
|
972
977
|
|
|
973
|
-
const textPath = join(
|
|
978
|
+
const textPath = join(writeCacheDir, "text.txt")
|
|
974
979
|
writeFileSync(textPath, `[Extracted from: ${basename(filePath)}]\n\n${text}`, "utf-8")
|
|
975
980
|
|
|
976
981
|
const pptxAssets = type === "pptx"
|
|
977
|
-
? extractPptxImages(files,
|
|
982
|
+
? extractPptxImages(files, writeCacheDir, workspaceDir)
|
|
978
983
|
: null
|
|
979
984
|
const images = type === "pptx"
|
|
980
985
|
? pptxAssets!.images
|
|
981
986
|
: type === "docx"
|
|
982
|
-
? extractDocxImages(files,
|
|
983
|
-
: extractXlsxImages(files,
|
|
987
|
+
? extractDocxImages(files, writeCacheDir, workspaceDir)
|
|
988
|
+
: extractXlsxImages(files, writeCacheDir, workspaceDir)
|
|
984
989
|
const slides = type === "pptx"
|
|
985
990
|
? extractPptxSlides(files, images, pptxAssets!.skipped_assets)
|
|
986
991
|
: undefined
|
|
987
|
-
const relativeManifestPath = workspaceRelative(
|
|
992
|
+
const relativeManifestPath = workspaceRelative(writeManifestPath, workspaceDir)
|
|
988
993
|
const relativeTextPath = workspaceRelative(textPath, workspaceDir)
|
|
989
994
|
const tables = extractTables(type, relativeTextPath)
|
|
990
995
|
const skippedAssets = pptxAssets?.skipped_assets ?? []
|
|
991
996
|
const readViewPath = writeReadView({
|
|
992
|
-
cacheDir,
|
|
997
|
+
cacheDir: writeCacheDir,
|
|
993
998
|
workspaceDir,
|
|
994
999
|
source: relativeSource,
|
|
995
1000
|
type,
|
|
@@ -1008,7 +1013,7 @@ async function processOfficeFile(filePath: string, workspaceDir: string, type: S
|
|
|
1008
1013
|
cache_status: "miss",
|
|
1009
1014
|
source: relativeSource,
|
|
1010
1015
|
type,
|
|
1011
|
-
cache_dir: workspaceRelative(
|
|
1016
|
+
cache_dir: workspaceRelative(writeCacheDir, workspaceDir),
|
|
1012
1017
|
manifest_path: relativeManifestPath,
|
|
1013
1018
|
text_path: relativeTextPath,
|
|
1014
1019
|
read_view_path: readViewPath,
|
|
@@ -1032,7 +1037,7 @@ async function processOfficeFile(filePath: string, workspaceDir: string, type: S
|
|
|
1032
1037
|
tables: result.tables ?? [],
|
|
1033
1038
|
}
|
|
1034
1039
|
|
|
1035
|
-
writeFileSync(
|
|
1040
|
+
writeFileSync(writeManifestPath, JSON.stringify(manifest, null, 2), "utf-8")
|
|
1036
1041
|
return result
|
|
1037
1042
|
}
|
|
1038
1043
|
|
package/lib/material-intake.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { basename, extname, isAbsolute, join, relative, resolve, sep } from "pat
|
|
|
3
3
|
import { extractDocumentMaterials, type DocumentMaterialsResult } from "./document-materials/extract"
|
|
4
4
|
import { sourceMaterialMetadata, sourceMaterialType } from "./source-materials"
|
|
5
5
|
import type { SourceMaterial } from "./decks-state"
|
|
6
|
+
import { existingWorkspaceMetaPath, workspaceMetaPath } from "./workspace-meta"
|
|
6
7
|
|
|
7
8
|
export type MaterialIntakeStatus =
|
|
8
9
|
| "scanned"
|
|
@@ -107,24 +108,28 @@ export interface CheckMaterialIntakeResult {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
const DOC_EXTENSIONS = new Set([".pdf", ".docx", ".doc", ".xlsx", ".xls", ".pptx", ".ppt", ".csv", ".md", ".txt"])
|
|
110
|
-
const EXCLUDE_DIRS = new Set(["node_modules", ".git", "dist", ".opencode", "researches", "revela-narrative", "designs", "domains"])
|
|
111
|
+
const EXCLUDE_DIRS = new Set(["node_modules", ".git", "dist", ".opencode", ".revela", "researches", "revela-narrative", "designs", "domains"])
|
|
111
112
|
const EXCLUDE_FILENAMES = new Set(["AGENTS.md", "DECKS.md", "README.md", "README.zh-CN.md"])
|
|
112
113
|
const EXTRACTION_EXTENSIONS = new Set(["pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx"])
|
|
113
114
|
const SUPPORTED_EXTRACTION_EXTENSIONS = new Set(["pdf", "pptx", "docx", "xlsx"])
|
|
114
115
|
|
|
115
116
|
export function materialRegistryPath(workspaceRoot: string): string {
|
|
116
|
-
return
|
|
117
|
+
return workspaceMetaPath(workspaceRoot, "material-intake", "registry.json")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function existingMaterialRegistryPath(workspaceRoot: string): string {
|
|
121
|
+
return existingWorkspaceMetaPath(workspaceRoot, "material-intake", "registry.json")
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
export function readMaterialRegistry(workspaceRoot: string): MaterialRegistry {
|
|
120
|
-
const path =
|
|
125
|
+
const path = existingMaterialRegistryPath(workspaceRoot)
|
|
121
126
|
if (!existsSync(path)) return { version: 1, updatedAt: new Date(0).toISOString(), sources: [] }
|
|
122
127
|
return JSON.parse(readFileSync(path, "utf-8")) as MaterialRegistry
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
export function writeMaterialRegistry(workspaceRoot: string, registry: MaterialRegistry): string {
|
|
126
131
|
const path = materialRegistryPath(workspaceRoot)
|
|
127
|
-
mkdirSync(join(workspaceRoot, ".
|
|
132
|
+
mkdirSync(join(workspaceRoot, ".revela", "material-intake"), { recursive: true })
|
|
128
133
|
writeFileSync(path, JSON.stringify({ ...registry, updatedAt: new Date().toISOString() }, null, 2), "utf-8")
|
|
129
134
|
return workspaceRelative(path, workspaceRoot)
|
|
130
135
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const NARRATIVE_VAULT_DIR = "revela-narrative"
|
|
2
|
-
export const NARRATIVE_VAULT_CACHE_DIR = ".
|
|
2
|
+
export const NARRATIVE_VAULT_CACHE_DIR = ".revela/narrative-cache"
|
|
3
3
|
|
|
4
4
|
export const NARRATIVE_VAULT_NODE_DIRS = ["claims", "evidence", "objections", "risks", "research-gaps"] as const
|
|
5
5
|
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { existsSync } from "fs"
|
|
2
2
|
import { join } from "path"
|
|
3
|
-
import {
|
|
3
|
+
import { existingWorkspaceMetaPath, workspaceMetaPath } from "../workspace-meta"
|
|
4
|
+
import { NARRATIVE_VAULT_DIR } from "./constants"
|
|
4
5
|
|
|
5
6
|
export function narrativeVaultPath(workspaceRoot: string): string {
|
|
6
7
|
return join(workspaceRoot, NARRATIVE_VAULT_DIR)
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function narrativeVaultCachePath(workspaceRoot: string): string {
|
|
10
|
-
return
|
|
11
|
+
return workspaceMetaPath(workspaceRoot, "narrative-cache")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function existingNarrativeVaultCachePath(workspaceRoot: string): string {
|
|
15
|
+
return existingWorkspaceMetaPath(workspaceRoot, "narrative-cache")
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export function hasNarrativeVault(workspaceRoot: string): boolean {
|
package/lib/runtime/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { formatArtifactQaUserNotice, formatMarkdownQaUserNotice } from "../hook-
|
|
|
27
27
|
import { deckPlanDesignDiagnostics, readDeckPlanArtifact, upsertDeckPlanSlideArtifact, type DeckPlanSlideUpsertInput } from "../narrative-state/deck-plan-artifact"
|
|
28
28
|
import { extractDesignClasses } from "../design/designs"
|
|
29
29
|
import { recordRenderedArtifact, workspaceRelative } from "../workspace-state/rendered-artifacts"
|
|
30
|
+
import { existingWorkspaceMetaPath, workspaceMetaPath } from "../workspace-meta"
|
|
30
31
|
import { checkMaterialIntake, extractMaterial, materialIntakeNoticeForCommand, prepareLocalMaterials, recordMaterialReview } from "../material-intake"
|
|
31
32
|
import type { ReviewDeckOpenInput, ReviewDeckReadInput } from "./review"
|
|
32
33
|
import pkg from "../../package.json"
|
|
@@ -414,7 +415,7 @@ export function checkDesignRulesReadiness(input: RuntimeWorkspaceInput = {}): De
|
|
|
414
415
|
}
|
|
415
416
|
|
|
416
417
|
function recordDesignRulesRead(workspaceRoot: string, designName: string, rules: string): void {
|
|
417
|
-
const markerPath =
|
|
418
|
+
const markerPath = workspaceMetaPath(workspaceRoot, "codex-hooks", "design-rules-read.json")
|
|
418
419
|
mkdirSync(dirname(markerPath), { recursive: true })
|
|
419
420
|
writeFileSync(markerPath, JSON.stringify({
|
|
420
421
|
designName,
|
|
@@ -424,7 +425,7 @@ function recordDesignRulesRead(workspaceRoot: string, designName: string, rules:
|
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
function designRulesMarkerPath(workspaceRoot: string): string {
|
|
427
|
-
return
|
|
428
|
+
return existingWorkspaceMetaPath(workspaceRoot, "codex-hooks", "design-rules-read.json")
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
function hashDesignRules(rules: string): string {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from "fs"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
|
|
4
|
+
export const WORKSPACE_META_DIR = ".revela"
|
|
5
|
+
export const LEGACY_WORKSPACE_META_DIR = ".opencode/revela"
|
|
6
|
+
|
|
7
|
+
export function workspaceMetaPath(workspaceRoot: string, ...segments: string[]): string {
|
|
8
|
+
return join(workspaceRoot, WORKSPACE_META_DIR, ...segments)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function legacyWorkspaceMetaPath(workspaceRoot: string, ...segments: string[]): string {
|
|
12
|
+
return join(workspaceRoot, LEGACY_WORKSPACE_META_DIR, ...segments)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function existingWorkspaceMetaPath(workspaceRoot: string, ...segments: string[]): string {
|
|
16
|
+
const current = workspaceMetaPath(workspaceRoot, ...segments)
|
|
17
|
+
if (existsSync(current)) return current
|
|
18
|
+
const legacy = legacyWorkspaceMetaPath(workspaceRoot, ...segments)
|
|
19
|
+
return existsSync(legacy) ? legacy : current
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function workspaceMetaRelativePath(...segments: string[]): string {
|
|
23
|
+
return join(WORKSPACE_META_DIR, ...segments).replace(/\\/g, "/")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isWorkspaceMetaRelativePath(path: string): boolean {
|
|
27
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "")
|
|
28
|
+
return normalized === WORKSPACE_META_DIR ||
|
|
29
|
+
normalized.startsWith(`${WORKSPACE_META_DIR}/`) ||
|
|
30
|
+
normalized === ".opencode" ||
|
|
31
|
+
normalized.startsWith(".opencode/")
|
|
32
|
+
}
|
package/package.json
CHANGED
package/plugin.ts
CHANGED
|
@@ -98,6 +98,7 @@ import { extractDesignClasses } from "./lib/design/designs"
|
|
|
98
98
|
import { log, childLog } from "./lib/log"
|
|
99
99
|
import { appendToolResult } from "./lib/tool-result"
|
|
100
100
|
import { formatArtifactQaUserNotice, formatMarkdownQaUserNotice, formatStateGateUserNotice } from "./lib/hook-notifications"
|
|
101
|
+
import { workspaceMetaPath, workspaceMetaRelativePath } from "./lib/workspace-meta"
|
|
101
102
|
|
|
102
103
|
// OpenCode internal agent signatures — used to skip system prompt injection
|
|
103
104
|
// for built-in system agents (title, summary, compaction).
|
|
@@ -712,7 +713,7 @@ const server: Plugin = (async (pluginCtx) => {
|
|
|
712
713
|
if (input.tool === "write") {
|
|
713
714
|
const filePath: string = (output.args as any)?.filePath ?? ""
|
|
714
715
|
if (isDecksStatePath(filePath)) {
|
|
715
|
-
const blockedDir =
|
|
716
|
+
const blockedDir = workspaceMetaPath(workspaceRoot, "blocked-writes")
|
|
716
717
|
mkdirSync(blockedDir, { recursive: true })
|
|
717
718
|
const blockedPath = join(blockedDir, "DECKS-json-direct-write.blocked.md")
|
|
718
719
|
const blocker = `${DECKS_STATE_FILE} is a controlled Revela state file. Use the revela-decks tool instead of write/apply_patch.`
|
|
@@ -738,9 +739,9 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
|
|
|
738
739
|
|
|
739
740
|
const stateTargets = extractDecksStateTargetsFromPatch(patchText)
|
|
740
741
|
if (stateTargets.length > 0) {
|
|
741
|
-
const blockedDir =
|
|
742
|
+
const blockedDir = workspaceMetaPath(workspaceRoot, "blocked-writes")
|
|
742
743
|
mkdirSync(blockedDir, { recursive: true })
|
|
743
|
-
const blockedRelativePath =
|
|
744
|
+
const blockedRelativePath = workspaceMetaRelativePath("blocked-writes", `DECKS-json-direct-patch-${Date.now()}.blocked.md`)
|
|
744
745
|
const blocker = `${DECKS_STATE_FILE} is a controlled Revela state file. Use the revela-decks tool instead of write/apply_patch.`
|
|
745
746
|
const blockedPatch = `*** Begin Patch
|
|
746
747
|
*** Add File: ${blockedRelativePath}
|
package/plugins/revela/.mcp.json
CHANGED
|
@@ -22,7 +22,7 @@ export async function runPreWriteChecks(input: string): Promise<HookResult> {
|
|
|
22
22
|
messages.push([
|
|
23
23
|
"Revela narrative cache patches are blocked.",
|
|
24
24
|
`Controlled cache target(s): ${cacheTargets.map((target) => `\`${target}\``).join(", ")}`,
|
|
25
|
-
"Edit `revela-narrative/**/*.md` instead; compile/cache files under `.
|
|
25
|
+
"Edit `revela-narrative/**/*.md` instead; compile/cache files under `.revela/narrative-cache/` are regenerated.",
|
|
26
26
|
].join("\n"))
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -67,7 +67,7 @@ export function extractDeckHtmlPatchTargets(input: string): string[] {
|
|
|
67
67
|
export function extractNarrativeCachePatchTargets(input: string): string[] {
|
|
68
68
|
const targets = new Set<string>()
|
|
69
69
|
for (const patch of patchPayloads(input)) {
|
|
70
|
-
const pattern = /(?:^\*\*\* Update File: |^\*\*\* Add File: |^\*\*\* Delete File: |^\*\*\* Move to: )([^\r\n]
|
|
70
|
+
const pattern = /(?:^\*\*\* Update File: |^\*\*\* Add File: |^\*\*\* Delete File: |^\*\*\* Move to: )([^\r\n]*(?:\.revela|\.opencode\/revela)\/narrative-cache\/[^\r\n]+)\s*$/gm
|
|
71
71
|
let match: RegExpExecArray | null
|
|
72
72
|
while ((match = pattern.exec(patch))) targets.add(match[1].trim())
|
|
73
73
|
}
|
package/tools/workspace-scan.ts
CHANGED
|
@@ -13,7 +13,7 @@ const DOC_EXTENSIONS = new Set([
|
|
|
13
13
|
|
|
14
14
|
// Directories to exclude from scanning
|
|
15
15
|
const EXCLUDE_DIRS = new Set([
|
|
16
|
-
"node_modules", ".git", "dist", ".opencode",
|
|
16
|
+
"node_modules", ".git", "dist", ".opencode", ".revela",
|
|
17
17
|
"researches", // Exclude revela's own research output
|
|
18
18
|
"revela-narrative", // Exclude canonical narrative vault source files
|
|
19
19
|
"designs", "domains", // Exclude revela plugin assets
|