valent-pipeline 0.19.49 → 0.19.51
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 +72 -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 {
|
|
@@ -337,6 +370,19 @@ export function readFlowRecord(root, story) {
|
|
|
337
370
|
}
|
|
338
371
|
}
|
|
339
372
|
|
|
373
|
+
// When the flow record isn't in the run context's working tree, read it from a git ref. A --from-story
|
|
374
|
+
// bug's git-flow.json is committed only on its own branch (evidence-on-branch — the class the .50 fixed
|
|
375
|
+
// for the backlog entry), so story-status run from a context that lacks it (the post-ship verify on
|
|
376
|
+
// build/v1) would otherwise read None. Returns null on any failure (missing path/ref) — callers fall
|
|
377
|
+
// back to their prior behavior.
|
|
378
|
+
function readFlowRecordFromRef(root, ref, story) {
|
|
379
|
+
try {
|
|
380
|
+
return JSON.parse(git(root, ['show', `${ref}:stories/${story}/evidence/git-flow.json`]));
|
|
381
|
+
} catch {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
340
386
|
function writeFlowRecord(root, story, record) {
|
|
341
387
|
const dir = join(root, 'stories', story, 'evidence');
|
|
342
388
|
mkdirSync(dir, { recursive: true });
|
|
@@ -475,8 +521,15 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
475
521
|
buildCommitMessage({ storyId: story, phase: 'sync', summary: `sync ${target} into ${branch} (inherit shared state from a behind base)` }), target]);
|
|
476
522
|
refreshedFromTarget = true;
|
|
477
523
|
} catch {
|
|
478
|
-
|
|
479
|
-
|
|
524
|
+
// A conflict ONLY on target-authoritative shared state (the backlog with the filed bug entry)
|
|
525
|
+
// → resolve toward the target so the entry reaches this branch (M-65/M-69); any real-code
|
|
526
|
+
// conflict aborts cleanly (setupWorktree re-materializes the install if the sync removed it).
|
|
527
|
+
if (resolveRefreshConflict(wtPath)) {
|
|
528
|
+
refreshedFromTarget = true;
|
|
529
|
+
} else {
|
|
530
|
+
if (mergeInProgress(wtPath)) git(wtPath, ['merge', '--abort']);
|
|
531
|
+
refreshConflict = true;
|
|
532
|
+
}
|
|
480
533
|
}
|
|
481
534
|
}
|
|
482
535
|
const setup = setupWorktree(root, wtPath, setupCommands);
|
|
@@ -533,8 +586,16 @@ export function startStory({ root, story, targetBranch = '', prefix = 'story/',
|
|
|
533
586
|
buildCommitMessage({ storyId: story, phase: 'sync', summary: `sync ${target} into ${branch} (inherit shared state from a behind base)` }), target]);
|
|
534
587
|
refreshedFromTarget = true;
|
|
535
588
|
} catch {
|
|
536
|
-
|
|
537
|
-
|
|
589
|
+
// A conflict ONLY on target-authoritative shared state (the backlog the disposition filed the
|
|
590
|
+
// bug entry into) is resolved toward the target so the entry reaches this branch (M-65/M-69); a
|
|
591
|
+
// conflict touching any real code aborts cleanly as before (degraded, but no worse). The M-52
|
|
592
|
+
// framework-deletion guard below still runs on the concluded merge.
|
|
593
|
+
if (resolveRefreshConflict(root)) {
|
|
594
|
+
refreshedFromTarget = true;
|
|
595
|
+
} else {
|
|
596
|
+
if (mergeInProgress(root)) git(root, ['merge', '--abort']);
|
|
597
|
+
refreshConflict = true;
|
|
598
|
+
}
|
|
538
599
|
}
|
|
539
600
|
// M-52 transition hazard: when `target` UNTRACKS the vendored framework (the post-untrack install
|
|
540
601
|
// model) while THIS branch still TRACKS it (a pre-untrack branch), the merge above applied target's
|
|
@@ -581,7 +642,13 @@ export function storyStatus({ root, story, targetBranch = '', prefix = 'story/'
|
|
|
581
642
|
if (!isRepoWithCommits(root)) return NOT_REPO;
|
|
582
643
|
const branch = storyBranchName(prefix, story);
|
|
583
644
|
const exists = branchExists(root, branch);
|
|
584
|
-
|
|
645
|
+
// Evidence-on-branch (M-90/M-91): a --from-story bug's git-flow.json is committed on its OWN branch,
|
|
646
|
+
// so a story-status run from a context lacking it (the post-ship verify on build/v1) reads None and
|
|
647
|
+
// defaults the target to build/v1 — a bug merged --into its SOURCE then reads merged:false (a FALSE
|
|
648
|
+
// git-ship-failed that strands the parent's revalidation). Fall back to the branch ref so basedOnStory
|
|
649
|
+
// still resolves (the .49 authority can only fire if the record is readable). Mirrors the .50 backlog
|
|
650
|
+
// branch-resolution. The record CONTENT is identical to the local copy — only the read source differs.
|
|
651
|
+
const record = readFlowRecord(root, story) || (exists ? readFlowRecordFromRef(root, branch, story) : null);
|
|
585
652
|
// A `--from-story` item (an inline bug fix) ships `--into` its SOURCE story branch (recorded as
|
|
586
653
|
// basedOnStory), NOT the sprint target — so "did it land" is ancestry against basedOnStory. The sprint
|
|
587
654
|
// `--target` that the post-ship verify passes globally (gitFlags `--target`) is the wrong base for such
|