valent-pipeline 0.19.34 → 0.19.36
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 +1 -1
- package/src/commands/evidence.js +14 -1
- package/src/lib/evidence.js +0 -0
- package/src/lib/sprint.js +33 -14
package/package.json
CHANGED
package/src/commands/evidence.js
CHANGED
|
@@ -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, resolveRunForAssert, latestPins,
|
|
19
|
+
runEvidence, pinGate, formatRunTrailer, formatRunDigest, resolveRoot, evidenceDir, latestRun, resolveRunForAssert, detectLabelMultiplex, latestPins,
|
|
20
20
|
verifyIntegrity, assertRules, assertRedRules, pinSourceDelta, snapshotFiles, buildProof, utcCompact,
|
|
21
21
|
latestAssert, collectFailingCases, authenticateBaseline, verifyShipEvidence, sha256Hex,
|
|
22
22
|
validStoryId, recordStageResults,
|
|
@@ -225,6 +225,19 @@ export async function evidenceAssertCmd(options) {
|
|
|
225
225
|
process.exit(0);
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Advisory (option C): residual label-multiplex at this SHA. Record-time auto-namespacing heals the
|
|
229
|
+
// common case (a sibling recorded AFTER the acceptance run); this catches one that recorded BEFORE it
|
|
230
|
+
// (so it kept the base label). Non-blocking — resolveRunForAssert already bound to the required-case
|
|
231
|
+
// run; the warning just surfaces the smell so the project fixes its labels at source.
|
|
232
|
+
try {
|
|
233
|
+
const mx = detectLabelMultiplex(evDir, { sha: run.record?.git?.sha, label });
|
|
234
|
+
if (mx.multiplexed) {
|
|
235
|
+
console.error(`evidence-assert: ⚠ label-multiplex — ${mx.suites.length} disjoint suites share label "${label}" ` +
|
|
236
|
+
`at SHA ${String(run.record?.git?.sha).slice(0, 8)} (runs ${mx.suites.map((s) => s.runId).join(', ')}). Bound to the ` +
|
|
237
|
+
`required-case run; give each suite a distinct --label in QA-B to retire this warning. (HARNESS-GAPS option C)`);
|
|
238
|
+
}
|
|
239
|
+
} catch { /* advisory only — never block the assert */ }
|
|
240
|
+
|
|
228
241
|
// Re-parse the frozen reports from disk — machine-derived twice (at run time and here).
|
|
229
242
|
const parsedReports = (run.record.reports || []).map((r) => {
|
|
230
243
|
let parsed = null;
|
package/src/lib/evidence.js
CHANGED
|
Binary file
|
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
|
|