borgmcp 0.9.36 → 0.9.37
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/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
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Per-worktree branch name (Q1). Strips the repo basename prefix from the
|
|
19
|
+
* worktree dir basename for readability (`borg-mcp-codex-builder` ->
|
|
20
|
+
* `wt-codex-builder`); falls back to the full dir basename when there is
|
|
21
|
+
* no shared prefix (`myrepo-feature` under repo `otherrepo` ->
|
|
22
|
+
* `wt-myrepo-feature`).
|
|
23
|
+
*/
|
|
24
|
+
export function perWorktreeBranchName(worktreeBasename, repoBasename) {
|
|
25
|
+
const prefix = `${repoBasename}-`;
|
|
26
|
+
const suffix = worktreeBasename.startsWith(prefix)
|
|
27
|
+
? worktreeBasename.slice(prefix.length)
|
|
28
|
+
: worktreeBasename;
|
|
29
|
+
return `wt-${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
/** True iff the working tree is clean (`git status --porcelain` empty). */
|
|
32
|
+
export function isCleanTree(runSync, cwd) {
|
|
33
|
+
const r = runSync('git', ['status', '--porcelain'], cwd);
|
|
34
|
+
return r.status === 0 && r.stdout.trim() === '';
|
|
35
|
+
}
|
|
36
|
+
const LOCAL_CONFIG_RE = /^\.claude\//;
|
|
37
|
+
/**
|
|
38
|
+
* Classify a dirty tree into staged / unstaged / untracked buckets, and
|
|
39
|
+
* flag local-config files separately. The STAGED bucket is load-bearing:
|
|
40
|
+
* the live UNBLOCK case (b15894be) had a *staged* leftover diff that
|
|
41
|
+
* blocked `pull --ff-only`, which an unstaged-only check would miss.
|
|
42
|
+
*/
|
|
43
|
+
export function classifyDirty(runSync, cwd) {
|
|
44
|
+
const r = runSync('git', ['status', '--porcelain'], cwd);
|
|
45
|
+
const out = { staged: [], unstaged: [], untracked: [], localConfig: [] };
|
|
46
|
+
if (r.status !== 0)
|
|
47
|
+
return out;
|
|
48
|
+
for (const line of r.stdout.split('\n')) {
|
|
49
|
+
if (!line.trim())
|
|
50
|
+
continue;
|
|
51
|
+
const path = line.slice(3);
|
|
52
|
+
if (line.startsWith('??')) {
|
|
53
|
+
out.untracked.push(path);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const x = line[0]; // staged (index) column
|
|
57
|
+
const y = line[1]; // unstaged (work-tree) column
|
|
58
|
+
if (x !== ' ' && x !== '?')
|
|
59
|
+
out.staged.push(path);
|
|
60
|
+
if (y !== ' ' && y !== '?')
|
|
61
|
+
out.unstaged.push(path);
|
|
62
|
+
}
|
|
63
|
+
if (LOCAL_CONFIG_RE.test(path))
|
|
64
|
+
out.localConfig.push(path);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/** True iff `branch` is an ancestor of `ref` — i.e. a clean fast-forward target. */
|
|
69
|
+
export function isFastForward(runSync, cwd, branch, ref) {
|
|
70
|
+
return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
|
|
71
|
+
}
|
|
72
|
+
/** True iff `branch`'s tip is an ancestor of `ref` — i.e. fully merged into it. */
|
|
73
|
+
export function isMerged(runSync, cwd, branch, ref) {
|
|
74
|
+
return runSync('git', ['merge-base', '--is-ancestor', branch, ref], cwd).status === 0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Idle-sync the current per-worktree branch to `ref` (Q2). NEVER discards
|
|
78
|
+
* work: dirty -> skipped-dirty (no mutation). Only fast-forwards (no
|
|
79
|
+
* merge/rebase): diverged -> skipped-diverged. The caller fetches first.
|
|
80
|
+
*
|
|
81
|
+
* `already-current` when the branch tip already equals `ref` (the common
|
|
82
|
+
* no-op case on every launch).
|
|
83
|
+
*/
|
|
84
|
+
export function syncWorktree(runSync, cwd, branch, ref) {
|
|
85
|
+
if (!isCleanTree(runSync, cwd)) {
|
|
86
|
+
return {
|
|
87
|
+
action: 'skipped-dirty',
|
|
88
|
+
message: 'uncommitted changes present; sync skipped (nothing discarded)',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!isFastForward(runSync, cwd, branch, ref)) {
|
|
92
|
+
return {
|
|
93
|
+
action: 'skipped-diverged',
|
|
94
|
+
message: `${branch} has diverged from ${ref}; resolve manually (no auto-merge/rebase)`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Already at ref? merge --ff-only is a no-op but we report it distinctly
|
|
98
|
+
// so callers can stay quiet on the common case.
|
|
99
|
+
const ahead = runSync('git', ['rev-list', '--count', `${branch}..${ref}`], cwd);
|
|
100
|
+
if (ahead.status === 0 && ahead.stdout.trim() === '0') {
|
|
101
|
+
return { action: 'already-current' };
|
|
102
|
+
}
|
|
103
|
+
const ff = runSync('git', ['merge', '--ff-only', ref], cwd);
|
|
104
|
+
if (ff.status !== 0) {
|
|
105
|
+
return { action: 'skipped-diverged', message: 'ff-only merge unexpectedly failed' };
|
|
106
|
+
}
|
|
107
|
+
return { action: 'fast-forwarded' };
|
|
108
|
+
}
|
|
109
|
+
/** True iff a local branch named `branch` already exists. */
|
|
110
|
+
export function localBranchExists(runSync, cwd, branch) {
|
|
111
|
+
return runSync('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], cwd).status === 0;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Migration (Q4/Q5/§4.5): bring a detached/stale worktree onto
|
|
115
|
+
* `wt-<basename>` at `ref`. Idempotent: re-running on an already-adopted
|
|
116
|
+
* clean worktree is a lossless reset to `ref`. Never discards:
|
|
117
|
+
* - dirty work tree -> skipped-dirty (surface)
|
|
118
|
+
* - current HEAD unmerged -> blocked-unmerged (surface)
|
|
119
|
+
* - TARGET `branch` exists with commits not on `ref` -> blocked-target-
|
|
120
|
+
* unmerged (surface). This is load-bearing: the switch uses `-C`
|
|
121
|
+
* (force-create/reset), which would ORPHAN commits on a pre-existing
|
|
122
|
+
* `wt-` branch. The HEAD-merged check alone misses this when the
|
|
123
|
+
* target branch != HEAD (e.g. on `main` while a prior `wt-x` holds
|
|
124
|
+
* committed-but-unmerged work). gh#33 CR-v2 blocker 078d1630.
|
|
125
|
+
*/
|
|
126
|
+
export function adoptWorktree(runSync, cwd, branch, ref) {
|
|
127
|
+
if (!isCleanTree(runSync, cwd)) {
|
|
128
|
+
return {
|
|
129
|
+
action: 'skipped-dirty',
|
|
130
|
+
message: 'uncommitted changes present; not switching (nothing discarded)',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (!isMerged(runSync, cwd, 'HEAD', ref)) {
|
|
134
|
+
return {
|
|
135
|
+
action: 'blocked-unmerged',
|
|
136
|
+
message: `current HEAD has commits not on ${ref}; commit/push or set aside before adopting`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Guard the TARGET branch ref before the `switch -C` force-reset. If
|
|
140
|
+
// `branch` already exists and is NOT an ancestor of `ref`, it carries
|
|
141
|
+
// committed-unmerged work that `-C` would discard — block instead.
|
|
142
|
+
// (Absent, or an ancestor of `ref` = a clean reset target → proceed.)
|
|
143
|
+
if (localBranchExists(runSync, cwd, branch) && !isMerged(runSync, cwd, branch, ref)) {
|
|
144
|
+
return {
|
|
145
|
+
action: 'blocked-target-unmerged',
|
|
146
|
+
message: `branch ${branch} exists with commits not on ${ref}; resolve before adopting (a force-switch would discard them)`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
runSync('git', ['switch', '-C', branch, ref], cwd);
|
|
150
|
+
return { action: 'adopted' };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Post-merge cleanup (Q3): when `feature` is fully merged into `ref`,
|
|
154
|
+
* either ANNOUNCE it as prunable (default) or actually prune it with the
|
|
155
|
+
* safe `git branch -d` (which itself refuses to delete an unmerged
|
|
156
|
+
* branch — defense in depth against a stale local ref). Unmerged ->
|
|
157
|
+
* not-merged (never touched).
|
|
158
|
+
*/
|
|
159
|
+
export function cleanupMerged(runSync, cwd, feature, ref, opts = { prune: false }) {
|
|
160
|
+
if (!isMerged(runSync, cwd, feature, ref)) {
|
|
161
|
+
return { action: 'not-merged', branch: feature };
|
|
162
|
+
}
|
|
163
|
+
if (!opts.prune) {
|
|
164
|
+
return {
|
|
165
|
+
action: 'announced',
|
|
166
|
+
branch: feature,
|
|
167
|
+
message: `${feature} is merged into ${ref} and can be pruned: \`git branch -d ${feature}\` (or re-run with --prune)`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
runSync('git', ['branch', '-d', feature], cwd);
|
|
171
|
+
return { action: 'pruned', branch: feature };
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=worktree-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-lifecycle.js","sourceRoot":"","sources":["../src/worktree-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.37",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|