bmad-method-test-architecture-enterprise 2.0.2 → 2.0.3-next.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 -3
- package/README.md +3 -3
- package/cli/evaluate.js +25 -2
- package/cli/lib/evaluate/preflight.js +77 -1
- package/cli/lib/evaluate/run.js +43 -4
- package/cli/lib/evaluate/status.js +184 -0
- package/package.json +3 -2
- package/skills/bmad-tea/SKILL.md +2 -2
- package/skills/bmad-tea/customize.toml +4 -4
- package/skills/bmad-teach-me-testing/steps-c/step-04-session-01.md +2 -2
- package/skills/bmad-teach-me-testing/steps-c/step-04-session-03.md +1 -1
- package/skills/bmad-teach-me-testing/steps-c/step-04-session-05.md +2 -2
- package/skills/bmad-testarch-automate/red/instructions.md +1 -2
- package/skills/bmad-testarch-automate/resources/test-generation-routing.md +17 -21
- package/skills/bmad-testarch-evaluate/SKILL.md +5 -3
- package/skills/bmad-testarch-evaluate/assets/inspection-record.md +1 -1
- package/skills/bmad-testarch-evaluate/references/authoring-routing.md +29 -0
- package/skills/bmad-testarch-evaluate/references/ci.md +5 -5
- package/skills/bmad-testarch-evaluate/references/run.md +19 -0
- package/skills/bmad-testarch-framework/SKILL.md +2 -2
- package/skills/bmad-testarch-framework/checklist.md +4 -4
- package/skills/bmad-testarch-framework/ci/checklist.md +2 -2
- package/skills/bmad-testarch-framework/ci/instructions.md +1 -1
- package/skills/bmad-testarch-framework/resources/setup-routing.md +4 -4
- package/skills/bmad-testarch-test-design/checklist.md +6 -6
- package/skills/bmad-testarch-test-design/resources/test-design-epic-3.example.md +1 -1
- package/skills/bmad-testarch-test-design/test-design-template.md +1 -1
- package/skills/bmad-testarch-trace/checklist.md +2 -2
- package/skills/bmad-testarch-trace/steps-c/step-04-analyze-gaps.md +1 -1
- package/skills/bmad-testarch-trace/trace-template.md +1 -1
- package/skills/bmod-tea/bmod.toml +2 -4
- package/skills/bmod-tea/help/help.md +1 -3
- package/skills/bmod-tea/help/integrations.md +1 -1
- package/skills/bmod-tea/knowledge/burn-in.md +1 -1
- package/skills/bmod-tea/migration-1.toml +1 -1
- package/skills/bmad-testarch-atdd/SKILL.md +0 -16
- package/skills/bmad-testarch-atdd/bmod.toml +0 -4
- package/skills/bmad-testarch-atdd/customize.toml +0 -38
- package/skills/bmad-testarch-atdd/workflow.yaml +0 -40
- package/skills/bmad-testarch-ci/SKILL.md +0 -14
- package/skills/bmad-testarch-ci/bmod.toml +0 -4
- package/skills/bmad-testarch-ci/customize.toml +0 -43
- package/skills/bmad-testarch-ci/workflow.yaml +0 -44
|
@@ -31,16 +31,14 @@
|
|
|
31
31
|
"name": "bmad-method-test-architecture-enterprise",
|
|
32
32
|
"source": "./",
|
|
33
33
|
"description": "Master Test Architect module for quality strategy, test generation in red and expand modes with automatic run-and-heal, combined framework and CI setup, CI/CD quality gates, and structured testing education. The CI and ATDD commands remain available with existing customizations. Part of the BMad Method ecosystem.",
|
|
34
|
-
"version": "2.0.
|
|
34
|
+
"version": "2.0.3-next.0",
|
|
35
35
|
"author": {
|
|
36
36
|
"name": "Murat K Ozcan (TEA Creator) & Brian (BMad) Madison"
|
|
37
37
|
},
|
|
38
38
|
"skills": [
|
|
39
39
|
"./skills/bmod-tea",
|
|
40
40
|
"./skills/bmad-tea",
|
|
41
|
-
"./skills/bmad-testarch-atdd",
|
|
42
41
|
"./skills/bmad-testarch-automate",
|
|
43
|
-
"./skills/bmad-testarch-ci",
|
|
44
42
|
"./skills/bmad-testarch-evaluate",
|
|
45
43
|
"./skills/bmad-testarch-framework",
|
|
46
44
|
"./skills/bmad-testarch-nfr",
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ npx skills add https://github.com/bmad-code-org/bmad-method-test-architecture-en
|
|
|
50
50
|
|
|
51
51
|
## Start with one skill
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Eight commands start the eight workflows: Framework includes CI setup; Automate includes red and expand modes.
|
|
54
54
|
|
|
55
55
|
In Claude Code, Cursor, and Windsurf, type `/bmad-testarch-test-design` in chat.
|
|
56
56
|
In Codex, type `$bmad-testarch-test-design`.
|
|
@@ -73,10 +73,10 @@ Evaluate:
|
|
|
73
73
|
|
|
74
74
|
Framework and CI setup share one skill, `bmad-testarch-framework`.
|
|
75
75
|
Ask for framework only, CI only, or both; TEA infers the scope from your prompt and asks "Do you want CI too?" once when CI scope is unclear in an interactive session. An unattended request with unclear scope runs framework setup only and states that CI was excluded.
|
|
76
|
-
`/bmad-testarch-
|
|
76
|
+
Request CI setup through `/bmad-testarch-framework` or the agent's `CI` menu code. Existing CI customizations remain usable.
|
|
77
77
|
Create, Resume, Validate, and Edit remain available for the selected setup scope.
|
|
78
78
|
|
|
79
|
-
Automation uses `red` mode before implementation and `expand` mode for existing code.
|
|
79
|
+
Automation uses `red` mode before implementation and `expand` mode for existing code. Request red mode through `/bmad-testarch-automate` or the agent's `AT` menu code; the direct command defaults to expand. Existing customization files and saved progress remain usable. Create runs execute the generated tests and repair test issues for up to three rounds. Red confirms the intended missing behavior; expand aims for passing coverage and reports product defects.
|
|
80
80
|
|
|
81
81
|
For Codex, replace the leading `/` in the table with `$`.
|
|
82
82
|
The agent menu also accepts `GATE` to route you through test review, NFR evidence audit, and trace Phase 2.
|
package/cli/evaluate.js
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
* compare a scored run's evidence with baseline/ through eval-quality's
|
|
30
30
|
* compareDominance (compared, first-run or refused, every one exit 0), or
|
|
31
31
|
* with --accept replace baseline/ with a byte-identical snapshot of the run
|
|
32
|
+
* tea-evaluate status --evaluation <path> [--run <invocationId>] [--follow]
|
|
33
|
+
* show the latest run's progress, or follow it until it ends
|
|
32
34
|
* tea-evaluate ci --evaluation <path> --tier <pr|merge|scheduled|release>
|
|
33
35
|
* run exactly the checks ci/evaluation-ci-plan.json places on the tier, in
|
|
34
36
|
* plan order, each one's exit, stdout and stderr kept under
|
|
@@ -130,6 +132,7 @@ const { runRunCommand } = require('./lib/evaluate/run');
|
|
|
130
132
|
const { runCompareCommand } = require('./lib/evaluate/compare');
|
|
131
133
|
const { runScoreCommand } = require('./lib/evaluate/score');
|
|
132
134
|
const { runCiCommand } = require('./lib/evaluate/ci');
|
|
135
|
+
const { runStatusCommand } = require('./lib/evaluate/status');
|
|
133
136
|
const { TIERS } = require('./lib/evaluate/ci-plan');
|
|
134
137
|
const { escapeUnprintable, findingLine } = require('./lib/evaluate/finding-lines');
|
|
135
138
|
const { calibrationInputsOf } = require('./lib/evaluate/records-calibration');
|
|
@@ -265,6 +268,17 @@ function compareCommand(options) {
|
|
|
265
268
|
return runDriven('compare', runCompareCommand, options, { run: options.run, accept: options.accept === true });
|
|
266
269
|
}
|
|
267
270
|
|
|
271
|
+
async function statusCommand(options) {
|
|
272
|
+
const folder = folderFrom(options);
|
|
273
|
+
const result = await runStatusCommand(folder, {
|
|
274
|
+
run: options.run,
|
|
275
|
+
follow: options.follow === true,
|
|
276
|
+
emit: (line) => process.stdout.write(`${NAME} status: ${escapeUnprintable(line)}\n`),
|
|
277
|
+
});
|
|
278
|
+
if (result.exitCode !== 0) process.stderr.write(`${NAME} status: ${escapeUnprintable(result.message)}\n`);
|
|
279
|
+
return result.exitCode;
|
|
280
|
+
}
|
|
281
|
+
|
|
268
282
|
async function ciCommand(options) {
|
|
269
283
|
if (!TIERS.includes(options.tier)) {
|
|
270
284
|
throw new UsageError(
|
|
@@ -278,7 +292,7 @@ function buildProgram(run) {
|
|
|
278
292
|
const program = new Command();
|
|
279
293
|
program
|
|
280
294
|
.name(NAME)
|
|
281
|
-
.description('Validate, digest, preflight, run, score, compare and run the CI tiers of an Evaluate evaluation folder.')
|
|
295
|
+
.description('Validate, digest, preflight, run, score, compare, report status and run the CI tiers of an Evaluate evaluation folder.')
|
|
282
296
|
.showHelpAfterError()
|
|
283
297
|
.exitOverride()
|
|
284
298
|
.configureOutput({ writeErr: (text) => process.stderr.write(text) });
|
|
@@ -338,6 +352,13 @@ function buildProgram(run) {
|
|
|
338
352
|
.option('--run <invocationId>', 'the run to compare or accept; the most recent run when omitted')
|
|
339
353
|
.option('--accept', 'replace baseline/ with a byte-identical snapshot of the run; refused for a dirty run')
|
|
340
354
|
.action((options) => run(compareCommand, options));
|
|
355
|
+
program
|
|
356
|
+
.command('status')
|
|
357
|
+
.description("Show the newest run's progress, or follow it until completion; --run selects a specific invocation.")
|
|
358
|
+
.option('--evaluation <path>', 'the evaluation folder, or its evaluation.json')
|
|
359
|
+
.option('--run <invocationId>', 'the run to inspect; the most recent run when omitted')
|
|
360
|
+
.option('--follow', 'print progress changes and periodic heartbeats until the run ends')
|
|
361
|
+
.action((options) => run(statusCommand, options));
|
|
341
362
|
program
|
|
342
363
|
.command('ci')
|
|
343
364
|
.description(
|
|
@@ -377,7 +398,9 @@ async function main(argv) {
|
|
|
377
398
|
return await pending;
|
|
378
399
|
} catch (error) {
|
|
379
400
|
if (error instanceof UsageError) {
|
|
380
|
-
process.stderr.write(
|
|
401
|
+
process.stderr.write(
|
|
402
|
+
`${NAME}: ${error.message}\nUsage: ${NAME} <check|digest|preflight|run|score|compare|status|ci> --evaluation <path>\n`,
|
|
403
|
+
);
|
|
381
404
|
return EXIT_CODES.usage;
|
|
382
405
|
}
|
|
383
406
|
if (error instanceof CorpusIndexError) {
|
|
@@ -513,6 +513,8 @@ function runPreflightCommand(folder, { partition, requirePartition = false, ...o
|
|
|
513
513
|
* @param {Set<string>|null} [options.selectedProbeIds] the probes the partition selects; every probe when null
|
|
514
514
|
* @param {NodeJS.ProcessEnv} [options.env]
|
|
515
515
|
* @param {(line: string) => void} [options.log]
|
|
516
|
+
* @param {number} [options.progressIntervalMs] heartbeat interval, for tests and long-running runs
|
|
517
|
+
* @param {number} [options.progressStartedAt] invocation start time, before setup
|
|
516
518
|
* @param {(context: object) => PreflightOutcome|null|Promise<PreflightOutcome|null>} [options.prepare]
|
|
517
519
|
* @param {(context: object) => Promise<PreflightOutcome>} [options.afterVerdict]
|
|
518
520
|
* @returns {Promise<PreflightOutcome>}
|
|
@@ -520,10 +522,11 @@ function runPreflightCommand(folder, { partition, requirePartition = false, ...o
|
|
|
520
522
|
async function runPipeline(folder, options) {
|
|
521
523
|
// The invocation's run directory and run.json, held in memory, so its end is
|
|
522
524
|
// recorded from what the runtime knows, never from a file read back.
|
|
523
|
-
const state = { writer: null, run: null, sealed: false, retractUnlessSealed: [] };
|
|
525
|
+
const state = { writer: null, run: null, sealed: false, retractUnlessSealed: [], progressTimer: null, finishProgress: null };
|
|
524
526
|
try {
|
|
525
527
|
return await recordEnd(await pipeline(folder, options, state), state);
|
|
526
528
|
} finally {
|
|
529
|
+
if (state.progressTimer !== null) clearInterval(state.progressTimer);
|
|
527
530
|
// The directories the run made stay held open until here, so none of
|
|
528
531
|
// their inode numbers can pass to a directory the target makes.
|
|
529
532
|
state.writer?.close();
|
|
@@ -543,6 +546,7 @@ function recordEnd(outcome, state) {
|
|
|
543
546
|
const failed = [];
|
|
544
547
|
for (const [what, step] of [
|
|
545
548
|
...state.retractUnlessSealed.map((file) => [`remove ${file}`, () => state.writer.remove(file)]),
|
|
549
|
+
['record final progress', () => state.finishProgress?.('stopped')],
|
|
546
550
|
['record this end in run.json', () => state.writer.replaceJson('run.json', state.run)],
|
|
547
551
|
]) {
|
|
548
552
|
try {
|
|
@@ -574,6 +578,8 @@ async function pipeline(
|
|
|
574
578
|
selectedProbeIds = null,
|
|
575
579
|
partition,
|
|
576
580
|
seed,
|
|
581
|
+
progressIntervalMs = 30_000,
|
|
582
|
+
progressStartedAt,
|
|
577
583
|
},
|
|
578
584
|
state,
|
|
579
585
|
) {
|
|
@@ -658,6 +664,7 @@ async function pipeline(
|
|
|
658
664
|
}
|
|
659
665
|
};
|
|
660
666
|
const onSignal = (name) => {
|
|
667
|
+
if (state.progressTimer !== null) clearInterval(state.progressTimer);
|
|
661
668
|
removeScratch();
|
|
662
669
|
if (journal) {
|
|
663
670
|
try {
|
|
@@ -671,6 +678,7 @@ async function pipeline(
|
|
|
671
678
|
for (const file of [...retractOnSignal, ...state.retractUnlessSealed]) state.writer.remove(file);
|
|
672
679
|
state.run.outcome = { stage: 'signal', exitCode: null, signal: name, message: `stopped by ${name} before it finished` };
|
|
673
680
|
if (state.run.command === 'run') state.run.completed = false;
|
|
681
|
+
state.finishProgress?.('stopped');
|
|
674
682
|
state.writer.replaceJson('run.json', state.run);
|
|
675
683
|
} catch {
|
|
676
684
|
// The signal still ends the process; run.json keeps its last state.
|
|
@@ -735,6 +743,11 @@ async function pipeline(
|
|
|
735
743
|
log(
|
|
736
744
|
`pristine workspace, ${pristine.kind === 'git-worktree' ? `a detached worktree at ${pristine.commit}` : `a temp copy${pristine.dirty ? ' of the working tree' : ''}`}: ${pristine.root}`,
|
|
737
745
|
);
|
|
746
|
+
if (pristine.kind === 'git-worktree' && sharedState) {
|
|
747
|
+
log(
|
|
748
|
+
'Git worktrees share repository refs, config and hooks. Keep Git writes in every worktree of this repository paused until this command ends, or use a dedicated clone for concurrent work.',
|
|
749
|
+
);
|
|
750
|
+
}
|
|
738
751
|
// The evaluation folder is read from the working tree (the workspaces
|
|
739
752
|
// leave it out), so uncommitted work in it reaches the run, which is then
|
|
740
753
|
// dirty: no committed state names what it measured.
|
|
@@ -766,6 +779,8 @@ async function pipeline(
|
|
|
766
779
|
runsDirectory,
|
|
767
780
|
invocationId,
|
|
768
781
|
seed: runSeed,
|
|
782
|
+
progressStartedAt,
|
|
783
|
+
progressIntervalMs,
|
|
769
784
|
retractOnSignal,
|
|
770
785
|
scratch,
|
|
771
786
|
state,
|
|
@@ -826,6 +841,8 @@ async function runInWorkspaces({
|
|
|
826
841
|
runsDirectory,
|
|
827
842
|
invocationId,
|
|
828
843
|
seed,
|
|
844
|
+
progressStartedAt,
|
|
845
|
+
progressIntervalMs,
|
|
829
846
|
retractOnSignal,
|
|
830
847
|
scratch,
|
|
831
848
|
state,
|
|
@@ -877,9 +894,65 @@ async function runInWorkspaces({
|
|
|
877
894
|
adopterTree: { repository: before.repository === null ? null : REPOSITORY, unchanged: null },
|
|
878
895
|
refused: [],
|
|
879
896
|
};
|
|
897
|
+
const progressStarted = progressStartedAt ?? Date.now();
|
|
898
|
+
const progressCreatedAt = Date.now();
|
|
899
|
+
const progressCreatedTick = globalThis.performance.now();
|
|
900
|
+
const setupBeforeRecordMs = Math.max(0, progressCreatedAt - progressStarted);
|
|
901
|
+
let phaseStartedTick = progressCreatedTick;
|
|
902
|
+
if (command === 'run') {
|
|
903
|
+
run.progress = {
|
|
904
|
+
phase: 'setup',
|
|
905
|
+
detail: 'Preparing the run',
|
|
906
|
+
startedAt: new Date(progressStarted).toISOString(),
|
|
907
|
+
updatedAt: new Date(progressCreatedAt).toISOString(),
|
|
908
|
+
phaseStartedAt: new Date(progressStarted).toISOString(),
|
|
909
|
+
phaseElapsedMs: setupBeforeRecordMs,
|
|
910
|
+
elapsedMs: setupBeforeRecordMs,
|
|
911
|
+
phaseDurationsMs: {},
|
|
912
|
+
completedTrials: 0,
|
|
913
|
+
totalTrials: null,
|
|
914
|
+
usage: null,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
880
917
|
state.run = run;
|
|
881
918
|
const writeRun = () => writer.replaceJson('run.json', run);
|
|
882
919
|
writeRun();
|
|
920
|
+
const progress = (phase, fields = {}) => {
|
|
921
|
+
if (command !== 'run' || state.sealed) return;
|
|
922
|
+
const now = Date.now();
|
|
923
|
+
const tick = globalThis.performance.now();
|
|
924
|
+
const sinceCreated = Math.max(0, tick - progressCreatedTick);
|
|
925
|
+
if (phase !== run.progress.phase) {
|
|
926
|
+
const prior = run.progress.phase;
|
|
927
|
+
const priorDuration = Math.max(0, tick - phaseStartedTick) + (prior === 'setup' ? setupBeforeRecordMs : 0);
|
|
928
|
+
run.progress.phaseDurationsMs[prior] = (run.progress.phaseDurationsMs[prior] ?? 0) + priorDuration;
|
|
929
|
+
phaseStartedTick = tick;
|
|
930
|
+
run.progress.phase = phase;
|
|
931
|
+
run.progress.phaseStartedAt = new Date(now).toISOString();
|
|
932
|
+
}
|
|
933
|
+
Object.assign(run.progress, fields, {
|
|
934
|
+
updatedAt: new Date(now).toISOString(),
|
|
935
|
+
elapsedMs: setupBeforeRecordMs + sinceCreated,
|
|
936
|
+
phaseElapsedMs: Math.max(0, tick - phaseStartedTick) + (phase === 'setup' ? setupBeforeRecordMs : 0),
|
|
937
|
+
});
|
|
938
|
+
writeRun();
|
|
939
|
+
};
|
|
940
|
+
state.finishProgress = (phase) => progress(phase, { detail: phase === 'complete' ? 'Run sealed' : 'Run stopped' });
|
|
941
|
+
if (command === 'run') {
|
|
942
|
+
state.progressTimer = setInterval(() => {
|
|
943
|
+
try {
|
|
944
|
+
progress(run.progress.phase);
|
|
945
|
+
log(
|
|
946
|
+
`progress: ${run.progress.phase}; ${run.progress.completedTrials ?? '?'}/${run.progress.totalTrials ?? '?'} trials; ${Math.floor(run.progress.elapsedMs / 1000)}s elapsed`,
|
|
947
|
+
);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
// A progress write is guarded like every other run-directory write. The main
|
|
950
|
+
// pipeline will detect the same changed directory at its next verification.
|
|
951
|
+
log(`progress could not be recorded: ${error.message}`);
|
|
952
|
+
}
|
|
953
|
+
}, progressIntervalMs);
|
|
954
|
+
state.progressTimer.unref?.();
|
|
955
|
+
}
|
|
883
956
|
|
|
884
957
|
// The run keeps its own copy of the contract this partition runs, so every stage below reads the
|
|
885
958
|
// same bytes and the verdict can be reproduced from the run directory alone.
|
|
@@ -951,6 +1024,7 @@ async function runInWorkspaces({
|
|
|
951
1024
|
}
|
|
952
1025
|
};
|
|
953
1026
|
|
|
1027
|
+
progress('qualification', { detail: 'Qualifying probes and clean controls' });
|
|
954
1028
|
const qualified = [];
|
|
955
1029
|
const policyPath = path.join(folder, POLICY_PATH);
|
|
956
1030
|
const policy = fs.existsSync(policyPath) ? readJson(policyPath) : null;
|
|
@@ -1192,6 +1266,7 @@ async function runInWorkspaces({
|
|
|
1192
1266
|
// Every leg has a port, and so a home, of its own.
|
|
1193
1267
|
resetHome() {},
|
|
1194
1268
|
};
|
|
1269
|
+
progress('preflight', { detail: 'Running preflight legs and verdict' });
|
|
1195
1270
|
try {
|
|
1196
1271
|
const recorder = recordingPort({
|
|
1197
1272
|
pristine: { label: 'pristine', cwd: workspaceDirectory(pristine), port: auditedLegs },
|
|
@@ -1325,6 +1400,7 @@ async function runInWorkspaces({
|
|
|
1325
1400
|
writer,
|
|
1326
1401
|
run,
|
|
1327
1402
|
writeRun,
|
|
1403
|
+
progress,
|
|
1328
1404
|
treeUnchanged,
|
|
1329
1405
|
retractUnlessSealed: state.retractUnlessSealed,
|
|
1330
1406
|
markSealed: () => {
|
package/cli/lib/evaluate/run.js
CHANGED
|
@@ -236,11 +236,12 @@ function refusal({ folder, selectedProbeIds = null }) {
|
|
|
236
236
|
* (`before-state.js`) may fail its baseline, and the run is marked so it is never accepted as a baseline
|
|
237
237
|
* @param {NodeJS.ProcessEnv} [options.env]
|
|
238
238
|
* @param {(line: string) => void} [options.log]
|
|
239
|
+
* @param {number} [options.progressIntervalMs] heartbeat interval used by integration tests
|
|
239
240
|
* @returns {Promise<PreflightOutcome>}
|
|
240
241
|
*/
|
|
241
242
|
function runRunCommand(
|
|
242
243
|
folder,
|
|
243
|
-
{ fromWorkingTree = false, partition, trials, seed, beforeState = false, env = process.env, log = () => {} } = {},
|
|
244
|
+
{ fromWorkingTree = false, partition, trials, seed, beforeState = false, env = process.env, log = () => {}, progressIntervalMs } = {},
|
|
244
245
|
) {
|
|
245
246
|
if (trials !== undefined && !(Number.isInteger(trials) && trials >= 1))
|
|
246
247
|
return Promise.resolve(
|
|
@@ -260,7 +261,7 @@ function runRunCommand(
|
|
|
260
261
|
} catch {
|
|
261
262
|
// The pipeline's check reports malformed source files with authoring findings; it keeps the partition, so a development
|
|
262
263
|
// run still does not open the held-out plan.
|
|
263
|
-
return runPipeline(folder, { command: 'run', partition, fromWorkingTree, seed, env, log });
|
|
264
|
+
return runPipeline(folder, { command: 'run', partition, fromWorkingTree, seed, env, log, progressIntervalMs });
|
|
264
265
|
}
|
|
265
266
|
if (selection.refusal !== undefined) return Promise.resolve(new PreflightOutcome({ stage: 'check', ...selection.refusal }));
|
|
266
267
|
const { selectedProbeIds } = selection;
|
|
@@ -276,6 +277,8 @@ function runRunCommand(
|
|
|
276
277
|
seed,
|
|
277
278
|
env,
|
|
278
279
|
log,
|
|
280
|
+
progressIntervalMs,
|
|
281
|
+
progressStartedAt: started,
|
|
279
282
|
prepare: async (context) => {
|
|
280
283
|
const refused = refusal({ ...context, selectedProbeIds });
|
|
281
284
|
if (refused !== null) return refused;
|
|
@@ -1148,6 +1151,8 @@ async function runTrialSets(given) {
|
|
|
1148
1151
|
sealed,
|
|
1149
1152
|
} = context;
|
|
1150
1153
|
const { treeUnchanged, stop, log, snapshot } = context;
|
|
1154
|
+
const progress = context.progress ?? (() => {});
|
|
1155
|
+
progress('clean-control-qualification', { detail: 'Qualifying clean controls' });
|
|
1151
1156
|
|
|
1152
1157
|
const cleanControls = await qualifyCleanControls({ ...context, seed: run.seed });
|
|
1153
1158
|
// The gameability probes the shared pipeline qualified before the verdict.
|
|
@@ -1214,8 +1219,19 @@ async function runTrialSets(given) {
|
|
|
1214
1219
|
const { kind } = snapshot.layer.evaluator;
|
|
1215
1220
|
if (kind === 'records') {
|
|
1216
1221
|
if (sealed === null) throw noStages();
|
|
1222
|
+
// Imported sets can share arm work, and the importer does not attest to
|
|
1223
|
+
// complete usage reports. Leave both the trial count and cost unknown.
|
|
1224
|
+
progress('records-import', {
|
|
1225
|
+
detail: 'Importing existing evaluator records',
|
|
1226
|
+
completedTrials: null,
|
|
1227
|
+
totalTrials: null,
|
|
1228
|
+
usage: null,
|
|
1229
|
+
});
|
|
1217
1230
|
return concludeImportedRecords({ ...context, arms, refusedIds });
|
|
1218
1231
|
}
|
|
1232
|
+
const trialCount = snapshot.trials ?? evaluation.trials;
|
|
1233
|
+
const totalTrials = arms.length * trialCount;
|
|
1234
|
+
progress('trial-preparation', { detail: 'Preparing scored trials', totalTrials });
|
|
1219
1235
|
if (convertsRows(kind)) {
|
|
1220
1236
|
// A command evaluator and a sealed-brief agent read the brief the run sealed, as its bytes were written.
|
|
1221
1237
|
if (sealed === null) throw noStages();
|
|
@@ -1242,6 +1258,7 @@ async function runTrialSets(given) {
|
|
|
1242
1258
|
|
|
1243
1259
|
let calibrationDigest = null;
|
|
1244
1260
|
if ((contract.rubrics ?? []).length > 0) {
|
|
1261
|
+
progress('calibration', { detail: 'Calibrating rubric judge' });
|
|
1245
1262
|
const { layer } = snapshot;
|
|
1246
1263
|
const judgeItem = async ({ rubric, criterion, response, responseKind }) => {
|
|
1247
1264
|
const stepId = /^\/interactions\/([^/]+)/.exec(criterion.evidence)?.[1];
|
|
@@ -1357,7 +1374,6 @@ async function runTrialSets(given) {
|
|
|
1357
1374
|
}
|
|
1358
1375
|
}
|
|
1359
1376
|
|
|
1360
|
-
const trialCount = snapshot.trials ?? evaluation.trials;
|
|
1361
1377
|
if (sealed === null) throw noStages();
|
|
1362
1378
|
const { contractDigest, sealedBriefDigest } = sealed;
|
|
1363
1379
|
const { conditions, layer } = snapshot;
|
|
@@ -1439,7 +1455,15 @@ async function runTrialSets(given) {
|
|
|
1439
1455
|
};
|
|
1440
1456
|
|
|
1441
1457
|
// A sealed-brief agent chooses its own calls, so its verdicts count only once it agrees with itself on the arms it will judge.
|
|
1442
|
-
if (kind === 'sealed-brief-agent')
|
|
1458
|
+
if (kind === 'sealed-brief-agent') {
|
|
1459
|
+
progress('evaluator-qualification', { detail: 'Qualifying evaluator agreement' });
|
|
1460
|
+
await qualifyEvaluator({ ...context, arms, sealing });
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
let completedTrials = 0;
|
|
1464
|
+
let reportedUse = ZERO;
|
|
1465
|
+
let missingReports = 0;
|
|
1466
|
+
progress('trials', { detail: 'Running scored trials', totalTrials, completedTrials });
|
|
1443
1467
|
|
|
1444
1468
|
for (const arm of arms) {
|
|
1445
1469
|
arm.trials = [];
|
|
@@ -1452,6 +1476,19 @@ async function runTrialSets(given) {
|
|
|
1452
1476
|
throw stop({ stage: 'trial', exitCode: 12, message: `the installed agent version could not be held after a trial: ${agentMoved}` });
|
|
1453
1477
|
trial.elapsedMs += Date.now() - versionReadBegan;
|
|
1454
1478
|
arm.trials.push(trial);
|
|
1479
|
+
reportedUse = addUsage(reportedUse, trial.resourceUse);
|
|
1480
|
+
missingReports += trial.unreportedSteps.length;
|
|
1481
|
+
completedTrials += 1;
|
|
1482
|
+
progress('trials', {
|
|
1483
|
+
detail: `${arm.conditionArm}: trial ${trialIndex} of ${trialCount}`,
|
|
1484
|
+
completedTrials,
|
|
1485
|
+
usage: {
|
|
1486
|
+
trialCostUsd: missingReports === 0 && completedTrials === totalTrials ? reportedUse.costUsd : null,
|
|
1487
|
+
reportedTrialCostUsd: reportedUse.costUsd,
|
|
1488
|
+
missingReports,
|
|
1489
|
+
scope: 'trials-only',
|
|
1490
|
+
},
|
|
1491
|
+
});
|
|
1455
1492
|
// Read after every trial, so a target that writes into the project stops the run at once.
|
|
1456
1493
|
treeUnchanged('trials');
|
|
1457
1494
|
}
|
|
@@ -1493,6 +1530,7 @@ async function runTrialSets(given) {
|
|
|
1493
1530
|
// wrote, and the run directory nothing else, before any trial set is written.
|
|
1494
1531
|
writer.verify('after the trials');
|
|
1495
1532
|
|
|
1533
|
+
progress('sealing', { detail: 'Sealing trial sets and evidence' });
|
|
1496
1534
|
const trialSets = [];
|
|
1497
1535
|
const recordDigests = {};
|
|
1498
1536
|
const manifestDigests = {};
|
|
@@ -2103,6 +2141,7 @@ async function completeRun(
|
|
|
2103
2141
|
// runtime wrote, before run.json says completed; that write is the run's last.
|
|
2104
2142
|
treeUnchanged('sealing', { record: false });
|
|
2105
2143
|
writer.verify('after the trial sets were sealed');
|
|
2144
|
+
context.progress?.('complete', { detail: 'Run sealed' });
|
|
2106
2145
|
Object.assign(run, {
|
|
2107
2146
|
artifacts,
|
|
2108
2147
|
contractDigest: sealed.contractDigest,
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/** Read-only progress view for a running or finished Evaluate invocation. */
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const { readFolderFile } = require('./folder');
|
|
9
|
+
|
|
10
|
+
const STALE_AFTER_MS = 90_000;
|
|
11
|
+
const POLL_MS = 2000;
|
|
12
|
+
const REPORT_EVERY_MS = 30_000;
|
|
13
|
+
|
|
14
|
+
/** Only the runtime's flat invocation identifiers may name a run. */
|
|
15
|
+
function validRunId(value) {
|
|
16
|
+
return typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value) && value !== '.' && value !== '..';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readRecord(folder, runId) {
|
|
20
|
+
const relative = `runs/${runId}/run.json`;
|
|
21
|
+
// An atomic progress replacement can land between the guarded path walk and
|
|
22
|
+
// open. Retry that one race, while retaining every link and file-type refusal.
|
|
23
|
+
let read;
|
|
24
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
25
|
+
read = readFolderFile(folder, relative);
|
|
26
|
+
if (read.ok || read.reason !== 'changed while it was read; run the command again') break;
|
|
27
|
+
}
|
|
28
|
+
if (!read.ok) return { error: `${relative} ${read.reason}` };
|
|
29
|
+
let record;
|
|
30
|
+
try {
|
|
31
|
+
record = JSON.parse(read.bytes.toString('utf8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return { error: `${relative} is not valid JSON` };
|
|
34
|
+
}
|
|
35
|
+
if (record === null || typeof record !== 'object' || Array.isArray(record) || record.command !== 'run' || record.invocationId !== runId) {
|
|
36
|
+
return { error: `${relative} does not identify a tea-evaluate run` };
|
|
37
|
+
}
|
|
38
|
+
return { record };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Resolve the requested run, or the newest run whose guarded record identifies it. */
|
|
42
|
+
function locateRun(folder, requested) {
|
|
43
|
+
if (requested !== undefined) {
|
|
44
|
+
if (!validRunId(requested)) return { error: `--run ${JSON.stringify(requested)} is not a run identifier` };
|
|
45
|
+
const read = readRecord(folder, requested);
|
|
46
|
+
return read.error === undefined ? { runId: requested, record: read.record } : read;
|
|
47
|
+
}
|
|
48
|
+
const runs = path.join(folder, 'runs');
|
|
49
|
+
let names;
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.lstatSync(runs).isDirectory()) return { error: `${runs} is not a runs directory` };
|
|
52
|
+
names = fs.readdirSync(runs, { withFileTypes: true });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code === 'ENOENT') return { error: `${runs} holds no tea-evaluate run` };
|
|
55
|
+
return { error: `${runs} cannot be read (${error.code ?? error.message})` };
|
|
56
|
+
}
|
|
57
|
+
for (const entry of names
|
|
58
|
+
.map((item) => item.name)
|
|
59
|
+
.filter(validRunId)
|
|
60
|
+
.sort()
|
|
61
|
+
.toReversed()) {
|
|
62
|
+
const read = readRecord(folder, entry);
|
|
63
|
+
if (read.error === undefined) return { runId: entry, record: read.record };
|
|
64
|
+
}
|
|
65
|
+
return { error: `${runs} holds no tea-evaluate run` };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function elapsedText(ms) {
|
|
69
|
+
if (!Number.isFinite(ms) || ms < 0) return 'unknown';
|
|
70
|
+
const seconds = Math.floor(ms / 1000);
|
|
71
|
+
const hours = Math.floor(seconds / 3600);
|
|
72
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
73
|
+
return `${hours}h ${minutes}m ${seconds % 60}s`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function shortDuration(ms) {
|
|
77
|
+
const seconds = Math.floor(ms / 1000);
|
|
78
|
+
if (seconds < 60) return `${seconds}s`;
|
|
79
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
80
|
+
return elapsedText(ms);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function safeText(value) {
|
|
84
|
+
return typeof value === 'string' ? value.replaceAll(/[\r\n\t]+/g, ' ').trim() : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function costAmount(value) {
|
|
88
|
+
return typeof value === 'string' && /^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value) ? value : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function costText(usage) {
|
|
92
|
+
if (usage === null || typeof usage !== 'object' || usage.scope !== 'trials-only') return 'trial cost unknown (trials only)';
|
|
93
|
+
const missing = Number.isInteger(usage.missingReports) && usage.missingReports >= 0 ? usage.missingReports : null;
|
|
94
|
+
const reported = costAmount(usage.reportedTrialCostUsd);
|
|
95
|
+
const complete = costAmount(usage.trialCostUsd);
|
|
96
|
+
if (complete !== null && missing === 0) return `trial cost $${complete} (trials only)`;
|
|
97
|
+
const parts = ['trial cost unknown'];
|
|
98
|
+
if (reported !== null) parts.push(`reported $${reported}`);
|
|
99
|
+
if (missing !== null) parts.push(`${missing} missing report${missing === 1 ? '' : 's'}`);
|
|
100
|
+
return `${parts[0]} (${[...parts.slice(1), 'trials only'].join('; ')})`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function statusOf(runId, record, now = Date.now()) {
|
|
104
|
+
const progress = record.progress && typeof record.progress === 'object' && !Array.isArray(record.progress) ? record.progress : {};
|
|
105
|
+
const outcome = record.outcome && typeof record.outcome === 'object' ? record.outcome : null;
|
|
106
|
+
const updatedAt = Date.parse(progress.updatedAt);
|
|
107
|
+
const startedAt = Date.parse(progress.startedAt ?? record.startedAt);
|
|
108
|
+
const terminal = record.completed === true || outcome !== null;
|
|
109
|
+
const stale = !terminal && (!Number.isFinite(updatedAt) || now - updatedAt >= STALE_AFTER_MS);
|
|
110
|
+
const state = terminal ? (record.completed === true ? 'complete' : 'stopped') : stale ? 'stale' : 'live';
|
|
111
|
+
const completedTrials = Number.isInteger(progress.completedTrials) && progress.completedTrials >= 0 ? progress.completedTrials : null;
|
|
112
|
+
const totalTrials = Number.isInteger(progress.totalTrials) && progress.totalTrials >= 0 ? progress.totalTrials : null;
|
|
113
|
+
const lastElapsed = Number.isFinite(progress.elapsedMs) && progress.elapsedMs >= 0 ? progress.elapsedMs : null;
|
|
114
|
+
const elapsed =
|
|
115
|
+
lastElapsed === null
|
|
116
|
+
? terminal && Number.isFinite(record.durationMs)
|
|
117
|
+
? record.durationMs
|
|
118
|
+
: stale
|
|
119
|
+
? Number.NaN
|
|
120
|
+
: now - startedAt
|
|
121
|
+
: lastElapsed;
|
|
122
|
+
const parts = [`run ${runId}: ${state}`];
|
|
123
|
+
if (safeText(progress.phase)) parts.push(safeText(progress.phase));
|
|
124
|
+
if (safeText(progress.detail)) parts.push(safeText(progress.detail));
|
|
125
|
+
if (Number.isFinite(progress.phaseElapsedMs) && progress.phaseElapsedMs >= 0) {
|
|
126
|
+
parts.push(`phase elapsed ${elapsedText(progress.phaseElapsedMs)}`);
|
|
127
|
+
}
|
|
128
|
+
if (completedTrials !== null && totalTrials !== null) parts.push(`trials ${completedTrials}/${totalTrials}`);
|
|
129
|
+
parts.push(`elapsed ${elapsedText(elapsed)}`);
|
|
130
|
+
const phaseDurations = progress.phaseDurationsMs;
|
|
131
|
+
if (phaseDurations !== null && typeof phaseDurations === 'object' && !Array.isArray(phaseDurations)) {
|
|
132
|
+
const finishedPhases = Object.entries(phaseDurations)
|
|
133
|
+
.filter(([phase, ms]) => safeText(phase) !== '' && Number.isFinite(ms) && ms >= 0)
|
|
134
|
+
.map(([phase, ms]) => `${safeText(phase)} ${shortDuration(ms)}`);
|
|
135
|
+
if (finishedPhases.length > 0) parts.push(`phase times ${finishedPhases.join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
parts.push(
|
|
138
|
+
costText(progress.usage),
|
|
139
|
+
Number.isFinite(updatedAt)
|
|
140
|
+
? `last update ${new Date(updatedAt).toISOString()} (${elapsedText(Math.max(0, now - updatedAt))} ago)`
|
|
141
|
+
: 'last update unknown',
|
|
142
|
+
);
|
|
143
|
+
if (outcome !== null) {
|
|
144
|
+
if (Number.isInteger(outcome.exitCode)) parts.push(`exit ${outcome.exitCode}`);
|
|
145
|
+
if (safeText(outcome.signal)) parts.push(`signal ${safeText(outcome.signal)}`);
|
|
146
|
+
}
|
|
147
|
+
return { runId, state, terminal, stale, line: parts.join(' | '), progress, outcome };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sleep(ms) {
|
|
151
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Emit one snapshot, then material changes and periodic heartbeats until the run ends. */
|
|
155
|
+
async function runStatusCommand(folder, { run, follow = false, emit, pollMs = POLL_MS, reportEveryMs = REPORT_EVERY_MS } = {}) {
|
|
156
|
+
const located = locateRun(folder, run);
|
|
157
|
+
if (located.error !== undefined) return { exitCode: 64, message: located.error };
|
|
158
|
+
const { runId } = located;
|
|
159
|
+
let lastIdentity = null;
|
|
160
|
+
let lastReportAt = 0;
|
|
161
|
+
for (;;) {
|
|
162
|
+
const read = readRecord(folder, runId);
|
|
163
|
+
if (read.error !== undefined) return { exitCode: 12, message: read.error };
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const status = statusOf(runId, read.record, now);
|
|
166
|
+
const identity = JSON.stringify({
|
|
167
|
+
state: status.state,
|
|
168
|
+
phase: status.progress.phase,
|
|
169
|
+
detail: status.progress.detail,
|
|
170
|
+
completedTrials: status.progress.completedTrials,
|
|
171
|
+
totalTrials: status.progress.totalTrials,
|
|
172
|
+
outcome: status.outcome,
|
|
173
|
+
});
|
|
174
|
+
if (lastIdentity !== identity || now - lastReportAt >= reportEveryMs) {
|
|
175
|
+
emit?.(status.line);
|
|
176
|
+
lastIdentity = identity;
|
|
177
|
+
lastReportAt = now;
|
|
178
|
+
}
|
|
179
|
+
if (!follow || status.terminal) return { exitCode: 0, message: status.line, status };
|
|
180
|
+
await sleep(pollMs);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = { STALE_AFTER_MS, locateRun, runStatusCommand, statusOf, validRunId };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method-test-architecture-enterprise",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.3-next.0",
|
|
5
5
|
"description": "Master Test Architect for quality strategy, test automation, and release gates",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"release:minor": "gh workflow run publish.yaml -f channel=latest -f bump=minor",
|
|
90
90
|
"release:next": "gh workflow run publish.yaml -f channel=next",
|
|
91
91
|
"release:patch": "gh workflow run publish.yaml -f channel=latest -f bump=patch",
|
|
92
|
-
"test": "npm run test:conflict-markers && npm run test:schemas && npm run test:install && npm run test:evaluate-guidance && npm run test:evaluate-authoring && npm run test:evaluate-gap-loop && npm run test:evaluate-dogfood && npm run test:evaluate-check && npm run test:evaluate-compare && npm run test:evaluate-before-state && npm run test:evaluate-ci-gates && npm run test:evaluate-ci-tiers && npm run test:evaluate-ci-repositories:tagged-release-pr && npm run test:evaluate-ci-repositories:tagged-release-merge && npm run test:evaluate-ci-repositories:tagged-release-release && npm run test:evaluate-ci-repositories:nightly-deploy-pr && npm run test:evaluate-ci-repositories:nightly-deploy-merge && npm run test:evaluate-ci-repositories:nightly-deploy-scheduled && npm run test:evaluate-ci-repositories:nightly-deploy-release && npm run test:evaluate-ci-render && npm run test:evaluate-pr-mcp && npm run test:evaluate-pr-api && npm run test:evaluate-pr-suite && npm run test:evaluate-pr-workflow && npm run test:evaluate-pr-tool-use && npm run test:evaluate-pr-promptfoo && npm run test:evaluate-pr-ai-feature && npm run test:evaluate-pr-test-review && npm run test:evaluate-pr-gap-loop && npm run test:evaluate-pr-learn && npm run test:evaluate-pr-tutorial && npm run test:evaluate-partitions && npm run test:evaluate-partition-plans-views-and-check && npm run test:evaluate-partition-plans-preflight-and-run && npm run test:evaluate-partition-plans-score && npm run test:evaluate-partition-plans-attempts-seal-and-mappings && npm run test:evaluate-partition-plans-attempts-records && npm run test:evaluate-partition-plans-attempts-gameability && npm run test:evaluate-interpret && npm run test:evaluate-calibration && npm run test:evaluate-boundaries && npm run test:evaluate-preflight && npm run test:evaluate-isolation-conformance && npm run test:evaluate-mutation:qualification-basics && npm run test:evaluate-mutation:qualification-guards && npm run test:evaluate-mutation:recovery && npm run test:evaluate-mutation:journals && npm run test:evaluate-run && npm run test:evaluate-aggregate && npm run test:evaluate-confinement && npm run test:evaluate-confinement-audit && npm run test:evaluate-confinement-history && npm run test:evaluate-confinement-git-state && npm run test:evaluate-confinement-network && npm run test:evaluate-held-inputs && npm run test:evaluate-arms-historical && npm run test:evaluate-arms-deployments && npm run test:evaluate-evaluators && npm run test:evaluate-evaluators-agent-version && npm run test:evaluate-agents && npm run test:evaluate-agents-importers && npm run test:evaluate-agents-sealed-brief && npm run test:evaluate-held-attempts && npm run test:evaluate-private && npm run test:evaluate-records && npm run test:evaluate-mcp && npm run test:evaluate-api && npm run test:evaluate-workflow && npm run test:evaluate-tool-use && npm run test:evaluate-promptfoo && npm run test:evaluate-learned-framework && npm run test:docs-tutorial && npm run test:knowledge && npm run test:criteria-fragments && npm run test:doc-count-sources && npm run test:planning-doc-sources && npm run test:doc-counts && npm run test:doc-claim-sources && npm run test:doc-claims && npm run test:doc-invocation-entry && npm run test:doc-invocations && npm run test:doc-link-anchors && npm run test:docs-build-names && npm run test:docs-evaluate-sidebar && npm run test:docs-gap-reader && npm run test:enforce-hook && npm run test:framework-scaffold && npm run test:isolation-primitives && npm run test:atdd-isolation && npm run test:atdd-net-guard && npm run test:atdd-scoring && npm run test:atdd-workflow-guidance && npm run test:generation-healing && npm run test:framework-scaffold-install-isolation && npm run test:transcript-harness && npm run test:suite-manifest && npm run test:teach-me-testing-scoring && npm run test:eval-data && npm run test:eval-atdd-data && npm run test:eval-ci-data && npm run test:eval-framework-scaffold-data && npm run test:eval-nfr-data && npm run test:nfr-validate-corpus && npm run test:nfr-evidence-guidance && npm run test:eval-test-design-data && npm run test:eval-trace-data && npm run test:eval-routing-data && npm run test:eval-routing-boundaries && npm run test:eval-routing-evidence && npm run test:eval-transcript-data && npm run test:eval-teach-me-testing-data && npm run test:eval-schemas && npm run test:eval-diagnostics && npm run test:eval-replay && npm run test:eval-quality-corpus && npm run test:schema-versions && npm run test:contract-sources && npm run test:contracts && npm run test:contract-oracles && npm run test:probe-sources && npm run test:test-design-qualification && npm run test:test-review-qualification && npm run test:trace-qualification && npm run test:nfr-qualification && npm run test:ci-qualification && npm run test:probe-corpus && npm run test:compare-dominance && npm run test:compare-eval-runs && npm run test:trial-set-scoring && npm run test:automate-eval-fixture && npm run test:eval-automate-data && npm run test:clock-port && npm run test:probe-conformance && npm run test:corpus-conformance && npm run test:file-system-conformance && npm run test:file-system-port && npm run test:probe-targets && npm run test:port-totality && npm run test:ci-coverage && npm run test:ci-coverage-filters && npm run test:install-actionlint && npm run test:workflows-lint && npm run test:install-linux-sandbox && npm run test:shards && npm run test:groups && npm run test:cli && npm run test:test-review-pr-scope && npm run test:test-review-report && npm run test:test-review-rubric && npm run test:bmad-output-gated && npm run test:release-metadata && npm run test:release-notes && npm run test:lockfile-age && npm run test:lockfile-age-cache && npm run test:licences && npm run test:supply-chain && npm run test:boundary && npm run test:direction && npm run test:lineage && npm run test:guard-publish && npm run test:layering-boundary-lineage && npm run test:changelog && npm run test:tea-workflow-descriptions && npm run test:docs-mermaid && npm run test:llms-headroom && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
|
|
92
|
+
"test": "npm run test:conflict-markers && npm run test:schemas && npm run test:install && npm run test:evaluate-guidance && npm run test:evaluate-authoring && npm run test:evaluate-gap-loop && npm run test:evaluate-dogfood && npm run test:evaluate-check && npm run test:evaluate-compare && npm run test:evaluate-before-state && npm run test:evaluate-ci-gates && npm run test:evaluate-ci-tiers && npm run test:evaluate-ci-repositories:tagged-release-pr && npm run test:evaluate-ci-repositories:tagged-release-merge && npm run test:evaluate-ci-repositories:tagged-release-release && npm run test:evaluate-ci-repositories:nightly-deploy-pr && npm run test:evaluate-ci-repositories:nightly-deploy-merge && npm run test:evaluate-ci-repositories:nightly-deploy-scheduled && npm run test:evaluate-ci-repositories:nightly-deploy-release && npm run test:evaluate-ci-render && npm run test:evaluate-pr-mcp && npm run test:evaluate-pr-api && npm run test:evaluate-pr-suite && npm run test:evaluate-pr-workflow && npm run test:evaluate-pr-tool-use && npm run test:evaluate-pr-promptfoo && npm run test:evaluate-pr-ai-feature && npm run test:evaluate-pr-test-review && npm run test:evaluate-pr-gap-loop && npm run test:evaluate-pr-learn && npm run test:evaluate-pr-tutorial && npm run test:evaluate-partitions && npm run test:evaluate-partition-plans-views-and-check && npm run test:evaluate-partition-plans-preflight-and-run && npm run test:evaluate-partition-plans-score && npm run test:evaluate-partition-plans-attempts-seal-and-mappings && npm run test:evaluate-partition-plans-attempts-records && npm run test:evaluate-partition-plans-attempts-gameability && npm run test:evaluate-interpret && npm run test:evaluate-calibration && npm run test:evaluate-boundaries && npm run test:evaluate-preflight && npm run test:evaluate-isolation-conformance && npm run test:evaluate-mutation:qualification-basics && npm run test:evaluate-mutation:qualification-guards && npm run test:evaluate-mutation:recovery && npm run test:evaluate-mutation:journals && npm run test:evaluate-run && npm run test:evaluate-progress && npm run test:evaluate-aggregate && npm run test:evaluate-confinement && npm run test:evaluate-confinement-audit && npm run test:evaluate-confinement-history && npm run test:evaluate-confinement-git-state && npm run test:evaluate-confinement-network && npm run test:evaluate-held-inputs && npm run test:evaluate-arms-historical && npm run test:evaluate-arms-deployments && npm run test:evaluate-evaluators && npm run test:evaluate-evaluators-agent-version && npm run test:evaluate-agents && npm run test:evaluate-agents-importers && npm run test:evaluate-agents-sealed-brief && npm run test:evaluate-held-attempts && npm run test:evaluate-private && npm run test:evaluate-records && npm run test:evaluate-mcp && npm run test:evaluate-api && npm run test:evaluate-workflow && npm run test:evaluate-tool-use && npm run test:evaluate-promptfoo && npm run test:evaluate-learned-framework && npm run test:docs-tutorial && npm run test:knowledge && npm run test:criteria-fragments && npm run test:doc-count-sources && npm run test:planning-doc-sources && npm run test:doc-counts && npm run test:doc-claim-sources && npm run test:doc-claims && npm run test:doc-invocation-entry && npm run test:doc-invocations && npm run test:doc-link-anchors && npm run test:docs-build-names && npm run test:docs-evaluate-sidebar && npm run test:docs-gap-reader && npm run test:enforce-hook && npm run test:framework-scaffold && npm run test:isolation-primitives && npm run test:atdd-isolation && npm run test:atdd-net-guard && npm run test:atdd-scoring && npm run test:atdd-workflow-guidance && npm run test:generation-healing && npm run test:framework-scaffold-install-isolation && npm run test:transcript-harness && npm run test:suite-manifest && npm run test:teach-me-testing-scoring && npm run test:eval-data && npm run test:eval-atdd-data && npm run test:eval-ci-data && npm run test:eval-framework-scaffold-data && npm run test:eval-nfr-data && npm run test:nfr-validate-corpus && npm run test:nfr-evidence-guidance && npm run test:eval-test-design-data && npm run test:eval-trace-data && npm run test:eval-routing-data && npm run test:eval-routing-boundaries && npm run test:eval-routing-evidence && npm run test:eval-transcript-data && npm run test:eval-teach-me-testing-data && npm run test:eval-schemas && npm run test:eval-diagnostics && npm run test:eval-replay && npm run test:eval-quality-corpus && npm run test:schema-versions && npm run test:contract-sources && npm run test:contracts && npm run test:contract-oracles && npm run test:probe-sources && npm run test:test-design-qualification && npm run test:test-review-qualification && npm run test:trace-qualification && npm run test:nfr-qualification && npm run test:ci-qualification && npm run test:probe-corpus && npm run test:compare-dominance && npm run test:compare-eval-runs && npm run test:trial-set-scoring && npm run test:automate-eval-fixture && npm run test:eval-automate-data && npm run test:clock-port && npm run test:probe-conformance && npm run test:corpus-conformance && npm run test:file-system-conformance && npm run test:file-system-port && npm run test:probe-targets && npm run test:port-totality && npm run test:ci-coverage && npm run test:ci-coverage-filters && npm run test:install-actionlint && npm run test:workflows-lint && npm run test:install-linux-sandbox && npm run test:shards && npm run test:groups && npm run test:cli && npm run test:test-review-pr-scope && npm run test:test-review-report && npm run test:test-review-rubric && npm run test:bmad-output-gated && npm run test:release-metadata && npm run test:release-notes && npm run test:lockfile-age && npm run test:lockfile-age-cache && npm run test:licences && npm run test:supply-chain && npm run test:boundary && npm run test:direction && npm run test:lineage && npm run test:guard-publish && npm run test:layering-boundary-lineage && npm run test:changelog && npm run test:tea-workflow-descriptions && npm run test:docs-mermaid && npm run test:llms-headroom && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
|
|
93
93
|
"test:atdd-isolation": "node test/test-atdd-isolation.js",
|
|
94
94
|
"test:atdd-net-guard": "node test/test-atdd-net-guard.js",
|
|
95
95
|
"test:atdd-scoring": "node test/test-atdd-scoring.js",
|
|
@@ -209,6 +209,7 @@
|
|
|
209
209
|
"test:evaluate-pr-workflow": "node test/test-evaluate-pr-tier.js workflow",
|
|
210
210
|
"test:evaluate-preflight": "node test/test-evaluate-preflight.js",
|
|
211
211
|
"test:evaluate-private": "node test/test-evaluate-evaluators.js --group=private",
|
|
212
|
+
"test:evaluate-progress": "node test/test-evaluate-progress.js",
|
|
212
213
|
"test:evaluate-promptfoo": "node test/test-evaluate-promptfoo.js",
|
|
213
214
|
"test:evaluate-records": "node test/test-evaluate-evaluators.js --group=records",
|
|
214
215
|
"test:evaluate-run": "node test/test-evaluate-run.js --group=run",
|