




















@@ -3,11 +3,32 @@ import { spawnSync } from "node:child_process";
33import fs from "node:fs";
44import path from "node:path";
55import { afterEach, describe, expect, it } from "vitest";
6+import { parse as parseYaml } from "yaml";
67import { createTempDirTracker } from "../helpers/temp-dir.js";
7889const repoRoot = path.resolve(__dirname, "../..");
910const tempDirs = createTempDirTracker();
101112+type TaxonomyFixture = {
13+surfaces?: TaxonomySurfaceFixture[];
14+};
15+16+type TaxonomySurfaceFixture = {
17+id?: string;
18+status?: string;
19+categories?: TaxonomyCategoryFixture[];
20+};
21+22+type TaxonomyCategoryFixture = {
23+id?: string;
24+name?: string;
25+features?: TaxonomyFeatureFixture[];
26+};
27+28+type TaxonomyFeatureFixture = {
29+coverageIds?: string[];
30+};
31+1132afterEach(() => {
1233tempDirs.cleanup();
1334});
@@ -26,7 +47,33 @@ function runCli(...args: string[]) {
2647function writeQaEvidence(params: {
2748dir: string;
2849entries: Array<{ id: string; status: "pass" | "fail" | "blocked" | "skipped" }>;
50+scorecard?: unknown;
2951}) {
52+const scorecard = params.scorecard ?? {
53+filters: { surface: null, category: null },
54+run: { evidenceEntryCount: params.entries.length },
55+categories: {
56+total: 0,
57+fulfilled: 0,
58+partial: 0,
59+missing: 0,
60+fulfillmentPercent: 0,
61+},
62+features: {
63+total: 0,
64+fulfilled: 0,
65+partial: 0,
66+missing: 0,
67+fulfillmentPercent: 0,
68+},
69+coverageIds: {
70+total: 0,
71+fulfilled: 0,
72+missing: 0,
73+fulfillmentPercent: 0,
74+},
75+categoryReports: [],
76+};
3077fs.mkdirSync(params.dir, { recursive: true });
3178fs.writeFileSync(
3279path.join(params.dir, "qa-evidence.json"),
@@ -47,31 +94,7 @@ function writeQaEvidence(params: {
4794 coverage: [{ id: "tools.evidence", role: "primary" }],
4895 result: { status: entry.status },
4996 })),
50- scorecard: {
51- filters: { surface: null, category: null },
52- run: { evidenceEntryCount: params.entries.length },
53- categories: {
54- total: 0,
55- fulfilled: 0,
56- partial: 0,
57- missing: 0,
58- fulfillmentPercent: 0,
59- },
60- features: {
61- total: 0,
62- fulfilled: 0,
63- partial: 0,
64- missing: 0,
65- fulfillmentPercent: 0,
66- },
67- coverageIds: {
68- total: 0,
69- fulfilled: 0,
70- missing: 0,
71- fulfillmentPercent: 0,
72- },
73- categoryReports: [],
74- },
97+ scorecard,
7598 },
7699 null,
77100 2,
@@ -80,6 +103,73 @@ function writeQaEvidence(params: {
80103);
81104}
82105106+function allProfileScorecardFixture() {
107+const taxonomy = parseYaml(
108+fs.readFileSync(path.join(repoRoot, "taxonomy.yaml"), "utf8"),
109+) as TaxonomyFixture;
110+const activeSurfaces = (taxonomy.surfaces ?? []).filter(
111+(surface) => surface.status !== "retired",
112+);
113+const categoryReports = activeSurfaces.flatMap((surface) =>
114+(surface.categories ?? []).map((category) => {
115+const coverageIds = [
116+ ...new Set((category.features ?? []).flatMap((feature) => feature.coverageIds ?? [])),
117+].sort();
118+return {
119+id: `${surface.id}.${category.id}`,
120+surfaceId: surface.id,
121+name: category.name,
122+status: "missing",
123+features: {
124+total: category.features.length,
125+fulfilled: 0,
126+partial: 0,
127+missing: category.features.length,
128+fulfillmentPercent: 0,
129+},
130+coverageIds: {
131+total: coverageIds.length,
132+fulfilled: 0,
133+missing: coverageIds.length,
134+fulfillmentPercent: 0,
135+secondaryOnly: 0,
136+},
137+missingCoverageIds: coverageIds,
138+};
139+}),
140+);
141+const featureCount = categoryReports.reduce((count, report) => count + report.features.total, 0);
142+const coverageIdCount = categoryReports.reduce(
143+(count, report) => count + report.coverageIds.total,
144+0,
145+);
146+return {
147+filters: { surface: null, category: null },
148+run: { evidenceEntryCount: 1 },
149+categories: {
150+total: categoryReports.length,
151+fulfilled: 0,
152+partial: 0,
153+missing: categoryReports.length,
154+fulfillmentPercent: 0,
155+},
156+features: {
157+total: featureCount,
158+fulfilled: 0,
159+partial: 0,
160+missing: featureCount,
161+fulfillmentPercent: 0,
162+},
163+coverageIds: {
164+total: coverageIdCount,
165+fulfilled: 0,
166+missing: coverageIdCount,
167+fulfillmentPercent: 0,
168+},
169+ categoryReports,
170+};
171+}
172+83173describe("maturity docs renderer CLI", () => {
84174it("checks maturity inputs without requiring QA evidence artifacts", () => {
85175const result = runCli("--check");
@@ -113,13 +203,7 @@ describe("maturity docs renderer CLI", () => {
113203],
114204});
115205116-const result = runCli(
117-"--output-dir",
118-outputDir,
119-"--evidence-dir",
120-evidenceDir,
121-"--strict-inputs",
122-);
206+const result = runCli("--output-dir", outputDir, "--evidence-dir", evidenceDir);
123207124208expect(result.status).toBe(1);
125209expect(result.stdout).toBe("");
@@ -147,4 +231,23 @@ describe("maturity docs renderer CLI", () => {
147231expect(scorecard).not.toContain("0 failed");
148232expect(scorecard).not.toContain("0 blocked");
149233});
234+235+it("renders the maturity score from quality and completeness without coverage", () => {
236+const outputDir = tempDirs.make("openclaw-maturity-docs-output-");
237+const evidenceDir = tempDirs.make("openclaw-maturity-docs-evidence-");
238+writeQaEvidence({
239+dir: evidenceDir,
240+entries: [{ id: "passing-scenario", status: "pass" }],
241+scorecard: allProfileScorecardFixture(),
242+});
243+244+const result = runCli("--output-dir", outputDir, "--evidence-dir", evidenceDir);
245+246+expect(result.status).toBe(0);
247+const scorecard = fs.readFileSync(path.join(outputDir, "maturity", "scorecard.md"), "utf8");
248+expect(scorecard).toContain("<span>Maturity score</span>");
249+expect(scorecard).toContain('<span className="maturity-summary-value">67%</span>');
250+expect(scorecard).toContain("Coverage Experimental - 0%");
251+expect(scorecard).toContain("end-to-end coverage above 90%");
252+});
150253});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。