@shipers-dev/multi 0.63.0 → 0.64.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/dist/index.js +110 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17116,29 +17116,49 @@ async function ensureWorktree(workingDir, issueKey, issueId, opts) {
|
|
|
17116
17116
|
return { path: workingDir, branch: "", created: false };
|
|
17117
17117
|
}
|
|
17118
17118
|
ensureGitignoreEntry(workingDir, ".multi/");
|
|
17119
|
-
const
|
|
17120
|
-
const branch = `multi/${
|
|
17119
|
+
const slug = normalizeKey(opts?.worktreeName ?? issueKey);
|
|
17120
|
+
const branch = `multi/${slug}`;
|
|
17121
17121
|
const wtDir = join5(workingDir, ".multi", "worktrees");
|
|
17122
|
-
const wtPath = join5(wtDir,
|
|
17122
|
+
const wtPath = join5(wtDir, slug);
|
|
17123
|
+
let baseRef = "HEAD";
|
|
17124
|
+
const explicit = opts?.baseBranch || opts?.parentBranch || null;
|
|
17125
|
+
if (explicit) {
|
|
17126
|
+
const localOk = await branchExists(workingDir, explicit);
|
|
17127
|
+
if (localOk) {
|
|
17128
|
+
baseRef = explicit;
|
|
17129
|
+
} else {
|
|
17130
|
+
const fetched = await run3(workingDir, "git", ["fetch", "origin", `${explicit}:${explicit}`]);
|
|
17131
|
+
if (fetched.code === 0)
|
|
17132
|
+
baseRef = explicit;
|
|
17133
|
+
}
|
|
17134
|
+
}
|
|
17123
17135
|
if (existsSync3(wtPath)) {
|
|
17136
|
+
if (await isDirty(wtPath)) {
|
|
17137
|
+
throw new WorktreeDirtyError({ path: wtPath, branch, reason: "dirty" });
|
|
17138
|
+
}
|
|
17139
|
+
if (opts?.baseBranch) {
|
|
17140
|
+
const mergeBase = await run3(wtPath, "git", ["merge-base", branch, opts.baseBranch]);
|
|
17141
|
+
if (mergeBase.code === 0 && mergeBase.stdout) {
|
|
17142
|
+
const forkOnBase = await run3(workingDir, "git", ["merge-base", "--is-ancestor", mergeBase.stdout, opts.baseBranch]);
|
|
17143
|
+
if (forkOnBase.code === 1) {
|
|
17144
|
+
const baseHead = await run3(workingDir, "git", ["rev-parse", "--verify", `${opts.baseBranch}^{commit}`]);
|
|
17145
|
+
throw new WorktreeDirtyError({
|
|
17146
|
+
path: wtPath,
|
|
17147
|
+
branch,
|
|
17148
|
+
reason: "base_mismatch",
|
|
17149
|
+
expectedBase: opts.baseBranch,
|
|
17150
|
+
actualBase: baseHead.code === 0 ? baseHead.stdout : mergeBase.stdout
|
|
17151
|
+
});
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17154
|
+
}
|
|
17124
17155
|
if (issueId)
|
|
17125
|
-
setWorktreeIndexEntry(workingDir,
|
|
17156
|
+
setWorktreeIndexEntry(workingDir, slug, issueId);
|
|
17126
17157
|
return { path: wtPath, branch, created: false };
|
|
17127
17158
|
}
|
|
17128
17159
|
try {
|
|
17129
17160
|
mkdirSync3(wtDir, { recursive: true });
|
|
17130
17161
|
} catch {}
|
|
17131
|
-
let baseRef = "HEAD";
|
|
17132
|
-
if (opts?.parentBranch) {
|
|
17133
|
-
const localOk = await branchExists(workingDir, opts.parentBranch);
|
|
17134
|
-
if (localOk) {
|
|
17135
|
-
baseRef = opts.parentBranch;
|
|
17136
|
-
} else {
|
|
17137
|
-
const fetched = await run3(workingDir, "git", ["fetch", "origin", `${opts.parentBranch}:${opts.parentBranch}`]);
|
|
17138
|
-
if (fetched.code === 0)
|
|
17139
|
-
baseRef = opts.parentBranch;
|
|
17140
|
-
}
|
|
17141
|
-
}
|
|
17142
17162
|
const exists4 = await branchExists(workingDir, branch);
|
|
17143
17163
|
const args2 = exists4 ? ["worktree", "add", wtPath, branch] : ["worktree", "add", "-b", branch, wtPath, baseRef];
|
|
17144
17164
|
const r = await run3(workingDir, "git", args2);
|
|
@@ -17147,7 +17167,7 @@ async function ensureWorktree(workingDir, issueKey, issueId, opts) {
|
|
|
17147
17167
|
}
|
|
17148
17168
|
await linkIgnoredFiles(workingDir, wtPath);
|
|
17149
17169
|
if (issueId)
|
|
17150
|
-
setWorktreeIndexEntry(workingDir,
|
|
17170
|
+
setWorktreeIndexEntry(workingDir, slug, issueId);
|
|
17151
17171
|
return { path: wtPath, branch, created: true };
|
|
17152
17172
|
}
|
|
17153
17173
|
async function linkIgnoredFiles(workingDir, wtPath) {
|
|
@@ -17274,7 +17294,17 @@ async function squashMergeChild(wtPath, childBranch, childKey) {
|
|
|
17274
17294
|
result.message = `${childKey} squash-merged (${stagedFiles.length} file${stagedFiles.length === 1 ? "" : "s"})`;
|
|
17275
17295
|
return result;
|
|
17276
17296
|
}
|
|
17277
|
-
var
|
|
17297
|
+
var WorktreeDirtyError;
|
|
17298
|
+
var init_worktree = __esm(() => {
|
|
17299
|
+
WorktreeDirtyError = class WorktreeDirtyError extends Error {
|
|
17300
|
+
details;
|
|
17301
|
+
code = "E_WORKTREE_DIRTY";
|
|
17302
|
+
constructor(details) {
|
|
17303
|
+
super(`worktree dirty or base-mismatched at ${details.path} (${details.reason})`);
|
|
17304
|
+
this.details = details;
|
|
17305
|
+
}
|
|
17306
|
+
};
|
|
17307
|
+
});
|
|
17278
17308
|
|
|
17279
17309
|
// ../../node_modules/zod/v4/core/core.js
|
|
17280
17310
|
function $constructor(name, initializer, params) {
|
|
@@ -34022,7 +34052,8 @@ var init_streams = __esm(() => {
|
|
|
34022
34052
|
"stopped",
|
|
34023
34053
|
"queued",
|
|
34024
34054
|
"worktree_created",
|
|
34025
|
-
"worktree_error"
|
|
34055
|
+
"worktree_error",
|
|
34056
|
+
"worktree_dirty"
|
|
34026
34057
|
];
|
|
34027
34058
|
StreamEventTypeSchema = exports_external.enum(STREAM_EVENT_TYPES);
|
|
34028
34059
|
StreamEventInputSchema = exports_external.object({
|
|
@@ -34220,7 +34251,7 @@ function parsePlanBlocks(text) {
|
|
|
34220
34251
|
}
|
|
34221
34252
|
return { actions, errors: errors3 };
|
|
34222
34253
|
}
|
|
34223
|
-
var PLAN_SCHEMA_VERSION =
|
|
34254
|
+
var PLAN_SCHEMA_VERSION = 11, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, EvalPolicy, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
|
|
34224
34255
|
var init_plans = __esm(() => {
|
|
34225
34256
|
init_zod();
|
|
34226
34257
|
Priority = exports_external.enum(["low", "medium", "high"]);
|
|
@@ -34243,6 +34274,8 @@ var init_plans = __esm(() => {
|
|
|
34243
34274
|
priority: Priority.optional(),
|
|
34244
34275
|
assignee_type: AssigneeType.optional(),
|
|
34245
34276
|
assignee_id: exports_external.string().optional(),
|
|
34277
|
+
base_ref: exports_external.string().min(1).max(200).optional(),
|
|
34278
|
+
worktree: exports_external.string().min(1).max(60).regex(/^[a-z0-9][a-z0-9_\-\/]*$/i).optional(),
|
|
34246
34279
|
parent_id: exports_external.string().optional(),
|
|
34247
34280
|
blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
|
|
34248
34281
|
await_children: exports_external.boolean().optional(),
|
|
@@ -34853,9 +34886,54 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
34853
34886
|
const ISSUE_BASE = tenantWsId && projectId ? `${apiUrl}/api/workspaces/${tenantWsId}/projects/${projectId}/issues/${issueId}` : null;
|
|
34854
34887
|
let workingDir = baseWorkingDir;
|
|
34855
34888
|
let worktreeBranch = "";
|
|
34856
|
-
|
|
34889
|
+
const inlineMode = task.inline_mode === true;
|
|
34890
|
+
const baseBranch = task.base_branch ?? null;
|
|
34891
|
+
const worktreeName = task.worktree_name ?? null;
|
|
34892
|
+
let inlineRestoreHead = null;
|
|
34893
|
+
const blockOnDirty = async (reason, extra = {}) => {
|
|
34894
|
+
await postStream(apiUrl, issueId, "worktree_dirty", { reason, ...extra });
|
|
34895
|
+
if (tenantWsId) {
|
|
34896
|
+
try {
|
|
34897
|
+
await patchIssueStatus(apiUrl, tenantWsId, issueId, "blocked");
|
|
34898
|
+
} catch {}
|
|
34899
|
+
}
|
|
34900
|
+
await postStream(apiUrl, issueId, "run_finished", { stopReason: "blocked", duration_ms: 0 });
|
|
34901
|
+
};
|
|
34902
|
+
if (baseWorkingDir && inlineMode) {
|
|
34857
34903
|
try {
|
|
34858
|
-
const
|
|
34904
|
+
const status3 = Bun.spawnSync(["git", "status", "--porcelain"], { cwd: baseWorkingDir, stdout: "pipe" });
|
|
34905
|
+
const dirty = (status3.stdout?.toString() || "").trim().length > 0;
|
|
34906
|
+
if (dirty) {
|
|
34907
|
+
await blockOnDirty("inline_dirty", { path: baseWorkingDir });
|
|
34908
|
+
return;
|
|
34909
|
+
}
|
|
34910
|
+
if (baseBranch) {
|
|
34911
|
+
const origSymRef = Bun.spawnSync(["git", "symbolic-ref", "--quiet", "HEAD"], { cwd: baseWorkingDir, stdout: "pipe" });
|
|
34912
|
+
if (origSymRef.exitCode === 0) {
|
|
34913
|
+
inlineRestoreHead = (origSymRef.stdout?.toString() || "").trim().replace(/^refs\/heads\//, "") || null;
|
|
34914
|
+
} else {
|
|
34915
|
+
const origSha = Bun.spawnSync(["git", "rev-parse", "HEAD"], { cwd: baseWorkingDir, stdout: "pipe" });
|
|
34916
|
+
inlineRestoreHead = (origSha.stdout?.toString() || "").trim() || null;
|
|
34917
|
+
}
|
|
34918
|
+
const co = Bun.spawnSync(["git", "checkout", baseBranch], { cwd: baseWorkingDir, stdout: "pipe", stderr: "pipe" });
|
|
34919
|
+
if (co.exitCode !== 0) {
|
|
34920
|
+
await postStream(apiUrl, issueId, "worktree_error", { mode: "inline", message: co.stderr?.toString() || "checkout failed" });
|
|
34921
|
+
await blockOnDirty("inline_checkout_failed", { baseBranch });
|
|
34922
|
+
return;
|
|
34923
|
+
}
|
|
34924
|
+
worktreeBranch = baseBranch;
|
|
34925
|
+
}
|
|
34926
|
+
await postStream(apiUrl, issueId, "worktree_created", { path: baseWorkingDir, branch: worktreeBranch, reused: true, inline: true });
|
|
34927
|
+
} catch (e) {
|
|
34928
|
+
await postStream(apiUrl, issueId, "worktree_error", { mode: "inline", message: fmtError(e) });
|
|
34929
|
+
}
|
|
34930
|
+
} else if (baseWorkingDir) {
|
|
34931
|
+
try {
|
|
34932
|
+
const wt = await ensureWorktree(baseWorkingDir, task.key || issueId, issueId, {
|
|
34933
|
+
baseBranch: baseBranch ?? undefined,
|
|
34934
|
+
parentBranch: task.parent_branch ?? undefined,
|
|
34935
|
+
worktreeName: worktreeName ?? undefined
|
|
34936
|
+
});
|
|
34859
34937
|
workingDir = wt.path;
|
|
34860
34938
|
worktreeBranch = wt.branch;
|
|
34861
34939
|
await postStream(apiUrl, issueId, "worktree_created", { path: wt.path, branch: wt.branch, reused: !wt.created });
|
|
@@ -34867,6 +34945,10 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
|
|
|
34867
34945
|
}
|
|
34868
34946
|
}
|
|
34869
34947
|
} catch (e) {
|
|
34948
|
+
if (e?.code === "E_WORKTREE_DIRTY") {
|
|
34949
|
+
await blockOnDirty("worktree_dirty", { details: e.details });
|
|
34950
|
+
return;
|
|
34951
|
+
}
|
|
34870
34952
|
await postStream(apiUrl, issueId, "worktree_error", { message: fmtError(e) });
|
|
34871
34953
|
}
|
|
34872
34954
|
}
|
|
@@ -35398,6 +35480,12 @@ ${userPart}` : userPart;
|
|
|
35398
35480
|
await apiClient.post(`${ISSUE_BASE}/fail`, {});
|
|
35399
35481
|
log3(` ✗ ${task.key} failed: ${msg}`);
|
|
35400
35482
|
}
|
|
35483
|
+
} finally {
|
|
35484
|
+
if (inlineMode && inlineRestoreHead && baseWorkingDir) {
|
|
35485
|
+
try {
|
|
35486
|
+
Bun.spawnSync(["git", "checkout", inlineRestoreHead], { cwd: baseWorkingDir, stdout: "pipe", stderr: "pipe" });
|
|
35487
|
+
} catch {}
|
|
35488
|
+
}
|
|
35401
35489
|
}
|
|
35402
35490
|
}
|
|
35403
35491
|
async function buildPlanningPreamble(apiUrl, task, _wsId) {
|
|
@@ -38681,7 +38769,7 @@ import { parseArgs } from "util";
|
|
|
38681
38769
|
// package.json
|
|
38682
38770
|
var package_default = {
|
|
38683
38771
|
name: "@shipers-dev/multi",
|
|
38684
|
-
version: "0.
|
|
38772
|
+
version: "0.64.0",
|
|
38685
38773
|
type: "module",
|
|
38686
38774
|
bin: {
|
|
38687
38775
|
"multi-agent": "./dist/index.js"
|