valent-pipeline 0.19.31 → 0.19.33

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.31",
3
+ "version": "0.19.33",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@ import { dirname, join, isAbsolute } from 'path';
16
16
  import { randomBytes } from 'crypto';
17
17
  import { parse as parseYaml } from 'yaml';
18
18
  import {
19
- runEvidence, pinGate, formatRunTrailer, formatRunDigest, resolveRoot, evidenceDir, latestRun, latestPins,
19
+ runEvidence, pinGate, formatRunTrailer, formatRunDigest, resolveRoot, evidenceDir, latestRun, resolveRunForAssert, latestPins,
20
20
  verifyIntegrity, assertRules, assertRedRules, pinSourceDelta, snapshotFiles, buildProof, utcCompact,
21
21
  latestAssert, collectFailingCases, authenticateBaseline, verifyShipEvidence, sha256Hex,
22
22
  validStoryId, recordStageResults,
@@ -180,6 +180,18 @@ export async function evidenceAssertCmd(options) {
180
180
  process.exit(2);
181
181
  }
182
182
 
183
+ // Required acceptance case ids, read early so the run resolver can bind to the suite that actually
184
+ // carries them when a project multiplexes suites under one label (HARNESS-GAPS 2026-06-18 Gap 2).
185
+ // The full manifest is re-read below for the count/skips rules.
186
+ let requiredCaseIds = [];
187
+ try {
188
+ const mp = options.manifest ? abs(options.manifest) : join(root, 'stories', story, 'output', 'qa-test-spec.manifest.json');
189
+ if (existsSync(mp)) {
190
+ const m = JSON.parse(readFileSync(mp, 'utf-8'));
191
+ if (Array.isArray(m?.cases)) requiredCaseIds = m.cases.filter((c) => c?.kind === 'acceptance' && c?.required !== false && c?.id).map((c) => c.id);
192
+ }
193
+ } catch { requiredCaseIds = []; }
194
+
183
195
  // Resolve the run: explicit --run, else the newest run for the label. Green demands a report
184
196
  // (junit is the verdict's substance); red takes the newest run regardless — "no report at all"
185
197
  // is itself a red-gate input (the weak-red rule judges it).
@@ -195,8 +207,10 @@ export async function evidenceAssertCmd(options) {
195
207
  // No requireReport filter (review 2026-06-10 #5): a suite that crashes before emitting junit
196
208
  // produces the NEWEST run with no reports — filtering it out silently asserted the previous
197
209
  // (green, report-bearing) run instead. The newest run is always the one asserted; a report-less
198
- // failing run now fails loudly via the green (zero cases) + run-integrity rules.
199
- run = latestRun(evDir, { label });
210
+ // failing run now fails loudly via the green (zero cases) + run-integrity rules. resolveRunForAssert
211
+ // additionally binds to the same-SHA run carrying the required cases when suites multiplex one label
212
+ // (Gap 2), preserving the report-less-crash-wins-newest invariant.
213
+ run = resolveRunForAssert(evDir, { label, requiredCaseIds });
200
214
  }
201
215
  if (!run) {
202
216
  // --require-evidence (review 2026-06-10 #37): the graceful no-op exists for pre-evidence
Binary file
@@ -66,10 +66,38 @@ export function isGatableToken(token) {
66
66
  return true;
67
67
  }
68
68
 
69
- /** Remove a trailing line-comment so a commented-out `// it.skip(...)` is not flagged as a skip. */
70
- function stripLineComment(line) {
71
- const i = line.indexOf('//');
72
- return i === -1 ? line : line.slice(0, i);
69
+ /**
70
+ * Blank out JS/TS comments — both line and block comments — so a skip marker that appears only inside
71
+ * a comment (a commented-out `// it.skip(...)`, or an `it.skip` mentioned inside a JSDoc/block comment)
72
+ * is not mistaken for a real skip (HARNESS-GAPS 2026-06-18). Comment bytes become spaces; newlines and
73
+ * tabs are preserved so line numbers and the waiver line-window stay exact. String / template literals
74
+ * are tracked, so a comment-looking substring INSIDE a string is left intact — a real marker is never
75
+ * hidden (no false-negatives).
76
+ */
77
+ export function stripComments(content) {
78
+ const s = String(content || '');
79
+ let out = '';
80
+ let state = 'code'; // code | line | block | sq | dq | tpl
81
+ const ws = (ch) => (ch === '\n' || ch === '\t' ? ch : ' ');
82
+ for (let i = 0; i < s.length; i++) {
83
+ const c = s[i];
84
+ const d = s[i + 1];
85
+ if (state === 'code') {
86
+ if (c === '/' && d === '/') { state = 'line'; out += ' '; i++; continue; }
87
+ if (c === '/' && d === '*') { state = 'block'; out += ' '; i++; continue; }
88
+ if (c === "'") { state = 'sq'; out += c; continue; }
89
+ if (c === '"') { state = 'dq'; out += c; continue; }
90
+ if (c === '`') { state = 'tpl'; out += c; continue; }
91
+ out += c; continue;
92
+ }
93
+ if (state === 'line') { if (c === '\n') { state = 'code'; out += c; } else out += ws(c); continue; }
94
+ if (state === 'block') { if (c === '*' && d === '/') { state = 'code'; out += ' '; i++; } else out += ws(c); continue; }
95
+ // inside a string/template literal: copy verbatim, honor backslash escapes, close on the quote
96
+ out += c;
97
+ if (c === '\\') { out += (s[i + 1] ?? ''); i++; continue; }
98
+ if ((state === 'sq' && c === "'") || (state === 'dq' && c === '"') || (state === 'tpl' && c === '`')) state = 'code';
99
+ }
100
+ return out;
73
101
  }
74
102
 
75
103
  /**
@@ -81,14 +109,16 @@ function stripLineComment(line) {
81
109
  */
82
110
  function scanSkips(file) {
83
111
  const out = [];
84
- const lines = (file.content || '').split(/\r?\n/);
85
- for (let i = 0; i < lines.length; i++) {
86
- const raw = lines[i];
87
- const code = stripLineComment(raw);
112
+ const raw = (file.content || '').split(/\r?\n/);
113
+ // Match skip markers in CODE only comments (line `//` and block `/* */`) are blanked so a marker
114
+ // that merely appears inside a comment is not flagged. The waiver check still reads the RAW line,
115
+ // since the `valent-waiver:` token lives in a comment / the it.skip() title.
116
+ const code = stripComments(file.content || '').split(/\r?\n/);
117
+ for (let i = 0; i < raw.length; i++) {
88
118
  SKIP_MARKER.lastIndex = 0;
89
- const m = code.match(SKIP_MARKER);
119
+ const m = (code[i] || '').match(SKIP_MARKER);
90
120
  if (!m) continue;
91
- const waived = WAIVER_RE.test(raw) || lines.slice(Math.max(0, i - 3), i).some((l) => WAIVER_RE.test(l));
121
+ const waived = WAIVER_RE.test(raw[i]) || raw.slice(Math.max(0, i - 3), i).some((l) => WAIVER_RE.test(l));
92
122
  if (waived) continue;
93
123
  out.push({
94
124
  kind: 'unwaived-skip',