valent-pipeline 0.19.63 → 0.19.65

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.63",
3
+ "version": "0.19.65",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -85,6 +85,8 @@ For each NFR-sensitive path: `[NFR-PERF]` response time + load patterns; `[NFR-S
85
85
  - If `ui` in `{testing_profiles}` → **MANDATORY.** Read `uxa-spec.md`. If `uxa-spec.md` is missing, write `status: blocked` to your frontmatter — do NOT proceed without it. For each page state define: Checkpoint ID (VV-{NNN}), Page/Route, State (Default/Loading/Empty/Error/Success or custom), AC Reference, Area labels in scope, Screenshot filename (`{story_id}_VV-{NNN}_{page}_{state}.png`), Expected visual elements, Setup instructions, Pass criteria. Write to `{story_output_dir}/visual-validation-checklist.md`.
86
86
  - If `ui` NOT in `{testing_profiles}` → skip, note "N/A — no UI profile."
87
87
 
88
+ **The reach step must use the app's ACTUAL navigation mechanism — never assume a URL route exists.** Read `uxa-spec.md` for HOW each screen is reached, then match the checklist to it. A **URL-routed** screen (a server route or a real client router maps a path → screen) takes a URL-nav reach step. A **store-driven / client-side-nav** screen does NOT: a single-page app mounts views from application state (e.g. app-root renders the in-career screen at `loadingState === 'active'`) with NO URL router, so a URL-nav to a path like `/game` SILENTLY drops to the default view (login) → the screen never mounts → the gate records 0/N checkpoints → a **false visual:fail that reads as a code defect** (and costs a full reject + re-run — Epic-3 3.4). For a store-driven screen the reach step MUST drive the real nav: run the user flow that produces the state (register → login → Start Career → `active`) OR inject the documented store state (`window.__<store>__`) at the verified DPR — never a URL-nav. Confirm the app's nav model BEFORE writing any `Page / Route` + `Setup Instructions`; if the app has no URL router, author NO URL-route reach steps. This is the reach-step analogue of the Step 6b reachability contract: the screen must be genuinely reachable by the mechanism the checklist drives, or the gate measures nothing.
89
+
88
90
  **Sanctioning a checkpoint `N/A (sanction)` — the visual gate covers browser-testable *static renders* only.** The PMCP visual gate drives a **stateless** browser-automation MCP: it can navigate, snapshot, and assert rendered DOM, but it CANNOT drive a persistent WebSocket event (`career.created`, `career.resumed`), sever a connection mid-session, or catch an in-flight transient. A checkpoint whose pass criteria *require* such a flow will false-FAIL the visual gate even when the feature works. For exactly those checkpoints, set the checklist's **`N/A (sanction)`** field to `yes — <rationale>`, where the rationale (a) states why the browser MCP cannot drive it and (b) **names the acceptance/integration test that DOES validate it** — that test MUST exist; an `na` with no validating test is a coverage hole, not a sanction. PMCP then records the checkpoint N/A (not FAIL) and JUDGE treats it as non-blocking. Keep every browser-testable static render (default/empty/error/success states, element presence, layout) as a normal PASS/FAIL checkpoint — only WS-event-driven / non-render flows are eligible. When in doubt, do NOT sanction: a real render that fails must stay a FAIL.
89
91
 
90
92
  **The validating test a WS-driven sanction names MUST be a true end-to-end reachability case — never a service-layer test that bypasses the live event path.** When the UXA spec carries a Step 6b reachability contract for a WS-driven screen, author a REQUIRED `e2e` acceptance case that **drives the real triggering event over the actual connection** (register/login → issue the WS command → assert the screen leaves loading and renders), then sanction the matching visual checkpoint to it. A test that calls the service/handler layer directly (e.g. asserts `POST /api/careers` or the service method) can pass green while the **live WS command path is structurally broken** — a missing initial-state push, or a bootstrap command that violates a pre-event invariant (writing an FK row keyed on an id that does not exist until the event lands). That gap stays invisible until the visual gate a full attempt later. The reachability case is what forces the live path; the service-layer test is necessary but NOT sufficient to back a sanction.
@@ -39,7 +39,7 @@ blockers: []
39
39
  | Field | Value |
40
40
  |-------|-------|
41
41
  | **Checkpoint ID** | VV-{NNN} |
42
- | **Page / Route** | {page name and URL path, e.g. Registration / /register} |
42
+ | **Page / Route** | {page name + how it is REACHED. A URL path ONLY if the app is genuinely URL-routed, e.g. Registration / /register. For a store-driven / client-side-nav SPA with no URL router, name the nav mechanism instead — e.g. "Office / reached via in-career flow (Start Career → active), NO URL route" — never a path that 404s to the default view} |
43
43
  | **State** | {Default \| Loading \| Empty \| Error \| Success \| custom state description} |
44
44
  | **AC Reference** | {AC-ID from qa-test-spec.md, e.g. AC-1} |
45
45
  | **Area Labels in Scope** | {list of area labels from uxa-spec.md that should be visible in this state} |
@@ -51,7 +51,7 @@ blockers: []
51
51
  - {element}: {expected appearance, position, or content}
52
52
 
53
53
  **Setup Instructions:**
54
- - {preconditions PMCP must establish before capturing -- e.g. seed data, auth state, form values}
54
+ - {preconditions PMCP must establish before capturing -- e.g. seed data, auth state, form values; for a store-driven-nav screen, the EXACT steps to REACH it (drive the user flow or inject the documented store) — never assume a URL-nav lands on it}
55
55
 
56
56
  **Pass Criteria:**
57
57
  - {what PMCP must verify in the screenshot to mark this checkpoint as pass}
@@ -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 = [];