codebyplan 1.11.2 → 1.13.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 +590 -405
- package/package.json +1 -1
- package/templates/hooks/README.md +1 -13
- 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/hooks/cbp-test-coverage-gate.sh +8 -0
- package/templates/hooks/cbp-test-hooks.sh +0 -42
- package/templates/hooks/hooks.json +0 -9
- 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-checkpoint-start/SKILL.md +2 -2
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +17 -1
- package/templates/skills/cbp-session-start/SKILL.md +28 -3
- package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/SKILL.md +1 -1
- package/templates/skills/cbp-setup-eslint/SKILL.md +199 -0
- package/templates/skills/cbp-setup-eslint/reference/base.md +82 -0
- package/templates/skills/cbp-setup-eslint/reference/cli.md +56 -0
- package/templates/skills/cbp-setup-eslint/reference/e2e.md +68 -0
- package/templates/skills/cbp-setup-eslint/reference/jest.md +59 -0
- package/templates/skills/cbp-setup-eslint/reference/nestjs.md +69 -0
- package/templates/skills/cbp-setup-eslint/reference/nextjs.md +63 -0
- package/templates/skills/cbp-setup-eslint/reference/node.md +74 -0
- package/templates/skills/cbp-setup-eslint/reference/react-native.md +60 -0
- package/templates/skills/cbp-setup-eslint/reference/react.md +82 -0
- package/templates/skills/cbp-setup-eslint/reference/tailwind.md +64 -0
- package/templates/skills/cbp-setup-eslint/reference/testing-react.md +57 -0
- package/templates/skills/cbp-setup-eslint/reference/vitest.md +62 -0
- 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-complete/SKILL.md +1 -3
- package/templates/skills/cbp-task-start/SKILL.md +5 -3
- package/templates/hooks/cbp-mcp-worktree-inject.sh +0 -76
- /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/maestro.md +0 -0
- /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/playwright.md +0 -0
- /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/tauri.md +0 -0
- /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/vscode.md +0 -0
- /package/templates/skills/{cbp-e2e-setup → cbp-setup-e2e}/reference/xcuitest.md +0 -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.13.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"
|
|
@@ -840,28 +978,36 @@ var init_browser = __esm({
|
|
|
840
978
|
});
|
|
841
979
|
|
|
842
980
|
// src/oauth/device-flow.ts
|
|
843
|
-
async function initiateDeviceCode(clientId) {
|
|
981
|
+
async function initiateDeviceCode(clientId, scope, resource = "https://mcp.codebyplan.com/") {
|
|
982
|
+
const body = { client_id: clientId, resource };
|
|
983
|
+
if (scope !== void 0) {
|
|
984
|
+
body.scope = scope;
|
|
985
|
+
}
|
|
844
986
|
const res = await fetch(deviceEndpoint(), {
|
|
845
987
|
method: "POST",
|
|
846
988
|
headers: { "Content-Type": "application/json" },
|
|
847
|
-
body: JSON.stringify(
|
|
989
|
+
body: JSON.stringify(body),
|
|
848
990
|
signal: AbortSignal.timeout(1e4)
|
|
849
991
|
});
|
|
850
992
|
if (!res.ok) {
|
|
851
|
-
const
|
|
852
|
-
if (
|
|
993
|
+
const body2 = await res.json().catch(() => ({}));
|
|
994
|
+
if (body2.error === "invalid_client") {
|
|
853
995
|
throw new OAuthInvalidClientError();
|
|
854
996
|
}
|
|
855
|
-
const msg =
|
|
997
|
+
const msg = body2.error_description ?? body2.error ?? `HTTP ${res.status}`;
|
|
856
998
|
throw new Error(`Failed to initiate device flow: ${msg}`);
|
|
857
999
|
}
|
|
858
|
-
|
|
1000
|
+
const dcResponse = await res.json();
|
|
1001
|
+
return { ...dcResponse, resource };
|
|
859
1002
|
}
|
|
860
|
-
async function pollOnce(deviceCode, clientId) {
|
|
1003
|
+
async function pollOnce(deviceCode, clientId, resource) {
|
|
861
1004
|
const params = new URLSearchParams();
|
|
862
1005
|
params.set("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
|
863
1006
|
params.set("device_code", deviceCode);
|
|
864
1007
|
params.set("client_id", clientId);
|
|
1008
|
+
if (resource !== void 0) {
|
|
1009
|
+
params.set("resource", resource);
|
|
1010
|
+
}
|
|
865
1011
|
const res = await fetch(tokenEndpoint(), {
|
|
866
1012
|
method: "POST",
|
|
867
1013
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
@@ -880,15 +1026,19 @@ async function pollOnce(deviceCode, clientId) {
|
|
|
880
1026
|
return { kind: "error", message: description };
|
|
881
1027
|
}
|
|
882
1028
|
async function pollUntilSettled(opts) {
|
|
883
|
-
const
|
|
1029
|
+
const sleep2 = opts.sleep ?? defaultSleep;
|
|
884
1030
|
const now = opts.now ?? Date.now;
|
|
885
1031
|
const startMs = now();
|
|
886
1032
|
const expiresAtMs = startMs + opts.expiresInSec * 1e3;
|
|
887
1033
|
while (now() < expiresAtMs) {
|
|
888
|
-
const outcome = await pollOnce(
|
|
1034
|
+
const outcome = await pollOnce(
|
|
1035
|
+
opts.deviceCode,
|
|
1036
|
+
opts.clientId,
|
|
1037
|
+
opts.resource
|
|
1038
|
+
);
|
|
889
1039
|
if (outcome.kind !== "pending") return outcome;
|
|
890
1040
|
if (opts.onTick) opts.onTick(Math.floor((now() - startMs) / 1e3));
|
|
891
|
-
await
|
|
1041
|
+
await sleep2(opts.intervalSec * 1e3);
|
|
892
1042
|
}
|
|
893
1043
|
return { kind: "expired" };
|
|
894
1044
|
}
|
|
@@ -988,10 +1138,11 @@ async function fetchEmail(accessToken) {
|
|
|
988
1138
|
return null;
|
|
989
1139
|
}
|
|
990
1140
|
}
|
|
991
|
-
async function runLogin() {
|
|
1141
|
+
async function runLogin(options = {}) {
|
|
1142
|
+
const scope = options.admin ? "mcp:read mcp:write mcp:admin" : "mcp:read mcp:write";
|
|
992
1143
|
console.log("\n CodeByPlan login\n");
|
|
993
1144
|
const { clientId, result: dc } = await ensureClientFor(
|
|
994
|
-
(id) => initiateDeviceCode(id)
|
|
1145
|
+
(id) => initiateDeviceCode(id, scope)
|
|
995
1146
|
);
|
|
996
1147
|
const verificationUri = dc.verification_uri_complete ?? dc.verification_uri;
|
|
997
1148
|
console.log(` Visit: ${verificationUri}`);
|
|
@@ -1005,7 +1156,8 @@ async function runLogin() {
|
|
|
1005
1156
|
deviceCode: dc.device_code,
|
|
1006
1157
|
clientId,
|
|
1007
1158
|
intervalSec: dc.interval,
|
|
1008
|
-
expiresInSec: dc.expires_in
|
|
1159
|
+
expiresInSec: dc.expires_in,
|
|
1160
|
+
resource: dc.resource
|
|
1009
1161
|
});
|
|
1010
1162
|
if (outcome.kind === "denied") {
|
|
1011
1163
|
console.log(" Denied. Authorization request was rejected.\n");
|
|
@@ -1057,21 +1209,21 @@ var setup_exports = {};
|
|
|
1057
1209
|
__export(setup_exports, {
|
|
1058
1210
|
runSetup: () => runSetup
|
|
1059
1211
|
});
|
|
1060
|
-
import { mkdir as mkdir4, readFile as
|
|
1212
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
|
|
1061
1213
|
import { homedir as homedir2 } from "node:os";
|
|
1062
|
-
import { join as
|
|
1214
|
+
import { join as join6 } from "node:path";
|
|
1063
1215
|
import { stdin, stdout as stdout2 } from "node:process";
|
|
1064
1216
|
import { createInterface } from "node:readline/promises";
|
|
1065
1217
|
function getConfigPath(scope) {
|
|
1066
|
-
return scope === "user" ?
|
|
1218
|
+
return scope === "user" ? join6(homedir2(), ".claude.json") : join6(process.cwd(), ".mcp.json");
|
|
1067
1219
|
}
|
|
1068
1220
|
function legacyMcpUrl() {
|
|
1069
1221
|
const baseUrl2 = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
1070
1222
|
return `${baseUrl2.replace(/\/$/, "")}/mcp`;
|
|
1071
1223
|
}
|
|
1072
|
-
async function readConfig(
|
|
1224
|
+
async function readConfig(path7) {
|
|
1073
1225
|
try {
|
|
1074
|
-
const raw = await
|
|
1226
|
+
const raw = await readFile6(path7, "utf-8");
|
|
1075
1227
|
const parsed = JSON.parse(raw);
|
|
1076
1228
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1077
1229
|
return parsed;
|
|
@@ -1094,7 +1246,7 @@ async function writeMcpConfig(scope, auth) {
|
|
|
1094
1246
|
config.mcpServers = {};
|
|
1095
1247
|
}
|
|
1096
1248
|
config.mcpServers.codebyplan = buildMcpEntry(auth);
|
|
1097
|
-
await
|
|
1249
|
+
await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1098
1250
|
return configPath;
|
|
1099
1251
|
}
|
|
1100
1252
|
async function fetchRepos(auth) {
|
|
@@ -1149,7 +1301,7 @@ async function chooseAuthMode(rl) {
|
|
|
1149
1301
|
return { kind: "legacy", apiKey };
|
|
1150
1302
|
}
|
|
1151
1303
|
async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
1152
|
-
const codebyplanDir =
|
|
1304
|
+
const codebyplanDir = join6(projectPath, ".codebyplan");
|
|
1153
1305
|
await mkdir4(codebyplanDir, { recursive: true });
|
|
1154
1306
|
const repoJson = {
|
|
1155
1307
|
repo_id: selectedRepo.id
|
|
@@ -1161,13 +1313,13 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1161
1313
|
if (typeof repoAny.project_id === "string") {
|
|
1162
1314
|
repoJson.project_id = repoAny.project_id;
|
|
1163
1315
|
}
|
|
1164
|
-
await
|
|
1165
|
-
|
|
1316
|
+
await writeFile6(
|
|
1317
|
+
join6(codebyplanDir, "repo.json"),
|
|
1166
1318
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
1167
1319
|
"utf-8"
|
|
1168
1320
|
);
|
|
1169
|
-
await
|
|
1170
|
-
|
|
1321
|
+
await writeFile6(
|
|
1322
|
+
join6(codebyplanDir, "server.json"),
|
|
1171
1323
|
JSON.stringify(
|
|
1172
1324
|
{
|
|
1173
1325
|
server_port: null,
|
|
@@ -1180,35 +1332,40 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1180
1332
|
) + "\n",
|
|
1181
1333
|
"utf-8"
|
|
1182
1334
|
);
|
|
1183
|
-
await
|
|
1184
|
-
|
|
1335
|
+
await writeFile6(
|
|
1336
|
+
join6(codebyplanDir, "git.json"),
|
|
1185
1337
|
JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
|
|
1186
1338
|
"utf-8"
|
|
1187
1339
|
);
|
|
1188
|
-
await
|
|
1189
|
-
|
|
1340
|
+
await writeFile6(
|
|
1341
|
+
join6(codebyplanDir, "shipment.json"),
|
|
1190
1342
|
JSON.stringify({ shipment: null }, null, 2) + "\n",
|
|
1191
1343
|
"utf-8"
|
|
1192
1344
|
);
|
|
1193
|
-
await
|
|
1194
|
-
|
|
1345
|
+
await writeFile6(
|
|
1346
|
+
join6(codebyplanDir, "vendor.json"),
|
|
1347
|
+
JSON.stringify({}, null, 2) + "\n",
|
|
1348
|
+
"utf-8"
|
|
1349
|
+
);
|
|
1350
|
+
await writeFile6(
|
|
1351
|
+
join6(codebyplanDir, "e2e.json"),
|
|
1195
1352
|
JSON.stringify({}, null, 2) + "\n",
|
|
1196
1353
|
"utf-8"
|
|
1197
1354
|
);
|
|
1198
|
-
await
|
|
1199
|
-
|
|
1355
|
+
await writeFile6(
|
|
1356
|
+
join6(codebyplanDir, "eslint.json"),
|
|
1200
1357
|
JSON.stringify({}, null, 2) + "\n",
|
|
1201
1358
|
"utf-8"
|
|
1202
1359
|
);
|
|
1203
|
-
const statuslinePath =
|
|
1360
|
+
const statuslinePath = join6(codebyplanDir, "statusline.json");
|
|
1204
1361
|
let statuslineExists = false;
|
|
1205
1362
|
try {
|
|
1206
|
-
await
|
|
1363
|
+
await readFile6(statuslinePath, "utf-8");
|
|
1207
1364
|
statuslineExists = true;
|
|
1208
1365
|
} catch {
|
|
1209
1366
|
}
|
|
1210
1367
|
if (!statuslineExists) {
|
|
1211
|
-
await
|
|
1368
|
+
await writeFile6(
|
|
1212
1369
|
statuslinePath,
|
|
1213
1370
|
JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
|
|
1214
1371
|
"utf-8"
|
|
@@ -1217,36 +1374,12 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1217
1374
|
await writeLocalConfig(projectPath, { device_id: deviceId });
|
|
1218
1375
|
console.log(` Created ${codebyplanDir}/`);
|
|
1219
1376
|
console.log(
|
|
1220
|
-
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, statusline.json`
|
|
1377
|
+
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, eslint.json, statusline.json`
|
|
1221
1378
|
);
|
|
1222
1379
|
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
|
-
);
|
|
1380
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
|
|
1381
|
+
if (gitignoreAction !== "unchanged") {
|
|
1382
|
+
console.log(" Updated .gitignore (codebyplan managed block)");
|
|
1250
1383
|
}
|
|
1251
1384
|
}
|
|
1252
1385
|
async function runSetup() {
|
|
@@ -1377,6 +1510,7 @@ async function runSetup() {
|
|
|
1377
1510
|
var init_setup = __esm({
|
|
1378
1511
|
"src/cli/setup.ts"() {
|
|
1379
1512
|
"use strict";
|
|
1513
|
+
init_gitignore_block();
|
|
1380
1514
|
init_local_config();
|
|
1381
1515
|
init_statusline_config();
|
|
1382
1516
|
init_resolve_worktree();
|
|
@@ -1387,21 +1521,21 @@ var init_setup = __esm({
|
|
|
1387
1521
|
});
|
|
1388
1522
|
|
|
1389
1523
|
// src/lib/flags.ts
|
|
1390
|
-
import { readFile as
|
|
1391
|
-
import { join as
|
|
1524
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
1525
|
+
import { join as join7, resolve } from "node:path";
|
|
1392
1526
|
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
1393
1527
|
let cursor = resolve(startDir);
|
|
1394
1528
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1395
|
-
const sentinelPath2 =
|
|
1529
|
+
const sentinelPath2 = join7(cursor, ".codebyplan", "repo.json");
|
|
1396
1530
|
try {
|
|
1397
|
-
const raw = await
|
|
1531
|
+
const raw = await readFile7(sentinelPath2, "utf-8");
|
|
1398
1532
|
const parsed = JSON.parse(raw);
|
|
1399
1533
|
return { path: sentinelPath2, contents: parsed };
|
|
1400
1534
|
} catch {
|
|
1401
1535
|
}
|
|
1402
|
-
const legacyPath =
|
|
1536
|
+
const legacyPath = join7(cursor, ".codebyplan.json");
|
|
1403
1537
|
try {
|
|
1404
|
-
const raw = await
|
|
1538
|
+
const raw = await readFile7(legacyPath, "utf-8");
|
|
1405
1539
|
const parsed = JSON.parse(raw);
|
|
1406
1540
|
return { path: legacyPath, contents: parsed };
|
|
1407
1541
|
} catch {
|
|
@@ -1628,15 +1762,15 @@ var upgrade_auth_exports = {};
|
|
|
1628
1762
|
__export(upgrade_auth_exports, {
|
|
1629
1763
|
runUpgradeAuth: () => runUpgradeAuth
|
|
1630
1764
|
});
|
|
1631
|
-
import { readFile as
|
|
1765
|
+
import { readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
1632
1766
|
import { homedir as homedir3 } from "node:os";
|
|
1633
|
-
import { join as
|
|
1767
|
+
import { join as join8 } from "node:path";
|
|
1634
1768
|
function configPaths() {
|
|
1635
|
-
return [
|
|
1769
|
+
return [join8(homedir3(), ".claude.json"), join8(process.cwd(), ".mcp.json")];
|
|
1636
1770
|
}
|
|
1637
|
-
async function readConfig2(
|
|
1771
|
+
async function readConfig2(path7) {
|
|
1638
1772
|
try {
|
|
1639
|
-
const raw = await
|
|
1773
|
+
const raw = await readFile8(path7, "utf-8");
|
|
1640
1774
|
const parsed = JSON.parse(raw);
|
|
1641
1775
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1642
1776
|
return parsed;
|
|
@@ -1650,14 +1784,14 @@ function entryHasLegacyApiKey(entry) {
|
|
|
1650
1784
|
if (!entry || !entry.headers) return false;
|
|
1651
1785
|
return "x-api-key" in entry.headers;
|
|
1652
1786
|
}
|
|
1653
|
-
async function rewriteConfig(
|
|
1787
|
+
async function rewriteConfig(path7, config, newUrl) {
|
|
1654
1788
|
const servers = config.mcpServers;
|
|
1655
1789
|
if (!servers) return false;
|
|
1656
1790
|
const entry = servers.codebyplan;
|
|
1657
1791
|
if (!entry) return false;
|
|
1658
1792
|
if (!entryHasLegacyApiKey(entry) && entry.url === newUrl) return false;
|
|
1659
1793
|
servers.codebyplan = { url: newUrl };
|
|
1660
|
-
await
|
|
1794
|
+
await writeFile7(path7, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1661
1795
|
return true;
|
|
1662
1796
|
}
|
|
1663
1797
|
async function runUpgradeAuth() {
|
|
@@ -1665,12 +1799,12 @@ async function runUpgradeAuth() {
|
|
|
1665
1799
|
await runLogin();
|
|
1666
1800
|
const newUrl = mcpEndpoint();
|
|
1667
1801
|
let migrated = 0;
|
|
1668
|
-
for (const
|
|
1669
|
-
const config = await readConfig2(
|
|
1802
|
+
for (const path7 of configPaths()) {
|
|
1803
|
+
const config = await readConfig2(path7);
|
|
1670
1804
|
if (!config) continue;
|
|
1671
|
-
const changed = await rewriteConfig(
|
|
1805
|
+
const changed = await rewriteConfig(path7, config, newUrl);
|
|
1672
1806
|
if (changed) {
|
|
1673
|
-
console.log(` Updated ${
|
|
1807
|
+
console.log(` Updated ${path7}`);
|
|
1674
1808
|
migrated++;
|
|
1675
1809
|
}
|
|
1676
1810
|
}
|
|
@@ -1738,8 +1872,8 @@ var init_confirm = __esm({
|
|
|
1738
1872
|
});
|
|
1739
1873
|
|
|
1740
1874
|
// src/lib/tech-detect.ts
|
|
1741
|
-
import { readFile as
|
|
1742
|
-
import { join as
|
|
1875
|
+
import { readFile as readFile9, access, readdir } from "node:fs/promises";
|
|
1876
|
+
import { join as join9, relative } from "node:path";
|
|
1743
1877
|
async function fileExists(filePath) {
|
|
1744
1878
|
try {
|
|
1745
1879
|
await access(filePath);
|
|
@@ -1752,8 +1886,8 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1752
1886
|
const apps = [];
|
|
1753
1887
|
const patterns = [];
|
|
1754
1888
|
try {
|
|
1755
|
-
const raw = await
|
|
1756
|
-
|
|
1889
|
+
const raw = await readFile9(
|
|
1890
|
+
join9(projectPath, "pnpm-workspace.yaml"),
|
|
1757
1891
|
"utf-8"
|
|
1758
1892
|
);
|
|
1759
1893
|
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
@@ -1767,7 +1901,7 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1767
1901
|
}
|
|
1768
1902
|
if (patterns.length === 0) {
|
|
1769
1903
|
try {
|
|
1770
|
-
const raw = await
|
|
1904
|
+
const raw = await readFile9(join9(projectPath, "package.json"), "utf-8");
|
|
1771
1905
|
const pkg = JSON.parse(raw);
|
|
1772
1906
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1773
1907
|
if (ws) patterns.push(...ws);
|
|
@@ -1777,14 +1911,14 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1777
1911
|
for (const pattern of patterns) {
|
|
1778
1912
|
if (pattern.endsWith("/*")) {
|
|
1779
1913
|
const dir = pattern.slice(0, -2);
|
|
1780
|
-
const absDir =
|
|
1914
|
+
const absDir = join9(projectPath, dir);
|
|
1781
1915
|
try {
|
|
1782
1916
|
const entries = await readdir(absDir, { withFileTypes: true });
|
|
1783
1917
|
for (const entry of entries) {
|
|
1784
1918
|
if (entry.isDirectory()) {
|
|
1785
|
-
const relPath =
|
|
1786
|
-
const absPath =
|
|
1787
|
-
if (await fileExists(
|
|
1919
|
+
const relPath = join9(dir, entry.name);
|
|
1920
|
+
const absPath = join9(absDir, entry.name);
|
|
1921
|
+
if (await fileExists(join9(absPath, "package.json"))) {
|
|
1788
1922
|
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1789
1923
|
}
|
|
1790
1924
|
}
|
|
@@ -1803,7 +1937,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1803
1937
|
const name = entry.name;
|
|
1804
1938
|
if (entry.isDirectory()) {
|
|
1805
1939
|
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1806
|
-
if (await hasJsxFile(
|
|
1940
|
+
if (await hasJsxFile(join9(dir, name), depth + 1)) return true;
|
|
1807
1941
|
} else if (entry.isFile()) {
|
|
1808
1942
|
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1809
1943
|
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
@@ -1822,7 +1956,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1822
1956
|
async function detectCapabilities(dirPath, pkgJson) {
|
|
1823
1957
|
const caps = /* @__PURE__ */ new Set();
|
|
1824
1958
|
for (const sub of JSX_SCAN_DIRS) {
|
|
1825
|
-
if (await hasJsxFile(
|
|
1959
|
+
if (await hasJsxFile(join9(dirPath, sub))) {
|
|
1826
1960
|
caps.add("jsx");
|
|
1827
1961
|
break;
|
|
1828
1962
|
}
|
|
@@ -1844,7 +1978,7 @@ async function detectCapabilities(dirPath, pkgJson) {
|
|
|
1844
1978
|
}
|
|
1845
1979
|
}
|
|
1846
1980
|
}
|
|
1847
|
-
if (!caps.has("node-server") && await fileExists(
|
|
1981
|
+
if (!caps.has("node-server") && await fileExists(join9(dirPath, "src", "main.ts"))) {
|
|
1848
1982
|
caps.add("node-server");
|
|
1849
1983
|
}
|
|
1850
1984
|
if (pkgJson && pkgJson.bin) {
|
|
@@ -1860,7 +1994,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1860
1994
|
const seen = /* @__PURE__ */ new Map();
|
|
1861
1995
|
let pkgJson = null;
|
|
1862
1996
|
try {
|
|
1863
|
-
const raw = await
|
|
1997
|
+
const raw = await readFile9(join9(dirPath, "package.json"), "utf-8");
|
|
1864
1998
|
pkgJson = JSON.parse(raw);
|
|
1865
1999
|
const allDeps = {
|
|
1866
2000
|
...pkgJson.dependencies ?? {},
|
|
@@ -1892,7 +2026,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1892
2026
|
}
|
|
1893
2027
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1894
2028
|
const key = rule.name.toLowerCase();
|
|
1895
|
-
if (!seen.has(key) && await fileExists(
|
|
2029
|
+
if (!seen.has(key) && await fileExists(join9(dirPath, file))) {
|
|
1896
2030
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1897
2031
|
}
|
|
1898
2032
|
}
|
|
@@ -2070,7 +2204,7 @@ function categorizeDependency(depName) {
|
|
|
2070
2204
|
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
2071
2205
|
if (depth > 4) return [];
|
|
2072
2206
|
const results = [];
|
|
2073
|
-
const pkgPath =
|
|
2207
|
+
const pkgPath = join9(dir, "package.json");
|
|
2074
2208
|
if (await fileExists(pkgPath)) {
|
|
2075
2209
|
results.push(pkgPath);
|
|
2076
2210
|
}
|
|
@@ -2079,7 +2213,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
|
2079
2213
|
for (const entry of entries) {
|
|
2080
2214
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
2081
2215
|
const subResults = await findPackageJsonFiles(
|
|
2082
|
-
|
|
2216
|
+
join9(dir, entry.name),
|
|
2083
2217
|
projectPath,
|
|
2084
2218
|
depth + 1
|
|
2085
2219
|
);
|
|
@@ -2094,7 +2228,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
2094
2228
|
const dependencies = [];
|
|
2095
2229
|
for (const pkgPath of packageJsonPaths) {
|
|
2096
2230
|
try {
|
|
2097
|
-
const raw = await
|
|
2231
|
+
const raw = await readFile9(pkgPath, "utf-8");
|
|
2098
2232
|
const pkg = JSON.parse(raw);
|
|
2099
2233
|
const sourcePath = relative(projectPath, pkgPath);
|
|
2100
2234
|
const depSections = [
|
|
@@ -2718,8 +2852,8 @@ __export(eslint_exports, {
|
|
|
2718
2852
|
eslintInit: () => eslintInit,
|
|
2719
2853
|
runEslint: () => runEslint
|
|
2720
2854
|
});
|
|
2721
|
-
import { readFile as
|
|
2722
|
-
import { join as
|
|
2855
|
+
import { readFile as readFile10, writeFile as writeFile8, access as access2, readdir as readdir2 } from "node:fs/promises";
|
|
2856
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
2723
2857
|
async function fileExists2(filePath) {
|
|
2724
2858
|
try {
|
|
2725
2859
|
await access2(filePath);
|
|
@@ -2730,7 +2864,7 @@ async function fileExists2(filePath) {
|
|
|
2730
2864
|
}
|
|
2731
2865
|
async function autoDetectIgnorePatterns(absPath) {
|
|
2732
2866
|
const patterns = [];
|
|
2733
|
-
if (await fileExists2(
|
|
2867
|
+
if (await fileExists2(join10(absPath, "esbuild.js"))) {
|
|
2734
2868
|
patterns.push("esbuild.js");
|
|
2735
2869
|
}
|
|
2736
2870
|
let entries = [];
|
|
@@ -2750,19 +2884,19 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2750
2884
|
}
|
|
2751
2885
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2752
2886
|
const candidate = `vitest.config.${ext}`;
|
|
2753
|
-
if (await fileExists2(
|
|
2887
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2754
2888
|
patterns.push(candidate);
|
|
2755
2889
|
break;
|
|
2756
2890
|
}
|
|
2757
2891
|
}
|
|
2758
2892
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2759
2893
|
const candidate = `vite.config.${ext}`;
|
|
2760
|
-
if (await fileExists2(
|
|
2894
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2761
2895
|
patterns.push(candidate);
|
|
2762
2896
|
break;
|
|
2763
2897
|
}
|
|
2764
2898
|
}
|
|
2765
|
-
if (await fileExists2(
|
|
2899
|
+
if (await fileExists2(join10(absPath, "tauri.conf.json"))) {
|
|
2766
2900
|
patterns.push("src-tauri/**");
|
|
2767
2901
|
patterns.push("**/*.d.ts");
|
|
2768
2902
|
}
|
|
@@ -2770,14 +2904,14 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2770
2904
|
}
|
|
2771
2905
|
function detectPackageManager(projectPath) {
|
|
2772
2906
|
return (async () => {
|
|
2773
|
-
if (await fileExists2(
|
|
2774
|
-
if (await fileExists2(
|
|
2907
|
+
if (await fileExists2(join10(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2908
|
+
if (await fileExists2(join10(projectPath, "yarn.lock"))) return "yarn";
|
|
2775
2909
|
return "npm";
|
|
2776
2910
|
})();
|
|
2777
2911
|
}
|
|
2778
2912
|
async function getInstalledDeps(pkgJsonPath) {
|
|
2779
2913
|
try {
|
|
2780
|
-
const raw = await
|
|
2914
|
+
const raw = await readFile10(pkgJsonPath, "utf-8");
|
|
2781
2915
|
const pkg = JSON.parse(raw);
|
|
2782
2916
|
const all = /* @__PURE__ */ new Set();
|
|
2783
2917
|
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
@@ -2890,7 +3024,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2890
3024
|
ignorePatterns: detectedIgnores
|
|
2891
3025
|
});
|
|
2892
3026
|
const hash = hashConfig(content);
|
|
2893
|
-
const configPath =
|
|
3027
|
+
const configPath = join10(target.absPath, "eslint.config.mjs");
|
|
2894
3028
|
configsToWrite.push({
|
|
2895
3029
|
target,
|
|
2896
3030
|
presets,
|
|
@@ -2912,11 +3046,11 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2912
3046
|
return;
|
|
2913
3047
|
}
|
|
2914
3048
|
const pm = await detectPackageManager(projectPath);
|
|
2915
|
-
const rootPkgJsonPath =
|
|
3049
|
+
const rootPkgJsonPath = join10(projectPath, "package.json");
|
|
2916
3050
|
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2917
3051
|
if (isMonorepo) {
|
|
2918
3052
|
for (const { target } of configsToWrite) {
|
|
2919
|
-
const appPkgJson =
|
|
3053
|
+
const appPkgJson = join10(target.absPath, "package.json");
|
|
2920
3054
|
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2921
3055
|
for (const dep of appDeps) {
|
|
2922
3056
|
installed.add(dep);
|
|
@@ -2968,7 +3102,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2968
3102
|
} of configsToWrite) {
|
|
2969
3103
|
if (await fileExists2(configPath)) {
|
|
2970
3104
|
try {
|
|
2971
|
-
const existing = await
|
|
3105
|
+
const existing = await readFile10(configPath, "utf-8");
|
|
2972
3106
|
const existingHash = hashConfig(existing);
|
|
2973
3107
|
if (existingHash === hash) {
|
|
2974
3108
|
console.log(
|
|
@@ -2988,7 +3122,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2988
3122
|
}
|
|
2989
3123
|
}
|
|
2990
3124
|
try {
|
|
2991
|
-
await
|
|
3125
|
+
await writeFile8(configPath, content, "utf-8");
|
|
2992
3126
|
} catch (err) {
|
|
2993
3127
|
console.error(
|
|
2994
3128
|
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3292,12 +3426,48 @@ var init_sync_approvals = __esm({
|
|
|
3292
3426
|
// src/cli/round.ts
|
|
3293
3427
|
var round_exports = {};
|
|
3294
3428
|
__export(round_exports, {
|
|
3429
|
+
RETRY_DELAY_MS: () => RETRY_DELAY_MS,
|
|
3430
|
+
fetchRoundsWithRetry: () => fetchRoundsWithRetry,
|
|
3431
|
+
isTransientMcpError: () => isTransientMcpError,
|
|
3295
3432
|
runRoundCommand: () => runRoundCommand,
|
|
3296
|
-
runRoundSyncApprovals: () => runRoundSyncApprovals
|
|
3433
|
+
runRoundSyncApprovals: () => runRoundSyncApprovals,
|
|
3434
|
+
setRetryDelayMs: () => setRetryDelayMs
|
|
3297
3435
|
});
|
|
3298
3436
|
import { access as access3 } from "node:fs/promises";
|
|
3299
|
-
import { join as
|
|
3437
|
+
import { join as join11 } from "node:path";
|
|
3300
3438
|
import { execSync as execSync2 } from "node:child_process";
|
|
3439
|
+
function setRetryDelayMs(ms) {
|
|
3440
|
+
RETRY_DELAY_MS = ms;
|
|
3441
|
+
}
|
|
3442
|
+
function sleep(ms) {
|
|
3443
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3444
|
+
}
|
|
3445
|
+
function isTransientMcpError(err) {
|
|
3446
|
+
if (!(err instanceof McpError)) return false;
|
|
3447
|
+
if (err.status !== void 0) {
|
|
3448
|
+
return err.status >= 500 && err.status <= 599;
|
|
3449
|
+
}
|
|
3450
|
+
return err.message.toLowerCase().includes("network error");
|
|
3451
|
+
}
|
|
3452
|
+
async function fetchRoundsWithRetry(taskId, options = {}) {
|
|
3453
|
+
const maxRetries = options.retries ?? 2;
|
|
3454
|
+
const delayMs = options.delayMs ?? RETRY_DELAY_MS;
|
|
3455
|
+
let lastErr;
|
|
3456
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3457
|
+
try {
|
|
3458
|
+
return await mcpCall("get_rounds", { task_id: taskId });
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
if (!isTransientMcpError(err)) {
|
|
3461
|
+
throw err;
|
|
3462
|
+
}
|
|
3463
|
+
lastErr = err;
|
|
3464
|
+
if (attempt < maxRetries) {
|
|
3465
|
+
await sleep(delayMs);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
throw lastErr;
|
|
3470
|
+
}
|
|
3301
3471
|
async function runRoundCommand(args) {
|
|
3302
3472
|
const subcommand = args[0];
|
|
3303
3473
|
if (subcommand === "sync-approvals") {
|
|
@@ -3321,7 +3491,7 @@ Run 'codebyplan round help' for usage.
|
|
|
3321
3491
|
}
|
|
3322
3492
|
function printRoundHelp() {
|
|
3323
3493
|
process.stdout.write(
|
|
3324
|
-
"\n codebyplan round <subcommand>\n\n Subcommands:\n sync-approvals Sync git diff and approvals with round/task state\n\n sync-approvals flags:\n --round-id <uuid> Round UUID (required)\n --task-id <uuid> Task UUID (required)\n --
|
|
3494
|
+
"\n codebyplan round <subcommand>\n\n Subcommands:\n sync-approvals Sync git diff and approvals with round/task state\n\n sync-approvals flags:\n --round-id <uuid> Round UUID (required)\n --task-id <uuid> Task UUID (required)\n --dry-run Print merged payload to stdout without writing\n\n"
|
|
3325
3495
|
);
|
|
3326
3496
|
}
|
|
3327
3497
|
function parseFlagsFromArgs(args) {
|
|
@@ -3359,95 +3529,95 @@ async function runRoundSyncApprovals(args) {
|
|
|
3359
3529
|
);
|
|
3360
3530
|
process.exit(1);
|
|
3361
3531
|
}
|
|
3532
|
+
let skipReason = null;
|
|
3362
3533
|
let stdoutPayload = null;
|
|
3363
3534
|
try {
|
|
3364
|
-
let callerWorktreeId = flags["worktree-id"];
|
|
3365
|
-
if (!callerWorktreeId) {
|
|
3366
|
-
callerWorktreeId = await autoResolveWorktreeId();
|
|
3367
|
-
}
|
|
3368
3535
|
const found = await findCodebyplanConfig(process.cwd());
|
|
3369
3536
|
const repoRoot = found ? (
|
|
3370
3537
|
// Walk up to the directory containing .codebyplan/ or .codebyplan.json
|
|
3371
3538
|
found.path.replace(/\/.codebyplan(\.json|\/repo\.json)$/, "")
|
|
3372
3539
|
) : 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 = "";
|
|
3383
|
-
try {
|
|
3384
|
-
gitStatusOutput = execSync2("git status --short --porcelain -z", {
|
|
3385
|
-
cwd: repoRoot,
|
|
3386
|
-
encoding: "utf-8"
|
|
3387
|
-
});
|
|
3388
|
-
} catch {
|
|
3389
|
-
process.stderr.write(
|
|
3390
|
-
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3391
|
-
);
|
|
3392
|
-
}
|
|
3393
|
-
const hookPath = join10(
|
|
3394
|
-
repoRoot,
|
|
3395
|
-
".claude",
|
|
3396
|
-
"hooks",
|
|
3397
|
-
"lint-format-on-edit.sh"
|
|
3398
|
-
);
|
|
3399
|
-
let lintFormatHookExists = false;
|
|
3540
|
+
let rounds;
|
|
3400
3541
|
try {
|
|
3401
|
-
await
|
|
3402
|
-
|
|
3403
|
-
|
|
3542
|
+
rounds = await fetchRoundsWithRetry(taskId);
|
|
3543
|
+
} catch (err) {
|
|
3544
|
+
if (isTransientMcpError(err)) {
|
|
3545
|
+
const reason = err instanceof McpError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3546
|
+
skipReason = reason;
|
|
3547
|
+
rounds = [];
|
|
3548
|
+
} else {
|
|
3549
|
+
throw err;
|
|
3550
|
+
}
|
|
3404
3551
|
}
|
|
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;
|
|
3552
|
+
if (!skipReason) {
|
|
3553
|
+
const currentRound = rounds.find((r) => r.id === roundId);
|
|
3554
|
+
if (!currentRound) {
|
|
3555
|
+
throw new Error(`Round ${roundId} not found for task ${taskId}`);
|
|
3430
3556
|
}
|
|
3431
|
-
await
|
|
3432
|
-
const
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3557
|
+
const taskResponse = await apiGet(`/tasks/${taskId}`);
|
|
3558
|
+
const currentTask = taskResponse.data;
|
|
3559
|
+
let gitStatusOutput = "";
|
|
3560
|
+
try {
|
|
3561
|
+
gitStatusOutput = execSync2("git status --short --porcelain -z", {
|
|
3562
|
+
cwd: repoRoot,
|
|
3563
|
+
encoding: "utf-8"
|
|
3564
|
+
});
|
|
3565
|
+
} catch {
|
|
3566
|
+
process.stderr.write(
|
|
3567
|
+
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3568
|
+
);
|
|
3439
3569
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
reactivated: result.reactivated,
|
|
3446
|
-
total_files: result.total_files
|
|
3447
|
-
},
|
|
3448
|
-
null,
|
|
3449
|
-
2
|
|
3570
|
+
const hookPath = join11(
|
|
3571
|
+
repoRoot,
|
|
3572
|
+
".claude",
|
|
3573
|
+
"hooks",
|
|
3574
|
+
"lint-format-on-edit.sh"
|
|
3450
3575
|
);
|
|
3576
|
+
let lintFormatHookExists = false;
|
|
3577
|
+
try {
|
|
3578
|
+
await access3(hookPath);
|
|
3579
|
+
lintFormatHookExists = true;
|
|
3580
|
+
} catch {
|
|
3581
|
+
}
|
|
3582
|
+
const result = runSyncApprovals({
|
|
3583
|
+
currentRound,
|
|
3584
|
+
currentTask,
|
|
3585
|
+
gitStatusOutput,
|
|
3586
|
+
lintFormatHookExists
|
|
3587
|
+
});
|
|
3588
|
+
if (dryRun) {
|
|
3589
|
+
stdoutPayload = JSON.stringify(
|
|
3590
|
+
{
|
|
3591
|
+
added: result.added,
|
|
3592
|
+
stale_marked: result.stale_marked,
|
|
3593
|
+
reactivated: result.reactivated,
|
|
3594
|
+
total_files: result.total_files,
|
|
3595
|
+
merged_files_changed: result.merged_files_changed
|
|
3596
|
+
},
|
|
3597
|
+
null,
|
|
3598
|
+
2
|
|
3599
|
+
);
|
|
3600
|
+
} else {
|
|
3601
|
+
await mcpCall("update_round", {
|
|
3602
|
+
round_id: roundId,
|
|
3603
|
+
files_changed: result.merged_files_changed
|
|
3604
|
+
});
|
|
3605
|
+
await mcpCall("update_task", {
|
|
3606
|
+
task_id: taskId,
|
|
3607
|
+
files_changed: result.merged_files_changed,
|
|
3608
|
+
app_file_approval_by_user: false
|
|
3609
|
+
});
|
|
3610
|
+
stdoutPayload = JSON.stringify(
|
|
3611
|
+
{
|
|
3612
|
+
added: result.added,
|
|
3613
|
+
stale_marked: result.stale_marked,
|
|
3614
|
+
reactivated: result.reactivated,
|
|
3615
|
+
total_files: result.total_files
|
|
3616
|
+
},
|
|
3617
|
+
null,
|
|
3618
|
+
2
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3451
3621
|
}
|
|
3452
3622
|
} catch (err) {
|
|
3453
3623
|
process.stderr.write(
|
|
@@ -3456,6 +3626,13 @@ async function runRoundSyncApprovals(args) {
|
|
|
3456
3626
|
);
|
|
3457
3627
|
process.exit(1);
|
|
3458
3628
|
}
|
|
3629
|
+
if (skipReason !== null) {
|
|
3630
|
+
process.stderr.write(
|
|
3631
|
+
`sync-approvals: MCP temporarily unavailable (${skipReason}); skipping approval sync. Re-run /cbp-round-update when the service recovers.
|
|
3632
|
+
`
|
|
3633
|
+
);
|
|
3634
|
+
process.exit(0);
|
|
3635
|
+
}
|
|
3459
3636
|
if (stdoutPayload === null) {
|
|
3460
3637
|
process.stderr.write("sync-approvals: internal error \u2014 payload not set\n");
|
|
3461
3638
|
process.exit(1);
|
|
@@ -3463,57 +3640,21 @@ async function runRoundSyncApprovals(args) {
|
|
|
3463
3640
|
process.stdout.write(stdoutPayload + "\n");
|
|
3464
3641
|
process.exit(0);
|
|
3465
3642
|
}
|
|
3466
|
-
|
|
3467
|
-
try {
|
|
3468
|
-
const projectPath = process.cwd();
|
|
3469
|
-
const found = await findCodebyplanConfig(projectPath);
|
|
3470
|
-
if (!found?.contents.repo_id) return void 0;
|
|
3471
|
-
const repoId = found.contents.repo_id;
|
|
3472
|
-
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
3473
|
-
let branch = "";
|
|
3474
|
-
try {
|
|
3475
|
-
branch = execSync2("git symbolic-ref --short HEAD", {
|
|
3476
|
-
cwd: projectPath,
|
|
3477
|
-
encoding: "utf-8"
|
|
3478
|
-
}).trim();
|
|
3479
|
-
} catch {
|
|
3480
|
-
}
|
|
3481
|
-
const worktreeId = await resolveWorktreeId({
|
|
3482
|
-
repoId,
|
|
3483
|
-
repoPath: projectPath,
|
|
3484
|
-
branch,
|
|
3485
|
-
deviceId
|
|
3486
|
-
});
|
|
3487
|
-
if (worktreeId) return worktreeId;
|
|
3488
|
-
const fallbackId = await resolveWorktreeByBranch({
|
|
3489
|
-
repoId,
|
|
3490
|
-
deviceId,
|
|
3491
|
-
branch
|
|
3492
|
-
});
|
|
3493
|
-
if (fallbackId) return fallbackId;
|
|
3494
|
-
process.stderr.write(
|
|
3495
|
-
"sync-approvals: could not resolve worktree id; proceeding without caller_worktree_id\n"
|
|
3496
|
-
);
|
|
3497
|
-
return void 0;
|
|
3498
|
-
} catch {
|
|
3499
|
-
return void 0;
|
|
3500
|
-
}
|
|
3501
|
-
}
|
|
3643
|
+
var RETRY_DELAY_MS;
|
|
3502
3644
|
var init_round = __esm({
|
|
3503
3645
|
"src/cli/round.ts"() {
|
|
3504
3646
|
"use strict";
|
|
3505
3647
|
init_api();
|
|
3506
3648
|
init_mcp_client();
|
|
3507
3649
|
init_flags();
|
|
3508
|
-
init_resolve_worktree();
|
|
3509
|
-
init_local_config();
|
|
3510
3650
|
init_sync_approvals();
|
|
3651
|
+
RETRY_DELAY_MS = 1e3;
|
|
3511
3652
|
}
|
|
3512
3653
|
});
|
|
3513
3654
|
|
|
3514
3655
|
// src/lib/migrate-branch-model.ts
|
|
3515
|
-
import { readFile as
|
|
3516
|
-
import { join as
|
|
3656
|
+
import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
3657
|
+
import { join as join12 } from "node:path";
|
|
3517
3658
|
import { execSync as execSync3 } from "node:child_process";
|
|
3518
3659
|
function assertValidBranchName(branch) {
|
|
3519
3660
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3523,7 +3664,7 @@ function assertValidBranchName(branch) {
|
|
|
3523
3664
|
}
|
|
3524
3665
|
}
|
|
3525
3666
|
async function readJsonFile(filePath) {
|
|
3526
|
-
const raw = await
|
|
3667
|
+
const raw = await readFile11(filePath, "utf-8");
|
|
3527
3668
|
const parsed = JSON.parse(raw);
|
|
3528
3669
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3529
3670
|
throw new Error(`${filePath} does not contain a JSON object`);
|
|
@@ -3592,12 +3733,12 @@ async function runBranchMigration(opts) {
|
|
|
3592
3733
|
if (found) {
|
|
3593
3734
|
if (found.path.endsWith("/repo.json")) {
|
|
3594
3735
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3595
|
-
configPath =
|
|
3736
|
+
configPath = join12(dir, "git.json");
|
|
3596
3737
|
} else {
|
|
3597
3738
|
configPath = found.path;
|
|
3598
3739
|
}
|
|
3599
3740
|
} else {
|
|
3600
|
-
configPath =
|
|
3741
|
+
configPath = join12(cwd, ".codebyplan", "git.json");
|
|
3601
3742
|
}
|
|
3602
3743
|
let fileRaw;
|
|
3603
3744
|
let fileParsed;
|
|
@@ -3671,7 +3812,7 @@ async function runBranchMigration(opts) {
|
|
|
3671
3812
|
const updatedParsed = { ...fileParsed, branch_config: after };
|
|
3672
3813
|
const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
|
|
3673
3814
|
if (newJson !== fileRaw) {
|
|
3674
|
-
await
|
|
3815
|
+
await writeFile9(configPath, newJson, "utf-8");
|
|
3675
3816
|
}
|
|
3676
3817
|
}
|
|
3677
3818
|
return {
|
|
@@ -3777,8 +3918,8 @@ var init_branch = __esm({
|
|
|
3777
3918
|
});
|
|
3778
3919
|
|
|
3779
3920
|
// src/lib/ship.ts
|
|
3780
|
-
import { readFile as
|
|
3781
|
-
import { join as
|
|
3921
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
3922
|
+
import { join as join13 } from "node:path";
|
|
3782
3923
|
import { execSync as execSync4, spawnSync as spawnSync2 } from "node:child_process";
|
|
3783
3924
|
function assertValidBranchName2(branch, label) {
|
|
3784
3925
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3793,15 +3934,15 @@ async function readBaseBranch(cwd) {
|
|
|
3793
3934
|
if (found) {
|
|
3794
3935
|
if (found.path.endsWith("/repo.json")) {
|
|
3795
3936
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3796
|
-
gitJsonPath =
|
|
3937
|
+
gitJsonPath = join13(dir, "git.json");
|
|
3797
3938
|
} else {
|
|
3798
3939
|
gitJsonPath = found.path;
|
|
3799
3940
|
}
|
|
3800
3941
|
} else {
|
|
3801
|
-
gitJsonPath =
|
|
3942
|
+
gitJsonPath = join13(cwd, ".codebyplan", "git.json");
|
|
3802
3943
|
}
|
|
3803
3944
|
try {
|
|
3804
|
-
const raw = await
|
|
3945
|
+
const raw = await readFile12(gitJsonPath, "utf-8");
|
|
3805
3946
|
const parsed = JSON.parse(raw);
|
|
3806
3947
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3807
3948
|
return "main";
|
|
@@ -4333,19 +4474,19 @@ var init_resolve_worktree2 = __esm({
|
|
|
4333
4474
|
});
|
|
4334
4475
|
|
|
4335
4476
|
// src/lib/migrate-local-config.ts
|
|
4336
|
-
import { mkdir as mkdir5, readFile as
|
|
4337
|
-
import { join as
|
|
4477
|
+
import { mkdir as mkdir5, readFile as readFile13, unlink as unlink2, writeFile as writeFile10 } from "node:fs/promises";
|
|
4478
|
+
import { join as join14 } from "node:path";
|
|
4338
4479
|
function legacySharedPath(projectPath) {
|
|
4339
|
-
return
|
|
4480
|
+
return join14(projectPath, ".codebyplan.json");
|
|
4340
4481
|
}
|
|
4341
4482
|
function legacyLocalPath(projectPath) {
|
|
4342
|
-
return
|
|
4483
|
+
return join14(projectPath, ".codebyplan.local.json");
|
|
4343
4484
|
}
|
|
4344
4485
|
function newDirPath(projectPath) {
|
|
4345
|
-
return
|
|
4486
|
+
return join14(projectPath, ".codebyplan");
|
|
4346
4487
|
}
|
|
4347
4488
|
function sentinelPath(projectPath) {
|
|
4348
|
-
return
|
|
4489
|
+
return join14(projectPath, ".codebyplan", "repo.json");
|
|
4349
4490
|
}
|
|
4350
4491
|
async function statSafe(p) {
|
|
4351
4492
|
const { stat: stat2 } = await import("node:fs/promises");
|
|
@@ -4384,7 +4525,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4384
4525
|
}
|
|
4385
4526
|
let legacyRaw;
|
|
4386
4527
|
try {
|
|
4387
|
-
legacyRaw = await
|
|
4528
|
+
legacyRaw = await readFile13(legacySharedPath(projectPath), "utf-8");
|
|
4388
4529
|
} catch {
|
|
4389
4530
|
return {
|
|
4390
4531
|
migrated: true,
|
|
@@ -4411,7 +4552,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4411
4552
|
let deviceId;
|
|
4412
4553
|
let deviceWrittenByHelper = false;
|
|
4413
4554
|
try {
|
|
4414
|
-
const localRaw = await
|
|
4555
|
+
const localRaw = await readFile13(legacyLocalPath(projectPath), "utf-8");
|
|
4415
4556
|
const localParsed = JSON.parse(localRaw);
|
|
4416
4557
|
if (typeof localParsed.device_id === "string") {
|
|
4417
4558
|
deviceId = localParsed.device_id;
|
|
@@ -4438,8 +4579,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4438
4579
|
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
4439
4580
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
4440
4581
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
4441
|
-
await
|
|
4442
|
-
|
|
4582
|
+
await writeFile10(
|
|
4583
|
+
join14(projectPath, ".codebyplan", "repo.json"),
|
|
4443
4584
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
4444
4585
|
"utf-8"
|
|
4445
4586
|
);
|
|
@@ -4451,8 +4592,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4451
4592
|
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
4452
4593
|
if ("port_allocations" in cfg)
|
|
4453
4594
|
serverJson.port_allocations = cfg.port_allocations;
|
|
4454
|
-
await
|
|
4455
|
-
|
|
4595
|
+
await writeFile10(
|
|
4596
|
+
join14(projectPath, ".codebyplan", "server.json"),
|
|
4456
4597
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
4457
4598
|
"utf-8"
|
|
4458
4599
|
);
|
|
@@ -4460,37 +4601,44 @@ async function runLocalMigration(projectPath) {
|
|
|
4460
4601
|
const gitJson = {};
|
|
4461
4602
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
4462
4603
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
4463
|
-
await
|
|
4464
|
-
|
|
4604
|
+
await writeFile10(
|
|
4605
|
+
join14(projectPath, ".codebyplan", "git.json"),
|
|
4465
4606
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
4466
4607
|
"utf-8"
|
|
4467
4608
|
);
|
|
4468
4609
|
filesChanged.push(".codebyplan/git.json");
|
|
4469
4610
|
const shipmentJson = {};
|
|
4470
4611
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
4471
|
-
await
|
|
4472
|
-
|
|
4612
|
+
await writeFile10(
|
|
4613
|
+
join14(projectPath, ".codebyplan", "shipment.json"),
|
|
4473
4614
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
4474
4615
|
"utf-8"
|
|
4475
4616
|
);
|
|
4476
4617
|
filesChanged.push(".codebyplan/shipment.json");
|
|
4477
4618
|
const vendorJson = {};
|
|
4478
|
-
await
|
|
4479
|
-
|
|
4619
|
+
await writeFile10(
|
|
4620
|
+
join14(projectPath, ".codebyplan", "vendor.json"),
|
|
4480
4621
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
4481
4622
|
"utf-8"
|
|
4482
4623
|
);
|
|
4483
4624
|
filesChanged.push(".codebyplan/vendor.json");
|
|
4484
4625
|
const e2eJson = {};
|
|
4485
|
-
await
|
|
4486
|
-
|
|
4626
|
+
await writeFile10(
|
|
4627
|
+
join14(projectPath, ".codebyplan", "e2e.json"),
|
|
4487
4628
|
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
4488
4629
|
"utf-8"
|
|
4489
4630
|
);
|
|
4490
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");
|
|
4491
4639
|
if (!deviceWrittenByHelper) {
|
|
4492
|
-
await
|
|
4493
|
-
|
|
4640
|
+
await writeFile10(
|
|
4641
|
+
join14(projectPath, ".codebyplan", "device.local.json"),
|
|
4494
4642
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
4495
4643
|
"utf-8"
|
|
4496
4644
|
);
|
|
@@ -4502,9 +4650,9 @@ async function runLocalMigration(projectPath) {
|
|
|
4502
4650
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
4503
4651
|
);
|
|
4504
4652
|
}
|
|
4505
|
-
const gitignorePath =
|
|
4653
|
+
const gitignorePath = join14(projectPath, ".gitignore");
|
|
4506
4654
|
try {
|
|
4507
|
-
const gitignoreContent = await
|
|
4655
|
+
const gitignoreContent = await readFile13(gitignorePath, "utf-8");
|
|
4508
4656
|
const legacyLine = ".codebyplan.local.json";
|
|
4509
4657
|
const newLine = ".codebyplan/device.local.json";
|
|
4510
4658
|
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
@@ -4523,7 +4671,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4523
4671
|
updated = gitignoreContent;
|
|
4524
4672
|
}
|
|
4525
4673
|
if (updated !== gitignoreContent) {
|
|
4526
|
-
await
|
|
4674
|
+
await writeFile10(gitignorePath, updated, "utf-8");
|
|
4527
4675
|
filesChanged.push(".gitignore");
|
|
4528
4676
|
}
|
|
4529
4677
|
} catch {
|
|
@@ -4563,8 +4711,8 @@ __export(config_exports, {
|
|
|
4563
4711
|
readVendorConfig: () => readVendorConfig,
|
|
4564
4712
|
runConfig: () => runConfig
|
|
4565
4713
|
});
|
|
4566
|
-
import { mkdir as mkdir6, readFile as
|
|
4567
|
-
import { join as
|
|
4714
|
+
import { mkdir as mkdir6, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
4715
|
+
import { join as join15 } from "node:path";
|
|
4568
4716
|
async function runConfig() {
|
|
4569
4717
|
const flags = parseFlags(3);
|
|
4570
4718
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -4597,7 +4745,7 @@ async function runConfig() {
|
|
|
4597
4745
|
console.log("\n Config complete.\n");
|
|
4598
4746
|
}
|
|
4599
4747
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
4600
|
-
const codebyplanDir =
|
|
4748
|
+
const codebyplanDir = join15(projectPath, ".codebyplan");
|
|
4601
4749
|
let resolvedWorktreeId;
|
|
4602
4750
|
try {
|
|
4603
4751
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
@@ -4723,6 +4871,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4723
4871
|
}
|
|
4724
4872
|
const vendorPayload = {};
|
|
4725
4873
|
const e2ePayload = {};
|
|
4874
|
+
const eslintPayload = {};
|
|
4726
4875
|
if (dryRun) {
|
|
4727
4876
|
console.log(" Config would be updated (dry-run).");
|
|
4728
4877
|
return;
|
|
@@ -4734,20 +4883,21 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4734
4883
|
{ name: "git.json", payload: gitPayload },
|
|
4735
4884
|
{ name: "shipment.json", payload: shipmentPayload },
|
|
4736
4885
|
{ name: "vendor.json", payload: vendorPayload },
|
|
4737
|
-
{ name: "e2e.json", payload: e2ePayload, createOnly: true }
|
|
4886
|
+
{ name: "e2e.json", payload: e2ePayload, createOnly: true },
|
|
4887
|
+
{ name: "eslint.json", payload: eslintPayload, createOnly: true }
|
|
4738
4888
|
];
|
|
4739
4889
|
let anyUpdated = false;
|
|
4740
4890
|
for (const { name, payload, createOnly } of files) {
|
|
4741
|
-
const filePath =
|
|
4891
|
+
const filePath = join15(codebyplanDir, name);
|
|
4742
4892
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
4743
4893
|
let currentJson = "";
|
|
4744
4894
|
try {
|
|
4745
|
-
currentJson = await
|
|
4895
|
+
currentJson = await readFile14(filePath, "utf-8");
|
|
4746
4896
|
} catch {
|
|
4747
4897
|
}
|
|
4748
4898
|
if (createOnly && currentJson !== "") continue;
|
|
4749
4899
|
if (currentJson === newJson) continue;
|
|
4750
|
-
await
|
|
4900
|
+
await writeFile11(filePath, newJson, "utf-8");
|
|
4751
4901
|
console.log(` Updated .codebyplan/${name}`);
|
|
4752
4902
|
anyUpdated = true;
|
|
4753
4903
|
}
|
|
@@ -4757,8 +4907,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4757
4907
|
}
|
|
4758
4908
|
async function readRepoConfig(projectPath) {
|
|
4759
4909
|
try {
|
|
4760
|
-
const raw = await
|
|
4761
|
-
|
|
4910
|
+
const raw = await readFile14(
|
|
4911
|
+
join15(projectPath, ".codebyplan", "repo.json"),
|
|
4762
4912
|
"utf-8"
|
|
4763
4913
|
);
|
|
4764
4914
|
return JSON.parse(raw);
|
|
@@ -4768,8 +4918,8 @@ async function readRepoConfig(projectPath) {
|
|
|
4768
4918
|
}
|
|
4769
4919
|
async function readServerConfig(projectPath) {
|
|
4770
4920
|
try {
|
|
4771
|
-
const raw = await
|
|
4772
|
-
|
|
4921
|
+
const raw = await readFile14(
|
|
4922
|
+
join15(projectPath, ".codebyplan", "server.json"),
|
|
4773
4923
|
"utf-8"
|
|
4774
4924
|
);
|
|
4775
4925
|
return JSON.parse(raw);
|
|
@@ -4779,8 +4929,8 @@ async function readServerConfig(projectPath) {
|
|
|
4779
4929
|
}
|
|
4780
4930
|
async function readGitConfig(projectPath) {
|
|
4781
4931
|
try {
|
|
4782
|
-
const raw = await
|
|
4783
|
-
|
|
4932
|
+
const raw = await readFile14(
|
|
4933
|
+
join15(projectPath, ".codebyplan", "git.json"),
|
|
4784
4934
|
"utf-8"
|
|
4785
4935
|
);
|
|
4786
4936
|
return JSON.parse(raw);
|
|
@@ -4790,8 +4940,8 @@ async function readGitConfig(projectPath) {
|
|
|
4790
4940
|
}
|
|
4791
4941
|
async function readShipmentConfig(projectPath) {
|
|
4792
4942
|
try {
|
|
4793
|
-
const raw = await
|
|
4794
|
-
|
|
4943
|
+
const raw = await readFile14(
|
|
4944
|
+
join15(projectPath, ".codebyplan", "shipment.json"),
|
|
4795
4945
|
"utf-8"
|
|
4796
4946
|
);
|
|
4797
4947
|
return JSON.parse(raw);
|
|
@@ -4801,8 +4951,8 @@ async function readShipmentConfig(projectPath) {
|
|
|
4801
4951
|
}
|
|
4802
4952
|
async function readVendorConfig(projectPath) {
|
|
4803
4953
|
try {
|
|
4804
|
-
const raw = await
|
|
4805
|
-
|
|
4954
|
+
const raw = await readFile14(
|
|
4955
|
+
join15(projectPath, ".codebyplan", "vendor.json"),
|
|
4806
4956
|
"utf-8"
|
|
4807
4957
|
);
|
|
4808
4958
|
return JSON.parse(raw);
|
|
@@ -4812,8 +4962,8 @@ async function readVendorConfig(projectPath) {
|
|
|
4812
4962
|
}
|
|
4813
4963
|
async function readE2eConfig(projectPath) {
|
|
4814
4964
|
try {
|
|
4815
|
-
const raw = await
|
|
4816
|
-
|
|
4965
|
+
const raw = await readFile14(
|
|
4966
|
+
join15(projectPath, ".codebyplan", "e2e.json"),
|
|
4817
4967
|
"utf-8"
|
|
4818
4968
|
);
|
|
4819
4969
|
return JSON.parse(raw);
|
|
@@ -4869,14 +5019,14 @@ var init_server_detect = __esm({
|
|
|
4869
5019
|
});
|
|
4870
5020
|
|
|
4871
5021
|
// src/lib/port-verify.ts
|
|
4872
|
-
import { readFile as
|
|
5022
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
4873
5023
|
async function verifyPorts(projectPath, portAllocations) {
|
|
4874
5024
|
const mismatches = [];
|
|
4875
5025
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
4876
5026
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
4877
5027
|
for (const pkgPath of packageJsonPaths) {
|
|
4878
5028
|
try {
|
|
4879
|
-
const raw = await
|
|
5029
|
+
const raw = await readFile15(pkgPath, "utf-8");
|
|
4880
5030
|
const pkg = JSON.parse(raw);
|
|
4881
5031
|
const scriptPort = detectPortFromScripts(pkg);
|
|
4882
5032
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -4939,7 +5089,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
4939
5089
|
}
|
|
4940
5090
|
let pkg;
|
|
4941
5091
|
try {
|
|
4942
|
-
const raw = await
|
|
5092
|
+
const raw = await readFile15(`${app.absPath}/package.json`, "utf-8");
|
|
4943
5093
|
pkg = JSON.parse(raw);
|
|
4944
5094
|
} catch {
|
|
4945
5095
|
continue;
|
|
@@ -5309,7 +5459,7 @@ var init_hash = __esm({
|
|
|
5309
5459
|
|
|
5310
5460
|
// src/lib/template-walker.ts
|
|
5311
5461
|
import * as fs from "node:fs";
|
|
5312
|
-
import * as
|
|
5462
|
+
import * as path2 from "node:path";
|
|
5313
5463
|
function walkTemplates(templatesDir) {
|
|
5314
5464
|
const absRoot = fs.realpathSync(templatesDir);
|
|
5315
5465
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -5322,7 +5472,7 @@ function walkTemplates(templatesDir) {
|
|
|
5322
5472
|
visited.add(realDir);
|
|
5323
5473
|
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
5324
5474
|
for (const entry of entries) {
|
|
5325
|
-
const absPath =
|
|
5475
|
+
const absPath = path2.join(absDir, entry.name);
|
|
5326
5476
|
if (entry.isDirectory()) {
|
|
5327
5477
|
recurse(absPath);
|
|
5328
5478
|
continue;
|
|
@@ -5331,7 +5481,7 @@ function walkTemplates(templatesDir) {
|
|
|
5331
5481
|
continue;
|
|
5332
5482
|
}
|
|
5333
5483
|
if (entry.name === ".gitkeep") continue;
|
|
5334
|
-
const relPosix =
|
|
5484
|
+
const relPosix = path2.relative(absRoot, absPath).split(path2.sep).join("/");
|
|
5335
5485
|
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
5336
5486
|
continue;
|
|
5337
5487
|
}
|
|
@@ -5359,6 +5509,10 @@ var init_template_walker = __esm({
|
|
|
5359
5509
|
"rules/README.md",
|
|
5360
5510
|
"settings.project.base.json",
|
|
5361
5511
|
"settings.user.base.json",
|
|
5512
|
+
// .gitignore — managed by ensureManagedGitignoreBlock; never copied into
|
|
5513
|
+
// consuming projects' .claude/ tree (it would overwrite the project root
|
|
5514
|
+
// .gitignore with a stale single-entry file).
|
|
5515
|
+
".gitignore",
|
|
5362
5516
|
// CBP-internal hooks — see templates/hooks/README.md "Hooks NOT included and why"
|
|
5363
5517
|
"hooks/validate-structure.sh",
|
|
5364
5518
|
"hooks/validate-structure-lib.sh",
|
|
@@ -5376,15 +5530,15 @@ var init_template_walker = __esm({
|
|
|
5376
5530
|
// src/lib/manifest.ts
|
|
5377
5531
|
import * as fs2 from "node:fs";
|
|
5378
5532
|
import * as os from "node:os";
|
|
5379
|
-
import * as
|
|
5533
|
+
import * as path3 from "node:path";
|
|
5380
5534
|
function manifestPath(projectDir) {
|
|
5381
|
-
return
|
|
5535
|
+
return path3.join(projectDir, ".claude", NEW_MANIFEST_FILENAME);
|
|
5382
5536
|
}
|
|
5383
5537
|
function midManifestPath(projectDir) {
|
|
5384
|
-
return
|
|
5538
|
+
return path3.join(projectDir, ".claude", MID_MANIFEST_FILENAME);
|
|
5385
5539
|
}
|
|
5386
5540
|
function oldManifestPath(projectDir) {
|
|
5387
|
-
return
|
|
5541
|
+
return path3.join(projectDir, ".claude", OLD_MANIFEST_FILENAME);
|
|
5388
5542
|
}
|
|
5389
5543
|
function readManifest(projectDir) {
|
|
5390
5544
|
const newFile = manifestPath(projectDir);
|
|
@@ -5406,7 +5560,7 @@ function readManifest(projectDir) {
|
|
|
5406
5560
|
}
|
|
5407
5561
|
function writeManifest(projectDir, manifest) {
|
|
5408
5562
|
const file = manifestPath(projectDir);
|
|
5409
|
-
fs2.mkdirSync(
|
|
5563
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5410
5564
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5411
5565
|
const mid = midManifestPath(projectDir);
|
|
5412
5566
|
if (fs2.existsSync(mid)) {
|
|
@@ -5425,16 +5579,16 @@ function defaultManifest() {
|
|
|
5425
5579
|
};
|
|
5426
5580
|
}
|
|
5427
5581
|
function userManifestPath(userDir) {
|
|
5428
|
-
const dir = userDir ??
|
|
5429
|
-
return
|
|
5582
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5583
|
+
return path3.join(dir, NEW_MANIFEST_FILENAME);
|
|
5430
5584
|
}
|
|
5431
5585
|
function userMidManifestPath(userDir) {
|
|
5432
|
-
const dir = userDir ??
|
|
5433
|
-
return
|
|
5586
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5587
|
+
return path3.join(dir, MID_MANIFEST_FILENAME);
|
|
5434
5588
|
}
|
|
5435
5589
|
function userOldManifestPath(userDir) {
|
|
5436
|
-
const dir = userDir ??
|
|
5437
|
-
return
|
|
5590
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5591
|
+
return path3.join(dir, OLD_MANIFEST_FILENAME);
|
|
5438
5592
|
}
|
|
5439
5593
|
function readManifestForScope(scope, arg2) {
|
|
5440
5594
|
if (scope === "user") {
|
|
@@ -5460,7 +5614,7 @@ function readManifestForScope(scope, arg2) {
|
|
|
5460
5614
|
function writeManifestForScope(scope, manifest, arg3) {
|
|
5461
5615
|
if (scope === "user") {
|
|
5462
5616
|
const file = userManifestPath(arg3);
|
|
5463
|
-
fs2.mkdirSync(
|
|
5617
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5464
5618
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5465
5619
|
const mid = userMidManifestPath(arg3);
|
|
5466
5620
|
if (fs2.existsSync(mid)) {
|
|
@@ -5716,14 +5870,14 @@ __export(install_exports, {
|
|
|
5716
5870
|
});
|
|
5717
5871
|
import * as fs3 from "node:fs";
|
|
5718
5872
|
import * as os2 from "node:os";
|
|
5719
|
-
import * as
|
|
5873
|
+
import * as path4 from "node:path";
|
|
5720
5874
|
import { fileURLToPath } from "node:url";
|
|
5721
5875
|
function resolveTemplatesDir() {
|
|
5722
|
-
const here =
|
|
5876
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
5723
5877
|
const candidates = [
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5878
|
+
path4.resolve(here, "..", "templates"),
|
|
5879
|
+
path4.resolve(here, "..", "..", "templates"),
|
|
5880
|
+
path4.resolve(here, "..", "..", "..", "templates")
|
|
5727
5881
|
];
|
|
5728
5882
|
for (const c of candidates) {
|
|
5729
5883
|
if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
|
|
@@ -5764,14 +5918,14 @@ async function runInstall(opts, deps = {}) {
|
|
|
5764
5918
|
const files = walkTemplates(templatesDir);
|
|
5765
5919
|
const manifestEntries = [];
|
|
5766
5920
|
for (const f of files) {
|
|
5767
|
-
const absDest =
|
|
5768
|
-
const absSrc =
|
|
5921
|
+
const absDest = path4.join(projectDir, ".claude", f.dest);
|
|
5922
|
+
const absSrc = path4.join(templatesDir, f.src);
|
|
5769
5923
|
if (opts.dryRun) {
|
|
5770
5924
|
if (opts.verbose) {
|
|
5771
5925
|
console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
|
|
5772
5926
|
}
|
|
5773
5927
|
} else {
|
|
5774
|
-
fs3.mkdirSync(
|
|
5928
|
+
fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
|
|
5775
5929
|
fs3.copyFileSync(absSrc, absDest);
|
|
5776
5930
|
if (opts.verbose) {
|
|
5777
5931
|
console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
|
|
@@ -5779,15 +5933,15 @@ async function runInstall(opts, deps = {}) {
|
|
|
5779
5933
|
}
|
|
5780
5934
|
manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
|
|
5781
5935
|
}
|
|
5782
|
-
const hooksJsonPath =
|
|
5783
|
-
const baseSettingsPath =
|
|
5936
|
+
const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5937
|
+
const baseSettingsPath = path4.join(
|
|
5784
5938
|
templatesDir,
|
|
5785
5939
|
"settings.project.base.json"
|
|
5786
5940
|
);
|
|
5787
5941
|
const hasHooks = fs3.existsSync(hooksJsonPath);
|
|
5788
5942
|
const hasBase = fs3.existsSync(baseSettingsPath);
|
|
5789
5943
|
if (hasHooks || hasBase) {
|
|
5790
|
-
const settingsPath =
|
|
5944
|
+
const settingsPath = path4.join(projectDir, ".claude", "settings.json");
|
|
5791
5945
|
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5792
5946
|
if (hasBase) {
|
|
5793
5947
|
const base = JSON.parse(
|
|
@@ -5802,7 +5956,7 @@ async function runInstall(opts, deps = {}) {
|
|
|
5802
5956
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
5803
5957
|
}
|
|
5804
5958
|
if (!opts.dryRun) {
|
|
5805
|
-
fs3.mkdirSync(
|
|
5959
|
+
fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
|
|
5806
5960
|
fs3.writeFileSync(
|
|
5807
5961
|
settingsPath,
|
|
5808
5962
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -5810,10 +5964,19 @@ async function runInstall(opts, deps = {}) {
|
|
|
5810
5964
|
);
|
|
5811
5965
|
} else if (opts.verbose) {
|
|
5812
5966
|
console.log(
|
|
5813
|
-
`[dry-run] would merge settings into ${
|
|
5967
|
+
`[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
|
|
5814
5968
|
);
|
|
5815
5969
|
}
|
|
5816
5970
|
}
|
|
5971
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
5972
|
+
projectDir,
|
|
5973
|
+
opts.dryRun
|
|
5974
|
+
);
|
|
5975
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
5976
|
+
console.log(
|
|
5977
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
|
|
5978
|
+
);
|
|
5979
|
+
}
|
|
5817
5980
|
if (!opts.dryRun) {
|
|
5818
5981
|
const manifest = defaultManifest();
|
|
5819
5982
|
manifest.files = manifestEntries;
|
|
@@ -5844,9 +6007,9 @@ function runInstallUser(opts, deps) {
|
|
|
5844
6007
|
return;
|
|
5845
6008
|
}
|
|
5846
6009
|
try {
|
|
5847
|
-
const userDir = deps.userDir ??
|
|
5848
|
-
const settingsPath =
|
|
5849
|
-
const userBaseSettingsPath =
|
|
6010
|
+
const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
|
|
6011
|
+
const settingsPath = path4.join(userDir, "settings.json");
|
|
6012
|
+
const userBaseSettingsPath = path4.join(
|
|
5850
6013
|
templatesDir,
|
|
5851
6014
|
"settings.user.base.json"
|
|
5852
6015
|
);
|
|
@@ -5888,7 +6051,7 @@ function runInstallUser(opts, deps) {
|
|
|
5888
6051
|
}
|
|
5889
6052
|
}
|
|
5890
6053
|
function countHookEntries(templatesDir) {
|
|
5891
|
-
const p =
|
|
6054
|
+
const p = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5892
6055
|
if (!fs3.existsSync(p)) return 0;
|
|
5893
6056
|
try {
|
|
5894
6057
|
const j = JSON.parse(fs3.readFileSync(p, "utf8"));
|
|
@@ -5908,6 +6071,7 @@ var init_install = __esm({
|
|
|
5908
6071
|
"src/cli/claude/install.ts"() {
|
|
5909
6072
|
"use strict";
|
|
5910
6073
|
init_template_walker();
|
|
6074
|
+
init_gitignore_block();
|
|
5911
6075
|
init_manifest();
|
|
5912
6076
|
init_settings_merge();
|
|
5913
6077
|
init_statusline_config();
|
|
@@ -6032,7 +6196,7 @@ __export(update_exports, {
|
|
|
6032
6196
|
});
|
|
6033
6197
|
import * as fs4 from "node:fs";
|
|
6034
6198
|
import * as os3 from "node:os";
|
|
6035
|
-
import * as
|
|
6199
|
+
import * as path5 from "node:path";
|
|
6036
6200
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6037
6201
|
async function runUpdate(opts, deps = {}) {
|
|
6038
6202
|
await Promise.resolve();
|
|
@@ -6072,9 +6236,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6072
6236
|
finalManifestEntries.push(e);
|
|
6073
6237
|
}
|
|
6074
6238
|
for (const { packaged, absSrc } of plan.overwriteSafe) {
|
|
6075
|
-
const absDest =
|
|
6239
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6076
6240
|
if (!opts.dryRun) {
|
|
6077
|
-
fs4.mkdirSync(
|
|
6241
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6078
6242
|
fs4.copyFileSync(absSrc, absDest);
|
|
6079
6243
|
if (opts.verbose) console.log(`updated ${packaged.dest}`);
|
|
6080
6244
|
} else if (opts.verbose) {
|
|
@@ -6087,7 +6251,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6087
6251
|
absSrc,
|
|
6088
6252
|
onDiskContent
|
|
6089
6253
|
} of plan.overwriteHandEdited) {
|
|
6090
|
-
const absDest =
|
|
6254
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6091
6255
|
const newContent = fs4.readFileSync(absSrc);
|
|
6092
6256
|
const showDiff = () => {
|
|
6093
6257
|
console.log(
|
|
@@ -6100,7 +6264,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6100
6264
|
const answer = await promptOverwrite(packaged.dest, opts, showDiff);
|
|
6101
6265
|
if (answer === "overwrite") {
|
|
6102
6266
|
if (!opts.dryRun) {
|
|
6103
|
-
fs4.mkdirSync(
|
|
6267
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6104
6268
|
fs4.copyFileSync(absSrc, absDest);
|
|
6105
6269
|
}
|
|
6106
6270
|
finalManifestEntries.push(packaged);
|
|
@@ -6116,9 +6280,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6116
6280
|
for (const { packaged, absSrc } of plan.newOptIn) {
|
|
6117
6281
|
const answer = await promptOptIn(packaged.dest, opts);
|
|
6118
6282
|
if (answer === "opt-in") {
|
|
6119
|
-
const absDest =
|
|
6283
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6120
6284
|
if (!opts.dryRun) {
|
|
6121
|
-
fs4.mkdirSync(
|
|
6285
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6122
6286
|
fs4.copyFileSync(absSrc, absDest);
|
|
6123
6287
|
}
|
|
6124
6288
|
finalManifestEntries.push(packaged);
|
|
@@ -6130,25 +6294,25 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6130
6294
|
for (const e of plan.removedFromPackage) {
|
|
6131
6295
|
const answer = await promptRemove(e.dest, opts);
|
|
6132
6296
|
if (answer === "remove") {
|
|
6133
|
-
const absDest =
|
|
6297
|
+
const absDest = path5.join(projectDir, ".claude", e.dest);
|
|
6134
6298
|
if (!opts.dryRun && fs4.existsSync(absDest)) {
|
|
6135
6299
|
fs4.rmSync(absDest);
|
|
6136
|
-
const claudeDir =
|
|
6137
|
-
let cur =
|
|
6138
|
-
while (cur !== claudeDir && cur !==
|
|
6139
|
-
if (
|
|
6300
|
+
const claudeDir = path5.join(projectDir, ".claude");
|
|
6301
|
+
let cur = path5.dirname(absDest);
|
|
6302
|
+
while (cur !== claudeDir && cur !== path5.dirname(cur)) {
|
|
6303
|
+
if (path5.dirname(cur) === claudeDir) break;
|
|
6140
6304
|
try {
|
|
6141
6305
|
fs4.rmdirSync(cur);
|
|
6142
6306
|
if (opts.verbose)
|
|
6143
6307
|
console.log(
|
|
6144
|
-
`pruned empty dir ${
|
|
6308
|
+
`pruned empty dir ${path5.relative(claudeDir, cur)}`
|
|
6145
6309
|
);
|
|
6146
|
-
cur =
|
|
6310
|
+
cur = path5.dirname(cur);
|
|
6147
6311
|
} catch (err) {
|
|
6148
6312
|
const code = err.code;
|
|
6149
6313
|
if (code !== "ENOTEMPTY" && code !== "ENOENT") {
|
|
6150
6314
|
console.warn(
|
|
6151
|
-
`codebyplan claude: could not prune empty dir ${
|
|
6315
|
+
`codebyplan claude: could not prune empty dir ${path5.relative(claudeDir, cur)}: ${err.message}`
|
|
6152
6316
|
);
|
|
6153
6317
|
}
|
|
6154
6318
|
break;
|
|
@@ -6160,16 +6324,16 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6160
6324
|
if (opts.verbose) console.log(`kept (untracked) ${e.dest}`);
|
|
6161
6325
|
}
|
|
6162
6326
|
}
|
|
6163
|
-
const hooksJsonPath =
|
|
6327
|
+
const hooksJsonPath = path5.join(templatesDir, "hooks", "hooks.json");
|
|
6164
6328
|
if (fs4.existsSync(hooksJsonPath)) {
|
|
6165
6329
|
const hooksJson = JSON.parse(
|
|
6166
6330
|
fs4.readFileSync(hooksJsonPath, "utf8")
|
|
6167
6331
|
);
|
|
6168
|
-
const settingsPath =
|
|
6332
|
+
const settingsPath = path5.join(projectDir, ".claude", "settings.json");
|
|
6169
6333
|
const existingSettings = fs4.existsSync(settingsPath) ? JSON.parse(fs4.readFileSync(settingsPath, "utf8")) : {};
|
|
6170
6334
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
6171
6335
|
if (!opts.dryRun) {
|
|
6172
|
-
fs4.mkdirSync(
|
|
6336
|
+
fs4.mkdirSync(path5.dirname(settingsPath), { recursive: true });
|
|
6173
6337
|
fs4.writeFileSync(
|
|
6174
6338
|
settingsPath,
|
|
6175
6339
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -6177,6 +6341,15 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6177
6341
|
);
|
|
6178
6342
|
}
|
|
6179
6343
|
}
|
|
6344
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
6345
|
+
projectDir,
|
|
6346
|
+
opts.dryRun
|
|
6347
|
+
);
|
|
6348
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
6349
|
+
console.log(
|
|
6350
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path5.relative(projectDir, path5.join(projectDir, ".gitignore"))}`
|
|
6351
|
+
);
|
|
6352
|
+
}
|
|
6180
6353
|
if (!opts.dryRun) {
|
|
6181
6354
|
const manifest = defaultManifest();
|
|
6182
6355
|
manifest.files = finalManifestEntries.sort(
|
|
@@ -6209,9 +6382,9 @@ function runUpdateUser(opts, deps) {
|
|
|
6209
6382
|
return;
|
|
6210
6383
|
}
|
|
6211
6384
|
try {
|
|
6212
|
-
const userDir = deps.userDir ??
|
|
6213
|
-
const settingsPath =
|
|
6214
|
-
const userBaseSettingsPath =
|
|
6385
|
+
const userDir = deps.userDir ?? path5.join(os3.homedir(), ".claude");
|
|
6386
|
+
const settingsPath = path5.join(userDir, "settings.json");
|
|
6387
|
+
const userBaseSettingsPath = path5.join(
|
|
6215
6388
|
templatesDir,
|
|
6216
6389
|
"settings.user.base.json"
|
|
6217
6390
|
);
|
|
@@ -6273,8 +6446,8 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6273
6446
|
};
|
|
6274
6447
|
for (const pkg of packaged) {
|
|
6275
6448
|
const inManifest = manifestBySrc.get(pkg.src);
|
|
6276
|
-
const absDest =
|
|
6277
|
-
const absSrc =
|
|
6449
|
+
const absDest = path5.join(projectDir, ".claude", pkg.dest);
|
|
6450
|
+
const absSrc = path5.join(templatesDir, pkg.src);
|
|
6278
6451
|
if (!inManifest) {
|
|
6279
6452
|
plan.newOptIn.push({
|
|
6280
6453
|
packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
|
|
@@ -6310,11 +6483,11 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6310
6483
|
return plan;
|
|
6311
6484
|
}
|
|
6312
6485
|
function resolveTemplatesDirFromInstall() {
|
|
6313
|
-
const here =
|
|
6486
|
+
const here = path5.dirname(fileURLToPath2(import.meta.url));
|
|
6314
6487
|
const candidates = [
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6488
|
+
path5.resolve(here, "..", "templates"),
|
|
6489
|
+
path5.resolve(here, "..", "..", "templates"),
|
|
6490
|
+
path5.resolve(here, "..", "..", "..", "templates")
|
|
6318
6491
|
];
|
|
6319
6492
|
for (const c of candidates) {
|
|
6320
6493
|
if (fs4.existsSync(c) && fs4.statSync(c).isDirectory()) {
|
|
@@ -6332,6 +6505,7 @@ var init_update = __esm({
|
|
|
6332
6505
|
"src/cli/claude/update.ts"() {
|
|
6333
6506
|
"use strict";
|
|
6334
6507
|
init_template_walker();
|
|
6508
|
+
init_gitignore_block();
|
|
6335
6509
|
init_hash();
|
|
6336
6510
|
init_manifest();
|
|
6337
6511
|
init_settings_merge();
|
|
@@ -6347,7 +6521,7 @@ __export(uninstall_exports, {
|
|
|
6347
6521
|
});
|
|
6348
6522
|
import * as fs5 from "node:fs";
|
|
6349
6523
|
import * as os4 from "node:os";
|
|
6350
|
-
import * as
|
|
6524
|
+
import * as path6 from "node:path";
|
|
6351
6525
|
async function runUninstall(opts, deps = {}) {
|
|
6352
6526
|
await Promise.resolve();
|
|
6353
6527
|
const scope = opts.scope ?? "project";
|
|
@@ -6376,7 +6550,7 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6376
6550
|
let removed = 0;
|
|
6377
6551
|
let warnings = 0;
|
|
6378
6552
|
for (const entry of manifest.files) {
|
|
6379
|
-
const abs =
|
|
6553
|
+
const abs = path6.join(projectDir, ".claude", entry.dest);
|
|
6380
6554
|
if (!fs5.existsSync(abs)) {
|
|
6381
6555
|
console.warn(
|
|
6382
6556
|
`codebyplan claude uninstall: ${entry.dest} already absent (skipping).`
|
|
@@ -6400,12 +6574,12 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6400
6574
|
if (!opts.dryRun) {
|
|
6401
6575
|
pruneEmptyManagedDirs(projectDir);
|
|
6402
6576
|
}
|
|
6403
|
-
const settingsPath =
|
|
6577
|
+
const settingsPath = path6.join(projectDir, ".claude", "settings.json");
|
|
6404
6578
|
if (fs5.existsSync(settingsPath)) {
|
|
6405
6579
|
const settings = JSON.parse(
|
|
6406
6580
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6407
6581
|
);
|
|
6408
|
-
const baseSettingsPath = templatesDir ?
|
|
6582
|
+
const baseSettingsPath = templatesDir ? path6.join(templatesDir, "settings.project.base.json") : null;
|
|
6409
6583
|
if (baseSettingsPath && fs5.existsSync(baseSettingsPath)) {
|
|
6410
6584
|
const base = JSON.parse(
|
|
6411
6585
|
fs5.readFileSync(baseSettingsPath, "utf8")
|
|
@@ -6426,6 +6600,15 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6426
6600
|
}
|
|
6427
6601
|
}
|
|
6428
6602
|
}
|
|
6603
|
+
const gitignoreAction = await removeManagedGitignoreBlock(
|
|
6604
|
+
projectDir,
|
|
6605
|
+
opts.dryRun
|
|
6606
|
+
);
|
|
6607
|
+
if (opts.verbose) {
|
|
6608
|
+
console.log(
|
|
6609
|
+
gitignoreAction === "removed" ? `${opts.dryRun ? "[dry-run] would remove" : "removed"} managed .gitignore block` : "managed .gitignore block already absent (no change)"
|
|
6610
|
+
);
|
|
6611
|
+
}
|
|
6429
6612
|
if (!opts.dryRun) {
|
|
6430
6613
|
const m = manifestPath(projectDir);
|
|
6431
6614
|
if (fs5.existsSync(m)) fs5.rmSync(m);
|
|
@@ -6454,7 +6637,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6454
6637
|
}
|
|
6455
6638
|
}
|
|
6456
6639
|
try {
|
|
6457
|
-
const userDir = deps.userDir ??
|
|
6640
|
+
const userDir = deps.userDir ?? path6.join(os4.homedir(), ".claude");
|
|
6458
6641
|
const existingManifest = readManifestForScope("user", userDir);
|
|
6459
6642
|
if (!existingManifest) {
|
|
6460
6643
|
console.error(
|
|
@@ -6463,12 +6646,12 @@ function runUninstallUser(opts, deps) {
|
|
|
6463
6646
|
process.exitCode = 1;
|
|
6464
6647
|
return;
|
|
6465
6648
|
}
|
|
6466
|
-
const settingsPath =
|
|
6649
|
+
const settingsPath = path6.join(userDir, "settings.json");
|
|
6467
6650
|
if (fs5.existsSync(settingsPath)) {
|
|
6468
6651
|
const settings = JSON.parse(
|
|
6469
6652
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6470
6653
|
);
|
|
6471
|
-
const userBaseSettingsPath = templatesDir != null ?
|
|
6654
|
+
const userBaseSettingsPath = templatesDir != null ? path6.join(templatesDir, "settings.user.base.json") : null;
|
|
6472
6655
|
if (userBaseSettingsPath && fs5.existsSync(userBaseSettingsPath)) {
|
|
6473
6656
|
const userBase = JSON.parse(
|
|
6474
6657
|
fs5.readFileSync(userBaseSettingsPath, "utf8")
|
|
@@ -6509,7 +6692,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6509
6692
|
function pruneEmptyManagedDirs(projectDir) {
|
|
6510
6693
|
const managedRoots = ["skills", "agents", "hooks", "rules"];
|
|
6511
6694
|
for (const root of managedRoots) {
|
|
6512
|
-
const abs =
|
|
6695
|
+
const abs = path6.join(projectDir, ".claude", root);
|
|
6513
6696
|
if (!fs5.existsSync(abs)) continue;
|
|
6514
6697
|
pruneLeafFirst(abs);
|
|
6515
6698
|
}
|
|
@@ -6520,7 +6703,7 @@ function pruneLeafFirst(dir) {
|
|
|
6520
6703
|
if (!stat2.isDirectory()) return;
|
|
6521
6704
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
6522
6705
|
if (entry.isDirectory()) {
|
|
6523
|
-
pruneLeafFirst(
|
|
6706
|
+
pruneLeafFirst(path6.join(dir, entry.name));
|
|
6524
6707
|
}
|
|
6525
6708
|
}
|
|
6526
6709
|
const remaining = fs5.readdirSync(dir);
|
|
@@ -6532,6 +6715,7 @@ var init_uninstall = __esm({
|
|
|
6532
6715
|
"src/cli/claude/uninstall.ts"() {
|
|
6533
6716
|
"use strict";
|
|
6534
6717
|
init_hash();
|
|
6718
|
+
init_gitignore_block();
|
|
6535
6719
|
init_manifest();
|
|
6536
6720
|
init_settings_merge();
|
|
6537
6721
|
init_install();
|
|
@@ -6582,8 +6766,9 @@ void (async () => {
|
|
|
6582
6766
|
}
|
|
6583
6767
|
if (arg === "login") {
|
|
6584
6768
|
const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
6769
|
+
const admin = process.argv.includes("--admin");
|
|
6585
6770
|
try {
|
|
6586
|
-
await runLogin2();
|
|
6771
|
+
await runLogin2({ admin });
|
|
6587
6772
|
process.exit(0);
|
|
6588
6773
|
} catch {
|
|
6589
6774
|
process.exit(1);
|
|
@@ -6727,7 +6912,7 @@ void (async () => {
|
|
|
6727
6912
|
|
|
6728
6913
|
Usage:
|
|
6729
6914
|
codebyplan setup Interactive setup (OAuth + project init)
|
|
6730
|
-
codebyplan login
|
|
6915
|
+
codebyplan login [--admin] Authenticate via OAuth device-code flow
|
|
6731
6916
|
codebyplan logout Clear cached OAuth tokens
|
|
6732
6917
|
codebyplan whoami Show currently authenticated identity
|
|
6733
6918
|
codebyplan upgrade-auth Migrate legacy x-api-key MCP config to OAuth
|