codebyplan 1.13.53 → 1.13.54

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 (84) hide show
  1. package/dist/cli.js +1354 -352
  2. package/package.json +1 -1
  3. package/templates/agents/cbp-database-agent.md +1 -1
  4. package/templates/agents/cbp-e2e-maestro.md +1 -1
  5. package/templates/agents/cbp-e2e-playwright.md +24 -16
  6. package/templates/agents/cbp-e2e-tauri.md +1 -1
  7. package/templates/agents/cbp-e2e-vscode.md +1 -1
  8. package/templates/agents/cbp-e2e-xcuitest.md +1 -1
  9. package/templates/agents/cbp-improve-claude.md +2 -2
  10. package/templates/agents/{cbp-round-executor.md → cbp-round-builder.md} +23 -23
  11. package/templates/agents/{cbp-task-planner.md → cbp-round-planner.md} +26 -25
  12. package/templates/agents/cbp-security-agent.md +1 -1
  13. package/templates/agents/cbp-stripe-agent.md +2 -2
  14. package/templates/agents/cbp-testing-qa-agent.md +11 -11
  15. package/templates/agents/cbp-verify-reviewer.md +236 -0
  16. package/templates/context/architecture-map.md +4 -4
  17. package/templates/context/mcp-docs.md +57 -11
  18. package/templates/context/testing/e2e.md +9 -9
  19. package/templates/github-workflows/ci.yml +41 -0
  20. package/templates/hooks/cbp-skill-context-guard.sh +1 -1
  21. package/templates/hooks/cbp-test-hooks.sh +9 -9
  22. package/templates/hooks/validate-structure-lengths.sh +1 -1
  23. package/templates/hooks/validate-structure-patterns.sh +1 -1
  24. package/templates/rules/README.md +1 -2
  25. package/templates/rules/agent-claim-verification.md +1 -1
  26. package/templates/rules/context-file-loading.md +10 -10
  27. package/templates/rules/development-workflow.md +73 -0
  28. package/templates/rules/e2e-mandatory.md +8 -8
  29. package/templates/rules/execution-proof.md +70 -0
  30. package/templates/rules/model-invocation-convention.md +2 -2
  31. package/templates/rules/parallel-waves.md +11 -11
  32. package/templates/rules/spawn-failure-is-gate-failure.md +76 -0
  33. package/templates/rules/task-routing-recommendation.md +1 -1
  34. package/templates/rules/todo-backend.md +3 -3
  35. package/templates/rules/two-tier-ci.md +63 -0
  36. package/templates/settings.project.base.json +8 -10
  37. package/templates/skills/cbp-build-cc-mode/SKILL.md +1 -1
  38. package/templates/skills/cbp-build-cc-settings/reference/cbp-permission-policy.md +7 -7
  39. package/templates/skills/cbp-build-cc-skill/SKILL.md +1 -1
  40. package/templates/skills/cbp-build-cc-skill/reference/cbp-quality.md +2 -2
  41. package/templates/skills/cbp-build-cc-skill/reference/fork-eligibility.md +11 -14
  42. package/templates/skills/cbp-checkpoint-check/SKILL.md +2 -2
  43. package/templates/skills/cbp-checkpoint-create/SKILL.md +16 -1
  44. package/templates/skills/cbp-checkpoint-update/SKILL.md +3 -3
  45. package/templates/skills/cbp-clear-continue/SKILL.md +2 -2
  46. package/templates/skills/cbp-clear-prep/SKILL.md +3 -3
  47. package/templates/skills/{cbp-task-complete → cbp-finalize}/SKILL.md +25 -29
  48. package/templates/skills/{cbp-task-complete → cbp-finalize}/reference/checkpoint-done-branching.md +1 -1
  49. package/templates/skills/{cbp-task-complete → cbp-finalize}/reference/next-step-heuristic.md +1 -1
  50. package/templates/skills/cbp-frontend-design/SKILL.md +1 -1
  51. package/templates/skills/cbp-frontend-ui/SKILL.md +7 -7
  52. package/templates/skills/cbp-git-commit/SKILL.md +3 -3
  53. package/templates/skills/cbp-merge-main/SKILL.md +4 -4
  54. package/templates/skills/{cbp-round-execute → cbp-round-build}/SKILL.md +93 -75
  55. package/templates/skills/cbp-round-complete/SKILL.md +15 -14
  56. package/templates/skills/cbp-round-plan/SKILL.md +344 -0
  57. package/templates/skills/cbp-session-end/SKILL.md +1 -1
  58. package/templates/skills/cbp-ship-main/SKILL.md +3 -2
  59. package/templates/skills/cbp-standalone-task-check/SKILL.md +10 -9
  60. package/templates/skills/cbp-standalone-task-complete/SKILL.md +12 -13
  61. package/templates/skills/cbp-standalone-task-create/SKILL.md +16 -9
  62. package/templates/skills/cbp-standalone-task-start/SKILL.md +9 -5
  63. package/templates/skills/cbp-standalone-task-testing/SKILL.md +5 -5
  64. package/templates/skills/cbp-task-create/SKILL.md +6 -7
  65. package/templates/skills/cbp-task-start/SKILL.md +8 -8
  66. package/templates/skills/cbp-todo/SKILL.md +6 -8
  67. package/templates/skills/cbp-verify/SKILL.md +146 -0
  68. package/templates/skills/cbp-verify/reference/deterministic-gates.md +114 -0
  69. package/templates/skills/{cbp-round-end → cbp-verify}/reference/findings-presentation.md +16 -12
  70. package/templates/skills/cbp-verify/reference/round-scope.md +62 -0
  71. package/templates/skills/cbp-verify/reference/task-scope.md +71 -0
  72. package/templates/agents/cbp-improve-round.md +0 -283
  73. package/templates/agents/cbp-task-check.md +0 -217
  74. package/templates/skills/cbp-round-check/SKILL.md +0 -134
  75. package/templates/skills/cbp-round-end/SKILL.md +0 -173
  76. package/templates/skills/cbp-round-end/reference/inline-fallback.md +0 -35
  77. package/templates/skills/cbp-round-execute/reference/inline-fallback.md +0 -55
  78. package/templates/skills/cbp-round-input/SKILL.md +0 -197
  79. package/templates/skills/cbp-round-start/SKILL.md +0 -261
  80. package/templates/skills/cbp-round-update/SKILL.md +0 -120
  81. package/templates/skills/cbp-ship/templates/workflow-eas-submit.yml +0 -53
  82. package/templates/skills/cbp-ship/templates/workflow-vsce-publish.yml +0 -31
  83. package/templates/skills/cbp-task-check/SKILL.md +0 -172
  84. package/templates/skills/cbp-task-testing/SKILL.md +0 -279
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ var VERSION, PACKAGE_NAME;
39
39
  var init_version = __esm({
40
40
  "src/lib/version.ts"() {
41
41
  "use strict";
42
- VERSION = "1.13.53";
42
+ VERSION = "1.13.54";
43
43
  PACKAGE_NAME = "codebyplan";
44
44
  }
45
45
  });
@@ -1105,14 +1105,16 @@ async function validateAuth() {
1105
1105
  await getAuthHeaders();
1106
1106
  }
1107
1107
  async function getAuthHeaders() {
1108
+ const key = legacyApiKey();
1109
+ if (key) {
1110
+ return { headers: { "x-api-key": key }, via: "api_key" };
1111
+ }
1108
1112
  try {
1109
1113
  const token = await getAccessToken();
1110
1114
  return { headers: { Authorization: `Bearer ${token}` }, via: "bearer" };
1111
1115
  } catch (err) {
1112
1116
  if (!(err instanceof NoTokenError)) throw err;
1113
- const key = legacyApiKey();
1114
- if (!key) throw new Error(noAuthHint());
1115
- return { headers: { "x-api-key": key }, via: "api_key" };
1117
+ throw new Error(noAuthHint());
1116
1118
  }
1117
1119
  }
1118
1120
  async function validateConnectivity() {
@@ -1757,7 +1759,18 @@ var init_template_walker = __esm({
1757
1759
  // SessionStart scope + sibling-parity sweep — fires ONLY in the templates source
1758
1760
  // repo (self-guards on templates/ presence). A no-op for consumers, so it ships
1759
1761
  // neither in hooks.json nor as a copied file.
1760
- "hooks/verify-parity.sh"
1762
+ "hooks/verify-parity.sh",
1763
+ // Monorepo-internal skill helpers — these qa-regression checklists hardcode
1764
+ // absolute /Users/... home paths and CBP worktree UUIDs that are meaningless
1765
+ // (and misleading) in any other consumer repo, so they ship neither as copied
1766
+ // files nor in the install manifest.
1767
+ "skills/cbp-todo/qa-regression.md",
1768
+ "skills/cbp-session-start/qa-regression.md",
1769
+ // task-routing-recommendation.md carries `scope: repo-only:codebyplan` and is
1770
+ // installed only in codebyplan-family repos (see templates/rules/README.md).
1771
+ // Excluding it here fixes a latent scope violation — it previously shipped to
1772
+ // every consumer despite the repo-only marker.
1773
+ "rules/task-routing-recommendation.md"
1761
1774
  ]);
1762
1775
  }
1763
1776
  });
