valent-pipeline 0.19.22 → 0.19.23
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
|
@@ -837,6 +837,13 @@ const gitBranchPrefix = (typeof (gitCfg.storyBranchPrefix ?? gitCfg.story_branch
|
|
|
837
837
|
// plain ref-safe names stay bare so the composed commands read naturally in prompts.
|
|
838
838
|
const refq = (s) => (/^[A-Za-z0-9._/-]+$/.test(s) ? s : shq(s))
|
|
839
839
|
const gitFlags = (gitTargetBranch ? ` --target ${refq(gitTargetBranch)}` : '') + (gitBranchPrefix !== 'story/' ? ` --prefix ${refq(gitBranchPrefix)}` : '')
|
|
840
|
+
// Ship-merge retry cap (review 2026-06-14): how many times a non-landed, non-conflict ship-story
|
|
841
|
+
// result (a transient GIT-runner failure — the haiku transcriber dying / returning null, a momentary
|
|
842
|
+
// index lock) is re-attempted before the story is declared merge-blocked. The merge is deterministic
|
|
843
|
+
// and ship-story is idempotent (an already-merged branch returns already:true), so retrying is always
|
|
844
|
+
// safe. A single hiccup used to drop a JUDGE-approved story to merge-blocked → requeued a whole sprint
|
|
845
|
+
// later, silently. Default 3 (1 + 2 retries); pipeline-config.yaml git.ship_merge_attempts overrides.
|
|
846
|
+
const shipMergeAttempts = Math.max(1, Number.isInteger(gitCfg.shipMergeAttempts ?? gitCfg.ship_merge_attempts) ? (gitCfg.shipMergeAttempts ?? gitCfg.ship_merge_attempts) : 3)
|
|
840
847
|
const gitCommitCmd = (sid, phase, extra = '') =>
|
|
841
848
|
`node .valent-pipeline/bin/cli.js git commit-phase --story ${sid} --phase ${phase}${extra}`
|
|
842
849
|
|
|
@@ -1423,6 +1430,16 @@ phase('Backlog')
|
|
|
1423
1430
|
// outcomes) is also REQUEUED to `groomed` — the sweep that closes the `sprint-planned` black hole
|
|
1424
1431
|
// (review #16): nothing the plan tagged may end the sprint still in-flight.
|
|
1425
1432
|
const isMergeBlocked = (r) => r.verdict === 'blocked' && (r.reason === 'merge-conflict' || r.reason === 'git-ship-failed')
|
|
1433
|
+
// Surface merge-blocked stories LOUDLY (review 2026-06-14): a JUDGE-approved story whose merge did
|
|
1434
|
+
// not land is requeued to groomed (correct — an infra outcome, not a quality reject), but that
|
|
1435
|
+
// requeue used to be silent: a run could end with a green-looking ledger while a shipped-on-paper
|
|
1436
|
+
// story sat un-integrated and a later story merged ahead of it. Name them in the summary + return.
|
|
1437
|
+
const mergeBlocked = results
|
|
1438
|
+
.filter((r) => !r.resumedSkip && isMergeBlocked(r))
|
|
1439
|
+
.map((r) => ({ storyId: r.storyId, reason: r.reason }))
|
|
1440
|
+
if (mergeBlocked.length) {
|
|
1441
|
+
log(`⚠ MERGE-BLOCKED: ${mergeBlocked.length} JUDGE-approved stor${mergeBlocked.length === 1 ? 'y' : 'ies'} did NOT reach the target after ${shipMergeAttempts} ship attempt(s) and ${mergeBlocked.length === 1 ? 'was' : 'were'} requeued to groomed for re-ship — ${mergeBlocked.map((m) => `${m.storyId} (${m.reason})`).join(', ')}. NOT lost, NOT integrated: a follow-up sprint re-ships from the retained branch, or ship manually.`)
|
|
1442
|
+
}
|
|
1426
1443
|
const backlog = await persistBacklog(
|
|
1427
1444
|
results.filter((r) => r.shipped),
|
|
1428
1445
|
results.filter((r) => r.partial),
|
|
@@ -1439,6 +1456,7 @@ return {
|
|
|
1439
1456
|
results,
|
|
1440
1457
|
integration,
|
|
1441
1458
|
post_merge_findings: postMergeFindings,
|
|
1459
|
+
merge_blocked: mergeBlocked,
|
|
1442
1460
|
backlog,
|
|
1443
1461
|
}
|
|
1444
1462
|
|
|
@@ -1612,6 +1630,25 @@ async function runGitStep(sid, commands, label, stepPhase, dirPhrase = 'the proj
|
|
|
1612
1630
|
return result
|
|
1613
1631
|
}
|
|
1614
1632
|
|
|
1633
|
+
// runShipStep: runGitStep specialized for the terminal ship-story merge, with a retry (review
|
|
1634
|
+
// 2026-06-14). A single transient GIT-runner failure (null/agent-death, or a non-landed non-conflict
|
|
1635
|
+
// result) used to drop a JUDGE-approved story straight to merge-blocked — requeued a whole sprint
|
|
1636
|
+
// later, with the only trace a mid-run log line. ship-story is deterministic and idempotent (an
|
|
1637
|
+
// already-merged branch returns already:true; a never-merged one re-attempts the same merge), so a
|
|
1638
|
+
// non-terminal result is re-run up to shipMergeAttempts. A REAL conflict (conflict===true) is NOT
|
|
1639
|
+
// retried — a blind re-merge cannot clear it; that needs the --reship sync/resolve path.
|
|
1640
|
+
async function runShipStep(sid, commandStr, stepPhase) {
|
|
1641
|
+
let res = null
|
|
1642
|
+
for (let attempt = 1; attempt <= shipMergeAttempts; attempt++) {
|
|
1643
|
+
res = await runGitStep(sid, [commandStr], 'ship', stepPhase)
|
|
1644
|
+
if (res && (res.merged === true || res.skipped === true || res.conflict === true)) break
|
|
1645
|
+
if (attempt < shipMergeAttempts) {
|
|
1646
|
+
log(`${sid}: ship-story attempt ${attempt}/${shipMergeAttempts} did not land (${res ? 'transient git-runner failure' : 'GIT runner died'}) — retrying the deterministic, idempotent merge`)
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return res
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1615
1652
|
// ===========================================================================
|
|
1616
1653
|
// runStory: the per-story pipeline (kept inline, not a nested workflow(), so the single
|
|
1617
1654
|
// workflow() nesting level stays free for plan/retro — see reimplementation-plan §5b).
|
|
@@ -1785,8 +1822,8 @@ async function runStory(story) {
|
|
|
1785
1822
|
}
|
|
1786
1823
|
} else if (sync && (sync.synced === true || sync.skipped === true)) {
|
|
1787
1824
|
// Clean (or non-repo skip): the standing JUDGE verdict still covers the tree — ship directly.
|
|
1788
|
-
const ship = await withShipTurn(storyId, () =>
|
|
1789
|
-
|
|
1825
|
+
const ship = await withShipTurn(storyId, () => runShipStep(storyId,
|
|
1826
|
+
`node .valent-pipeline/bin/cli.js git ship-story --story ${storyId} --verdict ship${gitFlags}`, 'QA'))
|
|
1790
1827
|
const merged = ship && (ship.skipped === true || ship.merged === true)
|
|
1791
1828
|
if (merged) {
|
|
1792
1829
|
recordStage('ship', { verdict: 'pass' })
|
|
@@ -1855,8 +1892,8 @@ async function runStory(story) {
|
|
|
1855
1892
|
if (gitFlowEnabled) {
|
|
1856
1893
|
if (shipped || shippedPartial) {
|
|
1857
1894
|
const mergeVerdict = shippedPartial ? 'ship-partial' : decision.verdict === 'ship-with-bugs' ? 'ship-with-bugs' : 'ship'
|
|
1858
|
-
const ship = await withShipTurn(storyId, () =>
|
|
1859
|
-
|
|
1895
|
+
const ship = await withShipTurn(storyId, () => runShipStep(storyId,
|
|
1896
|
+
`node .valent-pipeline/bin/cli.js git ship-story --story ${storyId} --verdict ${mergeVerdict}${gitFlags}`, 'Judge'))
|
|
1860
1897
|
const merged = ship && (ship.skipped === true || ship.merged === true)
|
|
1861
1898
|
if (!merged) {
|
|
1862
1899
|
const reason = ship && ship.conflict ? 'merge-conflict' : 'git-ship-failed'
|
|
@@ -2094,8 +2131,8 @@ async function runStory(story) {
|
|
|
2094
2131
|
// Standalone bugs (no sourceStory — defects in already-shipped code) merge to target as before.
|
|
2095
2132
|
const intoFlag = itemType === 'bug' && sourceStory ? ` --into ${gitBranchPrefix}${sourceStory}` : ''
|
|
2096
2133
|
const ship = await withShipTurn(storyId, async () => {
|
|
2097
|
-
const res = await
|
|
2098
|
-
|
|
2134
|
+
const res = await runShipStep(storyId,
|
|
2135
|
+
`node .valent-pipeline/bin/cli.js git ship-story --story ${storyId} --verdict ${mergeVerdict}${intoFlag}${wtFlag}${gitFlags}`, 'Judge')
|
|
2099
2136
|
// Post-merge acceptance verify (parallel mode + atdd armed): the story was QA'd on its
|
|
2100
2137
|
// PRE-merge SHA; other stories' merges may have landed since. Re-run its acceptance suite
|
|
2101
2138
|
// against the merged target — still under the mutex, so nothing else moves the target
|