borgmcp 0.9.36 → 0.9.38
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/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +31 -2
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/claude.js +2 -1
- package/dist/claude.js.map +1 -1
- package/dist/codex-remote.d.ts.map +1 -1
- package/dist/codex-remote.js +1 -5
- package/dist/codex-remote.js.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-log-guard.d.ts +29 -0
- package/dist/lifecycle-log-guard.d.ts.map +1 -0
- package/dist/lifecycle-log-guard.js +93 -0
- package/dist/lifecycle-log-guard.js.map +1 -0
- package/dist/remote-client.d.ts +33 -0
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +72 -0
- package/dist/remote-client.js.map +1 -1
- package/dist/sync.d.ts +94 -64
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +206 -160
- package/dist/sync.js.map +1 -1
- package/dist/worktree-lifecycle.d.ts +100 -0
- package/dist/worktree-lifecycle.d.ts.map +1 -0
- package/dist/worktree-lifecycle.js +173 -0
- package/dist/worktree-lifecycle.js.map +1 -0
- package/package.json +1 -1
package/dist/sync.js
CHANGED
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `borg sync` — worktree lifecycle management subcommand (gh#33).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Reconciled to the per-worktree `wt-<suffix>` branch model (PR-B,
|
|
5
|
+
* ruling ea643b33). Replaces the previous main-centric semantics
|
|
6
|
+
* ("idle = on main"; "post-merge = checkout main") — under the approved
|
|
7
|
+
* gh#33 model `main` is NEVER a working branch in any worktree; it is
|
|
8
|
+
* purely the integration target. Every worktree works on a named
|
|
9
|
+
* `wt-<suffix>` branch and `borg sync` keeps it current with
|
|
10
|
+
* origin/main, returns to it after a feature branch merges, and absorbs
|
|
11
|
+
* upstream into an in-progress feature branch — never touching `main`
|
|
12
|
+
* as a checkout.
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* advanced since the branch's merge base.
|
|
16
|
-
* Nothing to do.
|
|
17
|
-
* 4. mid-sprint-merge — on a feature branch; origin/main advanced.
|
|
18
|
-
* Merge origin/main into the feature branch
|
|
19
|
-
* (no rebase, per cube workflow rule (a)).
|
|
20
|
-
* 5. post-merge — on a local branch whose remote was deleted
|
|
21
|
-
* (typical post-PR-merge state); switch to
|
|
22
|
-
* main and pull. Local merged branch is left
|
|
23
|
-
* intact for the operator to delete manually.
|
|
14
|
+
* All git mutation/decision logic is delegated to
|
|
15
|
+
* `client/src/worktree-lifecycle.ts` (the seam PR-A added:
|
|
16
|
+
* `adoptWorktree`, `syncWorktree`, `cleanupMerged`, `isMerged`,
|
|
17
|
+
* `localBranchExists`) so the never-discard guards (dirty / unmerged
|
|
18
|
+
* HEAD / unmerged target wt- branch) and the args-array subprocess
|
|
19
|
+
* shape are shared, not duplicated.
|
|
24
20
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Lifecycle states:
|
|
22
|
+
* 1. dirty — uncommitted changes; refuse (never discard).
|
|
23
|
+
* 2. on-wt — on the per-worktree `wt-<suffix>` branch;
|
|
24
|
+
* fast-forward it to origin/main (ff-only).
|
|
25
|
+
* 3. on-main — on `main`/`master` (or detached at a merged
|
|
26
|
+
* point); adopt the `wt-<suffix>` branch
|
|
27
|
+
* (Q4: main is never a working branch).
|
|
28
|
+
* 4. feature-mid-sprint — on a feature branch not yet merged; absorb
|
|
29
|
+
* origin/main into it via `git merge --no-edit`
|
|
30
|
+
* (no rebase — cube workflow rule (a)) when
|
|
31
|
+
* origin/main advanced; else no-op.
|
|
32
|
+
* 5. feature-merged — on a feature branch fully merged into
|
|
33
|
+
* origin/main; return to `wt-<suffix>` and
|
|
34
|
+
* ANNOUNCE the prunable feature branch (prune
|
|
35
|
+
* only with `--prune`, Q3).
|
|
27
36
|
*
|
|
28
|
-
* Anti-features (intentional):
|
|
29
|
-
* - No auto-stash
|
|
30
|
-
*
|
|
31
|
-
* - No
|
|
32
|
-
* -
|
|
33
|
-
* - No remote-branch deletion (Coordinator owns merge actions, not
|
|
34
|
-
* this subcommand).
|
|
37
|
+
* Anti-features (intentional, unchanged):
|
|
38
|
+
* - No auto-stash / auto-commit / auto-discard on dirty tree.
|
|
39
|
+
* - No force-push, no rebase, no --force-with-lease.
|
|
40
|
+
* - No remote-branch deletion (Coordinator owns merge actions).
|
|
41
|
+
* - Local feature-branch deletion only on explicit `--prune` (Q3).
|
|
35
42
|
*/
|
|
36
43
|
import { spawnSync } from 'node:child_process';
|
|
44
|
+
import { basename } from 'node:path';
|
|
37
45
|
import chalk from 'chalk';
|
|
46
|
+
import { adoptWorktree, syncWorktree, cleanupMerged, isMerged, perWorktreeBranchName, } from './worktree-lifecycle.js';
|
|
38
47
|
const defaultDeps = {
|
|
39
48
|
runSync: (cmd, args, cwd) => {
|
|
40
49
|
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
|
|
@@ -48,111 +57,116 @@ const defaultDeps = {
|
|
|
48
57
|
stderr: (line) => process.stderr.write(line),
|
|
49
58
|
stdout: (line) => process.stdout.write(line),
|
|
50
59
|
};
|
|
60
|
+
const DEFAULT_BRANCH = 'origin/main';
|
|
51
61
|
// ------------------------------------------------------------------
|
|
52
|
-
//
|
|
62
|
+
// wt- branch resolution
|
|
53
63
|
// ------------------------------------------------------------------
|
|
54
64
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* latest remote tip.
|
|
65
|
+
* Resolve the per-worktree `wt-` branch for the current checkout,
|
|
66
|
+
* DETERMINISTICALLY per worktree.
|
|
58
67
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
68
|
+
* - current branch is already `wt-*` → that is the branch.
|
|
69
|
+
* - otherwise → derive `wt-<suffix>` from THIS worktree's directory
|
|
70
|
+
* basename minus the main worktree's (repo) basename prefix — the
|
|
71
|
+
* same `perWorktreeBranchName` derivation the spawn path uses, so
|
|
72
|
+
* the name matches what `borg assimilate --worktree` created.
|
|
73
|
+
*
|
|
74
|
+
* This must NOT list `git branch --list wt-*`: in linked worktrees the
|
|
75
|
+
* local branch namespace is SHARED across all siblings (gh#33 CR-v2
|
|
76
|
+
* blocker 32bc45da), so every drone's `wt-` branch is visible and a
|
|
77
|
+
* "single match" heuristic is never satisfied in a real multi-drone
|
|
78
|
+
* cube. The directory-derivation is unique per worktree and never
|
|
79
|
+
* ambiguous. The first `worktree <path>` line of
|
|
80
|
+
* `git worktree list --porcelain` is the main worktree (the repo dir);
|
|
81
|
+
* its basename is the prefix to strip. For an independent clone the
|
|
82
|
+
* main worktree IS this worktree, so the prefix == basename and the
|
|
83
|
+
* result is `wt-<basename>` (no strip) — consistent with PR-A's
|
|
84
|
+
* in-place adoption.
|
|
85
|
+
*/
|
|
86
|
+
export function resolveWtBranch(runSync, cwd, currentBranch) {
|
|
87
|
+
if (currentBranch.startsWith('wt-'))
|
|
88
|
+
return currentBranch;
|
|
89
|
+
const top = runSync('git', ['rev-parse', '--show-toplevel'], cwd);
|
|
90
|
+
const thisDir = top.status === 0 ? top.stdout.trim() : cwd;
|
|
91
|
+
const wtList = runSync('git', ['worktree', 'list', '--porcelain'], cwd);
|
|
92
|
+
let mainDir = thisDir;
|
|
93
|
+
if (wtList.status === 0) {
|
|
94
|
+
const firstWorktreeLine = wtList.stdout
|
|
95
|
+
.split('\n')
|
|
96
|
+
.find((l) => l.startsWith('worktree '));
|
|
97
|
+
if (firstWorktreeLine)
|
|
98
|
+
mainDir = firstWorktreeLine.slice('worktree '.length).trim();
|
|
99
|
+
}
|
|
100
|
+
return perWorktreeBranchName(basename(thisDir), basename(mainDir));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Detect the worktree's lifecycle state. Read-only except for the
|
|
104
|
+
* `git fetch origin --prune` needed to measure against the latest tip.
|
|
61
105
|
*/
|
|
62
106
|
export function detectState(deps) {
|
|
63
107
|
const { runSync, cwd } = deps;
|
|
64
108
|
const cwdValue = cwd();
|
|
65
|
-
// (1)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
kind: 'error',
|
|
70
|
-
reason: `not in a git repository (cwd: ${cwdValue})`,
|
|
71
|
-
};
|
|
109
|
+
// (1) git repo?
|
|
110
|
+
if (runSync('git', ['rev-parse', '--show-toplevel'], cwdValue).status !== 0) {
|
|
111
|
+
return { kind: 'error', reason: `not in a git repository (cwd: ${cwdValue})` };
|
|
72
112
|
}
|
|
73
|
-
// (2)
|
|
74
|
-
// uncommitted changes. `git status --porcelain` returns empty
|
|
75
|
-
// string when the worktree is clean; any line is a dirty file.
|
|
113
|
+
// (2) dirty FIRST — never act on uncommitted changes.
|
|
76
114
|
const status = runSync('git', ['status', '--porcelain'], cwdValue);
|
|
77
115
|
if (status.status !== 0) {
|
|
78
|
-
return {
|
|
79
|
-
kind: 'error',
|
|
80
|
-
reason: `git status failed: ${status.stderr.trim()}`,
|
|
81
|
-
};
|
|
116
|
+
return { kind: 'error', reason: `git status failed: ${status.stderr.trim()}` };
|
|
82
117
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// (3) Current branch.
|
|
91
|
-
const branch = runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], cwdValue);
|
|
92
|
-
if (branch.status !== 0) {
|
|
93
|
-
return {
|
|
94
|
-
kind: 'error',
|
|
95
|
-
reason: `cannot resolve current branch: ${branch.stderr.trim()}`,
|
|
96
|
-
};
|
|
118
|
+
const dirty = status.stdout.split('\n').map((l) => l.trim()).filter((l) => l.length > 0);
|
|
119
|
+
if (dirty.length > 0)
|
|
120
|
+
return { kind: 'dirty', files: dirty };
|
|
121
|
+
// (3) current branch.
|
|
122
|
+
const branchProbe = runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], cwdValue);
|
|
123
|
+
if (branchProbe.status !== 0) {
|
|
124
|
+
return { kind: 'error', reason: `cannot resolve current branch: ${branchProbe.stderr.trim()}` };
|
|
97
125
|
}
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
return {
|
|
101
|
-
kind: 'error',
|
|
102
|
-
reason: 'detached HEAD; cannot sync (checkout a branch first)',
|
|
103
|
-
};
|
|
126
|
+
const branch = branchProbe.stdout.trim();
|
|
127
|
+
if (branch === 'HEAD') {
|
|
128
|
+
return { kind: 'error', reason: 'detached HEAD; run `borg assimilate` to adopt a wt- branch first' };
|
|
104
129
|
}
|
|
105
|
-
// (4)
|
|
106
|
-
// fatal — we can't reason about lifecycle without a fresh remote
|
|
107
|
-
// view. --prune so the local view of remote-tracking branches
|
|
108
|
-
// reflects deletions on origin (load-bearing for post-merge state).
|
|
130
|
+
// (4) fetch — fatal on failure (can't reason about lifecycle offline).
|
|
109
131
|
const fetch = runSync('git', ['fetch', 'origin', '--prune'], cwdValue);
|
|
110
132
|
if (fetch.status !== 0) {
|
|
111
|
-
return {
|
|
112
|
-
kind: 'error',
|
|
113
|
-
reason: `git fetch origin failed: ${fetch.stderr.trim()}`,
|
|
114
|
-
};
|
|
133
|
+
return { kind: 'error', reason: `git fetch origin failed: ${fetch.stderr.trim()}` };
|
|
115
134
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// still exists; if not, we're in post-merge state.
|
|
125
|
-
const remoteBranchProbe = runSync('git', ['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${branchName}`], cwdValue);
|
|
126
|
-
if (remoteBranchProbe.status !== 0) {
|
|
127
|
-
return { kind: 'post-merge', branch: branchName, mergedSha: headSha };
|
|
128
|
-
}
|
|
129
|
-
// (8) Remote branch exists — check whether origin/main advanced past
|
|
130
|
-
// the merge-base with our feature branch.
|
|
131
|
-
const mergeBase = runSync('git', ['merge-base', 'HEAD', 'origin/main'], cwdValue);
|
|
132
|
-
if (mergeBase.status !== 0) {
|
|
133
|
-
return {
|
|
134
|
-
kind: 'error',
|
|
135
|
-
reason: `cannot compute merge-base with origin/main: ${mergeBase.stderr.trim()}`,
|
|
136
|
-
};
|
|
135
|
+
const wtBranch = resolveWtBranch(runSync, cwdValue, branch);
|
|
136
|
+
// (5) on the per-worktree wt- branch.
|
|
137
|
+
if (branch.startsWith('wt-')) {
|
|
138
|
+
return { kind: 'on-wt', branch, wtBranch: branch };
|
|
139
|
+
}
|
|
140
|
+
// (6) on main/master — Q4: never a working branch; adopt wt-.
|
|
141
|
+
if (branch === 'main' || branch === 'master') {
|
|
142
|
+
return { kind: 'on-main', branch, wtBranch };
|
|
137
143
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
// (7) feature branch — merged vs mid-sprint.
|
|
145
|
+
// ASSUMPTION (gh#33 CR NIT 0e19637a): merged-detection keys off commit
|
|
146
|
+
// ancestry (`isMerged` = HEAD is an ancestor of origin/main), which
|
|
147
|
+
// holds for merge-commit / fast-forward integration. A squash- or
|
|
148
|
+
// rebase-merged PR leaves the feature tip a NON-ancestor → it would
|
|
149
|
+
// classify here as mid-sprint, not merged. That degrades safely
|
|
150
|
+
// (merge-back of origin/main, no discard), and this cube integrates
|
|
151
|
+
// via merge commits, so it is not triggered. If squash-merge is ever
|
|
152
|
+
// adopted, switch this to PR-merged-state detection.
|
|
153
|
+
if (isMerged(runSync, cwdValue, 'HEAD', DEFAULT_BRANCH)) {
|
|
154
|
+
return { kind: 'feature-merged', branch, wtBranch };
|
|
142
155
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
// not merged → mid-sprint. Count how far origin/main advanced past the
|
|
157
|
+
// merge-base so the message can report it (0 → no-op).
|
|
158
|
+
const mergeBase = runSync('git', ['merge-base', 'HEAD', DEFAULT_BRANCH], cwdValue);
|
|
159
|
+
const base = mergeBase.status === 0 ? mergeBase.stdout.trim() : '';
|
|
160
|
+
const count = runSync('git', ['rev-list', '--count', `${base}..${DEFAULT_BRANCH}`], cwdValue);
|
|
161
|
+
const commits = parseInt(count.stdout.trim(), 10) || 0;
|
|
162
|
+
return { kind: 'feature-mid-sprint', branch, wtBranch, commits };
|
|
146
163
|
}
|
|
147
164
|
// ------------------------------------------------------------------
|
|
148
|
-
//
|
|
165
|
+
// Orchestrator
|
|
149
166
|
// ------------------------------------------------------------------
|
|
150
|
-
|
|
151
|
-
* Run the `borg sync` flow. Returns the desired process exit code.
|
|
152
|
-
*/
|
|
153
|
-
export async function runSync(deps = {}) {
|
|
167
|
+
export async function runSync(deps = {}, opts = { prune: false }) {
|
|
154
168
|
const merged = { ...defaultDeps, ...deps };
|
|
155
|
-
const { runSync:
|
|
169
|
+
const { runSync: run, cwd, stderr, stdout } = merged;
|
|
156
170
|
const state = detectState(merged);
|
|
157
171
|
if (state.kind === 'error') {
|
|
158
172
|
stderr(chalk.red(`◼ borg sync: ${state.reason}\n`));
|
|
@@ -160,81 +174,113 @@ export async function runSync(deps = {}) {
|
|
|
160
174
|
}
|
|
161
175
|
if (state.kind === 'dirty') {
|
|
162
176
|
stderr(chalk.yellow(`◼ Working tree has uncommitted changes.\n`));
|
|
163
|
-
const
|
|
164
|
-
for (const line of preview) {
|
|
177
|
+
for (const line of state.files.slice(0, 5))
|
|
165
178
|
stderr(chalk.gray(` ${line}\n`));
|
|
166
|
-
|
|
167
|
-
if (state.files.length > 5) {
|
|
179
|
+
if (state.files.length > 5)
|
|
168
180
|
stderr(chalk.gray(` ... and ${state.files.length - 5} more\n`));
|
|
169
|
-
|
|
170
|
-
stderr(chalk.yellow(`◼ Commit, stash, or restore before running \`borg sync\`.\n`));
|
|
181
|
+
stderr(chalk.yellow(`◼ Commit, stash, or restore before running \`borg sync\`. Nothing was changed.\n`));
|
|
171
182
|
return 1;
|
|
172
183
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
// on-wt: fast-forward the per-worktree branch to origin/main (ff-only,
|
|
185
|
+
// clean-gated, never merge/rebase). Delegates to syncWorktree.
|
|
186
|
+
if (state.kind === 'on-wt') {
|
|
187
|
+
const res = syncWorktree(run, cwd(), state.wtBranch, DEFAULT_BRANCH);
|
|
188
|
+
if (res.action === 'fast-forwarded') {
|
|
189
|
+
stdout(chalk.blue(`◼ On \`${state.wtBranch}\`; fast-forwarded to ${DEFAULT_BRANCH}.\n`));
|
|
178
190
|
return 0;
|
|
179
191
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return 1;
|
|
192
|
+
if (res.action === 'already-current') {
|
|
193
|
+
stdout(chalk.blue(`◼ On \`${state.wtBranch}\`; up to date with ${DEFAULT_BRANCH}.\n`));
|
|
194
|
+
return 0;
|
|
184
195
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return
|
|
196
|
+
// skipped-diverged: the wt- branch has local commits not on origin/main.
|
|
197
|
+
stderr(chalk.yellow(`◼ ${res.message ?? 'sync skipped'}.\n`));
|
|
198
|
+
return 1;
|
|
188
199
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
200
|
+
// on-main: adopt the wt- branch (Q4 — move off main). adoptWorktree
|
|
201
|
+
// applies the dirty / unmerged-HEAD / unmerged-target guards.
|
|
202
|
+
if (state.kind === 'on-main') {
|
|
203
|
+
return adoptAndReport(state.wtBranch, run, cwd, stdout, stderr);
|
|
193
204
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
// feature-mid-sprint: absorb origin/main into the feature branch (no
|
|
206
|
+
// rebase). Leaves the drone on the feature branch to keep working.
|
|
207
|
+
if (state.kind === 'feature-mid-sprint') {
|
|
208
|
+
if (state.commits === 0) {
|
|
209
|
+
stdout(chalk.blue(`◼ On \`${state.branch}\` (feature branch); up to date with ${DEFAULT_BRANCH}.\n`));
|
|
210
|
+
stdout(chalk.gray(`◼ Continue your sprint, or post REVIEW-READY when complete.\n`));
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
const merge = run('git', ['merge', '--no-edit', DEFAULT_BRANCH], cwd());
|
|
197
214
|
if (merge.status !== 0) {
|
|
198
|
-
stderr(chalk.red(`◼ borg sync: git merge
|
|
215
|
+
stderr(chalk.red(`◼ borg sync: git merge ${DEFAULT_BRANCH} failed (likely conflict). Resolve manually:\n${merge.stderr.trim()}\n`));
|
|
199
216
|
return 1;
|
|
200
217
|
}
|
|
201
|
-
|
|
202
|
-
stdout(chalk.blue(`◼ On \`${state.branch}\`; merged ${state.commits} commit${state.commits === 1 ? '' : 's'} from origin/main → @${newHead.stdout.trim()}.\n`));
|
|
218
|
+
stdout(chalk.blue(`◼ On \`${state.branch}\`; merged ${state.commits} commit${state.commits === 1 ? '' : 's'} from ${DEFAULT_BRANCH} (no rebase).\n`));
|
|
203
219
|
stdout(chalk.gray(`◼ Re-run tests; continue your sprint.\n`));
|
|
204
220
|
return 0;
|
|
205
221
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
// feature-merged: the PR merged. Return to the wt- branch (adopt) and
|
|
223
|
+
// announce the prunable feature branch (prune only with --prune, Q3).
|
|
224
|
+
if (state.kind === 'feature-merged') {
|
|
225
|
+
const feature = state.branch;
|
|
226
|
+
const code = adoptAndReport(state.wtBranch, run, cwd, stdout, stderr, {
|
|
227
|
+
adoptedPrefix: `◼ \`${feature}\` is merged into ${DEFAULT_BRANCH};`,
|
|
228
|
+
});
|
|
229
|
+
if (code !== 0)
|
|
230
|
+
return code; // adoption blocked (dirty/unmerged target) — don't prune
|
|
231
|
+
// Now on the wt- branch — safe to prune/announce the merged feature.
|
|
232
|
+
const cleanup = cleanupMerged(run, cwd(), feature, DEFAULT_BRANCH, { prune: opts.prune });
|
|
233
|
+
if (cleanup.action === 'pruned') {
|
|
234
|
+
stdout(chalk.blue(`◼ Pruned merged branch \`${feature}\`.\n`));
|
|
211
235
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
stderr(chalk.red(`◼ borg sync: git pull --ff-only failed:\n${pull.stderr.trim()}\n`));
|
|
215
|
-
return 1;
|
|
236
|
+
else if (cleanup.action === 'announced') {
|
|
237
|
+
stdout(chalk.gray(`◼ ${cleanup.message}\n`));
|
|
216
238
|
}
|
|
217
|
-
const newHead = runSyncCmd('git', ['rev-parse', '--short', 'HEAD'], cwd());
|
|
218
|
-
stdout(chalk.blue(`◼ Branch \`${state.branch}\` was merged on origin; switched to main @${newHead.stdout.trim()}.\n`));
|
|
219
|
-
stdout(chalk.gray(`◼ Local branch \`${state.branch}\` retained; delete with \`git branch -d ${state.branch}\` when ready.\n`));
|
|
220
239
|
return 0;
|
|
221
240
|
}
|
|
222
|
-
// Exhaustiveness
|
|
241
|
+
// Exhaustiveness.
|
|
223
242
|
const _exhaustive = state;
|
|
224
243
|
stderr(chalk.red(`◼ borg sync: unhandled state\n`));
|
|
225
244
|
return 1;
|
|
226
245
|
}
|
|
227
246
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
247
|
+
* Shared adopt-the-wt-branch path for the on-main + feature-merged
|
|
248
|
+
* states. Surfaces the never-discard outcomes; returns the process exit
|
|
249
|
+
* code (0 adopted, 1 blocked/ambiguous).
|
|
250
|
+
*/
|
|
251
|
+
function adoptAndReport(wtBranch, run, cwd, stdout, stderr, opts = {}) {
|
|
252
|
+
const res = adoptWorktree(run, cwd(), wtBranch, DEFAULT_BRANCH);
|
|
253
|
+
if (res.action === 'adopted') {
|
|
254
|
+
if (opts.adoptedPrefix) {
|
|
255
|
+
stdout(chalk.blue(`${opts.adoptedPrefix} switched to \`${wtBranch}\` at ${DEFAULT_BRANCH}.\n`));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
stdout(chalk.blue(`◼ On \`${wtBranch}\` at ${DEFAULT_BRANCH}.\n`));
|
|
259
|
+
}
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
// skipped-dirty handled upstream (dirty state), but defensively surface.
|
|
263
|
+
stderr(chalk.yellow(`◼ borg sync: ${res.message ?? 'not adopted'}. Nothing was changed.\n`));
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Parse args after `borg sync`. Supports `--prune` (Q3: delete a merged
|
|
268
|
+
* feature branch after returning to the wt- branch). Rejects anything
|
|
269
|
+
* else to keep room for future flags.
|
|
231
270
|
*/
|
|
232
271
|
export function parseSyncArgs(rawArgs) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
272
|
+
let prune = false;
|
|
273
|
+
for (const arg of rawArgs) {
|
|
274
|
+
if (arg === '--prune') {
|
|
275
|
+
prune = true;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
error: `unexpected argument: ${arg}. Usage: borg sync [--prune]`,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { ok: true, options: { prune } };
|
|
239
285
|
}
|
|
240
286
|
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gh#33 — worktree lifecycle as product behavior.
|
|
3
|
+
*
|
|
4
|
+
* Pure git-decision helpers behind an injected `runSync` seam (matching
|
|
5
|
+
* the `AssimilateDeps.runSync` shape), so every branch is unit-testable
|
|
6
|
+
* without a live repo. This module DECIDES + emits git command sequences;
|
|
7
|
+
* it never launches agents and never touches the cube API.
|
|
8
|
+
*
|
|
9
|
+
* Design spec: docs/superpowers/specs/2026-05-29-worktree-lifecycle-design.md
|
|
10
|
+
* Q-resolutions baked in (SPEC-APPROVED 3a80412d):
|
|
11
|
+
* Q1 branch naming — `wt-<suffix>` prefix-stripped, full-basename fallback.
|
|
12
|
+
* Q2 idle-sync — ff-only, clean-gated; never merge/rebase; never over dirty.
|
|
13
|
+
* Q3 post-merge — auto-return to wt-<basename>; ANNOUNCE the prunable
|
|
14
|
+
* merged branch, prune only when explicitly requested.
|
|
15
|
+
* Q4 uniform — no primary-worktree carve-out; main is never a working branch.
|
|
16
|
+
*/
|
|
17
|
+
/** Injected subprocess runner — matches AssimilateDeps.runSync. */
|
|
18
|
+
export type RunSync = (cmd: string, args: string[], cwd?: string) => {
|
|
19
|
+
status: number | null;
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Per-worktree branch name (Q1). Strips the repo basename prefix from the
|
|
25
|
+
* worktree dir basename for readability (`borg-mcp-codex-builder` ->
|
|
26
|
+
* `wt-codex-builder`); falls back to the full dir basename when there is
|
|
27
|
+
* no shared prefix (`myrepo-feature` under repo `otherrepo` ->
|
|
28
|
+
* `wt-myrepo-feature`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function perWorktreeBranchName(worktreeBasename: string, repoBasename: string): string;
|
|
31
|
+
/** True iff the working tree is clean (`git status --porcelain` empty). */
|
|
32
|
+
export declare function isCleanTree(runSync: RunSync, cwd: string): boolean;
|
|
33
|
+
export interface DirtyClassification {
|
|
34
|
+
staged: string[];
|
|
35
|
+
unstaged: string[];
|
|
36
|
+
untracked: string[];
|
|
37
|
+
/** Subset that are local-only config (e.g. `.claude/...`) — likely safe to set aside. */
|
|
38
|
+
localConfig: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Classify a dirty tree into staged / unstaged / untracked buckets, and
|
|
42
|
+
* flag local-config files separately. The STAGED bucket is load-bearing:
|
|
43
|
+
* the live UNBLOCK case (b15894be) had a *staged* leftover diff that
|
|
44
|
+
* blocked `pull --ff-only`, which an unstaged-only check would miss.
|
|
45
|
+
*/
|
|
46
|
+
export declare function classifyDirty(runSync: RunSync, cwd: string): DirtyClassification;
|
|
47
|
+
/** True iff `branch` is an ancestor of `ref` — i.e. a clean fast-forward target. */
|
|
48
|
+
export declare function isFastForward(runSync: RunSync, cwd: string, branch: string, ref: string): boolean;
|
|
49
|
+
/** True iff `branch`'s tip is an ancestor of `ref` — i.e. fully merged into it. */
|
|
50
|
+
export declare function isMerged(runSync: RunSync, cwd: string, branch: string, ref: string): boolean;
|
|
51
|
+
export interface SyncResult {
|
|
52
|
+
action: 'fast-forwarded' | 'already-current' | 'skipped-dirty' | 'skipped-diverged';
|
|
53
|
+
message?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Idle-sync the current per-worktree branch to `ref` (Q2). NEVER discards
|
|
57
|
+
* work: dirty -> skipped-dirty (no mutation). Only fast-forwards (no
|
|
58
|
+
* merge/rebase): diverged -> skipped-diverged. The caller fetches first.
|
|
59
|
+
*
|
|
60
|
+
* `already-current` when the branch tip already equals `ref` (the common
|
|
61
|
+
* no-op case on every launch).
|
|
62
|
+
*/
|
|
63
|
+
export declare function syncWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): SyncResult;
|
|
64
|
+
export interface AdoptResult {
|
|
65
|
+
action: 'adopted' | 'blocked-unmerged' | 'blocked-target-unmerged' | 'skipped-dirty';
|
|
66
|
+
message?: string;
|
|
67
|
+
}
|
|
68
|
+
/** True iff a local branch named `branch` already exists. */
|
|
69
|
+
export declare function localBranchExists(runSync: RunSync, cwd: string, branch: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Migration (Q4/Q5/§4.5): bring a detached/stale worktree onto
|
|
72
|
+
* `wt-<basename>` at `ref`. Idempotent: re-running on an already-adopted
|
|
73
|
+
* clean worktree is a lossless reset to `ref`. Never discards:
|
|
74
|
+
* - dirty work tree -> skipped-dirty (surface)
|
|
75
|
+
* - current HEAD unmerged -> blocked-unmerged (surface)
|
|
76
|
+
* - TARGET `branch` exists with commits not on `ref` -> blocked-target-
|
|
77
|
+
* unmerged (surface). This is load-bearing: the switch uses `-C`
|
|
78
|
+
* (force-create/reset), which would ORPHAN commits on a pre-existing
|
|
79
|
+
* `wt-` branch. The HEAD-merged check alone misses this when the
|
|
80
|
+
* target branch != HEAD (e.g. on `main` while a prior `wt-x` holds
|
|
81
|
+
* committed-but-unmerged work). gh#33 CR-v2 blocker 078d1630.
|
|
82
|
+
*/
|
|
83
|
+
export declare function adoptWorktree(runSync: RunSync, cwd: string, branch: string, ref: string): AdoptResult;
|
|
84
|
+
export interface CleanupResult {
|
|
85
|
+
action: 'pruned' | 'announced' | 'not-merged';
|
|
86
|
+
/** The feature branch this result concerns (for the announce message). */
|
|
87
|
+
branch?: string;
|
|
88
|
+
message?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Post-merge cleanup (Q3): when `feature` is fully merged into `ref`,
|
|
92
|
+
* either ANNOUNCE it as prunable (default) or actually prune it with the
|
|
93
|
+
* safe `git branch -d` (which itself refuses to delete an unmerged
|
|
94
|
+
* branch — defense in depth against a stale local ref). Unmerged ->
|
|
95
|
+
* not-merged (never touched).
|
|
96
|
+
*/
|
|
97
|
+
export declare function cleanupMerged(runSync: RunSync, cwd: string, feature: string, ref: string, opts?: {
|
|
98
|
+
prune: boolean;
|
|
99
|
+
}): CleanupResult;
|
|
100
|
+
//# sourceMappingURL=worktree-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-lifecycle.d.ts","sourceRoot":"","sources":["../src/worktree-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,mEAAmE;AACnE,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,CAAC,EAAE,MAAM,KACT;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM5F;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAGlE;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yFAAyF;IACzF,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAID;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAkBhF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjG;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5F;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,eAAe,GAAG,kBAAkB,CAAC;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAwBnG;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,GAAG,kBAAkB,GAAG,yBAAyB,GAAG,eAAe,CAAC;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAExF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAyBrG;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,YAAY,CAAC;IAC9C,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAqB,GAC1C,aAAa,CAaf"}
|