@@ -4744,6 +4757,7 @@ __export(state_store_exports, {
4744
4757
  readEntityFile: () => readEntityFile,
4745
4758
  roundPath: () => roundPath,
4746
4759
  sessionLogPath: () => sessionLogPath,
4760
+ standaloneTaskPath: () => standaloneTaskPath,
4747
4761
  taskPath: () => taskPath,
4748
4762
  todosPath: () => todosPath,
4749
4763
  worktreesPath: () => worktreesPath,
@@ -4790,6 +4804,9 @@ function roundPath(repoRoot, checkpointId, taskId, roundId) {
4790
4804
  `${roundId}.json`
4791
4805
  );
4792
4806
  }
4807
+ function standaloneTaskPath(repoRoot, taskId) {
4808
+ return join18(stateDir(repoRoot), "standalone_tasks", `${taskId}.json`);
4809
+ }
4793
4810
  function sessionLogPath(repoRoot) {
4794
4811
  return join18(stateDir(repoRoot), "session", "current.json");
4795
4812
  }
@@ -14280,8 +14297,8 @@ var require_RealtimeChannel = __commonJS({
14280
14297
  }
14281
14298
  /** @internal */
14282
14299
  _notThisChannelEvent(event, ref) {
14283
- const { close, error, leave, join: join53 } = constants_1.CHANNEL_EVENTS;
14284
- const events = [close, error, leave, join53];
14300
+ const { close, error, leave, join: join54 } = constants_1.CHANNEL_EVENTS;
14301
+ const events = [close, error, leave, join54];
14285
14302
  return ref && events.includes(event) && ref !== this.joinPush.ref;
14286
14303
  }
14287
14304
  /** @internal */
@@ -28253,244 +28270,6 @@ var init_task = __esm({
28253
28270
  }
28254
28271
  });
28255
28272
 
28256
- // src/cli/session.ts
28257
- var session_exports = {};
28258
- __export(session_exports, {
28259
- runSessionCommand: () => runSessionCommand
28260
- });
28261
- async function resolveRepoRoot3() {
28262
- const found = await findCodebyplanConfig(process.cwd());
28263
- if (!found?.contents.repo_id) return null;
28264
- const repoRoot = deriveRepoRoot(found.path);
28265
- return { repoRoot, repoId: found.contents.repo_id };
28266
- }
28267
- async function updateCursorHash3(repoRoot, entityId, row) {
28268
- const cursor = await readCursor(repoRoot);
28269
- if (!cursor) return;
28270
- const hash = hashEntity(row);
28271
- cursor.entity_hashes[entityId] = hash;
28272
- await writeCursor(repoRoot, cursor);
28273
- }
28274
- async function runSessionCreateLog(args) {
28275
- const { flags } = parseFlagsFromArgs(args);
28276
- const repoInfo = await resolveRepoRoot3();
28277
- if (!repoInfo) {
28278
- process.stderr.write(
28279
- "session create-log: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
28280
- );
28281
- process.exit(1);
28282
- }
28283
- const { repoRoot, repoId } = repoInfo;
28284
- const snakeFlags = coerceFieldValues(kebabToSnakeKeys(flags));
28285
- const body = {
28286
- repo_id: repoId,
28287
- ...snakeFlags
28288
- };
28289
- try {
28290
- const created = await apiBackendPost(new URL(backendSessionLogsEndpoint()).pathname, body);
28291
- const sessionLog = created.session_log ?? created;
28292
- const filePath = sessionLogPath(repoRoot);
28293
- await writeEntityFile(filePath, sessionLog);
28294
- const sessionLogId = sessionLog.id;
28295
- if (sessionLogId) {
28296
- await updateCursorHash3(repoRoot, sessionLogId, sessionLog);
28297
- }
28298
- process.stdout.write(JSON.stringify(created) + "\n");
28299
- } catch (err) {
28300
- if (err instanceof BackendError) {
28301
- process.stderr.write(
28302
- `session create-log: backend error ${err.status}: ${err.message}
28303
- `
28304
- );
28305
- } else {
28306
- process.stderr.write(
28307
- `session create-log: ${err instanceof Error ? err.message : String(err)}
28308
- `
28309
- );
28310
- }
28311
- process.exit(1);
28312
- }
28313
- process.exit(0);
28314
- }
28315
- async function runSessionUpdateLog(args) {
28316
- const { flags } = parseFlagsFromArgs(args);
28317
- const id = flags.id ?? flags["log-id"];
28318
- if (!id) {
28319
- process.stderr.write("session update-log: --id <log-id> is required\n");
28320
- process.exit(1);
28321
- }
28322
- const repoInfo = await resolveRepoRoot3();
28323
- if (!repoInfo) {
28324
- process.stderr.write(
28325
- "session update-log: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
28326
- );
28327
- process.exit(1);
28328
- }
28329
- const { repoRoot } = repoInfo;
28330
- const filePath = sessionLogPath(repoRoot);
28331
- const { id: _omit, ...patchBody } = flags;
28332
- void _omit;
28333
- const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
28334
- const snapshot = await readEntityFile(filePath);
28335
- const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
28336
- await writeEntityFile(filePath, optimistic);
28337
- try {
28338
- const updated = await apiBackendPatch(
28339
- `${new URL(backendSessionLogsEndpoint()).pathname}/${id}`,
28340
- snakePatch
28341
- );
28342
- await writeEntityFile(filePath, updated);
28343
- await updateCursorHash3(repoRoot, id, updated);
28344
- process.stdout.write(JSON.stringify(updated) + "\n");
28345
- } catch (err) {
28346
- if (err instanceof BackendError && err.status < 500) {
28347
- if (snapshot !== null) {
28348
- await writeEntityFile(filePath, snapshot);
28349
- } else {
28350
- await deleteEntityFile(filePath);
28351
- }
28352
- process.stderr.write(
28353
- `session update-log: backend rejected (${err.status}): ${err.message}
28354
- `
28355
- );
28356
- } else {
28357
- await writeEntityFile(pendingMarkerPath(repoRoot, id), {
28358
- entity: "session_log",
28359
- id,
28360
- operation: "update-log",
28361
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28362
- error: err instanceof Error ? err.message : String(err)
28363
- });
28364
- process.stderr.write(
28365
- `session update-log: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28366
- `
28367
- );
28368
- }
28369
- process.exit(1);
28370
- }
28371
- process.exit(0);
28372
- }
28373
- async function runSessionUpdateState(args) {
28374
- const { flags, booleans } = parseFlagsFromArgs(args);
28375
- if (booleans.has("action") && !flags["action"]) {
28376
- process.stderr.write(
28377
- "session update-state: --action requires a value \u2014 use --action <activate|deactivate>\n"
28378
- );
28379
- process.exit(1);
28380
- }
28381
- if (!flags["action"]) {
28382
- process.stderr.write(
28383
- "session update-state: --action <activate|deactivate> is required\n"
28384
- );
28385
- process.exit(1);
28386
- }
28387
- const VALID_ACTIONS = /* @__PURE__ */ new Set(["activate", "deactivate"]);
28388
- if (!VALID_ACTIONS.has(flags["action"])) {
28389
- process.stderr.write(
28390
- `session update-state: --action must be 'activate' or 'deactivate', got '${flags["action"]}'
28391
- `
28392
- );
28393
- process.exit(1);
28394
- }
28395
- const repoInfo = await resolveRepoRoot3();
28396
- if (!repoInfo) {
28397
- process.stderr.write(
28398
- "session update-state: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
28399
- );
28400
- process.exit(1);
28401
- }
28402
- const { repoRoot, repoId } = repoInfo;
28403
- const { ...patchBody } = flags;
28404
- const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
28405
- const stateFilePath = sessionLogPath(repoRoot).replace(
28406
- "current.json",
28407
- "state.json"
28408
- );
28409
- const snapshot = await readEntityFile(stateFilePath);
28410
- const optimistic = { ...snapshot ?? {}, ...snakePatch, repo_id: repoId };
28411
- await writeEntityFile(stateFilePath, optimistic);
28412
- try {
28413
- const updated = await apiBackendPatch(
28414
- new URL(backendSessionStateEndpoint(repoId)).pathname,
28415
- snakePatch
28416
- );
28417
- await writeEntityFile(stateFilePath, updated);
28418
- process.stdout.write(JSON.stringify(updated) + "\n");
28419
- } catch (err) {
28420
- if (err instanceof BackendError && err.status < 500) {
28421
- if (snapshot !== null) {
28422
- await writeEntityFile(stateFilePath, snapshot);
28423
- } else {
28424
- await deleteEntityFile(stateFilePath);
28425
- }
28426
- process.stderr.write(
28427
- `session update-state: backend rejected (${err.status}): ${err.message}
28428
- `
28429
- );
28430
- } else {
28431
- await writeEntityFile(
28432
- pendingMarkerPath(repoRoot, `session-state-${repoId}`),
28433
- {
28434
- entity: "session_state",
28435
- repo_id: repoId,
28436
- operation: "update-state",
28437
- attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
28438
- error: err instanceof Error ? err.message : String(err)
28439
- }
28440
- );
28441
- process.stderr.write(
28442
- `session update-state: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
28443
- `
28444
- );
28445
- }
28446
- process.exit(1);
28447
- }
28448
- process.exit(0);
28449
- }
28450
- function printSessionHelp() {
28451
- process.stdout.write(
28452
- "\n codebyplan session <subcommand>\n\n Subcommands:\n create-log Create a session log (fields as --key value pairs)\n update-log Update a session log (--id <uuid> required, then --key value pairs)\n update-state Update session state for the current repo (--key value pairs)\n\n"
28453
- );
28454
- }
28455
- async function runSessionCommand(args) {
28456
- const subcommand = args[0];
28457
- if (subcommand === "create-log") {
28458
- await runSessionCreateLog(args.slice(1));
28459
- return;
28460
- }
28461
- if (subcommand === "update-log") {
28462
- await runSessionUpdateLog(args.slice(1));
28463
- return;
28464
- }
28465
- if (subcommand === "update-state") {
28466
- await runSessionUpdateState(args.slice(1));
28467
- return;
28468
- }
28469
- if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
28470
- printSessionHelp();
28471
- process.exit(0);
28472
- }
28473
- if (subcommand) {
28474
- process.stderr.write(
28475
- `Unknown session subcommand: ${subcommand}
28476
- Run 'codebyplan session help' for usage.
28477
- `
28478
- );
28479
- } else {
28480
- printSessionHelp();
28481
- }
28482
- process.exit(1);
28483
- }
28484
- var init_session = __esm({
28485
- "src/cli/session.ts"() {
28486
- "use strict";
28487
- init_flags();
28488
- init_state_store();
28489
- init_state_client();
28490
- init_urls();
28491
- }
28492
- });
28493
-
28494
28273
  // src/lib/mcp-client.ts
