valent-pipeline 0.19.35 → 0.19.37

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.35",
3
+ "version": "0.19.37",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1875,6 +1875,21 @@ async function runStory(story) {
1875
1875
  await runGitStep(storyId, [
1876
1876
  `node .valent-pipeline/bin/cli.js git start-story --story ${storyId}${gitFlags}`,
1877
1877
  ], 'start', 'QA')
1878
+ // Cross-lane pin coherence on the inline-fix revalidation path (2026-06-19, M-28): the blocking
1879
+ // bug's fix already merged INTO this story branch (ship-story --into) and was CRITIC-approved in
1880
+ // its OWN lane — but that merge advanced the story tip PAST the story's last critic pin. The
1881
+ // revalidation runs no CRITIC (by design — the fix was already reviewed) and `commitAndRepin`
1882
+ // no-ops here (no devTasks), so the STALE story critic pin would trip `pins:critic`
1883
+ // (evidence assert --require-pins critic) against the merged tip and FALSE-VOID a genuine SHIP —
1884
+ // exactly the 2.11 reject. Re-pin critic so the pin records the cross-lane coverage that
1885
+ // genuinely exists (story code @ the old pin + the bug fix @ its lane's own approving pin); the
1886
+ // bug only merged here because it SHIPPED through its own CRITIC+JUDGE gates, and this no-rebuild
1887
+ // path introduces no new source, so the coverage is real. The v3 source-delta check still
1888
+ // fails-closed on any genuine un-reviewed change after this point. This is the "revalidation
1889
+ // legitimately re-pins" case the pin-coherence doctrine already anticipates (evidence.js).
1890
+ await runGitStep(storyId, [
1891
+ `node .valent-pipeline/bin/cli.js evidence pin --story ${storyId} --gate critic`,
1892
+ ], 'reval-repin-critic', 'QA')
1878
1893
  }
1879
1894
  await spawn('QA-B', 'qa-b.md',
1880
1895
  'This is a RE-VALIDATION of a previously-rejected story whose blocking bug has since been fixed and ' +
package/src/lib/sprint.js CHANGED
@@ -226,22 +226,27 @@ export function packSprint(stories, velocity, opts = {}) {
226
226
  // groomed story alone — pulling in its groomed prerequisites first so order stays dependency-safe
227
227
  // — accepting an over-budget sprint. `over_budget` is flagged so the planner can surface
228
228
  // "this story exceeds velocity; planned alone — consider splitting it."
229
+ // forceAdd: include a story over-budget, pulling in its unsatisfied blocking bugs + groomed deps
230
+ // FIRST so execution order stays dependency-safe. Shared by both over-budget guards below.
231
+ const forceAdd = (story) => {
232
+ if (inSprint.has(story.id)) return;
233
+ for (const bugId of bugsOf(story)) {
234
+ const bug = byId.get(bugId);
235
+ if (bug && !satisfied.has(bug.status) && !inSprint.has(bugId)) forceAdd(bug);
236
+ }
237
+ for (const depId of depsOf(story)) {
238
+ const dep = byId.get(depId);
239
+ if (dep && dep.status === 'groomed' && !inSprint.has(depId)) forceAdd(dep);
240
+ }
241
+ add(story);
242
+ };
243
+
229
244
  let overBudget = false;
245
+ // Anti-stall (empty sprint): nothing fit the budget but groomed work exists — the smallest eligible
246
+ // story exceeds the whole velocity. Plan the highest-priority groomed story alone, over budget, so the
247
+ // project can never stall on a story that can never be packed. Same out-of-batch guard as the main
248
+ // loop (review #20): never force-plan a story whose unpacked prerequisite never shipped.
230
249
  if (sprintStories.length === 0 && byPriority.length > 0) {
231
- const forceAdd = (story) => {
232
- if (inSprint.has(story.id)) return;
233
- for (const bugId of bugsOf(story)) {
234
- const bug = byId.get(bugId);
235
- if (bug && !satisfied.has(bug.status) && !inSprint.has(bugId)) forceAdd(bug);
236
- }
237
- for (const depId of depsOf(story)) {
238
- const dep = byId.get(depId);
239
- if (dep && dep.status === 'groomed' && !inSprint.has(depId)) forceAdd(dep);
240
- }
241
- add(story);
242
- };
243
- // Same out-of-batch guard as the main loop (review #20): the anti-stall must not force-plan
244
- // a story whose unpacked prerequisite never shipped.
245
250
  const candidate = byPriority.find((s) => outOfBatchDepsSatisfied(s));
246
251
  if (candidate) {
247
252
  forceAdd(candidate);
@@ -249,6 +254,20 @@ export function packSprint(stories, velocity, opts = {}) {
249
254
  }
250
255
  }
251
256
 
257
+ // Feature-starvation guard (HARNESS-GAPS 2026-06-18, M-19 Issue 2 / orchestrator-confirmed): the
258
+ // empty-sprint anti-stall above does NOT fire when a small bug/spike fits — so a 1-pt bug packed while
259
+ // every groomed FEATURE exceeds velocity strands all feature work indefinitely (a bug-only sprint makes
260
+ // no product progress). If the packed set has NO feature story yet a deps-satisfied groomed feature
261
+ // exists, force the highest-priority one in over budget (same dependency-safe forceAdd).
262
+ const isFeature = (s) => s && s.type !== 'bug' && s.type !== 'spike';
263
+ if (sprintStories.length > 0 && !sprintStories.some((id) => isFeature(byId.get(id)))) {
264
+ const topFeature = byPriority.find((s) => isFeature(s) && !inSprint.has(s.id) && outOfBatchDepsSatisfied(s));
265
+ if (topFeature) {
266
+ forceAdd(topFeature);
267
+ overBudget = true;
268
+ }
269
+ }
270
+
252
271
  const buffer = groomed.filter((s) => !inSprint.has(s.id)).map((s) => s.id);
253
272
  const pointsPlanned = sprintStories.reduce((sum, id) => sum + pointsOf(byId.get(id)), 0);
254
273