valent-pipeline 0.19.49 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valent-pipeline",
3
- "version": "0.19.49",
3
+ "version": "0.19.50",
4
4
  "description": "v3 multi-agent AI pipeline for software development lifecycle",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- if (mergeInProgress(wtPath)) git(wtPath, ['merge', '--abort']);
479
- refreshConflict = true;
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
- if (mergeInProgress(root)) git(root, ['merge', '--abort']);
537
- refreshConflict = true;
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