codebyplan 1.13.0 → 1.13.3
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 +2337 -1701
- package/package.json +2 -2
- package/templates/github-workflows/publish.yml +207 -0
- package/templates/settings.project.base.json +154 -3
- package/templates/skills/cbp-build-cc-settings/SKILL.md +2 -0
- package/templates/skills/cbp-build-cc-settings/reference/cbp-permission-policy.md +48 -0
- package/templates/skills/cbp-checkpoint-end/SKILL.md +7 -0
- package/templates/skills/cbp-ship/reference/versioning.md +31 -3
- package/templates/skills/cbp-ship-configure/SKILL.md +16 -36
- package/templates/skills/cbp-ship-configure/reference/npm-package.md +15 -6
- package/templates/skills/cbp-ship-main/SKILL.md +4 -0
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.3";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -225,8 +225,8 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
|
225
225
|
}
|
|
226
226
|
async function writeLocalConfig(projectPath, config) {
|
|
227
227
|
const content = { device_id: config.device_id };
|
|
228
|
-
const
|
|
229
|
-
const dirPath = dirname(
|
|
228
|
+
const path8 = localConfigPath(projectPath);
|
|
229
|
+
const dirPath = dirname(path8);
|
|
230
230
|
let phase = "stat config directory";
|
|
231
231
|
try {
|
|
232
232
|
try {
|
|
@@ -246,7 +246,7 @@ async function writeLocalConfig(projectPath, config) {
|
|
|
246
246
|
phase = "create config directory";
|
|
247
247
|
await mkdir(dirPath, { recursive: true });
|
|
248
248
|
phase = "write local config";
|
|
249
|
-
await writeFile2(
|
|
249
|
+
await writeFile2(path8, JSON.stringify(content, null, 2) + "\n", "utf-8");
|
|
250
250
|
} catch (err) {
|
|
251
251
|
const code = err.code;
|
|
252
252
|
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
@@ -467,12 +467,12 @@ async function readFallback(filename) {
|
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
469
|
async function writeFallback(filename, data) {
|
|
470
|
-
const
|
|
471
|
-
await mkdir3(dirname2(
|
|
472
|
-
await writeFile4(
|
|
470
|
+
const path8 = fallbackFile(filename);
|
|
471
|
+
await mkdir3(dirname2(path8), { recursive: true });
|
|
472
|
+
await writeFile4(path8, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
473
473
|
if (platform() !== "win32") {
|
|
474
474
|
try {
|
|
475
|
-
await chmod(
|
|
475
|
+
await chmod(path8, 384);
|
|
476
476
|
} catch {
|
|
477
477
|
}
|
|
478
478
|
}
|
|
@@ -679,8 +679,8 @@ async function getAuthHeaders() {
|
|
|
679
679
|
return { headers: { "x-api-key": key }, via: "api_key" };
|
|
680
680
|
}
|
|
681
681
|
}
|
|
682
|
-
function buildUrl(
|
|
683
|
-
const url = new URL(`${baseUrl()}/api${
|
|
682
|
+
function buildUrl(path8, params) {
|
|
683
|
+
const url = new URL(`${baseUrl()}/api${path8}`);
|
|
684
684
|
if (params) {
|
|
685
685
|
for (const [key, value] of Object.entries(params)) {
|
|
686
686
|
if (value !== void 0) {
|
|
@@ -697,10 +697,10 @@ function isRetryable(err) {
|
|
|
697
697
|
return false;
|
|
698
698
|
}
|
|
699
699
|
function delay(ms) {
|
|
700
|
-
return new Promise((
|
|
700
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
701
701
|
}
|
|
702
|
-
async function request(method,
|
|
703
|
-
const url = buildUrl(
|
|
702
|
+
async function request(method, path8, options) {
|
|
703
|
+
const url = buildUrl(path8, options?.params);
|
|
704
704
|
const auth = await getAuthHeaders();
|
|
705
705
|
let lastError;
|
|
706
706
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -720,7 +720,7 @@ async function request(method, path7, options) {
|
|
|
720
720
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
721
721
|
});
|
|
722
722
|
if (!res.ok) {
|
|
723
|
-
let message = `API ${method} ${
|
|
723
|
+
let message = `API ${method} ${path8} failed with status ${res.status}`;
|
|
724
724
|
let code;
|
|
725
725
|
try {
|
|
726
726
|
const body = await res.json();
|
|
@@ -754,14 +754,14 @@ async function request(method, path7, options) {
|
|
|
754
754
|
}
|
|
755
755
|
throw lastError;
|
|
756
756
|
}
|
|
757
|
-
async function apiGet(
|
|
758
|
-
return request("GET",
|
|
757
|
+
async function apiGet(path8, params) {
|
|
758
|
+
return request("GET", path8, { params });
|
|
759
759
|
}
|
|
760
|
-
async function apiPost(
|
|
761
|
-
return request("POST",
|
|
760
|
+
async function apiPost(path8, body) {
|
|
761
|
+
return request("POST", path8, { body });
|
|
762
762
|
}
|
|
763
|
-
async function apiPut(
|
|
764
|
-
return request("PUT",
|
|
763
|
+
async function apiPut(path8, body) {
|
|
764
|
+
return request("PUT", path8, { body });
|
|
765
765
|
}
|
|
766
766
|
async function callMcpTool(toolName, params) {
|
|
767
767
|
const url = mcpEndpoint();
|
|
@@ -1055,7 +1055,7 @@ var init_device_flow = __esm({
|
|
|
1055
1055
|
this.name = "OAuthInvalidClientError";
|
|
1056
1056
|
}
|
|
1057
1057
|
};
|
|
1058
|
-
defaultSleep = (ms) => new Promise((
|
|
1058
|
+
defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1059
1059
|
}
|
|
1060
1060
|
});
|
|
1061
1061
|
|
|
@@ -1217,13 +1217,9 @@ import { createInterface } from "node:readline/promises";
|
|
|
1217
1217
|
function getConfigPath(scope) {
|
|
1218
1218
|
return scope === "user" ? join6(homedir2(), ".claude.json") : join6(process.cwd(), ".mcp.json");
|
|
1219
1219
|
}
|
|
1220
|
-
function
|
|
1221
|
-
const baseUrl2 = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
1222
|
-
return `${baseUrl2.replace(/\/$/, "")}/mcp`;
|
|
1223
|
-
}
|
|
1224
|
-
async function readConfig(path7) {
|
|
1220
|
+
async function readConfig(path8) {
|
|
1225
1221
|
try {
|
|
1226
|
-
const raw = await readFile6(
|
|
1222
|
+
const raw = await readFile6(path8, "utf-8");
|
|
1227
1223
|
const parsed = JSON.parse(raw);
|
|
1228
1224
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1229
1225
|
return parsed;
|
|
@@ -1233,19 +1229,16 @@ async function readConfig(path7) {
|
|
|
1233
1229
|
return {};
|
|
1234
1230
|
}
|
|
1235
1231
|
}
|
|
1236
|
-
function buildMcpEntry(
|
|
1237
|
-
|
|
1238
|
-
return { url: mcpEndpoint() };
|
|
1239
|
-
}
|
|
1240
|
-
return { url: legacyMcpUrl(), headers: { "x-api-key": auth.apiKey } };
|
|
1232
|
+
function buildMcpEntry() {
|
|
1233
|
+
return { url: mcpEndpoint() };
|
|
1241
1234
|
}
|
|
1242
|
-
async function writeMcpConfig(scope
|
|
1235
|
+
async function writeMcpConfig(scope) {
|
|
1243
1236
|
const configPath = getConfigPath(scope);
|
|
1244
1237
|
const config = await readConfig(configPath);
|
|
1245
1238
|
if (typeof config.mcpServers !== "object" || config.mcpServers === null || Array.isArray(config.mcpServers)) {
|
|
1246
1239
|
config.mcpServers = {};
|
|
1247
1240
|
}
|
|
1248
|
-
config.mcpServers.codebyplan = buildMcpEntry(
|
|
1241
|
+
config.mcpServers.codebyplan = buildMcpEntry();
|
|
1249
1242
|
await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1250
1243
|
return configPath;
|
|
1251
1244
|
}
|
|
@@ -1395,7 +1388,7 @@ async function runSetup() {
|
|
|
1395
1388
|
const scopeInput = (await rl.question(" Select (1/2, default: 1): ")).trim();
|
|
1396
1389
|
const scope = scopeInput === "2" ? "project" : "user";
|
|
1397
1390
|
console.log("\n Configuring MCP server...");
|
|
1398
|
-
const configPath = await writeMcpConfig(scope
|
|
1391
|
+
const configPath = await writeMcpConfig(scope);
|
|
1399
1392
|
console.log(` Done! Config written to ${configPath}
|
|
1400
1393
|
`);
|
|
1401
1394
|
if (auth.kind === "legacy" && scope === "project") {
|
|
@@ -1768,9 +1761,9 @@ import { join as join8 } from "node:path";
|
|
|
1768
1761
|
function configPaths() {
|
|
1769
1762
|
return [join8(homedir3(), ".claude.json"), join8(process.cwd(), ".mcp.json")];
|
|
1770
1763
|
}
|
|
1771
|
-
async function readConfig2(
|
|
1764
|
+
async function readConfig2(path8) {
|
|
1772
1765
|
try {
|
|
1773
|
-
const raw = await readFile8(
|
|
1766
|
+
const raw = await readFile8(path8, "utf-8");
|
|
1774
1767
|
const parsed = JSON.parse(raw);
|
|
1775
1768
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1776
1769
|
return parsed;
|
|
@@ -1784,14 +1777,14 @@ function entryHasLegacyApiKey(entry) {
|
|
|
1784
1777
|
if (!entry || !entry.headers) return false;
|
|
1785
1778
|
return "x-api-key" in entry.headers;
|
|
1786
1779
|
}
|
|
1787
|
-
async function rewriteConfig(
|
|
1780
|
+
async function rewriteConfig(path8, config, newUrl) {
|
|
1788
1781
|
const servers = config.mcpServers;
|
|
1789
1782
|
if (!servers) return false;
|
|
1790
1783
|
const entry = servers.codebyplan;
|
|
1791
1784
|
if (!entry) return false;
|
|
1792
1785
|
if (!entryHasLegacyApiKey(entry) && entry.url === newUrl) return false;
|
|
1793
1786
|
servers.codebyplan = { url: newUrl };
|
|
1794
|
-
await writeFile7(
|
|
1787
|
+
await writeFile7(path8, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1795
1788
|
return true;
|
|
1796
1789
|
}
|
|
1797
1790
|
async function runUpgradeAuth() {
|
|
@@ -1799,12 +1792,12 @@ async function runUpgradeAuth() {
|
|
|
1799
1792
|
await runLogin();
|
|
1800
1793
|
const newUrl = mcpEndpoint();
|
|
1801
1794
|
let migrated = 0;
|
|
1802
|
-
for (const
|
|
1803
|
-
const config = await readConfig2(
|
|
1795
|
+
for (const path8 of configPaths()) {
|
|
1796
|
+
const config = await readConfig2(path8);
|
|
1804
1797
|
if (!config) continue;
|
|
1805
|
-
const changed = await rewriteConfig(
|
|
1798
|
+
const changed = await rewriteConfig(path8, config, newUrl);
|
|
1806
1799
|
if (changed) {
|
|
1807
|
-
console.log(` Updated ${
|
|
1800
|
+
console.log(` Updated ${path8}`);
|
|
1808
1801
|
migrated++;
|
|
1809
1802
|
}
|
|
1810
1803
|
}
|
|
@@ -3176,17 +3169,16 @@ var init_eslint = __esm({
|
|
|
3176
3169
|
|
|
3177
3170
|
// src/lib/mcp-client.ts
|
|
3178
3171
|
async function mcpCall(toolName, args) {
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
);
|
|
3172
|
+
let accessToken;
|
|
3173
|
+
try {
|
|
3174
|
+
accessToken = await getAccessToken();
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
if (err instanceof NoTokenError) {
|
|
3177
|
+
throw new McpError(
|
|
3178
|
+
"Not logged in. Run `codebyplan login` to authenticate."
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
throw err;
|
|
3190
3182
|
}
|
|
3191
3183
|
const body = {
|
|
3192
3184
|
jsonrpc: "2.0",
|
|
@@ -3196,12 +3188,12 @@ Or manually:
|
|
|
3196
3188
|
};
|
|
3197
3189
|
let res;
|
|
3198
3190
|
try {
|
|
3199
|
-
res = await fetch(
|
|
3191
|
+
res = await fetch(mcpEndpoint(), {
|
|
3200
3192
|
method: "POST",
|
|
3201
3193
|
headers: {
|
|
3202
3194
|
"Content-Type": "application/json",
|
|
3203
3195
|
Accept: "application/json, text/event-stream",
|
|
3204
|
-
|
|
3196
|
+
Authorization: `Bearer ${accessToken}`
|
|
3205
3197
|
},
|
|
3206
3198
|
body: JSON.stringify(body),
|
|
3207
3199
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
@@ -3212,7 +3204,7 @@ Or manually:
|
|
|
3212
3204
|
}
|
|
3213
3205
|
if (res.status === 401) {
|
|
3214
3206
|
throw new McpError(
|
|
3215
|
-
"
|
|
3207
|
+
"Authentication failed. Run `codebyplan login` to refresh your session.",
|
|
3216
3208
|
401
|
|
3217
3209
|
);
|
|
3218
3210
|
}
|
|
@@ -3271,13 +3263,12 @@ function parseSseEnvelope(text, toolName) {
|
|
|
3271
3263
|
}
|
|
3272
3264
|
throw new McpError(`mcp ${toolName} returned unparseable SSE body`);
|
|
3273
3265
|
}
|
|
3274
|
-
var
|
|
3266
|
+
var REQUEST_TIMEOUT_MS2, McpError;
|
|
3275
3267
|
var init_mcp_client = __esm({
|
|
3276
3268
|
"src/lib/mcp-client.ts"() {
|
|
3277
3269
|
"use strict";
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com").replace(/\/$/, "");
|
|
3270
|
+
init_token_refresh();
|
|
3271
|
+
init_urls();
|
|
3281
3272
|
REQUEST_TIMEOUT_MS2 = 12e4;
|
|
3282
3273
|
McpError = class extends Error {
|
|
3283
3274
|
status;
|
|
@@ -3440,7 +3431,7 @@ function setRetryDelayMs(ms) {
|
|
|
3440
3431
|
RETRY_DELAY_MS = ms;
|
|
3441
3432
|
}
|
|
3442
3433
|
function sleep(ms) {
|
|
3443
|
-
return new Promise((
|
|
3434
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
3444
3435
|
}
|
|
3445
3436
|
function isTransientMcpError(err) {
|
|
3446
3437
|
if (!(err instanceof McpError)) return false;
|
|
@@ -3917,17 +3908,10 @@ var init_branch = __esm({
|
|
|
3917
3908
|
}
|
|
3918
3909
|
});
|
|
3919
3910
|
|
|
3920
|
-
// src/lib/
|
|
3911
|
+
// src/lib/git-utils.ts
|
|
3921
3912
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
3922
3913
|
import { join as join13 } from "node:path";
|
|
3923
|
-
import {
|
|
3924
|
-
function assertValidBranchName2(branch, label) {
|
|
3925
|
-
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
3926
|
-
throw new Error(
|
|
3927
|
-
`Invalid branch name for ${label}: '${branch}' (refusing to pass shell metacharacters to git/gh)`
|
|
3928
|
-
);
|
|
3929
|
-
}
|
|
3930
|
-
}
|
|
3914
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3931
3915
|
async function readBaseBranch(cwd) {
|
|
3932
3916
|
const found = await findCodebyplanConfig(cwd);
|
|
3933
3917
|
let gitJsonPath;
|
|
@@ -3959,6 +3943,442 @@ async function readBaseBranch(cwd) {
|
|
|
3959
3943
|
return "main";
|
|
3960
3944
|
}
|
|
3961
3945
|
}
|
|
3946
|
+
function resolveBaseRef(cwd, baseBranch) {
|
|
3947
|
+
if (baseBranch.startsWith("origin/")) return baseBranch;
|
|
3948
|
+
const remoteRef = `origin/${baseBranch}`;
|
|
3949
|
+
const check = spawnSync2(
|
|
3950
|
+
"git",
|
|
3951
|
+
["rev-parse", "--verify", "--quiet", `${remoteRef}^{commit}`],
|
|
3952
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
3953
|
+
);
|
|
3954
|
+
if (check.status === 0 && (check.stdout ?? "").trim().length > 0) {
|
|
3955
|
+
return remoteRef;
|
|
3956
|
+
}
|
|
3957
|
+
return baseBranch;
|
|
3958
|
+
}
|
|
3959
|
+
var init_git_utils = __esm({
|
|
3960
|
+
"src/lib/git-utils.ts"() {
|
|
3961
|
+
"use strict";
|
|
3962
|
+
init_flags();
|
|
3963
|
+
}
|
|
3964
|
+
});
|
|
3965
|
+
|
|
3966
|
+
// src/lib/bump.ts
|
|
3967
|
+
import { readFile as readFile13, writeFile as writeFile10, access as access4, readdir as readdir3 } from "node:fs/promises";
|
|
3968
|
+
import { join as join14, relative as relative3, resolve as resolve2 } from "node:path";
|
|
3969
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3970
|
+
function parsePnpmWorkspaceGlobs(raw) {
|
|
3971
|
+
const lines = raw.split("\n");
|
|
3972
|
+
const globs = [];
|
|
3973
|
+
let inPackages = false;
|
|
3974
|
+
for (const line of lines) {
|
|
3975
|
+
const trimmed = line.trim();
|
|
3976
|
+
if (trimmed === "packages:") {
|
|
3977
|
+
inPackages = true;
|
|
3978
|
+
continue;
|
|
3979
|
+
}
|
|
3980
|
+
if (inPackages) {
|
|
3981
|
+
if (trimmed.length > 0 && !trimmed.startsWith("-")) {
|
|
3982
|
+
inPackages = false;
|
|
3983
|
+
continue;
|
|
3984
|
+
}
|
|
3985
|
+
if (trimmed.startsWith("-")) {
|
|
3986
|
+
const glob = trimmed.slice(1).trim().replace(/^['"]|['"]$/g, "");
|
|
3987
|
+
if (glob) globs.push(glob);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
return globs;
|
|
3992
|
+
}
|
|
3993
|
+
async function expandGlob(cwd, glob) {
|
|
3994
|
+
const parts = glob.split("/");
|
|
3995
|
+
if (parts.length !== 2 || parts[1] !== "*") {
|
|
3996
|
+
return [];
|
|
3997
|
+
}
|
|
3998
|
+
const parentDir = join14(cwd, parts[0]);
|
|
3999
|
+
let dirs;
|
|
4000
|
+
try {
|
|
4001
|
+
const entries = await readdir3(parentDir, { withFileTypes: true });
|
|
4002
|
+
dirs = entries.filter((e) => e.isDirectory()).map((e) => join14(parentDir, e.name));
|
|
4003
|
+
} catch {
|
|
4004
|
+
return [];
|
|
4005
|
+
}
|
|
4006
|
+
const results = [];
|
|
4007
|
+
for (const dir of dirs) {
|
|
4008
|
+
try {
|
|
4009
|
+
await access4(join14(dir, "package.json"));
|
|
4010
|
+
results.push(dir);
|
|
4011
|
+
} catch {
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
return results;
|
|
4015
|
+
}
|
|
4016
|
+
async function buildPackageMap(cwd) {
|
|
4017
|
+
const map = /* @__PURE__ */ new Map();
|
|
4018
|
+
let globs = [];
|
|
4019
|
+
try {
|
|
4020
|
+
const raw = await readFile13(join14(cwd, "pnpm-workspace.yaml"), "utf-8");
|
|
4021
|
+
globs = parsePnpmWorkspaceGlobs(raw);
|
|
4022
|
+
} catch {
|
|
4023
|
+
}
|
|
4024
|
+
for (const glob of globs) {
|
|
4025
|
+
const dirs = await expandGlob(cwd, glob);
|
|
4026
|
+
for (const dir of dirs) {
|
|
4027
|
+
try {
|
|
4028
|
+
const pkgRaw = await readFile13(join14(dir, "package.json"), "utf-8");
|
|
4029
|
+
const pkg = JSON.parse(pkgRaw);
|
|
4030
|
+
const name = pkg.name ?? relative3(cwd, dir);
|
|
4031
|
+
map.set(dir, { name, dir });
|
|
4032
|
+
} catch {
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
return map;
|
|
4037
|
+
}
|
|
4038
|
+
function findOwningPackage(filePath, packageDirs) {
|
|
4039
|
+
const sorted = [...packageDirs].sort((a, b) => b.length - a.length);
|
|
4040
|
+
for (const dir of sorted) {
|
|
4041
|
+
if (filePath.startsWith(dir + "/") || filePath === dir) {
|
|
4042
|
+
return dir;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return null;
|
|
4046
|
+
}
|
|
4047
|
+
function patchBump(version) {
|
|
4048
|
+
const hasV = version.startsWith("v");
|
|
4049
|
+
const stripped = hasV ? version.slice(1) : version;
|
|
4050
|
+
const coreMatch = stripped.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
4051
|
+
if (!coreMatch) {
|
|
4052
|
+
return version;
|
|
4053
|
+
}
|
|
4054
|
+
const major = coreMatch[1];
|
|
4055
|
+
const minor = coreMatch[2];
|
|
4056
|
+
const patch = parseInt(coreMatch[3], 10);
|
|
4057
|
+
return `${hasV ? "v" : ""}${major}.${minor}.${patch + 1}`;
|
|
4058
|
+
}
|
|
4059
|
+
function compareSemver(a, b) {
|
|
4060
|
+
const parseCore = (v) => {
|
|
4061
|
+
const stripped = v.startsWith("v") ? v.slice(1) : v;
|
|
4062
|
+
const m = stripped.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
4063
|
+
if (!m) return [-1, -1, -1];
|
|
4064
|
+
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
4065
|
+
};
|
|
4066
|
+
const hasPre = (v) => /^v?\d+\.\d+\.\d+-/.test(v);
|
|
4067
|
+
const [aMaj, aMin, aPat] = parseCore(a);
|
|
4068
|
+
const [bMaj, bMin, bPat] = parseCore(b);
|
|
4069
|
+
if (aMaj !== bMaj) return aMaj - bMaj;
|
|
4070
|
+
if (aMin !== bMin) return aMin - bMin;
|
|
4071
|
+
if (aPat !== bPat) return aPat - bPat;
|
|
4072
|
+
if (hasPre(a) && !hasPre(b)) return -1;
|
|
4073
|
+
if (!hasPre(a) && hasPre(b)) return 1;
|
|
4074
|
+
return 0;
|
|
4075
|
+
}
|
|
4076
|
+
function gitShowFile(cwd, ref, relPath) {
|
|
4077
|
+
const result = spawnSync3("git", ["show", `${ref}:${relPath}`], {
|
|
4078
|
+
cwd,
|
|
4079
|
+
encoding: "utf-8",
|
|
4080
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4081
|
+
});
|
|
4082
|
+
if (result.status !== 0 || result.error) return null;
|
|
4083
|
+
return result.stdout ?? null;
|
|
4084
|
+
}
|
|
4085
|
+
function extractVersion(raw, filePath) {
|
|
4086
|
+
try {
|
|
4087
|
+
const parsed = JSON.parse(raw);
|
|
4088
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
4089
|
+
const obj = parsed;
|
|
4090
|
+
if (filePath.endsWith("app.json") || filePath.endsWith("app.config.json")) {
|
|
4091
|
+
const expo = obj["expo"];
|
|
4092
|
+
if (typeof expo === "object" && expo !== null) {
|
|
4093
|
+
const expoObj = expo;
|
|
4094
|
+
if (typeof expoObj["version"] === "string") return expoObj["version"];
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
if (typeof obj["version"] === "string") return obj["version"];
|
|
4098
|
+
return null;
|
|
4099
|
+
} catch {
|
|
4100
|
+
return null;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
function injectVersion(raw, nextVersion, filePath) {
|
|
4104
|
+
const parsed = JSON.parse(raw);
|
|
4105
|
+
if (filePath.endsWith("app.json") || filePath.endsWith("app.config.json")) {
|
|
4106
|
+
const expo = parsed["expo"];
|
|
4107
|
+
if (typeof expo === "object" && expo !== null) {
|
|
4108
|
+
expo["version"] = nextVersion;
|
|
4109
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
parsed["version"] = nextVersion;
|
|
4113
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
4114
|
+
}
|
|
4115
|
+
async function prependChangelog(changelogPath, packageName, nextVersion, now, dryRun) {
|
|
4116
|
+
let existing;
|
|
4117
|
+
try {
|
|
4118
|
+
existing = await readFile13(changelogPath, "utf-8");
|
|
4119
|
+
} catch {
|
|
4120
|
+
return false;
|
|
4121
|
+
}
|
|
4122
|
+
const entry = `## [${nextVersion}] - ${now}
|
|
4123
|
+
|
|
4124
|
+
### Changed
|
|
4125
|
+
|
|
4126
|
+
- Version bump for ${packageName}
|
|
4127
|
+
|
|
4128
|
+
`;
|
|
4129
|
+
const updated = entry + existing;
|
|
4130
|
+
if (!dryRun) {
|
|
4131
|
+
await writeFile10(changelogPath, updated, "utf-8");
|
|
4132
|
+
}
|
|
4133
|
+
return true;
|
|
4134
|
+
}
|
|
4135
|
+
async function runBump(opts) {
|
|
4136
|
+
const cwd = resolve2(opts?.cwd ?? process.cwd());
|
|
4137
|
+
const dryRun = opts?.dryRun ?? false;
|
|
4138
|
+
const now = opts?.now ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4139
|
+
const baseBranch = await readBaseBranch(cwd);
|
|
4140
|
+
const baseRef = resolveBaseRef(cwd, baseBranch);
|
|
4141
|
+
const diffResult = spawnSync3(
|
|
4142
|
+
"git",
|
|
4143
|
+
["diff", "--name-only", `${baseRef}...HEAD`],
|
|
4144
|
+
{
|
|
4145
|
+
cwd,
|
|
4146
|
+
encoding: "utf-8",
|
|
4147
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4148
|
+
}
|
|
4149
|
+
);
|
|
4150
|
+
if (diffResult.status !== 0 || diffResult.error) {
|
|
4151
|
+
const errMsg = diffResult.stderr?.trim() || diffResult.error?.message || "git diff failed";
|
|
4152
|
+
throw new Error(`runBump: git diff failed: ${errMsg}`);
|
|
4153
|
+
}
|
|
4154
|
+
const changedFiles = (diffResult.stdout ?? "").trim().split("\n").filter(Boolean).map((f) => resolve2(cwd, f));
|
|
4155
|
+
const packageMap = await buildPackageMap(cwd);
|
|
4156
|
+
const packageDirs = Array.from(packageMap.keys());
|
|
4157
|
+
const rootPkgPath = join14(cwd, "package.json");
|
|
4158
|
+
const changedPackageDirs = /* @__PURE__ */ new Set();
|
|
4159
|
+
for (const absFile of changedFiles) {
|
|
4160
|
+
const owner = findOwningPackage(absFile, packageDirs);
|
|
4161
|
+
if (owner != null) {
|
|
4162
|
+
changedPackageDirs.add(owner);
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
const entries = [];
|
|
4166
|
+
for (const pkgDir of changedPackageDirs) {
|
|
4167
|
+
const pkgInfo = packageMap.get(pkgDir);
|
|
4168
|
+
const pkgJsonPath = join14(pkgDir, "package.json");
|
|
4169
|
+
if (pkgJsonPath === rootPkgPath) continue;
|
|
4170
|
+
const versionFileCandidates = [
|
|
4171
|
+
{ abs: pkgJsonPath, rel: relative3(cwd, pkgJsonPath).replace(/\\/g, "/") }
|
|
4172
|
+
];
|
|
4173
|
+
const tauriConfPath = join14(pkgDir, "src-tauri", "tauri.conf.json");
|
|
4174
|
+
const tauriRelPath = relative3(cwd, tauriConfPath).replace(/\\/g, "/");
|
|
4175
|
+
try {
|
|
4176
|
+
await access4(tauriConfPath);
|
|
4177
|
+
versionFileCandidates.push({ abs: tauriConfPath, rel: tauriRelPath });
|
|
4178
|
+
} catch {
|
|
4179
|
+
}
|
|
4180
|
+
const appJsonPath = join14(pkgDir, "app.json");
|
|
4181
|
+
const appJsonRelPath = relative3(cwd, appJsonPath).replace(/\\/g, "/");
|
|
4182
|
+
try {
|
|
4183
|
+
await access4(appJsonPath);
|
|
4184
|
+
versionFileCandidates.push({ abs: appJsonPath, rel: appJsonRelPath });
|
|
4185
|
+
} catch {
|
|
4186
|
+
}
|
|
4187
|
+
let currentPkgJsonRaw;
|
|
4188
|
+
try {
|
|
4189
|
+
currentPkgJsonRaw = await readFile13(pkgJsonPath, "utf-8");
|
|
4190
|
+
} catch (err) {
|
|
4191
|
+
console.warn(
|
|
4192
|
+
`runBump: could not read ${pkgJsonPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
4193
|
+
);
|
|
4194
|
+
continue;
|
|
4195
|
+
}
|
|
4196
|
+
const currentVersion = extractVersion(currentPkgJsonRaw, pkgJsonPath);
|
|
4197
|
+
if (currentVersion == null) {
|
|
4198
|
+
entries.push({
|
|
4199
|
+
name: pkgInfo.name,
|
|
4200
|
+
packageDir: pkgDir,
|
|
4201
|
+
currentVersion: "(none)",
|
|
4202
|
+
nextVersion: "(none)",
|
|
4203
|
+
versionFiles: [],
|
|
4204
|
+
changelogUpdated: false,
|
|
4205
|
+
skipped: true,
|
|
4206
|
+
skipReason: "No version field in package.json"
|
|
4207
|
+
});
|
|
4208
|
+
continue;
|
|
4209
|
+
}
|
|
4210
|
+
const pkgJsonRelPath = relative3(cwd, pkgJsonPath).replace(/\\/g, "/");
|
|
4211
|
+
const baseRaw = gitShowFile(cwd, baseRef, pkgJsonRelPath);
|
|
4212
|
+
if (baseRaw !== null) {
|
|
4213
|
+
const baseVersion = extractVersion(baseRaw, pkgJsonPath);
|
|
4214
|
+
if (baseVersion !== null && compareSemver(currentVersion, baseVersion) > 0) {
|
|
4215
|
+
entries.push({
|
|
4216
|
+
name: pkgInfo.name,
|
|
4217
|
+
packageDir: pkgDir,
|
|
4218
|
+
currentVersion,
|
|
4219
|
+
nextVersion: currentVersion,
|
|
4220
|
+
versionFiles: [],
|
|
4221
|
+
changelogUpdated: false,
|
|
4222
|
+
skipped: true,
|
|
4223
|
+
skipReason: `Already at ${currentVersion} > base ${baseVersion}`
|
|
4224
|
+
});
|
|
4225
|
+
continue;
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
const nextVersion = patchBump(currentVersion);
|
|
4229
|
+
const updatedVersionFiles = [];
|
|
4230
|
+
const skippedVersionFiles = [];
|
|
4231
|
+
for (const { abs, rel } of versionFileCandidates) {
|
|
4232
|
+
let raw;
|
|
4233
|
+
try {
|
|
4234
|
+
raw = await readFile13(abs, "utf-8");
|
|
4235
|
+
} catch {
|
|
4236
|
+
continue;
|
|
4237
|
+
}
|
|
4238
|
+
const ver = abs === pkgJsonPath ? currentVersion : extractVersion(raw, abs);
|
|
4239
|
+
if (ver == null) {
|
|
4240
|
+
console.warn(
|
|
4241
|
+
`runBump: ${pkgInfo.name}: no version field in ${abs} \u2014 skipping update`
|
|
4242
|
+
);
|
|
4243
|
+
skippedVersionFiles.push(rel);
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4246
|
+
const updated = injectVersion(raw, nextVersion, abs);
|
|
4247
|
+
if (!dryRun) {
|
|
4248
|
+
await writeFile10(abs, updated, "utf-8");
|
|
4249
|
+
}
|
|
4250
|
+
updatedVersionFiles.push(rel);
|
|
4251
|
+
}
|
|
4252
|
+
const changelogPath = join14(pkgDir, "CHANGELOG.md");
|
|
4253
|
+
const changelogUpdated = await prependChangelog(
|
|
4254
|
+
changelogPath,
|
|
4255
|
+
pkgInfo.name,
|
|
4256
|
+
nextVersion,
|
|
4257
|
+
now,
|
|
4258
|
+
dryRun
|
|
4259
|
+
);
|
|
4260
|
+
entries.push({
|
|
4261
|
+
name: pkgInfo.name,
|
|
4262
|
+
packageDir: pkgDir,
|
|
4263
|
+
currentVersion,
|
|
4264
|
+
nextVersion,
|
|
4265
|
+
versionFiles: updatedVersionFiles,
|
|
4266
|
+
...skippedVersionFiles.length > 0 ? { skippedVersionFiles } : {},
|
|
4267
|
+
changelogUpdated,
|
|
4268
|
+
skipped: false
|
|
4269
|
+
});
|
|
4270
|
+
}
|
|
4271
|
+
return { baseBranch, baseRef, entries, dryRun };
|
|
4272
|
+
}
|
|
4273
|
+
var init_bump = __esm({
|
|
4274
|
+
"src/lib/bump.ts"() {
|
|
4275
|
+
"use strict";
|
|
4276
|
+
init_git_utils();
|
|
4277
|
+
}
|
|
4278
|
+
});
|
|
4279
|
+
|
|
4280
|
+
// src/cli/bump.ts
|
|
4281
|
+
var bump_exports = {};
|
|
4282
|
+
__export(bump_exports, {
|
|
4283
|
+
runBumpCommand: () => runBumpCommand
|
|
4284
|
+
});
|
|
4285
|
+
function parseFlagsFromArgs2(args) {
|
|
4286
|
+
const flags = {};
|
|
4287
|
+
const booleans = /* @__PURE__ */ new Set();
|
|
4288
|
+
for (let i = 0; i < args.length; i++) {
|
|
4289
|
+
const arg = args[i];
|
|
4290
|
+
if (arg.startsWith("--")) {
|
|
4291
|
+
const key = arg.slice(2);
|
|
4292
|
+
const next = args[i + 1];
|
|
4293
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
4294
|
+
flags[key] = next;
|
|
4295
|
+
i++;
|
|
4296
|
+
} else {
|
|
4297
|
+
booleans.add(key);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return { flags, booleans };
|
|
4302
|
+
}
|
|
4303
|
+
function printBumpHelp() {
|
|
4304
|
+
process.stdout.write(
|
|
4305
|
+
"\n codebyplan bump\n\n Detect changed workspace packages and patch-bump their versions.\n Does NOT commit or push \u2014 pure version-file + changelog edits.\n\n Flags:\n --dry-run Preview planned bumps without writing any files\n --json Write structured JSON output to stdout\n\n"
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4308
|
+
function printHumanResult(result) {
|
|
4309
|
+
const lines = [];
|
|
4310
|
+
const baseLabel = result.baseRef === result.baseBranch ? result.baseBranch : `${result.baseBranch} (compared against ${result.baseRef})`;
|
|
4311
|
+
if (result.dryRun) {
|
|
4312
|
+
lines.push(`[dry-run] Base: ${baseLabel}`);
|
|
4313
|
+
} else {
|
|
4314
|
+
lines.push(`Base: ${baseLabel}`);
|
|
4315
|
+
}
|
|
4316
|
+
const active = result.entries.filter((e) => !e.skipped);
|
|
4317
|
+
const skipped = result.entries.filter((e) => e.skipped);
|
|
4318
|
+
if (active.length === 0 && skipped.length === 0) {
|
|
4319
|
+
lines.push("No changed packages detected.");
|
|
4320
|
+
}
|
|
4321
|
+
for (const entry of active) {
|
|
4322
|
+
const action = result.dryRun ? "Would bump" : "Bumped";
|
|
4323
|
+
lines.push(
|
|
4324
|
+
` ${action}: ${entry.name} ${entry.currentVersion} \u2192 ${entry.nextVersion}`
|
|
4325
|
+
);
|
|
4326
|
+
for (const f of entry.versionFiles) {
|
|
4327
|
+
lines.push(` - ${f}`);
|
|
4328
|
+
}
|
|
4329
|
+
if (entry.changelogUpdated) {
|
|
4330
|
+
lines.push(` - CHANGELOG.md`);
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
for (const entry of skipped) {
|
|
4334
|
+
lines.push(
|
|
4335
|
+
` Skipped: ${entry.name} (${entry.skipReason ?? "already bumped"})`
|
|
4336
|
+
);
|
|
4337
|
+
}
|
|
4338
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
4339
|
+
}
|
|
4340
|
+
async function runBumpCommand(args) {
|
|
4341
|
+
const firstArg = args[0];
|
|
4342
|
+
if (firstArg === "help" || firstArg === "--help" || firstArg === "-h") {
|
|
4343
|
+
printBumpHelp();
|
|
4344
|
+
process.exit(0);
|
|
4345
|
+
}
|
|
4346
|
+
const { booleans } = parseFlagsFromArgs2(args);
|
|
4347
|
+
const dryRun = booleans.has("dry-run");
|
|
4348
|
+
const jsonOutput = booleans.has("json");
|
|
4349
|
+
let result;
|
|
4350
|
+
try {
|
|
4351
|
+
result = await runBump({ dryRun });
|
|
4352
|
+
} catch (err) {
|
|
4353
|
+
process.stderr.write(
|
|
4354
|
+
`bump: ${err instanceof Error ? err.message : String(err)}
|
|
4355
|
+
`
|
|
4356
|
+
);
|
|
4357
|
+
process.exit(1);
|
|
4358
|
+
}
|
|
4359
|
+
if (jsonOutput) {
|
|
4360
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
printHumanResult(result);
|
|
4364
|
+
}
|
|
4365
|
+
var init_bump2 = __esm({
|
|
4366
|
+
"src/cli/bump.ts"() {
|
|
4367
|
+
"use strict";
|
|
4368
|
+
init_bump();
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
|
|
4372
|
+
// src/lib/ship.ts
|
|
4373
|
+
import { execSync as execSync4, spawnSync as spawnSync4 } from "node:child_process";
|
|
4374
|
+
import { relative as relative4 } from "node:path";
|
|
4375
|
+
function assertValidBranchName2(branch, label) {
|
|
4376
|
+
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
4377
|
+
throw new Error(
|
|
4378
|
+
`Invalid branch name for ${label}: '${branch}' (refusing to pass shell metacharacters to git/gh)`
|
|
4379
|
+
);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
3962
4382
|
function defaultPrBody(feat, base) {
|
|
3963
4383
|
return `## Summary
|
|
3964
4384
|
|
|
@@ -3975,7 +4395,7 @@ async function pollChecks(feat, timeoutSeconds, cwd, _sleepMs = (ms) => new Prom
|
|
|
3975
4395
|
let totalGhErrors = 0;
|
|
3976
4396
|
let iteration = 0;
|
|
3977
4397
|
while (Date.now() < deadline) {
|
|
3978
|
-
const ghResult =
|
|
4398
|
+
const ghResult = spawnSync4(
|
|
3979
4399
|
"gh",
|
|
3980
4400
|
["pr", "checks", feat, "--json", "name,state,conclusion"],
|
|
3981
4401
|
{
|
|
@@ -4041,7 +4461,9 @@ async function runShip(opts) {
|
|
|
4041
4461
|
const keepFeat = opts?.keepFeat ?? false;
|
|
4042
4462
|
const bodyFile = opts?.bodyFile ?? null;
|
|
4043
4463
|
const timeoutSeconds = opts?.timeoutSeconds ?? 600;
|
|
4464
|
+
const bump = opts?.bump ?? true;
|
|
4044
4465
|
const sleepMs = opts?._sleepMs ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4466
|
+
let bumpEntries;
|
|
4045
4467
|
let feat;
|
|
4046
4468
|
try {
|
|
4047
4469
|
feat = execSync4("git rev-parse --abbrev-ref HEAD", {
|
|
@@ -4102,15 +4524,66 @@ ${statusOut.trim()}`
|
|
|
4102
4524
|
}
|
|
4103
4525
|
} catch {
|
|
4104
4526
|
}
|
|
4527
|
+
if (bump) {
|
|
4528
|
+
const bumpResult = await runBump({ cwd, dryRun: true });
|
|
4529
|
+
bumpEntries = bumpResult.entries;
|
|
4530
|
+
}
|
|
4105
4531
|
return {
|
|
4106
4532
|
pr_url: prUrl2,
|
|
4107
4533
|
merge_commit: null,
|
|
4108
4534
|
branch_deleted: false,
|
|
4109
4535
|
dry_run: true,
|
|
4110
4536
|
feat_branch: feat,
|
|
4111
|
-
base_branch: base
|
|
4537
|
+
base_branch: base,
|
|
4538
|
+
...bumpEntries !== void 0 ? { bumps: bumpEntries } : {}
|
|
4112
4539
|
};
|
|
4113
4540
|
}
|
|
4541
|
+
if (bump) {
|
|
4542
|
+
let bumpResult;
|
|
4543
|
+
try {
|
|
4544
|
+
bumpResult = await runBump({ cwd });
|
|
4545
|
+
} catch (err) {
|
|
4546
|
+
throw new Error(
|
|
4547
|
+
`Version bump failed before push (working tree may have been partially modified \u2014 run 'git status'): ${err instanceof Error ? err.message : String(err)}`
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
bumpEntries = bumpResult.entries;
|
|
4551
|
+
const toStage = bumpResult.entries.filter((e) => !e.skipped).flatMap((e) => [
|
|
4552
|
+
...e.versionFiles,
|
|
4553
|
+
...e.changelogUpdated ? [relative4(cwd, e.packageDir).replace(/\\/g, "/") + "/CHANGELOG.md"] : []
|
|
4554
|
+
]);
|
|
4555
|
+
if (toStage.length > 0) {
|
|
4556
|
+
const addResult = spawnSync4("git", ["add", "--", ...toStage], {
|
|
4557
|
+
cwd,
|
|
4558
|
+
encoding: "utf-8",
|
|
4559
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4560
|
+
});
|
|
4561
|
+
if (addResult.status !== 0 || addResult.error) {
|
|
4562
|
+
throw new Error(
|
|
4563
|
+
`Failed to stage bump files: ${addResult.stderr?.trim() ?? addResult.error?.message}`
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
const commitResult = spawnSync4(
|
|
4567
|
+
"git",
|
|
4568
|
+
["commit", "-m", "chore(release): bump versions"],
|
|
4569
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
4570
|
+
);
|
|
4571
|
+
if (commitResult.status !== 0 || commitResult.error) {
|
|
4572
|
+
spawnSync4(
|
|
4573
|
+
"git",
|
|
4574
|
+
["restore", "--staged", "--worktree", "--", ...toStage],
|
|
4575
|
+
{
|
|
4576
|
+
cwd,
|
|
4577
|
+
encoding: "utf-8",
|
|
4578
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4579
|
+
}
|
|
4580
|
+
);
|
|
4581
|
+
throw new Error(
|
|
4582
|
+
`Failed to commit version bump: ${commitResult.stderr?.trim() ?? commitResult.error?.message}`
|
|
4583
|
+
);
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4114
4587
|
execSync4(`git push -u origin HEAD`, {
|
|
4115
4588
|
cwd,
|
|
4116
4589
|
encoding: "utf-8",
|
|
@@ -4141,7 +4614,7 @@ ${statusOut.trim()}`
|
|
|
4141
4614
|
} else {
|
|
4142
4615
|
createArgs.push("--body", defaultPrBody(feat, base));
|
|
4143
4616
|
}
|
|
4144
|
-
const createResult =
|
|
4617
|
+
const createResult = spawnSync4("gh", createArgs, {
|
|
4145
4618
|
cwd,
|
|
4146
4619
|
encoding: "utf-8",
|
|
4147
4620
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4161,7 +4634,8 @@ ${statusOut.trim()}`
|
|
|
4161
4634
|
checks_failed: true,
|
|
4162
4635
|
checks_failure_reason: checksResult.reason,
|
|
4163
4636
|
feat_branch: feat,
|
|
4164
|
-
base_branch: base
|
|
4637
|
+
base_branch: base,
|
|
4638
|
+
...bumpEntries !== void 0 ? { bumps: bumpEntries } : {}
|
|
4165
4639
|
};
|
|
4166
4640
|
}
|
|
4167
4641
|
execSync4(`gh pr merge --merge ${feat}`, {
|
|
@@ -4171,7 +4645,7 @@ ${statusOut.trim()}`
|
|
|
4171
4645
|
});
|
|
4172
4646
|
let mergeCommit = null;
|
|
4173
4647
|
try {
|
|
4174
|
-
const prViewResult =
|
|
4648
|
+
const prViewResult = spawnSync4(
|
|
4175
4649
|
"gh",
|
|
4176
4650
|
["pr", "view", feat, "--json", "state,mergeCommit"],
|
|
4177
4651
|
{
|
|
@@ -4215,13 +4689,15 @@ ${statusOut.trim()}`
|
|
|
4215
4689
|
merge_commit: mergeCommit,
|
|
4216
4690
|
branch_deleted: branchDeleted,
|
|
4217
4691
|
feat_branch: feat,
|
|
4218
|
-
base_branch: base
|
|
4692
|
+
base_branch: base,
|
|
4693
|
+
...bumpEntries !== void 0 ? { bumps: bumpEntries } : {}
|
|
4219
4694
|
};
|
|
4220
4695
|
}
|
|
4221
4696
|
var init_ship = __esm({
|
|
4222
4697
|
"src/lib/ship.ts"() {
|
|
4223
4698
|
"use strict";
|
|
4224
|
-
|
|
4699
|
+
init_git_utils();
|
|
4700
|
+
init_bump();
|
|
4225
4701
|
}
|
|
4226
4702
|
});
|
|
4227
4703
|
|
|
@@ -4230,7 +4706,7 @@ var ship_exports = {};
|
|
|
4230
4706
|
__export(ship_exports, {
|
|
4231
4707
|
runShipCommand: () => runShipCommand
|
|
4232
4708
|
});
|
|
4233
|
-
function
|
|
4709
|
+
function parseFlagsFromArgs3(args) {
|
|
4234
4710
|
const flags = {};
|
|
4235
4711
|
const booleans = /* @__PURE__ */ new Set();
|
|
4236
4712
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -4250,7 +4726,7 @@ function parseFlagsFromArgs2(args) {
|
|
|
4250
4726
|
}
|
|
4251
4727
|
function printShipHelp() {
|
|
4252
4728
|
process.stdout.write(
|
|
4253
|
-
"\n codebyplan ship\n\n Ship the current feat branch to production via PR.\n\n Flags:\n --dry-run Preview without pushing, creating, or merging\n --json Write JSON result to stdout\n --keep-feat Keep the feat branch after merge (no auto-delete)\n --body-file <path> Path to a markdown file used as the PR body\n --timeout <seconds> Max seconds to poll required checks (default: 600)\n\n"
|
|
4729
|
+
"\n codebyplan ship\n\n Ship the current feat branch to production via PR.\n\n Flags:\n --dry-run Preview without pushing, creating, or merging\n --json Write JSON result to stdout\n --keep-feat Keep the feat branch after merge (no auto-delete)\n --body-file <path> Path to a markdown file used as the PR body\n --timeout <seconds> Max seconds to poll required checks (default: 600)\n --no-bump Skip automatic patch-version bump before push\n\n"
|
|
4254
4730
|
);
|
|
4255
4731
|
}
|
|
4256
4732
|
async function runShipCommand(args) {
|
|
@@ -4262,10 +4738,11 @@ async function runShipCommand(args) {
|
|
|
4262
4738
|
await runShipDefault(args);
|
|
4263
4739
|
}
|
|
4264
4740
|
async function runShipDefault(args) {
|
|
4265
|
-
const { flags, booleans } =
|
|
4741
|
+
const { flags, booleans } = parseFlagsFromArgs3(args);
|
|
4266
4742
|
const dryRun = booleans.has("dry-run");
|
|
4267
4743
|
const jsonOutput = booleans.has("json");
|
|
4268
4744
|
const keepFeat = booleans.has("keep-feat");
|
|
4745
|
+
const noBump = booleans.has("no-bump");
|
|
4269
4746
|
const bodyFile = flags["body-file"] ?? void 0;
|
|
4270
4747
|
const timeoutRaw = flags["timeout"];
|
|
4271
4748
|
let timeoutSeconds;
|
|
@@ -4282,7 +4759,13 @@ async function runShipDefault(args) {
|
|
|
4282
4759
|
}
|
|
4283
4760
|
let result;
|
|
4284
4761
|
try {
|
|
4285
|
-
result = await runShip({
|
|
4762
|
+
result = await runShip({
|
|
4763
|
+
dryRun,
|
|
4764
|
+
keepFeat,
|
|
4765
|
+
bodyFile,
|
|
4766
|
+
timeoutSeconds,
|
|
4767
|
+
bump: !noBump
|
|
4768
|
+
});
|
|
4286
4769
|
} catch (err) {
|
|
4287
4770
|
process.stderr.write(
|
|
4288
4771
|
`ship: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -4319,6 +4802,13 @@ async function runShipDefault(args) {
|
|
|
4319
4802
|
lines.push(`Branch deleted: ${result.feat_branch ?? ""}`);
|
|
4320
4803
|
}
|
|
4321
4804
|
}
|
|
4805
|
+
const bumpedEntries = result.bumps?.filter((e) => !e.skipped) ?? [];
|
|
4806
|
+
if (bumpedEntries.length > 0) {
|
|
4807
|
+
lines.push("Bumped:");
|
|
4808
|
+
for (const e of bumpedEntries) {
|
|
4809
|
+
lines.push(` ${e.name} ${e.currentVersion} \u2192 ${e.nextVersion}`);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4322
4812
|
process.stdout.write(lines.join("\n") + "\n");
|
|
4323
4813
|
}
|
|
4324
4814
|
var init_ship2 = __esm({
|
|
@@ -4328,1753 +4818,1888 @@ var init_ship2 = __esm({
|
|
|
4328
4818
|
}
|
|
4329
4819
|
});
|
|
4330
4820
|
|
|
4331
|
-
// src/
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
});
|
|
4337
|
-
import { execSync as execSync5 } from "node:child_process";
|
|
4338
|
-
function distress(kind, message, jsonMode) {
|
|
4339
|
-
if (jsonMode) return;
|
|
4340
|
-
process.stderr.write(`resolve-worktree: ${kind}: ${message}
|
|
4341
|
-
`);
|
|
4821
|
+
// src/lib/hash.ts
|
|
4822
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4823
|
+
function sha256(input) {
|
|
4824
|
+
const buf = typeof input === "string" ? Buffer.from(input, "utf8") : input;
|
|
4825
|
+
return `sha256:${createHash3("sha256").update(buf).digest("hex")}`;
|
|
4342
4826
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
const repoId = found.contents.repo_id;
|
|
4361
|
-
try {
|
|
4362
|
-
await readLocalConfig(projectPath);
|
|
4363
|
-
} catch (readErr) {
|
|
4364
|
-
const readErrCode = readErr.code;
|
|
4365
|
-
errorContext = {
|
|
4366
|
-
kind: readErrCode === "LEGACY_FILE_BLOCKS_DIR" ? "legacy_file_blocks_dir" : "local_config_read_failed",
|
|
4367
|
-
message: readErr instanceof Error ? readErr.message : String(readErr)
|
|
4368
|
-
};
|
|
4827
|
+
var init_hash = __esm({
|
|
4828
|
+
"src/lib/hash.ts"() {
|
|
4829
|
+
"use strict";
|
|
4830
|
+
}
|
|
4831
|
+
});
|
|
4832
|
+
|
|
4833
|
+
// src/lib/template-walker.ts
|
|
4834
|
+
import * as fs from "node:fs";
|
|
4835
|
+
import * as path2 from "node:path";
|
|
4836
|
+
function walkTemplates(templatesDir) {
|
|
4837
|
+
const absRoot = fs.realpathSync(templatesDir);
|
|
4838
|
+
const visited = /* @__PURE__ */ new Set();
|
|
4839
|
+
const out = [];
|
|
4840
|
+
const recurse = (absDir) => {
|
|
4841
|
+
const realDir = fs.realpathSync(absDir);
|
|
4842
|
+
if (visited.has(realDir)) {
|
|
4843
|
+
return;
|
|
4369
4844
|
}
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
const code = deviceErr.code;
|
|
4378
|
-
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
4379
|
-
errorContext = {
|
|
4380
|
-
kind: "legacy_file_blocks_dir",
|
|
4381
|
-
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
4382
|
-
};
|
|
4383
|
-
} else if (errorContext === null || errorContext.kind !== "local_config_read_failed" && errorContext.kind !== "legacy_file_blocks_dir") {
|
|
4384
|
-
errorContext = {
|
|
4385
|
-
kind: "local_config_write_failed",
|
|
4386
|
-
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
4387
|
-
};
|
|
4845
|
+
visited.add(realDir);
|
|
4846
|
+
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
4847
|
+
for (const entry of entries) {
|
|
4848
|
+
const absPath = path2.join(absDir, entry.name);
|
|
4849
|
+
if (entry.isDirectory()) {
|
|
4850
|
+
recurse(absPath);
|
|
4851
|
+
continue;
|
|
4388
4852
|
}
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
let branch = "";
|
|
4392
|
-
try {
|
|
4393
|
-
branch = execSync5("git symbolic-ref --short HEAD", {
|
|
4394
|
-
cwd: projectPath,
|
|
4395
|
-
encoding: "utf-8"
|
|
4396
|
-
}).trim();
|
|
4397
|
-
} catch (gitErr) {
|
|
4398
|
-
if (errorContext === null) {
|
|
4399
|
-
errorContext = {
|
|
4400
|
-
kind: "git_failed",
|
|
4401
|
-
message: gitErr instanceof Error ? gitErr.message : String(gitErr)
|
|
4402
|
-
};
|
|
4853
|
+
if (!entry.isFile()) {
|
|
4854
|
+
continue;
|
|
4403
4855
|
}
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
if (
|
|
4407
|
-
|
|
4856
|
+
if (entry.name === ".gitkeep") continue;
|
|
4857
|
+
const relPosix = path2.relative(absRoot, absPath).split(path2.sep).join("/");
|
|
4858
|
+
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
4859
|
+
continue;
|
|
4408
4860
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
deviceId,
|
|
4415
|
-
onError: onResolverError
|
|
4416
|
-
});
|
|
4417
|
-
if (worktreeId) {
|
|
4418
|
-
emitAndExit(worktreeId, errorContext, jsonMode);
|
|
4419
|
-
}
|
|
4420
|
-
const useFallback = hasFlag("fallback-from-branch", 3);
|
|
4421
|
-
if (useFallback) {
|
|
4422
|
-
const fallbackId = await resolveWorktreeByBranch({
|
|
4423
|
-
repoId,
|
|
4424
|
-
deviceId,
|
|
4425
|
-
branch,
|
|
4426
|
-
onError: onResolverError
|
|
4861
|
+
const content = fs.readFileSync(absPath);
|
|
4862
|
+
out.push({
|
|
4863
|
+
src: relPosix,
|
|
4864
|
+
dest: relPosix,
|
|
4865
|
+
hash: sha256(content)
|
|
4427
4866
|
});
|
|
4428
|
-
if (fallbackId) {
|
|
4429
|
-
emitAndExit(fallbackId, errorContext, jsonMode);
|
|
4430
|
-
}
|
|
4431
|
-
}
|
|
4432
|
-
emitAndExit(null, errorContext, jsonMode);
|
|
4433
|
-
} catch (err) {
|
|
4434
|
-
if (err instanceof ProcessExitSignal) throw err;
|
|
4435
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4436
|
-
errorContext = { kind: "unhandled", message: msg };
|
|
4437
|
-
emitAndExit(null, errorContext, jsonMode);
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
function emitAndExit(worktreeId, errorContext, jsonMode) {
|
|
4441
|
-
if (jsonMode) {
|
|
4442
|
-
const errorKind = errorContext?.kind ?? (worktreeId === null ? "tuple_miss" : null);
|
|
4443
|
-
process.stdout.write(
|
|
4444
|
-
JSON.stringify({ worktree_id: worktreeId, error_kind: errorKind }) + "\n"
|
|
4445
|
-
);
|
|
4446
|
-
} else {
|
|
4447
|
-
if (worktreeId !== null) {
|
|
4448
|
-
process.stdout.write(worktreeId);
|
|
4449
|
-
}
|
|
4450
|
-
if (errorContext !== null) {
|
|
4451
|
-
if (errorContext.kind !== "unhandled" || process.env.CODEBYPLAN_DEBUG === "1") {
|
|
4452
|
-
distress(errorContext.kind, errorContext.message, jsonMode);
|
|
4453
|
-
}
|
|
4454
4867
|
}
|
|
4455
|
-
}
|
|
4456
|
-
|
|
4868
|
+
};
|
|
4869
|
+
recurse(absRoot);
|
|
4870
|
+
out.sort((a, b) => a.src.localeCompare(b.src));
|
|
4871
|
+
return out;
|
|
4457
4872
|
}
|
|
4458
|
-
var
|
|
4459
|
-
var
|
|
4460
|
-
"src/
|
|
4873
|
+
var EXCLUDED_RELATIVE_PATHS;
|
|
4874
|
+
var init_template_walker = __esm({
|
|
4875
|
+
"src/lib/template-walker.ts"() {
|
|
4461
4876
|
"use strict";
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4877
|
+
init_hash();
|
|
4878
|
+
EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
|
|
4879
|
+
// Meta files
|
|
4880
|
+
"hooks/hooks.json",
|
|
4881
|
+
"hooks/README.md",
|
|
4882
|
+
"rules/README.md",
|
|
4883
|
+
"settings.project.base.json",
|
|
4884
|
+
"settings.user.base.json",
|
|
4885
|
+
// .gitignore — managed by ensureManagedGitignoreBlock; never copied into
|
|
4886
|
+
// consuming projects' .claude/ tree (it would overwrite the project root
|
|
4887
|
+
// .gitignore with a stale single-entry file).
|
|
4888
|
+
".gitignore",
|
|
4889
|
+
// CBP-internal hooks — see templates/hooks/README.md "Hooks NOT included and why"
|
|
4890
|
+
"hooks/validate-structure.sh",
|
|
4891
|
+
"hooks/validate-structure-lib.sh",
|
|
4892
|
+
"hooks/validate-structure-patterns.sh",
|
|
4893
|
+
"hooks/validate-structure-templates.sh",
|
|
4894
|
+
"hooks/validate-structure-scope.sh",
|
|
4895
|
+
"hooks/validate-structure-lengths.sh",
|
|
4896
|
+
"hooks/validate-structure-smoke.sh",
|
|
4897
|
+
"hooks/validate-context-usage.sh",
|
|
4898
|
+
"hooks/validate-git-commit.sh"
|
|
4899
|
+
]);
|
|
4473
4900
|
}
|
|
4474
4901
|
});
|
|
4475
4902
|
|
|
4476
|
-
// src/lib/
|
|
4477
|
-
import
|
|
4478
|
-
import
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
function legacyLocalPath(projectPath) {
|
|
4483
|
-
return join14(projectPath, ".codebyplan.local.json");
|
|
4903
|
+
// src/lib/manifest.ts
|
|
4904
|
+
import * as fs2 from "node:fs";
|
|
4905
|
+
import * as os from "node:os";
|
|
4906
|
+
import * as path3 from "node:path";
|
|
4907
|
+
function manifestPath(projectDir) {
|
|
4908
|
+
return path3.join(projectDir, ".claude", NEW_MANIFEST_FILENAME);
|
|
4484
4909
|
}
|
|
4485
|
-
function
|
|
4486
|
-
return
|
|
4910
|
+
function midManifestPath(projectDir) {
|
|
4911
|
+
return path3.join(projectDir, ".claude", MID_MANIFEST_FILENAME);
|
|
4487
4912
|
}
|
|
4488
|
-
function
|
|
4489
|
-
return
|
|
4913
|
+
function oldManifestPath(projectDir) {
|
|
4914
|
+
return path3.join(projectDir, ".claude", OLD_MANIFEST_FILENAME);
|
|
4490
4915
|
}
|
|
4491
|
-
|
|
4492
|
-
const
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
return null;
|
|
4916
|
+
function readManifest(projectDir) {
|
|
4917
|
+
const newFile = manifestPath(projectDir);
|
|
4918
|
+
if (fs2.existsSync(newFile)) {
|
|
4919
|
+
const raw = fs2.readFileSync(newFile, "utf8");
|
|
4920
|
+
return JSON.parse(raw);
|
|
4497
4921
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
if (!legacyStat) return false;
|
|
4503
|
-
const sentinelStat = await statSafe(sentinelPath(projectPath));
|
|
4504
|
-
if (sentinelStat) return false;
|
|
4505
|
-
return true;
|
|
4506
|
-
} catch {
|
|
4507
|
-
return false;
|
|
4922
|
+
const midFile = midManifestPath(projectDir);
|
|
4923
|
+
if (fs2.existsSync(midFile)) {
|
|
4924
|
+
const raw = fs2.readFileSync(midFile, "utf8");
|
|
4925
|
+
return JSON.parse(raw);
|
|
4508
4926
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
throw new Error(
|
|
4514
|
-
".codebyplan exists as a file; remove or rename it before migrating."
|
|
4515
|
-
);
|
|
4927
|
+
const oldFile = oldManifestPath(projectDir);
|
|
4928
|
+
if (fs2.existsSync(oldFile)) {
|
|
4929
|
+
const raw = fs2.readFileSync(oldFile, "utf8");
|
|
4930
|
+
return JSON.parse(raw);
|
|
4516
4931
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4932
|
+
return null;
|
|
4933
|
+
}
|
|
4934
|
+
function writeManifest(projectDir, manifest) {
|
|
4935
|
+
const file = manifestPath(projectDir);
|
|
4936
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
4937
|
+
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
4938
|
+
const mid = midManifestPath(projectDir);
|
|
4939
|
+
if (fs2.existsSync(mid)) {
|
|
4940
|
+
fs2.rmSync(mid);
|
|
4525
4941
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
} catch {
|
|
4530
|
-
return {
|
|
4531
|
-
migrated: true,
|
|
4532
|
-
was_dirty: false,
|
|
4533
|
-
files_changed: [],
|
|
4534
|
-
summary: "legacy .codebyplan.json absent; nothing to migrate"
|
|
4535
|
-
};
|
|
4536
|
-
}
|
|
4537
|
-
let parsed;
|
|
4538
|
-
try {
|
|
4539
|
-
parsed = JSON.parse(legacyRaw);
|
|
4540
|
-
} catch (err) {
|
|
4541
|
-
const inner = err instanceof Error ? err.message : String(err);
|
|
4542
|
-
throw new Error(
|
|
4543
|
-
`.codebyplan.json contains invalid JSON \u2014 cannot migrate: ${inner}`
|
|
4544
|
-
);
|
|
4545
|
-
}
|
|
4546
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4547
|
-
throw new Error(
|
|
4548
|
-
".codebyplan.json does not contain a JSON object \u2014 cannot migrate"
|
|
4549
|
-
);
|
|
4942
|
+
const legacy = oldManifestPath(projectDir);
|
|
4943
|
+
if (fs2.existsSync(legacy)) {
|
|
4944
|
+
fs2.rmSync(legacy);
|
|
4550
4945
|
}
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4946
|
+
}
|
|
4947
|
+
function defaultManifest() {
|
|
4948
|
+
return {
|
|
4949
|
+
version: VERSION,
|
|
4950
|
+
installed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4951
|
+
files: []
|
|
4952
|
+
};
|
|
4953
|
+
}
|
|
4954
|
+
function userManifestPath(userDir) {
|
|
4955
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
4956
|
+
return path3.join(dir, NEW_MANIFEST_FILENAME);
|
|
4957
|
+
}
|
|
4958
|
+
function userMidManifestPath(userDir) {
|
|
4959
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
4960
|
+
return path3.join(dir, MID_MANIFEST_FILENAME);
|
|
4961
|
+
}
|
|
4962
|
+
function userOldManifestPath(userDir) {
|
|
4963
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
4964
|
+
return path3.join(dir, OLD_MANIFEST_FILENAME);
|
|
4965
|
+
}
|
|
4966
|
+
function readManifestForScope(scope, arg2) {
|
|
4967
|
+
if (scope === "user") {
|
|
4968
|
+
const newFile = userManifestPath(arg2);
|
|
4969
|
+
if (fs2.existsSync(newFile)) {
|
|
4970
|
+
const raw = fs2.readFileSync(newFile, "utf8");
|
|
4971
|
+
return JSON.parse(raw);
|
|
4559
4972
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
} catch (err) {
|
|
4565
|
-
const code = err.code;
|
|
4566
|
-
if (code === "ENOTDIR" || code === "EEXIST") {
|
|
4567
|
-
throw new Error(
|
|
4568
|
-
".codebyplan exists as a file; remove or rename it before migrating."
|
|
4569
|
-
);
|
|
4973
|
+
const midFile = userMidManifestPath(arg2);
|
|
4974
|
+
if (fs2.existsSync(midFile)) {
|
|
4975
|
+
const raw = fs2.readFileSync(midFile, "utf8");
|
|
4976
|
+
return JSON.parse(raw);
|
|
4570
4977
|
}
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
const filesChanged = [];
|
|
4578
|
-
const repoJson = {};
|
|
4579
|
-
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
4580
|
-
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
4581
|
-
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
4582
|
-
await writeFile10(
|
|
4583
|
-
join14(projectPath, ".codebyplan", "repo.json"),
|
|
4584
|
-
JSON.stringify(repoJson, null, 2) + "\n",
|
|
4585
|
-
"utf-8"
|
|
4586
|
-
);
|
|
4587
|
-
filesChanged.push(".codebyplan/repo.json");
|
|
4588
|
-
const serverJson = {};
|
|
4589
|
-
if ("server_port" in cfg) serverJson.server_port = cfg.server_port;
|
|
4590
|
-
if ("server_type" in cfg) serverJson.server_type = cfg.server_type;
|
|
4591
|
-
if ("auto_push_enabled" in cfg)
|
|
4592
|
-
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
4593
|
-
if ("port_allocations" in cfg)
|
|
4594
|
-
serverJson.port_allocations = cfg.port_allocations;
|
|
4595
|
-
await writeFile10(
|
|
4596
|
-
join14(projectPath, ".codebyplan", "server.json"),
|
|
4597
|
-
JSON.stringify(serverJson, null, 2) + "\n",
|
|
4598
|
-
"utf-8"
|
|
4599
|
-
);
|
|
4600
|
-
filesChanged.push(".codebyplan/server.json");
|
|
4601
|
-
const gitJson = {};
|
|
4602
|
-
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
4603
|
-
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
4604
|
-
await writeFile10(
|
|
4605
|
-
join14(projectPath, ".codebyplan", "git.json"),
|
|
4606
|
-
JSON.stringify(gitJson, null, 2) + "\n",
|
|
4607
|
-
"utf-8"
|
|
4608
|
-
);
|
|
4609
|
-
filesChanged.push(".codebyplan/git.json");
|
|
4610
|
-
const shipmentJson = {};
|
|
4611
|
-
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
4612
|
-
await writeFile10(
|
|
4613
|
-
join14(projectPath, ".codebyplan", "shipment.json"),
|
|
4614
|
-
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
4615
|
-
"utf-8"
|
|
4616
|
-
);
|
|
4617
|
-
filesChanged.push(".codebyplan/shipment.json");
|
|
4618
|
-
const vendorJson = {};
|
|
4619
|
-
await writeFile10(
|
|
4620
|
-
join14(projectPath, ".codebyplan", "vendor.json"),
|
|
4621
|
-
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
4622
|
-
"utf-8"
|
|
4623
|
-
);
|
|
4624
|
-
filesChanged.push(".codebyplan/vendor.json");
|
|
4625
|
-
const e2eJson = {};
|
|
4626
|
-
await writeFile10(
|
|
4627
|
-
join14(projectPath, ".codebyplan", "e2e.json"),
|
|
4628
|
-
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
4629
|
-
"utf-8"
|
|
4630
|
-
);
|
|
4631
|
-
filesChanged.push(".codebyplan/e2e.json");
|
|
4632
|
-
const eslintJson = {};
|
|
4633
|
-
await writeFile10(
|
|
4634
|
-
join14(projectPath, ".codebyplan", "eslint.json"),
|
|
4635
|
-
JSON.stringify(eslintJson, null, 2) + "\n",
|
|
4636
|
-
"utf-8"
|
|
4637
|
-
);
|
|
4638
|
-
filesChanged.push(".codebyplan/eslint.json");
|
|
4639
|
-
if (!deviceWrittenByHelper) {
|
|
4640
|
-
await writeFile10(
|
|
4641
|
-
join14(projectPath, ".codebyplan", "device.local.json"),
|
|
4642
|
-
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
4643
|
-
"utf-8"
|
|
4644
|
-
);
|
|
4645
|
-
}
|
|
4646
|
-
filesChanged.push(".codebyplan/device.local.json");
|
|
4647
|
-
const writtenSentinel = await statSafe(sentinelPath(projectPath));
|
|
4648
|
-
if (!writtenSentinel) {
|
|
4649
|
-
throw new Error(
|
|
4650
|
-
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
4651
|
-
);
|
|
4978
|
+
const oldFile = userOldManifestPath(arg2);
|
|
4979
|
+
if (fs2.existsSync(oldFile)) {
|
|
4980
|
+
const raw = fs2.readFileSync(oldFile, "utf8");
|
|
4981
|
+
return JSON.parse(raw);
|
|
4982
|
+
}
|
|
4983
|
+
return null;
|
|
4652
4984
|
}
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
const
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
if (
|
|
4662
|
-
|
|
4663
|
-
const stripped = l.trimEnd();
|
|
4664
|
-
return stripped === legacyLine ? newLine + (l.endsWith("\r") ? "\r" : "") : l;
|
|
4665
|
-
}).join("\n");
|
|
4666
|
-
} else if (hasLegacy && hasNew) {
|
|
4667
|
-
updated = gitignoreContent.split("\n").filter((l) => l.trimEnd() !== legacyLine).join("\n");
|
|
4668
|
-
} else if (!hasLegacy && !hasNew) {
|
|
4669
|
-
updated = gitignoreContent.endsWith("\n") ? gitignoreContent + newLine + "\n" : gitignoreContent + "\n" + newLine + "\n";
|
|
4670
|
-
} else {
|
|
4671
|
-
updated = gitignoreContent;
|
|
4985
|
+
return readManifest(arg2);
|
|
4986
|
+
}
|
|
4987
|
+
function writeManifestForScope(scope, manifest, arg3) {
|
|
4988
|
+
if (scope === "user") {
|
|
4989
|
+
const file = userManifestPath(arg3);
|
|
4990
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
4991
|
+
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
4992
|
+
const mid = userMidManifestPath(arg3);
|
|
4993
|
+
if (fs2.existsSync(mid)) {
|
|
4994
|
+
fs2.rmSync(mid);
|
|
4672
4995
|
}
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4996
|
+
const legacy = userOldManifestPath(arg3);
|
|
4997
|
+
if (fs2.existsSync(legacy)) {
|
|
4998
|
+
fs2.rmSync(legacy);
|
|
4676
4999
|
}
|
|
4677
|
-
|
|
4678
|
-
}
|
|
4679
|
-
try {
|
|
4680
|
-
await unlink2(legacySharedPath(projectPath));
|
|
4681
|
-
filesChanged.push(".codebyplan.json (deleted)");
|
|
4682
|
-
} catch {
|
|
4683
|
-
}
|
|
4684
|
-
try {
|
|
4685
|
-
await unlink2(legacyLocalPath(projectPath));
|
|
4686
|
-
filesChanged.push(".codebyplan.local.json (deleted)");
|
|
4687
|
-
} catch {
|
|
5000
|
+
return;
|
|
4688
5001
|
}
|
|
4689
|
-
|
|
4690
|
-
migrated: true,
|
|
4691
|
-
was_dirty: true,
|
|
4692
|
-
files_changed: filesChanged,
|
|
4693
|
-
summary: `migrated legacy .codebyplan.json to .codebyplan/ layout (device_id=${deviceId.slice(0, 8)})`
|
|
4694
|
-
};
|
|
5002
|
+
writeManifest(arg3, manifest);
|
|
4695
5003
|
}
|
|
4696
|
-
var
|
|
4697
|
-
|
|
5004
|
+
var NEW_MANIFEST_FILENAME, MID_MANIFEST_FILENAME, OLD_MANIFEST_FILENAME;
|
|
5005
|
+
var init_manifest = __esm({
|
|
5006
|
+
"src/lib/manifest.ts"() {
|
|
4698
5007
|
"use strict";
|
|
4699
|
-
|
|
5008
|
+
init_version();
|
|
5009
|
+
NEW_MANIFEST_FILENAME = ".cbp.manifest.json";
|
|
5010
|
+
MID_MANIFEST_FILENAME = ".cbp-claude.manifest.json";
|
|
5011
|
+
OLD_MANIFEST_FILENAME = ".codebyplan-claude.manifest.json";
|
|
4700
5012
|
}
|
|
4701
5013
|
});
|
|
4702
5014
|
|
|
4703
|
-
// src/
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
readShipmentConfig: () => readShipmentConfig,
|
|
4711
|
-
readVendorConfig: () => readVendorConfig,
|
|
4712
|
-
runConfig: () => runConfig
|
|
4713
|
-
});
|
|
4714
|
-
import { mkdir as mkdir6, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
4715
|
-
import { join as join15 } from "node:path";
|
|
4716
|
-
async function runConfig() {
|
|
4717
|
-
const flags = parseFlags(3);
|
|
4718
|
-
const dryRun = hasFlag("dry-run", 3);
|
|
4719
|
-
validateApiKey();
|
|
4720
|
-
const config = await resolveConfig(flags);
|
|
4721
|
-
const { repoId, projectPath } = config;
|
|
4722
|
-
console.log(`
|
|
4723
|
-
CodeByPlan Config`);
|
|
4724
|
-
console.log(` Repo: ${repoId}`);
|
|
4725
|
-
console.log(` Path: ${projectPath}`);
|
|
4726
|
-
if (dryRun) console.log(` Mode: dry-run`);
|
|
4727
|
-
console.log();
|
|
4728
|
-
if (!dryRun && await needsLocalMigration(projectPath)) {
|
|
4729
|
-
console.log(
|
|
4730
|
-
" Migrating legacy .codebyplan.json to .codebyplan/ layout..."
|
|
5015
|
+
// src/lib/settings-merge.ts
|
|
5016
|
+
function extractHookId(command) {
|
|
5017
|
+
const match = /([^\s/]+\.sh)(?:\s|$)/.exec(command);
|
|
5018
|
+
const id = match?.[1];
|
|
5019
|
+
if (!id) {
|
|
5020
|
+
throw new Error(
|
|
5021
|
+
`Cannot derive _hook_id from command (no .sh script segment): ${command}`
|
|
4731
5022
|
);
|
|
4732
|
-
try {
|
|
4733
|
-
const result = await runLocalMigration(projectPath);
|
|
4734
|
-
if (result.was_dirty) {
|
|
4735
|
-
console.log(" Migration complete.");
|
|
4736
|
-
}
|
|
4737
|
-
} catch (err) {
|
|
4738
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4739
|
-
console.warn(
|
|
4740
|
-
` Warning: migration failed (${msg}); continuing with config sync.`
|
|
4741
|
-
);
|
|
4742
|
-
}
|
|
4743
5023
|
}
|
|
4744
|
-
|
|
4745
|
-
console.log("\n Config complete.\n");
|
|
5024
|
+
return id;
|
|
4746
5025
|
}
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
try {
|
|
4754
|
-
const { execSync: execSync6 } = await import("node:child_process");
|
|
4755
|
-
branch = execSync6("git symbolic-ref --short HEAD", {
|
|
4756
|
-
cwd: projectPath,
|
|
4757
|
-
encoding: "utf-8"
|
|
4758
|
-
}).trim();
|
|
4759
|
-
} catch {
|
|
4760
|
-
}
|
|
4761
|
-
const tupleId = await resolveWorktreeId({
|
|
4762
|
-
repoId,
|
|
4763
|
-
repoPath: projectPath,
|
|
4764
|
-
branch,
|
|
4765
|
-
deviceId
|
|
4766
|
-
});
|
|
4767
|
-
if (tupleId) {
|
|
4768
|
-
resolvedWorktreeId = tupleId;
|
|
4769
|
-
} else {
|
|
4770
|
-
resolvedWorktreeId = await resolveAndCacheWorktreeId(repoId, projectPath) ?? void 0;
|
|
4771
|
-
}
|
|
4772
|
-
} catch (err) {
|
|
4773
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4774
|
-
console.warn(
|
|
4775
|
-
` Warning: failed to cache worktree_id (self-heal skipped): ${msg}`
|
|
4776
|
-
);
|
|
5026
|
+
function rewriteCommand(command) {
|
|
5027
|
+
return command.replace(PLACEHOLDER_RE, REPLACEMENT);
|
|
5028
|
+
}
|
|
5029
|
+
function mergeHooksIntoSettings(settings, hooksJson) {
|
|
5030
|
+
if (!settings.hooks) {
|
|
5031
|
+
settings.hooks = {};
|
|
4777
5032
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
let message;
|
|
4783
|
-
if (err instanceof ApiError) {
|
|
4784
|
-
if (err.status === 401) {
|
|
4785
|
-
message = "Session expired. Run `codebyplan login` and try again.";
|
|
4786
|
-
} else if (err.status === 403 || err.status === 404) {
|
|
4787
|
-
message = "Repo not found or not accessible to your account. Confirm the repo_id in .codebyplan/repo.json and that you are logged in as the right user.";
|
|
4788
|
-
} else if (err.status >= 500) {
|
|
4789
|
-
message = `CodeByPlan server error (status ${err.status}). Please try again shortly.`;
|
|
4790
|
-
} else {
|
|
4791
|
-
message = `Unexpected API error (status ${err.status}). Please try again.`;
|
|
4792
|
-
}
|
|
4793
|
-
} else {
|
|
4794
|
-
message = "Failed to reach the CodeByPlan API. Check your network connection and try again.";
|
|
5033
|
+
const target = settings.hooks;
|
|
5034
|
+
for (const [event, sourceMatchers] of Object.entries(hooksJson.hooks)) {
|
|
5035
|
+
if (!target[event]) {
|
|
5036
|
+
target[event] = [];
|
|
4795
5037
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
{ repo_id: repoId }
|
|
4805
|
-
);
|
|
4806
|
-
const allAllocations = portsRes.data ?? [];
|
|
4807
|
-
const filtered = resolvedWorktreeId ? allAllocations.filter((a) => a.worktree_id === resolvedWorktreeId) : allAllocations.filter((a) => !a.worktree_id);
|
|
4808
|
-
const ALLOWED_FIELDS = [
|
|
4809
|
-
"id",
|
|
4810
|
-
"repo_id",
|
|
4811
|
-
"port",
|
|
4812
|
-
"label",
|
|
4813
|
-
"server_type",
|
|
4814
|
-
"auto_start",
|
|
4815
|
-
"command",
|
|
4816
|
-
"working_dir",
|
|
4817
|
-
"env_vars",
|
|
4818
|
-
"external_refs",
|
|
4819
|
-
"worktree_id",
|
|
4820
|
-
"created_at",
|
|
4821
|
-
"updated_at"
|
|
4822
|
-
];
|
|
4823
|
-
portAllocations = filtered.map((a) => {
|
|
4824
|
-
const clean = {};
|
|
4825
|
-
for (const key of ALLOWED_FIELDS) {
|
|
4826
|
-
if (key in a) clean[key] = a[key];
|
|
5038
|
+
const eventBlocks = target[event];
|
|
5039
|
+
for (const sourceBlock of sourceMatchers) {
|
|
5040
|
+
let destBlock = eventBlocks.find(
|
|
5041
|
+
(b) => b.matcher === sourceBlock.matcher
|
|
5042
|
+
);
|
|
5043
|
+
if (!destBlock) {
|
|
5044
|
+
destBlock = { matcher: sourceBlock.matcher, hooks: [] };
|
|
5045
|
+
eventBlocks.push(destBlock);
|
|
4827
5046
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
process.stderr.write(
|
|
4846
|
-
`warning: legacy 3-branch branch_config detected (integration: '${String(branchConfig["integration"])}'). Run 'npx codebyplan branch migrate' to consolidate to main-only.
|
|
4847
|
-
`
|
|
4848
|
-
);
|
|
5047
|
+
for (const sourceCmd of sourceBlock.hooks) {
|
|
5048
|
+
const taggedEntry = {
|
|
5049
|
+
_owner: OWNER,
|
|
5050
|
+
_hook_id: extractHookId(sourceCmd.command),
|
|
5051
|
+
type: sourceCmd.type,
|
|
5052
|
+
command: rewriteCommand(sourceCmd.command)
|
|
5053
|
+
};
|
|
5054
|
+
const existingIdx = destBlock.hooks.findIndex(
|
|
5055
|
+
(e) => e._owner === OWNER && e._hook_id === taggedEntry._hook_id
|
|
5056
|
+
);
|
|
5057
|
+
if (existingIdx >= 0) {
|
|
5058
|
+
destBlock.hooks[existingIdx] = taggedEntry;
|
|
5059
|
+
} else {
|
|
5060
|
+
destBlock.hooks.push(taggedEntry);
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
4849
5064
|
}
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
5065
|
+
return settings;
|
|
5066
|
+
}
|
|
5067
|
+
function mergeBaseSettingsIntoSettings(settings, base) {
|
|
5068
|
+
for (const key of SCALAR_BASE_KEYS) {
|
|
5069
|
+
if (base[key] !== void 0 && settings[key] === void 0) {
|
|
5070
|
+
settings[key] = base[key];
|
|
5071
|
+
}
|
|
4854
5072
|
}
|
|
4855
|
-
if (
|
|
4856
|
-
|
|
5073
|
+
if (base.statusLine !== void 0 && settings.statusLine === void 0) {
|
|
5074
|
+
settings.statusLine = { ...base.statusLine };
|
|
4857
5075
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
server_type: resolvedWorktreeId && matchingAlloc ? matchingAlloc.server_type : repo.server_type,
|
|
4861
|
-
auto_push_enabled: repo.auto_push_enabled,
|
|
4862
|
-
port_allocations: portAllocations
|
|
4863
|
-
};
|
|
4864
|
-
const gitPayload = {
|
|
4865
|
-
git_branch: repo.git_branch ?? "main",
|
|
4866
|
-
branch_config: branchConfig
|
|
4867
|
-
};
|
|
4868
|
-
const shipmentPayload = {};
|
|
4869
|
-
if (typeof repoAny.shipment !== "undefined") {
|
|
4870
|
-
shipmentPayload.shipment = repoAny.shipment;
|
|
5076
|
+
if (base.subagentStatusLine !== void 0 && settings.subagentStatusLine === void 0) {
|
|
5077
|
+
settings.subagentStatusLine = { ...base.subagentStatusLine };
|
|
4871
5078
|
}
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
const eslintPayload = {};
|
|
4875
|
-
if (dryRun) {
|
|
4876
|
-
console.log(" Config would be updated (dry-run).");
|
|
4877
|
-
return;
|
|
5079
|
+
if (base.attribution !== void 0 && settings.attribution === void 0) {
|
|
5080
|
+
settings.attribution = { ...base.attribution };
|
|
4878
5081
|
}
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
let anyUpdated = false;
|
|
4890
|
-
for (const { name, payload, createOnly } of files) {
|
|
4891
|
-
const filePath = join15(codebyplanDir, name);
|
|
4892
|
-
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
4893
|
-
let currentJson = "";
|
|
4894
|
-
try {
|
|
4895
|
-
currentJson = await readFile14(filePath, "utf-8");
|
|
4896
|
-
} catch {
|
|
5082
|
+
if (base.worktree !== void 0) {
|
|
5083
|
+
if (settings.worktree === void 0) {
|
|
5084
|
+
settings.worktree = { ...base.worktree };
|
|
5085
|
+
} else {
|
|
5086
|
+
const sw = settings.worktree;
|
|
5087
|
+
for (const [k, v] of Object.entries(base.worktree)) {
|
|
5088
|
+
if (sw[k] === void 0) {
|
|
5089
|
+
sw[k] = v;
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
4897
5092
|
}
|
|
4898
|
-
if (createOnly && currentJson !== "") continue;
|
|
4899
|
-
if (currentJson === newJson) continue;
|
|
4900
|
-
await writeFile11(filePath, newJson, "utf-8");
|
|
4901
|
-
console.log(` Updated .codebyplan/${name}`);
|
|
4902
|
-
anyUpdated = true;
|
|
4903
5093
|
}
|
|
4904
|
-
if (
|
|
4905
|
-
|
|
5094
|
+
if (base.permissions !== void 0) {
|
|
5095
|
+
const existing = settings.permissions ?? {};
|
|
5096
|
+
if (base.permissions.defaultMode !== void 0 && existing.defaultMode === void 0) {
|
|
5097
|
+
existing.defaultMode = base.permissions.defaultMode;
|
|
5098
|
+
}
|
|
5099
|
+
if (base.permissions.skipDangerousModePermissionPrompt !== void 0 && existing.skipDangerousModePermissionPrompt === void 0) {
|
|
5100
|
+
existing.skipDangerousModePermissionPrompt = base.permissions.skipDangerousModePermissionPrompt;
|
|
5101
|
+
}
|
|
5102
|
+
for (const key of ["deny", "ask", "allow"]) {
|
|
5103
|
+
const incoming = base.permissions[key];
|
|
5104
|
+
if (!incoming || incoming.length === 0) continue;
|
|
5105
|
+
const current = existing[key] ?? [];
|
|
5106
|
+
const seen = new Set(current);
|
|
5107
|
+
const merged = [...current];
|
|
5108
|
+
for (const item of incoming) {
|
|
5109
|
+
if (!seen.has(item)) {
|
|
5110
|
+
merged.push(item);
|
|
5111
|
+
seen.add(item);
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
existing[key] = merged;
|
|
5115
|
+
}
|
|
5116
|
+
if (settings.permissions !== void 0 || Object.keys(existing).length > 0) {
|
|
5117
|
+
settings.permissions = existing;
|
|
5118
|
+
}
|
|
4906
5119
|
}
|
|
5120
|
+
return settings;
|
|
4907
5121
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
);
|
|
4914
|
-
return JSON.parse(raw);
|
|
4915
|
-
} catch {
|
|
4916
|
-
return null;
|
|
5122
|
+
function stripBaseSettingsFromSettings(settings, base) {
|
|
5123
|
+
for (const key of SCALAR_BASE_KEYS) {
|
|
5124
|
+
if (base[key] !== void 0 && settings[key] === base[key]) {
|
|
5125
|
+
delete settings[key];
|
|
5126
|
+
}
|
|
4917
5127
|
}
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
try {
|
|
4921
|
-
const raw = await readFile14(
|
|
4922
|
-
join15(projectPath, ".codebyplan", "server.json"),
|
|
4923
|
-
"utf-8"
|
|
4924
|
-
);
|
|
4925
|
-
return JSON.parse(raw);
|
|
4926
|
-
} catch {
|
|
4927
|
-
return null;
|
|
5128
|
+
if (base.statusLine !== void 0 && settings.statusLine !== void 0 && JSON.stringify(settings.statusLine) === JSON.stringify(base.statusLine)) {
|
|
5129
|
+
delete settings.statusLine;
|
|
4928
5130
|
}
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
try {
|
|
4932
|
-
const raw = await readFile14(
|
|
4933
|
-
join15(projectPath, ".codebyplan", "git.json"),
|
|
4934
|
-
"utf-8"
|
|
4935
|
-
);
|
|
4936
|
-
return JSON.parse(raw);
|
|
4937
|
-
} catch {
|
|
4938
|
-
return null;
|
|
5131
|
+
if (base.subagentStatusLine !== void 0 && settings.subagentStatusLine !== void 0 && JSON.stringify(settings.subagentStatusLine) === JSON.stringify(base.subagentStatusLine)) {
|
|
5132
|
+
delete settings.subagentStatusLine;
|
|
4939
5133
|
}
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
try {
|
|
4943
|
-
const raw = await readFile14(
|
|
4944
|
-
join15(projectPath, ".codebyplan", "shipment.json"),
|
|
4945
|
-
"utf-8"
|
|
4946
|
-
);
|
|
4947
|
-
return JSON.parse(raw);
|
|
4948
|
-
} catch {
|
|
4949
|
-
return null;
|
|
4950
|
-
}
|
|
4951
|
-
}
|
|
4952
|
-
async function readVendorConfig(projectPath) {
|
|
4953
|
-
try {
|
|
4954
|
-
const raw = await readFile14(
|
|
4955
|
-
join15(projectPath, ".codebyplan", "vendor.json"),
|
|
4956
|
-
"utf-8"
|
|
4957
|
-
);
|
|
4958
|
-
return JSON.parse(raw);
|
|
4959
|
-
} catch {
|
|
4960
|
-
return null;
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
async function readE2eConfig(projectPath) {
|
|
4964
|
-
try {
|
|
4965
|
-
const raw = await readFile14(
|
|
4966
|
-
join15(projectPath, ".codebyplan", "e2e.json"),
|
|
4967
|
-
"utf-8"
|
|
4968
|
-
);
|
|
4969
|
-
return JSON.parse(raw);
|
|
4970
|
-
} catch {
|
|
4971
|
-
return null;
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
var legacyBranchConfigWarned;
|
|
4975
|
-
var init_config = __esm({
|
|
4976
|
-
"src/cli/config.ts"() {
|
|
4977
|
-
"use strict";
|
|
4978
|
-
init_flags();
|
|
4979
|
-
init_api();
|
|
4980
|
-
init_resolve_worktree();
|
|
4981
|
-
init_local_config();
|
|
4982
|
-
init_migrate_local_config();
|
|
4983
|
-
legacyBranchConfigWarned = false;
|
|
5134
|
+
if (base.attribution !== void 0 && settings.attribution !== void 0 && JSON.stringify(settings.attribution) === JSON.stringify(base.attribution)) {
|
|
5135
|
+
delete settings.attribution;
|
|
4984
5136
|
}
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
4991
|
-
const hasDep = (name) => name in deps || name in devDeps;
|
|
4992
|
-
if (hasDep("next")) return "nextjs";
|
|
4993
|
-
if (hasDep("@tauri-apps/api") || hasDep("@tauri-apps/cli")) return "tauri";
|
|
4994
|
-
if (hasDep("expo")) return "expo";
|
|
4995
|
-
if (hasDep("vite")) return "vite";
|
|
4996
|
-
if (hasDep("express")) return "express";
|
|
4997
|
-
if (hasDep("@nestjs/core")) return "nestjs";
|
|
4998
|
-
return "custom";
|
|
4999
|
-
}
|
|
5000
|
-
function detectPortFromScripts(pkg) {
|
|
5001
|
-
const scripts = pkg.scripts;
|
|
5002
|
-
if (!scripts?.dev) return null;
|
|
5003
|
-
const parts = scripts.dev.split(/\s+/);
|
|
5004
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
5005
|
-
if (parts[i] === "--port" || parts[i] === "-p") {
|
|
5006
|
-
const next = parts[i + 1];
|
|
5007
|
-
if (next) {
|
|
5008
|
-
const port = parseInt(next, 10);
|
|
5009
|
-
if (!isNaN(port)) return port;
|
|
5137
|
+
if (base.worktree !== void 0 && settings.worktree !== void 0) {
|
|
5138
|
+
const sw = settings.worktree;
|
|
5139
|
+
for (const [k, v] of Object.entries(base.worktree)) {
|
|
5140
|
+
if (sw[k] === v) {
|
|
5141
|
+
delete sw[k];
|
|
5010
5142
|
}
|
|
5011
5143
|
}
|
|
5144
|
+
if (Object.keys(sw).length === 0) {
|
|
5145
|
+
delete settings.worktree;
|
|
5146
|
+
}
|
|
5012
5147
|
}
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
5033
|
-
const relativePath = pkgPath.replace(projectPath + "/", "");
|
|
5034
|
-
const matchingAlloc = portAllocations.find(
|
|
5035
|
-
(a) => a.label === getAppLabel(relativePath)
|
|
5036
|
-
);
|
|
5037
|
-
mismatches.push({
|
|
5038
|
-
packageJsonPath: relativePath,
|
|
5039
|
-
scriptPort,
|
|
5040
|
-
allocation: matchingAlloc ?? null,
|
|
5041
|
-
reason: matchingAlloc ? `Script uses port ${scriptPort} but allocation has port ${matchingAlloc.port}` : `Port ${scriptPort} in scripts is not in any allocation`
|
|
5042
|
-
});
|
|
5148
|
+
if (base.permissions !== void 0 && settings.permissions !== void 0) {
|
|
5149
|
+
const perms = settings.permissions;
|
|
5150
|
+
if (base.permissions.defaultMode !== void 0 && perms.defaultMode === base.permissions.defaultMode) {
|
|
5151
|
+
delete perms.defaultMode;
|
|
5152
|
+
}
|
|
5153
|
+
if (base.permissions.skipDangerousModePermissionPrompt !== void 0 && perms.skipDangerousModePermissionPrompt === base.permissions.skipDangerousModePermissionPrompt) {
|
|
5154
|
+
delete perms.skipDangerousModePermissionPrompt;
|
|
5155
|
+
}
|
|
5156
|
+
for (const key of ["deny", "ask", "allow"]) {
|
|
5157
|
+
const baseList = base.permissions[key];
|
|
5158
|
+
if (!baseList || baseList.length === 0) continue;
|
|
5159
|
+
const current = perms[key];
|
|
5160
|
+
if (!current) continue;
|
|
5161
|
+
const baseSet = new Set(baseList);
|
|
5162
|
+
const filtered = current.filter((x) => !baseSet.has(x));
|
|
5163
|
+
if (filtered.length === 0) {
|
|
5164
|
+
delete perms[key];
|
|
5165
|
+
} else {
|
|
5166
|
+
perms[key] = filtered;
|
|
5043
5167
|
}
|
|
5044
|
-
}
|
|
5168
|
+
}
|
|
5169
|
+
if (Object.keys(perms).length === 0) {
|
|
5170
|
+
delete settings.permissions;
|
|
5045
5171
|
}
|
|
5046
5172
|
}
|
|
5047
|
-
return
|
|
5048
|
-
}
|
|
5049
|
-
function isDevServerScript(pkg) {
|
|
5050
|
-
const scripts = pkg.scripts;
|
|
5051
|
-
const raw = scripts?.dev;
|
|
5052
|
-
if (!raw || typeof raw !== "string") return false;
|
|
5053
|
-
const script = raw.trim().toLowerCase();
|
|
5054
|
-
if (!script) return false;
|
|
5055
|
-
for (const pattern of DEV_SERVER_BIN_PATTERNS) {
|
|
5056
|
-
if (pattern.test(script)) return true;
|
|
5057
|
-
}
|
|
5058
|
-
const tokens = script.split(/\s+/);
|
|
5059
|
-
for (const token of tokens) {
|
|
5060
|
-
if (token === "--port" || token === "-p") return true;
|
|
5061
|
-
if (token.startsWith("--port=")) return true;
|
|
5062
|
-
}
|
|
5063
|
-
return false;
|
|
5064
|
-
}
|
|
5065
|
-
function labelMatchesAppName(label, appName) {
|
|
5066
|
-
if (!label || !appName) return false;
|
|
5067
|
-
const normalize = (s) => s.toLowerCase().replace(/-/g, " ").replace(/[()]/g, " ").replace(/\s+/g, " ").trim();
|
|
5068
|
-
const labelTokens = normalize(label).split(" ").filter(Boolean);
|
|
5069
|
-
const appToken = normalize(appName);
|
|
5070
|
-
if (!appToken) return false;
|
|
5071
|
-
const appTokens = appToken.split(" ").filter(Boolean);
|
|
5072
|
-
if (appTokens.length === 1) {
|
|
5073
|
-
return labelTokens.includes(appTokens[0]);
|
|
5074
|
-
}
|
|
5075
|
-
for (let i = 0; i <= labelTokens.length - appTokens.length; i++) {
|
|
5076
|
-
if (appTokens.every((t, j) => labelTokens[i + j] === t)) return true;
|
|
5077
|
-
}
|
|
5078
|
-
return false;
|
|
5173
|
+
return settings;
|
|
5079
5174
|
}
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
return [];
|
|
5175
|
+
function stripOwnedHooksFromSettings(settings) {
|
|
5176
|
+
if (!settings.hooks) {
|
|
5177
|
+
return settings;
|
|
5084
5178
|
}
|
|
5085
|
-
const
|
|
5086
|
-
|
|
5087
|
-
if (
|
|
5179
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
5180
|
+
const eventBlocks = settings.hooks[event];
|
|
5181
|
+
if (!eventBlocks) {
|
|
5088
5182
|
continue;
|
|
5089
5183
|
}
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5184
|
+
const survivingBlocks = [];
|
|
5185
|
+
for (const block of eventBlocks) {
|
|
5186
|
+
block.hooks = block.hooks.filter((e) => e._owner !== OWNER);
|
|
5187
|
+
if (block.hooks.length > 0) {
|
|
5188
|
+
survivingBlocks.push(block);
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
if (survivingBlocks.length > 0) {
|
|
5192
|
+
settings.hooks[event] = survivingBlocks;
|
|
5193
|
+
} else {
|
|
5194
|
+
delete settings.hooks[event];
|
|
5096
5195
|
}
|
|
5097
|
-
if (!isDevServerScript(pkg)) continue;
|
|
5098
|
-
const framework = detectFramework(pkg);
|
|
5099
|
-
const detectedPort = detectPortFromScripts(pkg);
|
|
5100
|
-
const command = `pnpm --filter ${app.name} dev`;
|
|
5101
|
-
unallocated.push({
|
|
5102
|
-
name: app.name,
|
|
5103
|
-
path: app.path,
|
|
5104
|
-
framework,
|
|
5105
|
-
detectedPort,
|
|
5106
|
-
command
|
|
5107
|
-
});
|
|
5108
5196
|
}
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
function getAppLabel(relativePath) {
|
|
5112
|
-
const parts = relativePath.split("/");
|
|
5113
|
-
if (parts.length >= 3 && parts[0] === "apps") {
|
|
5114
|
-
return parts[1];
|
|
5197
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
5198
|
+
delete settings.hooks;
|
|
5115
5199
|
}
|
|
5116
|
-
return
|
|
5200
|
+
return settings;
|
|
5117
5201
|
}
|
|
5118
|
-
var
|
|
5119
|
-
var
|
|
5120
|
-
"src/lib/
|
|
5202
|
+
var OWNER, PLACEHOLDER_RE, REPLACEMENT, SCALAR_BASE_KEYS;
|
|
5203
|
+
var init_settings_merge = __esm({
|
|
5204
|
+
"src/lib/settings-merge.ts"() {
|
|
5121
5205
|
"use strict";
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5206
|
+
OWNER = "codebyplan-claude";
|
|
5207
|
+
PLACEHOLDER_RE = /\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\//g;
|
|
5208
|
+
REPLACEMENT = "./.claude/hooks/";
|
|
5209
|
+
SCALAR_BASE_KEYS = [
|
|
5210
|
+
"alwaysThinkingEnabled",
|
|
5211
|
+
"autoUpdatesChannel",
|
|
5212
|
+
"awaySummaryEnabled",
|
|
5213
|
+
"disableAgentView",
|
|
5214
|
+
"disableRemoteControl",
|
|
5215
|
+
"editorMode",
|
|
5216
|
+
"outputStyle",
|
|
5217
|
+
"preferredNotifChannel",
|
|
5218
|
+
"prefersReducedMotion",
|
|
5219
|
+
"respectGitignore",
|
|
5220
|
+
"showTurnDuration",
|
|
5221
|
+
"spinnerTipsEnabled",
|
|
5222
|
+
"terminalProgressBarEnabled",
|
|
5223
|
+
"viewMode",
|
|
5224
|
+
"autoScrollEnabled",
|
|
5225
|
+
"cleanupPeriodDays",
|
|
5226
|
+
"includeGitInstructions",
|
|
5227
|
+
"showThinkingSummaries",
|
|
5228
|
+
"disableSkillShellExecution",
|
|
5229
|
+
"skipWebFetchPreflight",
|
|
5230
|
+
"fastModePerSessionOptIn",
|
|
5231
|
+
"effortLevel",
|
|
5232
|
+
"showClearContextOnPlanAccept",
|
|
5233
|
+
"syntaxHighlightingDisabled"
|
|
5132
5234
|
];
|
|
5133
5235
|
}
|
|
5134
5236
|
});
|
|
5135
5237
|
|
|
5136
|
-
// src/cli/
|
|
5137
|
-
var
|
|
5138
|
-
__export(
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
const
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
const portsRes = await apiGet(
|
|
5157
|
-
`/port-allocations`,
|
|
5158
|
-
{ repo_id: repoId }
|
|
5159
|
-
);
|
|
5160
|
-
const allocations = portsRes.data ?? [];
|
|
5161
|
-
if (allocations.length === 0) {
|
|
5162
|
-
console.log(" No port allocations found \u2014 skipping verification.");
|
|
5163
|
-
console.log("\n Ports complete.\n");
|
|
5164
|
-
return;
|
|
5238
|
+
// src/cli/claude/install.ts
|
|
5239
|
+
var install_exports = {};
|
|
5240
|
+
__export(install_exports, {
|
|
5241
|
+
resolveTemplatesDir: () => resolveTemplatesDir,
|
|
5242
|
+
runInstall: () => runInstall
|
|
5243
|
+
});
|
|
5244
|
+
import * as fs3 from "node:fs";
|
|
5245
|
+
import * as os2 from "node:os";
|
|
5246
|
+
import * as path4 from "node:path";
|
|
5247
|
+
import { fileURLToPath } from "node:url";
|
|
5248
|
+
function resolveTemplatesDir() {
|
|
5249
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
5250
|
+
const candidates = [
|
|
5251
|
+
path4.resolve(here, "..", "templates"),
|
|
5252
|
+
path4.resolve(here, "..", "..", "templates"),
|
|
5253
|
+
path4.resolve(here, "..", "..", "..", "templates")
|
|
5254
|
+
];
|
|
5255
|
+
for (const c of candidates) {
|
|
5256
|
+
if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
|
|
5257
|
+
return c;
|
|
5165
5258
|
}
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5259
|
+
}
|
|
5260
|
+
throw new Error(
|
|
5261
|
+
`codebyplan: could not locate templates/ directory. Probed:
|
|
5262
|
+
${candidates.join(
|
|
5263
|
+
"\n "
|
|
5264
|
+
)}`
|
|
5265
|
+
);
|
|
5266
|
+
}
|
|
5267
|
+
async function runInstall(opts, deps = {}) {
|
|
5268
|
+
await Promise.resolve();
|
|
5269
|
+
const scope = opts.scope ?? "project";
|
|
5270
|
+
if (scope === "user") {
|
|
5271
|
+
if (opts.renderer) {
|
|
5272
|
+
console.warn(
|
|
5273
|
+
"codebyplan claude install: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
|
|
5274
|
+
);
|
|
5172
5275
|
}
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
console.log(` Created allocation: ${app.name} \u2192 port ${port}`);
|
|
5197
|
-
} catch (err) {
|
|
5198
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5199
|
-
console.log(
|
|
5200
|
-
` Failed to create allocation for ${app.name}: ${msg}`
|
|
5201
|
-
);
|
|
5202
|
-
}
|
|
5203
|
-
if (app.detectedPort && app.detectedPort >= nextPort) {
|
|
5204
|
-
nextPort = app.detectedPort + 1;
|
|
5205
|
-
}
|
|
5276
|
+
runInstallUser(opts, deps);
|
|
5277
|
+
return;
|
|
5278
|
+
}
|
|
5279
|
+
const projectDir = deps.projectDir ?? process.cwd();
|
|
5280
|
+
let templatesDir;
|
|
5281
|
+
try {
|
|
5282
|
+
templatesDir = deps.templatesDir ?? resolveTemplatesDir();
|
|
5283
|
+
} catch (err) {
|
|
5284
|
+
console.error(
|
|
5285
|
+
err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
|
|
5286
|
+
);
|
|
5287
|
+
process.exitCode = 1;
|
|
5288
|
+
return;
|
|
5289
|
+
}
|
|
5290
|
+
try {
|
|
5291
|
+
const files = walkTemplates(templatesDir);
|
|
5292
|
+
const manifestEntries = [];
|
|
5293
|
+
for (const f of files) {
|
|
5294
|
+
const absDest = path4.join(projectDir, ".claude", f.dest);
|
|
5295
|
+
const absSrc = path4.join(templatesDir, f.src);
|
|
5296
|
+
if (opts.dryRun) {
|
|
5297
|
+
if (opts.verbose) {
|
|
5298
|
+
console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
|
|
5206
5299
|
}
|
|
5207
|
-
} else if (fix && dryRun) {
|
|
5208
|
-
console.log(" (dry-run \u2014 would create allocations with --fix)");
|
|
5209
5300
|
} else {
|
|
5210
|
-
|
|
5301
|
+
fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
|
|
5302
|
+
fs3.copyFileSync(absSrc, absDest);
|
|
5303
|
+
if (opts.verbose) {
|
|
5304
|
+
console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
|
|
5305
|
+
}
|
|
5211
5306
|
}
|
|
5307
|
+
manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
|
|
5212
5308
|
}
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
console.warn(
|
|
5218
|
-
` Port verification skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
5309
|
+
const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5310
|
+
const baseSettingsPath = path4.join(
|
|
5311
|
+
templatesDir,
|
|
5312
|
+
"settings.project.base.json"
|
|
5219
5313
|
);
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
init_port_verify();
|
|
5229
|
-
}
|
|
5230
|
-
});
|
|
5231
|
-
|
|
5232
|
-
// src/cli/tech-stack.ts
|
|
5233
|
-
var tech_stack_exports = {};
|
|
5234
|
-
__export(tech_stack_exports, {
|
|
5235
|
-
runFullTechStack: () => runFullTechStack,
|
|
5236
|
-
runTechStack: () => runTechStack
|
|
5237
|
-
});
|
|
5238
|
-
import { existsSync } from "node:fs";
|
|
5239
|
-
async function runTechStack() {
|
|
5240
|
-
const flags = parseFlags(3);
|
|
5241
|
-
const dryRun = hasFlag("dry-run", 3);
|
|
5242
|
-
if (hasFlag("full-tech-stack", 3)) {
|
|
5243
|
-
await runFullTechStack(dryRun);
|
|
5244
|
-
return;
|
|
5245
|
-
}
|
|
5246
|
-
validateApiKey();
|
|
5247
|
-
const config = await resolveConfig(flags);
|
|
5248
|
-
const { repoId, projectPath } = config;
|
|
5249
|
-
console.log(`
|
|
5250
|
-
CodeByPlan Tech Stack`);
|
|
5251
|
-
console.log(` Repo: ${repoId}`);
|
|
5252
|
-
console.log(` Path: ${projectPath}`);
|
|
5253
|
-
if (dryRun) console.log(` Mode: dry-run`);
|
|
5254
|
-
console.log();
|
|
5255
|
-
if (dryRun) {
|
|
5256
|
-
try {
|
|
5257
|
-
if (await needsLocalMigration(projectPath)) {
|
|
5258
|
-
console.log(
|
|
5259
|
-
` Would migrate .codebyplan.json -> worktree_id to .codebyplan.local.json (dry-run, skipping actual write).`
|
|
5314
|
+
const hasHooks = fs3.existsSync(hooksJsonPath);
|
|
5315
|
+
const hasBase = fs3.existsSync(baseSettingsPath);
|
|
5316
|
+
if (hasHooks || hasBase) {
|
|
5317
|
+
const settingsPath = path4.join(projectDir, ".claude", "settings.json");
|
|
5318
|
+
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5319
|
+
if (hasBase) {
|
|
5320
|
+
const base = JSON.parse(
|
|
5321
|
+
fs3.readFileSync(baseSettingsPath, "utf8")
|
|
5260
5322
|
);
|
|
5323
|
+
mergeBaseSettingsIntoSettings(existingSettings, base);
|
|
5261
5324
|
}
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5325
|
+
if (hasHooks) {
|
|
5326
|
+
const hooksJson = JSON.parse(
|
|
5327
|
+
fs3.readFileSync(hooksJsonPath, "utf8")
|
|
5328
|
+
);
|
|
5329
|
+
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
5330
|
+
}
|
|
5331
|
+
if (!opts.dryRun) {
|
|
5332
|
+
fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
|
|
5333
|
+
fs3.writeFileSync(
|
|
5334
|
+
settingsPath,
|
|
5335
|
+
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
5336
|
+
"utf8"
|
|
5270
5337
|
);
|
|
5338
|
+
} else if (opts.verbose) {
|
|
5271
5339
|
console.log(
|
|
5272
|
-
`
|
|
5340
|
+
`[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
|
|
5273
5341
|
);
|
|
5274
5342
|
}
|
|
5275
|
-
}
|
|
5276
|
-
|
|
5277
|
-
|
|
5343
|
+
}
|
|
5344
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
5345
|
+
projectDir,
|
|
5346
|
+
opts.dryRun
|
|
5347
|
+
);
|
|
5348
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
5349
|
+
console.log(
|
|
5350
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
|
|
5278
5351
|
);
|
|
5279
5352
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
console.log(" No dependencies found.");
|
|
5285
|
-
console.log("\n Tech stack complete.\n");
|
|
5286
|
-
return;
|
|
5353
|
+
if (!opts.dryRun) {
|
|
5354
|
+
const manifest = defaultManifest();
|
|
5355
|
+
manifest.files = manifestEntries;
|
|
5356
|
+
writeManifest(projectDir, manifest);
|
|
5287
5357
|
}
|
|
5288
|
-
const sourcePaths = new Set(dependencies.map((d) => d.source_path));
|
|
5289
5358
|
console.log(
|
|
5290
|
-
`
|
|
5359
|
+
`codebyplan claude install${opts.dryRun ? " (dry-run)" : ""}: ${manifestEntries.length} files, ${countHookEntries(templatesDir)} hook entries.`
|
|
5291
5360
|
);
|
|
5292
|
-
if (!dryRun) {
|
|
5293
|
-
|
|
5294
|
-
if (result.data.stale_removed > 0) {
|
|
5295
|
-
console.log(
|
|
5296
|
-
` ${result.data.stale_removed} stale dependencies removed`
|
|
5297
|
-
);
|
|
5298
|
-
}
|
|
5299
|
-
try {
|
|
5300
|
-
const { execSync: execSync6 } = await import("node:child_process");
|
|
5301
|
-
let branch = "main";
|
|
5302
|
-
try {
|
|
5303
|
-
branch = execSync6("git symbolic-ref --short HEAD", {
|
|
5304
|
-
cwd: projectPath,
|
|
5305
|
-
encoding: "utf-8"
|
|
5306
|
-
}).trim();
|
|
5307
|
-
} catch {
|
|
5308
|
-
}
|
|
5309
|
-
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
5310
|
-
const tupleId = await resolveWorktreeId({
|
|
5311
|
-
repoId,
|
|
5312
|
-
repoPath: projectPath,
|
|
5313
|
-
branch,
|
|
5314
|
-
deviceId
|
|
5315
|
-
});
|
|
5316
|
-
await callMcpTool("enqueue_todo_job", {
|
|
5317
|
-
repo_id: repoId,
|
|
5318
|
-
...tupleId ? { worktree_id: tupleId } : {},
|
|
5319
|
-
reason: "CLI_SYNC"
|
|
5320
|
-
});
|
|
5321
|
-
} catch {
|
|
5322
|
-
}
|
|
5323
|
-
}
|
|
5324
|
-
const detected = await detectTechStack(projectPath);
|
|
5325
|
-
if (detected.flat.length > 0) {
|
|
5326
|
-
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
5327
|
-
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
5328
|
-
const { merged, added } = mergeTechStack(remote, detected);
|
|
5329
|
-
if (added.length > 0) {
|
|
5330
|
-
console.log(` ${added.length} new tech entries`);
|
|
5331
|
-
if (!dryRun) {
|
|
5332
|
-
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
5333
|
-
}
|
|
5334
|
-
}
|
|
5361
|
+
if (opts.renderer && !opts.dryRun) {
|
|
5362
|
+
await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
|
|
5335
5363
|
}
|
|
5336
5364
|
} catch (err) {
|
|
5337
|
-
console.
|
|
5338
|
-
`
|
|
5365
|
+
console.error(
|
|
5366
|
+
`codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5339
5367
|
);
|
|
5368
|
+
process.exitCode = 1;
|
|
5340
5369
|
}
|
|
5341
|
-
console.log("\n Tech stack complete.\n");
|
|
5342
5370
|
}
|
|
5343
|
-
|
|
5371
|
+
function runInstallUser(opts, deps) {
|
|
5372
|
+
let templatesDir;
|
|
5344
5373
|
try {
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5374
|
+
templatesDir = deps.templatesDir ?? resolveTemplatesDir();
|
|
5375
|
+
} catch (err) {
|
|
5376
|
+
console.error(
|
|
5377
|
+
err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
|
|
5378
|
+
);
|
|
5379
|
+
process.exitCode = 1;
|
|
5380
|
+
return;
|
|
5381
|
+
}
|
|
5382
|
+
try {
|
|
5383
|
+
const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
|
|
5384
|
+
const settingsPath = path4.join(userDir, "settings.json");
|
|
5385
|
+
const userBaseSettingsPath = path4.join(
|
|
5386
|
+
templatesDir,
|
|
5387
|
+
"settings.user.base.json"
|
|
5388
|
+
);
|
|
5389
|
+
if (!fs3.existsSync(userBaseSettingsPath)) {
|
|
5390
|
+
console.error(
|
|
5391
|
+
"codebyplan claude install: settings.user.base.json not found in templates."
|
|
5392
|
+
);
|
|
5393
|
+
process.exitCode = 1;
|
|
5348
5394
|
return;
|
|
5349
5395
|
}
|
|
5350
|
-
const
|
|
5351
|
-
|
|
5352
|
-
` ${dependencies.length} dependencies from ${sourcePaths.size} package.json file${sourcePaths.size !== 1 ? "s" : ""}`
|
|
5396
|
+
const userBase = JSON.parse(
|
|
5397
|
+
fs3.readFileSync(userBaseSettingsPath, "utf8")
|
|
5353
5398
|
);
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
5371
|
-
}
|
|
5372
|
-
}
|
|
5399
|
+
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5400
|
+
mergeBaseSettingsIntoSettings(existingSettings, userBase);
|
|
5401
|
+
if (!opts.dryRun) {
|
|
5402
|
+
fs3.mkdirSync(userDir, { recursive: true });
|
|
5403
|
+
fs3.writeFileSync(
|
|
5404
|
+
settingsPath,
|
|
5405
|
+
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
5406
|
+
"utf8"
|
|
5407
|
+
);
|
|
5408
|
+
const manifest = defaultManifest();
|
|
5409
|
+
manifest.files = [];
|
|
5410
|
+
writeManifestForScope("user", manifest, userDir);
|
|
5411
|
+
} else if (opts.verbose) {
|
|
5412
|
+
console.log(
|
|
5413
|
+
`[dry-run] would merge user base settings into ${settingsPath}`
|
|
5414
|
+
);
|
|
5373
5415
|
}
|
|
5416
|
+
console.log(
|
|
5417
|
+
`codebyplan claude install --scope user${opts.dryRun ? " (dry-run)" : ""}: settings.json updated, 0 template files copied.`
|
|
5418
|
+
);
|
|
5374
5419
|
} catch (err) {
|
|
5375
|
-
console.
|
|
5376
|
-
`
|
|
5420
|
+
console.error(
|
|
5421
|
+
`codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5377
5422
|
);
|
|
5423
|
+
process.exitCode = 1;
|
|
5378
5424
|
}
|
|
5379
5425
|
}
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5426
|
+
function countHookEntries(templatesDir) {
|
|
5427
|
+
const p = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5428
|
+
if (!fs3.existsSync(p)) return 0;
|
|
5429
|
+
try {
|
|
5430
|
+
const j = JSON.parse(fs3.readFileSync(p, "utf8"));
|
|
5431
|
+
let n = 0;
|
|
5432
|
+
for (const blocks of Object.values(j.hooks)) {
|
|
5433
|
+
for (const b of blocks) n += b.hooks.length;
|
|
5434
|
+
}
|
|
5435
|
+
return n;
|
|
5436
|
+
} catch (err) {
|
|
5384
5437
|
console.error(
|
|
5385
|
-
|
|
5438
|
+
`codebyplan: could not count hook entries in hooks.json: ${err instanceof Error ? err.message : String(err)}`
|
|
5386
5439
|
);
|
|
5387
|
-
|
|
5388
|
-
return;
|
|
5440
|
+
return 0;
|
|
5389
5441
|
}
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5442
|
+
}
|
|
5443
|
+
var init_install = __esm({
|
|
5444
|
+
"src/cli/claude/install.ts"() {
|
|
5445
|
+
"use strict";
|
|
5446
|
+
init_template_walker();
|
|
5447
|
+
init_gitignore_block();
|
|
5448
|
+
init_manifest();
|
|
5449
|
+
init_settings_merge();
|
|
5450
|
+
init_statusline_config();
|
|
5451
|
+
}
|
|
5452
|
+
});
|
|
5453
|
+
|
|
5454
|
+
// src/lib/scaffold-publish-workflow.ts
|
|
5455
|
+
import * as fs4 from "node:fs";
|
|
5456
|
+
import * as path5 from "node:path";
|
|
5457
|
+
async function runScaffoldPublishWorkflow(opts) {
|
|
5458
|
+
await Promise.resolve();
|
|
5459
|
+
const dryRun = opts?.dryRun ?? false;
|
|
5460
|
+
const force = opts?.force ?? false;
|
|
5461
|
+
const projectDir = path5.resolve(opts?.projectDir ?? process.cwd());
|
|
5462
|
+
const templatesDir = opts?.templatesDir ?? resolveTemplatesDir();
|
|
5463
|
+
const templatePath = path5.join(
|
|
5464
|
+
templatesDir,
|
|
5465
|
+
"github-workflows",
|
|
5466
|
+
"publish.yml"
|
|
5467
|
+
);
|
|
5468
|
+
if (!fs4.existsSync(templatePath)) {
|
|
5469
|
+
throw new Error(
|
|
5470
|
+
`scaffold-publish-workflow: template not found at ${templatePath}`
|
|
5412
5471
|
);
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5472
|
+
}
|
|
5473
|
+
const templateContent = fs4.readFileSync(templatePath, "utf-8");
|
|
5474
|
+
const targetPath = path5.join(
|
|
5475
|
+
projectDir,
|
|
5476
|
+
".github",
|
|
5477
|
+
"workflows",
|
|
5478
|
+
"publish.yml"
|
|
5479
|
+
);
|
|
5480
|
+
if (dryRun) {
|
|
5481
|
+
return { status: "dry_run", path: targetPath };
|
|
5482
|
+
}
|
|
5483
|
+
if (fs4.existsSync(targetPath)) {
|
|
5484
|
+
const existingContent = fs4.readFileSync(targetPath, "utf-8");
|
|
5485
|
+
if (existingContent === templateContent) {
|
|
5486
|
+
return {
|
|
5487
|
+
status: "skipped",
|
|
5488
|
+
path: targetPath,
|
|
5489
|
+
reason: "already up to date"
|
|
5490
|
+
};
|
|
5417
5491
|
}
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
synced++;
|
|
5423
|
-
} catch (err) {
|
|
5424
|
-
console.error(
|
|
5425
|
-
` Error syncing tech stack for ${repo.name} @ ${wt.path}: ${err instanceof Error ? err.message : String(err)}`
|
|
5426
|
-
);
|
|
5427
|
-
}
|
|
5492
|
+
if (!force) {
|
|
5493
|
+
throw new Error(
|
|
5494
|
+
`scaffold-publish-workflow: ${targetPath} already exists and differs from the template. Pass --force to overwrite.`
|
|
5495
|
+
);
|
|
5428
5496
|
}
|
|
5429
5497
|
}
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
);
|
|
5498
|
+
const targetDir = path5.dirname(targetPath);
|
|
5499
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
5500
|
+
fs4.writeFileSync(targetPath, templateContent, "utf-8");
|
|
5501
|
+
return { status: "written", path: targetPath };
|
|
5435
5502
|
}
|
|
5436
|
-
var
|
|
5437
|
-
"src/
|
|
5503
|
+
var init_scaffold_publish_workflow = __esm({
|
|
5504
|
+
"src/lib/scaffold-publish-workflow.ts"() {
|
|
5438
5505
|
"use strict";
|
|
5439
|
-
|
|
5440
|
-
init_api();
|
|
5441
|
-
init_tech_detect();
|
|
5442
|
-
init_migrate_local_config();
|
|
5443
|
-
init_local_config();
|
|
5444
|
-
init_resolve_worktree();
|
|
5506
|
+
init_install();
|
|
5445
5507
|
}
|
|
5446
5508
|
});
|
|
5447
5509
|
|
|
5448
|
-
// src/
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5510
|
+
// src/cli/scaffold-publish-workflow.ts
|
|
5511
|
+
var scaffold_publish_workflow_exports = {};
|
|
5512
|
+
__export(scaffold_publish_workflow_exports, {
|
|
5513
|
+
runScaffoldPublishWorkflowCommand: () => runScaffoldPublishWorkflowCommand
|
|
5514
|
+
});
|
|
5515
|
+
function parseFlagsFromArgs4(args) {
|
|
5516
|
+
const flags = {};
|
|
5517
|
+
const booleans = /* @__PURE__ */ new Set();
|
|
5518
|
+
for (let i = 0; i < args.length; i++) {
|
|
5519
|
+
const arg = args[i];
|
|
5520
|
+
if (arg.startsWith("--")) {
|
|
5521
|
+
const key = arg.slice(2);
|
|
5522
|
+
const next = args[i + 1];
|
|
5523
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
5524
|
+
flags[key] = next;
|
|
5525
|
+
i++;
|
|
5526
|
+
} else {
|
|
5527
|
+
booleans.add(key);
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
return { flags, booleans };
|
|
5453
5532
|
}
|
|
5454
|
-
|
|
5455
|
-
|
|
5533
|
+
function printHelp() {
|
|
5534
|
+
process.stdout.write(
|
|
5535
|
+
"\n codebyplan scaffold-publish-workflow\n\n Write the publish-on-main GitHub Actions workflow into\n ./.github/workflows/publish.yml. Idempotent \u2014 safe to re-run.\n\n Flags:\n --dry-run Preview the operation without writing any files\n --force Overwrite an existing file that differs from the template\n --project-dir <p> Target project root (default: current directory)\n --json Write structured JSON to stdout\n\n"
|
|
5536
|
+
);
|
|
5537
|
+
}
|
|
5538
|
+
function printHumanResult2(result) {
|
|
5539
|
+
switch (result.status) {
|
|
5540
|
+
case "written":
|
|
5541
|
+
process.stdout.write(`Written: ${result.path}
|
|
5542
|
+
`);
|
|
5543
|
+
break;
|
|
5544
|
+
case "skipped":
|
|
5545
|
+
process.stdout.write(`Skipped: ${result.path} (${result.reason})
|
|
5546
|
+
`);
|
|
5547
|
+
break;
|
|
5548
|
+
case "dry_run":
|
|
5549
|
+
process.stdout.write(`[dry-run] Would write: ${result.path}
|
|
5550
|
+
`);
|
|
5551
|
+
break;
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
async function runScaffoldPublishWorkflowCommand(args) {
|
|
5555
|
+
const firstArg = args[0];
|
|
5556
|
+
if (firstArg === "help" || firstArg === "--help" || firstArg === "-h") {
|
|
5557
|
+
printHelp();
|
|
5558
|
+
process.exit(0);
|
|
5559
|
+
}
|
|
5560
|
+
const { flags, booleans } = parseFlagsFromArgs4(args);
|
|
5561
|
+
const dryRun = booleans.has("dry-run");
|
|
5562
|
+
const force = booleans.has("force");
|
|
5563
|
+
const jsonOutput = booleans.has("json");
|
|
5564
|
+
const projectDir = flags["project-dir"];
|
|
5565
|
+
let result;
|
|
5566
|
+
try {
|
|
5567
|
+
result = await runScaffoldPublishWorkflow({ dryRun, force, projectDir });
|
|
5568
|
+
} catch (err) {
|
|
5569
|
+
process.stderr.write(
|
|
5570
|
+
`scaffold-publish-workflow: ${err instanceof Error ? err.message : String(err)}
|
|
5571
|
+
`
|
|
5572
|
+
);
|
|
5573
|
+
process.exitCode = 1;
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
if (jsonOutput) {
|
|
5577
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
5578
|
+
return;
|
|
5579
|
+
}
|
|
5580
|
+
printHumanResult2(result);
|
|
5581
|
+
}
|
|
5582
|
+
var init_scaffold_publish_workflow2 = __esm({
|
|
5583
|
+
"src/cli/scaffold-publish-workflow.ts"() {
|
|
5456
5584
|
"use strict";
|
|
5585
|
+
init_scaffold_publish_workflow();
|
|
5457
5586
|
}
|
|
5458
5587
|
});
|
|
5459
5588
|
|
|
5460
|
-
// src/
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5589
|
+
// src/cli/resolve-worktree.ts
|
|
5590
|
+
var resolve_worktree_exports = {};
|
|
5591
|
+
__export(resolve_worktree_exports, {
|
|
5592
|
+
ProcessExitSignal: () => ProcessExitSignal,
|
|
5593
|
+
runResolveWorktree: () => runResolveWorktree
|
|
5594
|
+
});
|
|
5595
|
+
import { execSync as execSync5 } from "node:child_process";
|
|
5596
|
+
function distress(kind, message, jsonMode) {
|
|
5597
|
+
if (jsonMode) return;
|
|
5598
|
+
process.stderr.write(`resolve-worktree: ${kind}: ${message}
|
|
5599
|
+
`);
|
|
5600
|
+
}
|
|
5601
|
+
async function runResolveWorktree() {
|
|
5602
|
+
const jsonMode = hasFlag("json", 3);
|
|
5603
|
+
let errorContext = null;
|
|
5604
|
+
const migrationNoticeCallback = (legacyPath, primaryPath) => {
|
|
5605
|
+
if (!jsonMode) {
|
|
5606
|
+
process.stderr.write(
|
|
5607
|
+
`resolve-worktree: local_config_migration: ${legacyPath} is the legacy flat config; move device_id to ${primaryPath}
|
|
5608
|
+
`
|
|
5609
|
+
);
|
|
5471
5610
|
}
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5611
|
+
};
|
|
5612
|
+
try {
|
|
5613
|
+
const projectPath = process.cwd();
|
|
5614
|
+
const found = await findCodebyplanConfig(projectPath);
|
|
5615
|
+
if (!found?.contents.repo_id) {
|
|
5616
|
+
emitAndExit(null, null, jsonMode);
|
|
5617
|
+
}
|
|
5618
|
+
const repoId = found.contents.repo_id;
|
|
5619
|
+
try {
|
|
5620
|
+
await readLocalConfig(projectPath);
|
|
5621
|
+
} catch (readErr) {
|
|
5622
|
+
const readErrCode = readErr.code;
|
|
5623
|
+
errorContext = {
|
|
5624
|
+
kind: readErrCode === "LEGACY_FILE_BLOCKS_DIR" ? "legacy_file_blocks_dir" : "local_config_read_failed",
|
|
5625
|
+
message: readErr instanceof Error ? readErr.message : String(readErr)
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
let deviceId;
|
|
5629
|
+
try {
|
|
5630
|
+
deviceId = await getOrCreateDeviceId(
|
|
5631
|
+
projectPath,
|
|
5632
|
+
migrationNoticeCallback
|
|
5633
|
+
);
|
|
5634
|
+
} catch (deviceErr) {
|
|
5635
|
+
const code = deviceErr.code;
|
|
5636
|
+
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
5637
|
+
errorContext = {
|
|
5638
|
+
kind: "legacy_file_blocks_dir",
|
|
5639
|
+
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
5640
|
+
};
|
|
5641
|
+
} else if (errorContext === null || errorContext.kind !== "local_config_read_failed" && errorContext.kind !== "legacy_file_blocks_dir") {
|
|
5642
|
+
errorContext = {
|
|
5643
|
+
kind: "local_config_write_failed",
|
|
5644
|
+
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
5645
|
+
};
|
|
5479
5646
|
}
|
|
5480
|
-
|
|
5481
|
-
|
|
5647
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
5648
|
+
}
|
|
5649
|
+
let branch = "";
|
|
5650
|
+
try {
|
|
5651
|
+
branch = execSync5("git symbolic-ref --short HEAD", {
|
|
5652
|
+
cwd: projectPath,
|
|
5653
|
+
encoding: "utf-8"
|
|
5654
|
+
}).trim();
|
|
5655
|
+
} catch (gitErr) {
|
|
5656
|
+
if (errorContext === null) {
|
|
5657
|
+
errorContext = {
|
|
5658
|
+
kind: "git_failed",
|
|
5659
|
+
message: gitErr instanceof Error ? gitErr.message : String(gitErr)
|
|
5660
|
+
};
|
|
5482
5661
|
}
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
if (
|
|
5486
|
-
|
|
5662
|
+
}
|
|
5663
|
+
const onResolverError = (kind, err) => {
|
|
5664
|
+
if (errorContext === null) {
|
|
5665
|
+
errorContext = { kind, message: err.message };
|
|
5487
5666
|
}
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5667
|
+
};
|
|
5668
|
+
const worktreeId = await resolveWorktreeId({
|
|
5669
|
+
repoId,
|
|
5670
|
+
repoPath: projectPath,
|
|
5671
|
+
branch,
|
|
5672
|
+
deviceId,
|
|
5673
|
+
onError: onResolverError
|
|
5674
|
+
});
|
|
5675
|
+
if (worktreeId) {
|
|
5676
|
+
emitAndExit(worktreeId, errorContext, jsonMode);
|
|
5677
|
+
}
|
|
5678
|
+
const useFallback = hasFlag("fallback-from-branch", 3);
|
|
5679
|
+
if (useFallback) {
|
|
5680
|
+
const fallbackId = await resolveWorktreeByBranch({
|
|
5681
|
+
repoId,
|
|
5682
|
+
deviceId,
|
|
5683
|
+
branch,
|
|
5684
|
+
onError: onResolverError
|
|
5493
5685
|
});
|
|
5686
|
+
if (fallbackId) {
|
|
5687
|
+
emitAndExit(fallbackId, errorContext, jsonMode);
|
|
5688
|
+
}
|
|
5494
5689
|
}
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5690
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
5691
|
+
} catch (err) {
|
|
5692
|
+
if (err instanceof ProcessExitSignal) throw err;
|
|
5693
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5694
|
+
errorContext = { kind: "unhandled", message: msg };
|
|
5695
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
5696
|
+
}
|
|
5499
5697
|
}
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5698
|
+
function emitAndExit(worktreeId, errorContext, jsonMode) {
|
|
5699
|
+
if (jsonMode) {
|
|
5700
|
+
const errorKind = errorContext?.kind ?? (worktreeId === null ? "tuple_miss" : null);
|
|
5701
|
+
process.stdout.write(
|
|
5702
|
+
JSON.stringify({ worktree_id: worktreeId, error_kind: errorKind }) + "\n"
|
|
5703
|
+
);
|
|
5704
|
+
} else {
|
|
5705
|
+
if (worktreeId !== null) {
|
|
5706
|
+
process.stdout.write(worktreeId);
|
|
5707
|
+
}
|
|
5708
|
+
if (errorContext !== null) {
|
|
5709
|
+
if (errorContext.kind !== "unhandled" || process.env.CODEBYPLAN_DEBUG === "1") {
|
|
5710
|
+
distress(errorContext.kind, errorContext.message, jsonMode);
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
process.exit(0);
|
|
5715
|
+
}
|
|
5716
|
+
var ProcessExitSignal;
|
|
5717
|
+
var init_resolve_worktree2 = __esm({
|
|
5718
|
+
"src/cli/resolve-worktree.ts"() {
|
|
5503
5719
|
"use strict";
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
".gitignore",
|
|
5516
|
-
// CBP-internal hooks — see templates/hooks/README.md "Hooks NOT included and why"
|
|
5517
|
-
"hooks/validate-structure.sh",
|
|
5518
|
-
"hooks/validate-structure-lib.sh",
|
|
5519
|
-
"hooks/validate-structure-patterns.sh",
|
|
5520
|
-
"hooks/validate-structure-templates.sh",
|
|
5521
|
-
"hooks/validate-structure-scope.sh",
|
|
5522
|
-
"hooks/validate-structure-lengths.sh",
|
|
5523
|
-
"hooks/validate-structure-smoke.sh",
|
|
5524
|
-
"hooks/validate-context-usage.sh",
|
|
5525
|
-
"hooks/validate-git-commit.sh"
|
|
5526
|
-
]);
|
|
5720
|
+
init_flags();
|
|
5721
|
+
init_local_config();
|
|
5722
|
+
init_resolve_worktree();
|
|
5723
|
+
ProcessExitSignal = class extends Error {
|
|
5724
|
+
code;
|
|
5725
|
+
constructor(code) {
|
|
5726
|
+
super(`process.exit(${code})`);
|
|
5727
|
+
this.name = "ProcessExitSignal";
|
|
5728
|
+
this.code = code;
|
|
5729
|
+
}
|
|
5730
|
+
};
|
|
5527
5731
|
}
|
|
5528
5732
|
});
|
|
5529
5733
|
|
|
5530
|
-
// src/lib/
|
|
5531
|
-
import
|
|
5532
|
-
import
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
return path3.join(projectDir, ".claude", NEW_MANIFEST_FILENAME);
|
|
5734
|
+
// src/lib/migrate-local-config.ts
|
|
5735
|
+
import { mkdir as mkdir5, readFile as readFile14, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
|
|
5736
|
+
import { join as join19 } from "node:path";
|
|
5737
|
+
function legacySharedPath(projectPath) {
|
|
5738
|
+
return join19(projectPath, ".codebyplan.json");
|
|
5536
5739
|
}
|
|
5537
|
-
function
|
|
5538
|
-
return
|
|
5740
|
+
function legacyLocalPath(projectPath) {
|
|
5741
|
+
return join19(projectPath, ".codebyplan.local.json");
|
|
5539
5742
|
}
|
|
5540
|
-
function
|
|
5541
|
-
return
|
|
5743
|
+
function newDirPath(projectPath) {
|
|
5744
|
+
return join19(projectPath, ".codebyplan");
|
|
5542
5745
|
}
|
|
5543
|
-
function
|
|
5544
|
-
|
|
5545
|
-
if (fs2.existsSync(newFile)) {
|
|
5546
|
-
const raw = fs2.readFileSync(newFile, "utf8");
|
|
5547
|
-
return JSON.parse(raw);
|
|
5548
|
-
}
|
|
5549
|
-
const midFile = midManifestPath(projectDir);
|
|
5550
|
-
if (fs2.existsSync(midFile)) {
|
|
5551
|
-
const raw = fs2.readFileSync(midFile, "utf8");
|
|
5552
|
-
return JSON.parse(raw);
|
|
5553
|
-
}
|
|
5554
|
-
const oldFile = oldManifestPath(projectDir);
|
|
5555
|
-
if (fs2.existsSync(oldFile)) {
|
|
5556
|
-
const raw = fs2.readFileSync(oldFile, "utf8");
|
|
5557
|
-
return JSON.parse(raw);
|
|
5558
|
-
}
|
|
5559
|
-
return null;
|
|
5746
|
+
function sentinelPath(projectPath) {
|
|
5747
|
+
return join19(projectPath, ".codebyplan", "repo.json");
|
|
5560
5748
|
}
|
|
5561
|
-
function
|
|
5562
|
-
const
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
fs2.rmSync(mid);
|
|
5749
|
+
async function statSafe(p) {
|
|
5750
|
+
const { stat: stat2 } = await import("node:fs/promises");
|
|
5751
|
+
try {
|
|
5752
|
+
return await stat2(p);
|
|
5753
|
+
} catch {
|
|
5754
|
+
return null;
|
|
5568
5755
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5756
|
+
}
|
|
5757
|
+
async function needsLocalMigration(projectPath) {
|
|
5758
|
+
try {
|
|
5759
|
+
const legacyStat = await statSafe(legacySharedPath(projectPath));
|
|
5760
|
+
if (!legacyStat) return false;
|
|
5761
|
+
const sentinelStat = await statSafe(sentinelPath(projectPath));
|
|
5762
|
+
if (sentinelStat) return false;
|
|
5763
|
+
return true;
|
|
5764
|
+
} catch {
|
|
5765
|
+
return false;
|
|
5572
5766
|
}
|
|
5573
5767
|
}
|
|
5574
|
-
function
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
}
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
}
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
}
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5768
|
+
async function runLocalMigration(projectPath) {
|
|
5769
|
+
const dirStat = await statSafe(newDirPath(projectPath));
|
|
5770
|
+
if (dirStat && !dirStat.isDirectory()) {
|
|
5771
|
+
throw new Error(
|
|
5772
|
+
".codebyplan exists as a file; remove or rename it before migrating."
|
|
5773
|
+
);
|
|
5774
|
+
}
|
|
5775
|
+
const sentinel = await statSafe(sentinelPath(projectPath));
|
|
5776
|
+
if (sentinel) {
|
|
5777
|
+
return {
|
|
5778
|
+
migrated: true,
|
|
5779
|
+
was_dirty: false,
|
|
5780
|
+
files_changed: [],
|
|
5781
|
+
summary: "already on new layout"
|
|
5782
|
+
};
|
|
5783
|
+
}
|
|
5784
|
+
let legacyRaw;
|
|
5785
|
+
try {
|
|
5786
|
+
legacyRaw = await readFile14(legacySharedPath(projectPath), "utf-8");
|
|
5787
|
+
} catch {
|
|
5788
|
+
return {
|
|
5789
|
+
migrated: true,
|
|
5790
|
+
was_dirty: false,
|
|
5791
|
+
files_changed: [],
|
|
5792
|
+
summary: "legacy .codebyplan.json absent; nothing to migrate"
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
5795
|
+
let parsed;
|
|
5796
|
+
try {
|
|
5797
|
+
parsed = JSON.parse(legacyRaw);
|
|
5798
|
+
} catch (err) {
|
|
5799
|
+
const inner = err instanceof Error ? err.message : String(err);
|
|
5800
|
+
throw new Error(
|
|
5801
|
+
`.codebyplan.json contains invalid JSON \u2014 cannot migrate: ${inner}`
|
|
5802
|
+
);
|
|
5803
|
+
}
|
|
5804
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5805
|
+
throw new Error(
|
|
5806
|
+
".codebyplan.json does not contain a JSON object \u2014 cannot migrate"
|
|
5807
|
+
);
|
|
5808
|
+
}
|
|
5809
|
+
const cfg = parsed;
|
|
5810
|
+
let deviceId;
|
|
5811
|
+
let deviceWrittenByHelper = false;
|
|
5812
|
+
try {
|
|
5813
|
+
const localRaw = await readFile14(legacyLocalPath(projectPath), "utf-8");
|
|
5814
|
+
const localParsed = JSON.parse(localRaw);
|
|
5815
|
+
if (typeof localParsed.device_id === "string") {
|
|
5816
|
+
deviceId = localParsed.device_id;
|
|
5604
5817
|
}
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5818
|
+
} catch {
|
|
5819
|
+
}
|
|
5820
|
+
try {
|
|
5821
|
+
await mkdir5(newDirPath(projectPath), { recursive: true });
|
|
5822
|
+
} catch (err) {
|
|
5823
|
+
const code = err.code;
|
|
5824
|
+
if (code === "ENOTDIR" || code === "EEXIST") {
|
|
5825
|
+
throw new Error(
|
|
5826
|
+
".codebyplan exists as a file; remove or rename it before migrating."
|
|
5827
|
+
);
|
|
5609
5828
|
}
|
|
5610
|
-
|
|
5829
|
+
throw err;
|
|
5611
5830
|
}
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5831
|
+
if (!deviceId) {
|
|
5832
|
+
deviceId = await getOrCreateDeviceId(projectPath);
|
|
5833
|
+
deviceWrittenByHelper = true;
|
|
5834
|
+
}
|
|
5835
|
+
const filesChanged = [];
|
|
5836
|
+
const repoJson = {};
|
|
5837
|
+
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
5838
|
+
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
5839
|
+
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
5840
|
+
await writeFile11(
|
|
5841
|
+
join19(projectPath, ".codebyplan", "repo.json"),
|
|
5842
|
+
JSON.stringify(repoJson, null, 2) + "\n",
|
|
5843
|
+
"utf-8"
|
|
5844
|
+
);
|
|
5845
|
+
filesChanged.push(".codebyplan/repo.json");
|
|
5846
|
+
const serverJson = {};
|
|
5847
|
+
if ("server_port" in cfg) serverJson.server_port = cfg.server_port;
|
|
5848
|
+
if ("server_type" in cfg) serverJson.server_type = cfg.server_type;
|
|
5849
|
+
if ("auto_push_enabled" in cfg)
|
|
5850
|
+
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
5851
|
+
if ("port_allocations" in cfg)
|
|
5852
|
+
serverJson.port_allocations = cfg.port_allocations;
|
|
5853
|
+
await writeFile11(
|
|
5854
|
+
join19(projectPath, ".codebyplan", "server.json"),
|
|
5855
|
+
JSON.stringify(serverJson, null, 2) + "\n",
|
|
5856
|
+
"utf-8"
|
|
5857
|
+
);
|
|
5858
|
+
filesChanged.push(".codebyplan/server.json");
|
|
5859
|
+
const gitJson = {};
|
|
5860
|
+
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
5861
|
+
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
5862
|
+
await writeFile11(
|
|
5863
|
+
join19(projectPath, ".codebyplan", "git.json"),
|
|
5864
|
+
JSON.stringify(gitJson, null, 2) + "\n",
|
|
5865
|
+
"utf-8"
|
|
5866
|
+
);
|
|
5867
|
+
filesChanged.push(".codebyplan/git.json");
|
|
5868
|
+
const shipmentJson = {};
|
|
5869
|
+
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
5870
|
+
await writeFile11(
|
|
5871
|
+
join19(projectPath, ".codebyplan", "shipment.json"),
|
|
5872
|
+
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
5873
|
+
"utf-8"
|
|
5874
|
+
);
|
|
5875
|
+
filesChanged.push(".codebyplan/shipment.json");
|
|
5876
|
+
const vendorJson = {};
|
|
5877
|
+
await writeFile11(
|
|
5878
|
+
join19(projectPath, ".codebyplan", "vendor.json"),
|
|
5879
|
+
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
5880
|
+
"utf-8"
|
|
5881
|
+
);
|
|
5882
|
+
filesChanged.push(".codebyplan/vendor.json");
|
|
5883
|
+
const e2eJson = {};
|
|
5884
|
+
await writeFile11(
|
|
5885
|
+
join19(projectPath, ".codebyplan", "e2e.json"),
|
|
5886
|
+
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
5887
|
+
"utf-8"
|
|
5888
|
+
);
|
|
5889
|
+
filesChanged.push(".codebyplan/e2e.json");
|
|
5890
|
+
const eslintJson = {};
|
|
5891
|
+
await writeFile11(
|
|
5892
|
+
join19(projectPath, ".codebyplan", "eslint.json"),
|
|
5893
|
+
JSON.stringify(eslintJson, null, 2) + "\n",
|
|
5894
|
+
"utf-8"
|
|
5895
|
+
);
|
|
5896
|
+
filesChanged.push(".codebyplan/eslint.json");
|
|
5897
|
+
if (!deviceWrittenByHelper) {
|
|
5898
|
+
await writeFile11(
|
|
5899
|
+
join19(projectPath, ".codebyplan", "device.local.json"),
|
|
5900
|
+
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
5901
|
+
"utf-8"
|
|
5902
|
+
);
|
|
5903
|
+
}
|
|
5904
|
+
filesChanged.push(".codebyplan/device.local.json");
|
|
5905
|
+
const writtenSentinel = await statSafe(sentinelPath(projectPath));
|
|
5906
|
+
if (!writtenSentinel) {
|
|
5907
|
+
throw new Error(
|
|
5908
|
+
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
5909
|
+
);
|
|
5910
|
+
}
|
|
5911
|
+
const gitignorePath = join19(projectPath, ".gitignore");
|
|
5912
|
+
try {
|
|
5913
|
+
const gitignoreContent = await readFile14(gitignorePath, "utf-8");
|
|
5914
|
+
const legacyLine = ".codebyplan.local.json";
|
|
5915
|
+
const newLine = ".codebyplan/device.local.json";
|
|
5916
|
+
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
5917
|
+
const hasNew = gitignoreContent.split("\n").some((l) => l.trimEnd() === newLine);
|
|
5918
|
+
let updated;
|
|
5919
|
+
if (hasLegacy && !hasNew) {
|
|
5920
|
+
updated = gitignoreContent.split("\n").map((l) => {
|
|
5921
|
+
const stripped = l.trimEnd();
|
|
5922
|
+
return stripped === legacyLine ? newLine + (l.endsWith("\r") ? "\r" : "") : l;
|
|
5923
|
+
}).join("\n");
|
|
5924
|
+
} else if (hasLegacy && hasNew) {
|
|
5925
|
+
updated = gitignoreContent.split("\n").filter((l) => l.trimEnd() !== legacyLine).join("\n");
|
|
5926
|
+
} else if (!hasLegacy && !hasNew) {
|
|
5927
|
+
updated = gitignoreContent.endsWith("\n") ? gitignoreContent + newLine + "\n" : gitignoreContent + "\n" + newLine + "\n";
|
|
5928
|
+
} else {
|
|
5929
|
+
updated = gitignoreContent;
|
|
5622
5930
|
}
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5931
|
+
if (updated !== gitignoreContent) {
|
|
5932
|
+
await writeFile11(gitignorePath, updated, "utf-8");
|
|
5933
|
+
filesChanged.push(".gitignore");
|
|
5626
5934
|
}
|
|
5627
|
-
|
|
5935
|
+
} catch {
|
|
5628
5936
|
}
|
|
5629
|
-
|
|
5937
|
+
try {
|
|
5938
|
+
await unlink2(legacySharedPath(projectPath));
|
|
5939
|
+
filesChanged.push(".codebyplan.json (deleted)");
|
|
5940
|
+
} catch {
|
|
5941
|
+
}
|
|
5942
|
+
try {
|
|
5943
|
+
await unlink2(legacyLocalPath(projectPath));
|
|
5944
|
+
filesChanged.push(".codebyplan.local.json (deleted)");
|
|
5945
|
+
} catch {
|
|
5946
|
+
}
|
|
5947
|
+
return {
|
|
5948
|
+
migrated: true,
|
|
5949
|
+
was_dirty: true,
|
|
5950
|
+
files_changed: filesChanged,
|
|
5951
|
+
summary: `migrated legacy .codebyplan.json to .codebyplan/ layout (device_id=${deviceId.slice(0, 8)})`
|
|
5952
|
+
};
|
|
5630
5953
|
}
|
|
5631
|
-
var
|
|
5632
|
-
|
|
5633
|
-
"src/lib/manifest.ts"() {
|
|
5954
|
+
var init_migrate_local_config = __esm({
|
|
5955
|
+
"src/lib/migrate-local-config.ts"() {
|
|
5634
5956
|
"use strict";
|
|
5635
|
-
|
|
5636
|
-
NEW_MANIFEST_FILENAME = ".cbp.manifest.json";
|
|
5637
|
-
MID_MANIFEST_FILENAME = ".cbp-claude.manifest.json";
|
|
5638
|
-
OLD_MANIFEST_FILENAME = ".codebyplan-claude.manifest.json";
|
|
5957
|
+
init_local_config();
|
|
5639
5958
|
}
|
|
5640
5959
|
});
|
|
5641
5960
|
|
|
5642
|
-
// src/
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5961
|
+
// src/cli/config.ts
|
|
5962
|
+
var config_exports = {};
|
|
5963
|
+
__export(config_exports, {
|
|
5964
|
+
readE2eConfig: () => readE2eConfig,
|
|
5965
|
+
readGitConfig: () => readGitConfig,
|
|
5966
|
+
readRepoConfig: () => readRepoConfig,
|
|
5967
|
+
readServerConfig: () => readServerConfig,
|
|
5968
|
+
readShipmentConfig: () => readShipmentConfig,
|
|
5969
|
+
readVendorConfig: () => readVendorConfig,
|
|
5970
|
+
runConfig: () => runConfig
|
|
5971
|
+
});
|
|
5972
|
+
import { mkdir as mkdir6, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
5973
|
+
import { join as join20 } from "node:path";
|
|
5974
|
+
async function runConfig() {
|
|
5975
|
+
const flags = parseFlags(3);
|
|
5976
|
+
const dryRun = hasFlag("dry-run", 3);
|
|
5977
|
+
validateApiKey();
|
|
5978
|
+
const config = await resolveConfig(flags);
|
|
5979
|
+
const { repoId, projectPath } = config;
|
|
5980
|
+
console.log(`
|
|
5981
|
+
CodeByPlan Config`);
|
|
5982
|
+
console.log(` Repo: ${repoId}`);
|
|
5983
|
+
console.log(` Path: ${projectPath}`);
|
|
5984
|
+
if (dryRun) console.log(` Mode: dry-run`);
|
|
5985
|
+
console.log();
|
|
5986
|
+
if (!dryRun && await needsLocalMigration(projectPath)) {
|
|
5987
|
+
console.log(
|
|
5988
|
+
" Migrating legacy .codebyplan.json to .codebyplan/ layout..."
|
|
5649
5989
|
);
|
|
5990
|
+
try {
|
|
5991
|
+
const result = await runLocalMigration(projectPath);
|
|
5992
|
+
if (result.was_dirty) {
|
|
5993
|
+
console.log(" Migration complete.");
|
|
5994
|
+
}
|
|
5995
|
+
} catch (err) {
|
|
5996
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5997
|
+
console.warn(
|
|
5998
|
+
` Warning: migration failed (${msg}); continuing with config sync.`
|
|
5999
|
+
);
|
|
6000
|
+
}
|
|
5650
6001
|
}
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
function rewriteCommand(command) {
|
|
5654
|
-
return command.replace(PLACEHOLDER_RE, REPLACEMENT);
|
|
6002
|
+
await syncConfigToFile(repoId, projectPath, dryRun);
|
|
6003
|
+
console.log("\n Config complete.\n");
|
|
5655
6004
|
}
|
|
5656
|
-
function
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
6005
|
+
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
6006
|
+
const codebyplanDir = join20(projectPath, ".codebyplan");
|
|
6007
|
+
let resolvedWorktreeId;
|
|
6008
|
+
try {
|
|
6009
|
+
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
6010
|
+
let branch = "main";
|
|
6011
|
+
try {
|
|
6012
|
+
const { execSync: execSync6 } = await import("node:child_process");
|
|
6013
|
+
branch = execSync6("git symbolic-ref --short HEAD", {
|
|
6014
|
+
cwd: projectPath,
|
|
6015
|
+
encoding: "utf-8"
|
|
6016
|
+
}).trim();
|
|
6017
|
+
} catch {
|
|
5664
6018
|
}
|
|
5665
|
-
const
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
6019
|
+
const tupleId = await resolveWorktreeId({
|
|
6020
|
+
repoId,
|
|
6021
|
+
repoPath: projectPath,
|
|
6022
|
+
branch,
|
|
6023
|
+
deviceId
|
|
6024
|
+
});
|
|
6025
|
+
if (tupleId) {
|
|
6026
|
+
resolvedWorktreeId = tupleId;
|
|
6027
|
+
} else {
|
|
6028
|
+
resolvedWorktreeId = await resolveAndCacheWorktreeId(repoId, projectPath) ?? void 0;
|
|
6029
|
+
}
|
|
6030
|
+
} catch (err) {
|
|
6031
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6032
|
+
console.warn(
|
|
6033
|
+
` Warning: failed to cache worktree_id (self-heal skipped): ${msg}`
|
|
6034
|
+
);
|
|
6035
|
+
}
|
|
6036
|
+
let repoRes;
|
|
6037
|
+
try {
|
|
6038
|
+
repoRes = await apiGet(`/repos/${repoId}`);
|
|
6039
|
+
} catch (err) {
|
|
6040
|
+
let message;
|
|
6041
|
+
if (err instanceof ApiError) {
|
|
6042
|
+
if (err.status === 401) {
|
|
6043
|
+
message = "Session expired. Run `codebyplan login` and try again.";
|
|
6044
|
+
} else if (err.status === 403 || err.status === 404) {
|
|
6045
|
+
message = "Repo not found or not accessible to your account. Confirm the repo_id in .codebyplan/repo.json and that you are logged in as the right user.";
|
|
6046
|
+
} else if (err.status >= 500) {
|
|
6047
|
+
message = `CodeByPlan server error (status ${err.status}). Please try again shortly.`;
|
|
6048
|
+
} else {
|
|
6049
|
+
message = `Unexpected API error (status ${err.status}). Please try again.`;
|
|
5673
6050
|
}
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
6051
|
+
} else {
|
|
6052
|
+
message = "Failed to reach the CodeByPlan API. Check your network connection and try again.";
|
|
6053
|
+
}
|
|
6054
|
+
process.stderr.write(message + "\n");
|
|
6055
|
+
process.exit(1);
|
|
6056
|
+
}
|
|
6057
|
+
const repo = repoRes.data;
|
|
6058
|
+
let portAllocations = [];
|
|
6059
|
+
try {
|
|
6060
|
+
const portsRes = await apiGet(
|
|
6061
|
+
`/port-allocations`,
|
|
6062
|
+
{ repo_id: repoId }
|
|
6063
|
+
);
|
|
6064
|
+
const allAllocations = portsRes.data ?? [];
|
|
6065
|
+
const filtered = resolvedWorktreeId ? allAllocations.filter((a) => a.worktree_id === resolvedWorktreeId) : allAllocations.filter((a) => !a.worktree_id);
|
|
6066
|
+
const ALLOWED_FIELDS = [
|
|
6067
|
+
"id",
|
|
6068
|
+
"repo_id",
|
|
6069
|
+
"port",
|
|
6070
|
+
"label",
|
|
6071
|
+
"server_type",
|
|
6072
|
+
"auto_start",
|
|
6073
|
+
"command",
|
|
6074
|
+
"working_dir",
|
|
6075
|
+
"env_vars",
|
|
6076
|
+
"external_refs",
|
|
6077
|
+
"worktree_id",
|
|
6078
|
+
"created_at",
|
|
6079
|
+
"updated_at"
|
|
6080
|
+
];
|
|
6081
|
+
portAllocations = filtered.map((a) => {
|
|
6082
|
+
const clean = {};
|
|
6083
|
+
for (const key of ALLOWED_FIELDS) {
|
|
6084
|
+
if (key in a) clean[key] = a[key];
|
|
5689
6085
|
}
|
|
6086
|
+
return clean;
|
|
6087
|
+
});
|
|
6088
|
+
} catch (err) {
|
|
6089
|
+
console.warn(
|
|
6090
|
+
` Warning: failed to fetch port allocations: ${err instanceof Error ? err.message : String(err)}`
|
|
6091
|
+
);
|
|
6092
|
+
}
|
|
6093
|
+
const matchingAlloc = portAllocations[0];
|
|
6094
|
+
const defaultBranchConfig = {
|
|
6095
|
+
protected: ["main"],
|
|
6096
|
+
integration: null,
|
|
6097
|
+
production: "main",
|
|
6098
|
+
staging: null
|
|
6099
|
+
};
|
|
6100
|
+
const branchConfig = repo.branch_config ?? defaultBranchConfig;
|
|
6101
|
+
if (!legacyBranchConfigWarned && typeof branchConfig["integration"] === "string") {
|
|
6102
|
+
legacyBranchConfigWarned = true;
|
|
6103
|
+
process.stderr.write(
|
|
6104
|
+
`warning: legacy 3-branch branch_config detected (integration: '${String(branchConfig["integration"])}'). Run 'npx codebyplan branch migrate' to consolidate to main-only.
|
|
6105
|
+
`
|
|
6106
|
+
);
|
|
6107
|
+
}
|
|
6108
|
+
const repoPayload = { repo_id: repoId };
|
|
6109
|
+
const repoAny = repo;
|
|
6110
|
+
if (typeof repoAny.organization_id === "string") {
|
|
6111
|
+
repoPayload.organization_id = repoAny.organization_id;
|
|
6112
|
+
}
|
|
6113
|
+
if (typeof repoAny.project_id === "string") {
|
|
6114
|
+
repoPayload.project_id = repoAny.project_id;
|
|
6115
|
+
}
|
|
6116
|
+
const serverPayload = {
|
|
6117
|
+
server_port: resolvedWorktreeId && matchingAlloc ? matchingAlloc.port : repo.server_port,
|
|
6118
|
+
server_type: resolvedWorktreeId && matchingAlloc ? matchingAlloc.server_type : repo.server_type,
|
|
6119
|
+
auto_push_enabled: repo.auto_push_enabled,
|
|
6120
|
+
port_allocations: portAllocations
|
|
6121
|
+
};
|
|
6122
|
+
const gitPayload = {
|
|
6123
|
+
git_branch: repo.git_branch ?? "main",
|
|
6124
|
+
branch_config: branchConfig
|
|
6125
|
+
};
|
|
6126
|
+
const shipmentPayload = {};
|
|
6127
|
+
if (typeof repoAny.shipment !== "undefined") {
|
|
6128
|
+
shipmentPayload.shipment = repoAny.shipment;
|
|
6129
|
+
}
|
|
6130
|
+
const vendorPayload = {};
|
|
6131
|
+
const e2ePayload = {};
|
|
6132
|
+
const eslintPayload = {};
|
|
6133
|
+
if (dryRun) {
|
|
6134
|
+
console.log(" Config would be updated (dry-run).");
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
await mkdir6(codebyplanDir, { recursive: true });
|
|
6138
|
+
const files = [
|
|
6139
|
+
{ name: "repo.json", payload: repoPayload },
|
|
6140
|
+
{ name: "server.json", payload: serverPayload },
|
|
6141
|
+
{ name: "git.json", payload: gitPayload },
|
|
6142
|
+
{ name: "shipment.json", payload: shipmentPayload },
|
|
6143
|
+
{ name: "vendor.json", payload: vendorPayload },
|
|
6144
|
+
{ name: "e2e.json", payload: e2ePayload, createOnly: true },
|
|
6145
|
+
{ name: "eslint.json", payload: eslintPayload, createOnly: true }
|
|
6146
|
+
];
|
|
6147
|
+
let anyUpdated = false;
|
|
6148
|
+
for (const { name, payload, createOnly } of files) {
|
|
6149
|
+
const filePath = join20(codebyplanDir, name);
|
|
6150
|
+
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
6151
|
+
let currentJson = "";
|
|
6152
|
+
try {
|
|
6153
|
+
currentJson = await readFile15(filePath, "utf-8");
|
|
6154
|
+
} catch {
|
|
5690
6155
|
}
|
|
6156
|
+
if (createOnly && currentJson !== "") continue;
|
|
6157
|
+
if (currentJson === newJson) continue;
|
|
6158
|
+
await writeFile12(filePath, newJson, "utf-8");
|
|
6159
|
+
console.log(` Updated .codebyplan/${name}`);
|
|
6160
|
+
anyUpdated = true;
|
|
6161
|
+
}
|
|
6162
|
+
if (!anyUpdated) {
|
|
6163
|
+
console.log(" Config up to date.");
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
async function readRepoConfig(projectPath) {
|
|
6167
|
+
try {
|
|
6168
|
+
const raw = await readFile15(
|
|
6169
|
+
join20(projectPath, ".codebyplan", "repo.json"),
|
|
6170
|
+
"utf-8"
|
|
6171
|
+
);
|
|
6172
|
+
return JSON.parse(raw);
|
|
6173
|
+
} catch {
|
|
6174
|
+
return null;
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
async function readServerConfig(projectPath) {
|
|
6178
|
+
try {
|
|
6179
|
+
const raw = await readFile15(
|
|
6180
|
+
join20(projectPath, ".codebyplan", "server.json"),
|
|
6181
|
+
"utf-8"
|
|
6182
|
+
);
|
|
6183
|
+
return JSON.parse(raw);
|
|
6184
|
+
} catch {
|
|
6185
|
+
return null;
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6188
|
+
async function readGitConfig(projectPath) {
|
|
6189
|
+
try {
|
|
6190
|
+
const raw = await readFile15(
|
|
6191
|
+
join20(projectPath, ".codebyplan", "git.json"),
|
|
6192
|
+
"utf-8"
|
|
6193
|
+
);
|
|
6194
|
+
return JSON.parse(raw);
|
|
6195
|
+
} catch {
|
|
6196
|
+
return null;
|
|
5691
6197
|
}
|
|
5692
|
-
return settings;
|
|
5693
6198
|
}
|
|
5694
|
-
function
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
6199
|
+
async function readShipmentConfig(projectPath) {
|
|
6200
|
+
try {
|
|
6201
|
+
const raw = await readFile15(
|
|
6202
|
+
join20(projectPath, ".codebyplan", "shipment.json"),
|
|
6203
|
+
"utf-8"
|
|
6204
|
+
);
|
|
6205
|
+
return JSON.parse(raw);
|
|
6206
|
+
} catch {
|
|
6207
|
+
return null;
|
|
5699
6208
|
}
|
|
5700
|
-
|
|
5701
|
-
|
|
6209
|
+
}
|
|
6210
|
+
async function readVendorConfig(projectPath) {
|
|
6211
|
+
try {
|
|
6212
|
+
const raw = await readFile15(
|
|
6213
|
+
join20(projectPath, ".codebyplan", "vendor.json"),
|
|
6214
|
+
"utf-8"
|
|
6215
|
+
);
|
|
6216
|
+
return JSON.parse(raw);
|
|
6217
|
+
} catch {
|
|
6218
|
+
return null;
|
|
5702
6219
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
6220
|
+
}
|
|
6221
|
+
async function readE2eConfig(projectPath) {
|
|
6222
|
+
try {
|
|
6223
|
+
const raw = await readFile15(
|
|
6224
|
+
join20(projectPath, ".codebyplan", "e2e.json"),
|
|
6225
|
+
"utf-8"
|
|
6226
|
+
);
|
|
6227
|
+
return JSON.parse(raw);
|
|
6228
|
+
} catch {
|
|
6229
|
+
return null;
|
|
5705
6230
|
}
|
|
5706
|
-
|
|
5707
|
-
|
|
6231
|
+
}
|
|
6232
|
+
var legacyBranchConfigWarned;
|
|
6233
|
+
var init_config = __esm({
|
|
6234
|
+
"src/cli/config.ts"() {
|
|
6235
|
+
"use strict";
|
|
6236
|
+
init_flags();
|
|
6237
|
+
init_api();
|
|
6238
|
+
init_resolve_worktree();
|
|
6239
|
+
init_local_config();
|
|
6240
|
+
init_migrate_local_config();
|
|
6241
|
+
legacyBranchConfigWarned = false;
|
|
5708
6242
|
}
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
6243
|
+
});
|
|
6244
|
+
|
|
6245
|
+
// src/lib/server-detect.ts
|
|
6246
|
+
function detectFramework(pkg) {
|
|
6247
|
+
const deps = pkg.dependencies ?? {};
|
|
6248
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
6249
|
+
const hasDep = (name) => name in deps || name in devDeps;
|
|
6250
|
+
if (hasDep("next")) return "nextjs";
|
|
6251
|
+
if (hasDep("@tauri-apps/api") || hasDep("@tauri-apps/cli")) return "tauri";
|
|
6252
|
+
if (hasDep("expo")) return "expo";
|
|
6253
|
+
if (hasDep("vite")) return "vite";
|
|
6254
|
+
if (hasDep("express")) return "express";
|
|
6255
|
+
if (hasDep("@nestjs/core")) return "nestjs";
|
|
6256
|
+
return "custom";
|
|
6257
|
+
}
|
|
6258
|
+
function detectPortFromScripts(pkg) {
|
|
6259
|
+
const scripts = pkg.scripts;
|
|
6260
|
+
if (!scripts?.dev) return null;
|
|
6261
|
+
const parts = scripts.dev.split(/\s+/);
|
|
6262
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
6263
|
+
if (parts[i] === "--port" || parts[i] === "-p") {
|
|
6264
|
+
const next = parts[i + 1];
|
|
6265
|
+
if (next) {
|
|
6266
|
+
const port = parseInt(next, 10);
|
|
6267
|
+
if (!isNaN(port)) return port;
|
|
5718
6268
|
}
|
|
5719
6269
|
}
|
|
5720
6270
|
}
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
6271
|
+
return null;
|
|
6272
|
+
}
|
|
6273
|
+
var init_server_detect = __esm({
|
|
6274
|
+
"src/lib/server-detect.ts"() {
|
|
6275
|
+
"use strict";
|
|
6276
|
+
}
|
|
6277
|
+
});
|
|
6278
|
+
|
|
6279
|
+
// src/lib/port-verify.ts
|
|
6280
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
6281
|
+
async function verifyPorts(projectPath, portAllocations) {
|
|
6282
|
+
const mismatches = [];
|
|
6283
|
+
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
6284
|
+
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
6285
|
+
for (const pkgPath of packageJsonPaths) {
|
|
6286
|
+
try {
|
|
6287
|
+
const raw = await readFile16(pkgPath, "utf-8");
|
|
6288
|
+
const pkg = JSON.parse(raw);
|
|
6289
|
+
const scriptPort = detectPortFromScripts(pkg);
|
|
6290
|
+
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
6291
|
+
const relativePath = pkgPath.replace(projectPath + "/", "");
|
|
6292
|
+
const matchingAlloc = portAllocations.find(
|
|
6293
|
+
(a) => a.label === getAppLabel(relativePath)
|
|
6294
|
+
);
|
|
6295
|
+
mismatches.push({
|
|
6296
|
+
packageJsonPath: relativePath,
|
|
6297
|
+
scriptPort,
|
|
6298
|
+
allocation: matchingAlloc ?? null,
|
|
6299
|
+
reason: matchingAlloc ? `Script uses port ${scriptPort} but allocation has port ${matchingAlloc.port}` : `Port ${scriptPort} in scripts is not in any allocation`
|
|
6300
|
+
});
|
|
5740
6301
|
}
|
|
5741
|
-
|
|
5742
|
-
}
|
|
5743
|
-
if (settings.permissions !== void 0 || Object.keys(existing).length > 0) {
|
|
5744
|
-
settings.permissions = existing;
|
|
6302
|
+
} catch {
|
|
5745
6303
|
}
|
|
5746
6304
|
}
|
|
5747
|
-
return
|
|
6305
|
+
return mismatches;
|
|
5748
6306
|
}
|
|
5749
|
-
function
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
}
|
|
5758
|
-
if (base.subagentStatusLine !== void 0 && settings.subagentStatusLine !== void 0 && JSON.stringify(settings.subagentStatusLine) === JSON.stringify(base.subagentStatusLine)) {
|
|
5759
|
-
delete settings.subagentStatusLine;
|
|
6307
|
+
function isDevServerScript(pkg) {
|
|
6308
|
+
const scripts = pkg.scripts;
|
|
6309
|
+
const raw = scripts?.dev;
|
|
6310
|
+
if (!raw || typeof raw !== "string") return false;
|
|
6311
|
+
const script = raw.trim().toLowerCase();
|
|
6312
|
+
if (!script) return false;
|
|
6313
|
+
for (const pattern of DEV_SERVER_BIN_PATTERNS) {
|
|
6314
|
+
if (pattern.test(script)) return true;
|
|
5760
6315
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
6316
|
+
const tokens = script.split(/\s+/);
|
|
6317
|
+
for (const token of tokens) {
|
|
6318
|
+
if (token === "--port" || token === "-p") return true;
|
|
6319
|
+
if (token.startsWith("--port=")) return true;
|
|
5763
6320
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6321
|
+
return false;
|
|
6322
|
+
}
|
|
6323
|
+
function labelMatchesAppName(label, appName) {
|
|
6324
|
+
if (!label || !appName) return false;
|
|
6325
|
+
const normalize = (s) => s.toLowerCase().replace(/-/g, " ").replace(/[()]/g, " ").replace(/\s+/g, " ").trim();
|
|
6326
|
+
const labelTokens = normalize(label).split(" ").filter(Boolean);
|
|
6327
|
+
const appToken = normalize(appName);
|
|
6328
|
+
if (!appToken) return false;
|
|
6329
|
+
const appTokens = appToken.split(" ").filter(Boolean);
|
|
6330
|
+
if (appTokens.length === 1) {
|
|
6331
|
+
return labelTokens.includes(appTokens[0]);
|
|
5774
6332
|
}
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
if (base.permissions.defaultMode !== void 0 && perms.defaultMode === base.permissions.defaultMode) {
|
|
5778
|
-
delete perms.defaultMode;
|
|
5779
|
-
}
|
|
5780
|
-
if (base.permissions.skipDangerousModePermissionPrompt !== void 0 && perms.skipDangerousModePermissionPrompt === base.permissions.skipDangerousModePermissionPrompt) {
|
|
5781
|
-
delete perms.skipDangerousModePermissionPrompt;
|
|
5782
|
-
}
|
|
5783
|
-
for (const key of ["deny", "ask", "allow"]) {
|
|
5784
|
-
const baseList = base.permissions[key];
|
|
5785
|
-
if (!baseList || baseList.length === 0) continue;
|
|
5786
|
-
const current = perms[key];
|
|
5787
|
-
if (!current) continue;
|
|
5788
|
-
const baseSet = new Set(baseList);
|
|
5789
|
-
const filtered = current.filter((x) => !baseSet.has(x));
|
|
5790
|
-
if (filtered.length === 0) {
|
|
5791
|
-
delete perms[key];
|
|
5792
|
-
} else {
|
|
5793
|
-
perms[key] = filtered;
|
|
5794
|
-
}
|
|
5795
|
-
}
|
|
5796
|
-
if (Object.keys(perms).length === 0) {
|
|
5797
|
-
delete settings.permissions;
|
|
5798
|
-
}
|
|
6333
|
+
for (let i = 0; i <= labelTokens.length - appTokens.length; i++) {
|
|
6334
|
+
if (appTokens.every((t, j) => labelTokens[i + j] === t)) return true;
|
|
5799
6335
|
}
|
|
5800
|
-
return
|
|
6336
|
+
return false;
|
|
5801
6337
|
}
|
|
5802
|
-
function
|
|
5803
|
-
|
|
5804
|
-
|
|
6338
|
+
async function findUnallocatedApps(projectPath, portAllocations) {
|
|
6339
|
+
const apps = await discoverMonorepoApps(projectPath);
|
|
6340
|
+
if (apps.length === 0) {
|
|
6341
|
+
return [];
|
|
5805
6342
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
if (
|
|
6343
|
+
const unallocated = [];
|
|
6344
|
+
for (const app of apps) {
|
|
6345
|
+
if (portAllocations.some((a) => labelMatchesAppName(a.label ?? "", app.name))) {
|
|
5809
6346
|
continue;
|
|
5810
6347
|
}
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
}
|
|
5818
|
-
if (survivingBlocks.length > 0) {
|
|
5819
|
-
settings.hooks[event] = survivingBlocks;
|
|
5820
|
-
} else {
|
|
5821
|
-
delete settings.hooks[event];
|
|
6348
|
+
let pkg;
|
|
6349
|
+
try {
|
|
6350
|
+
const raw = await readFile16(`${app.absPath}/package.json`, "utf-8");
|
|
6351
|
+
pkg = JSON.parse(raw);
|
|
6352
|
+
} catch {
|
|
6353
|
+
continue;
|
|
5822
6354
|
}
|
|
6355
|
+
if (!isDevServerScript(pkg)) continue;
|
|
6356
|
+
const framework = detectFramework(pkg);
|
|
6357
|
+
const detectedPort = detectPortFromScripts(pkg);
|
|
6358
|
+
const command = `pnpm --filter ${app.name} dev`;
|
|
6359
|
+
unallocated.push({
|
|
6360
|
+
name: app.name,
|
|
6361
|
+
path: app.path,
|
|
6362
|
+
framework,
|
|
6363
|
+
detectedPort,
|
|
6364
|
+
command
|
|
6365
|
+
});
|
|
5823
6366
|
}
|
|
5824
|
-
|
|
5825
|
-
|
|
6367
|
+
return unallocated;
|
|
6368
|
+
}
|
|
6369
|
+
function getAppLabel(relativePath) {
|
|
6370
|
+
const parts = relativePath.split("/");
|
|
6371
|
+
if (parts.length >= 3 && parts[0] === "apps") {
|
|
6372
|
+
return parts[1];
|
|
5826
6373
|
}
|
|
5827
|
-
return
|
|
6374
|
+
return "root";
|
|
5828
6375
|
}
|
|
5829
|
-
var
|
|
5830
|
-
var
|
|
5831
|
-
"src/lib/
|
|
6376
|
+
var DEV_SERVER_BIN_PATTERNS;
|
|
6377
|
+
var init_port_verify = __esm({
|
|
6378
|
+
"src/lib/port-verify.ts"() {
|
|
5832
6379
|
"use strict";
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
"outputStyle",
|
|
5844
|
-
"preferredNotifChannel",
|
|
5845
|
-
"prefersReducedMotion",
|
|
5846
|
-
"respectGitignore",
|
|
5847
|
-
"showTurnDuration",
|
|
5848
|
-
"spinnerTipsEnabled",
|
|
5849
|
-
"terminalProgressBarEnabled",
|
|
5850
|
-
"viewMode",
|
|
5851
|
-
"autoScrollEnabled",
|
|
5852
|
-
"cleanupPeriodDays",
|
|
5853
|
-
"includeGitInstructions",
|
|
5854
|
-
"showThinkingSummaries",
|
|
5855
|
-
"disableSkillShellExecution",
|
|
5856
|
-
"skipWebFetchPreflight",
|
|
5857
|
-
"fastModePerSessionOptIn",
|
|
5858
|
-
"effortLevel",
|
|
5859
|
-
"showClearContextOnPlanAccept",
|
|
5860
|
-
"syntaxHighlightingDisabled"
|
|
6380
|
+
init_tech_detect();
|
|
6381
|
+
init_server_detect();
|
|
6382
|
+
DEV_SERVER_BIN_PATTERNS = [
|
|
6383
|
+
/\bnext\s+dev\b/,
|
|
6384
|
+
/\bnest\s+start\b/,
|
|
6385
|
+
/\bvite\s+(?:dev|serve)\b/,
|
|
6386
|
+
/\bvite\s+preview\b/,
|
|
6387
|
+
/\bnuxt\s+dev\b/,
|
|
6388
|
+
/\b(?:svelte-kit|sveltekit)\s+dev\b/,
|
|
6389
|
+
/\bexpo\s+start\b/
|
|
5861
6390
|
];
|
|
5862
6391
|
}
|
|
5863
6392
|
});
|
|
5864
6393
|
|
|
5865
|
-
// src/cli/
|
|
5866
|
-
var
|
|
5867
|
-
__export(
|
|
5868
|
-
|
|
5869
|
-
runInstall: () => runInstall
|
|
6394
|
+
// src/cli/ports.ts
|
|
6395
|
+
var ports_exports = {};
|
|
6396
|
+
__export(ports_exports, {
|
|
6397
|
+
runPorts: () => runPorts
|
|
5870
6398
|
});
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
const
|
|
5877
|
-
const
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
}
|
|
5886
|
-
}
|
|
5887
|
-
throw new Error(
|
|
5888
|
-
`codebyplan: could not locate templates/ directory. Probed:
|
|
5889
|
-
${candidates.join(
|
|
5890
|
-
"\n "
|
|
5891
|
-
)}`
|
|
5892
|
-
);
|
|
5893
|
-
}
|
|
5894
|
-
async function runInstall(opts, deps = {}) {
|
|
5895
|
-
await Promise.resolve();
|
|
5896
|
-
const scope = opts.scope ?? "project";
|
|
5897
|
-
if (scope === "user") {
|
|
5898
|
-
if (opts.renderer) {
|
|
5899
|
-
console.warn(
|
|
5900
|
-
"codebyplan claude install: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
|
|
5901
|
-
);
|
|
5902
|
-
}
|
|
5903
|
-
runInstallUser(opts, deps);
|
|
5904
|
-
return;
|
|
5905
|
-
}
|
|
5906
|
-
const projectDir = deps.projectDir ?? process.cwd();
|
|
5907
|
-
let templatesDir;
|
|
6399
|
+
async function runPorts() {
|
|
6400
|
+
const flags = parseFlags(3);
|
|
6401
|
+
const dryRun = hasFlag("dry-run", 3);
|
|
6402
|
+
const fix = hasFlag("fix", 3);
|
|
6403
|
+
validateApiKey();
|
|
6404
|
+
const config = await resolveConfig(flags);
|
|
6405
|
+
const { repoId, projectPath } = config;
|
|
6406
|
+
console.log(`
|
|
6407
|
+
CodeByPlan Ports`);
|
|
6408
|
+
console.log(` Repo: ${repoId}`);
|
|
6409
|
+
console.log(` Path: ${projectPath}`);
|
|
6410
|
+
if (dryRun) console.log(` Mode: dry-run`);
|
|
6411
|
+
if (fix) console.log(` Mode: fix`);
|
|
6412
|
+
console.log();
|
|
5908
6413
|
try {
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
|
|
6414
|
+
const portsRes = await apiGet(
|
|
6415
|
+
`/port-allocations`,
|
|
6416
|
+
{ repo_id: repoId }
|
|
5913
6417
|
);
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
|
|
5926
|
-
}
|
|
5927
|
-
} else {
|
|
5928
|
-
fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
|
|
5929
|
-
fs3.copyFileSync(absSrc, absDest);
|
|
5930
|
-
if (opts.verbose) {
|
|
5931
|
-
console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
|
|
5932
|
-
}
|
|
6418
|
+
const allocations = portsRes.data ?? [];
|
|
6419
|
+
if (allocations.length === 0) {
|
|
6420
|
+
console.log(" No port allocations found \u2014 skipping verification.");
|
|
6421
|
+
console.log("\n Ports complete.\n");
|
|
6422
|
+
return;
|
|
6423
|
+
}
|
|
6424
|
+
const mismatches = await verifyPorts(projectPath, allocations);
|
|
6425
|
+
if (mismatches.length > 0) {
|
|
6426
|
+
console.log(` Port mismatches: ${mismatches.length}`);
|
|
6427
|
+
for (const m of mismatches) {
|
|
6428
|
+
console.log(` ! ${m.packageJsonPath}: ${m.reason}`);
|
|
5933
6429
|
}
|
|
5934
|
-
manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
|
|
5935
6430
|
}
|
|
5936
|
-
const
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
const hasBase = fs3.existsSync(baseSettingsPath);
|
|
5943
|
-
if (hasHooks || hasBase) {
|
|
5944
|
-
const settingsPath = path4.join(projectDir, ".claude", "settings.json");
|
|
5945
|
-
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5946
|
-
if (hasBase) {
|
|
5947
|
-
const base = JSON.parse(
|
|
5948
|
-
fs3.readFileSync(baseSettingsPath, "utf8")
|
|
6431
|
+
const unallocated = await findUnallocatedApps(projectPath, allocations);
|
|
6432
|
+
if (unallocated.length > 0) {
|
|
6433
|
+
console.log(` Unallocated apps: ${unallocated.length}`);
|
|
6434
|
+
for (const app of unallocated) {
|
|
6435
|
+
console.log(
|
|
6436
|
+
` + ${app.name} (${app.framework}${app.detectedPort ? `, port ${app.detectedPort}` : ""})`
|
|
5949
6437
|
);
|
|
5950
|
-
mergeBaseSettingsIntoSettings(existingSettings, base);
|
|
5951
6438
|
}
|
|
5952
|
-
if (
|
|
5953
|
-
const
|
|
5954
|
-
|
|
5955
|
-
)
|
|
5956
|
-
|
|
6439
|
+
if (fix && !dryRun) {
|
|
6440
|
+
const maxPort = Math.max(...allocations.map((a) => a.port), 2999);
|
|
6441
|
+
let nextPort = maxPort + 1;
|
|
6442
|
+
for (const app of unallocated) {
|
|
6443
|
+
const port = app.detectedPort ?? nextPort++;
|
|
6444
|
+
try {
|
|
6445
|
+
await apiPost("/port-allocations", {
|
|
6446
|
+
repo_id: repoId,
|
|
6447
|
+
port,
|
|
6448
|
+
label: app.name,
|
|
6449
|
+
server_type: app.framework,
|
|
6450
|
+
auto_start: "manual",
|
|
6451
|
+
command: app.command,
|
|
6452
|
+
working_dir: app.path
|
|
6453
|
+
});
|
|
6454
|
+
console.log(` Created allocation: ${app.name} \u2192 port ${port}`);
|
|
6455
|
+
} catch (err) {
|
|
6456
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6457
|
+
console.log(
|
|
6458
|
+
` Failed to create allocation for ${app.name}: ${msg}`
|
|
6459
|
+
);
|
|
6460
|
+
}
|
|
6461
|
+
if (app.detectedPort && app.detectedPort >= nextPort) {
|
|
6462
|
+
nextPort = app.detectedPort + 1;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
} else if (fix && dryRun) {
|
|
6466
|
+
console.log(" (dry-run \u2014 would create allocations with --fix)");
|
|
6467
|
+
} else {
|
|
6468
|
+
console.log(" Run with --fix to auto-create allocations.");
|
|
5957
6469
|
}
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
6470
|
+
}
|
|
6471
|
+
if (mismatches.length === 0 && unallocated.length === 0) {
|
|
6472
|
+
console.log(" Ports verified.");
|
|
6473
|
+
}
|
|
6474
|
+
} catch (err) {
|
|
6475
|
+
console.warn(
|
|
6476
|
+
` Port verification skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
6477
|
+
);
|
|
6478
|
+
}
|
|
6479
|
+
console.log("\n Ports complete.\n");
|
|
6480
|
+
}
|
|
6481
|
+
var init_ports = __esm({
|
|
6482
|
+
"src/cli/ports.ts"() {
|
|
6483
|
+
"use strict";
|
|
6484
|
+
init_flags();
|
|
6485
|
+
init_api();
|
|
6486
|
+
init_port_verify();
|
|
6487
|
+
}
|
|
6488
|
+
});
|
|
6489
|
+
|
|
6490
|
+
// src/cli/tech-stack.ts
|
|
6491
|
+
var tech_stack_exports = {};
|
|
6492
|
+
__export(tech_stack_exports, {
|
|
6493
|
+
runFullTechStack: () => runFullTechStack,
|
|
6494
|
+
runTechStack: () => runTechStack
|
|
6495
|
+
});
|
|
6496
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
6497
|
+
async function runTechStack() {
|
|
6498
|
+
const flags = parseFlags(3);
|
|
6499
|
+
const dryRun = hasFlag("dry-run", 3);
|
|
6500
|
+
if (hasFlag("full-tech-stack", 3)) {
|
|
6501
|
+
await runFullTechStack(dryRun);
|
|
6502
|
+
return;
|
|
6503
|
+
}
|
|
6504
|
+
validateApiKey();
|
|
6505
|
+
const config = await resolveConfig(flags);
|
|
6506
|
+
const { repoId, projectPath } = config;
|
|
6507
|
+
console.log(`
|
|
6508
|
+
CodeByPlan Tech Stack`);
|
|
6509
|
+
console.log(` Repo: ${repoId}`);
|
|
6510
|
+
console.log(` Path: ${projectPath}`);
|
|
6511
|
+
if (dryRun) console.log(` Mode: dry-run`);
|
|
6512
|
+
console.log();
|
|
6513
|
+
if (dryRun) {
|
|
6514
|
+
try {
|
|
6515
|
+
if (await needsLocalMigration(projectPath)) {
|
|
5966
6516
|
console.log(
|
|
5967
|
-
`
|
|
6517
|
+
` Would migrate .codebyplan.json -> worktree_id to .codebyplan.local.json (dry-run, skipping actual write).`
|
|
5968
6518
|
);
|
|
5969
6519
|
}
|
|
6520
|
+
} catch {
|
|
5970
6521
|
}
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
6522
|
+
} else {
|
|
6523
|
+
try {
|
|
6524
|
+
if (await needsLocalMigration(projectPath)) {
|
|
6525
|
+
const result = await runLocalMigration(projectPath);
|
|
6526
|
+
console.log(
|
|
6527
|
+
` Migrated .codebyplan.json to .codebyplan/ layout: ${result.summary}`
|
|
6528
|
+
);
|
|
6529
|
+
console.log(
|
|
6530
|
+
` Suggest /cbp-git-commit to stage the cleaned shared file.`
|
|
6531
|
+
);
|
|
6532
|
+
}
|
|
6533
|
+
} catch (err) {
|
|
6534
|
+
console.warn(
|
|
6535
|
+
` Warning: local migration failed (continuing): ${err instanceof Error ? err.message : String(err)}`
|
|
5978
6536
|
);
|
|
5979
6537
|
}
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6538
|
+
}
|
|
6539
|
+
try {
|
|
6540
|
+
const { dependencies } = await scanAllDependencies(projectPath);
|
|
6541
|
+
if (dependencies.length === 0) {
|
|
6542
|
+
console.log(" No dependencies found.");
|
|
6543
|
+
console.log("\n Tech stack complete.\n");
|
|
6544
|
+
return;
|
|
5984
6545
|
}
|
|
6546
|
+
const sourcePaths = new Set(dependencies.map((d) => d.source_path));
|
|
5985
6547
|
console.log(
|
|
5986
|
-
`
|
|
6548
|
+
` ${dependencies.length} dependencies from ${sourcePaths.size} package.json file${sourcePaths.size !== 1 ? "s" : ""}`
|
|
5987
6549
|
);
|
|
5988
|
-
if (
|
|
5989
|
-
await
|
|
6550
|
+
if (!dryRun) {
|
|
6551
|
+
const result = await apiPost(`/repos/${repoId}/tech-stack`, { dependencies });
|
|
6552
|
+
if (result.data.stale_removed > 0) {
|
|
6553
|
+
console.log(
|
|
6554
|
+
` ${result.data.stale_removed} stale dependencies removed`
|
|
6555
|
+
);
|
|
6556
|
+
}
|
|
6557
|
+
try {
|
|
6558
|
+
const { execSync: execSync6 } = await import("node:child_process");
|
|
6559
|
+
let branch = "main";
|
|
6560
|
+
try {
|
|
6561
|
+
branch = execSync6("git symbolic-ref --short HEAD", {
|
|
6562
|
+
cwd: projectPath,
|
|
6563
|
+
encoding: "utf-8"
|
|
6564
|
+
}).trim();
|
|
6565
|
+
} catch {
|
|
6566
|
+
}
|
|
6567
|
+
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
6568
|
+
const tupleId = await resolveWorktreeId({
|
|
6569
|
+
repoId,
|
|
6570
|
+
repoPath: projectPath,
|
|
6571
|
+
branch,
|
|
6572
|
+
deviceId
|
|
6573
|
+
});
|
|
6574
|
+
await callMcpTool("enqueue_todo_job", {
|
|
6575
|
+
repo_id: repoId,
|
|
6576
|
+
...tupleId ? { worktree_id: tupleId } : {},
|
|
6577
|
+
reason: "CLI_SYNC"
|
|
6578
|
+
});
|
|
6579
|
+
} catch {
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
const detected = await detectTechStack(projectPath);
|
|
6583
|
+
if (detected.flat.length > 0) {
|
|
6584
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
6585
|
+
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
6586
|
+
const { merged, added } = mergeTechStack(remote, detected);
|
|
6587
|
+
if (added.length > 0) {
|
|
6588
|
+
console.log(` ${added.length} new tech entries`);
|
|
6589
|
+
if (!dryRun) {
|
|
6590
|
+
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
5990
6593
|
}
|
|
5991
6594
|
} catch (err) {
|
|
5992
|
-
console.
|
|
5993
|
-
`
|
|
6595
|
+
console.warn(
|
|
6596
|
+
` Tech stack detection skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
5994
6597
|
);
|
|
5995
|
-
process.exitCode = 1;
|
|
5996
6598
|
}
|
|
6599
|
+
console.log("\n Tech stack complete.\n");
|
|
5997
6600
|
}
|
|
5998
|
-
function
|
|
5999
|
-
let templatesDir;
|
|
6000
|
-
try {
|
|
6001
|
-
templatesDir = deps.templatesDir ?? resolveTemplatesDir();
|
|
6002
|
-
} catch (err) {
|
|
6003
|
-
console.error(
|
|
6004
|
-
err instanceof Error ? err.message : `codebyplan claude install: ${String(err)}`
|
|
6005
|
-
);
|
|
6006
|
-
process.exitCode = 1;
|
|
6007
|
-
return;
|
|
6008
|
-
}
|
|
6601
|
+
async function syncTechStackForPath(repoId, projectPath, dryRun) {
|
|
6009
6602
|
try {
|
|
6010
|
-
const
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
templatesDir,
|
|
6014
|
-
"settings.user.base.json"
|
|
6015
|
-
);
|
|
6016
|
-
if (!fs3.existsSync(userBaseSettingsPath)) {
|
|
6017
|
-
console.error(
|
|
6018
|
-
"codebyplan claude install: settings.user.base.json not found in templates."
|
|
6019
|
-
);
|
|
6020
|
-
process.exitCode = 1;
|
|
6603
|
+
const { dependencies } = await scanAllDependencies(projectPath);
|
|
6604
|
+
if (dependencies.length === 0) {
|
|
6605
|
+
console.log(" No dependencies found.");
|
|
6021
6606
|
return;
|
|
6022
6607
|
}
|
|
6023
|
-
const
|
|
6024
|
-
fs3.readFileSync(userBaseSettingsPath, "utf8")
|
|
6025
|
-
);
|
|
6026
|
-
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
6027
|
-
mergeBaseSettingsIntoSettings(existingSettings, userBase);
|
|
6028
|
-
if (!opts.dryRun) {
|
|
6029
|
-
fs3.mkdirSync(userDir, { recursive: true });
|
|
6030
|
-
fs3.writeFileSync(
|
|
6031
|
-
settingsPath,
|
|
6032
|
-
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
6033
|
-
"utf8"
|
|
6034
|
-
);
|
|
6035
|
-
const manifest = defaultManifest();
|
|
6036
|
-
manifest.files = [];
|
|
6037
|
-
writeManifestForScope("user", manifest, userDir);
|
|
6038
|
-
} else if (opts.verbose) {
|
|
6039
|
-
console.log(
|
|
6040
|
-
`[dry-run] would merge user base settings into ${settingsPath}`
|
|
6041
|
-
);
|
|
6042
|
-
}
|
|
6608
|
+
const sourcePaths = new Set(dependencies.map((d) => d.source_path));
|
|
6043
6609
|
console.log(
|
|
6044
|
-
`
|
|
6610
|
+
` ${dependencies.length} dependencies from ${sourcePaths.size} package.json file${sourcePaths.size !== 1 ? "s" : ""}`
|
|
6045
6611
|
);
|
|
6612
|
+
if (!dryRun) {
|
|
6613
|
+
const result = await apiPost(`/repos/${repoId}/tech-stack`, { dependencies });
|
|
6614
|
+
if (result.data.stale_removed > 0) {
|
|
6615
|
+
console.log(
|
|
6616
|
+
` ${result.data.stale_removed} stale dependencies removed`
|
|
6617
|
+
);
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
const detected = await detectTechStack(projectPath);
|
|
6621
|
+
if (detected.flat.length > 0) {
|
|
6622
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
6623
|
+
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
6624
|
+
const { merged, added } = mergeTechStack(remote, detected);
|
|
6625
|
+
if (added.length > 0) {
|
|
6626
|
+
console.log(` ${added.length} new tech entries`);
|
|
6627
|
+
if (!dryRun) {
|
|
6628
|
+
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
6629
|
+
}
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6046
6632
|
} catch (err) {
|
|
6633
|
+
console.warn(
|
|
6634
|
+
` Tech stack detection skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
6635
|
+
);
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
async function runFullTechStack(dryRun) {
|
|
6639
|
+
validateApiKey();
|
|
6640
|
+
const localConfig = await readLocalConfig(process.cwd());
|
|
6641
|
+
if (!localConfig?.device_id) {
|
|
6047
6642
|
console.error(
|
|
6048
|
-
`codebyplan
|
|
6643
|
+
" --full-tech-stack requires a device_id in .codebyplan/device.local.json.\n Run `npx codebyplan setup` in this directory first to register the device."
|
|
6049
6644
|
);
|
|
6050
6645
|
process.exitCode = 1;
|
|
6646
|
+
return;
|
|
6051
6647
|
}
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6648
|
+
console.log("\n CodeByPlan Tech Stack \u2014 Full (all local worktrees)");
|
|
6649
|
+
if (dryRun) console.log(" Mode: dry-run");
|
|
6650
|
+
console.log();
|
|
6651
|
+
const reposRes = await apiGet("/repos");
|
|
6652
|
+
const repos = reposRes.data ?? [];
|
|
6653
|
+
let synced = 0;
|
|
6654
|
+
let skipped = 0;
|
|
6655
|
+
for (const repo of repos) {
|
|
6656
|
+
let worktrees = [];
|
|
6657
|
+
try {
|
|
6658
|
+
const wtRes = await apiGet(
|
|
6659
|
+
`/worktrees?repo_id=${repo.id}`
|
|
6660
|
+
);
|
|
6661
|
+
worktrees = wtRes.data ?? [];
|
|
6662
|
+
} catch (err) {
|
|
6663
|
+
console.warn(
|
|
6664
|
+
` Warning: failed to fetch worktrees for ${repo.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
6665
|
+
);
|
|
6666
|
+
continue;
|
|
6061
6667
|
}
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
console.error(
|
|
6065
|
-
`codebyplan: could not count hook entries in hooks.json: ${err instanceof Error ? err.message : String(err)}`
|
|
6668
|
+
const localWorktrees = worktrees.filter(
|
|
6669
|
+
(wt) => wt.path ? existsSync4(wt.path) : false
|
|
6066
6670
|
);
|
|
6067
|
-
|
|
6671
|
+
if (localWorktrees.length === 0) {
|
|
6672
|
+
console.log(` skipping ${repo.name} \u2014 no local worktree on this device`);
|
|
6673
|
+
skipped++;
|
|
6674
|
+
continue;
|
|
6675
|
+
}
|
|
6676
|
+
for (const wt of localWorktrees) {
|
|
6677
|
+
console.log(` ==> Syncing ${repo.name} @ ${wt.path}`);
|
|
6678
|
+
try {
|
|
6679
|
+
await syncTechStackForPath(repo.id, wt.path, dryRun);
|
|
6680
|
+
synced++;
|
|
6681
|
+
} catch (err) {
|
|
6682
|
+
console.error(
|
|
6683
|
+
` Error syncing tech stack for ${repo.name} @ ${wt.path}: ${err instanceof Error ? err.message : String(err)}`
|
|
6684
|
+
);
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6068
6687
|
}
|
|
6688
|
+
console.log(
|
|
6689
|
+
`
|
|
6690
|
+
Walked ${repos.length} repos, synced ${synced} local worktrees, skipped ${skipped} remote.
|
|
6691
|
+
`
|
|
6692
|
+
);
|
|
6069
6693
|
}
|
|
6070
|
-
var
|
|
6071
|
-
"src/cli/
|
|
6694
|
+
var init_tech_stack = __esm({
|
|
6695
|
+
"src/cli/tech-stack.ts"() {
|
|
6072
6696
|
"use strict";
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6697
|
+
init_flags();
|
|
6698
|
+
init_api();
|
|
6699
|
+
init_tech_detect();
|
|
6700
|
+
init_migrate_local_config();
|
|
6701
|
+
init_local_config();
|
|
6702
|
+
init_resolve_worktree();
|
|
6078
6703
|
}
|
|
6079
6704
|
});
|
|
6080
6705
|
|
|
@@ -6096,11 +6721,11 @@ async function ask(q, opts) {
|
|
|
6096
6721
|
try {
|
|
6097
6722
|
while (true) {
|
|
6098
6723
|
const choices = q.choices.map((c) => `[${c.key}] ${c.label}`).join(" ");
|
|
6099
|
-
const answer = await new Promise((
|
|
6724
|
+
const answer = await new Promise((resolve7) => {
|
|
6100
6725
|
rl.question(`${q.message}
|
|
6101
6726
|
${choices}
|
|
6102
6727
|
> `, (input) => {
|
|
6103
|
-
|
|
6728
|
+
resolve7(input.trim().toLowerCase());
|
|
6104
6729
|
});
|
|
6105
6730
|
});
|
|
6106
6731
|
const match = q.choices.find(
|
|
@@ -6194,9 +6819,9 @@ var update_exports = {};
|
|
|
6194
6819
|
__export(update_exports, {
|
|
6195
6820
|
runUpdate: () => runUpdate
|
|
6196
6821
|
});
|
|
6197
|
-
import * as
|
|
6822
|
+
import * as fs5 from "node:fs";
|
|
6198
6823
|
import * as os3 from "node:os";
|
|
6199
|
-
import * as
|
|
6824
|
+
import * as path6 from "node:path";
|
|
6200
6825
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6201
6826
|
async function runUpdate(opts, deps = {}) {
|
|
6202
6827
|
await Promise.resolve();
|
|
@@ -6236,10 +6861,10 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6236
6861
|
finalManifestEntries.push(e);
|
|
6237
6862
|
}
|
|
6238
6863
|
for (const { packaged, absSrc } of plan.overwriteSafe) {
|
|
6239
|
-
const absDest =
|
|
6864
|
+
const absDest = path6.join(projectDir, ".claude", packaged.dest);
|
|
6240
6865
|
if (!opts.dryRun) {
|
|
6241
|
-
|
|
6242
|
-
|
|
6866
|
+
fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
|
|
6867
|
+
fs5.copyFileSync(absSrc, absDest);
|
|
6243
6868
|
if (opts.verbose) console.log(`updated ${packaged.dest}`);
|
|
6244
6869
|
} else if (opts.verbose) {
|
|
6245
6870
|
console.log(`[dry-run] would update ${packaged.dest}`);
|
|
@@ -6251,8 +6876,8 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6251
6876
|
absSrc,
|
|
6252
6877
|
onDiskContent
|
|
6253
6878
|
} of plan.overwriteHandEdited) {
|
|
6254
|
-
const absDest =
|
|
6255
|
-
const newContent =
|
|
6879
|
+
const absDest = path6.join(projectDir, ".claude", packaged.dest);
|
|
6880
|
+
const newContent = fs5.readFileSync(absSrc);
|
|
6256
6881
|
const showDiff = () => {
|
|
6257
6882
|
console.log(
|
|
6258
6883
|
renderDiff(
|
|
@@ -6264,8 +6889,8 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6264
6889
|
const answer = await promptOverwrite(packaged.dest, opts, showDiff);
|
|
6265
6890
|
if (answer === "overwrite") {
|
|
6266
6891
|
if (!opts.dryRun) {
|
|
6267
|
-
|
|
6268
|
-
|
|
6892
|
+
fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
|
|
6893
|
+
fs5.copyFileSync(absSrc, absDest);
|
|
6269
6894
|
}
|
|
6270
6895
|
finalManifestEntries.push(packaged);
|
|
6271
6896
|
} else {
|
|
@@ -6280,10 +6905,10 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6280
6905
|
for (const { packaged, absSrc } of plan.newOptIn) {
|
|
6281
6906
|
const answer = await promptOptIn(packaged.dest, opts);
|
|
6282
6907
|
if (answer === "opt-in") {
|
|
6283
|
-
const absDest =
|
|
6908
|
+
const absDest = path6.join(projectDir, ".claude", packaged.dest);
|
|
6284
6909
|
if (!opts.dryRun) {
|
|
6285
|
-
|
|
6286
|
-
|
|
6910
|
+
fs5.mkdirSync(path6.dirname(absDest), { recursive: true });
|
|
6911
|
+
fs5.copyFileSync(absSrc, absDest);
|
|
6287
6912
|
}
|
|
6288
6913
|
finalManifestEntries.push(packaged);
|
|
6289
6914
|
if (opts.verbose) console.log(`installed new file ${packaged.dest}`);
|
|
@@ -6294,25 +6919,25 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6294
6919
|
for (const e of plan.removedFromPackage) {
|
|
6295
6920
|
const answer = await promptRemove(e.dest, opts);
|
|
6296
6921
|
if (answer === "remove") {
|
|
6297
|
-
const absDest =
|
|
6298
|
-
if (!opts.dryRun &&
|
|
6299
|
-
|
|
6300
|
-
const claudeDir =
|
|
6301
|
-
let cur =
|
|
6302
|
-
while (cur !== claudeDir && cur !==
|
|
6303
|
-
if (
|
|
6922
|
+
const absDest = path6.join(projectDir, ".claude", e.dest);
|
|
6923
|
+
if (!opts.dryRun && fs5.existsSync(absDest)) {
|
|
6924
|
+
fs5.rmSync(absDest);
|
|
6925
|
+
const claudeDir = path6.join(projectDir, ".claude");
|
|
6926
|
+
let cur = path6.dirname(absDest);
|
|
6927
|
+
while (cur !== claudeDir && cur !== path6.dirname(cur)) {
|
|
6928
|
+
if (path6.dirname(cur) === claudeDir) break;
|
|
6304
6929
|
try {
|
|
6305
|
-
|
|
6930
|
+
fs5.rmdirSync(cur);
|
|
6306
6931
|
if (opts.verbose)
|
|
6307
6932
|
console.log(
|
|
6308
|
-
`pruned empty dir ${
|
|
6933
|
+
`pruned empty dir ${path6.relative(claudeDir, cur)}`
|
|
6309
6934
|
);
|
|
6310
|
-
cur =
|
|
6935
|
+
cur = path6.dirname(cur);
|
|
6311
6936
|
} catch (err) {
|
|
6312
6937
|
const code = err.code;
|
|
6313
6938
|
if (code !== "ENOTEMPTY" && code !== "ENOENT") {
|
|
6314
6939
|
console.warn(
|
|
6315
|
-
`codebyplan claude: could not prune empty dir ${
|
|
6940
|
+
`codebyplan claude: could not prune empty dir ${path6.relative(claudeDir, cur)}: ${err.message}`
|
|
6316
6941
|
);
|
|
6317
6942
|
}
|
|
6318
6943
|
break;
|
|
@@ -6324,17 +6949,17 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6324
6949
|
if (opts.verbose) console.log(`kept (untracked) ${e.dest}`);
|
|
6325
6950
|
}
|
|
6326
6951
|
}
|
|
6327
|
-
const hooksJsonPath =
|
|
6328
|
-
if (
|
|
6952
|
+
const hooksJsonPath = path6.join(templatesDir, "hooks", "hooks.json");
|
|
6953
|
+
if (fs5.existsSync(hooksJsonPath)) {
|
|
6329
6954
|
const hooksJson = JSON.parse(
|
|
6330
|
-
|
|
6955
|
+
fs5.readFileSync(hooksJsonPath, "utf8")
|
|
6331
6956
|
);
|
|
6332
|
-
const settingsPath =
|
|
6333
|
-
const existingSettings =
|
|
6957
|
+
const settingsPath = path6.join(projectDir, ".claude", "settings.json");
|
|
6958
|
+
const existingSettings = fs5.existsSync(settingsPath) ? JSON.parse(fs5.readFileSync(settingsPath, "utf8")) : {};
|
|
6334
6959
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
6335
6960
|
if (!opts.dryRun) {
|
|
6336
|
-
|
|
6337
|
-
|
|
6961
|
+
fs5.mkdirSync(path6.dirname(settingsPath), { recursive: true });
|
|
6962
|
+
fs5.writeFileSync(
|
|
6338
6963
|
settingsPath,
|
|
6339
6964
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
6340
6965
|
"utf8"
|
|
@@ -6347,7 +6972,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6347
6972
|
);
|
|
6348
6973
|
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
6349
6974
|
console.log(
|
|
6350
|
-
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${
|
|
6975
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path6.relative(projectDir, path6.join(projectDir, ".gitignore"))}`
|
|
6351
6976
|
);
|
|
6352
6977
|
}
|
|
6353
6978
|
if (!opts.dryRun) {
|
|
@@ -6382,9 +7007,9 @@ function runUpdateUser(opts, deps) {
|
|
|
6382
7007
|
return;
|
|
6383
7008
|
}
|
|
6384
7009
|
try {
|
|
6385
|
-
const userDir = deps.userDir ??
|
|
6386
|
-
const settingsPath =
|
|
6387
|
-
const userBaseSettingsPath =
|
|
7010
|
+
const userDir = deps.userDir ?? path6.join(os3.homedir(), ".claude");
|
|
7011
|
+
const settingsPath = path6.join(userDir, "settings.json");
|
|
7012
|
+
const userBaseSettingsPath = path6.join(
|
|
6388
7013
|
templatesDir,
|
|
6389
7014
|
"settings.user.base.json"
|
|
6390
7015
|
);
|
|
@@ -6396,7 +7021,7 @@ function runUpdateUser(opts, deps) {
|
|
|
6396
7021
|
process.exitCode = 1;
|
|
6397
7022
|
return;
|
|
6398
7023
|
}
|
|
6399
|
-
if (!
|
|
7024
|
+
if (!fs5.existsSync(userBaseSettingsPath)) {
|
|
6400
7025
|
console.error(
|
|
6401
7026
|
"codebyplan claude update: settings.user.base.json not found in templates."
|
|
6402
7027
|
);
|
|
@@ -6404,13 +7029,13 @@ function runUpdateUser(opts, deps) {
|
|
|
6404
7029
|
return;
|
|
6405
7030
|
}
|
|
6406
7031
|
const userBase = JSON.parse(
|
|
6407
|
-
|
|
7032
|
+
fs5.readFileSync(userBaseSettingsPath, "utf8")
|
|
6408
7033
|
);
|
|
6409
|
-
const existingSettings =
|
|
7034
|
+
const existingSettings = fs5.existsSync(settingsPath) ? JSON.parse(fs5.readFileSync(settingsPath, "utf8")) : {};
|
|
6410
7035
|
mergeBaseSettingsIntoSettings(existingSettings, userBase);
|
|
6411
7036
|
if (!opts.dryRun) {
|
|
6412
|
-
|
|
6413
|
-
|
|
7037
|
+
fs5.mkdirSync(userDir, { recursive: true });
|
|
7038
|
+
fs5.writeFileSync(
|
|
6414
7039
|
settingsPath,
|
|
6415
7040
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
6416
7041
|
"utf8"
|
|
@@ -6446,8 +7071,8 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6446
7071
|
};
|
|
6447
7072
|
for (const pkg of packaged) {
|
|
6448
7073
|
const inManifest = manifestBySrc.get(pkg.src);
|
|
6449
|
-
const absDest =
|
|
6450
|
-
const absSrc =
|
|
7074
|
+
const absDest = path6.join(projectDir, ".claude", pkg.dest);
|
|
7075
|
+
const absSrc = path6.join(templatesDir, pkg.src);
|
|
6451
7076
|
if (!inManifest) {
|
|
6452
7077
|
plan.newOptIn.push({
|
|
6453
7078
|
packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
|
|
@@ -6455,8 +7080,8 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6455
7080
|
});
|
|
6456
7081
|
continue;
|
|
6457
7082
|
}
|
|
6458
|
-
const onDiskExists =
|
|
6459
|
-
const onDiskContent = onDiskExists ?
|
|
7083
|
+
const onDiskExists = fs5.existsSync(absDest);
|
|
7084
|
+
const onDiskContent = onDiskExists ? fs5.readFileSync(absDest) : Buffer.alloc(0);
|
|
6460
7085
|
const onDiskHash = onDiskExists ? sha256(onDiskContent) : null;
|
|
6461
7086
|
if (pkg.hash === inManifest.hash && onDiskHash === inManifest.hash) {
|
|
6462
7087
|
plan.unchanged.push(inManifest);
|
|
@@ -6483,14 +7108,14 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6483
7108
|
return plan;
|
|
6484
7109
|
}
|
|
6485
7110
|
function resolveTemplatesDirFromInstall() {
|
|
6486
|
-
const here =
|
|
7111
|
+
const here = path6.dirname(fileURLToPath2(import.meta.url));
|
|
6487
7112
|
const candidates = [
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
7113
|
+
path6.resolve(here, "..", "templates"),
|
|
7114
|
+
path6.resolve(here, "..", "..", "templates"),
|
|
7115
|
+
path6.resolve(here, "..", "..", "..", "templates")
|
|
6491
7116
|
];
|
|
6492
7117
|
for (const c of candidates) {
|
|
6493
|
-
if (
|
|
7118
|
+
if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) {
|
|
6494
7119
|
return c;
|
|
6495
7120
|
}
|
|
6496
7121
|
}
|
|
@@ -6519,9 +7144,9 @@ var uninstall_exports = {};
|
|
|
6519
7144
|
__export(uninstall_exports, {
|
|
6520
7145
|
runUninstall: () => runUninstall
|
|
6521
7146
|
});
|
|
6522
|
-
import * as
|
|
7147
|
+
import * as fs6 from "node:fs";
|
|
6523
7148
|
import * as os4 from "node:os";
|
|
6524
|
-
import * as
|
|
7149
|
+
import * as path7 from "node:path";
|
|
6525
7150
|
async function runUninstall(opts, deps = {}) {
|
|
6526
7151
|
await Promise.resolve();
|
|
6527
7152
|
const scope = opts.scope ?? "project";
|
|
@@ -6550,15 +7175,15 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6550
7175
|
let removed = 0;
|
|
6551
7176
|
let warnings = 0;
|
|
6552
7177
|
for (const entry of manifest.files) {
|
|
6553
|
-
const abs =
|
|
6554
|
-
if (!
|
|
7178
|
+
const abs = path7.join(projectDir, ".claude", entry.dest);
|
|
7179
|
+
if (!fs6.existsSync(abs)) {
|
|
6555
7180
|
console.warn(
|
|
6556
7181
|
`codebyplan claude uninstall: ${entry.dest} already absent (skipping).`
|
|
6557
7182
|
);
|
|
6558
7183
|
warnings += 1;
|
|
6559
7184
|
continue;
|
|
6560
7185
|
}
|
|
6561
|
-
const onDiskHash = sha256(
|
|
7186
|
+
const onDiskHash = sha256(fs6.readFileSync(abs));
|
|
6562
7187
|
if (onDiskHash !== entry.hash) {
|
|
6563
7188
|
console.warn(
|
|
6564
7189
|
`codebyplan claude uninstall: ${entry.dest} has been modified locally; removing anyway.`
|
|
@@ -6566,7 +7191,7 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6566
7191
|
warnings += 1;
|
|
6567
7192
|
}
|
|
6568
7193
|
if (!opts.dryRun) {
|
|
6569
|
-
|
|
7194
|
+
fs6.rmSync(abs);
|
|
6570
7195
|
}
|
|
6571
7196
|
removed += 1;
|
|
6572
7197
|
if (opts.verbose) console.log(`removed ${entry.dest}`);
|
|
@@ -6574,15 +7199,15 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6574
7199
|
if (!opts.dryRun) {
|
|
6575
7200
|
pruneEmptyManagedDirs(projectDir);
|
|
6576
7201
|
}
|
|
6577
|
-
const settingsPath =
|
|
6578
|
-
if (
|
|
7202
|
+
const settingsPath = path7.join(projectDir, ".claude", "settings.json");
|
|
7203
|
+
if (fs6.existsSync(settingsPath)) {
|
|
6579
7204
|
const settings = JSON.parse(
|
|
6580
|
-
|
|
7205
|
+
fs6.readFileSync(settingsPath, "utf8")
|
|
6581
7206
|
);
|
|
6582
|
-
const baseSettingsPath = templatesDir ?
|
|
6583
|
-
if (baseSettingsPath &&
|
|
7207
|
+
const baseSettingsPath = templatesDir ? path7.join(templatesDir, "settings.project.base.json") : null;
|
|
7208
|
+
if (baseSettingsPath && fs6.existsSync(baseSettingsPath)) {
|
|
6584
7209
|
const base = JSON.parse(
|
|
6585
|
-
|
|
7210
|
+
fs6.readFileSync(baseSettingsPath, "utf8")
|
|
6586
7211
|
);
|
|
6587
7212
|
stripBaseSettingsFromSettings(settings, base);
|
|
6588
7213
|
}
|
|
@@ -6590,9 +7215,9 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6590
7215
|
if (!opts.dryRun) {
|
|
6591
7216
|
const isEmpty = Object.keys(settings).length === 0;
|
|
6592
7217
|
if (isEmpty) {
|
|
6593
|
-
|
|
7218
|
+
fs6.rmSync(settingsPath);
|
|
6594
7219
|
} else {
|
|
6595
|
-
|
|
7220
|
+
fs6.writeFileSync(
|
|
6596
7221
|
settingsPath,
|
|
6597
7222
|
JSON.stringify(settings, null, 2) + "\n",
|
|
6598
7223
|
"utf8"
|
|
@@ -6611,11 +7236,11 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6611
7236
|
}
|
|
6612
7237
|
if (!opts.dryRun) {
|
|
6613
7238
|
const m = manifestPath(projectDir);
|
|
6614
|
-
if (
|
|
7239
|
+
if (fs6.existsSync(m)) fs6.rmSync(m);
|
|
6615
7240
|
const mid = midManifestPath(projectDir);
|
|
6616
|
-
if (
|
|
7241
|
+
if (fs6.existsSync(mid)) fs6.rmSync(mid);
|
|
6617
7242
|
const legacy = oldManifestPath(projectDir);
|
|
6618
|
-
if (
|
|
7243
|
+
if (fs6.existsSync(legacy)) fs6.rmSync(legacy);
|
|
6619
7244
|
}
|
|
6620
7245
|
console.log(
|
|
6621
7246
|
`codebyplan claude uninstall${opts.dryRun ? " (dry-run)" : ""}: removed ${removed} files${warnings > 0 ? ` (${warnings} warnings)` : ""}.`
|
|
@@ -6637,7 +7262,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6637
7262
|
}
|
|
6638
7263
|
}
|
|
6639
7264
|
try {
|
|
6640
|
-
const userDir = deps.userDir ??
|
|
7265
|
+
const userDir = deps.userDir ?? path7.join(os4.homedir(), ".claude");
|
|
6641
7266
|
const existingManifest = readManifestForScope("user", userDir);
|
|
6642
7267
|
if (!existingManifest) {
|
|
6643
7268
|
console.error(
|
|
@@ -6646,24 +7271,24 @@ function runUninstallUser(opts, deps) {
|
|
|
6646
7271
|
process.exitCode = 1;
|
|
6647
7272
|
return;
|
|
6648
7273
|
}
|
|
6649
|
-
const settingsPath =
|
|
6650
|
-
if (
|
|
7274
|
+
const settingsPath = path7.join(userDir, "settings.json");
|
|
7275
|
+
if (fs6.existsSync(settingsPath)) {
|
|
6651
7276
|
const settings = JSON.parse(
|
|
6652
|
-
|
|
7277
|
+
fs6.readFileSync(settingsPath, "utf8")
|
|
6653
7278
|
);
|
|
6654
|
-
const userBaseSettingsPath = templatesDir != null ?
|
|
6655
|
-
if (userBaseSettingsPath &&
|
|
7279
|
+
const userBaseSettingsPath = templatesDir != null ? path7.join(templatesDir, "settings.user.base.json") : null;
|
|
7280
|
+
if (userBaseSettingsPath && fs6.existsSync(userBaseSettingsPath)) {
|
|
6656
7281
|
const userBase = JSON.parse(
|
|
6657
|
-
|
|
7282
|
+
fs6.readFileSync(userBaseSettingsPath, "utf8")
|
|
6658
7283
|
);
|
|
6659
7284
|
stripBaseSettingsFromSettings(settings, userBase);
|
|
6660
7285
|
}
|
|
6661
7286
|
if (!opts.dryRun) {
|
|
6662
7287
|
const isEmpty = Object.keys(settings).length === 0;
|
|
6663
7288
|
if (isEmpty) {
|
|
6664
|
-
|
|
7289
|
+
fs6.rmSync(settingsPath);
|
|
6665
7290
|
} else {
|
|
6666
|
-
|
|
7291
|
+
fs6.writeFileSync(
|
|
6667
7292
|
settingsPath,
|
|
6668
7293
|
JSON.stringify(settings, null, 2) + "\n",
|
|
6669
7294
|
"utf8"
|
|
@@ -6673,11 +7298,11 @@ function runUninstallUser(opts, deps) {
|
|
|
6673
7298
|
}
|
|
6674
7299
|
if (!opts.dryRun) {
|
|
6675
7300
|
const m = userManifestPath(userDir);
|
|
6676
|
-
if (
|
|
7301
|
+
if (fs6.existsSync(m)) fs6.rmSync(m);
|
|
6677
7302
|
const midUser = userMidManifestPath(userDir);
|
|
6678
|
-
if (
|
|
7303
|
+
if (fs6.existsSync(midUser)) fs6.rmSync(midUser);
|
|
6679
7304
|
const oldUser = userOldManifestPath(userDir);
|
|
6680
|
-
if (
|
|
7305
|
+
if (fs6.existsSync(oldUser)) fs6.rmSync(oldUser);
|
|
6681
7306
|
}
|
|
6682
7307
|
console.log(
|
|
6683
7308
|
`codebyplan claude uninstall --scope user${opts.dryRun ? " (dry-run)" : ""}: user base settings stripped.`
|
|
@@ -6692,23 +7317,23 @@ function runUninstallUser(opts, deps) {
|
|
|
6692
7317
|
function pruneEmptyManagedDirs(projectDir) {
|
|
6693
7318
|
const managedRoots = ["skills", "agents", "hooks", "rules"];
|
|
6694
7319
|
for (const root of managedRoots) {
|
|
6695
|
-
const abs =
|
|
6696
|
-
if (!
|
|
7320
|
+
const abs = path7.join(projectDir, ".claude", root);
|
|
7321
|
+
if (!fs6.existsSync(abs)) continue;
|
|
6697
7322
|
pruneLeafFirst(abs);
|
|
6698
7323
|
}
|
|
6699
7324
|
}
|
|
6700
7325
|
function pruneLeafFirst(dir) {
|
|
6701
|
-
if (!
|
|
6702
|
-
const stat2 =
|
|
7326
|
+
if (!fs6.existsSync(dir)) return;
|
|
7327
|
+
const stat2 = fs6.statSync(dir);
|
|
6703
7328
|
if (!stat2.isDirectory()) return;
|
|
6704
|
-
for (const entry of
|
|
7329
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
6705
7330
|
if (entry.isDirectory()) {
|
|
6706
|
-
pruneLeafFirst(
|
|
7331
|
+
pruneLeafFirst(path7.join(dir, entry.name));
|
|
6707
7332
|
}
|
|
6708
7333
|
}
|
|
6709
|
-
const remaining =
|
|
7334
|
+
const remaining = fs6.readdirSync(dir);
|
|
6710
7335
|
if (remaining.length === 0) {
|
|
6711
|
-
|
|
7336
|
+
fs6.rmdirSync(dir);
|
|
6712
7337
|
}
|
|
6713
7338
|
}
|
|
6714
7339
|
var init_uninstall = __esm({
|
|
@@ -6724,13 +7349,13 @@ var init_uninstall = __esm({
|
|
|
6724
7349
|
|
|
6725
7350
|
// src/index.ts
|
|
6726
7351
|
init_version();
|
|
6727
|
-
import { readFileSync as
|
|
6728
|
-
import { resolve as
|
|
7352
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
7353
|
+
import { resolve as resolve6 } from "node:path";
|
|
6729
7354
|
void (async () => {
|
|
6730
7355
|
if (!process.env.CODEBYPLAN_API_KEY) {
|
|
6731
7356
|
try {
|
|
6732
|
-
const envPath =
|
|
6733
|
-
const content =
|
|
7357
|
+
const envPath = resolve6(process.cwd(), ".env.local");
|
|
7358
|
+
const content = readFileSync7(envPath, "utf-8");
|
|
6734
7359
|
for (const line of content.split("\n")) {
|
|
6735
7360
|
const trimmed = line.trim();
|
|
6736
7361
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -6820,12 +7445,23 @@ void (async () => {
|
|
|
6820
7445
|
await runBranchCommand2(rest);
|
|
6821
7446
|
process.exit(0);
|
|
6822
7447
|
}
|
|
7448
|
+
if (arg === "bump") {
|
|
7449
|
+
const { runBumpCommand: runBumpCommand2 } = await Promise.resolve().then(() => (init_bump2(), bump_exports));
|
|
7450
|
+
const rest = process.argv.slice(3);
|
|
7451
|
+
await runBumpCommand2(rest);
|
|
7452
|
+
process.exit(0);
|
|
7453
|
+
}
|
|
6823
7454
|
if (arg === "ship") {
|
|
6824
7455
|
const { runShipCommand: runShipCommand2 } = await Promise.resolve().then(() => (init_ship2(), ship_exports));
|
|
6825
7456
|
const rest = process.argv.slice(3);
|
|
6826
7457
|
await runShipCommand2(rest);
|
|
6827
7458
|
process.exit(0);
|
|
6828
7459
|
}
|
|
7460
|
+
if (arg === "scaffold-publish-workflow") {
|
|
7461
|
+
const { runScaffoldPublishWorkflowCommand: runScaffoldPublishWorkflowCommand2 } = await Promise.resolve().then(() => (init_scaffold_publish_workflow2(), scaffold_publish_workflow_exports));
|
|
7462
|
+
await runScaffoldPublishWorkflowCommand2(process.argv.slice(3));
|
|
7463
|
+
process.exit(process.exitCode ?? 0);
|
|
7464
|
+
}
|
|
6829
7465
|
if (arg === "resolve-worktree") {
|
|
6830
7466
|
const { runResolveWorktree: runResolveWorktree2 } = await Promise.resolve().then(() => (init_resolve_worktree2(), resolve_worktree_exports));
|
|
6831
7467
|
await runResolveWorktree2();
|
|
@@ -6922,7 +7558,9 @@ void (async () => {
|
|
|
6922
7558
|
(--full-tech-stack: sync every local worktree on this device)
|
|
6923
7559
|
codebyplan eslint ESLint config management (init)
|
|
6924
7560
|
codebyplan round sync-approvals Sync git diff and approvals with round/task state
|
|
7561
|
+
codebyplan bump Detect changed packages and patch-bump versions
|
|
6925
7562
|
codebyplan ship Ship current feat branch to production via PR
|
|
7563
|
+
codebyplan scaffold-publish-workflow Write the publish-on-main GitHub workflow into ./.github/workflows/
|
|
6926
7564
|
codebyplan branch migrate Rewrite branch_config from 3-branch to 2-tier model
|
|
6927
7565
|
codebyplan claude Claude asset management (install/update/uninstall)
|
|
6928
7566
|
codebyplan statusline Show or set the statusline renderer (bash/node/python)
|
|
@@ -6969,8 +7607,6 @@ void (async () => {
|
|
|
6969
7607
|
URL: https://mcp.codebyplan.com/mcp
|
|
6970
7608
|
Auth: OAuth 2.1 (configure via \`codebyplan login\`)
|
|
6971
7609
|
|
|
6972
|
-
Legacy x-api-key at https://www.codebyplan.com/mcp is supported until 2026-06-30.
|
|
6973
|
-
|
|
6974
7610
|
Learn more: https://codebyplan.com
|
|
6975
7611
|
`);
|
|
6976
7612
|
process.exit(0);
|