codebyplan 1.13.28 → 1.13.30

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.
Files changed (46) hide show
  1. package/dist/cli.js +728 -18
  2. package/package.json +1 -1
  3. package/templates/README.md +16 -13
  4. package/templates/agents/cbp-cc-executor.md +6 -9
  5. package/templates/agents/cbp-improve-round.md +1 -1
  6. package/templates/agents/cbp-round-executor.md +1 -2
  7. package/templates/agents/cbp-task-check.md +12 -8
  8. package/templates/hooks/cbp-mcp-round-sync.sh +9 -0
  9. package/templates/rules/README.md +13 -8
  10. package/templates/rules/cbp-operating-gotchas.md +64 -0
  11. package/templates/settings.project.base.json +3 -3
  12. package/templates/skills/cbp-build-cc-agent/SKILL.md +3 -4
  13. package/templates/skills/cbp-build-cc-agent/examples/with-skills-preload.md +2 -3
  14. package/templates/skills/cbp-build-cc-agent/reference/frontmatter-fields.md +0 -1
  15. package/templates/skills/cbp-build-cc-agent/scripts/validate-agent.sh +0 -6
  16. package/templates/skills/cbp-build-cc-agent/templates/agent.md +1 -2
  17. package/templates/skills/cbp-build-cc-claude-file/SKILL.md +16 -2
  18. package/templates/skills/cbp-build-cc-claude-file/reference/what-belongs.md +1 -1
  19. package/templates/skills/cbp-build-cc-mode/SKILL.md +5 -4
  20. package/templates/skills/cbp-build-cc-rule/SKILL.md +2 -2
  21. package/templates/skills/cbp-build-cc-settings/reference/cbp-permission-policy.md +3 -2
  22. package/templates/skills/cbp-build-cc-skill/reference/cbp-quality.md +1 -1
  23. package/templates/skills/cbp-merge-main/SKILL.md +1 -1
  24. package/templates/skills/cbp-round-complete/SKILL.md +164 -0
  25. package/templates/skills/cbp-round-end/SKILL.md +16 -14
  26. package/templates/skills/cbp-round-end/reference/findings-presentation.md +7 -17
  27. package/templates/skills/cbp-round-execute/SKILL.md +4 -0
  28. package/templates/skills/cbp-round-input/SKILL.md +6 -6
  29. package/templates/skills/cbp-round-start/SKILL.md +12 -15
  30. package/templates/skills/cbp-round-update/SKILL.md +31 -143
  31. package/templates/skills/cbp-standalone-task-check/SKILL.md +2 -2
  32. package/templates/skills/cbp-standalone-task-complete/SKILL.md +4 -3
  33. package/templates/skills/cbp-standalone-task-testing/SKILL.md +4 -4
  34. package/templates/skills/cbp-task-check/SKILL.md +3 -3
  35. package/templates/skills/cbp-task-complete/SKILL.md +7 -6
  36. package/templates/skills/cbp-task-testing/SKILL.md +3 -5
  37. package/templates/skills/cbp-todo/SKILL.md +1 -1
  38. package/templates/skills/cbp-build-cc-memory/SKILL.md +0 -201
  39. package/templates/skills/cbp-build-cc-memory/examples/feedback-memory.md +0 -11
  40. package/templates/skills/cbp-build-cc-memory/examples/project-memory.md +0 -11
  41. package/templates/skills/cbp-build-cc-memory/examples/reference-memory.md +0 -13
  42. package/templates/skills/cbp-build-cc-memory/examples/user-memory.md +0 -14
  43. package/templates/skills/cbp-build-cc-memory/reference/memory-types.md +0 -59
  44. package/templates/skills/cbp-build-cc-memory/reference/when-to-save.md +0 -62
  45. package/templates/skills/cbp-build-cc-memory/templates/MEMORY-index.md +0 -4
  46. package/templates/skills/cbp-build-cc-memory/templates/memory-entry.md +0 -15
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.28";
17
+ VERSION = "1.13.30";
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((resolve9) => setTimeout(resolve9, ms));
701
+ return new Promise((resolve11) => setTimeout(resolve11, 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((resolve9) => setTimeout(resolve9, ms));
1059
+ defaultSleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, ms));
1060
1060
  }
1061
1061
  });
1062
1062
 
@@ -4596,7 +4596,7 @@ function setRetryDelayMs(ms) {
4596
4596
  RETRY_DELAY_MS = ms;
4597
4597
  }
4598
4598
  function sleep(ms) {
4599
- return new Promise((resolve9) => setTimeout(resolve9, ms));
4599
+ return new Promise((resolve11) => setTimeout(resolve11, ms));
4600
4600
  }
