@triflux/remote 10.0.0-alpha.2 → 10.0.0
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/hub/index.mjs +5 -5
- package/hub/team/notify.mjs +1 -1
- package/hub/team/remote-session.mjs +296 -0
- package/package.json +1 -1
- package/hub/team/swarm-hypervisor.mjs +0 -554
- package/hub/team/swarm-locks.mjs +0 -204
- package/hub/team/swarm-planner.mjs +0 -256
- package/hub/team/swarm-reconciler.mjs +0 -137
- package/hub/team/worktree-lifecycle.mjs +0 -172
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
// hub/team/worktree-lifecycle.mjs — Git worktree lifecycle management
|
|
2
|
-
// Replaces shell commands in tfx-codex-swarm Step 5 + merge-worktree Phase 6.
|
|
3
|
-
// Convention: .codex-swarm/wt-{slug} paths, swarm/{runId}/{slug} branches.
|
|
4
|
-
|
|
5
|
-
import { execFile } from 'node:child_process';
|
|
6
|
-
import { resolve, normalize } from 'node:path';
|
|
7
|
-
import { mkdir, rm, access } from 'node:fs/promises';
|
|
8
|
-
|
|
9
|
-
const SWARM_ROOT = '.codex-swarm';
|
|
10
|
-
const SLEEP_MS = 2000; // WT race-guard (MEMORY.md: wt-attach-spacing)
|
|
11
|
-
|
|
12
|
-
function git(args, cwd) {
|
|
13
|
-
return new Promise((res, rej) => {
|
|
14
|
-
execFile('git', args, { cwd, windowsHide: true, timeout: 30_000 }, (err, stdout, stderr) => {
|
|
15
|
-
if (err) {
|
|
16
|
-
const msg = `git ${args[0]} failed: ${stderr?.trim() || err.message}`;
|
|
17
|
-
rej(new Error(msg));
|
|
18
|
-
} else {
|
|
19
|
-
res(stdout.trim());
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function sleep(ms) {
|
|
26
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Normalize path for Windows compatibility. */
|
|
30
|
-
function normPath(p) {
|
|
31
|
-
return normalize(p).replace(/\\/g, '/');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Create a worktree for a shard.
|
|
36
|
-
*
|
|
37
|
-
* @param {object} opts
|
|
38
|
-
* @param {string} opts.slug — shard identifier (e.g. "issue-42" or "auth-refactor")
|
|
39
|
-
* @param {string} opts.runId — swarm run ID
|
|
40
|
-
* @param {string} [opts.rootDir=process.cwd()] — repo root
|
|
41
|
-
* @param {string} [opts.baseBranch='main'] — base branch to branch from
|
|
42
|
-
* @returns {Promise<{ worktreePath: string, branchName: string }>}
|
|
43
|
-
*/
|
|
44
|
-
export async function ensureWorktree({ slug, runId, rootDir = process.cwd(), baseBranch = 'main' }) {
|
|
45
|
-
const wtDir = resolve(rootDir, SWARM_ROOT, `wt-${slug}`);
|
|
46
|
-
const branchName = `swarm/${runId}/${slug}`;
|
|
47
|
-
|
|
48
|
-
await mkdir(resolve(rootDir, SWARM_ROOT), { recursive: true });
|
|
49
|
-
|
|
50
|
-
// Check if worktree already exists
|
|
51
|
-
try {
|
|
52
|
-
await access(wtDir);
|
|
53
|
-
// Already exists — verify it's a valid worktree
|
|
54
|
-
await git(['rev-parse', '--is-inside-work-tree'], wtDir);
|
|
55
|
-
return { worktreePath: normPath(wtDir), branchName };
|
|
56
|
-
} catch {
|
|
57
|
-
// Doesn't exist or invalid — create fresh
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Clean up stale references
|
|
61
|
-
try {
|
|
62
|
-
await git(['worktree', 'prune'], rootDir);
|
|
63
|
-
} catch { /* best-effort */ }
|
|
64
|
-
|
|
65
|
-
await sleep(SLEEP_MS);
|
|
66
|
-
|
|
67
|
-
// Try create with new branch, fallback to existing branch
|
|
68
|
-
try {
|
|
69
|
-
await git(['worktree', 'add', wtDir, '-b', branchName, baseBranch], rootDir);
|
|
70
|
-
} catch {
|
|
71
|
-
await git(['worktree', 'add', wtDir, branchName], rootDir);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { worktreePath: normPath(wtDir), branchName };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Create a temporary integration branch for merge operations.
|
|
79
|
-
*
|
|
80
|
-
* @param {object} opts
|
|
81
|
-
* @param {string} opts.runId
|
|
82
|
-
* @param {string} opts.baseBranch
|
|
83
|
-
* @param {string} [opts.rootDir=process.cwd()]
|
|
84
|
-
* @returns {Promise<{ integrationBranch: string, baseCommit: string }>}
|
|
85
|
-
*/
|
|
86
|
-
export async function prepareIntegrationBranch({ runId, baseBranch, rootDir = process.cwd() }) {
|
|
87
|
-
const integrationBranch = `swarm/${runId}/merge`;
|
|
88
|
-
|
|
89
|
-
// Record base commit for rollback
|
|
90
|
-
const baseCommit = await git(['rev-parse', baseBranch], rootDir);
|
|
91
|
-
|
|
92
|
-
// Create integration branch from base
|
|
93
|
-
try {
|
|
94
|
-
await git(['branch', integrationBranch, baseBranch], rootDir);
|
|
95
|
-
} catch {
|
|
96
|
-
// Branch may already exist — reset to base
|
|
97
|
-
await git(['branch', '-f', integrationBranch, baseBranch], rootDir);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return { integrationBranch, baseCommit };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Rebase a shard branch onto the integration branch.
|
|
105
|
-
* Uses rebase + fast-forward only (no merge commits).
|
|
106
|
-
*
|
|
107
|
-
* @param {object} opts
|
|
108
|
-
* @param {string} opts.shardBranch — the shard's branch name
|
|
109
|
-
* @param {string} opts.integrationBranch — target integration branch
|
|
110
|
-
* @param {string} [opts.rootDir=process.cwd()]
|
|
111
|
-
* @returns {Promise<{ ok: boolean, headCommit?: string, error?: string }>}
|
|
112
|
-
*/
|
|
113
|
-
export async function rebaseShardOntoIntegration({ shardBranch, integrationBranch, rootDir = process.cwd() }) {
|
|
114
|
-
// Backup integration HEAD for rollback
|
|
115
|
-
const backupCommit = await git(['rev-parse', integrationBranch], rootDir);
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
// Rebase shard onto integration
|
|
119
|
-
await git(['rebase', integrationBranch, shardBranch], rootDir);
|
|
120
|
-
|
|
121
|
-
// Fast-forward integration to include shard changes
|
|
122
|
-
await git(['checkout', integrationBranch], rootDir);
|
|
123
|
-
await git(['merge', '--ff-only', shardBranch], rootDir);
|
|
124
|
-
|
|
125
|
-
const headCommit = await git(['rev-parse', 'HEAD'], rootDir);
|
|
126
|
-
return { ok: true, headCommit };
|
|
127
|
-
} catch (err) {
|
|
128
|
-
// Abort rebase and restore integration branch
|
|
129
|
-
try { await git(['rebase', '--abort'], rootDir); } catch { /* already clean */ }
|
|
130
|
-
try { await git(['checkout', integrationBranch], rootDir); } catch { /* best-effort */ }
|
|
131
|
-
try { await git(['reset', '--hard', backupCommit], rootDir); } catch { /* best-effort */ }
|
|
132
|
-
|
|
133
|
-
return { ok: false, error: err.message };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Remove a worktree and its branch.
|
|
139
|
-
* Follows WT race-guard: sleep between operations.
|
|
140
|
-
*
|
|
141
|
-
* @param {object} opts
|
|
142
|
-
* @param {string} opts.worktreePath
|
|
143
|
-
* @param {string} [opts.branchName] — optional branch to delete
|
|
144
|
-
* @param {string} [opts.rootDir=process.cwd()]
|
|
145
|
-
* @param {boolean} [opts.force=false]
|
|
146
|
-
*/
|
|
147
|
-
export async function pruneWorktree({ worktreePath, branchName, rootDir = process.cwd(), force = false }) {
|
|
148
|
-
const forceFlag = force ? '--force' : '';
|
|
149
|
-
|
|
150
|
-
// Remove worktree (with retry for Windows file handle issues — E5)
|
|
151
|
-
for (let attempt = 0; attempt < 3; attempt++) {
|
|
152
|
-
try {
|
|
153
|
-
await git(['worktree', 'remove', worktreePath, ...(forceFlag ? [forceFlag] : [])], rootDir);
|
|
154
|
-
break;
|
|
155
|
-
} catch (err) {
|
|
156
|
-
if (attempt === 2) {
|
|
157
|
-
// Last resort: rm the directory and prune
|
|
158
|
-
try { await rm(worktreePath, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
159
|
-
}
|
|
160
|
-
await sleep(SLEEP_MS);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Prune stale worktree references
|
|
165
|
-
await sleep(SLEEP_MS);
|
|
166
|
-
try { await git(['worktree', 'prune'], rootDir); } catch { /* best-effort */ }
|
|
167
|
-
|
|
168
|
-
// Delete branch if specified
|
|
169
|
-
if (branchName) {
|
|
170
|
-
try { await git(['branch', '-D', branchName], rootDir); } catch { /* may not exist */ }
|
|
171
|
-
}
|
|
172
|
-
}
|