valent-pipeline 0.19.62 → 0.19.64

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/bin/cli.js CHANGED
@@ -760,6 +760,7 @@ traceCmd
760
760
  .option('--reqs-manifest <path>', 'AC manifest (defaults to stories/<id>/output/reqs-brief.manifest.json)')
761
761
  .option('--spec-manifest <path>', 'Spec manifest (defaults to stories/<id>/output/qa-test-spec.manifest.json)')
762
762
  .option('--label <name>', 'Evidence-run label whose junit results prove execution (default qa-b-tests)')
763
+ .option('--run <runId>', 'Bind to an exact evidence run id (overrides label resolution — use when several suites multiplex one label)')
763
764
  .action(async (options) => {
764
765
  const { traceCheckCmd } = await import('../src/commands/trace.js');
765
766
  await traceCheckCmd(options);
@@ -773,6 +774,7 @@ traceCmd
773
774
  .option('--reqs-manifest <path>', 'AC manifest path override')
774
775
  .option('--spec-manifest <path>', 'Spec manifest path override')
775
776
  .option('--label <name>', 'Evidence-run label for junit results (default qa-b-tests)')
777
+ .option('--run <runId>', 'Bind to an exact evidence run id (overrides label resolution — use when several suites multiplex one label)')
776
778
  .option('--out <path>', 'Output path (defaults to stories/<id>/output/traceability-matrix.generated.md)')
777
779
  .action(async (options) => {
778
780
  const { traceMatrixCmd } = await import('../src/commands/trace.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.62",
3
+ "version": "0.19.64",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1951,7 +1951,7 @@ async function runStory(story) {
1951
1951
  // reworks via the QA-B re-execution alone, then falls through to JUDGE at the cap as usual.
1952
1952
  const evGate = await runEvidenceGate(storyId, [])
1953
1953
  recordStage('evidence', evGate)
1954
- const visual = await runVisualGate(storyId, profiles)
1954
+ const visual = await runVisualGate(storyId, profiles, itemType)
1955
1955
  recordStage('visual', visual)
1956
1956
  phase('Judge')
1957
1957
  const decision = await runJudgeGate(storyId, visual, evGate)
@@ -2234,8 +2234,9 @@ async function runStory(story) {
2234
2234
  recordStage('evidence', evGate)
2235
2235
 
2236
2236
  // Visual validation (revived PMCP): for ui-profile stories, produce pmcp-evidence.md so JUDGE's
2237
- // evidence pass has the visual artifact it requires. No-op (returns null) for non-ui stories.
2238
- const visual = await runVisualGate(storyId, profiles)
2237
+ // evidence pass has the visual artifact it requires. No-op (returns null) for non-ui stories, and a
2238
+ // non-blocking N/A for itemType:bug (the QA-A-less bug-fix path mints no checklist — M-192).
2239
+ const visual = await runVisualGate(storyId, profiles, itemType)
2239
2240
  recordStage('visual', visual)
2240
2241
 
2241
2242
  phase('Judge')
@@ -2826,11 +2827,23 @@ async function runStory(story) {
2826
2827
  // exact trigger QA-A uses to produce the checklist (qa-a/write-spec.md Step 9: `ui` in profiles).
2827
2828
  // The agent starts its own dev server: unlike the old always-on topology where QA-B triggered an
2828
2829
  // idle PMCP while QA-B's server was up, here the stages are sequential, so PMCP owns its server.
2829
- async function runVisualGate(sid, storyProfiles) {
2830
+ async function runVisualGate(sid, storyProfiles, sidItemType = 'story') {
2830
2831
  if (!storyProfiles.includes('ui')) {
2831
2832
  log(`${sid}: no ui profile — visual validation N/A (QA-A wrote no checklist; nothing for PMCP to run)`)
2832
2833
  return null
2833
2834
  }
2835
+ // QA-A-less bug-fix path (owner-ruled M-192): a bug-fix item runs bug-intake -> Build -> CRITIC ->
2836
+ // QA-B -> Judge and STRUCTURALLY SKIPS the Spec phase, so QA-A never mints a
2837
+ // visual-validation-checklist.md. Running PMCP against a checklist that can NEVER exist yields a
2838
+ // 0/0 block-fail (onMissingMcp inherited from the parent story) on a path with no checklist to
2839
+ // drive — a config contradiction, not a real visual defect. Treat it as a non-blocking N/A: no
2840
+ // visual coverage is lost because the parent story's `revalidate` re-drives the FULL visual gate
2841
+ // on the merged fix (its checklist exists). A FULL story always runs QA-A, so a missing checklist
2842
+ // THERE stays a real, blocking fault — this scopes the gate down ONLY for itemType:bug.
2843
+ if (sidItemType === 'bug') {
2844
+ log(`${sid}: ui profile but itemType:bug — the QA-A-less bug-fix path mints no visual-validation-checklist.md; visual validation N/A (owner-ruled M-192). The parent story's revalidate re-drives the full visual gate on the merged fix.`)
2845
+ return { schema: 1, agent: 'pmcp', story: sid, verdict: 'na', skipped: true, mcpAvailable: null, totalCheckpoints: 0, passed: 0, failed: 0, naCheckpoints: 0, files: [], notes: 'QA-A-less bug-fix path: no visual-validation-checklist.md minted (Spec phase skipped for itemType:bug); visual gate N/A (owner-ruled M-192). The parent story revalidate re-drives the full visual gate on the merged fix.' }
2846
+ }
2834
2847
  phase('Visual')
2835
2848
  const naMode = visualOnMissingMcp === 'na'
2836
2849
  const result = await spawn('PMCP', 'pmcp.md',
@@ -2920,7 +2933,13 @@ async function runStory(story) {
2920
2933
  : ''
2921
2934
  // A ui story has a PMCP visual pass; tell the evidence agent the result so Step 9 (Verify PMCP
2922
2935
  // Evidence) is grounded rather than guessing whether visual evidence should exist.
2923
- const visualNote = visual
2936
+ const visualNote = visual && visual.skipped
2937
+ ? `\n\nThis is a UI-profile BUG-FIX item: the QA-A-less bug-fix path mints no ` +
2938
+ `visual-validation-checklist.md, so PMCP did NOT run and there is NO pmcp-evidence.md. Treat ` +
2939
+ `PMCP evidence as N/A in Step 9 (no checklist ⟹ visual N/A) — do NOT flag its absence as a ` +
2940
+ `missing required file (owner-ruled M-192). The parent story's revalidate re-drives the full ` +
2941
+ `visual gate on the merged fix.`
2942
+ : visual
2924
2943
  ? `\n\nThis is a UI story: a visual-validation (PMCP) pass already ran and wrote ` +
2925
2944
  `pmcp-evidence.md (verdict: ${visual.verdict}, ${visual.passed || 0}/${visual.totalCheckpoints || 0} ` +
2926
2945
  `checkpoint(s) passed${visual.mcpAvailable === false ? `, browser MCP unavailable` : ''}). ` +
@@ -6,7 +6,7 @@ Runs after bug review (Steps 1-4) approves with no P4->P1-P3 escalations.
6
6
 
7
7
  Read all inputs, record in `inputsRead`. If any REQUIRED file missing, set blocker and STOP.
8
8
 
9
- Required: `execution-report.md`, `traceability-matrix.md`, `bugs.md`, `qa-test-spec.md` — all in `{story_output_dir}/`. Also `pmcp-evidence.md` **if this is a UI story** — i.e. `ui` is in `{testing_profiles}` (equivalently, `visual-validation-checklist.md` exists). If there is no `ui` profile, PMCP evidence is N/A — do NOT treat its absence as a missing required file.
9
+ Required: `execution-report.md`, `traceability-matrix.md`, `bugs.md`, `qa-test-spec.md` — all in `{story_output_dir}/`. Also `pmcp-evidence.md` **if this is a UI story** — i.e. `ui` is in `{testing_profiles}` **AND** `visual-validation-checklist.md` exists. The checklist's existence is the operative trigger: a `ui`-profile item with **no** `visual-validation-checklist.md` has PMCP **N/A** — do NOT treat the absence of `pmcp-evidence.md` as a missing required file. This is exactly the **QA-A-less bug-fix path** (an `itemType:bug` fix runs bug-intake → Build → CRITIC → QA-B → Judge and structurally skips the Spec phase, so QA-A mints no checklist); its visual coverage is re-driven in full by the parent story's `revalidate` on the merged fix (owner-ruled M-192). If there is no `ui` profile at all, PMCP evidence is likewise N/A.
10
10
 
11
11
  **Machine evidence (when `stories/{story_id}/evidence/` exists — required in that case):** the newest JSON under `evidence/asserts/` (the deterministic green-gate's verdict), `evidence/pins.jsonl` (which commit each gate reviewed), and the run records under `evidence/runs/` (frozen junit reports + hashes, written only by the CLI). **ATDD stories add** `evidence/atdd-red.json` (the pre-implementation failing run: per-case statuses, the pre-dev SHA, the acceptance-source snapshot hashes) and — for a proven story — `evidence/proof.json` (red run + green run + the CLI-hashed diff between: the story's headline evidence). These are the authoritative numbers — the markdown artifacts are the human-readable companion. If the evidence directory does not exist, this is a LEGACY story: apply the legacy fallbacks below and flag every count as self-reported.
12
12
 
@@ -9,9 +9,21 @@
9
9
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
10
10
  import { join, isAbsolute, dirname } from 'path';
11
11
  import { traceCheck, traceMatrix } from '../lib/trace.js';
12
- import { evidenceDir, latestRun, resolveRoot } from '../lib/evidence.js';
12
+ import { evidenceDir, resolveRunForAssert, requiredCaseIdsForRun, resolveRoot } from '../lib/evidence.js';
13
13
  import { parseJunit } from '../lib/junit.js';
14
14
 
15
+ /** Load an exact evidence run by id (for `--run`), or null if absent/unreadable. */
16
+ function loadRunById(evDir, runId) {
17
+ const dir = join(evDir, 'runs', runId);
18
+ const recPath = join(dir, 'evidence.json');
19
+ if (!existsSync(recPath)) return null;
20
+ try {
21
+ return { runId, dir, record: JSON.parse(readFileSync(recPath, 'utf-8')) };
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
15
27
  function readJson(path, what) {
16
28
  try {
17
29
  return JSON.parse(readFileSync(path, 'utf-8'));
@@ -41,11 +53,24 @@ function loadInputs(options) {
41
53
  return { noop: 'spec manifest predates the per-case `ac` field — skipping (no-op pass). Re-run QA-A to gain mechanical AC coverage.' };
42
54
  }
43
55
 
44
- // Executed testcases from the newest report-bearing evidence run (junit re-parsed from the
45
- // frozen copies never from a transcription).
56
+ // Executed testcases from the evidence run that ACTUALLY carries this story's required acceptance
57
+ // casesNOT merely the newest same-label run. A REVALIDATE (and any multi-suite cycle) re-runs the
58
+ // full npm suite under the `qa-b-tests` label ALONGSIDE the acceptance run; binding to the newest by
59
+ // label alone globs the full suite (which carries ZERO of the story's required cases) and false-fails
60
+ // every required case as "unexecuted" — label-multiplexing, the M-11/.45/.60 class, of which the
61
+ // trace-check was the last unfixed surface (the `evidence assert` + verdict-crosscheck paths already
62
+ // resolve this way). resolveRunForAssert binds to the same-SHA sibling carrying a required case id and
63
+ // preserves the #5 crash-loud invariant (a report-less newest is never silently skipped). An explicit
64
+ // --run pins exactly, for the operator who wants to override resolution. (junit re-parsed from the
65
+ // frozen copies — never from a transcription.)
46
66
  let junitCases = null;
47
67
  let runId = null;
48
- const run = latestRun(evidenceDir(root, story), { label: options.label || 'qa-b-tests', requireReport: true });
68
+ const evDir = evidenceDir(root, story);
69
+ const label = options.label || 'qa-b-tests';
70
+ const requiredCaseIds = requiredCaseIdsForRun(cases);
71
+ const run = options.run
72
+ ? loadRunById(evDir, options.run)
73
+ : resolveRunForAssert(evDir, { label, requiredCaseIds });
49
74
  if (run) {
50
75
  runId = run.runId;
51
76
  junitCases = [];