🎉 init: 小龙的工作空间

This commit is contained in:
大海
2026-06-06 10:40:48 +08:00
commit a188ee1426
3201 changed files with 231817 additions and 0 deletions
@@ -0,0 +1,271 @@
/**
* PR-C Deprecation Warning System — Tests
*
* Run: node --test test/architecture/deprecation-warning.test.mjs
*/
import { describe, it, beforeEach } from "node:test";
import assert from "node:assert";
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { warnOnce, resetWarnings, getWarningCount, warnMany, runDeprecationCheck } from "../../scripts/lib/deprecation-warning.mjs";
const CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
const TEST_DIR = join(homedir(), ".openclaw", "workspace", "test", "architecture");
const TMP_CONFIG = join(homedir(), ".openclaw", "openclaw-prc-test.json");
beforeEach(() => {
resetWarnings();
});
describe("PR-C: Deprecation Warning System", () => {
// ─── warnOnce utility tests ──────────────────────────────
describe("warnOnce utility", () => {
it("warn-01: same key only warns once", () => {
const r1 = warnOnce("test.key1", "first message");
const r2 = warnOnce("test.key1", "second message");
const r3 = warnOnce("test.key1", "third message");
assert.equal(r1, true, "first call must emit");
assert.equal(r2, false, "second call must be suppressed");
assert.equal(r3, false, "third call must be suppressed");
assert.equal(getWarningCount(), 1, "only 1 unique key warned");
});
it("warn-02: different keys both warn", () => {
const r1 = warnOnce("test.key1", "msg1");
const r2 = warnOnce("test.key2", "msg2");
assert.equal(r1, true);
assert.equal(r2, true);
assert.equal(getWarningCount(), 2);
});
it("warn-03: force option bypasses deduplication", () => {
warnOnce("test.force", "first");
const r = warnOnce("test.force", "second", { force: true });
assert.equal(r, true, "force must emit even if already warned");
assert.equal(getWarningCount(), 1, "key count unchanged (same key)");
});
it("warn-04: resetWarnings clears all state", () => {
warnOnce("test.a", "a");
warnOnce("test.b", "b");
assert.equal(getWarningCount(), 2);
resetWarnings();
assert.equal(getWarningCount(), 0);
// Can warn again after reset
const r = warnOnce("test.a", "a again");
assert.equal(r, true);
assert.equal(getWarningCount(), 1);
});
it("warn-05: warnMany deduplicates across batch", () => {
const emitted = warnMany([
{ key: "a", message: "msg a" },
{ key: "b", message: "msg b" },
{ key: "a", message: "msg a again" }, // duplicate
]);
assert.equal(emitted.length, 2, "only 2 unique warnings emitted");
assert.equal(getWarningCount(), 2);
});
it("warn-06: runDeprecationCheck catches errors", () => {
const result = runDeprecationCheck("test-check", () => {
return [{ key: "test.ok", message: "all good" }];
});
assert.equal(result.name, "test-check");
assert.equal(result.warnings.length, 1);
const errResult = runDeprecationCheck("bad-check", () => {
throw new Error("simulated failure");
});
assert.equal(errResult.warnings[0].key, "bad-check");
assert.ok(errResult.warnings[0].message.includes("simulated failure"));
});
});
// ─── Config detection tests ─────────────────────────────
describe("config detection", () => {
it("detect-01: real config loads without crashing", () => {
assert.ok(existsSync(CONFIG_PATH), "openclaw.json must exist");
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
assert.ok(typeof config === "object");
assert.ok(config.gateway || config.agents, "config must have expected sections");
});
it("detect-02: active-memory blocking mode detected", () => {
// Read the actual config to check current state
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const activeCfg = config?.plugins?.entries?.["active-memory"];
if (activeCfg) {
const mode = activeCfg.config?.mode;
console.log(` [DETECT] active-memory mode: "${mode || "default"}"`);
if (mode === "blocking") {
const r = warnOnce("active-memory.blocking",
"active-memory blocking mode is deprecated. Set mode to 'precompute'.");
assert.equal(r, true);
}
}
assert.ok(true, "check completed without crash");
});
it("detect-03: dreaming REM phase detected", () => {
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const memoryCoreCfg = config?.plugins?.entries?.["memory-core"];
if (memoryCoreCfg?.config?.dreaming) {
const dreaming = memoryCoreCfg.config.dreaming;
const remEnabled = dreaming?.phases?.rem?.enabled;
console.log(` [DETECT] dreaming REM enabled: ${remEnabled ?? "not configured"}`);
if (remEnabled === true) {
warnOnce("dreaming.rem-phase",
"Dreaming REM phase is deprecated. Will be replaced by Collect/Promote.");
}
}
assert.ok(true, "check completed without crash");
});
it("detect-04: memory-wiki plugin reference detectable", () => {
const wikiPath = join(
"/opt/homebrew/lib/node_modules/openclaw",
"dist/extensions/memory-wiki/openclaw.plugin.json"
);
const exists = existsSync(wikiPath);
console.log(` [DETECT] memory-wiki plugin: ${exists ? "present" : "not found"}`);
if (exists) {
const plugin = JSON.parse(readFileSync(wikiPath, "utf8"));
assert.equal(plugin.id, "memory-wiki");
warnOnce("memory-wiki.plugin",
"memory-wiki plugin is legacy. Tools remain available during migration.");
}
assert.ok(true, "check completed without crash");
});
it("detect-05: QMD reference in config detectable", () => {
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const qmdRefs = [];
// Check memory search
if (config?.agents?.defaults?.memorySearch?.provider === "qmd") {
qmdRefs.push("memorySearch.provider=qmd");
}
if (config?.agents?.defaults?.memorySearch?.backend === "qmd") {
qmdRefs.push("memorySearch.backend=qmd");
}
// Check active-memory
const activeQmd = config?.plugins?.entries?.["active-memory"]?.config?.qmd;
if (activeQmd?.searchMode) {
qmdRefs.push("active-memory.qmd.searchMode");
}
console.log(` [DETECT] QMD references found: ${qmdRefs.length}`);
for (const ref of qmdRefs) {
console.log(`${ref}`);
warnOnce(`qmd.${ref}`, "QMD memory engine is legacy. Migrate to Builtin MemoryCore.");
}
assert.ok(true, "check completed without crash");
});
it("detect-06: Honcho/LanceDB plugins detectable", () => {
const extDir = "/opt/homebrew/lib/node_modules/openclaw/dist/extensions";
const legacyPlugins = ["memory-honcho", "memory-lancedb"];
let found = 0;
for (const name of legacyPlugins) {
// Check if directory or plugin reference exists
// These are NOT in the current install, which is expected (they're optional)
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const enabled = config?.plugins?.entries?.[name]?.enabled;
console.log(` [DETECT] ${name}: ${enabled === true ? "enabled" : "not enabled"}`);
if (enabled === true) {
warnOnce(`${name}.plugin`, `${name} memory plugin is legacy. Data readable during migration.`);
found++;
}
}
console.log(` [DETECT] legacy memory plugins enabled: ${found}`);
assert.ok(true, "check completed without crash");
});
it("detect-07: commitments auto-infer detectable", () => {
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const autoInfer = config?.commitments?.autoInfer;
console.log(` [DETECT] commitments auto-infer: ${autoInfer ?? "not configured"}`);
if (autoInfer === true) {
warnOnce("commitments.auto-infer",
"Commitments auto-infer is deprecated. Use explicit cron tasks.");
}
assert.ok(true, "check completed without crash");
});
});
// ─── Integration tests ──────────────────────────────────
describe("integration", () => {
it("integ-01: check-deprecations script runs without error", () => {
const scriptPath = join(import.meta.dirname, "../../scripts/check-deprecations.mjs");
const result = spawnSync("node", [scriptPath], {
encoding: "utf8",
timeout: 15000,
maxBuffer: 1024 * 1024,
});
console.log(" [INTEG] check-deprecations exit code:", result.status);
console.log(" [INTEG] check-deprecations stdout:",
result.stdout?.slice(0, 400) || "(empty)");
if (result.stderr) {
console.log(" [INTEG] stderr:", result.stderr.slice(0, 200));
}
// Script should never crash — always exit 0
assert.equal(result.status, 0,
"check-deprecations must exit 0 even with warnings");
});
it("integ-02: deprecation warnings do NOT cause process exit", () => {
// warnOnce writes to console.warn — it should never throw
assert.doesNotThrow(() => {
warnOnce("test.integration", "This is a test deprecation warning");
}, "warnOnce must never throw");
assert.equal(getWarningCount(), 1);
});
it("integ-03: all 8 deprecation categories checkable", () => {
const categories = [
"active-memory.blocking",
"dreaming.rem-phase",
"dreaming.dream-diary",
"qmd.reference",
"honcho.plugin",
"lancedb.plugin",
"memory-wiki.plugin",
"commitments.auto-infer",
];
// Each category should have a defined key pattern
for (const cat of categories) {
assert.ok(typeof cat === "string" && cat.length > 0,
`category ${cat} must be a non-empty string`);
}
console.log(` [INTEG] ${categories.length} deprecation categories defined`);
assert.equal(categories.length, 8, "must cover all 8 deprecation categories");
});
});
});
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env node
/**
* PR-1 Freeze Architecture Test
*
* Run: node --test test/architecture/freeze.test.mjs
*
* Tests:
* 1. architecture-freeze-v2.md exists
* 2. Document contains core/adapter/legacy/experimental sections
* 3. Document contains 90-day legacy policy
* 4. check-deprecations output includes replacement info
* 5. check-deprecations output includes migration window
* 6. production-check still passes
* 7. guard still passes
* 8. baseline still passes
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { execSync, spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
const ROOT = __dirname;
const SCRIPTS = join(ROOT, "scripts");
const DOCS = join(ROOT, "docs");
// ─── Helpers ──────────────────────────────────────────
function getFileSize(path) {
try { return statSync(path).size; } catch { return 0; }
}
describe("PR-1: Freeze Architecture", () => {
// ─── Test 1 ──────────────────────────────────────────
it("architecture-freeze-v2.md exists and is non-empty", () => {
const path = join(DOCS, "architecture-freeze-v2.md");
assert.ok(existsSync(path), `Missing: ${path}`);
const size = getFileSize(path);
assert.ok(size > 1000, `File too small: ${size} bytes (expected >1000)`);
});
// ─── Test 2 ──────────────────────────────────────────
it("document contains core / adapter / legacy / experimental sections", () => {
const content = readFileSync(join(DOCS, "architecture-freeze-v2.md"), "utf8");
const required = [
"Core Module",
"Adapter Module",
"Legacy Module",
"Experimental Module",
];
for (const section of required) {
assert.ok(content.includes(section),
`Missing section: "${section}"`);
}
});
// ─── Test 3 ──────────────────────────────────────────
it("document contains 90-day legacy migration policy", () => {
const content = readFileSync(join(DOCS, "architecture-freeze-v2.md"), "utf8");
assert.ok(content.includes("90-day") || content.includes("90-Day") ||
content.includes("90 day") || content.includes("Migration Window"),
"Missing 90-day migration policy reference");
assert.ok(content.includes("2026-09-04"),
"Missing removal target date 2026-09-04");
});
// ─── Test 4 ──────────────────────────────────────────
it("check-deprecations output includes replacement info", { timeout: 30000 }, () => {
const result = spawnSync("node", [join(SCRIPTS, "check-deprecations.mjs")], {
cwd: ROOT,
encoding: "utf8",
timeout: 25000,
maxBuffer: 1024 * 1024,
});
const output = result.stdout + result.stderr;
console.log(output.slice(-1000));
// Deprecation check always exits 0 (never blocks on deprecation count)
assert.equal(result.status, 0, `Script crashed (exit ${result.status})`);
// Must contain replacement info in enhanced output
assert.ok(
output.includes("Replacement:") || output.includes("replacement"),
"Missing replacement info in deprecation output"
);
});
// ─── Test 5 ──────────────────────────────────────────
it("check-deprecations output includes migration window", { timeout: 30000 }, () => {
const result = spawnSync("node", [join(SCRIPTS, "check-deprecations.mjs")], {
cwd: ROOT,
encoding: "utf8",
timeout: 25000,
maxBuffer: 1024 * 1024,
});
const output = result.stdout + result.stderr;
// Must contain migration window
assert.ok(
output.includes("Migration Window") || output.includes("migrationWindow") ||
output.includes("Migration Timeline") || output.includes("Removal Target"),
"Missing migration window info in deprecation output"
);
});
// ─── Test 6 ──────────────────────────────────────────
it("production-check still passes after freeze", { timeout: 120000 }, () => {
const result = spawnSync("bash", [join(SCRIPTS, "production-check.sh")], {
cwd: ROOT,
encoding: "utf8",
timeout: 110000,
maxBuffer: 2 * 1024 * 1024,
});
const output = result.stdout + result.stderr;
console.log(output.slice(-800));
assert.equal(result.status, 0,
`Production check failed after freeze (exit ${result.status}):\n${output.slice(-500)}`);
assert.ok(output.includes("PRODUCTION CHECK PASSED") ||
output.includes("Overall:"),
"Production check summary not found");
});
// ─── Test 7 ──────────────────────────────────────────
it("architecture guard still passes after freeze", { timeout: 60000 }, () => {
const result = spawnSync("bash", [join(SCRIPTS, "guard-all.sh")], {
cwd: ROOT,
encoding: "utf8",
timeout: 55000,
maxBuffer: 1024 * 1024,
});
const output = result.stdout + result.stderr;
console.log(output.slice(-500));
assert.equal(result.status, 0,
`Guard failed after freeze (exit ${result.status}):\n${output.slice(-300)}`);
});
// ─── Test 8 ──────────────────────────────────────────
it("baseline tests still pass after freeze", { timeout: 60000 }, () => {
const result = spawnSync("node", ["--test", "test/baseline/test-*.test.mjs"], {
cwd: ROOT,
encoding: "utf8",
timeout: 55000,
maxBuffer: 2 * 1024 * 1024,
shell: true,
});
const output = result.stdout + result.stderr;
assert.equal(result.status, 0,
`Baseline tests failed after freeze (exit ${result.status})`);
assert.ok(output.includes("fail 0") || !output.includes("fail"),
"Baseline tests have failures");
});
});
+76
View File
@@ -0,0 +1,76 @@
/**
* PR-B Architecture Guard — Unified Test
* Runs all 6 guards as node:test sub-tests.
*
* Run: node --test test/architecture/guard.test.mjs
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { spawnSync } from "node:child_process";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
const SCRIPTS_DIR = join(__dirname, "scripts");
function runGuard(name, script, expectExit0 = true) {
const result = spawnSync("node", [script], {
cwd: __dirname,
encoding: "utf8",
timeout: 30000,
maxBuffer: 1024 * 1024,
});
const output = result.stdout + result.stderr;
return {
name,
exitCode: result.status,
signal: result.signal,
output: output.slice(-500), // Keep last 500 chars for report
passed: result.status === 0,
};
}
describe("PR-B: Architecture Guard", () => {
it("Guard-01: Memory backend count ≤ allowed", () => {
const r = runGuard("Memory Backend", join(SCRIPTS_DIR, "guard-memory-backend.mjs"));
console.log(r.output);
assert.equal(r.exitCode, 0, `Guard 1 failed (exit ${r.exitCode})`);
});
it("Guard-02: Runtime core count ≤ 3", () => {
const r = runGuard("Runtime Core", join(SCRIPTS_DIR, "guard-runtime-core.mjs"));
console.log(r.output);
assert.equal(r.exitCode, 0, `Guard 2 failed (exit ${r.exitCode})`);
});
it("Guard-03: Tool path inventory (soft)", () => {
const r = runGuard("Tool Path", join(SCRIPTS_DIR, "guard-tool-path.mjs"));
console.log(r.output);
// Soft guard — informational only
assert.ok(r.exitCode === 0 || r.exitCode === null,
`Guard 3 unexpected error (exit ${r.exitCode})`);
});
it("Guard-04: MEMORY.md direct write paths unchanged", () => {
const r = runGuard("MEMORY.md Write", join(SCRIPTS_DIR, "guard-memory-write.mjs"));
console.log(r.output);
assert.equal(r.exitCode, 0, `Guard 4 failed (exit ${r.exitCode})`);
});
it("Guard-05: Tool trace coverage (soft)", () => {
const r = runGuard("Tool Trace", join(SCRIPTS_DIR, "guard-tool-trace.mjs"));
console.log(r.output);
// Soft guard — informational only
assert.ok(r.exitCode === 0 || r.exitCode === null,
`Guard 5 unexpected error (exit ${r.exitCode})`);
});
it("Guard-06: Dreaming phase count ≤ 3", () => {
const r = runGuard("Dreaming Phase", join(SCRIPTS_DIR, "guard-dreaming-phase.mjs"));
console.log(r.output);
assert.equal(r.exitCode, 0, `Guard 6 failed (exit ${r.exitCode})`);
});
});
+132
View File
@@ -0,0 +1,132 @@
/**
* PR-D Production Check — Architecture Test
*
* Run: node --test test/architecture/production-check.test.mjs
*
* Tests:
* 1. production-check.sh file exists
* 2. production-check.sh is executable
* 3. smoke-test.mjs file exists
* 4. package.json contains production-check script
* 5. Smoke test runs independently
* 6. production-check runs end-to-end and exits 0
*/
import { describe, it, before } from "node:test";
import assert from "node:assert";
import { existsSync, statSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { execSync, spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
const ROOT = __dirname;
const SCRIPTS = join(ROOT, "scripts");
// ─── Helpers ──────────────────────────────────────────
function isExecutable(path) {
try {
const mode = statSync(path).mode;
// Check owner execute bit
return (mode & 0o100) !== 0;
} catch {
return false;
}
}
// ─── Tests ────────────────────────────────────────────
describe("PR-D: Production Check Architecture", () => {
// Test 1
it("production-check.sh file exists", () => {
const path = join(SCRIPTS, "production-check.sh");
assert.ok(existsSync(path), `Missing: ${path}`);
});
// Test 2
it("production-check.sh is executable", () => {
const path = join(SCRIPTS, "production-check.sh");
assert.ok(existsSync(path), `Missing: ${path}`);
assert.ok(isExecutable(path), `Not executable: ${path}. Run: chmod +x ${path}`);
});
// Test 3
it("smoke-test.mjs file exists", () => {
const path = join(SCRIPTS, "smoke-test.mjs");
assert.ok(existsSync(path), `Missing: ${path}`);
});
// Test 4
it("package.json contains production-check, smoke, check:deprecations, test:baseline, guard scripts", () => {
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
const scripts = pkg.scripts || {};
assert.ok(typeof scripts["production-check"] === "string",
"Missing scripts.production-check in package.json");
assert.ok(typeof scripts["smoke"] === "string",
"Missing scripts.smoke in package.json");
assert.ok(typeof scripts["check:deprecations"] === "string",
"Missing scripts.check:deprecations in package.json");
assert.ok(typeof scripts["test:baseline"] === "string",
"Missing scripts.test:baseline in package.json");
assert.ok(typeof scripts["guard"] === "string",
"Missing scripts.guard in package.json");
});
// Test 5
it("smoke test runs independently and exits 0", { timeout: 30000 }, () => {
const script = join(SCRIPTS, "smoke-test.mjs");
const result = spawnSync("node", [script], {
cwd: ROOT,
encoding: "utf8",
timeout: 25000,
maxBuffer: 1024 * 1024,
});
const output = result.stdout + result.stderr;
console.log(output.slice(-800));
assert.equal(result.status, 0,
`Smoke test failed (exit ${result.status}):\n${output.slice(-500)}`);
assert.ok(output.includes("Smoke test PASSED") || output.includes("PASS"),
`Expected "Smoke test PASSED" in output:\n${output.slice(-300)}`);
});
// Test 6
it("production-check runs end-to-end and exits 0", { timeout: 120000 }, () => {
const script = join(SCRIPTS, "production-check.sh");
const result = spawnSync("bash", [script], {
cwd: ROOT,
encoding: "utf8",
timeout: 110000,
maxBuffer: 2 * 1024 * 1024,
});
const output = result.stdout + result.stderr;
console.log(output.slice(-1200));
assert.equal(result.status, 0,
`Production check failed (exit ${result.status}):\n${output.slice(-800)}`);
// Verify all 6 steps ran
const steps = [
"Step 1: Environment Check",
"Step 2: Baseline Tests",
"Step 3: Architecture Guards",
"Step 4: Deprecation Check",
"Step 5: Smoke Test",
"Step 6: Summary Report",
];
for (const step of steps) {
assert.ok(output.includes(step),
`Missing step in output: "${step}"`);
}
assert.ok(output.includes("Overall:"),
"Missing Overall summary");
assert.ok(output.includes("Duration:"),
"Missing Duration in summary");
});
});