valent-pipeline 0.19.48 → 0.19.50
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/lib/git-flow.js +60 -5
package/package.json
CHANGED
package/src/lib/git-flow.js
CHANGED
|
@@ -124,6 +124,39 @@ function mergeInProgress(root) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// The TARGET is the source of truth for these CLI-owned shared-state files: the disposition files a
|
|
128
|
+
// rejected story's bug entries into `pipeline-backlog.yaml` on the target at sprint end (.38
|
|
129
|
+
// commit-at-persist). A reused --from-story branch carries an OLDER copy, so the refresh merge below
|
|
130
|
+
// conflicts on it.
|
|
131
|
+
const REFRESH_TARGET_WINS = new Set(['pipeline-backlog.yaml']);
|
|
132
|
+
|
|
133
|
+
// On a refresh-merge conflict, if EVERY conflicted path is a target-authoritative shared-state file
|
|
134
|
+
// (REFRESH_TARGET_WINS), resolve it toward the TARGET ("theirs" in a merge run ON the branch is the
|
|
135
|
+
// target being merged in) and CONCLUDE the merge — the branch makes no legitimate edits of its own to
|
|
136
|
+
// these files, so target-wins delivers the filed bug entry and loses nothing. ANY other conflicted
|
|
137
|
+
// path (real code) → return false and let the caller abort exactly as before. Returns true iff it
|
|
138
|
+
// resolved + committed. Closes M-65/M-69: a backlog conflict used to ABORT the refresh, so the filed
|
|
139
|
+
// bug entry never reached the branch and `backlog ship --story <bug>` then errored "not found" →
|
|
140
|
+
// manual reconcile every bug-fix sprint. The ship CLAMP is untouched — evidence still verifies against
|
|
141
|
+
// the branch's own tree; this only delivers the backlog ENTRY the target already committed.
|
|
142
|
+
function resolveRefreshConflict(dir) {
|
|
143
|
+
if (!mergeInProgress(dir)) return false; // a non-conflict merge failure — the caller aborts
|
|
144
|
+
let conflicted;
|
|
145
|
+
try {
|
|
146
|
+
conflicted = git(dir, ['diff', '--name-only', '--diff-filter=U'])
|
|
147
|
+
.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (!conflicted.length || !conflicted.every((p) => REFRESH_TARGET_WINS.has(p))) return false;
|
|
152
|
+
for (const p of conflicted) {
|
|
153
|
+
git(dir, ['checkout', '--theirs', '--', p]);
|
|
154
|
+
git(dir, ['add', '--', p]);
|
|
155
|
+
}
|
|
156
|
+
git(dir, ['commit', '--no-edit']);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
127
160
|
/** Is `branch` already merged into `target` (its tip an ancestor of target's tip)? */
|
|
128
161
|
function isMergedInto(root, branch, target) {
|
|
129
162
|
try {
|
|
@@ -475,8 +508,15 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
475
508
|
buildCommitMessage({ storyId: story, phase: 'sync', summary: `sync ${target} into ${branch} (inherit shared state from a behind base)` }), target]);
|
|
476
509
|
refreshedFromTarget = true;
|
|
477
510
|
} catch {
|
|
478
|
-
|
|
479
|
-
|
|
511
|
+
// A conflict ONLY on target-authoritative shared state (the backlog with the filed bug entry)
|
|
512
|
+
// → resolve toward the target so the entry reaches this branch (M-65/M-69); any real-code
|
|
513
|
+
// conflict aborts cleanly (setupWorktree re-materializes the install if the sync removed it).
|
|
514
|
+
if (resolveRefreshConflict(wtPath)) {
|
|
515
|
+
refreshedFromTarget = true;
|
|
516
|
+
} else {
|
|
517
|
+
if (mergeInProgress(wtPath)) git(wtPath, ['merge', '--abort']);
|
|
518
|
+
refreshConflict = true;
|
|
519
|
+
}
|
|
480
520
|
}
|
|
481
521
|
}
|
|
482
522
|
const setup = setupWorktree(root, wtPath, setupCommands);
|
|
@@ -533,8 +573,16 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
533
573
|
buildCommitMessage({ storyId: story, phase: 'sync', summary: `sync ${target} into ${branch} (inherit shared state from a behind base)` }), target]);
|
|
534
574
|
refreshedFromTarget = true;
|
|
535
575
|
} catch {
|
|
536
|
-
|
|
537
|
-
|
|
576
|
+
// A conflict ONLY on target-authoritative shared state (the backlog the disposition filed the
|
|
577
|
+
// bug entry into) is resolved toward the target so the entry reaches this branch (M-65/M-69); a
|
|
578
|
+
// conflict touching any real code aborts cleanly as before (degraded, but no worse). The M-52
|
|
579
|
+
// framework-deletion guard below still runs on the concluded merge.
|
|
580
|
+
if (resolveRefreshConflict(root)) {
|
|
581
|
+
refreshedFromTarget = true;
|
|
582
|
+
} else {
|
|
583
|
+
if (mergeInProgress(root)) git(root, ['merge', '--abort']);
|
|
584
|
+
refreshConflict = true;
|
|
585
|
+
}
|
|
538
586
|
}
|
|
539
587
|
// M-52 transition hazard: when `target` UNTRACKS the vendored framework (the post-untrack install
|
|
540
588
|
// model) while THIS branch still TRACKS it (a pre-untrack branch), the merge above applied target's
|
|
@@ -582,7 +630,14 @@ export function storyStatus({ root, story, targetBranch = '', prefix = 'story/'
|
|
|
582
630
|
const branch = storyBranchName(prefix, story);
|
|
583
631
|
const exists = branchExists(root, branch);
|
|
584
632
|
const record = readFlowRecord(root, story);
|
|
585
|
-
|
|
633
|
+
// A `--from-story` item (an inline bug fix) ships `--into` its SOURCE story branch (recorded as
|
|
634
|
+
// basedOnStory), NOT the sprint target — so "did it land" is ancestry against basedOnStory. The sprint
|
|
635
|
+
// `--target` that the post-ship verify passes globally (gitFlags `--target`) is the wrong base for such
|
|
636
|
+
// an item: it reads a bug correctly merged into its source — but whose source hasn't reached the sprint
|
|
637
|
+
// target yet — as merged:false → a FALSE git-ship-failed that stranded the parent's revalidation
|
|
638
|
+
// (M-60, 2.17-BUG-002). basedOnStory is therefore AUTHORITATIVE when present (it overrides even an
|
|
639
|
+
// explicit --target); an ordinary story (basedOnStory null) still honors --target then the record.
|
|
640
|
+
const target = record?.basedOnStory || targetBranch || record?.target || currentBranch(root);
|
|
586
641
|
// A missing branch is UNKNOWN (merged:null), never a verdict: the flow retains branches on
|
|
587
642
|
// both ship and reject, so absence means the flow never ran here (or someone pruned by hand) —
|
|
588
643
|
// the caller must treat the state as unverified rather than as landed.
|