bmad-method-test-architecture-enterprise 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/cli/automate.js +7 -0
- package/cli/evaluate.js +20 -0
- package/cli/examples/gitlab-ci.yaml +1 -1
- package/cli/examples/pr-test-review.yml +3 -3
- package/cli/lib/agent-adapters.js +46 -0
- package/cli/lib/automate-inventory.js +258 -25
- package/cli/lib/automate-prompt.js +34 -2
- package/cli/lib/automate-result.js +74 -2
- package/cli/lib/evaluate/configuration-experiment.js +3401 -0
- package/cli/lib/evaluate/experiments.js +1299 -0
- package/cli/lib/evaluate/retain-skill-artifacts.js +78 -0
- package/cli/lib/evaluate/test-design-corpus/clean/docs/epics/epic-9-last-sync-indicator.md +47 -0
- package/cli/lib/evaluate/test-design-corpus/ground-truth.json +222 -0
- package/cli/lib/evaluate/test-design-corpus/native-identity.json +4 -0
- package/cli/lib/evaluate/test-design-corpus/seeded/docs/epics/epic-7-offline-order-capture.md +75 -0
- package/cli/lib/evaluate/test-design-replay.js +452 -0
- package/cli/lib/run-agent.js +18 -2
- package/cli/lib/test-design-staged-config.js +60 -0
- package/cli/lib/test-design-staging.js +229 -0
- package/cli/lib/unit-runners.js +219 -0
- package/cli/lib/vitest-attempts-reporter.mjs +30 -0
- package/cli/test-design-runner.js +245 -11
- package/cli/unit-check.js +595 -0
- package/package.json +13 -3
- package/skills/bmad-tea-automate/red/checklist.md +1 -1
- package/skills/bmad-tea-automate/resources/run-and-heal.md +3 -1
- package/skills/bmad-tea-evaluate/SKILL.md +11 -0
- package/skills/bmad-tea-evaluate/references/configuration-experiments.md +37 -0
- package/skills/bmad-tea-evaluate/references/evaluator.md +2 -2
- package/skills/bmad-tea-evaluate/references/gaps.md +1 -1
- package/skills/bmad-tea-test-review/steps-c/step-03f-aggregate-scores.md +3 -3
- package/skills/bmod-tea/bmod.toml +1 -1
- package/skills/bmod-tea/help/help.md +1 -1
- package/skills/bmod-tea/knowledge/auth-session.md +25 -20
- package/skills/bmod-tea/knowledge/test-healing-patterns.md +2 -0
|
@@ -7,6 +7,7 @@ const { isDeepStrictEqual, stripVTControlCharacters } = require('node:util');
|
|
|
7
7
|
const yaml = require('js-yaml');
|
|
8
8
|
const { projectPath } = require('./workflow-cli');
|
|
9
9
|
const { generatedInventory, reconcileInventory } = require('./automate-inventory');
|
|
10
|
+
const { UNIT_CHECK_KIND, normalizeUnitReport, unitReportRunner } = require('./unit-runners');
|
|
10
11
|
|
|
11
12
|
const STATUSES = ['passed', 'verified red', 'failed', 'could not measure', 'disabled'];
|
|
12
13
|
const RESULT_KEYS = [
|
|
@@ -64,6 +65,28 @@ function reportCounts(report) {
|
|
|
64
65
|
} else if (attempt.status === 'skipped') result.skipped++;
|
|
65
66
|
else throw new Error(`execution report has unsupported attempt status: ${attempt.status}`);
|
|
66
67
|
};
|
|
68
|
+
// Unit attempts carry a classified failure: assertion, timeout or other error.
|
|
69
|
+
const addUnit = (attempt) => {
|
|
70
|
+
switch (attempt.status) {
|
|
71
|
+
case 'passed': {
|
|
72
|
+
result.passed++;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'skipped': {
|
|
76
|
+
result.skipped++;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'failed': {
|
|
80
|
+
result.failed++;
|
|
81
|
+
if (attempt.failure === 'timeout') result.errors++;
|
|
82
|
+
else if (attempt.failure === 'assertion') result.assertionFailures++;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
default: {
|
|
86
|
+
throw new Error(`execution report has unsupported attempt status: ${attempt.status}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
67
90
|
if (Array.isArray(report.suites)) {
|
|
68
91
|
result.errors = (report.errors ?? []).length;
|
|
69
92
|
const walk = (suite) => {
|
|
@@ -71,6 +94,49 @@ function reportCounts(report) {
|
|
|
71
94
|
for (const child of suite.suites ?? []) walk(child);
|
|
72
95
|
};
|
|
73
96
|
for (const suite of report.suites) walk(suite);
|
|
97
|
+
} else if (unitReportRunner(report)) {
|
|
98
|
+
const normalized = normalizeUnitReport(report);
|
|
99
|
+
result.errors = normalized.errors;
|
|
100
|
+
for (const file of normalized.files) for (const test of file.tests) for (const attempt of test.attempts) addUnit(attempt);
|
|
101
|
+
} else if (report.kind === UNIT_CHECK_KIND) {
|
|
102
|
+
const red = report.mode === 'red';
|
|
103
|
+
if (
|
|
104
|
+
report.schemaVersion !== 1 ||
|
|
105
|
+
!['expand', 'red'].includes(report.mode) ||
|
|
106
|
+
!Array.isArray(report.files) ||
|
|
107
|
+
(red && !Array.isArray(report.productionFilesTouched))
|
|
108
|
+
)
|
|
109
|
+
throw new Error('tea-unit-check report is malformed');
|
|
110
|
+
result.errors = red ? report.productionFilesTouched.length : 0;
|
|
111
|
+
const text = (value) => value === null || (typeof value === 'string' && value.length > 0);
|
|
112
|
+
for (const file of report.files) {
|
|
113
|
+
if (!file || !text(file.loadError) || !text(file.runError) || !(file.exitCode === null || Number.isSafeInteger(file.exitCode)))
|
|
114
|
+
throw new Error('tea-unit-check file result lacks its outcome fields');
|
|
115
|
+
if (file.exitCode === null && !file.loadError && !file.runError)
|
|
116
|
+
throw new Error(`tea-unit-check records no exit code and no error: ${file.file}`);
|
|
117
|
+
if (file.loadError) result.errors++;
|
|
118
|
+
if (file.runError) result.errors++;
|
|
119
|
+
if (!Array.isArray(file.tests)) throw new Error('tea-unit-check file result lacks its tests');
|
|
120
|
+
let failedFinal = false;
|
|
121
|
+
for (const test of file.tests) {
|
|
122
|
+
// Every attempt counts, so a retry cannot hide an earlier wrong-reason failure.
|
|
123
|
+
if (!Array.isArray(test?.attempts) || test.attempts.length === 0) throw new Error('tea-unit-check test lacks its attempts');
|
|
124
|
+
const last = test.attempts.at(-1);
|
|
125
|
+
if (last.status !== test.status || last.failure !== test.failure)
|
|
126
|
+
throw new Error('tea-unit-check test outcome disagrees with its final attempt');
|
|
127
|
+
if (test.status === 'failed') failedFinal = true;
|
|
128
|
+
for (const attempt of test.attempts) {
|
|
129
|
+
if (attempt?.status === 'failed' && !['assertion', 'timeout', 'error'].includes(attempt.failure))
|
|
130
|
+
throw new Error('tea-unit-check failure lacks a classified reason');
|
|
131
|
+
if (attempt?.status !== 'failed' && attempt?.failure !== null)
|
|
132
|
+
throw new Error('tea-unit-check attempt has an unexpected failure reason');
|
|
133
|
+
addUnit(attempt);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Vitest and Jest exit nonzero exactly when a test fails, unless a load or run error explains it.
|
|
137
|
+
if (!file.loadError && !file.runError && (file.exitCode !== 0) !== failedFinal)
|
|
138
|
+
throw new Error(`tea-unit-check exit code ${file.exitCode} disagrees with its test results: ${file.file}`);
|
|
139
|
+
}
|
|
74
140
|
} else if (report.schemaVersion === 1 && Array.isArray(report.files) && Array.isArray(report.productionFilesTouched)) {
|
|
75
141
|
result.errors = report.productionFilesTouched.length;
|
|
76
142
|
for (const file of report.files) {
|
|
@@ -104,6 +170,7 @@ function parseResult({
|
|
|
104
170
|
selectedCheckpoint,
|
|
105
171
|
checkpointBefore,
|
|
106
172
|
evidenceDir,
|
|
173
|
+
runners,
|
|
107
174
|
}) {
|
|
108
175
|
let result;
|
|
109
176
|
try {
|
|
@@ -181,10 +248,15 @@ function parseResult({
|
|
|
181
248
|
const allRecognized = recognizedReports > 0 && recognizedReports === result.executionReports.length;
|
|
182
249
|
if (successful && !allRecognized) throw new Error('successful execution requires supported native reports for every final scope');
|
|
183
250
|
if (successful) {
|
|
184
|
-
|
|
251
|
+
// The report family selects the registration grammar; reconciliation then
|
|
252
|
+
// requires that family to match a runner the project declared before the run.
|
|
253
|
+
const unitRunner = nativeReports
|
|
254
|
+
.map((report) => (report.kind === UNIT_CHECK_KIND ? report.runner : unitReportRunner(report)))
|
|
255
|
+
.find(Boolean);
|
|
256
|
+
const inventory = generatedInventory(projectRoot, result.generatedFiles, { runner: unitRunner ?? 'playwright' });
|
|
185
257
|
if (evidenceDir)
|
|
186
258
|
fs.writeFileSync(path.join(evidenceDir, 'generated-test-inventory.json'), `${JSON.stringify(inventory, null, 2)}\n`, { flag: 'wx' });
|
|
187
|
-
const verified = reconcileInventory(inventory, nativeReports, { mode });
|
|
259
|
+
const verified = reconcileInventory(inventory, nativeReports, { mode, runners });
|
|
188
260
|
if (evidenceDir)
|
|
189
261
|
fs.writeFileSync(path.join(evidenceDir, 'validated-test-scopes.json'), `${JSON.stringify(verified, null, 2)}\n`, { flag: 'wx' });
|
|
190
262
|
}
|