142 lines
4.9 KiB
JavaScript
142 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Guard 4: MEMORY.md Direct Write Guard
|
|
* Prevents new code from writing directly to MEMORY.md.
|
|
*
|
|
* → Architecture Freeze v2 (PR-1): docs/architecture-freeze-v2.md
|
|
* → Forbidden Addition §6.6: 禁止直接写 MEMORY.md
|
|
*
|
|
* Only allowed writers (whitelisted):
|
|
* - session-memory hook (bundled/session-memory)
|
|
* - memory-core dreaming (extensions/memory-core)
|
|
* - memory-core manager (extensions/memory-core)
|
|
* - openclaw CLI (memory promote/write)
|
|
*
|
|
* FAIL: new code path writes to MEMORY.md
|
|
*/
|
|
|
|
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { homedir } from "node:os";
|
|
import { execSync } from "node:child_process";
|
|
|
|
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || "/opt/homebrew/lib/node_modules/openclaw";
|
|
const DIST_DIR = join(OPENCLAW_HOME, "dist");
|
|
const WORKSPACE = join(homedir(), ".openclaw", "workspace");
|
|
|
|
// Known/allowed MEMORY.md writers
|
|
const ALLOWED_MEMORY_MD_WRITERS = new Set([
|
|
"memory-core",
|
|
"memory-wiki",
|
|
"active-memory", // writes to prompt, not MEMORY.md directly
|
|
]);
|
|
|
|
function guardMemoryDirectWrite() {
|
|
const issues = [];
|
|
const foundWriters = [];
|
|
|
|
// Search JS files in dist for MEMORY.md write references
|
|
// We grep for patterns that write to MEMORY.md
|
|
const searchPatterns = [
|
|
"MEMORY.md",
|
|
"'MEMORY.md'",
|
|
'"MEMORY.md"',
|
|
];
|
|
|
|
try {
|
|
// Use grep to find references to MEMORY.md in dist JS files
|
|
const grepResult = execSync(
|
|
`grep -rl "MEMORY.md" "${DIST_DIR}" 2>/dev/null | grep -v ".d.ts$" | grep -v "node_modules" | grep -v ".json$" | head -50`,
|
|
{ timeout: 30000, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 }
|
|
).trim();
|
|
|
|
if (grepResult) {
|
|
const files = grepResult.split("\n").filter(Boolean);
|
|
for (const file of files) {
|
|
const relativePath = file.replace(DIST_DIR + "/", "");
|
|
|
|
// Determine which module this belongs to
|
|
let module = "unknown";
|
|
if (relativePath.includes("memory-core")) module = "memory-core";
|
|
else if (relativePath.includes("memory-wiki")) module = "memory-wiki";
|
|
else if (relativePath.includes("memory-lancedb")) module = "memory-lancedb";
|
|
else if (relativePath.includes("active-memory")) module = "active-memory";
|
|
else if (relativePath.includes("bundled/session-memory")) module = "bundled/session-memory";
|
|
else if (relativePath.includes("bundle")) module = "bundled";
|
|
else if (relativePath.includes("cli")) module = "cli";
|
|
else module = relativePath.split("/")[0] || "unknown";
|
|
|
|
foundWriters.push({ file: relativePath, module });
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// grep returns exit 1 if no matches found — that's OK
|
|
if (e.status !== 1) {
|
|
issues.push({ level: "WARN", msg: `grep error: ${e.message}` });
|
|
}
|
|
}
|
|
|
|
// Group by module
|
|
const moduleGroups = {};
|
|
for (const w of foundWriters) {
|
|
if (!moduleGroups[w.module]) moduleGroups[w.module] = [];
|
|
moduleGroups[w.module].push(w.file);
|
|
}
|
|
|
|
const knownModules = new Set(Object.keys(moduleGroups));
|
|
|
|
console.log("");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log(" Guard 4: MEMORY.md Direct Write Guard");
|
|
console.log("═══════════════════════════════════════");
|
|
console.log("");
|
|
|
|
console.log(` Modules referencing MEMORY.md: ${knownModules.size}`);
|
|
for (const [mod, files] of Object.entries(moduleGroups)) {
|
|
const status = ALLOWED_MEMORY_MD_WRITERS.has(mod) ? "✓ allowed" : "⚠ unknown";
|
|
console.log(` ${status} ${mod} (${files.length} files)`);
|
|
if (files.length <= 3) {
|
|
for (const f of files) {
|
|
console.log(` - ${f}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for unexpected writers
|
|
const unexpectedModules = [...knownModules].filter(m => !ALLOWED_MEMORY_MD_WRITERS.has(m));
|
|
|
|
if (unexpectedModules.length > 0) {
|
|
console.log("");
|
|
for (const mod of unexpectedModules) {
|
|
// some modules like "bundled" are expected to reference MEMORY.md for reading
|
|
// Only flag as WARN if they might be writing
|
|
issues.push({
|
|
level: "WARN",
|
|
msg: `Module "${mod}" references MEMORY.md — verify it's read-only`
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log("");
|
|
const failIssues = issues.filter(i => i.level === "FAIL");
|
|
const warnIssues = issues.filter(i => i.level === "WARN");
|
|
|
|
if (failIssues.length > 0) {
|
|
for (const issue of failIssues) {
|
|
console.log(` [FAIL] ${issue.msg}`);
|
|
}
|
|
console.log(" ✗ FAIL: new direct MEMORY.md writer(s) detected");
|
|
process.exit(1);
|
|
} else {
|
|
if (warnIssues.length > 0) {
|
|
for (const issue of warnIssues) {
|
|
console.log(` [WARN] ${issue.msg}`);
|
|
}
|
|
}
|
|
console.log(" ✓ PASS: MEMORY.md direct write paths unchanged");
|
|
process.exit(0);
|
|
}
|
|
}
|
|
|
|
guardMemoryDirectWrite();
|