4601
4601
  function isTransientMcpError(err) {
4602
4602
  if (!(err instanceof McpError)) return false;
@@ -8538,11 +8538,11 @@ async function ask(q, opts) {
8538
8538
  try {
8539
8539
  while (true) {
8540
8540
  const choices = q.choices.map((c) => `[${c.key}] ${c.label}`).join(" ");
8541
- const answer = await new Promise((resolve9) => {
8541
+ const answer = await new Promise((resolve11) => {
8542
8542
  rl.question(`${q.message}
8543
8543
  ${choices}
8544
8544
  > `, (input) => {
8545
- resolve9(input.trim().toLowerCase());
8545
+ resolve11(input.trim().toLowerCase());
8546
8546
  });
8547
8547
  });
8548
8548
  const match = q.choices.find(
@@ -9128,14 +9128,684 @@ var init_uninstall = __esm({
9128
9128
  }
9129
9129
  });
9130
9130
 
9131
+ // src/lib/structure-generator.ts
9132
+ function renderTechStack(techStack) {
9133
+ const entries = Object.entries(techStack).filter(([, value]) => value.trim().length > 0).sort(([a], [b]) => a.localeCompare(b));
9134
+ if (entries.length === 0) return "";
9135
+ const lines = entries.map(([cat, names]) => `- **${cat}**: ${names}`);
9136
+ return `## Tech Stack
9137
+
9138
+ ${lines.join("\n")}
9139
+ `;
9140
+ }
9141
+ function renderPackageManager(pm) {
9142
+ if (!pm.trim()) return "";
9143
+ return `## Package Manager
9144
+
9145
+ \`${pm}\`
9146
+ `;
9147
+ }
9148
+ function renderMonorepoStructure(packages) {
9149
+ if (packages.length === 0) return "";
9150
+ const sorted = [...packages].sort((a, b) => a.path.localeCompare(b.path));
9151
+ const rows = sorted.map(
9152
+ (p) => `| \`${p.path}\` | \`${p.name}\` | ${p.purpose ?? ""} |`
9153
+ );
9154
+ return `## Monorepo Structure
9155
+
9156
+ | Location | Package | Purpose |
9157
+ |----------|---------|----------|
9158
+ ` + rows.join("\n") + "\n";
9159
+ }
9160
+ function renderPorts(ports) {
9161
+ if (ports.length === 0) return "";
9162
+ const sorted = [...ports].sort((a, b) => a.port - b.port);
9163
+ const rows = sorted.map(
9164
+ (p) => `| ${p.port} | ${p.label} | ${p.server_type} |`
9165
+ );
9166
+ return `## Ports
9167
+
9168
+ | Port | Label | Type |
9169
+ |------|-------|------|
9170
+ ` + rows.join("\n") + "\n";
9171
+ }
9172
+ function renderBranchModel(model) {
9173
+ const lines = [];
9174
+ lines.push(`- **Production**: \`${model.production}\``);
9175
+ if (model.protected && model.protected.length > 0) {
9176
+ const sorted = [...model.protected].sort();
9177
+ lines.push(`- **Protected**: ${sorted.map((b) => `\`${b}\``).join(", ")}`);
9178
+ }
9179
+ return `## Branch Model
9180
+
9181
+ ${lines.join("\n")}
9182
+ `;
9183
+ }
9184
+ function resolvePlatform(key, platform2) {
9185
+ if (platform2) return platform2;
9186
+ if (key.startsWith("vercel")) return "vercel";
9187
+ if (key.startsWith("railway")) return "railway";
9188
+ if (key.startsWith("supabase")) return "supabase";
9189
+ if (key === "npm") return "npm";
9190
+ return key;
9191
+ }
9192
+ function renderShipmentSurfaces(surfaces) {
9193
+ if (surfaces.length === 0) return "";
9194
+ const sorted = [...surfaces].sort((a, b) => a.key.localeCompare(b.key));
9195
+ const rows = sorted.map(
9196
+ (s) => `| \`${s.key}\` | ${resolvePlatform(s.key, s.platform)} | ${s.urlOrPath ?? ""} |`
9197
+ );
9198
+ return `## Shipment Surfaces
9199
+
9200
+ | Surface | Platform/Type | Path/URL |
9201
+ |---------|---------------|----------|
9202
+ ` + rows.join("\n") + "\n";
9203
+ }
9204
+ function generateStructureMd(config) {
9205
+ const sections = [];
9206
+ if (config.techStack && Object.keys(config.techStack).length > 0) {
9207
+ const rendered = renderTechStack(config.techStack);
9208
+ if (rendered) sections.push(rendered);
9209
+ }
9210
+ if (config.packageManager) {
9211
+ const rendered = renderPackageManager(config.packageManager);
9212
+ if (rendered) sections.push(rendered);
9213
+ }
9214
+ if (config.packages && config.packages.length > 0) {
9215
+ const rendered = renderMonorepoStructure(config.packages);
9216
+ if (rendered) sections.push(rendered);
9217
+ }
9218
+ if (config.ports && config.ports.length > 0) {
9219
+ const rendered = renderPorts(config.ports);
9220
+ if (rendered) sections.push(rendered);
9221
+ }
9222
+ if (config.branchModel) {
9223
+ const rendered = renderBranchModel(config.branchModel);
9224
+ if (rendered) sections.push(rendered);
9225
+ }
9226
+ if (config.shipmentSurfaces && config.shipmentSurfaces.length > 0) {
9227
+ const rendered = renderShipmentSurfaces(config.shipmentSurfaces);
9228
+ if (rendered) sections.push(rendered);
9229
+ }
9230
+ const body = sections.join("\n");
9231
+ return `${SENTINEL_START}
9232
+ ${body}${SENTINEL_END}
9233
+ `;
9234
+ }
9235
+ var SENTINEL_START, SENTINEL_END;
9236
+ var init_structure_generator = __esm({
9237
+ "src/lib/structure-generator.ts"() {
9238
+ "use strict";
9239
+ SENTINEL_START = "<!-- @codebyplan-generated: structure start -->";
9240
+ SENTINEL_END = "<!-- @codebyplan-generated: structure end -->";
9241
+ }
9242
+ });
9243
+
9244
+ // src/cli/claude/generate.ts
9245
+ var generate_exports = {};
9246
+ __export(generate_exports, {
9247
+ runGenerate: () => runGenerate
9248
+ });
9249
+ import { readFile as readFile21, mkdir as mkdir10, writeFile as writeFile16 } from "node:fs/promises";
9250
+ import { join as join30, resolve as resolve8 } from "node:path";
9251
+ async function readJsonFile3(filePath) {
9252
+ try {
9253
+ const raw = await readFile21(filePath, "utf-8");
9254
+ return JSON.parse(raw);
9255
+ } catch {
9256
+ return null;
9257
+ }
9258
+ }
9259
+ async function readPkgName(absPath) {
9260
+ try {
9261
+ const raw = await readFile21(join30(absPath, "package.json"), "utf-8");
9262
+ const pkg = JSON.parse(raw);
9263
+ return typeof pkg.name === "string" ? pkg.name : null;
9264
+ } catch {
9265
+ return null;
9266
+ }
9267
+ }
9268
+ async function runGenerate(opts) {
9269
+ const projectDir = opts.projectDir ?? opts["project-dir"] ?? process.cwd();
9270
+ const dryRun = opts.dryRun ?? opts["dryRun"] ?? false;
9271
+ const rootDir = resolve8(projectDir);
9272
+ let packageManager;
9273
+ try {
9274
+ const raw = await readFile21(join30(rootDir, "package.json"), "utf-8");
9275
+ const pkg = JSON.parse(raw);
9276
+ if (typeof pkg.packageManager === "string") {
9277
+ packageManager = pkg.packageManager;
9278
+ }
9279
+ } catch {
9280
+ }
9281
+ const serverJson = await readJsonFile3(
9282
+ join30(rootDir, ".codebyplan", "server.json")
9283
+ );
9284
+ const ports = [];
9285
+ for (const alloc of serverJson?.port_allocations ?? []) {
9286
+ if (typeof alloc.port === "number") {
9287
+ ports.push({
9288
+ port: alloc.port,
9289
+ label: alloc.label ?? "",
9290
+ server_type: alloc.server_type ?? ""
9291
+ });
9292
+ }
9293
+ }
9294
+ const gitJson = await readJsonFile3(
9295
+ join30(rootDir, ".codebyplan", "git.json")
9296
+ );
9297
+ const branchModel = gitJson?.branch_config?.production ? {
9298
+ production: gitJson.branch_config.production,
9299
+ protected: gitJson.branch_config.protected?.filter(
9300
+ (b) => typeof b === "string"
9301
+ )
9302
+ } : void 0;
9303
+ const shipmentJson = await readJsonFile3(
9304
+ join30(rootDir, ".codebyplan", "shipment.json")
9305
+ );
9306
+ const shipmentSurfaces = [];
9307
+ const rawSurfaces = shipmentJson?.shipment?.surfaces ?? shipmentJson?.surfaces ?? {};
9308
+ for (const [key, surface] of Object.entries(rawSurfaces).sort(
9309
+ ([a], [b]) => a.localeCompare(b)
9310
+ )) {
9311
+ const urlOrPath = surface.custom_domain ?? surface.url ?? surface.app_path ?? void 0;
9312
+ shipmentSurfaces.push({
9313
+ key,
9314
+ platform: surface.platform,
9315
+ urlOrPath
9316
+ });
9317
+ }
9318
+ const discoveredApps = (await discoverMonorepoApps(rootDir)).sort(
9319
+ (a, b) => a.path.localeCompare(b.path)
9320
+ );
9321
+ const packages = [];
9322
+ for (const app of discoveredApps) {
9323
+ const pkgName = await readPkgName(app.absPath);
9324
+ packages.push({
9325
+ path: app.path,
9326
+ name: pkgName ?? app.name
9327
+ });
9328
+ }
9329
+ const techResult = await detectTechStack(rootDir);
9330
+ const CATEGORY_LABELS = {
9331
+ "component-lib": "Component Library",
9332
+ graphql: "GraphQL",
9333
+ quality: "Code Quality"
9334
+ };
9335
+ const categoryMap = {};
9336
+ for (const entry of techResult.flat) {
9337
+ if (entry.name === SYNTHETIC_CARRIER_NAME) continue;
9338
+ if (!categoryMap[entry.category]) {
9339
+ categoryMap[entry.category] = [];
9340
+ }
9341
+ categoryMap[entry.category].push(entry.name);
9342
+ }
9343
+ if ((categoryMap["mobile"]?.length ?? 0) > 0) {
9344
+ const frameworkEntries = categoryMap["framework"] ?? [];
9345
+ const filtered = frameworkEntries.filter((n) => n !== "React");
9346
+ if (filtered.length > 0) {
9347
+ categoryMap["framework"] = filtered;
9348
+ } else {
9349
+ delete categoryMap["framework"];
9350
+ }
9351
+ }
9352
+ const hasNextjs = (categoryMap["framework"] ?? []).some(
9353
+ (n) => n.toLowerCase().includes("next")
9354
+ );
9355
+ const filteredPorts = ports.filter((p) => {
9356
+ if (p.server_type === "nextjs" && !hasNextjs) return false;
9357
+ return true;
9358
+ });
9359
+ const techStack = {};
9360
+ for (const [cat, names] of Object.entries(categoryMap)) {
9361
+ const label = CATEGORY_LABELS[cat] ?? cat.charAt(0).toUpperCase() + cat.slice(1);
9362
+ techStack[label] = [...names].sort().join(" + ");
9363
+ }
9364
+ const config = {
9365
+ techStack: Object.keys(techStack).length > 0 ? techStack : void 0,
9366
+ packageManager,
9367
+ packages: packages.length > 0 ? packages : void 0,
9368
+ ports: filteredPorts.length > 0 ? filteredPorts : void 0,
9369
+ branchModel,
9370
+ shipmentSurfaces: shipmentSurfaces.length > 0 ? shipmentSurfaces : void 0
9371
+ };
9372
+ const content = generateStructureMd(config);
9373
+ if (dryRun) {
9374
+ process.stdout.write(
9375
+ `[dry-run] Would write: .claude/generated/structure.md
9376
+
9377
+ `
9378
+ );
9379
+ process.stdout.write(content);
9380
+ return;
9381
+ }
9382
+ const outputDir = join30(rootDir, ".claude", "generated");
9383
+ await mkdir10(outputDir, { recursive: true });
9384
+ const outputPath = join30(outputDir, "structure.md");
9385
+ await writeFile16(outputPath, content, "utf-8");
9386
+ process.stdout.write(`Wrote: .claude/generated/structure.md
9387
+ `);
9388
+ }
9389
+ var init_generate = __esm({
9390
+ "src/cli/claude/generate.ts"() {
9391
+ "use strict";
9392
+ init_tech_detect();
9393
+ init_structure_generator();
9394
+ }
9395
+ });
9396
+
9397
+ // src/cli/claude/migrate-memory.ts
9398
+ var migrate_memory_exports = {};
9399
+ __export(migrate_memory_exports, {
9400
+ applyPlan: () => applyPlan,
9401
+ buildPlan: () => buildPlan,
9402
+ encodeProjectPath: () => encodeProjectPath,
9403
+ flagDropCandidates: () => flagDropCandidates,
9404
+ inventoryFiles: () => inventoryFiles,
9405
+ resolveAutoMemoryDir: () => resolveAutoMemoryDir,
9406
+ runMigrateMemory: () => runMigrateMemory
9407
+ });
9408
+ import {
9409
+ readFile as readFile22,
9410
+ writeFile as writeFile17,
9411
+ mkdir as mkdir11,
9412
+ unlink as unlink4,
9413
+ rmdir,
9414
+ readdir as readdir4
9415
+ } from "node:fs/promises";
9416
+ import { existsSync as existsSync10 } from "node:fs";
9417
+ import { join as join31, resolve as resolve9, dirname as dirname11, sep as sep2 } from "node:path";
9418
+ import { homedir as homedir8 } from "node:os";
9419
+ function encodeProjectPath(absPath) {
9420
+ return resolve9(absPath).replace(/[/\\]/g, "-");
9421
+ }
9422
+ function resolveAutoMemoryDir(opts) {
9423
+ if (opts.autoMemoryDir) {
9424
+ return opts.autoMemoryDir;
9425
+ }
9426
+ const projectDir = opts.projectDir ?? process.cwd();
9427
+ const encoded = encodeProjectPath(projectDir);
9428
+ return join31(homedir8(), ".claude", "projects", encoded, "memory");
9429
+ }
9430
+ function parseFrontmatter(content) {
9431
+ content = content.replace(/\r\n/g, "\n");
9432
+ if (!content.startsWith("---")) {
9433
+ return {
9434
+ ok: false,
9435
+ error: "File does not start with a YAML frontmatter fence (---)"
9436
+ };
9437
+ }
9438
+ const rest = content.slice(3);
9439
+ const closingIdx = rest.indexOf("\n---");
9440
+ if (closingIdx === -1) {
9441
+ return { ok: false, error: "Frontmatter closing fence (---) not found" };
9442
+ }
9443
+ const fmBlock = rest.slice(0, closingIdx);
9444
+ const lines = fmBlock.split("\n");
9445
+ let name = "";
9446
+ let description = "";
9447
+ let metadataType = null;
9448
+ let inMetadata = false;
9449
+ for (const line of lines) {
9450
+ if (!line.trim()) continue;
9451
+ if (/^metadata:\s*$/.test(line)) {
9452
+ inMetadata = true;
9453
+ continue;
9454
+ }
9455
+ if (inMetadata && /^\s{2,}/.test(line)) {
9456
+ const match2 = line.trim().match(/^(\w+):\s*(.*)$/);
9457
+ if (match2) {
9458
+ const [, key2, val2] = match2;
9459
+ if (key2 === "type") {
9460
+ metadataType = val2?.trim() || null;
9461
+ }
9462
+ }
9463
+ continue;
9464
+ }
9465
+ if (inMetadata) {
9466
+ inMetadata = false;
9467
+ }
9468
+ const match = line.match(/^(\w+):\s*(.*)$/);
9469
+ if (!match) continue;
9470
+ const [, key, rawVal] = match;
9471
+ const val = (rawVal ?? "").trim().replace(/^["']|["']$/g, "");
9472
+ if (key === "name") name = val;
9473
+ if (key === "description") description = val;
9474
+ }
9475
+ return {
9476
+ ok: true,
9477
+ data: {
9478
+ name,
9479
+ description,
9480
+ metadata: { type: metadataType }
9481
+ }
9482
+ };
9483
+ }
9484
+ async function inventoryFiles(dir) {
9485
+ let filenames;
9486
+ try {
9487
+ const entries = await readdir4(dir);
9488
+ filenames = entries.filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
9489
+ } catch {
9490
+ return [];
9491
+ }
9492
+ const results = [];
9493
+ for (const filename of filenames) {
9494
+ const sourcePath = join31(dir, filename);
9495
+ let raw;
9496
+ try {
9497
+ raw = await readFile22(sourcePath, "utf-8");
9498
+ } catch (err) {
9499
+ const msg = err instanceof Error ? err.message : String(err);
9500
+ results.push({
9501
+ filename,
9502
+ source_path: sourcePath,
9503
+ name: filename.replace(/\.md$/, ""),
9504
+ description: "",
9505
+ metadata_type: null,
9506
+ suggested_action: "keep",
9507
+ parse_error: `Could not read file: ${msg}`
9508
+ });
9509
+ continue;
9510
+ }
9511
+ const parseResult = parseFrontmatter(raw);
9512
+ if (!parseResult.ok) {
9513
+ console.error(
9514
+ `[migrate-memory] WARNING: could not parse frontmatter in ${filename}: ${parseResult.error}`
9515
+ );
9516
+ results.push({
9517
+ filename,
9518
+ source_path: sourcePath,
9519
+ name: filename.replace(/\.md$/, ""),
9520
+ description: "",
9521
+ metadata_type: null,
9522
+ suggested_action: "keep",
9523
+ parse_error: parseResult.error
9524
+ });
9525
+ continue;
9526
+ }
9527
+ const { name, description, metadata } = parseResult.data;
9528
+ const entry = {
9529
+ filename,
9530
+ source_path: sourcePath,
9531
+ name: name || filename.replace(/\.md$/, ""),
9532
+ description,
9533
+ metadata_type: metadata.type,
9534
+ suggested_action: "keep"
9535
+ };
9536
+ if (DROP_PATTERN.test(description)) {
9537
+ entry.suggested_action = "drop";
9538
+ const match = description.match(DROP_PATTERN);
9539
+ entry.drop_reason = `Description contains "${match[0]}" keyword`;
9540
+ }
9541
+ if (entry.suggested_action === "keep") {
9542
+ const typeSlug = metadata.type?.toLowerCase().replace(/[^a-z0-9-]/g, "-") ?? "misc";
9543
+ entry.suggested_target = `nested:.claude/memory/${typeSlug}`;
9544
+ }
9545
+ results.push(entry);
9546
+ }
9547
+ return results;
9548
+ }
9549
+ function flagDropCandidates(entries) {
9550
+ return entries.map((entry) => {
9551
+ if (entry.parse_error) {
9552
+ return entry;
9553
+ }
9554
+ if (DROP_PATTERN.test(entry.description)) {
9555
+ const match = entry.description.match(DROP_PATTERN);
9556
+ return {
9557
+ ...entry,
9558
+ suggested_action: "drop",
9559
+ drop_reason: entry.drop_reason ?? `Description contains "${match[0]}" keyword`
9560
+ };
9561
+ }
9562
+ return entry;
9563
+ });
9564
+ }
9565
+ function buildPlan(entries, opts) {
9566
+ const plan = {
9567
+ auto_memory_dir: opts.autoMemoryDir,
9568
+ entries,
9569
+ generate_step: "After --apply, run `codebyplan claude generate` to regenerate .claude/generated/structure.md"
9570
+ };
9571
+ if (!opts.omitTimestamp) {
9572
+ plan.generated_at = opts.now ? opts.now() : (/* @__PURE__ */ new Date()).toISOString();
9573
+ }
9574
+ return plan;
9575
+ }
9576
+ async function applyPlan(plan, opts) {
9577
+ const projectDir = resolve9(opts.projectDir);
9578
+ const dryRun = opts.dryRun ?? false;
9579
+ for (const entry of plan.entries) {
9580
+ if (entry.suggested_action !== "keep") continue;
9581
+ if (!entry.suggested_target?.startsWith("nested:")) continue;
9582
+ const relPath = entry.suggested_target.slice("nested:".length);
9583
+ const targetDir = resolve9(join31(projectDir, relPath));
9584
+ const targetFile = join31(targetDir, "CLAUDE.md");
9585
+ if (!targetDir.startsWith(resolve9(projectDir) + sep2)) {
9586
+ process.stderr.write(
9587
+ `migrate-memory: skipping unsafe suggested_target "${entry.suggested_target}" \u2014 resolves outside projectDir
9588
+ `
9589
+ );
9590
+ continue;
9591
+ }
9592
+ const anchor = `_Source: ${entry.filename}_`;
9593
+ const appendContent = `
9594
+ ## ${entry.name}
9595
+
9596
+ ${entry.description}
9597
+
9598
+ ${anchor}
9599
+ `;
9600
+ if (dryRun) {
9601
+ process.stdout.write(`[dry-run] Would create/append: ${targetFile}
9602
+ `);
9603
+ if (resolve9(entry.source_path).startsWith(
9604
+ resolve9(plan.auto_memory_dir) + sep2
9605
+ )) {
9606
+ process.stdout.write(
9607
+ `[dry-run] Would delete migrated keep source: ${entry.source_path}
9608
+ `
9609
+ );
9610
+ }
9611
+ continue;
9612
+ }
9613
+ await mkdir11(targetDir, { recursive: true });
9614
+ let existing = "";
9615
+ try {
9616
+ existing = await readFile22(targetFile, "utf-8");
9617
+ } catch {
9618
+ }
9619
+ if (!existing.includes(anchor)) {
9620
+ await writeFile17(targetFile, existing + appendContent, "utf-8");
9621
+ }
9622
+ if (resolve9(entry.source_path).startsWith(resolve9(plan.auto_memory_dir) + sep2)) {
9623
+ try {
9624
+ await unlink4(entry.source_path);
9625
+ } catch {
9626
+ }
9627
+ } else {
9628
+ process.stderr.write(
9629
+ `migrate-memory: skipping delete of migrated keep source "${entry.source_path}" \u2014 resolves outside auto_memory_dir
9630
+ `
9631
+ );
9632
+ }
9633
+ }
9634
+ const rootClaudeMd = join31(projectDir, ".claude", "CLAUDE.md");
9635
+ if (dryRun) {
9636
+ process.stdout.write(
9637
+ `[dry-run] Would ensure ${rootClaudeMd} contains: ${IMPORT_LINE}
9638
+ `
9639
+ );
9640
+ } else {
9641
+ let claudeMdContent = "";
9642
+ try {
9643
+ claudeMdContent = await readFile22(rootClaudeMd, "utf-8");
9644
+ } catch {
9645
+ await mkdir11(dirname11(rootClaudeMd), { recursive: true });
9646
+ }
9647
+ if (!claudeMdContent.includes(IMPORT_LINE)) {
9648
+ await writeFile17(
9649
+ rootClaudeMd,
9650
+ claudeMdContent + `
9651
+ ${IMPORT_LINE}
9652
+ `,
9653
+ "utf-8"
9654
+ );
9655
+ }
9656
+ }
9657
+ for (const entry of plan.entries) {
9658
+ if (entry.suggested_action !== "drop") continue;
9659
+ if (!resolve9(entry.source_path).startsWith(
9660
+ resolve9(plan.auto_memory_dir) + sep2
9661
+ )) {
9662
+ process.stderr.write(
9663
+ `migrate-memory: skipping delete of "${entry.source_path}" \u2014 resolves outside auto_memory_dir
9664
+ `
9665
+ );
9666
+ continue;
9667
+ }
9668
+ if (dryRun) {
9669
+ process.stdout.write(`[dry-run] Would delete: ${entry.source_path}
9670
+ `);
9671
+ continue;
9672
+ }
9673
+ try {
9674
+ await unlink4(entry.source_path);
9675
+ } catch {
9676
+ }
9677
+ }
9678
+ const memoryMd = join31(plan.auto_memory_dir, "MEMORY.md");
9679
+ const safeRmdirBase = join31(homedir8(), ".claude", "projects");
9680
+ if (dryRun) {
9681
+ process.stdout.write(`[dry-run] Would delete MEMORY.md: ${memoryMd}
9682
+ `);
9683
+ } else {
9684
+ if (resolve9(plan.auto_memory_dir).startsWith(safeRmdirBase + sep2)) {
9685
+ try {
9686
+ await unlink4(memoryMd);
9687
+ } catch {
9688
+ }
9689
+ } else {
9690
+ process.stderr.write(
9691
+ `migrate-memory: skipping MEMORY.md deletion \u2014 auto_memory_dir not under ~/.claude/projects
9692
+ `
9693
+ );
9694
+ }
9695
+ }
9696
+ if (dryRun) {
9697
+ process.stdout.write(
9698
+ `[dry-run] Would rmdir (if empty): ${plan.auto_memory_dir}
9699
+ `
9700
+ );
9701
+ } else {
9702
+ if (!resolve9(plan.auto_memory_dir).startsWith(safeRmdirBase + sep2)) {
9703
+ process.stderr.write(
9704
+ `migrate-memory: skipping rmdir of "${plan.auto_memory_dir}" \u2014 not under ~/.claude/projects
9705
+ `
9706
+ );
9707
+ } else {
9708
+ try {
9709
+ const remaining = await readdir4(plan.auto_memory_dir);
9710
+ if (remaining.length === 0) {
9711
+ await rmdir(plan.auto_memory_dir);
9712
+ }
9713
+ } catch {
9714
+ }
9715
+ }
9716
+ }
9717
+ }
9718
+ async function runMigrateMemory(opts) {
9719
+ const rest = opts.rest ?? [];
9720
+ const projectDir = opts.projectDir ?? process.cwd();
9721
+ const filteredRest = rest.filter(
9722
+ (v, i) => v !== "--project-dir" && rest[i - 1] !== "--project-dir"
9723
+ );
9724
+ const isJson = filteredRest.includes("--json") || filteredRest.length === 0;
9725
+ const isDryRun = rest.includes("--dry-run");
9726
+ const applyIdx = rest.indexOf("--apply");
9727
+ const applyFile = applyIdx !== -1 ? rest[applyIdx + 1] : void 0;
9728
+ const autoMemoryDir = resolveAutoMemoryDir(opts);
9729
+ if (applyFile) {
9730
+ let planJson;
9731
+ try {
9732
+ planJson = await readFile22(resolve9(applyFile), "utf-8");
9733
+ } catch (err) {
9734
+ const msg = err instanceof Error ? err.message : String(err);
9735
+ process.stderr.write(
9736
+ `migrate-memory: cannot read plan file "${applyFile}": ${msg}
9737
+ `
9738
+ );
9739
+ process.exitCode = 1;
9740
+ return;
9741
+ }
9742
+ let plan2;
9743
+ try {
9744
+ plan2 = JSON.parse(planJson);
9745
+ } catch (err) {
9746
+ const msg = err instanceof Error ? err.message : String(err);
9747
+ process.stderr.write(
9748
+ `migrate-memory: plan file is not valid JSON: ${msg}
9749
+ `
9750
+ );
9751
+ process.exitCode = 1;
9752
+ return;
9753
+ }
9754
+ await applyPlan(plan2, { projectDir, dryRun: isDryRun });
9755
+ process.stdout.write(
9756
+ isDryRun ? "migrate-memory: dry-run complete (no changes written).\n" : "migrate-memory: apply complete.\n"
9757
+ );
9758
+ return;
9759
+ }
9760
+ if (!existsSync10(autoMemoryDir)) {
9761
+ process.stdout.write(
9762
+ JSON.stringify(
9763
+ {
9764
+ auto_memory_dir: autoMemoryDir,
9765
+ entries: [],
9766
+ generate_step: "After --apply, run `codebyplan claude generate` to regenerate .claude/generated/structure.md",
9767
+ note: "Auto-memory directory does not exist \u2014 nothing to migrate."
9768
+ },
9769
+ null,
9770
+ 2
9771
+ ) + "\n"
9772
+ );
9773
+ return;
9774
+ }
9775
+ const rawEntries = await inventoryFiles(autoMemoryDir);
9776
+ const entries = flagDropCandidates(rawEntries);
9777
+ const plan = buildPlan(entries, {
9778
+ autoMemoryDir,
9779
+ omitTimestamp: opts.omitTimestamp,
9780
+ now: opts.now
9781
+ });
9782
+ if (isDryRun) {
9783
+ process.stdout.write("[dry-run] Plan:\n");
9784
+ process.stdout.write(JSON.stringify(plan, null, 2) + "\n\n");
9785
+ await applyPlan(plan, { projectDir, dryRun: true });
9786
+ return;
9787
+ }
9788
+ if (isJson) {
9789
+ process.stdout.write(JSON.stringify(plan, null, 2) + "\n");
9790
+ }
9791
+ }
9792
+ var DROP_PATTERN, IMPORT_LINE;
9793
+ var init_migrate_memory = __esm({
9794
+ "src/cli/claude/migrate-memory.ts"() {
9795
+ "use strict";
9796
+ DROP_PATTERN = /\b(RESOLVED|SHIPPED|RETIRED|ROLLED.?BACK)\b/i;
9797
+ IMPORT_LINE = "@.claude/generated/structure.md";
9798
+ }
9799
+ });
9800
+
9131
9801
  // src/index.ts
9132
9802
  init_version();
9133
9803
  import { readFileSync as readFileSync10 } from "node:fs";
9134
- import { resolve as resolve8 } from "node:path";
9804
+ import { resolve as resolve10 } from "node:path";
9135
9805
  void (async () => {
9136
9806
  if (!process.env.CODEBYPLAN_API_KEY) {
9137
9807
  try {
9138
- const envPath = resolve8(process.cwd(), ".env.local");
9808
+ const envPath = resolve10(process.cwd(), ".env.local");
9139
9809
  const content = readFileSync10(envPath, "utf-8");
9140
9810
  for (const line of content.split("\n")) {
9141
9811
  const trimmed = line.trim();
@@ -9336,16 +10006,40 @@ void (async () => {
9336
10006
  );
9337
10007
  process.exit(process.exitCode ?? 0);
9338
10008
  }
10009
+ if (subcommand === "generate") {
10010
+ if (parsed === null) {
10011
+ process.exit(1);
10012
+ }
10013
+ const { runGenerate: runGenerate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
10014
+ await runGenerate2(
10015
+ parsed.projectDir != null ? { ...parsed.opts, projectDir: parsed.projectDir } : parsed.opts
10016
+ );
10017
+ process.exit(process.exitCode ?? 0);
10018
+ }
10019
+ if (subcommand === "migrate-memory") {
10020
+ if (parsed === null) {
10021
+ process.exit(1);
10022
+ }
10023
+ const { runMigrateMemory: runMigrateMemory2 } = await Promise.resolve().then(() => (init_migrate_memory(), migrate_memory_exports));
10024
+ const rest = process.argv.slice(4);
10025
+ await runMigrateMemory2({
10026
+ rest,
10027
+ projectDir: parsed.projectDir ?? void 0
10028
+ });
10029
+ process.exit(process.exitCode ?? 0);
10030
+ }
9339
10031
  console.log(`
9340
10032
  CodeByPlan Claude asset management v${VERSION}
9341
10033
 
9342
10034
  Usage:
9343
- codebyplan claude install [flags] Install skills/agents/hooks into ./.claude/
9344
- codebyplan claude update [flags] Update installed assets to latest versions
9345
- codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
9346
- codebyplan claude status Check package-sync state (drift, version skip, settings drift)
9347
- --write-cache Write result to .codebyplan/claude-status.local.json
9348
- --quiet Suppress stdout output
10035
+ codebyplan claude install [flags] Install skills/agents/hooks into ./.claude/
10036
+ codebyplan claude update [flags] Update installed assets to latest versions
10037
+ codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
10038
+ codebyplan claude status Check package-sync state (drift, version skip, settings drift)
10039
+ --write-cache Write result to .codebyplan/claude-status.local.json
10040
+ --quiet Suppress stdout output
10041
+ codebyplan claude generate [flags] Write .claude/generated/structure.md from local config
10042
+ codebyplan claude migrate-memory [flags] Inventory auto-memory files and emit migration plan
9349
10043
 
9350
10044
  Flags (apply to install/update/uninstall):
9351
10045
  --yes, -y Auto-accept every prompt with its recommended default
@@ -9359,6 +10053,10 @@ void (async () => {
9359
10053
  --bash Set statusline renderer to bash after install/update
9360
10054
  --node Set statusline renderer to node after install/update
9361
10055
  --python Set statusline renderer to python after install/update
10056
+
10057
+ Flags (apply to generate):
10058
+ --project-dir <path> Override the project root (default: cwd)
10059
+ --dry-run Print what would be written, write nothing
9362
10060
  `);
9363
10061
  process.exit(0);
9364
10062
  }
@@ -9387,7 +10085,7 @@ void (async () => {
9387
10085
  codebyplan upload-e2e-images Upload new/changed committed e2e PNGs for a checkpoint
9388
10086
  codebyplan scaffold-publish-workflow Write the publish-on-main GitHub workflow into ./.github/workflows/
9389
10087
  codebyplan branch migrate Rewrite branch_config from 3-branch to 2-tier model
9390
- codebyplan claude Claude asset management (install/update/uninstall)
10088
+ codebyplan claude Claude asset management (install/update/uninstall/generate/migrate-memory)
9391
10089
  codebyplan statusline Show or set the statusline renderer (bash/node/python)
9392
10090
  codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
9393
10091
  codebyplan version-status Report installed vs latest version + update guard (JSON)
@@ -9419,9 +10117,11 @@ void (async () => {
9419
10117
  codebyplan eslint init Detect tech stack, resolve presets, generate configs
9420
10118
 
9421
10119
  Claude asset management:
9422
- codebyplan claude install [flags] Install skills/agents/hooks into ./.claude/
9423
- codebyplan claude update [flags] Update installed assets to latest versions
9424
- codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
10120
+ codebyplan claude install [flags] Install skills/agents/hooks into ./.claude/
10121
+ codebyplan claude update [flags] Update installed assets to latest versions
10122
+ codebyplan claude uninstall [flags] Remove installed assets from ./.claude/
10123
+ codebyplan claude generate [flags] Write .claude/generated/structure.md from local config
10124
+ codebyplan claude migrate-memory [flags] Inventory auto-memory files and emit migration plan
9425
10125
 
9426
10126
  Flags (install/update/uninstall):
9427
10127
  --yes, -y Auto-accept prompts with recommended defaults
@@ -9433,6 +10133,16 @@ void (async () => {
9433
10133
  --node Set statusline renderer to node after install/update
9434
10134
  --python Set statusline renderer to python after install/update
9435
10135
 
10136
+ Flags (generate):
10137
+ --project-dir <path> Override the project root (default: cwd)
10138
+ --dry-run Print what would be written, write nothing
10139
+
10140
+ Flags (migrate-memory):
10141
+ --project-dir <path> Override the project root (default: cwd)
10142
+ --json Inventory mode: print plan JSON to stdout, write nothing (default)
10143
+ --dry-run Print plan + what apply would do, write nothing
10144
+ --apply <file> Read an approved plan JSON file and apply it
10145
+
9436
10146
  Resolve-worktree options:
9437
10147
  --json Structured JSON output instead of bare UUID
9438
10148
  --fallback-from-branch Fallback resolver: filter by (device_id, branch) client-side