codebyplan 1.13.50 → 1.13.52
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 +446 -14
- package/package.json +1 -1
- package/templates/rules/supabase-branch-lifecycle.md +2 -3
- package/templates/settings.project.base.json +0 -2
- package/templates/skills/cbp-build-cc-mode/SKILL.md +1 -1
- package/templates/skills/cbp-build-cc-skill/SKILL.md +1 -0
- package/templates/skills/cbp-build-cc-skill/reference/fork-eligibility.md +78 -0
- package/templates/skills/cbp-build-cc-skill/reference/frontmatter-fields.md +4 -0
- package/templates/skills/cbp-checkpoint-create/SKILL.md +1 -0
- package/templates/skills/cbp-checkpoint-start/SKILL.md +1 -0
- package/templates/skills/cbp-session-end/SKILL.md +2 -18
- package/templates/skills/cbp-session-start/SKILL.md +4 -18
- package/templates/skills/cbp-supabase-migrate/SKILL.md +1 -1
- package/templates/skills/cbp-git-worktree-create/SKILL.md +0 -199
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +0 -144
package/dist/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
39
39
|
var init_version = __esm({
|
|
40
40
|
"src/lib/version.ts"() {
|
|
41
41
|
"use strict";
|
|
42
|
-
VERSION = "1.13.
|
|
42
|
+
VERSION = "1.13.52";
|
|
43
43
|
PACKAGE_NAME = "codebyplan";
|
|
44
44
|
}
|
|
45
45
|
});
|
|
@@ -14280,8 +14280,8 @@ var require_RealtimeChannel = __commonJS({
|
|
|
14280
14280
|
}
|
|
14281
14281
|
/** @internal */
|
|
14282
14282
|
_notThisChannelEvent(event, ref) {
|
|
14283
|
-
const { close, error, leave, join:
|
|
14284
|
-
const events = [close, error, leave,
|
|
14283
|
+
const { close, error, leave, join: join48 } = constants_1.CHANNEL_EVENTS;
|
|
14284
|
+
const events = [close, error, leave, join48];
|
|
14285
14285
|
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
|
14286
14286
|
}
|
|
14287
14287
|
/** @internal */
|
|
@@ -37932,8 +37932,242 @@ var init_validate_waves2 = __esm({
|
|
|
37932
37932
|
}
|
|
37933
37933
|
});
|
|
37934
37934
|
|
|
37935
|
+
// src/cli/worktree/path.ts
|
|
37936
|
+
import { dirname as dirname13, basename as basename4, join as join45 } from "node:path";
|
|
37937
|
+
function computeWorktreePath(cwd, checkpointNumber) {
|
|
37938
|
+
const parent = dirname13(cwd);
|
|
37939
|
+
const base = basename4(cwd);
|
|
37940
|
+
const nnn = String(checkpointNumber).padStart(3, "0");
|
|
37941
|
+
return join45(parent, `${base}-CHK-${nnn}`);
|
|
37942
|
+
}
|
|
37943
|
+
var init_path = __esm({
|
|
37944
|
+
"src/cli/worktree/path.ts"() {
|
|
37945
|
+
"use strict";
|
|
37946
|
+
}
|
|
37947
|
+
});
|
|
37948
|
+
|
|
37949
|
+
// src/cli/worktree/add.ts
|
|
37950
|
+
import { join as join46, basename as basename5 } from "node:path";
|
|
37951
|
+
import { mkdir as mkdir14, readFile as readFile30, writeFile as writeFile23 } from "node:fs/promises";
|
|
37952
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
37953
|
+
async function defaultGetRepoId(cwd) {
|
|
37954
|
+
const found = await findCodebyplanConfig(cwd);
|
|
37955
|
+
return found?.contents.repo_id ?? null;
|
|
37956
|
+
}
|
|
37957
|
+
async function defaultLookupBranch(repoId, checkpointNumber) {
|
|
37958
|
+
const checkpoints = await mcpCall("get_checkpoints", {
|
|
37959
|
+
repo_id: repoId
|
|
37960
|
+
});
|
|
37961
|
+
const list = Array.isArray(checkpoints) ? checkpoints : [];
|
|
37962
|
+
const chk = list.find((c) => c.number === checkpointNumber);
|
|
37963
|
+
if (!chk) {
|
|
37964
|
+
return {
|
|
37965
|
+
found: false,
|
|
37966
|
+
pending: false,
|
|
37967
|
+
message: `No checkpoint found with number ${checkpointNumber} in this repo.`
|
|
37968
|
+
};
|
|
37969
|
+
}
|
|
37970
|
+
if (!chk.branch_name) {
|
|
37971
|
+
const nnn = String(checkpointNumber).padStart(3, "0");
|
|
37972
|
+
return {
|
|
37973
|
+
found: false,
|
|
37974
|
+
pending: true,
|
|
37975
|
+
message: `Branch not yet created for CHK-${nnn} \u2014 connect this repo to GitHub in Settings \u2192 Integrations.`
|
|
37976
|
+
};
|
|
37977
|
+
}
|
|
37978
|
+
return { found: true, branch_name: chk.branch_name };
|
|
37979
|
+
}
|
|
37980
|
+
function defaultGitRun(args, cwd) {
|
|
37981
|
+
const result = spawnSync16("git", args, {
|
|
37982
|
+
cwd,
|
|
37983
|
+
encoding: "utf-8",
|
|
37984
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
37985
|
+
});
|
|
37986
|
+
return {
|
|
37987
|
+
status: result.status,
|
|
37988
|
+
stdout: (result.stdout ?? "").toString(),
|
|
37989
|
+
stderr: (result.stderr ?? "").toString(),
|
|
37990
|
+
error: result.error
|
|
37991
|
+
};
|
|
37992
|
+
}
|
|
37993
|
+
async function defaultCopyConfigStubs(srcCwd, destPath) {
|
|
37994
|
+
await mkdir14(join46(destPath, ".codebyplan"), { recursive: true });
|
|
37995
|
+
const topLevelStubs = [".mcp.json", ".env.local"];
|
|
37996
|
+
for (const stub of topLevelStubs) {
|
|
37997
|
+
try {
|
|
37998
|
+
const content = await readFile30(join46(srcCwd, stub), "utf-8");
|
|
37999
|
+
await writeFile23(join46(destPath, stub), content, "utf-8");
|
|
38000
|
+
} catch {
|
|
38001
|
+
}
|
|
38002
|
+
}
|
|
38003
|
+
try {
|
|
38004
|
+
const content = await readFile30(
|
|
38005
|
+
join46(srcCwd, ".codebyplan", "repo.json"),
|
|
38006
|
+
"utf-8"
|
|
38007
|
+
);
|
|
38008
|
+
await writeFile23(
|
|
38009
|
+
join46(destPath, ".codebyplan", "repo.json"),
|
|
38010
|
+
content,
|
|
38011
|
+
"utf-8"
|
|
38012
|
+
);
|
|
38013
|
+
} catch {
|
|
38014
|
+
}
|
|
38015
|
+
}
|
|
38016
|
+
async function defaultRegisterWorktree(repoId, name, path16) {
|
|
38017
|
+
const res = await apiPost("/worktrees", {
|
|
38018
|
+
repo_id: repoId,
|
|
38019
|
+
name,
|
|
38020
|
+
path: path16,
|
|
38021
|
+
status: "active"
|
|
38022
|
+
});
|
|
38023
|
+
return { worktree_id: res.data.id };
|
|
38024
|
+
}
|
|
38025
|
+
function parseCheckpointNumber(arg) {
|
|
38026
|
+
const m = /^(?:CHK-)?(\d+)$/i.exec(arg.trim());
|
|
38027
|
+
if (!m?.[1]) return null;
|
|
38028
|
+
const n = parseInt(m[1], 10);
|
|
38029
|
+
return isNaN(n) || n < 0 ? null : n;
|
|
38030
|
+
}
|
|
38031
|
+
async function runWorktreeAdd(args, deps = {}) {
|
|
38032
|
+
const rawArg = args.find((a) => !a.startsWith("--"));
|
|
38033
|
+
if (!rawArg) {
|
|
38034
|
+
process.stderr.write(
|
|
38035
|
+
JSON.stringify({
|
|
38036
|
+
error: "usage: codebyplan worktree add <CHK-NNN>"
|
|
38037
|
+
}) + "\n"
|
|
38038
|
+
);
|
|
38039
|
+
return 1;
|
|
38040
|
+
}
|
|
38041
|
+
const checkpointNumber = parseCheckpointNumber(rawArg);
|
|
38042
|
+
if (checkpointNumber === null) {
|
|
38043
|
+
process.stderr.write(
|
|
38044
|
+
JSON.stringify({
|
|
38045
|
+
error: `Invalid checkpoint identifier '${rawArg}'. Use CHK-NNN or a bare number (e.g. CHK-207 or 207).`
|
|
38046
|
+
}) + "\n"
|
|
38047
|
+
);
|
|
38048
|
+
return 1;
|
|
38049
|
+
}
|
|
38050
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
38051
|
+
const getRepoId = deps.getRepoId ?? defaultGetRepoId;
|
|
38052
|
+
const lookupBranch = deps.lookupBranch ?? defaultLookupBranch;
|
|
38053
|
+
const gitRun = deps.gitRun ?? defaultGitRun;
|
|
38054
|
+
const sleep3 = deps.sleep ?? ((ms) => new Promise((resolve11) => setTimeout(resolve11, ms)));
|
|
38055
|
+
const copyConfigStubs = deps.copyConfigStubs ?? defaultCopyConfigStubs;
|
|
38056
|
+
const registerWorktree = deps.registerWorktree ?? defaultRegisterWorktree;
|
|
38057
|
+
try {
|
|
38058
|
+
const repoId = await getRepoId(cwd);
|
|
38059
|
+
if (!repoId) {
|
|
38060
|
+
process.stderr.write(
|
|
38061
|
+
JSON.stringify({
|
|
38062
|
+
error: "Could not determine repo_id. Ensure .codebyplan/repo.json exists in the project root."
|
|
38063
|
+
}) + "\n"
|
|
38064
|
+
);
|
|
38065
|
+
return 1;
|
|
38066
|
+
}
|
|
38067
|
+
const lookup = await lookupBranch(repoId, checkpointNumber);
|
|
38068
|
+
if (!lookup.found) {
|
|
38069
|
+
if (lookup.pending) {
|
|
38070
|
+
const result = {
|
|
38071
|
+
status: "branch_pending",
|
|
38072
|
+
message: lookup.message
|
|
38073
|
+
};
|
|
38074
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
38075
|
+
return 0;
|
|
38076
|
+
}
|
|
38077
|
+
process.stderr.write(JSON.stringify({ error: lookup.message }) + "\n");
|
|
38078
|
+
return 1;
|
|
38079
|
+
}
|
|
38080
|
+
const branchName = lookup.branch_name;
|
|
38081
|
+
const nnn = String(checkpointNumber).padStart(3, "0");
|
|
38082
|
+
gitRun(["fetch", "origin", branchName], cwd);
|
|
38083
|
+
let branchReady = false;
|
|
38084
|
+
for (let attempt = 0; attempt < BRANCH_RETRY_COUNT; attempt++) {
|
|
38085
|
+
if (attempt > 0) {
|
|
38086
|
+
await sleep3(BRANCH_RETRY_DELAY_MS);
|
|
38087
|
+
}
|
|
38088
|
+
const lsResult = gitRun(
|
|
38089
|
+
["ls-remote", "--heads", "origin", branchName],
|
|
38090
|
+
cwd
|
|
38091
|
+
);
|
|
38092
|
+
if (lsResult.status === 0 && lsResult.stdout.trim().length > 0) {
|
|
38093
|
+
branchReady = true;
|
|
38094
|
+
break;
|
|
38095
|
+
}
|
|
38096
|
+
}
|
|
38097
|
+
if (!branchReady) {
|
|
38098
|
+
const result = {
|
|
38099
|
+
status: "branch_not_ready",
|
|
38100
|
+
message: `Branch '${branchName}' not yet visible at origin after ${BRANCH_RETRY_COUNT} retries. Connect this repo to GitHub in Settings \u2192 Integrations.`
|
|
38101
|
+
};
|
|
38102
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
38103
|
+
return 1;
|
|
38104
|
+
}
|
|
38105
|
+
const worktreePath = computeWorktreePath(cwd, checkpointNumber);
|
|
38106
|
+
const worktreeName = `${basename5(cwd)}-CHK-${nnn}`;
|
|
38107
|
+
const addResult = gitRun(
|
|
38108
|
+
["worktree", "add", worktreePath, branchName],
|
|
38109
|
+
cwd
|
|
38110
|
+
);
|
|
38111
|
+
if (addResult.status !== 0) {
|
|
38112
|
+
const errMsg = addResult.stderr.trim() || addResult.error?.message || "unknown error";
|
|
38113
|
+
process.stderr.write(
|
|
38114
|
+
JSON.stringify({ error: `git worktree add failed: ${errMsg}` }) + "\n"
|
|
38115
|
+
);
|
|
38116
|
+
return 1;
|
|
38117
|
+
}
|
|
38118
|
+
try {
|
|
38119
|
+
await copyConfigStubs(cwd, worktreePath);
|
|
38120
|
+
} catch (err) {
|
|
38121
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38122
|
+
process.stderr.write(`Warning: failed to copy config stubs: ${msg}
|
|
38123
|
+
`);
|
|
38124
|
+
}
|
|
38125
|
+
try {
|
|
38126
|
+
const { worktree_id } = await registerWorktree(
|
|
38127
|
+
repoId,
|
|
38128
|
+
worktreeName,
|
|
38129
|
+
worktreePath
|
|
38130
|
+
);
|
|
38131
|
+
const result = {
|
|
38132
|
+
status: "added",
|
|
38133
|
+
worktree_path: worktreePath,
|
|
38134
|
+
branch_name: branchName,
|
|
38135
|
+
worktree_id
|
|
38136
|
+
};
|
|
38137
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
38138
|
+
return 0;
|
|
38139
|
+
} catch (err) {
|
|
38140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38141
|
+
const result = {
|
|
38142
|
+
status: "added",
|
|
38143
|
+
worktree_path: worktreePath,
|
|
38144
|
+
branch_name: branchName,
|
|
38145
|
+
warn: `Worktree created but registration failed: ${msg}`
|
|
38146
|
+
};
|
|
38147
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
38148
|
+
return 0;
|
|
38149
|
+
}
|
|
38150
|
+
} catch (err) {
|
|
38151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38152
|
+
process.stderr.write(JSON.stringify({ error: msg }) + "\n");
|
|
38153
|
+
return 1;
|
|
38154
|
+
}
|
|
38155
|
+
}
|
|
38156
|
+
var BRANCH_RETRY_COUNT, BRANCH_RETRY_DELAY_MS;
|
|
38157
|
+
var init_add = __esm({
|
|
38158
|
+
"src/cli/worktree/add.ts"() {
|
|
38159
|
+
"use strict";
|
|
38160
|
+
init_path();
|
|
38161
|
+
init_flags();
|
|
38162
|
+
init_mcp_client();
|
|
38163
|
+
init_api();
|
|
38164
|
+
BRANCH_RETRY_COUNT = 3;
|
|
38165
|
+
BRANCH_RETRY_DELAY_MS = 2e3;
|
|
38166
|
+
}
|
|
38167
|
+
});
|
|
38168
|
+
|
|
37935
38169
|
// src/cli/worktree/create.ts
|
|
37936
|
-
import { join as
|
|
38170
|
+
import { join as join47 } from "node:path";
|
|
37937
38171
|
async function defaultGetRepoIdentity(cwd) {
|
|
37938
38172
|
const found = await findCodebyplanConfig(cwd);
|
|
37939
38173
|
const contents = found?.contents ?? null;
|
|
@@ -37945,7 +38179,7 @@ async function defaultGetRepoIdentity(cwd) {
|
|
|
37945
38179
|
project_id: typeof contents?.["project_id"] === "string" ? contents["project_id"] : void 0
|
|
37946
38180
|
};
|
|
37947
38181
|
}
|
|
37948
|
-
async function
|
|
38182
|
+
async function defaultRegisterWorktree2(repoId, name, path16) {
|
|
37949
38183
|
const res = await apiPost("/worktrees", {
|
|
37950
38184
|
repo_id: repoId,
|
|
37951
38185
|
name,
|
|
@@ -37977,7 +38211,7 @@ async function runWorktreeCreate(args, deps = {}) {
|
|
|
37977
38211
|
const cwd = deps.cwd ?? process.cwd();
|
|
37978
38212
|
const getRepoIdentity = deps.getRepoIdentity ?? defaultGetRepoIdentity;
|
|
37979
38213
|
const getDeviceId = deps.getDeviceId ?? getOrCreateDeviceId;
|
|
37980
|
-
const registerWorktree = deps.registerWorktree ??
|
|
38214
|
+
const registerWorktree = deps.registerWorktree ?? defaultRegisterWorktree2;
|
|
37981
38215
|
const identity = await getRepoIdentity(cwd);
|
|
37982
38216
|
const repoId = identity?.repo_id ?? null;
|
|
37983
38217
|
if (!identity || !repoId) {
|
|
@@ -37988,7 +38222,7 @@ async function runWorktreeCreate(args, deps = {}) {
|
|
|
37988
38222
|
);
|
|
37989
38223
|
return 1;
|
|
37990
38224
|
}
|
|
37991
|
-
const worktreePath = explicitPath ??
|
|
38225
|
+
const worktreePath = explicitPath ?? join47(cwd, "..", name);
|
|
37992
38226
|
const deviceId = await getDeviceId(cwd);
|
|
37993
38227
|
let filesWritten = false;
|
|
37994
38228
|
try {
|
|
@@ -38044,7 +38278,9 @@ var init_create = __esm({
|
|
|
38044
38278
|
});
|
|
38045
38279
|
|
|
38046
38280
|
// src/cli/worktree/remove.ts
|
|
38047
|
-
|
|
38281
|
+
import { basename as basename6 } from "node:path";
|
|
38282
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
38283
|
+
async function defaultGetRepoId2(cwd) {
|
|
38048
38284
|
const found = await findCodebyplanConfig(cwd);
|
|
38049
38285
|
return found?.contents.repo_id ?? null;
|
|
38050
38286
|
}
|
|
@@ -38064,6 +38300,192 @@ async function defaultFindWorktreeId(repoId, name) {
|
|
|
38064
38300
|
async function defaultDeregisterWorktree(worktreeId) {
|
|
38065
38301
|
await mcpCall("delete_worktree", { worktree_id: worktreeId });
|
|
38066
38302
|
}
|
|
38303
|
+
function defaultGitRun2(args, cwd) {
|
|
38304
|
+
const result = spawnSync17("git", args, {
|
|
38305
|
+
cwd,
|
|
38306
|
+
encoding: "utf-8",
|
|
38307
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
38308
|
+
});
|
|
38309
|
+
return {
|
|
38310
|
+
status: result.status,
|
|
38311
|
+
stdout: (result.stdout ?? "").toString(),
|
|
38312
|
+
stderr: (result.stderr ?? "").toString(),
|
|
38313
|
+
error: result.error
|
|
38314
|
+
};
|
|
38315
|
+
}
|
|
38316
|
+
function defaultGetWorktreeBranchName(worktreePath) {
|
|
38317
|
+
const result = spawnSync17("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
38318
|
+
cwd: worktreePath,
|
|
38319
|
+
encoding: "utf-8",
|
|
38320
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
38321
|
+
});
|
|
38322
|
+
if (result.status === 0 && result.stdout) {
|
|
38323
|
+
return result.stdout.trim() || null;
|
|
38324
|
+
}
|
|
38325
|
+
return null;
|
|
38326
|
+
}
|
|
38327
|
+
async function defaultDeleteSupabaseBranch(branchName, cwd) {
|
|
38328
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
38329
|
+
if (!token) {
|
|
38330
|
+
return {
|
|
38331
|
+
deleted: false,
|
|
38332
|
+
warn: "SUPABASE_ACCESS_TOKEN not set \u2014 skipping Supabase preview-branch teardown."
|
|
38333
|
+
};
|
|
38334
|
+
}
|
|
38335
|
+
const shipment = readShipmentConfig(cwd);
|
|
38336
|
+
const parentRef = shipment.parentRef;
|
|
38337
|
+
if (!parentRef) {
|
|
38338
|
+
return {
|
|
38339
|
+
deleted: false,
|
|
38340
|
+
warn: "Supabase project_ref not configured in .codebyplan/shipment.json \u2014 skipping teardown."
|
|
38341
|
+
};
|
|
38342
|
+
}
|
|
38343
|
+
const gitCfg = readGitConfig(cwd);
|
|
38344
|
+
if (branchName === gitCfg.production || gitCfg.protected.includes(branchName)) {
|
|
38345
|
+
return {
|
|
38346
|
+
deleted: false,
|
|
38347
|
+
warn: `Skipping Supabase teardown for protected branch '${branchName}'.`
|
|
38348
|
+
};
|
|
38349
|
+
}
|
|
38350
|
+
if (gitCfg.integration !== null && branchName === gitCfg.integration) {
|
|
38351
|
+
return {
|
|
38352
|
+
deleted: false,
|
|
38353
|
+
warn: `Skipping Supabase teardown for integration branch '${branchName}'.`
|
|
38354
|
+
};
|
|
38355
|
+
}
|
|
38356
|
+
try {
|
|
38357
|
+
const listRes = await fetch(
|
|
38358
|
+
`https://api.supabase.com/v1/projects/${parentRef}/branches`,
|
|
38359
|
+
{
|
|
38360
|
+
headers: {
|
|
38361
|
+
Authorization: `Bearer ${token}`,
|
|
38362
|
+
"Content-Type": "application/json"
|
|
38363
|
+
}
|
|
38364
|
+
}
|
|
38365
|
+
);
|
|
38366
|
+
if (!listRes.ok) {
|
|
38367
|
+
return {
|
|
38368
|
+
deleted: false,
|
|
38369
|
+
warn: `Supabase list_branches failed (HTTP ${listRes.status}) \u2014 verify the branch manually in the dashboard.`
|
|
38370
|
+
};
|
|
38371
|
+
}
|
|
38372
|
+
const branches = await listRes.json();
|
|
38373
|
+
if (!Array.isArray(branches)) {
|
|
38374
|
+
return {
|
|
38375
|
+
deleted: false,
|
|
38376
|
+
warn: "Supabase list_branches returned unexpected shape \u2014 verify manually in the dashboard."
|
|
38377
|
+
};
|
|
38378
|
+
}
|
|
38379
|
+
const match = branches.find((b) => b.name === branchName);
|
|
38380
|
+
if (!match) {
|
|
38381
|
+
return { deleted: true };
|
|
38382
|
+
}
|
|
38383
|
+
if (match.is_default === true) {
|
|
38384
|
+
return {
|
|
38385
|
+
deleted: false,
|
|
38386
|
+
warn: `Refusing to delete is_default Supabase branch '${branchName}'.`
|
|
38387
|
+
};
|
|
38388
|
+
}
|
|
38389
|
+
if (!match.id) {
|
|
38390
|
+
return {
|
|
38391
|
+
deleted: false,
|
|
38392
|
+
warn: `Supabase branch '${branchName}' found but has no id \u2014 skipping.`
|
|
38393
|
+
};
|
|
38394
|
+
}
|
|
38395
|
+
const deleteRes = await fetch(
|
|
38396
|
+
`https://api.supabase.com/v1/projects/${parentRef}/branches/${match.id}`,
|
|
38397
|
+
{
|
|
38398
|
+
method: "DELETE",
|
|
38399
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
38400
|
+
}
|
|
38401
|
+
);
|
|
38402
|
+
if (!deleteRes.ok) {
|
|
38403
|
+
return {
|
|
38404
|
+
deleted: false,
|
|
38405
|
+
warn: `Supabase delete_branch failed (HTTP ${deleteRes.status}) \u2014 verify the branch manually in the dashboard.`
|
|
38406
|
+
};
|
|
38407
|
+
}
|
|
38408
|
+
return { deleted: true };
|
|
38409
|
+
} catch (err) {
|
|
38410
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38411
|
+
return {
|
|
38412
|
+
deleted: false,
|
|
38413
|
+
warn: `Supabase teardown error: ${msg} \u2014 verify manually in the dashboard.`
|
|
38414
|
+
};
|
|
38415
|
+
}
|
|
38416
|
+
}
|
|
38417
|
+
function parseChkNumber(name) {
|
|
38418
|
+
const chkMatch = /^CHK-(\d+)$/i.exec(name);
|
|
38419
|
+
if (chkMatch?.[1]) {
|
|
38420
|
+
const n = parseInt(chkMatch[1], 10);
|
|
38421
|
+
return isNaN(n) ? null : n;
|
|
38422
|
+
}
|
|
38423
|
+
if (/^\d+$/.test(name)) {
|
|
38424
|
+
const n = parseInt(name, 10);
|
|
38425
|
+
return isNaN(n) ? null : n;
|
|
38426
|
+
}
|
|
38427
|
+
return null;
|
|
38428
|
+
}
|
|
38429
|
+
async function runWorktreeRemoveChk(checkpointNumber, deps) {
|
|
38430
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
38431
|
+
const getRepoId = deps.getRepoId ?? defaultGetRepoId2;
|
|
38432
|
+
const findWorktreeId = deps.findWorktreeId ?? defaultFindWorktreeId;
|
|
38433
|
+
const deregisterWorktree = deps.deregisterWorktree ?? defaultDeregisterWorktree;
|
|
38434
|
+
const gitRun = deps.gitRun ?? defaultGitRun2;
|
|
38435
|
+
const getWorktreeBranchName = deps.getWorktreeBranchName ?? defaultGetWorktreeBranchName;
|
|
38436
|
+
const deleteSupabaseBranch = deps.deleteSupabaseBranch ?? defaultDeleteSupabaseBranch;
|
|
38437
|
+
const nnn = String(checkpointNumber).padStart(3, "0");
|
|
38438
|
+
const worktreeName = `${basename6(cwd)}-CHK-${nnn}`;
|
|
38439
|
+
const worktreePath = computeWorktreePath(cwd, checkpointNumber);
|
|
38440
|
+
const warnings = [];
|
|
38441
|
+
const branchName = getWorktreeBranchName(worktreePath);
|
|
38442
|
+
const removeResult = gitRun(["worktree", "remove", worktreePath], cwd);
|
|
38443
|
+
const git_removed = removeResult.status === 0;
|
|
38444
|
+
if (!git_removed) {
|
|
38445
|
+
const errMsg = removeResult.stderr.trim() || removeResult.error?.message || "unknown error";
|
|
38446
|
+
warnings.push(`git worktree remove failed: ${errMsg}`);
|
|
38447
|
+
}
|
|
38448
|
+
let mcp_deregistered = false;
|
|
38449
|
+
const repoId = await getRepoId(cwd);
|
|
38450
|
+
if (repoId) {
|
|
38451
|
+
const worktreeId = await findWorktreeId(repoId, worktreeName);
|
|
38452
|
+
if (worktreeId) {
|
|
38453
|
+
try {
|
|
38454
|
+
await deregisterWorktree(worktreeId);
|
|
38455
|
+
mcp_deregistered = true;
|
|
38456
|
+
} catch (err) {
|
|
38457
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38458
|
+
warnings.push(`MCP deregistration failed: ${msg}`);
|
|
38459
|
+
}
|
|
38460
|
+
} else {
|
|
38461
|
+
warnings.push(
|
|
38462
|
+
`No registered worktree found with name '${worktreeName}' for this repo.`
|
|
38463
|
+
);
|
|
38464
|
+
}
|
|
38465
|
+
} else {
|
|
38466
|
+
warnings.push("Could not determine repo_id \u2014 worktree not deregistered.");
|
|
38467
|
+
}
|
|
38468
|
+
let supabase_torn_down = false;
|
|
38469
|
+
if (branchName) {
|
|
38470
|
+
const teardown = await deleteSupabaseBranch(branchName, cwd);
|
|
38471
|
+
supabase_torn_down = teardown.deleted;
|
|
38472
|
+
if (teardown.warn) {
|
|
38473
|
+
warnings.push(teardown.warn);
|
|
38474
|
+
}
|
|
38475
|
+
} else {
|
|
38476
|
+
warnings.push(
|
|
38477
|
+
`Could not read the branch name for '${worktreePath}' (directory may already be deleted) \u2014 the Supabase preview branch was NOT torn down. Delete it manually in the Supabase dashboard if it still exists.`
|
|
38478
|
+
);
|
|
38479
|
+
}
|
|
38480
|
+
const result = {
|
|
38481
|
+
mcp_deregistered,
|
|
38482
|
+
git_removed,
|
|
38483
|
+
supabase_torn_down,
|
|
38484
|
+
...warnings.length > 0 ? { warn: warnings.join("; ") } : {}
|
|
38485
|
+
};
|
|
38486
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
38487
|
+
return 0;
|
|
38488
|
+
}
|
|
38067
38489
|
async function runWorktreeRemove(args, deps = {}) {
|
|
38068
38490
|
const name = args.find((a) => !a.startsWith("--"));
|
|
38069
38491
|
if (!name) {
|
|
@@ -38074,8 +38496,12 @@ async function runWorktreeRemove(args, deps = {}) {
|
|
|
38074
38496
|
);
|
|
38075
38497
|
return 1;
|
|
38076
38498
|
}
|
|
38499
|
+
const chkNumber = parseChkNumber(name);
|
|
38500
|
+
if (chkNumber !== null) {
|
|
38501
|
+
return runWorktreeRemoveChk(chkNumber, deps);
|
|
38502
|
+
}
|
|
38077
38503
|
const cwd = deps.cwd ?? process.cwd();
|
|
38078
|
-
const getRepoId = deps.getRepoId ??
|
|
38504
|
+
const getRepoId = deps.getRepoId ?? defaultGetRepoId2;
|
|
38079
38505
|
const findWorktreeId = deps.findWorktreeId ?? defaultFindWorktreeId;
|
|
38080
38506
|
const deregisterWorktree = deps.deregisterWorktree ?? defaultDeregisterWorktree;
|
|
38081
38507
|
const repoId = await getRepoId(cwd);
|
|
@@ -38114,9 +38540,11 @@ async function runWorktreeRemove(args, deps = {}) {
|
|
|
38114
38540
|
var init_remove = __esm({
|
|
38115
38541
|
"src/cli/worktree/remove.ts"() {
|
|
38116
38542
|
"use strict";
|
|
38543
|
+
init_path();
|
|
38117
38544
|
init_api();
|
|
38118
38545
|
init_mcp_client();
|
|
38119
38546
|
init_flags();
|
|
38547
|
+
init_supabase();
|
|
38120
38548
|
}
|
|
38121
38549
|
});
|
|
38122
38550
|
|
|
@@ -38127,6 +38555,9 @@ __export(worktree_exports, {
|
|
|
38127
38555
|
});
|
|
38128
38556
|
async function runWorktreeCommand(args) {
|
|
38129
38557
|
const subcommand = args[0];
|
|
38558
|
+
if (subcommand === "add") {
|
|
38559
|
+
return runWorktreeAdd(args.slice(1));
|
|
38560
|
+
}
|
|
38130
38561
|
if (subcommand === "create") {
|
|
38131
38562
|
return runWorktreeCreate(args.slice(1));
|
|
38132
38563
|
}
|
|
@@ -38140,7 +38571,7 @@ async function runWorktreeCommand(args) {
|
|
|
38140
38571
|
if (subcommand) {
|
|
38141
38572
|
process.stderr.write(
|
|
38142
38573
|
`Unknown subcommand: codebyplan worktree ${subcommand}
|
|
38143
|
-
Available: create, remove
|
|
38574
|
+
Available: add, create, remove
|
|
38144
38575
|
`
|
|
38145
38576
|
);
|
|
38146
38577
|
return 1;
|
|
@@ -38150,12 +38581,13 @@ Available: create, remove
|
|
|
38150
38581
|
}
|
|
38151
38582
|
function printWorktreeHelp() {
|
|
38152
38583
|
process.stdout.write(
|
|
38153
|
-
"\n codebyplan worktree <subcommand>\n\n Subcommands:\n create <name> Write .codebyplan/ files and register the worktree in CodeByPlan\n remove <name> Deregister a worktree
|
|
38584
|
+
"\n codebyplan worktree <subcommand>\n\n Subcommands:\n add <CHK-NNN> Create a per-checkpoint git worktree on the checkpoint's feat branch and register it\n create <name> Write .codebyplan/ files and register the worktree in CodeByPlan\n remove <name> Deregister a worktree (or `remove CHK-NNN` to prune the per-checkpoint worktree + Supabase preview)\n\n create flags:\n --path <abs> Explicit absolute path to the worktree root (primary mechanism)\n\n Output is JSON on stdout. Exit 0 on success or non-fatal failure; exit 1 on usage error.\n\n"
|
|
38154
38585
|
);
|
|
38155
38586
|
}
|
|
38156
38587
|
var init_worktree2 = __esm({
|
|
38157
38588
|
"src/cli/worktree.ts"() {
|
|
38158
38589
|
"use strict";
|
|
38590
|
+
init_add();
|
|
38159
38591
|
init_create();
|
|
38160
38592
|
init_remove();
|
|
38161
38593
|
}
|
|
@@ -38275,7 +38707,7 @@ var init_e2e = __esm({
|
|
|
38275
38707
|
});
|
|
38276
38708
|
|
|
38277
38709
|
// src/cli/e2e/verify-round.ts
|
|
38278
|
-
import { readFile as
|
|
38710
|
+
import { readFile as readFile31 } from "node:fs/promises";
|
|
38279
38711
|
import { resolve as resolve9 } from "node:path";
|
|
38280
38712
|
async function defaultFetchRounds(taskId) {
|
|
38281
38713
|
return mcpCall("get_rounds", { task_id: taskId });
|
|
@@ -38283,7 +38715,7 @@ async function defaultFetchRounds(taskId) {
|
|
|
38283
38715
|
async function defaultReadE2eConfig(cwd) {
|
|
38284
38716
|
try {
|
|
38285
38717
|
const p = resolve9(cwd, ".codebyplan", "e2e.json");
|
|
38286
|
-
const raw = await
|
|
38718
|
+
const raw = await readFile31(p, "utf-8");
|
|
38287
38719
|
return JSON.parse(raw);
|
|
38288
38720
|
} catch {
|
|
38289
38721
|
return null;
|
|
@@ -39063,7 +39495,7 @@ void (async () => {
|
|
|
39063
39495
|
codebyplan claude Claude asset management (install/update/uninstall/generate/migrate-memory)
|
|
39064
39496
|
codebyplan supabase Preview-branch helpers (resolve-preview/teardown-preview/new-migration/preview-check)
|
|
39065
39497
|
codebyplan session Session helpers (home-ff/freshness-gate/infra-files)
|
|
39066
|
-
codebyplan worktree Worktree management (create/remove)
|
|
39498
|
+
codebyplan worktree Worktree management (add/create/remove)
|
|
39067
39499
|
codebyplan e2e E2E deterministic gates (verify-round)
|
|
39068
39500
|
codebyplan statusline Show or set the statusline renderer (bash/node/python)
|
|
39069
39501
|
codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
|
package/package.json
CHANGED
|
@@ -7,7 +7,6 @@ paths:
|
|
|
7
7
|
- ".claude/skills/cbp-supabase-migrate/**"
|
|
8
8
|
- ".claude/skills/cbp-supabase-branch-check/**"
|
|
9
9
|
- ".claude/skills/cbp-checkpoint-end/**"
|
|
10
|
-
- ".claude/skills/cbp-git-worktree-remove/**"
|
|
11
10
|
- ".claude/skills/cbp-ship-main/**"
|
|
12
11
|
- ".claude/skills/cbp-standalone-task-complete/**"
|
|
13
12
|
---
|
|
@@ -48,7 +47,7 @@ The Supabase branch is removed wherever the git branch is deleted:
|
|
|
48
47
|
| Skill | Trigger |
|
|
49
48
|
|---|---|
|
|
50
49
|
| `cbp-checkpoint-end` | stale-branch cleanup + current feat-branch delete on ship |
|
|
51
|
-
| `
|
|
50
|
+
| `codebyplan worktree remove CHK-NNN` | worktree teardown removes the coupled Supabase branch |
|
|
52
51
|
| `cbp-ship-main` | `branch_deleted` event after PR merge |
|
|
53
52
|
| `cbp-standalone-task-complete` | `branch_deleted` event after standalone PR merge (Step 7.3) |
|
|
54
53
|
|
|
@@ -94,7 +93,7 @@ or auto-created by the GitHub integration — both paths use the same branch nam
|
|
|
94
93
|
| Role | Skill |
|
|
95
94
|
|---|---|
|
|
96
95
|
| Create (lazy) | `cbp-supabase-migrate` (Step 2.3) |
|
|
97
|
-
| Delete | `cbp-checkpoint-end`, `cbp-standalone-task-complete`, `
|
|
96
|
+
| Delete | `cbp-checkpoint-end`, `cbp-standalone-task-complete`, `codebyplan worktree remove`, `cbp-ship-main` |
|
|
98
97
|
| PR gate | `cbp-supabase-branch-check` |
|
|
99
98
|
|
|
100
99
|
Each skill in the Skill Map above carries an inline back-reference to this rule at its create or teardown step.
|
|
@@ -123,8 +123,6 @@
|
|
|
123
123
|
"Skill(cbp-frontend-ux)",
|
|
124
124
|
"Skill(cbp-git-branch-feat-create)",
|
|
125
125
|
"Skill(cbp-git-commit)",
|
|
126
|
-
"Skill(cbp-git-worktree-create)",
|
|
127
|
-
"Skill(cbp-git-worktree-remove)",
|
|
128
126
|
"Skill(cbp-map-architecture)",
|
|
129
127
|
"Skill(cbp-merge-main)",
|
|
130
128
|
"Skill(cbp-refresh-arch-map)",
|
|
@@ -32,7 +32,7 @@ Omit `model:` entirely (inherit the session model). Set `effort:` per the skill'
|
|
|
32
32
|
| `xhigh` | deep authoring / planning / orchestration (most skills) | build-cc-*, checkpoint-check/end/plan, round-start/input/execute, task-create/start/complete/testing, standalone-task-*, frontend-*, ship, ship-configure, supabase-*, setup-e2e/eslint, session-end |
|
|
33
33
|
| `high` | summary / orchestration, lighter than xhigh | checkpoint-create, checkpoint-start, checkpoint-update, round-end, task-check, standalone-task-check, ship-main, merge-main |
|
|
34
34
|
| `medium` | moderate, scoped sync work | refresh-infra |
|
|
35
|
-
| `low` | pure mechanical / dispatch / templated work | checkpoint-complete, round-check, round-complete, round-update, todo, session-start, git-commit, git-branch-feat-create
|
|
35
|
+
| `low` | pure mechanical / dispatch / templated work | checkpoint-complete, round-check, round-complete, round-update, todo, session-start, git-commit, git-branch-feat-create |
|
|
36
36
|
|
|
37
37
|
A skill that carries a `model:` line is a **gap** — remove it unless a deliberate, documented exception is recorded here (currently none).
|
|
38
38
|
|
|
@@ -157,5 +157,6 @@ Checks: name matches directory, frontmatter parses, no invalid fields, arg-hint
|
|
|
157
157
|
- Rendered `SKILL.md` content enters the conversation once when invoked and stays until compaction — write standing instructions, not one-time steps
|
|
158
158
|
- Preloaded subagent skills cannot have `disable-model-invocation: true`
|
|
159
159
|
- `context: fork` is only meaningful when the skill body contains an actionable task; otherwise the subagent receives guidelines with no prompt
|
|
160
|
+
- `context: fork` ALSO disqualifies fan-out / spawn-then-route / inline-by-design / consumed-inline skills — they run in the main context for a reason; see [reference/fork-eligibility.md](reference/fork-eligibility.md) for the eligibility test + the CBP classification of which skills may fork and which must stay inline
|
|
160
161
|
- Descriptions front-load key use case: `description` + `when_to_use` cap at 1,536 chars in the skill listing
|
|
161
162
|
- `paths:` scoping applies when Claude reads files matching the globs, not on every tool use
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# `context: fork` Eligibility — when to fork a skill, and when not to
|
|
2
|
+
|
|
3
|
+
`context: fork` runs the **entire SKILL.md body in a forked subagent** — it inherits the
|
|
4
|
+
parent conversation and, per the runtime, **runs in the background by default**. It is
|
|
5
|
+
isolation for a *whole skill*, not a way to delegate one sub-step. A forked body therefore
|
|
6
|
+
cannot drive the main pipeline: it can't `AskUserQuestion`, can't auto-trigger another
|
|
7
|
+
skill, and can't run an inline-fallback that the orchestrator depends on.
|
|
8
|
+
|
|
9
|
+
So forking only helps a narrow shape of skill. The canonical eligible example is
|
|
10
|
+
[examples/fork-skill.md](../examples/fork-skill.md): a single self-contained analytical task
|
|
11
|
+
that reads, reasons, and returns a summary — nothing else.
|
|
12
|
+
|
|
13
|
+
## Eligibility test
|
|
14
|
+
|
|
15
|
+
A skill is **fork-eligible** only when ALL hold:
|
|
16
|
+
|
|
17
|
+
1. Its whole body is **one** self-contained analytical/generative task (read → reason → return).
|
|
18
|
+
2. It is **non-interactive** — no `AskUserQuestion`, no user decision gates.
|
|
19
|
+
3. It does **not route** — no auto-trigger of another skill, no close-out directive that must
|
|
20
|
+
fire in the main context.
|
|
21
|
+
4. It does **not fan out** — it does not spawn multiple subagents and coordinate them.
|
|
22
|
+
5. It has **no inline-fallback** contract the orchestrator relies on.
|
|
23
|
+
|
|
24
|
+
Fail any one → the skill stays **inline** (main context). Inline skills still get clean
|
|
25
|
+
context isolation the right way: by delegating their heavy step to a dedicated **agent**
|
|
26
|
+
(e.g. `cbp-task-check`, `cbp-improve-round`, `cbp-round-executor`). The agent is the
|
|
27
|
+
isolation boundary; the skill stays in the main thread to orchestrate, route, and interact.
|
|
28
|
+
|
|
29
|
+
## When NOT to use `context: fork` (the disqualifying patterns)
|
|
30
|
+
|
|
31
|
+
| Pattern | Why it can't fork | Example skills |
|
|
32
|
+
|---------|-------------------|----------------|
|
|
33
|
+
| **fan-out** | spawns multiple agents in parallel and coordinates them | `cbp-round-execute`, `cbp-checkpoint-check`, `cbp-map-architecture`, `cbp-refresh-arch-map` |
|
|
34
|
+
| **spawn-then-route** | spawns one agent, then `AskUserQuestion` / auto-triggers the next skill / runs inline-fallback | `cbp-task-check`, `cbp-standalone-task-check`, `cbp-round-start`, `cbp-round-end`, `cbp-checkpoint-plan` |
|
|
35
|
+
| **inline-by-design** | interactive Q&A or stepwise writes that must stay in the main context | `cbp-task-create`, `cbp-task-complete`, `cbp-round-update`, `cbp-merge-main` |
|
|
36
|
+
| **consumed-inline** | invoked *by* an agent (e.g. round-executor) and applies fixes synchronously into that context | `cbp-frontend-design`, `cbp-frontend-ui`, `cbp-frontend-ux` |
|
|
37
|
+
| **doc-ref-only** | mentions subagents/fork only as documentation; runs inline authoring | the `cbp-build-cc-*` authoring skills, `cbp-supabase-migrate` |
|
|
38
|
+
|
|
39
|
+
## CHK-220 audit — classification matrix
|
|
40
|
+
|
|
41
|
+
Every skill whose `SKILL.md` touches the subagent/fork boundary — by spawning a subagent, by
|
|
42
|
+
being invoked inline by an agent, or by documenting the feature — was classified against the
|
|
43
|
+
eligibility test. **Result: 0 of 25 are fork-eligible** — none were migrated, because every
|
|
44
|
+
one either already isolates heavy work in a dedicated agent (the correct boundary) or depends
|
|
45
|
+
on inline orchestration/interaction that a background fork would break.
|
|
46
|
+
|
|
47
|
+
| Skill | Pattern | Fork-eligible |
|
|
48
|
+
|-------|---------|:---:|
|
|
49
|
+
| cbp-round-execute | fan-out | no |
|
|
50
|
+
| cbp-checkpoint-check | fan-out | no |
|
|
51
|
+
| cbp-map-architecture | fan-out | no |
|
|
52
|
+
| cbp-refresh-arch-map | fan-out | no |
|
|
53
|
+
| cbp-round-start | spawn-then-route | no |
|
|
54
|
+
| cbp-round-end | spawn-then-route | no |
|
|
55
|
+
| cbp-task-check | spawn-then-route | no |
|
|
56
|
+
| cbp-standalone-task-check | spawn-then-route | no |
|
|
57
|
+
| cbp-checkpoint-plan | spawn-then-route | no |
|
|
58
|
+
| cbp-round-update | inline-by-design | no |
|
|
59
|
+
| cbp-task-create | inline-by-design | no |
|
|
60
|
+
| cbp-standalone-task-create | inline-by-design | no |
|
|
61
|
+
| cbp-task-complete | inline-by-design | no |
|
|
62
|
+
| cbp-standalone-task-complete | inline-by-design | no |
|
|
63
|
+
| cbp-merge-main | inline-by-design | no |
|
|
64
|
+
| cbp-task-testing | inline-by-design | no |
|
|
65
|
+
| cbp-standalone-task-testing | inline-by-design | no |
|
|
66
|
+
| cbp-frontend-design | consumed-inline | no |
|
|
67
|
+
| cbp-frontend-ui | consumed-inline | no |
|
|
68
|
+
| cbp-frontend-ux | consumed-inline | no |
|
|
69
|
+
| cbp-supabase-migrate | doc-ref-only | no |
|
|
70
|
+
| cbp-build-cc-skill | doc-ref-only | no |
|
|
71
|
+
| cbp-build-cc-agent | doc-ref-only | no |
|
|
72
|
+
| cbp-build-cc-settings | doc-ref-only | no |
|
|
73
|
+
| cbp-build-cc-mode | doc-ref-only | no |
|
|
74
|
+
|
|
75
|
+
If a **new** skill is authored that passes the eligibility test above, fork it: add
|
|
76
|
+
`context: fork` + `agent: <Explore|Plan|general-purpose|custom>` (use `--fork` in
|
|
77
|
+
`/cbp-build-cc-skill`). Do not retrofit fork onto any skill in the table — their inline
|
|
78
|
+
shape is load-bearing.
|
|
@@ -20,6 +20,10 @@ Source: official Claude Code skills spec. All fields are optional; `description`
|
|
|
20
20
|
| `paths` | Globs that auto-load the skill when matching files are in play |
|
|
21
21
|
| `shell` | `bash` (default) or `powershell` for `!`-commands |
|
|
22
22
|
|
|
23
|
+
> `context: fork` / `agent` are right for only a narrow shape of skill — a single
|
|
24
|
+
> non-interactive analytical body. See [fork-eligibility.md](fork-eligibility.md) for the
|
|
25
|
+
> eligibility test and the CBP classification of which skills may fork (and which must stay inline).
|
|
26
|
+
|
|
23
27
|
## Character budget
|
|
24
28
|
|
|
25
29
|
Combined `description` + `when_to_use` truncates at **1,536 characters** in the skill listing. Front-load the key use case.
|
|
@@ -113,6 +113,7 @@ Persist the branch via `codebyplan checkpoint update --id <checkpoint-id> --bran
|
|
|
113
113
|
|
|
114
114
|
**CHK-NNN**: [title] • **Deadline**: [date] • **Branch**: feat/CHK-NNN-slug
|
|
115
115
|
**Claim**: [claimed by this worktree / left open]
|
|
116
|
+
**Worktree**: `npx codebyplan worktree add CHK-{NNN}`
|
|
116
117
|
|
|
117
118
|
Now planning CHK-NNN… handing off to /cbp-checkpoint-plan.
|
|
118
119
|
```
|
|
@@ -74,23 +74,9 @@ Same rule as `/cbp-session-start` Step 5.7 — only commit files that are **not*
|
|
|
74
74
|
|
|
75
75
|
Non-blocking — session end proceeds either way.
|
|
76
76
|
|
|
77
|
-
### Step 1.6: Home-Branch Fast-Forward
|
|
78
|
-
|
|
79
|
-
Keep this worktree's **home branch** rooted at the freshest production tip — no prompt, fully non-blocking. Home branches are the folder-named placeholder branches each worktree rests on (e.g. `codebyplan-web`); `feat/*` branches are deliberately skipped — they integrate via `/cbp-merge-main` with QA, never an auto fast-forward.
|
|
80
|
-
|
|
81
|
-
Runs after Step 1.5 so any infra commits land first, and before Step 1.7 so the freshness gate's asset update operates on the freshest tree.
|
|
82
|
-
|
|
83
|
-
Run `codebyplan session home-ff` and parse the JSON output (`{ result: 'skipped'|'warn'|'fast_forwarded', reason?, warn? }`):
|
|
84
|
-
|
|
85
|
-
- **`result === 'skipped'`** (non-home branch or production mismatch): proceed silently.
|
|
86
|
-
- **`result === 'warn'`** (fast-forward attempt failed): surface the `warn` field as one line, then proceed silently.
|
|
87
|
-
- **`result === 'fast_forwarded'`** (home branch updated): proceed silently.
|
|
88
|
-
|
|
89
|
-
Never rebase, reset, force-push, or stash. A non-fast-forwardable home branch is a signal to reconcile manually, not to overwrite.
|
|
90
|
-
|
|
91
77
|
### Step 1.7: Package Freshness Gate
|
|
92
78
|
|
|
93
|
-
Check whether a newer `codebyplan` is published and safe to auto-install on this worktree's current branch, then run the local install + asset update. Runs after the Step 1.
|
|
79
|
+
Check whether a newer `codebyplan` is published and safe to auto-install on this worktree's current branch, then run the local install + asset update. Runs after the Step 1.5 non-task-file commit and before Step 2. Fully non-blocking — the guard and skip branches proceed silently; any failure in the install/update commands warns once and continues to Step 2. session-end never halts.
|
|
94
80
|
|
|
95
81
|
Run `codebyplan session freshness-gate` (WITHOUT `--halt-on-update`; session-end is continue-only) and parse the JSON output (`{ result: 'skipped'|'guarded'|'up_to_date'|'updated'|'error', ... }`):
|
|
96
82
|
|
|
@@ -110,8 +96,6 @@ Run `codebyplan session freshness-gate` (WITHOUT `--halt-on-update`; session-end
|
|
|
110
96
|
On `yes`: `git add` the listed paths only (not the whole directories), then trigger `/cbp-git-commit`. On `no`: skip. On `select`: ask which subset.
|
|
111
97
|
- Continue to Step 2 — session-end does NOT halt. The update lands in the current worktree on its current branch; main/protected branches and the canonical source repo are already excluded by the `result !== 'guarded'` check above.
|
|
112
98
|
|
|
113
|
-
**Home branches are intentionally not guarded.** A worktree's folder-named home branch (e.g. `codebyplan-web`) is neither protected/main nor the canonical source, so the freshness gate returns `result !== 'guarded'` there and this gate runs the update exactly as on a feat branch — landing it on whichever branch the worktree currently rests on (the deliberate "update the branch they're on, except main/canonical" behavior).
|
|
114
|
-
|
|
115
99
|
Non-blocking — session end proceeds regardless of outcome.
|
|
116
100
|
|
|
117
101
|
### Step 2: Auto-Stop Servers
|
|
@@ -149,7 +133,7 @@ You can close this window.
|
|
|
149
133
|
## Integration
|
|
150
134
|
|
|
151
135
|
- **Triggered by**: user invocation (prompted by `/cbp-todo` when no work remains)
|
|
152
|
-
- **Reads**: `.codebyplan/repo.json
|
|
136
|
+
- **Reads**: `.codebyplan/repo.json`; local-first reads (with `npx codebyplan sync` + MCP break-glass): `.codebyplan/state/session/current.json` (Step 1 resolve log), `.codebyplan/state/todos.json` (Step 1.3 handoff snapshot + Step 1.5 active-task lookup), `.codebyplan/state/checkpoints/<id>/tasks/<id>/rounds/` (Step 1.5 task-file resolution); `codebyplan session freshness-gate` (Step 1.7 package-freshness gate, without --halt-on-update); `codebyplan session infra-files --json --task-files <csv>` (Step 1.5 infra-file set math)
|
|
153
137
|
- **Writes**: `codebyplan session update-log --id <id> ...` (Step 1 finalize — CLI write-through to `.codebyplan/state/session/current.json`; break-glass: MCP `update_session_log`), `codebyplan session create-log` (Step 1 fallback when no log exists; break-glass: MCP `create_session_log`), `codebyplan session update-state --action deactivate` (Step 3 — CLI write-through to `.codebyplan/state/session/state.json`; break-glass: MCP `update_session_state`)
|
|
154
138
|
- **Spawns**: none
|
|
155
139
|
- **Triggers**: none at the skill-contract level. Step 1.5 may invoke `/cbp-git-commit` inline on user approval; Step 1.7 may invoke `/cbp-git-commit` on the `result === 'updated'` path (committing changed `.claude/` and `.codebyplan/` paths).
|
|
@@ -11,7 +11,7 @@ Activate the session, open a fresh session log, and surface the previous log's p
|
|
|
11
11
|
|
|
12
12
|
## Instructions
|
|
13
13
|
|
|
14
|
-
Run Steps 0 through 5.8 silently (no intermediate output) — except Step 0 aborts the session on MCP failure, Step 1.
|
|
14
|
+
Run Steps 0 through 5.8 silently (no intermediate output) — except Step 0 aborts the session on MCP failure, Step 1.5 may surface a one-line infra-drift nudge, Step 1.55 may surface a one-line architecture-map drift nudge, Step 1.6 may run an install-and-halt path, Step 1.7 may surface a one-line LSP binary nudge, Step 4.5 may auto-resume a handoff and exit session-start entirely (no Step 6 output), and Step 5.7 may surface an approval gate. (Step numbers are organizational labels; execution order is 0 → 1 → 1.5 → 1.55 → 1.6 → 1.7 → 3 → 4 → 4.5 → 5 → 5.7 → 5.8 → 6 → 7.) Produce ONE output block at Step 6, then auto-trigger or stop per Step 7.
|
|
15
15
|
|
|
16
16
|
### Step 0: MCP Health Gate
|
|
17
17
|
|
|
@@ -50,23 +50,11 @@ Extract `worktree_id` and `error_kind` from the JSON output.
|
|
|
50
50
|
|
|
51
51
|
Pass `WORKTREE_ID` to MCP tools that support it. Null `WORKTREE_ID` means the (device, path, branch) tuple is unregistered — note this for Step 6.
|
|
52
52
|
|
|
53
|
-
### Step 1.4: Home-Branch Fast-Forward
|
|
54
|
-
|
|
55
|
-
Keep this worktree's **home branch** rooted at the freshest production tip — no prompt, fully non-blocking. Home branches are the folder-named placeholder branches each worktree rests on (e.g. `codebyplan-web`); `feat/*` branches are deliberately skipped — they integrate via `/cbp-merge-main` with QA, never an auto fast-forward.
|
|
56
|
-
|
|
57
|
-
Run `codebyplan session home-ff` and parse the JSON output (`{ result: 'skipped'|'warn'|'fast_forwarded', reason?, warn? }`):
|
|
58
|
-
|
|
59
|
-
- **`result === 'skipped'`** (non-home branch or production mismatch): proceed silently.
|
|
60
|
-
- **`result === 'warn'`** (fast-forward attempt failed): surface the `warn` field as one line, then proceed silently.
|
|
61
|
-
- **`result === 'fast_forwarded'`** (home branch updated): proceed silently.
|
|
62
|
-
|
|
63
|
-
Never rebase, reset, force-push, or stash. A non-fast-forwardable home branch is a signal to reconcile manually, not to overwrite.
|
|
64
|
-
|
|
65
53
|
### Step 1.5: Infra Drift Check
|
|
66
54
|
|
|
67
|
-
Surface — never block — when this worktree's source-monorepo `.claude/` infra has fallen behind. Runs after Step 1
|
|
55
|
+
Surface — never block — when this worktree's source-monorepo `.claude/` infra has fallen behind. Runs after Step 1 and may add one line to the Step 6 output (`$PRODUCTION` is `branch_config.production` read in Step 1). Consumer-repo package-version freshness is handled by Step 1.6 (the freshness gate), not here:
|
|
68
56
|
|
|
69
|
-
- **Monorepo (concept A)** — both `packages/codebyplan-package/templates/` and `scripts/infra-drift.mjs` exist.
|
|
57
|
+
- **Monorepo (concept A)** — both `packages/codebyplan-package/templates/` and `scripts/infra-drift.mjs` exist. Refresh `origin/$PRODUCTION` best-effort first, then run the reporter:
|
|
70
58
|
|
|
71
59
|
```bash
|
|
72
60
|
git fetch origin "$PRODUCTION" 2>/dev/null || true
|
|
@@ -136,8 +124,6 @@ Run `codebyplan session freshness-gate --halt-on-update` and parse the JSON outp
|
|
|
136
124
|
|
|
137
125
|
On this update-and-halt path the session is NOT continued: `update_session_state(activate)` is NOT called, `create_session_log` is NOT called, and `/cbp-todo` is NOT triggered.
|
|
138
126
|
|
|
139
|
-
**Home branches are intentionally not guarded.** A worktree's folder-named home branch (e.g. `codebyplan-web`) is neither protected/main nor the canonical source, so the freshness gate returns `result !== 'guarded'` there and this gate fires exactly as on a feat branch. That is deliberate — the update lands on whatever branch the worktree is currently resting on (the "update the branch they're on, except main/canonical" decision), not an accidental halt.
|
|
140
|
-
|
|
141
127
|
Populate the claude-status cache best-effort (pure cache population — never gates session-start):
|
|
142
128
|
|
|
143
129
|
```bash
|
|
@@ -271,7 +257,7 @@ Three-branch gate using `owned_count` and `total_count` from Step 5.8:
|
|
|
271
257
|
|
|
272
258
|
- **Triggered by**: user invocation, `/clear` recovery
|
|
273
259
|
- **Resolves**: `npx codebyplan resolve-worktree --json` (worktree id + distress signal; non-tuple-miss distress is non-blocking at session-start)
|
|
274
|
-
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for
|
|
260
|
+
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for Step 1.5 infra-drift fetch), MCP `health_check` (Step 0 hard gate — stays MCP unconditionally); local-first reads (with `npx codebyplan sync` + MCP break-glass): `.codebyplan/state/session/current.json` (Step 4 previous log + Step 4.5 handoff probe), `.codebyplan/state/checkpoints/<id>.json` / `tasks/<id>.json` / `rounds/<id>.json` (Step 4.5 freshness probe), `.codebyplan/state/todos.json` (Step 5.7 active-task lookup); MCP `get_checkpoints({ repo_id, status: 'active' })` (Step 5.8 ownership partition — MCP only, no local mirror for active-filter query); `scripts/infra-drift.mjs` + a best-effort `git fetch` (Step 1.5 monorepo drift); `npx codebyplan arch-map drift` + `.codebyplan/architecture.json` presence (Step 1.55 architecture-map drift nudge, non-blocking); `codebyplan session freshness-gate --halt-on-update` (Step 1.6 package-freshness gate); `codebyplan session infra-files --json --task-files <csv>` (Step 5.7 infra-file set math); `npx codebyplan lsp --check` (Step 1.7 LSP binary nudge — reads `.codebyplan/lsp.json`, non-blocking). Reads at Step 3 and later do NOT fire on a Step 0 MCP hard-fail or the Step 1.6 update-and-halt path
|
|
275
261
|
- **Writes**: `codebyplan session create-log` (Step 5 — CLI write-through; break-glass: MCP `create_session_log`), `codebyplan session update-state --action activate` (Step 3 — CLI write-through to `.codebyplan/state/session/state.json`; break-glass: MCP `update_session_state`) — both SKIPPED on a Step 0 MCP hard-fail and on the Step 1.6 update-and-halt path
|
|
276
262
|
- **Spawns**: none
|
|
277
263
|
- **Triggers**: `/cbp-git-commit` (conditional, on user approval at Step 5.7 or the Step 1.6 update path), `handoff.command` (on fresh handoff hit at Step 4.5), `/cbp-todo` (auto fall-through when owned_count >= 1 or total_count === 0; STOPS with no trigger when total_count >= 1 AND owned_count === 0; NOT triggered on a Step 0 hard-fail or the Step 1.6 update-and-halt path)
|
|
@@ -170,7 +170,7 @@ Stop.
|
|
|
170
170
|
|
|
171
171
|
### Record connection
|
|
172
172
|
|
|
173
|
-
Record the branch so cleanup (see `cbp-checkpoint-end`, `
|
|
173
|
+
Record the branch so cleanup (see `cbp-checkpoint-end`, `codebyplan worktree remove CHK-NNN`) and other
|
|
174
174
|
skills can discover it. Phrase any context payload as prose — the MCP edge rejects raw
|
|
175
175
|
uppercase database keywords (Cloudflare WAF).
|
|
176
176
|
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cbp-git-worktree-create
|
|
3
|
-
description: Create git worktree with clean command setup and register in CodeByPlan
|
|
4
|
-
argument-hint: <branch-name> e.g. codebyplan-app
|
|
5
|
-
effort: low
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Git Worktree Create
|
|
9
|
-
|
|
10
|
-
Create a git worktree as a sibling folder and register it in CodeByPlan. The worktree gets its own full copy of `.claude/` files on disk (rules, skills, hooks, agents, context) — git deduplicates at the object level so there's no real cost.
|
|
11
|
-
|
|
12
|
-
## Arguments
|
|
13
|
-
|
|
14
|
-
`$ARGUMENTS`: branch name — becomes both the branch name and the folder name.
|
|
15
|
-
|
|
16
|
-
Examples:
|
|
17
|
-
- `codebyplan-app` → folder `../codebyplan-app/`, branch `codebyplan-app`
|
|
18
|
-
- `codebyplan-package` → folder `../codebyplan-package/`, branch `codebyplan-package`
|
|
19
|
-
|
|
20
|
-
## Prerequisites
|
|
21
|
-
|
|
22
|
-
- Must run from the **main repo** (the non-worktree checkout)
|
|
23
|
-
- Working tree should be clean
|
|
24
|
-
- Branch should not already exist as a worktree
|
|
25
|
-
- New branches are always rooted at `origin/{production}` (read from `.codebyplan/git.json`), independent of the main repo's currently checked-out branch
|
|
26
|
-
|
|
27
|
-
## Instructions
|
|
28
|
-
|
|
29
|
-
### Step 1: Verify Main Repo
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
MAIN_REPO="$(git rev-parse --show-toplevel)"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Verify this is the main repo (not itself a worktree):
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
git rev-parse --git-dir
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If the result is a file (not `.git` directory), this is already a worktree. Stop:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
## Error
|
|
45
|
-
|
|
46
|
-
Run this command from the main repo, not from a worktree.
|
|
47
|
-
Main repo: [path from git-common-dir parent]
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Step 2: Validate Arguments
|
|
51
|
-
|
|
52
|
-
If `$ARGUMENTS` is empty, ask the user:
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
What should the worktree be called? (e.g. codebyplan-app)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Set:
|
|
59
|
-
- `BRANCH_NAME` = `$ARGUMENTS`
|
|
60
|
-
- `WORKTREE_PATH` = `$MAIN_REPO/../$BRANCH_NAME`
|
|
61
|
-
|
|
62
|
-
### Step 3: Check for Conflicts
|
|
63
|
-
|
|
64
|
-
Check if the worktree already exists:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
git worktree list | grep "$BRANCH_NAME"
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
If it exists:
|
|
71
|
-
```
|
|
72
|
-
## Worktree Already Exists
|
|
73
|
-
|
|
74
|
-
Branch `[name]` is already checked out at [path].
|
|
75
|
-
|
|
76
|
-
Use `/cbp-git-worktree-remove [name]` to remove it first.
|
|
77
|
-
```
|
|
78
|
-
Stop here.
|
|
79
|
-
|
|
80
|
-
Check if the folder already exists:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
ls -d "$WORKTREE_PATH" 2>/dev/null
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
If it exists, stop and inform the user.
|
|
87
|
-
|
|
88
|
-
### Step 4: Create Branch If Needed
|
|
89
|
-
|
|
90
|
-
Resolve the production branch: read `.codebyplan/git.json` and take `branch_config.production` (fall back to `main` if the file or field is absent). Call this `$PRODUCTION`.
|
|
91
|
-
|
|
92
|
-
Check if branch exists:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
git branch --list "$BRANCH_NAME"
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
If branch does NOT exist, create it from the freshest production tip — **not** from the main repo's ambient HEAD (which is often a stale home branch such as `codebyplan-main`):
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
git fetch origin "$PRODUCTION" 2>/dev/null || true
|
|
102
|
-
git branch "$BRANCH_NAME" "origin/$PRODUCTION"
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
This guarantees every worktree starts at `origin/{production}` regardless of which branch the main repo currently has checked out.
|
|
106
|
-
|
|
107
|
-
### Step 5: Create Worktree
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
git worktree add "$WORKTREE_PATH" "$BRANCH_NAME"
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Step 6: Set Up MCP Connection
|
|
114
|
-
|
|
115
|
-
Copy `.mcp.json` from the main repo to the worktree. The file is committed and contains only the public MCP URL (no secret), so this is a plain `cp` — no path rewriting or key substitution needed:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
cp "$MAIN_REPO/.mcp.json" "$WORKTREE_PATH/.mcp.json"
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Expected shape (do NOT rewrite paths — this is remote HTTP):
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{
|
|
125
|
-
"mcpServers": {
|
|
126
|
-
"codebyplan": {
|
|
127
|
-
"type": "http",
|
|
128
|
-
"url": "https://mcp.codebyplan.com/mcp"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Step 7: Set Up Environment
|
|
135
|
-
|
|
136
|
-
Copy `.env.local` from the main repo to the worktree (contains the app's environment variables such as Supabase keys and feature flags — MCP auth is OAuth Bearer handled by Claude Code, not a key in this file):
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
cp "$MAIN_REPO/.env.local" "$WORKTREE_PATH/.env.local"
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Verify `.env.local` is already in `.gitignore` (it should be via `.env.local` pattern). If not, add it.
|
|
143
|
-
|
|
144
|
-
Also copy the gitignored E2E credentials source (`.codebyplan/e2e.env`, referenced by `.codebyplan/e2e.json`) so the new worktree can run Playwright auth flows immediately:
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
mkdir -p "$WORKTREE_PATH/.codebyplan"
|
|
148
|
-
if [ -f "$MAIN_REPO/.codebyplan/e2e.env" ]; then
|
|
149
|
-
cp "$MAIN_REPO/.codebyplan/e2e.env" "$WORKTREE_PATH/.codebyplan/e2e.env"
|
|
150
|
-
fi
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
If the main repo has no `.codebyplan/e2e.env` yet, provision it after setup by running `codebyplan ports --path "$WORKTREE_PATH" --provision-e2e` (copies the canonical E2E vars from `apps/web/.env.local`). Pass `--path` BEFORE the boolean flag. `.codebyplan/e2e.env` is gitignored — never commit it.
|
|
154
|
-
|
|
155
|
-
### Step 8: Push Branch
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
cd "$WORKTREE_PATH" && git push -u origin "$BRANCH_NAME"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Step 9: Register Worktree and Write `.codebyplan/` Config
|
|
162
|
-
|
|
163
|
-
Run `codebyplan worktree create "$BRANCH_NAME" --path "$WORKTREE_PATH"` and parse the JSON output (`{ worktree_files_written: boolean, mcp_registered: boolean, worktree_id?, warn? }`):
|
|
164
|
-
|
|
165
|
-
- If `warn` is present: surface it as a non-blocking warning.
|
|
166
|
-
- Save the returned `worktree_id` for reference (if present).
|
|
167
|
-
|
|
168
|
-
The CLI atomically writes the `.codebyplan/` directory with per-concern config stubs and registers the worktree in the CodeByPlan database. The `.codebyplan/device.local.json` file is created by `npx codebyplan setup` on the device (gitignored). The `worktree_id` is never COMMITTED; it may be cached per-device in the gitignored `.codebyplan/worktree.local.json` (branch-keyed, re-derivable via `codebyplan resolve-worktree --cache`), otherwise resolved at runtime from the `(device_id, repo path, branch)` tuple via `npx codebyplan resolve-worktree`.
|
|
169
|
-
|
|
170
|
-
No need to mark as `skip-worktree` — the committed files are merge-safe per CHK-108 and CHK-120.
|
|
171
|
-
|
|
172
|
-
### Step 10: Show Result
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
## Worktree Created
|
|
176
|
-
|
|
177
|
-
**Branch**: [branch-name]
|
|
178
|
-
**Path**: [worktree-path]
|
|
179
|
-
**Main repo**: [main-repo-path]
|
|
180
|
-
**Base**: origin/[production] ([sha])
|
|
181
|
-
**CodeByPlan**: Registered (worktree ID: [id])
|
|
182
|
-
|
|
183
|
-
### Setup
|
|
184
|
-
- MCP: connected (remote endpoint, OAuth Bearer)
|
|
185
|
-
- `.claude/` files: full copy on disk (rules, skills, hooks, agents, context)
|
|
186
|
-
|
|
187
|
-
### Next Steps
|
|
188
|
-
- Open `[worktree-path]` in your editor
|
|
189
|
-
- Run `/cbp-session-start` to begin working
|
|
190
|
-
- Checkpoints can be assigned to this worktree via `worktree_id`
|
|
191
|
-
|
|
192
|
-
### Existing Worktrees
|
|
193
|
-
[output of git worktree list]
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Integration
|
|
197
|
-
|
|
198
|
-
- **Related**: `/cbp-git-worktree-remove` (cleanup and deregister)
|
|
199
|
-
- **CLI**: `codebyplan worktree create <name> --path <abs>` (Step 9 — writes `.codebyplan/` config and registers worktree)
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cbp-git-worktree-remove
|
|
3
|
-
description: Remove git worktree and deregister from CodeByPlan
|
|
4
|
-
argument-hint: <name> e.g. codebyplan-app
|
|
5
|
-
effort: low
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Git Worktree Remove
|
|
9
|
-
|
|
10
|
-
Remove a git worktree folder, deregister it from CodeByPlan, and optionally delete its branch.
|
|
11
|
-
|
|
12
|
-
## Arguments
|
|
13
|
-
|
|
14
|
-
`$ARGUMENTS`: worktree name or path to remove.
|
|
15
|
-
|
|
16
|
-
## Prerequisites
|
|
17
|
-
|
|
18
|
-
- Must run from the **main repo** (not from the worktree being removed)
|
|
19
|
-
|
|
20
|
-
## Instructions
|
|
21
|
-
|
|
22
|
-
### Step 1: Verify Main Repo
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
MAIN_REPO="$(git rev-parse --show-toplevel)"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Verify not in a worktree (`.git` should be a directory, not a file).
|
|
29
|
-
|
|
30
|
-
### Step 2: List Worktrees
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
git worktree list
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
If `$ARGUMENTS` is empty, show the list and ask which to remove.
|
|
37
|
-
|
|
38
|
-
### Step 3: Resolve Worktree
|
|
39
|
-
|
|
40
|
-
Find matching worktree from the list. Match by:
|
|
41
|
-
1. Exact path
|
|
42
|
-
2. Folder name (e.g. `codebyplan-app`)
|
|
43
|
-
3. Branch name
|
|
44
|
-
|
|
45
|
-
If no match found, show available worktrees and stop.
|
|
46
|
-
|
|
47
|
-
Set:
|
|
48
|
-
- `WORKTREE_PATH` = resolved path
|
|
49
|
-
- `BRANCH_NAME` = branch checked out in that worktree
|
|
50
|
-
|
|
51
|
-
### Step 4: Look Up and Deregister Worktree in CodeByPlan
|
|
52
|
-
|
|
53
|
-
Run `codebyplan worktree remove "$BRANCH_NAME"` and parse the JSON output (`{ mcp_deregistered: boolean, warn? }`):
|
|
54
|
-
|
|
55
|
-
- If `warn` is present: surface it as a non-blocking warning. The worktree was not found in CodeByPlan or deregistration failed, but local removal will proceed.
|
|
56
|
-
- If `mcp_deregistered === true`: worktree was successfully deregistered.
|
|
57
|
-
- If `mcp_deregistered === false`: worktree was not registered or deregistration failed; continue with local removal.
|
|
58
|
-
|
|
59
|
-
### Step 5: Check for Assigned Checkpoints
|
|
60
|
-
|
|
61
|
-
If the worktree was successfully deregistered in Step 4 (`mcp_deregistered === true`), any checkpoints that were assigned to it will become unassigned. This is expected behavior and requires no additional action.
|
|
62
|
-
|
|
63
|
-
### Step 6: Confirm with User
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
## Remove Worktree
|
|
67
|
-
|
|
68
|
-
**Path**: [worktree-path]
|
|
69
|
-
**Branch**: [branch-name]
|
|
70
|
-
**CodeByPlan**: [registered | not registered]
|
|
71
|
-
|
|
72
|
-
Remove worktree and delete the branch? (The main repo is not affected)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Ask:
|
|
76
|
-
1. Remove worktree only (keep branch)
|
|
77
|
-
2. Remove worktree and delete branch
|
|
78
|
-
3. Cancel
|
|
79
|
-
|
|
80
|
-
### Step 7: Remove Git Worktree
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
git worktree remove "$WORKTREE_PATH"
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
If force needed (uncommitted changes):
|
|
87
|
-
```bash
|
|
88
|
-
git worktree remove --force "$WORKTREE_PATH"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Only use `--force` if the user confirms.
|
|
92
|
-
|
|
93
|
-
### Step 8: Delete Branch (if requested)
|
|
94
|
-
|
|
95
|
-
**Protected branch check:** Read the protected set from `.codebyplan/git.json`:
|
|
96
|
-
```bash
|
|
97
|
-
PRODUCTION=$(jq -r '.branch_config.production // "main"' .codebyplan/git.json)
|
|
98
|
-
PROTECTED=$(jq -r '.branch_config.protected[]? // empty' .codebyplan/git.json)
|
|
99
|
-
```
|
|
100
|
-
If `$BRANCH_NAME` equals `$PRODUCTION` or appears in `$PROTECTED` — refuse deletion and stop.
|
|
101
|
-
|
|
102
|
-
**Checkpoint verification:** Before deleting a feat branch, verify that the associated checkpoint has completed via `/cbp-checkpoint-end`. If the checkpoint is still active, warn the user that unshipped work may be lost.
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
git branch -d "$BRANCH_NAME" && git push origin --delete "$BRANCH_NAME"
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Use `-d` (not `-D`) to prevent deleting unmerged work. If it fails because the branch is not fully merged, inform the user and ask if they want to force delete with `-D`.
|
|
109
|
-
|
|
110
|
-
After the git branch delete succeeds, run a conditional Supabase preview-branch teardown for `$BRANCH_NAME`:
|
|
111
|
-
|
|
112
|
-
> Lifecycle contract: see [[supabase-branch-lifecycle]].
|
|
113
|
-
|
|
114
|
-
- Resolve the parent project ref and apply the lifecycle guard in one deterministic call:
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
codebyplan supabase teardown-preview "$BRANCH_NAME"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Parse its JSON `{ status, parent_ref, project_ref, reason }`. The command never deletes anything — it reads the parent ref from `.codebyplan/shipment.json` (`.shipment.surfaces.supabase.project_ref`) and applies the protected / production / parent-ref guard from [[supabase-branch-lifecycle]].
|
|
121
|
-
- If `status === "rejected"`: STOP the teardown and surface `reason` — never delete a production / protected / integration branch or one whose preview ref equals the parent.
|
|
122
|
-
- Otherwise (`allowed` or `not_found`), use `parent_ref` for the live existence check — `mcp__supabase__list_branches` with `project_id: <parent_ref>`, then scan for an entry whose `name` exactly equals `$BRANCH_NAME`:
|
|
123
|
-
- If found: call `mcp__supabase__delete_branch` with its `branch_id`. Report "Supabase preview branch deleted: `$BRANCH_NAME`".
|
|
124
|
-
- If not found: no-op silently — the GitHub integration may have already removed it on PR close; not-found is success, NOT an error.
|
|
125
|
-
- If the `list_branches` call itself fails (network, auth, or a non-success response — distinct from a successful lookup that returns no match): emit a non-blocking warning that the Supabase preview branch for `$BRANCH_NAME` may still exist and should be verified in the dashboard. Do not treat an API failure as a not-found success.
|
|
126
|
-
- Never delete the parent project (`parent_ref` from `codebyplan supabase teardown-preview`) itself or any persistent/production branch — the `teardown-preview` guard enforces this.
|
|
127
|
-
|
|
128
|
-
### Step 9: Show Result
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
## Worktree Removed
|
|
132
|
-
|
|
133
|
-
**Removed**: [worktree-path]
|
|
134
|
-
**Branch**: [deleted | kept]
|
|
135
|
-
**CodeByPlan**: [deregistered | was not registered]
|
|
136
|
-
|
|
137
|
-
### Remaining Worktrees
|
|
138
|
-
[output of git worktree list]
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Integration
|
|
142
|
-
|
|
143
|
-
- **Related**: `/cbp-git-worktree-create` (create and register)
|
|
144
|
-
- **CLI**: `codebyplan worktree remove <name>` (Step 4 — deregister from CodeByPlan)
|