valent-pipeline 0.19.21 → 0.19.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.21",
3
+ "version": "0.19.22",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,11 +55,25 @@ export function crosscheckVerdict({ reported = null, bugsBlock = null, assertVer
55
55
  }
56
56
 
57
57
  const claim = (field) => reported && reported[field] !== undefined && reported[field] !== null;
58
+ // Counts of BAD things (failures, open blocking bugs): a self-report HIGHER than the machine
59
+ // recount is the SAFE, conservative direction — the gate was harder on itself than reality (e.g. a
60
+ // JUDGE that counted a pre-existing, baseline-excluded failure as testsFailed/openP1toP3). That is
61
+ // NOT the integrity breach this module exists to catch — a gate HIDING failures/bugs to sneak a
62
+ // ship is claimed < actual — and refusing it stranded genuinely shippable stories. So an
63
+ // over-report on these fields reconciles to the authoritative recount with a warning; an
64
+ // under-report stays a hard mismatch. (Prompt tightening alone did not hold — the LLM still
65
+ // over-counts; this is the mechanical backstop.) Good-thing counts (testsPassed) and exact-match
66
+ // fields keep strict equality, where over-claiming IS the unsafe direction.
67
+ const SAFE_IF_OVER_REPORTED = new Set(['testsFailed', 'openP1toP3']);
58
68
  const compare = (field, actual) => {
59
69
  recounted[field] = actual;
60
- if (claim(field) && reported[field] !== actual) {
61
- mismatches.push({ field, claimed: reported[field], actual });
70
+ if (!claim(field) || reported[field] === actual) return;
71
+ if (SAFE_IF_OVER_REPORTED.has(field) && typeof reported[field] === 'number'
72
+ && typeof actual === 'number' && reported[field] > actual) {
73
+ warnings.push(`${field}: self-reported ${reported[field]} but artifacts show ${actual} — over-report (safe, conservative direction), reconciled to the recount`);
74
+ return;
62
75
  }
76
+ mismatches.push({ field, claimed: reported[field], actual });
63
77
  };
64
78
 
65
79
  // Test counts — from the assert verdict (machine-derived twice over).
@@ -402,7 +402,19 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
402
402
  snapshotSha = snap.sha || null;
403
403
  }
404
404
 
405
- const target = targetBranch || onBranch;
405
+ // An inline fix (--from-story) inherits its SOURCE story's recorded target NOT the
406
+ // currently-checked-out branch. Running several fixes for one story back-to-back leaves HEAD on the
407
+ // previous fix's branch; defaulting `target` to onBranch made each later fix target (and, via the
408
+ // base check below, base on) its SIBLING fix branch — so the fixes silently chained onto the first
409
+ // fix's branch instead of the story, stranded off the real target (2026-06-14). An explicit
410
+ // --target still wins; onBranch stays the fallback for a plain story or a fix whose source has no
411
+ // flow record yet. With the right target, the base check (`!isMergedInto(source, target)`) also
412
+ // resolves correctly — the source story is unmerged into the REAL target, so the fix bases on it.
413
+ let inheritedTarget = null;
414
+ if (fromStory && !targetBranch) {
415
+ inheritedTarget = readFlowRecord(root, fromStory)?.target || null;
416
+ }
417
+ const target = targetBranch || inheritedTarget || onBranch;
406
418
  let base = target;
407
419
  let basedOnStory = false;
408
420
  if (fromStory) {