28495
28274
  async function mcpCall(toolName, args) {
28496
28275
  let accessToken;
@@ -29126,7 +28905,7 @@ async function runRoundSyncApprovals(args) {
29126
28905
  );
29127
28906
  if (!dryRun && !callerWorktreeId) {
29128
28907
  throw new Error(
29129
- "could not resolve caller_worktree_id for this worktree.\n Run: codebyplan resolve-worktree --cache\n If this worktree is not registered, run: npx codebyplan setup\n Then re-run /cbp-round-update (sync-approvals)."
28908
+ "could not resolve caller_worktree_id for this worktree.\n Run: codebyplan resolve-worktree --cache\n If this worktree is not registered, run: npx codebyplan setup\n Then re-run codebyplan round sync-approvals."
29130
28909
  );
29131
28910
  }
29132
28911
  let gitStatusOutput = "";
@@ -29205,7 +28984,7 @@ async function runRoundSyncApprovals(args) {
29205
28984
  }
29206
28985
  if (skipReason !== null) {
29207
28986
  process.stderr.write(
29208
- `sync-approvals: MCP temporarily unavailable (${skipReason}); skipping approval sync. Re-run /cbp-round-update when the service recovers.
28987
+ `sync-approvals: MCP temporarily unavailable (${skipReason}); skipping approval sync. Re-run codebyplan round sync-approvals when the service recovers.
29209
28988
  `
29210
28989
  );
29211
28990
  process.exit(0);
@@ -29237,6 +29016,577 @@ var init_round = __esm({
29237
29016
  }
29238
29017
  });
29239
29018
 
29019
+ // src/cli/standalone-task.ts
29020
+ var standalone_task_exports = {};
29021
+ __export(standalone_task_exports, {
29022
+ runStandaloneTaskCommand: () => runStandaloneTaskCommand
29023
+ });
29024
+ async function resolveRepoRoot3() {
29025
+ const found = await findCodebyplanConfig(process.cwd());
29026
+ if (!found?.contents.repo_id) return null;
29027
+ const repoRoot = deriveRepoRoot(found.path);
29028
+ return { repoRoot, repoId: found.contents.repo_id };
29029
+ }
29030
+ async function updateCursorHash3(repoRoot, entityId, row) {
29031
+ const cursor = await readCursor(repoRoot);
29032
+ if (!cursor) return;
29033
+ const hash = hashEntity(row);
29034
+ cursor.entity_hashes[entityId] = hash;
29035
+ await writeCursor(repoRoot, cursor);
29036
+ }
29037
+ async function resolveCallerWorktreeId2(repoRoot, currentBranch, repoId, overrideId) {
29038
+ if (overrideId) {
29039
+ return overrideId;
29040
+ }
29041
+ const cached = await readCachedWorktreeId(repoRoot, currentBranch);
29042
+ if (cached) {
29043
+ return cached;
29044
+ }
29045
+ if (!repoId || !currentBranch) {
29046
+ return null;
29047
+ }
29048
+ const deviceId = await getOrCreateDeviceId(repoRoot);
29049
+ const wid = await resolveWorktreeId({
29050
+ repoId,
29051
+ repoPath: repoRoot,
29052
+ branch: currentBranch,
29053
+ deviceId
29054
+ });
29055
+ if (wid) {
29056
+ await writeWorktreeCache(repoRoot, {
29057
+ worktree_id: wid,
29058
+ branch: currentBranch,
29059
+ device_id: deviceId
29060
+ });
29061
+ return wid;
29062
+ }
29063
+ return null;
29064
+ }
29065
+ function currentBranchSafe(repoRoot) {
29066
+ try {
29067
+ return getCurrentBranch(repoRoot);
29068
+ } catch {
29069
+ return "";
29070
+ }
29071
+ }
29072
+ async function revertLocalWrite(filePath, snapshot) {
29073
+ if (snapshot !== null) {
29074
+ await writeEntityFile(filePath, snapshot);
29075
+ } else {
29076
+ await deleteEntityFile(filePath);
29077
+ }
29078
+ }
29079
+ async function handleMcpWriteError(opts) {
29080
+ const { verb, err, filePath, snapshot, repoRoot, id, operation } = opts;
29081
+ const msg = err instanceof Error ? err.message : String(err);
29082
+ const status = err instanceof McpError ? err.status : void 0;
29083
+ if (status === 401) {
29084
+ await revertLocalWrite(filePath, snapshot);
29085
+ process.stderr.write(
29086
+ `${verb}: authentication failed (401): ${msg}
29087
+ Run: codebyplan login
29088
+ `
29089
+ );
29090
+ return;
29091
+ }
29092
+ if (isTransientMcpError(err)) {
29093
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29094
+ entity: "standalone_task",
29095
+ id,
29096
+ operation,
29097
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29098
+ error: msg
29099
+ });
29100
+ process.stderr.write(
29101
+ `${verb}: MCP unavailable \u2014 local write kept, pending marker written. Error: ${msg}
29102
+ `
29103
+ );
29104
+ return;
29105
+ }
29106
+ await revertLocalWrite(filePath, snapshot);
29107
+ process.stderr.write(`${verb}: rejected: ${msg}
29108
+ `);
29109
+ }
29110
+ async function runStandaloneTaskCreate(args) {
29111
+ const { flags } = parseFlagsFromArgs(args);
29112
+ if (!flags.title) {
29113
+ process.stderr.write(
29114
+ "standalone-task create: --title <title> is required\nUsage: codebyplan standalone-task create --title <title> [--key value ...]\n"
29115
+ );
29116
+ process.exit(1);
29117
+ }
29118
+ const found = await findCodebyplanConfig(process.cwd());
29119
+ const repoId = flags["repo-id"] ?? found?.contents.repo_id;
29120
+ if (!repoId) {
29121
+ process.stderr.write(
29122
+ "standalone-task create: could not determine repo_id.\n Pass --repo-id <uuid> or run `codebyplan setup` so .codebyplan/repo.json exists.\n"
29123
+ );
29124
+ process.exit(1);
29125
+ }
29126
+ const repoRoot = found ? deriveRepoRoot(found.path) : process.cwd();
29127
+ const snakeFlags = coerceFieldValues(kebabToSnakeKeys(flags));
29128
+ const currentBranch = currentBranchSafe(repoRoot);
29129
+ const callerWorktreeId = await resolveCallerWorktreeId2(
29130
+ repoRoot,
29131
+ currentBranch,
29132
+ repoId,
29133
+ flags["caller-worktree-id"]
29134
+ );
29135
+ const mcpArgs = {};
29136
+ for (const key of CREATE_KEYS) {
29137
+ if (snakeFlags[key] !== void 0) mcpArgs[key] = snakeFlags[key];
29138
+ }
29139
+ mcpArgs.repo_id = repoId;
29140
+ if (flags.number !== void 0) mcpArgs.number = Number(flags.number);
29141
+ if (callerWorktreeId) mcpArgs.caller_worktree_id = callerWorktreeId;
29142
+ try {
29143
+ const created = await mcpCall(
29144
+ "create_standalone_task",
29145
+ mcpArgs
29146
+ );
29147
+ const filePath = standaloneTaskPath(repoRoot, created.id);
29148
+ await writeEntityFile(filePath, created);
29149
+ await updateCursorHash3(repoRoot, created.id, created);
29150
+ process.stdout.write(JSON.stringify(created) + "\n");
29151
+ } catch (err) {
29152
+ const msg = err instanceof Error ? err.message : String(err);
29153
+ process.stderr.write(`standalone-task create: ${msg}
29154
+ `);
29155
+ process.exit(1);
29156
+ }
29157
+ process.exit(0);
29158
+ }
29159
+ async function runStandaloneTaskUpdate(args) {
29160
+ const { flags } = parseFlagsFromArgs(args);
29161
+ const id = flags.id ?? flags["standalone-task-id"];
29162
+ if (!id) {
29163
+ process.stderr.write(
29164
+ "standalone-task update: --id <standalone-task-id> is required\n"
29165
+ );
29166
+ process.exit(1);
29167
+ }
29168
+ const repoInfo = await resolveRepoRoot3();
29169
+ if (!repoInfo) {
29170
+ process.stderr.write(
29171
+ "standalone-task update: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
29172
+ );
29173
+ process.exit(1);
29174
+ }
29175
+ const { repoRoot, repoId } = repoInfo;
29176
+ const filePath = standaloneTaskPath(repoRoot, id);
29177
+ const snapshot = await readEntityFile(filePath);
29178
+ const {
29179
+ id: _omit,
29180
+ "standalone-task-id": _omit2,
29181
+ "caller-worktree-id": _omit3,
29182
+ ...patchBody
29183
+ } = flags;
29184
+ void _omit;
29185
+ void _omit2;
29186
+ void _omit3;
29187
+ const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
29188
+ if (typeof snakePatch.app_file_approval_by_user === "string") {
29189
+ const v = snakePatch.app_file_approval_by_user;
29190
+ if (v === "true") snakePatch.app_file_approval_by_user = true;
29191
+ else if (v === "false") snakePatch.app_file_approval_by_user = false;
29192
+ }
29193
+ const currentBranch = currentBranchSafe(repoRoot);
29194
+ const callerWorktreeId = await resolveCallerWorktreeId2(
29195
+ repoRoot,
29196
+ currentBranch,
29197
+ repoId,
29198
+ flags["caller-worktree-id"]
29199
+ );
29200
+ const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
29201
+ await writeEntityFile(filePath, optimistic);
29202
+ const mcpArgs = {
29203
+ standalone_task_id: id,
29204
+ ...snakePatch
29205
+ };
29206
+ if (callerWorktreeId) mcpArgs.caller_worktree_id = callerWorktreeId;
29207
+ try {
29208
+ const updated = await mcpCall(
29209
+ "update_standalone_task",
29210
+ mcpArgs
29211
+ );
29212
+ await writeEntityFile(filePath, updated);
29213
+ await updateCursorHash3(repoRoot, id, updated);
29214
+ process.stdout.write(JSON.stringify(updated) + "\n");
29215
+ } catch (err) {
29216
+ await handleMcpWriteError({
29217
+ verb: "standalone-task update",
29218
+ err,
29219
+ filePath,
29220
+ snapshot,
29221
+ repoRoot,
29222
+ id,
29223
+ operation: "update"
29224
+ });
29225
+ process.exit(1);
29226
+ }
29227
+ process.exit(0);
29228
+ }
29229
+ async function runStandaloneTaskComplete(args) {
29230
+ const { flags } = parseFlagsFromArgs(args);
29231
+ const id = flags.id ?? flags["standalone-task-id"];
29232
+ if (!id) {
29233
+ process.stderr.write(
29234
+ "standalone-task complete: --id <standalone-task-id> is required\n"
29235
+ );
29236
+ process.exit(1);
29237
+ }
29238
+ const repoInfo = await resolveRepoRoot3();
29239
+ if (!repoInfo) {
29240
+ process.stderr.write(
29241
+ "standalone-task complete: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
29242
+ );
29243
+ process.exit(1);
29244
+ }
29245
+ const { repoRoot, repoId } = repoInfo;
29246
+ const currentBranch = currentBranchSafe(repoRoot);
29247
+ const callerWorktreeId = await resolveCallerWorktreeId2(
29248
+ repoRoot,
29249
+ currentBranch,
29250
+ repoId,
29251
+ flags["caller-worktree-id"]
29252
+ );
29253
+ if (!callerWorktreeId) {
29254
+ process.stderr.write(
29255
+ "standalone-task complete: could not resolve caller_worktree_id for this worktree.\n Run: codebyplan resolve-worktree --cache\n If this worktree is not registered, run: npx codebyplan setup\n Then re-run /cbp-standalone-task-complete.\n"
29256
+ );
29257
+ process.exit(1);
29258
+ }
29259
+ const filePath = standaloneTaskPath(repoRoot, id);
29260
+ const snapshot = await readEntityFile(filePath);
29261
+ const optimistic = { ...snapshot ?? {}, id, status: "completed" };
29262
+ await writeEntityFile(filePath, optimistic);
29263
+ try {
29264
+ const completed = await mcpCall(
29265
+ "complete_standalone_task",
29266
+ { standalone_task_id: id, caller_worktree_id: callerWorktreeId }
29267
+ );
29268
+ await writeEntityFile(filePath, completed);
29269
+ await updateCursorHash3(repoRoot, id, completed);
29270
+ process.stdout.write(JSON.stringify(completed) + "\n");
29271
+ } catch (err) {
29272
+ await handleMcpWriteError({
29273
+ verb: "standalone-task complete",
29274
+ err,
29275
+ filePath,
29276
+ snapshot,
29277
+ repoRoot,
29278
+ id,
29279
+ operation: "complete"
29280
+ });
29281
+ process.exit(1);
29282
+ }
29283
+ process.exit(0);
29284
+ }
29285
+ function printStandaloneTaskHelp() {
29286
+ process.stdout.write(
29287
+ "\n codebyplan standalone-task <subcommand>\n\n Standalone tasks are independent work items (no checkpoint parent).\n Writes go through the standalone MCP tools \u2014 no --checkpoint-id flag.\n\n Subcommands:\n create Create a standalone task (--title required, pass extra fields as --key value)\n update Update a standalone task (--id required, then --key value pairs)\n complete Complete a standalone task (--id required; caller worktree must resolve)\n\n"
29288
+ );
29289
+ }
29290
+ async function runStandaloneTaskCommand(args) {
29291
+ const subcommand = args[0];
29292
+ if (subcommand === "create") {
29293
+ await runStandaloneTaskCreate(args.slice(1));
29294
+ return;
29295
+ }
29296
+ if (subcommand === "update") {
29297
+ await runStandaloneTaskUpdate(args.slice(1));
29298
+ return;
29299
+ }
29300
+ if (subcommand === "complete") {
29301
+ await runStandaloneTaskComplete(args.slice(1));
29302
+ return;
29303
+ }
29304
+ if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
29305
+ printStandaloneTaskHelp();
29306
+ process.exit(0);
29307
+ }
29308
+ if (subcommand) {
29309
+ process.stderr.write(
29310
+ `Unknown standalone-task subcommand: ${subcommand}
29311
+ Run 'codebyplan standalone-task help' for usage.
29312
+ `
29313
+ );
29314
+ } else {
29315
+ printStandaloneTaskHelp();
29316
+ }
29317
+ process.exit(1);
29318
+ }
29319
+ var CREATE_KEYS;
29320
+ var init_standalone_task = __esm({
29321
+ "src/cli/standalone-task.ts"() {
29322
+ "use strict";
29323
+ init_mcp_client();
29324
+ init_round();
29325
+ init_flags();
29326
+ init_git_utils();
29327
+ init_worktree_cache();
29328
+ init_resolve_worktree();
29329
+ init_local_config();
29330
+ init_state_store();
29331
+ CREATE_KEYS = [
29332
+ "repo_id",
29333
+ "title",
29334
+ "number",
29335
+ "requirements",
29336
+ "status",
29337
+ "context",
29338
+ "qa",
29339
+ "research",
29340
+ "context_development",
29341
+ "resources",
29342
+ "user_context",
29343
+ "is_claude_written",
29344
+ "deadline",
29345
+ "branch_name",
29346
+ "assigned_worktree_id",
29347
+ "caller_worktree_id"
29348
+ ];
29349
+ }
29350
+ });
29351
+
29352
+ // src/cli/session.ts
29353
+ var session_exports = {};
29354
+ __export(session_exports, {
29355
+ runSessionCommand: () => runSessionCommand
29356
+ });
29357
+ async function resolveRepoRoot4() {
29358
+ const found = await findCodebyplanConfig(process.cwd());
29359
+ if (!found?.contents.repo_id) return null;
29360
+ const repoRoot = deriveRepoRoot(found.path);
29361
+ return { repoRoot, repoId: found.contents.repo_id };
29362
+ }
29363
+ async function updateCursorHash4(repoRoot, entityId, row) {
29364
+ const cursor = await readCursor(repoRoot);
29365
+ if (!cursor) return;
29366
+ const hash = hashEntity(row);
29367
+ cursor.entity_hashes[entityId] = hash;
29368
+ await writeCursor(repoRoot, cursor);
29369
+ }
29370
+ async function runSessionCreateLog(args) {
29371
+ const { flags } = parseFlagsFromArgs(args);
29372
+ const repoInfo = await resolveRepoRoot4();
29373
+ if (!repoInfo) {
29374
+ process.stderr.write(
29375
+ "session create-log: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
29376
+ );
29377
+ process.exit(1);
29378
+ }
29379
+ const { repoRoot, repoId } = repoInfo;
29380
+ const snakeFlags = coerceFieldValues(kebabToSnakeKeys(flags));
29381
+ const body = {
29382
+ repo_id: repoId,
29383
+ ...snakeFlags
29384
+ };
29385
+ try {
29386
+ const created = await apiBackendPost(new URL(backendSessionLogsEndpoint()).pathname, body);
29387
+ const sessionLog = created.session_log ?? created;
29388
+ const filePath = sessionLogPath(repoRoot);
29389
+ await writeEntityFile(filePath, sessionLog);
29390
+ const sessionLogId = sessionLog.id;
29391
+ if (sessionLogId) {
29392
+ await updateCursorHash4(repoRoot, sessionLogId, sessionLog);
29393
+ }
29394
+ process.stdout.write(JSON.stringify(created) + "\n");
29395
+ } catch (err) {
29396
+ if (err instanceof BackendError) {
29397
+ process.stderr.write(
29398
+ `session create-log: backend error ${err.status}: ${err.message}
29399
+ `
29400
+ );
29401
+ } else {
29402
+ process.stderr.write(
29403
+ `session create-log: ${err instanceof Error ? err.message : String(err)}
29404
+ `
29405
+ );
29406
+ }
29407
+ process.exit(1);
29408
+ }
29409
+ process.exit(0);
29410
+ }
29411
+ async function runSessionUpdateLog(args) {
29412
+ const { flags } = parseFlagsFromArgs(args);
29413
+ const id = flags.id ?? flags["log-id"];
29414
+ if (!id) {
29415
+ process.stderr.write("session update-log: --id <log-id> is required\n");
29416
+ process.exit(1);
29417
+ }
29418
+ const repoInfo = await resolveRepoRoot4();
29419
+ if (!repoInfo) {
29420
+ process.stderr.write(
29421
+ "session update-log: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
29422
+ );
29423
+ process.exit(1);
29424
+ }
29425
+ const { repoRoot } = repoInfo;
29426
+ const filePath = sessionLogPath(repoRoot);
29427
+ const { id: _omit, ...patchBody } = flags;
29428
+ void _omit;
29429
+ const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
29430
+ const snapshot = await readEntityFile(filePath);
29431
+ const optimistic = { ...snapshot ?? {}, ...snakePatch, id };
29432
+ await writeEntityFile(filePath, optimistic);
29433
+ try {
29434
+ const updated = await apiBackendPatch(
29435
+ `${new URL(backendSessionLogsEndpoint()).pathname}/${id}`,
29436
+ snakePatch
29437
+ );
29438
+ await writeEntityFile(filePath, updated);
29439
+ await updateCursorHash4(repoRoot, id, updated);
29440
+ process.stdout.write(JSON.stringify(updated) + "\n");
29441
+ } catch (err) {
29442
+ if (err instanceof BackendError && err.status < 500) {
29443
+ if (snapshot !== null) {
29444
+ await writeEntityFile(filePath, snapshot);
29445
+ } else {
29446
+ await deleteEntityFile(filePath);
29447
+ }
29448
+ process.stderr.write(
29449
+ `session update-log: backend rejected (${err.status}): ${err.message}
29450
+ `
29451
+ );
29452
+ } else {
29453
+ await writeEntityFile(pendingMarkerPath(repoRoot, id), {
29454
+ entity: "session_log",
29455
+ id,
29456
+ operation: "update-log",
29457
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29458
+ error: err instanceof Error ? err.message : String(err)
29459
+ });
29460
+ process.stderr.write(
29461
+ `session update-log: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29462
+ `
29463
+ );
29464
+ }
29465
+ process.exit(1);
29466
+ }
29467
+ process.exit(0);
29468
+ }
29469
+ async function runSessionUpdateState(args) {
29470
+ const { flags, booleans } = parseFlagsFromArgs(args);
29471
+ if (booleans.has("action") && !flags["action"]) {
29472
+ process.stderr.write(
29473
+ "session update-state: --action requires a value \u2014 use --action <activate|deactivate>\n"
29474
+ );
29475
+ process.exit(1);
29476
+ }
29477
+ if (!flags["action"]) {
29478
+ process.stderr.write(
29479
+ "session update-state: --action <activate|deactivate> is required\n"
29480
+ );
29481
+ process.exit(1);
29482
+ }
29483
+ const VALID_ACTIONS = /* @__PURE__ */ new Set(["activate", "deactivate"]);
29484
+ if (!VALID_ACTIONS.has(flags["action"])) {
29485
+ process.stderr.write(
29486
+ `session update-state: --action must be 'activate' or 'deactivate', got '${flags["action"]}'
29487
+ `
29488
+ );
29489
+ process.exit(1);
29490
+ }
29491
+ const repoInfo = await resolveRepoRoot4();
29492
+ if (!repoInfo) {
29493
+ process.stderr.write(
29494
+ "session update-state: no .codebyplan/repo.json found. Run `codebyplan setup`.\n"
29495
+ );
29496
+ process.exit(1);
29497
+ }
29498
+ const { repoRoot, repoId } = repoInfo;
29499
+ const { ...patchBody } = flags;
29500
+ const snakePatch = coerceFieldValues(kebabToSnakeKeys(patchBody));
29501
+ const stateFilePath = sessionLogPath(repoRoot).replace(
29502
+ "current.json",
29503
+ "state.json"
29504
+ );
29505
+ const snapshot = await readEntityFile(stateFilePath);
29506
+ const optimistic = { ...snapshot ?? {}, ...snakePatch, repo_id: repoId };
29507
+ await writeEntityFile(stateFilePath, optimistic);
29508
+ try {
29509
+ const updated = await apiBackendPatch(
29510
+ new URL(backendSessionStateEndpoint(repoId)).pathname,
29511
+ snakePatch
29512
+ );
29513
+ await writeEntityFile(stateFilePath, updated);
29514
+ process.stdout.write(JSON.stringify(updated) + "\n");
29515
+ } catch (err) {
29516
+ if (err instanceof BackendError && err.status < 500) {
29517
+ if (snapshot !== null) {
29518
+ await writeEntityFile(stateFilePath, snapshot);
29519
+ } else {
29520
+ await deleteEntityFile(stateFilePath);
29521
+ }
29522
+ process.stderr.write(
29523
+ `session update-state: backend rejected (${err.status}): ${err.message}
29524
+ `
29525
+ );
29526
+ } else {
29527
+ await writeEntityFile(
29528
+ pendingMarkerPath(repoRoot, `session-state-${repoId}`),
29529
+ {
29530
+ entity: "session_state",
29531
+ repo_id: repoId,
29532
+ operation: "update-state",
29533
+ attempted_at: (/* @__PURE__ */ new Date()).toISOString(),
29534
+ error: err instanceof Error ? err.message : String(err)
29535
+ }
29536
+ );
29537
+ process.stderr.write(
29538
+ `session update-state: backend unavailable \u2014 local write kept, pending marker written. Error: ${err instanceof Error ? err.message : String(err)}
29539
+ `
29540
+ );
29541
+ }
29542
+ process.exit(1);
29543
+ }
29544
+ process.exit(0);
29545
+ }
29546
+ function printSessionHelp() {
29547
+ process.stdout.write(
29548
+ "\n codebyplan session <subcommand>\n\n Subcommands:\n create-log Create a session log (fields as --key value pairs)\n update-log Update a session log (--id <uuid> required, then --key value pairs)\n update-state Update session state for the current repo (--key value pairs)\n\n"
29549
+ );
29550
+ }
29551
+ async function runSessionCommand(args) {
29552
+ const subcommand = args[0];
29553
+ if (subcommand === "create-log") {
29554
+ await runSessionCreateLog(args.slice(1));
29555
+ return;
29556
+ }
29557
+ if (subcommand === "update-log") {
29558
+ await runSessionUpdateLog(args.slice(1));
29559
+ return;
29560
+ }
29561
+ if (subcommand === "update-state") {
29562
+ await runSessionUpdateState(args.slice(1));
29563
+ return;
29564
+ }
29565
+ if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
29566
+ printSessionHelp();
29567
+ process.exit(0);
29568
+ }
29569
+ if (subcommand) {
29570
+ process.stderr.write(
29571
+ `Unknown session subcommand: ${subcommand}
29572
+ Run 'codebyplan session help' for usage.
29573
+ `
29574
+ );
29575
+ } else {
29576
+ printSessionHelp();
29577
+ }
29578
+ process.exit(1);
29579
+ }
29580
+ var init_session = __esm({
29581
+ "src/cli/session.ts"() {
29582
+ "use strict";
29583
+ init_flags();
29584
+ init_state_store();
29585
+ init_state_client();
29586
+ init_urls();
29587
+ }
29588
+ });
29589
+
29240
29590
  // src/lib/migrate-branch-model.ts
29241
29591
  import { readFile as readFile17, writeFile as writeFile14 } from "node:fs/promises";
29242
29592
  import { join as join21 } from "node:path";
@@ -34905,9 +35255,600 @@ var init_tech_stack = __esm({
34905
35255
  }
34906
35256
  });
34907
35257
 
35258
+ // src/cli/docs.ts
35259
+ var docs_exports = {};
35260
+ __export(docs_exports, {
35261
+ libDirName: () => libDirName,
35262
+ runDocs: () => runDocs,
35263
+ sanitizeDocPath: () => sanitizeDocPath,
35264
+ selectDependencies: () => selectDependencies,
35265
+ stripRangePrefix: () => stripRangePrefix
35266
+ });
35267
+ import { existsSync as existsSync13 } from "node:fs";
35268
+ import { mkdir as mkdir12, readFile as readFile26, readdir as readdir6, rm as rm2, writeFile as writeFile20 } from "node:fs/promises";
35269
+ import { dirname as dirname13, isAbsolute, join as join37, relative as relative7, sep as sep2 } from "node:path";
35270
+ function selectDependencies(deps) {
35271
+ const byName = /* @__PURE__ */ new Map();
35272
+ for (const dep of deps) {
35273
+ const name = dep.name?.trim() ?? "";
35274
+ if (name === "") continue;
35275
+ if (name.startsWith("@types/")) continue;
35276
+ const declared = dep.version?.trim() ?? "";
35277
+ if (SKIP_VERSION_RE.test(declared)) continue;
35278
+ const candidate = {
35279
+ name,
35280
+ declared,
35281
+ isDev: dep.is_dev === true,
35282
+ sourcePath: dep.source_path ?? "package.json"
35283
+ };
35284
+ const existing = byName.get(name);
35285
+ if (!existing || existing.isDev && !candidate.isDev) {
35286
+ byName.set(name, candidate);
35287
+ }
35288
+ }
35289
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
35290
+ }
35291
+ function stripRangePrefix(range) {
35292
+ return range.replace(/^(>=|\^|~)\s*/, "").trim();
35293
+ }
35294
+ function sanitizeDocPath(docPath) {
35295
+ const trimmed = docPath.trim();
35296
+ if (trimmed === "") return null;
35297
+ if (trimmed.includes("\0")) return null;
35298
+ if (trimmed.includes("..")) return null;
35299
+ if (trimmed.startsWith("/")) return null;
35300
+ return trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`;
35301
+ }
35302
+ function libDirName(name) {
35303
+ return name.replace(/\//g, "__");
35304
+ }
35305
+ async function mapWithConcurrency(items, limit, fn) {
35306
+ const results = new Array(items.length);
35307
+ let next = 0;
35308
+ const workers = Array.from(
35309
+ { length: Math.min(limit, items.length) },
35310
+ async () => {
35311
+ for (; ; ) {
35312
+ const i = next++;
35313
+ if (i >= items.length) return;
35314
+ results[i] = await fn(items[i]);
35315
+ }
35316
+ }
35317
+ );
35318
+ await Promise.all(workers);
35319
+ return results;
35320
+ }
35321
+ async function resolveExactVersion(projectPath, dep) {
35322
+ const candidateDirs = [projectPath];
35323
+ const sourceDir = join37(projectPath, dirname13(dep.sourcePath));
35324
+ if (sourceDir !== projectPath) candidateDirs.push(sourceDir);
35325
+ for (const base of candidateDirs) {
35326
+ try {
35327
+ const raw = await readFile26(
35328
+ join37(base, "node_modules", dep.name, "package.json"),
35329
+ "utf-8"
35330
+ );
35331
+ const pkg = JSON.parse(raw);
35332
+ if (typeof pkg.version === "string" && pkg.version.length > 0) {
35333
+ return pkg.version;
35334
+ }
35335
+ } catch {
35336
+ }
35337
+ }
35338
+ return stripRangePrefix(dep.declared);
35339
+ }
35340
+ async function readVendorDocsPath(projectPath) {
35341
+ try {
35342
+ const raw = await readFile26(
35343
+ join37(projectPath, ".codebyplan", "vendor.json"),
35344
+ "utf-8"
35345
+ );
35346
+ const parsed = JSON.parse(raw);
35347
+ if (typeof parsed.vendor_docs_path === "string" && parsed.vendor_docs_path.trim() !== "") {
35348
+ return parsed.vendor_docs_path.trim();
35349
+ }
35350
+ } catch {
35351
+ }
35352
+ return null;
35353
+ }
35354
+ async function resolveDocsDir(projectPath, flags) {
35355
+ const configured = flags["dir"] ?? await readVendorDocsPath(projectPath) ?? DEFAULT_DOCS_DIR;
35356
+ const absDir = isAbsolute(configured) ? configured : join37(projectPath, configured);
35357
+ const rel = relative7(projectPath, absDir);
35358
+ const relDir = rel === "" || rel.startsWith("..") ? null : rel.split(sep2).join("/");
35359
+ return { absDir, relDir };
35360
+ }
35361
+ async function readDocsLock(absDir) {
35362
+ const empty = { generated_at: "", libraries: {} };
35363
+ let raw;
35364
+ try {
35365
+ raw = await readFile26(join37(absDir, LOCK_FILE), "utf-8");
35366
+ } catch {
35367
+ return empty;
35368
+ }
35369
+ try {
35370
+ const parsed = JSON.parse(raw);
35371
+ if (parsed !== null && typeof parsed === "object" && parsed.libraries !== null && typeof parsed.libraries === "object") {
35372
+ return {
35373
+ generated_at: typeof parsed.generated_at === "string" ? parsed.generated_at : "",
35374
+ libraries: parsed.libraries
35375
+ };
35376
+ }
35377
+ } catch {
35378
+ }
35379
+ console.warn(` Warning: corrupt ${LOCK_FILE} \u2014 treating as empty.`);
35380
+ return empty;
35381
+ }
35382
+ function manifestMatchesLock(manifest, entry) {
35383
+ if (!entry) return false;
35384
+ if (entry.resolved_version !== manifest.resolved_version) return false;
35385
+ if (Object.keys(entry.files).length !== manifest.files.length) return false;
35386
+ return manifest.files.every((f) => entry.files[f.path] === f.content_hash);
35387
+ }
35388
+ function chunkTotal(manifest) {
35389
+ return manifest.files.reduce((sum, f) => sum + (f.chunk_count ?? 0), 0);
35390
+ }
35391
+ function isThin(manifest) {
35392
+ return chunkTotal(manifest) < THIN_MIN_CHUNKS || manifest.files.length < THIN_MIN_FILES;
35393
+ }
35394
+ async function fetchAllFiles(versionId) {
35395
+ const collected = [];
35396
+ let cursor = null;
35397
+ for (; ; ) {
35398
+ const res = await apiGet("/library-docs/export/files", {
35399
+ version_id: versionId,
35400
+ limit: String(FILES_PAGE_LIMIT),
35401
+ ...cursor !== null ? { cursor } : {}
35402
+ });
35403
+ const page = res.data;
35404
+ collected.push(...page.files);
35405
+ if (page.next_cursor === null || page.files.length === 0) break;
35406
+ cursor = page.next_cursor;
35407
+ }
35408
+ return collected;
35409
+ }
35410
+ function buildLibIndex(name, manifest) {
35411
+ const sorted = [...manifest.files].sort(
35412
+ (a, b) => a.path.localeCompare(b.path)
35413
+ );
35414
+ return [
35415
+ `# ${name}`,
35416
+ "",
35417
+ `- Version: ${manifest.resolved_version}`,
35418
+ `- Resolution: ${manifest.resolution}`,
35419
+ `- Files: ${sorted.length}`,
35420
+ `- Chunks: ${chunkTotal(manifest)}`,
35421
+ "",
35422
+ "## Files",
35423
+ "",
35424
+ ...sorted.map((f) => `- ${sanitizeDocPath(f.path) ?? f.path}`),
35425
+ ""
35426
+ ].join("\n");
35427
+ }
35428
+ function buildTopIndex(outcomes) {
35429
+ const covered = outcomes.filter(
35430
+ (o) => o.kind === "synced" || o.kind === "unchanged"
35431
+ ).sort((a, b) => a.dep.name.localeCompare(b.dep.name));
35432
+ const uncovered = outcomes.filter((o) => o.kind === "uncovered").sort((a, b) => a.dep.name.localeCompare(b.dep.name));
35433
+ const lines = [
35434
+ "# Dependency Docs Mirror",
35435
+ "",
35436
+ "Generated by `codebyplan docs sync`.",
35437
+ "",
35438
+ "## Libraries",
35439
+ ""
35440
+ ];
35441
+ if (covered.length === 0) {
35442
+ lines.push("- (none)");
35443
+ } else {
35444
+ for (const o of covered) {
35445
+ lines.push(
35446
+ `- ${o.dep.name}@${o.manifest.resolved_version} \u2014 ${o.manifest.files.length} files${isThin(o.manifest) ? " (thin)" : ""}`
35447
+ );
35448
+ }
35449
+ }
35450
+ if (uncovered.length > 0) {
35451
+ lines.push("", "## Uncovered", "");
35452
+ for (const o of uncovered) {
35453
+ lines.push(`- ${o.dep.name}@${o.exactVersion || "?"}`);
35454
+ }
35455
+ }
35456
+ lines.push("");
35457
+ return lines.join("\n");
35458
+ }
35459
+ async function ensureDocsGitignoreEntry(projectPath, relDir, dryRun) {
35460
+ const entry = `/${relDir}/`;
35461
+ const gitignorePath = join37(projectPath, ".gitignore");
35462
+ let existing = "";
35463
+ try {
35464
+ existing = await readFile26(gitignorePath, "utf-8");
35465
+ } catch {
35466
+ }
35467
+ const lines = existing.split(/\r?\n/).map((l) => l.trim());
35468
+ const variants = /* @__PURE__ */ new Set([entry, entry.slice(1), entry.slice(0, -1), relDir]);
35469
+ if (lines.some((l) => variants.has(l))) return "unchanged";
35470
+ if (!dryRun) {
35471
+ let content = existing;
35472
+ if (content.length > 0 && !content.endsWith("\n")) content += "\n";
35473
+ content += `${GITIGNORE_COMMENT}
35474
+ ${entry}
35475
+ `;
35476
+ await writeFile20(gitignorePath, content, "utf-8");
35477
+ }
35478
+ return "added";
35479
+ }
35480
+ async function syncOneLibrary(dep, ctx) {
35481
+ const exactVersion = await resolveExactVersion(ctx.projectPath, dep);
35482
+ let manifest;
35483
+ try {
35484
+ const res = await apiGet(
35485
+ "/library-docs/export/manifest",
35486
+ {
35487
+ slug: dep.name,
35488
+ ...exactVersion !== "" ? { version: exactVersion } : {}
35489
+ }
35490
+ );
35491
+ manifest = res.data;
35492
+ } catch (err) {
35493
+ if (err instanceof ApiError && err.status === 404) {
35494
+ console.log(
35495
+ ` uncovered ${dep.name}@${exactVersion || "?"} \u2014 no docs in the library mirror`
35496
+ );
35497
+ return { kind: "uncovered", dep, exactVersion };
35498
+ }
35499
+ const message = err instanceof Error ? err.message : String(err);
35500
+ console.warn(
35501
+ ` Warning: docs sync failed for ${dep.name}@${exactVersion || "?"}: ${message}`
35502
+ );
35503
+ return { kind: "failed", dep, exactVersion, message };
35504
+ }
35505
+ if (!manifest || !Array.isArray(manifest.files) || manifest.files.length === 0) {
35506
+ console.log(
35507
+ ` uncovered ${dep.name}@${exactVersion || "?"} \u2014 no docs in the library mirror`
35508
+ );
35509
+ return { kind: "uncovered", dep, exactVersion };
35510
+ }
35511
+ const unsafeCount = manifest.files.filter(
35512
+ (f) => sanitizeDocPath(f.path) === null
35513
+ ).length;
35514
+ if (unsafeCount > 0) {
35515
+ const sample = manifest.files.find((f) => sanitizeDocPath(f.path) === null);
35516
+ console.warn(
35517
+ ` Warning: ${dep.name}: dropping ${unsafeCount} unsafe doc path(s) from manifest (e.g. ${JSON.stringify(sample?.path)})`
35518
+ );
35519
+ manifest = {
35520
+ ...manifest,
35521
+ files: manifest.files.filter((f) => sanitizeDocPath(f.path) !== null)
35522
+ };
35523
+ if (manifest.files.length === 0) {
35524
+ console.log(
35525
+ ` uncovered ${dep.name}@${exactVersion || "?"} \u2014 no docs in the library mirror`
35526
+ );
35527
+ return { kind: "uncovered", dep, exactVersion };
35528
+ }
35529
+ }
35530
+ const lockEntry = ctx.lock.libraries[dep.name];
35531
+ const libPath = join37(ctx.absDir, libDirName(dep.name));
35532
+ const versionPath = join37(libPath, manifest.resolved_version);
35533
+ if (manifestMatchesLock(manifest, lockEntry)) {
35534
+ console.log(` unchanged ${dep.name}@${manifest.resolved_version}`);
35535
+ if (!ctx.dryRun && !existsSync13(join37(libPath, "INDEX.md"))) {
35536
+ await mkdir12(libPath, { recursive: true });
35537
+ await writeFile20(
35538
+ join37(libPath, "INDEX.md"),
35539
+ buildLibIndex(dep.name, manifest),
35540
+ "utf-8"
35541
+ );
35542
+ }
35543
+ return { kind: "unchanged", dep, exactVersion, manifest };
35544
+ }
35545
+ const fetched = await fetchAllFiles(manifest.version_id);
35546
+ const sameVersionLockFiles = lockEntry && lockEntry.resolved_version === manifest.resolved_version ? lockEntry.files : {};
35547
+ let written = 0;
35548
+ for (const file of fetched) {
35549
+ const rel = sanitizeDocPath(file.path);
35550
+ if (rel === null) {
35551
+ console.warn(
35552
+ ` Warning: skipping unsafe doc path from ${dep.name}: ${JSON.stringify(file.path)}`
35553
+ );
35554
+ continue;
35555
+ }
35556
+ const target = join37(versionPath, rel);
35557
+ if (sameVersionLockFiles[file.path] === file.content_hash && existsSync13(target)) {
35558
+ continue;
35559
+ }
35560
+ written++;
35561
+ if (!ctx.dryRun) {
35562
+ await mkdir12(dirname13(target), { recursive: true });
35563
+ await writeFile20(target, file.content, "utf-8");
35564
+ }
35565
+ }
35566
+ let removedFiles = 0;
35567
+ if (lockEntry && lockEntry.resolved_version === manifest.resolved_version) {
35568
+ const manifestPaths = new Set(manifest.files.map((f) => f.path));
35569
+ for (const lockedPath of Object.keys(lockEntry.files)) {
35570
+ if (manifestPaths.has(lockedPath)) continue;
35571
+ const rel = sanitizeDocPath(lockedPath);
35572
+ if (rel === null) continue;
35573
+ removedFiles++;
35574
+ if (!ctx.dryRun) await rm2(join37(versionPath, rel), { force: true });
35575
+ }
35576
+ }
35577
+ let removedVersionDirs = 0;
35578
+ let libEntries = [];
35579
+ try {
35580
+ libEntries = await readdir6(libPath, { withFileTypes: true });
35581
+ } catch {
35582
+ }
35583
+ for (const entry of libEntries) {
35584
+ if (!entry.isDirectory() || entry.name === manifest.resolved_version) {
35585
+ continue;
35586
+ }
35587
+ removedVersionDirs++;
35588
+ if (!ctx.dryRun) {
35589
+ await rm2(join37(libPath, entry.name), { recursive: true, force: true });
35590
+ }
35591
+ }
35592
+ if (!ctx.dryRun) {
35593
+ await mkdir12(libPath, { recursive: true });
35594
+ await writeFile20(
35595
+ join37(libPath, "INDEX.md"),
35596
+ buildLibIndex(dep.name, manifest),
35597
+ "utf-8"
35598
+ );
35599
+ }
35600
+ const removals = removedFiles + removedVersionDirs;
35601
+ console.log(
35602
+ ctx.dryRun ? ` would sync ${dep.name}@${manifest.resolved_version} \u2014 ${written} file(s), ${removals} removal(s)` : ` synced ${dep.name}@${manifest.resolved_version} \u2014 ${written} file(s) written${removals > 0 ? `, ${removals} removed` : ""}${isThin(manifest) ? " (thin)" : ""}`
35603
+ );
35604
+ return {
35605
+ kind: "synced",
35606
+ dep,
35607
+ exactVersion,
35608
+ manifest,
35609
+ written,
35610
+ removedFiles,
35611
+ removedVersionDirs
35612
+ };
35613
+ }
35614
+ async function runDocsSync() {
35615
+ const flags = parseFlags(4);
35616
+ const dryRun = hasFlag("dry-run", 4);
35617
+ await validateAuth();
35618
+ const config = await resolveConfig(flags);
35619
+ const { repoId, projectPath } = config;
35620
+ const { absDir, relDir } = await resolveDocsDir(projectPath, flags);
35621
+ console.log(`
35622
+ CodeByPlan Docs Sync`);
35623
+ console.log(` Repo: ${repoId}`);
35624
+ console.log(` Path: ${projectPath}`);
35625
+ console.log(` Dir: ${absDir}`);
35626
+ if (dryRun) console.log(` Mode: dry-run`);
35627
+ if (hasFlag("include-dev", 4)) {
35628
+ console.log(` Dev dependencies: included (already the default)`);
35629
+ }
35630
+ console.log();
35631
+ const started = Date.now();
35632
+ const { dependencies } = await scanAllDependencies(projectPath);
35633
+ const selected = selectDependencies(dependencies);
35634
+ if (selected.length === 0) {
35635
+ console.log(" No dependencies eligible for docs sync.\n");
35636
+ return;
35637
+ }
35638
+ console.log(` ${selected.length} dependencies after skip rules + dedup
35639
+ `);
35640
+ const lock = await readDocsLock(absDir);
35641
+ const ctx = { projectPath, absDir, lock, dryRun };
35642
+ const outcomes = await mapWithConcurrency(
35643
+ selected,
35644
+ SYNC_CONCURRENCY,
35645
+ (dep) => syncOneLibrary(dep, ctx)
35646
+ );
35647
+ if (!dryRun) {
35648
+ await mkdir12(absDir, { recursive: true });
35649
+ await writeFile20(join37(absDir, "INDEX.md"), buildTopIndex(outcomes), "utf-8");
35650
+ const libraries = {};
35651
+ for (const o of outcomes) {
35652
+ if (o.kind === "synced" || o.kind === "unchanged") {
35653
+ libraries[o.dep.name] = {
35654
+ version: o.exactVersion,
35655
+ resolved_version: o.manifest.resolved_version,
35656
+ resolution: o.manifest.resolution,
35657
+ files: Object.fromEntries(
35658
+ o.manifest.files.map((f) => [f.path, f.content_hash])
35659
+ )
35660
+ };
35661
+ } else if (o.kind === "failed") {
35662
+ const previous = lock.libraries[o.dep.name];
35663
+ if (previous) libraries[o.dep.name] = previous;
35664
+ }
35665
+ }
35666
+ const sortedLibraries = {};
35667
+ for (const name of Object.keys(libraries).sort()) {
35668
+ sortedLibraries[name] = libraries[name];
35669
+ }
35670
+ const newLock = {
35671
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
35672
+ libraries: sortedLibraries
35673
+ };
35674
+ await writeFile20(
35675
+ join37(absDir, LOCK_FILE),
35676
+ JSON.stringify(newLock, null, 2) + "\n",
35677
+ "utf-8"
35678
+ );
35679
+ }
35680
+ if (relDir !== null) {
35681
+ const action = await ensureDocsGitignoreEntry(projectPath, relDir, dryRun);
35682
+ if (action === "added") {
35683
+ console.log(
35684
+ dryRun ? `
35685
+ would add /${relDir}/ to .gitignore` : `
35686
+ Added /${relDir}/ to .gitignore`
35687
+ );
35688
+ }
35689
+ }
35690
+ const synced = outcomes.filter((o) => o.kind === "synced").length;
35691
+ const unchanged = outcomes.filter((o) => o.kind === "unchanged").length;
35692
+ const uncovered = outcomes.filter((o) => o.kind === "uncovered").length;
35693
+ const failed = outcomes.filter((o) => o.kind === "failed").length;
35694
+ const thin = outcomes.filter(
35695
+ (o) => (o.kind === "synced" || o.kind === "unchanged") && isThin(o.manifest)
35696
+ ).length;
35697
+ const durationS = ((Date.now() - started) / 1e3).toFixed(1);
35698
+ if (failed > 0) {
35699
+ console.warn(` Warning: ${failed} libraries failed to sync (see above).`);
35700
+ }
35701
+ console.log(
35702
+ `
35703
+ Docs sync ${dryRun ? "plan " : ""}complete: ${synced} synced, ${unchanged} unchanged, ${uncovered} uncovered, ${thin} thin (${durationS}s).
35704
+ `
35705
+ );
35706
+ }
35707
+ async function countFilesRecursively(dirPath) {
35708
+ let entries;
35709
+ try {
35710
+ entries = await readdir6(dirPath, { withFileTypes: true });
35711
+ } catch {
35712
+ return 0;
35713
+ }
35714
+ let count = 0;
35715
+ for (const entry of entries) {
35716
+ if (entry.isDirectory()) {
35717
+ count += await countFilesRecursively(join37(dirPath, entry.name));
35718
+ } else if (entry.isFile()) {
35719
+ count++;
35720
+ }
35721
+ }
35722
+ return count;
35723
+ }
35724
+ async function runDocsStatus() {
35725
+ const flags = parseFlags(4);
35726
+ const projectPath = flags["path"] ?? process.cwd();
35727
+ const { absDir } = await resolveDocsDir(projectPath, flags);
35728
+ console.log(`
35729
+ CodeByPlan Docs Status`);
35730
+ console.log(` Dir: ${absDir}
35731
+ `);
35732
+ let raw;
35733
+ try {
35734
+ raw = await readFile26(join37(absDir, LOCK_FILE), "utf-8");
35735
+ } catch {
35736
+ console.log(
35737
+ ` No ${LOCK_FILE} found \u2014 run \`codebyplan docs sync\` first.
35738
+ `
35739
+ );
35740
+ return;
35741
+ }
35742
+ let lock;
35743
+ try {
35744
+ lock = JSON.parse(raw);
35745
+ if (lock === null || typeof lock.libraries !== "object") {
35746
+ throw new Error("malformed lock");
35747
+ }
35748
+ } catch {
35749
+ console.warn(
35750
+ ` Warning: corrupt ${LOCK_FILE} \u2014 run \`codebyplan docs sync\` to regenerate.
35751
+ `
35752
+ );
35753
+ return;
35754
+ }
35755
+ const names = Object.keys(lock.libraries).sort();
35756
+ if (names.length === 0) {
35757
+ console.log(" Lock contains no libraries.\n");
35758
+ return;
35759
+ }
35760
+ let outOfSync = 0;
35761
+ for (const name of names) {
35762
+ const entry = lock.libraries[name];
35763
+ const versionPath = join37(absDir, libDirName(name), entry.resolved_version);
35764
+ const expected = Object.keys(entry.files).length;
35765
+ if (!existsSync13(versionPath)) {
35766
+ outOfSync++;
35767
+ console.log(
35768
+ ` ${name}@${entry.resolved_version} \u2014 MISSING dir (${expected} files in lock)`
35769
+ );
35770
+ continue;
35771
+ }
35772
+ const onDisk = await countFilesRecursively(versionPath);
35773
+ if (onDisk === expected) {
35774
+ console.log(` ${name}@${entry.resolved_version} \u2014 ok (${onDisk} files)`);
35775
+ } else {
35776
+ outOfSync++;
35777
+ console.log(
35778
+ ` ${name}@${entry.resolved_version} \u2014 MISMATCH (${onDisk} files on disk / ${expected} in lock)`
35779
+ );
35780
+ }
35781
+ }
35782
+ console.log(
35783
+ outOfSync === 0 ? `
35784
+ ${names.length} libraries in sync.
35785
+ ` : `
35786
+ ${outOfSync} of ${names.length} libraries out of sync \u2014 run \`codebyplan docs sync\`.
35787
+ `
35788
+ );
35789
+ }
35790
+ function printUsage() {
35791
+ console.log(`
35792
+ codebyplan docs
35793
+
35794
+ Local dependency docs mirror \u2014 sync covered library docs from CodeByPlan.
35795
+
35796
+ Subcommands:
35797
+ sync Fetch doc files for installed dependencies into the local mirror
35798
+ status Offline report of lock vs on-disk mirror state (no network)
35799
+
35800
+ Flags (sync):
35801
+ --path <dir> Project root directory (default: cwd)
35802
+ --dir <dir> Mirror directory (default: .codebyplan/vendor.json
35803
+ vendor_docs_path, else ${DEFAULT_DOCS_DIR})
35804
+ --include-dev Explicitly include dev dependencies (already the default)
35805
+ --dry-run Print the full sync plan without writing any files
35806
+
35807
+ Flags (status):
35808
+ --path <dir> Project root directory (default: cwd)
35809
+ --dir <dir> Mirror directory (same resolution as sync)
35810
+ `);
35811
+ }
35812
+ async function runDocs() {
35813
+ const subcommand = process.argv[3];
35814
+ if (subcommand === "sync") {
35815
+ await runDocsSync();
35816
+ return;
35817
+ }
35818
+ if (subcommand === "status") {
35819
+ await runDocsStatus();
35820
+ return;
35821
+ }
35822
+ if (subcommand === void 0 || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
35823
+ printUsage();
35824
+ return;
35825
+ }
35826
+ console.error(
35827
+ ` docs: unknown subcommand '${subcommand}'. Run 'codebyplan docs help' for usage.`
35828
+ );
35829
+ process.exitCode = 1;
35830
+ }
35831
+ var DEFAULT_DOCS_DIR, LOCK_FILE, SYNC_CONCURRENCY, FILES_PAGE_LIMIT, THIN_MIN_CHUNKS, THIN_MIN_FILES, GITIGNORE_COMMENT, SKIP_VERSION_RE;
35832
+ var init_docs = __esm({
35833
+ "src/cli/docs.ts"() {
35834
+ "use strict";
35835
+ init_flags();
35836
+ init_api();
35837
+ init_tech_detect();
35838
+ DEFAULT_DOCS_DIR = "docs/dependencies";
35839
+ LOCK_FILE = "docs.lock.json";
35840
+ SYNC_CONCURRENCY = 5;
35841
+ FILES_PAGE_LIMIT = 200;
35842
+ THIN_MIN_CHUNKS = 25;
35843
+ THIN_MIN_FILES = 5;
35844
+ GITIGNORE_COMMENT = "# DocsByPlan local mirror";
35845
+ SKIP_VERSION_RE = /^(workspace:|file:|link:|git\+|https?:)/;
35846
+ }
35847
+ });
35848
+
34908
35849
  // src/lib/check-baseline.ts
34909
35850
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
34910
- import { join as join37 } from "node:path";
35851
+ import { join as join38 } from "node:path";
34911
35852
  function emptyBaseline() {
34912
35853
  return {
34913
35854
  lint: { known_failing: [] },
@@ -34917,7 +35858,7 @@ function emptyBaseline() {
34917
35858
  };
34918
35859
  }
34919
35860
  function loadBaseline(projectRoot) {
34920
- const filePath = join37(projectRoot, BASELINE_FILENAME);
35861
+ const filePath = join38(projectRoot, BASELINE_FILENAME);
34921
35862
  try {
34922
35863
  const raw = readFileSync12(filePath, "utf-8");
34923
35864
  const parsed = JSON.parse(raw);
@@ -34941,7 +35882,7 @@ function loadBaseline(projectRoot) {
34941
35882
  }
34942
35883
  }
34943
35884
  function saveBaseline(projectRoot, baseline) {
34944
- const filePath = join37(projectRoot, BASELINE_FILENAME);
35885
+ const filePath = join38(projectRoot, BASELINE_FILENAME);
34945
35886
  writeFileSync7(filePath, JSON.stringify(baseline, null, 2) + "\n", "utf-8");
34946
35887
  }
34947
35888
  function diffBaseline(check, currentFailingPackages, baseline) {
@@ -34990,8 +35931,8 @@ var init_check_baseline = __esm({
34990
35931
  });
34991
35932
 
34992
35933
  // src/lib/check.ts
34993
- import { readFileSync as readFileSync13, existsSync as existsSync13 } from "node:fs";
34994
- import { join as join38 } from "node:path";
35934
+ import { readFileSync as readFileSync13, existsSync as existsSync14 } from "node:fs";
35935
+ import { join as join39 } from "node:path";
34995
35936
  import { spawnSync as spawnSync10 } from "node:child_process";
34996
35937
  function hasSentinelValue(arrays) {
34997
35938
  const SENTINELS = /* @__PURE__ */ new Set([
@@ -35001,6 +35942,11 @@ function hasSentinelValue(arrays) {
35001
35942
  ]);
35002
35943
  return arrays.some((arr) => arr.some((v) => SENTINELS.has(v)));
35003
35944
  }
35945
+ function resolveNewFailures(check, failingPackages, baseline, updateBaseline, noBaseline) {
35946
+ if (updateBaseline) return [];
35947
+ if (noBaseline) return failingPackages;
35948
+ return diffBaseline(check, failingPackages, baseline);
35949
+ }
35004
35950
  function defaultSpawnFn(command, opts) {
35005
35951
  const result = spawnSync10(command, {
35006
35952
  shell: true,
@@ -35054,9 +36000,9 @@ function parseFailingPackagesFromSummary(summaryPath) {
35054
36000
  return Array.from(failing).sort();
35055
36001
  }
35056
36002
  function resolveTurboBin(projectRoot) {
35057
- const localBin = join38(projectRoot, "node_modules", ".bin", "turbo");
35058
- if (existsSync13(localBin)) return localBin;
35059
- const workspaceRootBin = join38(
36003
+ const localBin = join39(projectRoot, "node_modules", ".bin", "turbo");
36004
+ if (existsSync14(localBin)) return localBin;
36005
+ const workspaceRootBin = join39(
35060
36006
  projectRoot,
35061
36007
  "..",
35062
36008
  "..",
@@ -35064,7 +36010,7 @@ function resolveTurboBin(projectRoot) {
35064
36010
  ".bin",
35065
36011
  "turbo"
35066
36012
  );
35067
- if (existsSync13(workspaceRootBin)) return workspaceRootBin;
36013
+ if (existsSync14(workspaceRootBin)) return workspaceRootBin;
35068
36014
  return TURBO_NOT_FOUND_SENTINEL;
35069
36015
  }
35070
36016
  function runTurboWithSummary(task, projectRoot, spawnFn) {
@@ -35101,13 +36047,20 @@ function runCheck(opts) {
35101
36047
  projectRoot = process.cwd(),
35102
36048
  changedFiles,
35103
36049
  spawnFn = defaultSpawnFn,
35104
- updateBaseline = false,
36050
+ updateBaseline: updateBaselineOpt = false,
36051
+ noBaseline = false,
35105
36052
  loadBaselineFn = loadBaseline,
35106
36053
  saveBaselineFn = saveBaseline
35107
36054
  } = opts;
35108
36055
  if (changedFiles !== void 0) {
35109
36056
  process.stderr.write("check: --files is ignored in whole-repo mode\n");
35110
36057
  }
36058
+ if (noBaseline && updateBaselineOpt) {
36059
+ process.stderr.write(
36060
+ "check: --no-baseline and --update-baseline are mutually exclusive \u2014 ignoring --update-baseline (strict mode never writes the baseline)\n"
36061
+ );
36062
+ }
36063
+ const updateBaseline = noBaseline ? false : updateBaselineOpt;
35111
36064
  const baseline = loadBaselineFn(projectRoot);
35112
36065
  const results = [];
35113
36066
  {
@@ -35146,7 +36099,13 @@ function runCheck(opts) {
35146
36099
  command: lintCommand
35147
36100
  } = runTurboWithSummary("lint", projectRoot, spawnFn);
35148
36101
  currentFailing.lint = failingPackages;
35149
- const newFailures = updateBaseline ? [] : diffBaseline("lint", failingPackages, baseline);
36102
+ const newFailures = resolveNewFailures(
36103
+ "lint",
36104
+ failingPackages,
36105
+ baseline,
36106
+ updateBaseline,
36107
+ noBaseline
36108
+ );
35150
36109
  results.push({
35151
36110
  check: "lint",
35152
36111
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35165,7 +36124,13 @@ function runCheck(opts) {
35165
36124
  command: typecheckCommand
35166
36125
  } = runTurboWithSummary("typecheck", projectRoot, spawnFn);
35167
36126
  currentFailing.typecheck = failingPackages;
35168
- const newFailures = updateBaseline ? [] : diffBaseline("typecheck", failingPackages, baseline);
36127
+ const newFailures = resolveNewFailures(
36128
+ "typecheck",
36129
+ failingPackages,
36130
+ baseline,
36131
+ updateBaseline,
36132
+ noBaseline
36133
+ );
35169
36134
  results.push({
35170
36135
  check: "typecheck",
35171
36136
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35184,7 +36149,13 @@ function runCheck(opts) {
35184
36149
  command: testsCommand
35185
36150
  } = runTurboWithSummary("test", projectRoot, spawnFn);
35186
36151
  currentFailing.tests = failingPackages;
35187
- const newFailures = updateBaseline ? [] : diffBaseline("tests", failingPackages, baseline);
36152
+ const newFailures = resolveNewFailures(
36153
+ "tests",
36154
+ failingPackages,
36155
+ baseline,
36156
+ updateBaseline,
36157
+ noBaseline
36158
+ );
35188
36159
  results.push({
35189
36160
  check: "tests",
35190
36161
  status: newFailures.length > 0 ? "fail" : "pass",
@@ -35210,7 +36181,14 @@ function runCheck(opts) {
35210
36181
  };
35211
36182
  }
35212
36183
  const currentGhsaIds = parseAuditJson(result.stdout);
35213
- const newAdvisories = updateBaseline ? [] : diffAudit(currentGhsaIds, baseline.audit.ghsa_ids);
36184
+ let newAdvisories;
36185
+ if (updateBaseline) {
36186
+ newAdvisories = [];
36187
+ } else if (noBaseline) {
36188
+ newAdvisories = currentGhsaIds;
36189
+ } else {
36190
+ newAdvisories = diffAudit(currentGhsaIds, baseline.audit.ghsa_ids);
36191
+ }
35214
36192
  results.push({
35215
36193
  check: "audit",
35216
36194
  status: newAdvisories.length > 0 ? "fail" : "pass",
@@ -35346,7 +36324,7 @@ function runCheck(opts) {
35346
36324
  }
35347
36325
  const hard_fail_checks = results.filter((r) => r.status === "fail").map((r) => r.check);
35348
36326
  const any_failed = hard_fail_checks.length > 0;
35349
- return { results, any_failed, hard_fail_checks };
36327
+ return { results, any_failed, hard_fail_checks, no_baseline: noBaseline };
35350
36328
  }
35351
36329
  var SUMMARY_UNREADABLE, SPAWN_KILLED, TURBO_NOT_FOUND_SENTINEL;
35352
36330
  var init_check = __esm({
@@ -35369,6 +36347,7 @@ function parseCheckArgs(args) {
35369
36347
  let json = false;
35370
36348
  let files;
35371
36349
  let updateBaseline = false;
36350
+ let noBaseline = false;
35372
36351
  for (let i = 0; i < args.length; i++) {
35373
36352
  const arg = args[i];
35374
36353
  if (arg === "--scope") {
@@ -35388,6 +36367,8 @@ function parseCheckArgs(args) {
35388
36367
  json = true;
35389
36368
  } else if (arg === "--update-baseline") {
35390
36369
  updateBaseline = true;
36370
+ } else if (arg === "--no-baseline") {
36371
+ noBaseline = true;
35391
36372
  } else if (arg === "--files") {
35392
36373
  const val = args[i + 1];
35393
36374
  if (val !== void 0 && !val.startsWith("--")) {
@@ -35401,15 +36382,16 @@ function parseCheckArgs(args) {
35401
36382
  }
35402
36383
  }
35403
36384
  }
35404
- return { scope, json, files, updateBaseline };
36385
+ return { scope, json, files, updateBaseline, noBaseline };
35405
36386
  }
35406
36387
  function emitTable(result) {
36388
+ const strict = result.no_baseline === true;
35407
36389
  const headers = ["check", "status", "exit_code", "new_failures", "command"];
35408
36390
  const rows = result.results.map((r) => {
35409
36391
  let newFailuresCell = "";
35410
36392
  if (r.new_failures !== void 0) {
35411
36393
  if (r.new_failures.length === 0 && r.executed) {
35412
- newFailuresCell = r.exit_code !== null && r.exit_code !== 0 ? "(baselined)" : "";
36394
+ newFailuresCell = !strict && r.exit_code !== null && r.exit_code !== 0 ? "(baselined)" : "";
35413
36395
  } else {
35414
36396
  newFailuresCell = r.new_failures.join(", ");
35415
36397
  }
@@ -35436,6 +36418,11 @@ function emitTable(result) {
35436
36418
  );
35437
36419
  }
35438
36420
  lines.push("");
36421
+ if (strict) {
36422
+ lines.push(
36423
+ "MODE: strict (--no-baseline) \u2014 baseline ignored; every failure counts"
36424
+ );
36425
+ }
35439
36426
  if (result.any_failed) {
35440
36427
  lines.push(
35441
36428
  `FAILED: ${result.hard_fail_checks.join(", ")} (${result.hard_fail_checks.length} check(s) failed)`
@@ -35468,12 +36455,13 @@ function runCheckCommand(args) {
35468
36455
  if (parsed === null) {
35469
36456
  return;
35470
36457
  }
35471
- const { scope, json, files, updateBaseline } = parsed;
36458
+ const { scope, json, files, updateBaseline, noBaseline } = parsed;
35472
36459
  const result = runCheck({
35473
36460
  scope,
35474
36461
  changedFiles: files,
35475
36462
  // NO-OP in whole-repo mode; notice emitted by runCheck
35476
- updateBaseline
36463
+ updateBaseline,
36464
+ noBaseline
35477
36465
  });
35478
36466
  if (json) {
35479
36467
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
@@ -37116,11 +38104,11 @@ var generate_exports = {};
37116
38104
  __export(generate_exports, {
37117
38105
  runGenerate: () => runGenerate
37118
38106
  });
37119
- import { readFile as readFile26, mkdir as mkdir12, writeFile as writeFile20 } from "node:fs/promises";
37120
- import { join as join45, resolve as resolve11 } from "node:path";
38107
+ import { readFile as readFile27, mkdir as mkdir13, writeFile as writeFile21 } from "node:fs/promises";
38108
+ import { join as join46, resolve as resolve11 } from "node:path";
37121
38109
  async function readJsonFile4(filePath) {
37122
38110
  try {
37123
- const raw = await readFile26(filePath, "utf-8");
38111
+ const raw = await readFile27(filePath, "utf-8");
37124
38112
  return JSON.parse(raw);
37125
38113
  } catch {
37126
38114
  return null;
@@ -37128,7 +38116,7 @@ async function readJsonFile4(filePath) {
37128
38116
  }
37129
38117
  async function readPkgName(absPath) {
37130
38118
  try {
37131
- const raw = await readFile26(join45(absPath, "package.json"), "utf-8");
38119
+ const raw = await readFile27(join46(absPath, "package.json"), "utf-8");
37132
38120
  const pkg = JSON.parse(raw);
37133
38121
  return typeof pkg.name === "string" ? pkg.name : null;
37134
38122
  } catch {
@@ -37142,7 +38130,7 @@ async function runGenerate(opts) {
37142
38130
  const rootDir = resolve11(projectDir);
37143
38131
  let packageManager;
37144
38132
  try {
37145
- const raw = await readFile26(join45(rootDir, "package.json"), "utf-8");
38133
+ const raw = await readFile27(join46(rootDir, "package.json"), "utf-8");
37146
38134
  const pkg = JSON.parse(raw);
37147
38135
  if (typeof pkg.packageManager === "string") {
37148
38136
  packageManager = pkg.packageManager;
@@ -37150,7 +38138,7 @@ async function runGenerate(opts) {
37150
38138
  } catch {
37151
38139
  }
37152
38140
  const serverJson = await readJsonFile4(
37153
- join45(rootDir, ".codebyplan", "server.json")
38141
+ join46(rootDir, ".codebyplan", "server.json")
37154
38142
  );
37155
38143
  const ports = [];
37156
38144
  for (const alloc of serverJson?.port_allocations ?? []) {
@@ -37163,7 +38151,7 @@ async function runGenerate(opts) {
37163
38151
  }
37164
38152
  }
37165
38153
  const gitJson = await readJsonFile4(
37166
- join45(rootDir, ".codebyplan", "git.json")
38154
+ join46(rootDir, ".codebyplan", "git.json")
37167
38155
  );
37168
38156
  const branchModel = gitJson?.branch_config?.production ? {
37169
38157
  production: gitJson.branch_config.production,
@@ -37172,7 +38160,7 @@ async function runGenerate(opts) {
37172
38160
  )
37173
38161
  } : void 0;
37174
38162
  const shipmentJson = await readJsonFile4(
37175
- join45(rootDir, ".codebyplan", "shipment.json")
38163
+ join46(rootDir, ".codebyplan", "shipment.json")
37176
38164
  );
37177
38165
  const shipmentSurfaces = [];
37178
38166
  const rawSurfaces = shipmentJson?.shipment?.surfaces ?? shipmentJson?.surfaces ?? {};
@@ -37243,10 +38231,10 @@ async function runGenerate(opts) {
37243
38231
  const structureMdContent = generateStructureMd(config);
37244
38232
  const agentsContent = generateAgentsMd(structureMdContent);
37245
38233
  if (check) {
37246
- const agentsMdPath2 = join45(rootDir, "AGENTS.md");
38234
+ const agentsMdPath2 = join46(rootDir, "AGENTS.md");
37247
38235
  let existingAgents = null;
37248
38236
  try {
37249
- existingAgents = await readFile26(agentsMdPath2, "utf-8");
38237
+ existingAgents = await readFile27(agentsMdPath2, "utf-8");
37250
38238
  } catch {
37251
38239
  existingAgents = null;
37252
38240
  }
@@ -37282,16 +38270,16 @@ async function runGenerate(opts) {
37282
38270
  process.stdout.write(agentsContent);
37283
38271
  return;
37284
38272
  }
37285
- const outputDir = join45(rootDir, ".claude", "generated");
37286
- await mkdir12(outputDir, { recursive: true });
37287
- const outputPath = join45(outputDir, "structure.md");
37288
- await writeFile20(outputPath, structureMdContent, "utf-8");
38273
+ const outputDir = join46(rootDir, ".claude", "generated");
38274
+ await mkdir13(outputDir, { recursive: true });
38275
+ const outputPath = join46(outputDir, "structure.md");
38276
+ await writeFile21(outputPath, structureMdContent, "utf-8");
37289
38277
  process.stdout.write(`Wrote: .claude/generated/structure.md
37290
38278
  `);
37291
- const agentsMdPath = join45(rootDir, "AGENTS.md");
38279
+ const agentsMdPath = join46(rootDir, "AGENTS.md");
37292
38280
  let existingAgentsContent = null;
37293
38281
  try {
37294
- existingAgentsContent = await readFile26(agentsMdPath, "utf-8");
38282
+ existingAgentsContent = await readFile27(agentsMdPath, "utf-8");
37295
38283
  } catch {
37296
38284
  existingAgentsContent = null;
37297
38285
  }
@@ -37299,7 +38287,7 @@ async function runGenerate(opts) {
37299
38287
  process.stdout.write(`Up to date: AGENTS.md
37300
38288
  `);
37301
38289
  } else {
37302
- await writeFile20(agentsMdPath, agentsContent, "utf-8");
38290
+ await writeFile21(agentsMdPath, agentsContent, "utf-8");
37303
38291
  process.stdout.write(`Wrote: AGENTS.md
37304
38292
  `);
37305
38293
  }
@@ -37319,11 +38307,11 @@ __export(readme_exports, {
37319
38307
  runReadme: () => runReadme,
37320
38308
  runReadmeCommand: () => runReadmeCommand
37321
38309
  });
37322
- import { readFile as readFile27, writeFile as writeFile21 } from "node:fs/promises";
37323
- import { join as join46, resolve as resolve12, relative as relative8 } from "node:path";
38310
+ import { readFile as readFile28, writeFile as writeFile22 } from "node:fs/promises";
38311
+ import { join as join47, resolve as resolve12, relative as relative9 } from "node:path";
37324
38312
  async function readJsonFile5(filePath) {
37325
38313
  try {
37326
- const raw = await readFile27(filePath, "utf-8");
38314
+ const raw = await readFile28(filePath, "utf-8");
37327
38315
  return JSON.parse(raw);
37328
38316
  } catch {
37329
38317
  return null;
@@ -37392,7 +38380,7 @@ async function discoverUnits(rootDir, rootPkgJson) {
37392
38380
  const discovered = await discoverMonorepoApps(rootDir);
37393
38381
  for (const app of discovered) {
37394
38382
  const pkgJson = await readJsonFile5(
37395
- join46(app.absPath, "package.json")
38383
+ join47(app.absPath, "package.json")
37396
38384
  );
37397
38385
  pkgJsonByPath.set(app.absPath, pkgJson);
37398
38386
  allPackages.push({
@@ -37418,7 +38406,7 @@ async function runReadme(opts) {
37418
38406
  const init = opts.init ?? opts["init"] ?? false;
37419
38407
  const rootDir = resolve12(projectDir);
37420
38408
  const rootPkgJson = await readJsonFile5(
37421
- join46(rootDir, "package.json")
38409
+ join47(rootDir, "package.json")
37422
38410
  );
37423
38411
  const { units, allPackages, pkgJsonByPath } = await discoverUnits(
37424
38412
  rootDir,
@@ -37427,11 +38415,11 @@ async function runReadme(opts) {
37427
38415
  const driftUnits = [];
37428
38416
  const missingUnits = [];
37429
38417
  for (const unit of units) {
37430
- const readmePath = join46(unit.absPath, "README.md");
37431
- const relPath = unit.isRoot ? "README.md" : join46(relative8(rootDir, unit.absPath), "README.md");
38418
+ const readmePath = join47(unit.absPath, "README.md");
38419
+ const relPath = unit.isRoot ? "README.md" : join47(relative9(rootDir, unit.absPath), "README.md");
37432
38420
  let existingContent = null;
37433
38421
  try {
37434
- existingContent = await readFile27(readmePath, "utf-8");
38422
+ existingContent = await readFile28(readmePath, "utf-8");
37435
38423
  } catch {
37436
38424
  existingContent = null;
37437
38425
  }
@@ -37466,7 +38454,7 @@ ${newContent}
37466
38454
  `
37467
38455
  );
37468
38456
  } else {
37469
- await writeFile21(readmePath, newContent, "utf-8");
38457
+ await writeFile22(readmePath, newContent, "utf-8");
37470
38458
  process.stdout.write(`Wrote (scaffold): ${relPath}
37471
38459
  `);
37472
38460
  }
@@ -37504,7 +38492,7 @@ ${newContent}
37504
38492
  `
37505
38493
  );
37506
38494
  } else {
37507
- await writeFile21(readmePath, newContent, "utf-8");
38495
+ await writeFile22(readmePath, newContent, "utf-8");
37508
38496
  process.stdout.write(`Wrote (refresh): ${relPath}
37509
38497
  `);
37510
38498
  }
@@ -37523,7 +38511,7 @@ ${newContent}
37523
38511
  `
37524
38512
  );
37525
38513
  } else {
37526
- await writeFile21(readmePath, newContent, "utf-8");
38514
+ await writeFile22(readmePath, newContent, "utf-8");
37527
38515
  process.stdout.write(`Wrote (init): ${relPath}
37528
38516
  `);
37529
38517
  }
@@ -37611,15 +38599,15 @@ __export(migrate_memory_exports, {
37611
38599
  runMigrateMemory: () => runMigrateMemory
37612
38600
  });
37613
38601
  import {
37614
- readFile as readFile28,
37615
- writeFile as writeFile22,
37616
- mkdir as mkdir13,
38602
+ readFile as readFile29,
38603
+ writeFile as writeFile23,
38604
+ mkdir as mkdir14,
37617
38605
  unlink as unlink6,
37618
38606
  rmdir,
37619
- readdir as readdir6
38607
+ readdir as readdir7
37620
38608
  } from "node:fs/promises";
37621
- import { existsSync as existsSync20 } from "node:fs";
37622
- import { join as join47, resolve as resolve13, dirname as dirname14, sep as sep3 } from "node:path";
38609
+ import { existsSync as existsSync21 } from "node:fs";
38610
+ import { join as join48, resolve as resolve13, dirname as dirname15, sep as sep4 } from "node:path";
37623
38611
  import { homedir as homedir8 } from "node:os";
37624
38612
  function encodeProjectPath(absPath) {
37625
38613
  return resolve13(absPath).replace(/[/\\]/g, "-");
@@ -37630,7 +38618,7 @@ function resolveAutoMemoryDir(opts) {
37630
38618
  }
37631
38619
  const projectDir = opts.projectDir ?? process.cwd();
37632
38620
  const encoded = encodeProjectPath(projectDir);
37633
- return join47(homedir8(), ".claude", "projects", encoded, "memory");
38621
+ return join48(homedir8(), ".claude", "projects", encoded, "memory");
37634
38622
  }
37635
38623
  function parseFrontmatter(content) {
37636
38624
  content = content.replace(/\r\n/g, "\n");
@@ -37689,17 +38677,17 @@ function parseFrontmatter(content) {
37689
38677
  async function inventoryFiles(dir) {
37690
38678
  let filenames;
37691
38679
  try {
37692
- const entries = await readdir6(dir);
38680
+ const entries = await readdir7(dir);
37693
38681
  filenames = entries.filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
37694
38682
  } catch {
37695
38683
  return [];
37696
38684
  }
37697
38685
  const results = [];
37698
38686
  for (const filename of filenames) {
37699
- const sourcePath = join47(dir, filename);
38687
+ const sourcePath = join48(dir, filename);
37700
38688
  let raw;
37701
38689
  try {
37702
- raw = await readFile28(sourcePath, "utf-8");
38690
+ raw = await readFile29(sourcePath, "utf-8");
37703
38691
  } catch (err) {
37704
38692
  const msg = err instanceof Error ? err.message : String(err);
37705
38693
  results.push({
@@ -37785,9 +38773,9 @@ async function applyPlan(plan, opts) {
37785
38773
  if (entry.suggested_action !== "keep") continue;
37786
38774
  if (!entry.suggested_target?.startsWith("nested:")) continue;
37787
38775
  const relPath = entry.suggested_target.slice("nested:".length);
37788
- const targetDir = resolve13(join47(projectDir, relPath));
37789
- const targetFile = join47(targetDir, "CLAUDE.md");
37790
- if (!targetDir.startsWith(resolve13(projectDir) + sep3)) {
38776
+ const targetDir = resolve13(join48(projectDir, relPath));
38777
+ const targetFile = join48(targetDir, "CLAUDE.md");
38778
+ if (!targetDir.startsWith(resolve13(projectDir) + sep4)) {
37791
38779
  process.stderr.write(
37792
38780
  `migrate-memory: skipping unsafe suggested_target "${entry.suggested_target}" \u2014 resolves outside projectDir
37793
38781
  `
@@ -37806,7 +38794,7 @@ ${anchor}
37806
38794
  process.stdout.write(`[dry-run] Would create/append: ${targetFile}
37807
38795
  `);
37808
38796
  if (resolve13(entry.source_path).startsWith(
37809
- resolve13(plan.auto_memory_dir) + sep3
38797
+ resolve13(plan.auto_memory_dir) + sep4
37810
38798
  )) {
37811
38799
  process.stdout.write(
37812
38800
  `[dry-run] Would delete migrated keep source: ${entry.source_path}
@@ -37815,16 +38803,16 @@ ${anchor}
37815
38803
  }
37816
38804
  continue;
37817
38805
  }
37818
- await mkdir13(targetDir, { recursive: true });
38806
+ await mkdir14(targetDir, { recursive: true });
37819
38807
  let existing = "";
37820
38808
  try {
37821
- existing = await readFile28(targetFile, "utf-8");
38809
+ existing = await readFile29(targetFile, "utf-8");
37822
38810
  } catch {
37823
38811
  }
37824
38812
  if (!existing.includes(anchor)) {
37825
- await writeFile22(targetFile, existing + appendContent, "utf-8");
38813
+ await writeFile23(targetFile, existing + appendContent, "utf-8");
37826
38814
  }
37827
- if (resolve13(entry.source_path).startsWith(resolve13(plan.auto_memory_dir) + sep3)) {
38815
+ if (resolve13(entry.source_path).startsWith(resolve13(plan.auto_memory_dir) + sep4)) {
37828
38816
  try {
37829
38817
  await unlink6(entry.source_path);
37830
38818
  } catch {
@@ -37836,7 +38824,7 @@ ${anchor}
37836
38824
  );
37837
38825
  }
37838
38826
  }
37839
- const rootClaudeMd = join47(projectDir, ".claude", "CLAUDE.md");
38827
+ const rootClaudeMd = join48(projectDir, ".claude", "CLAUDE.md");
37840
38828
  if (dryRun) {
37841
38829
  process.stdout.write(
37842
38830
  `[dry-run] Would ensure ${rootClaudeMd} contains: ${IMPORT_LINE}
@@ -37845,12 +38833,12 @@ ${anchor}
37845
38833
  } else {
37846
38834
  let claudeMdContent = "";
37847
38835
  try {
37848
- claudeMdContent = await readFile28(rootClaudeMd, "utf-8");
38836
+ claudeMdContent = await readFile29(rootClaudeMd, "utf-8");
37849
38837
  } catch {
37850
- await mkdir13(dirname14(rootClaudeMd), { recursive: true });
38838
+ await mkdir14(dirname15(rootClaudeMd), { recursive: true });
37851
38839
  }
37852
38840
  if (!claudeMdContent.includes(IMPORT_LINE)) {
37853
- await writeFile22(
38841
+ await writeFile23(
37854
38842
  rootClaudeMd,
37855
38843
  claudeMdContent + `
37856
38844
  ${IMPORT_LINE}
@@ -37862,7 +38850,7 @@ ${IMPORT_LINE}
37862
38850
  for (const entry of plan.entries) {
37863
38851
  if (entry.suggested_action !== "drop") continue;
37864
38852
  if (!resolve13(entry.source_path).startsWith(
37865
- resolve13(plan.auto_memory_dir) + sep3
38853
+ resolve13(plan.auto_memory_dir) + sep4
37866
38854
  )) {
37867
38855
  process.stderr.write(
37868
38856
  `migrate-memory: skipping delete of "${entry.source_path}" \u2014 resolves outside auto_memory_dir
@@ -37880,13 +38868,13 @@ ${IMPORT_LINE}
37880
38868
  } catch {
37881
38869
  }
37882
38870
  }
37883
- const memoryMd = join47(plan.auto_memory_dir, "MEMORY.md");
37884
- const safeRmdirBase = join47(homedir8(), ".claude", "projects");
38871
+ const memoryMd = join48(plan.auto_memory_dir, "MEMORY.md");
38872
+ const safeRmdirBase = join48(homedir8(), ".claude", "projects");
37885
38873
  if (dryRun) {
37886
38874
  process.stdout.write(`[dry-run] Would delete MEMORY.md: ${memoryMd}
37887
38875
  `);
37888
38876
  } else {
37889
- if (resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep3)) {
38877
+ if (resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep4)) {
37890
38878
  try {
37891
38879
  await unlink6(memoryMd);
37892
38880
  } catch {
@@ -37904,14 +38892,14 @@ ${IMPORT_LINE}
37904
38892
  `
37905
38893
  );
37906
38894
  } else {
37907
- if (!resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep3)) {
38895
+ if (!resolve13(plan.auto_memory_dir).startsWith(safeRmdirBase + sep4)) {
37908
38896
  process.stderr.write(
37909
38897
  `migrate-memory: skipping rmdir of "${plan.auto_memory_dir}" \u2014 not under ~/.claude/projects
37910
38898
  `
37911
38899
  );
37912
38900
  } else {
37913
38901
  try {
37914
- const remaining = await readdir6(plan.auto_memory_dir);
38902
+ const remaining = await readdir7(plan.auto_memory_dir);
37915
38903
  if (remaining.length === 0) {
37916
38904
  await rmdir(plan.auto_memory_dir);
37917
38905
  }
@@ -37934,7 +38922,7 @@ async function runMigrateMemory(opts) {
37934
38922
  if (applyFile) {
37935
38923
  let planJson;
37936
38924
  try {
37937
- planJson = await readFile28(resolve13(applyFile), "utf-8");
38925
+ planJson = await readFile29(resolve13(applyFile), "utf-8");
37938
38926
  } catch (err) {
37939
38927
  const msg = err instanceof Error ? err.message : String(err);
37940
38928
  process.stderr.write(
@@ -37962,7 +38950,7 @@ async function runMigrateMemory(opts) {
37962
38950
  );
37963
38951
  return;
37964
38952
  }
37965
- if (!existsSync20(autoMemoryDir)) {
38953
+ if (!existsSync21(autoMemoryDir)) {
37966
38954
  process.stdout.write(
37967
38955
  JSON.stringify(
37968
38956
  {
@@ -38004,8 +38992,8 @@ var init_migrate_memory = __esm({
38004
38992
  });
38005
38993
 
38006
38994
  // src/lib/claude-mode-audit.ts
38007
- import { readdirSync as readdirSync7, readFileSync as readFileSync19, existsSync as existsSync21 } from "node:fs";
38008
- import { join as join48, basename as basename2 } from "node:path";
38995
+ import { readdirSync as readdirSync7, readFileSync as readFileSync19, existsSync as existsSync22 } from "node:fs";
38996
+ import { join as join49, basename as basename2 } from "node:path";
38009
38997
  function parseFrontmatter2(content) {
38010
38998
  const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
38011
38999
  if (!match) return {};
@@ -38082,20 +39070,20 @@ function auditSkill(filePath) {
38082
39070
  }
38083
39071
  function auditMode(templatesDir) {
38084
39072
  const entries = [];
38085
- const agentsDir = join48(templatesDir, "agents");
38086
- if (existsSync21(agentsDir)) {
39073
+ const agentsDir = join49(templatesDir, "agents");
39074
+ if (existsSync22(agentsDir)) {
38087
39075
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md")).sort();
38088
39076
  for (const f of agentFiles) {
38089
- entries.push(auditAgent(join48(agentsDir, f)));
39077
+ entries.push(auditAgent(join49(agentsDir, f)));
38090
39078
  }
38091
39079
  }
38092
- const skillsDir = join48(templatesDir, "skills");
38093
- if (existsSync21(skillsDir)) {
39080
+ const skillsDir = join49(templatesDir, "skills");
39081
+ if (existsSync22(skillsDir)) {
38094
39082
  const skillDirs = readdirSync7(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
38095
39083
  for (const dir of skillDirs) {
38096
- if (existsSync21(join48(skillsDir, dir, "PROVENANCE.md"))) continue;
38097
- const skillMd = join48(skillsDir, dir, "SKILL.md");
38098
- if (existsSync21(skillMd)) {
39084
+ if (existsSync22(join49(skillsDir, dir, "PROVENANCE.md"))) continue;
39085
+ const skillMd = join49(skillsDir, dir, "SKILL.md");
39086
+ if (existsSync22(skillMd)) {
38099
39087
  entries.push(auditSkill(skillMd));
38100
39088
  }
38101
39089
  }
@@ -39149,7 +40137,7 @@ var validate_waves_exports = {};
39149
40137
  __export(validate_waves_exports, {
39150
40138
  runValidateWavesCommand: () => runValidateWavesCommand
39151
40139
  });
39152
- import { readFile as readFile29 } from "node:fs/promises";
40140
+ import { readFile as readFile30 } from "node:fs/promises";
39153
40141
  async function readStdin() {
39154
40142
  return new Promise((resolve16, reject) => {
39155
40143
  const chunks = [];
@@ -39168,7 +40156,7 @@ async function runValidateWavesCommand(args) {
39168
40156
  let raw;
39169
40157
  if (filePath) {
39170
40158
  try {
39171
- raw = await readFile29(filePath, "utf-8");
40159
+ raw = await readFile30(filePath, "utf-8");
39172
40160
  } catch (err) {
39173
40161
  const msg = err instanceof Error ? err.message : String(err);
39174
40162
  process.stderr.write(
@@ -39248,12 +40236,12 @@ var init_validate_waves2 = __esm({
39248
40236
  });
39249
40237
 
39250
40238
  // src/cli/worktree/path.ts
39251
- import { dirname as dirname15, basename as basename4, join as join50 } from "node:path";
40239
+ import { dirname as dirname16, basename as basename4, join as join51 } from "node:path";
39252
40240
  function computeWorktreePath(cwd, checkpointNumber) {
39253
- const parent = dirname15(cwd);
40241
+ const parent = dirname16(cwd);
39254
40242
  const base = basename4(cwd);
39255
40243
  const nnn = String(checkpointNumber).padStart(3, "0");
39256
- return join50(parent, `${base}-CHK-${nnn}`);
40244
+ return join51(parent, `${base}-CHK-${nnn}`);
39257
40245
  }
39258
40246
  var init_path = __esm({
39259
40247
  "src/cli/worktree/path.ts"() {
@@ -39262,8 +40250,8 @@ var init_path = __esm({
39262
40250
  });
39263
40251
 
39264
40252
  // src/cli/worktree/add.ts
39265
- import { join as join51, basename as basename5 } from "node:path";
39266
- import { mkdir as mkdir14, readFile as readFile30, writeFile as writeFile23 } from "node:fs/promises";
40253
+ import { join as join52, basename as basename5 } from "node:path";
40254
+ import { mkdir as mkdir15, readFile as readFile31, writeFile as writeFile24 } from "node:fs/promises";
39267
40255
  import { spawnSync as spawnSync16 } from "node:child_process";
39268
40256
  async function defaultGetRepoId(cwd) {
39269
40257
  const found = await findCodebyplanConfig(cwd);
@@ -39306,22 +40294,22 @@ function defaultGitRun(args, cwd) {
39306
40294
  };
39307
40295
  }
39308
40296
  async function defaultCopyConfigStubs(srcCwd, destPath) {
39309
- await mkdir14(join51(destPath, ".codebyplan"), { recursive: true });
40297
+ await mkdir15(join52(destPath, ".codebyplan"), { recursive: true });
39310
40298
  const topLevelStubs = [".mcp.json", ".env.local"];
39311
40299
  for (const stub of topLevelStubs) {
39312
40300
  try {
39313
- const content = await readFile30(join51(srcCwd, stub), "utf-8");
39314
- await writeFile23(join51(destPath, stub), content, "utf-8");
40301
+ const content = await readFile31(join52(srcCwd, stub), "utf-8");
40302
+ await writeFile24(join52(destPath, stub), content, "utf-8");
39315
40303
  } catch {
39316
40304
  }
39317
40305
  }
39318
40306
  try {
39319
- const content = await readFile30(
39320
- join51(srcCwd, ".codebyplan", "repo.json"),
40307
+ const content = await readFile31(
40308
+ join52(srcCwd, ".codebyplan", "repo.json"),
39321
40309
  "utf-8"
39322
40310
  );
39323
- await writeFile23(
39324
- join51(destPath, ".codebyplan", "repo.json"),
40311
+ await writeFile24(
40312
+ join52(destPath, ".codebyplan", "repo.json"),
39325
40313
  content,
39326
40314
  "utf-8"
39327
40315
  );
@@ -39482,7 +40470,7 @@ var init_add = __esm({
39482
40470
  });
39483
40471
 
39484
40472
  // src/cli/worktree/create.ts
39485
- import { join as join52 } from "node:path";
40473
+ import { join as join53 } from "node:path";
39486
40474
  async function defaultGetRepoIdentity(cwd) {
39487
40475
  const found = await findCodebyplanConfig(cwd);
39488
40476
  const contents = found?.contents ?? null;
@@ -39537,7 +40525,7 @@ async function runWorktreeCreate(args, deps = {}) {
39537
40525
  );
39538
40526
  return 1;
39539
40527
  }
39540
- const worktreePath = explicitPath ?? join52(cwd, "..", name);
40528
+ const worktreePath = explicitPath ?? join53(cwd, "..", name);
39541
40529
  const deviceId = await getDeviceId(cwd);
39542
40530
  let filesWritten = false;
39543
40531
  try {
@@ -40022,7 +41010,7 @@ var init_e2e = __esm({
40022
41010
  });
40023
41011
 
40024
41012
  // src/cli/e2e/verify-round.ts
40025
- import { readFile as readFile31 } from "node:fs/promises";
41013
+ import { readFile as readFile32 } from "node:fs/promises";
40026
41014
  import { resolve as resolve14 } from "node:path";
40027
41015
  async function defaultFetchRounds(taskId) {
40028
41016
  return mcpCall("get_rounds", { task_id: taskId });
@@ -40030,7 +41018,7 @@ async function defaultFetchRounds(taskId) {
40030
41018
  async function defaultReadE2eConfig(cwd) {
40031
41019
  try {
40032
41020
  const p = resolve14(cwd, ".codebyplan", "e2e.json");
40033
- const raw = await readFile31(p, "utf-8");
41021
+ const raw = await readFile32(p, "utf-8");
40034
41022
  return JSON.parse(raw);
40035
41023
  } catch {
40036
41024
  return null;
@@ -40436,6 +41424,12 @@ void (async () => {
40436
41424
  await runTaskCommand2(rest);
40437
41425
  process.exit(0);
40438
41426
  }
41427
+ if (arg === "standalone-task") {
41428
+ const { runStandaloneTaskCommand: runStandaloneTaskCommand2 } = await Promise.resolve().then(() => (init_standalone_task(), standalone_task_exports));
41429
+ const rest = process.argv.slice(3);
41430
+ await runStandaloneTaskCommand2(rest);
41431
+ process.exit(0);
41432
+ }
40439
41433
  if (arg === "session") {
40440
41434
  const { runSessionCommand: runSessionCommand2 } = await Promise.resolve().then(() => (init_session(), session_exports));
40441
41435
  const rest = process.argv.slice(3);
@@ -40540,6 +41534,11 @@ void (async () => {
40540
41534
  await runTechStack2();
40541
41535
  process.exit(0);
40542
41536
  }
41537
+ if (arg === "docs") {
41538
+ const { runDocs: runDocs2 } = await Promise.resolve().then(() => (init_docs(), docs_exports));
41539
+ await runDocs2();
41540
+ process.exit(process.exitCode ?? 0);
41541
+ }
40543
41542
  if (arg === "check") {
40544
41543
  const { runCheckCommand: runCheckCommand2 } = await Promise.resolve().then(() => (init_check2(), check_exports));
40545
41544
  const rest = process.argv.slice(3);
@@ -40798,6 +41797,7 @@ void (async () => {
40798
41797
  codebyplan doctor Run diagnostics: auth, version, worktree (always exits 0)
40799
41798
  codebyplan tech-stack Detect and sync tech stack dependencies
40800
41799
  (--full-tech-stack: sync every local worktree on this device)
41800
+ codebyplan docs Local dependency docs mirror (sync / status)
40801
41801
  codebyplan eslint ESLint config management (init)
40802
41802
  codebyplan ci CI configuration management (init / scaffold-workflow / enforce-check)
40803
41803
  codebyplan cd CD configuration management (init / scaffold-workflow)
@@ -40809,6 +41809,8 @@ void (async () => {
40809
41809
  codebyplan watch status Show daemon status (--json for machine-readable)
40810
41810
  codebyplan watch run Run daemon in foreground (used internally by start)
40811
41811
  codebyplan round sync-approvals Sync git diff and approvals with round/task state
41812
+ codebyplan standalone-task Standalone task writes via MCP (create/update/complete)
41813
+ (create --title; update --id; complete --id \u2014 no --checkpoint-id)
40812
41814
  codebyplan bump [--prerelease <id>] Detect changed packages and patch-bump versions
40813
41815
  codebyplan ship Ship current feat branch to production via PR
40814
41816
  codebyplan upload-e2e-images Upload new/changed committed e2e PNGs for a checkpoint