codebyplan 1.13.37 → 1.13.39
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 +1205 -121
- package/package.json +1 -1
- package/templates/agents/cbp-map-architecture.md +90 -0
- package/templates/agents/cbp-round-executor.md +10 -0
- package/templates/agents/cbp-task-planner.md +5 -0
- package/templates/context/architecture/arch-map-spec.md +204 -0
- package/templates/context/architecture-map.md +117 -0
- package/templates/hooks/validate-structure.sh +3 -2
- package/templates/rules/architecture-map.md +30 -0
- package/templates/rules/context-file-loading.md +3 -0
- package/templates/rules/supabase-branch-lifecycle.md +1 -1
- package/templates/settings.project.base.json +5 -1
- package/templates/skills/cbp-checkpoint-end/SKILL.md +6 -3
- package/templates/skills/cbp-git-worktree-remove/SKILL.md +3 -2
- package/templates/skills/cbp-map-architecture/SKILL.md +170 -0
- package/templates/skills/cbp-refresh-arch-map/SKILL.md +191 -0
- package/templates/skills/cbp-session-start/SKILL.md +33 -2
- package/templates/skills/cbp-ship/reference/surface-supabase.md +3 -3
- package/templates/skills/cbp-ship-configure/SKILL.md +1 -1
- package/templates/skills/cbp-ship-configure/reference/railway-backend.md +2 -2
- package/templates/skills/cbp-ship-configure/reference/vercel.md +1 -1
- package/templates/skills/cbp-ship-main/SKILL.md +3 -2
- package/templates/skills/cbp-standalone-task-complete/SKILL.md +3 -2
- package/templates/skills/cbp-supabase-migrate/SKILL.md +2 -6
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.13.
|
|
17
|
+
VERSION = "1.13.39";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -698,7 +698,7 @@ function isRetryable(err) {
|
|
|
698
698
|
return false;
|
|
699
699
|
}
|
|
700
700
|
function delay(ms) {
|
|
701
|
-
return new Promise((
|
|
701
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
702
702
|
}
|
|
703
703
|
async function request(method, path10, options) {
|
|
704
704
|
const url = buildUrl(path10, options?.params);
|
|
@@ -1056,7 +1056,7 @@ var init_device_flow = __esm({
|
|
|
1056
1056
|
this.name = "OAuthInvalidClientError";
|
|
1057
1057
|
}
|
|
1058
1058
|
};
|
|
1059
|
-
defaultSleep = (ms) => new Promise((
|
|
1059
|
+
defaultSleep = (ms) => new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
1060
1060
|
}
|
|
1061
1061
|
});
|
|
1062
1062
|
|
|
@@ -3134,6 +3134,11 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
3134
3134
|
JSON.stringify({}, null, 2) + "\n",
|
|
3135
3135
|
"utf-8"
|
|
3136
3136
|
);
|
|
3137
|
+
await writeFile7(
|
|
3138
|
+
join12(codebyplanDir, "architecture.json"),
|
|
3139
|
+
JSON.stringify({ version: 1, modules: [] }, null, 2) + "\n",
|
|
3140
|
+
"utf-8"
|
|
3141
|
+
);
|
|
3137
3142
|
const statuslinePath = join12(codebyplanDir, "statusline.json");
|
|
3138
3143
|
let statuslineExists = false;
|
|
3139
3144
|
try {
|
|
@@ -3151,7 +3156,7 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
3151
3156
|
await writeLocalConfig(projectPath, { device_id: deviceId });
|
|
3152
3157
|
console.log(` Created ${codebyplanDir}/`);
|
|
3153
3158
|
console.log(
|
|
3154
|
-
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, eslint.json, statusline.json`
|
|
3159
|
+
` repo.json, server.json, git.json, shipment.json, vendor.json, e2e.json, eslint.json, architecture.json, statusline.json`
|
|
3155
3160
|
);
|
|
3156
3161
|
console.log(` device.local.json (gitignored)`);
|
|
3157
3162
|
const gitignoreAction = await ensureManagedGitignoreBlock(projectPath);
|
|
@@ -4670,7 +4675,7 @@ function setRetryDelayMs(ms) {
|
|
|
4670
4675
|
RETRY_DELAY_MS = ms;
|
|
4671
4676
|
}
|
|
4672
4677
|
function sleep(ms) {
|
|
4673
|
-
return new Promise((
|
|
4678
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
4674
4679
|
}
|
|
4675
4680
|
function isTransientMcpError(err) {
|
|
4676
4681
|
if (!(err instanceof McpError)) return false;
|
|
@@ -7142,6 +7147,425 @@ var init_upload_e2e_images = __esm({
|
|
|
7142
7147
|
}
|
|
7143
7148
|
});
|
|
7144
7149
|
|
|
7150
|
+
// src/cli/arch-map.ts
|
|
7151
|
+
var arch_map_exports = {};
|
|
7152
|
+
__export(arch_map_exports, {
|
|
7153
|
+
mapFileSlug: () => mapFileSlug,
|
|
7154
|
+
runArchMapCommand: () => runArchMapCommand,
|
|
7155
|
+
updateFrontmatterFields: () => updateFrontmatterFields
|
|
7156
|
+
});
|
|
7157
|
+
import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
|
|
7158
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
7159
|
+
import { join as join23 } from "node:path";
|
|
7160
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
7161
|
+
function normalizeModulePath(modulePath) {
|
|
7162
|
+
return modulePath.replace(/\/+$/, "");
|
|
7163
|
+
}
|
|
7164
|
+
function repoRootFromCodebyplanDir(codebyplanDir) {
|
|
7165
|
+
return codebyplanDir.endsWith("/.codebyplan") ? codebyplanDir.slice(0, codebyplanDir.lastIndexOf("/")) : codebyplanDir;
|
|
7166
|
+
}
|
|
7167
|
+
function mapFileSlug(modulePath) {
|
|
7168
|
+
const slug = modulePath.replace(/\//g, "-").replace(/^\./, "dot-");
|
|
7169
|
+
return `${slug}.md`;
|
|
7170
|
+
}
|
|
7171
|
+
async function resolveCodebyplanDir(startDir) {
|
|
7172
|
+
try {
|
|
7173
|
+
const found = await findCodebyplanConfig(startDir);
|
|
7174
|
+
if (!found) return null;
|
|
7175
|
+
if (found.path.endsWith("/repo.json")) {
|
|
7176
|
+
return found.path.slice(0, found.path.lastIndexOf("/"));
|
|
7177
|
+
}
|
|
7178
|
+
return found.path.slice(0, found.path.lastIndexOf("/"));
|
|
7179
|
+
} catch {
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
async function readArchitectureConfig(codebyplanDir) {
|
|
7184
|
+
const filePath = join23(codebyplanDir, "architecture.json");
|
|
7185
|
+
try {
|
|
7186
|
+
const raw = await readFile17(filePath, "utf-8");
|
|
7187
|
+
const parsed = JSON.parse(raw);
|
|
7188
|
+
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.modules)) {
|
|
7189
|
+
return { version: 1, modules: [] };
|
|
7190
|
+
}
|
|
7191
|
+
return parsed;
|
|
7192
|
+
} catch {
|
|
7193
|
+
return { version: 1, modules: [] };
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
async function writeArchitectureConfig(codebyplanDir, config) {
|
|
7197
|
+
const filePath = join23(codebyplanDir, "architecture.json");
|
|
7198
|
+
await writeFile13(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7199
|
+
}
|
|
7200
|
+
function resolveGitSha(modulePath, cwd) {
|
|
7201
|
+
try {
|
|
7202
|
+
const result = spawnSync7(
|
|
7203
|
+
"git",
|
|
7204
|
+
["log", "--format=%H", "-1", "--", modulePath],
|
|
7205
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
7206
|
+
);
|
|
7207
|
+
if (result.status !== 0 || result.error) return null;
|
|
7208
|
+
const sha = (result.stdout ?? "").trim();
|
|
7209
|
+
return sha.length > 0 ? sha : null;
|
|
7210
|
+
} catch {
|
|
7211
|
+
return null;
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
async function discoverModulePaths(projectRoot) {
|
|
7215
|
+
const paths = [];
|
|
7216
|
+
const workspacePath = join23(projectRoot, "pnpm-workspace.yaml");
|
|
7217
|
+
let patterns = [];
|
|
7218
|
+
try {
|
|
7219
|
+
const raw = await readFile17(workspacePath, "utf-8");
|
|
7220
|
+
const lines = raw.split("\n");
|
|
7221
|
+
let inPackages = false;
|
|
7222
|
+
for (const line of lines) {
|
|
7223
|
+
const trimmed = line.trim();
|
|
7224
|
+
if (trimmed.split("#")[0].trim() === "packages:") {
|
|
7225
|
+
inPackages = true;
|
|
7226
|
+
continue;
|
|
7227
|
+
}
|
|
7228
|
+
if (inPackages) {
|
|
7229
|
+
if (trimmed.startsWith("-")) {
|
|
7230
|
+
const pat = trimmed.slice(1).trim().replace(/['"]/g, "");
|
|
7231
|
+
patterns.push(pat);
|
|
7232
|
+
} else if (trimmed && !trimmed.startsWith("#")) {
|
|
7233
|
+
break;
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
} catch {
|
|
7238
|
+
patterns = ["apps/*", "packages/*"];
|
|
7239
|
+
}
|
|
7240
|
+
for (const pattern of patterns) {
|
|
7241
|
+
if (pattern.endsWith("/*")) {
|
|
7242
|
+
const dir = pattern.slice(0, -2);
|
|
7243
|
+
const absDir = join23(projectRoot, dir);
|
|
7244
|
+
try {
|
|
7245
|
+
if (existsSync5(absDir)) {
|
|
7246
|
+
const entries = readdirSync2(absDir, { withFileTypes: true });
|
|
7247
|
+
for (const entry of entries) {
|
|
7248
|
+
if (entry.isDirectory()) {
|
|
7249
|
+
paths.push(`${dir}/${entry.name}`);
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
}
|
|
7253
|
+
} catch {
|
|
7254
|
+
}
|
|
7255
|
+
} else if (!pattern.includes("*")) {
|
|
7256
|
+
const absPath = join23(projectRoot, pattern);
|
|
7257
|
+
if (existsSync5(absPath)) {
|
|
7258
|
+
paths.push(pattern);
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
const crossCutting = ["supabase", ".github", "scripts"];
|
|
7263
|
+
for (const dir of crossCutting) {
|
|
7264
|
+
const absPath = join23(projectRoot, dir);
|
|
7265
|
+
try {
|
|
7266
|
+
if (existsSync5(absPath)) {
|
|
7267
|
+
paths.push(dir);
|
|
7268
|
+
}
|
|
7269
|
+
} catch {
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7272
|
+
return paths;
|
|
7273
|
+
}
|
|
7274
|
+
async function runStatus(projectRoot) {
|
|
7275
|
+
console.log("\n CodeByPlan arch-map status\n");
|
|
7276
|
+
try {
|
|
7277
|
+
const codebyplanDir = await resolveCodebyplanDir(projectRoot);
|
|
7278
|
+
const repoRoot = codebyplanDir ? repoRootFromCodebyplanDir(codebyplanDir) : projectRoot;
|
|
7279
|
+
const config = codebyplanDir ? await readArchitectureConfig(codebyplanDir) : { version: 1, modules: [] };
|
|
7280
|
+
const manifestByPath = /* @__PURE__ */ new Map();
|
|
7281
|
+
for (const entry of config.modules) {
|
|
7282
|
+
manifestByPath.set(normalizeModulePath(entry.path), entry);
|
|
7283
|
+
}
|
|
7284
|
+
let modulePaths = [];
|
|
7285
|
+
try {
|
|
7286
|
+
modulePaths = await discoverModulePaths(repoRoot);
|
|
7287
|
+
} catch {
|
|
7288
|
+
console.warn(" Warning: could not discover modules from workspace");
|
|
7289
|
+
}
|
|
7290
|
+
if (modulePaths.length === 0 && config.modules.length === 0) {
|
|
7291
|
+
console.log(" No modules found.\n");
|
|
7292
|
+
return;
|
|
7293
|
+
}
|
|
7294
|
+
const allPaths = /* @__PURE__ */ new Set([
|
|
7295
|
+
...modulePaths.map(normalizeModulePath),
|
|
7296
|
+
...config.modules.map((e) => normalizeModulePath(e.path))
|
|
7297
|
+
]);
|
|
7298
|
+
console.log(
|
|
7299
|
+
" Module".padEnd(40) + "Map".padEnd(8) + "Stamped".padEnd(12) + "Status"
|
|
7300
|
+
);
|
|
7301
|
+
console.log(" " + "-".repeat(70));
|
|
7302
|
+
for (const modPath of allPaths) {
|
|
7303
|
+
const entry = manifestByPath.get(modPath);
|
|
7304
|
+
const hasMap = !!entry;
|
|
7305
|
+
const stamped = entry?.generated_from_sha != null;
|
|
7306
|
+
let freshness = "-";
|
|
7307
|
+
if (stamped && entry) {
|
|
7308
|
+
const currentSha = resolveGitSha(modPath, repoRoot);
|
|
7309
|
+
if (currentSha == null) {
|
|
7310
|
+
freshness = "unknown";
|
|
7311
|
+
} else if (currentSha === entry.generated_from_sha) {
|
|
7312
|
+
freshness = "fresh";
|
|
7313
|
+
} else {
|
|
7314
|
+
freshness = "stale";
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
console.log(
|
|
7318
|
+
" " + modPath.padEnd(38) + (hasMap ? "yes" : "no").padEnd(8) + (stamped ? "yes" : "no").padEnd(12) + freshness
|
|
7319
|
+
);
|
|
7320
|
+
}
|
|
7321
|
+
console.log();
|
|
7322
|
+
} catch (err) {
|
|
7323
|
+
console.warn(
|
|
7324
|
+
` Warning: arch-map status error: ${err instanceof Error ? err.message : String(err)}`
|
|
7325
|
+
);
|
|
7326
|
+
}
|
|
7327
|
+
}
|
|
7328
|
+
async function runDrift(projectRoot) {
|
|
7329
|
+
console.log("\n CodeByPlan arch-map drift\n");
|
|
7330
|
+
try {
|
|
7331
|
+
const codebyplanDir = await resolveCodebyplanDir(projectRoot);
|
|
7332
|
+
if (!codebyplanDir) {
|
|
7333
|
+
console.log(" No .codebyplan/ directory found \u2014 no manifest to check.");
|
|
7334
|
+
console.log();
|
|
7335
|
+
return;
|
|
7336
|
+
}
|
|
7337
|
+
const repoRoot = repoRootFromCodebyplanDir(codebyplanDir);
|
|
7338
|
+
const config = await readArchitectureConfig(codebyplanDir);
|
|
7339
|
+
const stamped = config.modules.filter((e) => e.generated_from_sha != null);
|
|
7340
|
+
if (stamped.length === 0) {
|
|
7341
|
+
console.log(" No stamped modules found \u2014 run arch-map stamp first.");
|
|
7342
|
+
console.log();
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
let driftCount = 0;
|
|
7346
|
+
for (const entry of stamped) {
|
|
7347
|
+
const currentSha = resolveGitSha(entry.path, repoRoot);
|
|
7348
|
+
if (currentSha == null) {
|
|
7349
|
+
console.log(
|
|
7350
|
+
` ? ${entry.path} \u2014 could not resolve current SHA (git unavailable or path not tracked)`
|
|
7351
|
+
);
|
|
7352
|
+
continue;
|
|
7353
|
+
}
|
|
7354
|
+
if (currentSha !== entry.generated_from_sha) {
|
|
7355
|
+
console.log(` DRIFTED ${entry.path}`);
|
|
7356
|
+
console.log(` stamped: ${entry.generated_from_sha}`);
|
|
7357
|
+
console.log(` current: ${currentSha}`);
|
|
7358
|
+
driftCount++;
|
|
7359
|
+
}
|
|
7360
|
+
}
|
|
7361
|
+
if (driftCount === 0) {
|
|
7362
|
+
console.log(" All stamped modules are fresh.");
|
|
7363
|
+
} else {
|
|
7364
|
+
console.log(`
|
|
7365
|
+
${driftCount} module(s) have drifted since last stamp.`);
|
|
7366
|
+
}
|
|
7367
|
+
console.log();
|
|
7368
|
+
} catch (err) {
|
|
7369
|
+
console.warn(
|
|
7370
|
+
` Warning: arch-map drift error: ${err instanceof Error ? err.message : String(err)}`
|
|
7371
|
+
);
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
function updateFrontmatterFields(content, fields) {
|
|
7375
|
+
const lines = content.split("\n");
|
|
7376
|
+
if (lines.length === 0 || lines[0] !== "---") {
|
|
7377
|
+
return null;
|
|
7378
|
+
}
|
|
7379
|
+
let closeIndex = -1;
|
|
7380
|
+
for (let i = 1; i < lines.length; i++) {
|
|
7381
|
+
if (lines[i] === "---") {
|
|
7382
|
+
closeIndex = i;
|
|
7383
|
+
break;
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
if (closeIndex === -1) {
|
|
7387
|
+
return null;
|
|
7388
|
+
}
|
|
7389
|
+
const result = lines.map((line, idx) => {
|
|
7390
|
+
if (idx === 0 || idx >= closeIndex) {
|
|
7391
|
+
return line;
|
|
7392
|
+
}
|
|
7393
|
+
if (/^generated_from_sha:\s*/.test(line)) {
|
|
7394
|
+
return `generated_from_sha: ${fields.generated_from_sha}`;
|
|
7395
|
+
}
|
|
7396
|
+
if (/^generated_at:\s*/.test(line)) {
|
|
7397
|
+
return `generated_at: ${fields.generated_at}`;
|
|
7398
|
+
}
|
|
7399
|
+
if (fields.depth !== void 0 && /^depth:\s*/.test(line)) {
|
|
7400
|
+
return `depth: ${fields.depth}`;
|
|
7401
|
+
}
|
|
7402
|
+
return line;
|
|
7403
|
+
});
|
|
7404
|
+
return result.join("\n");
|
|
7405
|
+
}
|
|
7406
|
+
async function runStamp(modulePath, sha, depthArg, projectRoot) {
|
|
7407
|
+
const codebyplanDir = await resolveCodebyplanDir(projectRoot);
|
|
7408
|
+
if (!codebyplanDir) {
|
|
7409
|
+
process.stderr.write(
|
|
7410
|
+
" Error: no .codebyplan/ directory found. Run `codebyplan setup` first.\n"
|
|
7411
|
+
);
|
|
7412
|
+
process.exit(1);
|
|
7413
|
+
}
|
|
7414
|
+
const repoRoot = repoRootFromCodebyplanDir(codebyplanDir);
|
|
7415
|
+
let resolvedSha = sha;
|
|
7416
|
+
if (!resolvedSha) {
|
|
7417
|
+
resolvedSha = resolveGitSha(modulePath, repoRoot);
|
|
7418
|
+
if (!resolvedSha) {
|
|
7419
|
+
process.stderr.write(
|
|
7420
|
+
` Error: could not resolve SHA for ${modulePath}. Provide --sha <value>.
|
|
7421
|
+
`
|
|
7422
|
+
);
|
|
7423
|
+
process.exit(1);
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
const config = await readArchitectureConfig(codebyplanDir);
|
|
7427
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7428
|
+
const mapFile = mapFileSlug(modulePath);
|
|
7429
|
+
const mapFilePath = `.claude/architecture/${mapFile}`;
|
|
7430
|
+
const existingIndex = config.modules.findIndex((e) => e.path === modulePath);
|
|
7431
|
+
if (existingIndex >= 0) {
|
|
7432
|
+
config.modules[existingIndex] = {
|
|
7433
|
+
...config.modules[existingIndex],
|
|
7434
|
+
map_file: mapFilePath,
|
|
7435
|
+
generated_from_sha: resolvedSha,
|
|
7436
|
+
generated_at: now,
|
|
7437
|
+
...depthArg !== null ? { depth: depthArg } : {}
|
|
7438
|
+
};
|
|
7439
|
+
} else {
|
|
7440
|
+
config.modules.push({
|
|
7441
|
+
path: modulePath,
|
|
7442
|
+
map_file: mapFilePath,
|
|
7443
|
+
generated_from_sha: resolvedSha,
|
|
7444
|
+
depth: depthArg ?? "skeleton",
|
|
7445
|
+
generated_at: now
|
|
7446
|
+
});
|
|
7447
|
+
}
|
|
7448
|
+
await writeArchitectureConfig(codebyplanDir, config);
|
|
7449
|
+
const stampedEntry = existingIndex >= 0 ? config.modules[existingIndex] : config.modules[config.modules.length - 1];
|
|
7450
|
+
const mapAbsPath = join23(repoRoot, stampedEntry.map_file);
|
|
7451
|
+
let mapContent = null;
|
|
7452
|
+
try {
|
|
7453
|
+
mapContent = await readFile17(mapAbsPath, "utf-8");
|
|
7454
|
+
} catch {
|
|
7455
|
+
console.warn(
|
|
7456
|
+
" Warning: map file " + stampedEntry.map_file + " not found \u2014 stamped manifest only"
|
|
7457
|
+
);
|
|
7458
|
+
}
|
|
7459
|
+
if (mapContent !== null) {
|
|
7460
|
+
const updated = updateFrontmatterFields(mapContent, {
|
|
7461
|
+
generated_from_sha: resolvedSha,
|
|
7462
|
+
generated_at: now,
|
|
7463
|
+
...depthArg !== null ? { depth: depthArg } : {}
|
|
7464
|
+
});
|
|
7465
|
+
if (updated === null) {
|
|
7466
|
+
console.warn(
|
|
7467
|
+
" Warning: no frontmatter block in " + stampedEntry.map_file + " \u2014 stamped manifest only"
|
|
7468
|
+
);
|
|
7469
|
+
} else if (!updated.includes(`generated_from_sha: ${resolvedSha}`) || !updated.includes(`generated_at: ${now}`)) {
|
|
7470
|
+
console.warn(
|
|
7471
|
+
" Warning: " + stampedEntry.map_file + " frontmatter missing generated_from_sha/generated_at \u2014 not mirrored (manifest stamped)"
|
|
7472
|
+
);
|
|
7473
|
+
} else {
|
|
7474
|
+
try {
|
|
7475
|
+
await writeFile13(mapAbsPath, updated, "utf-8");
|
|
7476
|
+
} catch (writeErr) {
|
|
7477
|
+
console.warn(
|
|
7478
|
+
" Warning: could not write map file " + stampedEntry.map_file + " \u2014 " + (writeErr instanceof Error ? writeErr.message : String(writeErr)) + " \u2014 stamped manifest only"
|
|
7479
|
+
);
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
console.log(` Stamped ${modulePath} @ ${resolvedSha.slice(0, 8)} (${now})`);
|
|
7484
|
+
}
|
|
7485
|
+
async function runArchMapCommand(rest) {
|
|
7486
|
+
const subcommand = rest[0];
|
|
7487
|
+
const projectRoot = process.cwd();
|
|
7488
|
+
if (subcommand === "status") {
|
|
7489
|
+
await runStatus(projectRoot);
|
|
7490
|
+
return;
|
|
7491
|
+
}
|
|
7492
|
+
if (subcommand === "drift") {
|
|
7493
|
+
await runDrift(projectRoot);
|
|
7494
|
+
return;
|
|
7495
|
+
}
|
|
7496
|
+
if (subcommand === "stamp") {
|
|
7497
|
+
const rawModulePath = rest[1];
|
|
7498
|
+
if (!rawModulePath) {
|
|
7499
|
+
process.stderr.write(
|
|
7500
|
+
" Usage: codebyplan arch-map stamp <module> [--sha <value>]\n"
|
|
7501
|
+
);
|
|
7502
|
+
process.exit(1);
|
|
7503
|
+
}
|
|
7504
|
+
if (rawModulePath.startsWith("/")) {
|
|
7505
|
+
process.stderr.write(
|
|
7506
|
+
" Error: module path must be relative to the repo root (e.g. apps/web), not absolute.\n"
|
|
7507
|
+
);
|
|
7508
|
+
process.exit(1);
|
|
7509
|
+
}
|
|
7510
|
+
const modulePath = normalizeModulePath(rawModulePath);
|
|
7511
|
+
if (modulePath.split("/").length > 2) {
|
|
7512
|
+
console.warn(
|
|
7513
|
+
` Warning: ${modulePath} has more than 2 path segments; expected a top-level module path (e.g. apps/web).`
|
|
7514
|
+
);
|
|
7515
|
+
}
|
|
7516
|
+
let sha = null;
|
|
7517
|
+
const shaIdx = rest.indexOf("--sha");
|
|
7518
|
+
if (shaIdx !== -1) {
|
|
7519
|
+
const shaVal = rest[shaIdx + 1];
|
|
7520
|
+
if (!shaVal || shaVal.startsWith("--")) {
|
|
7521
|
+
process.stderr.write(" Error: --sha requires a value.\n");
|
|
7522
|
+
process.exit(1);
|
|
7523
|
+
}
|
|
7524
|
+
if (!/^[0-9a-f]{40}$/i.test(shaVal)) {
|
|
7525
|
+
process.stderr.write(
|
|
7526
|
+
" Error: --sha value does not look like a git SHA (expected 40 hex chars).\n"
|
|
7527
|
+
);
|
|
7528
|
+
process.exit(1);
|
|
7529
|
+
}
|
|
7530
|
+
sha = shaVal;
|
|
7531
|
+
}
|
|
7532
|
+
let depthArg = null;
|
|
7533
|
+
const depthIdx = rest.indexOf("--depth");
|
|
7534
|
+
if (depthIdx !== -1) {
|
|
7535
|
+
const depthVal = rest[depthIdx + 1];
|
|
7536
|
+
if (!depthVal || depthVal.startsWith("--")) {
|
|
7537
|
+
process.stderr.write(" Error: --depth requires a value.\n");
|
|
7538
|
+
process.exit(1);
|
|
7539
|
+
}
|
|
7540
|
+
if (depthVal !== "skeleton" && depthVal !== "deep") {
|
|
7541
|
+
process.stderr.write(" Error: --depth must be skeleton or deep.\n");
|
|
7542
|
+
process.exit(1);
|
|
7543
|
+
}
|
|
7544
|
+
depthArg = depthVal;
|
|
7545
|
+
}
|
|
7546
|
+
await runStamp(modulePath, sha, depthArg, projectRoot);
|
|
7547
|
+
return;
|
|
7548
|
+
}
|
|
7549
|
+
console.log(`
|
|
7550
|
+
codebyplan arch-map \u2014 Architecture map pipeline
|
|
7551
|
+
|
|
7552
|
+
Note: arch-map commands are intended to run from the repo root.
|
|
7553
|
+
|
|
7554
|
+
Usage:
|
|
7555
|
+
codebyplan arch-map status List all modules and map freshness
|
|
7556
|
+
codebyplan arch-map drift Report modules drifted since last stamp
|
|
7557
|
+
codebyplan arch-map stamp <module> Stamp a module with its current git SHA
|
|
7558
|
+
codebyplan arch-map stamp <module> --sha <sha> Stamp with an explicit SHA
|
|
7559
|
+
codebyplan arch-map stamp <module> --depth <skeleton|deep> Stamp with an explicit depth
|
|
7560
|
+
`);
|
|
7561
|
+
}
|
|
7562
|
+
var init_arch_map = __esm({
|
|
7563
|
+
"src/cli/arch-map.ts"() {
|
|
7564
|
+
"use strict";
|
|
7565
|
+
init_flags();
|
|
7566
|
+
}
|
|
7567
|
+
});
|
|
7568
|
+
|
|
7145
7569
|
// src/lib/worktree-port-resolver.ts
|
|
7146
7570
|
async function resolveWorktreePortAllocations(repoId, projectPath) {
|
|
7147
7571
|
let resolvedWorktreeId;
|
|
@@ -7231,19 +7655,19 @@ var init_worktree_port_resolver = __esm({
|
|
|
7231
7655
|
});
|
|
7232
7656
|
|
|
7233
7657
|
// src/lib/migrate-local-config.ts
|
|
7234
|
-
import { mkdir as mkdir7, readFile as
|
|
7235
|
-
import { join as
|
|
7658
|
+
import { mkdir as mkdir7, readFile as readFile18, unlink as unlink3, writeFile as writeFile14 } from "node:fs/promises";
|
|
7659
|
+
import { join as join24 } from "node:path";
|
|
7236
7660
|
function legacySharedPath(projectPath) {
|
|
7237
|
-
return
|
|
7661
|
+
return join24(projectPath, ".codebyplan.json");
|
|
7238
7662
|
}
|
|
7239
7663
|
function legacyLocalPath(projectPath) {
|
|
7240
|
-
return
|
|
7664
|
+
return join24(projectPath, ".codebyplan.local.json");
|
|
7241
7665
|
}
|
|
7242
7666
|
function newDirPath(projectPath) {
|
|
7243
|
-
return
|
|
7667
|
+
return join24(projectPath, ".codebyplan");
|
|
7244
7668
|
}
|
|
7245
7669
|
function sentinelPath(projectPath) {
|
|
7246
|
-
return
|
|
7670
|
+
return join24(projectPath, ".codebyplan", "repo.json");
|
|
7247
7671
|
}
|
|
7248
7672
|
async function statSafe(p) {
|
|
7249
7673
|
const { stat: stat2 } = await import("node:fs/promises");
|
|
@@ -7282,7 +7706,7 @@ async function runLocalMigration(projectPath) {
|
|
|
7282
7706
|
}
|
|
7283
7707
|
let legacyRaw;
|
|
7284
7708
|
try {
|
|
7285
|
-
legacyRaw = await
|
|
7709
|
+
legacyRaw = await readFile18(legacySharedPath(projectPath), "utf-8");
|
|
7286
7710
|
} catch {
|
|
7287
7711
|
return {
|
|
7288
7712
|
migrated: true,
|
|
@@ -7309,7 +7733,7 @@ async function runLocalMigration(projectPath) {
|
|
|
7309
7733
|
let deviceId;
|
|
7310
7734
|
let deviceWrittenByHelper = false;
|
|
7311
7735
|
try {
|
|
7312
|
-
const localRaw = await
|
|
7736
|
+
const localRaw = await readFile18(legacyLocalPath(projectPath), "utf-8");
|
|
7313
7737
|
const localParsed = JSON.parse(localRaw);
|
|
7314
7738
|
if (typeof localParsed.device_id === "string") {
|
|
7315
7739
|
deviceId = localParsed.device_id;
|
|
@@ -7336,8 +7760,8 @@ async function runLocalMigration(projectPath) {
|
|
|
7336
7760
|
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
7337
7761
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
7338
7762
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
7339
|
-
await
|
|
7340
|
-
|
|
7763
|
+
await writeFile14(
|
|
7764
|
+
join24(projectPath, ".codebyplan", "repo.json"),
|
|
7341
7765
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
7342
7766
|
"utf-8"
|
|
7343
7767
|
);
|
|
@@ -7349,8 +7773,8 @@ async function runLocalMigration(projectPath) {
|
|
|
7349
7773
|
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
7350
7774
|
if ("port_allocations" in cfg)
|
|
7351
7775
|
serverJson.port_allocations = cfg.port_allocations;
|
|
7352
|
-
await
|
|
7353
|
-
|
|
7776
|
+
await writeFile14(
|
|
7777
|
+
join24(projectPath, ".codebyplan", "server.json"),
|
|
7354
7778
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
7355
7779
|
"utf-8"
|
|
7356
7780
|
);
|
|
@@ -7358,44 +7782,44 @@ async function runLocalMigration(projectPath) {
|
|
|
7358
7782
|
const gitJson = {};
|
|
7359
7783
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
7360
7784
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
7361
|
-
await
|
|
7362
|
-
|
|
7785
|
+
await writeFile14(
|
|
7786
|
+
join24(projectPath, ".codebyplan", "git.json"),
|
|
7363
7787
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
7364
7788
|
"utf-8"
|
|
7365
7789
|
);
|
|
7366
7790
|
filesChanged.push(".codebyplan/git.json");
|
|
7367
7791
|
const shipmentJson = {};
|
|
7368
7792
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
7369
|
-
await
|
|
7370
|
-
|
|
7793
|
+
await writeFile14(
|
|
7794
|
+
join24(projectPath, ".codebyplan", "shipment.json"),
|
|
7371
7795
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
7372
7796
|
"utf-8"
|
|
7373
7797
|
);
|
|
7374
7798
|
filesChanged.push(".codebyplan/shipment.json");
|
|
7375
7799
|
const vendorJson = {};
|
|
7376
|
-
await
|
|
7377
|
-
|
|
7800
|
+
await writeFile14(
|
|
7801
|
+
join24(projectPath, ".codebyplan", "vendor.json"),
|
|
7378
7802
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
7379
7803
|
"utf-8"
|
|
7380
7804
|
);
|
|
7381
7805
|
filesChanged.push(".codebyplan/vendor.json");
|
|
7382
7806
|
const e2eJson = {};
|
|
7383
|
-
await
|
|
7384
|
-
|
|
7807
|
+
await writeFile14(
|
|
7808
|
+
join24(projectPath, ".codebyplan", "e2e.json"),
|
|
7385
7809
|
JSON.stringify(e2eJson, null, 2) + "\n",
|
|
7386
7810
|
"utf-8"
|
|
7387
7811
|
);
|
|
7388
7812
|
filesChanged.push(".codebyplan/e2e.json");
|
|
7389
7813
|
const eslintJson = {};
|
|
7390
|
-
await
|
|
7391
|
-
|
|
7814
|
+
await writeFile14(
|
|
7815
|
+
join24(projectPath, ".codebyplan", "eslint.json"),
|
|
7392
7816
|
JSON.stringify(eslintJson, null, 2) + "\n",
|
|
7393
7817
|
"utf-8"
|
|
7394
7818
|
);
|
|
7395
7819
|
filesChanged.push(".codebyplan/eslint.json");
|
|
7396
7820
|
if (!deviceWrittenByHelper) {
|
|
7397
|
-
await
|
|
7398
|
-
|
|
7821
|
+
await writeFile14(
|
|
7822
|
+
join24(projectPath, ".codebyplan", "device.local.json"),
|
|
7399
7823
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
7400
7824
|
"utf-8"
|
|
7401
7825
|
);
|
|
@@ -7407,9 +7831,9 @@ async function runLocalMigration(projectPath) {
|
|
|
7407
7831
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
7408
7832
|
);
|
|
7409
7833
|
}
|
|
7410
|
-
const gitignorePath =
|
|
7834
|
+
const gitignorePath = join24(projectPath, ".gitignore");
|
|
7411
7835
|
try {
|
|
7412
|
-
const gitignoreContent = await
|
|
7836
|
+
const gitignoreContent = await readFile18(gitignorePath, "utf-8");
|
|
7413
7837
|
const legacyLine = ".codebyplan.local.json";
|
|
7414
7838
|
const newLine = ".codebyplan/device.local.json";
|
|
7415
7839
|
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
@@ -7428,7 +7852,7 @@ async function runLocalMigration(projectPath) {
|
|
|
7428
7852
|
updated = gitignoreContent;
|
|
7429
7853
|
}
|
|
7430
7854
|
if (updated !== gitignoreContent) {
|
|
7431
|
-
await
|
|
7855
|
+
await writeFile14(gitignorePath, updated, "utf-8");
|
|
7432
7856
|
filesChanged.push(".gitignore");
|
|
7433
7857
|
}
|
|
7434
7858
|
} catch {
|
|
@@ -7469,8 +7893,8 @@ __export(config_exports, {
|
|
|
7469
7893
|
readVendorConfig: () => readVendorConfig,
|
|
7470
7894
|
runConfig: () => runConfig
|
|
7471
7895
|
});
|
|
7472
|
-
import { mkdir as mkdir8, readFile as
|
|
7473
|
-
import { join as
|
|
7896
|
+
import { mkdir as mkdir8, readFile as readFile19, writeFile as writeFile15 } from "node:fs/promises";
|
|
7897
|
+
import { join as join25 } from "node:path";
|
|
7474
7898
|
async function runConfig() {
|
|
7475
7899
|
const flags = parseFlags(3);
|
|
7476
7900
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -7503,7 +7927,7 @@ async function runConfig() {
|
|
|
7503
7927
|
console.log("\n Config complete.\n");
|
|
7504
7928
|
}
|
|
7505
7929
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
7506
|
-
const codebyplanDir =
|
|
7930
|
+
const codebyplanDir = join25(projectPath, ".codebyplan");
|
|
7507
7931
|
const {
|
|
7508
7932
|
resolvedWorktreeId,
|
|
7509
7933
|
portAllocations,
|
|
@@ -7571,6 +7995,10 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
7571
7995
|
const vendorPayload = {};
|
|
7572
7996
|
const e2ePayload = {};
|
|
7573
7997
|
const eslintPayload = {};
|
|
7998
|
+
const architecturePayload = {
|
|
7999
|
+
version: 1,
|
|
8000
|
+
modules: []
|
|
8001
|
+
};
|
|
7574
8002
|
if (dryRun) {
|
|
7575
8003
|
console.log(" Config would be updated (dry-run).");
|
|
7576
8004
|
return;
|
|
@@ -7583,20 +8011,25 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
7583
8011
|
{ name: "shipment.json", payload: shipmentPayload },
|
|
7584
8012
|
{ name: "vendor.json", payload: vendorPayload },
|
|
7585
8013
|
{ name: "e2e.json", payload: e2ePayload, createOnly: true },
|
|
7586
|
-
{ name: "eslint.json", payload: eslintPayload, createOnly: true }
|
|
8014
|
+
{ name: "eslint.json", payload: eslintPayload, createOnly: true },
|
|
8015
|
+
{
|
|
8016
|
+
name: "architecture.json",
|
|
8017
|
+
payload: architecturePayload,
|
|
8018
|
+
createOnly: true
|
|
8019
|
+
}
|
|
7587
8020
|
];
|
|
7588
8021
|
let anyUpdated = false;
|
|
7589
8022
|
for (const { name, payload, createOnly } of files) {
|
|
7590
|
-
const filePath =
|
|
8023
|
+
const filePath = join25(codebyplanDir, name);
|
|
7591
8024
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
7592
8025
|
let currentJson = "";
|
|
7593
8026
|
try {
|
|
7594
|
-
currentJson = await
|
|
8027
|
+
currentJson = await readFile19(filePath, "utf-8");
|
|
7595
8028
|
} catch {
|
|
7596
8029
|
}
|
|
7597
8030
|
if (createOnly && currentJson !== "") continue;
|
|
7598
8031
|
if (currentJson === newJson) continue;
|
|
7599
|
-
await
|
|
8032
|
+
await writeFile15(filePath, newJson, "utf-8");
|
|
7600
8033
|
console.log(` Updated .codebyplan/${name}`);
|
|
7601
8034
|
anyUpdated = true;
|
|
7602
8035
|
}
|
|
@@ -7606,8 +8039,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
7606
8039
|
}
|
|
7607
8040
|
async function readRepoConfig(projectPath) {
|
|
7608
8041
|
try {
|
|
7609
|
-
const raw = await
|
|
7610
|
-
|
|
8042
|
+
const raw = await readFile19(
|
|
8043
|
+
join25(projectPath, ".codebyplan", "repo.json"),
|
|
7611
8044
|
"utf-8"
|
|
7612
8045
|
);
|
|
7613
8046
|
return JSON.parse(raw);
|
|
@@ -7617,8 +8050,8 @@ async function readRepoConfig(projectPath) {
|
|
|
7617
8050
|
}
|
|
7618
8051
|
async function readServerConfig(projectPath) {
|
|
7619
8052
|
try {
|
|
7620
|
-
const raw = await
|
|
7621
|
-
|
|
8053
|
+
const raw = await readFile19(
|
|
8054
|
+
join25(projectPath, ".codebyplan", "server.json"),
|
|
7622
8055
|
"utf-8"
|
|
7623
8056
|
);
|
|
7624
8057
|
return JSON.parse(raw);
|
|
@@ -7628,8 +8061,8 @@ async function readServerConfig(projectPath) {
|
|
|
7628
8061
|
}
|
|
7629
8062
|
async function readGitConfig(projectPath) {
|
|
7630
8063
|
try {
|
|
7631
|
-
const raw = await
|
|
7632
|
-
|
|
8064
|
+
const raw = await readFile19(
|
|
8065
|
+
join25(projectPath, ".codebyplan", "git.json"),
|
|
7633
8066
|
"utf-8"
|
|
7634
8067
|
);
|
|
7635
8068
|
return JSON.parse(raw);
|
|
@@ -7639,8 +8072,8 @@ async function readGitConfig(projectPath) {
|
|
|
7639
8072
|
}
|
|
7640
8073
|
async function readShipmentConfig(projectPath) {
|
|
7641
8074
|
try {
|
|
7642
|
-
const raw = await
|
|
7643
|
-
|
|
8075
|
+
const raw = await readFile19(
|
|
8076
|
+
join25(projectPath, ".codebyplan", "shipment.json"),
|
|
7644
8077
|
"utf-8"
|
|
7645
8078
|
);
|
|
7646
8079
|
return JSON.parse(raw);
|
|
@@ -7650,8 +8083,8 @@ async function readShipmentConfig(projectPath) {
|
|
|
7650
8083
|
}
|
|
7651
8084
|
async function readVendorConfig(projectPath) {
|
|
7652
8085
|
try {
|
|
7653
|
-
const raw = await
|
|
7654
|
-
|
|
8086
|
+
const raw = await readFile19(
|
|
8087
|
+
join25(projectPath, ".codebyplan", "vendor.json"),
|
|
7655
8088
|
"utf-8"
|
|
7656
8089
|
);
|
|
7657
8090
|
return JSON.parse(raw);
|
|
@@ -7661,8 +8094,8 @@ async function readVendorConfig(projectPath) {
|
|
|
7661
8094
|
}
|
|
7662
8095
|
async function readE2eConfig2(projectPath) {
|
|
7663
8096
|
try {
|
|
7664
|
-
const raw = await
|
|
7665
|
-
|
|
8097
|
+
const raw = await readFile19(
|
|
8098
|
+
join25(projectPath, ".codebyplan", "e2e.json"),
|
|
7666
8099
|
"utf-8"
|
|
7667
8100
|
);
|
|
7668
8101
|
return JSON.parse(raw);
|
|
@@ -7672,8 +8105,8 @@ async function readE2eConfig2(projectPath) {
|
|
|
7672
8105
|
}
|
|
7673
8106
|
async function readServerLocalConfig(projectPath) {
|
|
7674
8107
|
try {
|
|
7675
|
-
const raw = await
|
|
7676
|
-
|
|
8108
|
+
const raw = await readFile19(
|
|
8109
|
+
join25(projectPath, ".codebyplan", "server.local.json"),
|
|
7677
8110
|
"utf-8"
|
|
7678
8111
|
);
|
|
7679
8112
|
return JSON.parse(raw);
|
|
@@ -7728,14 +8161,14 @@ var init_server_detect = __esm({
|
|
|
7728
8161
|
});
|
|
7729
8162
|
|
|
7730
8163
|
// src/lib/port-verify.ts
|
|
7731
|
-
import { readFile as
|
|
8164
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
7732
8165
|
async function verifyPorts(projectPath, portAllocations) {
|
|
7733
8166
|
const mismatches = [];
|
|
7734
8167
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
7735
8168
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
7736
8169
|
for (const pkgPath of packageJsonPaths) {
|
|
7737
8170
|
try {
|
|
7738
|
-
const raw = await
|
|
8171
|
+
const raw = await readFile20(pkgPath, "utf-8");
|
|
7739
8172
|
const pkg = JSON.parse(raw);
|
|
7740
8173
|
const scriptPort = detectPortFromScripts(pkg);
|
|
7741
8174
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -7798,7 +8231,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
7798
8231
|
}
|
|
7799
8232
|
let pkg;
|
|
7800
8233
|
try {
|
|
7801
|
-
const raw = await
|
|
8234
|
+
const raw = await readFile20(`${app.absPath}/package.json`, "utf-8");
|
|
7802
8235
|
pkg = JSON.parse(raw);
|
|
7803
8236
|
} catch {
|
|
7804
8237
|
continue;
|
|
@@ -7848,8 +8281,8 @@ __export(ports_exports, {
|
|
|
7848
8281
|
parseEnvFile: () => parseEnvFile,
|
|
7849
8282
|
runPorts: () => runPorts
|
|
7850
8283
|
});
|
|
7851
|
-
import { mkdir as mkdir9, readFile as
|
|
7852
|
-
import { join as
|
|
8284
|
+
import { mkdir as mkdir9, readFile as readFile21, writeFile as writeFile16 } from "node:fs/promises";
|
|
8285
|
+
import { join as join26 } from "node:path";
|
|
7853
8286
|
async function runPorts() {
|
|
7854
8287
|
const flags = parseFlags(3);
|
|
7855
8288
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -7970,12 +8403,12 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
|
|
|
7970
8403
|
// and ServerLocalConfig.port_allocations is typed the same — honest end-to-end.
|
|
7971
8404
|
port_allocations: portAllocations
|
|
7972
8405
|
};
|
|
7973
|
-
const codebyplanDir =
|
|
7974
|
-
const filePath =
|
|
8406
|
+
const codebyplanDir = join26(projectPath, ".codebyplan");
|
|
8407
|
+
const filePath = join26(codebyplanDir, "server.local.json");
|
|
7975
8408
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
7976
8409
|
let currentJson = "";
|
|
7977
8410
|
try {
|
|
7978
|
-
currentJson = await
|
|
8411
|
+
currentJson = await readFile21(filePath, "utf-8");
|
|
7979
8412
|
} catch {
|
|
7980
8413
|
}
|
|
7981
8414
|
if (currentJson === newJson) {
|
|
@@ -7987,17 +8420,17 @@ async function writeServerLocalConfig(repoId, projectPath, dryRun) {
|
|
|
7987
8420
|
return;
|
|
7988
8421
|
}
|
|
7989
8422
|
await mkdir9(codebyplanDir, { recursive: true });
|
|
7990
|
-
await
|
|
8423
|
+
await writeFile16(filePath, newJson, "utf-8");
|
|
7991
8424
|
console.log(
|
|
7992
8425
|
` Updated .codebyplan/server.local.json (worktree ${resolvedWorktreeId ?? "\u2014"}, ${portAllocations.length} allocation${portAllocations.length === 1 ? "" : "s"}).`
|
|
7993
8426
|
);
|
|
7994
8427
|
}
|
|
7995
8428
|
async function provisionE2eEnv(projectPath, dryRun) {
|
|
7996
|
-
const relSource =
|
|
7997
|
-
const sourcePath =
|
|
8429
|
+
const relSource = join26("apps", "web", ".env.local");
|
|
8430
|
+
const sourcePath = join26(projectPath, relSource);
|
|
7998
8431
|
let sourceRaw;
|
|
7999
8432
|
try {
|
|
8000
|
-
sourceRaw = await
|
|
8433
|
+
sourceRaw = await readFile21(sourcePath, "utf-8");
|
|
8001
8434
|
} catch {
|
|
8002
8435
|
console.warn(
|
|
8003
8436
|
` Skipped .codebyplan/e2e.env \u2014 source ${relSource} not found.`
|
|
@@ -8026,12 +8459,12 @@ async function provisionE2eEnv(projectPath, dryRun) {
|
|
|
8026
8459
|
);
|
|
8027
8460
|
return;
|
|
8028
8461
|
}
|
|
8029
|
-
const codebyplanDir =
|
|
8030
|
-
const filePath =
|
|
8462
|
+
const codebyplanDir = join26(projectPath, ".codebyplan");
|
|
8463
|
+
const filePath = join26(codebyplanDir, "e2e.env");
|
|
8031
8464
|
const newContent = lines.join("\n") + "\n";
|
|
8032
8465
|
let currentContent = "";
|
|
8033
8466
|
try {
|
|
8034
|
-
currentContent = await
|
|
8467
|
+
currentContent = await readFile21(filePath, "utf-8");
|
|
8035
8468
|
} catch {
|
|
8036
8469
|
}
|
|
8037
8470
|
if (currentContent === newContent) {
|
|
@@ -8043,7 +8476,7 @@ async function provisionE2eEnv(projectPath, dryRun) {
|
|
|
8043
8476
|
return;
|
|
8044
8477
|
}
|
|
8045
8478
|
await mkdir9(codebyplanDir, { recursive: true });
|
|
8046
|
-
await
|
|
8479
|
+
await writeFile16(filePath, newContent, "utf-8");
|
|
8047
8480
|
console.log(
|
|
8048
8481
|
` Provisioned .codebyplan/e2e.env (${lines.length} var${lines.length === 1 ? "" : "s"}).`
|
|
8049
8482
|
);
|
|
@@ -8093,7 +8526,7 @@ __export(tech_stack_exports, {
|
|
|
8093
8526
|
runFullTechStack: () => runFullTechStack,
|
|
8094
8527
|
runTechStack: () => runTechStack
|
|
8095
8528
|
});
|
|
8096
|
-
import { existsSync as
|
|
8529
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
8097
8530
|
async function runTechStack() {
|
|
8098
8531
|
const flags = parseFlags(3);
|
|
8099
8532
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -8266,7 +8699,7 @@ async function runFullTechStack(dryRun) {
|
|
|
8266
8699
|
continue;
|
|
8267
8700
|
}
|
|
8268
8701
|
const localWorktrees = worktrees.filter(
|
|
8269
|
-
(wt) => wt.path ?
|
|
8702
|
+
(wt) => wt.path ? existsSync6(wt.path) : false
|
|
8270
8703
|
);
|
|
8271
8704
|
if (localWorktrees.length === 0) {
|
|
8272
8705
|
console.log(` skipping ${repo.name} \u2014 no local worktree on this device`);
|
|
@@ -8366,7 +8799,7 @@ var init_claude_plan = __esm({
|
|
|
8366
8799
|
// src/cli/claude/status.ts
|
|
8367
8800
|
var status_exports = {};
|
|
8368
8801
|
__export(status_exports, {
|
|
8369
|
-
runStatus: () =>
|
|
8802
|
+
runStatus: () => runStatus2
|
|
8370
8803
|
});
|
|
8371
8804
|
import * as fs6 from "node:fs";
|
|
8372
8805
|
import * as path7 from "node:path";
|
|
@@ -8433,7 +8866,7 @@ function resolveCurrentBranch2() {
|
|
|
8433
8866
|
}
|
|
8434
8867
|
}
|
|
8435
8868
|
}
|
|
8436
|
-
async function
|
|
8869
|
+
async function runStatus2(argv) {
|
|
8437
8870
|
const checked_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
8438
8871
|
const writeCache = argv.includes("--write-cache");
|
|
8439
8872
|
const quiet = argv.includes("--quiet");
|
|
@@ -8616,11 +9049,11 @@ async function ask(q, opts) {
|
|
|
8616
9049
|
try {
|
|
8617
9050
|
while (true) {
|
|
8618
9051
|
const choices = q.choices.map((c) => `[${c.key}] ${c.label}`).join(" ");
|
|
8619
|
-
const answer = await new Promise((
|
|
9052
|
+
const answer = await new Promise((resolve12) => {
|
|
8620
9053
|
rl.question(`${q.message}
|
|
8621
9054
|
${choices}
|
|
8622
9055
|
> `, (input) => {
|
|
8623
|
-
|
|
9056
|
+
resolve12(input.trim().toLowerCase());
|
|
8624
9057
|
});
|
|
8625
9058
|
});
|
|
8626
9059
|
const match = q.choices.find(
|
|
@@ -9326,16 +9759,303 @@ var init_structure_generator = __esm({
|
|
|
9326
9759
|
}
|
|
9327
9760
|
});
|
|
9328
9761
|
|
|
9762
|
+
// src/lib/readme-generator.ts
|
|
9763
|
+
function managedStartMarker(section) {
|
|
9764
|
+
return `${MANAGED_START_PREFIX}${section}${MANAGED_MARKER_SUFFIX}`;
|
|
9765
|
+
}
|
|
9766
|
+
function managedEndMarker(section) {
|
|
9767
|
+
return `${MANAGED_END_PREFIX}${section}${MANAGED_MARKER_SUFFIX}`;
|
|
9768
|
+
}
|
|
9769
|
+
function hasManagedMarkers(content) {
|
|
9770
|
+
return content.includes(MANAGED_START_PREFIX) || content.includes(MANAGED_END_PREFIX);
|
|
9771
|
+
}
|
|
9772
|
+
function validateManagedMarkers(content) {
|
|
9773
|
+
const errors = [];
|
|
9774
|
+
const startRe = /<!-- codebyplan:managed:start:([a-z0-9][a-z0-9-]*) -->/g;
|
|
9775
|
+
const endRe = /<!-- codebyplan:managed:end:([a-z0-9][a-z0-9-]*) -->/g;
|
|
9776
|
+
const starts = [...content.matchAll(startRe)].map((m) => m[1]);
|
|
9777
|
+
const ends = [...content.matchAll(endRe)].map((m) => m[1]);
|
|
9778
|
+
const seenStarts = /* @__PURE__ */ new Set();
|
|
9779
|
+
for (const s of starts) {
|
|
9780
|
+
if (seenStarts.has(s)) {
|
|
9781
|
+
errors.push(`duplicate managed section '${s}'`);
|
|
9782
|
+
}
|
|
9783
|
+
seenStarts.add(s);
|
|
9784
|
+
if (!ends.includes(s)) {
|
|
9785
|
+
errors.push(`missing end marker for section '${s}'`);
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9788
|
+
for (const e of ends) {
|
|
9789
|
+
if (!starts.includes(e)) {
|
|
9790
|
+
errors.push(`end marker without start marker for section '${e}'`);
|
|
9791
|
+
}
|
|
9792
|
+
}
|
|
9793
|
+
return errors;
|
|
9794
|
+
}
|
|
9795
|
+
function renderTechStackSection(techStack) {
|
|
9796
|
+
const entries = Object.entries(techStack).filter(([, value]) => value.trim().length > 0).sort(([a], [b]) => a.localeCompare(b));
|
|
9797
|
+
if (entries.length === 0) return "";
|
|
9798
|
+
const lines = entries.map(([cat, names]) => `- **${cat}**: ${names}`);
|
|
9799
|
+
return `## Tech Stack
|
|
9800
|
+
|
|
9801
|
+
${lines.join("\n")}
|
|
9802
|
+
`;
|
|
9803
|
+
}
|
|
9804
|
+
function renderStructureSection(packages) {
|
|
9805
|
+
if (packages.length === 0) return "";
|
|
9806
|
+
const sorted = [...packages].sort((a, b) => a.path.localeCompare(b.path));
|
|
9807
|
+
const rows = sorted.map(
|
|
9808
|
+
(p) => `| \`${p.path}\` | \`${p.name}\` | ${p.purpose ?? ""} |`
|
|
9809
|
+
);
|
|
9810
|
+
return `## Structure
|
|
9811
|
+
|
|
9812
|
+
| Location | Package | Purpose |
|
|
9813
|
+
|----------|---------|----------|
|
|
9814
|
+
` + rows.join("\n") + "\n";
|
|
9815
|
+
}
|
|
9816
|
+
function renderScriptsSection(scripts, installCommand) {
|
|
9817
|
+
const sortedEntries = Object.entries(scripts).filter(([, cmd]) => typeof cmd === "string" && cmd.trim().length > 0).sort(([a], [b]) => a.localeCompare(b));
|
|
9818
|
+
const lines = [];
|
|
9819
|
+
lines.push(`\`\`\`bash`);
|
|
9820
|
+
lines.push(`# Install`);
|
|
9821
|
+
lines.push(installCommand);
|
|
9822
|
+
lines.push(``);
|
|
9823
|
+
if (sortedEntries.length > 0) {
|
|
9824
|
+
lines.push(`# Available scripts`);
|
|
9825
|
+
for (const [name, cmd] of sortedEntries) {
|
|
9826
|
+
lines.push(`# ${name}`);
|
|
9827
|
+
lines.push(cmd);
|
|
9828
|
+
}
|
|
9829
|
+
}
|
|
9830
|
+
lines.push(`\`\`\``);
|
|
9831
|
+
return `## Scripts
|
|
9832
|
+
|
|
9833
|
+
${lines.join("\n")}
|
|
9834
|
+
`;
|
|
9835
|
+
}
|
|
9836
|
+
function buildManagedSections(config) {
|
|
9837
|
+
const sections = {};
|
|
9838
|
+
if (config.techStack && Object.keys(config.techStack).length > 0) {
|
|
9839
|
+
const rendered = renderTechStackSection(config.techStack);
|
|
9840
|
+
if (rendered) {
|
|
9841
|
+
sections["tech-stack"] = rendered;
|
|
9842
|
+
}
|
|
9843
|
+
}
|
|
9844
|
+
if (config.packages && config.packages.length > 0) {
|
|
9845
|
+
const rendered = renderStructureSection(config.packages);
|
|
9846
|
+
if (rendered) {
|
|
9847
|
+
sections["structure"] = rendered;
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
if (config.scripts && Object.keys(config.scripts).length > 0) {
|
|
9851
|
+
const pm = config.packageManager ?? "pnpm";
|
|
9852
|
+
const pmBase = pm.split("@")[0] ?? "pnpm";
|
|
9853
|
+
const installCmd = config.installCommand ?? `${pmBase} install`;
|
|
9854
|
+
const rendered = renderScriptsSection(config.scripts, installCmd);
|
|
9855
|
+
if (rendered) {
|
|
9856
|
+
sections["scripts"] = rendered;
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
return sections;
|
|
9860
|
+
}
|
|
9861
|
+
function hashManagedContent(sections) {
|
|
9862
|
+
const sortedKeys = Object.keys(sections).sort();
|
|
9863
|
+
const payload = {};
|
|
9864
|
+
for (const k of sortedKeys) {
|
|
9865
|
+
payload[k] = sections[k];
|
|
9866
|
+
}
|
|
9867
|
+
return sha256(JSON.stringify(payload));
|
|
9868
|
+
}
|
|
9869
|
+
function scaffoldReadme(config) {
|
|
9870
|
+
const title = config.name ?? "Project";
|
|
9871
|
+
const description = config.description ?? "";
|
|
9872
|
+
const sections = buildManagedSections(config);
|
|
9873
|
+
const hash = hashManagedContent(sections);
|
|
9874
|
+
const lines = [];
|
|
9875
|
+
lines.push(`# ${title}`);
|
|
9876
|
+
lines.push(``);
|
|
9877
|
+
if (description) {
|
|
9878
|
+
lines.push(description);
|
|
9879
|
+
lines.push(``);
|
|
9880
|
+
}
|
|
9881
|
+
const sectionKeys = Object.keys(sections).sort();
|
|
9882
|
+
for (const key of sectionKeys) {
|
|
9883
|
+
lines.push(managedStartMarker(key));
|
|
9884
|
+
lines.push(sections[key].trimEnd());
|
|
9885
|
+
lines.push(managedEndMarker(key));
|
|
9886
|
+
lines.push(``);
|
|
9887
|
+
}
|
|
9888
|
+
lines.push(`${HASH_MARKER_PREFIX}${hash}${MANAGED_MARKER_SUFFIX}`);
|
|
9889
|
+
lines.push(``);
|
|
9890
|
+
return lines.join("\n");
|
|
9891
|
+
}
|
|
9892
|
+
function refreshManagedSections(existing, sections) {
|
|
9893
|
+
let content = existing;
|
|
9894
|
+
const startPattern = new RegExp(
|
|
9895
|
+
`${escapeRegex(MANAGED_START_PREFIX)}([a-z0-9][a-z0-9-]*)${escapeRegex(MANAGED_MARKER_SUFFIX)}[\\s\\S]*?${escapeRegex(MANAGED_END_PREFIX)}\\1${escapeRegex(MANAGED_MARKER_SUFFIX)}`,
|
|
9896
|
+
"g"
|
|
9897
|
+
);
|
|
9898
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9899
|
+
content = content.replace(startPattern, (_, sectionName) => {
|
|
9900
|
+
const key = sectionName.trim();
|
|
9901
|
+
seen.add(key);
|
|
9902
|
+
if (Object.prototype.hasOwnProperty.call(sections, key)) {
|
|
9903
|
+
const newBody = sections[key].trimEnd();
|
|
9904
|
+
return managedStartMarker(key) + "\n" + newBody + "\n" + managedEndMarker(key);
|
|
9905
|
+
} else {
|
|
9906
|
+
return "";
|
|
9907
|
+
}
|
|
9908
|
+
});
|
|
9909
|
+
content = content.replace(/\n{3,}/g, "\n\n");
|
|
9910
|
+
const newSectionKeys = Object.keys(sections).sort().filter((k) => !seen.has(k));
|
|
9911
|
+
if (newSectionKeys.length > 0) {
|
|
9912
|
+
const hashCommentPattern = new RegExp(
|
|
9913
|
+
`
|
|
9914
|
+
${escapeRegex(HASH_MARKER_PREFIX)}[^
|
|
9915
|
+
]*${escapeRegex(MANAGED_MARKER_SUFFIX)}(
|
|
9916
|
+
|$)`
|
|
9917
|
+
);
|
|
9918
|
+
content = content.replace(hashCommentPattern, "\n");
|
|
9919
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
9920
|
+
for (const key of newSectionKeys) {
|
|
9921
|
+
content += "\n" + managedStartMarker(key) + "\n" + sections[key].trimEnd() + "\n" + managedEndMarker(key) + "\n";
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
const newHash = hashManagedContent(sections);
|
|
9925
|
+
const hashLine = `${HASH_MARKER_PREFIX}${newHash}${MANAGED_MARKER_SUFFIX}`;
|
|
9926
|
+
const existingHashPattern = new RegExp(
|
|
9927
|
+
`${escapeRegex(HASH_MARKER_PREFIX)}[^
|
|
9928
|
+
]*${escapeRegex(MANAGED_MARKER_SUFFIX)}`
|
|
9929
|
+
);
|
|
9930
|
+
if (existingHashPattern.test(content)) {
|
|
9931
|
+
content = content.replace(existingHashPattern, hashLine);
|
|
9932
|
+
} else {
|
|
9933
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
9934
|
+
content += `${hashLine}
|
|
9935
|
+
`;
|
|
9936
|
+
}
|
|
9937
|
+
content = content.trimEnd() + "\n";
|
|
9938
|
+
return content;
|
|
9939
|
+
}
|
|
9940
|
+
function escapeRegex(str) {
|
|
9941
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9942
|
+
}
|
|
9943
|
+
function extractManagedHash(content) {
|
|
9944
|
+
const hashPattern = new RegExp(
|
|
9945
|
+
`${escapeRegex(HASH_MARKER_PREFIX)}(sha256:[0-9a-f]{64})${escapeRegex(MANAGED_MARKER_SUFFIX)}`
|
|
9946
|
+
);
|
|
9947
|
+
const m = hashPattern.exec(content);
|
|
9948
|
+
return m?.[1] ?? null;
|
|
9949
|
+
}
|
|
9950
|
+
var MANAGED_START_PREFIX, MANAGED_END_PREFIX, MANAGED_MARKER_SUFFIX, HASH_MARKER_PREFIX;
|
|
9951
|
+
var init_readme_generator = __esm({
|
|
9952
|
+
"src/lib/readme-generator.ts"() {
|
|
9953
|
+
"use strict";
|
|
9954
|
+
init_hash();
|
|
9955
|
+
MANAGED_START_PREFIX = "<!-- codebyplan:managed:start:";
|
|
9956
|
+
MANAGED_END_PREFIX = "<!-- codebyplan:managed:end:";
|
|
9957
|
+
MANAGED_MARKER_SUFFIX = " -->";
|
|
9958
|
+
HASH_MARKER_PREFIX = "<!-- codebyplan:hash:";
|
|
9959
|
+
}
|
|
9960
|
+
});
|
|
9961
|
+
|
|
9962
|
+
// src/lib/agents-generator.ts
|
|
9963
|
+
function stripStructureSentinels(structureContent) {
|
|
9964
|
+
const lines = structureContent.split("\n");
|
|
9965
|
+
const filtered = lines.filter((line) => {
|
|
9966
|
+
const trimmed = line.trim();
|
|
9967
|
+
return !trimmed.startsWith("<!-- @codebyplan-generated:");
|
|
9968
|
+
});
|
|
9969
|
+
let result = filtered.join("\n");
|
|
9970
|
+
result = result.replace(/^\n+/, "").replace(/\n+$/, "");
|
|
9971
|
+
return result;
|
|
9972
|
+
}
|
|
9973
|
+
function buildCuratedGuidance(cleanStructureContent) {
|
|
9974
|
+
const lines = [];
|
|
9975
|
+
lines.push(`## Project Context`);
|
|
9976
|
+
lines.push(``);
|
|
9977
|
+
lines.push(
|
|
9978
|
+
`This file is auto-generated for cross-tool compatibility. The canonical source of truth for project instructions is \`.claude/CLAUDE.md\`. If your tool supports Claude Code's \`@\` import syntax, read \`.claude/CLAUDE.md\` directly.`
|
|
9979
|
+
);
|
|
9980
|
+
lines.push(``);
|
|
9981
|
+
if (cleanStructureContent.trim().length > 0) {
|
|
9982
|
+
lines.push(cleanStructureContent.trim());
|
|
9983
|
+
} else {
|
|
9984
|
+
lines.push(
|
|
9985
|
+
`_Structure not yet generated. Run \`codebyplan claude generate\` to populate._`
|
|
9986
|
+
);
|
|
9987
|
+
}
|
|
9988
|
+
lines.push(``);
|
|
9989
|
+
lines.push(`## Source of Truth`);
|
|
9990
|
+
lines.push(``);
|
|
9991
|
+
lines.push(
|
|
9992
|
+
`The database is the sole source of truth for all development data. No local markdown files for tracking \u2014 everything is in the DB, accessed via MCP tools or the Web UI.`
|
|
9993
|
+
);
|
|
9994
|
+
lines.push(``);
|
|
9995
|
+
lines.push(`## \`.claude/\` Layout`);
|
|
9996
|
+
lines.push(``);
|
|
9997
|
+
lines.push(`| Directory | Purpose |`);
|
|
9998
|
+
lines.push(`|-----------|---------|`);
|
|
9999
|
+
lines.push(`| \`rules/\` | Auto-loaded behavioral constraints |`);
|
|
10000
|
+
lines.push(`| \`skills/\` | User-invocable \`/cbp-*\` commands |`);
|
|
10001
|
+
lines.push(`| \`agents/\` | Spawned sub-agents |`);
|
|
10002
|
+
lines.push(`| \`context/\` | Agent/skill input data |`);
|
|
10003
|
+
lines.push(
|
|
10004
|
+
`| \`CLAUDE.md\` | Canonical project context (always loaded by Claude Code) |`
|
|
10005
|
+
);
|
|
10006
|
+
lines.push(``);
|
|
10007
|
+
lines.push(`## Port Rule`);
|
|
10008
|
+
lines.push(``);
|
|
10009
|
+
lines.push(
|
|
10010
|
+
`Never hardcode port numbers. Resolve from \`.codebyplan/server.json\` \`port_allocations[]\`. Each entry carries \`port\`, \`label\`, and \`server_type\`.`
|
|
10011
|
+
);
|
|
10012
|
+
lines.push(``);
|
|
10013
|
+
lines.push(`## Git`);
|
|
10014
|
+
lines.push(``);
|
|
10015
|
+
lines.push(
|
|
10016
|
+
`Do not include any mention of AI tools or code-assistant products in git commit messages.`
|
|
10017
|
+
);
|
|
10018
|
+
lines.push(``);
|
|
10019
|
+
return lines.join("\n");
|
|
10020
|
+
}
|
|
10021
|
+
function generateAgentsMd(structureContent) {
|
|
10022
|
+
const cleanStructure = stripStructureSentinels(structureContent);
|
|
10023
|
+
const bodyContent = buildCuratedGuidance(cleanStructure);
|
|
10024
|
+
const managedBody = [
|
|
10025
|
+
managedStartMarker(AGENTS_SECTION_NAME),
|
|
10026
|
+
bodyContent.trimEnd(),
|
|
10027
|
+
managedEndMarker(AGENTS_SECTION_NAME)
|
|
10028
|
+
].join("\n");
|
|
10029
|
+
const hash = hashManagedContent({ [AGENTS_SECTION_NAME]: bodyContent });
|
|
10030
|
+
const hashLine = `${HASH_MARKER_PREFIX}${hash}${MANAGED_MARKER_SUFFIX}`;
|
|
10031
|
+
const header = [
|
|
10032
|
+
`<!-- GENERATED \u2014 do not hand-edit. Canonical source: .claude/CLAUDE.md -->`,
|
|
10033
|
+
`<!-- Regenerate: codebyplan claude generate -->`
|
|
10034
|
+
].join("\n");
|
|
10035
|
+
return [header, ``, managedBody, ``, hashLine, ``].join("\n");
|
|
10036
|
+
}
|
|
10037
|
+
function extractAgentsHash(content) {
|
|
10038
|
+
return extractManagedHash(content);
|
|
10039
|
+
}
|
|
10040
|
+
var AGENTS_SECTION_NAME;
|
|
10041
|
+
var init_agents_generator = __esm({
|
|
10042
|
+
"src/lib/agents-generator.ts"() {
|
|
10043
|
+
"use strict";
|
|
10044
|
+
init_readme_generator();
|
|
10045
|
+
AGENTS_SECTION_NAME = "agents-context";
|
|
10046
|
+
}
|
|
10047
|
+
});
|
|
10048
|
+
|
|
9329
10049
|
// src/cli/claude/generate.ts
|
|
9330
10050
|
var generate_exports = {};
|
|
9331
10051
|
__export(generate_exports, {
|
|
9332
10052
|
runGenerate: () => runGenerate
|
|
9333
10053
|
});
|
|
9334
|
-
import { readFile as
|
|
9335
|
-
import { join as
|
|
10054
|
+
import { readFile as readFile22, mkdir as mkdir10, writeFile as writeFile17 } from "node:fs/promises";
|
|
10055
|
+
import { join as join31, resolve as resolve8 } from "node:path";
|
|
9336
10056
|
async function readJsonFile3(filePath) {
|
|
9337
10057
|
try {
|
|
9338
|
-
const raw = await
|
|
10058
|
+
const raw = await readFile22(filePath, "utf-8");
|
|
9339
10059
|
return JSON.parse(raw);
|
|
9340
10060
|
} catch {
|
|
9341
10061
|
return null;
|
|
@@ -9343,7 +10063,7 @@ async function readJsonFile3(filePath) {
|
|
|
9343
10063
|
}
|
|
9344
10064
|
async function readPkgName(absPath) {
|
|
9345
10065
|
try {
|
|
9346
|
-
const raw = await
|
|
10066
|
+
const raw = await readFile22(join31(absPath, "package.json"), "utf-8");
|
|
9347
10067
|
const pkg = JSON.parse(raw);
|
|
9348
10068
|
return typeof pkg.name === "string" ? pkg.name : null;
|
|
9349
10069
|
} catch {
|
|
@@ -9353,10 +10073,11 @@ async function readPkgName(absPath) {
|
|
|
9353
10073
|
async function runGenerate(opts) {
|
|
9354
10074
|
const projectDir = opts.projectDir ?? opts["project-dir"] ?? process.cwd();
|
|
9355
10075
|
const dryRun = opts.dryRun ?? opts["dryRun"] ?? false;
|
|
10076
|
+
const check = opts.check ?? opts["check"] ?? false;
|
|
9356
10077
|
const rootDir = resolve8(projectDir);
|
|
9357
10078
|
let packageManager;
|
|
9358
10079
|
try {
|
|
9359
|
-
const raw = await
|
|
10080
|
+
const raw = await readFile22(join31(rootDir, "package.json"), "utf-8");
|
|
9360
10081
|
const pkg = JSON.parse(raw);
|
|
9361
10082
|
if (typeof pkg.packageManager === "string") {
|
|
9362
10083
|
packageManager = pkg.packageManager;
|
|
@@ -9364,7 +10085,7 @@ async function runGenerate(opts) {
|
|
|
9364
10085
|
} catch {
|
|
9365
10086
|
}
|
|
9366
10087
|
const serverJson = await readJsonFile3(
|
|
9367
|
-
|
|
10088
|
+
join31(rootDir, ".codebyplan", "server.json")
|
|
9368
10089
|
);
|
|
9369
10090
|
const ports = [];
|
|
9370
10091
|
for (const alloc of serverJson?.port_allocations ?? []) {
|
|
@@ -9377,7 +10098,7 @@ async function runGenerate(opts) {
|
|
|
9377
10098
|
}
|
|
9378
10099
|
}
|
|
9379
10100
|
const gitJson = await readJsonFile3(
|
|
9380
|
-
|
|
10101
|
+
join31(rootDir, ".codebyplan", "git.json")
|
|
9381
10102
|
);
|
|
9382
10103
|
const branchModel = gitJson?.branch_config?.production ? {
|
|
9383
10104
|
production: gitJson.branch_config.production,
|
|
@@ -9386,7 +10107,7 @@ async function runGenerate(opts) {
|
|
|
9386
10107
|
)
|
|
9387
10108
|
} : void 0;
|
|
9388
10109
|
const shipmentJson = await readJsonFile3(
|
|
9389
|
-
|
|
10110
|
+
join31(rootDir, ".codebyplan", "shipment.json")
|
|
9390
10111
|
);
|
|
9391
10112
|
const shipmentSurfaces = [];
|
|
9392
10113
|
const rawSurfaces = shipmentJson?.shipment?.surfaces ?? shipmentJson?.surfaces ?? {};
|
|
@@ -9454,28 +10175,362 @@ async function runGenerate(opts) {
|
|
|
9454
10175
|
branchModel,
|
|
9455
10176
|
shipmentSurfaces: shipmentSurfaces.length > 0 ? shipmentSurfaces : void 0
|
|
9456
10177
|
};
|
|
9457
|
-
const
|
|
10178
|
+
const structureMdContent = generateStructureMd(config);
|
|
10179
|
+
const agentsContent = generateAgentsMd(structureMdContent);
|
|
10180
|
+
if (check) {
|
|
10181
|
+
const agentsMdPath2 = join31(rootDir, "AGENTS.md");
|
|
10182
|
+
let existingAgents = null;
|
|
10183
|
+
try {
|
|
10184
|
+
existingAgents = await readFile22(agentsMdPath2, "utf-8");
|
|
10185
|
+
} catch {
|
|
10186
|
+
existingAgents = null;
|
|
10187
|
+
}
|
|
10188
|
+
if (existingAgents === null) {
|
|
10189
|
+
process.stdout.write(`AGENTS.md missing
|
|
10190
|
+
`);
|
|
10191
|
+
process.exitCode = 1;
|
|
10192
|
+
} else {
|
|
10193
|
+
const existingHash = extractAgentsHash(existingAgents);
|
|
10194
|
+
const freshEmbeddedHash = extractAgentsHash(agentsContent);
|
|
10195
|
+
if (existingHash !== freshEmbeddedHash) {
|
|
10196
|
+
process.stdout.write(`AGENTS.md drifted
|
|
10197
|
+
`);
|
|
10198
|
+
process.exitCode = 1;
|
|
10199
|
+
} else {
|
|
10200
|
+
process.stdout.write(`AGENTS.md up to date
|
|
10201
|
+
`);
|
|
10202
|
+
}
|
|
10203
|
+
}
|
|
10204
|
+
return;
|
|
10205
|
+
}
|
|
9458
10206
|
if (dryRun) {
|
|
9459
10207
|
process.stdout.write(
|
|
9460
10208
|
`[dry-run] Would write: .claude/generated/structure.md
|
|
9461
10209
|
|
|
9462
10210
|
`
|
|
9463
10211
|
);
|
|
9464
|
-
process.stdout.write(
|
|
10212
|
+
process.stdout.write(structureMdContent);
|
|
10213
|
+
process.stdout.write(`
|
|
10214
|
+
[dry-run] Would write: AGENTS.md
|
|
10215
|
+
|
|
10216
|
+
`);
|
|
10217
|
+
process.stdout.write(agentsContent);
|
|
9465
10218
|
return;
|
|
9466
10219
|
}
|
|
9467
|
-
const outputDir =
|
|
10220
|
+
const outputDir = join31(rootDir, ".claude", "generated");
|
|
9468
10221
|
await mkdir10(outputDir, { recursive: true });
|
|
9469
|
-
const outputPath =
|
|
9470
|
-
await
|
|
10222
|
+
const outputPath = join31(outputDir, "structure.md");
|
|
10223
|
+
await writeFile17(outputPath, structureMdContent, "utf-8");
|
|
9471
10224
|
process.stdout.write(`Wrote: .claude/generated/structure.md
|
|
9472
10225
|
`);
|
|
10226
|
+
const agentsMdPath = join31(rootDir, "AGENTS.md");
|
|
10227
|
+
let existingAgentsContent = null;
|
|
10228
|
+
try {
|
|
10229
|
+
existingAgentsContent = await readFile22(agentsMdPath, "utf-8");
|
|
10230
|
+
} catch {
|
|
10231
|
+
existingAgentsContent = null;
|
|
10232
|
+
}
|
|
10233
|
+
if (existingAgentsContent !== null && existingAgentsContent === agentsContent) {
|
|
10234
|
+
process.stdout.write(`Up to date: AGENTS.md
|
|
10235
|
+
`);
|
|
10236
|
+
} else {
|
|
10237
|
+
await writeFile17(agentsMdPath, agentsContent, "utf-8");
|
|
10238
|
+
process.stdout.write(`Wrote: AGENTS.md
|
|
10239
|
+
`);
|
|
10240
|
+
}
|
|
9473
10241
|
}
|
|
9474
10242
|
var init_generate = __esm({
|
|
9475
10243
|
"src/cli/claude/generate.ts"() {
|
|
9476
10244
|
"use strict";
|
|
9477
10245
|
init_tech_detect();
|
|
9478
10246
|
init_structure_generator();
|
|
10247
|
+
init_agents_generator();
|
|
10248
|
+
}
|
|
10249
|
+
});
|
|
10250
|
+
|
|
10251
|
+
// src/cli/claude/readme.ts
|
|
10252
|
+
var readme_exports = {};
|
|
10253
|
+
__export(readme_exports, {
|
|
10254
|
+
runReadme: () => runReadme,
|
|
10255
|
+
runReadmeCommand: () => runReadmeCommand
|
|
10256
|
+
});
|
|
10257
|
+
import { readFile as readFile23, writeFile as writeFile18 } from "node:fs/promises";
|
|
10258
|
+
import { join as join32, resolve as resolve9, relative as relative8 } from "node:path";
|
|
10259
|
+
async function readJsonFile4(filePath) {
|
|
10260
|
+
try {
|
|
10261
|
+
const raw = await readFile23(filePath, "utf-8");
|
|
10262
|
+
return JSON.parse(raw);
|
|
10263
|
+
} catch {
|
|
10264
|
+
return null;
|
|
10265
|
+
}
|
|
10266
|
+
}
|
|
10267
|
+
async function buildConfig(absPath, isRoot, allPackages, rootPkgJson, pkgJson) {
|
|
10268
|
+
let techStack;
|
|
10269
|
+
try {
|
|
10270
|
+
const techResult = await detectTechStack(absPath);
|
|
10271
|
+
const CATEGORY_LABELS = {
|
|
10272
|
+
"component-lib": "Component Library",
|
|
10273
|
+
graphql: "GraphQL",
|
|
10274
|
+
quality: "Code Quality"
|
|
10275
|
+
};
|
|
10276
|
+
const categoryMap = {};
|
|
10277
|
+
for (const entry of techResult.flat) {
|
|
10278
|
+
if (entry.name === SYNTHETIC_CARRIER_NAME) continue;
|
|
10279
|
+
if (!categoryMap[entry.category]) {
|
|
10280
|
+
categoryMap[entry.category] = [];
|
|
10281
|
+
}
|
|
10282
|
+
categoryMap[entry.category].push(entry.name);
|
|
10283
|
+
}
|
|
10284
|
+
if ((categoryMap["mobile"]?.length ?? 0) > 0) {
|
|
10285
|
+
const frameworkEntries = categoryMap["framework"] ?? [];
|
|
10286
|
+
const filtered = frameworkEntries.filter((n) => n !== "React");
|
|
10287
|
+
if (filtered.length > 0) {
|
|
10288
|
+
categoryMap["framework"] = filtered;
|
|
10289
|
+
} else {
|
|
10290
|
+
delete categoryMap["framework"];
|
|
10291
|
+
}
|
|
10292
|
+
}
|
|
10293
|
+
if (Object.keys(categoryMap).length > 0) {
|
|
10294
|
+
techStack = {};
|
|
10295
|
+
for (const [cat, names] of Object.entries(categoryMap)) {
|
|
10296
|
+
const label = CATEGORY_LABELS[cat] ?? cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
10297
|
+
techStack[label] = [...names].sort().join(" + ");
|
|
10298
|
+
}
|
|
10299
|
+
}
|
|
10300
|
+
} catch {
|
|
10301
|
+
}
|
|
10302
|
+
const packages = isRoot ? allPackages : void 0;
|
|
10303
|
+
const packageManager = typeof rootPkgJson?.packageManager === "string" ? rootPkgJson.packageManager : void 0;
|
|
10304
|
+
const pmBase = packageManager?.split("@")[0] ?? "pnpm";
|
|
10305
|
+
const installCommand = `${pmBase} install`;
|
|
10306
|
+
return {
|
|
10307
|
+
name: pkgJson?.name ?? void 0,
|
|
10308
|
+
description: pkgJson?.description ?? void 0,
|
|
10309
|
+
scripts: pkgJson?.scripts && Object.keys(pkgJson.scripts).length > 0 ? pkgJson.scripts : void 0,
|
|
10310
|
+
packageManager,
|
|
10311
|
+
techStack,
|
|
10312
|
+
packages: packages && packages.length > 0 ? packages : void 0,
|
|
10313
|
+
installCommand
|
|
10314
|
+
};
|
|
10315
|
+
}
|
|
10316
|
+
async function discoverUnits(rootDir, rootPkgJson) {
|
|
10317
|
+
const units = [];
|
|
10318
|
+
const allPackages = [];
|
|
10319
|
+
const pkgJsonByPath = /* @__PURE__ */ new Map();
|
|
10320
|
+
units.push({
|
|
10321
|
+
name: "root",
|
|
10322
|
+
path: ".",
|
|
10323
|
+
absPath: rootDir,
|
|
10324
|
+
isRoot: true
|
|
10325
|
+
});
|
|
10326
|
+
pkgJsonByPath.set(rootDir, rootPkgJson);
|
|
10327
|
+
const discovered = await discoverMonorepoApps(rootDir);
|
|
10328
|
+
for (const app of discovered) {
|
|
10329
|
+
const pkgJson = await readJsonFile4(
|
|
10330
|
+
join32(app.absPath, "package.json")
|
|
10331
|
+
);
|
|
10332
|
+
pkgJsonByPath.set(app.absPath, pkgJson);
|
|
10333
|
+
allPackages.push({
|
|
10334
|
+
path: app.path,
|
|
10335
|
+
name: pkgJson?.name ?? app.name,
|
|
10336
|
+
purpose: pkgJson?.description ?? void 0
|
|
10337
|
+
});
|
|
10338
|
+
const isApp = app.path === "apps" || app.path.startsWith("apps/");
|
|
10339
|
+
if (!isApp && pkgJson?.private === true) continue;
|
|
10340
|
+
units.push({
|
|
10341
|
+
name: app.name,
|
|
10342
|
+
path: app.path,
|
|
10343
|
+
absPath: app.absPath,
|
|
10344
|
+
isRoot: false
|
|
10345
|
+
});
|
|
10346
|
+
}
|
|
10347
|
+
return { units, allPackages, pkgJsonByPath };
|
|
10348
|
+
}
|
|
10349
|
+
async function runReadme(opts) {
|
|
10350
|
+
const projectDir = opts.projectDir ?? opts["project-dir"] ?? process.cwd();
|
|
10351
|
+
const dryRun = opts.dryRun ?? opts["dryRun"] ?? false;
|
|
10352
|
+
const check = opts.check ?? opts["check"] ?? false;
|
|
10353
|
+
const init = opts.init ?? opts["init"] ?? false;
|
|
10354
|
+
const rootDir = resolve9(projectDir);
|
|
10355
|
+
const rootPkgJson = await readJsonFile4(
|
|
10356
|
+
join32(rootDir, "package.json")
|
|
10357
|
+
);
|
|
10358
|
+
const { units, allPackages, pkgJsonByPath } = await discoverUnits(
|
|
10359
|
+
rootDir,
|
|
10360
|
+
rootPkgJson
|
|
10361
|
+
);
|
|
10362
|
+
const driftUnits = [];
|
|
10363
|
+
const missingUnits = [];
|
|
10364
|
+
for (const unit of units) {
|
|
10365
|
+
const readmePath = join32(unit.absPath, "README.md");
|
|
10366
|
+
const relPath = unit.isRoot ? "README.md" : join32(relative8(rootDir, unit.absPath), "README.md");
|
|
10367
|
+
let existingContent = null;
|
|
10368
|
+
try {
|
|
10369
|
+
existingContent = await readFile23(readmePath, "utf-8");
|
|
10370
|
+
} catch {
|
|
10371
|
+
existingContent = null;
|
|
10372
|
+
}
|
|
10373
|
+
let config;
|
|
10374
|
+
try {
|
|
10375
|
+
config = await buildConfig(
|
|
10376
|
+
unit.absPath,
|
|
10377
|
+
unit.isRoot,
|
|
10378
|
+
allPackages,
|
|
10379
|
+
rootPkgJson,
|
|
10380
|
+
pkgJsonByPath.get(unit.absPath) ?? null
|
|
10381
|
+
);
|
|
10382
|
+
} catch (err) {
|
|
10383
|
+
process.stderr.write(
|
|
10384
|
+
`readme: error building config for ${relPath}: ${err instanceof Error ? err.message : String(err)}
|
|
10385
|
+
`
|
|
10386
|
+
);
|
|
10387
|
+
continue;
|
|
10388
|
+
}
|
|
10389
|
+
const sections = buildManagedSections(config);
|
|
10390
|
+
if (existingContent === null) {
|
|
10391
|
+
if (check) {
|
|
10392
|
+
missingUnits.push(relPath);
|
|
10393
|
+
continue;
|
|
10394
|
+
}
|
|
10395
|
+
const newContent = scaffoldReadme(config);
|
|
10396
|
+
if (dryRun) {
|
|
10397
|
+
process.stdout.write(
|
|
10398
|
+
`[dry-run] Would write (scaffold): ${relPath}
|
|
10399
|
+
|
|
10400
|
+
${newContent}
|
|
10401
|
+
`
|
|
10402
|
+
);
|
|
10403
|
+
} else {
|
|
10404
|
+
await writeFile18(readmePath, newContent, "utf-8");
|
|
10405
|
+
process.stdout.write(`Wrote (scaffold): ${relPath}
|
|
10406
|
+
`);
|
|
10407
|
+
}
|
|
10408
|
+
} else if (hasManagedMarkers(existingContent)) {
|
|
10409
|
+
const markerErrors = validateManagedMarkers(existingContent);
|
|
10410
|
+
if (markerErrors.length > 0) {
|
|
10411
|
+
process.stderr.write(
|
|
10412
|
+
`readme: skipping ${relPath} \u2014 malformed managed markers: ${markerErrors.join("; ")}
|
|
10413
|
+
`
|
|
10414
|
+
);
|
|
10415
|
+
if (check) {
|
|
10416
|
+
driftUnits.push(`${relPath} (malformed markers)`);
|
|
10417
|
+
}
|
|
10418
|
+
continue;
|
|
10419
|
+
}
|
|
10420
|
+
const existingHash = extractManagedHash(existingContent);
|
|
10421
|
+
const newHash = hashManagedContent(sections);
|
|
10422
|
+
if (existingHash === newHash) {
|
|
10423
|
+
if (!check) {
|
|
10424
|
+
process.stdout.write(`Up to date: ${relPath}
|
|
10425
|
+
`);
|
|
10426
|
+
}
|
|
10427
|
+
continue;
|
|
10428
|
+
}
|
|
10429
|
+
if (check) {
|
|
10430
|
+
driftUnits.push(relPath);
|
|
10431
|
+
continue;
|
|
10432
|
+
}
|
|
10433
|
+
const newContent = refreshManagedSections(existingContent, sections);
|
|
10434
|
+
if (dryRun) {
|
|
10435
|
+
process.stdout.write(
|
|
10436
|
+
`[dry-run] Would write (refresh): ${relPath}
|
|
10437
|
+
|
|
10438
|
+
${newContent}
|
|
10439
|
+
`
|
|
10440
|
+
);
|
|
10441
|
+
} else {
|
|
10442
|
+
await writeFile18(readmePath, newContent, "utf-8");
|
|
10443
|
+
process.stdout.write(`Wrote (refresh): ${relPath}
|
|
10444
|
+
`);
|
|
10445
|
+
}
|
|
10446
|
+
} else {
|
|
10447
|
+
if (init) {
|
|
10448
|
+
if (check) {
|
|
10449
|
+
driftUnits.push(`${relPath} (no markers, needs --init)`);
|
|
10450
|
+
continue;
|
|
10451
|
+
}
|
|
10452
|
+
const newContent = appendManagedBlock(existingContent, sections);
|
|
10453
|
+
if (dryRun) {
|
|
10454
|
+
process.stdout.write(
|
|
10455
|
+
`[dry-run] Would write (init): ${relPath}
|
|
10456
|
+
|
|
10457
|
+
${newContent}
|
|
10458
|
+
`
|
|
10459
|
+
);
|
|
10460
|
+
} else {
|
|
10461
|
+
await writeFile18(readmePath, newContent, "utf-8");
|
|
10462
|
+
process.stdout.write(`Wrote (init): ${relPath}
|
|
10463
|
+
`);
|
|
10464
|
+
}
|
|
10465
|
+
} else {
|
|
10466
|
+
process.stdout.write(`Skipped (no markers, use --init): ${relPath}
|
|
10467
|
+
`);
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
if (check) {
|
|
10472
|
+
if (driftUnits.length > 0 || missingUnits.length > 0) {
|
|
10473
|
+
if (driftUnits.length > 0) {
|
|
10474
|
+
process.stdout.write(
|
|
10475
|
+
`
|
|
10476
|
+
README drift detected:
|
|
10477
|
+
${driftUnits.map((f) => ` - ${f}`).join("\n")}
|
|
10478
|
+
`
|
|
10479
|
+
);
|
|
10480
|
+
}
|
|
10481
|
+
if (missingUnits.length > 0) {
|
|
10482
|
+
process.stdout.write(
|
|
10483
|
+
`
|
|
10484
|
+
README missing:
|
|
10485
|
+
${missingUnits.map((f) => ` - ${f}`).join("\n")}
|
|
10486
|
+
`
|
|
10487
|
+
);
|
|
10488
|
+
}
|
|
10489
|
+
process.exitCode = 1;
|
|
10490
|
+
} else {
|
|
10491
|
+
process.stdout.write(
|
|
10492
|
+
`README check passed \u2014 all managed sections current.
|
|
10493
|
+
`
|
|
10494
|
+
);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
function appendManagedBlock(existing, sections) {
|
|
10499
|
+
let content = existing.trimEnd();
|
|
10500
|
+
const sectionKeys = Object.keys(sections).sort();
|
|
10501
|
+
for (const key of sectionKeys) {
|
|
10502
|
+
content += "\n\n" + managedStartMarker(key) + "\n" + sections[key].trimEnd() + "\n" + managedEndMarker(key);
|
|
10503
|
+
}
|
|
10504
|
+
const newHash = hashManagedContent(sections);
|
|
10505
|
+
content += `
|
|
10506
|
+
|
|
10507
|
+
${HASH_MARKER_PREFIX}${newHash}${MANAGED_MARKER_SUFFIX}`;
|
|
10508
|
+
content += "\n";
|
|
10509
|
+
return content;
|
|
10510
|
+
}
|
|
10511
|
+
async function runReadmeCommand(args) {
|
|
10512
|
+
const dryRun = args.includes("--dry-run");
|
|
10513
|
+
const check = args.includes("--check");
|
|
10514
|
+
const init = args.includes("--init");
|
|
10515
|
+
let projectDir;
|
|
10516
|
+
const pdIdx = args.indexOf("--project-dir");
|
|
10517
|
+
if (pdIdx !== -1) {
|
|
10518
|
+
const pdVal = args[pdIdx + 1];
|
|
10519
|
+
if (pdVal && !pdVal.startsWith("--")) {
|
|
10520
|
+
projectDir = pdVal;
|
|
10521
|
+
} else {
|
|
10522
|
+
process.stderr.write("error: --project-dir requires a path argument.\n");
|
|
10523
|
+
process.exitCode = 1;
|
|
10524
|
+
return;
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
await runReadme({ dryRun, check, init, projectDir });
|
|
10528
|
+
}
|
|
10529
|
+
var init_readme = __esm({
|
|
10530
|
+
"src/cli/claude/readme.ts"() {
|
|
10531
|
+
"use strict";
|
|
10532
|
+
init_tech_detect();
|
|
10533
|
+
init_readme_generator();
|
|
9479
10534
|
}
|
|
9480
10535
|
});
|
|
9481
10536
|
|
|
@@ -9491,18 +10546,18 @@ __export(migrate_memory_exports, {
|
|
|
9491
10546
|
runMigrateMemory: () => runMigrateMemory
|
|
9492
10547
|
});
|
|
9493
10548
|
import {
|
|
9494
|
-
readFile as
|
|
9495
|
-
writeFile as
|
|
10549
|
+
readFile as readFile24,
|
|
10550
|
+
writeFile as writeFile19,
|
|
9496
10551
|
mkdir as mkdir11,
|
|
9497
10552
|
unlink as unlink4,
|
|
9498
10553
|
rmdir,
|
|
9499
10554
|
readdir as readdir4
|
|
9500
10555
|
} from "node:fs/promises";
|
|
9501
|
-
import { existsSync as
|
|
9502
|
-
import { join as
|
|
10556
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
10557
|
+
import { join as join33, resolve as resolve10, dirname as dirname11, sep as sep2 } from "node:path";
|
|
9503
10558
|
import { homedir as homedir8 } from "node:os";
|
|
9504
10559
|
function encodeProjectPath(absPath) {
|
|
9505
|
-
return
|
|
10560
|
+
return resolve10(absPath).replace(/[/\\]/g, "-");
|
|
9506
10561
|
}
|
|
9507
10562
|
function resolveAutoMemoryDir(opts) {
|
|
9508
10563
|
if (opts.autoMemoryDir) {
|
|
@@ -9510,7 +10565,7 @@ function resolveAutoMemoryDir(opts) {
|
|
|
9510
10565
|
}
|
|
9511
10566
|
const projectDir = opts.projectDir ?? process.cwd();
|
|
9512
10567
|
const encoded = encodeProjectPath(projectDir);
|
|
9513
|
-
return
|
|
10568
|
+
return join33(homedir8(), ".claude", "projects", encoded, "memory");
|
|
9514
10569
|
}
|
|
9515
10570
|
function parseFrontmatter(content) {
|
|
9516
10571
|
content = content.replace(/\r\n/g, "\n");
|
|
@@ -9576,10 +10631,10 @@ async function inventoryFiles(dir) {
|
|
|
9576
10631
|
}
|
|
9577
10632
|
const results = [];
|
|
9578
10633
|
for (const filename of filenames) {
|
|
9579
|
-
const sourcePath =
|
|
10634
|
+
const sourcePath = join33(dir, filename);
|
|
9580
10635
|
let raw;
|
|
9581
10636
|
try {
|
|
9582
|
-
raw = await
|
|
10637
|
+
raw = await readFile24(sourcePath, "utf-8");
|
|
9583
10638
|
} catch (err) {
|
|
9584
10639
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9585
10640
|
results.push({
|
|
@@ -9659,15 +10714,15 @@ function buildPlan(entries, opts) {
|
|
|
9659
10714
|
return plan;
|
|
9660
10715
|
}
|
|
9661
10716
|
async function applyPlan(plan, opts) {
|
|
9662
|
-
const projectDir =
|
|
10717
|
+
const projectDir = resolve10(opts.projectDir);
|
|
9663
10718
|
const dryRun = opts.dryRun ?? false;
|
|
9664
10719
|
for (const entry of plan.entries) {
|
|
9665
10720
|
if (entry.suggested_action !== "keep") continue;
|
|
9666
10721
|
if (!entry.suggested_target?.startsWith("nested:")) continue;
|
|
9667
10722
|
const relPath = entry.suggested_target.slice("nested:".length);
|
|
9668
|
-
const targetDir =
|
|
9669
|
-
const targetFile =
|
|
9670
|
-
if (!targetDir.startsWith(
|
|
10723
|
+
const targetDir = resolve10(join33(projectDir, relPath));
|
|
10724
|
+
const targetFile = join33(targetDir, "CLAUDE.md");
|
|
10725
|
+
if (!targetDir.startsWith(resolve10(projectDir) + sep2)) {
|
|
9671
10726
|
process.stderr.write(
|
|
9672
10727
|
`migrate-memory: skipping unsafe suggested_target "${entry.suggested_target}" \u2014 resolves outside projectDir
|
|
9673
10728
|
`
|
|
@@ -9685,8 +10740,8 @@ ${anchor}
|
|
|
9685
10740
|
if (dryRun) {
|
|
9686
10741
|
process.stdout.write(`[dry-run] Would create/append: ${targetFile}
|
|
9687
10742
|
`);
|
|
9688
|
-
if (
|
|
9689
|
-
|
|
10743
|
+
if (resolve10(entry.source_path).startsWith(
|
|
10744
|
+
resolve10(plan.auto_memory_dir) + sep2
|
|
9690
10745
|
)) {
|
|
9691
10746
|
process.stdout.write(
|
|
9692
10747
|
`[dry-run] Would delete migrated keep source: ${entry.source_path}
|
|
@@ -9698,13 +10753,13 @@ ${anchor}
|
|
|
9698
10753
|
await mkdir11(targetDir, { recursive: true });
|
|
9699
10754
|
let existing = "";
|
|
9700
10755
|
try {
|
|
9701
|
-
existing = await
|
|
10756
|
+
existing = await readFile24(targetFile, "utf-8");
|
|
9702
10757
|
} catch {
|
|
9703
10758
|
}
|
|
9704
10759
|
if (!existing.includes(anchor)) {
|
|
9705
|
-
await
|
|
10760
|
+
await writeFile19(targetFile, existing + appendContent, "utf-8");
|
|
9706
10761
|
}
|
|
9707
|
-
if (
|
|
10762
|
+
if (resolve10(entry.source_path).startsWith(resolve10(plan.auto_memory_dir) + sep2)) {
|
|
9708
10763
|
try {
|
|
9709
10764
|
await unlink4(entry.source_path);
|
|
9710
10765
|
} catch {
|
|
@@ -9716,7 +10771,7 @@ ${anchor}
|
|
|
9716
10771
|
);
|
|
9717
10772
|
}
|
|
9718
10773
|
}
|
|
9719
|
-
const rootClaudeMd =
|
|
10774
|
+
const rootClaudeMd = join33(projectDir, ".claude", "CLAUDE.md");
|
|
9720
10775
|
if (dryRun) {
|
|
9721
10776
|
process.stdout.write(
|
|
9722
10777
|
`[dry-run] Would ensure ${rootClaudeMd} contains: ${IMPORT_LINE}
|
|
@@ -9725,12 +10780,12 @@ ${anchor}
|
|
|
9725
10780
|
} else {
|
|
9726
10781
|
let claudeMdContent = "";
|
|
9727
10782
|
try {
|
|
9728
|
-
claudeMdContent = await
|
|
10783
|
+
claudeMdContent = await readFile24(rootClaudeMd, "utf-8");
|
|
9729
10784
|
} catch {
|
|
9730
10785
|
await mkdir11(dirname11(rootClaudeMd), { recursive: true });
|
|
9731
10786
|
}
|
|
9732
10787
|
if (!claudeMdContent.includes(IMPORT_LINE)) {
|
|
9733
|
-
await
|
|
10788
|
+
await writeFile19(
|
|
9734
10789
|
rootClaudeMd,
|
|
9735
10790
|
claudeMdContent + `
|
|
9736
10791
|
${IMPORT_LINE}
|
|
@@ -9741,8 +10796,8 @@ ${IMPORT_LINE}
|
|
|
9741
10796
|
}
|
|
9742
10797
|
for (const entry of plan.entries) {
|
|
9743
10798
|
if (entry.suggested_action !== "drop") continue;
|
|
9744
|
-
if (!
|
|
9745
|
-
|
|
10799
|
+
if (!resolve10(entry.source_path).startsWith(
|
|
10800
|
+
resolve10(plan.auto_memory_dir) + sep2
|
|
9746
10801
|
)) {
|
|
9747
10802
|
process.stderr.write(
|
|
9748
10803
|
`migrate-memory: skipping delete of "${entry.source_path}" \u2014 resolves outside auto_memory_dir
|
|
@@ -9760,13 +10815,13 @@ ${IMPORT_LINE}
|
|
|
9760
10815
|
} catch {
|
|
9761
10816
|
}
|
|
9762
10817
|
}
|
|
9763
|
-
const memoryMd =
|
|
9764
|
-
const safeRmdirBase =
|
|
10818
|
+
const memoryMd = join33(plan.auto_memory_dir, "MEMORY.md");
|
|
10819
|
+
const safeRmdirBase = join33(homedir8(), ".claude", "projects");
|
|
9765
10820
|
if (dryRun) {
|
|
9766
10821
|
process.stdout.write(`[dry-run] Would delete MEMORY.md: ${memoryMd}
|
|
9767
10822
|
`);
|
|
9768
10823
|
} else {
|
|
9769
|
-
if (
|
|
10824
|
+
if (resolve10(plan.auto_memory_dir).startsWith(safeRmdirBase + sep2)) {
|
|
9770
10825
|
try {
|
|
9771
10826
|
await unlink4(memoryMd);
|
|
9772
10827
|
} catch {
|
|
@@ -9784,7 +10839,7 @@ ${IMPORT_LINE}
|
|
|
9784
10839
|
`
|
|
9785
10840
|
);
|
|
9786
10841
|
} else {
|
|
9787
|
-
if (!
|
|
10842
|
+
if (!resolve10(plan.auto_memory_dir).startsWith(safeRmdirBase + sep2)) {
|
|
9788
10843
|
process.stderr.write(
|
|
9789
10844
|
`migrate-memory: skipping rmdir of "${plan.auto_memory_dir}" \u2014 not under ~/.claude/projects
|
|
9790
10845
|
`
|
|
@@ -9814,7 +10869,7 @@ async function runMigrateMemory(opts) {
|
|
|
9814
10869
|
if (applyFile) {
|
|
9815
10870
|
let planJson;
|
|
9816
10871
|
try {
|
|
9817
|
-
planJson = await
|
|
10872
|
+
planJson = await readFile24(resolve10(applyFile), "utf-8");
|
|
9818
10873
|
} catch (err) {
|
|
9819
10874
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9820
10875
|
process.stderr.write(
|
|
@@ -9842,7 +10897,7 @@ async function runMigrateMemory(opts) {
|
|
|
9842
10897
|
);
|
|
9843
10898
|
return;
|
|
9844
10899
|
}
|
|
9845
|
-
if (!
|
|
10900
|
+
if (!existsSync11(autoMemoryDir)) {
|
|
9846
10901
|
process.stdout.write(
|
|
9847
10902
|
JSON.stringify(
|
|
9848
10903
|
{
|
|
@@ -9886,11 +10941,11 @@ var init_migrate_memory = __esm({
|
|
|
9886
10941
|
// src/index.ts
|
|
9887
10942
|
init_version();
|
|
9888
10943
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
9889
|
-
import { resolve as
|
|
10944
|
+
import { resolve as resolve11 } from "node:path";
|
|
9890
10945
|
void (async () => {
|
|
9891
10946
|
if (!process.env.CODEBYPLAN_API_KEY) {
|
|
9892
10947
|
try {
|
|
9893
|
-
const envPath =
|
|
10948
|
+
const envPath = resolve11(process.cwd(), ".env.local");
|
|
9894
10949
|
const content = readFileSync10(envPath, "utf-8");
|
|
9895
10950
|
for (const line of content.split("\n")) {
|
|
9896
10951
|
const trimmed = line.trim();
|
|
@@ -10029,6 +11084,12 @@ void (async () => {
|
|
|
10029
11084
|
await runUploadE2eImagesCommand2(rest);
|
|
10030
11085
|
process.exit(0);
|
|
10031
11086
|
}
|
|
11087
|
+
if (arg === "arch-map") {
|
|
11088
|
+
const { runArchMapCommand: runArchMapCommand2 } = await Promise.resolve().then(() => (init_arch_map(), arch_map_exports));
|
|
11089
|
+
const rest = process.argv.slice(3);
|
|
11090
|
+
await runArchMapCommand2(rest);
|
|
11091
|
+
process.exit(0);
|
|
11092
|
+
}
|
|
10032
11093
|
if (arg === "config") {
|
|
10033
11094
|
const { runConfig: runConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
10034
11095
|
await runConfig2();
|
|
@@ -10054,8 +11115,8 @@ void (async () => {
|
|
|
10054
11115
|
const flagArgs = process.argv.slice(3);
|
|
10055
11116
|
const parsed = parseClaudeFlags(flagArgs);
|
|
10056
11117
|
if (subcommand === "status") {
|
|
10057
|
-
const { runStatus:
|
|
10058
|
-
await
|
|
11118
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
11119
|
+
await runStatus3(process.argv.slice(4));
|
|
10059
11120
|
return;
|
|
10060
11121
|
}
|
|
10061
11122
|
if (subcommand === "install") {
|
|
@@ -10096,11 +11157,17 @@ void (async () => {
|
|
|
10096
11157
|
process.exit(1);
|
|
10097
11158
|
}
|
|
10098
11159
|
const { runGenerate: runGenerate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
11160
|
+
const check = process.argv.slice(4).includes("--check");
|
|
10099
11161
|
await runGenerate2(
|
|
10100
|
-
parsed.projectDir != null ? { ...parsed.opts, projectDir: parsed.projectDir } : parsed.opts
|
|
11162
|
+
parsed.projectDir != null ? { ...parsed.opts, check, projectDir: parsed.projectDir } : { ...parsed.opts, check }
|
|
10101
11163
|
);
|
|
10102
11164
|
process.exit(process.exitCode ?? 0);
|
|
10103
11165
|
}
|
|
11166
|
+
if (subcommand === "readme") {
|
|
11167
|
+
const { runReadmeCommand: runReadmeCommand2 } = await Promise.resolve().then(() => (init_readme(), readme_exports));
|
|
11168
|
+
await runReadmeCommand2(process.argv.slice(4));
|
|
11169
|
+
process.exit(process.exitCode ?? 0);
|
|
11170
|
+
}
|
|
10104
11171
|
if (subcommand === "migrate-memory") {
|
|
10105
11172
|
if (parsed === null) {
|
|
10106
11173
|
process.exit(1);
|
|
@@ -10124,6 +11191,7 @@ void (async () => {
|
|
|
10124
11191
|
--write-cache Write result to .codebyplan/claude-status.local.json
|
|
10125
11192
|
--quiet Suppress stdout output
|
|
10126
11193
|
codebyplan claude generate [flags] Write .claude/generated/structure.md from local config
|
|
11194
|
+
codebyplan claude readme [flags] Generate / refresh README.md for outward-facing units
|
|
10127
11195
|
codebyplan claude migrate-memory [flags] Inventory auto-memory files and emit migration plan
|
|
10128
11196
|
|
|
10129
11197
|
Flags (apply to install/update/uninstall):
|
|
@@ -10142,6 +11210,13 @@ void (async () => {
|
|
|
10142
11210
|
Flags (apply to generate):
|
|
10143
11211
|
--project-dir <path> Override the project root (default: cwd)
|
|
10144
11212
|
--dry-run Print what would be written, write nothing
|
|
11213
|
+
--check Exit non-zero when AGENTS.md is missing or drifted
|
|
11214
|
+
|
|
11215
|
+
Flags (apply to readme):
|
|
11216
|
+
--project-dir <path> Override the project root (default: cwd)
|
|
11217
|
+
--dry-run Print what would be written, write nothing
|
|
11218
|
+
--check Exit non-zero on drift or missing READMEs
|
|
11219
|
+
--init Append managed block to marker-less existing READMEs
|
|
10145
11220
|
`);
|
|
10146
11221
|
process.exit(0);
|
|
10147
11222
|
}
|
|
@@ -10159,6 +11234,7 @@ void (async () => {
|
|
|
10159
11234
|
codebyplan create-project Create a new project within an organization (interactive prompt)
|
|
10160
11235
|
codebyplan create-repo Create a new repository within a project (interactive prompt)
|
|
10161
11236
|
codebyplan config Sync repo config from DB to .codebyplan/ files
|
|
11237
|
+
codebyplan arch-map Map architecture modules (status/drift/stamp)
|
|
10162
11238
|
codebyplan ports Verify port allocations against local package.json scripts
|
|
10163
11239
|
codebyplan tech-stack Detect and sync tech stack dependencies
|
|
10164
11240
|
(--full-tech-stack: sync every local worktree on this device)
|
|
@@ -10206,6 +11282,7 @@ void (async () => {
|
|
|
10206
11282
|
codebyplan claude update [flags] Update installed assets to latest versions
|
|
10207
11283
|
codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
|
|
10208
11284
|
codebyplan claude generate [flags] Write .claude/generated/structure.md from local config
|
|
11285
|
+
codebyplan claude readme [flags] Generate / refresh README.md for outward-facing units
|
|
10209
11286
|
codebyplan claude migrate-memory [flags] Inventory auto-memory files and emit migration plan
|
|
10210
11287
|
|
|
10211
11288
|
Flags (install/update/uninstall):
|
|
@@ -10221,6 +11298,13 @@ void (async () => {
|
|
|
10221
11298
|
Flags (generate):
|
|
10222
11299
|
--project-dir <path> Override the project root (default: cwd)
|
|
10223
11300
|
--dry-run Print what would be written, write nothing
|
|
11301
|
+
--check Exit non-zero when AGENTS.md is missing or drifted
|
|
11302
|
+
|
|
11303
|
+
Flags (readme):
|
|
11304
|
+
--project-dir <path> Override the project root (default: cwd)
|
|
11305
|
+
--dry-run Print what would be written, write nothing
|
|
11306
|
+
--check Exit non-zero on drift or missing READMEs
|
|
11307
|
+
--init Append managed block to marker-less existing READMEs
|
|
10224
11308
|
|
|
10225
11309
|
Flags (migrate-memory):
|
|
10226
11310
|
--project-dir <path> Override the project root (default: cwd)
|