codebyplan 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +602 -345
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/agents/cbp-cc-executor.md +1 -1
- package/templates/agents/cbp-e2e-maestro.md +202 -0
- package/templates/agents/cbp-e2e-playwright.md +229 -0
- package/templates/agents/cbp-e2e-tauri.md +184 -0
- package/templates/agents/cbp-e2e-vscode.md +203 -0
- package/templates/agents/cbp-e2e-xcuitest.md +224 -0
- package/templates/agents/cbp-improve-claude.md +1 -1
- package/templates/agents/cbp-round-executor.md +11 -11
- package/templates/agents/cbp-task-check.md +1 -1
- package/templates/agents/cbp-task-planner.md +2 -0
- package/templates/agents/cbp-testing-qa-agent.md +9 -9
- package/templates/context/testing/e2e.md +303 -0
- 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/validate-structure-lengths.sh +2 -0
- package/templates/hooks/validate-structure-smoke.sh +2 -1
- package/templates/hooks/validate-structure-templates.sh +1 -0
- package/templates/rules/README.md +8 -1
- package/templates/rules/context-file-loading.md +4 -1
- package/templates/rules/e2e-mandatory.md +70 -0
- package/templates/rules/supabase-branch-lifecycle.md +99 -0
- package/templates/settings.project.base.json +1 -2
- package/templates/skills/cbp-build-cc-agent/SKILL.md +16 -14
- package/templates/skills/cbp-build-cc-agent/reference/cbp-quality.md +4 -4
- package/templates/skills/cbp-build-cc-agent/scripts/validate-agent.sh +8 -6
- package/templates/skills/cbp-build-cc-mode/SKILL.md +4 -4
- package/templates/skills/cbp-build-cc-settings/reference/cbp-conventions.md +1 -2
- package/templates/skills/cbp-checkpoint-check/SKILL.md +12 -8
- 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-plan/SKILL.md +2 -2
- package/templates/skills/cbp-checkpoint-plan/reference/e2e-discovery-probe.md +5 -5
- package/templates/skills/cbp-e2e-setup/SKILL.md +254 -0
- package/templates/skills/cbp-e2e-setup/reference/maestro.md +200 -0
- package/templates/skills/cbp-e2e-setup/reference/playwright.md +212 -0
- package/templates/skills/cbp-e2e-setup/reference/tauri.md +147 -0
- package/templates/skills/cbp-e2e-setup/reference/vscode.md +154 -0
- package/templates/skills/cbp-e2e-setup/reference/xcuitest.md +185 -0
- package/templates/skills/cbp-frontend-ui/SKILL.md +6 -6
- package/templates/skills/cbp-frontend-ux/SKILL.md +1 -1
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +17 -1
- package/templates/skills/cbp-round-execute/SKILL.md +30 -17
- package/templates/skills/cbp-session-start/SKILL.md +27 -2
- package/templates/skills/cbp-ship-main/SKILL.md +13 -0
- package/templates/skills/cbp-supabase-branch-check/SKILL.md +12 -5
- package/templates/skills/cbp-supabase-migrate/SKILL.md +139 -9
- package/templates/skills/cbp-supabase-migrate/reference/preflight-dry-run.md +1 -1
- package/templates/skills/cbp-supabase-setup/SKILL.md +13 -7
- package/templates/skills/cbp-supabase-setup/reference/branching-setup.md +2 -2
- package/templates/skills/cbp-task-check/SKILL.md +2 -2
- package/templates/skills/cbp-task-start/SKILL.md +2 -0
- package/templates/agents/cbp-test-e2e-agent.md +0 -363
package/dist/cli.js
CHANGED
|
@@ -14,26 +14,163 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.
|
|
17
|
+
VERSION = "1.12.0";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
// src/lib/gitignore-block.ts
|
|
23
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
async function ensureManagedGitignoreBlock(projectDir, dryRun = false) {
|
|
26
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
27
|
+
let existing = null;
|
|
28
|
+
try {
|
|
29
|
+
existing = await readFile(gitignorePath, "utf-8");
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
if (existing === null) {
|
|
33
|
+
if (!dryRun) {
|
|
34
|
+
const block = buildBlock("\n");
|
|
35
|
+
await writeFile(gitignorePath, block, "utf-8");
|
|
36
|
+
}
|
|
37
|
+
return "created";
|
|
38
|
+
}
|
|
39
|
+
const nl = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
40
|
+
const lines = existing.split(/\r?\n/);
|
|
41
|
+
const startIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_START);
|
|
42
|
+
const endIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_END);
|
|
43
|
+
const hasValidBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
44
|
+
if (!hasValidBlock) {
|
|
45
|
+
const hasOrphanMarker = startIdx !== -1 || endIdx !== -1;
|
|
46
|
+
if (hasOrphanMarker) {
|
|
47
|
+
const hadTrailingNewline = existing.endsWith("\n");
|
|
48
|
+
const cleanedLines = lines.filter(
|
|
49
|
+
(l) => l !== GITIGNORE_BLOCK_START && l !== GITIGNORE_BLOCK_END
|
|
50
|
+
);
|
|
51
|
+
if (hadTrailingNewline && cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1] === "") {
|
|
52
|
+
cleanedLines.pop();
|
|
53
|
+
}
|
|
54
|
+
let content2 = cleanedLines.join(nl);
|
|
55
|
+
if (content2.length > 0) content2 += nl;
|
|
56
|
+
const newContent3 = content2 + buildBlock(nl);
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
await writeFile(gitignorePath, newContent3, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
return "refreshed";
|
|
61
|
+
}
|
|
62
|
+
let content = existing;
|
|
63
|
+
if (!content.endsWith("\n") && !content.endsWith("\r\n")) {
|
|
64
|
+
content = content + nl;
|
|
65
|
+
}
|
|
66
|
+
const block = buildBlock(nl);
|
|
67
|
+
const newContent2 = content + block;
|
|
68
|
+
if (!dryRun) {
|
|
69
|
+
await writeFile(gitignorePath, newContent2, "utf-8");
|
|
70
|
+
}
|
|
71
|
+
return "appended";
|
|
72
|
+
}
|
|
73
|
+
const blockBodyLines = lines.slice(startIdx + 1, endIdx);
|
|
74
|
+
const canonicalBodyLines = [...CANONICAL_GITIGNORE_ENTRIES];
|
|
75
|
+
const bodyMatches = blockBodyLines.length === canonicalBodyLines.length && blockBodyLines.every((l, i) => l === canonicalBodyLines[i]);
|
|
76
|
+
if (bodyMatches) {
|
|
77
|
+
return "unchanged";
|
|
78
|
+
}
|
|
79
|
+
const blockLines = [
|
|
80
|
+
GITIGNORE_BLOCK_START,
|
|
81
|
+
...CANONICAL_GITIGNORE_ENTRIES,
|
|
82
|
+
GITIGNORE_BLOCK_END
|
|
83
|
+
];
|
|
84
|
+
const newLines = [
|
|
85
|
+
...lines.slice(0, startIdx),
|
|
86
|
+
...blockLines,
|
|
87
|
+
...lines.slice(endIdx + 1)
|
|
88
|
+
];
|
|
89
|
+
const newContent = newLines.join(nl);
|
|
90
|
+
const finalContent = newContent.endsWith(nl) ? newContent : newContent + nl;
|
|
91
|
+
if (!dryRun) {
|
|
92
|
+
await writeFile(gitignorePath, finalContent, "utf-8");
|
|
93
|
+
}
|
|
94
|
+
return "refreshed";
|
|
95
|
+
}
|
|
96
|
+
async function removeManagedGitignoreBlock(projectDir, dryRun = false) {
|
|
97
|
+
const gitignorePath = path.join(projectDir, ".gitignore");
|
|
98
|
+
let existing = null;
|
|
99
|
+
try {
|
|
100
|
+
existing = await readFile(gitignorePath, "utf-8");
|
|
101
|
+
} catch {
|
|
102
|
+
return "unchanged";
|
|
103
|
+
}
|
|
104
|
+
const nl = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
105
|
+
const hadTrailingNewline = existing.endsWith("\n");
|
|
106
|
+
const lines = existing.split(/\r?\n/);
|
|
107
|
+
if (hadTrailingNewline && lines.length > 0 && lines[lines.length - 1] === "") {
|
|
108
|
+
lines.pop();
|
|
109
|
+
}
|
|
110
|
+
const startIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_START);
|
|
111
|
+
const endIdx = lines.findIndex((l) => l === GITIGNORE_BLOCK_END);
|
|
112
|
+
const hasValidBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
113
|
+
if (!hasValidBlock) {
|
|
114
|
+
const hasOrphanMarker = startIdx !== -1 || endIdx !== -1;
|
|
115
|
+
if (!hasOrphanMarker) {
|
|
116
|
+
return "unchanged";
|
|
117
|
+
}
|
|
118
|
+
const cleaned = lines.filter(
|
|
119
|
+
(l) => l !== GITIGNORE_BLOCK_START && l !== GITIGNORE_BLOCK_END
|
|
120
|
+
);
|
|
121
|
+
const orphanContent = cleaned.length > 0 ? cleaned.join(nl) + nl : "";
|
|
122
|
+
if (!dryRun) {
|
|
123
|
+
await writeFile(gitignorePath, orphanContent, "utf-8");
|
|
124
|
+
}
|
|
125
|
+
return "removed";
|
|
126
|
+
}
|
|
127
|
+
const before = lines.slice(0, startIdx);
|
|
128
|
+
const after = lines.slice(endIdx + 1);
|
|
129
|
+
if (before.length > 0 && after.length > 0 && before[before.length - 1].trim() === "" && after[0].trim() === "") {
|
|
130
|
+
after.shift();
|
|
131
|
+
}
|
|
132
|
+
const merged = [...before, ...after];
|
|
133
|
+
const newContent = merged.length > 0 ? merged.join(nl) + nl : "";
|
|
134
|
+
if (!dryRun) {
|
|
135
|
+
await writeFile(gitignorePath, newContent, "utf-8");
|
|
136
|
+
}
|
|
137
|
+
return "removed";
|
|
138
|
+
}
|
|
139
|
+
function buildBlock(nl) {
|
|
140
|
+
return GITIGNORE_BLOCK_START + nl + CANONICAL_GITIGNORE_ENTRIES.join(nl) + nl + GITIGNORE_BLOCK_END + nl;
|
|
141
|
+
}
|
|
142
|
+
var CANONICAL_GITIGNORE_ENTRIES, GITIGNORE_BLOCK_START, GITIGNORE_BLOCK_END;
|
|
143
|
+
var init_gitignore_block = __esm({
|
|
144
|
+
"src/lib/gitignore-block.ts"() {
|
|
145
|
+
"use strict";
|
|
146
|
+
CANONICAL_GITIGNORE_ENTRIES = [
|
|
147
|
+
".claude/settings.local.json",
|
|
148
|
+
".claude/scheduled_tasks.lock",
|
|
149
|
+
".codebyplan/device.local.json",
|
|
150
|
+
".codebyplan/statusline.local.json",
|
|
151
|
+
".codebyplan.local.json",
|
|
152
|
+
".mcp.json"
|
|
153
|
+
];
|
|
154
|
+
GITIGNORE_BLOCK_START = "# >>> codebyplan (managed) >>>";
|
|
155
|
+
GITIGNORE_BLOCK_END = "# <<< codebyplan <<<";
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
22
159
|
// src/lib/local-config.ts
|
|
23
160
|
import { execSync } from "node:child_process";
|
|
24
161
|
import { createHash } from "node:crypto";
|
|
25
|
-
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
162
|
+
import { mkdir, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
26
163
|
import { hostname } from "node:os";
|
|
27
|
-
import { dirname, join } from "node:path";
|
|
164
|
+
import { dirname, join as join2 } from "node:path";
|
|
28
165
|
function localConfigPath(projectPath) {
|
|
29
|
-
return
|
|
166
|
+
return join2(projectPath, ".codebyplan", "device.local.json");
|
|
30
167
|
}
|
|
31
168
|
function legacyLocalConfigPath(projectPath) {
|
|
32
|
-
return
|
|
169
|
+
return join2(projectPath, ".codebyplan.local.json");
|
|
33
170
|
}
|
|
34
171
|
async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
35
172
|
try {
|
|
36
|
-
const raw = await
|
|
173
|
+
const raw = await readFile2(localConfigPath(projectPath), "utf-8");
|
|
37
174
|
const parsed = JSON.parse(raw);
|
|
38
175
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
39
176
|
return parsed;
|
|
@@ -68,7 +205,7 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
|
68
205
|
}
|
|
69
206
|
}
|
|
70
207
|
try {
|
|
71
|
-
const raw = await
|
|
208
|
+
const raw = await readFile2(legacyLocalConfigPath(projectPath), "utf-8");
|
|
72
209
|
const parsed = JSON.parse(raw);
|
|
73
210
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
74
211
|
onMigrationNotice?.(
|
|
@@ -88,8 +225,8 @@ async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
|
88
225
|
}
|
|
89
226
|
async function writeLocalConfig(projectPath, config) {
|
|
90
227
|
const content = { device_id: config.device_id };
|
|
91
|
-
const
|
|
92
|
-
const dirPath = dirname(
|
|
228
|
+
const path7 = localConfigPath(projectPath);
|
|
229
|
+
const dirPath = dirname(path7);
|
|
93
230
|
let phase = "stat config directory";
|
|
94
231
|
try {
|
|
95
232
|
try {
|
|
@@ -109,7 +246,7 @@ async function writeLocalConfig(projectPath, config) {
|
|
|
109
246
|
phase = "create config directory";
|
|
110
247
|
await mkdir(dirPath, { recursive: true });
|
|
111
248
|
phase = "write local config";
|
|
112
|
-
await
|
|
249
|
+
await writeFile2(path7, JSON.stringify(content, null, 2) + "\n", "utf-8");
|
|
113
250
|
} catch (err) {
|
|
114
251
|
const code = err.code;
|
|
115
252
|
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
@@ -121,7 +258,7 @@ async function writeLocalConfig(projectPath, config) {
|
|
|
121
258
|
}
|
|
122
259
|
async function resolveMachineSeed() {
|
|
123
260
|
try {
|
|
124
|
-
const raw = await
|
|
261
|
+
const raw = await readFile2("/etc/machine-id", "utf-8");
|
|
125
262
|
const trimmed = raw.trim();
|
|
126
263
|
if (trimmed) return trimmed;
|
|
127
264
|
} catch {
|
|
@@ -153,13 +290,13 @@ var init_local_config = __esm({
|
|
|
153
290
|
|
|
154
291
|
// src/lib/statusline-config.ts
|
|
155
292
|
import { spawnSync } from "node:child_process";
|
|
156
|
-
import { mkdir as mkdir2, readFile as
|
|
157
|
-
import { join as
|
|
293
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
294
|
+
import { join as join3 } from "node:path";
|
|
158
295
|
function statuslineConfigPath(projectPath) {
|
|
159
|
-
return
|
|
296
|
+
return join3(projectPath, ".codebyplan", "statusline.json");
|
|
160
297
|
}
|
|
161
298
|
function statuslineLocalConfigPath(projectPath) {
|
|
162
|
-
return
|
|
299
|
+
return join3(projectPath, ".codebyplan", "statusline.local.json");
|
|
163
300
|
}
|
|
164
301
|
function isValidRenderer(value) {
|
|
165
302
|
return typeof value === "string" && VALID_RENDERERS.has(value);
|
|
@@ -188,7 +325,7 @@ function mergeOverDefaults(raw) {
|
|
|
188
325
|
}
|
|
189
326
|
async function readStatuslineConfig(projectPath) {
|
|
190
327
|
try {
|
|
191
|
-
const raw = await
|
|
328
|
+
const raw = await readFile3(statuslineConfigPath(projectPath), "utf-8");
|
|
192
329
|
const parsed = JSON.parse(raw);
|
|
193
330
|
return mergeOverDefaults(parsed);
|
|
194
331
|
} catch {
|
|
@@ -197,7 +334,7 @@ async function readStatuslineConfig(projectPath) {
|
|
|
197
334
|
}
|
|
198
335
|
async function readStatuslineLocalConfig(projectPath) {
|
|
199
336
|
try {
|
|
200
|
-
const raw = await
|
|
337
|
+
const raw = await readFile3(statuslineLocalConfigPath(projectPath), "utf-8");
|
|
201
338
|
const parsed = JSON.parse(raw);
|
|
202
339
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
203
340
|
const obj = parsed;
|
|
@@ -217,8 +354,8 @@ async function writeStatuslineLocalConfig(projectPath, localConfig) {
|
|
|
217
354
|
);
|
|
218
355
|
}
|
|
219
356
|
const filePath = statuslineLocalConfigPath(projectPath);
|
|
220
|
-
await mkdir2(
|
|
221
|
-
await
|
|
357
|
+
await mkdir2(join3(projectPath, ".codebyplan"), { recursive: true });
|
|
358
|
+
await writeFile3(
|
|
222
359
|
filePath,
|
|
223
360
|
JSON.stringify(localConfig, null, 2) + "\n",
|
|
224
361
|
"utf-8"
|
|
@@ -258,7 +395,8 @@ var init_statusline_config = __esm({
|
|
|
258
395
|
cost: true,
|
|
259
396
|
rate_limits: true,
|
|
260
397
|
repo_pr: true,
|
|
261
|
-
worktree: true
|
|
398
|
+
worktree: true,
|
|
399
|
+
infra_drift: true
|
|
262
400
|
},
|
|
263
401
|
no_color: false
|
|
264
402
|
};
|
|
@@ -297,9 +435,9 @@ var init_jwt_decode = __esm({
|
|
|
297
435
|
});
|
|
298
436
|
|
|
299
437
|
// src/oauth/keychain.ts
|
|
300
|
-
import { chmod, mkdir as mkdir3, readFile as
|
|
438
|
+
import { chmod, mkdir as mkdir3, readFile as readFile4, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
301
439
|
import { homedir, platform } from "node:os";
|
|
302
|
-
import { dirname as dirname2, join as
|
|
440
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
303
441
|
async function loadKeyring() {
|
|
304
442
|
if (keyringOverride !== void 0) return keyringOverride;
|
|
305
443
|
try {
|
|
@@ -311,30 +449,30 @@ async function loadKeyring() {
|
|
|
311
449
|
}
|
|
312
450
|
function fallbackDir() {
|
|
313
451
|
if (platform() === "win32") {
|
|
314
|
-
const appData = process.env.APPDATA ??
|
|
315
|
-
return
|
|
452
|
+
const appData = process.env.APPDATA ?? join4(homedir(), "AppData", "Roaming");
|
|
453
|
+
return join4(appData, "codebyplan");
|
|
316
454
|
}
|
|
317
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
318
|
-
return
|
|
455
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join4(homedir(), ".config");
|
|
456
|
+
return join4(xdg, "codebyplan");
|
|
319
457
|
}
|
|
320
458
|
function fallbackFile(filename) {
|
|
321
|
-
return
|
|
459
|
+
return join4(fallbackDir(), filename);
|
|
322
460
|
}
|
|
323
461
|
async function readFallback(filename) {
|
|
324
462
|
try {
|
|
325
|
-
const raw = await
|
|
463
|
+
const raw = await readFile4(fallbackFile(filename), "utf-8");
|
|
326
464
|
return JSON.parse(raw);
|
|
327
465
|
} catch {
|
|
328
466
|
return null;
|
|
329
467
|
}
|
|
330
468
|
}
|
|
331
469
|
async function writeFallback(filename, data) {
|
|
332
|
-
const
|
|
333
|
-
await mkdir3(dirname2(
|
|
334
|
-
await
|
|
470
|
+
const path7 = fallbackFile(filename);
|
|
471
|
+
await mkdir3(dirname2(path7), { recursive: true });
|
|
472
|
+
await writeFile4(path7, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
335
473
|
if (platform() !== "win32") {
|
|
336
474
|
try {
|
|
337
|
-
await chmod(
|
|
475
|
+
await chmod(path7, 384);
|
|
338
476
|
} catch {
|
|
339
477
|
}
|
|
340
478
|
}
|
|
@@ -541,8 +679,8 @@ async function getAuthHeaders() {
|
|
|
541
679
|
return { headers: { "x-api-key": key }, via: "api_key" };
|
|
542
680
|
}
|
|
543
681
|
}
|
|
544
|
-
function buildUrl(
|
|
545
|
-
const url = new URL(`${baseUrl()}/api${
|
|
682
|
+
function buildUrl(path7, params) {
|
|
683
|
+
const url = new URL(`${baseUrl()}/api${path7}`);
|
|
546
684
|
if (params) {
|
|
547
685
|
for (const [key, value] of Object.entries(params)) {
|
|
548
686
|
if (value !== void 0) {
|
|
@@ -561,8 +699,8 @@ function isRetryable(err) {
|
|
|
561
699
|
function delay(ms) {
|
|
562
700
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
563
701
|
}
|
|
564
|
-
async function request(method,
|
|
565
|
-
const url = buildUrl(
|
|
702
|
+
async function request(method, path7, options) {
|
|
703
|
+
const url = buildUrl(path7, options?.params);
|
|
566
704
|
const auth = await getAuthHeaders();
|
|
567
705
|
let lastError;
|
|
568
706
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -582,7 +720,7 @@ async function request(method, path6, options) {
|
|
|
582
720
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
583
721
|
});
|
|
584
722
|
if (!res.ok) {
|
|
585
|
-
let message = `API ${method} ${
|
|
723
|
+
let message = `API ${method} ${path7} failed with status ${res.status}`;
|
|
586
724
|
let code;
|
|
587
725
|
try {
|
|
588
726
|
const body = await res.json();
|
|
@@ -616,14 +754,14 @@ async function request(method, path6, options) {
|
|
|
616
754
|
}
|
|
617
755
|
throw lastError;
|
|
618
756
|
}
|
|
619
|
-
async function apiGet(
|
|
620
|
-
return request("GET",
|
|
757
|
+
async function apiGet(path7, params) {
|
|
758
|
+
return request("GET", path7, { params });
|
|
621
759
|
}
|
|
622
|
-
async function apiPost(
|
|
623
|
-
return request("POST",
|
|
760
|
+
async function apiPost(path7, body) {
|
|
761
|
+
return request("POST", path7, { body });
|
|
624
762
|
}
|
|
625
|
-
async function apiPut(
|
|
626
|
-
return request("PUT",
|
|
763
|
+
async function apiPut(path7, body) {
|
|
764
|
+
return request("PUT", path7, { body });
|
|
627
765
|
}
|
|
628
766
|
async function callMcpTool(toolName, params) {
|
|
629
767
|
const url = mcpEndpoint();
|
|
@@ -682,8 +820,8 @@ var init_api = __esm({
|
|
|
682
820
|
});
|
|
683
821
|
|
|
684
822
|
// src/lib/resolve-worktree.ts
|
|
685
|
-
import { readFile as
|
|
686
|
-
import { join as
|
|
823
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
824
|
+
import { join as join5 } from "node:path";
|
|
687
825
|
async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
688
826
|
let worktreeId;
|
|
689
827
|
try {
|
|
@@ -705,10 +843,10 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
705
843
|
if (options?.skipWrite) {
|
|
706
844
|
return worktreeId;
|
|
707
845
|
}
|
|
708
|
-
const codebyplanPath =
|
|
846
|
+
const codebyplanPath = join5(projectPath, ".codebyplan.json");
|
|
709
847
|
let currentConfig = {};
|
|
710
848
|
try {
|
|
711
|
-
const raw = await
|
|
849
|
+
const raw = await readFile5(codebyplanPath, "utf-8");
|
|
712
850
|
const parsed = JSON.parse(raw);
|
|
713
851
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
714
852
|
currentConfig = parsed;
|
|
@@ -723,7 +861,7 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
723
861
|
worktree_id: worktreeId
|
|
724
862
|
};
|
|
725
863
|
try {
|
|
726
|
-
await
|
|
864
|
+
await writeFile5(
|
|
727
865
|
codebyplanPath,
|
|
728
866
|
JSON.stringify(merged, null, 2) + "\n",
|
|
729
867
|
"utf-8"
|
|
@@ -880,7 +1018,7 @@ async function pollOnce(deviceCode, clientId) {
|
|
|
880
1018
|
return { kind: "error", message: description };
|
|
881
1019
|
}
|
|
882
1020
|
async function pollUntilSettled(opts) {
|
|
883
|
-
const
|
|
1021
|
+
const sleep2 = opts.sleep ?? defaultSleep;
|
|
884
1022
|
const now = opts.now ?? Date.now;
|
|
885
1023
|
const startMs = now();
|
|
886
1024
|
const expiresAtMs = startMs + opts.expiresInSec * 1e3;
|
|
@@ -888,7 +1026,7 @@ async function pollUntilSettled(opts) {
|
|
|
888
1026
|
const outcome = await pollOnce(opts.deviceCode, opts.clientId);
|
|
889
1027
|
if (outcome.kind !== "pending") return outcome;
|
|
890
1028
|
if (opts.onTick) opts.onTick(Math.floor((now() - startMs) / 1e3));
|
|
891
|
-
await
|
|
1029
|
+
await sleep2(opts.intervalSec * 1e3);
|
|
892
1030
|
}
|
|
893
1031
|
return { kind: "expired" };
|
|
894
1032
|
}
|
|
@@ -1057,21 +1195,21 @@ var setup_exports = {};
|
|
|
1057
1195
|
__export(setup_exports, {
|
|
1058
1196
|
runSetup: () => runSetup
|
|
1059
1197
|
});
|
|
1060
|
-
import { mkdir as mkdir4, readFile as
|
|
1198
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
|
|
1061
1199
|
import { homedir as homedir2 } from "node:os";
|
|
1062
|
-
import { join as
|
|
1200
|
+
import { join as join6 } from "node:path";
|
|
1063
1201
|
import { stdin, stdout as stdout2 } from "node:process";
|
|
1064
1202
|
import { createInterface } from "node:readline/promises";
|
|
1065
1203
|
function getConfigPath(scope) {
|
|
1066
|
-
return scope === "user" ?
|
|
1204
|
+
return scope === "user" ? join6(homedir2(), ".claude.json") : join6(process.cwd(), ".mcp.json");
|
|
1067
1205
|
}
|
|
1068
1206
|
function legacyMcpUrl() {
|
|
1069
1207
|
const baseUrl2 = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
1070
1208
|
return `${baseUrl2.replace(/\/$/, "")}/mcp`;
|
|
1071
1209
|
}
|
|
1072
|
-
async function readConfig(
|
|
1210
|
+
async function readConfig(path7) {
|
|
1073
1211
|
try {
|
|
1074
|
-
const raw = await
|
|
1212
|
+
const raw = await readFile6(path7, "utf-8");
|
|
1075
1213
|
const parsed = JSON.parse(raw);
|
|
1076
1214
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1077
1215
|
return parsed;
|
|
@@ -1094,7 +1232,7 @@ async function writeMcpConfig(scope, auth) {
|
|
|
1094
1232
|
config.mcpServers = {};
|
|
1095
1233
|
}
|
|
1096
1234
|
config.mcpServers.codebyplan = buildMcpEntry(auth);
|
|
1097
|
-
await
|
|
1235
|
+
await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1098
1236
|
return configPath;
|
|
1099
1237
|
}
|
|
1100
1238
|
async function fetchRepos(auth) {
|
|
@@ -1149,7 +1287,7 @@ async function chooseAuthMode(rl) {
|
|
|
1149
1287
|
return { kind: "legacy", apiKey };
|
|
1150
1288
|
}
|
|
1151
1289
|
async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
1152
|
-
const codebyplanDir =
|
|
1290
|
+
const codebyplanDir = join6(projectPath, ".codebyplan");
|
|
1153
1291
|
await mkdir4(codebyplanDir, { recursive: true });
|
|
1154
1292
|
const repoJson = {
|
|
1155
1293
|
repo_id: selectedRepo.id
|
|
@@ -1161,13 +1299,13 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1161
1299
|
if (typeof repoAny.project_id === "string") {
|
|
1162
1300
|
repoJson.project_id = repoAny.project_id;
|
|
1163
1301
|
}
|
|
1164
|
-
await
|
|
1165
|
-
|
|
1302
|
+
await writeFile6(
|
|
1303
|
+
join6(codebyplanDir, "repo.json"),
|
|
1166
1304
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
1167
1305
|
"utf-8"
|
|
1168
1306
|
);
|
|
1169
|
-
await
|
|
1170
|
-
|
|
1307
|
+
await writeFile6(
|
|
1308
|
+
join6(codebyplanDir, "server.json"),
|
|
1171
1309
|
JSON.stringify(
|
|
1172
1310
|
{
|
|
1173
1311
|
server_port: null,
|
|
@@ -1180,30 +1318,35 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1180
1318
|
) + "\n",
|
|
1181
1319
|
"utf-8"
|
|
1182
1320
|
);
|
|
1183
|
-
await
|
|
1184
|
-
|
|
1321
|
+
await writeFile6(
|
|
1322
|
+
join6(codebyplanDir, "git.json"),
|
|
1185
1323
|
JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
|
|
1186
1324
|
"utf-8"
|
|
1187
1325
|
);
|
|
1188
|
-
await
|
|
1189
|
-
|
|
1326
|
+
await writeFile6(
|
|
1327
|
+
join6(codebyplanDir, "shipment.json"),
|
|
1190
1328
|
JSON.stringify({ shipment: null }, null, 2) + "\n",
|
|
1191
1329
|
"utf-8"
|
|
1192
1330
|
);
|
|
1193
|
-
await
|
|
1194
|
-
|
|
1331
|
+
await writeFile6(
|
|
1332
|
+
join6(codebyplanDir, "vendor.json"),
|
|
1195
1333
|
JSON.stringify({}, null, 2) + "\n",
|
|
1196
1334
|
"utf-8"
|
|
1197
1335
|
);
|
|
1198
|
-
|
|
1336
|
+
await writeFile6(
|
|
1337
|
+
join6(codebyplanDir, "e2e.json"),
|
|
1338
|
+
JSON.stringify({}, null, 2) + "\n",
|
|
1339
|
+
"utf-8"
|
|
1340
|
+
);
|
|
1341
|
+
const statuslinePath = join6(codebyplanDir, "statusline.json");
|
|
1199
1342
|
let statuslineExists = false;
|
|
1200
1343
|
try {
|
|
1201
|
-
await
|
|
1344
|
+
await readFile6(statuslinePath, "utf-8");
|
|
1202
1345
|
statuslineExists = true;
|
|
1203
1346
|
} catch {
|
|
1204
1347
|
}
|
|
1205
1348
|
if (!statuslineExists) {
|
|
1206
|
-
await
|
|
1349
|
+
await writeFile6(
|
|
1207
1350
|
statuslinePath,
|
|
1208
1351
|
JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
|
|
1209
1352
|
"utf-8"
|
|
@@ -1212,36 +1355,12 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
1212
1355
|
await writeLocalConfig(projectPath, { device_id: deviceId });
|
|
1213
1356
|
console.log(` Created ${codebyplanDir}/`);
|
|
1214
1357
|
console.log(
|
|
1215
|
-
` repo.json, server.json, git.json, shipment.json, vendor.json, statusline.json`
|
|
1358
|
+
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, statusline.json`
|
|
1216
1359
|
);
|
|
1217
1360
|
console.log(` device.local.json (gitignored)`);
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
try {
|
|
1222
|
-
const existing = await readFile5(gitignorePath, "utf-8");
|
|
1223
|
-
const lines = existing.split("\n");
|
|
1224
|
-
let content = existing;
|
|
1225
|
-
if (!lines.some((l) => l.trimEnd() === gitignoreEntry)) {
|
|
1226
|
-
content = (content.endsWith("\n") ? content : content + "\n") + gitignoreEntry + "\n";
|
|
1227
|
-
console.log(` Added '${gitignoreEntry}' to .gitignore`);
|
|
1228
|
-
}
|
|
1229
|
-
if (!lines.some((l) => l.trimEnd() === statuslineLocalEntry)) {
|
|
1230
|
-
content = (content.endsWith("\n") ? content : content + "\n") + statuslineLocalEntry + "\n";
|
|
1231
|
-
console.log(` Added '${statuslineLocalEntry}' to .gitignore`);
|
|
1232
|
-
}
|
|
1233
|
-
if (content !== existing) {
|
|
1234
|
-
await writeFile5(gitignorePath, content, "utf-8");
|
|
1235
|
-
}
|
|
1236
|
-
} catch {
|
|
1237
|
-
await writeFile5(
|
|
1238
|
-
gitignorePath,
|
|
1239
|
-
gitignoreEntry + "\n" + statuslineLocalEntry + "\n",
|
|
1240
|
-
"utf-8"
|
|
1241
|
-
);
|
|
1242
|
-
console.log(
|
|
1243
|
-
` Created .gitignore with '${gitignoreEntry}' and '${statuslineLocalEntry}'`
|
|
1244
|
-
);
|
|
1361
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
|
|
1362
|
+
if (gitignoreAction !== "unchanged") {
|
|
1363
|
+
console.log(" Updated .gitignore (codebyplan managed block)");
|
|
1245
1364
|
}
|
|
1246
1365
|
}
|
|
1247
1366
|
async function runSetup() {
|
|
@@ -1372,6 +1491,7 @@ async function runSetup() {
|
|
|
1372
1491
|
var init_setup = __esm({
|
|
1373
1492
|
"src/cli/setup.ts"() {
|
|
1374
1493
|
"use strict";
|
|
1494
|
+
init_gitignore_block();
|
|
1375
1495
|
init_local_config();
|
|
1376
1496
|
init_statusline_config();
|
|
1377
1497
|
init_resolve_worktree();
|
|
@@ -1382,21 +1502,21 @@ var init_setup = __esm({
|
|
|
1382
1502
|
});
|
|
1383
1503
|
|
|
1384
1504
|
// src/lib/flags.ts
|
|
1385
|
-
import { readFile as
|
|
1386
|
-
import { join as
|
|
1505
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
1506
|
+
import { join as join7, resolve } from "node:path";
|
|
1387
1507
|
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
1388
1508
|
let cursor = resolve(startDir);
|
|
1389
1509
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1390
|
-
const sentinelPath2 =
|
|
1510
|
+
const sentinelPath2 = join7(cursor, ".codebyplan", "repo.json");
|
|
1391
1511
|
try {
|
|
1392
|
-
const raw = await
|
|
1512
|
+
const raw = await readFile7(sentinelPath2, "utf-8");
|
|
1393
1513
|
const parsed = JSON.parse(raw);
|
|
1394
1514
|
return { path: sentinelPath2, contents: parsed };
|
|
1395
1515
|
} catch {
|
|
1396
1516
|
}
|
|
1397
|
-
const legacyPath =
|
|
1517
|
+
const legacyPath = join7(cursor, ".codebyplan.json");
|
|
1398
1518
|
try {
|
|
1399
|
-
const raw = await
|
|
1519
|
+
const raw = await readFile7(legacyPath, "utf-8");
|
|
1400
1520
|
const parsed = JSON.parse(raw);
|
|
1401
1521
|
return { path: legacyPath, contents: parsed };
|
|
1402
1522
|
} catch {
|
|
@@ -1623,15 +1743,15 @@ var upgrade_auth_exports = {};
|
|
|
1623
1743
|
__export(upgrade_auth_exports, {
|
|
1624
1744
|
runUpgradeAuth: () => runUpgradeAuth
|
|
1625
1745
|
});
|
|
1626
|
-
import { readFile as
|
|
1746
|
+
import { readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
1627
1747
|
import { homedir as homedir3 } from "node:os";
|
|
1628
|
-
import { join as
|
|
1748
|
+
import { join as join8 } from "node:path";
|
|
1629
1749
|
function configPaths() {
|
|
1630
|
-
return [
|
|
1750
|
+
return [join8(homedir3(), ".claude.json"), join8(process.cwd(), ".mcp.json")];
|
|
1631
1751
|
}
|
|
1632
|
-
async function readConfig2(
|
|
1752
|
+
async function readConfig2(path7) {
|
|
1633
1753
|
try {
|
|
1634
|
-
const raw = await
|
|
1754
|
+
const raw = await readFile8(path7, "utf-8");
|
|
1635
1755
|
const parsed = JSON.parse(raw);
|
|
1636
1756
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1637
1757
|
return parsed;
|
|
@@ -1645,14 +1765,14 @@ function entryHasLegacyApiKey(entry) {
|
|
|
1645
1765
|
if (!entry || !entry.headers) return false;
|
|
1646
1766
|
return "x-api-key" in entry.headers;
|
|
1647
1767
|
}
|
|
1648
|
-
async function rewriteConfig(
|
|
1768
|
+
async function rewriteConfig(path7, config, newUrl) {
|
|
1649
1769
|
const servers = config.mcpServers;
|
|
1650
1770
|
if (!servers) return false;
|
|
1651
1771
|
const entry = servers.codebyplan;
|
|
1652
1772
|
if (!entry) return false;
|
|
1653
1773
|
if (!entryHasLegacyApiKey(entry) && entry.url === newUrl) return false;
|
|
1654
1774
|
servers.codebyplan = { url: newUrl };
|
|
1655
|
-
await
|
|
1775
|
+
await writeFile7(path7, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1656
1776
|
return true;
|
|
1657
1777
|
}
|
|
1658
1778
|
async function runUpgradeAuth() {
|
|
@@ -1660,12 +1780,12 @@ async function runUpgradeAuth() {
|
|
|
1660
1780
|
await runLogin();
|
|
1661
1781
|
const newUrl = mcpEndpoint();
|
|
1662
1782
|
let migrated = 0;
|
|
1663
|
-
for (const
|
|
1664
|
-
const config = await readConfig2(
|
|
1783
|
+
for (const path7 of configPaths()) {
|
|
1784
|
+
const config = await readConfig2(path7);
|
|
1665
1785
|
if (!config) continue;
|
|
1666
|
-
const changed = await rewriteConfig(
|
|
1786
|
+
const changed = await rewriteConfig(path7, config, newUrl);
|
|
1667
1787
|
if (changed) {
|
|
1668
|
-
console.log(` Updated ${
|
|
1788
|
+
console.log(` Updated ${path7}`);
|
|
1669
1789
|
migrated++;
|
|
1670
1790
|
}
|
|
1671
1791
|
}
|
|
@@ -1733,8 +1853,8 @@ var init_confirm = __esm({
|
|
|
1733
1853
|
});
|
|
1734
1854
|
|
|
1735
1855
|
// src/lib/tech-detect.ts
|
|
1736
|
-
import { readFile as
|
|
1737
|
-
import { join as
|
|
1856
|
+
import { readFile as readFile9, access, readdir } from "node:fs/promises";
|
|
1857
|
+
import { join as join9, relative } from "node:path";
|
|
1738
1858
|
async function fileExists(filePath) {
|
|
1739
1859
|
try {
|
|
1740
1860
|
await access(filePath);
|
|
@@ -1747,8 +1867,8 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1747
1867
|
const apps = [];
|
|
1748
1868
|
const patterns = [];
|
|
1749
1869
|
try {
|
|
1750
|
-
const raw = await
|
|
1751
|
-
|
|
1870
|
+
const raw = await readFile9(
|
|
1871
|
+
join9(projectPath, "pnpm-workspace.yaml"),
|
|
1752
1872
|
"utf-8"
|
|
1753
1873
|
);
|
|
1754
1874
|
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
@@ -1762,7 +1882,7 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1762
1882
|
}
|
|
1763
1883
|
if (patterns.length === 0) {
|
|
1764
1884
|
try {
|
|
1765
|
-
const raw = await
|
|
1885
|
+
const raw = await readFile9(join9(projectPath, "package.json"), "utf-8");
|
|
1766
1886
|
const pkg = JSON.parse(raw);
|
|
1767
1887
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1768
1888
|
if (ws) patterns.push(...ws);
|
|
@@ -1772,14 +1892,14 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1772
1892
|
for (const pattern of patterns) {
|
|
1773
1893
|
if (pattern.endsWith("/*")) {
|
|
1774
1894
|
const dir = pattern.slice(0, -2);
|
|
1775
|
-
const absDir =
|
|
1895
|
+
const absDir = join9(projectPath, dir);
|
|
1776
1896
|
try {
|
|
1777
1897
|
const entries = await readdir(absDir, { withFileTypes: true });
|
|
1778
1898
|
for (const entry of entries) {
|
|
1779
1899
|
if (entry.isDirectory()) {
|
|
1780
|
-
const relPath =
|
|
1781
|
-
const absPath =
|
|
1782
|
-
if (await fileExists(
|
|
1900
|
+
const relPath = join9(dir, entry.name);
|
|
1901
|
+
const absPath = join9(absDir, entry.name);
|
|
1902
|
+
if (await fileExists(join9(absPath, "package.json"))) {
|
|
1783
1903
|
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1784
1904
|
}
|
|
1785
1905
|
}
|
|
@@ -1798,7 +1918,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1798
1918
|
const name = entry.name;
|
|
1799
1919
|
if (entry.isDirectory()) {
|
|
1800
1920
|
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1801
|
-
if (await hasJsxFile(
|
|
1921
|
+
if (await hasJsxFile(join9(dir, name), depth + 1)) return true;
|
|
1802
1922
|
} else if (entry.isFile()) {
|
|
1803
1923
|
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1804
1924
|
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
@@ -1817,7 +1937,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1817
1937
|
async function detectCapabilities(dirPath, pkgJson) {
|
|
1818
1938
|
const caps = /* @__PURE__ */ new Set();
|
|
1819
1939
|
for (const sub of JSX_SCAN_DIRS) {
|
|
1820
|
-
if (await hasJsxFile(
|
|
1940
|
+
if (await hasJsxFile(join9(dirPath, sub))) {
|
|
1821
1941
|
caps.add("jsx");
|
|
1822
1942
|
break;
|
|
1823
1943
|
}
|
|
@@ -1839,7 +1959,7 @@ async function detectCapabilities(dirPath, pkgJson) {
|
|
|
1839
1959
|
}
|
|
1840
1960
|
}
|
|
1841
1961
|
}
|
|
1842
|
-
if (!caps.has("node-server") && await fileExists(
|
|
1962
|
+
if (!caps.has("node-server") && await fileExists(join9(dirPath, "src", "main.ts"))) {
|
|
1843
1963
|
caps.add("node-server");
|
|
1844
1964
|
}
|
|
1845
1965
|
if (pkgJson && pkgJson.bin) {
|
|
@@ -1855,7 +1975,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1855
1975
|
const seen = /* @__PURE__ */ new Map();
|
|
1856
1976
|
let pkgJson = null;
|
|
1857
1977
|
try {
|
|
1858
|
-
const raw = await
|
|
1978
|
+
const raw = await readFile9(join9(dirPath, "package.json"), "utf-8");
|
|
1859
1979
|
pkgJson = JSON.parse(raw);
|
|
1860
1980
|
const allDeps = {
|
|
1861
1981
|
...pkgJson.dependencies ?? {},
|
|
@@ -1887,7 +2007,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1887
2007
|
}
|
|
1888
2008
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1889
2009
|
const key = rule.name.toLowerCase();
|
|
1890
|
-
if (!seen.has(key) && await fileExists(
|
|
2010
|
+
if (!seen.has(key) && await fileExists(join9(dirPath, file))) {
|
|
1891
2011
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1892
2012
|
}
|
|
1893
2013
|
}
|
|
@@ -2065,7 +2185,7 @@ function categorizeDependency(depName) {
|
|
|
2065
2185
|
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
2066
2186
|
if (depth > 4) return [];
|
|
2067
2187
|
const results = [];
|
|
2068
|
-
const pkgPath =
|
|
2188
|
+
const pkgPath = join9(dir, "package.json");
|
|
2069
2189
|
if (await fileExists(pkgPath)) {
|
|
2070
2190
|
results.push(pkgPath);
|
|
2071
2191
|
}
|
|
@@ -2074,7 +2194,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
|
2074
2194
|
for (const entry of entries) {
|
|
2075
2195
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
2076
2196
|
const subResults = await findPackageJsonFiles(
|
|
2077
|
-
|
|
2197
|
+
join9(dir, entry.name),
|
|
2078
2198
|
projectPath,
|
|
2079
2199
|
depth + 1
|
|
2080
2200
|
);
|
|
@@ -2089,7 +2209,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
2089
2209
|
const dependencies = [];
|
|
2090
2210
|
for (const pkgPath of packageJsonPaths) {
|
|
2091
2211
|
try {
|
|
2092
|
-
const raw = await
|
|
2212
|
+
const raw = await readFile9(pkgPath, "utf-8");
|
|
2093
2213
|
const pkg = JSON.parse(raw);
|
|
2094
2214
|
const sourcePath = relative(projectPath, pkgPath);
|
|
2095
2215
|
const depSections = [
|
|
@@ -2161,6 +2281,9 @@ var init_tech_detect = __esm({
|
|
|
2161
2281
|
"@playwright/test": { name: "Playwright", category: "testing" },
|
|
2162
2282
|
cypress: { name: "Cypress", category: "testing" },
|
|
2163
2283
|
supertest: { name: "Supertest", category: "testing" },
|
|
2284
|
+
webdriverio: { name: "WebdriverIO", category: "testing" },
|
|
2285
|
+
"@wdio/cli": { name: "WebdriverIO", category: "testing" },
|
|
2286
|
+
"@vscode/test-cli": { name: "VS Code Test CLI", category: "testing" },
|
|
2164
2287
|
// Build tools
|
|
2165
2288
|
turbo: { name: "Turborepo", category: "build" },
|
|
2166
2289
|
vite: { name: "Vite", category: "build" },
|
|
@@ -2245,7 +2368,28 @@ var init_tech_detect = __esm({
|
|
|
2245
2368
|
rule: { name: "shadcn/ui", category: "component-lib" }
|
|
2246
2369
|
},
|
|
2247
2370
|
{ file: "nx.json", rule: { name: "Nx", category: "build" } },
|
|
2248
|
-
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } }
|
|
2371
|
+
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } },
|
|
2372
|
+
{
|
|
2373
|
+
file: "playwright.config.ts",
|
|
2374
|
+
rule: { name: "Playwright", category: "testing" }
|
|
2375
|
+
},
|
|
2376
|
+
{
|
|
2377
|
+
file: "playwright.config.js",
|
|
2378
|
+
rule: { name: "Playwright", category: "testing" }
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
file: "playwright.config.mjs",
|
|
2382
|
+
rule: { name: "Playwright", category: "testing" }
|
|
2383
|
+
},
|
|
2384
|
+
{ file: "wdio.conf.ts", rule: { name: "WebdriverIO", category: "testing" } },
|
|
2385
|
+
{
|
|
2386
|
+
file: "wdio.conf.js",
|
|
2387
|
+
rule: { name: "WebdriverIO", category: "testing" }
|
|
2388
|
+
},
|
|
2389
|
+
{
|
|
2390
|
+
file: "maestro/config.yaml",
|
|
2391
|
+
rule: { name: "Maestro", category: "testing" }
|
|
2392
|
+
}
|
|
2249
2393
|
];
|
|
2250
2394
|
SYNTHETIC_CARRIER_NAME = "__capabilities__";
|
|
2251
2395
|
CAPABILITY_BEARER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -2689,8 +2833,8 @@ __export(eslint_exports, {
|
|
|
2689
2833
|
eslintInit: () => eslintInit,
|
|
2690
2834
|
runEslint: () => runEslint
|
|
2691
2835
|
});
|
|
2692
|
-
import { readFile as
|
|
2693
|
-
import { join as
|
|
2836
|
+
import { readFile as readFile10, writeFile as writeFile8, access as access2, readdir as readdir2 } from "node:fs/promises";
|
|
2837
|
+
import { join as join10, relative as relative2 } from "node:path";
|
|
2694
2838
|
async function fileExists2(filePath) {
|
|
2695
2839
|
try {
|
|
2696
2840
|
await access2(filePath);
|
|
@@ -2701,7 +2845,7 @@ async function fileExists2(filePath) {
|
|
|
2701
2845
|
}
|
|
2702
2846
|
async function autoDetectIgnorePatterns(absPath) {
|
|
2703
2847
|
const patterns = [];
|
|
2704
|
-
if (await fileExists2(
|
|
2848
|
+
if (await fileExists2(join10(absPath, "esbuild.js"))) {
|
|
2705
2849
|
patterns.push("esbuild.js");
|
|
2706
2850
|
}
|
|
2707
2851
|
let entries = [];
|
|
@@ -2721,19 +2865,19 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2721
2865
|
}
|
|
2722
2866
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2723
2867
|
const candidate = `vitest.config.${ext}`;
|
|
2724
|
-
if (await fileExists2(
|
|
2868
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2725
2869
|
patterns.push(candidate);
|
|
2726
2870
|
break;
|
|
2727
2871
|
}
|
|
2728
2872
|
}
|
|
2729
2873
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2730
2874
|
const candidate = `vite.config.${ext}`;
|
|
2731
|
-
if (await fileExists2(
|
|
2875
|
+
if (await fileExists2(join10(absPath, candidate))) {
|
|
2732
2876
|
patterns.push(candidate);
|
|
2733
2877
|
break;
|
|
2734
2878
|
}
|
|
2735
2879
|
}
|
|
2736
|
-
if (await fileExists2(
|
|
2880
|
+
if (await fileExists2(join10(absPath, "tauri.conf.json"))) {
|
|
2737
2881
|
patterns.push("src-tauri/**");
|
|
2738
2882
|
patterns.push("**/*.d.ts");
|
|
2739
2883
|
}
|
|
@@ -2741,14 +2885,14 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2741
2885
|
}
|
|
2742
2886
|
function detectPackageManager(projectPath) {
|
|
2743
2887
|
return (async () => {
|
|
2744
|
-
if (await fileExists2(
|
|
2745
|
-
if (await fileExists2(
|
|
2888
|
+
if (await fileExists2(join10(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2889
|
+
if (await fileExists2(join10(projectPath, "yarn.lock"))) return "yarn";
|
|
2746
2890
|
return "npm";
|
|
2747
2891
|
})();
|
|
2748
2892
|
}
|
|
2749
2893
|
async function getInstalledDeps(pkgJsonPath) {
|
|
2750
2894
|
try {
|
|
2751
|
-
const raw = await
|
|
2895
|
+
const raw = await readFile10(pkgJsonPath, "utf-8");
|
|
2752
2896
|
const pkg = JSON.parse(raw);
|
|
2753
2897
|
const all = /* @__PURE__ */ new Set();
|
|
2754
2898
|
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
@@ -2861,7 +3005,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2861
3005
|
ignorePatterns: detectedIgnores
|
|
2862
3006
|
});
|
|
2863
3007
|
const hash = hashConfig(content);
|
|
2864
|
-
const configPath =
|
|
3008
|
+
const configPath = join10(target.absPath, "eslint.config.mjs");
|
|
2865
3009
|
configsToWrite.push({
|
|
2866
3010
|
target,
|
|
2867
3011
|
presets,
|
|
@@ -2883,11 +3027,11 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2883
3027
|
return;
|
|
2884
3028
|
}
|
|
2885
3029
|
const pm = await detectPackageManager(projectPath);
|
|
2886
|
-
const rootPkgJsonPath =
|
|
3030
|
+
const rootPkgJsonPath = join10(projectPath, "package.json");
|
|
2887
3031
|
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2888
3032
|
if (isMonorepo) {
|
|
2889
3033
|
for (const { target } of configsToWrite) {
|
|
2890
|
-
const appPkgJson =
|
|
3034
|
+
const appPkgJson = join10(target.absPath, "package.json");
|
|
2891
3035
|
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2892
3036
|
for (const dep of appDeps) {
|
|
2893
3037
|
installed.add(dep);
|
|
@@ -2939,7 +3083,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2939
3083
|
} of configsToWrite) {
|
|
2940
3084
|
if (await fileExists2(configPath)) {
|
|
2941
3085
|
try {
|
|
2942
|
-
const existing = await
|
|
3086
|
+
const existing = await readFile10(configPath, "utf-8");
|
|
2943
3087
|
const existingHash = hashConfig(existing);
|
|
2944
3088
|
if (existingHash === hash) {
|
|
2945
3089
|
console.log(
|
|
@@ -2959,7 +3103,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2959
3103
|
}
|
|
2960
3104
|
}
|
|
2961
3105
|
try {
|
|
2962
|
-
await
|
|
3106
|
+
await writeFile8(configPath, content, "utf-8");
|
|
2963
3107
|
} catch (err) {
|
|
2964
3108
|
console.error(
|
|
2965
3109
|
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3263,12 +3407,48 @@ var init_sync_approvals = __esm({
|
|
|
3263
3407
|
// src/cli/round.ts
|
|
3264
3408
|
var round_exports = {};
|
|
3265
3409
|
__export(round_exports, {
|
|
3410
|
+
RETRY_DELAY_MS: () => RETRY_DELAY_MS,
|
|
3411
|
+
fetchRoundsWithRetry: () => fetchRoundsWithRetry,
|
|
3412
|
+
isTransientMcpError: () => isTransientMcpError,
|
|
3266
3413
|
runRoundCommand: () => runRoundCommand,
|
|
3267
|
-
runRoundSyncApprovals: () => runRoundSyncApprovals
|
|
3414
|
+
runRoundSyncApprovals: () => runRoundSyncApprovals,
|
|
3415
|
+
setRetryDelayMs: () => setRetryDelayMs
|
|
3268
3416
|
});
|
|
3269
3417
|
import { access as access3 } from "node:fs/promises";
|
|
3270
|
-
import { join as
|
|
3418
|
+
import { join as join11 } from "node:path";
|
|
3271
3419
|
import { execSync as execSync2 } from "node:child_process";
|
|
3420
|
+
function setRetryDelayMs(ms) {
|
|
3421
|
+
RETRY_DELAY_MS = ms;
|
|
3422
|
+
}
|
|
3423
|
+
function sleep(ms) {
|
|
3424
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3425
|
+
}
|
|
3426
|
+
function isTransientMcpError(err) {
|
|
3427
|
+
if (!(err instanceof McpError)) return false;
|
|
3428
|
+
if (err.status !== void 0) {
|
|
3429
|
+
return err.status >= 500 && err.status <= 599;
|
|
3430
|
+
}
|
|
3431
|
+
return err.message.toLowerCase().includes("network error");
|
|
3432
|
+
}
|
|
3433
|
+
async function fetchRoundsWithRetry(taskId, options = {}) {
|
|
3434
|
+
const maxRetries = options.retries ?? 2;
|
|
3435
|
+
const delayMs = options.delayMs ?? RETRY_DELAY_MS;
|
|
3436
|
+
let lastErr;
|
|
3437
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3438
|
+
try {
|
|
3439
|
+
return await mcpCall("get_rounds", { task_id: taskId });
|
|
3440
|
+
} catch (err) {
|
|
3441
|
+
if (!isTransientMcpError(err)) {
|
|
3442
|
+
throw err;
|
|
3443
|
+
}
|
|
3444
|
+
lastErr = err;
|
|
3445
|
+
if (attempt < maxRetries) {
|
|
3446
|
+
await sleep(delayMs);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
throw lastErr;
|
|
3451
|
+
}
|
|
3272
3452
|
async function runRoundCommand(args) {
|
|
3273
3453
|
const subcommand = args[0];
|
|
3274
3454
|
if (subcommand === "sync-approvals") {
|
|
@@ -3330,6 +3510,7 @@ async function runRoundSyncApprovals(args) {
|
|
|
3330
3510
|
);
|
|
3331
3511
|
process.exit(1);
|
|
3332
3512
|
}
|
|
3513
|
+
let skipReason = null;
|
|
3333
3514
|
let stdoutPayload = null;
|
|
3334
3515
|
try {
|
|
3335
3516
|
let callerWorktreeId = flags["worktree-id"];
|
|
@@ -3341,84 +3522,95 @@ async function runRoundSyncApprovals(args) {
|
|
|
3341
3522
|
// Walk up to the directory containing .codebyplan/ or .codebyplan.json
|
|
3342
3523
|
found.path.replace(/\/.codebyplan(\.json|\/repo\.json)$/, "")
|
|
3343
3524
|
) : process.cwd();
|
|
3344
|
-
|
|
3345
|
-
task_id: taskId
|
|
3346
|
-
});
|
|
3347
|
-
const currentRound = rounds.find((r) => r.id === roundId);
|
|
3348
|
-
if (!currentRound) {
|
|
3349
|
-
throw new Error(`Round ${roundId} not found for task ${taskId}`);
|
|
3350
|
-
}
|
|
3351
|
-
const taskResponse = await apiGet(`/tasks/${taskId}`);
|
|
3352
|
-
const currentTask = taskResponse.data;
|
|
3353
|
-
let gitStatusOutput = "";
|
|
3354
|
-
try {
|
|
3355
|
-
gitStatusOutput = execSync2("git status --short --porcelain -z", {
|
|
3356
|
-
cwd: repoRoot,
|
|
3357
|
-
encoding: "utf-8"
|
|
3358
|
-
});
|
|
3359
|
-
} catch {
|
|
3360
|
-
process.stderr.write(
|
|
3361
|
-
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3362
|
-
);
|
|
3363
|
-
}
|
|
3364
|
-
const hookPath = join10(
|
|
3365
|
-
repoRoot,
|
|
3366
|
-
".claude",
|
|
3367
|
-
"hooks",
|
|
3368
|
-
"lint-format-on-edit.sh"
|
|
3369
|
-
);
|
|
3370
|
-
let lintFormatHookExists = false;
|
|
3525
|
+
let rounds;
|
|
3371
3526
|
try {
|
|
3372
|
-
await
|
|
3373
|
-
|
|
3374
|
-
|
|
3527
|
+
rounds = await fetchRoundsWithRetry(taskId);
|
|
3528
|
+
} catch (err) {
|
|
3529
|
+
if (isTransientMcpError(err)) {
|
|
3530
|
+
const reason = err instanceof McpError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3531
|
+
skipReason = reason;
|
|
3532
|
+
rounds = [];
|
|
3533
|
+
} else {
|
|
3534
|
+
throw err;
|
|
3535
|
+
}
|
|
3375
3536
|
}
|
|
3376
|
-
|
|
3377
|
-
currentRound
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
lintFormatHookExists
|
|
3381
|
-
});
|
|
3382
|
-
if (dryRun) {
|
|
3383
|
-
stdoutPayload = JSON.stringify(
|
|
3384
|
-
{
|
|
3385
|
-
added: result.added,
|
|
3386
|
-
stale_marked: result.stale_marked,
|
|
3387
|
-
reactivated: result.reactivated,
|
|
3388
|
-
total_files: result.total_files,
|
|
3389
|
-
merged_files_changed: result.merged_files_changed
|
|
3390
|
-
},
|
|
3391
|
-
null,
|
|
3392
|
-
2
|
|
3393
|
-
);
|
|
3394
|
-
} else {
|
|
3395
|
-
const roundArgs = {
|
|
3396
|
-
round_id: roundId,
|
|
3397
|
-
files_changed: result.merged_files_changed
|
|
3398
|
-
};
|
|
3399
|
-
if (callerWorktreeId) {
|
|
3400
|
-
roundArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3537
|
+
if (!skipReason) {
|
|
3538
|
+
const currentRound = rounds.find((r) => r.id === roundId);
|
|
3539
|
+
if (!currentRound) {
|
|
3540
|
+
throw new Error(`Round ${roundId} not found for task ${taskId}`);
|
|
3401
3541
|
}
|
|
3402
|
-
await
|
|
3403
|
-
const
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3542
|
+
const taskResponse = await apiGet(`/tasks/${taskId}`);
|
|
3543
|
+
const currentTask = taskResponse.data;
|
|
3544
|
+
let gitStatusOutput = "";
|
|
3545
|
+
try {
|
|
3546
|
+
gitStatusOutput = execSync2("git status --short --porcelain -z", {
|
|
3547
|
+
cwd: repoRoot,
|
|
3548
|
+
encoding: "utf-8"
|
|
3549
|
+
});
|
|
3550
|
+
} catch {
|
|
3551
|
+
process.stderr.write(
|
|
3552
|
+
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3553
|
+
);
|
|
3410
3554
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
reactivated: result.reactivated,
|
|
3417
|
-
total_files: result.total_files
|
|
3418
|
-
},
|
|
3419
|
-
null,
|
|
3420
|
-
2
|
|
3555
|
+
const hookPath = join11(
|
|
3556
|
+
repoRoot,
|
|
3557
|
+
".claude",
|
|
3558
|
+
"hooks",
|
|
3559
|
+
"lint-format-on-edit.sh"
|
|
3421
3560
|
);
|
|
3561
|
+
let lintFormatHookExists = false;
|
|
3562
|
+
try {
|
|
3563
|
+
await access3(hookPath);
|
|
3564
|
+
lintFormatHookExists = true;
|
|
3565
|
+
} catch {
|
|
3566
|
+
}
|
|
3567
|
+
const result = runSyncApprovals({
|
|
3568
|
+
currentRound,
|
|
3569
|
+
currentTask,
|
|
3570
|
+
gitStatusOutput,
|
|
3571
|
+
lintFormatHookExists
|
|
3572
|
+
});
|
|
3573
|
+
if (dryRun) {
|
|
3574
|
+
stdoutPayload = JSON.stringify(
|
|
3575
|
+
{
|
|
3576
|
+
added: result.added,
|
|
3577
|
+
stale_marked: result.stale_marked,
|
|
3578
|
+
reactivated: result.reactivated,
|
|
3579
|
+
total_files: result.total_files,
|
|
3580
|
+
merged_files_changed: result.merged_files_changed
|
|
3581
|
+
},
|
|
3582
|
+
null,
|
|
3583
|
+
2
|
|
3584
|
+
);
|
|
3585
|
+
} else {
|
|
3586
|
+
const roundArgs = {
|
|
3587
|
+
round_id: roundId,
|
|
3588
|
+
files_changed: result.merged_files_changed
|
|
3589
|
+
};
|
|
3590
|
+
if (callerWorktreeId) {
|
|
3591
|
+
roundArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3592
|
+
}
|
|
3593
|
+
await mcpCall("update_round", roundArgs);
|
|
3594
|
+
const taskArgs = {
|
|
3595
|
+
task_id: taskId,
|
|
3596
|
+
files_changed: result.merged_files_changed,
|
|
3597
|
+
app_file_approval_by_user: false
|
|
3598
|
+
};
|
|
3599
|
+
if (callerWorktreeId) {
|
|
3600
|
+
taskArgs["caller_worktree_id"] = callerWorktreeId;
|
|
3601
|
+
}
|
|
3602
|
+
await mcpCall("update_task", taskArgs);
|
|
3603
|
+
stdoutPayload = JSON.stringify(
|
|
3604
|
+
{
|
|
3605
|
+
added: result.added,
|
|
3606
|
+
stale_marked: result.stale_marked,
|
|
3607
|
+
reactivated: result.reactivated,
|
|
3608
|
+
total_files: result.total_files
|
|
3609
|
+
},
|
|
3610
|
+
null,
|
|
3611
|
+
2
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3422
3614
|
}
|
|
3423
3615
|
} catch (err) {
|
|
3424
3616
|
process.stderr.write(
|
|
@@ -3427,6 +3619,13 @@ async function runRoundSyncApprovals(args) {
|
|
|
3427
3619
|
);
|
|
3428
3620
|
process.exit(1);
|
|
3429
3621
|
}
|
|
3622
|
+
if (skipReason !== null) {
|
|
3623
|
+
process.stderr.write(
|
|
3624
|
+
`sync-approvals: MCP temporarily unavailable (${skipReason}); skipping approval sync. Re-run /cbp-round-update when the service recovers.
|
|
3625
|
+
`
|
|
3626
|
+
);
|
|
3627
|
+
process.exit(0);
|
|
3628
|
+
}
|
|
3430
3629
|
if (stdoutPayload === null) {
|
|
3431
3630
|
process.stderr.write("sync-approvals: internal error \u2014 payload not set\n");
|
|
3432
3631
|
process.exit(1);
|
|
@@ -3470,6 +3669,7 @@ async function autoResolveWorktreeId() {
|
|
|
3470
3669
|
return void 0;
|
|
3471
3670
|
}
|
|
3472
3671
|
}
|
|
3672
|
+
var RETRY_DELAY_MS;
|
|
3473
3673
|
var init_round = __esm({
|
|
3474
3674
|
"src/cli/round.ts"() {
|
|
3475
3675
|
"use strict";
|
|
@@ -3479,12 +3679,13 @@ var init_round = __esm({
|
|
|
3479
3679
|
init_resolve_worktree();
|
|
3480
3680
|
init_local_config();
|
|
3481
3681
|
init_sync_approvals();
|
|
3682
|
+
RETRY_DELAY_MS = 1e3;
|
|
3482
3683
|
}
|
|
3483
3684
|
});
|
|
3484
3685
|
|
|
3485
3686
|
// src/lib/migrate-branch-model.ts
|
|
3486
|
-
import { readFile as
|
|
3487
|
-
import { join as
|
|
3687
|
+
import { readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
3688
|
+
import { join as join12 } from "node:path";
|
|
3488
3689
|
import { execSync as execSync3 } from "node:child_process";
|
|
3489
3690
|
function assertValidBranchName(branch) {
|
|
3490
3691
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3494,7 +3695,7 @@ function assertValidBranchName(branch) {
|
|
|
3494
3695
|
}
|
|
3495
3696
|
}
|
|
3496
3697
|
async function readJsonFile(filePath) {
|
|
3497
|
-
const raw = await
|
|
3698
|
+
const raw = await readFile11(filePath, "utf-8");
|
|
3498
3699
|
const parsed = JSON.parse(raw);
|
|
3499
3700
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3500
3701
|
throw new Error(`${filePath} does not contain a JSON object`);
|
|
@@ -3563,12 +3764,12 @@ async function runBranchMigration(opts) {
|
|
|
3563
3764
|
if (found) {
|
|
3564
3765
|
if (found.path.endsWith("/repo.json")) {
|
|
3565
3766
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3566
|
-
configPath =
|
|
3767
|
+
configPath = join12(dir, "git.json");
|
|
3567
3768
|
} else {
|
|
3568
3769
|
configPath = found.path;
|
|
3569
3770
|
}
|
|
3570
3771
|
} else {
|
|
3571
|
-
configPath =
|
|
3772
|
+
configPath = join12(cwd, ".codebyplan", "git.json");
|
|
3572
3773
|
}
|
|
3573
3774
|
let fileRaw;
|
|
3574
3775
|
let fileParsed;
|
|
@@ -3642,7 +3843,7 @@ async function runBranchMigration(opts) {
|
|
|
3642
3843
|
const updatedParsed = { ...fileParsed, branch_config: after };
|
|
3643
3844
|
const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
|
|
3644
3845
|
if (newJson !== fileRaw) {
|
|
3645
|
-
await
|
|
3846
|
+
await writeFile9(configPath, newJson, "utf-8");
|
|
3646
3847
|
}
|
|
3647
3848
|
}
|
|
3648
3849
|
return {
|
|
@@ -3748,8 +3949,8 @@ var init_branch = __esm({
|
|
|
3748
3949
|
});
|
|
3749
3950
|
|
|
3750
3951
|
// src/lib/ship.ts
|
|
3751
|
-
import { readFile as
|
|
3752
|
-
import { join as
|
|
3952
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
3953
|
+
import { join as join13 } from "node:path";
|
|
3753
3954
|
import { execSync as execSync4, spawnSync as spawnSync2 } from "node:child_process";
|
|
3754
3955
|
function assertValidBranchName2(branch, label) {
|
|
3755
3956
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3764,15 +3965,15 @@ async function readBaseBranch(cwd) {
|
|
|
3764
3965
|
if (found) {
|
|
3765
3966
|
if (found.path.endsWith("/repo.json")) {
|
|
3766
3967
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3767
|
-
gitJsonPath =
|
|
3968
|
+
gitJsonPath = join13(dir, "git.json");
|
|
3768
3969
|
} else {
|
|
3769
3970
|
gitJsonPath = found.path;
|
|
3770
3971
|
}
|
|
3771
3972
|
} else {
|
|
3772
|
-
gitJsonPath =
|
|
3973
|
+
gitJsonPath = join13(cwd, ".codebyplan", "git.json");
|
|
3773
3974
|
}
|
|
3774
3975
|
try {
|
|
3775
|
-
const raw = await
|
|
3976
|
+
const raw = await readFile12(gitJsonPath, "utf-8");
|
|
3776
3977
|
const parsed = JSON.parse(raw);
|
|
3777
3978
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3778
3979
|
return "main";
|
|
@@ -4304,19 +4505,19 @@ var init_resolve_worktree2 = __esm({
|
|
|
4304
4505
|
});
|
|
4305
4506
|
|
|
4306
4507
|
// src/lib/migrate-local-config.ts
|
|
4307
|
-
import { mkdir as mkdir5, readFile as
|
|
4308
|
-
import { join as
|
|
4508
|
+
import { mkdir as mkdir5, readFile as readFile13, unlink as unlink2, writeFile as writeFile10 } from "node:fs/promises";
|
|
4509
|
+
import { join as join14 } from "node:path";
|
|
4309
4510
|
function legacySharedPath(projectPath) {
|
|
4310
|
-
return
|
|
4511
|
+
return join14(projectPath, ".codebyplan.json");
|
|
4311
4512
|
}
|
|
4312
4513
|
function legacyLocalPath(projectPath) {
|
|
4313
|
-
return
|
|
4514
|
+
return join14(projectPath, ".codebyplan.local.json");
|
|
4314
4515
|
}
|
|
4315
4516
|
function newDirPath(projectPath) {
|
|
4316
|
-
return
|
|
4517
|
+
return join14(projectPath, ".codebyplan");
|
|
4317
4518
|
}
|
|
4318
4519
|
function sentinelPath(projectPath) {
|
|
4319
|
-
return
|
|
4520
|
+
return join14(projectPath, ".codebyplan", "repo.json");
|
|
4320
4521
|
}
|
|
4321
4522
|
async function statSafe(p) {
|
|
4322
4523
|
const { stat: stat2 } = await import("node:fs/promises");
|
|
@@ -4355,7 +4556,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4355
4556
|
}
|
|
4356
4557
|
let legacyRaw;
|
|
4357
4558
|
try {
|
|
4358
|
-
legacyRaw = await
|
|
4559
|
+
legacyRaw = await readFile13(legacySharedPath(projectPath), "utf-8");
|
|
4359
4560
|
} catch {
|
|
4360
4561
|
return {
|
|
4361
4562
|
migrated: true,
|
|
@@ -4382,7 +4583,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4382
4583
|
let deviceId;
|
|
4383
4584
|
let deviceWrittenByHelper = false;
|
|
4384
4585
|
try {
|
|
4385
|
-
const localRaw = await
|
|
4586
|
+
const localRaw = await readFile13(legacyLocalPath(projectPath), "utf-8");
|
|
4386
4587
|
const localParsed = JSON.parse(localRaw);
|
|
4387
4588
|
if (typeof localParsed.device_id === "string") {
|
|
4388
4589
|
deviceId = localParsed.device_id;
|
|
@@ -4409,8 +4610,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4409
4610
|
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
4410
4611
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
4411
4612
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
4412
|
-
await
|
|
4413
|
-
|
|
4613
|
+
await writeFile10(
|
|
4614
|
+
join14(projectPath, ".codebyplan", "repo.json"),
|
|
4414
4615
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
4415
4616
|
"utf-8"
|
|
4416
4617
|
);
|
|
@@ -4422,8 +4623,8 @@ async function runLocalMigration(projectPath) {
|
|
|
4422
4623
|
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
4423
4624
|
if ("port_allocations" in cfg)
|
|
4424
4625
|
serverJson.port_allocations = cfg.port_allocations;
|
|
4425
|
-
await
|
|
4426
|
-
|
|
4626
|
+
await writeFile10(
|
|
4627
|
+
join14(projectPath, ".codebyplan", "server.json"),
|
|
4427
4628
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
4428
4629
|
"utf-8"
|
|
4429
4630
|
);
|
|
@@ -4431,30 +4632,37 @@ async function runLocalMigration(projectPath) {
|
|
|
4431
4632
|
const gitJson = {};
|
|
4432
4633
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
4433
4634
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
4434
|
-
await
|
|
4435
|
-
|
|
4635
|
+
await writeFile10(
|
|
4636
|
+
join14(projectPath, ".codebyplan", "git.json"),
|
|
4436
4637
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
4437
4638
|
"utf-8"
|
|
4438
4639
|
);
|
|
4439
4640
|
filesChanged.push(".codebyplan/git.json");
|
|
4440
4641
|
const shipmentJson = {};
|
|
4441
4642
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
4442
|
-
await
|
|
4443
|
-
|
|
4643
|
+
await writeFile10(
|
|
4644
|
+
join14(projectPath, ".codebyplan", "shipment.json"),
|
|
4444
4645
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
4445
4646
|
"utf-8"
|
|
4446
4647
|
);
|
|
4447
4648
|
filesChanged.push(".codebyplan/shipment.json");
|
|
4448
4649
|
const vendorJson = {};
|
|
4449
|
-
await
|
|
4450
|
-
|
|
4650
|
+
await writeFile10(
|
|
4651
|
+
join14(projectPath, ".codebyplan", "vendor.json"),
|
|
4451
4652
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
4452
4653
|
"utf-8"
|
|
4453
4654
|
);
|
|
4454
4655
|
filesChanged.push(".codebyplan/vendor.json");
|
|
4656
|
+
const e2eJson = {};
|
|
4657
|
+
await writeFile10(
|
|
4658
|
+
join14(projectPath, ".codebyplan", "e2e.json"),
|
|
4659
|
+
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
4660
|
+
"utf-8"
|
|
4661
|
+
);
|
|
4662
|
+
filesChanged.push(".codebyplan/e2e.json");
|
|
4455
4663
|
if (!deviceWrittenByHelper) {
|
|
4456
|
-
await
|
|
4457
|
-
|
|
4664
|
+
await writeFile10(
|
|
4665
|
+
join14(projectPath, ".codebyplan", "device.local.json"),
|
|
4458
4666
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
4459
4667
|
"utf-8"
|
|
4460
4668
|
);
|
|
@@ -4466,9 +4674,9 @@ async function runLocalMigration(projectPath) {
|
|
|
4466
4674
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
4467
4675
|
);
|
|
4468
4676
|
}
|
|
4469
|
-
const gitignorePath =
|
|
4677
|
+
const gitignorePath = join14(projectPath, ".gitignore");
|
|
4470
4678
|
try {
|
|
4471
|
-
const gitignoreContent = await
|
|
4679
|
+
const gitignoreContent = await readFile13(gitignorePath, "utf-8");
|
|
4472
4680
|
const legacyLine = ".codebyplan.local.json";
|
|
4473
4681
|
const newLine = ".codebyplan/device.local.json";
|
|
4474
4682
|
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
@@ -4487,7 +4695,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4487
4695
|
updated = gitignoreContent;
|
|
4488
4696
|
}
|
|
4489
4697
|
if (updated !== gitignoreContent) {
|
|
4490
|
-
await
|
|
4698
|
+
await writeFile10(gitignorePath, updated, "utf-8");
|
|
4491
4699
|
filesChanged.push(".gitignore");
|
|
4492
4700
|
}
|
|
4493
4701
|
} catch {
|
|
@@ -4519,6 +4727,7 @@ var init_migrate_local_config = __esm({
|
|
|
4519
4727
|
// src/cli/config.ts
|
|
4520
4728
|
var config_exports = {};
|
|
4521
4729
|
__export(config_exports, {
|
|
4730
|
+
readE2eConfig: () => readE2eConfig,
|
|
4522
4731
|
readGitConfig: () => readGitConfig,
|
|
4523
4732
|
readRepoConfig: () => readRepoConfig,
|
|
4524
4733
|
readServerConfig: () => readServerConfig,
|
|
@@ -4526,8 +4735,8 @@ __export(config_exports, {
|
|
|
4526
4735
|
readVendorConfig: () => readVendorConfig,
|
|
4527
4736
|
runConfig: () => runConfig
|
|
4528
4737
|
});
|
|
4529
|
-
import { mkdir as mkdir6, readFile as
|
|
4530
|
-
import { join as
|
|
4738
|
+
import { mkdir as mkdir6, readFile as readFile14, writeFile as writeFile11 } from "node:fs/promises";
|
|
4739
|
+
import { join as join15 } from "node:path";
|
|
4531
4740
|
async function runConfig() {
|
|
4532
4741
|
const flags = parseFlags(3);
|
|
4533
4742
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -4560,7 +4769,7 @@ async function runConfig() {
|
|
|
4560
4769
|
console.log("\n Config complete.\n");
|
|
4561
4770
|
}
|
|
4562
4771
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
4563
|
-
const codebyplanDir =
|
|
4772
|
+
const codebyplanDir = join15(projectPath, ".codebyplan");
|
|
4564
4773
|
let resolvedWorktreeId;
|
|
4565
4774
|
try {
|
|
4566
4775
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
@@ -4685,6 +4894,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4685
4894
|
shipmentPayload.shipment = repoAny.shipment;
|
|
4686
4895
|
}
|
|
4687
4896
|
const vendorPayload = {};
|
|
4897
|
+
const e2ePayload = {};
|
|
4688
4898
|
if (dryRun) {
|
|
4689
4899
|
console.log(" Config would be updated (dry-run).");
|
|
4690
4900
|
return;
|
|
@@ -4695,19 +4905,21 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4695
4905
|
{ name: "server.json", payload: serverPayload },
|
|
4696
4906
|
{ name: "git.json", payload: gitPayload },
|
|
4697
4907
|
{ name: "shipment.json", payload: shipmentPayload },
|
|
4698
|
-
{ name: "vendor.json", payload: vendorPayload }
|
|
4908
|
+
{ name: "vendor.json", payload: vendorPayload },
|
|
4909
|
+
{ name: "e2e.json", payload: e2ePayload, createOnly: true }
|
|
4699
4910
|
];
|
|
4700
4911
|
let anyUpdated = false;
|
|
4701
|
-
for (const { name, payload } of files) {
|
|
4702
|
-
const filePath =
|
|
4912
|
+
for (const { name, payload, createOnly } of files) {
|
|
4913
|
+
const filePath = join15(codebyplanDir, name);
|
|
4703
4914
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
4704
4915
|
let currentJson = "";
|
|
4705
4916
|
try {
|
|
4706
|
-
currentJson = await
|
|
4917
|
+
currentJson = await readFile14(filePath, "utf-8");
|
|
4707
4918
|
} catch {
|
|
4708
4919
|
}
|
|
4920
|
+
if (createOnly && currentJson !== "") continue;
|
|
4709
4921
|
if (currentJson === newJson) continue;
|
|
4710
|
-
await
|
|
4922
|
+
await writeFile11(filePath, newJson, "utf-8");
|
|
4711
4923
|
console.log(` Updated .codebyplan/${name}`);
|
|
4712
4924
|
anyUpdated = true;
|
|
4713
4925
|
}
|
|
@@ -4717,8 +4929,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4717
4929
|
}
|
|
4718
4930
|
async function readRepoConfig(projectPath) {
|
|
4719
4931
|
try {
|
|
4720
|
-
const raw = await
|
|
4721
|
-
|
|
4932
|
+
const raw = await readFile14(
|
|
4933
|
+
join15(projectPath, ".codebyplan", "repo.json"),
|
|
4722
4934
|
"utf-8"
|
|
4723
4935
|
);
|
|
4724
4936
|
return JSON.parse(raw);
|
|
@@ -4728,8 +4940,8 @@ async function readRepoConfig(projectPath) {
|
|
|
4728
4940
|
}
|
|
4729
4941
|
async function readServerConfig(projectPath) {
|
|
4730
4942
|
try {
|
|
4731
|
-
const raw = await
|
|
4732
|
-
|
|
4943
|
+
const raw = await readFile14(
|
|
4944
|
+
join15(projectPath, ".codebyplan", "server.json"),
|
|
4733
4945
|
"utf-8"
|
|
4734
4946
|
);
|
|
4735
4947
|
return JSON.parse(raw);
|
|
@@ -4739,8 +4951,8 @@ async function readServerConfig(projectPath) {
|
|
|
4739
4951
|
}
|
|
4740
4952
|
async function readGitConfig(projectPath) {
|
|
4741
4953
|
try {
|
|
4742
|
-
const raw = await
|
|
4743
|
-
|
|
4954
|
+
const raw = await readFile14(
|
|
4955
|
+
join15(projectPath, ".codebyplan", "git.json"),
|
|
4744
4956
|
"utf-8"
|
|
4745
4957
|
);
|
|
4746
4958
|
return JSON.parse(raw);
|
|
@@ -4750,8 +4962,8 @@ async function readGitConfig(projectPath) {
|
|
|
4750
4962
|
}
|
|
4751
4963
|
async function readShipmentConfig(projectPath) {
|
|
4752
4964
|
try {
|
|
4753
|
-
const raw = await
|
|
4754
|
-
|
|
4965
|
+
const raw = await readFile14(
|
|
4966
|
+
join15(projectPath, ".codebyplan", "shipment.json"),
|
|
4755
4967
|
"utf-8"
|
|
4756
4968
|
);
|
|
4757
4969
|
return JSON.parse(raw);
|
|
@@ -4761,8 +4973,19 @@ async function readShipmentConfig(projectPath) {
|
|
|
4761
4973
|
}
|
|
4762
4974
|
async function readVendorConfig(projectPath) {
|
|
4763
4975
|
try {
|
|
4764
|
-
const raw = await
|
|
4765
|
-
|
|
4976
|
+
const raw = await readFile14(
|
|
4977
|
+
join15(projectPath, ".codebyplan", "vendor.json"),
|
|
4978
|
+
"utf-8"
|
|
4979
|
+
);
|
|
4980
|
+
return JSON.parse(raw);
|
|
4981
|
+
} catch {
|
|
4982
|
+
return null;
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
async function readE2eConfig(projectPath) {
|
|
4986
|
+
try {
|
|
4987
|
+
const raw = await readFile14(
|
|
4988
|
+
join15(projectPath, ".codebyplan", "e2e.json"),
|
|
4766
4989
|
"utf-8"
|
|
4767
4990
|
);
|
|
4768
4991
|
return JSON.parse(raw);
|
|
@@ -4818,14 +5041,14 @@ var init_server_detect = __esm({
|
|
|
4818
5041
|
});
|
|
4819
5042
|
|
|
4820
5043
|
// src/lib/port-verify.ts
|
|
4821
|
-
import { readFile as
|
|
5044
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
4822
5045
|
async function verifyPorts(projectPath, portAllocations) {
|
|
4823
5046
|
const mismatches = [];
|
|
4824
5047
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
4825
5048
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
4826
5049
|
for (const pkgPath of packageJsonPaths) {
|
|
4827
5050
|
try {
|
|
4828
|
-
const raw = await
|
|
5051
|
+
const raw = await readFile15(pkgPath, "utf-8");
|
|
4829
5052
|
const pkg = JSON.parse(raw);
|
|
4830
5053
|
const scriptPort = detectPortFromScripts(pkg);
|
|
4831
5054
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -4888,7 +5111,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
4888
5111
|
}
|
|
4889
5112
|
let pkg;
|
|
4890
5113
|
try {
|
|
4891
|
-
const raw = await
|
|
5114
|
+
const raw = await readFile15(`${app.absPath}/package.json`, "utf-8");
|
|
4892
5115
|
pkg = JSON.parse(raw);
|
|
4893
5116
|
} catch {
|
|
4894
5117
|
continue;
|
|
@@ -5258,7 +5481,7 @@ var init_hash = __esm({
|
|
|
5258
5481
|
|
|
5259
5482
|
// src/lib/template-walker.ts
|
|
5260
5483
|
import * as fs from "node:fs";
|
|
5261
|
-
import * as
|
|
5484
|
+
import * as path2 from "node:path";
|
|
5262
5485
|
function walkTemplates(templatesDir) {
|
|
5263
5486
|
const absRoot = fs.realpathSync(templatesDir);
|
|
5264
5487
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -5271,7 +5494,7 @@ function walkTemplates(templatesDir) {
|
|
|
5271
5494
|
visited.add(realDir);
|
|
5272
5495
|
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
5273
5496
|
for (const entry of entries) {
|
|
5274
|
-
const absPath =
|
|
5497
|
+
const absPath = path2.join(absDir, entry.name);
|
|
5275
5498
|
if (entry.isDirectory()) {
|
|
5276
5499
|
recurse(absPath);
|
|
5277
5500
|
continue;
|
|
@@ -5280,7 +5503,7 @@ function walkTemplates(templatesDir) {
|
|
|
5280
5503
|
continue;
|
|
5281
5504
|
}
|
|
5282
5505
|
if (entry.name === ".gitkeep") continue;
|
|
5283
|
-
const relPosix =
|
|
5506
|
+
const relPosix = path2.relative(absRoot, absPath).split(path2.sep).join("/");
|
|
5284
5507
|
if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) {
|
|
5285
5508
|
continue;
|
|
5286
5509
|
}
|
|
@@ -5308,6 +5531,10 @@ var init_template_walker = __esm({
|
|
|
5308
5531
|
"rules/README.md",
|
|
5309
5532
|
"settings.project.base.json",
|
|
5310
5533
|
"settings.user.base.json",
|
|
5534
|
+
// .gitignore — managed by ensureManagedGitignoreBlock; never copied into
|
|
5535
|
+
// consuming projects' .claude/ tree (it would overwrite the project root
|
|
5536
|
+
// .gitignore with a stale single-entry file).
|
|
5537
|
+
".gitignore",
|
|
5311
5538
|
// CBP-internal hooks — see templates/hooks/README.md "Hooks NOT included and why"
|
|
5312
5539
|
"hooks/validate-structure.sh",
|
|
5313
5540
|
"hooks/validate-structure-lib.sh",
|
|
@@ -5325,15 +5552,15 @@ var init_template_walker = __esm({
|
|
|
5325
5552
|
// src/lib/manifest.ts
|
|
5326
5553
|
import * as fs2 from "node:fs";
|
|
5327
5554
|
import * as os from "node:os";
|
|
5328
|
-
import * as
|
|
5555
|
+
import * as path3 from "node:path";
|
|
5329
5556
|
function manifestPath(projectDir) {
|
|
5330
|
-
return
|
|
5557
|
+
return path3.join(projectDir, ".claude", NEW_MANIFEST_FILENAME);
|
|
5331
5558
|
}
|
|
5332
5559
|
function midManifestPath(projectDir) {
|
|
5333
|
-
return
|
|
5560
|
+
return path3.join(projectDir, ".claude", MID_MANIFEST_FILENAME);
|
|
5334
5561
|
}
|
|
5335
5562
|
function oldManifestPath(projectDir) {
|
|
5336
|
-
return
|
|
5563
|
+
return path3.join(projectDir, ".claude", OLD_MANIFEST_FILENAME);
|
|
5337
5564
|
}
|
|
5338
5565
|
function readManifest(projectDir) {
|
|
5339
5566
|
const newFile = manifestPath(projectDir);
|
|
@@ -5355,7 +5582,7 @@ function readManifest(projectDir) {
|
|
|
5355
5582
|
}
|
|
5356
5583
|
function writeManifest(projectDir, manifest) {
|
|
5357
5584
|
const file = manifestPath(projectDir);
|
|
5358
|
-
fs2.mkdirSync(
|
|
5585
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5359
5586
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5360
5587
|
const mid = midManifestPath(projectDir);
|
|
5361
5588
|
if (fs2.existsSync(mid)) {
|
|
@@ -5374,16 +5601,16 @@ function defaultManifest() {
|
|
|
5374
5601
|
};
|
|
5375
5602
|
}
|
|
5376
5603
|
function userManifestPath(userDir) {
|
|
5377
|
-
const dir = userDir ??
|
|
5378
|
-
return
|
|
5604
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5605
|
+
return path3.join(dir, NEW_MANIFEST_FILENAME);
|
|
5379
5606
|
}
|
|
5380
5607
|
function userMidManifestPath(userDir) {
|
|
5381
|
-
const dir = userDir ??
|
|
5382
|
-
return
|
|
5608
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5609
|
+
return path3.join(dir, MID_MANIFEST_FILENAME);
|
|
5383
5610
|
}
|
|
5384
5611
|
function userOldManifestPath(userDir) {
|
|
5385
|
-
const dir = userDir ??
|
|
5386
|
-
return
|
|
5612
|
+
const dir = userDir ?? path3.join(os.homedir(), ".claude");
|
|
5613
|
+
return path3.join(dir, OLD_MANIFEST_FILENAME);
|
|
5387
5614
|
}
|
|
5388
5615
|
function readManifestForScope(scope, arg2) {
|
|
5389
5616
|
if (scope === "user") {
|
|
@@ -5409,7 +5636,7 @@ function readManifestForScope(scope, arg2) {
|
|
|
5409
5636
|
function writeManifestForScope(scope, manifest, arg3) {
|
|
5410
5637
|
if (scope === "user") {
|
|
5411
5638
|
const file = userManifestPath(arg3);
|
|
5412
|
-
fs2.mkdirSync(
|
|
5639
|
+
fs2.mkdirSync(path3.dirname(file), { recursive: true });
|
|
5413
5640
|
fs2.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
5414
5641
|
const mid = userMidManifestPath(arg3);
|
|
5415
5642
|
if (fs2.existsSync(mid)) {
|
|
@@ -5665,14 +5892,14 @@ __export(install_exports, {
|
|
|
5665
5892
|
});
|
|
5666
5893
|
import * as fs3 from "node:fs";
|
|
5667
5894
|
import * as os2 from "node:os";
|
|
5668
|
-
import * as
|
|
5895
|
+
import * as path4 from "node:path";
|
|
5669
5896
|
import { fileURLToPath } from "node:url";
|
|
5670
5897
|
function resolveTemplatesDir() {
|
|
5671
|
-
const here =
|
|
5898
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
5672
5899
|
const candidates = [
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5900
|
+
path4.resolve(here, "..", "templates"),
|
|
5901
|
+
path4.resolve(here, "..", "..", "templates"),
|
|
5902
|
+
path4.resolve(here, "..", "..", "..", "templates")
|
|
5676
5903
|
];
|
|
5677
5904
|
for (const c of candidates) {
|
|
5678
5905
|
if (fs3.existsSync(c) && fs3.statSync(c).isDirectory()) {
|
|
@@ -5713,14 +5940,14 @@ async function runInstall(opts, deps = {}) {
|
|
|
5713
5940
|
const files = walkTemplates(templatesDir);
|
|
5714
5941
|
const manifestEntries = [];
|
|
5715
5942
|
for (const f of files) {
|
|
5716
|
-
const absDest =
|
|
5717
|
-
const absSrc =
|
|
5943
|
+
const absDest = path4.join(projectDir, ".claude", f.dest);
|
|
5944
|
+
const absSrc = path4.join(templatesDir, f.src);
|
|
5718
5945
|
if (opts.dryRun) {
|
|
5719
5946
|
if (opts.verbose) {
|
|
5720
5947
|
console.log(`[dry-run] would copy ${f.src} \u2192 .claude/${f.dest}`);
|
|
5721
5948
|
}
|
|
5722
5949
|
} else {
|
|
5723
|
-
fs3.mkdirSync(
|
|
5950
|
+
fs3.mkdirSync(path4.dirname(absDest), { recursive: true });
|
|
5724
5951
|
fs3.copyFileSync(absSrc, absDest);
|
|
5725
5952
|
if (opts.verbose) {
|
|
5726
5953
|
console.log(`copied ${f.src} \u2192 .claude/${f.dest}`);
|
|
@@ -5728,15 +5955,15 @@ async function runInstall(opts, deps = {}) {
|
|
|
5728
5955
|
}
|
|
5729
5956
|
manifestEntries.push({ src: f.src, dest: f.dest, hash: f.hash });
|
|
5730
5957
|
}
|
|
5731
|
-
const hooksJsonPath =
|
|
5732
|
-
const baseSettingsPath =
|
|
5958
|
+
const hooksJsonPath = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5959
|
+
const baseSettingsPath = path4.join(
|
|
5733
5960
|
templatesDir,
|
|
5734
5961
|
"settings.project.base.json"
|
|
5735
5962
|
);
|
|
5736
5963
|
const hasHooks = fs3.existsSync(hooksJsonPath);
|
|
5737
5964
|
const hasBase = fs3.existsSync(baseSettingsPath);
|
|
5738
5965
|
if (hasHooks || hasBase) {
|
|
5739
|
-
const settingsPath =
|
|
5966
|
+
const settingsPath = path4.join(projectDir, ".claude", "settings.json");
|
|
5740
5967
|
const existingSettings = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf8")) : {};
|
|
5741
5968
|
if (hasBase) {
|
|
5742
5969
|
const base = JSON.parse(
|
|
@@ -5751,7 +5978,7 @@ async function runInstall(opts, deps = {}) {
|
|
|
5751
5978
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
5752
5979
|
}
|
|
5753
5980
|
if (!opts.dryRun) {
|
|
5754
|
-
fs3.mkdirSync(
|
|
5981
|
+
fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
|
|
5755
5982
|
fs3.writeFileSync(
|
|
5756
5983
|
settingsPath,
|
|
5757
5984
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -5759,10 +5986,19 @@ async function runInstall(opts, deps = {}) {
|
|
|
5759
5986
|
);
|
|
5760
5987
|
} else if (opts.verbose) {
|
|
5761
5988
|
console.log(
|
|
5762
|
-
`[dry-run] would merge settings into ${
|
|
5989
|
+
`[dry-run] would merge settings into ${path4.relative(projectDir, settingsPath)}`
|
|
5763
5990
|
);
|
|
5764
5991
|
}
|
|
5765
5992
|
}
|
|
5993
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
5994
|
+
projectDir,
|
|
5995
|
+
opts.dryRun
|
|
5996
|
+
);
|
|
5997
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
5998
|
+
console.log(
|
|
5999
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path4.relative(projectDir, path4.join(projectDir, ".gitignore"))}`
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
5766
6002
|
if (!opts.dryRun) {
|
|
5767
6003
|
const manifest = defaultManifest();
|
|
5768
6004
|
manifest.files = manifestEntries;
|
|
@@ -5793,9 +6029,9 @@ function runInstallUser(opts, deps) {
|
|
|
5793
6029
|
return;
|
|
5794
6030
|
}
|
|
5795
6031
|
try {
|
|
5796
|
-
const userDir = deps.userDir ??
|
|
5797
|
-
const settingsPath =
|
|
5798
|
-
const userBaseSettingsPath =
|
|
6032
|
+
const userDir = deps.userDir ?? path4.join(os2.homedir(), ".claude");
|
|
6033
|
+
const settingsPath = path4.join(userDir, "settings.json");
|
|
6034
|
+
const userBaseSettingsPath = path4.join(
|
|
5799
6035
|
templatesDir,
|
|
5800
6036
|
"settings.user.base.json"
|
|
5801
6037
|
);
|
|
@@ -5837,7 +6073,7 @@ function runInstallUser(opts, deps) {
|
|
|
5837
6073
|
}
|
|
5838
6074
|
}
|
|
5839
6075
|
function countHookEntries(templatesDir) {
|
|
5840
|
-
const p =
|
|
6076
|
+
const p = path4.join(templatesDir, "hooks", "hooks.json");
|
|
5841
6077
|
if (!fs3.existsSync(p)) return 0;
|
|
5842
6078
|
try {
|
|
5843
6079
|
const j = JSON.parse(fs3.readFileSync(p, "utf8"));
|
|
@@ -5857,6 +6093,7 @@ var init_install = __esm({
|
|
|
5857
6093
|
"src/cli/claude/install.ts"() {
|
|
5858
6094
|
"use strict";
|
|
5859
6095
|
init_template_walker();
|
|
6096
|
+
init_gitignore_block();
|
|
5860
6097
|
init_manifest();
|
|
5861
6098
|
init_settings_merge();
|
|
5862
6099
|
init_statusline_config();
|
|
@@ -5981,7 +6218,7 @@ __export(update_exports, {
|
|
|
5981
6218
|
});
|
|
5982
6219
|
import * as fs4 from "node:fs";
|
|
5983
6220
|
import * as os3 from "node:os";
|
|
5984
|
-
import * as
|
|
6221
|
+
import * as path5 from "node:path";
|
|
5985
6222
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5986
6223
|
async function runUpdate(opts, deps = {}) {
|
|
5987
6224
|
await Promise.resolve();
|
|
@@ -6021,9 +6258,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6021
6258
|
finalManifestEntries.push(e);
|
|
6022
6259
|
}
|
|
6023
6260
|
for (const { packaged, absSrc } of plan.overwriteSafe) {
|
|
6024
|
-
const absDest =
|
|
6261
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6025
6262
|
if (!opts.dryRun) {
|
|
6026
|
-
fs4.mkdirSync(
|
|
6263
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6027
6264
|
fs4.copyFileSync(absSrc, absDest);
|
|
6028
6265
|
if (opts.verbose) console.log(`updated ${packaged.dest}`);
|
|
6029
6266
|
} else if (opts.verbose) {
|
|
@@ -6036,7 +6273,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6036
6273
|
absSrc,
|
|
6037
6274
|
onDiskContent
|
|
6038
6275
|
} of plan.overwriteHandEdited) {
|
|
6039
|
-
const absDest =
|
|
6276
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6040
6277
|
const newContent = fs4.readFileSync(absSrc);
|
|
6041
6278
|
const showDiff = () => {
|
|
6042
6279
|
console.log(
|
|
@@ -6049,7 +6286,7 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6049
6286
|
const answer = await promptOverwrite(packaged.dest, opts, showDiff);
|
|
6050
6287
|
if (answer === "overwrite") {
|
|
6051
6288
|
if (!opts.dryRun) {
|
|
6052
|
-
fs4.mkdirSync(
|
|
6289
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6053
6290
|
fs4.copyFileSync(absSrc, absDest);
|
|
6054
6291
|
}
|
|
6055
6292
|
finalManifestEntries.push(packaged);
|
|
@@ -6065,9 +6302,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6065
6302
|
for (const { packaged, absSrc } of plan.newOptIn) {
|
|
6066
6303
|
const answer = await promptOptIn(packaged.dest, opts);
|
|
6067
6304
|
if (answer === "opt-in") {
|
|
6068
|
-
const absDest =
|
|
6305
|
+
const absDest = path5.join(projectDir, ".claude", packaged.dest);
|
|
6069
6306
|
if (!opts.dryRun) {
|
|
6070
|
-
fs4.mkdirSync(
|
|
6307
|
+
fs4.mkdirSync(path5.dirname(absDest), { recursive: true });
|
|
6071
6308
|
fs4.copyFileSync(absSrc, absDest);
|
|
6072
6309
|
}
|
|
6073
6310
|
finalManifestEntries.push(packaged);
|
|
@@ -6079,25 +6316,25 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6079
6316
|
for (const e of plan.removedFromPackage) {
|
|
6080
6317
|
const answer = await promptRemove(e.dest, opts);
|
|
6081
6318
|
if (answer === "remove") {
|
|
6082
|
-
const absDest =
|
|
6319
|
+
const absDest = path5.join(projectDir, ".claude", e.dest);
|
|
6083
6320
|
if (!opts.dryRun && fs4.existsSync(absDest)) {
|
|
6084
6321
|
fs4.rmSync(absDest);
|
|
6085
|
-
const claudeDir =
|
|
6086
|
-
let cur =
|
|
6087
|
-
while (cur !== claudeDir && cur !==
|
|
6088
|
-
if (
|
|
6322
|
+
const claudeDir = path5.join(projectDir, ".claude");
|
|
6323
|
+
let cur = path5.dirname(absDest);
|
|
6324
|
+
while (cur !== claudeDir && cur !== path5.dirname(cur)) {
|
|
6325
|
+
if (path5.dirname(cur) === claudeDir) break;
|
|
6089
6326
|
try {
|
|
6090
6327
|
fs4.rmdirSync(cur);
|
|
6091
6328
|
if (opts.verbose)
|
|
6092
6329
|
console.log(
|
|
6093
|
-
`pruned empty dir ${
|
|
6330
|
+
`pruned empty dir ${path5.relative(claudeDir, cur)}`
|
|
6094
6331
|
);
|
|
6095
|
-
cur =
|
|
6332
|
+
cur = path5.dirname(cur);
|
|
6096
6333
|
} catch (err) {
|
|
6097
6334
|
const code = err.code;
|
|
6098
6335
|
if (code !== "ENOTEMPTY" && code !== "ENOENT") {
|
|
6099
6336
|
console.warn(
|
|
6100
|
-
`codebyplan claude: could not prune empty dir ${
|
|
6337
|
+
`codebyplan claude: could not prune empty dir ${path5.relative(claudeDir, cur)}: ${err.message}`
|
|
6101
6338
|
);
|
|
6102
6339
|
}
|
|
6103
6340
|
break;
|
|
@@ -6109,16 +6346,16 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6109
6346
|
if (opts.verbose) console.log(`kept (untracked) ${e.dest}`);
|
|
6110
6347
|
}
|
|
6111
6348
|
}
|
|
6112
|
-
const hooksJsonPath =
|
|
6349
|
+
const hooksJsonPath = path5.join(templatesDir, "hooks", "hooks.json");
|
|
6113
6350
|
if (fs4.existsSync(hooksJsonPath)) {
|
|
6114
6351
|
const hooksJson = JSON.parse(
|
|
6115
6352
|
fs4.readFileSync(hooksJsonPath, "utf8")
|
|
6116
6353
|
);
|
|
6117
|
-
const settingsPath =
|
|
6354
|
+
const settingsPath = path5.join(projectDir, ".claude", "settings.json");
|
|
6118
6355
|
const existingSettings = fs4.existsSync(settingsPath) ? JSON.parse(fs4.readFileSync(settingsPath, "utf8")) : {};
|
|
6119
6356
|
mergeHooksIntoSettings(existingSettings, hooksJson);
|
|
6120
6357
|
if (!opts.dryRun) {
|
|
6121
|
-
fs4.mkdirSync(
|
|
6358
|
+
fs4.mkdirSync(path5.dirname(settingsPath), { recursive: true });
|
|
6122
6359
|
fs4.writeFileSync(
|
|
6123
6360
|
settingsPath,
|
|
6124
6361
|
JSON.stringify(existingSettings, null, 2) + "\n",
|
|
@@ -6126,6 +6363,15 @@ async function runUpdate(opts, deps = {}) {
|
|
|
6126
6363
|
);
|
|
6127
6364
|
}
|
|
6128
6365
|
}
|
|
6366
|
+
const gitignoreAction = await ensureManagedGitignoreBlock(
|
|
6367
|
+
projectDir,
|
|
6368
|
+
opts.dryRun
|
|
6369
|
+
);
|
|
6370
|
+
if (opts.verbose && gitignoreAction !== "unchanged") {
|
|
6371
|
+
console.log(
|
|
6372
|
+
`${opts.dryRun ? "[dry-run] would " : ""}${gitignoreAction} managed .gitignore block in ${path5.relative(projectDir, path5.join(projectDir, ".gitignore"))}`
|
|
6373
|
+
);
|
|
6374
|
+
}
|
|
6129
6375
|
if (!opts.dryRun) {
|
|
6130
6376
|
const manifest = defaultManifest();
|
|
6131
6377
|
manifest.files = finalManifestEntries.sort(
|
|
@@ -6158,9 +6404,9 @@ function runUpdateUser(opts, deps) {
|
|
|
6158
6404
|
return;
|
|
6159
6405
|
}
|
|
6160
6406
|
try {
|
|
6161
|
-
const userDir = deps.userDir ??
|
|
6162
|
-
const settingsPath =
|
|
6163
|
-
const userBaseSettingsPath =
|
|
6407
|
+
const userDir = deps.userDir ?? path5.join(os3.homedir(), ".claude");
|
|
6408
|
+
const settingsPath = path5.join(userDir, "settings.json");
|
|
6409
|
+
const userBaseSettingsPath = path5.join(
|
|
6164
6410
|
templatesDir,
|
|
6165
6411
|
"settings.user.base.json"
|
|
6166
6412
|
);
|
|
@@ -6222,8 +6468,8 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6222
6468
|
};
|
|
6223
6469
|
for (const pkg of packaged) {
|
|
6224
6470
|
const inManifest = manifestBySrc.get(pkg.src);
|
|
6225
|
-
const absDest =
|
|
6226
|
-
const absSrc =
|
|
6471
|
+
const absDest = path5.join(projectDir, ".claude", pkg.dest);
|
|
6472
|
+
const absSrc = path5.join(templatesDir, pkg.src);
|
|
6227
6473
|
if (!inManifest) {
|
|
6228
6474
|
plan.newOptIn.push({
|
|
6229
6475
|
packaged: { src: pkg.src, dest: pkg.dest, hash: pkg.hash },
|
|
@@ -6259,11 +6505,11 @@ function buildPlan(projectDir, templatesDir, manifest) {
|
|
|
6259
6505
|
return plan;
|
|
6260
6506
|
}
|
|
6261
6507
|
function resolveTemplatesDirFromInstall() {
|
|
6262
|
-
const here =
|
|
6508
|
+
const here = path5.dirname(fileURLToPath2(import.meta.url));
|
|
6263
6509
|
const candidates = [
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6510
|
+
path5.resolve(here, "..", "templates"),
|
|
6511
|
+
path5.resolve(here, "..", "..", "templates"),
|
|
6512
|
+
path5.resolve(here, "..", "..", "..", "templates")
|
|
6267
6513
|
];
|
|
6268
6514
|
for (const c of candidates) {
|
|
6269
6515
|
if (fs4.existsSync(c) && fs4.statSync(c).isDirectory()) {
|
|
@@ -6281,6 +6527,7 @@ var init_update = __esm({
|
|
|
6281
6527
|
"src/cli/claude/update.ts"() {
|
|
6282
6528
|
"use strict";
|
|
6283
6529
|
init_template_walker();
|
|
6530
|
+
init_gitignore_block();
|
|
6284
6531
|
init_hash();
|
|
6285
6532
|
init_manifest();
|
|
6286
6533
|
init_settings_merge();
|
|
@@ -6296,7 +6543,7 @@ __export(uninstall_exports, {
|
|
|
6296
6543
|
});
|
|
6297
6544
|
import * as fs5 from "node:fs";
|
|
6298
6545
|
import * as os4 from "node:os";
|
|
6299
|
-
import * as
|
|
6546
|
+
import * as path6 from "node:path";
|
|
6300
6547
|
async function runUninstall(opts, deps = {}) {
|
|
6301
6548
|
await Promise.resolve();
|
|
6302
6549
|
const scope = opts.scope ?? "project";
|
|
@@ -6325,7 +6572,7 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6325
6572
|
let removed = 0;
|
|
6326
6573
|
let warnings = 0;
|
|
6327
6574
|
for (const entry of manifest.files) {
|
|
6328
|
-
const abs =
|
|
6575
|
+
const abs = path6.join(projectDir, ".claude", entry.dest);
|
|
6329
6576
|
if (!fs5.existsSync(abs)) {
|
|
6330
6577
|
console.warn(
|
|
6331
6578
|
`codebyplan claude uninstall: ${entry.dest} already absent (skipping).`
|
|
@@ -6349,12 +6596,12 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6349
6596
|
if (!opts.dryRun) {
|
|
6350
6597
|
pruneEmptyManagedDirs(projectDir);
|
|
6351
6598
|
}
|
|
6352
|
-
const settingsPath =
|
|
6599
|
+
const settingsPath = path6.join(projectDir, ".claude", "settings.json");
|
|
6353
6600
|
if (fs5.existsSync(settingsPath)) {
|
|
6354
6601
|
const settings = JSON.parse(
|
|
6355
6602
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6356
6603
|
);
|
|
6357
|
-
const baseSettingsPath = templatesDir ?
|
|
6604
|
+
const baseSettingsPath = templatesDir ? path6.join(templatesDir, "settings.project.base.json") : null;
|
|
6358
6605
|
if (baseSettingsPath && fs5.existsSync(baseSettingsPath)) {
|
|
6359
6606
|
const base = JSON.parse(
|
|
6360
6607
|
fs5.readFileSync(baseSettingsPath, "utf8")
|
|
@@ -6375,6 +6622,15 @@ async function runUninstall(opts, deps = {}) {
|
|
|
6375
6622
|
}
|
|
6376
6623
|
}
|
|
6377
6624
|
}
|
|
6625
|
+
const gitignoreAction = await removeManagedGitignoreBlock(
|
|
6626
|
+
projectDir,
|
|
6627
|
+
opts.dryRun
|
|
6628
|
+
);
|
|
6629
|
+
if (opts.verbose) {
|
|
6630
|
+
console.log(
|
|
6631
|
+
gitignoreAction === "removed" ? `${opts.dryRun ? "[dry-run] would remove" : "removed"} managed .gitignore block` : "managed .gitignore block already absent (no change)"
|
|
6632
|
+
);
|
|
6633
|
+
}
|
|
6378
6634
|
if (!opts.dryRun) {
|
|
6379
6635
|
const m = manifestPath(projectDir);
|
|
6380
6636
|
if (fs5.existsSync(m)) fs5.rmSync(m);
|
|
@@ -6403,7 +6659,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6403
6659
|
}
|
|
6404
6660
|
}
|
|
6405
6661
|
try {
|
|
6406
|
-
const userDir = deps.userDir ??
|
|
6662
|
+
const userDir = deps.userDir ?? path6.join(os4.homedir(), ".claude");
|
|
6407
6663
|
const existingManifest = readManifestForScope("user", userDir);
|
|
6408
6664
|
if (!existingManifest) {
|
|
6409
6665
|
console.error(
|
|
@@ -6412,12 +6668,12 @@ function runUninstallUser(opts, deps) {
|
|
|
6412
6668
|
process.exitCode = 1;
|
|
6413
6669
|
return;
|
|
6414
6670
|
}
|
|
6415
|
-
const settingsPath =
|
|
6671
|
+
const settingsPath = path6.join(userDir, "settings.json");
|
|
6416
6672
|
if (fs5.existsSync(settingsPath)) {
|
|
6417
6673
|
const settings = JSON.parse(
|
|
6418
6674
|
fs5.readFileSync(settingsPath, "utf8")
|
|
6419
6675
|
);
|
|
6420
|
-
const userBaseSettingsPath = templatesDir != null ?
|
|
6676
|
+
const userBaseSettingsPath = templatesDir != null ? path6.join(templatesDir, "settings.user.base.json") : null;
|
|
6421
6677
|
if (userBaseSettingsPath && fs5.existsSync(userBaseSettingsPath)) {
|
|
6422
6678
|
const userBase = JSON.parse(
|
|
6423
6679
|
fs5.readFileSync(userBaseSettingsPath, "utf8")
|
|
@@ -6458,7 +6714,7 @@ function runUninstallUser(opts, deps) {
|
|
|
6458
6714
|
function pruneEmptyManagedDirs(projectDir) {
|
|
6459
6715
|
const managedRoots = ["skills", "agents", "hooks", "rules"];
|
|
6460
6716
|
for (const root of managedRoots) {
|
|
6461
|
-
const abs =
|
|
6717
|
+
const abs = path6.join(projectDir, ".claude", root);
|
|
6462
6718
|
if (!fs5.existsSync(abs)) continue;
|
|
6463
6719
|
pruneLeafFirst(abs);
|
|
6464
6720
|
}
|
|
@@ -6469,7 +6725,7 @@ function pruneLeafFirst(dir) {
|
|
|
6469
6725
|
if (!stat2.isDirectory()) return;
|
|
6470
6726
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
6471
6727
|
if (entry.isDirectory()) {
|
|
6472
|
-
pruneLeafFirst(
|
|
6728
|
+
pruneLeafFirst(path6.join(dir, entry.name));
|
|
6473
6729
|
}
|
|
6474
6730
|
}
|
|
6475
6731
|
const remaining = fs5.readdirSync(dir);
|
|
@@ -6481,6 +6737,7 @@ var init_uninstall = __esm({
|
|
|
6481
6737
|
"src/cli/claude/uninstall.ts"() {
|
|
6482
6738
|
"use strict";
|
|
6483
6739
|
init_hash();
|
|
6740
|
+
init_gitignore_block();
|
|
6484
6741
|
init_manifest();
|
|
6485
6742
|
init_settings_merge();
|
|
6486
6743
|
init_install();
|