@tablation/crew 0.10.2 → 0.11.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/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { resolve as resolve4, dirname as
|
|
4
|
+
import { resolve as resolve4, dirname as dirname10, join as join19, basename as basename7 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { hostname as hostname2, homedir as homedir5 } from "os";
|
|
7
7
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -3991,16 +3991,20 @@ function refreshBaseBranch(cwd, remote, base, dryRun = false) {
|
|
|
3991
3991
|
const [behindStr, aheadStr] = counts.split(/\s+/);
|
|
3992
3992
|
const behind = Number.parseInt(behindStr ?? "0", 10);
|
|
3993
3993
|
const ahead = Number.parseInt(aheadStr ?? "0", 10);
|
|
3994
|
+
const named = () => {
|
|
3995
|
+
const subjects = (gitOk(cwd, ["log", "--format=%h %s", "-5", `${upstream}..${base}`]) ?? "").split("\n").filter(Boolean);
|
|
3996
|
+
return subjects.length ? `: ${subjects.join("; ")}${ahead > subjects.length ? "; \u2026" : ""}` : "";
|
|
3997
|
+
};
|
|
3994
3998
|
if (ahead > 0 && behind > 0) {
|
|
3995
3999
|
return {
|
|
3996
4000
|
action: "diverged",
|
|
3997
4001
|
ahead,
|
|
3998
4002
|
behind,
|
|
3999
|
-
detail: `${base} has diverged from ${upstream} (${ahead} ahead, ${behind} behind)`
|
|
4003
|
+
detail: `${base} has diverged from ${upstream} (${ahead} ahead, ${behind} behind)${named()}`
|
|
4000
4004
|
};
|
|
4001
4005
|
}
|
|
4002
4006
|
if (behind === 0 && ahead > 0) {
|
|
4003
|
-
return { action: "ahead", ahead, detail: `${base} has ${ahead} commit(s) ${upstream} does not` };
|
|
4007
|
+
return { action: "ahead", ahead, detail: `${base} has ${ahead} commit(s) ${upstream} does not${named()}` };
|
|
4004
4008
|
}
|
|
4005
4009
|
if (behind === 0) return { action: "level", detail: `${base} is level with ${upstream}` };
|
|
4006
4010
|
if (dryRun) {
|
|
@@ -4015,8 +4019,22 @@ function refreshBaseBranch(cwd, remote, base, dryRun = false) {
|
|
|
4015
4019
|
detail: `fast-forwarded ${base} to ${upstream} (${behind} commit(s) from elsewhere)`
|
|
4016
4020
|
};
|
|
4017
4021
|
}
|
|
4018
|
-
function
|
|
4019
|
-
|
|
4022
|
+
function recoverStrandedReleaseCheckouts(cwd) {
|
|
4023
|
+
gitOk(cwd, ["worktree", "prune"]);
|
|
4024
|
+
const out = gitOk(cwd, ["worktree", "list", "--porcelain"]) ?? "";
|
|
4025
|
+
let path = "";
|
|
4026
|
+
for (const line of out.split("\n")) {
|
|
4027
|
+
if (line.startsWith("worktree ")) path = line.slice("worktree ".length);
|
|
4028
|
+
else if (line.startsWith("branch refs/heads/crew/release-") && path) {
|
|
4029
|
+
gitOk(cwd, ["worktree", "remove", "--force", path]);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
const stranded = (gitOk(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/crew/release-*"]) ?? "").split("\n").filter(Boolean);
|
|
4033
|
+
for (const b of stranded) gitOk(cwd, ["branch", "-q", "-D", b]);
|
|
4034
|
+
return stranded;
|
|
4035
|
+
}
|
|
4036
|
+
function pushBranch(cwd, remote, branch, dst = branch) {
|
|
4037
|
+
git(cwd, ["push", remote, `${branch}:${dst}`]);
|
|
4020
4038
|
}
|
|
4021
4039
|
function worktrees(cwd) {
|
|
4022
4040
|
const out = gitOk(cwd, ["worktree", "list", "--porcelain"]) ?? "";
|
|
@@ -5453,8 +5471,9 @@ Wrote ${o.configPath}.
|
|
|
5453
5471
|
}
|
|
5454
5472
|
|
|
5455
5473
|
// src/release-run.ts
|
|
5456
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
|
|
5457
|
-
import { join as join9 } from "path";
|
|
5474
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9, copyFileSync, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync2 } from "fs";
|
|
5475
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
5476
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
5458
5477
|
|
|
5459
5478
|
// src/release.ts
|
|
5460
5479
|
function describeUnplaceable(unplaceable, verifiedStatus) {
|
|
@@ -6029,6 +6048,31 @@ async function confirmLastRelease(o, decision) {
|
|
|
6029
6048
|
if (!confirmed) o.emit.warn(`${decision.lastTag} is still not confirmed live \u2014 tickets stay verified`);
|
|
6030
6049
|
return { confirmed, version };
|
|
6031
6050
|
}
|
|
6051
|
+
function openReleaseCheckout(o) {
|
|
6052
|
+
const { remote, base } = o.repo.branch;
|
|
6053
|
+
const branch = `crew/release-${Date.now()}`;
|
|
6054
|
+
const root = mkdtempSync(join9(tmpdir2(), "crew-release-"));
|
|
6055
|
+
const dir = join9(root, "checkout");
|
|
6056
|
+
const close = () => {
|
|
6057
|
+
gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
|
|
6058
|
+
rmSync2(root, { recursive: true, force: true });
|
|
6059
|
+
gitOk(o.cwd, ["worktree", "prune"]);
|
|
6060
|
+
gitOk(o.cwd, ["branch", "-D", branch]);
|
|
6061
|
+
};
|
|
6062
|
+
try {
|
|
6063
|
+
git(o.cwd, ["worktree", "add", "-q", "-b", branch, dir, `${remote}/${base}`]);
|
|
6064
|
+
} catch (e) {
|
|
6065
|
+
close();
|
|
6066
|
+
throw e;
|
|
6067
|
+
}
|
|
6068
|
+
for (const f of o.repo.worktrees.copy) {
|
|
6069
|
+
const src = join9(o.cwd, f);
|
|
6070
|
+
if (!existsSync9(src)) continue;
|
|
6071
|
+
mkdirSync7(dirname7(join9(dir, f)), { recursive: true });
|
|
6072
|
+
copyFileSync(src, join9(dir, f));
|
|
6073
|
+
}
|
|
6074
|
+
return { dir, branch, close };
|
|
6075
|
+
}
|
|
6032
6076
|
async function runRelease(o) {
|
|
6033
6077
|
const scoped = (msg) => o.scope ? `${o.scope}: ${msg}` : msg;
|
|
6034
6078
|
const tagPattern = o.repo.release.tagPattern ?? "v*";
|
|
@@ -6075,6 +6119,33 @@ async function runRelease(o) {
|
|
|
6075
6119
|
const externalClosures = await confirmExternalReleased(o, landed);
|
|
6076
6120
|
return { merged: [], deployed: false, stopped: "external", decision, externalClosures };
|
|
6077
6121
|
}
|
|
6122
|
+
const { remote, base } = o.repo.branch;
|
|
6123
|
+
if (o.dryRun || o.mergeOnly || !remoteBranchExists(o.cwd, remote, base)) {
|
|
6124
|
+
return mergeAndRelease(o, decision, fresh);
|
|
6125
|
+
}
|
|
6126
|
+
let checkout;
|
|
6127
|
+
const stranded = recoverStrandedReleaseCheckouts(o.cwd);
|
|
6128
|
+
if (stranded.length > 0) o.emit.warn(scoped(`dropped release checkout(s) stranded by a crash: ${stranded.join(", ")}`));
|
|
6129
|
+
try {
|
|
6130
|
+
checkout = openReleaseCheckout(o);
|
|
6131
|
+
} catch (e) {
|
|
6132
|
+
o.emit.error(scoped(`could not cut a release checkout from ${remote}/${base} \u2014 ${e.message}`));
|
|
6133
|
+
return { merged: [], deployed: false, stopped: "release checkout failed", decision };
|
|
6134
|
+
}
|
|
6135
|
+
try {
|
|
6136
|
+
return await mergeAndRelease({ ...o, cwd: checkout.dir }, decision, fresh, checkout.branch);
|
|
6137
|
+
} finally {
|
|
6138
|
+
checkout.close();
|
|
6139
|
+
const refreshed = refreshBaseBranch(o.cwd, remote, base);
|
|
6140
|
+
if (refreshed.action === "fast-forwarded" && o.repo.hooks.build) {
|
|
6141
|
+
const r = await hook(o, "build");
|
|
6142
|
+
if (r && r.code !== 0) {
|
|
6143
|
+
o.emit.warn(scoped(`rebuilding the primary checkout after the release failed (exit ${r.code}) \u2014 its dist/ is stale until \`${o.repo.hooks.build}\` is run there`));
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
async function mergeAndRelease(o, decision, fresh, releaseBranch) {
|
|
6078
6149
|
o.emit.enter("merge");
|
|
6079
6150
|
const merged = [];
|
|
6080
6151
|
const conflicts = [];
|
|
@@ -6236,6 +6307,16 @@ async function runRelease(o) {
|
|
|
6236
6307
|
}
|
|
6237
6308
|
}
|
|
6238
6309
|
}
|
|
6310
|
+
if (releaseBranch && !o.dryRun) {
|
|
6311
|
+
const { remote, base } = o.repo.branch;
|
|
6312
|
+
if (fetchRemote(o.cwd, remote) && remoteBranchExists(o.cwd, remote, base)) {
|
|
6313
|
+
const moved = resolve2(o.cwd, `${remote}/${base}`);
|
|
6314
|
+
if (moved && !isAncestor(o.cwd, moved, "HEAD")) {
|
|
6315
|
+
o.emit.warn(`${remote}/${base} moved during the release \u2014 discarding it; the tickets stay verified and the next cycle retries`);
|
|
6316
|
+
return { merged, conflicts, unbuildable, version, deployed: false, decision, stopped: "base branch moved during release" };
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6239
6320
|
let deployed = false;
|
|
6240
6321
|
let integrated = false;
|
|
6241
6322
|
if (o.repo.release.mode === "integrate") {
|
|
@@ -6265,7 +6346,7 @@ async function runRelease(o) {
|
|
|
6265
6346
|
o.emit.emit(`would push ${o.repo.branch.base} to ${o.repo.branch.remote}`);
|
|
6266
6347
|
} else {
|
|
6267
6348
|
try {
|
|
6268
|
-
pushBranch(o.cwd, o.repo.branch.remote, o.repo.branch.base);
|
|
6349
|
+
pushBranch(o.cwd, o.repo.branch.remote, releaseBranch ?? o.repo.branch.base, o.repo.branch.base);
|
|
6269
6350
|
o.emit.emit(`pushed ${o.repo.branch.base} to ${o.repo.branch.remote}`);
|
|
6270
6351
|
} catch (e) {
|
|
6271
6352
|
o.emit.error(
|
|
@@ -8020,7 +8101,7 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
8020
8101
|
}
|
|
8021
8102
|
|
|
8022
8103
|
// src/stream-sweep.ts
|
|
8023
|
-
import { existsSync as existsSync15, readdirSync as readdirSync3, rmSync as
|
|
8104
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync6 } from "fs";
|
|
8024
8105
|
import { join as join14 } from "path";
|
|
8025
8106
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
8026
8107
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
@@ -8043,7 +8124,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
8043
8124
|
continue;
|
|
8044
8125
|
}
|
|
8045
8126
|
try {
|
|
8046
|
-
|
|
8127
|
+
rmSync3(path, { force: true });
|
|
8047
8128
|
removed++;
|
|
8048
8129
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
8049
8130
|
} catch (e) {
|
|
@@ -8054,14 +8135,14 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
8054
8135
|
}
|
|
8055
8136
|
|
|
8056
8137
|
// src/cli.ts
|
|
8057
|
-
import { readFileSync as readFileSync14, existsSync as existsSync19, mkdirSync as
|
|
8138
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync } from "fs";
|
|
8058
8139
|
import { createInterface } from "readline/promises";
|
|
8059
8140
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
8060
8141
|
|
|
8061
8142
|
// src/ssh-keys.ts
|
|
8062
8143
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
8063
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
8064
|
-
import { dirname as
|
|
8144
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync11, chmodSync } from "fs";
|
|
8145
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
8065
8146
|
var SshKeyError = class extends Error {
|
|
8066
8147
|
};
|
|
8067
8148
|
function shipSshKeyPathFor(stateDir) {
|
|
@@ -8071,7 +8152,7 @@ function ensureShipSshKeypair(stateDir) {
|
|
|
8071
8152
|
const privateKeyPath = shipSshKeyPathFor(stateDir);
|
|
8072
8153
|
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
8073
8154
|
if (!existsSync16(privateKeyPath)) {
|
|
8074
|
-
|
|
8155
|
+
mkdirSync8(dirname9(privateKeyPath), { recursive: true });
|
|
8075
8156
|
try {
|
|
8076
8157
|
execFileSync3("ssh-keygen", [
|
|
8077
8158
|
"-t",
|
|
@@ -8308,7 +8389,7 @@ function syncAllPassengerCheckouts(cfg) {
|
|
|
8308
8389
|
|
|
8309
8390
|
// src/tunnel.ts
|
|
8310
8391
|
import { spawn as spawn3 } from "child_process";
|
|
8311
|
-
import { existsSync as existsSync17, mkdirSync as
|
|
8392
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
|
|
8312
8393
|
import { randomInt } from "crypto";
|
|
8313
8394
|
import { join as join16 } from "path";
|
|
8314
8395
|
var DEFAULT_SPAWN_OPTIONS = { stdio: "ignore", detached: true };
|
|
@@ -8390,7 +8471,7 @@ function readPersistedTunnel(stateDir, workspaceId) {
|
|
|
8390
8471
|
}
|
|
8391
8472
|
function writePersistedTunnel(stateDir, state) {
|
|
8392
8473
|
const path = tunnelStatePath(stateDir, state.workspaceId);
|
|
8393
|
-
|
|
8474
|
+
mkdirSync9(join16(stateDir, "passengers"), { recursive: true });
|
|
8394
8475
|
writeFileSync7(path, `${JSON.stringify(state, null, 2)}
|
|
8395
8476
|
`);
|
|
8396
8477
|
}
|
|
@@ -8610,7 +8691,7 @@ async function unregisterEndpoint(workspaceId, info, d) {
|
|
|
8610
8691
|
import { createServer as createHttpServer } from "http";
|
|
8611
8692
|
import { spawn as spawn4 } from "child_process";
|
|
8612
8693
|
import { randomBytes } from "crypto";
|
|
8613
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
8694
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync8, unlinkSync as unlinkSync4, readdirSync as readdirSync5 } from "fs";
|
|
8614
8695
|
import { join as join17 } from "path";
|
|
8615
8696
|
|
|
8616
8697
|
// src/secret-compare.ts
|
|
@@ -8713,7 +8794,7 @@ function readPersistedSyncDaemon(stateDir, workspaceId) {
|
|
|
8713
8794
|
}
|
|
8714
8795
|
function writePersistedSyncDaemon(stateDir, state) {
|
|
8715
8796
|
const path = syncDaemonStatePath(stateDir, state.workspaceId);
|
|
8716
|
-
|
|
8797
|
+
mkdirSync10(join17(stateDir, "passengers"), { recursive: true });
|
|
8717
8798
|
writeFileSync8(path, `${JSON.stringify(state, null, 2)}
|
|
8718
8799
|
`);
|
|
8719
8800
|
}
|
|
@@ -9077,7 +9158,7 @@ async function buildEnvironment(route, ship, ticket) {
|
|
|
9077
9158
|
}
|
|
9078
9159
|
|
|
9079
9160
|
// src/cli.ts
|
|
9080
|
-
var CREW_HOME = isCompiledBinary(import.meta.url) ?
|
|
9161
|
+
var CREW_HOME = isCompiledBinary(import.meta.url) ? dirname10(process.execPath) : resolve4(dirname10(fileURLToPath(import.meta.url)), "..");
|
|
9081
9162
|
var RELEASE_LOCK_TTL_MS = 20 * 60 * 1e3;
|
|
9082
9163
|
var CYCLE_SECONDS = 120;
|
|
9083
9164
|
var STALE_CYCLES = 3;
|
|
@@ -10529,7 +10610,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10529
10610
|
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10530
10611
|
raw.agentPersonas = personaResult.agentPersonas;
|
|
10531
10612
|
raw.agentSkills = skillResult.agentSkills;
|
|
10532
|
-
|
|
10613
|
+
mkdirSync11(dirOf(resolvedPath), { recursive: true });
|
|
10533
10614
|
writeFileSync9(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
10534
10615
|
`);
|
|
10535
10616
|
}
|
|
@@ -10577,7 +10658,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10577
10658
|
const resolvedPath = resolvedPathFor(cfg.ship.stateDir, target.route);
|
|
10578
10659
|
const raw = existsSync19(resolvedPath) ? JSON.parse(readFileSync14(resolvedPath, "utf8")) : {};
|
|
10579
10660
|
raw.agentSkills = result.agentSkills;
|
|
10580
|
-
|
|
10661
|
+
mkdirSync11(dirOf(resolvedPath), { recursive: true });
|
|
10581
10662
|
writeFileSync9(resolvedPath, `${JSON.stringify(raw, null, 2)}
|
|
10582
10663
|
`);
|
|
10583
10664
|
}
|
|
@@ -10621,7 +10702,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
10621
10702
|
}
|
|
10622
10703
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
10623
10704
|
const text = configText;
|
|
10624
|
-
const base =
|
|
10705
|
+
const base = dirname10(cfg.configFile);
|
|
10625
10706
|
try {
|
|
10626
10707
|
if (sub === "list") {
|
|
10627
10708
|
const repos = configuredRepos(text, routeName, base);
|
|
@@ -10982,8 +11063,8 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
10982
11063
|
process.stderr.write(`(dry run) would write resolved ids to ${resolvedPath}
|
|
10983
11064
|
`);
|
|
10984
11065
|
} else {
|
|
10985
|
-
|
|
10986
|
-
if (hadPreviousFile)
|
|
11066
|
+
mkdirSync11(dirname10(resolvedPath), { recursive: true });
|
|
11067
|
+
if (hadPreviousFile) copyFileSync2(resolvedPath, `${resolvedPath}.bak`);
|
|
10987
11068
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
10988
11069
|
writeFileSync9(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
10989
11070
|
`);
|
|
@@ -10999,7 +11080,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
10999
11080
|
process.stderr.write(`(dry run) would write the minted API key to ${apiKeyFile}
|
|
11000
11081
|
`);
|
|
11001
11082
|
} else {
|
|
11002
|
-
|
|
11083
|
+
mkdirSync11(dirname10(apiKeyFile), { recursive: true });
|
|
11003
11084
|
writeFileSync9(apiKeyFile, `${apiKeyVar}=${apiKey}
|
|
11004
11085
|
`, { mode: 384 });
|
|
11005
11086
|
process.stderr.write(`API key written to ${apiKeyFile}
|
package/package.json
CHANGED
|
@@ -353,9 +353,12 @@ if `report_type` is either of those.
|
|
|
353
353
|
reconcile it yourself. Post a comment on the ticket naming the repo and the
|
|
354
354
|
counts `crew sync` printed, leave the ticket as you found it, and end the
|
|
355
355
|
run — the operator reconciles the checkout. Otherwise, from
|
|
356
|
-
the primary checkout, `git worktree add
|
|
357
|
-
ticket, on a new branch cut from the base
|
|
358
|
-
|
|
356
|
+
the primary checkout, `git worktree add <dir> -b <branch> <remote>/<base>`
|
|
357
|
+
a sibling worktree for this ticket, on a new branch cut from the base
|
|
358
|
+
branch's REMOTE-TRACKING ref (`origin/main`, say), never from the primary
|
|
359
|
+
checkout's own local base — that copy is a cache of the remote, and a
|
|
360
|
+
local commit on it would be carried into every ticket cut from it
|
|
361
|
+
(CREW-1383). The digest's `worktree` and `branch` columns give this ticket's own directory
|
|
359
362
|
and branch name — use them verbatim. The Environment section names the
|
|
360
363
|
base branch and describes the general pattern, but its own worked example
|
|
361
364
|
is illustrative only (rendered before any ticket is chosen, so it cannot
|
|
@@ -334,8 +334,12 @@ if `report_type` is either of those.
|
|
|
334
334
|
reconcile it yourself. Post a comment on the ticket naming the repo and the
|
|
335
335
|
counts `crew sync` printed, leave the ticket as you found it, and end the
|
|
336
336
|
run — the operator reconciles the checkout. Otherwise, from
|
|
337
|
-
the primary checkout, `git worktree add
|
|
338
|
-
ticket, on a new branch cut from the base
|
|
337
|
+
the primary checkout, `git worktree add <dir> -b <branch> <remote>/<base>`
|
|
338
|
+
a sibling worktree for this ticket, on a new branch cut from the base
|
|
339
|
+
branch's REMOTE-TRACKING ref (`origin/main`, say), never from the primary
|
|
340
|
+
checkout's own local base — that copy is a cache of the remote, and a
|
|
341
|
+
local commit on it would be carried into every ticket cut from it
|
|
342
|
+
(CREW-1383). The
|
|
339
343
|
digest's `worktree` and `branch` columns give this ticket's own directory
|
|
340
344
|
and branch name — use them verbatim. The Environment section names the
|
|
341
345
|
base branch and describes the general pattern, but its own worked example
|