valent-pipeline 0.19.39 → 0.19.41
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
|
@@ -1672,6 +1672,25 @@ async function runShipStep(sid, commandStr, stepPhase) {
|
|
|
1672
1672
|
log(`${sid}: ship-story attempt ${attempt}/${shipMergeAttempts} did not land (${res ? 'transient git-runner failure' : 'GIT runner died'}) — retrying the deterministic, idempotent merge`)
|
|
1673
1673
|
}
|
|
1674
1674
|
}
|
|
1675
|
+
// Deterministic post-ship verify (M-36/M-37, 2026-06-19): ship-story is a code-owned git merge, but
|
|
1676
|
+
// its result is transcribed by a haiku GIT runner — a transcription slip (or an agent death AFTER the
|
|
1677
|
+
// merge landed) drops `merged:true`, so the loop above reports "did not land" on a merge that ACTUALLY
|
|
1678
|
+
// SUCCEEDED, falsely blocking a shipped story (the git-ship-failed false-positive that stranded 2.20).
|
|
1679
|
+
// Git is the ground truth: when we did NOT confirm a land/conflict/skip, ask `git story-status` — its
|
|
1680
|
+
// `merged` is `isMergedInto` (computed in code, not by the agent); if the branch IS now an ancestor of
|
|
1681
|
+
// the target, the merge LANDED regardless of what the ship runner said → recover to merged:true. A real
|
|
1682
|
+
// conflict (broken out above) and a genuine non-landing (story-status merged:false → real merge-block)
|
|
1683
|
+
// are untouched. The ancestor check cleanly separates the two classes — field-validated by hand on 2.20
|
|
1684
|
+
// (M-38). Only runs on the rare non-landed path, so the happy ship adds zero overhead.
|
|
1685
|
+
const confirmed = !!(res && (res.merged === true || res.skipped === true || res.conflict === true))
|
|
1686
|
+
if (gitFlowEnabled && !confirmed) {
|
|
1687
|
+
const status = await runGitStep(sid,
|
|
1688
|
+
[`node .valent-pipeline/bin/cli.js git story-status --story ${sid}${gitFlags}`], 'merge-verify', stepPhase)
|
|
1689
|
+
if (status && status.merged === true) {
|
|
1690
|
+
log(`${sid}: ship-story runner reported no-land but git story-status confirms the branch IS merged into the target — recovering to merged:true (transcription false-failure, NOT a real merge-block)`)
|
|
1691
|
+
return { ...(res || {}), merged: true, recoveredByVerify: true }
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1675
1694
|
return res
|
|
1676
1695
|
}
|
|
1677
1696
|
|
package/src/lib/git-flow.js
CHANGED
|
@@ -489,6 +489,29 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
489
489
|
|
|
490
490
|
git(root, ['checkout', base]);
|
|
491
491
|
git(root, ['checkout', '-b', branch]);
|
|
492
|
+
// BUG-2 (M-36, 2026-06-19): a branch BASED ON a source story branch (--from-story, e.g. an inline bug
|
|
493
|
+
// fix) starts at the SOURCE's tip — which can predate commits made to the TARGET since the source was
|
|
494
|
+
// cut, most importantly the rejected story's BUG ENTRIES that the disposition filed to the shared
|
|
495
|
+
// backlog on the target. The freshly-based branch then can't see them, so `backlog ship --story <bug>`
|
|
496
|
+
// errors "not found" (and an eventual --into merge fights stale target state). The target is the source
|
|
497
|
+
// of truth for shared sprint state, so sync it into the new branch. Guarded tightly: ONLY when actually
|
|
498
|
+
// basing on another branch (basedOnStory) and the target is genuinely ahead — a fresh story off the
|
|
499
|
+
// target is already current (no-op) — and ONLY on this create path, never on a resume (a refresh after
|
|
500
|
+
// the critic pin would move HEAD past it). A conflict (source + target diverged on the SAME file) aborts
|
|
501
|
+
// cleanly and leaves the branch at its base — degraded but no worse than before, and surfaced — rather
|
|
502
|
+
// than wedging a half-merge that would hard-stop every later commit in this checkout.
|
|
503
|
+
let refreshedFromTarget = false;
|
|
504
|
+
let refreshConflict = false;
|
|
505
|
+
if (basedOnStory && branchExists(root, target) && !isMergedInto(root, target, branch)) {
|
|
506
|
+
try {
|
|
507
|
+
git(root, ['merge', '--no-ff', '-m',
|
|
508
|
+
buildCommitMessage({ storyId: story, phase: 'sync', summary: `sync ${target} into ${branch} (inherit shared state from a behind base)` }), target]);
|
|
509
|
+
refreshedFromTarget = true;
|
|
510
|
+
} catch {
|
|
511
|
+
if (mergeInProgress(root)) git(root, ['merge', '--abort']);
|
|
512
|
+
refreshConflict = true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
492
515
|
writeFlowRecord(root, story, {
|
|
493
516
|
schema: 1,
|
|
494
517
|
kind: 'git-flow',
|
|
@@ -499,7 +522,9 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
499
522
|
basedOnStory: basedOnStory ? storyBranchName(prefix, fromStory) : null,
|
|
500
523
|
startedAt: new Date().toISOString(),
|
|
501
524
|
});
|
|
502
|
-
return { branch, base, target, created: true, resumed: false, snapshotSha
|
|
525
|
+
return { branch, base, target, created: true, resumed: false, snapshotSha,
|
|
526
|
+
...(refreshedFromTarget ? { refreshedFromTarget: true } : {}),
|
|
527
|
+
...(refreshConflict ? { refreshConflict: true } : {}) };
|
|
503
528
|
}
|
|
504
529
|
|
|
505
530
|
/**
|