bmad-method-test-architecture-enterprise 3.0.1 → 3.1.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.
@@ -31,7 +31,7 @@
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. Existing CI and ATDD customizations can be migrated to the new skill names. Part of the BMad Method ecosystem.",
34
- "version": "3.0.1",
34
+ "version": "3.1.0",
35
35
  "author": {
36
36
  "name": "Murat K Ozcan (TEA Creator) & Brian (BMad) Madison"
37
37
  },
@@ -15,6 +15,10 @@
15
15
  * regular file held by the evaluation folder, its committed bytes disagree with the recorded digest, or
16
16
  * they digest to something other than the contract's `sourceSpecDigest` (contract-source freshness, Story
17
17
  * 2.2: a requirements change the contract has not absorbed blocks the pull request).
18
+ * - `candidate-placement`: a `*.candidate.json` file sits anywhere in the folder outside its top-level `discovery/` directory
19
+ * (the lane exploratory candidates live in, which no scored artifact reads), so a candidate cannot be digested into the corpus,
20
+ * the evaluator layer, the policy or the baseline. A development check leaves `corpus/held-out/` unopened, as it does for the
21
+ * corpus index.
18
22
  * - `runtime-owned-field`: a committed probe carries a field the runtime writes.
19
23
  * - `mutation-operator`: a mutation is not `replace-exact` with exactly one occurrence.
20
24
  * - `provisioned-target`: a mutation's `targetArtifact` sits inside a provisioned directory, which every
@@ -195,7 +199,7 @@ const {
195
199
 
196
200
  /** The skill runner's infrastructure exit codes (`cli/skill-runner.js`), which a registry entry for it must declare. */
197
201
  const SKILL_RUNNER_INFRASTRUCTURE_CODES = [3, 4, 5, 6];
198
- const { CorpusIndexError, INDEX_NAME, corpusIndexProblem } = require('./corpus-index');
202
+ const { CorpusIndexError, INDEX_NAME, corpusIndexProblem, unreadPaths } = require('./corpus-index');
199
203
 
200
204
  const Ajv = AjvModule.default ?? AjvModule;
201
205
 
@@ -221,6 +225,11 @@ const QUALIFICATION_PREFIX = 'baseline/qualification/';
221
225
  const MUTATION_ID_PATTERN = '^M-[0-9]{3,}$';
222
226
  const PROBE_FILE = /^(.+)\.probe\.json$/;
223
227
  const MUTATION_FILE = /^(.+)\.mutation\.json$/;
228
+ /** The one directory of an evaluation folder that holds exploratory candidates, and the suffix of their files. */
229
+ const CANDIDATE_LANE = 'discovery';
230
+ const CANDIDATE_SUFFIX = '.candidate.json';
231
+ /** Top-level entries a placement scan leaves out: the lane itself, runtime output and installed packages. */
232
+ const PLACEMENT_SKIPPED = new Set([CANDIDATE_LANE, 'runs', 'node_modules']);
224
233
  const WRITTEN_FILE_POINTER = /^\/interactions\/[^/]+\/artifact(?:\/|$)/;
225
234
  const INTERACTION_POINTER = /^\/interactions\/([^/]+)(?:\/|$)/;
226
235
  /** The step budget a signature's regular-expression operators get while `check` resolves them. */
@@ -514,6 +523,36 @@ function optionSetsByOperation(contract) {
514
523
  return sets;
515
524
  }
516
525
 
526
+ /**
527
+ * `candidate-placement`: a candidate file anywhere a scored artifact could read it. Links are not followed, so the scan stays
528
+ * inside the folder the way `listDirectory` does. `unread` is the `unreadPaths` of the directories the scan leaves unopened, the
529
+ * same set the corpus index leaves unread, so a development check never opens a sealed held-out folder in any spelling.
530
+ */
531
+ function checkCandidatePlacement(report, folder, unread, relativeDirectory = '') {
532
+ let entries;
533
+ try {
534
+ entries = listDirectory(folder, relativeDirectory);
535
+ } catch (error) {
536
+ // A directory this process cannot read is reported by the rule that owns it (the corpus index for corpus/), and a
537
+ // candidate cannot be read from it either.
538
+ if (error.code === 'EACCES' || error.code === 'EPERM') return;
539
+ throw error;
540
+ }
541
+ for (const entry of entries ?? []) {
542
+ const relative = relativeDirectory === '' ? entry.name : `${relativeDirectory}/${entry.name}`;
543
+ if (relativeDirectory === '' && PLACEMENT_SKIPPED.has(entry.name)) continue;
544
+ if (entry.isDirectory && unread.has(relative)) continue;
545
+ if (entry.isDirectory) checkCandidatePlacement(report, folder, unread, relative);
546
+ else if (entry.name.endsWith(CANDIDATE_SUFFIX)) {
547
+ report.add(
548
+ relative,
549
+ 'candidate-placement',
550
+ `a candidate file belongs under ${CANDIDATE_LANE}/<campaignId>/, which no scored artifact reads; here it would be digested into the evaluation`,
551
+ );
552
+ }
553
+ }
554
+ }
555
+
517
556
  /**
518
557
  * The `skill-root` and `skill-runner` rules over the registry, the launch and
519
558
  * the contract (AD-4): the runner must run the skill the launch names, declare
@@ -2482,6 +2521,9 @@ async function checkEvaluation(folder, { platform = process.platform, env = proc
2482
2521
  const routes = await checkProbes(report, folder, context, behaviors, mutations, registry, compiled, evaluation);
2483
2522
  checkHeldOut(report, folder, evaluation);
2484
2523
  const openPlan = partition !== 'development';
2524
+ const planFile = evaluation.partitionPlan?.heldOutPlan;
2525
+ const heldOutUnread = openPlan || typeof planFile !== 'string' ? [] : [HELD_OUT_DIRECTORY];
2526
+ checkCandidatePlacement(report, folder, unreadPaths(heldOutUnread));
2485
2527
  const heldOutPlan = checkPartitionPlan(report, folder, evaluation, context, { openPlan });
2486
2528
  // The answers a gameability probe gives the held-out plan's steps sit beside the plan, so a check that opens a sound plan reads them
2487
2529
  // and a development check never does (Story 1.109).
@@ -2520,10 +2562,7 @@ async function checkEvaluation(folder, { platform = process.platform, env = proc
2520
2562
  });
2521
2563
 
2522
2564
  try {
2523
- const planFile = evaluation.partitionPlan?.heldOutPlan;
2524
- const stale = await corpusIndexProblem(folder, {
2525
- unread: openPlan || typeof planFile !== 'string' ? [] : [HELD_OUT_DIRECTORY],
2526
- });
2565
+ const stale = await corpusIndexProblem(folder, { unread: heldOutUnread });
2527
2566
  if (stale !== null) report.add(INDEX_NAME, 'stale-index', stale);
2528
2567
  } catch (error) {
2529
2568
  if (!(error instanceof CorpusIndexError)) throw error;
@@ -221,4 +221,13 @@ async function corpusIndexProblem(folder, { unread = [] } = {}) {
221
221
  );
222
222
  }
223
223
 
224
- module.exports = { CorpusIndexError, INDEXED_ROOTS, INDEX_NAME, buildCorpusIndex, corpusDigestOf, corpusIndexProblem, writeCorpusIndex };
224
+ module.exports = {
225
+ CorpusIndexError,
226
+ INDEXED_ROOTS,
227
+ INDEX_NAME,
228
+ buildCorpusIndex,
229
+ corpusDigestOf,
230
+ corpusIndexProblem,
231
+ unreadPaths,
232
+ writeCorpusIndex,
233
+ };