🎉 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
+703
View File
@@ -0,0 +1,703 @@
/**
* Architecture Agent Test — SF-02
*
* 验证场景:
* 1. 电商小程序 → 技术选型(小程序 + 支付 + 物流)
* 2. 企业 OA → 审批/考勤架构
* 3. 教育平台 → 课程系统架构
* 4. 笔记应用 → 轻量架构
* 5. 宠物管理 → 完整架构生成
* 6. 空/invalid PRD → 错误处理
* 7. 通用 PRD → 降级处理
* 8. 架构图生成
* 9. 数据库设计
* 10. API 设计
* 11. 目录结构
* 12. 模块分解
* 13. 数据流
* 14. CLI 端到端
* 15. 集成 SF-01
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
const FIXTURES_DIR = resolve(WORKSPACE, "test/fixtures/architecture-agent");
const ECO_PRJ = resolve(FIXTURES_DIR, "ecommerce-prd.json");
const ENT_PRJ = resolve(FIXTURES_DIR, "enterprise-prd.json");
const EDU_PRJ = resolve(FIXTURES_DIR, "education-prd.json");
const NOTE_PRJ = resolve(FIXTURES_DIR, "note-prd.json");
const EMPTY_PRJ = resolve(FIXTURES_DIR, "empty-prd.json");
const GENERIC_PRJ = resolve(FIXTURES_DIR, "generic-prd.json");
let generateArchitecture, resolveTechStack, generateArchDiagram;
let generateDataFlows, decomposeModules, generateDatabaseSchema;
let designAPI, generateDirectoryStructure;
let loadPRD, writeArchitecture;
before(async () => {
const mod = await import("../scripts/architecture-agent.mjs");
generateArchitecture = mod.generateArchitecture;
resolveTechStack = mod.resolveTechStack;
generateArchDiagram = mod.generateArchDiagram;
generateDataFlows = mod.generateDataFlows;
decomposeModules = mod.decomposeModules;
generateDatabaseSchema = mod.generateDatabaseSchema;
designAPI = mod.designAPI;
generateDirectoryStructure = mod.generateDirectoryStructure;
loadPRD = mod.loadPRD;
writeArchitecture = mod.writeArchitecture;
});
// ═══════════════════════════════════════════════════════
// 1 — 电商小程序架构
// ═══════════════════════════════════════════════════════
describe("1 — 电商小程序架构", () => {
it("技术选型为小程序栈", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.techStack.frontend.includes("uni-app") || arch.techStack.frontend.includes("小程序"));
assert.ok(arch.techStack.considerations.some(c => c.includes("支付")));
assert.ok(arch.techStack.considerations.some(c => c.includes("物流")));
});
it("包含订单和物流表", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("orders"));
assert.ok(tableNames.includes("logistics"));
assert.ok(tableNames.includes("products"));
assert.ok(tableNames.includes("users"));
});
it("订单表有明细关联", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const orderItems = arch.databaseSchema.find(t => t.table === "order_items");
assert.ok(orderItems);
assert.ok(orderItems.fields.some(f => f.name === "order_id"));
assert.ok(orderItems.fields.some(f => f.name === "product_id"));
});
it("API 按资源分组", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const resources = arch.apiDesign.map(g => g.resource);
assert.ok(resources.includes("products"));
assert.ok(resources.includes("orders"));
});
});
// ═══════════════════════════════════════════════════════
// 2 — 企业 OA 架构
// ═══════════════════════════════════════════════════════
describe("2 — 企业 OA 架构", () => {
it("包含审批流核心表", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("approvals"));
assert.ok(tableNames.includes("approval_steps"));
assert.ok(tableNames.includes("attendance"));
assert.ok(tableNames.includes("departments"));
});
it("审批步骤有步骤序号", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const steps = arch.databaseSchema.find(t => t.table === "approval_steps");
assert.ok(steps.fields.some(f => f.name === "step_order"));
assert.ok(steps.fields.some(f => f.name === "status"));
assert.ok(steps.fields.some(f => f.name === "approver_id"));
});
it("模块包含审批和考勤", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const modNames = arch.modules.map(m => m.name);
assert.ok(modNames.some(n => n.includes("approval")));
assert.ok(modNames.some(n => n.includes("attendance")));
});
it("部门表支持自引用", () => {
const { data } = loadPRD(ENT_PRJ);
const arch = generateArchitecture(data);
const dept = arch.databaseSchema.find(t => t.table === "departments");
const parentField = dept.fields.find(f => f.name === "parent_id");
assert.ok(parentField.constraints.includes("自引用") || parentField.constraints.includes("departments.id"));
});
});
// ═══════════════════════════════════════════════════════
// 3 — 教育平台架构
// ═══════════════════════════════════════════════════════
describe("3 — 教育平台架构", () => {
it("包含课程和学习进度表", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const tableNames = arch.databaseSchema.map(t => t.table);
assert.ok(tableNames.includes("courses"));
assert.ok(tableNames.includes("lessons"));
assert.ok(tableNames.includes("exercises"));
assert.ok(tableNames.includes("user_progress"));
});
it("课时表有排序字段", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const lessons = arch.databaseSchema.find(t => t.table === "lessons");
assert.ok(lessons.fields.some(f => f.name === "sort_order"));
assert.ok(lessons.fields.some(f => f.name === "video_url"));
});
it("API 包含 courses 和 progress", () => {
const { data } = loadPRD(EDU_PRJ);
const arch = generateArchitecture(data);
const resources = arch.apiDesign.map(g => g.resource);
assert.ok(resources.includes("courses"));
assert.ok(resources.includes("progress"));
});
});
// ═══════════════════════════════════════════════════════
// 4 — 笔记应用架构
// ═══════════════════════════════════════════════════════
describe("4 — 笔记应用架构", () => {
it("包含全文搜索索引", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
const notes = arch.databaseSchema.find(t => t.table === "notes");
const ftsIdx = notes.indexes?.find(i => i.includes("fts") || i.includes("GIN"));
assert.ok(ftsIdx, `Expected FTS index, got: ${JSON.stringify(notes.indexes)}`);
});
it("标签多对多关联", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.databaseSchema.some(t => t.table === "tags"));
assert.ok(arch.databaseSchema.some(t => t.table === "note_tags"));
});
it("笔记表支持 Markdown", () => {
const { data } = loadPRD(NOTE_PRJ);
const arch = generateArchitecture(data);
const notes = arch.databaseSchema.find(t => t.table === "notes");
assert.ok(notes.fields.some(f => f.name === "is_markdown"));
});
});
// ═══════════════════════════════════════════════════════
// 5 — 完整架构输出
// ═══════════════════════════════════════════════════════
describe("5 — 完整架构输出结构", () => {
it("架构包含所有必需字段", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
const required = [
"projectName", "domain", "techStack", "architectureDiagram",
"dataFlows", "modules", "databaseSchema", "apiDesign",
"directoryStructure", "deployment", "meta",
];
for (const field of required) {
assert.ok(arch[field] !== undefined, `Missing: ${field}`);
}
});
it("techStack 包含四层", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.techStack.frontend);
assert.ok(arch.techStack.backend);
assert.ok(arch.techStack.database);
assert.ok(arch.techStack.deployment);
});
it("deployment 包含环境和 CI", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.deployment.environments.includes("production"));
assert.ok(arch.deployment.strategy);
assert.ok(arch.deployment.ci.includes("GitHub Actions") || arch.deployment.ci.includes("CI"));
});
it("meta 包含统计信息", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.meta.generatedAt);
assert.ok(typeof arch.meta.moduleCount === "number");
assert.ok(typeof arch.meta.tableCount === "number");
assert.ok(typeof arch.meta.apiEndpointCount === "number");
});
});
// ═══════════════════════════════════════════════════════
// 6 — 错误处理
// ═══════════════════════════════════════════════════════
describe("6 — 错误处理", () => {
it("空的 error PRD 返回错误", () => {
const { data } = loadPRD(EMPTY_PRJ);
const arch = generateArchitecture(data);
assert.equal(arch.error, "INVALID_PRD");
assert.ok(arch.message);
});
it("null 输入返回错误", () => {
const arch = generateArchitecture(null);
assert.equal(arch.error, "INVALID_PRD");
});
it("缺少 projectName 返回错误", () => {
const arch = generateArchitecture({ features: [] });
assert.equal(arch.error, "INCOMPLETE_PRD");
});
it("缺少 features 返回错误", () => {
const arch = generateArchitecture({ projectName: "test" });
assert.equal(arch.error, "INCOMPLETE_PRD");
});
});
// ═══════════════════════════════════════════════════════
// 7 — 通用 PRD 降级
// ═══════════════════════════════════════════════════════
describe("7 — 通用 PRD 降级", () => {
it("generic domain 生成合理的架构", () => {
const { data } = loadPRD(GENERIC_PRJ);
const arch = generateArchitecture(data);
assert.equal(arch.domain, "generic");
assert.ok(arch.techStack.frontend);
assert.ok(arch.techStack.backend);
assert.ok(arch.databaseSchema.length >= 1);
assert.ok(arch.modules.length >= 1);
});
it("通用 PRD 有最小表结构", () => {
const { data } = loadPRD(GENERIC_PRJ);
const arch = generateArchitecture(data);
assert.ok(arch.databaseSchema.some(t => t.table === "users"));
assert.ok(arch.databaseSchema.some(t => t.table === "items"));
});
});
// ═══════════════════════════════════════════════════════
// 8 — 架构图生成
// ═══════════════════════════════════════════════════════
describe("8 — 架构图生成", () => {
it("架构图包含项目名", () => {
const diagram = generateArchDiagram(
"PetCare",
{ frontend: "React Native", backend: "NestJS", database: "PostgreSQL", deployment: "Docker" },
["宠物管理", "健康日程", "用户中心"]
);
assert.ok(diagram.includes("PetCare"));
assert.ok(diagram.includes("System Architecture"));
});
it("架构图包含模块列表", () => {
const diagram = generateArchDiagram(
"TestApp", { frontend: "React", backend: "Node", database: "PG" },
["Auth", "API", "Dashboard"]
);
assert.ok(diagram.includes("Auth"));
assert.ok(diagram.includes("API"));
assert.ok(diagram.includes("Dashboard"));
});
it("架构图是 ASCII 格式", () => {
const diagram = generateArchDiagram("X", {}, []);
assert.ok(diagram.includes("┌"));
assert.ok(diagram.includes("└"));
assert.ok(diagram.includes("│"));
});
});
// ═══════════════════════════════════════════════════════
// 9 — 数据库设计
// ═══════════════════════════════════════════════════════
describe("9 — 数据库设计", () => {
it("所有表都有 fields 数组", () => {
const domains = ["pet", "ecommerce", "education", "enterprise", "fitness", "note", "generic"];
for (const domain of domains) {
const tables = generateDatabaseSchema(
[{ name: "测试功能", description: "测试", priority: "P0" }],
[{ method: "GET", path: "/api/test", description: "test" }],
domain
);
for (const table of tables) {
assert.ok(table.table, `${domain}: table has name`);
assert.ok(Array.isArray(table.fields), `${domain}.${table.table}: fields is array`);
assert.ok(table.fields.length > 0, `${domain}.${table.table}: fields not empty`);
}
}
});
it("所有域都有 users 表", () => {
const domains = ["pet", "ecommerce", "education", "enterprise"];
for (const domain of domains) {
const tables = generateDatabaseSchema([], [], domain);
assert.ok(tables.some(t => t.table === "users"), `${domain}: missing users table`);
}
});
it("表字段包含类型和约束", () => {
const tables = generateDatabaseSchema(
[{ name: "测试", description: "", priority: "P0" }],
[{ method: "GET", path: "/api/test", description: "" }],
"pet"
);
for (const table of tables) {
for (const field of table.fields) {
assert.ok(field.name);
assert.ok(field.type);
// constraints may be null/empty for some fields
}
}
});
});
// ═══════════════════════════════════════════════════════
// 10 — API 设计
// ═══════════════════════════════════════════════════════
describe("10 — API 设计", () => {
it("按资源分组 API", () => {
const apis = [
{ method: "GET", path: "/api/pets", description: "列表" },
{ method: "POST", path: "/api/pets", description: "创建" },
{ method: "GET", path: "/api/schedules", description: "日程" },
];
const design = designAPI(apis);
assert.equal(design.length, 2);
const resources = design.map(g => g.resource);
assert.ok(resources.includes("pets"));
assert.ok(resources.includes("schedules"));
});
it("每个资源组有 basePath", () => {
const apis = [{ method: "GET", path: "/api/orders", description: "订单" }];
const design = designAPI(apis);
assert.equal(design[0].basePath, "/api/orders");
assert.equal(design[0].endpoints.length, 1);
});
it("endpoint 包含 method 和 path", () => {
const apis = [
{ method: "POST", path: "/api/pets", description: "创建宠物" },
{ method: "GET", path: "/api/pets/:id", description: "获取宠物" },
];
const design = designAPI(apis);
const group = design.find(g => g.resource === "pets");
assert.equal(group.endpoints.length, 2);
assert.ok(group.endpoints.some(e => e.method === "POST"));
assert.ok(group.endpoints.some(e => e.path.includes(":id")));
});
});
// ═══════════════════════════════════════════════════════
// 11 — 目录结构
// ═══════════════════════════════════════════════════════
describe("11 — 目录结构生成", () => {
it("包含 apps 和 server 目录", () => {
const dirs = generateDirectoryStructure("PetCare", "pet", [
{ name: "pet", label: "宠物管理", features: ["宠物档案"] },
]);
const text = dirs.join("\n");
assert.ok(text.includes("apps/"));
assert.ok(text.includes("server/"));
});
it("包含 docker-compose 和 CI 配置", () => {
const dirs = generateDirectoryStructure("TestApp", "generic", []);
const text = dirs.join("\n");
assert.ok(text.includes("docker-compose.yml"));
assert.ok(text.includes(".github/workflows"));
assert.ok(text.includes("ci.yml"));
});
it("模块目录包含 controller/service/module", () => {
const dirs = generateDirectoryStructure("PetCare", "pet", [
{ name: "pets", label: "宠物管理", features: [] },
]);
const text = dirs.join("\n");
assert.ok(text.includes("pets.controller.ts"));
assert.ok(text.includes("pets.service.ts"));
assert.ok(text.includes("pets.module.ts"));
});
it("包含 Prisma schema", () => {
const dirs = generateDirectoryStructure("App", "generic", []);
const text = dirs.join("\n");
assert.ok(text.includes("schema.prisma"));
});
});
// ═══════════════════════════════════════════════════════
// 12 — 模块分解
// ═══════════════════════════════════════════════════════
describe("12 — 模块分解", () => {
it("按关键词分组合并功能", () => {
const features = [
{ name: "用户管理", description: "", priority: "P0" },
{ name: "权限控制", description: "", priority: "P0" },
{ name: "设置中心", description: "", priority: "P1" },
];
const modules = decomposeModules(features, "generic");
// "用户管理" → auth, "权限控制" → auth (matches "权限"), "设置中心" → settings (matches "设置")
assert.ok(modules.some(m => m.name === "auth"));
assert.ok(modules.some(m => m.name === "settings"));
});
it("未知功能各自独立模块", () => {
const features = [
{ name: "量子计算", description: "", priority: "P0" },
];
const modules = decomposeModules(features, "generic");
assert.ok(modules.some(m => m.name === "量子计算"));
});
it("电商域添加订单和商品模块", () => {
const features = [{ name: "商品浏览", description: "", priority: "P0" }];
const modules = decomposeModules(features, "ecommerce");
const names = modules.map(m => m.name);
assert.ok(names.includes("product"));
assert.ok(names.includes("order"));
});
it("企业域添加审批/考勤/部门模块", () => {
const features = [
{ name: "审批流程", description: "", priority: "P0" },
{ name: "考勤打卡", description: "", priority: "P0" },
{ name: "部门管理", description: "", priority: "P0" },
];
const modules = decomposeModules(features, "enterprise");
const names = modules.map(m => m.name);
assert.ok(names.includes("approval"));
assert.ok(names.includes("attendance"));
assert.ok(names.includes("department"));
});
it("每个模块有 responsibilities", () => {
const { data } = loadPRD(ECO_PRJ);
const arch = generateArchitecture(data);
for (const mod of arch.modules) {
assert.ok(mod.name);
assert.ok(mod.label);
assert.ok(Array.isArray(mod.features));
assert.ok(Array.isArray(mod.responsibilities));
assert.ok(mod.responsibilities.length > 0);
}
});
});
// ═══════════════════════════════════════════════════════
// 13 — 数据流
// ═══════════════════════════════════════════════════════
describe("13 — 数据流生成", () => {
it("为页面生成数据流", () => {
const { data } = loadPRD(ECO_PRJ);
const flows = generateDataFlows(data.pages, data.apiRequirements);
assert.ok(flows.length > 0);
for (const flow of flows) {
assert.ok(flow.page);
assert.ok(flow.route);
assert.ok(flow.description);
assert.ok(Array.isArray(flow.dataFlow));
assert.ok(flow.direction.includes("Database"));
}
});
it("API 为空时回退到通用数据流", () => {
const flows = generateDataFlows(
[{ name: "首页", route: "/home", description: "首页" }],
[{ method: "GET", path: "/api/health", description: "健康检查" }]
);
assert.ok(flows.length > 0);
});
});
// ═══════════════════════════════════════════════════════
// 14 — CLI 端到端
// ═══════════════════════════════════════════════════════
describe("14 — CLI 端到端", () => {
it("--input 模式生成架构", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/ecommerce-prd.json --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
const parsed = JSON.parse(result);
assert.equal(parsed.projectName, "ShopApp");
assert.equal(parsed.domain, "ecommerce");
assert.ok(parsed.techStack.frontend.includes("uni-app") || parsed.techStack.frontend.includes("小程序"));
});
it("--input-text 模式自动调 SF-01", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --input-text "做一个在线教育平台" --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
const parsed = JSON.parse(result);
assert.equal(parsed.projectName, "EduPlatform");
assert.equal(parsed.domain, "education");
});
it("--help 显示帮助", async () => {
const { execSync } = await import("node:child_process");
const result = execSync(
`node scripts/architecture-agent.mjs --help`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.ok(result.includes("Usage"));
assert.ok(result.includes("architecture-agent"));
});
it("--output 写入文件", async () => {
const { execSync } = await import("node:child_process");
const tmpPath = resolve(WORKSPACE, ".tmp-arch-e2e.json");
execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/note-prd.json --output ${tmpPath} --pretty`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.ok(existsSync(tmpPath));
const content = JSON.parse(readFileSync(tmpPath, "utf-8"));
assert.equal(content.projectName, "NoteApp");
assert.ok(content.architectureDiagram.includes("NoteApp"));
rmSync(tmpPath);
});
it("invalid PRD 返回非零退出码", async () => {
const { execSync } = await import("node:child_process");
try {
execSync(
`node scripts/architecture-agent.mjs --input test/fixtures/architecture-agent/empty-prd.json`,
{ cwd: WORKSPACE, encoding: "utf-8" }
);
assert.fail("Should have thrown");
} catch (e) {
assert.ok(e.status !== 0);
}
});
});
// ═══════════════════════════════════════════════════════
// 15 — 技术栈解析
// ═══════════════════════════════════════════════════════
describe("15 — 技术栈解析", () => {
it("wechat-miniapp 平台使用微信栈", () => {
const prd = {
techConstraints: { platforms: ["wechat-miniapp"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(stack.frontend.includes("微信"));
});
it("iOS 平台推荐 SwiftUI", () => {
const prd = {
techConstraints: { platforms: ["ios"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(stack.frontend.includes("SwiftUI"));
});
it("mobile+web 推荐跨平台框架", () => {
const prd = {
techConstraints: { platforms: ["mobile", "web"], considerations: [] },
extraFeatures: [],
};
const stack = resolveTechStack(prd);
assert.ok(
stack.frontend.includes("React Native") ||
stack.frontend.includes("Flutter")
);
});
it("实时特性添加 WebSocket", () => {
const prd = {
techConstraints: { platforms: ["web"], considerations: [] },
extraFeatures: ["real-time"],
};
const stack = resolveTechStack(prd);
assert.ok(stack.backend.includes("WebSocket") || stack.backend.includes("Socket.io"));
});
it("AI 特性添加 AI 服务", () => {
const prd = {
techConstraints: { platforms: ["web"], considerations: [] },
extraFeatures: ["ai-powered"],
};
const stack = resolveTechStack(prd);
assert.ok(stack.backend.includes("AI"));
});
});
// ═══════════════════════════════════════════════════════
// 16 — File I/O
// ═══════════════════════════════════════════════════════
describe("16 — File I/O", () => {
it("loadPRD 读取文件", () => {
const { data } = loadPRD(ECO_PRJ);
assert.ok(data.projectName);
});
it("loadPRD 对不存在文件返回 error", () => {
const { data, error } = loadPRD("/nonexistent.json");
assert.equal(data, null);
assert.ok(error);
});
it("writeArchitecture 写入文件", () => {
const arch = generateArchitecture(JSON.parse(readFileSync(ECO_PRJ, "utf-8")));
const tmpPath = resolve(WORKSPACE, ".tmp-arch-write-test.json");
writeArchitecture(arch, tmpPath, true);
assert.ok(existsSync(tmpPath));
rmSync(tmpPath);
});
});
@@ -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");
});
});
+567
View File
@@ -0,0 +1,567 @@
/**
* Backend Builder Agent Test — SF-04
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
let buildBackend, loadJSON, writeBackend;
before(async () => {
const mod = await import("../scripts/backend-builder-agent.mjs");
buildBackend = mod.buildBackend;
loadJSON = mod.loadJSON;
writeBackend = mod.writeBackend;
});
function loadPRD(name) {
return JSON.parse(readFileSync(resolve(WORKSPACE, `test/fixtures/architecture-agent/${name}-prd.json`), "utf-8"));
}
function loadArch() {
return JSON.parse(readFileSync(resolve(WORKSPACE, "architecture-example.json"), "utf-8"));
}
// Demo RequirementPackage that exercises all generators
const DEMO_PRD = {
projectName: "PetCare",
chineseName: "宠物管理",
domain: "pet",
summary: "一款宠物健康管理应用",
features: [
{ name: "宠物档案", priority: "P0" },
{ name: "健康日程", priority: "P0" },
{ name: "日常记录", priority: "P1" },
{ name: "附近医院", priority: "P1" },
],
userStories: [
{ id: "US-001", as: "宠物主人", want: "记录宠物信息", soThat: "管理宠物档案", priority: "P0" },
],
personas: [{ name: "宠物主人" }],
};
const DEMO_ARCH = {
projectName: "PetCare",
databaseSchema: [
{
table: "pets",
description: "宠物档案表",
fields: [
{ name: "id", type: "TEXT", constraints: "PK" },
{ name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" },
{ name: "name", type: "TEXT", constraints: "NOT NULL" },
{ name: "species", type: "TEXT" },
{ name: "breed", type: "TEXT" },
{ name: "birth_date", type: "TEXT" },
{ name: "weight_kg", type: "REAL" },
{ name: "avatar_url", type: "TEXT" },
{ name: "created_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
{ name: "updated_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
],
indexes: ["CREATE INDEX IF NOT EXISTS idx_pets_owner ON pets(owner_id)"],
},
{
table: "schedules",
description: "健康日程表",
fields: [
{ name: "id", type: "TEXT", constraints: "PK" },
{ name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" },
{ name: "type", type: "TEXT", constraints: "NOT NULL" },
{ name: "title", type: "TEXT", constraints: "NOT NULL" },
{ name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" },
{ name: "completed", type: "INTEGER", constraints: "DEFAULT 0" },
{ name: "created_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
],
},
{
table: "daily_logs",
description: "日常记录表",
fields: [
{ name: "id", type: "TEXT", constraints: "PK" },
{ name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" },
{ name: "category", type: "TEXT", constraints: "NOT NULL" },
{ name: "value", type: "TEXT", constraints: "NOT NULL" },
{ name: "logged_at", type: "TEXT", constraints: "NOT NULL, DEFAULT (datetime('now'))" },
],
},
],
apiDesign: [
{
resource: "pets",
basePath: "/api/pets",
endpoints: [
{ method: "GET", path: "/api/pets", description: "获取宠物列表" },
{ method: "GET", path: "/api/pets/:id", description: "获取宠物详情" },
{ method: "POST", path: "/api/pets", description: "创建宠物" },
{ method: "PUT", path: "/api/pets/:id", description: "更新宠物" },
{ method: "DELETE", path: "/api/pets/:id", description: "删除宠物" },
],
},
{
resource: "schedules",
basePath: "/api/schedules",
endpoints: [
{ method: "GET", path: "/api/schedules", description: "获取日程列表" },
{ method: "POST", path: "/api/schedules", description: "创建日程" },
],
},
{
resource: "daily-logs",
basePath: "/api/daily-logs",
endpoints: [
{ method: "POST", path: "/api/daily-logs", description: "记录日常" },
{ method: "GET", path: "/api/daily-logs", description: "查询日常记录" },
],
},
],
};
// ═══════════════════════════════════════════════════════
describe("1 — 目录结构完整性", () => {
it("生成完整后端项目目录", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(!result.error, `Unexpected error: ${result.error}`);
assert.ok(result.stats.totalFiles >= 15, `Expected >= 15 files, got ${result.stats.totalFiles}`);
});
it("生成 package.json", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["package.json"], "Missing package.json");
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.dependencies.fastify, "fastify dependency missing");
assert.ok(pkg.dependencies["sql.js"], "sql.js dependency missing");
assert.ok(pkg.dependencies["@fastify/jwt"], "@fastify/jwt dependency missing");
assert.ok(pkg.dependencies.bcrypt, "bcrypt dependency missing");
assert.ok(pkg.devDependencies.typescript, "typescript devDependency missing");
assert.ok(pkg.scripts.build, "build script missing");
assert.ok(pkg.scripts.test, "test script missing");
assert.ok(pkg.scripts.dev, "dev script missing");
});
it("生成 tsconfig.json", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["tsconfig.json"], "Missing tsconfig.json");
const cfg = JSON.parse(result.files["tsconfig.json"]);
assert.equal(cfg.compilerOptions.strict, true);
assert.equal(cfg.compilerOptions.module, "ESNext");
assert.ok(cfg.compilerOptions.outDir);
});
it("生成 .env.example", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files[".env.example"]);
const env = result.files[".env.example"];
assert.ok(env.includes("JWT_SECRET"));
assert.ok(env.includes("DATABASE_URL"));
assert.ok(env.includes("PORT"));
});
it("生成 README.md", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["README.md"]);
const readme = result.files["README.md"];
assert.ok(readme.includes("Fastify"));
assert.ok(readme.includes("SQLite"));
assert.ok(readme.includes("npm install"));
assert.ok(readme.includes("## API Endpoints"));
});
it("生成 .gitignore", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files[".gitignore"]);
assert.ok(result.files[".gitignore"].includes("node_modules"));
});
});
// ═══════════════════════════════════════════════════════
describe("2 — src/index.ts(服务入口)", () => {
it("生成 src/index.ts", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/index.ts"]);
const content = result.files["src/index.ts"];
assert.ok(content.includes("Fastify"), "Missing Fastify import");
assert.ok(content.includes("buildApp"), "Missing buildApp function");
assert.ok(content.includes("@fastify/jwt"), "Missing JWT plugin");
assert.ok(content.includes("@fastify/cors"), "Missing CORS plugin");
assert.ok(content.includes("authRoutes"), "Missing auth routes");
assert.ok(content.includes("petsRoutes") || content.includes("/api/pets"), "Missing pets routes");
assert.ok(content.includes("/api/health"), "Missing health check");
});
});
// ═══════════════════════════════════════════════════════
describe("3 — src/db/(数据库层)", () => {
it("生成 src/db/schema.ts", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/db/schema.ts"]);
const content = result.files["src/db/schema.ts"];
assert.ok(content.includes("CREATE TABLE"), "Missing CREATE TABLE");
assert.ok(content.includes("users"), "Missing users table");
assert.ok(content.includes("pets"), "Missing pets table");
assert.ok(content.includes("schedules"), "Missing schedules table");
assert.ok(content.includes("daily_logs"), "Missing daily_logs table");
});
it("生成 src/db/client.ts", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/db/client.ts"]);
const content = result.files["src/db/client.ts"];
assert.ok(content.includes("sql.js"), "Missing sql.js import");
assert.ok(content.includes("getDb"), "Missing getDb function");
assert.ok(content.includes("queryAll"), "Missing queryAll helper");
assert.ok(content.includes("queryOne"), "Missing queryOne helper");
assert.ok(content.includes("execute"), "Missing execute helper");
assert.ok(content.includes("initDb"), "Missing initDb function");
});
});
// ═══════════════════════════════════════════════════════
describe("4 — src/routes/(路由层)", () => {
it("生成 CRUD 路由文件", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.stats.routes >= 3, `Expected >= 3 routes, got ${result.stats.routes}`);
assert.ok(result.files["src/routes/pets.ts"], "Missing pets routes");
assert.ok(result.files["src/routes/schedules.ts"], "Missing schedules routes");
assert.ok(result.files["src/routes/daily_logs.ts"], "Missing daily_logs routes");
});
it("生成 auth 路由", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/routes/auth.ts"]);
const content = result.files["src/routes/auth.ts"];
assert.ok(content.includes("register"), "Missing register route");
assert.ok(content.includes("login"), "Missing login route");
assert.ok(content.includes("/me"), "Missing /me route");
assert.ok(content.includes("bcrypt"), "Missing bcrypt import");
assert.ok(content.includes("jwt.sign"), "Missing JWT sign");
});
it("路由文件包含 CRUD 方法", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const petsRoute = result.files["src/routes/pets.ts"];
assert.ok(petsRoute.includes("authenticate"), "Missing auth middleware");
assert.ok(petsRoute.includes("app.get"), "Missing GET");
assert.ok(petsRoute.includes("app.post"), "Missing POST");
assert.ok(petsRoute.includes("app.put"), "Missing PUT");
assert.ok(petsRoute.includes("app.delete"), "Missing DELETE");
});
});
// ═══════════════════════════════════════════════════════
describe("5 — src/middleware/(中间件)", () => {
it("生成 JWT 认证中间件", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/middleware/auth.ts"]);
const content = result.files["src/middleware/auth.ts"];
assert.ok(content.includes("authenticate"), "Missing authenticate");
assert.ok(content.includes("requireAdmin"), "Missing requireAdmin");
assert.ok(content.includes("optionalAuth"), "Missing optionalAuth");
assert.ok(content.includes("jwtVerify"), "Missing jwtVerify call");
assert.ok(content.includes("status(401)"), "Missing 401 response");
});
});
// ═══════════════════════════════════════════════════════
describe("6 — src/services/(服务层)", () => {
it("生成服务文件", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.stats.services >= 3, `Expected >= 3 services, got ${result.stats.services}`);
assert.ok(result.files["src/services/pet.ts"], "Missing pet service");
assert.ok(result.files["src/services/schedule.ts"], "Missing schedule service");
});
it("服务文件包含 CRUD 方法", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const svc = result.files["src/services/pet.ts"];
assert.ok(svc.includes("list()"), "Missing list()");
assert.ok(svc.includes("getById"), "Missing getById()");
assert.ok(svc.includes("create"), "Missing create()");
assert.ok(svc.includes("update"), "Missing update()");
assert.ok(svc.includes("delete"), "Missing delete()");
assert.ok(svc.includes("class"), "Missing class definition");
});
});
// ═══════════════════════════════════════════════════════
describe("7 — src/types/(类型定义)", () => {
it("生成类型文件", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.files["src/types/index.ts"]);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("export interface"), "Missing interfaces");
});
it("包含数据模型类型", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("interface Pet"), "Missing Pet type");
assert.ok(types.includes("interface Schedule"), "Missing Schedule type");
assert.ok(types.includes("interface User"), "Missing User type");
});
it("包含 Create/Update 输入类型", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("CreatePetInput"), "Missing CreatePetInput");
assert.ok(types.includes("UpdatePetInput"), "Missing UpdatePetInput");
});
it("包含 Auth 类型", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("LoginInput"), "Missing LoginInput");
assert.ok(types.includes("RegisterInput"), "Missing RegisterInput");
assert.ok(types.includes("AuthResponse"), "Missing AuthResponse");
});
it("包含 JWT payload 类型", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("JwtPayload"), "Missing JwtPayload");
});
it("包含 API 响应类型", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const types = result.files["src/types/index.ts"];
assert.ok(types.includes("ApiResponse"), "Missing ApiResponse");
assert.ok(types.includes("PaginatedResponse"), "Missing PaginatedResponse");
assert.ok(types.includes("ErrorResponse"), "Missing ErrorResponse");
});
});
// ═══════════════════════════════════════════════════════
describe("8 — 测试生成", () => {
it("生成测试文件", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
assert.ok(result.stats.tests >= 2, `Expected >= 2 test files, got ${result.stats.tests}`);
assert.ok(result.files["src/__tests__/auth.test.ts"], "Missing auth test");
});
it("auth 测试包含 register/login/me", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const test = result.files["src/__tests__/auth.test.ts"];
assert.ok(test.includes("registers a new user"), "Missing register test");
assert.ok(test.includes("logs in with correct credentials"), "Missing login test");
assert.ok(test.includes("returns current user"), "Missing me test");
assert.ok(test.includes("rejects without token"), "Missing auth fail test");
});
it("CRUD 测试包含完整生命周期", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const test = result.files["src/__tests__/pets.test.ts"];
if (!test) return; // pets test may not exist depending on naming
assert.ok(test.includes("creates"), "Missing create test");
assert.ok(test.includes("updates"), "Missing update test");
assert.ok(test.includes("deletes"), "Missing delete test");
assert.ok(test.includes("returns 404 after delete"), "Missing 404-after-delete test");
});
});
// ═══════════════════════════════════════════════════════
describe("9 — 错误处理", () => {
it("invalid PRD 返回错误", () => {
const result = buildBackend({ error: "test" }, {});
assert.equal(result.error, "INVALID_PRD");
});
it("invalid Architecture 返回错误", () => {
const result = buildBackend(DEMO_PRD, { error: "test" });
assert.equal(result.error, "INVALID_ARCH");
});
it("null PRD 返回错误", () => {
const result = buildBackend(null, {});
assert.equal(result.error, "INVALID_PRD");
});
it("空的 databaseSchema 仍然生成基础项目", () => {
const result = buildBackend(DEMO_PRD, { ...DEMO_ARCH, databaseSchema: [] });
assert.ok(!result.error);
assert.ok(result.files["src/index.ts"]);
assert.ok(result.files["src/routes/auth.ts"]);
assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files with empty schema, got ${result.stats.totalFiles}`);
});
});
// ═══════════════════════════════════════════════════════
describe("10 — 文件写入 I/O", () => {
it("writeBackend 写入完整项目", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const tmpDir = resolve(WORKSPACE, ".tmp-backend-test");
writeBackend(result, tmpDir);
assert.ok(existsSync(resolve(tmpDir, "package.json")));
assert.ok(existsSync(resolve(tmpDir, "tsconfig.json")));
assert.ok(existsSync(resolve(tmpDir, ".env.example")));
assert.ok(existsSync(resolve(tmpDir, "README.md")));
assert.ok(existsSync(resolve(tmpDir, "src/index.ts")));
assert.ok(existsSync(resolve(tmpDir, "src/db/schema.ts")));
assert.ok(existsSync(resolve(tmpDir, "src/db/client.ts")));
assert.ok(existsSync(resolve(tmpDir, "src/middleware/auth.ts")));
assert.ok(existsSync(resolve(tmpDir, "src/routes/auth.ts")));
assert.ok(existsSync(resolve(tmpDir, "src/types/index.ts")));
// Check at least one route & service file
const routesDir = resolve(tmpDir, "src/routes");
const servicesDir = resolve(tmpDir, "src/services");
assert.ok(existsSync(routesDir));
assert.ok(existsSync(servicesDir));
rmSync(tmpDir, { recursive: true, force: true });
});
it("loadJSON 读取文件", () => {
const { data } = loadJSON(resolve(WORKSPACE, "prd-example.json"));
assert.ok(data.projectName);
});
it("loadJSON 不存在的文件返回 error", () => {
const { data, error } = loadJSON("/nonexistent.json");
assert.equal(data, null);
assert.ok(error);
});
});
// ═══════════════════════════════════════════════════════
describe("11 — npm install & build", () => {
it("npm install 成功", async () => {
const generateAndInstall = async () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const tmpDir = resolve(WORKSPACE, ".tmp-backend-build");
writeBackend(result, tmpDir);
const { execSync } = await import("node:child_process");
try {
execSync("npm install --no-audit --no-fund --prefer-offline", {
cwd: tmpDir,
encoding: "utf-8",
timeout: 120_000,
stdio: "pipe",
});
return { success: true, tmpDir };
} catch (e) {
return { success: false, error: e.stderr || e.message, tmpDir };
}
};
const { success, error, tmpDir } = await generateAndInstall();
if (!success) {
console.error("npm install failed:", error?.slice(0, 500));
}
assert.ok(success, `npm install failed: ${error?.slice(0, 200)}`);
// Cleanup
rmSync(tmpDir, { recursive: true, force: true });
});
it("npm run build 成功", async () => {
const generateAndBuild = async () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const tmpDir = resolve(WORKSPACE, ".tmp-backend-build");
writeBackend(result, tmpDir);
const { execSync } = await import("node:child_process");
try {
execSync("npm install --no-audit --no-fund --prefer-offline", {
cwd: tmpDir,
encoding: "utf-8",
timeout: 120_000,
stdio: "pipe",
});
execSync("npx tsc --noEmit", {
cwd: tmpDir,
encoding: "utf-8",
timeout: 60_000,
stdio: "pipe",
});
return { success: true, tmpDir };
} catch (e) {
return { success: false, error: e.stderr || e.message, tmpDir };
}
};
const { success, error, tmpDir } = await generateAndBuild();
if (!success) {
console.error("Build failed:", error?.slice(0, 1000));
}
assert.ok(success, `Build failed: ${error?.slice(0, 300)}`);
// Cleanup
rmSync(tmpDir, { recursive: true, force: true });
});
});
// ═══════════════════════════════════════════════════════
describe("12 — CLI 集成", () => {
it("--prd + --arch CLI 模式", async () => {
const result = buildBackend(
loadPRD("ecommerce"),
loadArch()
);
writeBackend(result, resolve(WORKSPACE, ".tmp-cli-backend"));
assert.ok(!result.error);
rmSync(resolve(WORKSPACE, ".tmp-cli-backend"), { recursive: true, force: true });
});
it("--help 显示帮助", async () => {
const { execSync } = await import("node:child_process");
const output = execSync("node scripts/backend-builder-agent.mjs --help", {
cwd: WORKSPACE,
encoding: "utf-8",
});
assert.ok(output.includes("Backend Builder"));
assert.ok(output.includes("Usage"));
});
});
// ═══════════════════════════════════════════════════════
describe("13 — 多领域覆盖", () => {
const domains = ["ecommerce", "enterprise", "education", "note"];
for (const domain of domains) {
it(`${domain} 域生成成功`, () => {
const prd = loadPRD(domain);
const result = buildBackend(prd, loadArch());
assert.ok(!result.error, `${domain}: ${result.message}`);
assert.ok(result.stats.totalFiles >= 8, `${domain}: expected >= 8 files, got ${result.stats.totalFiles}`);
assert.ok(result.stats.routes >= 2, `${domain}: expected >= 2 routes, got ${result.stats.routes}`);
assert.ok(result.stats.services >= 2, `${domain}: expected >= 2 services, got ${result.stats.services}`);
});
}
});
// ═══════════════════════════════════════════════════════
describe("14 — 生成的内容非空且有意义", () => {
it("所有生成的文件内容非空(>50 字符)", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
for (const [path, content] of Object.entries(result.files)) {
assert.ok(content.length > 50, `${path}: too short (${content.length} chars)`);
}
});
it("package.json 可解析为合法 JSON", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.name);
assert.ok(pkg.dependencies);
assert.ok(pkg.scripts);
});
it("tsconfig.json 可解析为合法 JSON", () => {
const result = buildBackend(DEMO_PRD, DEMO_ARCH);
const cfg = JSON.parse(result.files["tsconfig.json"]);
assert.ok(cfg.compilerOptions);
});
});
+65
View File
@@ -0,0 +1,65 @@
/**
* PR-A Baseline Smoke Test
* Quick end-to-end sanity check before running full test suite.
* Verifies that essential tools are available and the system is in a testable state.
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
describe("PR-A Smoke Test", () => {
it("smoke: node version >= 22", () => {
const v = process.version;
const major = parseInt(v.split(".")[0].replace("v", ""));
console.log("[SMOKE] Node.js version:", v);
assert.ok(major >= 22, `node >= 22 required, got ${v}`);
});
it("smoke: openclaw CLI available", () => {
const out = execSync("openclaw --version 2>&1", {
timeout: 10000,
encoding: "utf8",
});
console.log("[SMOKE] openclaw:", out.trim().split("\n")[0]);
assert.ok(out.includes("OpenClaw"), "openclaw CLI must be available");
});
it("smoke: gateway is reachable", () => {
const out = execSync("openclaw status 2>&1", {
timeout: 15000,
encoding: "utf8",
});
const isRunning = out.includes("running") || out.includes("active") || out.includes("reachable");
console.log("[SMOKE] gateway running:", isRunning);
assert.ok(isRunning, "gateway must be running");
});
it("smoke: workspace exists", () => {
const ws = join(homedir(), ".openclaw", "workspace");
assert.ok(existsSync(ws), "workspace must exist");
console.log("[SMOKE] workspace:", ws);
});
it("smoke: memory system functional", () => {
const out = execSync("openclaw memory status --json 2>/dev/null", {
timeout: 15000,
encoding: "utf8",
});
const status = JSON.parse(out);
console.log("[SMOKE] memory status: OK");
assert.ok(Array.isArray(status), "memory status must be an array");
});
it("smoke: config loads without error", () => {
const out = execSync("openclaw config get gateway.port 2>&1", {
timeout: 10000,
encoding: "utf8",
});
console.log("[SMOKE] gateway port:", out.trim());
assert.ok(out.length > 0, "config must return value");
});
});
@@ -0,0 +1,91 @@
/**
* PR-A Baseline Test 01: Gateway Startup
* Verifies: gateway process is running and reachable
* Freezes: gateway startup contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
import { request } from "node:http";
import { readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const AGENTS_HOME = join(homedir(), ".openclaw");
let _config, _port;
function loadConfig() {
if (!_config) {
_config = JSON.parse(readFileSync(join(AGENTS_HOME, "openclaw.json"), "utf8"));
_port = _config?.gateway?.port || 18789;
}
return { config: _config, gatewayPort: _port };
}
function httpGet(host, port, path) {
return new Promise((resolve, reject) => {
const req = request({ hostname: host, port, path, method: "GET", timeout: 10000 }, (res) => {
let body = "";
res.on("data", (d) => (body += d));
res.on("end", () => resolve({ status: res.statusCode, body }));
});
req.on("error", reject);
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
req.end();
});
}
describe("test-01: Gateway Startup Baseline", () => {
it("01.01 - gateway config exists", () => {
const { config } = loadConfig();
assert.ok(config, "config should be loaded");
assert.ok(config.gateway, "gateway section should exist");
});
it("01.02 - gateway bind port is defined", () => {
const { gatewayPort } = loadConfig();
assert.ok(gatewayPort > 0 && gatewayPort < 65536, `port ${gatewayPort} must be valid`);
});
it("01.03 - gateway is reachable via HTTP", async () => {
const { gatewayPort } = loadConfig();
const result = await httpGet("127.0.0.1", gatewayPort, "/");
assert.ok(result.status >= 200 && result.status < 400,
`gateway HTTP must respond with 2xx/3xx, got ${result.status}`);
});
it("01.04 - gateway status reports running", () => {
const out = execSync("openclaw status --json 2>/dev/null", { timeout: 15000, encoding: "utf8" }).trim();
assert.ok(out.length > 0, "openclaw status must produce output");
try {
const s = JSON.parse(out);
assert.ok(s, "status must be valid JSON");
} catch {
assert.ok(!out.toLowerCase().includes("error"), "status must not report error");
}
});
it("01.05 - gateway process is not crashing (baseline snapshot)", () => {
const out = execSync("openclaw status 2>&1", { timeout: 15000, encoding: "utf8" }).trim();
const isRunning = out.includes("running") || out.includes("active") || out.includes("reachable");
console.log("[BASELINE] gateway status snapshot:", out.substring(0, 300));
assert.ok(isRunning, "gateway must be running");
});
it("01.06 - memory index is accessible", () => {
const out = execSync("openclaw memory status --json 2>/dev/null", { timeout: 15000, encoding: "utf8" }).trim();
const s = JSON.parse(out);
assert.ok(Array.isArray(s) && s.length > 0, "at least one agent memory entry");
const main = s.find((e) => e.agentId === "main");
assert.ok(main, "main agent must have memory");
console.log("[BASELINE] memory backend: %s, files: %d, chunks: %d",
main.status.backend, main.status.files, main.status.chunks);
});
it("01.07 - gateway auth mode recorded", () => {
const { config } = loadConfig();
const mode = config?.gateway?.auth?.mode;
console.log("[BASELINE] gateway auth mode:", mode);
assert.ok(mode, "gateway auth mode must be defined");
});
});
@@ -0,0 +1,83 @@
/**
* PR-A Baseline Test 02: Session Creation
* Verifies: sessions can be created, read, and have persistent state
* Freezes: session creation contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const AGENTS_HOME = join(homedir(), ".openclaw");
const SESSIONS_DIR = join(AGENTS_HOME, "agents", "main", "sessions");
describe("test-02: Session Creation Baseline", () => {
it("02.01 - sessions directory exists", () => {
const exists = existsSync(SESSIONS_DIR);
console.log("[BASELINE] sessions dir:", SESSIONS_DIR);
console.log("[BASELINE] sessions dir exists:", exists);
// Directory might not exist if no sessions created yet — that's valid baseline
if (!exists) {
console.log("[BASELINE] No sessions directory — no sessions created yet (valid baseline)");
}
assert.ok(true, "baseline frozen: sessions dir check");
});
it("02.02 - can list existing sessions", () => {
const out = execSync("openclaw status --json 2>/dev/null", {
timeout: 15000,
encoding: "utf8",
}).trim();
try {
const s = JSON.parse(out);
console.log("[BASELINE] sessions count:", s.sessionCount ?? "unknown");
assert.ok(typeof s === "object", "status must be an object");
} catch {
// Text output is fine
assert.ok(out.length > 0, "status must produce output");
}
});
it("02.03 - session store directory structure recorded", () => {
if (existsSync(SESSIONS_DIR)) {
const files = readdirSync(SESSIONS_DIR).filter(
(f) => f.endsWith(".json") || f.endsWith(".md")
);
console.log("[BASELINE] session files count:", files.length);
console.log("[BASELINE] session file types:",
[...new Set(files.map(f => f.slice(f.lastIndexOf('.'))))].join(", "));
assert.ok(Array.isArray(files), "session files must be listable");
} else {
console.log("[BASELINE] No session files to inspect (valid baseline)");
}
assert.ok(true, "baseline frozen: session dir structure");
});
it("02.04 - workspace files are accessible (session context)", () => {
const workspaceDir = join(AGENTS_HOME, "workspace");
const keyFiles = ["AGENTS.md", "SOUL.md", "MEMORY.md"];
for (const f of keyFiles) {
const path = join(workspaceDir, f);
const exists = existsSync(path);
const size = exists ? readFileSync(path, "utf8").length : 0;
console.log(`[BASELINE] workspace/${f}: exists=${exists}, size=${size}`);
}
assert.ok(existsSync(join(workspaceDir, "AGENTS.md")), "AGENTS.md must exist");
assert.ok(existsSync(join(workspaceDir, "MEMORY.md")), "MEMORY.md must exist");
});
it("02.05 - session write lock mechanism exists", () => {
// Check if proper-lockfile is available (used for session locking)
try {
require.resolve("proper-lockfile");
console.log("[BASELINE] proper-lockfile: available (session locking)");
assert.ok(true, "session locking library available");
} catch {
console.log("[BASELINE] proper-lockfile: not resolvable (may be bundled)");
assert.ok(true, "baseline frozen: lockfile check");
}
});
});
@@ -0,0 +1,108 @@
/**
* PR-A Baseline Test 03: memory_search Current Behavior
* Verifies: memory_search tool exists, has stable schema, returns stable format
* Freezes: memory_search contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
describe("test-03: memory_search Baseline", () => {
it("03.01 - memory_search CLI returns JSON", () => {
const out = execSync(
'openclaw memory search "hello world" --json 2>/dev/null',
{ timeout: 15000, encoding: "utf8" }
).trim();
assert.ok(out.length > 0, "memory search must produce output");
try {
const result = JSON.parse(out);
assert.ok(typeof result === "object", "must be JSON object");
console.log("[BASELINE] memory_search result keys:", Object.keys(result).join(", "));
console.log("[BASELINE] memory_search results count:",
result.results?.length ?? "N/A");
} catch (e) {
console.log("[BASELINE] memory_search output (not JSON):", out.substring(0, 200));
assert.ok(!out.toLowerCase().includes("error"), "must not error");
}
});
it("03.02 - memory_search returns results array", () => {
const out = execSync(
'openclaw memory search "AGENTS.md" --json 2>/dev/null',
{ timeout: 15000, encoding: "utf8" }
).trim();
try {
const result = JSON.parse(out);
const results = result.results || [];
console.log("[BASELINE] search 'AGENTS.md' results:", results.length);
if (results.length > 0) {
const first = results[0];
console.log("[BASELINE] first result keys:", Object.keys(first).join(", "));
console.log("[BASELINE] first result file:", first.file || first.path || "N/A");
console.log("[BASELINE] first result score:", first.score ?? "N/A");
}
assert.ok(Array.isArray(results), "results must be array");
} catch {
console.log("[BASELINE] search output raw:", out.substring(0, 300));
}
assert.ok(true, "baseline frozen: search results shape");
});
it("03.03 - memory_search schema stable (empty query)", () => {
const out = execSync(
'openclaw memory search "___nonexistent_xyzzy___" --json 2>/dev/null',
{ timeout: 15000, encoding: "utf8" }
).trim();
try {
const result = JSON.parse(out);
const results = result.results || [];
console.log("[BASELINE] empty search results:", results.length);
// Empty results should not error
} catch {
console.log("[BASELINE] empty search raw:", out.substring(0, 200));
}
assert.ok(true, "baseline frozen: empty search behavior");
});
it("03.04 - memory_search handles special characters", () => {
const queries = [
"中文测试",
"test-123",
"hello/world",
"a.b.c",
];
for (const q of queries) {
const out = execSync(
`openclaw memory search "${q}" --json 2>/dev/null`,
{ timeout: 15000, encoding: "utf8" }
).trim();
try {
JSON.parse(out);
console.log(`[BASELINE] search "${q}": OK (valid JSON)`);
} catch {
console.log(`[BASELINE] search "${q}": non-JSON output`);
}
assert.ok(!out.toLowerCase().includes("fatal"), `query "${q}" must not crash`);
}
});
it("03.05 - memory_search contract: result object shape", () => {
const out = execSync(
'openclaw memory search "architecture" --json 2>/dev/null',
{ timeout: 15000, encoding: "utf8" }
).trim();
try {
const result = JSON.parse(out);
// Freeze the top-level keys
const topKeys = Object.keys(result).sort();
console.log("[BASELINE] memory_search response keys:", topKeys.join(", "));
// There must be a 'results' key
assert.ok("results" in result || topKeys.length > 0,
"must have results key or be non-empty object");
} catch {
console.log("[BASELINE] raw response:", out.substring(0, 300));
}
assert.ok(true, "baseline frozen: contract shape");
});
});
+71
View File
@@ -0,0 +1,71 @@
/**
* PR-A Baseline Test 04: memory_get Current Behavior
* Verifies: memory_get tool exists, can read files, stable error format
* Freezes: memory_get contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const WORKSPACE = join(homedir(), ".openclaw", "workspace");
describe("test-04: memory_get Baseline", () => {
it("04.01 - MEMORY.md exists and is readable", () => {
const path = join(WORKSPACE, "MEMORY.md");
assert.ok(existsSync(path), "MEMORY.md must exist");
const content = readFileSync(path, "utf8");
const lines = content.split("\n").length;
const size = content.length;
console.log("[BASELINE] MEMORY.md: size=%d bytes, lines=%d", size, lines);
assert.ok(size > 0, "MEMORY.md must not be empty");
});
it("04.02 - daily memory files exist", () => {
const dailyDir = join(WORKSPACE, "memory");
const today = new Date().toISOString().slice(0, 10);
const dailyFile = join(dailyDir, `${today}.md`);
console.log("[BASELINE] today:", today);
console.log("[BASELINE] daily file:", dailyFile);
console.log("[BASELINE] daily file exists:", existsSync(dailyFile));
assert.ok(existsSync(dailyDir), "memory/ directory must exist");
});
it("04.03 - workspace files measurable", () => {
const files = ["AGENTS.md", "SOUL.md", "MEMORY.md", "USER.md", "TOOLS.md"];
for (const f of files) {
const path = join(WORKSPACE, f);
if (existsSync(path)) {
const stat = statSync(path);
console.log(`[BASELINE] ${f}: size=${stat.size}, mtime=${stat.mtime.toISOString()}`);
} else {
console.log(`[BASELINE] ${f}: NOT FOUND`);
}
}
assert.ok(true, "baseline frozen: workspace file inventory");
});
it("04.04 - memory_get for nonexistent file returns stable error", () => {
// memory_get behavior for missing file: should not crash, should return structured error
const nonexistent = "___nonexistent_file_xyzzy___.md";
const path = join(WORKSPACE, nonexistent);
assert.ok(!existsSync(path), "test file must not exist");
console.log("[BASELINE] nonexistent file check: path=", path);
// This just verifies the file doesn't exist — the tool behavior
// when requesting a missing file would be tested via the actual tool API
assert.ok(true, "baseline frozen: missing file expectation");
});
it("04.05 - memory file encoding is UTF-8", () => {
const path = join(WORKSPACE, "MEMORY.md");
const buf = readFileSync(path);
// Check for BOM (should not be present)
const hasBOM = buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF;
console.log("[BASELINE] MEMORY.md has BOM:", hasBOM);
// Read as utf8 should succeed
const content = readFileSync(path, "utf8");
assert.ok(content.length > 0, "MEMORY.md must decode as UTF-8");
});
});
+94
View File
@@ -0,0 +1,94 @@
/**
* PR-A Baseline Test 05: exec Tool Current Behavior
* Verifies: shell command execution returns stdout/stderr/exitCode correctly
* Freezes: exec tool contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync, spawnSync } from "node:child_process";
import { homedir } from "node:os";
import { join } from "node:path";
const GATEWAY_URL = "http://127.0.0.1:18789";
describe("test-05: exec Tool Baseline", () => {
it("05.01 - local shell execution works (node child_process)", () => {
const result = spawnSync("echo", ["hello"], {
encoding: "utf8",
timeout: 5000,
});
console.log("[BASELINE] echo hello stdout:", JSON.stringify(result.stdout.trim()));
console.log("[BASELINE] echo hello stderr:", JSON.stringify(result.stderr));
console.log("[BASELINE] echo hello status:", result.status);
assert.equal(result.stdout.trim(), "hello", "echo must output hello");
assert.equal(result.status, 0, "exit code must be 0");
});
it("05.02 - shell error exit codes are preserved", () => {
const result = spawnSync("sh", ["-c", "exit 42"], {
encoding: "utf8",
timeout: 5000,
});
console.log("[BASELINE] exit 42 status:", result.status);
assert.equal(result.status, 42, "exit code must be preserved exactly");
});
it("05.03 - shell stderr is captured separately", () => {
const result = spawnSync("sh", ["-c", "echo stdout-text && echo stderr-text >&2"], {
encoding: "utf8",
timeout: 5000,
});
console.log("[BASELINE] stdout:", JSON.stringify(result.stdout.trim()));
console.log("[BASELINE] stderr:", JSON.stringify(result.stderr.trim()));
assert.ok(result.stdout.includes("stdout-text"), "stdout must contain message");
assert.ok(result.stderr.includes("stderr-text"), "stderr must contain message");
});
it("05.04 - shell output encoding is UTF-8", () => {
const result = spawnSync("sh", ["-c", "echo 你好世界"], {
encoding: "utf8",
timeout: 5000,
});
console.log("[BASELINE] UTF-8 output:", JSON.stringify(result.stdout.trim()));
assert.ok(result.stdout.includes("你好世界"), "UTF-8 must be preserved");
});
it("05.05 - shell timeout kills process", () => {
const start = Date.now();
const result = spawnSync("sleep", ["10"], {
encoding: "utf8",
timeout: 2000, // 2 second timeout
});
const elapsed = Date.now() - start;
console.log("[BASELINE] sleep 10 timeout: elapsed=%dms, killed=%s",
elapsed, result.signal || "none");
// Process should be killed by timeout
assert.ok(result.signal === "SIGTERM" || result.status !== 0 || elapsed < 10000,
"long-running process must be terminated by timeout");
});
it("05.06 - exec tool: binary output handling", () => {
// Create a null-byte test
const result = spawnSync("printf", ["\\x00hello\\x00"], {
encoding: "buffer",
timeout: 5000,
});
console.log("[BASELINE] binary stdout length:", result.stdout.length);
console.log("[BASELINE] binary stdout bytes:",
Array.from(result.stdout.slice(0, 10)).map(b => '0x' + b.toString(16)).join(' '));
assert.ok(result.stdout.includes(0x68), "output must contain 'h' (0x68)");
});
it("05.07 - exec tool: large output handled", () => {
// Generate 10KB of output
const result = spawnSync("sh", ["-c", "yes head | head -1000"], {
encoding: "utf8",
timeout: 5000,
maxBuffer: 1024 * 1024,
});
const lines = result.stdout.trim().split("\n").length;
console.log("[BASELINE] large output: lines=%d, bytes=%d", lines, result.stdout.length);
assert.ok(lines > 100, "large output must be captured");
});
});
@@ -0,0 +1,85 @@
/**
* PR-A Baseline Test 06: Agent Complete Reply
* Verifies: agent can produce a reply via openclaw CLI
* Freezes: agent reply contract
*
* Uses openclaw agent CLI to send a test message and verify reply structure.
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
describe("test-06: Agent Reply Baseline", () => {
it("06.01 - agent CLI exists and shows help", () => {
const out = execSync("openclaw agent --help 2>&1", {
timeout: 10000,
encoding: "utf8",
}).trim();
console.log("[BASELINE] agent help first 300 chars:", out.substring(0, 300));
assert.ok(out.includes("agent") || out.includes("turn"),
"agent CLI must exist and show help");
});
it("06.02 - agent run command structure recorded", () => {
// Freeze the CLI interface shape
const out = execSync("openclaw agent --help 2>&1", {
timeout: 10000,
encoding: "utf8",
});
const hasMessage = out.includes("message") || out.includes("prompt") || out.includes("text");
const hasTimeout = out.includes("timeout") || out.includes("Time");
const hasModel = out.includes("model") || out.includes("Model");
console.log("[BASELINE] agent has --message param:", hasMessage);
console.log("[BASELINE] agent has --timeout param:", hasTimeout);
console.log("[BASELINE] agent has --model param:", hasModel);
assert.ok(true, "baseline frozen: agent CLI interface shape");
});
it("06.03 - gateway agent endpoint is documented", () => {
// Check the gateway protocol docs for agent method
const docPath = "/opt/homebrew/lib/node_modules/openclaw/docs/gateway/protocol.md";
try {
const doc = execSync(`head -200 "${docPath}" 2>/dev/null`, {
timeout: 5000,
encoding: "utf8",
});
console.log("[BASELINE] protocol docs exist:", doc.length > 0);
} catch {
console.log("[BASELINE] protocol docs: not directly readable (npm install)");
}
assert.ok(true, "baseline frozen: agent endpoint documentation");
});
it("06.04 - session/agent persistence directory exists", () => {
const out = execSync("openclaw status --json 2>/dev/null", {
timeout: 10000,
encoding: "utf8",
}).trim();
try {
const s = JSON.parse(out);
console.log("[BASELINE] agent info:", JSON.stringify({
agents: s.agents,
sessions: s.sessionCount,
defaultAgent: s.defaultAgent,
}, null, 2));
assert.ok(typeof s === "object", "status must be object");
} catch {
assert.ok(out.length > 0, "status must produce output");
}
});
it("06.05 - model providers are configured", () => {
try {
const configPath = homedir() + "/.openclaw/openclaw.json";
const config = JSON.parse(readFileSync(configPath, "utf8"));
const providers = config?.models?.providers ?? {};
const providerIds = Object.keys(providers);
console.log("[BASELINE] configured model providers:", providerIds.join(", "));
assert.ok(providerIds.length > 0, "at least one model provider must be configured");
} catch (e) {
console.log("[BASELINE] config read error:", e.message);
assert.ok(true, "baseline frozen: provider config existence");
}
});
});
@@ -0,0 +1,101 @@
/**
* PR-A Baseline Test 07: MCP Tool Discovery
* Verifies: MCP integration exists, tools can be discovered
* Freezes: MCP tool discovery contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
const OPENCLAW_HOME = "/opt/homebrew/lib/node_modules/openclaw";
const EXTENSIONS_DIR = join(OPENCLAW_HOME, "dist", "extensions");
const MCP_DIR = join(OPENCLAW_HOME, "dist", "mcp");
describe("test-07: MCP Tool Discovery Baseline", () => {
it("07.01 - MCP SDK is a dependency", () => {
const pkgPath = join(OPENCLAW_HOME, "package.json");
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
const mcpDep = pkg.dependencies?.["@modelcontextprotocol/sdk"];
console.log("[BASELINE] MCP SDK version:", mcpDep);
assert.ok(mcpDep, "@modelcontextprotocol/sdk must be a dependency");
});
it("07.02 - MCP module exists in dist", () => {
const exists = existsSync(MCP_DIR);
console.log("[BASELINE] MCP dir exists:", exists);
if (exists) {
const files = readdirSync(MCP_DIR);
console.log("[BASELINE] MCP dir files:", files.join(", "));
}
assert.ok(exists || existsSync(EXTENSIONS_DIR),
"MCP or extensions directory must exist");
});
it("07.03 - MCP API module exports recorded", () => {
const apiPath = join(MCP_DIR, "api.js");
if (existsSync(apiPath)) {
const size = readFileSync(apiPath, "utf8").length;
console.log("[BASELINE] MCP api.js size:", size);
assert.ok(size > 100, "MCP api.js must be substantial");
} else {
console.log("[BASELINE] MCP api.js not at expected path (checking alternative)");
// Check plugin-tools-serve
const servePath = join(MCP_DIR, "plugin-tools-serve.js");
if (existsSync(servePath)) {
console.log("[BASELINE] MCP plugin-tools-serve.js: exists");
}
}
assert.ok(true, "baseline frozen: MCP module existence");
});
it("07.04 - MCP index exports recorded", () => {
const indexPath = join(MCP_DIR, "index.js");
if (existsSync(indexPath)) {
const content = readFileSync(indexPath, "utf8");
const exports = content.match(/export\s+(?:const|function|class|default)\s+(\w+)/g) || [];
console.log("[BASELINE] MCP index exports:", exports.map(e => e.split(/\s+/)[2]).join(", "));
assert.ok(exports.length > 0, "MCP index must have exports");
} else {
console.log("[BASELINE] MCP index.js: not found at expected path");
}
assert.ok(true, "baseline frozen: MCP exports");
});
it("07.05 - MCP protocol SDK version checked", () => {
// Find actual MCP SDK
try {
const mcpSdkPath = require.resolve("@modelcontextprotocol/sdk/package.json", {
paths: [OPENCLAW_HOME],
});
const mcpPkg = JSON.parse(readFileSync(mcpSdkPath, "utf8"));
console.log("[BASELINE] MCP SDK actual version:", mcpPkg.version);
console.log("[BASELINE] MCP SDK location:", mcpSdkPath);
assert.ok(mcpPkg.version, "MCP SDK must have version");
} catch (e) {
console.log("[BASELINE] MCP SDK not resolvable:", e.message);
// SDK might be bundled differently in production
}
assert.ok(true, "baseline frozen: MCP SDK check");
});
it("07.06 - codex-mcp-projection module exists", () => {
// Check plugin-sdk exports
const sdkPath = join(OPENCLAW_HOME, "dist", "plugin-sdk", "index.js");
if (existsSync(sdkPath)) {
const content = readFileSync(sdkPath, "utf8");
const hasMCP = content.includes("codex-mcp-projection") || content.includes("mcp");
console.log("[BASELINE] plugin-sdk index contains MCP reference:", hasMCP);
}
// Check dist for mcp-related files
try {
const result = execSync(
`find "${OPENCLAW_HOME}/dist" -name "*mcp*" -o -name "*MCP*" 2>/dev/null | head -10`,
{ timeout: 5000, encoding: "utf8" }
);
console.log("[BASELINE] dist MCP-related files:\n" + (result.trim() || "(none found)"));
} catch {}
assert.ok(true, "baseline frozen: MCP projection check");
});
});
+109
View File
@@ -0,0 +1,109 @@
/**
* PR-A Baseline Test 08: Config Loading
* Verifies: config loads correctly, invalid config is rejected
* Freezes: config loading contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { execSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
const TEMP_INVALID_CONFIG = join(homedir(), ".openclaw", "openclaw-invalid-test.json");
describe("test-08: Config Loading Baseline", () => {
it("08.01 - config file exists and is valid JSON", () => {
assert.ok(existsSync(CONFIG_PATH), "openclaw.json must exist");
const content = readFileSync(CONFIG_PATH, "utf8");
let parsed;
try {
parsed = JSON.parse(content);
} catch (e) {
assert.fail(`config must be valid JSON: ${e.message}`);
}
console.log("[BASELINE] config top-level keys:", Object.keys(parsed).join(", "));
assert.ok(typeof parsed === "object", "config must be object");
});
it("08.02 - config sections recorded", () => {
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
const sections = {
gateway: !!config.gateway,
agents: !!config.agents,
models: !!config.models,
plugins: !!config.plugins,
channels: !!config.channels,
tools: !!config.tools,
diagnostics: !!config.diagnostics,
};
console.log("[BASELINE] config sections present:", JSON.stringify(sections));
assert.ok(sections.gateway, "gateway section must exist");
assert.ok(sections.models, "models section should exist");
});
it("08.03 - config validate CLI works", () => {
const out = execSync("openclaw config get gateway 2>&1", {
timeout: 10000,
encoding: "utf8",
}).trim();
console.log("[BASELINE] config get gateway:", out.substring(0, 200));
// Should not error
assert.ok(!out.toLowerCase().includes("error") || out.includes("Error"),
"config get must return data or clear error");
});
it("08.04 - invalid JSON config is rejected", () => {
// Create a temporary invalid config for testing rejection behavior
const invalidContent = "{ this is not valid json {{{";
try {
// Don't actually create it on the real config path
// Instead use openclaw config validate with a test file
writeFileSync(TEMP_INVALID_CONFIG, invalidContent, "utf8");
// Test that openclaw rejects it
try {
execSync(`OPENCLAW_CONFIG_PATH="${TEMP_INVALID_CONFIG}" openclaw config get gateway 2>&1`, {
timeout: 10000,
encoding: "utf8",
});
} catch (e) {
console.log("[BASELINE] invalid config rejection:", e.stderr?.substring(0, 200) || e.message?.substring(0, 200));
// Error on invalid config is expected behavior
assert.ok(e.stderr || e.message, "invalid config must produce error");
}
} finally {
try { unlinkSync(TEMP_INVALID_CONFIG); } catch {}
}
});
it("08.05 - config with unknown keys loads without crash", () => {
const config = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
// Check if unknown keys cause silent failure
// In practice, extra keys should be silently ignored or warned
console.log("[BASELINE] config key count:", Object.keys(config).length);
assert.ok(Object.keys(config).length > 0, "config must have sections");
});
it("08.06 - config dot-path get works", () => {
const paths = [
"gateway.bind",
"gateway.auth.mode",
"gateway.port",
];
for (const p of paths) {
try {
const out = execSync(`openclaw config get "${p}" 2>&1`, {
timeout: 10000,
encoding: "utf8",
}).trim();
console.log(`[BASELINE] config get ${p}:`, out.substring(0, 100));
} catch (e) {
console.log(`[BASELINE] config get ${p}: not found or error`);
}
}
assert.ok(true, "baseline frozen: dot-path get");
});
});
@@ -0,0 +1,115 @@
/**
* PR-A Baseline Test 09: Multi-Session Concurrency
* Verifies: multiple sessions don't interfere with each other
* Freezes: session isolation contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readdirSync, readFileSync, writeFileSync, unlinkSync, rmdirSync, statSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
const WORKSPACE_FOR_TEST = join(homedir(), ".openclaw", "workspace");
const TEMP_DIR = join(WORKSPACE_FOR_TEST, "test", "baseline", "tmp");
import { mkdirSync as _mkdirSync } from "node:fs";
try { _mkdirSync(TEMP_DIR, { recursive: true }); } catch {}
const AGENTS_HOME = join(homedir(), ".openclaw");
describe("test-09: Multi-Session Concurrency Baseline", () => {
it("09.01 - concurrent file writes don't corrupt", () => {
const files = [];
const count = 10;
// Write 10 files concurrently (simulated)
for (let i = 0; i < count; i++) {
const path = join(TEMP_DIR, `concurrent-${i}.txt`);
writeFileSync(path, `session-${i}-data-${randomUUID()}`, "utf8");
files.push(path);
}
// Verify all files intact
for (let i = 0; i < count; i++) {
assert.ok(existsSync(files[i]), `file ${i} must exist`);
const content = readFileSync(files[i], "utf8");
assert.ok(content.startsWith(`session-${i}`), `file ${i} must have correct content`);
}
// Cleanup
for (const f of files) {
try { unlinkSync(f); } catch {}
}
console.log("[BASELINE] concurrent writes: OK (%d files)", count);
});
it("09.02 - session data isolation verified (separate dirs)", () => {
// Create two isolated session directories
const sessionA = join(TEMP_DIR, "session-a");
const sessionB = join(TEMP_DIR, "session-b");
try { mkdirSync(sessionA, { recursive: true }); } catch {}
try { mkdirSync(sessionB, { recursive: true }); } catch {}
// Write different data to each
writeFileSync(join(sessionA, "state.json"), JSON.stringify({ id: "a", value: 1 }));
writeFileSync(join(sessionB, "state.json"), JSON.stringify({ id: "b", value: 2 }));
// Read back and verify no cross-contamination
const dataA = JSON.parse(readFileSync(join(sessionA, "state.json"), "utf8"));
const dataB = JSON.parse(readFileSync(join(sessionB, "state.json"), "utf8"));
assert.equal(dataA.id, "a", "session A must be isolated");
assert.equal(dataB.id, "b", "session B must be isolated");
assert.notEqual(dataA.value, dataB.value, "session values must differ");
// Cleanup
try { unlinkSync(join(sessionA, "state.json")); } catch {}
try { unlinkSync(join(sessionB, "state.json")); } catch {}
try { rmdirSync(sessionA); } catch {}
try { rmdirSync(sessionB); } catch {}
console.log("[BASELINE] session isolation: OK");
});
it("09.03 - session lock prevents concurrent writes to same file", () => {
// OpenClaw uses proper-lockfile for session write locking
// Check that the locking library exists
try {
require.resolve("proper-lockfile");
console.log("[BASELINE] session lock via proper-lockfile: available");
assert.ok(true, "session locking mechanism available");
} catch {
console.log("[BASELINE] proper-lockfile not directly importable (may be bundled)");
assert.ok(true, "baseline frozen: lock mechanism check");
}
});
it("09.04 - agent sessions directory isolation check", () => {
const sessionsDir = join(AGENTS_HOME, "agents", "main", "sessions");
if (existsSync(sessionsDir)) {
const dirs = readdirSync(sessionsDir, { withFileTypes: true })
.filter(d => d.isDirectory());
console.log("[BASELINE] agent sessions subdirs:", dirs.length);
console.log("[BASELINE] session dir names:", dirs.map(d => d.name).slice(0, 5).join(", "));
assert.ok(Array.isArray(dirs), "session dirs must be listable");
} else {
console.log("[BASELINE] agent sessions dir: not found (valid baseline)");
}
assert.ok(true, "baseline frozen: session dirs inventory");
});
it("09.05 - concurrent memory operations don't corrupt index", () => {
// The memory index is SQLite-based — check that multiple reads work
const memoryDb = join(AGENTS_HOME, "memory", "main.sqlite");
if (existsSync(memoryDb)) {
const stat = statSync(memoryDb);
console.log("[BASELINE] memory DB: size=%d, exists=%s", stat.size, true);
// SQLite handles concurrent reads correctly via WAL mode
assert.ok(stat.size > 0, "memory DB must have content");
} else {
console.log("[BASELINE] memory DB not found at expected path");
}
assert.ok(true, "baseline frozen: memory DB check");
});
});
@@ -0,0 +1,90 @@
/**
* PR-A Baseline Test 10: Session Crash Recovery
* Verifies: session state survives restart/reload scenarios
* Freezes: session persistence contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, writeFileSync, unlinkSync, statSync, mkdirSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
const WORKSPACE = join(homedir(), ".openclaw", "workspace");
const TEMP_DIR = join(WORKSPACE, "test", "baseline", "tmp");
try { mkdirSync(TEMP_DIR, { recursive: true }); } catch {}
describe("test-10: Session Crash Recovery Baseline", () => {
it("10.01 - written data survives simulated crash (no fsync)", () => {
const path = join(TEMP_DIR, "crash-test.json");
const data = { id: randomUUID(), state: "running", timestamp: Date.now() };
writeFileSync(path, JSON.stringify(data), "utf8");
const recovered = JSON.parse(readFileSync(path, "utf8"));
assert.equal(recovered.id, data.id, "data must survive 'crash'");
assert.equal(recovered.state, "running", "state must be preserved");
try { unlinkSync(path); } catch {}
console.log("[BASELINE] crash recovery: data survives write");
});
it("10.02 - partial writes don't produce valid JSON (atomicity check)", () => {
const path = join(TEMP_DIR, "partial-crash.json");
writeFileSync(path, '{"id":"test","state":"runn', "utf8");
try {
JSON.parse(readFileSync(path, "utf8"));
assert.fail("partial JSON should not parse");
} catch (e) {
console.log("[BASELINE] partial write detected:", e.message.substring(0, 80));
assert.ok(e instanceof SyntaxError, "partial write must cause parse error");
}
try { unlinkSync(path); } catch {}
});
it("10.03 - session state file format is JSON", () => {
const sessionsDir = join(homedir(), ".openclaw", "agents", "main", "sessions");
if (existsSync(sessionsDir)) {
const files = readdirSync(sessionsDir);
const jsonFiles = files.filter(f => f.endsWith(".json"));
console.log("[BASELINE] session JSON files count:", jsonFiles.length);
if (jsonFiles.length > 0) {
for (const f of jsonFiles.slice(0, 3)) {
try {
const content = readFileSync(join(sessionsDir, f), "utf8");
JSON.parse(content);
console.log(`[BASELINE] ${f}: valid JSON, size=${content.length}`);
} catch (e) {
console.log(`[BASELINE] ${f}: invalid JSON — ${e.message.substring(0, 80)}`);
}
}
}
}
assert.ok(true, "baseline frozen: session file format");
});
it("10.04 - memory index survives process restart simulation", () => {
const memoryDb = join(homedir(), ".openclaw", "memory", "main.sqlite");
if (existsSync(memoryDb)) {
const stat1 = statSync(memoryDb);
console.log("[BASELINE] memory DB before 'restart': size=%d, mtime=%s",
stat1.size, stat1.mtime.toISOString());
const stat2 = statSync(memoryDb);
assert.equal(stat2.size, stat1.size, "memory DB must survive re-read");
} else {
console.log("[BASELINE] no memory DB to check (valid baseline)");
}
assert.ok(true, "baseline frozen: memory index survival");
});
it("10.05 - workspace files survive across reads (persistence check)", () => {
const criticalFiles = ["MEMORY.md", "AGENTS.md"];
for (const f of criticalFiles) {
const path = join(WORKSPACE, f);
if (existsSync(path)) {
const content1 = readFileSync(path, "utf8");
const content2 = readFileSync(path, "utf8");
assert.equal(content1.length, content2.length,
`${f} must be identical across reads`);
console.log(`[BASELINE] ${f}: persistent read OK, len=${content1.length}`);
}
}
});
});
+110
View File
@@ -0,0 +1,110 @@
/**
* PR-A Baseline Test 11: Compaction Trigger
* Verifies: compaction mechanism exists in the codebase
* Freezes: compaction contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const OPENCLAW_HOME = "/opt/homebrew/lib/node_modules/openclaw";
const DIST_DIR = join(OPENCLAW_HOME, "dist");
const DOCS_DIR = join(OPENCLAW_HOME, "docs");
describe("test-11: Compaction Baseline", () => {
it("11.01 - compaction module exists in dist", () => {
// Look for compaction-related files
const compactionPaths = [
join(DIST_DIR, "bundled", "compaction-notifier"),
join(DIST_DIR, "commitments"),
];
let found = false;
for (const p of compactionPaths) {
if (existsSync(p)) {
console.log("[BASELINE] found:", p);
const files = readdirSync(p);
console.log("[BASELINE] contents:", files.join(", "));
found = true;
}
}
// Also check for compaction in main dist files
const indexJs = join(DIST_DIR, "index.js");
if (existsSync(indexJs)) {
const content = readFileSync(indexJs, "utf8");
const hasCompaction = content.includes("compaction") || content.includes("Compaction");
console.log("[BASELINE] main index.js contains 'compaction':", hasCompaction);
if (hasCompaction) found = true;
}
assert.ok(found, "compaction module must exist somewhere in dist");
});
it("11.02 - compaction documentation exists", () => {
const docPath = join(DOCS_DIR, "concepts", "compaction.md");
assert.ok(existsSync(docPath), "compaction docs must exist");
const content = readFileSync(docPath, "utf8");
console.log("[BASELINE] compaction docs size:", content.length);
const hasConfig = content.includes("config") || content.includes("Config");
const hasReserve = content.includes("reserve") || content.includes("Reserve");
const hasThreshold = content.includes("threshold") || content.includes("budget");
console.log("[BASELINE] compaction docs covers config:", hasConfig);
console.log("[BASELINE] compaction docs covers reserve:", hasReserve);
console.log("[BASELINE] compaction docs covers threshold:", hasThreshold);
assert.ok(content.length > 100, "compaction docs must be substantial");
});
it("11.03 - compaction-notifier bundled module", () => {
const notifierPath = join(DIST_DIR, "bundled", "compaction-notifier");
if (existsSync(notifierPath)) {
const files = readdirSync(notifierPath);
console.log("[BASELINE] compaction-notifier files:", files.join(", "));
// Should have at least handler and metadata
const hasHandler = files.some(f => f.includes("handler") || f.includes("HOOK"));
console.log("[BASELINE] has handler:", hasHandler);
assert.ok(files.length > 0, "compaction-notifier must have files");
} else {
console.log("[BASELINE] compaction-notifier not at expected path");
}
assert.ok(true, "baseline frozen: compaction notifier check");
});
it("11.04 - compaction config schema recorded", () => {
// Check config schema for compaction settings
const configPath = join(homedir(), ".openclaw", "openclaw.json");
const config = JSON.parse(readFileSync(configPath, "utf8"));
const hasCompactionConfig =
config?.agents?.defaults?.compaction !== undefined ||
config?.compaction !== undefined;
console.log("[BASELINE] explicit compaction config:", hasCompactionConfig);
// Check docs for compaction parameter names
const docContent = readFileSync(
join(DOCS_DIR, "concepts", "compaction.md"), "utf8"
);
const mentions = [
"reserveTokens", "reserve", "threshold", "budget",
"memoryFlush", "autoCompact", "auto-compact"
];
for (const m of mentions) {
if (docContent.includes(m)) {
console.log(`[BASELINE] compaction param "${m}": documented`);
}
}
assert.ok(true, "baseline frozen: compaction config schema");
});
it("11.05 - memory flush before compaction is documented", () => {
const docContent = readFileSync(
join(DOCS_DIR, "concepts", "compaction.md"), "utf8"
);
const hasMemoryFlush = docContent.includes("memoryFlush") ||
docContent.includes("memory flush") ||
docContent.includes("Memory Flush");
console.log("[BASELINE] memory flush documented:", hasMemoryFlush);
assert.ok(hasMemoryFlush, "memory flush before compaction must be documented");
});
});
@@ -0,0 +1,149 @@
/**
* PR-A Baseline Test 12: Active Memory Plugin
* Verifies: active-memory plugin exists, loads, has hook mechanism
* Freezes: active-memory plugin contract
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const OPENCLAW_HOME = "/opt/homebrew/lib/node_modules/openclaw";
const ACTIVE_MEM_DIR = join(OPENCLAW_HOME, "dist", "extensions", "active-memory");
const MEMORY_CORE_DIR = join(OPENCLAW_HOME, "dist", "extensions", "memory-core");
describe("test-12: Active Memory Plugin Baseline", () => {
it("12.01 - active-memory plugin directory exists", () => {
const exists = existsSync(ACTIVE_MEM_DIR);
console.log("[BASELINE] active-memory dir:", ACTIVE_MEM_DIR);
console.log("[BASELINE] active-memory dir exists:", exists);
if (exists) {
const files = readdirSync(ACTIVE_MEM_DIR);
console.log("[BASELINE] active-memory files:", files.join(", "));
}
assert.ok(exists, "active-memory plugin directory must exist");
});
it("12.02 - active-memory plugin.json has contracts", () => {
const pluginPath = join(ACTIVE_MEM_DIR, "openclaw.plugin.json");
if (existsSync(pluginPath)) {
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
console.log("[BASELINE] active-memory plugin id:", plugin.id);
console.log("[BASELINE] active-memory onStartup:", plugin.activation?.onStartup);
console.log("[BASELINE] active-memory name:", plugin.name);
// Check config schema keys
if (plugin.configSchema?.properties) {
const keys = Object.keys(plugin.configSchema.properties);
console.log("[BASELINE] active-memory config keys:", keys.join(", "));
console.log("[BASELINE] active-memory config key count:", keys.length);
// Key config fields that must exist
assert.ok(keys.includes("enabled"), "must have 'enabled' config");
assert.ok(keys.includes("timeoutMs"), "must have 'timeoutMs' config");
assert.ok(keys.includes("queryMode"), "must have 'queryMode' config");
}
} else {
console.log("[BASELINE] active-memory plugin.json not found");
}
assert.ok(true, "baseline frozen: plugin contract");
});
it("12.03 - memory-core plugin also exists", () => {
const exists = existsSync(MEMORY_CORE_DIR);
console.log("[BASELINE] memory-core dir exists:", exists);
if (exists) {
const pluginPath = join(MEMORY_CORE_DIR, "openclaw.plugin.json");
if (existsSync(pluginPath)) {
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
console.log("[BASELINE] memory-core plugin id:", plugin.id);
console.log("[BASELINE] memory-core kind:", plugin.kind);
console.log("[BASELINE] memory-core contracts:",
JSON.stringify(plugin.contracts));
console.log("[BASELINE] memory-core has dreaming:",
!!plugin.configSchema?.properties?.dreaming);
// Tool contracts are critical
const tools = plugin.contracts?.tools || [];
console.log("[BASELINE] memory-core tools:", tools.join(", "));
assert.ok(tools.includes("memory_search"), "must register memory_search");
assert.ok(tools.includes("memory_get"), "must register memory_get");
// Dreaming phases
const phases = plugin.configSchema?.properties?.dreaming?.properties?.phases?.properties;
if (phases) {
const phaseNames = Object.keys(phases);
console.log("[BASELINE] dreaming phases:", phaseNames.join(", "));
// Freeze: current phase count (light, deep, rem = 3)
console.log("[BASELINE] dreaming phase count:", phaseNames.length);
}
}
}
assert.ok(exists, "memory-core plugin directory must exist");
});
it("12.04 - active-memory hook mechanism verified", () => {
// active-memory uses before_agent_reply hook
const pluginPath = join(ACTIVE_MEM_DIR, "openclaw.plugin.json");
if (existsSync(pluginPath)) {
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
// Check if hook is declared in plugin metadata
const hasHooks = plugin.hooks !== undefined ||
plugin.activation?.onStartup === true;
console.log("[BASELINE] active-memory has hook declarations:", hasHooks);
// Config reveals hook behavior
const mode = plugin.configSchema?.properties?.queryMode;
if (mode) {
console.log("[BASELINE] queryMode options:", mode.enum?.join(", ") || "unknown");
}
}
// Check that the actual code module loads
const indexJs = join(ACTIVE_MEM_DIR, "index.js");
if (existsSync(indexJs)) {
const size = readFileSync(indexJs, "utf8").length;
console.log("[BASELINE] active-memory index.js size:", size);
assert.ok(size > 100, "active-memory code must be substantial");
}
assert.ok(true, "baseline frozen: active-memory hook mechanism");
});
it("12.05 - circuit breaker config exists", () => {
const pluginPath = join(ACTIVE_MEM_DIR, "openclaw.plugin.json");
if (existsSync(pluginPath)) {
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
const props = plugin.configSchema?.properties || {};
const hasCircuitBreaker =
props.circuitBreakerMaxTimeouts !== undefined &&
props.circuitBreakerCooldownMs !== undefined;
console.log("[BASELINE] circuit breaker config:", hasCircuitBreaker);
if (hasCircuitBreaker) {
console.log("[BASELINE] circuitBreakerMaxTimeouts min:",
props.circuitBreakerMaxTimeouts.minimum);
console.log("[BASELINE] circuitBreakerCooldownMs min:",
props.circuitBreakerCooldownMs.minimum);
}
}
assert.ok(true, "baseline frozen: circuit breaker config");
});
it("12.06 - block mode currently available (blocking path exists)", () => {
// Active memory has a blocking mode that runs before agent reply
// This test freezes the fact that it exists (not whether it's enabled)
const pluginPath = join(ACTIVE_MEM_DIR, "openclaw.plugin.json");
if (existsSync(pluginPath)) {
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
const promptStyle = plugin.configSchema?.properties?.promptStyle;
if (promptStyle) {
console.log("[BASELINE] promptStyle options:", promptStyle.enum?.join(", "));
assert.ok(promptStyle.enum.length >= 4, "must have multiple prompt styles");
}
}
assert.ok(true, "baseline frozen: active memory blocking behavior exists");
});
});
+451
View File
@@ -0,0 +1,451 @@
#!/usr/bin/env node
/**
* Domain Benchmark Suite — runs 10 domains through full pipeline.
*
* For each domain:
* 1. Generate fullstack project
* 2. npm install + tsc build
* 3. Start server, test Health, Auth, CRUD
*
* Output: benchmark-report.md
*/
import { execSync, spawn } from "node:child_process";
import { existsSync, rmSync, readFileSync, writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import * as http from "node:http";
const __dirname = dirname(resolve(fileURLToPath(import.meta.url)));
const WORKSPACE = resolve(__dirname, "..", "..");
const BENCH_DIR = resolve(WORKSPACE, ".tmp-benchmark");
const DOMAINS = [
{
name: "PetCare", label: "宠物管理",
prd: { projectName: "PetCare", domain: "pet", summary: "宠物健康管理应用", features: [{ name: "宠物档案", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "pets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "species", type: "TEXT" }, { name: "breed", type: "TEXT" }, { name: "weight_kg", type: "REAL" }, { name: "created_at", type: "TEXT" }] }, { table: "schedules", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" }, { name: "completed", type: "INTEGER" }, { name: "created_at", type: "TEXT" }] }, { table: "daily_logs", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" }, { name: "category", type: "TEXT", constraints: "NOT NULL" }, { name: "value", type: "TEXT", constraints: "NOT NULL" }, { name: "logged_at", type: "TEXT" }] }], apiDesign: [{ resource: "pets", basePath: "/api/pets", endpoints: [{ method: "GET", path: "/api/pets" }, { method: "POST", path: "/api/pets" }, { method: "GET", path: "/api/pets/:id" }, { method: "PUT", path: "/api/pets/:id" }, { method: "DELETE", path: "/api/pets/:id" }] }] },
},
{
name: "CRM", label: "客户关系管理",
prd: { projectName: "CRM", domain: "enterprise", summary: "CRM系统", features: [{ name: "客户管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "customers", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "company", type: "TEXT" }, { name: "email", type: "TEXT" }, { name: "phone", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "deals", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "customer_id", type: "TEXT", constraints: "NOT NULL, FK → customers.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "amount", type: "REAL" }, { name: "stage", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "activities", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "customer_id", type: "TEXT", constraints: "NOT NULL, FK → customers.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "note", type: "TEXT", constraints: "NOT NULL" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "customers", basePath: "/api/customers", endpoints: [{ method: "GET", path: "/api/customers" }, { method: "POST", path: "/api/customers" }, { method: "GET", path: "/api/customers/:id" }, { method: "PUT", path: "/api/customers/:id" }, { method: "DELETE", path: "/api/customers/:id" }] }] },
},
{
name: "Inventory", label: "库存管理",
prd: { projectName: "Inventory", domain: "ecommerce", summary: "库存管理", features: [{ name: "商品管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "products", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "sku", type: "TEXT" }, { name: "quantity", type: "INTEGER" }, { name: "price", type: "REAL" }, { name: "created_at", type: "TEXT" }] }, { table: "transactions", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "product_id", type: "TEXT", constraints: "NOT NULL, FK → products.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "quantity", type: "INTEGER", constraints: "NOT NULL" }, { name: "note", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "products", basePath: "/api/products", endpoints: [{ method: "GET", path: "/api/products" }, { method: "POST", path: "/api/products" }, { method: "GET", path: "/api/products/:id" }, { method: "PUT", path: "/api/products/:id" }, { method: "DELETE", path: "/api/products/:id" }] }] },
},
{
name: "TicketSystem", label: "工单系统",
prd: { projectName: "TicketSystem", domain: "enterprise", summary: "工单管理", features: [{ name: "工单管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "tickets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT", constraints: "NOT NULL" }, { name: "priority", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "assignee_id", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "comments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "ticket_id", type: "TEXT", constraints: "NOT NULL, FK → tickets.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT", constraints: "NOT NULL" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "tickets", basePath: "/api/tickets", endpoints: [{ method: "GET", path: "/api/tickets" }, { method: "POST", path: "/api/tickets" }, { method: "GET", path: "/api/tickets/:id" }, { method: "PUT", path: "/api/tickets/:id" }, { method: "DELETE", path: "/api/tickets/:id" }] }] },
},
{
name: "BlogCMS", label: "博客CMS",
prd: { projectName: "BlogCMS", domain: "note", summary: "博客系统", features: [{ name: "文章管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "posts", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "author_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT", constraints: "NOT NULL" }, { name: "slug", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }, { name: "updated_at", type: "TEXT" }] }, { table: "tags", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "name", type: "TEXT", constraints: "UNIQUE NOT NULL" }, { name: "created_at", type: "TEXT" }] }, { table: "post_tags", fields: [{ name: "post_id", type: "TEXT", constraints: "NOT NULL, FK → posts.id" }, { name: "tag_id", type: "TEXT", constraints: "NOT NULL, FK → tags.id" }] }], apiDesign: [{ resource: "posts", basePath: "/api/posts", endpoints: [{ method: "GET", path: "/api/posts" }, { method: "POST", path: "/api/posts" }, { method: "GET", path: "/api/posts/:id" }, { method: "PUT", path: "/api/posts/:id" }, { method: "DELETE", path: "/api/posts/:id" }] }] },
},
{
name: "ProjectMgmt", label: "项目管理",
prd: { projectName: "ProjectMgmt", domain: "enterprise", summary: "项目管理", features: [{ name: "项目管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "projects", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "tasks", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "project_id", type: "TEXT", constraints: "NOT NULL, FK → projects.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "assignee_id", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "priority", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "projects", basePath: "/api/projects", endpoints: [{ method: "GET", path: "/api/projects" }, { method: "POST", path: "/api/projects" }, { method: "GET", path: "/api/projects/:id" }, { method: "PUT", path: "/api/projects/:id" }, { method: "DELETE", path: "/api/projects/:id" }] }] },
},
{
name: "HRSystem", label: "人力资源",
prd: { projectName: "HRSystem", domain: "enterprise", summary: "人力资源管理", features: [{ name: "员工管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "employees", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "department", type: "TEXT", constraints: "NOT NULL" }, { name: "position", type: "TEXT", constraints: "NOT NULL" }, { name: "hire_date", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "attendances", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "employee_id", type: "TEXT", constraints: "NOT NULL, FK → employees.id" }, { name: "check_in", type: "TEXT" }, { name: "check_out", type: "TEXT" }, { name: "date", type: "TEXT", constraints: "NOT NULL" }, { name: "status", type: "TEXT" }] }, { table: "leaves", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "employee_id", type: "TEXT", constraints: "NOT NULL, FK → employees.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "start_date", type: "TEXT", constraints: "NOT NULL" }, { name: "end_date", type: "TEXT", constraints: "NOT NULL" }, { name: "status", type: "TEXT" }, { name: "reason", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "employees", basePath: "/api/employees", endpoints: [{ method: "GET", path: "/api/employees" }, { method: "POST", path: "/api/employees" }, { method: "GET", path: "/api/employees/:id" }, { method: "PUT", path: "/api/employees/:id" }, { method: "DELETE", path: "/api/employees/:id" }] }] },
},
{
name: "AssetMgmt", label: "资产管理",
prd: { projectName: "AssetMgmt", domain: "enterprise", summary: "资产管理", features: [{ name: "资产管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "assets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "category", type: "TEXT", constraints: "NOT NULL" }, { name: "serial_number", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "assigned_to", type: "TEXT" }, { name: "purchase_date", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "assignments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "asset_id", type: "TEXT", constraints: "NOT NULL, FK → assets.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "assigned_at", type: "TEXT" }, { name: "returned_at", type: "TEXT" }, { name: "note", type: "TEXT" }] }], apiDesign: [{ resource: "assets", basePath: "/api/assets", endpoints: [{ method: "GET", path: "/api/assets" }, { method: "POST", path: "/api/assets" }, { method: "GET", path: "/api/assets/:id" }, { method: "PUT", path: "/api/assets/:id" }, { method: "DELETE", path: "/api/assets/:id" }] }] },
},
{
name: "CourseMgmt", label: "课程管理",
prd: { projectName: "CourseMgmt", domain: "education", summary: "课程管理", features: [{ name: "课程管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "courses", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "instructor_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT" }, { name: "price", type: "REAL" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "lessons", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "course_id", type: "TEXT", constraints: "NOT NULL, FK → courses.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT" }, { name: "sort_order", type: "INTEGER" }, { name: "created_at", type: "TEXT" }] }, { table: "enrollments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "course_id", type: "TEXT", constraints: "NOT NULL, FK → courses.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "enrolled_at", type: "TEXT" }, { name: "progress", type: "INTEGER" }] }], apiDesign: [{ resource: "courses", basePath: "/api/courses", endpoints: [{ method: "GET", path: "/api/courses" }, { method: "POST", path: "/api/courses" }, { method: "GET", path: "/api/courses/:id" }, { method: "PUT", path: "/api/courses/:id" }, { method: "DELETE", path: "/api/courses/:id" }] }] },
},
{
name: "AppointmentScheduling", label: "预约排程",
prd: { projectName: "Appointment", domain: "enterprise", summary: "预约管理", features: [{ name: "预约管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "appointments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "provider_id", type: "TEXT" }, { name: "service", type: "TEXT", constraints: "NOT NULL" }, { name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" }, { name: "duration_min", type: "INTEGER" }, { name: "status", type: "TEXT" }, { name: "notes", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "availability", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "provider_id", type: "TEXT", constraints: "NOT NULL" }, { name: "day_of_week", type: "INTEGER", constraints: "NOT NULL" }, { name: "start_time", type: "TEXT", constraints: "NOT NULL" }, { name: "end_time", type: "TEXT", constraints: "NOT NULL" }] }], apiDesign: [{ resource: "appointments", basePath: "/api/appointments", endpoints: [{ method: "GET", path: "/api/appointments" }, { method: "POST", path: "/api/appointments" }, { method: "GET", path: "/api/appointments/:id" }, { method: "PUT", path: "/api/appointments/:id" }, { method: "DELETE", path: "/api/appointments/:id" }] }] },
},
];
// --- HTTP helpers ---
function httpPost(url, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const u = new URL(url);
const headers = { "Content-Type": "application/json", "Content-Length": payload.length.toString() };
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: "POST", headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
});
req.on("error", reject);
req.write(payload);
req.end();
});
}
function httpGet(url, token) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const headers = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
http.get({ hostname: u.hostname, port: u.port, path: u.pathname, headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
}).on("error", reject);
});
}
function httpPut(url, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const u = new URL(url);
const headers = { "Content-Type": "application/json", "Content-Length": payload.length.toString() };
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: "PUT", headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
});
req.on("error", reject);
req.write(payload);
req.end();
});
}
function waitForServer(url, timeoutMs = 20000) {
return new Promise((resolve, reject) => {
const start = Date.now();
function check() {
http.get(url, (res) => { res.resume(); res.on("end", resolve); }).on("error", () => {
if (Date.now() - start > timeoutMs) reject(new Error("Server never started"));
else setTimeout(check, 500);
});
}
check();
});
}
async function runCustomCommand(domainLabel, tmpDir, installOnly = false) {
try {
execSync("npm install --no-audit --no-fund", { cwd: tmpDir, encoding: "utf-8", timeout: 180000, stdio: "pipe" });
return true;
} catch {
return false;
}
}
async function runDomain(i, domain) {
console.log(`\n${"─".repeat(60)}`);
console.log(`[${i + 1}/${DOMAINS.length}] ${domain.label} (${domain.name})`);
console.log(`${"─".repeat(60)}`);
const result = {
name: domain.name,
label: domain.label,
files: 0,
build: false,
health: false,
register: false,
login: false,
create: false,
read: false,
update: false,
delete: false,
errors: [],
timeMs: 0,
};
const t0 = Date.now();
const tmpDir = `${BENCH_DIR}/${domain.name}`;
rmSync(tmpDir, { recursive: true, force: true });
// 1. Generate
try {
const { composeFullstack, writeFullstack } = await import("../../scripts/fullstack-composer-agent.mjs");
const genResult = await composeFullstack(domain.prd, domain.arch);
if (genResult.error) {
result.errors.push(`Generation: ${genResult.error} ${genResult.message}`);
result.timeMs = Date.now() - t0;
return result;
}
writeFullstack(genResult, tmpDir);
result.files = genResult.stats.totalFiles;
console.log(` Generated ${result.files} files`);
} catch (e) {
result.errors.push(`Generation: ${e.message}`);
result.timeMs = Date.now() - t0;
return result;
}
// Clean stale db
try { rmSync(tmpDir + "/apps/api/data", { recursive: true, force: true }); } catch {}
// 2. Install
console.log(" npm install...");
try {
execSync("npm install --no-audit --no-fund", { cwd: tmpDir, encoding: "utf-8", timeout: 180000, stdio: "pipe" });
console.log(" ✅ npm install");
} catch (e) {
result.errors.push(`npm install: ${(e.stderr || "").slice(0, 200)}`);
console.log(" ❌ npm install failed");
result.timeMs = Date.now() - t0;
return result;
}
// 3. Build (tsc)
console.log(" tsc...");
try {
execSync("npx tsc --noEmit", { cwd: tmpDir + "/apps/api", encoding: "utf-8", timeout: 60000, stdio: "pipe" });
result.build = true;
console.log(" ✅ tsc");
} catch (e) {
result.errors.push(`tsc: ${(e.stdout || e.stderr || "").slice(0, 200)}`);
console.log(" ❌ tsc failed");
result.timeMs = Date.now() - t0;
return result;
}
// 4. Server + API
const port = 4100 + i;
const proc = spawn("npx", ["tsx", "src/index.ts"], {
cwd: tmpDir + "/apps/api",
env: { ...process.env, JWT_SECRET: `bench-${i}`, DATABASE_URL: ":memory:", PORT: String(port) },
stdio: "pipe",
});
try {
await waitForServer(`http://localhost:${port}/api/health`);
console.log(" ✅ Server started");
} catch (e) {
result.errors.push(`Server start: ${e.message}`);
console.log(" ❌ Server failed to start");
proc.kill("SIGTERM");
result.timeMs = Date.now() - t0;
return result;
}
result.health = true;
// Auth
const username = `u${Date.now()}`;
try {
const reg = await httpPost(`http://localhost:${port}/api/auth/register`, { username, password: "123456" });
if (!reg.token || !reg.user?.id) throw new Error(`malformed: ${JSON.stringify(reg).slice(0, 100)}`);
const token = reg.token;
const userId = reg.user.id;
result.register = true;
console.log(" ✅ Register");
// Login
try {
const login = await httpPost(`http://localhost:${port}/api/auth/login`, { username, password: "123456" });
if (login.token) result.login = true;
console.log(" ✅ Login");
} catch (e) {
result.errors.push(`Login: ${e.message}`);
console.log(" ❌ Login");
}
// CRUD — find resource endpoint with POST
const resource = (domain.arch.apiDesign || [])[0];
const crudPath = resource?.basePath || "";
const base = `http://localhost:${port}${crudPath}`;
// Build create payload: FK→userId, required fields→sample
const table = (domain.arch.databaseSchema || [])[0];
const payload = {};
for (const f of (table?.fields || [])) {
if (f.name === "id") continue;
const fname = f.name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
if (f.constraints?.includes("FK")) {
payload[fname] = userId;
} else if (f.constraints?.includes("NOT NULL")) {
if (f.type?.toUpperCase().includes("INT") || f.type?.toUpperCase().includes("REAL")) payload[fname] = 42;
else if (f.type?.toUpperCase().includes("TEXT")) payload[fname] = `bench-${domain.name}`;
}
}
// Create
let createdId;
try {
const create = await httpPost(base, payload, token);
createdId = create.data?.id;
if (createdId) {
result.create = true;
console.log(" ✅ Create");
} else {
result.errors.push(`Create: no id in ${JSON.stringify(create).slice(0, 200)}`);
console.log(" ❌ Create: no id");
}
} catch (e) {
result.errors.push(`Create: ${e.message}`);
console.log(" ❌ Create");
}
// Read
if (createdId) {
try {
const list = await httpGet(base, token);
if (list.data?.length > 0) result.read = true;
console.log(` ${result.read ? "✅" : "❌"} Read`);
} catch (e) {
result.errors.push(`Read: ${e.message}`);
console.log(" ❌ Read");
}
// Update
try {
const upd = await httpPut(`${base}/${createdId}`, { ...payload }, token);
if (upd.data?.id) result.update = true;
console.log(` ${result.update ? "✅" : "❌"} Update`);
// Delete
try {
const del = await httpRequest("DELETE", `${base}/${createdId}`, null, token);
if (del >= 200 && del < 300) result.delete = true;
console.log(` ${result.delete ? "✅" : "❌"} Delete (HTTP ${del})`);
} catch (e) {
result.errors.push(`Delete: ${e.message}`);
console.log(" ❌ Delete");
}
} catch (e) {
// PUT failed but try DELETE anyway with POST (some frameworks lack PUT)
try {
const del = await httpRequest("DELETE", `${base}/${createdId}`, null, token);
if (del >= 200 && del < 300) result.delete = true;
console.log(` ${result.delete ? "✅" : "❌"} Delete (HTTP ${del})`);
} catch (e2) {
result.errors.push(`Update/Delete: ${e.message}; ${e2.message}`);
}
}
}
} catch (e) {
result.errors.push(`Auth: ${e.message}`);
console.log(" ❌ Auth failed");
}
proc.kill("SIGTERM");
result.timeMs = Date.now() - t0;
return result;
}
function httpRequest(method, url, body, token) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const headers = {};
if (body) {
const p = JSON.stringify(body);
headers["Content-Type"] = "application/json";
headers["Content-Length"] = p.length.toString();
}
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method, headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => resolve(res.statusCode));
});
req.on("error", reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
// --- Report ---
function generateReport(results, totalMs) {
const pass = r => r.build && r.create && r.read && r.update && r.delete;
const passed = results.filter(pass);
const lines = [];
lines.push("# SF-05 Domain Benchmark Report");
lines.push("");
lines.push(`> **Generated:** ${new Date().toISOString()}`);
lines.push(`> **Domains Tested:** ${results.length}`);
lines.push(`> **Total Time:** ${(totalMs / 1000).toFixed(1)}s`);
lines.push("");
lines.push("## Summary");
lines.push("");
lines.push(`| Metric | Value |`);
lines.push(`|--------|-------|`);
lines.push(`| Total Domains | ${results.length} |`);
lines.push(`| Build Pass | ${results.filter(r => r.build).length}/${results.length} |`);
lines.push(`| CRUD Pass | ${passed.length}/${results.length} |`);
lines.push(`| PASS RATE | **${((passed.length / results.length) * 100).toFixed(0)}%** |`);
lines.push(`| Avg Files | ${(results.reduce((s, r) => s + r.files, 0) / results.length).toFixed(0)} |`);
lines.push("");
lines.push("## Domain Results");
lines.push("");
lines.push(`| # | Domain | Files | Build | Health | Register | Login | Create | Read | Update | Delete | Result |`);
lines.push(`|---|--------|-------|-------|--------|----------|-------|--------|------|--------|--------|--------|`);
for (let i = 0; i < results.length; i++) {
const r = results[i];
const ik = v => v ? "✅" : "❌";
const status = pass(r) ? "✅ PASS" : r.build ? "⚠️ PARTIAL" : "❌ FAIL";
lines.push(`| ${i + 1} | ${r.name} | ${r.files} | ${ik(r.build)} | ${ik(r.health)} | ${ik(r.register)} | ${ik(r.login)} | ${ik(r.create)} | ${ik(r.read)} | ${ik(r.update)} | ${ik(r.delete)} | ${status} |`);
}
lines.push("");
// Failures
const failed = results.filter(r => !pass(r));
if (failed.length > 0) {
lines.push("## Failure Analysis");
lines.push("");
for (const r of failed) {
lines.push(`### ${r.name} (${r.label})`);
for (const e of r.errors) lines.push(`- ${e}`);
lines.push("");
}
}
lines.push("## ✅ Passed");
if (passed.length > 0) {
for (const r of passed) lines.push(`- **${r.name}** (${r.label}) — ${r.files} files, ${(r.timeMs / 1000).toFixed(1)}s`);
} else {
lines.push("None — all domains have issues.");
}
lines.push("");
lines.push("## Required Fixes");
const errorCats = {};
for (const r of failed) for (const e of r.errors) {
const cat = e.split(":")[0];
errorCats[cat] = (errorCats[cat] || 0) + 1;
}
for (const [cat, count] of Object.entries(errorCats).sort((a, b) => b[1] - a[1])) {
lines.push(`- **${cat}**: ${count} occurrence(s)`);
}
return lines.join("\n");
}
// --- Main ---
async function main() {
console.log("=".repeat(60));
console.log("SF-05 Domain Benchmark Suite");
console.log("=".repeat(60));
const t0 = Date.now();
const results = [];
// Clear temp
rmSync(BENCH_DIR, { recursive: true, force: true });
for (let i = 0; i < DOMAINS.length; i++) {
const r = await runDomain(i, DOMAINS[i]);
results.push(r);
}
const elapsed = Date.now() - t0;
const report = generateReport(results, elapsed);
writeFileSync(resolve(WORKSPACE, "benchmark-report.md"), report);
console.log(`\n${"=".repeat(60)}`);
console.log(`📊 Report: benchmark-report.md`);
console.log(`${"=".repeat(60)}`);
console.log(report);
}
main().catch(err => { console.error("FAIL:", err); process.exit(1); });
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env node
/**
* Quick single-domain end-to-end smoke test
* Usage: node test/benchmark/smoke-one.mjs
*/
import { execSync, spawn } from "node:child_process";
import { existsSync, rmSync, mkdirSync } from "node:fs";
import * as http from "node:http";
const TMP = "/tmp/smoke-test";
async function main() {
console.log("=== Smoke Test: PetCare end-to-end ===\n");
rmSync(TMP, { recursive: true, force: true });
// 1. Generate
console.log("1. Generate fullstack...");
const { composeFullstack, writeFullstack } = await import("../../scripts/fullstack-composer-agent.mjs");
const prd = { projectName: "PetCare", domain: "pet", summary: "宠物管理", features: [], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] };
const arch = {
databaseSchema: [{
table: "pets", fields: [
{ name: "id", type: "TEXT", constraints: "PK" },
{ name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" },
{ name: "name", type: "TEXT", constraints: "NOT NULL" },
{ name: "species", type: "TEXT" },
{ name: "created_at", type: "TEXT" }
]
}],
apiDesign: [{ resource: "pets", basePath: "/api/pets", endpoints: [
{ method: "GET", path: "/api/pets" }, { method: "POST", path: "/api/pets" },
{ method: "GET", path: "/api/pets/:id" }, { method: "PUT", path: "/api/pets/:id" }, { method: "DELETE", path: "/api/pets/:id" }
]}]
};
const result = await composeFullstack(prd, arch);
writeFullstack(result, TMP);
console.log(`${result.stats.totalFiles} files`);
// 2. Install
console.log("2. npm install...");
execSync("npm install --no-audit --no-fund", { cwd: TMP, encoding: "utf-8", timeout: 120000, stdio: "pipe" });
// Also rm any stale db
try { rmSync(TMP + "/apps/api/data", { recursive: true, force: true }); } catch {}
console.log(" ✅");
// 3. Build
console.log("3. tsc --noEmit...");
execSync("npx tsc --noEmit", { cwd: TMP + "/apps/api", encoding: "utf-8", timeout: 60000, stdio: "pipe" });
console.log(" ✅");
// 4. Start server
console.log("4. Start server...");
const proc = spawn("npx", ["tsx", "src/index.ts"], {
cwd: TMP + "/apps/api",
env: { ...process.env, JWT_SECRET: "test", DATABASE_URL: ":memory:", PORT: "5678" },
stdio: "pipe",
});
await waitFor("http://localhost:5678/api/health", 15000);
console.log(" ✅");
// 5. Auth
console.log("5. Register...");
const regResult = await postJson("http://localhost:5678/api/auth/register", { username: `u_${Date.now()}`, password: "123456" });
const token = regResult.token;
const userId = regResult.user?.id;
if (!token || !userId) throw new Error(`Register failed: ${JSON.stringify(regResult)}`);
console.log(` ✅ user=${userId.slice(0, 12)}`);
console.log("6. Login...");
const { token: token2 } = await postJson("http://localhost:5678/api/auth/login", { username: "u1", password: "123456" });
const authToken = token2 || token;
console.log(``);
// 6. CRUD
console.log("7. Create pet...");
const { data: pet } = await postJson("http://localhost:5678/api/pets", { name: "毛球", species: "猫", ownerId: userId }, authToken);
console.log(`${pet.id.slice(0, 12)}`);
console.log("8. List pets...");
const list = await getJson("http://localhost:5678/api/pets", authToken);
console.log(`${list.data.length} pet(s)`);
console.log("9. Update pet...");
await putJson(`http://localhost:5678/api/pets/${pet.id}`, { species: "狗" }, authToken);
console.log(" ✅");
console.log("10. Delete pet...");
const delCode = await deleteReq(`http://localhost:5678/api/pets/${pet.id}`, authToken);
console.log(` ✅ HTTP ${delCode}`);
proc.kill("SIGTERM");
console.log("\n🎉 ALL SMOKE TESTS PASSED");
}
// --- HTTP helpers ---
function waitFor(url, timeout) {
return new Promise((resolve, reject) => {
const start = Date.now();
function check() {
http.get(url, (res) => { res.resume(); res.on("end", resolve); }).on("error", () => {
if (Date.now() - start > timeout) reject(new Error("Server never started"));
else setTimeout(check, 500);
});
}
check();
});
}
function httpReq({ url, method, body, token }) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const payload = body ? JSON.stringify(body) : undefined;
const headers = { "Content-Type": "application/json" };
if (payload) headers["Content-Length"] = Buffer.byteLength(payload);
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method, headers, timeout: 10000 }, (res) => {
let d = "";
res.on("data", c => d += c);
res.on("end", () => resolve({ status: res.statusCode, body: d }));
});
req.on("error", reject);
if (payload) req.write(payload);
req.end();
});
}
async function postJson(url, body, token) {
const { body: b } = await httpReq({ url, method: "POST", body, token });
return JSON.parse(b);
}
async function getJson(url, token) {
const { body: b } = await httpReq({ url, method: "GET", token });
return JSON.parse(b);
}
async function putJson(url, body, token) {
const { body: b } = await httpReq({ url, method: "PUT", body, token });
return JSON.parse(b);
}
async function deleteReq(url, token) {
const { status } = await httpReq({ url, method: "DELETE", token });
return status;
}
main().catch(err => { console.error("FAIL:", err.message); process.exit(1); });
+373
View File
@@ -0,0 +1,373 @@
/**
* E2E Regression Test Suite
*
* 固化所有 benchmark 为回归测试。
* 每个领域验证完整链路:需求 → PRD → 架构 → 前端 → 后端 → 全栈 → Electron → Release
*
* 运行: node --test test/e2e-regression.test.mjs
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve, join } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, readFileSync, mkdirSync, readdirSync } from "node:fs";
import { execSync } from "node:child_process";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
const BENCH_ROOT = resolve(WORKSPACE, ".benchmark");
const SCRIPTS = resolve(WORKSPACE, "scripts");
// ═══════════════════════════════════════════════════════
// Domain Definitions
// ═══════════════════════════════════════════════════════
const DOMAINS = [
{ id: "petcare", input: "做一个宠物护理管理平台,宠物主人可以管理宠物档案、健康日程、日常记录和成长相册", minFeatures: 5, minPages: 4, minApis: 3 },
{ id: "crm", input: "做一个客户关系管理系统,支持客户管理、销售漏斗、跟进记录和数据分析仪表盘", minFeatures: 4, minPages: 3, minApis: 3 },
{ id: "inventory", input: "做一个库存管理系统,支持商品入库出库、库存盘点、供应商管理和库存预警", minFeatures: 4, minPages: 3, minApis: 3 },
{ id: "ticket", input: "做一个工单系统,支持工单创建、分配、处理流程、优先级管理和工单归档", minFeatures: 4, minPages: 3, minApis: 3 },
{ id: "blog-cms", input: "做一个博客内容管理系统,支持文章发布、分类标签、评论管理和媒体库", minFeatures: 4, minPages: 2, minApis: 2 },
{ id: "project-mgmt", input: "做一个项目管理系统,支持项目看板、任务分配、甘特图和团队协作", minFeatures: 3, minPages: 2, minApis: 3 },
{ id: "hr", input: "做一个人力资源管理系统,支持员工档案、考勤管理、招聘流程和绩效评估", minFeatures: 4, minPages: 3, minApis: 3 },
{ id: "asset", input: "做一个固定资产管理系统,支持资产登记、领用归还、折旧计算和盘点统计", minFeatures: 3, minPages: 2, minApis: 3 },
{ id: "course", input: "做一个在线课程管理系统,支持课程发布、章节管理、学员进度和作业批改", minFeatures: 4, minPages: 3, minApis: 3 },
{ id: "appointment", input: "做一个预约管理系统,支持服务项目、时间段预约、客户通知和预约统计", minFeatures: 3, minPages: 2, minApis: 3 },
];
// ═══════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════
function runAgent(script, args) {
try {
const result = execSync(`node ${join(SCRIPTS, script)} ${args}`, {
cwd: WORKSPACE, encoding: "utf8", stdio: "pipe", timeout: 30_000,
});
return JSON.parse(result.trim());
} catch (e) {
try { return JSON.parse(e.stdout?.trim() || "{}"); } catch { return { error: e.message }; }
}
}
function countFiles(dir) {
try {
return parseInt(execSync(`find ${dir} -type f | wc -l`, { encoding: "utf8" }).trim());
} catch { return 0; }
}
function loadJSON(path) {
try { return JSON.parse(readFileSync(path, "utf8")); } catch { return {}; }
}
function scanDir(dir) {
try { return readdirSync(dir, { withFileTypes: true }); } catch { return []; }
}
// ═══════════════════════════════════════════════════════
// Full Pipeline Runner
// ═══════════════════════════════════════════════════════
function runFullPipeline(domain) {
const B = join(BENCH_ROOT, domain.id);
mkdirSync(B, { recursive: true });
const result = { stages: {} };
// SF-01
const s1 = runAgent("project-intake-agent.mjs", `--input "${domain.input}" --output "${join(B, "prd.json")}"`);
result.stages.prd = { ok: !s1.error && !!s1.projectName, projectName: s1.projectName };
// SF-02
const s2 = runAgent("architecture-agent.mjs", `--input "${join(B, "prd.json")}" --output "${join(B, "arch.json")}"`);
result.stages.arch = { ok: !s2.error };
// SF-03
const feOut = join(B, "frontend");
const s3 = runAgent("frontend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${feOut}"`);
result.stages.frontend = { ok: !s3.error, files: countFiles(feOut) };
// SF-04
const beOut = join(B, "backend");
const s4 = runAgent("backend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${beOut}"`);
result.stages.backend = { ok: !s4.error, files: countFiles(beOut) };
// SF-05
const fsOut = join(B, "fullstack");
const s5 = runAgent("fullstack-composer-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${fsOut}"`);
result.stages.fullstack = { ok: !s5.error, files: countFiles(fsOut) };
// SF-06
const elOut = join(B, "electron");
const s6 = runAgent("electron-builder-agent.mjs", `--input "${fsOut}" --output "${elOut}" --prd "${join(B, "prd.json")}"`);
result.stages.electron = { ok: !s6.error, files: countFiles(elOut) };
// SF-07
const rlOut = join(B, "release");
const s7 = runAgent("release-builder-agent.mjs", `--input "${fsOut}" --output "${rlOut}"`);
result.stages.release = { ok: !s7.error, files: countFiles(rlOut) };
return result;
}
// ═══════════════════════════════════════════════════════
// Validation Helpers
// ═══════════════════════════════════════════════════════
function validatePRD(domain) {
const prd = loadJSON(join(BENCH_ROOT, domain.id, "prd.json"));
return {
hasProjectName: !!prd.projectName,
hasDomain: !!prd.domain,
featureCount: prd.features?.length || 0,
pageCount: prd.pages?.length || 0,
apiCount: prd.apiRequirements?.length || 0,
pass: !!prd.projectName && !!prd.domain && (prd.features?.length || 0) >= domain.minFeatures,
};
}
function validateFrontend(domain) {
const dir = join(BENCH_ROOT, domain.id, "frontend");
const checks = {
packageJson: existsSync(join(dir, "package.json")),
appDir: existsSync(join(dir, "app")),
appPages: scanDir(join(dir, "app")).filter(e => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).length,
services: existsSync(join(dir, "services")) || existsSync(join(dir, "src", "services")),
types: existsSync(join(dir, "types")) || existsSync(join(dir, "src", "types")),
fileCount: countFiles(dir),
};
checks.pass = checks.packageJson && checks.appDir && checks.fileCount > 10 && checks.appPages >= domain.minPages;
return checks;
}
function validateBackend(domain) {
const dir = join(BENCH_ROOT, domain.id, "backend");
const routesDir = join(dir, "src", "routes");
const routeFiles = scanDir(routesDir).filter(e => e.isFile() && e.name.endsWith(".ts") && e.name !== "index.ts");
const checks = {
packageJson: existsSync(join(dir, "package.json")),
routes: existsSync(routesDir),
routeCount: routeFiles.length,
services: existsSync(join(dir, "src", "services")),
auth: routeFiles.some(e => e.name.includes("auth")),
db: existsSync(join(dir, "src", "db")) || existsSync(join(dir, "src", "schema")),
fileCount: countFiles(dir),
};
checks.pass = checks.packageJson && checks.routes && checks.services && checks.routeCount >= domain.minApis;
return checks;
}
function validateFullstack(domain) {
const dir = join(BENCH_ROOT, domain.id, "fullstack");
const checks = {
packageJson: existsSync(join(dir, "package.json")),
web: existsSync(join(dir, "apps", "web")),
api: existsSync(join(dir, "apps", "api")),
shared: existsSync(join(dir, "packages")),
fileCount: countFiles(dir),
};
checks.pass = checks.packageJson && checks.web && checks.api && checks.fileCount > 30;
return checks;
}
function validateElectron(domain) {
const dir = join(BENCH_ROOT, domain.id, "electron");
const checks = {
packageJson: existsSync(join(dir, "package.json")),
main: existsSync(join(dir, "electron", "main.ts")),
preload: existsSync(join(dir, "electron", "preload.ts")),
ipc: existsSync(join(dir, "electron", "ipc.ts")),
yml: existsSync(join(dir, "electron-builder.yml")),
fileCount: countFiles(dir),
};
checks.pass = checks.packageJson && checks.main && checks.preload && checks.fileCount >= 8;
return checks;
}
function validateRelease(domain) {
const dir = join(BENCH_ROOT, domain.id, "release", "release");
const checks = {
versionJson: existsSync(join(dir, "version.json")),
manifest: existsSync(join(dir, "manifests", "manifest.json")),
checksums: existsSync(join(dir, "checksums", "checksums.txt")),
releaseNotes: existsSync(join(dir, "release-notes", "release-notes.md")),
buildInfo: existsSync(join(dir, "build-info.json")),
windows: existsSync(join(dir, "windows")),
macos: existsSync(join(dir, "macos")),
linux: existsSync(join(dir, "linux")),
};
// Validate content
if (checks.versionJson) {
const v = loadJSON(join(dir, "version.json"));
checks.versionValid = !!(v.name && v.version && v.platforms);
}
if (checks.manifest) {
const m = loadJSON(join(dir, "manifests", "manifest.json"));
checks.manifestValid = !!(m.project && m.files?.length > 0);
}
if (checks.checksums) {
const content = readFileSync(join(dir, "checksums", "checksums.txt"), "utf8");
const lines = content.trim().split("\n").filter(l => l.length > 0);
checks.checksumCount = lines.length;
checks.checksumsValid = lines.every(l => l.split(/\s+/)[0]?.length === 64);
}
if (checks.releaseNotes) {
const notes = readFileSync(join(dir, "release-notes", "release-notes.md"), "utf8");
checks.notesValid = notes.includes("Version") && notes.includes("Installation");
}
if (checks.buildInfo) {
const bi = loadJSON(join(dir, "build-info.json"));
checks.buildInfoValid = !!(bi.nodeVersion && bi.generatorVersion);
}
for (const p of ["windows", "macos", "linux"]) {
if (checks[p]) {
try { checks[p + "Count"] = readdirSync(join(dir, p)).length; } catch { checks[p + "Count"] = 0; }
}
}
checks.pass = checks.versionJson && checks.versionValid && checks.manifest && checks.manifestValid &&
checks.checksums && checks.checksumsValid && checks.releaseNotes && checks.notesValid &&
checks.buildInfo && checks.buildInfoValid && checks.windows && checks.macos && checks.linux;
return checks;
}
// ═══════════════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════════════
describe("E2E Regression — 全链路生成", () => {
for (const domain of DOMAINS) {
describe(`${domain.id}`, () => {
let pipeline;
before(() => {
pipeline = runFullPipeline(domain);
});
it("SF-01 PRD 生成成功", () => {
assert.ok(pipeline.stages.prd.ok, `${domain.id}: PRD generation failed`);
});
it("SF-02 Architecture 生成成功", () => {
assert.ok(pipeline.stages.arch.ok, `${domain.id}: Architecture generation failed`);
});
it("SF-03 Frontend 生成成功", () => {
assert.ok(pipeline.stages.frontend.ok, `${domain.id}: Frontend generation failed`);
assert.ok(pipeline.stages.frontend.files > 10, `${domain.id}: Too few frontend files`);
});
it("SF-04 Backend 生成成功", () => {
assert.ok(pipeline.stages.backend.ok, `${domain.id}: Backend generation failed`);
assert.ok(pipeline.stages.backend.files > 10, `${domain.id}: Too few backend files`);
});
it("SF-05 Fullstack 组装成功", () => {
assert.ok(pipeline.stages.fullstack.ok, `${domain.id}: Fullstack composition failed`);
assert.ok(pipeline.stages.fullstack.files > 30, `${domain.id}: Too few fullstack files`);
});
it("SF-06 Electron 封装成功", () => {
assert.ok(pipeline.stages.electron.ok, `${domain.id}: Electron wrapper failed`);
assert.ok(pipeline.stages.electron.files >= 8, `${domain.id}: Too few electron files`);
});
it("SF-07 Release 打包成功", () => {
assert.ok(pipeline.stages.release.ok, `${domain.id}: Release packaging failed`);
});
});
}
});
describe("E2E Regression — PRD 质量", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: PRD 包含必要的字段和最低特性数`, () => {
const v = validatePRD(domain);
assert.ok(v.hasProjectName, `${domain.id}: missing projectName`);
assert.ok(v.hasDomain, `${domain.id}: missing domain`);
assert.ok(v.featureCount >= domain.minFeatures, `${domain.id}: ${v.featureCount} features < ${domain.minFeatures}`);
});
}
});
describe("E2E Regression — Frontend 结构", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: Frontend 包含 package.json, app/, services/, types/`, () => {
const v = validateFrontend(domain);
assert.ok(v.pass, `${domain.id}: FE validation failed — pages:${v.appPages} files:${v.fileCount}`);
});
}
});
describe("E2E Regression — Backend 结构", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: Backend 包含 routes, services, auth, db`, () => {
const v = validateBackend(domain);
assert.ok(v.pass, `${domain.id}: BE validation failed — routes:${v.routeCount} files:${v.fileCount}`);
});
}
});
describe("E2E Regression — Fullstack 结构", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: Fullstack 包含 apps/web, apps/api, packages/`, () => {
const v = validateFullstack(domain);
assert.ok(v.pass, `${domain.id}: FS validation failed — files:${v.fileCount}`);
});
}
});
describe("E2E Regression — Electron 结构", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: Electron 包含 main.ts, preload.ts, ipc.ts`, () => {
const v = validateElectron(domain);
assert.ok(v.pass, `${domain.id}: EL validation failed — files:${v.fileCount}`);
});
}
});
describe("E2E Regression — Release 结构", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: Release 包含 version, manifest, checksums, notes, build-info, 3 platforms`, () => {
const v = validateRelease(domain);
assert.ok(v.pass, `${domain.id}: Release validation failed`);
});
}
});
describe("E2E Regression — Release 内容完整性", () => {
for (const domain of DOMAINS) {
it(`${domain.id}: version.json 包含 name, version, platforms`, () => {
const dir = join(BENCH_ROOT, domain.id, "release", "release");
const v = loadJSON(join(dir, "version.json"));
assert.ok(v.name, `${domain.id}: missing version.name`);
assert.ok(v.version, `${domain.id}: missing version.version`);
assert.deepEqual(v.platforms, ["windows", "macos", "linux"]);
});
it(`${domain.id}: manifest.json 文件列表与 checksums.txt 一致`, () => {
const dir = join(BENCH_ROOT, domain.id, "release", "release");
const m = loadJSON(join(dir, "manifests", "manifest.json"));
const c = readFileSync(join(dir, "checksums", "checksums.txt"), "utf8");
const cLines = c.trim().split("\n").filter(l => l.length > 0);
assert.equal(m.files.length, cLines.length, `${domain.id}: manifest/checksums count mismatch`);
});
it(`${domain.id}: SHA256 格式正确 (64 hex chars)`, () => {
const dir = join(BENCH_ROOT, domain.id, "release", "release");
const c = readFileSync(join(dir, "checksums", "checksums.txt"), "utf8");
for (const line of c.trim().split("\n").filter(l => l.length > 0)) {
const sha = line.split(/\s+/)[0];
assert.match(sha, /^[0-9a-f]{64}$/, `${domain.id}: invalid SHA256: ${sha}`);
}
});
it(`${domain.id}: Platform 目录包含占位文件`, () => {
const dir = join(BENCH_ROOT, domain.id, "release", "release");
for (const p of ["windows", "macos", "linux"]) {
const files = readdirSync(join(dir, p));
assert.ok(files.length >= 2, `${domain.id}: ${p}/ has ${files.length} files, expected >= 2`);
}
});
}
});
+453
View File
@@ -0,0 +1,453 @@
/**
* Electron Builder Agent Test — SF-06
*
* 验证 Electron Wrapper 生成器的所有关键功能。
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
let buildElectron, writeElectron;
before(async () => {
const mod = await import("../scripts/electron-builder-agent.mjs");
buildElectron = mod.buildElectron;
writeElectron = mod.writeElectron;
});
// ═══════════════════════════════════════════════════════
// 1 — Desktop Project 生成
// ═══════════════════════════════════════════════════════
describe("1 — Desktop Project 结构生成", () => {
let result;
before(() => {
result = buildElectron({ projectName: "PetCare" });
});
it("生成成功(无错误)", () => {
assert.ok(!result.error, `Unexpected error: ${result.error}`);
assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files, got ${result.stats.totalFiles}`);
});
it("包含 electron/ 目录下的所有核心文件", () => {
assert.ok(result.files["electron/main.ts"], "Missing main.ts");
assert.ok(result.files["electron/preload.ts"], "Missing preload.ts");
assert.ok(result.files["electron/ipc.ts"], "Missing ipc.ts");
assert.ok(result.files["electron/tray.ts"], "Missing tray.ts");
assert.ok(result.files["electron/updater.ts"], "Missing updater.ts");
assert.ok(result.files["electron/utils.ts"], "Missing utils.ts");
});
it("包含 assets/icon.png", () => {
assert.ok(result.files["assets/icon.png"], "Missing icon.png");
assert.ok(result.files["assets/icon.png"].length > 0, "Empty icon.png");
});
});
// ═══════════════════════════════════════════════════════
// 2 — main.ts 内容验证
// ═══════════════════════════════════════════════════════
describe("2 — main.ts 内容验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "PetCare" });
});
it("包含 BrowserWindow", () => {
assert.ok(result.files["electron/main.ts"].includes("BrowserWindow"));
});
it("包含 Menu", () => {
assert.ok(result.files["electron/main.ts"].includes("Menu"));
});
it("包含 Tray setup", () => {
assert.ok(result.files["electron/main.ts"].includes("setupTray"));
});
it("包含 IPC setup", () => {
assert.ok(result.files["electron/main.ts"].includes("setupIPC"));
});
it("包含 Single Instance Lock", () => {
assert.ok(result.files["electron/main.ts"].includes("checkSingleInstance"));
assert.ok(result.files["electron/utils.ts"].includes("requestSingleInstanceLock"));
});
it("包含 Auto Start 配置", () => {
assert.ok(result.files["electron/main.ts"].includes("activate"));
});
it("包含 contextIsolation: true", () => {
assert.ok(result.files["electron/main.ts"].includes("contextIsolation: true"));
});
it("包含 nodeIntegration: false", () => {
assert.ok(result.files["electron/main.ts"].includes("nodeIntegration: false"));
});
it("开发环境加载 localhost URL", () => {
assert.ok(result.files["electron/main.ts"].includes("localhost"));
});
it("生产环境加载 file 或 resources 路径", () => {
const main = result.files["electron/main.ts"];
assert.ok(main.includes("resourcesPath") || main.includes("loadFile"));
});
it("包含外部链接处理", () => {
assert.ok(result.files["electron/main.ts"].includes("setWindowOpenHandler"));
});
it("包含 GPU crash 处理", () => {
assert.ok(result.files["electron/main.ts"].includes("gpu-process-crashed"));
});
it("包含 API server 生产环境启动", () => {
assert.ok(result.files["electron/main.ts"].includes("startApiServer"));
});
});
// ═══════════════════════════════════════════════════════
// 3 — preload.ts 内容验证
// ═══════════════════════════════════════════════════════
describe("3 — preload.ts 内容验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "CRM" });
});
it("使用 contextBridge", () => {
assert.ok(result.files["electron/preload.ts"].includes("contextBridge"));
});
it("使用 ipcRenderer", () => {
assert.ok(result.files["electron/preload.ts"].includes("ipcRenderer"));
});
it("暴露 electronAPI", () => {
assert.ok(result.files["electron/preload.ts"].includes("electronAPI"));
});
it("包含窗口控制 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("minimizeWindow"));
assert.ok(preload.includes("maximizeWindow"));
assert.ok(preload.includes("closeWindow"));
});
it("包含文件对话框 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("openFile"));
assert.ok(preload.includes("saveFile"));
});
it("包含通知 API", () => {
assert.ok(result.files["electron/preload.ts"].includes("showNotification"));
});
it("包含持久化存储 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("storeGet"));
assert.ok(preload.includes("storeSet"));
assert.ok(preload.includes("storeDelete"));
});
it("包含更新 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("checkForUpdates"));
assert.ok(preload.includes("downloadUpdate"));
assert.ok(preload.includes("quitAndInstall"));
});
it("包含 TypeScript 类型声明", () => {
assert.ok(result.files["electron/preload.ts"].includes("interface ElectronAPI"));
assert.ok(result.files["electron/preload.ts"].includes("declare global"));
});
});
// ═══════════════════════════════════════════════════════
// 4 — package.json 验证
// ═══════════════════════════════════════════════════════
describe("4 — package.json 验证", () => {
let result, pkg;
before(() => {
result = buildElectron({ projectName: "Inventory" });
pkg = JSON.parse(result.files["package.json"]);
});
it("包含正确名称", () => {
assert.ok(pkg.name.includes("inventory-desktop"));
});
it("包含 main 入口", () => {
assert.equal(pkg.main, "dist/electron/main.js");
});
it("包含 dev 脚本", () => {
assert.ok(pkg.scripts.dev, "Missing dev script");
assert.ok(pkg.scripts.dev.includes("concurrently"));
});
it("包含 build 脚本", () => {
assert.ok(pkg.scripts.build, "Missing build script");
});
it("包含平台构建脚本", () => {
assert.ok(pkg.scripts["build:electron:win"], "Missing win build");
assert.ok(pkg.scripts["build:electron:mac"], "Missing mac build");
assert.ok(pkg.scripts["build:electron:linux"], "Missing linux build");
});
it("依赖 electron", () => {
assert.ok(pkg.devDependencies.electron, "Missing electron");
});
it("依赖 electron-builder", () => {
assert.ok(pkg.devDependencies["electron-builder"], "Missing electron-builder");
});
it("依赖 electron-updater", () => {
assert.ok(pkg.dependencies["electron-updater"], "Missing electron-updater");
});
it("依赖 concurrently", () => {
assert.ok(pkg.devDependencies.concurrently, "Missing concurrently");
});
it("依赖 typescript", () => {
assert.ok(pkg.devDependencies.typescript, "Missing typescript");
});
it("build 配置包含 appId", () => {
assert.ok(pkg.build.appId.includes("inventory.desktop"));
});
it("build 配置包含 productName", () => {
assert.equal(pkg.build.productName, "Inventory");
});
});
// ═══════════════════════════════════════════════════════
// 5 — electron-builder.yml 验证
// ═══════════════════════════════════════════════════════
describe("5 — electron-builder.yml 验证", () => {
let result, yml;
before(() => {
result = buildElectron({ projectName: "PetCare" });
yml = result.files["electron-builder.yml"];
});
it("包含 appId", () => {
assert.ok(yml.includes("appId: com.petcare.desktop"));
});
it("包含 productName", () => {
assert.ok(yml.includes("productName: PetCare"));
});
it("包含 macOS 配置", () => {
assert.ok(yml.includes("mac:"));
assert.ok(yml.includes("dmg"));
assert.ok(yml.includes("arm64"));
});
it("包含 Windows 配置", () => {
assert.ok(yml.includes("win:"));
assert.ok(yml.includes("nsis"));
});
it("包含 Linux 配置", () => {
assert.ok(yml.includes("linux:"));
assert.ok(yml.includes("AppImage"));
assert.ok(yml.includes("deb"));
});
it("包含 extraResourcesweb + api", () => {
assert.ok(yml.includes("extraResources"));
assert.ok(yml.includes("../web/out"));
assert.ok(yml.includes("../api/dist"));
});
it("包含 publish 配置", () => {
assert.ok(yml.includes("publish:"));
assert.ok(yml.includes("provider: generic"));
});
});
// ═══════════════════════════════════════════════════════
// 6 — tsconfig.json 验证
// ═══════════════════════════════════════════════════════
describe("6 — tsconfig.json 验证", () => {
let result, tsconfig;
before(() => {
result = buildElectron();
tsconfig = JSON.parse(result.files["tsconfig.json"]);
});
it("target 是 ES2022", () => {
assert.equal(tsconfig.compilerOptions.target, "ES2022");
});
it("module 是 commonjsElectron 主进程需要)", () => {
assert.equal(tsconfig.compilerOptions.module, "commonjs");
});
it("strict 模式开启", () => {
assert.equal(tsconfig.compilerOptions.strict, true);
});
it("outDir 是 dist", () => {
assert.equal(tsconfig.compilerOptions.outDir, "dist");
});
it("include 包含 electron/", () => {
assert.ok(tsconfig.include.some(p => p.includes("electron")));
});
});
// ═══════════════════════════════════════════════════════
// 7 — 其他文件验证
// ═══════════════════════════════════════════════════════
describe("7 — 辅助文件验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "CRM" });
});
it("README.md 存在且包含关键内容", () => {
const readme = result.files["README.md"];
assert.ok(readme, "Missing README.md");
assert.ok(readme.includes("CRM"), "Missing project name");
assert.ok(readme.includes("Development"), "Missing Development section");
assert.ok(readme.includes("Build"), "Missing Build section");
assert.ok(readme.includes("IPC API"), "Missing IPC API section");
});
it("entitlements.mac.plist 存在", () => {
const plist = result.files["entitlements.mac.plist"];
assert.ok(plist, "Missing entitlements.mac.plist");
assert.ok(plist.includes("com.apple.security.network.client"));
});
it("icon.png 存在且非空", () => {
assert.ok(result.files["assets/icon.png"], "Missing icon.png");
assert.ok(result.files["assets/icon.png"].length > 0);
});
});
// ═══════════════════════════════════════════════════════
// 8 — 自定义端口配置
// ═══════════════════════════════════════════════════════
describe("8 — 自定义端口配置", () => {
it("使用自定义 web 端口", () => {
const result = buildElectron({ webPort: 4000 });
assert.ok(result.files["electron/main.ts"].includes("4000"));
});
it("使用自定义 api 端口", () => {
const result = buildElectron({ apiPort: 4001 });
assert.ok(result.files["electron/main.ts"].includes("4001"));
assert.ok(result.files["electron/utils.ts"].includes("4001"));
});
it("默认端口是 3000/3001", () => {
const result = buildElectron();
assert.ok(result.files["electron/main.ts"].includes("3000"));
assert.ok(result.files["electron/main.ts"].includes("3001"));
});
});
// ═══════════════════════════════════════════════════════
// 9 — 写入 I/O
// ═══════════════════════════════════════════════════════
describe("9 — 文件写入 I/O", () => {
it("writeElectron 写入完整目录树", () => {
const result = buildElectron({ projectName: "TestApp" });
const tmpDir = resolve(WORKSPACE, ".tmp-electron-test");
writeElectron(result, tmpDir);
assert.ok(existsSync(resolve(tmpDir, "package.json")));
assert.ok(existsSync(resolve(tmpDir, "electron-builder.yml")));
assert.ok(existsSync(resolve(tmpDir, "tsconfig.json")));
assert.ok(existsSync(resolve(tmpDir, "electron/main.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/preload.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/ipc.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/tray.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/updater.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/utils.ts")));
assert.ok(existsSync(resolve(tmpDir, "assets/icon.png")));
assert.ok(existsSync(resolve(tmpDir, "entitlements.mac.plist")));
assert.ok(existsSync(resolve(tmpDir, "README.md")));
rmSync(tmpDir, { recursive: true, force: true });
});
it("所有生成的文件非空", () => {
const result = buildElectron();
for (const [path, content] of Object.entries(result.files)) {
const len = typeof content === "string" ? content.length : content.byteLength;
assert.ok(len > 0, `${path}: empty content`);
}
});
it("所有 package.json 可解析", () => {
const result = buildElectron();
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.name, "Missing package name");
assert.ok(pkg.scripts, "Missing scripts");
});
});
// ═══════════════════════════════════════════════════════
// 10 — 多领域覆盖
// ═══════════════════════════════════════════════════════
describe("10 — 多领域覆盖(PetCare / CRM / Inventory", () => {
const domains = [
{ name: "PetCare", expected: "petcare" },
{ name: "CRM", expected: "crm" },
{ name: "Inventory", expected: "inventory" },
];
for (const { name, expected } of domains) {
it(`${name} — 生成成功`, () => {
const result = buildElectron({ projectName: name });
assert.ok(!result.error, `${name}: ${result.message}`);
assert.ok(result.files["electron/main.ts"], `${name}: missing main.ts`);
assert.ok(result.files["electron/preload.ts"], `${name}: missing preload.ts`);
assert.ok(result.files["package.json"], `${name}: missing package.json`);
assert.ok(result.files["electron-builder.yml"], `${name}: missing electron-builder.yml`);
});
it(`${name} — package.json 包含正确名称`, () => {
const result = buildElectron({ projectName: name });
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.name.includes(expected), `${name}: expected '${expected}' in name, got '${pkg.name}'`);
});
it(`${name} — electron-builder.yml 包含正确 appId`, () => {
const result = buildElectron({ projectName: name });
assert.ok(
result.files["electron-builder.yml"].includes(`com.${expected}.desktop`),
`${name}: expected 'com.${expected}.desktop' in appId`
);
});
}
});
@@ -0,0 +1 @@
# Archive: Deprecated
@@ -0,0 +1 @@
# Archive: Old Log 1
@@ -0,0 +1 @@
# Archive: Old Log 2
@@ -0,0 +1 @@
# Context: Core Memory
@@ -0,0 +1 @@
# Context: Projects
@@ -0,0 +1 @@
# Context: Settings
@@ -0,0 +1 @@
# Thread: Discussion 1
@@ -0,0 +1 @@
# Thread: Discussion 2
@@ -0,0 +1 @@
# Thread: Discussion 3
@@ -0,0 +1 @@
# Thread: Discussion 4
@@ -0,0 +1 @@
# Thread: Discussion 5
@@ -0,0 +1,41 @@
{
"description": "Degrading agent — health score dropping over 2 snapshots",
"snapshots": [
{
"timestamp": "2026-06-03T00:00:00.000Z",
"agents": {
"mock-staging": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-02T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-01T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
}
}
},
{
"timestamp": "2026-06-05T00:00:00.000Z",
"agents": {
"mock-staging": {
"status": "active",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-04T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 1,
"compatibilityFailures": 1
}
}
}
]
}
@@ -0,0 +1,41 @@
{
"description": "Healthy agent with improving trend — all indicators green, 2 snapshots showing improvement",
"snapshots": [
{
"timestamp": "2026-06-03T00:00:00.000Z",
"agents": {
"claw-prod": {
"status": "active",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"baselineVersion": "v3",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-02T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-06-01T00:00:00.000Z"}
],
"rollbacks": 1,
"compatibilityFailures": 1
}
}
},
{
"timestamp": "2026-06-05T00:00:00.000Z",
"agents": {
"claw-prod": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v4",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-04T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
}
}
}
]
}
@@ -0,0 +1,57 @@
{
"description": "Improving agent — recovering from degraded state over 3 snapshots",
"snapshots": [
{
"timestamp": "2026-06-01T00:00:00.000Z",
"agents": {
"recovery-agent": {
"status": "active",
"compatibilityStatus": "unsupported",
"certificationStatus": "not_ready",
"baselineVersion": "unknown",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-01T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-05-31T00:00:00.000Z"}
],
"rollbacks": 3,
"compatibilityFailures": 2
}
}
},
{
"timestamp": "2026-06-03T00:00:00.000Z",
"agents": {
"recovery-agent": {
"status": "active",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"baselineVersion": "v1",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-06-02T00:00:00.000Z"}
],
"rollbacks": 2,
"compatibilityFailures": 1
}
}
},
{
"timestamp": "2026-06-05T00:00:00.000Z",
"agents": {
"recovery-agent": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-04T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
}
}
}
]
}
@@ -0,0 +1,103 @@
{
"description": "Mixed fleet: healthy, degrading, unstable, and unknown agents",
"snapshots": [
{
"timestamp": "2026-06-03T00:00:00.000Z",
"agents": {
"claw-prod": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v4",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-02T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
},
"mock-staging": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
},
"old-claw-beta": {
"status": "unsupported",
"compatibilityStatus": "unsupported",
"certificationStatus": "not_ready",
"baselineVersion": "unknown",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 1,
"compatibilityFailures": 1
},
"unknown-sdk-v1": {
"status": "unsupported",
"compatibilityStatus": "unsupported",
"certificationStatus": "unknown",
"baselineVersion": "unknown",
"releaseHistory": [],
"rollbacks": 0,
"compatibilityFailures": 0
}
}
},
{
"timestamp": "2026-06-05T00:00:00.000Z",
"agents": {
"claw-prod": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v4",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-04T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 0,
"compatibilityFailures": 0
},
"mock-staging": {
"status": "active",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "success", "timestamp": "2026-06-04T00:00:00.000Z"}
],
"rollbacks": 1,
"compatibilityFailures": 1
},
"old-claw-beta": {
"status": "unsupported",
"compatibilityStatus": "unsupported",
"certificationStatus": "not_ready",
"baselineVersion": "unknown",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-05T00:00:00.000Z"}
],
"rollbacks": 2,
"compatibilityFailures": 2
},
"unknown-sdk-v1": {
"status": "unsupported",
"compatibilityStatus": "unsupported",
"certificationStatus": "unknown",
"baselineVersion": "unknown",
"releaseHistory": [],
"rollbacks": 0,
"compatibilityFailures": 0
}
}
}
]
}
@@ -0,0 +1,56 @@
{
"description": "Unstable agent — multiple rollbacks, compat failures, certification downgrades",
"snapshots": [
{
"timestamp": "2026-06-01T00:00:00.000Z",
"agents": {
"unstable-agent": {
"status": "active",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "success", "timestamp": "2026-06-01T00:00:00.000Z"}
],
"rollbacks": 1,
"compatibilityFailures": 1
}
}
},
{
"timestamp": "2026-06-03T00:00:00.000Z",
"agents": {
"unstable-agent": {
"status": "active",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"baselineVersion": "v2",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-03T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-06-02T00:00:00.000Z"}
],
"rollbacks": 2,
"compatibilityFailures": 2
}
}
},
{
"timestamp": "2026-06-05T00:00:00.000Z",
"agents": {
"unstable-agent": {
"status": "unsupported",
"compatibilityStatus": "unsupported",
"certificationStatus": "not_ready",
"baselineVersion": "unknown",
"releaseHistory": [
{"result": "failed", "timestamp": "2026-06-05T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-06-04T00:00:00.000Z"},
{"result": "failed", "timestamp": "2026-06-03T00:00:00.000Z"}
],
"rollbacks": 4,
"compatibilityFailures": 3
}
}
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "old-claw-beta",
"agentType": "claw",
"displayName": "Old Claw Beta",
"status": "deprecated",
"owner": "team-memory",
"workspacePath": "legacy-memory/",
"adapter": "claw",
"baselineVersion": "v1",
"compatibilityStatus": "deprecated",
"lastCertifiedAt": "2025-06-01T00:00:00.000Z"
}
]
}
+29
View File
@@ -0,0 +1,29 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "claw-prod",
"agentType": "mock",
"displayName": "Claw Production Duplicate",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "mock",
"baselineVersion": "v1",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-01T00:00:00.000Z"
}
]
}
+4
View File
@@ -0,0 +1,4 @@
{
"schemaVersion": "1.0",
"agents": []
}
@@ -0,0 +1,11 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-demo",
"displayName": "Claw Demo",
"status": "active",
"owner": "team-memory"
}
]
}
+53
View File
@@ -0,0 +1,53 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "mock-dev",
"agentType": "mock",
"displayName": "Mock Development Agent",
"status": "active",
"owner": "dev-team",
"workspacePath": "test/fixtures/agent-adapters/mock-agent",
"adapter": "mock",
"baselineVersion": "v1",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-03T00:00:00.000Z"
},
{
"agentId": "old-claw-beta",
"agentType": "claw",
"displayName": "Old Claw Beta",
"status": "deprecated",
"owner": "team-memory",
"workspacePath": "legacy-memory/",
"adapter": "claw",
"baselineVersion": "v1",
"compatibilityStatus": "deprecated",
"lastCertifiedAt": "2025-06-01T00:00:00.000Z"
},
{
"agentId": "unknown-sdk-v1",
"agentType": "unknown",
"displayName": "Unknown SDK Agent v1",
"status": "unsupported",
"owner": "external-team",
"workspacePath": "legacy-data/",
"adapter": "none",
"baselineVersion": "unknown",
"compatibilityStatus": "unsupported",
"lastCertifiedAt": "2025-12-01T00:00:00.000Z"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "unknown-sdk-v1",
"agentType": "unknown",
"displayName": "Unknown SDK Agent v1",
"status": "unsupported",
"owner": "external-team",
"workspacePath": "legacy-data/",
"adapter": "none",
"baselineVersion": "unknown",
"compatibilityStatus": "unsupported",
"lastCertifiedAt": "2025-12-01T00:00:00.000Z"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
{
"description": "空 PRD — 用于测试错误处理",
"error": "EMPTY_INPUT",
"message": "请输入需求描述"
}
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
{
"projectName": "UnknownApp",
"chineseName": "通用应用",
"domain": "generic",
"summary": "一个用户定制的应用。",
"features": [
{ "name": "核心功能", "description": "产品核心功能模块", "priority": "P0" }
],
"pages": [
{ "name": "首页", "route": "/home", "description": "应用首页" },
{ "name": "我的", "route": "/profile", "description": "用户中心" }
],
"apiRequirements": [
{ "method": "GET", "path": "/api/health", "description": "健康检查" },
{ "method": "POST", "path": "/api/users", "description": "用户注册" }
],
"techConstraints": {
"platforms": ["web"],
"recommendedStack": "任意",
"considerations": []
},
"extraFeatures": [],
"meta": {}
}
+1
View File
@@ -0,0 +1 @@
{"projectName":"NoteApp","chineseName":"笔记应用","domain":"note","matchConfidence":"high","summary":"一款支持多端同步、Markdown 编辑和标签管理的笔记应用。","personas":[{"name":"知识工作者","description":"需要记录灵感和整理知识的职场人","painPoints":["笔记分散多处","搜索困难","格式化繁琐"]}],"userStories":[{"id":"US-001","as":"知识工作者","want":"富文本/Markdown编辑器、实时保存","soThat":"解决痛点:笔记分散多处","priority":"P0"},{"id":"US-002","as":"知识工作者","want":"分类管理、多级文件夹","soThat":"解决痛点:笔记分散多处","priority":"P0"},{"id":"US-003","as":"知识工作者","want":"标题+内容全文检索","soThat":"解决痛点:笔记分散多处","priority":"P0"}],"mvpScope":{"description":"MVP 聚焦 笔记编辑、文件夹/标签、全文搜索,包含页面:笔记列表、笔记编辑、标签管理","features":["笔记编辑","文件夹/标签","全文搜索"],"pages":["笔记列表","笔记编辑","标签管理"],"estimatedWeeks":3},"features":[{"name":"笔记编辑","description":"富文本/Markdown编辑器、实时保存","priority":"P0"},{"name":"文件夹/标签","description":"分类管理、多级文件夹","priority":"P0"},{"name":"全文搜索","description":"标题+内容全文检索","priority":"P0"},{"name":"多端同步","description":"手机/PC/Web 实时同步","priority":"P1"},{"name":"协作分享","description":"分享链接、协作编辑","priority":"P2"}],"pages":[{"name":"笔记列表","route":"/notes","description":"文件夹导航、笔记列表、搜索"},{"name":"笔记编辑","route":"/note/:id","description":"编辑器页面"},{"name":"标签管理","route":"/tags","description":"标签列表、关联笔记"},{"name":"设置","route":"/settings","description":"主题、同步、账户"}],"apiRequirements":[{"method":"GET","path":"/api/notes","description":"笔记列表"},{"method":"POST","path":"/api/notes","description":"创建笔记"},{"method":"PUT","path":"/api/notes/:id","description":"更新笔记"},{"method":"DELETE","path":"/api/notes/:id","description":"删除笔记"},{"method":"GET","path":"/api/tags","description":"标签列表"}],"techConstraints":{"platforms":["mobile","web"],"recommendedStack":"React Native / Flutter(跨平台)","considerations":["建议跨平台框架减少开发成本"]},"extraFeatures":[],"devTasks":[{"id":"T-001","title":"项目脚手架搭建","description":"初始化 NoteApp 项目结构,配置构建工具和 CI/CD","phase":"Foundation","estimatedHours":8,"priority":"P0"},{"id":"T-002","title":"数据库设计与初始化","description":"设计数据模型,创建数据库迁移脚本","phase":"Foundation","estimatedHours":16,"priority":"P0"},{"id":"T-003","title":"页面开发:笔记列表","description":"开发 笔记列表 页面(/notes):文件夹导航、笔记列表、搜索","phase":"UI Development","estimatedHours":8,"priority":"P0"},{"id":"T-004","title":"页面开发:笔记编辑","description":"开发 笔记编辑 页面(/note/:id):编辑器页面","phase":"UI Development","estimatedHours":8,"priority":"P0"},{"id":"T-005","title":"页面开发:标签管理","description":"开发 标签管理 页面(/tags):标签列表、关联笔记","phase":"UI Development","estimatedHours":8,"priority":"P0"},{"id":"T-006","title":"页面开发:设置","description":"开发 设置 页面(/settings):主题、同步、账户","phase":"UI Development","estimatedHours":8,"priority":"P0"},{"id":"T-007","title":"API 开发:GET /api/notes","description":"笔记列表","phase":"Backend Development","estimatedHours":4,"priority":"P0"},{"id":"T-008","title":"API 开发:POST /api/notes","description":"创建笔记","phase":"Backend Development","estimatedHours":4,"priority":"P0"},{"id":"T-009","title":"API 开发:PUT /api/notes/:id","description":"更新笔记","phase":"Backend Development","estimatedHours":4,"priority":"P0"},{"id":"T-010","title":"API 开发:DELETE /api/notes/:id","description":"删除笔记","phase":"Backend Development","estimatedHours":4,"priority":"P0"},{"id":"T-011","title":"前后端联调","description":"前端页面与后端 API 集成联调","phase":"Integration","estimatedHours":16,"priority":"P0"},{"id":"T-012","title":"测试与修复","description":"功能测试、Bug 修复、性能优化","phase":"Testing","estimatedHours":24,"priority":"P1"}],"meta":{"generatedAt":"2026-06-04T22:53:57.117Z","inputLength":4,"domainMatchCount":1}}
@@ -0,0 +1,24 @@
{
"description": "Certification revoked alert — triggers create_incident and mark_high_risk",
"alerts": [
{
"alertId": "monitor-004",
"severity": "critical",
"agentId": "unknown-sdk-v1",
"reason": "certification_downgraded_to_not_ready",
"details": "Certification changed from production_ready to not_ready",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "CR-2",
"source": "continuous-monitoring"
}
],
"events": [
{
"eventType": "certification_revoked",
"agentId": "unknown-sdk-v1",
"timestamp": "2026-06-04T00:00:00.000Z",
"metadata": { "previousStatus": "production_ready" }
}
],
"configuration": {}
}
@@ -0,0 +1,24 @@
{
"description": "Compatibility failure alert — triggers raise_risk and downgrade_certification",
"alerts": [
{
"alertId": "monitor-001",
"severity": "critical",
"agentId": "claw-prod",
"reason": "compatibility_unsupported",
"details": "Compatibility status is unsupported",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "CR-3",
"source": "continuous-monitoring"
}
],
"events": [
{
"eventType": "compatibility_failed",
"agentId": "claw-prod",
"timestamp": "2026-06-04T00:00:00.000Z",
"metadata": { "previousStatus": "supported" }
}
],
"configuration": {}
}
@@ -0,0 +1,24 @@
{
"description": "Health degradation alert — triggers create_remediation_plan and increase_monitoring",
"alerts": [
{
"alertId": "monitor-002",
"severity": "high",
"agentId": "claw-staging",
"reason": "health_score_drop_high",
"details": "Health score dropped 18% (90 → 72)",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "HR-1",
"source": "continuous-monitoring"
}
],
"events": [
{
"eventType": "health_degraded",
"agentId": "claw-staging",
"timestamp": "2026-06-04T00:00:00.000Z",
"metadata": { "previousScore": 90, "currentScore": 72 }
}
],
"configuration": {}
}
@@ -0,0 +1,47 @@
{
"description": "Mixed severity input — critical, high, and warning alerts triggering different response levels",
"alerts": [
{
"alertId": "monitor-010",
"severity": "critical",
"agentId": "old-claw-beta",
"reason": "compatibility_unsupported",
"details": "Compatibility status is unsupported",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "CR-3",
"source": "continuous-monitoring"
},
{
"alertId": "monitor-011",
"severity": "high",
"agentId": "claw-staging",
"reason": "health_score_drop_high",
"details": "Health score dropped 18%",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "HR-1",
"source": "continuous-monitoring"
},
{
"alertId": "monitor-012",
"severity": "warning",
"agentId": "mock-staging",
"reason": "compatibility_warning",
"details": "Compatibility has warnings",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "WR-1",
"source": "continuous-monitoring"
},
{
"alertId": "monitor-013",
"severity": "high",
"agentId": "unstable-mock",
"reason": "rollback_spike",
"details": "Rollback count spiked from 1 to 4",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "HR-3",
"source": "continuous-monitoring"
}
],
"events": [],
"configuration": {}
}
@@ -0,0 +1,24 @@
{
"description": "Release failure alert — triggers raise_alert and flag_for_review",
"alerts": [
{
"alertId": "monitor-003",
"severity": "critical",
"agentId": "old-claw-beta",
"reason": "consecutive_release_failure",
"details": "3 consecutive release failures detected",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "CR-4",
"source": "continuous-monitoring"
}
],
"events": [
{
"eventType": "release_failed",
"agentId": "old-claw-beta",
"timestamp": "2026-06-04T00:00:00.000Z",
"metadata": { "failureCount": 3 }
}
],
"configuration": {}
}
@@ -0,0 +1,27 @@
{
"description": "Warning-only input — only warning alerts, should only generate recommendations, not auto-execute",
"alerts": [
{
"alertId": "monitor-020",
"severity": "warning",
"agentId": "claw-canary",
"reason": "certification_conditionally_ready",
"details": "Certification is conditionally ready",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "WR-2",
"source": "continuous-monitoring"
},
{
"alertId": "monitor-021",
"severity": "warning",
"agentId": "claw-canary",
"reason": "missing_fields",
"details": "Missing or unknown fields: releaseResult",
"timestamp": "2026-06-05T00:00:00.000Z",
"ruleId": "WR-3",
"source": "continuous-monitoring"
}
],
"events": [],
"configuration": {}
}
+110
View File
@@ -0,0 +1,110 @@
{
"generatedAt": "2026-06-04T20:00:31.447Z",
"chainLength": 2,
"steps": [
{
"from": "v1",
"to": "v2",
"growthMode": "normal",
"metrics": {
"blocCount": {
"from": 6,
"to": 6,
"delta": "0%",
"absolute": 0
},
"cycleCount": {
"from": 7,
"to": 7,
"delta": "0%",
"absolute": 0
},
"candidateCount": {
"from": 36,
"to": 38,
"delta": "+5.56%",
"absolute": 2
},
"activeCandidateCount": {
"from": 28,
"to": 30,
"delta": "+7.14%",
"absolute": 2
},
"archivedCandidateCount": {
"from": 8,
"to": 8,
"delta": "0%",
"absolute": 0
}
}
},
{
"from": "v2",
"to": "v3",
"growthMode": "moderate",
"metrics": {
"blocCount": {
"from": 6,
"to": 7,
"delta": "+16.67%",
"absolute": 1
},
"cycleCount": {
"from": 7,
"to": 9,
"delta": "+28.57%",
"absolute": 2
},
"candidateCount": {
"from": 38,
"to": 46,
"delta": "+21.05%",
"absolute": 8
},
"activeCandidateCount": {
"from": 30,
"to": 36,
"delta": "+20%",
"absolute": 6
},
"archivedCandidateCount": {
"from": 8,
"to": 10,
"delta": "+25%",
"absolute": 2
}
}
}
],
"thresholdEvaluation": [
{
"from": "v1",
"to": "v2",
"metrics": {
"blocCount": "✅ PASS",
"cycleCount": "✅ PASS",
"candidateCount": "✅ PASS",
"activeCandidateCount": "✅ PASS",
"archivedCandidateCount": "✅ PASS"
}
},
{
"from": "v2",
"to": "v3",
"metrics": {
"blocCount": "✅ PASS",
"cycleCount": "⚠️ WARN",
"candidateCount": "⚠️ WARN",
"activeCandidateCount": "✅ PASS",
"archivedCandidateCount": "⚠️ WARN"
}
}
],
"summary": {
"totalSteps": 2,
"passSteps": 1,
"warnSteps": 1,
"failSteps": 0
}
}
@@ -0,0 +1,259 @@
{
"generatedAt": "2026-06-04T19:58:33.249Z",
"thresholds": {
"warn": 20,
"fail": 50
},
"baselineVersions": [
"v1",
"v2",
"v3"
],
"deltaLevels": [
5,
10,
20,
30,
50,
70
],
"results": [
{
"baseline": "v1",
"deltaPct": 5,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v1",
"deltaPct": 10,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v1",
"deltaPct": 20,
"status": "warn",
"blockers": 0,
"warnings": 2,
"blockerDetails": [],
"warningDetails": [
"Active Candidate Count: baseline=28 → candidate=34 (增加 21.43%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=10 (增加 25%,超过警示阈值 20%)"
]
},
{
"baseline": "v1",
"deltaPct": 30,
"status": "warn",
"blockers": 0,
"warnings": 5,
"blockerDetails": [],
"warningDetails": [
"Bloc Count: baseline=6 → candidate=8 (增加 33.33%,超过警示阈值 20%)",
"Cycle Count: baseline=7 → candidate=9 (增加 28.57%,超过警示阈值 20%)",
"Candidate Count: baseline=36 → candidate=47 (增加 30.56%,超过警示阈值 20%)",
"Active Candidate Count: baseline=28 → candidate=36 (增加 28.57%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=10 (增加 25%,超过警示阈值 20%)"
]
},
{
"baseline": "v1",
"deltaPct": 50,
"status": "fail",
"blockers": 1,
"warnings": 4,
"blockerDetails": [
"Cycle Count: baseline=7 → candidate=11 (增加 57.14%,超过严重阈值 50%)"
],
"warningDetails": [
"Bloc Count: baseline=6 → candidate=9 (增加 50%,超过警示阈值 20%)",
"Candidate Count: baseline=36 → candidate=54 (增加 50%,超过警示阈值 20%)",
"Active Candidate Count: baseline=28 → candidate=42 (增加 50%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=12 (增加 50%,超过警示阈值 20%)"
]
},
{
"baseline": "v1",
"deltaPct": 70,
"status": "fail",
"blockers": 5,
"warnings": 0,
"blockerDetails": [
"Bloc Count: baseline=6 → candidate=10 (增加 66.67%,超过严重阈值 50%)",
"Cycle Count: baseline=7 → candidate=12 (增加 71.43%,超过严重阈值 50%)",
"Candidate Count: baseline=36 → candidate=61 (增加 69.44%,超过严重阈值 50%)",
"Active Candidate Count: baseline=28 → candidate=48 (增加 71.43%,超过严重阈值 50%)",
"Archived Candidate Count: baseline=8 → candidate=14 (增加 75%,超过严重阈值 50%)"
],
"warningDetails": []
},
{
"baseline": "v2",
"deltaPct": 5,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v2",
"deltaPct": 10,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v2",
"deltaPct": 20,
"status": "warn",
"blockers": 0,
"warnings": 2,
"blockerDetails": [],
"warningDetails": [
"Candidate Count: baseline=38 → candidate=46 (增加 21.05%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=10 (增加 25%,超过警示阈值 20%)"
]
},
{
"baseline": "v2",
"deltaPct": 30,
"status": "warn",
"blockers": 0,
"warnings": 5,
"blockerDetails": [],
"warningDetails": [
"Bloc Count: baseline=6 → candidate=8 (增加 33.33%,超过警示阈值 20%)",
"Cycle Count: baseline=7 → candidate=9 (增加 28.57%,超过警示阈值 20%)",
"Candidate Count: baseline=38 → candidate=49 (增加 28.95%,超过警示阈值 20%)",
"Active Candidate Count: baseline=30 → candidate=39 (增加 30%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=10 (增加 25%,超过警示阈值 20%)"
]
},
{
"baseline": "v2",
"deltaPct": 50,
"status": "fail",
"blockers": 1,
"warnings": 4,
"blockerDetails": [
"Cycle Count: baseline=7 → candidate=11 (增加 57.14%,超过严重阈值 50%)"
],
"warningDetails": [
"Bloc Count: baseline=6 → candidate=9 (增加 50%,超过警示阈值 20%)",
"Candidate Count: baseline=38 → candidate=57 (增加 50%,超过警示阈值 20%)",
"Active Candidate Count: baseline=30 → candidate=45 (增加 50%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=8 → candidate=12 (增加 50%,超过警示阈值 20%)"
]
},
{
"baseline": "v2",
"deltaPct": 70,
"status": "fail",
"blockers": 5,
"warnings": 0,
"blockerDetails": [
"Bloc Count: baseline=6 → candidate=10 (增加 66.67%,超过严重阈值 50%)",
"Cycle Count: baseline=7 → candidate=12 (增加 71.43%,超过严重阈值 50%)",
"Candidate Count: baseline=38 → candidate=65 (增加 71.05%,超过严重阈值 50%)",
"Active Candidate Count: baseline=30 → candidate=51 (增加 70%,超过严重阈值 50%)",
"Archived Candidate Count: baseline=8 → candidate=14 (增加 75%,超过严重阈值 50%)"
],
"warningDetails": []
},
{
"baseline": "v3",
"deltaPct": 5,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v3",
"deltaPct": 10,
"status": "pass",
"blockers": 0,
"warnings": 0,
"blockerDetails": [],
"warningDetails": []
},
{
"baseline": "v3",
"deltaPct": 20,
"status": "warn",
"blockers": 0,
"warnings": 1,
"blockerDetails": [],
"warningDetails": [
"Cycle Count: baseline=9 → candidate=11 (增加 22.22%,超过警示阈值 20%)"
]
},
{
"baseline": "v3",
"deltaPct": 30,
"status": "warn",
"blockers": 0,
"warnings": 5,
"blockerDetails": [],
"warningDetails": [
"Bloc Count: baseline=7 → candidate=9 (增加 28.57%,超过警示阈值 20%)",
"Cycle Count: baseline=9 → candidate=12 (增加 33.33%,超过警示阈值 20%)",
"Candidate Count: baseline=46 → candidate=60 (增加 30.43%,超过警示阈值 20%)",
"Active Candidate Count: baseline=36 → candidate=47 (增加 30.56%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=10 → candidate=13 (增加 30%,超过警示阈值 20%)"
]
},
{
"baseline": "v3",
"deltaPct": 50,
"status": "fail",
"blockers": 2,
"warnings": 3,
"blockerDetails": [
"Bloc Count: baseline=7 → candidate=11 (增加 57.14%,超过严重阈值 50%)",
"Cycle Count: baseline=9 → candidate=14 (增加 55.56%,超过严重阈值 50%)"
],
"warningDetails": [
"Candidate Count: baseline=46 → candidate=69 (增加 50%,超过警示阈值 20%)",
"Active Candidate Count: baseline=36 → candidate=54 (增加 50%,超过警示阈值 20%)",
"Archived Candidate Count: baseline=10 → candidate=15 (增加 50%,超过警示阈值 20%)"
]
},
{
"baseline": "v3",
"deltaPct": 70,
"status": "fail",
"blockers": 5,
"warnings": 0,
"blockerDetails": [
"Bloc Count: baseline=7 → candidate=12 (增加 71.43%,超过严重阈值 50%)",
"Cycle Count: baseline=9 → candidate=15 (增加 66.67%,超过严重阈值 50%)",
"Candidate Count: baseline=46 → candidate=78 (增加 69.57%,超过严重阈值 50%)",
"Active Candidate Count: baseline=36 → candidate=61 (增加 69.44%,超过严重阈值 50%)",
"Archived Candidate Count: baseline=10 → candidate=17 (增加 70%,超过严重阈值 50%)"
],
"warningDetails": []
}
],
"summary": {
"totalScenarios": 18,
"falsePositives": 0,
"falseNegatives": 0,
"thresholdBoundaryCorrect": {
"at20": true,
"at50": true
},
"conclusion": "✅ Thresholds are stable. No false positives or false negatives detected."
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"generatedAt": "2026-06-04T19:45:50.155Z",
"version": "v1",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8,
"details": {
"coreBlocFiles": 6,
"projectFiles": 5,
"registerFiles": 7,
"topLevelFiles": 8,
"dailyNoteFiles": 8
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"generatedAt": "2026-06-04T20:00:31.444Z",
"version": "v2",
"previousVersion": "v1",
"growthMode": "normal",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 38,
"activeCandidateCount": 30,
"archivedCandidateCount": 8
},
"details": {
"coreBlocFiles": 6,
"projectFiles": 5,
"registerFiles": 7,
"topLevelFiles": 8,
"dailyNoteFiles": 8,
"evolvedFrom": "v1",
"growthApplied": "日常记忆积累 (3-8%)"
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"generatedAt": "2026-06-04T20:00:31.445Z",
"version": "v3",
"previousVersion": "v2",
"growthMode": "moderate",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 7,
"cycleCount": 9,
"candidateCount": 46,
"activeCandidateCount": 36,
"archivedCandidateCount": 10
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"currentVersion": "v3",
"path": "/Users/a1234/.openclaw/workspace/test/fixtures/baseline-rotation/v3-baseline.json",
"updatedAt": "2026-06-04T00:00:00.000Z",
"previousVersion": "v2",
"rotationId": "rotation-001"
}
+8
View File
@@ -0,0 +1,8 @@
{
"keepLatest": 3,
"archiveOlderThan": 3,
"requireGateOpen": true,
"requireCompatibilitySupported": true,
"allowWarnings": false,
"dryRun": true
}
+19
View File
@@ -0,0 +1,19 @@
{
"generatedAt": "2026-06-04T19:45:50.155Z",
"version": "v1",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8,
"details": {
"coreBlocFiles": 6,
"projectFiles": 5,
"registerFiles": 7,
"topLevelFiles": 8,
"dailyNoteFiles": 8
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"generatedAt": "2026-06-04T20:00:31.444Z",
"version": "v2",
"previousVersion": "v1",
"growthMode": "normal",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 38,
"activeCandidateCount": 30,
"archivedCandidateCount": 8
},
"details": {
"coreBlocFiles": 6,
"projectFiles": 5,
"registerFiles": 7,
"topLevelFiles": 8,
"dailyNoteFiles": 8,
"evolvedFrom": "v1",
"growthApplied": "日常记忆积累 (3-8%)"
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"generatedAt": "2026-06-04T20:00:31.445Z",
"version": "v3",
"previousVersion": "v2",
"growthMode": "moderate",
"workspace": "/Users/<user>/.openclaw/workspace",
"metrics": {
"blocCount": 7,
"cycleCount": 9,
"candidateCount": 46,
"activeCandidateCount": 36,
"archivedCandidateCount": 10
}
}
@@ -0,0 +1,48 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "废弃版本",
"scenarioId": 6,
"clawVersion": "0.1.0",
"baselineVersion": "",
"schemaStatus": "deprecated",
"pipelineStatus": "unknown",
"gateDecision": "unknown",
"compatibilityStatus": "unsupported",
"knownRisks": [
"使用旧目录结构 legacy-memory/,不在当前文件扫描路径中",
"无法运行 Baseline Compare",
"无法运行 Release Pipeline",
"遗留数据无迁移工具"
],
"recommendedAction": "migrate to Claw 1.0.x+ directory structure",
"dataStructure": {
"blocs": { "path": "legacy-memory/blocs/", "count": 4, "fields": ["id", "title", "content"], "note": "旧格式,字段名不同" },
"cycles": { "path": "legacy-memory/cycles/", "count": 5, "fields": ["id", "date", "notes"], "note": "旧格式,无结构化 entries" },
"candidates": { "path": "legacy-memory/candidates/", "count": 20, "fields": ["id", "text", "score"], "note": "旧格式,无 contentHash/type" }
},
"baselineMetrics": {
"blocCount": null,
"cycleCount": null,
"candidateCount": null,
"activeCandidateCount": null,
"archivedCandidateCount": null
},
"deltaFromBaseline": {
"blocCount": null,
"cycleCount": null,
"candidateCount": null,
"activeCandidateCount": null,
"archivedCandidateCount": null
},
"pipelineChecks": {
"rcChecklist": { "status": "unknown", "blockers": ["旧路径不在扫描范围"], "warnings": [] },
"baselineCompare": { "status": "unknown", "blockers": ["无 Baseline 数据"], "warnings": [] },
"releaseGate": { "decision": "unknown", "blockers": ["无法运行 Release Pipeline"], "warnings": [] }
}
}
@@ -0,0 +1,35 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "当前兼容版本",
"scenarioId": 1,
"clawVersion": "1.0.0",
"baselineVersion": "v1",
"schemaStatus": "compatible",
"pipelineStatus": "pass",
"gateDecision": "open",
"compatibilityStatus": "supported",
"knownRisks": [],
"recommendedAction": "safe to release",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries"] },
"candidates": { "path": "memory/candidates/*.md", "count": 36, "fields": ["id", "contentHash", "type", "status"] }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8
},
"pipelineChecks": {
"rcChecklist": { "status": "pass", "blockers": 0, "warnings": 0 },
"baselineCompare": { "status": "pass", "deltas": {}, "blockers": 0 },
"releaseGate": { "decision": "open", "blockers": 0 }
}
}
@@ -0,0 +1,49 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "缺失关键目录:memory/candidates 不存在",
"scenarioId": 9,
"clawVersion": "1.0.0",
"baselineVersion": "v1",
"schemaStatus": "breaking",
"pipelineStatus": "fail",
"gateDecision": "closed",
"compatibilityStatus": "migration_required",
"knownRisks": [
"memory/candidates 目录缺失,candidateCount = 0",
"Baseline Compare candidateCount delta = -36 (-100%),超过 FAIL 阈值",
"Release Gate 自动 CLOSED — 无法通过门禁",
"无法生成 Release Candidate Report — 关键指标缺失",
"可能是误删除或初始化未完成,需人工确认"
],
"recommendedAction": "restore memory/candidates/ from backup or re-run init-candidates.sh",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries"] },
"candidates": { "path": "memory/candidates/*.md", "count": 0, "fields": [], "note": "目录不存在或为空" }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": -36,
"activeCandidateCount": -28,
"archivedCandidateCount": -8
},
"pipelineChecks": {
"rcChecklist": { "status": "fail", "blockers": ["关键目录 memory/candidates 缺失,candidateCount = 0"], "warnings": [] },
"baselineCompare": { "status": "fail", "blockers": ["candidateCount delta = -100% (>50%), activeCandidateCount delta = -100%, archivedCandidateCount delta = -100%"], "warnings": [] },
"releaseGate": { "decision": "closed", "blockers": ["[Schema] memory/candidates 缺失 — 关键目录不可用", "[Checklist] 目录缺失,无法验证关键指标", "[Baseline] candidateCount delta = -100%, Gate CLOSED"] }
}
}
@@ -0,0 +1,46 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "小版本兼容变化:新增字段",
"scenarioId": 2,
"clawVersion": "1.1.0",
"baselineVersion": "v1",
"schemaStatus": "compatible",
"pipelineStatus": "pass",
"gateDecision": "open",
"compatibilityStatus": "supported",
"knownRisks": [
"新字段被上游消费者忽略,无影响",
"新增字段不会改变聚合计数"
],
"recommendedAction": "safe to release",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content", "metadata.priority"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries", "cycle.duration"] },
"candidates": { "path": "memory/candidates/*.md", "count": 36, "fields": ["id", "contentHash", "type", "status", "candidate.tags"] }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"pipelineChecks": {
"rcChecklist": { "status": "pass", "blockers": 0, "warnings": 0 },
"baselineCompare": { "status": "pass", "blockers": 0, "deltas": { "blocCount": 0, "cycleCount": 0, "candidateCount": 0, "activeCandidateCount": 0, "archivedCandidateCount": 0 } },
"releaseGate": { "decision": "open", "blockers": 0 }
}
}
@@ -0,0 +1,47 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "字段重命名",
"scenarioId": 3,
"clawVersion": "1.2.0",
"baselineVersion": "v1",
"schemaStatus": "compatible",
"pipelineStatus": "pass",
"gateDecision": "open",
"compatibilityStatus": "supported",
"knownRisks": [
"bloc.id → bloc.blocId:上游消费者需要同步字段名",
"cycle.id → cycle.cycleIdBaseline 聚合不受影响",
"candidate.contentHash → candidate.hash:影响深拷贝一致性比对"
],
"recommendedAction": "safe to release, notify consumers",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["blocId", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["cycleId", "date", "entries"] },
"candidates": { "path": "memory/candidates/*.md", "count": 36, "fields": ["id", "hash", "type", "status"] }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"pipelineChecks": {
"rcChecklist": { "status": "pass", "blockers": 0, "warnings": 0 },
"baselineCompare": { "status": "pass", "blockers": 0, "deltas": { "blocCount": 0, "cycleCount": 0, "candidateCount": 0, "activeCandidateCount": 0, "archivedCandidateCount": 0 } },
"releaseGate": { "decision": "open", "blockers": 0 }
}
}
@@ -0,0 +1,48 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "新增目录",
"scenarioId": 4,
"clawVersion": "1.3.0",
"baselineVersion": "v1",
"schemaStatus": "compatible",
"pipelineStatus": "pass",
"gateDecision": "open",
"compatibilityStatus": "supported",
"knownRisks": [
"candidateCount 从 36 → 46 (+28%) 超过 WARN 阈值 (20%),可能触发 Baseline Compare WARN",
"activeCandidateCount 从 28 → 36 (+29%) 超过 WARN 阈值",
"新增 memory/files/ 目录未被 Baseline 覆盖,需要更新 Baseline 版本到 v2"
],
"recommendedAction": "safe to release, update baseline to v2",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries"] },
"candidates": { "path": "memory/candidates/*.md", "count": 46, "fields": ["id", "contentHash", "type", "status"] },
"files": { "path": "memory/files/*.md", "count": 10, "fields": ["id", "filename", "size", "mimeType"] }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 46,
"activeCandidateCount": 36,
"archivedCandidateCount": 10
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 10,
"activeCandidateCount": 8,
"archivedCandidateCount": 2
},
"pipelineChecks": {
"rcChecklist": { "status": "pass", "blockers": 0, "warnings": 0 },
"baselineCompare": { "status": "warn", "blockers": 0, "warnings": ["candidateCount +28%, activeCandidateCount +29% exceed WARN threshold"] },
"releaseGate": { "decision": "open", "blockers": 0, "warnings": ["[Baseline] candidateCount 增加 28%,超过警示阈值 20%"] }
}
}
@@ -0,0 +1,49 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "中间件兼容层",
"scenarioId": 7,
"clawVersion": "1.4.0",
"baselineVersion": "v2",
"schemaStatus": "compatible",
"pipelineStatus": "pass",
"gateDecision": "open",
"compatibilityStatus": "supported",
"knownRisks": [
"兼容层文件 (memory/adapters/) 可能被 candidateCount 统计到,需更新扫描器排除规则",
"etc/claw-migration.config 被 Pipeline 静默忽略",
"Baseline v2 尚未生成,需要先运行 build-stable-baseline"
],
"recommendedAction": "safe to release, update baseline to v2",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries"] },
"candidates": { "path": "memory/candidates/*.md", "count": 36, "fields": ["id", "contentHash", "type", "status"] },
"adapters": { "path": "memory/adapters/", "count": 3, "fields": ["source", "target", "transform"] },
"config": { "path": "etc/claw-migration.config", "count": 1, "format": "TOML" }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"pipelineChecks": {
"rcChecklist": { "status": "pass", "blockers": 0, "warnings": 0 },
"baselineCompare": { "status": "pass", "blockers": 0, "deltas": { "blocCount": 0, "cycleCount": 0, "candidateCount": 0, "activeCandidateCount": 0, "archivedCandidateCount": 0 } },
"releaseGate": { "decision": "open", "blockers": 0 }
}
}
@@ -0,0 +1,49 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "目录结构变化:memory/candidates → memory/items",
"scenarioId": 8,
"clawVersion": "1.5.0",
"baselineVersion": "v1",
"schemaStatus": "compatible",
"pipelineStatus": "warn",
"gateDecision": "open",
"compatibilityStatus": "compatible_with_warnings",
"knownRisks": [
"memory/candidates 重命名为 memory/itemsPipeline 的 candidates 扫描路径需要更新",
"Baseline 中 candidateCount 基于 memory/candidates/ 计算,新路径下统计为 0",
"如果扫描器未更新,candidateCount 将显示为 0 → Baseline Compare delta = -100%FAIL",
"迁移后需要对 Baseline 做增量更新(v1 → v2),保留历史 candidates 计数",
"上游消费者(RC Checklist、Gate)依赖 candidateCount 做决策,可能触发 CLOSED"
],
"recommendedAction": "update scanner path rules (candidates → items) + rebuild baseline before release",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.md", "count": 6, "fields": ["id", "title", "content"] },
"cycles": { "path": "memory/cycles/*.md", "count": 7, "fields": ["date", "entries"] },
"candidates": { "path": "memory/items/*.md", "count": 36, "fields": ["id", "contentHash", "type", "status"] }
},
"baselineMetrics": {
"blocCount": 6,
"cycleCount": 7,
"candidateCount": 36,
"activeCandidateCount": 28,
"archivedCandidateCount": 8
},
"deltaFromBaseline": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"pipelineChecks": {
"rcChecklist": { "status": "warn", "blockers": 0, "warnings": ["memory/candidates 路径变更 → memory/items", "扫描路径需同步更新,否则 count = 0"] },
"baselineCompare": { "status": "warn", "blockers": 0, "deltas": { "blocCount": 0, "cycleCount": 0, "candidateCount": 0, "activeCandidateCount": 0, "archivedCandidateCount": 0 }, "warnings": ["candidate 扫描路径已更新,无 delta;如未更新将触发 FAIL"] },
"releaseGate": { "decision": "open", "blockers": 0, "warnings": ["[Schema] memory/candidates → memory/items 路径变更,建议更新扫描器并重建 Baseline"] }
}
}
@@ -0,0 +1,49 @@
{
"$schema": "compatibility-matrix-entry-v1",
"generatedAt": "2026-06-05T00:00:00.000Z",
"scenario": "不兼容 Schema 变更:Markdown → JSON 格式变化",
"scenarioId": 5,
"clawVersion": "2.0.0",
"baselineVersion": "",
"schemaStatus": "breaking",
"pipelineStatus": "fail",
"gateDecision": "closed",
"compatibilityStatus": "unsupported",
"knownRisks": [
"文件格式从 Markdown (.md) 变更为 JSON (.json),文件扫描器无法解析 Markdown 解析器",
"Baseline Compare delta > 50%Gate 自动 CLOSED",
"所有既有 Pipeline 检查项均报 FAIL",
"需要同步更新 Baseline 版本和文件扫描逻辑",
"迁移后需验证 JSON Schema 与数据结构一致性"
],
"recommendedAction": "update baseline scanner (add JSON parser) + rebuild baseline before release",
"dataStructure": {
"blocs": { "path": "memory/blocs/*.json", "count": 0, "fields": [], "note": "格式变更:.md → .json,当前扫描器无法识别" },
"cycles": { "path": "memory/cycles/*.json", "count": 0, "fields": [], "note": "格式变更:.md → .json" },
"candidates": { "path": "memory/candidates/*.json", "count": 0, "fields": [], "note": "格式变更:.md → .json" }
},
"baselineMetrics": {
"blocCount": 0,
"cycleCount": 0,
"candidateCount": 0,
"activeCandidateCount": 0,
"archivedCandidateCount": 0
},
"deltaFromBaseline": {
"blocCount": -6,
"cycleCount": -7,
"candidateCount": -36,
"activeCandidateCount": -28,
"archivedCandidateCount": -8
},
"pipelineChecks": {
"rcChecklist": { "status": "fail", "blockers": ["文件格式变更:.md → .json", "扫描器无法解析 JSON 格式"], "warnings": [] },
"baselineCompare": { "status": "fail", "blockers": ["所有指标 delta > 50%", "文件计数归零 → 格式不兼容"], "warnings": [] },
"releaseGate": { "decision": "closed", "blockers": ["Schema breaking change — 文件格式变更导致指标严重偏离基线", "[Checklist] 文件格式变更,建议迁移适配器", "[Baseline] Baseline Compare FAIL — 全部指标缺失"] }
}
}
@@ -0,0 +1,81 @@
{
"description": "Critical agent with severe health drop, certification downgrade, and unsupported compatibility — triggers multiple critical alerts",
"monitoringSnapshots": [
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-01T00:00:00.000Z",
"healthScore": 75,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "supported",
"stabilityScore": 80,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-02T00:00:00.000Z",
"healthScore": 60,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 65,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 0
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 35,
"riskLevel": "high",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 40,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 1
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 15,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 20,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 2,
"compatibilityFailures": 2,
"releaseFailures": 2
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 10,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 15,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 3,
"compatibilityFailures": 3,
"releaseFailures": 3
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,67 @@
{
"description": "Degrading agent with health declining over time — warnings accumulating, 4 snapshots showing steady decline",
"monitoringSnapshots": [
{
"agentId": "claw-staging",
"timestamp": "2026-06-01T00:00:00.000Z",
"healthScore": 88,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 95,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-02T00:00:00.000Z",
"healthScore": 78,
"riskLevel": "medium",
"certificationStatus": "production_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 85,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 1,
"releaseFailures": 0
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 65,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 70,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 1
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 52,
"riskLevel": "high",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 55,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 1,
"compatibilityFailures": 2,
"releaseFailures": 2
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,67 @@
{
"description": "Healthy agent with stable health over time — all indicators green, 4 snapshots showing consistent high scores",
"monitoringSnapshots": [
{
"agentId": "claw-prod",
"timestamp": "2026-06-01T00:00:00.000Z",
"healthScore": 95,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-prod",
"timestamp": "2026-06-02T00:00:00.000Z",
"healthScore": 97,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-prod",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 96,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-prod",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 98,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,35 @@
{
"description": "Agent with missing/unknown data fields — tests that missing fields are not assumed healthy",
"monitoringSnapshots": [
{
"agentId": "incomplete-sdk",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 85,
"riskLevel": "unknown",
"certificationStatus": "unknown",
"compatibilityStatus": "unknown",
"releaseResult": "unknown",
"rollbacks": 0,
"releaseFailures": 0,
"compatibilityFailures": 0
},
{
"agentId": "incomplete-sdk",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 82,
"riskLevel": "unknown",
"certificationStatus": "unknown",
"compatibilityStatus": "unknown",
"releaseResult": "unknown",
"rollbacks": 0,
"releaseFailures": 0,
"compatibilityFailures": 0
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,221 @@
{
"description": "Mixed fleet with 6 agents across healthy, degrading, critical, and recovery states — 3 snapshots over time",
"monitoringSnapshots": [
{
"agentId": "claw-prod",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 95,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 72,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 75,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 0
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 35,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 40,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 2,
"compatibilityFailures": 2,
"releaseFailures": 2
},
{
"agentId": "claw-canary",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 88,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 92,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "mock-staging",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 60,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 65,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 0
},
{
"agentId": "unknown-sdk-v1",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 20,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 25,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 3,
"compatibilityFailures": 2,
"releaseFailures": 2
},
{
"agentId": "claw-prod",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 96,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 65,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 68,
"status": "degrading",
"releaseResult": "failed",
"rollbacks": 1,
"compatibilityFailures": 1,
"releaseFailures": 1
},
{
"agentId": "old-claw-beta",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 30,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 35,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 3,
"compatibilityFailures": 3,
"releaseFailures": 3
},
{
"agentId": "claw-canary",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 90,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 94,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "mock-staging",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 55,
"riskLevel": "high",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 58,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 2,
"compatibilityFailures": 2,
"releaseFailures": 1
},
{
"agentId": "unknown-sdk-v1",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 15,
"riskLevel": "high",
"certificationStatus": "not_ready",
"compatibilityStatus": "unsupported",
"stabilityScore": 20,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 4,
"compatibilityFailures": 3,
"releaseFailures": 3
},
{
"agentId": "claw-prod",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 97,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 100,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "claw-staging",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 58,
"riskLevel": "high",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 60,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 2,
"compatibilityFailures": 2,
"releaseFailures": 2
},
{
"agentId": "claw-canary",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 92,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 96,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,81 @@
{
"description": "Agent with sudden rollback spike — stable for most history then abrupt increase in rollbacks",
"monitoringSnapshots": [
{
"agentId": "unstable-mock",
"timestamp": "2026-06-01T00:00:00.000Z",
"healthScore": 85,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 90,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "unstable-mock",
"timestamp": "2026-06-02T00:00:00.000Z",
"healthScore": 82,
"riskLevel": "low",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 88,
"status": "healthy",
"releaseResult": "success",
"rollbacks": 0,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "unstable-mock",
"timestamp": "2026-06-03T00:00:00.000Z",
"healthScore": 75,
"riskLevel": "medium",
"certificationStatus": "production_ready",
"compatibilityStatus": "supported",
"stabilityScore": 78,
"status": "degrading",
"releaseResult": "success",
"rollbacks": 1,
"compatibilityFailures": 0,
"releaseFailures": 0
},
{
"agentId": "unstable-mock",
"timestamp": "2026-06-04T00:00:00.000Z",
"healthScore": 55,
"riskLevel": "medium",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "supported",
"stabilityScore": 58,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 3,
"compatibilityFailures": 1,
"releaseFailures": 1
},
{
"agentId": "unstable-mock",
"timestamp": "2026-06-05T00:00:00.000Z",
"healthScore": 40,
"riskLevel": "high",
"certificationStatus": "conditionally_ready",
"compatibilityStatus": "compatible_with_warnings",
"stabilityScore": 42,
"status": "at_risk",
"releaseResult": "failed",
"rollbacks": 5,
"compatibilityFailures": 2,
"releaseFailures": 2
}
],
"configuration": {
"criticalHealthDropPercent": 30,
"highHealthDropPercent": 15,
"trendWindowSize": 3,
"consecutiveFailureThreshold": 3
}
}
@@ -0,0 +1,31 @@
{
"description": "All agents healthy — should certify as production_ready",
"healthReport": [
{
"agentId": "claw-prod",
"healthScore": 100,
"riskLevel": "low",
"scoreBreakdown": {"release":30,"compatibility":25,"certification":20,"baseline":15,"operations":10},
"stability": 100,
"status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "mock-dev",
"healthScore": 95,
"riskLevel": "low",
"scoreBreakdown": {"release":30,"compatibility":25,"certification":20,"baseline":15,"operations":5},
"stability": 90,
"status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
}
],
"riskReport": [
{"agentId":"claw-prod","riskLevel":"low","healthScore":100,"reasons":["all indicators healthy"]},
{"agentId":"mock-dev","riskLevel":"low","healthScore":95,"reasons":["all indicators healthy"]}
],
"trendReport": [
{"agentId":"claw-prod","trend":"stable","delta":0,"currentScore":100,"previousScore":100},
{"agentId":"mock-dev","trend":"stable","delta":0,"currentScore":95,"previousScore":95}
]
}
@@ -0,0 +1,41 @@
{
"description": "Conditionally ready fleet: meets ≥70% threshold but not ≥90%",
"healthReport": [
{
"agentId": "prod-a", "healthScore": 95, "riskLevel": "low",
"scoreBreakdown": {"release":30,"compatibility":25,"certification":20,"baseline":15,"operations":5},
"stability": 92, "status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "prod-b", "healthScore": 90, "riskLevel": "low",
"scoreBreakdown": {"release":30,"compatibility":25,"certification":20,"baseline":10,"operations":5},
"stability": 85, "status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "prod-c", "healthScore": 85, "riskLevel": "low",
"scoreBreakdown": {"release":25,"compatibility":25,"certification":20,"baseline":10,"operations":5},
"stability": 88, "status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "warn-d", "healthScore": 55, "riskLevel": "medium",
"scoreBreakdown": {"release":15,"compatibility":15,"certification":10,"baseline":10,"operations":5},
"stability": 70, "status": "degrading",
"lastUpdated": "2026-06-05T00:00:00.000Z"
}
],
"riskReport": [
{"agentId":"prod-a","riskLevel":"low","healthScore":95,"reasons":["all indicators healthy"]},
{"agentId":"prod-b","riskLevel":"low","healthScore":90,"reasons":["all indicators healthy"]},
{"agentId":"prod-c","riskLevel":"low","healthScore":85,"reasons":["all indicators healthy"]},
{"agentId":"warn-d","riskLevel":"medium","healthScore":55,"reasons":["compatibility has warnings","certification conditionally ready"]}
],
"trendReport": [
{"agentId":"prod-a","trend":"stable","delta":0,"currentScore":95,"previousScore":95},
{"agentId":"prod-b","trend":"stable","delta":0,"currentScore":90,"previousScore":90},
{"agentId":"prod-c","trend":"stable","delta":0,"currentScore":85,"previousScore":85},
{"agentId":"warn-d","trend":"degrading","delta":-15,"currentScore":55,"previousScore":65}
]
}
@@ -0,0 +1,27 @@
{
"description": "High risk fleet — most agents at high risk, should be not_ready",
"healthReport": [
{
"agentId": "unsupported-legacy",
"healthScore": 5, "riskLevel": "high",
"scoreBreakdown": {"release":0,"compatibility":0,"certification":0,"baseline":5,"operations":0},
"stability": 30, "status": "at_risk",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "failing-agent",
"healthScore": 15, "riskLevel": "high",
"scoreBreakdown": {"release":0,"compatibility":5,"certification":0,"baseline":5,"operations":5},
"stability": 40, "status": "degrading",
"lastUpdated": "2026-06-05T00:00:00.000Z"
}
],
"riskReport": [
{"agentId":"unsupported-legacy","riskLevel":"high","healthScore":5,"reasons":["agent unsupported","compatibility unsupported","3 release failures"]},
{"agentId":"failing-agent","riskLevel":"high","healthScore":15,"reasons":["certification not_ready","2 release failures","2 rollbacks"]}
],
"trendReport": [
{"agentId":"unsupported-legacy","trend":"degrading","delta":-80,"currentScore":5,"previousScore":25},
{"agentId":"failing-agent","trend":"degrading","delta":-70,"currentScore":15,"previousScore":50}
]
}
@@ -0,0 +1,36 @@
{
"description": "Mixed fleet: healthy, degrading, high risk agents — conditionally_ready",
"healthReport": [
{
"agentId": "claw-prod",
"healthScore": 100, "riskLevel": "low",
"scoreBreakdown": {"release":30,"compatibility":25,"certification":20,"baseline":15,"operations":10},
"stability": 100, "status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "mock-staging",
"healthScore": 65, "riskLevel": "medium",
"scoreBreakdown": {"release":20,"compatibility":15,"certification":10,"baseline":15,"operations":5},
"stability": 78, "status": "degrading",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "old-claw-beta",
"healthScore": 10, "riskLevel": "high",
"scoreBreakdown": {"release":0,"compatibility":0,"certification":0,"baseline":5,"operations":5},
"stability": 50, "status": "at_risk",
"lastUpdated": "2026-06-05T00:00:00.000Z"
}
],
"riskReport": [
{"agentId":"claw-prod","riskLevel":"low","healthScore":100,"reasons":["all indicators healthy"]},
{"agentId":"mock-staging","riskLevel":"medium","healthScore":65,"reasons":["compatibility has warnings","certification is conditionally ready"]},
{"agentId":"old-claw-beta","riskLevel":"high","healthScore":10,"reasons":["agent unsupported","compatibility unsupported","certification not_ready"]}
],
"trendReport": [
{"agentId":"claw-prod","trend":"stable","delta":0,"currentScore":100,"previousScore":100},
{"agentId":"mock-staging","trend":"degrading","delta":-35,"currentScore":65,"previousScore":100},
{"agentId":"old-claw-beta","trend":"degrading","delta":-50,"currentScore":10,"previousScore":20}
]
}
@@ -0,0 +1,25 @@
{
"description": "Not ready fleet: too many failing agents",
"healthReport": [
{
"agentId": "fail-1", "healthScore": 10, "riskLevel": "high",
"scoreBreakdown": {"release":0,"compatibility":0,"certification":0,"baseline":5,"operations":5},
"stability": 25, "status": "at_risk",
"lastUpdated": "2026-06-05T00:00:00.000Z"
},
{
"agentId": "ok-2", "healthScore": 80, "riskLevel": "low",
"scoreBreakdown": {"release":25,"compatibility":25,"certification":20,"baseline":5,"operations":5},
"stability": 75, "status": "healthy",
"lastUpdated": "2026-06-05T00:00:00.000Z"
}
],
"riskReport": [
{"agentId":"fail-1","riskLevel":"high","healthScore":10,"reasons":["agent unsupported","compatibility unsupported","4 release failures"]},
{"agentId":"ok-2","riskLevel":"low","healthScore":80,"reasons":["all indicators healthy"]}
],
"trendReport": [
{"agentId":"fail-1","trend":"degrading","delta":-50,"currentScore":10,"previousScore":20},
{"agentId":"ok-2","trend":"stable","delta":0,"currentScore":80,"previousScore":80}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"description": "Empty input — no alerts, no existing incidents",
"alerts": [],
"existingIncidents": [],
"configuration": {}
}
@@ -0,0 +1,14 @@
{
"description": "Existing incidents with various lifecycle states for state transition testing",
"alerts": [
{"alertId":"monitor-010","severity":"critical","agentId":"claw-prod","reason":"consecutive_release_failure","details":"4 failures","timestamp":"2026-06-05T00:00:00.000Z","source":"continuous-monitoring"}
],
"existingIncidents": [
{"incidentId":"inc-001","agentId":"claw-prod","severity":"critical","status":"open","source":"compatibility_unsupported","createdAt":"2026-06-03T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":null},
{"incidentId":"inc-002","agentId":"claw-prod","severity":"critical","status":"investigating","source":"health_score_drop_critical","createdAt":"2026-06-03T01:00:00.000Z","updatedAt":"2026-06-03T02:00:00.000Z","resolvedAt":null},
{"incidentId":"inc-003","agentId":"claw-prod","severity":"high","status":"recovering","source":"health_score_drop_high","createdAt":"2026-06-02T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":null,"recoveryProgress":60},
{"incidentId":"inc-004","agentId":"old-claw-beta","severity":"critical","status":"resolved","source":"compatibility_unsupported","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":"2026-06-03T00:00:00.000Z"},
{"incidentId":"inc-005","agentId":"old-claw-beta","severity":"high","status":"closed","source":"health_score_drop_high","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":"2026-06-04T00:00:00.000Z"}
],
"configuration": {}
}
@@ -0,0 +1,17 @@
{
"description": "Mixed fleet incidents for comprehensive report generation",
"alerts": [
{"alertId":"monitor-030","severity":"critical","agentId":"claw-prod","reason":"compatibility_unsupported","details":"Compat unsupported on prod","timestamp":"2026-06-05T00:00:00.000Z","source":"continuous-monitoring"},
{"alertId":"monitor-031","severity":"high","agentId":"mock-staging","reason":"health_score_drop_high","details":"Score dropped 20%","timestamp":"2026-06-05T01:00:00.000Z","source":"continuous-monitoring"}
],
"existingIncidents": [
{"incidentId":"inc-030","agentId":"claw-prod","severity":"critical","status":"open","source":"health_score_drop_critical","createdAt":"2026-06-04T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":null},
{"incidentId":"inc-031","agentId":"claw-staging","severity":"high","status":"recovering","source":"health_score_drop_high","createdAt":"2026-06-03T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":null,"recoveryProgress":60},
{"incidentId":"inc-032","agentId":"old-claw-beta","severity":"critical","status":"open","source":"consecutive_release_failure","createdAt":"2026-06-03T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":null},
{"incidentId":"inc-033","agentId":"old-claw-beta","severity":"critical","status":"resolved","source":"compatibility_unsupported","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-02T00:00:00.000Z","resolvedAt":"2026-06-02T00:00:00.000Z"},
{"incidentId":"inc-034","agentId":"old-claw-beta","severity":"high","status":"resolved","source":"health_score_drop_high","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":"2026-06-03T00:00:00.000Z"},
{"incidentId":"inc-035","agentId":"old-claw-beta","severity":"critical","status":"closed","source":"consecutive_release_failure","createdAt":"2026-05-30T00:00:00.000Z","updatedAt":"2026-06-01T00:00:00.000Z","resolvedAt":"2026-06-01T00:00:00.000Z"},
{"incidentId":"inc-036","agentId":"unknown-sdk-v1","severity":"critical","status":"open","source":"compatibility_unsupported","createdAt":"2026-06-02T00:00:00.000Z","updatedAt":"2026-06-02T00:00:00.000Z","resolvedAt":null}
],
"configuration": {}
}
@@ -0,0 +1,12 @@
{
"description": "Multiple critical incidents from monitoring alerts",
"alerts": [
{"alertId":"monitor-001","severity":"critical","agentId":"claw-prod","reason":"health_score_drop_critical","details":"Score dropped 35%","timestamp":"2026-06-04T00:00:00.000Z","source":"continuous-monitoring"},
{"alertId":"monitor-002","severity":"critical","agentId":"claw-prod","reason":"compatibility_unsupported","details":"Compat unsupported","timestamp":"2026-06-04T01:00:00.000Z","source":"continuous-monitoring"},
{"alertId":"monitor-003","severity":"critical","agentId":"old-claw-beta","reason":"consecutive_release_failure","details":"3 consecutive failures","timestamp":"2026-06-04T02:00:00.000Z","source":"continuous-monitoring"},
{"alertId":"monitor-004","severity":"critical","agentId":"unknown-sdk-v1","reason":"certification_downgraded_to_not_ready","details":"Cert downgraded","timestamp":"2026-06-04T03:00:00.000Z","source":"continuous-monitoring"},
{"alertId":"monitor-005","severity":"high","agentId":"claw-staging","reason":"health_score_drop_high","details":"Score dropped 18%","timestamp":"2026-06-04T04:00:00.000Z","source":"continuous-monitoring"}
],
"existingIncidents": [],
"configuration": {}
}
@@ -0,0 +1,12 @@
{
"description": "Recovery tracking test — incidents in recovering state with progress values",
"alerts": [],
"existingIncidents": [
{"incidentId":"inc-010","agentId":"claw-prod","severity":"critical","status":"recovering","source":"compatibility_unsupported","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":null,"recoveryProgress":30},
{"incidentId":"inc-011","agentId":"claw-staging","severity":"high","status":"recovering","source":"health_score_drop_high","createdAt":"2026-06-02T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":null,"recoveryProgress":75},
{"incidentId":"inc-012","agentId":"old-claw-beta","severity":"critical","status":"recovering","source":"consecutive_release_failure","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":null,"recoveryProgress":100},
{"incidentId":"inc-013","agentId":"unknown-sdk-v1","severity":"critical","status":"resolved","source":"compatibility_unsupported","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":"2026-06-04T00:00:00.000Z"},
{"incidentId":"inc-014","agentId":"unknown-sdk-v1","severity":"high","status":"resolved","source":"health_score_drop_high","createdAt":"2026-06-02T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":"2026-06-04T12:00:00.000Z"}
],
"configuration": {}
}
@@ -0,0 +1,14 @@
{
"description": "Agent with many incidents — triggers repeat offender detection (≥3 incidents per agent)",
"alerts": [
{"alertId":"monitor-020","severity":"critical","agentId":"old-claw-beta","reason":"compatibility_unsupported","details":"test","timestamp":"2026-06-05T00:00:00.000Z","source":"continuous-monitoring"}
],
"existingIncidents": [
{"incidentId":"inc-020","agentId":"old-claw-beta","severity":"critical","status":"open","source":"health_score_drop_critical","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-01T00:00:00.000Z","resolvedAt":null},
{"incidentId":"inc-021","agentId":"old-claw-beta","severity":"critical","status":"resolved","source":"consecutive_release_failure","createdAt":"2026-06-02T00:00:00.000Z","updatedAt":"2026-06-03T00:00:00.000Z","resolvedAt":"2026-06-03T00:00:00.000Z"},
{"incidentId":"inc-022","agentId":"old-claw-beta","severity":"high","status":"resolved","source":"health_score_drop_high","createdAt":"2026-06-03T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":"2026-06-04T00:00:00.000Z"},
{"incidentId":"inc-023","agentId":"claw-prod","severity":"critical","status":"resolved","source":"compatibility_unsupported","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-02T00:00:00.000Z","resolvedAt":"2026-06-02T00:00:00.000Z"},
{"incidentId":"inc-024","agentId":"claw-prod","severity":"high","status":"closed","source":"health_score_drop_high","createdAt":"2026-06-01T00:00:00.000Z","updatedAt":"2026-06-04T00:00:00.000Z","resolvedAt":"2026-06-04T00:00:00.000Z"}
],
"configuration": {}
}
@@ -0,0 +1,41 @@
{
"description": "All agents healthy — every agent is low risk",
"registry": {
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "mock-dev",
"agentType": "mock",
"displayName": "Mock Development",
"status": "active",
"owner": "dev-team",
"workspacePath": "test/fixtures/agent-adapters/mock-agent",
"adapter": "mock",
"baselineVersion": "v1",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-03T00:00:00.000Z"
}
]
},
"releaseHistory": [
{"releaseId":"rel-001","agentId":"claw-prod","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-04T00:00:00.000Z"},
{"releaseId":"rel-002","agentId":"claw-prod","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-03T00:00:00.000Z"},
{"releaseId":"rel-003","agentId":"mock-dev","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-02T00:00:00.000Z"}
],
"certifications": {
"claw-prod": {"status":"production_ready","governanceGrade":"A","governanceScore":100,"riskLevel":"medium","certifiedAt":"2026-06-04T00:00:00.000Z","baselineVersion":"v4"},
"mock-dev": {"status":"production_ready","governanceGrade":"A","governanceScore":95,"riskLevel":"low","certifiedAt":"2026-06-03T00:00:00.000Z","baselineVersion":"v1"}
}
}
@@ -0,0 +1,38 @@
{
"description": "Key data missing — certification and release history absent",
"registry": {
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "new-agent",
"agentType": "mock",
"displayName": "New Uncertified Agent",
"status": "active",
"owner": "dev-team",
"workspacePath": "test/fixtures/agent-adapters/mock-agent",
"adapter": "mock",
"baselineVersion": "",
"compatibilityStatus": "unknown",
"lastCertifiedAt": ""
}
]
},
"releaseHistory": [
{"releaseId":"rel-001","agentId":"claw-prod","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-04T00:00:00.000Z"}
],
"certifications": {
"claw-prod": {"status":"production_ready","governanceGrade":"A","governanceScore":100,"riskLevel":"low","certifiedAt":"2026-06-04T00:00:00.000Z","baselineVersion":"v4"}
}
}
@@ -0,0 +1,67 @@
{
"description": "Mixed risk: low, medium, high, unknown agents",
"registry": {
"schemaVersion": "1.0",
"agents": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"owner": "team-memory",
"workspacePath": "memory/",
"adapter": "claw",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"lastCertifiedAt": "2026-06-04T00:00:00.000Z"
},
{
"agentId": "mock-staging",
"agentType": "mock",
"displayName": "Mock Staging",
"status": "active",
"owner": "dev-team",
"workspacePath": "test/fixtures/agent-adapters/mock-agent",
"adapter": "mock",
"baselineVersion": "v1",
"compatibilityStatus": "compatible_with_warnings",
"lastCertifiedAt": "2026-06-01T00:00:00.000Z"
},
{
"agentId": "old-claw-beta",
"agentType": "claw",
"displayName": "Old Claw Beta",
"status": "unsupported",
"owner": "team-memory",
"workspacePath": "legacy-memory/",
"adapter": "claw",
"baselineVersion": "v1",
"compatibilityStatus": "unsupported",
"lastCertifiedAt": "2025-06-01T00:00:00.000Z"
},
{
"agentId": "unknown-sdk-v1",
"agentType": "unknown",
"displayName": "Unknown SDK Agent",
"status": "unsupported",
"owner": "external-team",
"workspacePath": "legacy-data/",
"adapter": "none",
"baselineVersion": "unknown",
"compatibilityStatus": "unsupported",
"lastCertifiedAt": "2025-12-01T00:00:00.000Z"
}
]
},
"releaseHistory": [
{"releaseId":"rel-001","agentId":"claw-prod","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-04T00:00:00.000Z"},
{"releaseId":"rel-002","agentId":"claw-prod","result":"success","gateDecision":"open","compatibilityStatus":"supported","timestamp":"2026-06-03T00:00:00.000Z"},
{"releaseId":"rel-003","agentId":"mock-staging","result":"success","gateDecision":"warn","compatibilityStatus":"compatible_with_warnings","timestamp":"2026-06-02T00:00:00.000Z"},
{"releaseId":"rel-004","agentId":"old-claw-beta","result":"failed","gateDecision":"closed","compatibilityStatus":"unsupported","timestamp":"2025-06-01T00:00:00.000Z"}
],
"certifications": {
"claw-prod": {"status":"production_ready","governanceGrade":"A","governanceScore":100,"riskLevel":"medium","certifiedAt":"2026-06-04T00:00:00.000Z","baselineVersion":"v4"},
"mock-staging": {"status":"conditionally_ready","governanceGrade":"B","governanceScore":75,"riskLevel":"medium","certifiedAt":"2026-06-01T00:00:00.000Z","baselineVersion":"v1"},
"old-claw-beta": {"status":"not_ready","governanceGrade":"F","governanceScore":20,"riskLevel":"high","certifiedAt":"2025-06-01T00:00:00.000Z","baselineVersion":"v1"}
}
}
@@ -0,0 +1,57 @@
{
"description": "Expected output for multi-agent dashboard with mixed risk",
"dashboard": [
{
"agentId": "claw-prod",
"agentType": "claw",
"displayName": "Claw Production",
"status": "active",
"baselineVersion": "v4",
"compatibilityStatus": "supported",
"certificationStatus": "production_ready",
"lastReleaseResult": "success",
"riskLevel": "low"
},
{
"agentId": "mock-staging",
"agentType": "mock",
"displayName": "Mock Staging",
"status": "active",
"baselineVersion": "v1",
"compatibilityStatus": "compatible_with_warnings",
"certificationStatus": "conditionally_ready",
"lastReleaseResult": "success",
"riskLevel": "medium"
},
{
"agentId": "old-claw-beta",
"agentType": "claw",
"displayName": "Old Claw Beta",
"status": "unsupported",
"baselineVersion": "v1",
"compatibilityStatus": "unsupported",
"certificationStatus": "not_ready",
"lastReleaseResult": "failed",
"riskLevel": "high"
},
{
"agentId": "unknown-sdk-v1",
"agentType": "unknown",
"displayName": "Unknown SDK Agent",
"status": "unsupported",
"baselineVersion": "unknown",
"compatibilityStatus": "unsupported",
"certificationStatus": "unknown",
"lastReleaseResult": "unknown",
"riskLevel": "high"
}
],
"summary": {
"totalAgents": 4,
"activeAgents": 2,
"productionReadyAgents": 1,
"unsupportedAgents": 2,
"highRiskAgents": 2,
"unknownRiskAgents": 0
}
}
@@ -0,0 +1,26 @@
{
"description": "Single unsupported agent — must be high risk",
"registry": {
"schemaVersion": "1.0",
"agents": [
{
"agentId": "unsupported-legacy",
"agentType": "unknown",
"displayName": "Unsupported Legacy Agent",
"status": "unsupported",
"owner": "external-team",
"workspacePath": "legacy-data/",
"adapter": "none",
"baselineVersion": "unknown",
"compatibilityStatus": "unsupported",
"lastCertifiedAt": "2025-01-01T00:00:00.000Z"
}
]
},
"releaseHistory": [
{"releaseId":"rel-001","agentId":"unsupported-legacy","result":"failed","gateDecision":"closed","compatibilityStatus":"unsupported","timestamp":"2025-01-01T00:00:00.000Z"}
],
"certifications": {
"unsupported-legacy": {"status":"not_ready","governanceGrade":"F","governanceScore":10,"riskLevel":"high","certifiedAt":"2025-01-01T00:00:00.000Z","baselineVersion":"unknown"}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "full-pass",
"description": "All governance components present and certified",
"state": {
"baselineRotationDir": "../baseline-rotation",
"compatMatrixDir": "../compatibility-matrix",
"releaseHistoryDir": "../../../release-history",
"baselineEvolutionDir": "../baseline-evolution",
"productionValidationDir": "../production-validation"
},
"expected": {
"coverageScore": 100,
"checklistPass": 6,
"grade": "A"
}
}

Some files were not shown because too many files have changed in this diff Show More