codebyplan 1.11.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +553 -347
- package/package.json +1 -1
- package/templates/hooks/cbp-statusline.mjs +44 -0
- package/templates/hooks/cbp-statusline.py +24 -2
- package/templates/hooks/cbp-statusline.sh +22 -2
- package/templates/rules/README.md +8 -1
- package/templates/rules/supabase-branch-lifecycle.md +99 -0
- package/templates/settings.project.base.json +1 -2
- package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +1 -2
- package/templates/skills/cbp-checkpoint-create/SKILL.md +2 -0
- package/templates/skills/cbp-checkpoint-end/SKILL.md +27 -5
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +17 -1
- package/templates/skills/cbp-session-start/SKILL.md +27 -2
- package/templates/skills/cbp-ship-main/SKILL.md +13 -0
- package/templates/skills/cbp-supabase-branch-check/SKILL.md +12 -5
- package/templates/skills/cbp-supabase-migrate/SKILL.md +139 -9
- package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +1 -1
- package/templates/skills/cbp-supabase-setup/SKILL.md +13 -7
- package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +2 -2
- package/templates/skills/cbp-task-start/SKILL.md +2 -0
package/dist/cli.js
CHANGED
|
@@ -14,26 +14,163 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.
|
|
17
|
+
VERSION = "1.12.0";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
// src/lib/gitignore-block.ts
|
|
23
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
async function ensureManagedGitignoreBlock(projectDir, dryRun = false) {
|
|
26
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
27
|
+
let existing = null;
|
|
28
|
+
try {
|
|
29
|
+
existing = await readFile(gitignorePath, "utf-8");
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
if (existing === null) {
|
|
33
|
+
if (!dryRun) {
|
|
34
|
+
const block = buildBlock("\n");
|
|
35
|
+
await writeFile(gitignorePath, block, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
return "created";
|
|
38
|
+
}
|
|
39
|
+
const nl = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
40
|
+
const lines = existing.split(/\r?\n/);
|
|
41
|
+
const startIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_START);
|
|
42
|
+
const endIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_END);
|
|
43
|
+
const hasValidBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
44
|
+
if (!hasValidBlock) {
|
|
45
|
+
const hasOrphanMarker = startIdx !== -1 || endIdx !== -1;
|
|
46
|
+
if (hasOrphanMarker) {
|
|
47
|
+
const hadTrailingNewline = existing.endsWith("\n");
|
|
48
|
+
const cleanedLines = lines.filter(
|
|
49
|
+
(l) => l !== GITIGNORE_BLOCK_START && l !== GITIGNORE_BLOCK_END
|
|
50
|
+
);
|
|
51
|
+
if (hadTrailingNewline && cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1] === "") {
|
|
52
|
+
cleanedLines.pop();
|
|
53
|
+
}
|
|
54
|
+
let content2 = cleanedLines.join(nl);
|
|
55
|
+
if (content2.length > 0) content2 += nl;
|
|
56
|
+
const newContent3 = content2 + buildBlock(nl);
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
await writeFile(gitignorePath, newContent3, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
return "refreshed";
|
|
61
|
+
}
|
|
62
|
+
let content = existing;
|
|
63
|
+
if (!content.endsWith("\n") && !content.endsWith("\r\n")) {
|
|
64
|
+
content = content + nl;
|
|
65
|
+
}
|
|
66
|
+
const block = buildBlock(nl);
|
|
67
|
+
const newContent2 = content + block;
|
|
68
|
+
if (!dryRun) {
|
|
69
|
+
await writeFile(gitignorePath, newContent2, "utf-8");
|
|
70
|
+
}
|
|
71
|
+
return "appended";
|
|
72
|
+
}
|
|
73
|
+
const blockBodyLines = lines.slice(startIdx + 1, endIdx);
|
|
74
|
+
const canonicalBodyLines = [...CANONICAL_GITIGNORE_ENTRIES];
|
|
75
|
+
const bodyMatches = blockBodyLines.length === canonicalBodyLines.length && blockBodyLines.every((l, i) => l === canonicalBodyLines[i]);
|
|
76
|
+
if (bodyMatches) {
|
|
77
|
+
return "unchanged";
|
|
78
|
+
}
|
|
79
|
+
const blockLines = [
|
|
80
|
+
GITIGNORE_BLOCK_START,
|
|
81
|
+
...CANONICAL_GITIGNORE_ENTRIES,
|
|
82
|
+
GITIGNORE_BLOCK_END
|
|
83
|
+
];
|
|
84
|
+
const newLines = [
|
|
85
|
+
...lines.slice(0, startIdx),
|
|
86
|
+
...blockLines,
|
|
87
|
+
...lines.slice(endIdx + 1)
|
|
88
|
+
];
|
|
89
|
+
const newContent = newLines.join(nl);
|
|
90
|
+
const finalContent = newContent.endsWith(nl) ? newContent : newContent + nl;
|
|
91
|
+
if (!dryRun) {
|
|
92
|
+
await writeFile(gitignorePath, finalContent, "utf-8");
|
|
93
|
+
}
|
|
94
|
+
return "refreshed";
|
|
95
|
+
}
|
|
96
|
+
async function removeManagedGitignoreBlock(projectDir, dryRun = false) {
|
|
97
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
98
|
+
let existing = null;
|
|
99
|
+
try {
|
|
100
|
+
existing = await readFile(gitignorePath, "utf-8");
|
|
101
|
+
} catch {
|
|
102
|
+
return "unchanged";
|
|
103
|
+
}
|
|
104
|
+
const nl = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
105
|
+
const hadTrailingNewline = existing.endsWith("\n");
|
|
106
|
+
const lines = existing.split(/\r?\n/);
|
|
107
|
+
if (hadTrailingNewline && lines.length > 0 && lines[lines.length - 1] === "") {
|
|
108
|
+
lines.pop();
|
|
109
|
+
}
|
|
110
|
+
const startIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_START);
|
|
111
|
+
const endIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_END);
|
|
112
|
+
const hasValidBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
113
|
+
if (!hasValidBlock) {
|
|
114
|
+
const hasOrphanMarker = startIdx !== -1 || endIdx !== -1;
|
|
115
|
+
if (!hasOrphanMarker) {
|
|
116
|
+
return "unchanged";
|
|
117
|
+
}
|
|
118
|
+
const cleaned = lines.filter(
|
|
119
|
+
(l) => l !== GITIGNORE_BLOCK_START && l !== GITIGNORE_BLOCK_END
|
|
120
|
+
);
|
|
121
|
+
const orphanContent = cleaned.length > 0 ? cleaned.join(nl) + nl : "";
|
|
122
|
+
if (!dryRun) {
|
|
123
|
+
await writeFile(gitignorePath, orphanContent, "utf-8");
|
|
124
|
+
}
|
|
125
|
+
return "removed";
|
|
126
|
+
}
|
|
127
|
+
const before = lines.slice(0, startIdx);
|
|
128
|
+
const after = lines.slice(endIdx + 1);
|
|
129
|
+
if (before.length > 0 && after.length > 0 && before[before.length - 1].trim() === "" && after[0].trim() === "") {
|
|
130
|
+
after.shift();
|
|
131
|
+
}
|
|
132
|
+
const merged = [...before, ...after];
|
|
133
|
+
const newContent = merged.length > 0 ? merged.join(nl) + nl : "";
|
|
134
|
+
if (!dryRun) {
|
|
135
|
+
await writeFile(gitignorePath, newContent, "utf-8");
|
|
136
|
+
}
|
|
137
|
+
return "removed";
|
|
138
|
+
}
|
|
139
|
+
function buildBlock(nl) {
|
|
140
|
+
return GITIGNORE_BLOCK_START + nl + CANONICAL_GITIGNORE_ENTRIES.join(nl) + nl + GITIGNORE_BLOCK_END + nl;
|
|
141
|
+
}
|
|
142
|
+
var CANONICAL_GITIGNORE_ENTRIES, GITIGNORE_BLOCK_START, GITIGNORE_BLOCK_END;
|
|
143
|
+
var init_gitignore_block = __esm({
|
|
144
|
+
"src/lib/gitignore-block.ts"() {
|
|
145
|
+
"use strict";
|
|
146
|
+
CANONICAL_GITIGNORE_ENTRIES = [
|
|
147
|
+
".claude/settings.local.json",
|
|
148
|
+
".claude/scheduled_tasks.lock",
|
|
149
|
+
".codebyplan/device.local.json",
|
|
150
|
+
".codebyplan/statusline.local.json",
|
|
151
|
+
".codebyplan.local.json",
|
|
152
|
+
".mcp.json"
|
|
153
|
+
];
|
|
154
|
+
GITIGNORE_BLOCK_START = "# >>> codebyplan (managed) >>>";
|
|
155
|
+
GITIGNORE_BLOCK_END = "# <<< codebyplan <<<";
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
22
159
|
// src/lib/local-config.ts
|
|
23
160
|
import { execSync } from "node:child_process";
|
|
24
161
|
import { createHash } from "node:crypto";
|
|
25
|
-
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
162
|
+
import { mkdir, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
26
163
|
import { hostname } from "node:os";
|
|
27
|
-
import { dirname, join } from "node:path";
|
|
164
|
+
import { dirname, join as join2 } from "node:path";
|
|
28
165
|
function localConfigPath(projectPath) {
|
|
29
|
-
return
|
|
166
|
+
return join2(projectPath, ".codebyplan", "device.local.json");
|
|
30
167
|
}
|
|
31
168
|
function legacyLocalConfigPath(projectPath) {
|
|
32
|
-
return
|
|
169
|
+
return join2(projectPath, ".codebyplan.local.json");
|
|
33
170
|
}
|
|
34
171
|
async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
35
172
|
try {
|
|
36
|
-
const raw = await
|
|
173
|
+
const raw = await readFile2(localConfigPath(projectPath), "utf-8");
|
|
37
174
|
const parsed = JSON.parse(raw);
|
|
38
175
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
39
176
|
return parsed;
|
|
@@ -68,7 +205,7 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
|
68
205
|
}
|
|
69
206
|
}
|
|
70
207
|
try {
|
|
71
|
-
const raw = await
|
|
208
|
+
const raw = await readFile2(legacyLocalConfigPath(projectPath), "utf-8");
|
|
72
209
|
const parsed = JSON.parse(raw);
|
|
73
210
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
74
211
|
onMigrationNotice?.(
|
|
@@ -88,8 +225,8 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
|
88
225
|
}
|
|
89
226
|
async function writeLocalConfig(projectPath, config) {
|
|
90
227
|
const content = { device_id: config.device_id };
|
|
91
|
-
const
|
|
92
|
-
const dirPath = dirname(
|
|
228
|
+
const path7 = localConfigPath(projectPath);
|
|
229
|
+
const dirPath = dirname(path7);
|
|
93
230
|
let phase = "stat config directory";
|
|
94
231
|
try {
|
|
95
232
|
try {
|
|
@@ -109,7 +246,7 @@ async function writeLocalConfig(projectPath, config) {
|
|
|
109
246
|
phase = "create config directory";
|
|
110
247
|
await mkdir(dirPath, { recursive: true });
|
|
111
248
|
phase = "write local config";
|
|
112
|
-
await
|
|
249
|
+
await writeFile2(path7, JSON.stringify(content, null, 2) + "\n", "utf-8");
|
|
113
250
|
} catch (err) {
|
|
114
251
|
const code = err.code;
|
|
115
252
|
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
@@ -121,7 +258,7 @@ async function writeLocalConfig(projectPath, config) {
|
|
|
121
258
|
}
|
|
122
259
|
async function resolveMachineSeed() {
|
|
123
260
|
try {
|
|
124
|
-
const raw = await
|
|
261
|
+
const raw = await readFile2("/etc/machine-id", "utf-8");
|
|
125
262
|
const trimmed = raw.trim();
|
|
126
263
|
if (trimmed) return trimmed;
|
|
127
264
|
} catch {
|
|
@@ -153,13 +290,13 @@ var init_local_config = __esm({
|
|
|
153
290
|
|
|
154
291
|
// src/lib/statusline-config.ts
|
|
155
292
|
import { spawnSync } from "node:child_process";
|
|
156
|
-
import { mkdir as mkdir2, readFile as
|
|
157
|
-
import { join as
|
|
293
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
294
|
+
import { join as join3 } from "node:path";
|
|
158
295
|
function statuslineConfigPath(projectPath) {
|
|
159
|
-
return
|
|
296
|
+
return join3(projectPath, ".codebyplan", "statusline.json");
|
|
160
297
|
}
|
|
161
298
|
function statuslineLocalConfigPath(projectPath) {
|
|
162
|
-
return
|
|
299
|
+
return join3(projectPath, ".codebyplan", "statusline.local.json");
|
|
163
300
|
}
|
|
164
301
|
function isValidRenderer(value) {
|
|
165
302
|
return typeof value === "string" && VALID_RENDERERS.has(value);
|
|
@@ -188,7 +325,7 @@ function mergeOverDefaults(raw) {
|
|
|
188
325
|
}
|
|
189
326
|
async function readStatuslineConfig(projectPath) {
|
|
190
327
|
try {
|
|
191
|
-
const raw = await
|
|
328
|
+
const raw = await readFile3(statuslineConfigPath(projectPath), "utf-8");
|
|
192
329
|
const parsed = JSON.parse(raw);
|
|
193
330
|
return mergeOverDefaults(parsed);
|
|
194
331
|
} catch {
|
|
@@ -197,7 +334,7 @@ async function readStatuslineConfig(projectPath) {
|
|
|
197
334
|
}
|
|
198
335
|
async function readStatuslineLocalConfig(projectPath) {
|
|
199
336
|
try {
|
|
200
|
-
const raw = await
|
|
337
|
+
const raw = await readFile3(statuslineLocalConfigPath(projectPath), "utf-8");
|
|
201
338
|
const parsed = JSON.parse(raw);
|
|
202
339
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
203
340
|
const obj = parsed;
|
|
@@ -217,8 +354,8 @@ async function writeStatuslineLocalConfig(projectPath, localConfig) {
|
|
|
217
354
|
);
|
|
218
355
|
}
|
|
219
356
|
const filePath = statuslineLocalConfigPath(projectPath);
|
|
220
|
-
await mkdir2(
|
|
221
|
-
await
|
|
357
|
+
await mkdir2(join3(projectPath, ".codebyplan"), { recursive: true });
|
|
358
|
+
await writeFile3(
|
|
222
359
|
filePath,
|
|
223
360
|
JSON.stringify(localConfig, null, 2) + "\n",
|
|
224
361
|
"utf-8"
|
|
@@ -258,7 +395,8 @@ var init_statusline_config = __esm({
|
|
|
258
395
|
cost: true,
|
|
259
396
|
rate_limits: true,
|
|
260
397
|
repo_pr: true,
|
|
261
|
-
worktree: true
|
|
398
|
+
worktree: true,
|
|
399
|
+
infra_drift: true
|
|
262
400
|
},
|
|
263
401
|
no_color: false
|
|
264
402
|
};
|
|
@@ -297,9 +435,9 @@ var init_jwt_decode = __esm({
|
|
|
297
435
|
});
|
|
298
436
|
|
|
299
437
|
// src/oauth/keychain.ts
|
|
300
|
-
import { chmod, mkdir as mkdir3, readFile as
|
|
438
|
+
import { chmod, mkdir as mkdir3, readFile as readFile4, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
301
439
|
import { homedir, platform } from "node:os";
|
|
302
|
-
import { dirname as dirname2, join as
|
|
440
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
303
441
|
async function loadKeyring() {
|
|
304
442
|
if (keyringOverride !== void 0) return keyringOverride;
|
|
305
443
|
try {
|
|
@@ -311,30 +449,30 @@ async function loadKeyring() {
|
|
|
311
449
|
}
|
|
312
450
|
function fallbackDir() {
|
|
313
451
|
if (platform() === "win32") {
|
|
314
|
-
const appData = process.env.APPDATA ??
|
|
315
|
-
return
|
|
452
|
+
const appData = process.env.APPDATA ?? join4(homedir(), "AppData", "Roaming");
|
|
453
|
+
return join4(appData, "codebyplan");
|
|
316
454
|
}
|
|
317
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
318
|
-
return
|
|
455
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join4(homedir(), ".config");
|
|
456
|
+
return join4(xdg, "codebyplan");
|
|
319
457
|
}
|
|
320
458
|
function fallbackFile(filename) {
|
|
321
|
-
return
|
|
459
|
+
return join4(fallbackDir(), filename);
|
|
322
460
|
}
|
|
323
461
|
async function readFallback(filename) {
|
|
324
462
|
try {
|
|
325
|
-
const raw = await
|
|
463
|
+
const raw = await readFile4(fallbackFile(filename), "utf-8");
|
|
326
464
|
return JSON.parse(raw);
|
|
327
465
|
} catch {
|
|
328
466
|
return null;
|
|
329
467
|
}
|
|
330
468
|
}
|
|
331
469
|
async function writeFallback(filename, data) {
|
|
332
|
-
const
|
|
333
|
-
await mkdir3(dirname2(
|
|
334
|
-
await
|
|
470
|
+
const path7 = fallbackFile(filename);
|
|
471
|
+
await mkdir3(dirname2(path7), { recursive: true });
|
|
472
|
+
await writeFile4(path7, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
335
473
|
if (platform() !== "win32") {
|
|
336
474
|
try {
|
|
337
|
-
await chmod(
|
|
475
|
+
await chmod(path7, 384);
|
|
338
476
|
} catch {
|
|
339
477
|
}
|
|
340
478
|
}
|
|
@@ -541,8 +679,8 @@ async function getAuthHeaders() {
|
|
|
541
679
|
return { headers: { "x-api-key": key }, via: "api_key" };
|
|
542
680
|
}
|
|
543
681
|
}
|
|
544
|
-
function buildUrl(
|
|
545
|
-
const url = new URL(`${baseUrl()}/api${
|
|
682
|
+
function buildUrl(path7, params) {
|
|
683
|
+
const url = new URL(`${baseUrl()}/api${path7}`);
|
|
546
684
|
if (params) {
|
|
547
685
|
for (const [key, value] of Object.entries(params)) {
|
|
548
686
|
if (value !== void 0) {
|
|
@@ -561,8 +699,8 @@ function isRetryable(err) {
|
|
|
561
699
|
function delay(ms) {
|
|
562
700
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
563
701
|
}
|
|
564
|
-
async function request(method,
|
|
565
|
-
const url = buildUrl(
|
|
702
|
+
async function request(method, path7, options) {
|
|
703
|
+
const url = buildUrl(path7, options?.params);
|
|
566
704
|
const auth = await getAuthHeaders();
|
|
567
705
|
let lastError;
|
|
568
706
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -582,7 +720,7 @@ async function request(method, path6, options) {
|
|
|
582
720
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
583
721
|
});
|
|
584
722
|
if (!res.ok) {
|
|
585
|
-
let message = `API ${method} ${
|
|
723
|
+
let message = `API ${method} ${path7} failed with status ${res.status}`;
|
|
586
724
|
let code;
|
|
587
725
|
try {
|
|
588
726
|
const body = await res.json();
|
|
@@ -616,14 +754,14 @@ async function request(method, path6, options) {
|
|
|
616
754
|
}
|
|
617
755
|
throw lastError;
|
|
618
756
|
}
|
|
619
|
-
async function apiGet(
|
|
620
|
-
return request("GET",
|
|
757
|
+
async function apiGet(path7, params) {
|
|
758
|
+
return request("GET", path7, { params });
|
|
621
759
|
}
|
|
622
|
-
async function apiPost(
|
|
623
|
-
return request("POST",
|
|
760
|
+
async function apiPost(path7, body) {
|
|
761
|
+
return request("POST", path7, { body });
|
|
624
762
|
}
|
|
625
|
-
async function apiPut(
|
|
626
|
-
return request("PUT",
|
|
763
|
+
async function apiPut(path7, body) {
|
|
764
|
+
return request("PUT", path7, { body });
|
|
627
765
|
}
|
|
628
766
|
async function callMcpTool(toolName, params) {
|
|
629
767
|
const url = mcpEndpoint();
|
|
@@ -682,8 +820,8 @@ var init_api = __esm({
|
|
|
682
820
|
});
|
|
683
821
|
|
|
684
822
|
// src/lib/resolve-worktree.ts
|
|
685
|
-
import { readFile as
|
|
686
|
-
import { join as
|
|
823
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
824
|
+
import { join as join5 } from "node:path";
|
|
687
825
|
async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
688
826
|
let worktreeId;
|
|
689
827
|
try {
|
|
@@ -705,10 +843,10 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
705
843
|
if (options?.skipWrite) {
|
|
706
844
|
return worktreeId;
|
|
707
845
|
}
|
|
708
|
-
const codebyplanPath =
|
|
846
|
+
const codebyplanPath = join5(projectPath, ".codebyplan.json");
|
|
709
847
|
let currentConfig = {};
|
|
710
848
|
try {
|
|
711
|
-
const raw = await
|
|
849
|
+
const raw = await readFile5(codebyplanPath, "utf-8");
|
|
712
850
|
const parsed = JSON.parse(raw);
|
|
713
851
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
714
852
|
currentConfig = parsed;
|
|
@@ -723,7 +861,7 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
723
861
|
worktree_id: worktreeId
|
|
724
862
|
};
|
|
725
863
|
try {
|
|
726
|
-
await
|
|
864
|
+
await writeFile5(
|
|
727
865
|
codebyplanPath,
|
|
728
866
|
JSON.stringify(merged, null, 2) + "\n",
|
|
729
867
|
"utf-8"
|
|
@@ -880,7 +1018,7 @@ async function pollOnce(deviceCode, clientId) {
|
|
|
880
1018
|
return { kind: "error", message: description };
|
|
881
1019
|
}
|
|
882
1020
|
async function pollUntilSettled(opts) {
|
|
883
|
-
const
|
|
1021
|
+
const sleep2 = opts.sleep ?? defaultSleep;
|
|
884
1022
|
const now = opts.now ?? Date.now;
|
|
885
1023
|
const startMs = now();
|
|
886
1024
|
const expiresAtMs = startMs + opts.expiresInSec * 1e3;
|
|
@@ -888,7 +1026,7 @@ async function pollUntilSettled(opts) {
|
|
|
888
1026
|
const outcome = await pollOnce(opts.deviceCode, opts.clientId);
|
|
889
1027
|
if (outcome.kind !== "pending") return outcome;
|
|
890
1028
|
if (opts.onTick) opts.onTick(Math.floor((now() - startMs) / 1e3));
|
|
891
|
-
await
|
|
1029
|
+
await sleep2(opts.intervalSec * 1e3);
|
|
892
1030
|
}
|
|
893
1031
|
return { kind: "expired" };
|
|
894
1032
|
}
|
|
@@ -1057,21 +1195,21 @@ var setup_exports = {};
|
|
|
1057
1195
|
__export(setup_exports, {
|
|
1058
1196
|
runSetup: () => runSetup
|
|
1059
1197
|
});
|
|
1060
|
-
import { mkdir as mkdir4, readFile as
|
|
1198
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
|
|
1061
1199
|
import { homedir as homedir2 } from "node:os";
|
|
1062
|
-
import { join as
|
|
1200
|
+
import { join as join6 } from "node:path";
|
|
1063
1201
|
import { stdin, stdout as stdout2 } from "node:process";
|
|
1064
1202
|
import { createInterface } from "node:readline/promises";
|
|
1065
1203
|
function getConfigPath(scope) {
|
|
1066
|
-
return scope === "user" ?
|
|
1204
|
+
return scope === "user" ? join6(homedir2(), ".claude.json") : join6(process.cwd(), ".mcp.json");
|
|
1067
1205
|
}
|
|
1068
1206
|
function legacyMcpUrl() {
|
|
1069
1207
|
const baseUrl2 = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
1070
1208
|
return `${baseUrl2.replace(/\/$/, "")}/mcp`;
|
|
1071
1209
|
}
|
|
1072
|
-
async function readConfig(
|
|
1210
|
+
async function readConfig(path7) {
|
|
1073
1211
|
try {
|
|
1074
|
-
const raw = await
|
|
1212
|
+
const raw = await readFile6(path7, "utf-8");
|
|
1075
1213
|
const parsed = JSON.parse(raw);
|
|
1076
1214
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1077
1215
|
return parsed;
|
|
@@ -1094,7 +1232,7 @@ async function writeMcpConfig(scope, auth) {
|
|
|
1094
1232
|
config.mcpServers = {};
|
|
1095
1233
|
}
|
|
1096
1234
|
config.mcpServers.codebyplan = buildMcpEntry(auth);
|
|
1097
|
-
await
|
|
1235
|
+
await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1098
1236
|
return configPath;
|
|
1099
1237
|
}
|
|
1100
1238
|
async function fetchRepos(auth) {
|
|
@@ -1149,7 +1287,7 @@ async function chooseAuthMode(rl) {
|
|
|
1149
1287
|
return { kind: "legacy", apiKey };
|
|
1150
1288
|
}
|
|
1151
1289
|
async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
1152
|
-
const codebyplanDir =
|
|
1290
|
+
const codebyplanDir = join6(projectPath, ".codebyplan");
|
|
1153
1291
|
await mkdir4(codebyplanDir, { recursive: true });
|
|
1154
1292
|
const repoJson = {
|
|
1155
1293
|
repo_id: selectedRepo.id
|
|
@@ -1161,13 +1299,13 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1161
1299
|
if (typeof repoAny.project_id === "string") {
|
|
1162
1300
|
repoJson.project_id = repoAny.project_id;
|
|
1163
1301
|
}
|
|
1164
|
-
await
|
|
1165
|
-
|
|
1302
|
+
await writeFile6(
|
|
1303
|
+
join6(codebyplanDir, "repo.json"),
|
|
1166
1304
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
1167
1305
|
"utf-8"
|
|
1168
1306
|
);
|
|
1169
|
-
await
|
|
1170
|
-
|
|
1307
|
+
await writeFile6(
|
|
1308
|
+
join6(codebyplanDir, "server.json"),
|
|
1171
1309
|
JSON.stringify(
|
|
1172
1310
|
{
|
|
1173
1311
|
server_port: null,
|
|
@@ -1180,35 +1318,35 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1180
1318
|
) + "\n",
|
|
1181
1319
|
"utf-8"
|
|
1182
1320
|
);
|
|
1183
|
-
await
|
|
1184
|
-
|
|
1321
|
+
await writeFile6(
|
|
1322
|
+
join6(codebyplanDir, "git.json"),
|
|
1185
1323
|
JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
|
|
1186
1324
|
"utf-8"
|
|
1187
1325
|
);
|
|
1188
|
-
await
|
|
1189
|
-
|
|
1326
|
+
await writeFile6(
|
|
1327
|
+
join6(codebyplanDir, "shipment.json"),
|
|
1190
1328
|
JSON.stringify({ shipment: null }, null, 2) + "\n",
|
|
1191
1329
|
"utf-8"
|
|
1192
1330
|
);
|
|
1193
|
-
await
|
|
1194
|
-
|
|
1331
|
+
await writeFile6(
|
|
1332
|
+
join6(codebyplanDir, "vendor.json"),
|
|
1195
1333
|
JSON.stringify({}, null, 2) + "\n",
|
|
1196
1334
|
"utf-8"
|
|
1197
1335
|
);
|
|
1198
|
-
await
|
|
1199
|
-
|
|
1336
|
+
await writeFile6(
|
|
1337
|
+
join6(codebyplanDir, "e2e.json"),
|
|
1200
1338
|
JSON.stringify({}, null, 2) + "\n",
|
|
1201
1339
|
"utf-8"
|
|
1202
1340
|
);
|
|
1203
|
-
const statuslinePath =
|
|
1341
|
+
const statuslinePath = join6(codebyplanDir, "statusline.json");
|
|
1204
1342
|
let statuslineExists = false;
|
|
1205
1343
|
try {
|
|
1206
|
-
await
|
|
1344
|
+
await readFile6(statuslinePath, "utf-8");
|
|
1207
1345
|
statuslineExists = true;
|
|
1208
1346
|
} catch {
|
|
1209
1347
|
}
|
|
1210
1348
|
if (!statuslineExists) {
|
|
1211
|
-
await
|
|
1349
|
+
await writeFile6(
|
|
1212
1350
|
statuslinePath,
|
|
1213
1351
|
JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
|
|
1214
1352
|
"utf-8"
|
|
@@ -1220,33 +1358,9 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1220
1358
|
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, statusline.json`
|
|
1221
1359
|
);
|
|
1222
1360
|
console.log(` device.local.json (gitignored)`);
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
try {
|
|
1227
|
-
const existing = await readFile5(gitignorePath, "utf-8");
|
|
1228
|
-
const lines = existing.split("\n");
|
|
1229
|
-
let content = existing;
|
|
1230
|
-
if (!lines.some((l) => l.trimEnd() === gitignoreEntry)) {
|
|
1231
|
-
content = (content.endsWith("\n") ? content : content + "\n") + gitignoreEntry + "\n";
|
|
1232
|
-
console.log(` Added '${gitignoreEntry}' to .gitignore`);
|
|
1233
|
-
}
|
|
1234
|
-
if (!lines.some((l) => l.trimEnd() === statuslineLocalEntry)) {
|
|
1235
|
-
content = (content.endsWith("\n") ? content : content + "\n") + statuslineLocalEntry + "\n";
|
|
1236
|
-
console.log(` Added '${statuslineLocalEntry}' to .gitignore`);
|
|
1237
|
-
}
|
|
1238
|
-
if (content !== existing) {
|
|
1239
|
-
await writeFile5(gitignorePath, content, "utf-8");
|
|
1240
|
-
}
|
|
1241
|
-
} catch {
|
|
1242
|
-
await writeFile5(
|
|
1243
|
-
gitignorePath,
|
|
1244
|
-
gitignoreEntry + "\n" + statuslineLocalEntry + "\n",
|
|
1245
|
-
"utf-8"
|
|
1246
|
-
);
|
|
1247
|
-
console.log(
|
|
1248
|
-
` Created .gitignore with '${gitignoreEntry}' and '${statuslineLocalEntry}'`
|
|
1249
|
-
);
|
|
1361
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
|
|
1362
|
+
if (gitignoreAction !== "unchanged") {
|
|
1363
|
+
console.log(" Updated .gitignore (codebyplan managed block)");
|
|
1250
1364
|
}
|
|
1251
1365
|
}
|
|
1252
1366
|
async function runSetup() {
|
|
@@ -1377,6 +1491,7 @@ async function runSetup() {
|
|
|
1377
1491
|
var init_setup = __esm({
|
|
1378
1492
|
"src/cli/setup.ts"() {
|
|
1379
1493
|
"use strict";
|
|
1494
|
+
init_gitignore_block();
|
|
1380
1495
|
init_local_config();
|
|
1381
1496
|
init_statusline_config();
|
|
1382
1497
|
init_resolve_worktree();
|
|
@@ -1387,21 +1502,21 @@ var init_setup = __esm({
|
|
|
1387
1502
|
});
|
|
1388
1503
|
|
|
1389
1504
|
// src/lib/flags.ts
|
|
1390
|
-
import { readFile as
|
|
1391
|
-
import { join as
|
|
1505
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
1506
|
+
import { join as join7, resolve } from "node:path";
|
|
1392
1507
|
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
1393
1508
|
let cursor = resolve(startDir);
|
|
1394
1509
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1395
|
-
const sentinelPath2 =
|
|
1510
|
+
const sentinelPath2 = join7(cursor, ".codebyplan", "repo.json");
|
|
1396
1511
|
try {
|
|
1397
|
-
const raw = await
|
|
1512
|
+
const raw = await readFile7(sentinelPath2, "utf-8");
|
|
1398
1513
|
const parsed = JSON.parse(raw);
|
|
1399
1514
|
return { path: sentinelPath2, contents: parsed };
|
|
1400
1515
|
} catch {
|
|
1401
1516
|
}
|
|
1402
|
-
const legacyPath =
|
|
1517
|
+
const legacyPath = join7(cursor, ".codebyplan.json");
|
|
1403
1518
|
try {
|
|
1404
|
-
const raw = await
|
|
1519
|
+
const raw = await readFile7(legacyPath, "utf-8");
|
|
1405
1520
|
const parsed = JSON.parse(raw);
|
|
1406
1521
|
return { path: legacyPath, contents: parsed };
|
|
1407
1522
|
} catch {
|
|
@@ -1628,15 +1743,15 @@ var upgrade_auth_exports = {};
|
|
|
1628
1743
|
__export(upgrade_auth_exports, {
|
|
1629
1744
|
runUpgradeAuth: () => runUpgradeAuth
|
|
1630
1745
|
});
|
|
1631
|
-
import { readFile as
|
|
1746
|
+
import { readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
1632
1747
|
import { homedir as homedir3 } from "node:os";
|
|
1633
|
-
import { join as
|
|
1748
|
+
import { join as join8 } from "node:path";
|
|
1634
1749
|
function configPaths() {
|
|
1635
|
-
return [
|
|
1750
|
+
return [join8(homedir3(), ".claude.json"), join8(process.cwd(), ".mcp.json")];
|
|
1636
1751
|
}
|
|
1637
|
-
async function readConfig2(
|
|
1752
|
+
async function readConfig2(path7) {
|
|
1638
1753
|
try {
|
|
1639
|
-
const raw = await
|
|
1754
|
+
const raw = await readFile8(path7, "utf-8");
|
|
1640
1755
|
const parsed = JSON.parse(raw);
|
|
1641
1756
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1642
1757
|
return parsed;
|
|
@@ -1650,14 +1765,14 @@ function entryHasLegacyApiKey(entry) {
|
|
|
1650
1765
|
if (!entry || !entry.headers) return false;
|
|
1651
1766
|
return "x-api-key" in entry.headers;
|
|
1652
1767
|
}
|
|
1653
|
-
async function rewriteConfig(
|
|
1768
|
+
async function rewriteConfig(path7, config, newUrl) {
|
|
1654
1769
|
const servers = config.mcpServers;
|
|
1655
1770
|
if (!servers) return false;
|
|
1656
1771
|
const entry = servers.codebyplan;
|
|
1657
1772
|
if (!entry) return false;
|
|
1658
1773
|
if (!entryHasLegacyApiKey(entry) && entry.url === newUrl) return false;
|
|
1659
1774
|
servers.codebyplan = { url: newUrl };
|
|
1660
|
-
await
|
|
1775
|
+
await writeFile7(path7, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1661
1776
|
return true;
|
|
1662
1777
|
}
|
|
1663
1778
|
async function runUpgradeAuth() {
|
|
@@ -1665,12 +1780,12 @@ async function runUpgradeAuth() {
|
|
|
1665
1780
|
await runLogin();
|
|
1666
1781
|
const newUrl = mcpEndpoint();
|
|
1667
1782
|
let migrated = 0;
|
|
1668
|
-
for (const
|
|
1669
|
-
const config = await readConfig2(
|
|
1783
|
+
for (const path7 of configPaths()) {
|
|
1784
|
+
const config = await readConfig2(path7);
|
|
1670
1785
|
if (!config) continue;
|
|
1671
|
-
const changed = await rewriteConfig(
|
|
1786
|
+
const changed = await rewriteConfig(path7, config, newUrl);
|
|
1672
1787
|
if (changed) {
|
|
1673
|
-
console.log(` Updated ${
|
|
1788
|
+
console.log(` Updated ${path7}`);
|
|
1674
1789
|
migrated++;
|
|
1675
1790
|
}
|
|
1676
1791
|
}
|
|
@@ -1738,8 +1853,8 @@ var init_confirm = __esm({
|
|
|
1738
1853
|
});
|
|
1739
1854
|
|
|
1740
1855
|
// src/lib/tech-detect.ts
|
|
1741
|
-
import { readFile as
|
|
1742
|
-
import { join as
|
|
1856
|
+
import { readFile as readFile9, access, readdir } from "node:fs/promises";
|
|
1857
|
+
import { join as join9, relative } from "node:path";
|
|
1743
1858
|
async function fileExists(filePath) {
|
|
1744
1859
|
try {
|
|
1745
1860
|
await access(filePath);
|
|
@@ -1752,8 +1867,8 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1752
1867
|
const apps = [];
|
|
1753
1868
|
const patterns = [];
|
|
1754
1869
|
try {
|
|
1755
|
-
const raw = await
|
|
1756
|
-
|
|
1870
|
+
const raw = await readFile9(
|
|
1871
|
+
join9(projectPath, "pnpm-workspace.yaml"),
|
|
1757
1872
|
"utf-8"
|
|
1758
1873
|
);
|
|
1759
1874
|
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
@@ -1767,7 +1882,7 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1767
1882
|
}
|
|
1768
1883
|
if (patterns.length === 0) {
|
|
1769
1884
|
try {
|
|
1770
|
-
const raw = await
|
|
1885
|
+
const raw = await readFile9(join9(projectPath, "package.json"), "utf-8");
|
|
1771
1886
|
const pkg = JSON.parse(raw);
|
|
1772
1887
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1773
1888
|
if (ws) patterns.push(...ws);
|
|
@@ -1777,14 +1892,14 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1777
1892
|
for (const pattern of patterns) {
|
|
1778
1893
|
if (pattern.endsWith("/*")) {
|
|
1779
1894
|
const dir = pattern.slice(0, -2);
|
|
1780
|
-
const absDir =
|
|
1895
|
+
const absDir = join9(projectPath, dir);
|
|
1781
1896
|
try {
|
|
1782
1897
|
const entries = await readdir(absDir, { withFileTypes: true });
|
|
1783
1898
|
for (const entry of entries) {
|
|
1784
1899
|
if (entry.isDirectory()) {
|
|
1785
|
-
const relPath =
|
|
1786
|
-
const absPath =
|
|
1787
|
-
if (await fileExists(
|
|
1900
|
+
const relPath = join9(dir, entry.name);
|
|
1901
|
+
const absPath = join9(absDir, entry.name);
|
|
1902
|
+
if (await fileExists(join9(absPath, "package.json"))) {
|
|
1788
1903
|
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1789
1904
|
}
|
|
1790
1905
|
}
|
|
@@ -1803,7 +1918,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1803
1918
|
const name = entry.name;
|
|
1804
1919
|
if (entry.isDirectory()) {
|
|
1805
1920
|
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1806
|
-
if (await hasJsxFile(
|
|
1921
|
+
if (await hasJsxFile(join9(dir, name), depth + 1)) return true;
|
|
1807
1922
|
} else if (entry.isFile()) {
|
|
1808
1923
|
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1809
1924
|
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
@@ -1822,7 +1937,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1822
1937
|
async function detectCapabilities(dirPath, pkgJson) {
|
|
1823
1938
|
const caps = /* @__PURE__ */ new Set();
|
|
1824
1939
|
for (const sub of JSX_SCAN_DIRS) {
|
|
1825
|
-
if (await hasJsxFile(
|
|
1940
|
+
if (await hasJsxFile(join9(dirPath, sub))) {
|
|
1826
1941
|
caps.add("jsx");
|
|
1827
1942
|
break;
|
|
1828
1943
|
}
|
|
@@ -1844,7 +1959,7 @@ async function detectCapabilities(dirPath, pkgJson) {
|
|
|
1844
1959
|
}
|
|
1845
1960
|
}
|
|
1846
1961
|
}
|
|
1847
|
-
if (!caps.has("node-server") && await fileExists(
|
|
1962
|
+
if (!caps.has("node-server") && await fileExists(join9(dirPath, "src", "main.ts"))) {
|
|
1848
1963
|
caps.add("node-server");
|
|
1849
1964
|
}
|
|
1850
1965
|
if (pkgJson && pkgJson.bin) {
|
|
@@ -1860,7 +1975,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1860
1975
|
const seen = /* @__PURE__ */ new Map();
|
|
1861
1976
|
let pkgJson = null;
|
|
1862
1977
|
try {
|
|
1863
|
-
const raw = await
|
|
1978
|
+
const raw = await readFile9(join9(dirPath, "package.json"), "utf-8");
|
|
1864
1979
|
pkgJson = JSON.parse(raw);
|
|
1865
1980
|
const allDeps = {
|
|
1866
1981
|
...pkgJson.dependencies ?? {},
|
|
@@ -1892,7 +2007,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1892
2007
|
}
|
|
1893
2008
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1894
2009
|
const key = rule.name.toLowerCase();
|
|
1895
|
-
if (!seen.has(key) && await fileExists(
|
|
2010
|
+
if (!seen.has(key) && await fileExists(join9(dirPath, file))) {
|
|
1896
2011
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1897
2012
|
}
|
|
1898
2013
|
}
|
|
@@ -2070,7 +2185,7 @@ function categorizeDependency(depName) {
|
|
|
2070
2185
|
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
2071
2186
|
if (depth > 4) return [];
|
|
2072
2187
|
const results = [];
|
|
2073
|
-
const pkgPath =
|
|
2188
|
+
const pkgPath = join9(dir, "package.json");
|
|
2074
2189
|
if (await fileExists(pkgPath)) {
|
|
2075
2190
|
results.push(pkgPath);
|
|
2076
2191
|
}
|
|
@@ -2079,7 +2194,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
|
2079
2194
|
for (const entry of entries) {
|
|
2080
2195
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
2081
2196
|
const subResults = await findPackageJsonFiles(
|
|
2082
|
-
|
|
2197
|
+
join9(dir, entry.name),
|
|
2083
2198
|
projectPath,
|
|
2084
2199
|
depth + 1
|
|
2085
2200
|
);
|
|
@@ -2094,7 +2209,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
2094
2209
|
const dependencies = [];
|
|
2095
2210
|
for (const pkgPath of packageJsonPaths) {
|
|
2096
2211
|
try {
|
|
2097
|
-
const raw = await
|
|
2212
|
+
const raw = await readFile9(pkgPath, "utf-8");
|
|
2098
2213
|
const pkg = JSON.parse(raw);
|
|
2099
2214
|
const sourcePath = relative(projectPath, pkgPath);
|
|
2100
2215
|
const depSections = [
|
|
@@ -2718,8 +2833,8 @@ __export(eslint_exports, {
|
|
|
2718
2833
|
eslintInit: () => eslintInit,
|
|
2719
2834
|
runEslint: () => runEslint
|
|
2720
2835
|
});
|
|
2721
|
-
import { readFile as
|
|
2722
|
-
import { join as
|
|
2836
|
+
import { readFile as readFile10, writeFile as writeFile8, access as access2, readdir as readdir2 } from "node:fs/promises";
|
|
2837
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
2723
2838
|
async function fileExists2(filePath) {
|
|
2724
2839
|
try {
|
|
2725
2840
|
await access2(filePath);
|
|
@@ -2730,7 +2845,7 @@ async function fileExists2(filePath) {
|
|
|
2730
2845
|
}
|
|
2731
2846
|
async function autoDetectIgnorePatterns(absPath) {
|
|
2732
2847
|
const patterns = [];
|
|
2733
|
-
if (await fileExists2(
|
|
2848
|
+
if (await fileExists2(join10(absPath, "esbuild.js"))) {
|
|
2734
2849
|
patterns.push("esbuild.js");
|
|
2735
2850
|
}
|
|
2736
2851
|
let entries = [];
|
|
@@ -2750,19 +2865,19 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2750
2865
|
}
|
|
2751
2866
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2752
2867
|
const candidate = `vitest.config.${ext}`;
|
|
2753
|
-
if (await fileExists2(
|
|
2868
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2754
2869
|
patterns.push(candidate);
|
|
2755
2870
|
break;
|
|
2756
2871
|
}
|
|
2757
2872
|
}
|
|
2758
2873
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2759
2874
|
const candidate = `vite.config.${ext}`;
|
|
2760
|
-
if (await fileExists2(
|
|
2875
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2761
2876
|
patterns.push(candidate);
|
|
2762
2877
|
break;
|
|
2763
2878
|
}
|
|
2764
2879
|
}
|
|
2765
|
-
if (await fileExists2(
|
|
2880
|
+
if (await fileExists2(join10(absPath, "tauri.conf.json"))) {
|
|
2766
2881
|
patterns.push("src-tauri/**");
|
|
2767
2882
|
patterns.push("**/*.d.ts");
|
|
2768
2883
|
}
|
|
@@ -2770,14 +2885,14 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2770
2885
|
}
|
|
2771
2886
|
function detectPackageManager(projectPath) {
|
|
2772
2887
|
return (async () => {
|
|
2773
|
-
if (await fileExists2(
|
|
2774
|
-
if (await fileExists2(
|
|
2888
|
+
if (await fileExists2(join10(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2889
|
+
if (await fileExists2(join10(projectPath, "yarn.lock"))) return "yarn";
|
|
2775
2890
|
return "npm";
|
|
2776
2891
|
})();
|
|
2777
2892
|
}
|
|
2778
2893
|
async function getInstalledDeps(pkgJsonPath) {
|
|
2779
2894
|
try {
|
|
2780
|
-
const raw = await
|
|
2895
|
+
const raw = await readFile10(pkgJsonPath, "utf-8");
|
|
2781
2896
|
const pkg = JSON.parse(raw);
|
|
2782
2897
|
const all = /* @__PURE__ */ new Set();
|
|
2783
2898
|
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
@@ -2890,7 +3005,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2890
3005
|
ignorePatterns: detectedIgnores
|
|
2891
3006
|
});
|
|
2892
3007
|
const hash = hashConfig(content);
|
|
2893
|
-
const configPath =
|
|
3008
|
+
const configPath = join10(target.absPath, "eslint.config.mjs");
|
|
2894
3009
|
configsToWrite.push({
|
|
2895
3010
|
target,
|
|
2896
3011
|
presets,
|
|
@@ -2912,11 +3027,11 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2912
3027
|
return;
|
|
2913
3028
|
}
|
|
2914
3029
|
const pm = await detectPackageManager(projectPath);
|
|
2915
|
-
const rootPkgJsonPath =
|
|
3030
|
+
const rootPkgJsonPath = join10(projectPath, "package.json");
|
|
2916
3031
|
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2917
3032
|
if (isMonorepo) {
|
|
2918
3033
|
for (const { target } of configsToWrite) {
|
|
2919
|
-
const appPkgJson =
|
|
3034
|
+
const appPkgJson = join10(target.absPath, "package.json");
|
|
2920
3035
|
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2921
3036
|
for (const dep of appDeps) {
|
|
2922
3037
|
installed.add(dep);
|
|
@@ -2968,7 +3083,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2968
3083
|
} of configsToWrite) {
|
|
2969
3084
|
if (await fileExists2(configPath)) {
|
|
2970
3085
|
try {
|
|
2971
|
-
const existing = await
|
|
3086
|
+
const existing = await readFile10(configPath, "utf-8");
|
|
2972
3087
|
const existingHash = hashConfig(existing);
|
|
2973
3088
|
if (existingHash === hash) {
|
|
2974
3089
|
console.log(
|
|
@@ -2988,7 +3103,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2988
3103
|
}
|
|
2989
3104
|
}
|
|
2990
3105
|
try {
|
|
2991
|
-
await
|
|
3106
|
+
await writeFile8(configPath, content, "utf-8");
|
|
2992
3107
|
} catch (err) {
|
|
2993
3108
|
console.error(
|
|
2994
3109
|
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3292,12 +3407,48 @@ var init_sync_approvals = __esm({
|
|
|
3292
3407
|
// src/cli/round.ts
|
|
3293
3408
|
var round_exports = {};
|
|
3294
3409
|
__export(round_exports, {
|
|
3410
|
+
RETRY_DELAY_MS: () => RETRY_DELAY_MS,
|
|
3411
|
+
fetchRoundsWithRetry: () => fetchRoundsWithRetry,
|
|
3412
|
+
isTransientMcpError: () => isTransientMcpError,
|
|
3295
3413
|
runRoundCommand: () => runRoundCommand,
|
|
3296
|
-
runRoundSyncApprovals: () => runRoundSyncApprovals
|
|
3414
|
+
runRoundSyncApprovals: () => runRoundSyncApprovals,
|
|
3415
|
+
setRetryDelayMs: () => setRetryDelayMs
|
|
3297
3416
|
});
|
|
3298
3417
|
import { access as access3 } from "node:fs/promises";
|
|
3299
|
-
import { join as
|
|
3418
|
+
import { join as join11 } from "node:path";
|
|
3300
3419
|
import { execSync as execSync2 } from "node:child_process";
|
|
3420
|
+
function setRetryDelayMs(ms) {
|
|
3421
|
+
RETRY_DELAY_MS = ms;
|
|
3422
|
+
}
|
|
3423
|
+
function sleep(ms) {
|
|
3424
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3425
|
+
}
|
|
3426
|
+
function isTransientMcpError(err) {
|
|
3427
|
+
if (!(err instanceof McpError)) return false;
|
|
3428
|
+
if (err.status !== void 0) {
|
|
3429
|
+
return err.status >= 500 && err.status <= 599;
|
|
3430
|
+
}
|
|
3431
|
+
return err.message.toLowerCase().includes("network error");
|
|
3432
|
+
}
|
|
3433
|
+
async function fetchRoundsWithRetry(taskId, options = {}) {
|
|
3434
|
+
const maxRetries = options.retries ?? 2;
|
|
3435
|
+
const delayMs = options.delayMs ?? RETRY_DELAY_MS;
|
|
3436
|
+
let lastErr;
|
|
3437
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3438
|
+
try {
|
|
3439
|
+
return await mcpCall("get_rounds", { task_id: taskId });
|
|
3440
|
+
} catch (err) {
|
|
3441
|
+
if (!isTransientMcpError(err)) {
|
|
3442
|
+
throw err;
|
|
3443
|
+
}
|
|
3444
|
+
lastErr = err;
|
|
3445
|
+
if (attempt < maxRetries) {
|
|
3446
|
+
await sleep(delayMs);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
throw lastErr;
|
|
3451
|
+
}
|
|
3301
3452
|
async function runRoundCommand(args) {
|
|
3302
3453
|
const subcommand = args[0];
|
|
3303
3454
|
if (subcommand === "sync-approvals") {
|
|
@@ -3359,6 +3510,7 @@ async function runRoundSyncApprovals(args) {
|
|
|
3359
3510
|
);
|
|
3360
3511
|
process.exit(1);
|
|
3361
3512
|
}
|
|
3513
|
+
let skipReason = null;
|
|
3362
3514
|
let stdoutPayload = null;
|
|
3363
3515
|
try {
|
|
3364
3516
|
let callerWorktreeId = flags["worktree-id"];
|
|
@@ -3370,84 +3522,95 @@ async function runRoundSyncApprovals(args) {
|
|
|
3370
3522
|
// Walk up to the directory containing .codebyplan/ or .codebyplan.json
|
|
3371
3523
|
found.path.replace(/\/.codebyplan(\.json|\/repo\.json)$/, "")
|
|
3372
3524
|
) : process.cwd();
|
|
3373
|
-
|
|
3374
|
-
task_id: taskId
|
|
3375
|
-
});
|
|
3376
|
-
const currentRound = rounds.find((r) => r.id === roundId);
|
|
3377
|
-
if (!currentRound) {
|
|
3378
|
-
throw new Error(`Round ${roundId} not found for task ${taskId}`);
|
|
3379
|
-
}
|
|
3380
|
-
const taskResponse = await apiGet(`/tasks/${taskId}`);
|
|
3381
|
-
const currentTask = taskResponse.data;
|
|
3382
|
-
let gitStatusOutput = "";
|
|
3525
|
+
let rounds;
|
|
3383
3526
|
try {
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
const hookPath = join10(
|
|
3394
|
-
repoRoot,
|
|
3395
|
-
".claude",
|
|
3396
|
-
"hooks",
|
|
3397
|
-
"lint-format-on-edit.sh"
|
|
3398
|
-
);
|
|
3399
|
-
let lintFormatHookExists = false;
|
|
3400
|
-
try {
|
|
3401
|
-
await access3(hookPath);
|
|
3402
|
-
lintFormatHookExists = true;
|
|
3403
|
-
} catch {
|
|
3527
|
+
rounds = await fetchRoundsWithRetry(taskId);
|
|
3528
|
+
} catch (err) {
|
|
3529
|
+
if (isTransientMcpError(err)) {
|
|
3530
|
+
const reason = err instanceof McpError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3531
|
+
skipReason = reason;
|
|
3532
|
+
rounds = [];
|
|
3533
|
+
} else {
|
|
3534
|
+
throw err;
|
|
3535
|
+
}
|
|
3404
3536
|
}
|
|
3405
|
-
|
|
3406
|
-
currentRound
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
lintFormatHookExists
|
|
3410
|
-
});
|
|
3411
|
-
if (dryRun) {
|
|
3412
|
-
stdoutPayload = JSON.stringify(
|
|
3413
|
-
{
|
|
3414
|
-
added: result.added,
|
|
3415
|
-
stale_marked: result.stale_marked,
|
|
3416
|
-
reactivated: result.reactivated,
|
|
3417
|
-
total_files: result.total_files,
|
|
3418
|
-
merged_files_changed: result.merged_files_changed
|
|
3419
|
-
},
|
|
3420
|
-
null,
|
|
3421
|
-
2
|
|
3422
|
-
);
|
|
3423
|
-
} else {
|
|
3424
|
-
const roundArgs = {
|
|
3425
|
-
round_id: roundId,
|
|
3426
|
-
files_changed: result.merged_files_changed
|
|
3427
|
-
};
|
|
3428
|
-
if (callerWorktreeId) {
|
|
3429
|
-
roundArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3537
|
+
if (!skipReason) {
|
|
3538
|
+
const currentRound = rounds.find((r) => r.id === roundId);
|
|
3539
|
+
if (!currentRound) {
|
|
3540
|
+
throw new Error(`Round ${roundId} not found for task ${taskId}`);
|
|
3430
3541
|
}
|
|
3431
|
-
await
|
|
3432
|
-
const
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3542
|
+
const taskResponse = await apiGet(`/tasks/${taskId}`);
|
|
3543
|
+
const currentTask = taskResponse.data;
|
|
3544
|
+
let gitStatusOutput = "";
|
|
3545
|
+
try {
|
|
3546
|
+
gitStatusOutput = execSync2("git status --short --porcelain -z", {
|
|
3547
|
+
cwd: repoRoot,
|
|
3548
|
+
encoding: "utf-8"
|
|
3549
|
+
});
|
|
3550
|
+
} catch {
|
|
3551
|
+
process.stderr.write(
|
|
3552
|
+
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3553
|
+
);
|
|
3439
3554
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
reactivated: result.reactivated,
|
|
3446
|
-
total_files: result.total_files
|
|
3447
|
-
},
|
|
3448
|
-
null,
|
|
3449
|
-
2
|
|
3555
|
+
const hookPath = join11(
|
|
3556
|
+
repoRoot,
|
|
3557
|
+
".claude",
|
|
3558
|
+
"hooks",
|
|
3559
|
+
"lint-format-on-edit.sh"
|
|
3450
3560
|
);
|
|
3561
|
+
let lintFormatHookExists = false;
|
|
3562
|
+
try {
|
|
3563
|
+
await access3(hookPath);
|
|
3564
|
+
lintFormatHookExists = true;
|
|
3565
|
+
} catch {
|
|
3566
|
+
}
|
|
3567
|
+
const result = runSyncApprovals({
|
|
3568
|
+
currentRound,
|
|
3569
|
+
currentTask,
|
|
3570
|
+
gitStatusOutput,
|
|
3571
|
+
lintFormatHookExists
|
|
3572
|
+
});
|
|
3573
|
+
if (dryRun) {
|
|
3574
|
+
stdoutPayload = JSON.stringify(
|
|
3575
|
+
{
|
|
3576
|
+
added: result.added,
|
|
3577
|
+
stale_marked: result.stale_marked,
|
|
3578
|
+
reactivated: result.reactivated,
|
|
3579
|
+
total_files: result.total_files,
|
|
3580
|
+
merged_files_changed: result.merged_files_changed
|
|
3581
|
+
},
|
|
3582
|
+
null,
|
|
3583
|
+
2
|
|
3584
|
+
);
|
|
3585
|
+
} else {
|
|
3586
|
+
const roundArgs = {
|
|
3587
|
+
round_id: roundId,
|
|
3588
|
+
files_changed: result.merged_files_changed
|
|
3589
|
+
};
|
|
3590
|
+
if (callerWorktreeId) {
|
|
3591
|
+
roundArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3592
|
+
}
|
|
3593
|
+
await mcpCall("update_round", roundArgs);
|
|
3594
|
+
const taskArgs = {
|
|
3595
|
+
task_id: taskId,
|
|
3596
|
+
files_changed: result.merged_files_changed,
|
|
3597
|
+
app_file_approval_by_user: false
|
|
3598
|
+
};
|
|
3599
|
+
if (callerWorktreeId) {
|
|
3600
|
+
taskArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3601
|
+
}
|
|
3602
|
+
await mcpCall("update_task", taskArgs);
|
|
3603
|
+
stdoutPayload = JSON.stringify(
|
|
3604
|
+
{
|
|
3605
|
+
added: result.added,
|
|
3606
|
+
stale_marked: result.stale_marked,
|
|
3607
|
+
reactivated: result.reactivated,
|
|
3608
|
+
total_files: result.total_files
|
|
3609
|
+
},
|
|
3610
|
+
null,
|
|
3611
|
+
2
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3451
3614
|
}
|
|
3452
3615
|
} catch (err) {
|
|
3453
3616
|
process.stderr.write(
|
|
@@ -3456,6 +3619,13 @@ async function runRoundSyncApprovals(args) {
|
|
|
3456
3619
|
);
|
|
3457
3620
|
process.exit(1);
|
|
3458
3621
|
}
|
|
3622
|
+
if (skipReason !== null) {
|
|
3623
|
+
process.stderr.write(
|
|
3624
|
+
`sync-approvals: MCP temporarily unavailable (${skipReason}); skipping approval sync. Re-run /cbp-round-update when the service recovers.
|
|
3625
|
+
`
|
|
3626
|
+
);
|
|
3627
|
+
process.exit(0);
|
|
3628
|
+
}
|
|
3459
3629
|
if (stdoutPayload === null) {
|
|
3460
3630
|
process.stderr.write("sync-approvals: internal error \u2014 payload not set\n");
|
|
3461
3631
|
process.exit(1);
|
|
@@ -3499,6 +3669,7 @@ async function autoResolveWorktreeId() {
|
|
|
3499
3669
|
return void 0;
|
|
3500
3670
|
}
|
|
3501
3671
|
}
|
|
3672
|
+
var RETRY_DELAY_MS;
|
|
3502
3673
|
var init_round = __esm({
|
|
3503
3674
|
"src/cli/round.ts"() {
|
|
3504
3675
|
"use strict";
|
|
@@ -3508,12 +3679,13 @@ var init_round = __esm({
|
|
|
3508
3679
|
init_resolve_worktree();
|
|
3509
3680
|
init_local_config();
|
|
3510
3681
|
init_sync_approvals();
|
|
3682
|
+
RETRY_DELAY_MS = 1e3;
|
|
3511
3683
|
}
|
|
3512
3684
|
});
|
|
3513
3685
|
|
|
3514
3686
|
// src/lib/migrate-branch-model.ts
|
|
3515
|
-
import { readFile as
|
|
3516
|
-
import { join as
|
|
3687
|
+
import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
3688
|
+
import { join as join12 } from "node:path";
|
|
3517
3689
|
import { execSync as execSync3 } from "node:child_process";
|
|
3518
3690
|
function assertValidBranchName(branch) {
|
|
3519
3691
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3523,7 +3695,7 @@ function assertValidBranchName(branch) {
|
|
|
3523
3695
|
}
|
|
3524
3696
|
}
|
|
3525
3697
|
async function readJsonFile(filePath) {
|
|
3526
|
-
const raw = await
|
|
3698
|
+
const raw = await readFile11(filePath, "utf-8");
|
|
3527
3699
|
const parsed = JSON.parse(raw);
|
|
3528
3700
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3529
3701
|
throw new Error(`${filePath} does not contain a JSON object`);
|
|
@@ -3592,12 +3764,12 @@ async function runBranchMigration(opts) {
|
|
|
3592
3764
|
if (found) {
|
|
3593
3765
|
if (found.path.endsWith("/repo.json")) {
|
|
3594
3766
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3595
|
-
configPath =
|
|
3767
|
+
configPath = join12(dir, "git.json");
|
|
3596
3768
|
} else {
|
|
3597
3769
|
configPath = found.path;
|
|
3598
3770
|
}
|
|
3599
3771
|
} else {
|
|
3600
|
-
configPath =
|
|
3772
|
+
configPath = join12(cwd, ".codebyplan", "git.json");
|
|
3601
3773
|
}
|
|
3602
3774
|
let fileRaw;
|
|
3603
3775
|
let fileParsed;
|
|
@@ -3671,7 +3843,7 @@ async function runBranchMigration(opts) {
|
|
|
3671
3843
|
const updatedParsed = { ...fileParsed, branch_config: after };
|
|
3672
3844
|
const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
|
|
3673
3845
|
if (newJson !== fileRaw) {
|
|
3674
|
-
await
|
|
3846
|
+
await writeFile9(configPath, newJson, "utf-8");
|
|
3675
3847
|
}
|
|
3676
3848
|
}
|
|
3677
3849
|
return {
|
|
@@ -3777,8 +3949,8 @@ var init_branch = __esm({
|
|
|
3777
3949
|
});
|
|
3778
3950
|
|
|
3779
3951
|
// src/lib/ship.ts
|
|
3780
|
-
import { readFile as
|
|
3781
|
-
import { join as
|
|
3952
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
3953
|
+
import { join as join13 } from "node:path";
|
|
3782
3954
|
import { execSync as execSync4, spawnSync as spawnSync2 } from "node:child_process";
|
|
3783
3955
|
function assertValidBranchName2(branch, label) {
|
|
3784
3956
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3793,15 +3965,15 @@ async function readBaseBranch(cwd) {
|
|
|
3793
3965
|
if (found) {
|
|
3794
3966
|
if (found.path.endsWith("/repo.json")) {
|
|
3795
3967
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3796
|
-
gitJsonPath =
|
|
3968
|
+
gitJsonPath = join13(dir, "git.json");
|
|
3797
3969
|
} else {
|
|
3798
3970
|
gitJsonPath = found.path;
|
|
3799
3971
|
}
|
|
3800
3972
|
} else {
|
|
3801
|
-
gitJsonPath =
|
|
3973
|
+
gitJsonPath = join13(cwd, ".codebyplan", "git.json");
|
|
3802
3974
|
}
|
|
3803
3975
|
try {
|
|
3804
|
-
const raw = await
|
|
3976
|
+
const raw = await readFile12(gitJsonPath, "utf-8");
|
|
3805
3977
|
const parsed = JSON.parse(raw);
|
|
3806
3978
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3807
3979
|
return "main";
|
|
@@ -4333,19 +4505,19 @@ var init_resolve_worktree2 = __esm({
|
|
|
4333
4505
|
});
|
|
4334
4506
|
|
|
4335
4507
|
// src/lib/migrate-local-config.ts
|
|
4336
|
-
import { mkdir as mkdir5, readFile as
|
|
4337
|
-
import { join as
|
|
4508
|
+
import { mkdir as mkdir5, readFile as readFile13, unlink as unlink2, writeFile as writeFile10 } from "node:fs/promises";
|
|
4509
|
+
import { join as join14 } from "node:path";
|
|
4338
4510
|
function legacySharedPath(projectPath) {
|
|
4339
|
-
return
|
|
4511
|
+
return join14(projectPath, ".codebyplan.json");
|
|
4340
4512
|
}
|
|
4341
4513
|
function legacyLocalPath(projectPath) {
|
|
4342
|
-
return
|
|
4514
|
+
return join14(projectPath, ".codebyplan.local.json");
|
|
4343
4515
|
}
|
|
4344
4516
|
function newDirPath(projectPath) {
|
|
4345
|
-
return
|
|
4517
|
+
return join14(projectPath, ".codebyplan");
|
|
4346
4518
|
}
|
|
4347
4519
|
function sentinelPath(projectPath) {
|
|
4348
|
-
return
|
|
4520
|
+
return join14(projectPath, ".codebyplan", "repo.json");
|
|
4349
4521
|
}
|
|
4350
4522
|
async function statSafe(p) {
|
|
4351
4523
|
const { stat: stat2 } = await import("node:fs/promises");
|
|
@@ -4384,7 +4556,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4384
4556
|
}
|
|
4385
4557
|
let legacyRaw;
|
|
4386
4558
|
try {
|
|
4387
|
-
legacyRaw = await
|
|
4559
|
+
legacyRaw = await readFile13(legacySharedPath(projectPath), "utf-8");
|
|
4388
4560
|
} catch {
|
|
4389
4561
|
return {
|
|
4390
4562
|
migrated: true,
|
|
@@ -4411,7 +4583,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4411
4583
|
let deviceId;
|
|
4412
4584
|
let deviceWrittenByHelper = false;
|
|
4413
4585
|
try {
|
|
4414
|
-
const localRaw = await
|
|
4586
|
+
const localRaw = await readFile13(legacyLocalPath(projectPath), "utf-8");
|
|
4415
4587
|
const localParsed = JSON.parse(localRaw);
|
|
4416
4588
|
if (typeof localParsed.device_id === "string") {
|
|
4417
4589
|
deviceId = localParsed.device_id;
|
|
@@ -4438,8 +4610,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4438
4610
|
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
4439
4611
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
4440
4612
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
4441
|
-
await
|
|
4442
|
-
|
|
4613
|
+
await writeFile10(
|
|
4614
|
+
join14(projectPath, ".codebyplan", "repo.json"),
|
|
4443
4615
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
4444
4616
|
"utf-8"
|
|
4445
4617
|
);
|
|
@@ -4451,8 +4623,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4451
4623
|
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
4452
4624
|
if ("port_allocations" in cfg)
|
|
4453
4625
|
serverJson.port_allocations = cfg.port_allocations;
|
|
4454
|
-
await
|
|
4455
|
-
|
|
4626
|
+
await writeFile10(
|
|
4627
|
+
join14(projectPath, ".codebyplan", "server.json"),
|
|
4456
4628
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
4457
4629
|
"utf-8"
|
|
4458
4630
|
);
|
|
@@ -4460,37 +4632,37 @@ async function runLocalMigration(projectPath) {
|
|
|
4460
4632
|
const gitJson = {};
|
|
4461
4633
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
4462
4634
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
4463
|
-
await
|
|
4464
|
-
|
|
4635
|
+
await writeFile10(
|
|
4636
|
+
join14(projectPath, ".codebyplan", "git.json"),
|
|
4465
4637
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
4466
4638
|
"utf-8"
|
|
4467
4639
|
);
|
|
4468
4640
|
filesChanged.push(".codebyplan/git.json");
|
|
4469
4641
|
const shipmentJson = {};
|
|
4470
4642
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
4471
|
-
await
|
|
4472
|
-
|
|
4643
|
+
await writeFile10(
|
|
4644
|
+
join14(projectPath, ".codebyplan", "shipment.json"),
|
|
4473
4645
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
4474
4646
|
"utf-8"
|
|
4475
4647
|
);
|
|
4476
4648
|
filesChanged.push(".codebyplan/shipment.json");
|
|
4477
4649
|
const vendorJson = {};
|
|
4478
|
-
await
|
|
4479
|
-
|
|
4650
|
+
await writeFile10(
|
|
4651
|
+
join14(projectPath, ".codebyplan", "vendor.json"),
|
|
4480
4652
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
4481
4653
|
"utf-8"
|
|
4482
4654
|
);
|
|
4483
4655
|
filesChanged.push(".codebyplan/vendor.json");
|
|
4484
4656
|
const e2eJson = {};
|
|
4485
|
-
await
|
|
4486
|
-
|
|
4657
|
+
await writeFile10(
|
|
4658
|
+
join14(projectPath, ".codebyplan", "e2e.json"),
|
|
4487
4659
|
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
4488
4660
|
"utf-8"
|
|
4489
4661
|
);
|
|
4490
4662
|
filesChanged.push(".codebyplan/e2e.json");
|
|
4491
4663
|
if (!deviceWrittenByHelper) {
|
|
4492
|
-
await
|
|
4493
|
-
|
|
4664
|
+
await writeFile10(
|
|
4665
|
+
join14(projectPath, ".codebyplan", "device.local.json"),
|
|
4494
4666
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
4495
4667
|
"utf-8"
|
|
4496
4668
|
);
|
|
@@ -4502,9 +4674,9 @@ async function runLocalMigration(projectPath) {
|
|
|
4502
4674
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
4503
4675
|
);
|
|
4504
4676
|
}
|
|
4505
|
-
const gitignorePath =
|
|
4677
|
+
const gitignorePath = join14(projectPath, ".gitignore");
|
|
4506
4678
|
try {
|
|
4507
|
-
const gitignoreContent = await
|
|
4679
|
+
const gitignoreContent = await readFile13(gitignorePath, "utf-8");
|
|
4508
4680
|
const legacyLine = ".codebyplan.local.json";
|
|
4509
4681
|
const newLine = ".codebyplan/device.local.json";
|
|
4510
4682
|
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
@@ -4523,7 +4695,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4523
4695
|
updated = gitignoreContent;
|
|
4524
4696
|
}
|
|
4525
4697
|
if (updated !== gitignoreContent) {
|
|
4526
|
-
await
|
|
4698
|
+
await writeFile10(gitignorePath, updated, "utf-8");
|
|
4527
4699
|
filesChanged.push(".gitignore");
|
|
4528
4700
|
}
|
|
4529
4701
|
} catch {
|
|
@@ -4563,8 +4735,8 @@ __export(config_exports, {
|
|
|
4563
4735
|
readVendorConfig: () => readVendorConfig,
|
|
4564
4736
|
runConfig: () => runConfig
|
|
4565
4737
|
});
|
|
4566
|
-
import { mkdir as mkdir6, readFile as
|
|
4567
|
-
import { join as
|
|
4738
|
+
import { mkdir as mkdir6, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
4739
|
+
import { join as join15 } from "node:path";
|
|
4568
4740
|
async function runConfig() {
|
|
4569
4741
|
const flags = parseFlags(3);
|
|
4570
4742
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -4597,7 +4769,7 @@ async function runConfig() {
|
|
|
4597
4769
|
console.log("\n Config complete.\n");
|
|
4598
4770
|
}
|
|
4599
4771
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
4600
|
-
const codebyplanDir =
|
|
4772
|
+
const codebyplanDir = join15(projectPath, ".codebyplan");
|
|
4601
4773
|
let resolvedWorktreeId;
|
|
4602
4774
|
try {
|
|
4603
4775
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
@@ -4738,16 +4910,16 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4738
4910
|
];
|
|
4739
4911
|
let anyUpdated = false;
|
|
4740
4912
|
for (const { name, payload, createOnly } of files) {
|
|
4741
|
-
const filePath =
|
|
4913
|
+
const filePath = join15(codebyplanDir, name);
|
|
4742
4914
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
4743
4915
|
let currentJson = "";
|
|
4744
4916
|
try {
|
|
4745
|
-
currentJson = await
|
|
4917
|
+
currentJson = await readFile14(filePath, "utf-8");
|
|
4746
4918
|
} catch {
|
|
4747
4919
|
}
|
|
4748
4920
|
if (createOnly && currentJson !== "") continue;
|
|
4749
4921
|
if (currentJson === newJson) continue;
|
|
4750
|
-
await
|
|
4922
|
+
await writeFile11(filePath, newJson, "utf-8");
|
|
4751
4923
|
console.log(` Updated .codebyplan/${name}`);
|
|
4752
4924
|
anyUpdated = true;
|
|
4753
4925
|
}
|
|
@@ -4757,8 +4929,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4757
4929
|
}
|
|
4758
4930
|
async function readRepoConfig(projectPath) {
|
|
4759
4931
|
try {
|
|
4760
|
-
const raw = await
|
|
4761
|
-
|
|
4932
|
+
const raw = await readFile14(
|
|
4933
|
+
join15(projectPath, ".codebyplan", "repo.json"),
|
|
4762
4934
|
"utf-8"
|
|
4763
4935
|
);
|
|
4764
4936
|
return JSON.parse(raw);
|
|
@@ -4768,8 +4940,8 @@ async function readRepoConfig(projectPath) {
|
|
|
4768
4940
|
}
|
|
4769
4941
|
async function readServerConfig(projectPath) {
|
|
4770
4942
|
try {
|
|
4771
|
-
const raw = await
|
|
4772
|
-
|
|
4943
|
+
const raw = await readFile14(
|
|
4944
|
+
join15(projectPath, ".codebyplan", "server.json"),
|
|
4773
4945
|
"utf-8"
|
|
4774
4946
|
);
|
|
4775
4947
|
return JSON.parse(raw);
|
|
@@ -4779,8 +4951,8 @@ async function readServerConfig(projectPath) {
|
|
|
4779
4951
|
}
|
|
4780
4952
|
async function readGitConfig(projectPath) {
|
|
4781
4953
|
try {
|
|
4782
|
-
const raw = await
|
|
4783
|
-
|
|
4954
|
+
const raw = await readFile14(
|
|
4955
|
+
join15(projectPath, ".codebyplan", "git.json"),
|
|
4784
4956
|
"utf-8"
|
|
4785
4957
|
);
|
|
4786
4958
|
return JSON.parse(raw);
|
|
@@ -4790,8 +4962,8 @@ async function readGitConfig(projectPath) {
|
|
|
4790
4962
|
}
|
|
4791
4963
|
async function readShipmentConfig(projectPath) {
|
|
4792
4964
|
try {
|
|
4793
|
-
const raw = await
|
|
4794
|
-
|
|
4965
|
+
const raw = await readFile14(
|
|
4966
|
+
join15(projectPath, ".codebyplan", "shipment.json"),
|
|
4795
4967
|
"utf-8"
|
|
4796
4968
|
);
|
|
4797
4969
|
return JSON.parse(raw);
|
|
@@ -4801,8 +4973,8 @@ async function readShipmentConfig(projectPath) {
|
|
|
4801
4973
|
}
|
|
4802
4974
|
async function readVendorConfig(projectPath) {
|
|
4803
4975
|
try {
|
|
4804
|
-
const raw = await
|
|
4805
|
-
|
|
4976
|
+
const raw = await readFile14(
|
|
4977
|
+
join15(projectPath, ".codebyplan", "vendor.json"),
|
|
4806
4978
|
"utf-8"
|
|
4807
4979
|
);
|
|
4808
4980
|
return JSON.parse(raw);
|
|
@@ -4812,8 +4984,8 @@ async function readVendorConfig(projectPath) {
|
|
|
4812
4984
|
}
|
|
4813
4985
|
async function readE2eConfig(projectPath) {
|
|
4814
4986
|
try {
|
|
4815
|
-
const raw = await
|
|
4816
|
-
|
|
4987
|
+
const raw = await readFile14(
|
|
4988
|
+
join15(projectPath, ".codebyplan", "e2e.json"),
|
|
4817
4989
|
"utf-8"
|
|
4818
4990
|
);
|
|
4819
4991
|
return JSON.parse(raw);
|
|
@@ -4869,14 +5041,14 @@ var init_server_detect = __esm({
|
|
|
4869
5041
|
});
|
|
4870
5042
|
|
|
4871
5043
|
// src/lib/port-verify.ts
|
|
4872
|
-
import { readFile as
|
|
5044
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
4873
5045
|
async function verifyPorts(projectPath, portAllocations) {
|
|
4874
5046
|
const mismatches = [];
|
|
4875
5047
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
4876
5048
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
4877
5049
|
for (const pkgPath of packageJsonPaths) {
|
|
4878
5050
|
try {
|
|
4879
|
-
const raw = await
|
|
5051
|
+
const raw = await readFile15(pkgPath, "utf-8");
|
|
4880
5052
|
const pkg = JSON.parse(raw);
|
|
4881
5053
|
const scriptPort = detectPortFromScripts(pkg);
|
|
4882
5054
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -4939,7 +5111,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
4939
5111
|
}
|
|
4940
5112
|
let pkg;
|
|
4941
5113
|
try {
|
|
4942
|
-
const raw = await
|
|
5114
|
+
const raw = await readFile15(`${app.absPath}/package.json`, "utf-8");
|
|
4943
5115
|
pkg = JSON.parse(raw);
|
|
4944
5116
|
} catch {
|
|
4945
5117
|
continue;
|
|
@@ -5309,7 +5481,7 @@ var init_hash = __esm({
|
|
|
5309
5481
|
|
|
5310
5482
|
// src/lib/template-walker.ts
|
|
5311
5483
|
import * as fs from "node:fs";
|
|
5312
|
-
import * as
|
|
5484
|
+
import * as path2 from "node:path";
|
|
5313
5485
|
function walkTemplates(templatesDir) {
|
|
5314
5486
|
const absRoot = fs.realpathSync(templatesDir);
|
|
5315
5487
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -5322,7 +5494,7 @@ function walkTemplates(templatesDir) {
|
|
|
5322
5494
|
visited.add(realDir);
|
|
5323
5495
|
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
5324
5496
|
for (const entry of entries) {
|
|
5325
|
-
const absPath =
|
|
5497
|
+
const absPath = path2.join(absDir, entry.name);
|
|
5326
5498
|
if (entry.isDirectory()) {
|
|
5327
5499
|
recurse(absPath);
|
|
5328
5500
|
continue;
|
|
@@ -5331,7 +5503,7 @@ function walkTemplates(templatesDir) {
|
|
|
5331
5503
|
continue;
|
|
5332
5504
|
}
|
|
5333
5505
|
if (entry.name === ".gitkeep") continue;
|
|
5334
|
-
const relPosix =
|
|
5506
|
+
const relPosix = path2.relative(absRoot, absPath).split(path2.sep).join("/");
|
|
5335
5507
|
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
5336
5508
|
continue;
|
|
5337
5509
|
}
|
|
@@ -5359,6 +5531,10 @@ var init_template_walker = __esm({
|
|
|
5359
5531
|
"rules/README.md",
|
|
5360
5532
|
"settings.project.base.json",
|
|
5361
5533
|
"settings.user.base.json",
|
|
5534
|
+
// .gitignore — managed by ensureManagedGitignoreBlock; never copied into
|
|
5535
|
+
// consuming projects' .claude/ tree (it would overwrite the project root
|
|
5536
|
+
// .gitignore with a stale single-entry file).
|
|
5537
|
+
".gitignore",
|
|
5362
5538
|
// CBP-internal hooks — see templates/hooks/README.md "Hooks NOT included and why"
|
|
5363
5539
|
"hooks/validate-structure.sh",
|
|
5364
5540
|
"hooks/validate-structure-lib.sh",
|
|
@@ -5376,15 +5552,15 @@ var init_template_walker = __esm({
|
|
|
5376
5552
|
// src/lib/manifest.ts
|
|
5377
5553
|
import * as fs2 from "node:fs";
|
|
5378
5554
|
import * as os from "node:os";
|
|
5379
|
-
import * as
|
|
5555
|
+
import * as path3 from "node:path";
|
|
5380
5556
|
function manifestPath(projectDir) {
|
|
5381
|
-
return
|
|
5557
|
+
return path3.join(projectDir, ".claude", NEW_MANIFEST_FILENAME);
|
|
5382
5558
|
}
|
|
5383
5559
|
function midManifestPath(projectDir) {
|
|
5384
|
-
return
|
|
5560
|
+
return path3.join(projectDir, ".claude", MID_MANIFEST_FILENAME);
|
|
5385
5561
|
}
|
|
5386
5562
|
function oldManifestPath(projectDir) {
|
|
5387
|
-
return
|
|
5563
|
+
return path3.join(projectDir, ".claude", OLD_MANIFEST_FILENAME);
|
|
5388
5564
|
}
|
|
5389
5565
|
function readManifest(projectDir) {
|
|
5390
5566
|
const newFile = manifestPath(projectDir);
|
|
@@ -5406,7 +5582,7 @@ function readManifest(projectDir) {
|
|
|
5406
5582
|
}
|
|
5407
5583
|
function writeManifest(projectDir, manifest) {
|
|
5408
5584
|
const file = manifestPath(projectDir);
|
|
5409
|
-
fs2.mkdirSync(
|
|
5585
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5410
5586
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5411
5587
|
const mid = midManifestPath(projectDir);
|
|
5412
5588
|
if (fs2.existsSync(mid)) {
|
|
@@ -5425,16 +5601,16 @@ function defaultManifest() {
|
|
|
5425
5601
|
};
|
|
5426
5602
|
}
|
|
5427
5603
|
function userManifestPath(userDir) {
|
|
5428
|
-
const dir = userDir ??
|
|
5429
|
-
return
|
|
5604
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5605
|
+
return path3.join(dir, NEW_MANIFEST_FILENAME);
|
|
5430
5606
|
}
|
|
5431
5607
|
function userMidManifestPath(userDir) {
|
|
5432
|
-
const dir = userDir ??
|
|
5433
|
-
return
|
|
5608
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5609
|
+
return path3.join(dir, MID_MANIFEST_FILENAME);
|
|
5434
5610
|
}
|
|
5435
5611
|
function userOldManifestPath(userDir) {
|
|
5436
|
-
const dir = userDir ??
|
|
5437
|
-
return
|
|
5612
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5613
|
+
return path3.join(dir, OLD_MANIFEST_FILENAME);
|
|
5438
5614
|
}
|
|
5439
5615
|
function readManifestForScope(scope, arg2) {
|
|
5440
5616
|
if (scope === "user") {
|
|
@@ -5460,7 +5636,7 @@ function readManifestForScope(scope, arg2) {
|
|
|
5460
5636
|
function writeManifestForScope(scope, manifest, arg3) {
|
|
5461
5637
|
if (scope === "user") {
|
|
5462
5638
|
const file = userManifestPath(arg3);
|
|
5463
|
-
fs2.mkdirSync(
|
|
5639
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5464
5640
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5465
5641
|
const mid = userMidManifestPath(arg3);
|
|
5466
5642
|
if (fs2.existsSync(mid)) {
|
|
@@ -5716,14 +5892,14 @@ __export(install_exports, {
|
|
|
5716
5892
|
});
|
|
5717
5893
|
import * as fs3 from "node:fs";
|
|
5718
5894
|
import * as os2 from "node:os";
|
|
5719
|
-
import * as
|
|
5895
|
+
import * as path4 from "node:path";
|
|
5720
5896
|
import { fileURLToPath } from "node:url";
|
|
5721
5897
|
function resolveTemplatesDir() {
|
|
5722
|
-
const here =
|
|
5898
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
5723
5899
|
const candidates = [
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5900
|
+
path4.resolve(here, "..", "templates"),
|
|
5901
|
+
path4.resolve(here, "..", "..", "templates"),
|
|
5902
|
+
path4.resolve(here, "..", "..", "..", "templates")
|
|
5727
5903
|
];
|
|
5728
5904
|
for (const c of candidates) {
|
|
5729
5905
|
if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
|
|
@@ -5764,14 +5940,14 @@ async function runInstall(opts, deps = {}) {
|
|
|
5764
5940
|
const files = walkTemplates(templatesDir);
|
|
5765
5941
|
const manifestEntries = [];
|
|
5766
5942
|
for (const f of files) {
|
|
5767
|
-
const absDest =
|
|
5768
|
-
const absSrc =
|
|
5943
|
+
const absDest = path4.join(projectDir, ".claude", f.dest);
|
|
5944
|
+
const absSrc = path4.join(templatesDir, f.src);
|
|
5769
5945
|
if (opts.dryRun) {
|
|
5770
5946
|
if (opts.verbose) {
|
|
5771
5947
|
console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
|
|
5772
5948
|
}
|
|
5773
5949
|
} else {
|
|
5774
|
-
fs3.mkdirSync(
|
|
5950
|
+
fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
|
|
5775
5951
|
fs3.copyFileSync(absSrc, absDest);
|
|
5776
5952
|
if (opts.verbose) {
|
|
5777
5953
|
console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
|
|
@@ -5779,15 +5955,15 @@ async function runInstall(opts, deps = {}) {
|
|
|
5779
5955
|
}
|
|
5780
5956
|
manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
|
|
5781
5957
|
}
|
|
5782
|
-
const hooksJsonPath =
|
|
5783
|
-
const baseSettingsPath =
|
|
5958
|
+
const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5959
|
+
const baseSettingsPath = path4.join(
|
|
5784
5960
|
templatesDir,
|
|
5785
5961
|
"settings.project.base.json"
|
|
5786
5962
|
);
|
|
5787
5963
|
const hasHooks = fs3.existsSync(hooksJsonPath);
|
|
5788
5964
|
const hasBase = fs3.existsSync(baseSettingsPath);
|
|
5789
5965
|
if (hasHooks || hasBase) {
|
|
5790
|
-
const settingsPath =
|
|
5966
|
+
const settingsPath = path4.join(projectDir, ".claude", "settings.json");
|
|
5791
5967
|
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5792
5968
|
if (hasBase) {
|
|
5793
5969
|
const base = JSON.parse(
|
|
@@ -5802,7 +5978,7 @@ async function runInstall(opts, deps = {}) {
|
|
|
5802
5978
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
5803
5979
|
}
|
|
5804
5980
|
if (!opts.dryRun) {
|
|
5805
|
-
fs3.mkdirSync(
|
|
5981
|
+
fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
|
|
5806
5982
|
fs3.writeFileSync(
|
|
5807
5983
|
settingsPath,
|
|
5808
5984
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -5810,10 +5986,19 @@ async function runInstall(opts, deps = {}) {
|
|
|
5810
5986
|
);
|
|
5811
5987
|
} else if (opts.verbose) {
|
|
5812
5988
|
console.log(
|
|
5813
|
-
`[dry-run] would merge settings into ${
|
|
5989
|
+
`[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
|
|
5814
5990
|
);
|
|
5815
5991
|
}
|
|
5816
5992
|
}
|
|
5993
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
5994
|
+
projectDir,
|
|
5995
|
+
opts.dryRun
|
|
5996
|
+
);
|
|
5997
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
5998
|
+
console.log(
|
|
5999
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
5817
6002
|
if (!opts.dryRun) {
|
|
5818
6003
|
const manifest = defaultManifest();
|
|
5819
6004
|
manifest.files = manifestEntries;
|
|
@@ -5844,9 +6029,9 @@ function runInstallUser(opts, deps) {
|
|
|
5844
6029
|
return;
|
|
5845
6030
|
}
|
|
5846
6031
|
try {
|
|
5847
|
-
const userDir = deps.userDir ??
|
|
5848
|
-
const settingsPath =
|
|
5849
|
-
const userBaseSettingsPath =
|
|
6032
|
+
const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
|
|
6033
|
+
const settingsPath = path4.join(userDir, "settings.json");
|
|
6034
|
+
const userBaseSettingsPath = path4.join(
|
|
5850
6035
|
templatesDir,
|
|
5851
6036
|
"settings.user.base.json"
|
|
5852
6037
|
);
|
|
@@ -5888,7 +6073,7 @@ function runInstallUser(opts, deps) {
|
|
|
5888
6073
|
}
|
|
5889
6074
|
}
|
|
5890
6075
|
function countHookEntries(templatesDir) {
|
|
5891
|
-
const p =
|
|
6076
|
+
const p = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5892
6077
|
if (!fs3.existsSync(p)) return 0;
|
|
5893
6078
|
try {
|
|
5894
6079
|
const j = JSON.parse(fs3.readFileSync(p, "utf8"));
|
|
@@ -5908,6 +6093,7 @@ var init_install = __esm({
|
|
|
5908
6093
|
"src/cli/claude/install.ts"() {
|
|
5909
6094
|
"use strict";
|
|
5910
6095
|
init_template_walker();
|
|
6096
|
+
init_gitignore_block();
|
|
5911
6097
|
init_manifest();
|
|
5912
6098
|
init_settings_merge();
|
|
5913
6099
|
init_statusline_config();
|
|
@@ -6032,7 +6218,7 @@ __export(update_exports, {
|
|
|
6032
6218
|
});
|
|
6033
6219
|
import * as fs4 from "node:fs";
|
|
6034
6220
|
import * as os3 from "node:os";
|
|
6035
|
-
import * as
|
|
6221
|
+
import * as path5 from "node:path";
|
|
6036
6222
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6037
6223
|
async function runUpdate(opts, deps = {}) {
|
|
6038
6224
|
await Promise.resolve();
|
|
@@ -6072,9 +6258,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6072
6258
|
finalManifestEntries.push(e);
|
|
6073
6259
|
}
|
|
6074
6260
|
for (const { packaged, absSrc } of plan.overwriteSafe) {
|
|
6075
|
-
const absDest =
|
|
6261
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6076
6262
|
if (!opts.dryRun) {
|
|
6077
|
-
fs4.mkdirSync(
|
|
6263
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6078
6264
|
fs4.copyFileSync(absSrc, absDest);
|
|
6079
6265
|
if (opts.verbose) console.log(`updated ${packaged.dest}`);
|
|
6080
6266
|
} else if (opts.verbose) {
|
|
@@ -6087,7 +6273,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6087
6273
|
absSrc,
|
|
6088
6274
|
onDiskContent
|
|
6089
6275
|
} of plan.overwriteHandEdited) {
|
|
6090
|
-
const absDest =
|
|
6276
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6091
6277
|
const newContent = fs4.readFileSync(absSrc);
|
|
6092
6278
|
const showDiff = () => {
|
|
6093
6279
|
console.log(
|
|
@@ -6100,7 +6286,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6100
6286
|
const answer = await promptOverwrite(packaged.dest, opts, showDiff);
|
|
6101
6287
|
if (answer === "overwrite") {
|
|
6102
6288
|
if (!opts.dryRun) {
|
|
6103
|
-
fs4.mkdirSync(
|
|
6289
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6104
6290
|
fs4.copyFileSync(absSrc, absDest);
|
|
6105
6291
|
}
|
|
6106
6292
|
finalManifestEntries.push(packaged);
|
|
@@ -6116,9 +6302,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6116
6302
|
for (const { packaged, absSrc } of plan.newOptIn) {
|
|
6117
6303
|
const answer = await promptOptIn(packaged.dest, opts);
|
|
6118
6304
|
if (answer === "opt-in") {
|
|
6119
|
-
const absDest =
|
|
6305
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6120
6306
|
if (!opts.dryRun) {
|
|
6121
|
-
fs4.mkdirSync(
|
|
6307
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6122
6308
|
fs4.copyFileSync(absSrc, absDest);
|
|
6123
6309
|
}
|
|
6124
6310
|
finalManifestEntries.push(packaged);
|
|
@@ -6130,25 +6316,25 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6130
6316
|
for (const e of plan.removedFromPackage) {
|
|
6131
6317
|
const answer = await promptRemove(e.dest, opts);
|
|
6132
6318
|
if (answer === "remove") {
|
|
6133
|
-
const absDest =
|
|
6319
|
+
const absDest = path5.join(projectDir, ".claude", e.dest);
|
|
6134
6320
|
if (!opts.dryRun && fs4.existsSync(absDest)) {
|
|
6135
6321
|
fs4.rmSync(absDest);
|
|
6136
|
-
const claudeDir =
|
|
6137
|
-
let cur =
|
|
6138
|
-
while (cur !== claudeDir && cur !==
|
|
6139
|
-
if (
|
|
6322
|
+
const claudeDir = path5.join(projectDir, ".claude");
|
|
6323
|
+
let cur = path5.dirname(absDest);
|
|
6324
|
+
while (cur !== claudeDir && cur !== path5.dirname(cur)) {
|
|
6325
|
+
if (path5.dirname(cur) === claudeDir) break;
|
|
6140
6326
|
try {
|
|
6141
6327
|
fs4.rmdirSync(cur);
|
|
6142
6328
|
if (opts.verbose)
|
|
6143
6329
|
console.log(
|
|
6144
|
-
`pruned empty dir ${
|
|
6330
|
+
`pruned empty dir ${path5.relative(claudeDir, cur)}`
|
|
6145
6331
|
);
|
|
6146
|
-
cur =
|
|
6332
|
+
cur = path5.dirname(cur);
|
|
6147
6333
|
} catch (err) {
|
|
6148
6334
|
const code = err.code;
|
|
6149
6335
|
if (code !== "ENOTEMPTY" && code !== "ENOENT") {
|
|
6150
6336
|
console.warn(
|
|
6151
|
-
`codebyplan claude: could not prune empty dir ${
|
|
6337
|
+
`codebyplan claude: could not prune empty dir ${path5.relative(claudeDir, cur)}: ${err.message}`
|
|
6152
6338
|
);
|
|
6153
6339
|
}
|
|
6154
6340
|
break;
|
|
@@ -6160,16 +6346,16 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6160
6346
|
if (opts.verbose) console.log(`kept (untracked) ${e.dest}`);
|
|
6161
6347
|
}
|
|
6162
6348
|
}
|
|
6163
|
-
const hooksJsonPath =
|
|
6349
|
+
const hooksJsonPath = path5.join(templatesDir, "hooks", "hooks.json");
|
|
6164
6350
|
if (fs4.existsSync(hooksJsonPath)) {
|
|
6165
6351
|
const hooksJson = JSON.parse(
|
|
6166
6352
|
fs4.readFileSync(hooksJsonPath, "utf8")
|
|
6167
6353
|
);
|
|
6168
|
-
const settingsPath =
|
|
6354
|
+
const settingsPath = path5.join(projectDir, ".claude", "settings.json");
|
|
6169
6355
|
const existingSettings = fs4.existsSync(settingsPath) ? JSON.parse(fs4.readFileSync(settingsPath, "utf8")) : {};
|
|
6170
6356
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
6171
6357
|
if (!opts.dryRun) {
|
|
6172
|
-
fs4.mkdirSync(
|
|
6358
|
+
fs4.mkdirSync(path5.dirname(settingsPath), { recursive: true });
|
|
6173
6359
|
fs4.writeFileSync(
|
|
6174
6360
|
settingsPath,
|
|
6175
6361
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -6177,6 +6363,15 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6177
6363
|
);
|
|
6178
6364
|
}
|
|
6179
6365
|
}
|
|
6366
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
6367
|
+
projectDir,
|
|
6368
|
+
opts.dryRun
|
|
6369
|
+
);
|
|
6370
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
6371
|
+
console.log(
|
|
6372
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path5.relative(projectDir, path5.join(projectDir, ".gitignore"))}`
|
|
6373
|
+
);
|
|
6374
|
+
}
|
|
6180
6375
|
if (!opts.dryRun) {
|
|
6181
6376
|
const manifest = defaultManifest();
|
|
6182
6377
|
manifest.files = finalManifestEntries.sort(
|
|
@@ -6209,9 +6404,9 @@ function runUpdateUser(opts, deps) {
|
|
|
6209
6404
|
return;
|
|
6210
6405
|
}
|
|
6211
6406
|
try {
|
|
6212
|
-
const userDir = deps.userDir ??
|
|
6213
|
-
const settingsPath =
|
|
6214
|
-
const userBaseSettingsPath =
|
|
6407
|
+
const userDir = deps.userDir ?? path5.join(os3.homedir(), ".claude");
|
|
6408
|
+
const settingsPath = path5.join(userDir, "settings.json");
|
|
6409
|
+
const userBaseSettingsPath = path5.join(
|
|
6215
6410
|
templatesDir,
|
|
6216
6411
|
"settings.user.base.json"
|
|
6217
6412
|
);
|
|
@@ -6273,8 +6468,8 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6273
6468
|
};
|
|
6274
6469
|
for (const pkg of packaged) {
|
|
6275
6470
|
const inManifest = manifestBySrc.get(pkg.src);
|
|
6276
|
-
const absDest =
|
|
6277
|
-
const absSrc =
|
|
6471
|
+
const absDest = path5.join(projectDir, ".claude", pkg.dest);
|
|
6472
|
+
const absSrc = path5.join(templatesDir, pkg.src);
|
|
6278
6473
|
if (!inManifest) {
|
|
6279
6474
|
plan.newOptIn.push({
|
|
6280
6475
|
packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
|
|
@@ -6310,11 +6505,11 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6310
6505
|
return plan;
|
|
6311
6506
|
}
|
|
6312
6507
|
function resolveTemplatesDirFromInstall() {
|
|
6313
|
-
const here =
|
|
6508
|
+
const here = path5.dirname(fileURLToPath2(import.meta.url));
|
|
6314
6509
|
const candidates = [
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6510
|
+
path5.resolve(here, "..", "templates"),
|
|
6511
|
+
path5.resolve(here, "..", "..", "templates"),
|
|
6512
|
+
path5.resolve(here, "..", "..", "..", "templates")
|
|
6318
6513
|
];
|
|
6319
6514
|
for (const c of candidates) {
|
|
6320
6515
|
if (fs4.existsSync(c) && fs4.statSync(c).isDirectory()) {
|
|
@@ -6332,6 +6527,7 @@ var init_update = __esm({
|
|
|
6332
6527
|
"src/cli/claude/update.ts"() {
|
|
6333
6528
|
"use strict";
|
|
6334
6529
|
init_template_walker();
|
|
6530
|
+
init_gitignore_block();
|
|
6335
6531
|
init_hash();
|
|
6336
6532
|
init_manifest();
|
|
6337
6533
|
init_settings_merge();
|
|
@@ -6347,7 +6543,7 @@ __export(uninstall_exports, {
|
|
|
6347
6543
|
});
|
|
6348
6544
|
import * as fs5 from "node:fs";
|
|
6349
6545
|
import * as os4 from "node:os";
|
|
6350
|
-
import * as
|
|
6546
|
+
import * as path6 from "node:path";
|
|
6351
6547
|
async function runUninstall(opts, deps = {}) {
|
|
6352
6548
|
await Promise.resolve();
|
|
6353
6549
|
const scope = opts.scope ?? "project";
|
|
@@ -6376,7 +6572,7 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6376
6572
|
let removed = 0;
|
|
6377
6573
|
let warnings = 0;
|
|
6378
6574
|
for (const entry of manifest.files) {
|
|
6379
|
-
const abs =
|
|
6575
|
+
const abs = path6.join(projectDir, ".claude", entry.dest);
|
|
6380
6576
|
if (!fs5.existsSync(abs)) {
|
|
6381
6577
|
console.warn(
|
|
6382
6578
|
`codebyplan claude uninstall: ${entry.dest} already absent (skipping).`
|
|
@@ -6400,12 +6596,12 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6400
6596
|
if (!opts.dryRun) {
|
|
6401
6597
|
pruneEmptyManagedDirs(projectDir);
|
|
6402
6598
|
}
|
|
6403
|
-
const settingsPath =
|
|
6599
|
+
const settingsPath = path6.join(projectDir, ".claude", "settings.json");
|
|
6404
6600
|
if (fs5.existsSync(settingsPath)) {
|
|
6405
6601
|
const settings = JSON.parse(
|
|
6406
6602
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6407
6603
|
);
|
|
6408
|
-
const baseSettingsPath = templatesDir ?
|
|
6604
|
+
const baseSettingsPath = templatesDir ? path6.join(templatesDir, "settings.project.base.json") : null;
|
|
6409
6605
|
if (baseSettingsPath && fs5.existsSync(baseSettingsPath)) {
|
|
6410
6606
|
const base = JSON.parse(
|
|
6411
6607
|
fs5.readFileSync(baseSettingsPath, "utf8")
|
|
@@ -6426,6 +6622,15 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6426
6622
|
}
|
|
6427
6623
|
}
|
|
6428
6624
|
}
|
|
6625
|
+
const gitignoreAction = await removeManagedGitignoreBlock(
|
|
6626
|
+
projectDir,
|
|
6627
|
+
opts.dryRun
|
|
6628
|
+
);
|
|
6629
|
+
if (opts.verbose) {
|
|
6630
|
+
console.log(
|
|
6631
|
+
gitignoreAction === "removed" ? `${opts.dryRun ? "[dry-run] would remove" : "removed"} managed .gitignore block` : "managed .gitignore block already absent (no change)"
|
|
6632
|
+
);
|
|
6633
|
+
}
|
|
6429
6634
|
if (!opts.dryRun) {
|
|
6430
6635
|
const m = manifestPath(projectDir);
|
|
6431
6636
|
if (fs5.existsSync(m)) fs5.rmSync(m);
|
|
@@ -6454,7 +6659,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6454
6659
|
}
|
|
6455
6660
|
}
|
|
6456
6661
|
try {
|
|
6457
|
-
const userDir = deps.userDir ??
|
|
6662
|
+
const userDir = deps.userDir ?? path6.join(os4.homedir(), ".claude");
|
|
6458
6663
|
const existingManifest = readManifestForScope("user", userDir);
|
|
6459
6664
|
if (!existingManifest) {
|
|
6460
6665
|
console.error(
|
|
@@ -6463,12 +6668,12 @@ function runUninstallUser(opts, deps) {
|
|
|
6463
6668
|
process.exitCode = 1;
|
|
6464
6669
|
return;
|
|
6465
6670
|
}
|
|
6466
|
-
const settingsPath =
|
|
6671
|
+
const settingsPath = path6.join(userDir, "settings.json");
|
|
6467
6672
|
if (fs5.existsSync(settingsPath)) {
|
|
6468
6673
|
const settings = JSON.parse(
|
|
6469
6674
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6470
6675
|
);
|
|
6471
|
-
const userBaseSettingsPath = templatesDir != null ?
|
|
6676
|
+
const userBaseSettingsPath = templatesDir != null ? path6.join(templatesDir, "settings.user.base.json") : null;
|
|
6472
6677
|
if (userBaseSettingsPath && fs5.existsSync(userBaseSettingsPath)) {
|
|
6473
6678
|
const userBase = JSON.parse(
|
|
6474
6679
|
fs5.readFileSync(userBaseSettingsPath, "utf8")
|
|
@@ -6509,7 +6714,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6509
6714
|
function pruneEmptyManagedDirs(projectDir) {
|
|
6510
6715
|
const managedRoots = ["skills", "agents", "hooks", "rules"];
|
|
6511
6716
|
for (const root of managedRoots) {
|
|
6512
|
-
const abs =
|
|
6717
|
+
const abs = path6.join(projectDir, ".claude", root);
|
|
6513
6718
|
if (!fs5.existsSync(abs)) continue;
|
|
6514
6719
|
pruneLeafFirst(abs);
|
|
6515
6720
|
}
|
|
@@ -6520,7 +6725,7 @@ function pruneLeafFirst(dir) {
|
|
|
6520
6725
|
if (!stat2.isDirectory()) return;
|
|
6521
6726
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
6522
6727
|
if (entry.isDirectory()) {
|
|
6523
|
-
pruneLeafFirst(
|
|
6728
|
+
pruneLeafFirst(path6.join(dir, entry.name));
|
|
6524
6729
|
}
|
|
6525
6730
|
}
|
|
6526
6731
|
const remaining = fs5.readdirSync(dir);
|
|
@@ -6532,6 +6737,7 @@ var init_uninstall = __esm({
|
|
|
6532
6737
|
"src/cli/claude/uninstall.ts"() {
|
|
6533
6738
|
"use strict";
|
|
6534
6739
|
init_hash();
|
|
6740
|
+
init_gitignore_block();
|
|
6535
6741
|
init_manifest();
|
|
6536
6742
|
init_settings_merge();
|
|
6537
6743
|
init_install();
|