codebyplan 1.13.7 → 1.13.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -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.9";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -1443,8 +1443,8 @@ async function runSetup() {
|
|
|
1443
1443
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
1444
1444
|
let branch = "main";
|
|
1445
1445
|
try {
|
|
1446
|
-
const { execSync:
|
|
1447
|
-
branch =
|
|
1446
|
+
const { execSync: execSync8 } = await import("node:child_process");
|
|
1447
|
+
branch = execSync8("git symbolic-ref --short HEAD", {
|
|
1448
1448
|
cwd: projectPath,
|
|
1449
1449
|
encoding: "utf-8"
|
|
1450
1450
|
}).trim();
|
|
@@ -3064,9 +3064,9 @@ async function eslintInit(repoId, projectPath) {
|
|
|
3064
3064
|
Install ${missingPkgs.length} missing packages? [Y/n] `
|
|
3065
3065
|
);
|
|
3066
3066
|
if (confirmed) {
|
|
3067
|
-
const { execSync:
|
|
3067
|
+
const { execSync: execSync8 } = await import("node:child_process");
|
|
3068
3068
|
try {
|
|
3069
|
-
|
|
3069
|
+
execSync8(installCmd, { cwd: projectPath, stdio: "inherit" });
|
|
3070
3070
|
console.log(" Packages installed.\n");
|
|
3071
3071
|
} catch (err) {
|
|
3072
3072
|
console.error(
|
|
@@ -5735,12 +5735,181 @@ var init_resolve_worktree2 = __esm({
|
|
|
5735
5735
|
}
|
|
5736
5736
|
});
|
|
5737
5737
|
|
|
5738
|
+
// src/cli/version-status.ts
|
|
5739
|
+
var version_status_exports = {};
|
|
5740
|
+
__export(version_status_exports, {
|
|
5741
|
+
runVersionStatus: () => runVersionStatus
|
|
5742
|
+
});
|
|
5743
|
+
import { execFileSync, execSync as execSync6 } from "node:child_process";
|
|
5744
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
5745
|
+
import { dirname as dirname7, join as join19 } from "node:path";
|
|
5746
|
+
function fetchLatestVersion() {
|
|
5747
|
+
try {
|
|
5748
|
+
return execFileSync("npm", ["view", "codebyplan", "version"], {
|
|
5749
|
+
encoding: "utf-8",
|
|
5750
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5751
|
+
}).trim() || null;
|
|
5752
|
+
} catch {
|
|
5753
|
+
return null;
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
function resolveGitRoot() {
|
|
5757
|
+
try {
|
|
5758
|
+
return execSync6("git rev-parse --show-toplevel", {
|
|
5759
|
+
encoding: "utf-8"
|
|
5760
|
+
}).trim();
|
|
5761
|
+
} catch {
|
|
5762
|
+
return null;
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
function resolveCurrentBranch() {
|
|
5766
|
+
try {
|
|
5767
|
+
return execSync6("git symbolic-ref --short HEAD", {
|
|
5768
|
+
encoding: "utf-8"
|
|
5769
|
+
}).trim();
|
|
5770
|
+
} catch {
|
|
5771
|
+
try {
|
|
5772
|
+
return execSync6("git rev-parse --abbrev-ref HEAD", {
|
|
5773
|
+
encoding: "utf-8"
|
|
5774
|
+
}).trim();
|
|
5775
|
+
} catch {
|
|
5776
|
+
return "";
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5780
|
+
function detectPackageManager2(gitRoot) {
|
|
5781
|
+
let dir = process.cwd();
|
|
5782
|
+
const stopAt = gitRoot ?? null;
|
|
5783
|
+
while (true) {
|
|
5784
|
+
if (existsSync4(join19(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
5785
|
+
if (existsSync4(join19(dir, "yarn.lock"))) return "yarn";
|
|
5786
|
+
if (existsSync4(join19(dir, "package-lock.json"))) return "npm";
|
|
5787
|
+
if (stopAt !== null && dir === stopAt) break;
|
|
5788
|
+
const parent = dirname7(dir);
|
|
5789
|
+
if (parent === dir) break;
|
|
5790
|
+
dir = parent;
|
|
5791
|
+
}
|
|
5792
|
+
return "npm";
|
|
5793
|
+
}
|
|
5794
|
+
function buildInstallCommand2(pm) {
|
|
5795
|
+
switch (pm) {
|
|
5796
|
+
case "pnpm":
|
|
5797
|
+
return "pnpm add codebyplan@latest";
|
|
5798
|
+
case "yarn":
|
|
5799
|
+
return "yarn add codebyplan@latest";
|
|
5800
|
+
case "npm":
|
|
5801
|
+
return "npm install codebyplan@latest";
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
async function resolveGuard(gitRoot, currentBranch) {
|
|
5805
|
+
if (gitRoot !== null) {
|
|
5806
|
+
const canonicalPkgPath = join19(
|
|
5807
|
+
gitRoot,
|
|
5808
|
+
"packages",
|
|
5809
|
+
"codebyplan-package",
|
|
5810
|
+
"package.json"
|
|
5811
|
+
);
|
|
5812
|
+
try {
|
|
5813
|
+
if (existsSync4(canonicalPkgPath)) {
|
|
5814
|
+
const raw = readFileSync5(canonicalPkgPath, "utf-8");
|
|
5815
|
+
const parsed = JSON.parse(raw);
|
|
5816
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && parsed["name"] === "codebyplan") {
|
|
5817
|
+
return { guarded: true, guardReason: "canonical_source" };
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
} catch {
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
let protectedBranches = [];
|
|
5824
|
+
try {
|
|
5825
|
+
const found = await findCodebyplanConfig(process.cwd());
|
|
5826
|
+
if (found) {
|
|
5827
|
+
let gitJsonPath;
|
|
5828
|
+
if (found.path.endsWith("/repo.json")) {
|
|
5829
|
+
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
5830
|
+
gitJsonPath = join19(dir, "git.json");
|
|
5831
|
+
} else {
|
|
5832
|
+
const legacyDir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
5833
|
+
gitJsonPath = join19(legacyDir, ".codebyplan", "git.json");
|
|
5834
|
+
}
|
|
5835
|
+
const raw = readFileSync5(gitJsonPath, "utf-8");
|
|
5836
|
+
const parsed = JSON.parse(raw);
|
|
5837
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
5838
|
+
const bc = parsed["branch_config"];
|
|
5839
|
+
if (typeof bc === "object" && bc !== null && !Array.isArray(bc)) {
|
|
5840
|
+
const protectedField = bc["protected"];
|
|
5841
|
+
if (Array.isArray(protectedField)) {
|
|
5842
|
+
protectedBranches = protectedField.filter(
|
|
5843
|
+
(v) => typeof v === "string"
|
|
5844
|
+
);
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
} catch {
|
|
5850
|
+
protectedBranches = [];
|
|
5851
|
+
}
|
|
5852
|
+
const isProtected = currentBranch === "main" || currentBranch !== "" && protectedBranches.includes(currentBranch);
|
|
5853
|
+
if (isProtected) {
|
|
5854
|
+
return { guarded: true, guardReason: "protected_branch" };
|
|
5855
|
+
}
|
|
5856
|
+
return { guarded: false, guardReason: null };
|
|
5857
|
+
}
|
|
5858
|
+
async function runVersionStatus() {
|
|
5859
|
+
try {
|
|
5860
|
+
const installed = VERSION;
|
|
5861
|
+
const latest = fetchLatestVersion();
|
|
5862
|
+
const newer = latest !== null && compareSemver(latest, installed) > 0;
|
|
5863
|
+
const gitRoot = resolveGitRoot();
|
|
5864
|
+
const pm = detectPackageManager2(gitRoot);
|
|
5865
|
+
const installCommand = buildInstallCommand2(pm);
|
|
5866
|
+
const currentBranch = resolveCurrentBranch();
|
|
5867
|
+
const { guarded, guardReason } = await resolveGuard(gitRoot, currentBranch);
|
|
5868
|
+
const result = {
|
|
5869
|
+
installed,
|
|
5870
|
+
latest,
|
|
5871
|
+
newer,
|
|
5872
|
+
packageManager: pm,
|
|
5873
|
+
installCommand,
|
|
5874
|
+
guarded,
|
|
5875
|
+
guardReason
|
|
5876
|
+
};
|
|
5877
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
5878
|
+
process.exit(0);
|
|
5879
|
+
} catch (err) {
|
|
5880
|
+
if (err instanceof ProcessExitSignal) throw err;
|
|
5881
|
+
process.stdout.write(JSON.stringify(FAIL_SAFE) + "\n");
|
|
5882
|
+
process.exit(0);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
var FAIL_SAFE;
|
|
5886
|
+
var init_version_status = __esm({
|
|
5887
|
+
"src/cli/version-status.ts"() {
|
|
5888
|
+
"use strict";
|
|
5889
|
+
init_version();
|
|
5890
|
+
init_bump();
|
|
5891
|
+
init_flags();
|
|
5892
|
+
init_process_exit_signal();
|
|
5893
|
+
FAIL_SAFE = {
|
|
5894
|
+
installed: VERSION,
|
|
5895
|
+
latest: null,
|
|
5896
|
+
newer: false,
|
|
5897
|
+
packageManager: "npm",
|
|
5898
|
+
installCommand: "npm install codebyplan@latest",
|
|
5899
|
+
guarded: true,
|
|
5900
|
+
// Detection failed entirely — guard conservatively with an explicit reason
|
|
5901
|
+
// so consumers never see guarded:true paired with a null reason.
|
|
5902
|
+
guardReason: "unknown"
|
|
5903
|
+
};
|
|
5904
|
+
}
|
|
5905
|
+
});
|
|
5906
|
+
|
|
5738
5907
|
// src/cli/cmux-sync.ts
|
|
5739
5908
|
var cmux_sync_exports = {};
|
|
5740
5909
|
__export(cmux_sync_exports, {
|
|
5741
5910
|
runCmuxSync: () => runCmuxSync
|
|
5742
5911
|
});
|
|
5743
|
-
import { execSync as
|
|
5912
|
+
import { execSync as execSync7, execFileSync as execFileSync2 } from "node:child_process";
|
|
5744
5913
|
import { basename } from "node:path";
|
|
5745
5914
|
async function runCmuxSync() {
|
|
5746
5915
|
try {
|
|
@@ -5750,14 +5919,14 @@ async function runCmuxSync() {
|
|
|
5750
5919
|
const bin = process.env.CMUX_BUNDLED_CLI_PATH || process.env.CMUX_CLAUDE_HOOK_CMUX_BIN || "cmux";
|
|
5751
5920
|
let branch = "";
|
|
5752
5921
|
try {
|
|
5753
|
-
branch =
|
|
5922
|
+
branch = execSync7("git rev-parse --abbrev-ref HEAD", {
|
|
5754
5923
|
encoding: "utf8"
|
|
5755
5924
|
}).trim();
|
|
5756
5925
|
} catch {
|
|
5757
5926
|
}
|
|
5758
5927
|
let folder = "";
|
|
5759
5928
|
try {
|
|
5760
|
-
const toplevel =
|
|
5929
|
+
const toplevel = execSync7("git rev-parse --show-toplevel", {
|
|
5761
5930
|
encoding: "utf8"
|
|
5762
5931
|
}).trim();
|
|
5763
5932
|
folder = basename(toplevel);
|
|
@@ -5765,7 +5934,7 @@ async function runCmuxSync() {
|
|
|
5765
5934
|
}
|
|
5766
5935
|
if (branch) {
|
|
5767
5936
|
try {
|
|
5768
|
-
|
|
5937
|
+
execFileSync2(bin, [
|
|
5769
5938
|
"workspace-action",
|
|
5770
5939
|
"--action",
|
|
5771
5940
|
"rename",
|
|
@@ -5777,7 +5946,7 @@ async function runCmuxSync() {
|
|
|
5777
5946
|
}
|
|
5778
5947
|
if (folder) {
|
|
5779
5948
|
try {
|
|
5780
|
-
|
|
5949
|
+
execFileSync2(bin, [
|
|
5781
5950
|
"workspace-action",
|
|
5782
5951
|
"--action",
|
|
5783
5952
|
"set-description",
|
|
@@ -5802,18 +5971,18 @@ var init_cmux_sync = __esm({
|
|
|
5802
5971
|
|
|
5803
5972
|
// src/lib/migrate-local-config.ts
|
|
5804
5973
|
import { mkdir as mkdir5, readFile as readFile14, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
|
|
5805
|
-
import { join as
|
|
5974
|
+
import { join as join20 } from "node:path";
|
|
5806
5975
|
function legacySharedPath(projectPath) {
|
|
5807
|
-
return
|
|
5976
|
+
return join20(projectPath, ".codebyplan.json");
|
|
5808
5977
|
}
|
|
5809
5978
|
function legacyLocalPath(projectPath) {
|
|
5810
|
-
return
|
|
5979
|
+
return join20(projectPath, ".codebyplan.local.json");
|
|
5811
5980
|
}
|
|
5812
5981
|
function newDirPath(projectPath) {
|
|
5813
|
-
return
|
|
5982
|
+
return join20(projectPath, ".codebyplan");
|
|
5814
5983
|
}
|
|
5815
5984
|
function sentinelPath(projectPath) {
|
|
5816
|
-
return
|
|
5985
|
+
return join20(projectPath, ".codebyplan", "repo.json");
|
|
5817
5986
|
}
|
|
5818
5987
|
async function statSafe(p) {
|
|
5819
5988
|
const { stat: stat2 } = await import("node:fs/promises");
|
|
@@ -5907,7 +6076,7 @@ async function runLocalMigration(projectPath) {
|
|
|
5907
6076
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
5908
6077
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
5909
6078
|
await writeFile11(
|
|
5910
|
-
|
|
6079
|
+
join20(projectPath, ".codebyplan", "repo.json"),
|
|
5911
6080
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
5912
6081
|
"utf-8"
|
|
5913
6082
|
);
|
|
@@ -5920,7 +6089,7 @@ async function runLocalMigration(projectPath) {
|
|
|
5920
6089
|
if ("port_allocations" in cfg)
|
|
5921
6090
|
serverJson.port_allocations = cfg.port_allocations;
|
|
5922
6091
|
await writeFile11(
|
|
5923
|
-
|
|
6092
|
+
join20(projectPath, ".codebyplan", "server.json"),
|
|
5924
6093
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
5925
6094
|
"utf-8"
|
|
5926
6095
|
);
|
|
@@ -5929,7 +6098,7 @@ async function runLocalMigration(projectPath) {
|
|
|
5929
6098
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
5930
6099
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
5931
6100
|
await writeFile11(
|
|
5932
|
-
|
|
6101
|
+
join20(projectPath, ".codebyplan", "git.json"),
|
|
5933
6102
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
5934
6103
|
"utf-8"
|
|
5935
6104
|
);
|
|
@@ -5937,35 +6106,35 @@ async function runLocalMigration(projectPath) {
|
|
|
5937
6106
|
const shipmentJson = {};
|
|
5938
6107
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
5939
6108
|
await writeFile11(
|
|
5940
|
-
|
|
6109
|
+
join20(projectPath, ".codebyplan", "shipment.json"),
|
|
5941
6110
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
5942
6111
|
"utf-8"
|
|
5943
6112
|
);
|
|
5944
6113
|
filesChanged.push(".codebyplan/shipment.json");
|
|
5945
6114
|
const vendorJson = {};
|
|
5946
6115
|
await writeFile11(
|
|
5947
|
-
|
|
6116
|
+
join20(projectPath, ".codebyplan", "vendor.json"),
|
|
5948
6117
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
5949
6118
|
"utf-8"
|
|
5950
6119
|
);
|
|
5951
6120
|
filesChanged.push(".codebyplan/vendor.json");
|
|
5952
6121
|
const e2eJson = {};
|
|
5953
6122
|
await writeFile11(
|
|
5954
|
-
|
|
6123
|
+
join20(projectPath, ".codebyplan", "e2e.json"),
|
|
5955
6124
|
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
5956
6125
|
"utf-8"
|
|
5957
6126
|
);
|
|
5958
6127
|
filesChanged.push(".codebyplan/e2e.json");
|
|
5959
6128
|
const eslintJson = {};
|
|
5960
6129
|
await writeFile11(
|
|
5961
|
-
|
|
6130
|
+
join20(projectPath, ".codebyplan", "eslint.json"),
|
|
5962
6131
|
JSON.stringify(eslintJson, null, 2) + "\n",
|
|
5963
6132
|
"utf-8"
|
|
5964
6133
|
);
|
|
5965
6134
|
filesChanged.push(".codebyplan/eslint.json");
|
|
5966
6135
|
if (!deviceWrittenByHelper) {
|
|
5967
6136
|
await writeFile11(
|
|
5968
|
-
|
|
6137
|
+
join20(projectPath, ".codebyplan", "device.local.json"),
|
|
5969
6138
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
5970
6139
|
"utf-8"
|
|
5971
6140
|
);
|
|
@@ -5977,7 +6146,7 @@ async function runLocalMigration(projectPath) {
|
|
|
5977
6146
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
5978
6147
|
);
|
|
5979
6148
|
}
|
|
5980
|
-
const gitignorePath =
|
|
6149
|
+
const gitignorePath = join20(projectPath, ".gitignore");
|
|
5981
6150
|
try {
|
|
5982
6151
|
const gitignoreContent = await readFile14(gitignorePath, "utf-8");
|
|
5983
6152
|
const legacyLine = ".codebyplan.local.json";
|
|
@@ -6039,7 +6208,7 @@ __export(config_exports, {
|
|
|
6039
6208
|
runConfig: () => runConfig
|
|
6040
6209
|
});
|
|
6041
6210
|
import { mkdir as mkdir6, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
6042
|
-
import { join as
|
|
6211
|
+
import { join as join21 } from "node:path";
|
|
6043
6212
|
async function runConfig() {
|
|
6044
6213
|
const flags = parseFlags(3);
|
|
6045
6214
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -6072,14 +6241,14 @@ async function runConfig() {
|
|
|
6072
6241
|
console.log("\n Config complete.\n");
|
|
6073
6242
|
}
|
|
6074
6243
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
6075
|
-
const codebyplanDir =
|
|
6244
|
+
const codebyplanDir = join21(projectPath, ".codebyplan");
|
|
6076
6245
|
let resolvedWorktreeId;
|
|
6077
6246
|
try {
|
|
6078
6247
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
6079
6248
|
let branch = "main";
|
|
6080
6249
|
try {
|
|
6081
|
-
const { execSync:
|
|
6082
|
-
branch =
|
|
6250
|
+
const { execSync: execSync8 } = await import("node:child_process");
|
|
6251
|
+
branch = execSync8("git symbolic-ref --short HEAD", {
|
|
6083
6252
|
cwd: projectPath,
|
|
6084
6253
|
encoding: "utf-8"
|
|
6085
6254
|
}).trim();
|
|
@@ -6215,7 +6384,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
6215
6384
|
];
|
|
6216
6385
|
let anyUpdated = false;
|
|
6217
6386
|
for (const { name, payload, createOnly } of files) {
|
|
6218
|
-
const filePath =
|
|
6387
|
+
const filePath = join21(codebyplanDir, name);
|
|
6219
6388
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
6220
6389
|
let currentJson = "";
|
|
6221
6390
|
try {
|
|
@@ -6235,7 +6404,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
6235
6404
|
async function readRepoConfig(projectPath) {
|
|
6236
6405
|
try {
|
|
6237
6406
|
const raw = await readFile15(
|
|
6238
|
-
|
|
6407
|
+
join21(projectPath, ".codebyplan", "repo.json"),
|
|
6239
6408
|
"utf-8"
|
|
6240
6409
|
);
|
|
6241
6410
|
return JSON.parse(raw);
|
|
@@ -6246,7 +6415,7 @@ async function readRepoConfig(projectPath) {
|
|
|
6246
6415
|
async function readServerConfig(projectPath) {
|
|
6247
6416
|
try {
|
|
6248
6417
|
const raw = await readFile15(
|
|
6249
|
-
|
|
6418
|
+
join21(projectPath, ".codebyplan", "server.json"),
|
|
6250
6419
|
"utf-8"
|
|
6251
6420
|
);
|
|
6252
6421
|
return JSON.parse(raw);
|
|
@@ -6257,7 +6426,7 @@ async function readServerConfig(projectPath) {
|
|
|
6257
6426
|
async function readGitConfig(projectPath) {
|
|
6258
6427
|
try {
|
|
6259
6428
|
const raw = await readFile15(
|
|
6260
|
-
|
|
6429
|
+
join21(projectPath, ".codebyplan", "git.json"),
|
|
6261
6430
|
"utf-8"
|
|
6262
6431
|
);
|
|
6263
6432
|
return JSON.parse(raw);
|
|
@@ -6268,7 +6437,7 @@ async function readGitConfig(projectPath) {
|
|
|
6268
6437
|
async function readShipmentConfig(projectPath) {
|
|
6269
6438
|
try {
|
|
6270
6439
|
const raw = await readFile15(
|
|
6271
|
-
|
|
6440
|
+
join21(projectPath, ".codebyplan", "shipment.json"),
|
|
6272
6441
|
"utf-8"
|
|
6273
6442
|
);
|
|
6274
6443
|
return JSON.parse(raw);
|
|
@@ -6279,7 +6448,7 @@ async function readShipmentConfig(projectPath) {
|
|
|
6279
6448
|
async function readVendorConfig(projectPath) {
|
|
6280
6449
|
try {
|
|
6281
6450
|
const raw = await readFile15(
|
|
6282
|
-
|
|
6451
|
+
join21(projectPath, ".codebyplan", "vendor.json"),
|
|
6283
6452
|
"utf-8"
|
|
6284
6453
|
);
|
|
6285
6454
|
return JSON.parse(raw);
|
|
@@ -6290,7 +6459,7 @@ async function readVendorConfig(projectPath) {
|
|
|
6290
6459
|
async function readE2eConfig(projectPath) {
|
|
6291
6460
|
try {
|
|
6292
6461
|
const raw = await readFile15(
|
|
6293
|
-
|
|
6462
|
+
join21(projectPath, ".codebyplan", "e2e.json"),
|
|
6294
6463
|
"utf-8"
|
|
6295
6464
|
);
|
|
6296
6465
|
return JSON.parse(raw);
|
|
@@ -6562,7 +6731,7 @@ __export(tech_stack_exports, {
|
|
|
6562
6731
|
runFullTechStack: () => runFullTechStack,
|
|
6563
6732
|
runTechStack: () => runTechStack
|
|
6564
6733
|
});
|
|
6565
|
-
import { existsSync as
|
|
6734
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
6566
6735
|
async function runTechStack() {
|
|
6567
6736
|
const flags = parseFlags(3);
|
|
6568
6737
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -6624,10 +6793,10 @@ async function runTechStack() {
|
|
|
6624
6793
|
);
|
|
6625
6794
|
}
|
|
6626
6795
|
try {
|
|
6627
|
-
const { execSync:
|
|
6796
|
+
const { execSync: execSync8 } = await import("node:child_process");
|
|
6628
6797
|
let branch = "main";
|
|
6629
6798
|
try {
|
|
6630
|
-
branch =
|
|
6799
|
+
branch = execSync8("git symbolic-ref --short HEAD", {
|
|
6631
6800
|
cwd: projectPath,
|
|
6632
6801
|
encoding: "utf-8"
|
|
6633
6802
|
}).trim();
|
|
@@ -6735,7 +6904,7 @@ async function runFullTechStack(dryRun) {
|
|
|
6735
6904
|
continue;
|
|
6736
6905
|
}
|
|
6737
6906
|
const localWorktrees = worktrees.filter(
|
|
6738
|
-
(wt) => wt.path ?
|
|
6907
|
+
(wt) => wt.path ? existsSync5(wt.path) : false
|
|
6739
6908
|
);
|
|
6740
6909
|
if (localWorktrees.length === 0) {
|
|
6741
6910
|
console.log(` skipping ${repo.name} \u2014 no local worktree on this device`);
|
|
@@ -7418,13 +7587,13 @@ var init_uninstall = __esm({
|
|
|
7418
7587
|
|
|
7419
7588
|
// src/index.ts
|
|
7420
7589
|
init_version();
|
|
7421
|
-
import { readFileSync as
|
|
7590
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
7422
7591
|
import { resolve as resolve6 } from "node:path";
|
|
7423
7592
|
void (async () => {
|
|
7424
7593
|
if (!process.env.CODEBYPLAN_API_KEY) {
|
|
7425
7594
|
try {
|
|
7426
7595
|
const envPath = resolve6(process.cwd(), ".env.local");
|
|
7427
|
-
const content =
|
|
7596
|
+
const content = readFileSync8(envPath, "utf-8");
|
|
7428
7597
|
for (const line of content.split("\n")) {
|
|
7429
7598
|
const trimmed = line.trim();
|
|
7430
7599
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -7536,6 +7705,11 @@ void (async () => {
|
|
|
7536
7705
|
await runResolveWorktree2();
|
|
7537
7706
|
process.exit(0);
|
|
7538
7707
|
}
|
|
7708
|
+
if (arg === "version-status") {
|
|
7709
|
+
const { runVersionStatus: runVersionStatus2 } = await Promise.resolve().then(() => (init_version_status(), version_status_exports));
|
|
7710
|
+
await runVersionStatus2();
|
|
7711
|
+
process.exit(0);
|
|
7712
|
+
}
|
|
7539
7713
|
if (arg === "cmux-sync") {
|
|
7540
7714
|
const { runCmuxSync: runCmuxSync2 } = await Promise.resolve().then(() => (init_cmux_sync(), cmux_sync_exports));
|
|
7541
7715
|
await runCmuxSync2();
|
|
@@ -7639,6 +7813,7 @@ void (async () => {
|
|
|
7639
7813
|
codebyplan claude Claude asset management (install/update/uninstall)
|
|
7640
7814
|
codebyplan statusline Show or set the statusline renderer (bash/node/python)
|
|
7641
7815
|
codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
|
|
7816
|
+
codebyplan version-status Report installed vs latest version + update guard (JSON)
|
|
7642
7817
|
codebyplan cmux-sync Sync cmux workspace title/description to current git branch and repo folder
|
|
7643
7818
|
codebyplan help Show this help message
|
|
7644
7819
|
codebyplan --version Print version
|
package/package.json
CHANGED
|
@@ -149,6 +149,7 @@
|
|
|
149
149
|
"mcp__codebyplan__get_worktrees",
|
|
150
150
|
"mcp__codebyplan__list_tech_stack_sync_sessions",
|
|
151
151
|
"mcp__codebyplan__get_chunk",
|
|
152
|
+
"mcp__codebyplan__get_library_toc",
|
|
152
153
|
"mcp__codebyplan__list_migrations",
|
|
153
154
|
"mcp__codebyplan__lookup_symbol",
|
|
154
155
|
"mcp__codebyplan__resolve_library_id",
|
|
@@ -180,6 +181,8 @@
|
|
|
180
181
|
"Bash(npx codebyplan resolve-worktree:*)",
|
|
181
182
|
"Bash(codebyplan cmux-sync:*)",
|
|
182
183
|
"Bash(npx codebyplan cmux-sync:*)",
|
|
184
|
+
"Bash(codebyplan version-status:*)",
|
|
185
|
+
"Bash(npx codebyplan version-status:*)",
|
|
183
186
|
"Bash(codebyplan statusline:*)",
|
|
184
187
|
"Bash(npx codebyplan statusline:*)",
|
|
185
188
|
"Bash(codebyplan ports:*)",
|
|
@@ -25,7 +25,7 @@ Always write a session log for this session — **even if empty**. `/cbp-session
|
|
|
25
25
|
|
|
26
26
|
### Step 1.3: Capture Handoff Snapshot
|
|
27
27
|
|
|
28
|
-
Snapshot the current next-action so the next `/cbp-session-start` (Step 4.5) can auto-resume.
|
|
28
|
+
Snapshot the current next-action so the next `/cbp-session-start` (Step 4.5) can auto-resume. The handoff write-path + payload shape are specified inline here and in `/cbp-session-start` Step 4.5 (freshness gate).
|
|
29
29
|
|
|
30
30
|
1. Call MCP `get_next_action({ repo_id, worktree_id })`.
|
|
31
31
|
2. If the returned `command` is non-empty (active work in flight):
|
|
@@ -60,7 +60,8 @@ Same rule as `/cbp-session-start` Step 5.7 — only commit files that are **not*
|
|
|
60
60
|
- Collect `files[]` from those rounds → `task_files` set
|
|
61
61
|
- If no active task exists, `task_files` is empty
|
|
62
62
|
3. `infra_files = changed_files − task_files`
|
|
63
|
-
4.
|
|
63
|
+
4. Re-run `git status --porcelain` immediately before showing the commit-prompt (after Steps 1–1.3 have completed their MCP round-trips). Recompute `infra_files` from the fresh listing — eliminates the race where files appear in the index only after the network round-trips complete.
|
|
64
|
+
5. If `infra_files` is empty → skip. Otherwise present once:
|
|
64
65
|
|
|
65
66
|
```
|
|
66
67
|
Commit these non-task files before ending session?
|
|
@@ -69,7 +70,7 @@ Same rule as `/cbp-session-start` Step 5.7 — only commit files that are **not*
|
|
|
69
70
|
Reply: yes | no | select
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
6. On `yes`: `git add` the listed files, then trigger `/cbp-git-commit`.
|
|
73
74
|
On `no`: skip. On `select`: ask which subset.
|
|
74
75
|
|
|
75
76
|
Non-blocking — session end proceeds either way.
|
|
@@ -78,7 +79,7 @@ Non-blocking — session end proceeds either way.
|
|
|
78
79
|
|
|
79
80
|
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
|
|
|
81
|
-
Runs after Step 1.5 so any infra commits land first, and before Step 1.7 so the asset
|
|
82
|
+
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
|
|
|
83
84
|
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`. Then compare the current branch against the worktree's folder name:
|
|
84
85
|
|
|
@@ -99,83 +100,35 @@ CURRENT="$(git rev-parse --abbrev-ref HEAD)"
|
|
|
99
100
|
|
|
100
101
|
Never rebase, reset, force-push, or stash. A non-fast-forwardable home branch is a signal to reconcile manually, not to overwrite.
|
|
101
102
|
|
|
102
|
-
### Step 1.7:
|
|
103
|
+
### Step 1.7: Package Freshness Gate
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
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.6 home-branch fast-forward (so any update lands on the freshest tree) 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.
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Run the latest published CLI's asset-update verb to pull any skill / agent / hook changes:
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
npx codebyplan@latest claude update
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Always run — do not skip, even when the current repo is the canonical-owner monorepo source.
|
|
115
|
-
|
|
116
|
-
The command may pause for interactive prompts when:
|
|
117
|
-
|
|
118
|
-
- a tracked file has been hand-edited locally (overwrite / skip / diff)
|
|
119
|
-
- a new file is being shipped for the first time (opt-in / skip)
|
|
120
|
-
- a file has been removed from the package (remove / keep)
|
|
121
|
-
|
|
122
|
-
Respond to each prompt in the terminal as it appears. The commit prompt below runs only after all per-file prompts are resolved.
|
|
123
|
-
|
|
124
|
-
Failure handling:
|
|
125
|
-
|
|
126
|
-
- On non-zero exit: wait ~5 s, retry once.
|
|
127
|
-
- If retry also fails: print a warning and continue — this step is non-blocking.
|
|
128
|
-
|
|
129
|
-
After the command exits (success):
|
|
130
|
-
|
|
131
|
-
1. Run `git status --porcelain -- .claude/` to detect any file changes.
|
|
132
|
-
2. If non-empty, present once (same pattern as Step 1.5):
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
.claude/ was updated. Commit these changes?
|
|
136
|
-
[list of changed paths under .claude/]
|
|
137
|
-
|
|
138
|
-
Reply: yes | no | select
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
On `yes`: `git add` the listed paths only (not the whole `.claude/` directory), then trigger `/cbp-git-commit`.
|
|
142
|
-
On `no`: skip. On `select`: ask which subset.
|
|
143
|
-
|
|
144
|
-
3. Print the final stdout line from the update command as a one-line session summary (e.g. `codebyplan claude update: 47 files tracked.`). Silent when the command produces no stdout.
|
|
145
|
-
|
|
146
|
-
Non-blocking — session end proceeds regardless of outcome.
|
|
147
|
-
|
|
148
|
-
**b) `codebyplan` (project CLI)**
|
|
149
|
-
|
|
150
|
-
Fetch the latest published version of the project CLI. The `codebyplan` CLI has no `update` verb (subcommands are `setup`, `sync`, `eslint`); the freshness idiom is to invoke a fast read-only flag with `@latest` so npx fetches and caches the newest published version:
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
npx codebyplan@latest --version
|
|
107
|
+
```bash
|
|
108
|
+
VERSION_JSON=$(npx codebyplan version-status 2>/dev/null)
|
|
154
109
|
```
|
|
155
110
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
Failure handling:
|
|
159
|
-
|
|
160
|
-
- On non-zero exit: wait ~5 s, retry once.
|
|
161
|
-
- If retry also fails: print a warning and continue — this step is non-blocking.
|
|
162
|
-
|
|
163
|
-
After the command exits (success):
|
|
111
|
+
Parse `$VERSION_JSON` as JSON and branch on the result:
|
|
164
112
|
|
|
165
|
-
|
|
166
|
-
2.
|
|
113
|
+
- **Probe failed / unparseable** — the command errored, produced no output, or the output cannot be parsed as JSON carrying the required keys (`newer`, `guarded`, `installCommand`). This includes an installed `codebyplan` too old to have the `version-status` subcommand (which prints `Unknown command`). → **FAIL-SAFE SKIP**: proceed silently to Step 2. A best-effort freshness probe must never disrupt session-end.
|
|
114
|
+
- **`guarded: true`** (protected/main branch OR the canonical `codebyplan` source monorepo — any `guardReason`) → skip silently, proceed to Step 2. Gate on the `guarded` boolean only; never branch on the specific `guardReason` string. This is the guard that prevents clobbering the canonical monorepo's ahead-of-published `.claude/` and blocks any update or commit on a protected branch.
|
|
115
|
+
- **`newer: false`** (already up to date) → skip silently, proceed to Step 2.
|
|
116
|
+
- **`newer: true` AND `guarded: false`** → run the update path:
|
|
117
|
+
1. Run the JSON's `installCommand` (e.g. `pnpm add codebyplan@latest`) to LOCALLY install `codebyplan@latest` via the repo's package manager. Never a global `-g` install.
|
|
118
|
+
2. Run `npx codebyplan claude update` to refresh the worktree's `.claude/` assets on the current branch. If `installCommand` (step 1) or this command exits non-zero, warn once and skip to Step 2 — this path is non-blocking.
|
|
119
|
+
3. Detect changes across BOTH asset directories in one pass: `git status --porcelain -- .claude/ .codebyplan/`. If non-empty, present a single combined offer (same pattern as Step 1.5):
|
|
167
120
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
121
|
+
```
|
|
122
|
+
codebyplan updated. Commit these changes before ending the session?
|
|
123
|
+
[list of changed paths under .claude/ and .codebyplan/]
|
|
171
124
|
|
|
172
|
-
|
|
173
|
-
|
|
125
|
+
Reply: yes | no | select
|
|
126
|
+
```
|
|
174
127
|
|
|
175
|
-
|
|
176
|
-
|
|
128
|
+
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.
|
|
129
|
+
4. 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 `guarded` check above.
|
|
177
130
|
|
|
178
|
-
|
|
131
|
+
**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 `version-status` returns `guarded:false` 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).
|
|
179
132
|
|
|
180
133
|
Non-blocking — session end proceeds regardless of outcome.
|
|
181
134
|
|
|
@@ -204,9 +157,9 @@ You can close this window.
|
|
|
204
157
|
## Integration
|
|
205
158
|
|
|
206
159
|
- **Triggered by**: user invocation (prompted by `/cbp-todo` when no work remains)
|
|
207
|
-
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.6 home-branch fast-forward), MCP `get_session_logs` (resolve current log), MCP `get_current_task`, MCP `get_rounds`, MCP `get_next_action` (Step 1.3 handoff snapshot)
|
|
160
|
+
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.6 home-branch fast-forward), MCP `get_session_logs` (resolve current log), MCP `get_current_task`, MCP `get_rounds`, MCP `get_next_action` (Step 1.3 handoff snapshot); `npx codebyplan version-status` (Step 1.7 package-freshness gate)
|
|
208
161
|
- **Writes**: MCP `update_session_log` (with `ended_at` + `handoff` per TASK-2 alias surface; or `create_session_log` fallback), MCP `update_session_state` (deactivate)
|
|
209
162
|
- **Spawns**: none
|
|
210
|
-
- **Triggers**: none at the skill-contract level.
|
|
163
|
+
- **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 `newer:true AND guarded:false` update path (committing changed `.claude/` and `.codebyplan/` paths).
|
|
211
164
|
- **Paired with**: `/cbp-session-start`
|
|
212
|
-
- **Pairs with**:
|
|
165
|
+
- **Pairs with**: `/cbp-session-start` Step 4.5 (handoff payload shape + freshness-gate contract; specified inline — no separate rule file)
|
|
@@ -12,32 +12,29 @@ Activate the session, open a fresh session log, and surface the previous log's p
|
|
|
12
12
|
|
|
13
13
|
## Instructions
|
|
14
14
|
|
|
15
|
-
Run Steps 0 through 5.8 silently (no intermediate output) — except Step 1.4 may surface a one-line fast-forward note or warning, Step 1.5 may surface a one-line infra-drift nudge, and Step 5.7 may surface an approval gate. (Step numbers are organizational labels; execution order is 0 → 1 → 1.4 → 1.5 →
|
|
15
|
+
Run Steps 0 through 5.8 silently (no intermediate output) — except Step 0 aborts the session on MCP failure, Step 1.4 may surface a one-line fast-forward note or warning, Step 1.5 may surface a one-line infra-drift nudge, Step 1.6 may run an install-and-halt path, 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.4 → 1.5 → 1.6 → 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.
|
|
16
16
|
|
|
17
|
-
### Step 0: MCP Health
|
|
17
|
+
### Step 0: MCP Health Gate
|
|
18
18
|
|
|
19
|
-
Call MCP `health_check` tool.
|
|
19
|
+
Call MCP `health_check` tool. **The MCP connection is vital — this is a hard gate.**
|
|
20
20
|
|
|
21
|
-
- **If succeeds**: Continue silently
|
|
22
|
-
- **If fails**:
|
|
21
|
+
- **If succeeds**: Continue silently to Step 1.
|
|
22
|
+
- **If fails**: Print the error below and **STOP the entire session-start**. Do NOT continue to Step 1 or any later step — no config load, no `update_session_state(activate)`, no `create_session_log`, no `/cbp-todo` trigger:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
|
|
25
|
+
✖ MCP connection failed — session-start aborted. Check:
|
|
26
26
|
1. Network connectivity
|
|
27
27
|
2. API key validity (CODEBYPLAN_API_KEY env var)
|
|
28
28
|
3. codebyplan.com availability
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Fix the connection, then run /cbp-session-start again.
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Continue to Step 1 (non-blocking).
|
|
34
|
-
|
|
35
33
|
### Step 1: Load Config
|
|
36
34
|
|
|
37
35
|
Read per-concern config files from the project root. Single load point for the session:
|
|
38
36
|
|
|
39
37
|
- `repo_id` (UUID) — from `.codebyplan/repo.json`, required for all MCP operations
|
|
40
|
-
- `server_port`, `server_type`, `auto_push_enabled` — from `.codebyplan/server.json`
|
|
41
38
|
- `git_branch` — from `.codebyplan/git.json`
|
|
42
39
|
|
|
43
40
|
Resolve `worktree_id` at runtime using the structured JSON form:
|
|
@@ -79,7 +76,7 @@ Never rebase, reset, force-push, or stash. A non-fast-forwardable home branch is
|
|
|
79
76
|
|
|
80
77
|
### Step 1.5: Infra Drift Check
|
|
81
78
|
|
|
82
|
-
Surface — never block — when this worktree's
|
|
79
|
+
Surface — never block — when this worktree's source-monorepo `.claude/` infra has fallen behind. Runs after Step 1.4 and may add one line to the Step 6 output (`$PRODUCTION` is the branch resolved in Step 1.4). Consumer-repo package-version freshness is handled by Step 1.6 (the freshness gate), not here:
|
|
83
80
|
|
|
84
81
|
- **Monorepo (concept A)** — both `packages/codebyplan-package/templates/` and `scripts/infra-drift.mjs` exist. Step 1.4 skips the fetch on a feat branch, so refresh `origin/$PRODUCTION` best-effort first, then run the reporter:
|
|
85
82
|
|
|
@@ -90,26 +87,46 @@ Surface — never block — when this worktree's CBP tooling has fallen behind.
|
|
|
90
87
|
|
|
91
88
|
The script self-guards (feat branch + behind > 0) and emits at most one `⚠ .claude/ infra is N behind — run /cbp-refresh-infra` line. Hold any output for Step 6.
|
|
92
89
|
|
|
93
|
-
- **
|
|
90
|
+
- **Neither** → skip silently.
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
LATEST="$(npm view codebyplan version 2>/dev/null)"
|
|
97
|
-
```
|
|
92
|
+
Fully non-blocking; every failure path falls through with no output.
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
### Step 1.6: Package Freshness Gate
|
|
100
95
|
|
|
101
|
-
-
|
|
96
|
+
Check whether a newer `codebyplan` is published and safe to auto-install on this worktree's current branch. Runs AFTER the infra-drift check (Step 1.5) and BEFORE session activation (Step 3).
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
VERSION_JSON=$(npx codebyplan version-status 2>/dev/null)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Parse `$VERSION_JSON` as JSON and branch on the result:
|
|
103
|
+
|
|
104
|
+
- **Probe failed / unparseable** — the command errored, produced no output, or the output cannot be parsed as JSON carrying the required keys (`newer`, `guarded`, `installCommand`). This includes an installed `codebyplan` too old to have the `version-status` subcommand (which prints `Unknown command`). → **FAIL-SAFE SKIP**: proceed silently to Step 3. Never disrupt a session over a best-effort freshness probe — the MCP gate (Step 0) is the only vital gate.
|
|
105
|
+
- **`guarded: true`** (protected/main branch OR the canonical `codebyplan` source monorepo — any `guardReason`) → skip silently, proceed to Step 3. Gate on the `guarded` boolean only; never branch on the specific `guardReason` string.
|
|
106
|
+
- **`newer: false`** (already up to date) → skip silently, proceed to Step 3.
|
|
107
|
+
- **`newer: true` AND `guarded: false`** → run the update path:
|
|
108
|
+
1. Run the JSON's `installCommand` (e.g. `pnpm add codebyplan@latest`) to LOCALLY install `codebyplan@latest` via the repo's package manager. Never a global `-g` install.
|
|
109
|
+
2. Run `npx codebyplan claude update` to refresh the worktree's `.claude/` assets on the current branch.
|
|
110
|
+
3. Detect changes across BOTH asset directories in one pass: `git status --porcelain -- .claude/ .codebyplan/`. If non-empty, offer the same commit gate as Step 5.7:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
codebyplan updated. Commit the resulting .claude/ and .codebyplan/ changes before exiting?
|
|
114
|
+
[list of changed paths under .claude/ and .codebyplan/]
|
|
115
|
+
|
|
116
|
+
Reply: yes | no | select
|
|
117
|
+
```
|
|
102
118
|
|
|
103
|
-
|
|
119
|
+
On `yes`: `git add` the listed paths only, then trigger `/cbp-git-commit`. On `no`: skip. On `select`: ask which subset.
|
|
120
|
+
4. **HALT** — do NOT proceed to Step 3. Print:
|
|
104
121
|
|
|
105
|
-
|
|
122
|
+
```
|
|
123
|
+
✓ codebyplan updated to {latest}. Start a FRESH Claude Code session
|
|
124
|
+
(run /clear or open a new window) so the updated .claude/ takes effect.
|
|
125
|
+
```
|
|
106
126
|
|
|
107
|
-
|
|
127
|
+
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.
|
|
108
128
|
|
|
109
|
-
|
|
110
|
-
2. Check if running: `lsof -ti:{PORT} 2>/dev/null`
|
|
111
|
-
3. If running, verify health: `curl -s -o /dev/null -w "%{http_code}" http://localhost:{PORT}/ --max-time 2`
|
|
112
|
-
4. If NOT running, note in output that user should start via desktop app
|
|
129
|
+
**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 `version-status` returns `guarded:false` 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.
|
|
113
130
|
|
|
114
131
|
### Step 3: Update Session State
|
|
115
132
|
|
|
@@ -136,7 +153,7 @@ Hold the new log's ID in context so `/cbp-session-end` can update the same recor
|
|
|
136
153
|
|
|
137
154
|
### Step 4.5: Handoff Auto-Resume Probe
|
|
138
155
|
|
|
139
|
-
Probe the most-recent closed session log for a structured handoff payload (
|
|
156
|
+
Probe the most-recent closed session log for a structured handoff payload (the handoff freshness-gate contract is specified inline in this step) and auto-resume directly into the captured command when fresh. Additive — placed BEFORE the existing `/cbp-todo` auto-trigger; ALL failure paths fall through silently to Step 7.
|
|
140
157
|
|
|
141
158
|
1. Reuse the row held from Step 4 (same `get_session_logs({ repo_id, worktree_id, limit: 1 })` call shape — no extra MCP round-trip).
|
|
142
159
|
2. **Defensive gates** (any failure → silent fall-through to Step 7):
|
|
@@ -205,7 +222,6 @@ Ownership: [total_count] active CHK(s), [owned_count] owned by this worktree
|
|
|
205
222
|
[Owned: CHK-NNN (title), … — only when owned_count > 0]
|
|
206
223
|
[Cross-worktree: CHK-ZZZ (name), … — only when total_count > owned_count]
|
|
207
224
|
|
|
208
|
-
[⚠ Dev server not running — start via desktop app — only if applicable]
|
|
209
225
|
[⚠ Worktree unregistered — run `npx codebyplan setup` to register — only when WORKTREE_ID is null and no resolver distress was already shown]
|
|
210
226
|
```
|
|
211
227
|
|
|
@@ -223,9 +239,9 @@ Three-branch gate using `owned_count` and `total_count` from Step 5.8:
|
|
|
223
239
|
|
|
224
240
|
- **Triggered by**: user invocation, `/clear` recovery
|
|
225
241
|
- **Resolves**: `npx codebyplan resolve-worktree --json` (worktree id + distress signal; non-tuple-miss distress is non-blocking at session-start)
|
|
226
|
-
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.4 home-branch fast-forward), MCP `get_session_logs` (worktree-filtered, limit 1 — single call shared by Step 4 and Step 4.5), MCP `health_check
|
|
227
|
-
- **Writes**: MCP `create_session_log` (new, possibly empty), MCP `update_session_state` (activate)
|
|
242
|
+
- **Reads**: `.codebyplan/repo.json`, `.codebyplan/git.json` (`branch_config.production` for the Step 1.4 home-branch fast-forward), MCP `get_session_logs` (worktree-filtered, limit 1 — single call shared by Step 4 and Step 4.5), MCP `health_check` (Step 0 hard gate), MCP `get_current_task`, MCP `get_rounds`, MCP `get_checkpoints` (two calls: `{ repo_id, status: 'active' }` for the Step 5.8 ownership partition; `{ repo_id }` unfiltered for the Step 4.5 freshness probe, which may resolve a non-active checkpoint), MCP `get_tasks` / `get_rounds` for the Step 4.5 freshness probe; `scripts/infra-drift.mjs` + a best-effort `git fetch` (Step 1.5 monorepo drift); `npx codebyplan version-status` (Step 1.6 package-freshness gate). Reads at Step 3 and later (session-state, session logs, checkpoints, tasks/rounds) do NOT fire on a Step 0 MCP hard-fail or the Step 1.6 update-and-halt path
|
|
243
|
+
- **Writes**: MCP `create_session_log` (new, possibly empty), MCP `update_session_state` (activate) — both SKIPPED on a Step 0 MCP hard-fail and on the Step 1.6 update-and-halt path
|
|
228
244
|
- **Spawns**: none
|
|
229
|
-
- **Triggers**: `/cbp-git-commit` (conditional, on user approval), `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)
|
|
245
|
+
- **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)
|
|
230
246
|
- **Paired with**: `/cbp-session-end`
|
|
231
|
-
- **Pairs with**:
|
|
247
|
+
- **Pairs with**: `/cbp-session-end` Step 1.3 (handoff write-path; the freshness-gate contract is specified inline in Step 4.5 above)
|