codebyplan 1.13.11 → 1.13.13
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 +328 -2
- package/package.json +2 -2
- package/templates/agents/cbp-research.md +2 -0
- package/templates/agents/cbp-round-executor.md +2 -0
- package/templates/agents/cbp-task-planner.md +4 -1
- package/templates/hooks/cbp-canonical-templates-nudge.sh +68 -0
- package/templates/rules/agent-claim-verification.md +50 -0
- package/templates/rules/context-file-loading.md +1 -0
- package/templates/rules/parallel-waves.md +59 -0
- package/templates/settings.project.base.json +8 -0
- package/templates/skills/cbp-round-update/SKILL.md +21 -0
- package/templates/skills/cbp-task-start/SKILL.md +19 -1
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.13.
|
|
17
|
+
VERSION = "1.13.13";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -1281,7 +1281,11 @@ var init_template_walker = __esm({
|
|
|
1281
1281
|
"hooks/validate-structure-lengths.sh",
|
|
1282
1282
|
"hooks/validate-structure-smoke.sh",
|
|
1283
1283
|
"hooks/validate-context-usage.sh",
|
|
1284
|
-
"hooks/validate-git-commit.sh"
|
|
1284
|
+
"hooks/validate-git-commit.sh",
|
|
1285
|
+
// Advisory GATE-6 sibling-identity nudge — fires ONLY in the templates source
|
|
1286
|
+
// repo (detects packages/codebyplan-package/templates/). A no-op for consumers,
|
|
1287
|
+
// so it ships neither in hooks.json nor as a copied file.
|
|
1288
|
+
"hooks/cbp-canonical-templates-nudge.sh"
|
|
1285
1289
|
]);
|
|
1286
1290
|
}
|
|
1287
1291
|
});
|
|
@@ -5897,6 +5901,310 @@ var init_resolve_worktree2 = __esm({
|
|
|
5897
5901
|
}
|
|
5898
5902
|
});
|
|
5899
5903
|
|
|
5904
|
+
// src/cli/create-org.ts
|
|
5905
|
+
var create_org_exports = {};
|
|
5906
|
+
__export(create_org_exports, {
|
|
5907
|
+
runCreateOrg: () => runCreateOrg
|
|
5908
|
+
});
|
|
5909
|
+
import { stdin as stdin3, stdout as stdout4 } from "node:process";
|
|
5910
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
5911
|
+
function deriveSlug(name) {
|
|
5912
|
+
const trimmed = name.trim();
|
|
5913
|
+
if (trimmed === "") return "";
|
|
5914
|
+
const slug = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5915
|
+
return slug || "org";
|
|
5916
|
+
}
|
|
5917
|
+
async function runCreateOrg() {
|
|
5918
|
+
const rl = createInterface3({ input: stdin3, output: stdout4 });
|
|
5919
|
+
console.log("\n CodeByPlan \u2014 Create Organization\n");
|
|
5920
|
+
try {
|
|
5921
|
+
const rawName = (await rl.question(" Organization name: ")).trim();
|
|
5922
|
+
if (!rawName) {
|
|
5923
|
+
console.error("\n Error: Organization name cannot be empty.\n");
|
|
5924
|
+
process.exitCode = 1;
|
|
5925
|
+
return;
|
|
5926
|
+
}
|
|
5927
|
+
const slug = deriveSlug(rawName);
|
|
5928
|
+
if (slug) {
|
|
5929
|
+
console.log(` Slug preview: ${slug}
|
|
5930
|
+
`);
|
|
5931
|
+
}
|
|
5932
|
+
let response;
|
|
5933
|
+
try {
|
|
5934
|
+
response = await apiPost("/organizations", {
|
|
5935
|
+
name: rawName
|
|
5936
|
+
});
|
|
5937
|
+
} catch (err) {
|
|
5938
|
+
if (err instanceof ApiError) {
|
|
5939
|
+
console.error(`
|
|
5940
|
+
Error: ${err.message} (HTTP ${err.status})
|
|
5941
|
+
`);
|
|
5942
|
+
} else {
|
|
5943
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5944
|
+
console.error(`
|
|
5945
|
+
Error: ${msg}
|
|
5946
|
+
`);
|
|
5947
|
+
}
|
|
5948
|
+
process.exitCode = 1;
|
|
5949
|
+
return;
|
|
5950
|
+
}
|
|
5951
|
+
const org = response.data;
|
|
5952
|
+
console.log("\n Organization created successfully.\n");
|
|
5953
|
+
console.log(` id: ${org.id}`);
|
|
5954
|
+
console.log(` name: ${org.name}`);
|
|
5955
|
+
console.log(` slug: ${org.slug}
|
|
5956
|
+
`);
|
|
5957
|
+
} finally {
|
|
5958
|
+
rl.close();
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
var init_create_org = __esm({
|
|
5962
|
+
"src/cli/create-org.ts"() {
|
|
5963
|
+
"use strict";
|
|
5964
|
+
init_api();
|
|
5965
|
+
}
|
|
5966
|
+
});
|
|
5967
|
+
|
|
5968
|
+
// src/cli/create-project.ts
|
|
5969
|
+
var create_project_exports = {};
|
|
5970
|
+
__export(create_project_exports, {
|
|
5971
|
+
runCreateProject: () => runCreateProject
|
|
5972
|
+
});
|
|
5973
|
+
import { stdin as stdin4, stdout as stdout5 } from "node:process";
|
|
5974
|
+
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
5975
|
+
function deriveSlug2(name) {
|
|
5976
|
+
const trimmed = name.trim();
|
|
5977
|
+
if (trimmed === "") return "";
|
|
5978
|
+
const slug = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5979
|
+
return slug || "project";
|
|
5980
|
+
}
|
|
5981
|
+
async function runCreateProject() {
|
|
5982
|
+
const rl = createInterface4({ input: stdin4, output: stdout5 });
|
|
5983
|
+
console.log("\n CodeByPlan \u2014 Create Project\n");
|
|
5984
|
+
try {
|
|
5985
|
+
const orgIdFlag = (() => {
|
|
5986
|
+
const idx = process.argv.indexOf("--org-id");
|
|
5987
|
+
return idx !== -1 ? process.argv[idx + 1] ?? "" : "";
|
|
5988
|
+
})();
|
|
5989
|
+
let organizationId;
|
|
5990
|
+
if (orgIdFlag) {
|
|
5991
|
+
organizationId = orgIdFlag.trim();
|
|
5992
|
+
} else {
|
|
5993
|
+
let orgs = null;
|
|
5994
|
+
try {
|
|
5995
|
+
const resp = await apiGet("/organizations");
|
|
5996
|
+
if (resp.data && resp.data.length > 0) {
|
|
5997
|
+
orgs = resp.data;
|
|
5998
|
+
}
|
|
5999
|
+
} catch {
|
|
6000
|
+
}
|
|
6001
|
+
if (orgs && orgs.length > 0) {
|
|
6002
|
+
console.log(" Available organizations:\n");
|
|
6003
|
+
orgs.forEach((org, i) => {
|
|
6004
|
+
console.log(` ${i + 1}. ${org.name} (${org.slug}) [${org.id}]`);
|
|
6005
|
+
});
|
|
6006
|
+
console.log("");
|
|
6007
|
+
const rawChoice = (await rl.question(
|
|
6008
|
+
` Select organization (1\u2013${orgs.length}) or paste an org ID: `
|
|
6009
|
+
)).trim();
|
|
6010
|
+
const choiceNum = parseInt(rawChoice, 10);
|
|
6011
|
+
if (!isNaN(choiceNum) && choiceNum >= 1 && choiceNum <= orgs.length) {
|
|
6012
|
+
organizationId = orgs[choiceNum - 1].id;
|
|
6013
|
+
} else if (rawChoice) {
|
|
6014
|
+
organizationId = rawChoice;
|
|
6015
|
+
} else {
|
|
6016
|
+
console.error("\n Error: Organization selection cannot be empty.\n");
|
|
6017
|
+
process.exitCode = 1;
|
|
6018
|
+
return;
|
|
6019
|
+
}
|
|
6020
|
+
} else {
|
|
6021
|
+
const rawOrgId = (await rl.question(" Organization ID: ")).trim();
|
|
6022
|
+
if (!rawOrgId) {
|
|
6023
|
+
console.error("\n Error: Organization ID cannot be empty.\n");
|
|
6024
|
+
process.exitCode = 1;
|
|
6025
|
+
return;
|
|
6026
|
+
}
|
|
6027
|
+
organizationId = rawOrgId;
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
const rawName = (await rl.question(" Project name: ")).trim();
|
|
6031
|
+
if (!rawName) {
|
|
6032
|
+
console.error("\n Error: Project name cannot be empty.\n");
|
|
6033
|
+
process.exitCode = 1;
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
6036
|
+
const slug = deriveSlug2(rawName);
|
|
6037
|
+
if (slug) {
|
|
6038
|
+
console.log(` Slug preview: ${slug}
|
|
6039
|
+
`);
|
|
6040
|
+
}
|
|
6041
|
+
let response;
|
|
6042
|
+
try {
|
|
6043
|
+
response = await apiPost("/projects", {
|
|
6044
|
+
organization_id: organizationId,
|
|
6045
|
+
name: rawName
|
|
6046
|
+
});
|
|
6047
|
+
} catch (err) {
|
|
6048
|
+
if (err instanceof ApiError) {
|
|
6049
|
+
console.error(`
|
|
6050
|
+
Error: ${err.message} (HTTP ${err.status})
|
|
6051
|
+
`);
|
|
6052
|
+
} else {
|
|
6053
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6054
|
+
console.error(`
|
|
6055
|
+
Error: ${msg}
|
|
6056
|
+
`);
|
|
6057
|
+
}
|
|
6058
|
+
process.exitCode = 1;
|
|
6059
|
+
return;
|
|
6060
|
+
}
|
|
6061
|
+
const project = response.data;
|
|
6062
|
+
console.log("\n Project created successfully.\n");
|
|
6063
|
+
console.log(` id: ${project.id}`);
|
|
6064
|
+
console.log(` name: ${project.name}`);
|
|
6065
|
+
console.log(` slug: ${project.slug}
|
|
6066
|
+
`);
|
|
6067
|
+
} finally {
|
|
6068
|
+
rl.close();
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
var init_create_project = __esm({
|
|
6072
|
+
"src/cli/create-project.ts"() {
|
|
6073
|
+
"use strict";
|
|
6074
|
+
init_api();
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
|
|
6078
|
+
// src/cli/create-repo.ts
|
|
6079
|
+
var create_repo_exports = {};
|
|
6080
|
+
__export(create_repo_exports, {
|
|
6081
|
+
runCreateRepo: () => runCreateRepo
|
|
6082
|
+
});
|
|
6083
|
+
import { stdin as stdin5, stdout as stdout6 } from "node:process";
|
|
6084
|
+
import { createInterface as createInterface5 } from "node:readline/promises";
|
|
6085
|
+
async function runCreateRepo() {
|
|
6086
|
+
const rl = createInterface5({ input: stdin5, output: stdout6 });
|
|
6087
|
+
console.log("\n CodeByPlan \u2014 Create Repository\n");
|
|
6088
|
+
try {
|
|
6089
|
+
const projectIdFlag = (() => {
|
|
6090
|
+
const idx = process.argv.indexOf("--project-id");
|
|
6091
|
+
return idx !== -1 ? process.argv[idx + 1] ?? "" : "";
|
|
6092
|
+
})();
|
|
6093
|
+
let projectId;
|
|
6094
|
+
if (projectIdFlag) {
|
|
6095
|
+
const trimmedFlag = projectIdFlag.trim();
|
|
6096
|
+
if (!UUID_RE.test(trimmedFlag)) {
|
|
6097
|
+
console.error("\n Error: Project ID must be a valid UUID.\n");
|
|
6098
|
+
process.exitCode = 1;
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
projectId = trimmedFlag;
|
|
6102
|
+
} else {
|
|
6103
|
+
let projects = null;
|
|
6104
|
+
try {
|
|
6105
|
+
const resp = await apiGet("/projects");
|
|
6106
|
+
if (resp.data && resp.data.length > 0) {
|
|
6107
|
+
projects = resp.data;
|
|
6108
|
+
}
|
|
6109
|
+
} catch {
|
|
6110
|
+
}
|
|
6111
|
+
if (projects && projects.length > 0) {
|
|
6112
|
+
console.log(" Available projects:\n");
|
|
6113
|
+
projects.forEach((proj, i) => {
|
|
6114
|
+
console.log(
|
|
6115
|
+
` ${i + 1}. ${proj.name} (${proj.slug}) [${proj.id}]`
|
|
6116
|
+
);
|
|
6117
|
+
});
|
|
6118
|
+
console.log("");
|
|
6119
|
+
const rawChoice = (await rl.question(
|
|
6120
|
+
` Select project (1\u2013${projects.length}) or paste a project ID: `
|
|
6121
|
+
)).trim();
|
|
6122
|
+
const choiceNum = parseInt(rawChoice, 10);
|
|
6123
|
+
if (!isNaN(choiceNum) && choiceNum >= 1 && choiceNum <= projects.length) {
|
|
6124
|
+
projectId = projects[choiceNum - 1].id;
|
|
6125
|
+
} else if (rawChoice) {
|
|
6126
|
+
if (!UUID_RE.test(rawChoice)) {
|
|
6127
|
+
console.error("\n Error: Project ID must be a valid UUID.\n");
|
|
6128
|
+
process.exitCode = 1;
|
|
6129
|
+
return;
|
|
6130
|
+
}
|
|
6131
|
+
projectId = rawChoice;
|
|
6132
|
+
} else {
|
|
6133
|
+
console.error("\n Error: Project selection cannot be empty.\n");
|
|
6134
|
+
process.exitCode = 1;
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
} else {
|
|
6138
|
+
const rawProjectId = (await rl.question(" Project ID: ")).trim();
|
|
6139
|
+
if (!rawProjectId) {
|
|
6140
|
+
console.error("\n Error: Project ID cannot be empty.\n");
|
|
6141
|
+
process.exitCode = 1;
|
|
6142
|
+
return;
|
|
6143
|
+
}
|
|
6144
|
+
if (!UUID_RE.test(rawProjectId)) {
|
|
6145
|
+
console.error("\n Error: Project ID must be a valid UUID.\n");
|
|
6146
|
+
process.exitCode = 1;
|
|
6147
|
+
return;
|
|
6148
|
+
}
|
|
6149
|
+
projectId = rawProjectId;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
const rawName = (await rl.question(" Repository name: ")).trim();
|
|
6153
|
+
if (!rawName) {
|
|
6154
|
+
console.error("\n Error: Repository name cannot be empty.\n");
|
|
6155
|
+
process.exitCode = 1;
|
|
6156
|
+
return;
|
|
6157
|
+
}
|
|
6158
|
+
const rawPath = (await rl.question(" Local path (optional, press Enter to skip): ")).trim();
|
|
6159
|
+
const rawBranch = (await rl.question(
|
|
6160
|
+
" Default git branch (optional, press Enter for 'main'): "
|
|
6161
|
+
)).trim();
|
|
6162
|
+
const git_branch = rawBranch || "main";
|
|
6163
|
+
let response;
|
|
6164
|
+
try {
|
|
6165
|
+
response = await apiPost("/repos", {
|
|
6166
|
+
project_id: projectId,
|
|
6167
|
+
name: rawName,
|
|
6168
|
+
...rawPath ? { path: rawPath } : {},
|
|
6169
|
+
git_branch
|
|
6170
|
+
});
|
|
6171
|
+
} catch (err) {
|
|
6172
|
+
if (err instanceof ApiError) {
|
|
6173
|
+
console.error(`
|
|
6174
|
+
Error: ${err.message} (HTTP ${err.status})
|
|
6175
|
+
`);
|
|
6176
|
+
} else {
|
|
6177
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6178
|
+
console.error(`
|
|
6179
|
+
Error: ${msg}
|
|
6180
|
+
`);
|
|
6181
|
+
}
|
|
6182
|
+
process.exitCode = 1;
|
|
6183
|
+
return;
|
|
6184
|
+
}
|
|
6185
|
+
const repo = response.data;
|
|
6186
|
+
console.log("\n Repository created successfully.\n");
|
|
6187
|
+
console.log(` id: ${repo.id}`);
|
|
6188
|
+
console.log(` name: ${repo.name}`);
|
|
6189
|
+
console.log(` git_branch: ${repo.git_branch ?? git_branch}`);
|
|
6190
|
+
console.log(
|
|
6191
|
+
`
|
|
6192
|
+
Tip: run \`codebyplan setup\` from the repo directory to link it.
|
|
6193
|
+
`
|
|
6194
|
+
);
|
|
6195
|
+
} finally {
|
|
6196
|
+
rl.close();
|
|
6197
|
+
}
|
|
6198
|
+
}
|
|
6199
|
+
var UUID_RE;
|
|
6200
|
+
var init_create_repo = __esm({
|
|
6201
|
+
"src/cli/create-repo.ts"() {
|
|
6202
|
+
"use strict";
|
|
6203
|
+
init_api();
|
|
6204
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6205
|
+
}
|
|
6206
|
+
});
|
|
6207
|
+
|
|
5900
6208
|
// src/cli/version-status.ts
|
|
5901
6209
|
var version_status_exports = {};
|
|
5902
6210
|
__export(version_status_exports, {
|
|
@@ -7875,6 +8183,21 @@ void (async () => {
|
|
|
7875
8183
|
await runResolveWorktree2();
|
|
7876
8184
|
process.exit(0);
|
|
7877
8185
|
}
|
|
8186
|
+
if (arg === "create-org") {
|
|
8187
|
+
const { runCreateOrg: runCreateOrg2 } = await Promise.resolve().then(() => (init_create_org(), create_org_exports));
|
|
8188
|
+
await runCreateOrg2();
|
|
8189
|
+
process.exit(0);
|
|
8190
|
+
}
|
|
8191
|
+
if (arg === "create-project") {
|
|
8192
|
+
const { runCreateProject: runCreateProject2 } = await Promise.resolve().then(() => (init_create_project(), create_project_exports));
|
|
8193
|
+
await runCreateProject2();
|
|
8194
|
+
process.exit(0);
|
|
8195
|
+
}
|
|
8196
|
+
if (arg === "create-repo") {
|
|
8197
|
+
const { runCreateRepo: runCreateRepo2 } = await Promise.resolve().then(() => (init_create_repo(), create_repo_exports));
|
|
8198
|
+
await runCreateRepo2();
|
|
8199
|
+
process.exit(0);
|
|
8200
|
+
}
|
|
7878
8201
|
if (arg === "version-status") {
|
|
7879
8202
|
const { runVersionStatus: runVersionStatus2 } = await Promise.resolve().then(() => (init_version_status(), version_status_exports));
|
|
7880
8203
|
await runVersionStatus2();
|
|
@@ -7970,6 +8293,9 @@ void (async () => {
|
|
|
7970
8293
|
codebyplan logout Clear cached OAuth tokens
|
|
7971
8294
|
codebyplan whoami Show currently authenticated identity
|
|
7972
8295
|
codebyplan upgrade-auth Migrate legacy x-api-key MCP config to OAuth
|
|
8296
|
+
codebyplan create-org Create a new organization (interactive prompt)
|
|
8297
|
+
codebyplan create-project Create a new project within an organization (interactive prompt)
|
|
8298
|
+
codebyplan create-repo Create a new repository within a project (interactive prompt)
|
|
7973
8299
|
codebyplan config Sync repo config from DB to .codebyplan/ files
|
|
7974
8300
|
codebyplan ports Verify port allocations against local package.json scripts
|
|
7975
8301
|
codebyplan tech-stack Detect and sync tech stack dependencies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebyplan",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.13",
|
|
4
4
|
"description": "CLI for CodeByPlan — AI-powered development planning and tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"prettier": "^3.8.1",
|
|
62
62
|
"typescript": "^5",
|
|
63
63
|
"typescript-eslint": "^8.20.0",
|
|
64
|
-
"vitest": "^4.
|
|
64
|
+
"vitest": "^4.1.2"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -9,6 +9,8 @@ effort: xhigh
|
|
|
9
9
|
|
|
10
10
|
# Research Agent
|
|
11
11
|
|
|
12
|
+
> Adheres to [[agent-claim-verification]] — cite a source file (`path:line`) or vendor docs before asserting any JSON key, schema field, env-var, or API shape exists.
|
|
13
|
+
|
|
12
14
|
Intelligent research with discovery levels (0-3). Consults `.claude/docs/stack/` first, uses web research when needed, produces DISCOVERY.md for Level 2+.
|
|
13
15
|
|
|
14
16
|
## Purpose
|
|
@@ -9,6 +9,8 @@ effort: xhigh
|
|
|
9
9
|
|
|
10
10
|
# Round Executor Agent
|
|
11
11
|
|
|
12
|
+
> Adheres to [[agent-claim-verification]] — cite a source file (`path:line`) or vendor docs before asserting any JSON key, schema field, env-var, or API shape exists.
|
|
13
|
+
|
|
12
14
|
Execute an already-approved implementation plan. The planner agent has already done analysis and the user has approved the plan - this agent focuses purely on quality execution.
|
|
13
15
|
|
|
14
16
|
## Purpose
|
|
@@ -531,6 +531,7 @@ After Phase 5 (solution design) and before Phase 6 (context summary), decompose
|
|
|
531
531
|
3. **Check DAG invariant**: `depends_on[]` must be acyclic. Any cycle is a plan error — resolve by merging the cyclic waves.
|
|
532
532
|
4. **Populate `skill_preloads[]`**: for each wave whose `files[]` contains UI-bearing paths (`*.tsx`, `*.jsx`, `*.scss`, etc.), add `"frontend-design"` and `"frontend-a11y"` to `skill_preloads[]` (in that order).
|
|
533
533
|
5. **Single-wave default**: if no independence is found, produce ONE wave covering all files. Parallel waves add orchestration overhead — only decompose when the benefit is clear.
|
|
534
|
+
6. **15-file cap**: after decomposition (including the single-wave default), count files in each wave. If any wave would exceed 15 files, auto-split it using the proximity-split algorithm in priority order: (a) **shared directory subtree** — split at the deepest common ancestor that produces two groups each ≥3 files; (b) **shared module** — split at the next directory level below the common ancestor; (c) **arbitrary boundary** — split at the 15-file boundary and add a one-line `note` on the continuation wave explaining the boundary. Split siblings are **independent**: do NOT add `depends_on` between them unless a real shared-file or data dependency requires ordering. **Tail rule**: choose boundaries so every resulting wave holds 3–15 files. A split must never leave a wave with <3 files; rebalance the boundary rather than absorbing a tail into a sibling in a way that pushes it above 15. The 3–15 range is a hard invariant — there is no exception above 15. **Apply the cap iteratively**: after a split, re-check each resulting wave and split again any that still exceeds 15 — a 40-file single-concern plan therefore yields ≥3 waves. When no natural boundary yields groups each ≥3 files, take the smallest ≥3-file prefix as one wave and apply the same procedure to the remainder. The single-wave default is itself subject to this cap. See `rules/parallel-waves.md` for the full algorithm and invariants.
|
|
534
535
|
|
|
535
536
|
**Output** (added to plan):
|
|
536
537
|
|
|
@@ -541,6 +542,7 @@ plan.waves:
|
|
|
541
542
|
files: string[]
|
|
542
543
|
depends_on: string[]
|
|
543
544
|
skill_preloads: string[]
|
|
545
|
+
note: string # optional — set on continuation waves from an arbitrary-boundary split
|
|
544
546
|
```
|
|
545
547
|
|
|
546
548
|
If `files_to_modify[]` contains ≤5 files across a single app, skip decomposition and emit a single wave or omit `waves[]` entirely (single-wave default in `round-execute` handles this gracefully).
|
|
@@ -550,7 +552,8 @@ If `files_to_modify[]` contains ≤5 files across a single app, skip decompositi
|
|
|
550
552
|
- [ ] Every file in `files_to_modify[]` appears in exactly one wave
|
|
551
553
|
- [ ] `depends_on[]` forms a DAG (no cycles)
|
|
552
554
|
- [ ] UI-bearing waves have `frontend-design` + `frontend-a11y` in `skill_preloads[]`
|
|
553
|
-
- [ ] Each wave has ≥3 files (if not, merge into
|
|
555
|
+
- [ ] Each wave has ≥3 files (if not, merge into sibling wave)
|
|
556
|
+
- [ ] No wave has >15 files (if so, apply the proximity-split algorithm)
|
|
554
557
|
|
|
555
558
|
### Phase 6: Build Context Summary
|
|
556
559
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# @scope: org-shared
|
|
3
|
+
# @event: PreToolUse
|
|
4
|
+
# @matcher: Edit|Write|MultiEdit
|
|
5
|
+
# Advisory source-of-truth nudge. When editing a GATE-6-tracked .claude/ file
|
|
6
|
+
# (skills/agents/hooks/rules) that already has a packages/codebyplan-package/
|
|
7
|
+
# templates/ counterpart, remind the author to mirror the change so the two
|
|
8
|
+
# trees stay byte-identical (scripts/check-sibling-identity.mjs / GATE 6).
|
|
9
|
+
#
|
|
10
|
+
# Fires ONLY in the templates source repo, detected by the presence of
|
|
11
|
+
# packages/codebyplan-package/templates/. Always exits 0 — advisory only,
|
|
12
|
+
# never blocks the edit. (set -u catches typos; -e / -o pipefail are
|
|
13
|
+
# deliberately omitted so the hook can never abort mid-run.)
|
|
14
|
+
|
|
15
|
+
set -u
|
|
16
|
+
|
|
17
|
+
INPUT=$(cat)
|
|
18
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
19
|
+
|
|
20
|
+
case "$TOOL_NAME" in
|
|
21
|
+
Edit|Write|MultiEdit) ;;
|
|
22
|
+
*) exit 0 ;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
# MultiEdit shares the single top-level tool_input.file_path with Edit/Write
|
|
26
|
+
# (only the per-edit content differs); filePath is a defensive camelCase fallback.
|
|
27
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty' 2>/dev/null)
|
|
28
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
29
|
+
|
|
30
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
31
|
+
[ -z "$REPO_ROOT" ] && exit 0
|
|
32
|
+
# Symlink-resolve so a /Users vs /private/Users mismatch (macOS) still compares equal.
|
|
33
|
+
REPO_ROOT=$(cd "$REPO_ROOT" 2>/dev/null && pwd -P || echo "$REPO_ROOT")
|
|
34
|
+
|
|
35
|
+
TEMPLATES_DIR="$REPO_ROOT/packages/codebyplan-package/templates"
|
|
36
|
+
# Not the templates source repo → nothing to mirror.
|
|
37
|
+
[ -d "$TEMPLATES_DIR" ] || exit 0
|
|
38
|
+
|
|
39
|
+
# Resolve the edited path to a repo-root-relative form. Absolute paths are
|
|
40
|
+
# symlink-normalised (so the prefix match is apples-to-apples); relative paths
|
|
41
|
+
# are taken as-is.
|
|
42
|
+
case "$FILE_PATH" in
|
|
43
|
+
/*)
|
|
44
|
+
_dir=$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd -P || echo "")
|
|
45
|
+
[ -z "$_dir" ] && exit 0
|
|
46
|
+
FILE_PATH="$_dir/$(basename "$FILE_PATH")"
|
|
47
|
+
case "$FILE_PATH" in
|
|
48
|
+
"$REPO_ROOT"/*) REL="${FILE_PATH#"$REPO_ROOT"/}" ;;
|
|
49
|
+
*) exit 0 ;;
|
|
50
|
+
esac
|
|
51
|
+
;;
|
|
52
|
+
*) REL="$FILE_PATH" ;;
|
|
53
|
+
esac
|
|
54
|
+
|
|
55
|
+
# Only the GATE-6-tracked subtrees: .claude/{skills,agents,hooks,rules}/...
|
|
56
|
+
case "$REL" in
|
|
57
|
+
.claude/skills/*|.claude/agents/*|.claude/hooks/*|.claude/rules/*) ;;
|
|
58
|
+
*) exit 0 ;;
|
|
59
|
+
esac
|
|
60
|
+
|
|
61
|
+
SUBREL="${REL#.claude/}"
|
|
62
|
+
COUNTERPART="$TEMPLATES_DIR/$SUBREL"
|
|
63
|
+
|
|
64
|
+
if [ -e "$COUNTERPART" ]; then
|
|
65
|
+
echo "cbp: editing .claude/$SUBREL — also mirror this change to packages/codebyplan-package/templates/$SUBREL to keep them byte-identical (GATE 6: node scripts/check-sibling-identity.mjs). Advisory only; continuing." >&2
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
exit 0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
scope: org-shared
|
|
3
|
+
name: agent-claim-verification
|
|
4
|
+
description: Subagents must cite the source file (path:line) or vendor docs before asserting that any JSON key, schema field, env-var name, or external API shape exists.
|
|
5
|
+
paths:
|
|
6
|
+
- ".claude/agents/**/*"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Agent Claim Verification
|
|
10
|
+
|
|
11
|
+
<!-- Delivery: subagents receive this rule via the [[agent-claim-verification]] pointer in each
|
|
12
|
+
agent's .md file. The `paths:` frontmatter surfaces it when an agent file is being *edited*,
|
|
13
|
+
not when the agent is *running* — both mechanisms are intentional, so don't drop the pointer.
|
|
14
|
+
Scope is agents-only by design (cbp-round-executor, cbp-research); skill files are out of
|
|
15
|
+
scope this round. -->
|
|
16
|
+
|
|
17
|
+
Subagents routinely emit tool calls and explanatory text that depend on a named thing *existing* — a JSON config key, a schema field, an environment-variable name, an external API's request/response shape. When that name is recalled from memory instead of read from the source, it is often subtly wrong: a renamed field, a key that moved to a different file, an API shape from an older version. Those hallucinated names cost correction rounds.
|
|
18
|
+
|
|
19
|
+
## The Rule
|
|
20
|
+
|
|
21
|
+
Before stating that a **JSON key, schema field, env-var name, or external API shape exists**, the agent MUST first read the authoritative source and cite it inline:
|
|
22
|
+
|
|
23
|
+
- **In-repo facts** → cite the source file as `path:line` (e.g. `packages/cli/src/config.ts:42`).
|
|
24
|
+
- **External library / API facts** → cite the vendored docs as `vendor/{lib}/v{ver}/{file}` (or the upstream URL when no vendor mirror exists).
|
|
25
|
+
|
|
26
|
+
A claim that cannot be cited has not been verified — do not state it as fact. Read the file first, or mark the statement explicitly as an assumption still to confirm.
|
|
27
|
+
|
|
28
|
+
## Applies When
|
|
29
|
+
|
|
30
|
+
- Emitting a tool call whose correctness depends on a named key/field/shape (passing a config key, asserting an MCP tool parameter, referencing a settings field).
|
|
31
|
+
- Writing explanatory prose that asserts a named key/field/env-var/API shape is present.
|
|
32
|
+
|
|
33
|
+
## Does NOT Apply To
|
|
34
|
+
|
|
35
|
+
- **Structural proposals** — "this *should* be a new `retryDelay` field" is a proposal, not a claim. Proposals describe what ought to exist; they need no citation.
|
|
36
|
+
- Well-known language/runtime built-ins (`Array.prototype.map`, `JSON.parse`) — not the failure mode this rule targets.
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
**BAD** (unverified — the field name is recalled, not read):
|
|
41
|
+
|
|
42
|
+
> The merge config supports a `retryDelay` field, so set it to 500.
|
|
43
|
+
|
|
44
|
+
**GOOD** (read and cited):
|
|
45
|
+
|
|
46
|
+
> The merge config schema at `packages/codebyplan-package/src/lib/settings-merge.ts:31` defines `conflictStrategy` (there is no `retryDelay` field), so use `conflictStrategy: "theirs"`.
|
|
47
|
+
|
|
48
|
+
## Why
|
|
49
|
+
|
|
50
|
+
Silent recall of config/API names is the single cheapest-to-prevent class of subagent error: one `Read` before the claim replaces a correction round after it. The citation doubles as an audit trail — a reviewer can confirm the fact at the cited line without re-deriving it.
|
|
@@ -23,6 +23,7 @@ paths:
|
|
|
23
23
|
| `context/testing/eslint.md` | `cbp-improve-round` | Phase 1.5 | Config-file compliance audit |
|
|
24
24
|
| `context/mcp-docs.md` | `cbp-task-planner` | Phase 2.6 | MCP library doc lookup contract — per-dependency consultation via DocsByPlan MCP tools (resolve_library_id → search_chunks/lookup_symbol → get_chunk) |
|
|
25
25
|
| `context/mcp-docs.md` | `cbp-round-executor` | Step 3.4 | Library-specific reference — pre-write API verification via DocsByPlan MCP tools |
|
|
26
|
+
| `rules/parallel-waves.md` | `cbp-task-planner` | Phase 5.6 | Wave schema, invariants (3..15 file-count), and the proximity-split algorithm (a `rules/` file, not `context/**`; listed here for consumer discoverability) |
|
|
26
27
|
|
|
27
28
|
New context files MUST be added here in the same change that introduces the consumer — or the file is orphan infrastructure.
|
|
28
29
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
scope: org-shared
|
|
3
|
+
name: parallel-waves
|
|
4
|
+
description: Wave schema, invariants, and proximity-split algorithm for cbp-task-planner Phase 5.6 wave decomposition.
|
|
5
|
+
paths:
|
|
6
|
+
- .claude/agents/cbp-task-planner.md
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Parallel Waves
|
|
10
|
+
|
|
11
|
+
Authoritative expansion of `cbp-task-planner` Phase 5.6. The planner reads this file at wave decomposition time.
|
|
12
|
+
|
|
13
|
+
## Wave Schema
|
|
14
|
+
|
|
15
|
+
Each entry in `plan.waves[]` carries these fields (source: `.claude/agents/cbp-task-planner.md:540`):
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
- name: string # short identifier, e.g. "web-ui", "backend", "db"
|
|
19
|
+
agent_type: 'round-executor' | 'inline'
|
|
20
|
+
files: string[] # repo-relative paths owned by this wave
|
|
21
|
+
depends_on: string[] # names of waves that must complete before this one starts
|
|
22
|
+
skill_preloads: string[] # skills invoked by the executor before Step 3 (e.g. "frontend-design")
|
|
23
|
+
note: string # optional — required on continuation waves from an arbitrary-boundary split
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Invariants
|
|
27
|
+
|
|
28
|
+
**(I) Disjoint files** — a file appears in exactly one wave. If two waves need a shared file, assign it to the earlier wave; the later wave lists the earlier in `depends_on`.
|
|
29
|
+
|
|
30
|
+
**(II) DAG** — `depends_on[]` must be acyclic. A cycle is a plan error; resolve by merging the cyclic waves.
|
|
31
|
+
|
|
32
|
+
**(III) 3–15 files per wave** — every wave holds between 3 and 15 files (inclusive).
|
|
33
|
+
- Below 3: merge into a sibling wave.
|
|
34
|
+
- Above 15: apply the proximity-split algorithm below.
|
|
35
|
+
- Sole exception — trivially small plans are exempt from the lower bound: a plan with fewer than 3 total files uses one single wave, and a single-app plan with ≤5 total files MAY skip decomposition entirely (one wave, or `waves[]` omitted — see `cbp-task-planner` Phase 5.6). Zero waves (omitted `waves[]`) trivially satisfies this invariant.
|
|
36
|
+
|
|
37
|
+
**(IV) UI skill preloads** — for each wave whose `files[]` contains UI-bearing paths (`*.tsx`, `*.jsx`, `*.scss`, etc.), add `"frontend-design"` and `"frontend-a11y"` to `skill_preloads[]` in that order (source: `.claude/agents/cbp-task-planner.md:532`).
|
|
38
|
+
|
|
39
|
+
## Proximity-Split Algorithm
|
|
40
|
+
|
|
41
|
+
When a wave would exceed 15 files, split it in priority order:
|
|
42
|
+
|
|
43
|
+
1. **Shared directory subtree** — split at the deepest common ancestor that produces two groups each ≥3 files. This is the preferred split because it keeps logically related files together.
|
|
44
|
+
2. **Shared module** — split at the next directory level below the common ancestor when subtree split cannot achieve ≥3 per group.
|
|
45
|
+
3. **Arbitrary boundary** — split at the 15-file boundary as a last resort. The continuation wave MUST carry a one-line `note` field explaining the boundary (e.g. `"files 16–28 of auth module, continued from auth-1"`).
|
|
46
|
+
|
|
47
|
+
**Independence clause** — split siblings are independent. Do NOT add `depends_on` between them unless a real shared-file or data dependency requires ordering.
|
|
48
|
+
|
|
49
|
+
**Tail rule** — choose split boundaries so every resulting wave holds 3–15 files. A split must never leave a wave with fewer than 3 files; rebalance the boundary rather than absorbing a tail into a sibling in a way that pushes that sibling above 15. The 3–15 range is a hard invariant — there is no exception above 15.
|
|
50
|
+
|
|
51
|
+
## Single-Wave Default
|
|
52
|
+
|
|
53
|
+
When no cross-app or cross-concern independence is found, the planner emits one wave covering all files. The 15-file cap applies to this single wave — if the total exceeds 15, apply the proximity-split algorithm even though the original decomposition was a single wave.
|
|
54
|
+
|
|
55
|
+
## Cross-References
|
|
56
|
+
|
|
57
|
+
- `agents/cbp-task-planner.md` Phase 5.6 — consumer of this rule; steps 1–6 and verification checklist.
|
|
58
|
+
- `agents/cbp-round-executor.md` Step 2.6 — wave-mode skill preloads.
|
|
59
|
+
- `skills/cbp-round-execute/SKILL.md` Step 3 — per-wave executor dispatch.
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"mcp__codebyplan__create_launch",
|
|
59
59
|
"mcp__codebyplan__update_launch",
|
|
60
60
|
"mcp__codebyplan__delete_launch",
|
|
61
|
+
"mcp__codebyplan__create_organization",
|
|
62
|
+
"mcp__codebyplan__create_project",
|
|
61
63
|
"mcp__codebyplan__create_repo",
|
|
62
64
|
"mcp__codebyplan__delete_session_log",
|
|
63
65
|
"mcp__codebyplan__delete_worktree",
|
|
@@ -67,6 +69,12 @@
|
|
|
67
69
|
"mcp__codebyplan__release_assignment",
|
|
68
70
|
"Bash(codebyplan setup:*)",
|
|
69
71
|
"Bash(npx codebyplan setup:*)",
|
|
72
|
+
"Bash(codebyplan create-org:*)",
|
|
73
|
+
"Bash(npx codebyplan create-org:*)",
|
|
74
|
+
"Bash(codebyplan create-project:*)",
|
|
75
|
+
"Bash(npx codebyplan create-project:*)",
|
|
76
|
+
"Bash(codebyplan create-repo:*)",
|
|
77
|
+
"Bash(npx codebyplan create-repo:*)",
|
|
70
78
|
"Bash(codebyplan login:*)",
|
|
71
79
|
"Bash(npx codebyplan login:*)",
|
|
72
80
|
"Bash(codebyplan logout:*)",
|
|
@@ -61,6 +61,23 @@ Given the parse from Step 1:
|
|
|
61
61
|
|
|
62
62
|
If no task found: `No active task. Nothing to update.`
|
|
63
63
|
|
|
64
|
+
### Step 1.6: Permission Gate
|
|
65
|
+
|
|
66
|
+
Step 1 (parse) and Step 1.5 (resolve task + round) are read-only. Step 2 onward mutates state — the `sync-approvals` CLI write, `complete_round`, and the auto-trigger of the next step. Before any of that, confirm the user wants this skill to run.
|
|
67
|
+
|
|
68
|
+
This gate fires on **every** invocation — manual, auto-triggered by `/cbp-round-end`, and on every iteration of the Step 4 auto-loop. There is no bypass. It sits **before** and is independent of the top-of-file HARD GATE (which governs Step 2's exit code, not user consent), and it is distinct from the Branch A clean-exit route choice in Step 5 (that one picks where to go next; this one authorizes running at all).
|
|
69
|
+
|
|
70
|
+
Ask via AskUserQuestion, naming the resolved round and disclosing the actions:
|
|
71
|
+
|
|
72
|
+
> Update ROUND-{N} of TASK-{M}?
|
|
73
|
+
> This will sync the git diff + file approvals, complete the round, and route to the next step (which may auto-start a new round).
|
|
74
|
+
>
|
|
75
|
+
> - **Proceed** — run the skill
|
|
76
|
+
> - **Cancel** — do nothing
|
|
77
|
+
|
|
78
|
+
- **Proceed** → continue to Step 2.
|
|
79
|
+
- **Cancel** → abort cleanly: make NO writes (no `sync-approvals`, no `complete_round`, no auto-trigger) and exit with one line: `Cancelled by user — ROUND-{N} not updated.`
|
|
80
|
+
|
|
64
81
|
### Step 2: Sync git diff + approvals via CLI
|
|
65
82
|
|
|
66
83
|
Run:
|
|
@@ -103,6 +120,8 @@ Procedure:
|
|
|
103
120
|
2. **If `next_index > (round.context.auto_loop_cap ?? 5)`**: surface the cap-exhausted prompt via AskUserQuestion (options: extend cap, stop loop / drop into round-input, close task as-is). Persist `round.context.auto_loop_cap_exhausted = { user_choice, decided_at }` and route per choice.
|
|
104
121
|
3. **Otherwise**: persist `round.context.auto_loop_decision = { spawned_next: true, next_index, decided_at }` on the CURRENT round via `update_round` (audit trail), then auto-trigger `/cbp-round-input` with NO AskUserQuestion. Pass `auto_loop_mode: true`, `auto_loop_index: next_index`, `auto_loop_cap: (prior cap ?? 5)` forward — round-start Step 4 persists them on the new round.
|
|
105
122
|
|
|
123
|
+
> **Permission-gate note**: the "NO AskUserQuestion" in item 3 governs only the auto-loop's decision to spawn `/cbp-round-input` — it does not add a prompt for that hand-off. It is NOT a bypass of the Step 1.6 permission gate: when the spawned round eventually re-enters `/cbp-round-update`, Step 1.6 prompts again (the gate always fires, including inside the auto-loop).
|
|
124
|
+
|
|
106
125
|
If BOTH signals are clean, fall through to Step 5 (exit routing).
|
|
107
126
|
|
|
108
127
|
### Step 5: Exit Routing
|
|
@@ -163,6 +182,7 @@ Unreachable in the auto-loop path — Step 4 catches it first. Retained for MANU
|
|
|
163
182
|
|
|
164
183
|
## Key Rules
|
|
165
184
|
|
|
185
|
+
- **Step 1.6 permission gate fires first** — every invocation (incl. auto-trigger and the Step 4 auto-loop) asks the user to confirm before any write; **Cancel** is a clean abort (no `sync-approvals`, no `complete_round`, no auto-trigger).
|
|
166
186
|
- **Step 2 (CLI) must exit 0** — if it fails, STOP. The merge semantics are enforced by the CLI.
|
|
167
187
|
- **Step 4 owns the dirty-loop case**; Step 5 owns the clean-exit case. Step 5 Branch C is for manual invocation only.
|
|
168
188
|
- **NEVER ask user to git add files** — only reads staging status. **NEVER stage files** — Claude does not touch git staging area.
|
|
@@ -170,6 +190,7 @@ Unreachable in the auto-loop path — Step 4 catches it first. Retained for MANU
|
|
|
170
190
|
|
|
171
191
|
## Integration
|
|
172
192
|
|
|
193
|
+
- **Gates**: Step 1.6 permission gate — asks the user to confirm before any side effect; **Cancel** aborts cleanly with no writes. Fires on every invocation incl. the Step 4 auto-loop; sits before and independent of the top-of-file Step 2 hard gate.
|
|
173
194
|
- **Triggered by**: `/cbp-round-end` (auto), or user manually
|
|
174
195
|
- **Reads**: MCP `get_current_task`, `get_rounds`; delegates git+approval sync to `npx codebyplan round sync-approvals`
|
|
175
196
|
- **Writes**: MCP `update_round` (auto_loop_decision / auto_loop_exit / auto_loop_cap_exhausted), `complete_round`; round+task files_changed written by CLI
|
|
@@ -65,6 +65,23 @@ task-start: no task found for `{ARG}`.
|
|
|
65
65
|
- For `45`: no standalone TASK-45 exists. If a checkpoint-bound TASK-45 exists, invoke as `{chk}-45`.
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
### Step 2.5: Permission Gate
|
|
69
|
+
|
|
70
|
+
Steps 1–2 are read-only (argument parse + DB resolve). Everything from Step 3 onward mutates state — branch switch/create, the clean-slate commit, the task status write, and the round-1 auto-start. Before any of that, confirm the user wants this skill to run.
|
|
71
|
+
|
|
72
|
+
This gate fires on **every** invocation — manual, auto-triggered from the pipeline, and inside any auto-loop. There is no bypass.
|
|
73
|
+
|
|
74
|
+
Ask via AskUserQuestion, naming the resolved task and disclosing the actions:
|
|
75
|
+
|
|
76
|
+
> Start TASK-{N} — {checkpoint or standalone title}?
|
|
77
|
+
> This will switch/create its feat branch, commit a clean slate if the working tree is dirty, set the task `in_progress`, and auto-start round 1.
|
|
78
|
+
>
|
|
79
|
+
> - **Proceed** — run the skill
|
|
80
|
+
> - **Cancel** — do nothing
|
|
81
|
+
|
|
82
|
+
- **Proceed** → continue to Step 3.
|
|
83
|
+
- **Cancel** → abort cleanly: make NO writes (no branch switch, no commit, no `update_task`, no `/cbp-round-start` trigger) and exit with one line: `Cancelled by user — TASK-{N} not started.`
|
|
84
|
+
|
|
68
85
|
### Step 3: Branch Auto-Handling
|
|
69
86
|
|
|
70
87
|
The task MUST run on its target feat branch. Claude switches/creates that branch automatically — never asks the user to do it.
|
|
@@ -226,7 +243,7 @@ If worktree_id present, include `claim_worktree_id` to auto-claim the checkpoint
|
|
|
226
243
|
|
|
227
244
|
### Step 6: Auto-trigger Round Start
|
|
228
245
|
|
|
229
|
-
|
|
246
|
+
The Step 2.5 permission gate already covered this hand-off (the user approved running the skill, round-1 auto-start included), so no further prompt is needed here — automatically trigger `/cbp-round-start` for the first round.
|
|
230
247
|
|
|
231
248
|
```
|
|
232
249
|
Starting first round...
|
|
@@ -236,6 +253,7 @@ Trigger `/cbp-round-start` (which will use task.requirements for round 1).
|
|
|
236
253
|
|
|
237
254
|
## Integration
|
|
238
255
|
|
|
256
|
+
- **Gates**: Step 2.5 permission gate — asks the user to confirm before any side effect; **Cancel** aborts cleanly with no writes. Fires on every invocation (manual, auto-trigger, auto-loop).
|
|
239
257
|
- **Reads**: MCP `get_current_task`, `get_tasks`, `get_rounds`
|
|
240
258
|
- **Writes**: MCP `update_task`
|
|
241
259
|
- **Triggers**: `/cbp-round-start` (auto, round 1)
|